分享一个分析的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;  给这一帧打上被破坏的标签
+  }

发布了270 篇原创文章 · 获赞 273 · 访问量 377万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览