NuPlayer播放框架RTP数据包获取和解析
2017-03-01 19:19
399 查看
1.NuPlayer播放框架RTP包的获取
下面贴出安卓N版本ARTPConnection::receive函数的源码:status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) { ALOGV("receiving %s", receiveRTP ? "RTP" : "RTCP"); CHECK(!s->mIsInjected); sp<ABuffer> buffer = new ABuffer(65536); socklen_t remoteAddrLen = (!receiveRTP && s->mNumRTCPPacketsReceived == 0) ? sizeof(s->mRemoteRTCPAddr) : 0; 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); if (nbytes <= 0) { return -ECONNRESET; } 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; }
1.1创建缓冲区
sp<ABuffer> buffer = new ABuffer(65536);
1.2调用recvfrom函数接收数据
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); if (nbytes <= 0) { return -ECONNRESET; } buffer->setRange(0, nbytes);
setRange函数设置有效的数据范围。
1.3调用parseRTP函数解析RTP数据包
status_t err; if (receiveRTP) { err = parseRTP(s, buffer); } else { err = parseRTCP(s, buffer); }
这里分析receiveRTP位true的情况,即解析收到的RTP数据包。
2.NuPlayer播放框架RTP数据包解析
下面贴出安卓N版本ARTPConnection::parseRTP函数的源码:status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) { if (s->mNumRTPPacketsReceived++ == 0) { sp<AMessage> notify = s->mNotifyMsg->dup(); notify->setInt32("first-rtp", true); notify->post(); } size_t size = buffer->size(); if (size < 12) { // Too short to be a valid RTP header. return -1; } const uint8_t *data = buffer->data(); if ((data[0] >> 6) != 2) { // Unsupported version. return -1; } if (data[0] & 0x20) { // Padding present. size_t paddingLength = data[size - 1]; if (paddingLength + 12 > size) { // If we removed this much padding we'd end up with something // that's too short to be a valid RTP header. return -1; } size -= paddingLength; } int numCSRCs = data[0] & 0x0f; size_t payloadOffset = 12 + 4 * numCSRCs; if (size < payloadOffset) { // Not enough data to fit the basic header and all the CSRC entries. return -1; } if (data[0] & 0x10) { // Header eXtension present. if (size < payloadOffset + 4) { // Not enough data to fit the basic header, all CSRC entries // and the first 4 bytes of the extension header. return -1; } const uint8_t *extensionData = &data[payloadOffset]; size_t extensionLength = 4 * (extensionData[2] << 8 | extensionData[3]); if (size < payloadOffset + 4 + extensionLength) { return -1; } payloadOffset += 4 + extensionLength; } uint32_t srcId = u32at(&data[8]); sp<ARTPSource> source = findSource(s, srcId); uint32_t rtpTime = u32at(&data[4]); sp<AMessage> 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; }
2.1RTP协议的报文结构
V (0~1) | P (2) | X (3) | CC (4~7) | M (8) | PT (9~15) | 序列号 (16~31,2个字节) | 时间戳 (32~63 4个字节) | 同步信源(SSRC)标识符 (4个字节) | 特约信源(CSRC)标识符 | … |
---|
V:RTP协议的版本号,占2位,当前协议版本号为2。
P:填充标志,占1位,如果P=1,则在该报文的尾部填充一个或多个额外的八位组,它们不是有效载荷的一部分。
X:扩展标志,占1位,如果X=1,则在RTP报头后跟有一个扩展报头。
CC:CSRC计数器,占4位,指示CSRC 标识符的个数。
M: 标记,占1位,不同的有效载荷有不同的含义,对于视频,标记一帧的结束;对于音频,标记会话的开始。
PT: 有效载荷类型,占7位,用于说明RTP报文中有效载荷的类型,如GSM音频、JPEM图像等。
序列号:占16位,用于标识发送者所发送的RTP报文的序列号,每发送一个报文,序列号增1。接收者通过序列号来检测报文丢失情况,重新排序报文,恢复数据。
时戳(Timestamp):占32位,时戳反映了该RTP报文的第一个八位组的采样时刻。接收者使用时戳来计算延迟和延迟抖动,并进行同步控制。
同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。
特约信源(CSRC)标识符:每个CSRC标识符占32位,可以有0~15个。每个CSRC标识了包含在该RTP报文有效载荷中的所有特约信源。
注意:一个RTP包的头部部分固定部分的大小为12个字节,由V,P,X,CC,M,PT,序列号,时间戳,同步信源(SSRC)标识符共同组成。其中V,P,X,CC,M,PT占用2个字节,序列号占用2个字节,时间戳占用4个字节,同步信源(SSRC)标识符占用4个字节,一共组成RTP包的头部部分固定部分的大小12个字节。
注意:一个RTP包的头部可变部分部分大小为4 * numCSRCs+(4 + extensionLength),numCSRCs为特约信源(CSRC)标识符的个数,每个特约信源(CSRC)标识符占用4个字节。特约信源(CSRC)标识符个数由CC表示,CC占用第一个字节的低4位,可表示的范围为0000~1111即可表示有0~15个特约信源(CSRC)标识符。(4 + extensionLength)表示的扩展报头和扩展长度,紧跟在特约信源(CSRC)标识符后面,即表中…表示的部分。4,表示的是一个扩展报头至少为4个字节,extensionLength表示的为扩展部分的长度,是否有扩展报头由X位标识。特约信源(CSRC)标识符和扩展报头共同组成RTP包头部的可变部分。
2.2处理第一个接收到的RTP包
if (s->mNumRTPPacketsReceived++ == 0) { sp<AMessage> notify = s->mNotifyMsg->dup(); notify->setInt32("first-rtp", true); notify->post(); }
如果s->mNumRTPPacketsReceived的值为0,说明当前收到的RTP包为第一个包,发送消息”first-rtp”对该情况进行异步处理,并将该值自增1。其他情况直接将该值自增1,不进行异步处理。
2.3检查RTP包的大小
size_t size = buffer->size(); if (size < 12) { // Too short to be a valid RTP header. return -1; }
RTP包头部部分最少是12字节,所以如果接收到的RTP包小于12字节则返回错误码 -1。
2.4检查协议的版本号V
const uint8_t *data = buffer->data(); if ((data[0] >> 6) != 2) { // Unsupported version. return -1; }
data[0]指向RTP包的第一个字节,使用的RTP协议的版本号存储在第一个字节的高两位,所以data[0]右移6位,表示版本号的高两位移到了低两位,高6位全部被填充零,这样就得到了当前协议的版本号。如果标识的当前协议版本号不为2则返回错误码。
2.5判断填充位P
if (data[0] & 0x20) { // Padding present. size_t paddingLength = data[size - 1]; if (paddingLength + 12 > size) { // If we removed this much padding we'd end up with something // that's too short to be a valid RTP header. return -1; } size -= paddingLength; }
填充位P在第一个字节的第三位,(data[0] & 0x20) 即 (data[0] & 0010 0000) 就得到了填充位的值。如果该值为1,则说明存在填充位,如果为0,则说明不存在填充位。当存在填充位的时候,整个RTP包的最后一个字节即data[size-1]的值表示填充的长度(以字节为单位,包括data[size-1])。所以存在填充位的时候,在解析需要将该填充长度丢弃掉。通过size -= paddingLength;限定size的范围来丢弃该填充位。
某些加密算法需要固定大小的填充字,或为在底层协议数据单元中携带几个RTP包。
2.6CSRC计数器:CC
int numCSRCs = data[0] & 0x0f; size_t payloadOffset = 12 + 4 * numCSRCs;
CSRC计数器CC是在第一个字节的低四位,所以(data[0] & 0x0f) 即(data[0] & 0000 1111)就得到了CSRC计数器CC的值,每一个CSRC标识符占四个字节,4 * numCSRCs得到所有CSRC标识符所占的字节数。
2.7扩展报头部分
if (data[0] & 0x10) { // Header eXtension present. if (size < payloadOffset + 4) { // Not enough data to fit the basic header, all CSRC entries // and the first 4 bytes of the extension header. return -1; } const uint8_t *extensionData = &data[payloadOffset]; size_t extensionLength = 4 * (extensionData[2] << 8 | extensionData[3]); if (size < payloadOffset + 4 + extensionLength) { return -1; } payloadOffset += 4 + extensionLength; }
X位标识是否有扩展报头,在第一个字节的第4位,所以(data[0] & 0x10)即(data[0] & 0x0001 0000)就得到了X的值,如果X的值为1,说明有扩展报头,如果为0,则说明没有扩展报头。扩展报头是紧跟在CSRC标识符后面的。所以前面计算得到的payloadOffset(payloadOffset = 12 + 4 * numCSRCs;),该偏移就是指向扩展报头部分(如果有扩展报头的话)。
所以const uint8_t *extensionData = &data[payloadOffset];就得到指向扩展报头其实位置的指针extensionData。
注意:扩展报头的固定部分大小为4个字节即extensionData[0],extensionData[1],extensionData[2],extensionData[3]。extensionData[2],extensionData[3],这两个字节标识了扩展报头的可变部分元素个数,每个元素所占用的字节为4个字节。
采用了这样的extensionData[2] << 8 | extensionData[3]计算技巧得到了extensionData[2],extensionData[3]这两个字节所表示的扩展报头可变部分所包含的元素的个数(由16位整数值表示),4 * (extensionData[2] << 8 | extensionData[3]);就得到了扩展报头可变部分的长度(以字节为单位),因为每个元素所占用的大小位4个字节。
2.8同步信源(SSRC)标识符
同步信源(SSRC)标识符:占32位,用于标识同步信源。该标识符是随机选择的,参加同一视频会议的两个同步信源不能有相同的SSRC。uint32_t srcId = u32at(&data[8]); sp<ARTPSource> source = findSource(s, srcId);
同步信源(SSRC)标识符是在RTP包头部部分固定部分的后4个字节,即8,9,10,11这4个字节,也即data[8],data[9],data[10],data[11]。采用u32at(&data[8]);这样一个计算技巧得到这4个字节所表示的32位整数的值。
2.9时间戳
uint32_t rtpTime = u32at(&data[4]);
时间戳是在在RTP包头部部分固定部分的中间4个字节所表示的32位整数的值,也即data[4],data[5],data[6],data[7]所表示的32位整数的值。
2.10序列号
buffer->setInt32Data(u16at(&data[2]));
序列号是在RTP包头部部分固定部分的前4个字节的后两个字节所表示的16位整数的值,也即data[2],data[3]所表示的16位整数的值。采取这样的计算技巧u16at(&data[2])来得到这个16位整数的值。
设置有效的RTP包数据部分
sp<AMessage> 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);
将srcId(同步信源(SSRC)标识符),rtpTime(时间戳),PT(有效载荷类型),M(标记)的值全部设置到buffer->meta()中,将序列号设置到buffer->setInt32Data(u16at(&data[2]))即buffer的mInt32Data值中。
调用source->processRTPPacket(buffer);进行后续的处理。
相关文章推荐
- day03-spring与数据库操作的框架解析即dataSource的获取(JDBC)、使用spring中已经有的事务进行JDBC操作
- 网络通信框架Volley之二——访问服务器获取Json.xml数据和详细的解析数据
- Android NuPlayer播放框架
- Android NuPlayer播放框架
- ⑤NuPlayer播放框架之GenericSource源码分析
- 分享一款解析json数据包获取全国学校Jquery特效
- ④NuPlayer播放框架之Renderer源码分析
- ③NuPlayer播放框架之类NuPlayer源码分析
- 图片框架 三重更新 联网获取json 解析
- c#网络通信框架networkcomms内核解析之七 数据包创建器(PacketBuilder)
- c#网络通信框架networkcomms内核解析之八 数据包的核心处理器
- c#网络通信框架networkcomms内核解析之八 数据包的核心处理器
- ⑤NuPlayer播放框架之GenericSource源码分析
- ①Android NuPlayer播放框架
- 【框架-MFC】mfc命令行的解析和获取
- c#网络通信框架networkcomms内核解析之七 数据包创建器(PacketBuilder)
- EasyPusher RTSP直播之RTP数据包格式解析
- ③NuPlayer播放框架之类NuPlayer源码分析
- Android中基于Nuplayer的RTSP框架解析
- ②NuPlayer播放框架之ALooper-AHandler-AMessage底层机制分析