RTCP包之SR和RR
RTCP包是RTP协议中的一部分,用来传递rtp包统计信息及一些控制信息,RFC3550中定义了RTCP结构及最通用的两种包类型发送者报告(SR)和接收者报告(RR)。在webrtc中对RTCP包类型进行了扩展,用于不同的用途。这篇文章主要介绍了RTCP包的基本结构,SR和RR包的中各字段的意义。
基本包结构

公共头+特定的rtcp报类型
公共头
就是所有的rtcp包,都带有这个头,总共4个字节。

- v 表示version,固定为2,占2位。
- p 表示padding,填充标识占1位,填充的字节放在payload的最后。
- count/format 在sr/rr中表示Report Block的个数,在其它的rtcp中表示子类型,占5位。
- type 标识packet类型,占1个字节。
- length 表示包长度(包括头和padding),单位为4字节,指4字节的个数。
包类型
RFC3550中定义的RTCP包类型

webrtc中扩展的RTCP包类型
- FIR 关键帧请求。
- NACK 重传请求。
- RTPFB 反馈包,指应用层的反馈包。
- PSFB 反馈包,指负载层的反馈包。
其中RTPFB和PSFB,又分别包含几种子类型,子类型就是通过common header中的formate字段标识(也是count字段)
发送者报告(SR包)
顾名思义,SR包是在RTP的发送端产生的RTCP包,发送给RTP接收端,如下是它的包结构:

包括三部分:
- common header,这个是所有的rtcp包都有的头。
- sender info,SR包所特有的部分。
- report block,本地接收的rtp包的统计信息,在SR包和RR包都有,但是如果本端只发不收,那么就不会产生report block。
sender info
- ssrc of sender,32位,这个SR包的发送者的ssrc。
- ntp,64位,这个包发送的ntp时间戳。
- rtp timestamp,32位,ntp时间对应的rtp时间戳。
- sender’s packet count,32位,从发送端开始发包,到该SR产生时总的发包数。
- sender’s octet count,32位,从发送端开始发包,到该SR产生时总的payload字节数(不包括header和padding)。
report block
一个report block带了一路媒体流的统计信息
- src ssrc,32位,对应的媒体流(rtp包)ssrc。
- fraction lost,8位,丢失的比例,它是个间隔统计值,从上一个SR或RR,到产生当前SR或RR时的丢包率,转换公式是loss fraction = lost rate x 256。
丢包率是这个报告间隔中所丢失包的数量,除以预期到达的数量(即所丢失数据包数量占所发送数据包的比例)。
计算公式是:lost rate = (cumulativeloss- last_report_cumulative_loss_)/(received_seq_max_ - last_report_seq_max_)
丢包率的计算:
fraction lost在rtp接收端计算,但是rtp发送端也可以自己计算,根据report block中的cumulative number of packet lost和extended highest sequence number received来计算。通过两个report block中的
cumulative number of packet lost差值得出间隔值,extended highest sequence number received差值得出间隔间期望收到的包数,这两个差值的比例就是丢包率。当有重复的包时,可能计算出来是负的丢包率。
- cumulative number of packet lost,24位,从开始接收包到当前SR包产生的时间点,src ssrc对应的媒体流,丢包的总数。
这个数字被定义为预期的数据包数(最大的seq+1)减去实际收到的数据包数,其中接收到的数据包数包括任何迟到或重复的数据包。因为迟到的包和重复的包存在(迟到的包不算丢包),这个值可能是个负值。
- extended highest sequence number received,32位,从开始到SR包产生的时间点,低16位表示收到的最大seq,高16位表示seq循环的此时。
计算方法:extended_seq_num = seq_num + (65536 * wrap_around_count),其中wrap_around_count为sequence翻转的次数。
因为seq只有16位,所以到最大值后,又从0开始,这就叫做wrap around,计算时要算上wrap around的次数。
- interarrival jiiter,32位,接收时间差 - 发送时间的统计方差,接收时间差 - 发送时间的值在理想情况下是一个恒定值,一般情况,这个值会变大或变小,这个就是interarrival jiiter。
RTP 数据包到达间隔时间的统计方差的估计,以时间戳为单位进行测量,并表示为无符号整数。可用于衡量网络拥塞,在出现丢包之前,jitter值会越来越大。
算法:
If Si is the RTP timestamp from packet i, and Ri is the time of arrival in RTP timestamp
units for packet i, then for two packets i and j, D may be expressed as
D(i, j)=(Rj-Ri)-(Sj-Si)=(Rj-Sj)-(Ri-Si)
The interarrival jitter should be calculated continuously as each data packet i is received
from source SSRC n, using this difference D for that packet and the previous packet i-1 in
order of arrival (not necessarily in sequence), according to the formula
J(i) = J(i-1) + (|D(i-1, i)| − J(i-1))/16
-
last sr timestamp (LSR),32位,最近接收到的SR包中NTP时间戳字段的中间32位(就是发送SR包的时间)。
-
delay since last SR(DLSR),32位,接收到SR报文的时刻与发送报文时刻的时间差值,如果没收到SR报文,该字段为0。
rtt的计算

