关于音视频:音视频同步RTCP-协议解析及代码实现

32次阅读

共计 11807 个字符,预计需要花费 30 分钟才能阅读完成。

RTCP 是实时控制协定(Real-Time Control Protocol)的缩写。RTCP 由 RFC 3550 定义(取代作废的 RFC 1889)。

实时传输协定(RTP)和实时控制协定(RTCP)联合应用,能够监督大型多播网络的数据传递。RTP 承载媒体流,而 RTCP 用于 监督传输统计信息和服务质量。监督使接收器可能检测是否有任何丢包并弥补任何提早抖动。

两种协定都独立于根底传输层协定和网络层协定工作。RTP 标头中的信息通知接收器如何重建数据,并形容编解码器比特流的打包形式。

上面咱们重点解说 RTCP 性能、RTCP 信息包等。最初 RTCP 协定解析实现。

RTCP 有哪些性能

1、RTCP 次要性能是提供无关品质的反馈数据散发。这是 RTP 角色不可或缺的一部分,传输协定,与流量和拥塞无关其余传输协定的管制性能。

2、RTCP 带有 RTP 的持久性传输级别标识符源称为标准名称或 CNAME。自从如果发现抵触或程序重新启动,接管方要求 CNAME 跟踪每个参与者。

接收者也可能要求 CNAME 将来自给定参与者的多个数据流关联到汇合中相干 RTP 会话的数量,例如同步音频和视频。媒体间同步还须要 NTP 和 RTP 数据发送方在 RTCP 数据包中蕴含的工夫戳。

3、前两个性能要求所有参与者发送 RTCP 数据包,因而必须管制速率以使 RTP 可能扩充到大量参与者。通过让每个参与者将其管制包发送给所有其他人,每个人都能够独立察看参与者的数量。

4、这项性能对于参加者能够任意进入和来到的涣散会话过程非常有用,参加者能够自在进入或来到,没有成员管制或参数协调。

性能 1-3 应该在所有环境中应用,尤其是在 IP 多播环境。RTP 应用程序设计师应防止只能在单播模式下工作且无奈扩大到的机制更大的数字。RTCP 的传输能够独自管制对于发送者和接收者,实用于例如单向链接,而接收者没有反馈可能的。

RTCP 协定的端口

RTSP 通常应用 RTP 协定来传送实时流,RTP 个别应用偶数端口,而 RTCP 应用相邻的奇数端口,即 RTP 端口号 +1。

RTP 端口

RTCP 端口

RTCP 信息包有哪些

在 RTCP 通信管制中,RTCP 协定的性能是通过不同类型的 RTCP 包来实现的。RTCP 也是基于 UDP 包来传送的,次要有五种类型的封包:

  1. SR:发送端报告,由发送 RTP 数据报的应用程序或中端收回的。
  2. RR:接收端报告,由承受但不发送 RTP 数据报的应用程序或中端收回。
  3. SDES:源形容,传递与会话成员无关的标识信息的载体,如用户名、邮件、电话等。
  4. BYE:告诉来到,告诉回话中的其余成员将退出会话。
  5. APP:由应用程序本人定义,作为 RTCP 协定的扩大。
#define RTCP_SR      200
#define RTCP_RR      201
#define RTCP_SDES    202
#define RTCP_BYE     203
#define RTCP_APP     204

咱们能够依据这五种类型包判断 RTCP 头部

static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
    unsigned int offset = 0;
    unsigned int first_byte = 0;
    unsigned int packet_type = 0;


    /* 查看第一个字节 */
    first_byte = rtcp_info[offset];

    /* 版本位是否设置为 2 */
    //printf("version: %d\n",((first_byte & 0xC0) >> 6));
    if (((first_byte & 0xC0) >> 6) != 2)
    {return false;}

    /* 看包类型 */
    offset += 1;
    packet_type = rtcp_info[offset];
    //printf("packet_type: %d\n",packet_type);
    /* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
    if (!((packet_type == RTCP_SR)  || (packet_type == RTCP_RR) ||
          (packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
          (packet_type == RTCP_PSFB)))
    {return false;}

    /* 总长度必须是 4 个字节的倍数 */
    //printf("PayloadLen: %d\n",PayloadLen);
    if (PayloadLen % 4)
    {return false;}

    /* OK, dissect as RTCP */
    dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
    return true;
}

RTCP 协定报文格式

SR:发送端报告

版本(V):同 RTP 包头部

填充 (P):同 RTP 包头部。

接管报告计数器(RC):5b 该 SR 包中接管的报告块的数目。

