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

来源:互联网 发布:穿越火线手游开挂软件 编辑:程序博客网 时间:2024/06/08 04:40
转自 http://blog.sina.com.cn/foreverlovelost

前面几篇博文都是关于http协议的流媒体,这篇博客分享一篇分析的rtsp协议的流媒体的问题。

问题北京:播放一个内网服务器上面的音频文件,拖动进度条,毕现的会有so crash的现象
查看log,crash的地方是:
CHECK_LE(offset + payloadLength,buffer->size());
这个宏没有满足导致。

在分析这个问题之前,先大致了解一下rtsp协议的流媒体的数据处理流程:
ARTPConnction.cpp这个类主要是用于客户端和服务器交互,用于发送和接受数据的类,先不管其是怎么监听端口用来接受数据的,其有一个receive方法,当收到数据的时候会进入到这个方法中,下面贴出这个函数的主要代码:
status_t ARTPConnection::receive(StreamInfo *s, boolreceiveRTP) {
    ALOGV("receiving %s",receiveRTP ? "RTP" : "RTCP");
   CHECK(!s->mIsInjected);
    sp buffer = newABuffer(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 %dbytes.", 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(constsp &source) {
    AssemblyStatus status;
     for(;;) {
        status =assembleMore(source);
        ........
    }
 }
这个函数就是一个无限循环调用assembleMore,assembleMore在本身自己这个类中并没有实现,而是根据不同的音视频编码格式选择不同的子类的assembleMore函数实现,我们这个问题最后调用了AMPEG4AudioAssembler的assembleMore,看下该函数
ARTPAssembler::AssemblyStatusAMPEG4AudioAssembler::assembleMore(
       const sp &source) {
    AssemblyStatus status =addPacket(source);
    if (status ==MALFORMED_PACKET) {
       mAccessUnitDamaged = true;
    }
    return status;
}
ARTPAssembler::AssemblyStatusAMPEG4AudioAssembler::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 = newABuffer(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 beimplemented
    sp out = newABuffer(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) {
           case0:
           {
              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的长度还长,所以宏判断失败,结果就导致socrash了,这个也就是我们问题的所在,最后通过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 wantto 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; 给这一帧打上被破坏的标签
+  }
0 0
原创粉丝点击