记录和同事分析的一个流媒体花屏问题
size_t size = buffer->size();
const uint8_t *data = buffer->data();
if ((data[0] >> 6) != 2) {
// Unsupported version.
return -1;
}
int numCSRCs = data[0] & 0x0f;
size_t payloadOffset = 12 + 4 * numCSRCs;
uint32_t srcId = u32at(&data[8]);
sp source = findSource(s, srcId);
uint32_t rtpTime = u32at(&data[4]);
sp meta = buffer->meta();
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->processRTPPacket(buffer);
return OK;
if (queuePacket(buffer) && mAssembler != NULL) {
mAssembler->onPacketReceived(this);
}
uint32_t seqNum = (uint32_t)buffer->int32Data();
List >::iterator it = mQueue.begin();
while (it != mQueue.end() && (uint32_t)(*it)->int32Data() < seqNum) {
++it;
}
if (it != mQueue.end() && (uint32_t)(*it)->int32Data() == seqNum) {
ALOGW("Discarding duplicate buffer");
return false;
}
mQueue.insert(it, buffer);
return true;
void ARTPAssembler::onPacketReceived(const sp &source) {
AssemblyStatus status;
for (;;) {
status = assembleMore(source);
if (status == WRONG_SEQUENCE_NUMBER) {
if (mFirstFailureTimeUs >= 0) {
if (getNowUs() - mFirstFailureTimeUs > 10000ll) {
mFirstFailureTimeUs = -1;
// LOG(VERBOSE) << "waited too long for packet.";
packetLost();
continue;
}
} else {
mFirstFailureTimeUs = getNowUs();
}
break;
} else {
mFirstFailureTimeUs = -1;
if (status == NOT_ENOUGH_DATA) {
break;
}
}
}
}
ARTPAssembler::AssemblyStatus AAVCAssembler::assembleMore(
const sp &source) {
AssemblyStatus status = addNALUnit(source);
if (status == MALFORMED_PACKET) {
mAccessUnitDamaged = true;
}
return status;
const sp &source) {
List > *queue = source->queue();
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
if (mNextExpectedSeqNoValid) {
List >::iterator it = queue->begin();
while (it != queue->end()) {
if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) {
break;
}
it = queue->erase(it);
}
if (queue->empty()) {
return NOT_ENOUGH_DATA;
}
}
sp buffer = *queue->begin();
if (!mNextExpectedSeqNoValid) {
mNextExpectedSeqNoValid = true;
mNextExpectedSeqNo = (uint32_t)buffer->int32Data();
} else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) {
ALOGV("Not the sequence number I expected");
return WRONG_SEQUENCE_NUMBER;
}
const uint8_t *data = buffer->data();
size_t size = buffer->size();
void ARTPAssembler::onPacketReceived(const sp &source) {
AssemblyStatus status;
for (;;) {
status = assembleMore(source);
if (status == WRONG_SEQUENCE_NUMBER) {
if (mFirstFailureTimeUs >= 0) {
if (getNowUs() - mFirstFailureTimeUs > 10000ll) {
mFirstFailureTimeUs = -1;
LOG(VERBOSE) << "waited too long for packet.";
packetLost();
continue;
}
} else {
mFirstFailureTimeUs = getNowUs();
}
break;
} else {
mFirstFailureTimeUs = -1;
if (status == NOT_ENOUGH_DATA) {
break;
}
}
}
}
CHECK(mNextExpectedSeqNoValid);
ALOGV("packetLost (expected %d)", mNextExpectedSeqNo);
++mNextExpectedSeqNo;
mAccessUnitDamaged = true;
ALOGV("Access unit complete (%d nal units)", mNALUnits.size());
if (mAccessUnitDamaged) {
accessUnit->meta()->setInt32("damaged", true);
}
mNALUnits.clear();
mAccessUnitDamaged = false;
sp msg = mNotifyMsg->dup();
msg->setBuffer("access-unit", accessUnit);
msg->post();
总的来说,播放流媒体的过程中花屏大多说是由于解码前后存在丢帧,存在丢帧的原因就可能有很多了
1:文件本身就是不完整的,存在很多丢帧或者错误帧的情况;
2:直播流媒体,第一帧不是I帧,会扔掉所有开头的这些帧,直到等到第一个I帧过来
3:流媒体通过RTP等封装格式在网络上传输过程中存在丢包,或者包延迟时间太长
4:解码器解码能力限制或者解码器的其他因素导致的丢帧
下面就简要分析一个我们在android平台上遇到的一个丢帧导致的播放花屏的情况
背景:我们的手机在播放一线某几个流媒体的时候,花屏非常严重,存在影响的重叠
由于问题发声在欧洲一线的内网测试,在本地无法测试,所以只能由一线的员工帮我们抓取相关的log,我们分析log后在本地修改,发给他们继续抓取log,然后分析修改,循环直到问题搞定。
一线给我们抓取的log主要包括tcpdump抓取的IP log和adb抓取的ddms log。
我们分析了正常和不正常的视频的IP log发现:
正常播放的流媒体的视频帧的RTP包非常有顺序,严格按照sequence number(不知道的话,可以参考RTP/RTCP协议)到来,而不正常播放的流媒体的视频帧RTP包存在乱序,比如:
我们0,1,2,3四个RTP包将封装成一个时间戳为0的第一帧,正常情况这四个包到来的顺序是:0,1,2,3
但是我们看到不正常情况到来的顺序确实0,3,2,1。
我们就需要跟踪代码,看看这种乱序会不会有问题,谷歌是否对乱序已经有了相应处理:
Android中收到一个rtp包后,是在ARTPConnection.pp中的parseRTP函数中进行解析,解析按照RTP的协议进行,主要过程是取出header中的一些关键信息存起来,然后将header部分扔掉,继续解析和封装payload部分。
status_t ARTPConnection::parseRTP(StreamInfo *s, const sp &buffer) {
}
继续看ARTPSource中的processRTPPacket函数:
void ARTPSource::processRTPPacket(const sp &buffer) {
}
首先会将这个buffer入队,入队之后会继续调用各个assembler对buffer进行封装
bool ARTPSource::queuePacket(const sp &buffer) {
}
上面这段代码,就是按照rtp包的sequence number大小,插入到已排序的队列中,形成一个有序的rtp队列。
假如说此时队列中有0这个序列,此时来了一个3,队列变成了:0,3
接下来看对队列的处理:
这个时候会调用各个子类的assembler去封装帧,封装过程中会进行错误处理,主要是rtp乱序和rtp数据不足两种情况----通过这里可以看出,谷歌肯定是对乱序这种情况有处理的。
}
ARTPAssembler::AssemblyStatus AAVCAssembler::addNALUnit(
}
从这段代码中可以看到:AAVCAssember中维护了一个变量mNextExpectedSeqNo用来记录下一个到达的RTP的sequence number,如果到来的rtp的sequence number不是mNextExpectedSeqNo就会抛出一个WRONG_SEQUENCE_NUMBER错误.
前面ARTPAssembler::onPacketReceived函数中会接受到这个错误码,进行纠错处理:停止封装,等待下一个rtp的到来,如果此时1到来了,正好是需要的rtp sequence number,就可以进行正常的封装。
从以上代码似乎谷歌已经对rtp 乱序做了很好的处理,为什么我们这里还会有花屏的情况呢,经过一天的仔细分析,我们找到了问题的根源:谷歌对下一个正确的rtp sequence number的等待并不是无限制的,还是在ARTPAssembler::onPacketReceived这个函数中,我们在看看代码:
我们看看,如果遇到第一个乱序的rtp包,程序会记录一个当前的时间(原始为负数,记录后就是一个大于等于零的值),如果后续到来的rtp sequence number是正确的,又会将这个值初始化为负数;
但是如果连续两次来的都是不正确的rtp sequence number,且两次的时间已经超过了10000ll微妙,则程序会认为rtp包已经丢失,永远不会到来,程序调用下面的函数给当前帧打一个damaged的标签,后面解码器解码的时候会分析有没有这个标签,如果存在的话就会将这一帧扔掉,不解码。
void AAVCAssembler::packetLost() {
}
void AAVCAssembler::submitAccessUnit() {
}
我们的问题就出现在这里,我们的rtp虽然没有一个包丢失,但是存在大量的乱序的情况,并且存在连续乱序的情况,并且,rtp包到达的时间大部分都超过了10000ll这个值。
最后我们经过一定的调试,实验,将这个值增加到120000ll,问题得到解决,不过具体这个参数改多大,还需要评估。
PS:这篇博客完全忽略RTP的解析,包括header和payload部分,有兴趣可以学习上面提到的几个文件。