Android:RTSP流媒体花屏

记录和同事分析的一个流媒体花屏问题


总的来说,播放流媒体的过程中花屏大多说是由于解码前后存在丢帧,存在丢帧的原因就可能有很多了
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) {
    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;
}


继续看ARTPSource中的processRTPPacket函数:
void ARTPSource::processRTPPacket(const sp &buffer) {
    if (queuePacket(buffer) && mAssembler != NULL) {
        mAssembler->onPacketReceived(this);
    }
}
首先会将这个buffer入队,入队之后会继续调用各个assembler对buffer进行封装
bool ARTPSource::queuePacket(const sp &buffer) {
    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;
}
上面这段代码,就是按照rtp包的sequence number大小,插入到已排序的队列中,形成一个有序的rtp队列。


假如说此时队列中有0这个序列,此时来了一个3,队列变成了:0,3


接下来看对队列的处理:
 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;
             }
         }
     }
 }


这个时候会调用各个子类的assembler去封装帧,封装过程中会进行错误处理,主要是rtp乱序和rtp数据不足两种情况----通过这里可以看出,谷歌肯定是对乱序这种情况有处理的。


 ARTPAssembler::AssemblyStatus AAVCAssembler::assembleMore(
        const sp &source) {
    AssemblyStatus status = addNALUnit(source);
    if (status == MALFORMED_PACKET) {
        mAccessUnitDamaged = true;
    }
    return status;
}


ARTPAssembler::AssemblyStatus AAVCAssembler::addNALUnit(
        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();
}
从这段代码中可以看到: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这个函数中,我们在看看代码:
 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;
             }
         }
     }
 }


我们看看,如果遇到第一个乱序的rtp包,程序会记录一个当前的时间(原始为负数,记录后就是一个大于等于零的值),如果后续到来的rtp sequence number是正确的,又会将这个值初始化为负数;
但是如果连续两次来的都是不正确的rtp sequence number,且两次的时间已经超过了10000ll微妙,则程序会认为rtp包已经丢失,永远不会到来,程序调用下面的函数给当前帧打一个damaged的标签,后面解码器解码的时候会分析有没有这个标签,如果存在的话就会将这一帧扔掉,不解码。
void AAVCAssembler::packetLost() {
    CHECK(mNextExpectedSeqNoValid);
    ALOGV("packetLost (expected %d)", mNextExpectedSeqNo);
    ++mNextExpectedSeqNo;
    mAccessUnitDamaged = true;
}


void AAVCAssembler::submitAccessUnit() {
    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();
}


我们的问题就出现在这里,我们的rtp虽然没有一个包丢失,但是存在大量的乱序的情况,并且存在连续乱序的情况,并且,rtp包到达的时间大部分都超过了10000ll这个值。


最后我们经过一定的调试,实验,将这个值增加到120000ll,问题得到解决,不过具体这个参数改多大,还需要评估。


PS:这篇博客完全忽略RTP的解析,包括header和payload部分,有兴趣可以学习上面提到的几个文件。


下面是我个人的总结:

一:这个时间的大小是可调的,当你只是数据包的顺序乱,而且需要尽量不丢失数据的话,可以设置时间长一点,这样能保证顺序号在前但来的较晚的数据包也可以顺利接收到;也就是说尽量不丢失数据包。

二:当你需要容错能力强的话,举例说,行车记录仪的预览的时候,是不需要保存这些数据的,只是显示给用户看,数据丢失点没有关系;这时如果出现丢包或者数据乱的情况,可以尽快丢弃,以免影响显示效果,这时这个时间就要调小。


  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值