前面几篇博文都是关于http协议的流媒体,这篇博客分享一篇分析的rtsp协议的流媒体的问题。
问题北京:播放一个内网服务器上面的音频文件,拖动进度条,必现的会有so crash的现象
查看log,crash的地方是:
CHECK_LE(offset + payloadLength, buffer->size());
这个宏没有满足导致。
在分析这个问题之前,先大致了解一下rtsp协议的流媒体的数据处理流程:
ARTPConnction.cpp这个类主要是用于客户端和服务器交互,用于发送和接受数据的类,先不管其是怎么监听端口用来接受数据的,其有一个receive方法,当收到数据的时候会进入到这个方法中,下面贴出这个函数的主要代码:
status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) {
ALOGV("receiving %s", receiveRTP ? "RTP" : "RTCP");
CHECK(!s->mIsInjected);
sp buffer = new ABuffer(65536);
ssize_t nbytes;
do {
nbytes = recvfrom(
receiveRTP ? s->mRTPSocket : s->mRTCPSocket,
buffer->data(),
buffer->capacity(),
0,
remoteAddrLen > 0 ? (struct sockaddr *)&s->mRemoteRTCPAddr : NULL,
remoteAddrLen > 0 ? &remoteAddrLen : NULL);
} while (nbytes < 0 && errno == EINTR);
buffer->setRange(0, nbytes);
// ALOGI("received %d bytes.", buffer->size());
status_t err;
if (receiveRTP) {
err = parseRTP(s, buffer);
} else {
err = parseRTCP(s, buffer);
}
return err;
}
由于文件的多媒体数据都是封装在rtp这种包中进行传输的,这里我们暂时分析parseRTP这个函数,对于控制信息包的解析函数parseRTCP放在后面的博客中分析,这个函数也是非常重要的。
下面贴出parseRTP的代码
status_t ARTPConnection::parseRTP(StreamInfo *s, const sp &buffer) {
如果是收到的第一个rtp包,需要做一些参数设置,会给MyHandler类中发消息
if (s->mNumRTPPacketsReceived++ == 0) {
sp notify = s->mNotifyMsg->dup();
notify->setInt32("first-rtp", true);
notify->post();
}
size_t size = buffer->size();
const uint8_t *data = buffer->data();
uint32_t srcId = u32at(&data[8]);
sp source = findSource(s, srcId);
uint32_t rtpTime = u32at(&data[4]);
sp meta = buffer->meta();
从rtp包中取出一些参数,然后设置的meta中,并且给buffer做一些设置
meta->setInt32("ssrc", srcId);
meta->setInt32("rtp-time", rtpTime);
meta->setInt32("PT", data[1] & 0x7f);
meta->setInt32("M", data[1] >> 7);
buffer->setInt32Data(u16at(&data[2]));
buffer->setRange(payloadOffset, size - payloadOffset);
这里的source是ARTPSource,是上面调用findSource返回的
source->processRTPPacket(buffer);
return OK;
}
下面到ARTPSource这个类中的processRTPPacket函数中
void ARTPSource::processRTPPacket(const sp &buffer) {
if (queuePacket(buffer) && mAssembler != NULL) {
mAssembler->onPacketReceived(this);
}
}
先调用queuePacket(buffer)将这个buffer放到队列中
然后调用mAssembler的onPacketReceived函数,传递的参数从前面的ABuffer变成现在的this,其实后面会通过这个this获取这个类中保存的ABuffer队列,从中取出ABuffer进行封装。
这里mAssembler是ARTPAssembler,看看这个类的onPacketReceived函数。
void ARTPAssembler::onPacketReceived(const sp &source) {
AssemblyStatus status;
for (;;) {
status = assembleMore(source);
........
}
}
这个函数就是一个无限循环调用assembleMore,assembleMore在本身自己这个类中并没有实现,而是根据不同的音视频编码格式选择不同的子类的assembleMore函数实现,我们这个问题最后调用了AMPEG4AudioAssembler的assembleMore,看下该函数
ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::assembleMore(
const sp &source) {
AssemblyStatus status = addPacket(source);
if (status == MALFORMED_PACKET) {
mAccessUnitDamaged = true;
}
return status;
}
ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::addPacket(
const sp &source) {
List > *queue = source->queue();
注释:ARTPSource List > *queue() { return &mQueue; }
上面说了,传递了一个this作为参数,就是通过this获取到了保存的buffer队列。
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
sp buffer = *queue->begin(); 取出buffer队列中的第一个buffer,
后面会将这个buffer放到mPackets这个容器中,每一个buffer中都会带有一个rtp-time的时间戳,时间戳相同的buffer将封装成一个完成的帧,然后扔给解码器解码,这个就是下面if条件的工作。
uint32_t rtpTime;
CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime));
if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) {
取出来的帧的时间戳与上一帧的时间戳不一致了,这个时候就要将mPackets中保存的所有帧进行封装成一个完成的音视频编码格式的帧,扔去解码,然后将mPackets清空,继续存放下一个完整帧的所有的Buffer。
submitAccessUnit();
}
mAccessUnitRTPTime = rtpTime;
mPackets.push_back(buffer);继续存放下一个完整帧的所有的Buffer
queue->erase(queue->begin());
++mNextExpectedSeqNo;
return OK;
}
看一下submitAccessUnit()是如何将mPackets中的buffers封装成帧的
void AMPEG4AudioAssembler::submitAccessUnit() {
CHECK(!mPackets.empty());
MakeCompoundFromPackets就是将mPackets中的buffer连接起来,具体可以下面的代码分析
removeLATMFraming,由于上面拼接出来的帧是一种适宜在网络上传输的封装格式LAMT,要得到真正的帧,还需要做一些处理,去除LAMT的一些信息,得到真正的数据部分,可以继续参考下面的分析。
sp accessUnit = MakeCompoundFromPackets(mPackets);
accessUnit = removeLATMFraming(accessUnit);
CopyTimes(accessUnit, *mPackets.begin());
if (mAccessUnitDamaged) {
accessUnit->meta()->setInt32("damaged", true);
}
mPackets.clear();
mAccessUnitDamaged = false;
封装好的一帧可以去解码了,往nuplayer中发一个消息
sp msg = mNotifyMsg->dup();
msg->setBuffer("access-unit", accessUnit);
msg->post();
}
sp ARTPAssembler::MakeCompoundFromPackets(
const List > &packets) {
size_t totalSize = 0;
for (List >::const_iterator it = packets.begin();
it != packets.end(); ++it) {
totalSize += (*it)->size();
}
sp accessUnit = new ABuffer(totalSize);
size_t offset = 0;
for (List >::const_iterator it = packets.begin();
it != packets.end(); ++it) {
sp nal = *it;
memcpy(accessUnit->data() + offset, nal->data(), nal->size());
offset += nal->size();
}
CopyTimes(accessUnit, *packets.begin());
return accessUnit;
}
sp AMPEG4AudioAssembler::removeLATMFraming(const sp &buffer) {
CHECK(!mMuxConfigPresent); // XXX to be implemented
sp out = new ABuffer(buffer->size());
out->setRange(0, 0);
size_t offset = 0;
uint8_t *ptr = buffer->data();
mNumSubFrames一般情况下都是0,没有子frame
先简单说一下LAMT帧的组成:PayloadLengthInfo和,下面这个for循环就是解析PayloadLengthInfo的。
for (size_t i = 0; i <= mNumSubFrames; ++i) {
// parse PayloadLengthInfo
unsigned payloadLength = 0;
switch (mFrameLengthType) {
case 0:
{
unsigned muxSlotLengthBytes = 0;
unsigned tmp;
do {
CHECK_LT(offset, buffer->size());
tmp = ptr[offset++];
muxSlotLengthBytes += tmp;
} while (tmp == 0xff);
payloadLength = muxSlotLengthBytes;
break;
}
}
payloadLength是真正数据的长度,从offset开始,我们的buffer大小至少要比payloadLength大吧,所以谷歌在这里加了一个check宏,正常情况肯定是要满足的,但是谷歌没有考虑的一种情况,就是有网络传输过程中有丢包的现象
,
如果网络传输中有丢包,存放在mPackets中的buffer将不足,而从LAMT头信息中读取出来的文件长度是payloadLength,比我们现在拼接的buffer的长度还长,所以宏判断失败,结果就导致so crash了
,这个也就是我们问题的所在,最后通过tcpdump抓包发现,每次播放这个音频文件的时候,seek的时候都有有端口无法到达的log,也就是有些包丢了,证实了问题的推测。
CHECK_LE(offset + payloadLength, buffer->size());
memcpy(out->data() + out->size(), &ptr[offset], payloadLength);
out->setRange(0, out->size() + payloadLength);
offset += payloadLength;
if (mOtherDataPresent) {
// We want to stay byte-aligned.
CHECK((mOtherDataLenBits % 8) == 0);
CHECK_LE(offset + (mOtherDataLenBits / 8), buffer->size());
offset += mOtherDataLenBits / 8;
}
}
if (offset < buffer->size()) {
ALOGI("ignoring %d bytes of trailing data", buffer->size() - offset);
}
CHECK_LE(offset, buffer->size());
return out;
}
最后的规避方法可以参考下面的修改
- CHECK_LE(offset + payloadLength, buffer->size());
+ if(offset + payloadLength > buffer->size()){
+ mAccessUnitDamaged = true; 给这一帧打上被破坏的标签
+ }