WebRTC GCC翻译和理解

A Google Congestion Control Algorithm for Real-Time Communication

GCC算法分析综述

本文为翻译和理解。这里是原文链接

1. 简介

总所周知,在各个应用程序共享网络资源时,拥塞控制就显得十分重要。拥塞控制对于实时流媒体来说,确实有一些挑战,这是由于:

  1. 媒体流通常会被编码为某种格式(某种分辨率和FPS),这使得它在网络带宽动态变化时,很难快速适应。

  2. 当网络拥塞发生时,共享网络的其他程序有可能不会减小带宽。

  3. 编码对于丢包十分敏感,然而,媒体流对于实时性的要求会抑制一些重传包的修复作用。(对于一些超时的包,就不会重传了)

1.1 记号标注

  • X_bar 表示随机变量X。X是一个向量

  • X_hat 表示X的估计值

  • X(i) 表示X向量的第i个次取值

  • [x y z] 表示x,y,z的组成的向量

  • X_bar^T X向量的转置

  • E{X} X的数学期望

2. 系统模型

  • RTCP receiver at RTP sender 它会收到一些 receiver report、REMB 和 transport-wide 这些 RTCP 反馈消息,这些消息将会传递给发送端的控制器。

  • Loss-based controller 以测量到的丢包率、rtt 和 REMB 消息做为输入,计算出目标发送带宽。

  • Delay-based controller 以包的到达时间(这个可以在 RTP 的接收端或发送端收到的反馈信息中得知)做为输入,计算出最大 bitrate,并传递给 loss-based controller。 简而言之,Loss-based controller 和 Delay-based controller 共同实现了拥塞控制算法。

3. 反馈信息

有两种方式实现这种算法。其中一种方式是,两个 controller 均运行在发送端,另外一种方式是 delay-based controller 运行在接收端,loss-based controller 运行在发送端。

第一种实现方式是通过逐包的反馈信息实现的。RTP 的 receiver 对于每个接收到的包,会记录到达时间和 transport-wide 序号,它将这些信息反馈给发送端。建议反馈的间隔是收到一个视频帧,又或者至少每30ms发送一次(当该流为混合流时)。如果反馈的包预算有限,则这个间隔可以增加到100ms。 发送端将会将反馈的数据(序号和到达时间)和发送时间配对,将这些时间戳的配对传递给 delay-based controller。依赖序列号,也可以计算出丢包率。

第二种实现方式,在接收端的 delay-based controller 可以观测 arrival time 和接受包的大小。发送端应该使用 abs-send-time 拓展 RTP 头,来让接收端可以准确的计算延迟的变化。于是乎,这个位于接收端的 delay-based controller 可以计算出一个 bitrate,并通过 remb 反馈给发送端。发送端可以通过 rtcp report 获得丢包率。于是发送端的 loss-based controller 可以获得接收端计算出的 bitrate 和丢包率。它将会输出最终的 target bitrate。建议当接收端检测到拥塞的时候,立即发送 REMB(这会使得发送端的 delay-based controller 在真正发生丢包之前,可以快速降低带宽),在平时至少每秒钟都发送一个。

4. Delay-based control

Delay-base control算法将会分解为以下三部分:到达时间滤波器、过渡使用检测器和速率控制器。

4.1 到达时间模型

这一节描述了一个自适应的滤波器,它可以基于收到的包的时间来不断更新网络参数的估计值。另 t(i) - t(i-1) 为两个包或两组包的到达时间间隔。 T(i) - T(i-1) 为两个包或两组包的离开时间间隔。最终,两者之间的延迟变化 d(i) = t(i) - t(i-1) - (T(i) - T(i-1)) 在接收端,我们可以观测到多组的到达包。我们将一组包定义为:

  • 在一个 burst_time 间隔内的一系列的包构成一个组。建议 burst_time 为5ms。

  • 另外,任意到达时间间隔小于 burst_time 并且组间延迟差 d(i)< 0 的组都被考虑做为当前组的一部分。将这些数据包含在组中的原因是为了更好地处理延迟瞬变,这些瞬变是数据包由于一些拥塞无关的原因而排队。已经观察到在许多无线网络中会发生这种情况。例如:某些 wifi 设备的转发模式是,在某个固定时间片内才有机会转发数据包,这个时间片的间隔可能长达100ms,造成的结果是100ms的数据包堆积,并在发送时形成 burst,这个 busrt 内的所有数据包就会被视为一组。如果形成了这样的数据堆积,由于队列后面的分片等待时间小于队列前面的数据。所以组间延迟差确实是降低的,所以我们要判断 d(i) < 0。在这种情况下,如果我们错误地没有将这些组合并,则会以为是队列长度减小而导致 d(i) < 0。

