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);进行后续的处理。

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