音视频同步!RTCP协议解析及代码实现

音视频同步!RTCP协议解析及代码实现

2023年7月3日发(作者:)

⾳视频同步!RTCP协议解析及代码实现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: %dn",((first_byte & 0xC0) >> 6)); if (((first_byte & 0xC0) >> 6) != 2) { return false; } /* 看包类型 */ offset += 1; packet_type = rtcp_info[offset]; //printf("packet_type: %dn",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: %dn",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%xn",ts_msw); ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4)); printf("ts_lsw: 0x%xn",ts_lsw); printf("MSW: %sn",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 /assignments/rtp-parameters/ */#define RTCP_SR 200#define RTCP_RR 201#define RTCP_SDES 202#define RTCP_BYE 203#define RTCP_APP 204#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 210static 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; 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%xn",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%xn",ts_msw); ts_lsw = ntohl(*(uint32_t*)(rtcp_info + offset + 4)); printf("ts_lsw: 0x%xn",ts_lsw); //printf("offset: 0x%x 0x%x 0x%x 0x%xn",rtcp_info[offset],rtcp_info[offset + 1],rtcp_info[offset + 2],rtcp_info[offset + 3]); printf("MSW: %sn",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 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%Xn", 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: %dn",sdes_type); if (sdes_type == 0) break; offset++; /* Item length, 8 bits */ item_len = rtcp_info[offset]; printf("Length: %dn",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 = %sn",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: %dn",packet_length);

temp_byte = rtcp_info[offset-1]; elem_count = RTCP_COUNT( temp_byte );/* Source count, 5 bits */ printf("Reception report count: %dn",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_srn"); } 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%xn",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 :-) * 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: %dn",((first_byte & 0xC0) >> 6)); if (((first_byte & 0xC0) >> 6) != 2) { return false; } /* 看包类型 */ offset += 1; packet_type = rtcp_info[offset]; //printf("packet_type: %dn",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: %dn",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 %dn", iPayloadLen);

u_char *pDnsHdr = (u_char*)(pUdpHdr+1); u_char *pDnsHdr = (u_char*)(pUdpHdr+1); dissect_rtcp_heur(pDnsHdr,iPayloadLen);

}

}编译运⾏:总结RTCP 协议是流媒体通信的基⽯。RTCP 协议则负责可靠传输、流量控制和拥塞控制等服务质量保证。上⾯讲解了 RTCP 功能、RTCP 数据包格式及代码实现。最后,学习⼀个新的协议,最好还是研究学习官⽅⽂档,因为这是最权威的资料。

发布者:admin,转转请注明出处:http://www.yc00.com/web/1688337252a122624.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信