连续的组的离开时间差为 T(i) - T(i-1),T(i) 是当前组中的最后一个包的离开时间。所有乱序的包都会被到达时间模型所忽略。 每个组都会有一个到达时间 t(i),如果一组包是被推迟了,则 t(i) - t(i-1) > T(i) - T(i-1)。这表明到达时间间隔大于离开时间间隔。 发送一组包的时间粗略可以计算为:ts = L/C。L 为这组包的总大小,C 为 信道容量(考虑信噪比的物理链路理论传输速率)。 这样我们就可以对延迟差 d(i) 进行建模: d(i) = L(i)/C(i) - L(i-1)/C(i-1) + w(i) = (L(i) - L(i-1))/C(i) + w(i) = dL(i)/C(i) + w(i) 这里的 w(i) 为随机过程 W 的样本。W 是容量 C(i)、当前交叉流量(也就是和其他流竞争的流量)和当前的发送速率的函数。C 被建模为一个常量,因为我们认为它比这个模型的其他参数的变化慢得多。W 建模为高斯白噪声。当我们过渡使用这条通道,我们认为 w(i) 会增加,如果网络通道中的队列正在清空的,则 w(i) 的期望会减小。否则,w(i) 的期望应该是0。(也就是每组包的延迟差只与 dL(i) 有关) 由于高斯噪声分解之后依然是高斯噪声。我们将 w(i) 分解为 m(i) 和 v(i)。其中 v(i) 表示期望为0的噪声。于是我们得到 等式一: d(i) = dL(i)/C(i) + m(i) + v(i) 这个公式是我们的基础模型,在这个模型中,我们将大的组比小的组需要更多的时间来穿过这个通道这个因素考虑进去了(也就是 dL(i)/C(i))。噪声代表网络的延迟抖动和其他未被捕捉的延迟的影响因素。

4.2 到达时间滤波器

我们用 d(i) 和 dL(i) 估计出 C(i) 和 m(i),并且使用这些预估值来判断网络是否发生了拥塞。这些参数可以用任何的自适应滤波器来计算。在这里我们使用卡尔曼滤波器。 我们使 theta_bar(i) = [1/C(i) m(i)]^T(这是个列向量),称它为时间 i 的状态(卡尔曼滤波中的Xi)。于此同时,我们建立预测模型,theta_bar(i+1) = theta_bar(i) + u_bar(i) (也就是状态转移方程为单位矩阵)。u_bar(i) 状态转移噪声,我们把它建模为高斯模型,均值为0,并拥有协方差 Q(i) = E{u_bar(i) * u_bar(i)^T}。建议 Q(i) 为主对角矩阵 diag(Q(i)) = [10^-13 10^-3]^T(意思是1/C(i)和u_bar(i))的状态转移的协方差为0,方差为10^-13和10^-3,我们很信任这个状态转移模型)。根据等式一我们可以得知:

d(i) = h_bar(i)^T * theta_bar(i) + v(i)  
h_bar(i) = [dL(i)  1]^T

其中,v(i) 为 0 均值的高斯白噪声,方差为 var_v = sigma(v, i)^2 于是,卡尔曼滤波器可以递归的更新我们的估计值 theta_hat(i) = [1/C_hat(i) m_hat(i)]^T。 根据卡尔曼方程:

z(i) = d(i) - h_bar(i)^T * theta_hat(i-1)  
theta_hat(i) = theta_hat(i-1) + z(i) * k_bar(i)  
                       ( E(i-1) + Q(i) ) * h_bar(i)  
k_bar(i) = --------------------------------------------------------------- 
                var_v_hat(i) + h_bar(i)^T * (E(i-1) + Q(i)) * h_bar(i)  
E(i) = (I - k_bar(i) * h_bar(i)^T) * (E(i-1) + Q(i))

