分享一个分析的rtsp流媒体的问题

转自:http://blog.sina.com.cn/s/blog_696bcf8f0101cevn.html

基于Android 4.1分析的解析rtsp流媒体rtp包,组装发给解码器进行解码的过程。

以下是原文:

前面几篇博文都是关于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;   给这一帧打上被破坏的标签
+   }

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值