包类型(PT):8bit SR 包类型为 200

长度(length):SR 包以 32bit 为 1 单位的长度减 1

同步源(SSRC):SR 包发送的同步源标识符。与对应 RTP 包中的 SSRC 一样。

NTP 工夫戳(Network Time Protocol):SR 包发送时的相对工夫。用于同步不同的流。

RTP 工夫戳:与 NTP 工夫戳对应,与 RTP 包中的工夫戳具备雷同的初始值。

Send’s Packet count:从开始发包到产生这个 SR 包的这段时间内发送者发送的无效数据的总字节数,不包含头部和填充,发送者扭转 SSRC 时,该域要清零。

同步源 n 的 SSRC 标识符:该报告中蕴含的是从该源接管到的包的统计信息。

失落率:表明从上一个 SR 或 RR 包收回依来从同步源 n 发送的 RTP 包的失落率。

累计失落数据:从开始承受 SSRC_n 的包到发送 SR 这个时间段内 SSRC_n 发送的 RTP 失落的总数目。

收到的扩大最大序列号:从 SSRC_n 收到的从 RTP 数据包中的最大序列号。

接管抖动(Interarrival jitter):RTP 数据包接管工夫的统计方差预计。

上次 SR 工夫戳(Last SR):取最近从 SSRC_n 收到的 SR 包中的 NTP 工夫戳中的两头 32bit。如果还未收到 SR 包,则为 0。

上次依赖 SR 提早(Delay since Last SR):从上次 SSRC_n 收到 SR 包到发送本包的提早

Wireshark 抓包:

流动会话的参与者在发送和接管 RTP 分组时应用 SR。SR 有三个不同的局部:报头信息、发送方信息和许多接管方报告块。SR 也能够有一个与纲要相干的扩大域。

Wireshark 抓包:

SDES:源形容

SDES 提供了传递与会话成员无关的标识信息的载体,如用户名、邮件、电话等。每个 RTCP 混合分组中必须有 SDES 分组。

报头蕴含一个长度域、一个净荷类型域(PT=202)和一个源计数(RC)域。RC 域 5 个 bit,示意分组中信息块的数量。

每个信息块蕴含一个 SSRC 或 CSRC 值,其后跟着一个或多个的标识符和一些可用于 SSRC 或 CSRC 的信息。

CNAME 项的 SDES 包必须蕴含在每个组合 RTCP 包中。SDES 包可能包含其余源形容项,这要依据特地的利用须要,并同时思考带宽限度。

Wireshark 抓包:

SDES 源形容包提供了直观的文本信息来形容会话的参加者,包含 CNAME、NAME、EMAIL、PHONE、LOC 等源形容项。

这些为接管方获取发送方的无关信息提供了不便。SDES 包由包头与数据块组成,数据块能够没有,也可有多个。包头由版本(V)、填充(P)、长度批示、包类型(PT)和源计数(SC)组成。

PT 占 8 位,用于辨认 RTCP 的 SDES 包,SC 占 5 位,批示蕴含在 SDES 包中的 SSRC/CSRC 块数量,零值无效,但没有意义。

BYE: 告诉来到

BYE 分组用于示意一个或多个媒体源不再是处于激活状态。

Wireshark 抓包:

作为可选项,BYE 包可包含一个 8 位八进制计数,后跟文本信息,示意来到起因。

最初,组合包中每个 RTCP 包可独立解决,而不须要依照包组合的先后顺序解决。

在组合包中有以下几条强制束缚。

  1. 只有带宽容许,在 SR 包或 RR 包中的接管统计应该常常发送,因而每个周期发送的组合 RTCP 包中应蕴含报告包。
  2. 每个组合包中都应该蕴含 SDES CNAME,因为新接收者须要通过接管 CNAME 来辨认源,并与媒体分割进行同步。
  3. 组合包后面是包类型数量,其增长应该受到限制。

RTCP 协定如何实现媒体流的同步

通过抓包剖析 RTCP 发送端报告,RTP 的同步其实就靠这三个域:

  1. sender SSRC:SR 包发送的同步源标识符。与对应 RTP 包中的 SSRC 一样。
  2. NTP timestamp:SR 包发送时的相对工夫。用于同步不同的流。
  3. RTP timestamp:与 NTP 工夫戳对应,与 RTP 包中的工夫戳具备雷同的初始值。

那怎么计算 NTP 工夫呢?在 RTCP 中 NTP 工夫寄存在 8 个字节中,分为:MSW 和 LSW,别离占用 4 个字节。