LSR和DLSR用于计算rtt,如上图,Receiver侧发送的RR中,携带了LSR和DLSR,在Sender侧通过计算公式:
rtt = receive_time_ntp - DLSR - LSR
SR包中的时间戳
在SR包中最重要的两个字段是ntp和rtp timestamp,这个两个字段都表示ntp时间戳。起到了统一时间基准线的作用,因为发送端和接收端的时间基准可能并不相同,在SR包中携带时间戳,就是将本地的时间基准线告知接收端。接收端知道时间戳后,就可以计算rtp包的时间戳,计算从发送端到接收端的延迟,进行音视频同步等。SR包都是周期性的发送,所以接收端都可以基于SR包中的时间戳来实时计算。搞清楚SR时间戳的生成及转换非常有必要。
NTP
ntp是网络时间同步协议,ntp时间戳是指在协议中携带的时间格式。在rtp协议中,并不要求实现ntp协议,发送端和接收端见也不需要同步时间,但是需要知道对端的时间基准,通过SR中携带的ntp时间戳就是告知时间的基准,它表示当前SR发送的时钟时间点,对应的就是SR中sender info中的时间戳字段。
它表示的值是从 0h UTC on 1 January 1900到现在的时间的秒数。是一个64位数,高32位表示整数(整数秒),低32位表示小数(小于秒的单位),用两个字段来表示。
它的低32位表示小数部分,表示1秒被分割成232等份(32位二进制小数表示的值就是"各个位的取值x对应权重"的累加,1x2-1 + 1x2^-2… + 1x2-32,值无限接近1秒),**所以它的精度是1/232秒(0.233纳秒)。**
二进制怎么表示小数,是属于计算机基础知识,可以看看下面两个链接:
小数的二进制表示
深入理解计算机系统(2.7)------二进制小数和IEEE浮点标准
从SR Sender Info中分别取出了整秒数(高32位),小数(低32位),那么整合成uint64_t表示的ntp值,就是seconds * 2^32 + fraction second。
从uint64_t的ntp值中分离出来秒数和小数,秒数是ntp/232,就是右移32位;小数是ntp%232,就是value & 0xFFFFFFFF。
那么将整个64位NTP值转换为秒数,就是double seconds = (ntp >>32) + std::static_cast<double>((ntp & 0xFFFFFFFF)) / 2^32(数量乘以精度)。
转换为毫秒,更通用,可以将上面double类型的秒数乘以1000,表示位毫秒数。如果是通过uint64_t表示毫秒数,则可以如下计算:
double frac_ms = static_cast<double>(ntp & 0xFFFFFFFF)*1000.0/2^32;
//+ 0.5是对frac_ms做四舍五入处理
uint64_t s_ms = 1000 * static_cast<int64_t>(ntp >>32) + static_cast<int64_t>(frac_ms + 0.5);
compat ntp
在RFC3550中,说明了使用NTP的中间32位来表示时间值的方法,取高32位的低16位和低32位的高16位组合成compat ntp。recevier report block中的 LSR字段,就是用该形式来表示时间值。
这种取中间32位表示的时间值,可以理解成是一个时间段。比如对NTP值,高32位0xE85A1F30,低32位0xC7A2F1E3,取中间32位即0x1F30C7A2,0x1F30 表示整数秒,为7984秒,0xC7A2表示的小数秒,0xC7A2 * (1/2^16) 约等于0.764秒。那么组合起来对应的时间是:7984 秒 + 0.764 秒 ≈ 7984.764 秒(约2小时13分4.764秒)。
将compact ntp转换成毫秒,本质与ntp转换的原理相同,但是精度值变化了,如下:
double CompactNtpToMillis(uint32_t ntpTime) {// 分离高16位表示秒数,低16位表示小数部分uint16_t seconds = (ntpTime >> 16) & 0xFFFF;uint16_t fraction = ntpTime & 0xFFFF;// 将秒数转换为毫秒数double milliseconds = static_cast<double>(seconds) * 1000.0;/*精度变为了1/2^16*/milliseconds += static_cast<double>(fraction) * 1000.0/65536.0 ;return milliseconds;
}
转换为整数毫秒
double frac_ms = static_cast<double>(ntpTime & 0xFFFF)*1000.0/2^16;
//+ 0.5是对frac_ms做四舍五入处理
uint64_t s_ms = 1000 * static_cast<int64_t>(ntpTime >> 16) + static_cast<int64_t>(frac_ms + 0.5);
将ntp转换为compact ntp
uint32_t NtpToCompactNtp(uint64_t ntp) {uint32_t seconds = static_cast<uint32_t>(ntp >> 32);uint32_t fractions = static_cast<uint32_t>(ntp && 0xFFFFFFFF);uint32_t compactNtp = seconds << 16 | fractions >> 16;return compactNtp;
}
将当前时间(UTC)转换为NTP
NTP的起始时间是0h UTC on 1 January 1900,而UTC的起始时间是 0h UTC on 1 January 1970,所以首先要将起始时间对齐,两者的相差值为2208988800UL秒。
uint64_t ConvertTimestampToNtpTime(uint64_t ms) {const uint32_t kNtpJan1970 = 2208988800UL;//当前时间的单位为毫秒uint32_t seconds = (ms / 1000) + kNtpJan1970;uint32_t fractions = static_cast<uint32_t>((ms % 1000) * kMagicNtpFractionalUnit / 1000);uint64_t ntp = seconds*2^32 + fractions;return ntp;
}
计算rtt时的时间转换
上面提到了rtt的计算方法 rtt = receive_time_ntp - DLSR - LSR,receive_time_ntp是收到SR的时间为UTC时间,而LSR是用32位的compat ntp表示的ntp时间,计算rtt时,需要recevice_time_ntp先要转换为32位的compact ntp值,即NtpToCompactNtp(ConvertTimestampToNtpTime(currentTimeInMs),再将rtt值转换为毫秒,即CompactNtpToMillis(rtt)。
接收者报告(RR包)
顾名思义,RR包是在RTP的接收端产生的RTCP包,发送给RTP的发送端,一定会带report block。
RR也是包括三个部分:
- common header。
- header,只有一个ssrc,标识这个RR的发送者。
- report block,跟sr中的一样。

