WebRTC之RTT的两种计算方式
简述
RTT(round-trip time),往返时延,表示在通信过程中,发送端发送的数据包或信号抵达接收端后,再返回发送端的整个往返过程所需时长。
RTT由三个部分决定,分别是
- 链路的传输时长
- 路由器的排队和处理时长
- 接收端的处理时长
由于接收端的处理时长只与接收端的处理逻辑相关,不涉及网络传输,所以在RTT计算过程中一般不包括接收端的处理时长。即:
1 | RTT = 链路的传输时长 + 路由器的排队和处理时长 |
其中,路由器的排队和处理时长会随着整个网络的拥塞程度的变化而变化。
简言之,网络拥塞程度越严重,RTT的值也就越大。因此,RTT的变化在一定程度上反应了整个网络的拥塞程度的变化。这也是RTT常被当做重要指标用于分析网络性能的原因。
RTT的计算方式
在WebRTC中根据RTT计算的发起端的不同,可分为以下两种的计算方式:
基于发送端
这是WebRTC中默认开启的RTT计算方式,其流程图如下所示:

计算公式:
与之相关的RTCP报文有:SR(Sender Report)、RR(Receiver Report)和Report Block。
相关报文的格式
Send Report
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// Sender report (SR) (RFC 3550).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P| RC | PT=SR=200 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of sender |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
sender | NTP timestamp, most significant word |
info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, least significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| RTP timestamp |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's packet count |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sender's octet count |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| report block(s) |
| ... |字段解析:
NTP timestamp:64 bits
表示该SR包发送时的NTP时间戳,之所以使用NTP时间,是因为NTP可以同步不同计算机之间的时钟,降低时间误差。
关于NTP时间的介绍,可参考文章: 计算机中几个与时间相关的概念
RTP timestamp:32 bits
表示该SR包发送时的RTP时间戳,单位是采样时间戳,一般与NTP timestamp指向同一时刻,但也有可能会引入一个随机的偏移值。
sender’s packet count :32 bits
表示在生成该SR包时已经发送的RTP包的总个数。
sender’s octet count:32 bits
表示在生成该SR包时已经发送的RTP包的总字节数。
SR包除了自身信息之外,还可以作为Report Block的载体。
Receiver Report
1
2
3
4
5
6
7
8
9
10// RTCP receiver report (RFC 3550).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
header |V=2|P| RC | PT=RR=201 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| report block(s) |
| ... |RR包只是Report Block的载体,此外除了RTCP头,无其它字段。
Report Block
1
2
3
4
5
6
7
8
9
10
11
12
13+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
report | SSRC of media source |
block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| fraction lost | cumulative number of packets lost |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| extended highest sequence number received |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| interarrival jitter |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| last SR (LSR) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| delay since last SR (DLSR) |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+字段解析:
SSRC of media source:32 bits
表示该Report Block包所属的数据流,与发送SR包的数据源所对应的SSRC一致。
fraction lost:8 bits
表示从发送上一个Report Block包到发送当前Report Block包的这段时间内的丢包率。
采用Q8格式,即用0
255映射0.01.0范围。cumulative number of packets lost:24 bits
表示SSRC对应的数据流从开始到现在所累积的丢包个数,
extended highest sequence number received:32 bits
表示接收端当前所接收到的最大的RTP包序号。
该序号是一个解回绕后的值,高16 bits表示序号回绕的次数,低16位表示RTP序号。
NOTE:关于序号回绕技术的介绍留待后续。
interarrival jitter:32 bits
表示接收端先后接收到的两个RTP包之间的抵达间隔抖动。关于抖动的计算,可参考文章: WebRTC之抖动估计
last SR:32 bits
该字段对应接收端接收到的最新SR包中所携带的NTP时间戳。
NOTE:该字段是一个紧凑型的NTP时间戳,即分别取NTP时间中秒部分的低16 位和小数秒部分的高16位,然后拼凑成一个32位的值。采用紧凑型的NTP时间戳是一种以精度换空间的优化。
NTP时间转换成紧凑型NTP时间戳的方式:
1
2
3
4
5
6
7
8
9
10
11struct NTPTime {
uint32_t seconds; // 秒
uint32_t fractions; // 小数秒
};
// 接收端接收到的最新SR包中的NTP字段
NTPTime last_received_ntp;
// Compacted NTP timestamp
// 紧凑型的NTP时间戳
uint32_t last_sr_timestamp = ((last_received_ntp.seconds & 0x0000ffff) << 16) +
((last_received_ntp.fractions & 0xffff0000) >> 16);delay since last SR:32 bits
表示从接收端接收到的最新SR包到发送当前Report Block包之间的时间间隔,即接收端处理SR包的时间。如果截止目前没有收到SR包,则该值为0。
NOTE:该字段也是一个紧凑型的NTP时间戳。
代码导读
发送端发送SR包
有两种情况会触发SR包的发送,分别是:
在每次发送RTP包之前会检查是否可以发送一个SR包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19bool ModuleRtpRtcpImpl2::OnSendingRtpFrame(uint32_t timestamp,
int64_t capture_time_ms,
int payload_type,
bool force_sender_report) {
// 如果当前端不是发送端,则直接返回
if (!Sending())
return false;
...
// 确保SR包在关键帧之前发送,因为SR包的RTP timestamp字段会用于抖动估计和音视频同步
// Make sure an RTCP report isn't queued behind a key frame.
// 检查是否可以发送新的SR包
if (rtcp_sender_.TimeToSendRTCPReport(force_sender_report))
// 通过RTCPSender来发送SR包
rtcp_sender_.SendRTCP(GetFeedbackState(), kRtcpReport);
return true;
}在RTCPSender发送Report Block包时,如果是当前端是发送端则会使用SR作为载体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69// RTCP的Report包:Report Block、XR中扩展报告、SDES包等
void RTCPSender::PrepareReport(const FeedbackState& feedback_state) {
bool generate_report;
// 允许发送RTCP Report的两类情况:
if (IsFlagPresent(kRtcpSr) || IsFlagPresent(kRtcpRr)) {
// 情况一:在每次发送SR包或RR包时携带Report Block,因为Report Block只能以SR包或RR包作为载体发送
// Report type already explicitly set, don't automatically populate.
generate_report = true;
RTC_DCHECK(ConsumeFlag(kRtcpReport) == false);
} else {
// 情况二:当前发送的RTCP包类型中不包括SR或RR,则需要满足以下任一条件:
// a. 当前RTCP模式是复合模式(kCompound)
// 这种模式下,发送任意的RTCP包都会触发Report Block的发送。
// b. 当前RTCP模式是简化模式(kReducedSize)
// 只有当前发送的RTCP包类型中包含了Report Block类型,即添加了kRtcpReport标识,才会触发Report Block的发送。
generate_report =
(ConsumeFlag(kRtcpReport) && method_ == RtcpMode::kReducedSize) ||
method_ == RtcpMode::kCompound;
// 如果是发送端则使用SR包作为Report Block的载体,否则使用RR包作为载体。
if (generate_report)
SetFlag(sending_ ? kRtcpSr : kRtcpRr, true);
}
// 检测是否需要发送SDES包
if (IsFlagPresent(kRtcpSr) || (IsFlagPresent(kRtcpRr) && !cname_.empty()))
SetFlag(kRtcpSdes, true);
// 允许发送RTCP Report。
if (generate_report) {
// 检测是否需要发送扩展报告包,载体是XR包。
// 与基于接收端的RTT计算方式相关的两个RTCP包:Receiver Reference Time Report Block
// 和DLRR Report Block**,**以及视频分配码率的报告都是通过XR包作为载体发送。
if ((!sending_ && xr_send_receiver_reference_time_enabled_) ||
!feedback_state.last_xr_rtis.empty() ||
send_video_bitrate_allocation_) {
SetFlag(kRtcpAnyExtendedReports, true);
}
// Report Block的发送是周期性的
// generate next time to send an RTCP report
TimeDelta min_interval = report_interval_;
if (!audio_ && sending_) {
// 音频的RTCP报告使用固定的周期,视频的RTCP报告周期则与当前的码率相关:优先取当前码率的36%用于发送RTCP报文。
// Calculate bandwidth for video; 360 / send bandwidth in kbit/s.
int send_bitrate_kbit = feedback_state.send_bitrate / 1000;
if (send_bitrate_kbit != 0) {
min_interval = std::min(TimeDelta::Millis(360000 / send_bitrate_kbit),
report_interval_);
}
}
// 部署下一次发送Report Block的间隔时间,是一个任意值。
// 取范围是下一次发送时刻的前后0.5毫秒内:[interval_ms - 0.5,interval_ms + 0.5]
// The interval between RTCP packets is varied randomly over the
// range [1/2,3/2] times the calculated interval.
int min_interval_int = rtc::dchecked_cast<int>(min_interval.ms());
TimeDelta time_to_next = TimeDelta::Millis(
random_.Rand(min_interval_int * 1 / 2, min_interval_int * 3 / 2));
// 部署下一次Report Block的发送
RTC_DCHECK(!time_to_next.IsZero());
SetNextRtcpSendEvaluationDuration(time_to_next);
// RtcpSender expected to be used for sending either just sender reports
// or just receiver reports.
RTC_DCHECK(!(IsFlagPresent(kRtcpSr) && IsFlagPresent(kRtcpRr)));
}
}
接收端收到SR包之后,返回一个RR包或SR包
如果当前端本身既是发送端又是接收端,则在收到SR包后会在下次发送SR包时将Report Block一并返回。
如果当前端只是接受端,那么则为以RR为载体将Report Block返回。
同上,相关代码见函数RTCPSender::PrepareReport。
发送端在收到RR包或SR包后,提取相关字段计算RTT值
RTT的计算位于发送端的RTCPReceiver中,以下是相关代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40void RTCPReceiver::HandleReportBlock(const ReportBlock& report_block,
PacketInformation* packet_information,
uint32_t remote_ssrc) {
// 过滤不属于当前端的Report Blocks
if (!registered_ssrcs_.contains(report_block.source_ssrc()))
return;
// 接收到Report Block的系统时间
last_received_rb_ = clock_->CurrentTime();
...
// 根据Report Block计算RTT
int64_t rtt_ms = 0;
// T0:接收端收到的最新的SR包中的NTP时间戳
uint32_t send_time_ntp = report_block.last_sr();
// send_time_ntp为0表示接收端暂未收到SR包
if (send_time_ntp != 0) {
// 接收端的处理时长
uint32_t delay_ntp = report_block.delay_since_last_sr();
// T1:将系统时间转换成NTP时间后再格式化成紧凑型NTP时间戳
// Local NTP time.
uint32_t receive_time_ntp =
CompactNtp(clock_->ConvertTimestampToNtpTime(last_received_rb_));
// 计算RTT,因为在NTP时间戳中,秒和小数秒部分都是16位,所以单位是1/(2^16)秒
// RTT in 1/(2^16) seconds.
uint32_t rtt_ntp = receive_time_ntp - delay_ntp - send_time_ntp;
// 将RTT的单位转换成毫秒
// Convert to 1/1000 seconds (milliseconds).
rtt_ms = CompactNtpRttToMs(rtt_ntp);
report_block_data->AddRoundTripTimeSample(rtt_ms);
if (report_block.source_ssrc() == main_ssrc_) {
rtts_[remote_ssrc].AddRtt(TimeDelta::Millis(rtt_ms));
}
packet_information->rtt_ms = rtt_ms;
}
...
}
基于接收端
WebRTC中可通过设置RTCPSender中的xr_send_receiver_reference_time_enabled_来开启这种计算方式,其流程图如下所示:

