在GCC(Google Congestion Control)中,包含两种拥塞控制算法。一种是基于丢包的,一种是基于时延的。GCC最后综合这两种算法得到一个目标码率。
基于时延的拥塞控制算法主要由四部分组成:预处理(pre-filtering), 到达时间滤波器(arrival-time filter), 过载检测器(over-use detector),码率控制器(and a rate controller)。
到达时间模型
在A Google Congestion Control Algorithm for Real-Time Communication定义了一种到达时间模型(Arrival-time model),在这里我们先介绍几个概念。
两个包组(包组概念下一小节介绍)的到达时间差:inter-arrival时间:
1 |
t(i) - t(i-1) |
两个包组离开(发送)时间差:inter-departure时间:
1 |
T(i) - T(i-1) |
包组i与包组i-1的时延变化:
1 |
d(i) = t(i) - t(i-1) - (T(i) - T(i-1)) |
这个时延变化将在后续评估网络时延增长趋势的滤波器中用到,用于判断网络拥塞状况,这里就先不展开讨论,本文主要介绍这些时间差如何计算。
包组
在WebRTC中计算时延不是一个个包计算的,而是通过将包分组,然后计算这些包组间的时延,这样做可以减少计算次数,同时减少误差。如何对包进行分组呢?
主要是通过计算发送时间差值来分组。在包组中,除了第一个包外,后面的包距离包组第一个包的发送时间差小于5ms。假设每个包都有一个发送时间t,第一个包的发送时间为t0,如果后续的包发送时间与t0时间差△t = t -to <= 5ms
,那么这些包都可以与t0发送时间的包归为一组,如果某个包得到的△t > 5ms
,那么该包就作为下一个包组的第一个包,接着后面的包就跟该包继续比较时间差,判断能否归为一组。下面我们盗个图举个例子说明下。
上图中有两个包组G1和G2, 其中第100号包与103号包的时间差小于5毫秒,那么100 ~ 103被划作一个包组。104与100之间时间超过5毫秒,那么104就是G2的第一个包,它与105、106、107划作一个包组。
在后续评估时延增长趋势的滤波器需要三个主要参数:发送时刻差值(timestamp_delta
)、到达时刻差值(arrival_time_delta
)和包组数据大小差值(packet_size_delta
)。由上图可知:
1 2 3 |
timestamp_delta = T2 - T1; arrival_time_delta = t2 - t1; packet_size_delta = G2_size - G1_size; |
WebRTC源码导读
ComputeDeltas函数
前面说到的时间差值计算以及包组判断代码主要由InterArrival
类实现。InterArrival
类主要就一个接口:ComputeDeltas
。传入每个RTP包时间、大小等参数,计算包组时间间隔,包组大小差值,得到新的包组相关差值后返回true。
1 2 3 4 5 6 7 |
bool InterArrival::ComputeDeltas(uint32_t timestamp, int64_t arrival_time_ms, int64_t system_time_ms, size_t packet_size, uint32_t* timestamp_delta, int64_t* arrival_time_delta_ms, int* packet_size_delta) |
下面解释下各个参数:
timestamp
:RTP包发送时间arrival_time_ms
:RTP包到达时间system_time_ms
:当前时间timestamp_delta
(output):包组发送时间差arrival_time_delta_ms
(output): 包组到达时间差packet_size_delta
(output) :包组大小差值
接下来看下相关代码流程,看timestamp_delta
、arrival_time_delta_ms
如何计算:
1 2 3 4 5 6 7 8 9 10 11 12 |
// 若当前包属于新的包组 if (NewTimestampGroup(arrival_time_ms, timestamp)) { // 前一个包组有数据 if (prev_timestamp_group_.complete_time_ms >= 0) { // 包组发送时间差:当前包组最后一个包发送时间减去前一个包组最后一个包发送时间 *timestamp_delta = current_timestamp_group_.timestamp - prev_timestamp_group_.timestamp; // 包组到达时间差:当前包组最后一个包到达时间减去前一个包组最后一个包到达时间 *arrival_time_delta_ms = current_timestamp_group_.complete_time_ms - prev_timestamp_group_.complete_time_ms; } } |
packet_size_delta
计算如下:
1 2 3 |
// 包组大小差值:当前包组大小减去前一个包组大小 *packet_size_delta = static_cast<int>(current_timestamp_group_.size) - static_cast<int>(prev_timestamp_group_.size); |
NewTimestampGroup函数
WebRTC新包组判断代码:
1 2 3 4 5 6 7 8 9 10 11 12 |
bool InterArrival::NewTimestampGroup(int64_t arrival_time_ms, uint32_t timestamp) const { if (current_timestamp_group_.IsFirstPacket()) { return false; } else if (BelongsToBurst(arrival_time_ms, timestamp)) { return false; } else { uint32_t timestamp_diff = timestamp - current_timestamp_group_.first_timestamp; return timestamp_diff > kTimestampGroupLengthTicks; } } |
- 若是第一个包,得作为后续包对比基准,不认为是新包组
- 若是突发数据(burst),不认为是新包组
- 与第一个包发送时间间隔大于
kMaxBurstDurationMs
(值为5),认为该包属于新包组
BelongsToBurst函数
这里我们也介绍下上一小节说到的突发数据。有些客户端发送数据时,没用使用pacing平滑发送模块,来数据时没有控制发送速率,直接往网络中发送,这样容易导致突发流量,尤其是发送关键帧数据时。我们看下WebRTC中如何判断突发数据的。
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 |
bool InterArrival::BelongsToBurst(int64_t arrival_time_ms, uint32_t timestamp) const { if (!burst_grouping_) { return false; } assert(current_timestamp_group_.complete_time_ms >= 0); // 与当前包组最后一个包的到达时间差 int64_t arrival_time_delta_ms = arrival_time_ms - current_timestamp_group_.complete_time_ms; // 与当前包组最后一个包的发送时间差 uint32_t timestamp_diff = timestamp - current_timestamp_group_.timestamp; int64_t ts_delta_ms = timestamp_to_ms_coeff_ * timestamp_diff + 0.5; if (ts_delta_ms == 0) return true; // 传输时延 int propagation_delta_ms = arrival_time_delta_ms - ts_delta_ms; // 传输时延变化小于0 // 与当前包组最后一个包的到达时间差不大于kBurstDeltaThresholdMs(5ms) // 与当前包组第一个包的到达时间差小于kMaxBurstDurationMs(100ms) if (propagation_delta_ms < 0 && arrival_time_delta_ms <= kBurstDeltaThresholdMs && arrival_time_ms - current_timestamp_group_.first_arrival_ms < kMaxBurstDurationMs) return true; return false; } |
总结
本篇文章主要讲了到达时间模型以及在WebRTC中的源码实现:InterArrival
。后面文章我们将研究下trendline滤波器。前面计算得到的相关差值我们将传递给trendline
滤波器,进行网络拥塞情况判断。
文章评论
请问这个值是怎么得出的,为什么不是直接写5ms,而是用了下面这个公式
(kTimestampGroupLengthMs << 26) / 1000
@holden 这个是内部不同时间单位的转换,kTimestampGroupLengthMs经过这个公式转换后就变为kTimestampGroupLengthTicks,你看下Ms与Ticks后缀,就明白具体单位含义了,一个是以毫秒为单位,一个是以采样频率为单位。
@Jeff 再请教一下,我再win10上编译好了webRTC的文件,该怎么调式goog_cc的那个模块,我试着启了example中的peerconnection.exe,但是好像没调到congestion controller 模块
@Holden WebRTC本身是P2P传输,所以你还得跑P2P服务器,example也带有stun,turn服务器,因为是P2P,所以得跑两个peerconnection.exe。嫌麻烦,可以调试自带的gcc单元测试代码。
@Jeff 请问怎么才能调试自带的gcc测试代码。。。我现在只能调试example里面的代码,其他路径下的文件都没有设为启动项这个选项
@Holden 我是自己在WebRTC里新建一个工程,把单元测试代码拷贝过来,然后调试
大佬可不可以解释下这个公式:
uint32_t send_time_24bits =
static_cast(
((static_cast(info.send_time_ms) << kAbsSendTimeFraction) +
500) /
1000) &
0x00FFFFFF;
--------------------------------
我看文档上NTP转ABS的公式是:abs_send_time_24 = (ntp_timestamp_64 » 14) & 0x00ffffff
那开头的那个公式是怎么得出来的?
而且后18位是数据为,左移18是做什么用的?
+500/1000是在做向上圆整吗?
最新的算法不是不适用abs时间了吗,为啥代码中还有使用?
问题比较多,望大佬答疑解惑!
@Holden 抱歉,这个新转换公式我也无法解释,查了下,没看到相关文档说明。
@Holden 参考这个:https://blog.jianchihu.net/webrtc-research-dbb-abstime-trans.html
根据发送时间来分组,如果没有remb
不是就没有abs time吗?那怎么根据发送时间来分组
@nativertc 建议先好好了解下WebRTC现在代码在哪一端计算这个。
@Jeff 我应该找到了,rfc5450, toffset,我想要的应该是这个。虽然还没有看的太明白
不是接收端计算到达包组的到达时间差?
@nativertc 计算都在发送端
终于搞懂了,看了下源码,和toffset这个没有关系。是通过rtcp反馈然后在发送端计算的,abs-send-time是记录在history里面的