const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
    uint32_t tempstmp = 0;
    time_t temptime = 0;
    struct tm *bd;
    char *buff = NULL;
    
    tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
    if (tempstmp == 0){return "NULL";}

    /* We need a temporary variable here so the unsigned math
    * works correctly (for years > 2036 according to RFC 2030
    * chapter 3).
    */
    temptime = (time_t)(tempstmp - NTP_BASETIME);
    bd = gmtime(&temptime);
    if (!bd){return "Not representable";}

    buff = (char *)malloc(NTP_TS_SIZE);
    snprintf(buff, NTP_TS_SIZE,
        "%s %2d, %d %02d:%02d:%02d UTC",
        mon_names[bd->tm_mon],
        bd->tm_mday,
        bd->tm_year + 1900,
        bd->tm_hour,
        bd->tm_min,
        bd->tm_sec);
    return buff;
}

NTP timestamp

  
    /* NTP timestamp */
    ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
    printf("ts_msw: 0x%x\n",ts_msw);
    ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
    printf("ts_lsw: 0x%x\n",ts_lsw);
    printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
    offset += 8;

RTCP 协定实现

上面我给出对 RTCP 协定解析实现的代码,依据回放报文,解析字段信息。

/* 接收者 / 发送者计数是最初 5 位   */
#define RTCP_COUNT(octet)   ((octet) & 0x1F)


#define RTCP_PT_MIN  192
/* Supplemental H.261 specific RTCP packet types according to Section C.3.5 */
#define RTCP_FIR     192
#define RTCP_NACK    193
#define RTCP_SMPTETC 194
#define RTCP_IJ      195
/* RTCP packet types according to Section A.11.1 */
/* And https://www.iana.org/assignments/rtp-parameters/rtp-parameters.xhtml */
#define RTCP_SR      200
#define RTCP_RR      201
#define RTCP_SDES    202
#define RTCP_BYE     203
#define RTCP_APP     204
#define RTCP_RTPFB   205
#define RTCP_PSFB    206
#define RTCP_XR      207
#define RTCP_AVB     208
#define RTCP_RSI     209
#define RTCP_TOKEN   210

#define RTCP_PT_MAX  210


static const char mon_names[12][4] = {
    "Jan",
    "Feb",
    "Mar",
    "Apr",
    "May",
    "Jun",
    "Jul",
    "Aug",
    "Sep",
    "Oct",
    "Nov",
    "Dec"
};

/** data structure to hold time values with nanosecond resolution*/
typedef struct {
    time_t    secs;
    int    nsecs;
} nstime_t;


/*
 * 1900-01-01 00:00:00 (proleptic?) UTC.
 * Used by a number of time formats.
 */
#define EPOCH_DELTA_1900_01_01_00_00_00_UTC 2208988800U

/*
 * NTP_BASETIME is in fact epoch - ntp_start_time; ntp_start_time
 * is January 1, 2036, 00:00:00 UTC.
 */
#define NTP_BASETIME EPOCH_DELTA_1900_01_01_00_00_00_UTC
#define NTP_FLOAT_DENOM 4294967296.0
#define NTP_TS_SIZE 100


/* 解剖长度字段。附加到此字段的文字示意转换为的理论字节数 (即 (原始值 + 1) * 4) */
static int dissect_rtcp_length_field(u_char *rtcp_info, int offset)
{uint16_t  raw_length = ntohs(*(uint16_t*)(rtcp_info + offset));
    printf("(%u bytes)\n", (raw_length+1)*4);
    offset += 2;
    return offset;
}
static int dissect_rtcp_rr(u_char *rtcp_info, int offset,int count, int packet_length)
{
    int counter = 0;
    uint8_t  rr_flt = 0;
    int    rr_offset = offset;
    
    counter = 1;
    while (counter <= count) {
        uint32_t lsr = 0, dlsr = 0;

        /* Create a new subtree for a length of 24 bytes */

        /* SSRC_n source identifier, 32 bits */

        offset += 4;

        /* Fraction lost, 8bits */
        rr_flt = rtcp_info[offset];

        offset++;

        /* Cumulative number of packets lost, 24 bits */
        offset += 3;


        /* Sequence number cycles */

        offset += 2;
        /* highest sequence number received */

        offset += 2;

        /* Interarrival jitter */

        offset += 4;

        /* Last SR timestamp */
        lsr = ntohl(*(uint32_t*)(rtcp_info + offset));
        printf("Last SR timestamp: 0x%x\n",lsr);
        offset += 4;

        /* Delay since last SR timestamp */
        dlsr = ntohl(*(uint32_t*)(rtcp_info + offset));

        printf("(%d milliseconds)\n",(int)(((double)dlsr/(double)65536) * 1000.0));
        offset += 4;

        counter++;
    }

    return offset;
}

