您的位置:首页 > 编程语言 > PHP开发

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)标识符
RTP报文由两部分组成:报头和有效载荷。其中:

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);进行后续的处理。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  rtp nuplayer