NuPlayer播放框架解析RTCP包

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章