const char *tvb_ntp_fmt_ts_sec(u_char *rtcp_info, int offset)
{
    uint32_t tempstmp = 0;
    time_t temptime = 0;
    struct tm *bd;
    char *buff = NULL;
    
    tempstmp = ntohl(*(uint32_t*)(rtcp_info + offset));
    if (tempstmp == 0){return "NULL";}

    /* We need a temporary variable here so the unsigned math
    * works correctly (for years > 2036 according to RFC 2030
    * chapter 3).
    */
    temptime = (time_t)(tempstmp - NTP_BASETIME);
    bd = gmtime(&temptime);
    if (!bd){return "Not representable";}

    buff = (char *)malloc(NTP_TS_SIZE);
    snprintf(buff, NTP_TS_SIZE,
        "%s %2d, %d %02d:%02d:%02d UTC",
        mon_names[bd->tm_mon],
        bd->tm_mday,
        bd->tm_year + 1900,
        bd->tm_hour,
        bd->tm_min,
        bd->tm_sec);
    return buff;
}

static int dissect_rtcp_sr(u_char *rtcp_info, int offset,int count, int packet_length)
{

    uint32_t     ts_msw = 0, ts_lsw = 0;
    int         sr_offset = offset;

    /* NTP timestamp */
    ts_msw = ntohl(*(uint32_t*)(rtcp_info + offset));
    printf("ts_msw: 0x%x\n",ts_msw);
    ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4));
    printf("ts_lsw: 0x%x\n",ts_lsw);

    //printf("offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]);
    printf("MSW: %s\n",tvb_ntp_fmt_ts_sec(rtcp_info,offset));
    offset += 8;

    /* RTP timestamp, 32 bits */
    
    offset += 4;
    /* Sender's packet count, 32 bits */

    offset += 4;
    /* Sender's octet count, 32 bits */

    offset += 4;


    /* The rest of the packet is equal to the RR packet */
    if (count != 0)
        offset = dissect_rtcp_rr(rtcp_info, offset, count, packet_length-(offset-sr_offset));
    else
    {
        /* If length remaining, assume profile-specific extension bytes */
        if ((offset-sr_offset) < packet_length)
        {offset = sr_offset + packet_length;}
    }

    return offset;
}

static int dissect_rtcp_sdes(u_char *rtcp_info, int offset, int count)
{
    int           chunk = 0;

    int           start_offset = 0;
    int           items_start_offset = 0;
    uint32_t       ssrc = 0;
    unsigned int  item_len = 0;
    unsigned int  sdes_type = 0;
    unsigned int  prefix_len = 0;

    chunk = 1;
    while (chunk <= count) 
    {
        /* Create a subtree for this chunk; we don't yet know
           the length. */
        start_offset = offset;

        ssrc = ntohl(*(uint32_t*)(rtcp_info + offset));
        printf("Chunk %u, SSRC/CSRC 0x%X\n", chunk, ssrc);

        /* SSRC_n source identifier, 32 bits */
        offset += 4;

        /* Create a subtree for the SDES items; we don't yet know
           the length */


        /*
         * Not every message is ended with "null" bytes, so check for
         * end of frame as well.
         */
         
          /* ID, 8 bits */
        sdes_type = rtcp_info[offset];
        printf("Type: %d\n",sdes_type);
        if (sdes_type == 0)
            break;
        offset++;
        /* Item length, 8 bits */
        item_len = rtcp_info[offset];
        printf("Length: %d\n",item_len);
        offset++;
        
        char *pszText = (char*)malloc(item_len);
        if (pszText != 0)
        {memcpy(pszText, rtcp_info + offset,item_len);
            pszText[item_len] = '\0';
            printf("Text = %s\n",pszText);
        }    

        chunk++;
    }

    return offset;
}

