分享一个分析的 rtsp 流媒体的问题
来源:互联网 发布:穿越火线手游开挂软件 编辑:程序博客网 时间:2024/06/08 04:40
转自 http://blog.sina.com.cn/foreverlovelost
前面几篇博文都是关于http协议的流媒体,这篇博客分享一篇分析的rtsp协议的流媒体的问题。
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包,需要做一些参数设置,会给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;
if (queuePacket(buffer)&& mAssembler != NULL) { mAssembler->onPacketReceived(this); } void ARTPAssembler::onPacketReceived(constsp &source) { AssemblyStatus status; for(;;) { status =assembleMore(source); ........ } } const sp &source) { AssemblyStatus status =addPacket(source); if (status ==MALFORMED_PACKET) { mAccessUnitDamaged = true; } return status; 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;
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();
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;
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;
前面几篇博文都是关于http协议的流媒体,这篇博客分享一篇分析的rtsp协议的流媒体的问题。
问题北京:播放一个内网服务器上面的音频文件,拖动进度条,毕现的会有so crash的现象
查看log,crash的地方是:
CHECK_LE(offset + payloadLength,buffer->size());
这个宏没有满足导致。
在分析这个问题之前,先大致了解一下rtsp协议的流媒体的数据处理流程:
ARTPConnction.cpp这个类主要是用于客户端和服务器交互,用于发送和接受数据的类,先不管其是怎么监听端口用来接受数据的,其有一个receive方法,当收到数据的时候会进入到这个方法中,下面贴出这个函数的主要代码:
status_t ARTPConnection::receive(StreamInfo *s, boolreceiveRTP) {
}
由于文件的多媒体数据都是封装在rtp这种包中进行传输的,这里我们暂时分析parseRTP这个函数,对于控制信息包的解析函数parseRTCP放在后面的博客中分析,这个函数也是非常重要的。
下面贴出parseRTP的代码
status_t ARTPConnection::parseRTP(StreamInfo *s, const sp&buffer) {
}
下面到ARTPSource这个类中的processRTPPacket函数中
void ARTPSource::processRTPPacket(const sp&buffer) {
}
先调用queuePacket(buffer)将这个buffer放到队列中
然后调用mAssembler的onPacketReceived函数,传递的参数从前面的ABuffer变成现在的this,其实后面会通过这个this获取这个类中保存的ABuffer队列,从中取出ABuffer进行封装。
这里mAssembler是ARTPAssembler,看看这个类的onPacketReceived函数。
这个函数就是一个无限循环调用assembleMore,assembleMore在本身自己这个类中并没有实现,而是根据不同的音视频编码格式选择不同的子类的assembleMore函数实现,我们这个问题最后调用了AMPEG4AudioAssembler的assembleMore,看下该函数
ARTPAssembler::AssemblyStatusAMPEG4AudioAssembler::assembleMore(
}
ARTPAssembler::AssemblyStatusAMPEG4AudioAssembler::addPacket(
}
看一下submitAccessUnit()是如何将mPackets中的buffers封装成帧的
void AMPEG4AudioAssembler::submitAccessUnit() {
}
sp ARTPAssembler::MakeCompoundFromPackets(
}
sp AMPEG4AudioAssembler::removeLATMFraming(const sp&buffer) {
}
最后的规避方法可以参考下面的修改
- CHECK_LE(offset + payloadLength,buffer->size());
+ if(offset + payloadLength >buffer->size()){
+ mAccessUnitDamaged = true; 给这一帧打上被破坏的标签
+ }
0 0
- 分享一个分析的rtsp流媒体的问题
- 分享一个分析的 rtsp 流媒体的问题
- 分享一个稳定的 RTSP 流媒体 资源
- 分析的rtsp流媒体的问题
- Android 源码分析之基于NuPlayer的RTSP流媒体协议
- Android 源码分析之基于NuPlayer的RTSP流媒体协议
- rtsp流媒体服务器的搭建
- rtsp流媒体服务器的搭建
- rtsp流媒体服务器的搭建
- RTSP流媒体播放分析
- RTSP流媒体播放分析
- RTSP流媒体播放分析
- 【RTSP/RTP流媒体】9、编写简单的RTSP服务器
- 【RTSP/RTP流媒体】10、编写简单的RTSP客户端
- 基于RTSP协议流媒体服务器的实现
- rtsp流媒体buffer填充的处理过程
- RTSP流媒体服务器的搭建与测试
- rtsp流媒体buffer填充的处理过程
- MySQL的语句执行顺序
- JavaScript对象及继承教程
- ASP.NET(C#)中遍历所有控件
- mysql存储引擎简介
- 二分法查找C++程序
- 分享一个分析的 rtsp 流媒体的问题
- windows server 2003 安装软件就报错“无法定位程序输入点Encodepointer于动态链接库KERNEL32.dll上”
- 强制垃圾回收:(即著名的hack方式)
- mysql最大并发数设置
- 高通平台android开发总结 .
- Java HotSpot JVM内存管理之详解
- oracle忘记用户名和密码的解决方案
- <Excel>怎么把所有文本都加上小三角?
- 测试用例之因果图