1.ARTPConnection::parseRTCP源码
下面贴出安卓N版本ARTPConnection::parseRTCP源码:
status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) {
if (s->mNumRTCPPacketsReceived++ == 0) {
sp<AMessage> notify = s->mNotifyMsg->dup();
notify->setInt32("first-rtcp", true);
notify->post();
}
const uint8_t *data = buffer->data();
size_t size = buffer->size();
while (size > 0) {
if (size < 8) {
// Too short to be a valid RTCP header
return -1;
}
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;
}
size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
if (size < headerLength) {
// Only received a partial packet?
return -1;
}
switch (data[1]) {
case 200:
{
parseSR(s, data, headerLength);
break;
}
case 201: // RR
case 202: // SDES
case 204: // APP
break;
case 205: // TSFB (transport layer specific feedback)
case 206: // PSFB (payload specific feedback)
// hexdump(data, headerLength);
break;
case 203:
{
parseBYE(s, data, headerLength);
break;
}
default:
{
ALOGW("Unknown RTCP packet type %u of size %zu",
(unsigned)data[1], headerLength);
break;
}
}
data += headerLength;
size -= headerLength;
}
return OK;
}
2.RTCP报文结构
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
| v=2 (0~1) | p (2) | reserved (3~7)| PT (8 bits)| legnth (16 bits)| SSRC (32bits)| report blocks |
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
关于RTCP报文结构推荐阅读文章:
https://en.wikipedia.org/wiki/RTP_Control_Protocol
https://tools.ietf.org/html/rfc3611#page-7
可以看出一个完整的一个RTCP报文头部部分由8个字节的固定部分和可变部分组成。
关于各个字段的解释:
version (V): 2 bits
识别 RTP 版本。RTP 数据包中的该值与 RTCP 数据包中的一样。当前规范定义的版本值为 2。
padding (P): 1 bit
间隙(Padding)。设置时, RTCP 数据包包含一些其它 padding 八位位组,它们不属于控制信息。Padding 的最后八位是用于计算应该忽略多少间隙八位位组。一些加密算法中需要计算固定块大小时也可能需要使用 Padding 字段。在一个复合 RTCP 数据包中,只有最后的个别数据包中才需要使用 padding ,这是因为复合数据包是作为一个整体来加密的。
reserved: 5 bits
保留位。通常被设置为0,并且被接收者忽略。
packet type (PT): 8 bits
RTCP的分组类型
length: 16 bits
RTCP 数据包的大小(32 位字减去 1),包含头和任意间隙 。
SSRC: 32 bits
同步信源标识符(SSRC)
2.1RTCP的分组类型
类型 | 缩写表示 | 意义 |
---|---|---|
200 | SR(Sender Report) | 发送端报告 |
201 | RR(Receiver Roport) | 接收端报告 |
202 | SDES(Source Descripition Items) | 源点 |
203 | BYE | 结束 |
204 | APP(Application) | 特定应用 |
205 | TSFB (transport layer specific feedback) | 运输层反馈信息 |
206 | PSFB (payload specific feedback) | 载荷反馈信息 |
SR(Sender Report)
发送端报告分组SR用来使发送端周期性地向所有接收端用多播方式进行报告。发送端每发送一个RTP流就要发送一个发送端报告分组SR。
RR(Receiver Roport)
接收端报告分组RR用来使接收端周期性地向所有的点用多播方式进行报告。接收端每收到一个RTP流(一次会话包含多个RTP流)就产生一个接收端报告分组RR。
SDES(Source Descripition Items)
源点描述分组SDES给出会话中参加者的描述,它包含参加者的规范名CNAME(Canonical NAME)。规范名是参加者的电子邮件地址的字符串。
BYE|结束
结束分组BYE表示关闭一个数据流。
APP(Application)
特定应用分组APP使应用程序能够定义新的分组类型。
3.解析RTCP包
3.1处理接收到的第一个RTCP包
if (s->mNumRTCPPacketsReceived++ == 0) {
sp<AMessage> notify = s->mNotifyMsg->dup();
notify->setInt32("first-rtcp", true);
notify->post();
}
如果s->mNumRTCPPacketsReceived的值为0,说明当前收到的RTCP包为第一个RTCP包,发送消息”first-rtcp”进行相应的异步处理,并且将该值自增1。如果不为0,则只将该值自增1,不发送消息进行异步处理。
3.2得到RTCP数据包的起始地址
const uint8_t *data = buffer->data();
size_t size = buffer->size();
3.3解析RTCP包
由于接收到的可能是一个复合包,所以采用了一个循环,依次解析每一个包
while (size > 0) {
if (size < 8) {
// Too short to be a valid RTCP header
return -1;
}
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;
}
size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
if (size < headerLength) {
// Only received a partial packet?
return -1;
}
switch (data[1]) {
case 200:
{
parseSR(s, data, headerLength);
break;
}
case 201: // RR
case 202: // SDES
case 204: // APP
break;
case 205: // TSFB (transport layer specific feedback)
case 206: // PSFB (payload specific feedback)
// hexdump(data, headerLength);
break;
case 203:
{
parseBYE(s, data, headerLength);
break;
}
default:
{
ALOGW("Unknown RTCP packet type %u of size %zu",
(unsigned)data[1], headerLength);
break;
}
}
data += headerLength;
size -= headerLength;
}
3.3.1判断当前协议的版本号
if (size < 8) {
// Too short to be a valid RTCP header
return -1;
}
if ((data[0] >> 6) != 2) {
// Unsupported version.
return -1;
}
一个RTCP包的头部部分固定的大小是8个字节,如果小于这个数值则不是一个完成的RTCP包。
一个RTCP包头部部分的第一个字节的前两位标识的是协议的版本号,所以data[0] >> 6即第一个字节向右移动6位,则标识协议版本号的两位则被移动到了低两位,高六位被填充0.即得到了协议版本号的值。当前的规范的协议版本号为2.
3.3.2判断填充位
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的范围来丢弃该填充位。
注:即使是符合包也只会在最后一个包添加填充位来进行加密算法,因为加密算法可能需要某个数字的整数倍的bit位数,所以只需要在最后一个RTCP包进行填充就好了。
3.3.3计算一个RTCP包的长度
size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4;
计算得到的headerLength值是界定一个完整的单个RTCP包的范围。一个RTCP包的第3,第4个字节所表示的整数值反应了一个RTCP包的大小。data[2] << 8 | data[3]计算技巧得到这个值,经过线性变换4 * (data[2] << 8 | data[3]) + 4;得到一个一个RTCP包的大小。
3.3.4处理不同的分组类型PT
switch (data[1]) {
case 200:
{
parseSR(s, data, headerLength);
break;
}
case 201: // RR
case 202: // SDES
case 204: // APP
break;
case 205: // TSFB (transport layer specific feedback)
case 206: // PSFB (payload specific feedback)
// hexdump(data, headerLength);
break;
case 203:
{
parseBYE(s, data, headerLength);
break;
}
default:
{
ALOGW("Unknown RTCP packet type %u of size %zu",
(unsigned)data[1], headerLength);
break;
}
}
分组类型PT是RTCP包第二个字节所表示的一个8bit整数,即data[1]。可以看出针对不同的分组类型对应不同的处理,传入的参数是界定一个单独的RTCP的参数。
3.3.4解析下一个RTCP包
data += headerLength;
size -= headerLength;
移动data指针,进入下一个循环,解析下一个RTCP包。