NuPlayer播放框架RTP數據包獲取和解析

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);進行後續的處理。

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