其中,E(i) 表示当前的估计 theta_hat(i) 的协方差,将会在下一次的迭代中使用。 E(i-1) + Q(i) 表示前一步的估计的协方差 + 当前的状态转移噪声,也就是当前预测值的协方差。 z(i) 表示观测值和预测值之差。

而状态转移方差v(i) = sigma_v(i)^2 是使用指数平滑滤波估计出来的,z(i)^2为当前测量和预测差值的平方。 var_v_hat(i) = max(beta * var_v_hat(i-1) + (1-beta) * z(i)^2, 1) 其中,beta = (1-chi)^(30/(1000 * f_max))。f_max = max{1/(T(j) - T(j-1))},j 为从 i-K+1 到 i,我们取最近收到的K个组中的最大值。chi是一个滤波系数,可以从0.001到0.1之间选择。如果我们假定 v(i) 在某些情况下是不准确的,所以我们需要增加一个异常点滤波器,如果 z(i) > 3sqrt(var_v_hat),这个滤波器将会以 3sqrt(var_v_hat) 更新,而不是 z(i)。例如,当发包的速率高于通道容量时,这些包将会排队。这将会导致测量值要大于预测值。 两组的离开时间间隔越大,则 T(j) - T(j-1) 越大,则beta越小,则越依赖当前值。

4.3 过度使用检测器

偏移 offset 的预估值 m(i) 可以做为到达时间滤波器的输出,它将会和门限值 gamma_1(i) 比较。如果预估值高于门限,则我们认为是over-use的。但仅仅这样是不够的。只有在至少 gamma_2 时间内,over-use 被持续检测到,我们才会确定网络确实被 over-use。 然而,当 m(i) < m(i-1) 时,即使上述条件均满足,over-use 也不会被确认。 同样,当 m(i) < -gamma_1(i) 时,under-use 被检测到。如果两者都没有,则处于 normal 状态。 门限值 gamma_1 对于算法的表现有着重要的作用。特别的,当使用一个固定的门限值的时候,这个算法将会被并发的TCP流饿死。我们只有不断的增加门限值 gamma_1 到一个足够大的值才能避免这种情况的发生。 大的门限值会使这个 delay-based 算法可以容忍较大的排队延迟,而小的门限值会使 over-use 检测器对偏移 offset 更加敏感,从而减小 delay-based 估计出来的可用带宽 A_hat(见4.4)。所以动态调节门限值 gamma_1 可以使得这个算法在大多数情况下有一个良好的表现,例如在和基于丢包的拥塞控制的流竞争的时候(例如TCP的Reno算法)。 由于这种原因,我们需要根据以下方程更新门限值 gamma_1(i): gamma_1(i) = gamma_1(i-1) + (t(i)-t(i-1)) * K(i) * (|m(i)|-gamma_1()),当 |m(i)| < gamma_1(i-1 )时,K(i)=K_d,否则K(i)=K_u。这里的基本原理是,当 |m(i)| 超过 [-gamma_1(i-1) gamma_1(i-1)]的范围时,我们应该增加 gamma_1。当 |m(i)| 会到这个范围后, gamma_1 应该减小。例如:当TCP流进入相同的通道时,它会导致 m(i) 增加,此时我们需要增加 gamma_1(i) 使得 over-use 信号不断生成,导致这个算法被饿死。另外当|m(i)| - gamma_1(i) > 15 时,gamma_1(i) 不应该更新。同样建议 gamma_1(i) 应该限制在 [6, 600] 范围内,应该太小的 gamma_1(i) 会导致 delay-based 检测器过于敏感。另外,当 m(i) 回到 [-gamma_1(i-1), gamma_1(i-1)]的范围时,gamma_1(i) 应该下降,从而降低排队延迟(使算法对延迟变化更敏感一些)。 建议 选择 K_u > K_d,使得 gamma_1 上升的速度高于下降的。从而防止饥饿和增强协议间的公平性。建议 gamma_1(0),gamma_2,K_u,K_d分别取值 12.5ms,10ms,0.01 和 0.00018。

4.4 速率控制

速率控制分为 delay-based 带宽预测,和 loss-based 带宽预测。当没有检测到拥塞的时候,二者都会增加可用带宽的估计A_hat,确保我们最终达到可用带宽并且检测到拥塞。 只要 over-use 被检测到,基于延迟的可以用带宽会下降。 在本文中,我们假定速率控制子系统以固定周期执行。速率控制子系统拥有三种状态:Increase,Decrease,和Hold。其中 Hold 状态是为了在 进入 Increase 状态之前,排空已有的队列。以下是状态转移方式:

