RTSP流媒体花屏

Android:RTSP流媒体花屏

(2013-02-23 07:33:33)
标签:

android

流媒体

rtp

rtsp

it

分类: Android机制
 
 
记录和同事分析的一个流媒体花屏问题

总的来说,播放流媒体的过程中花屏大多说是由于解码前后存在丢帧,存在丢帧的原因就可能有很多了
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部分,有兴趣可以学习上面提到的几个文件。
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值