static void dissect_rtcp(u_char *rtcp_info,int packet_type, int offset,int PayloadLen)
{
    unsigned int  temp_byte = 0;
    int  elem_count = 0;
    int  packet_length = 0;
    int  total_packet_length = 0;
    int loop = 2;
    bool flag_rtcp = false;

        /* 查看是否为无效类型 */
        if (( packet_type < RTCP_PT_MIN) || (packet_type >  RTCP_PT_MAX) )
            exit(-1);

        /*
         * 获取残缺的 RTCP 数据包的长度
         */
         
        packet_length = (ntohs(*(uint16_t*)(rtcp_info + offset + 1)) + 1) * 4 ;
        //printf("packet_length: %d\n",packet_length);

        
        temp_byte = rtcp_info[offset-1];
        elem_count = RTCP_COUNT(temp_byte);/* Source count, 5 bits */
        printf("Reception report count: %d\n",elem_count);  

        switch (packet_type) 
        {
            
            case RTCP_SR:
            case RTCP_RR:
                /*
                    Real-time Transport Control Protocol (Receiver Report)
                    10.. .... = Version: RFC 1889 Version (2)
                    ..0. .... = Padding: False
                    ...0 0001 = Reception report count: 1
                    Packet type: Receiver Report (201)
                    Length: 7 (32 bytes)
                    Sender SSRC: 0xb584b03e (3045371966)
                    Source 1
                */

                /* Packet type, 8 bits */
                offset++;
                /* Packet length in 32 bit words MINUS one, 16 bits */
                offset = dissect_rtcp_length_field(rtcp_info, offset);
                /* Sender Synchronization source, 32 bits */
                offset += 4;

                if (packet_type == RTCP_SR)
                {offset = dissect_rtcp_sr(rtcp_info, offset, elem_count, packet_length-8);
                    printf("dissect_rtcp_sr\n");
                }
                else
                {offset = dissect_rtcp_rr(rtcp_info, offset, elem_count, packet_length-8);                            
                }
                
                //uint16_t second_packet_type = ntohs(*(uint16_t*)(rtcp_info + offset));

                //printf("111offset: 0x%x 0x%x 0x%x 0x%x\n",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]);
                
                if (rtcp_info[offset + 1] == RTCP_SDES)
                {
                    
                    /* Source count, 5 bits */
                    offset++;
                    /* Packet type, 8 bits */
                    offset++;
                    /* Packet length in 32 bit words MINUS one, 16 bits */
                    offset = dissect_rtcp_length_field(rtcp_info, offset);
                    offset = dissect_rtcp_sdes(rtcp_info,offset,elem_count);

                }

            break;

            default:
                /*
                 * To prevent endless loops in case of an unknown message type
                 * increase offset. Some time the while will end :-)
                 */
                offset++;
                break;

        }
        
}


static bool dissect_rtcp_heur(u_char *rtcp_info,int PayloadLen)
{
    unsigned int offset = 0;
    unsigned int first_byte = 0;
    unsigned int packet_type = 0;


    /* 查看第一个字节 */
    first_byte = rtcp_info[offset];

    /* 版本位是否设置为 2 */
    //printf("version: %d\n",((first_byte & 0xC0) >> 6));
    if (((first_byte & 0xC0) >> 6) != 2)
    {return false;}

    /* 看包类型 */
    offset += 1;
    packet_type = rtcp_info[offset];
    //printf("packet_type: %d\n",packet_type);
    /* 复合数据包中的第一个数据包应该是发送方或接收者报告 */
    if (!((packet_type == RTCP_SR)  || (packet_type == RTCP_RR) ||
          (packet_type == RTCP_BYE) || (packet_type == RTCP_APP) ||
          (packet_type == RTCP_PSFB)))
    {return false;}

    /* 总长度必须是 4 个字节的倍数 */
    //printf("PayloadLen: %d\n",PayloadLen);
    if (PayloadLen % 4)
    {return false;}

    /* OK, dissect as RTCP */
    dissect_rtcp(rtcp_info,packet_type,offset,PayloadLen);
    return true;
}


static void confirm_rtcp_packet(struct ip *pIp)
{int iIpTotalLen = ntohs(pIp->ip_len);
    int offset = 0;
    int nFragSeq = 0;
    struct udphdr* pUdpHdr = (struct udphdr*)((char*)pIp + (pIp->ip_hl<<2));
    if (pIp->ip_p == IPPROTO_UDP) 
    {printf("\n");
        
        int iPayloadLen = iIpTotalLen - (pIp->ip_hl<<2) - 8;
        printf("UDP Payload Len %d\n", iPayloadLen);
        
        u_char *pDnsHdr = (u_char*)(pUdpHdr+1);
        dissect_rtcp_heur(pDnsHdr,iPayloadLen);
        
    }    
}

编译运行:

总结

RTCP 协定是流媒体通信的基石。RTCP 协定则负责牢靠传输、流量管制和拥塞管制等服务质量保障。下面解说了 RTCP 性能、RTCP 数据包格局及代码实现。最初,学习一个新的协定,最好还是钻研学习官网文档,因为这是最权威的材料。

正文完
 0