+----+--------+-----------+------------+--------+
   |     \ State |   Hold    |  Increase  |Decrease|
   |      \      |           |            |        |
   | Signal\     |           |            |        |
   +--------+----+-----------+------------+--------+
   |  Over-use   | Decrease  |  Decrease  |        |
   +-------------+-----------+------------+--------+
   |  Normal     | Increase  |            |  Hold  |
   +-------------+-----------+------------+--------+
   |  Under-use  |           |   Hold     |  Hold  |
   +-------------+-----------+------------+--------+

这个子系统以 Increase 状态启动,直到检测到 over-use 或者 under-use 被检测到。在 Increase 状态下,是采用乘性增加或者线性增加,取决于当前的状态。 如果当前系统距离收敛还比较远,则采用乘性增加。反之,则使用线性增加以逼近收敛。我们假定,如果我们之前在 Decrease 状态,且当前的输入的 bitrate 的值 R_hat(i) 逼近 在 Decrease 状态下 输入的 bitrate 的平均值(在队列排空之后,这个 bitrate 如果和排空过程中的 bitrate 差不多,则这个 bitrate 近乎达到了通道的最大的 bitrate。),则我们离收敛不远了。这个不远,我们定义为3个标准差之内。我们建议使用0.95作为指数平滑系数来测量输入的 bitrate 的平均值和标准差,因为我们认为它可以覆盖在 Decrease 状态下的多个场景。 如果 R_hat(i) 增加并超过平均最大 bitrate(在Hold状态测量的) 的三个标准差。我们假定当前的拥塞等级已经发生了变化,于是我们重置平均最大 bitrate 并回到乘性增加状态。 R_hat(i) 是 delay-based controller 在 T 秒内的测量的输入 bitrate: R_hat(i) = 1/T * sum(L(j)) j 从1到 N(i) N(i) 是在过去 T 秒内收到的数据包的数量,L(i) 是 数据包 j 的 payload size。建议 T 在0.5到1秒之间。 在乘性增加期间,这个估计值 A_hat(i) 大概每秒增加 8%。

eta = 1.08^min(time_since_last_update_ms / 1000, 1.0)  
A_hat(i) = eta * A_hat(i-1)

在线性增加期间,每过 response_time 的间隔时间,这个估计值 A_hat(i-1) 会增加大约半个数据包的大小。

response_time_ms = 100 + rtt_ms  
beta = 0.5 * min(time_since_last_update_ms / response_time_ms, 1.0)  
A_hat(i) = A_hat(i-1) + max(1000, beta * expected_packet_size_bits)

我们假设帧率是30的时候:

bits_per_frame = A_hat(i-1) / 30 
packets_per_frame = ceil(bits_per_frame / (1200 * 8)) (相当于bytes_per_frame/mtu)  
avg_packet_size_bits = bits_per_frame / packets_per_frame

因为这个算法依赖 over-use 这条通道来判断当前的可用带宽估计值,我们必须确保我们的估计值不会远离发送端实际的发送码率。所以,如果发送端无法产生这个拥塞控制器要求的 bitrate,我们需要将可用带宽估计值限制到一个范围:A_hat(i) < 1.5 * R_hat(i)。 当一个 over-use 被探测到,这个系统会转换为 Decrease 状态,可用带宽会降低到:A_hat(i) = alpha * R_hat(i)。 alpha在范围[0.8 0.95]之间,建议为0.85 当我们检测到 under-use 信号,我们检测到网络路径上的队列正在清空,这表明我们的可用带宽估计值 A_hat 低于实际的可用带宽。(但是此时输入的 bitrate 正处于带宽最大值)。此时,系统将要进入 Hold 状态,此时接收端的可用带宽估计值会维持不变,直到队列长度稳定。这是一种控制低延迟的方法。延迟的减小可能是由于这里的带宽估计值减小,也有可能由于一些交叉网络的连接减少了。 我们建议每个 response_time 间隔都要更新一个 A_hat(i)

4.5 参数设定