计算公式:
与之相关的RTCP报文有:RRTR(Receiver Reference Time Report Block)、DLRR(DLRR Report Block)。
相关报文的格式
Receiver Reference Time Report Block
1
2
3
4
5
6
7
8
9
10
11// Receiver Reference Time Report Block (RFC 3611).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| BT=4 | reserved | block length = 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, most significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| NTP timestamp, least significant word |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+RRTRB其实就是一个简化版的SR,字段解析可参考上述SR报文的解析。
DLRR Report Block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// DLRR Report Block (RFC 3611).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| BT=5 | reserved | block length |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| SSRC_1 (SSRC of first receiver) | sub-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
| last RR (LRR) | 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| delay since last RR (DLRR) |
+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
| SSRC_2 (SSRC of second receiver) | sub-
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ block
: ... : 2sub-block其实就是一个简化版的Report Block,而DLRR则是sub-block的载体。一个DLRR报文可包含多个sub-block,字段解析可参考上述Report Block报文的解析。
代码导读
接收端发送一个RRTRB包
RRTRB包的发送逻辑同Report block一样,位于RTCPSender::PrepareReport中。
发送端收到RRTRB包后,返回一个DLRR包
DLRR包的发送逻辑同Report block一样,位于RTCPSender::PrepareReport中。
接收端收到DLRR包后,提取相关字段计算RTT值
RTT的计算位于接收端的RTCPReceiver中,以下是相关的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36void RTCPReceiver::HandleXrDlrrReportBlock(uint32_t sender_ssrc,
const rtcp::ReceiveTimeInfo& rti) {
// 过滤不属于当前接收端的DLRR包
if (!registered_ssrcs_.contains(rti.ssrc))
return;
// 这种RTT计算方式需要显示设置启动
// Caller should explicitly enable rtt calculation using extended reports.
if (!xr_rrtr_status_)
return;
// T0:发送端收到的最新的RRTRB包中的NTP时间戳,因为在NTP时间戳中,秒和小数秒部分都是16位,所以单位是1/(2^16)秒
// The send_time and delay_rr fields are in units of 1/2^16 sec.
uint32_t send_time_ntp = rti.last_rr;
// send_time_ntp == 0表示发送端暂时尚未收到RRTRB包
// RFC3611, section 4.5, LRR field discription states:
// If no such block has been received, the field is set to zero.
if (send_time_ntp == 0) {
auto rtt_stats = non_sender_rtts_.find(sender_ssrc);
if (rtt_stats != non_sender_rtts_.end()) {
rtt_stats->second.Invalidate();
}
return;
}
// 发送端的处理时长
uint32_t delay_ntp = rti.delay_since_last_rr;
// T1:接收端收到DLRR的NTP时间戳
uint32_t now_ntp = CompactNtp(clock_->CurrentNtpTime());
// 计算RTT,格式为NTP时间戳
uint32_t rtt_ntp = now_ntp - delay_ntp - send_time_ntp;
// 将RTT的单位转换成毫秒
xr_rr_rtt_ms_ = CompactNtpRttToMs(rtt_ntp);
non_sender_rtts_[sender_ssrc].Update(TimeDelta::Millis(xr_rr_rtt_ms_));
}
参考资料
RFC 3550 - RTP: A Transport Protocol for Real-Time Applications