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包。