+------------+-------------------------------------+----------------+
   | Parameter  | Description                         | RECOMMENDED    |
   |            |                                     | Value          |
   +------------+-------------------------------------+----------------+
   | burst_time | Time limit in milliseconds between  | 5 ms           |
   |            | packet bursts which identifies a    |                |
   |            | group                               |                |
   | Q          | State noise covariance matrix       | diag(Q(i)) =   |
   |            |                                     | [10^-13        |
   |            |                                     | 10^-3]^T       |
   | E(0)       | Initial value of the  system error  | diag(E(0)) =   |
   |            | covariance                          | [100 0.1]^T    |
   | chi        | Coefficient used  for the measured  | [0.1, 0.001]   |
   |            | noise variance                      |                |
   | gamma_1(0) | Initial value for the adaptive      | 12.5 ms        |
   |            | threshold                           |                |
   | gamma_2    | Time required to trigger an overuse | 10 ms          |
   |            | signal                              |                |
   | K_u        | Coefficient for the adaptive        | 0.01           |
   |            | threshold                           |                |
   | K_d        | Coefficient for the adaptive        | 0.00018        |
   |            | threshold                           |                |
   | T          | Time window for measuring the       | [0.5, 1] s     |
   |            | received bitrate                    |                |
   | alpha      | Decrease rate factor                | 0.85           |
   +------------+-------------------------------------+----------------+

5. Loss-based control

这个拥塞控制算法的第二部分基于 rtt,丢包率和可用带宽估计值 A_hat(从 delay-based controller 中获得的估计值)。我们把 loss-based controller 计算出的可用带宽估计值称为 As_hat。 A_hat 的准确性依赖于路径的队列长度足够大。如果队列长度很短,则 over-use 状态只能通过丢包得知,这样 delay-based controller 便不可用了。 丢包率控制器应该在收到每个反馈信息(收到 rtcp)的时候运行一次。

  • 如果丢包率控制在 2-10%,则 As_hat(i) 保持不变。

  • 如果丢包率超过 10%,则我们计算 As_hat(i) = As_hat(i-1)(1-0.5p)。其中p是丢包率。

  • 如果丢包率小于 2%,则 As_hat(i) 应该增加,As_hat(i) = 1.05*As_hat(i-1)。 这个新的带宽预测值的下界是 TFRC,这是一种 TCP 友好的速率控制公式(这个公式产生的带宽相对与 TCP 来说相当公平,且速率较为平稳)。上界是A_hat(i)。上界和下界的公式如下:

                                       8*s
  As_hat(i) >= ---------------------------------------------------------
               R*sqrt(2*b*p/3) + (t_RTO*(3*sqrt(3*b*p/8)*p*(1+32*p^2)))
​
  As_hat(i) <= A_hat(i)

公式中的 b 表示单个 tcp 的确认的包的数量。t_RTO 是TCP的重传超时时间,单位是秒,我们在这里设它为 4*R。s 是 平均每个包的字节数。R 是 rtt 时间,单位是秒。(我们将上述公式的分子乘8,用于将字节数转换为比特数) 简而言之:loss-based 的估计值不会大于 delay-based 估计值,也不会小于 TFRC 计算公式。(除非 delay-based 的估计值小于 TFRC) 一开始当传输通道 over-use,它应该只有很小的丢包率,这时我们由于发现丢包率没有达到 10%,所以并不会降低发送码率。于是,信道由于不断堆积,很快就会超过10%的丢包率(由于前面的数据包已经将队列填满,后续的数据包到来的速度又大于队列消耗的速度)。如果这个丢包率的产生和拥塞无关(那么它将上升到10%的丢包率),那么我们不应该对它进行处理。

6. 交互性操作考虑

如果接受端没有实现 transport-wide-cc,那么发送端应该关注接受端发出的 RTCP,使用其中的丢包率和 rtt 作为 loss-based controller 的输入,并禁止掉本端的 delay-based controller。

7.实现经验

这个算法在 WebRTC M23 实现,并运用在 Google Hangouts了。在算法的实施过程中,一些拥塞或 WIFI 网络等问题的发现促进了算法的进步。该算法也在视频会议中运用了,我们测试了会议服务器和多个终端之间的拥塞控制。

原文 WebRTC GCC翻译和理解 - 知乎

★文末名片可以免费领取音视频开发学习资料,内容包括(FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)以及音视频学习路线图等等。

见下方!↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值