Sender Side Bandwidth Estimation 发送方带宽预估。Sender Side BWE 是新方案,利用的是 RTCP 中的 TransportCC 协议。
Receiver Estimated Maximum Bitrate 接收端预估最大码率。REMB 是旧方案,利用的是 RTCP 中的 REMB 协议。
WebRTC 中的拥塞控制算法有三种:GCC、BBR、PCC。GCC 是 WebRTC 的默认算法,GCC 包含基于 丢包 和 延迟 两种情况的算法。以下所有内容都是 GCC 中的。
拥塞控制的源码在:目录 src/modules/congestion_controller/ 下
GCC 全称 Google Congestion Control。
GCC 由于新旧版本兼容原因 有三种 实现的方式。
也就是新的比旧的好在哪?
Google 给的官方解释是:> https://groups.google.com/g/discuss-webrtc/c/ZyKcu3E9XgA/m/hF0saddeLgAJ
当然更实际的好处是:新的方案在应对峰值流量的能力上比旧的好。
基本思想:丢包率小,提高码率;丢包率大,降低码率;丢包率适中,不进行调整。
As(tk)={As(tk−1)(1−0.5fl(tk))fl(tk)>0.11.05(As(tk−1))fl(tk)<0.02As(tk−1)otherwiseA_s(t_k) = \begin{cases} A_s(t_{k-1})(1-0.5f_l(t_k))&f_l(t_k)>0.1\\ 1.05(A_s(t_{k-1}))&f_l(t_k)<0.02\\ A_s(t_{k-1})&otherwise\\ \end{cases}As(tk)=⎩⎪⎨⎪⎧As(tk−1)(1−0.5fl(tk))1.05(As(tk−1))As(tk−1)fl(tk)>0.1fl(tk)<0.02otherwise
As(tk)A_s(t_k)As(tk) 即为 tkt_ktk 时刻的带宽估计值,fl(tk)f_l(t_k)fl(tk) 即为 tkt_ktk 时刻的丢包率。
基本思想:以时间为x轴,延迟梯度为y轴。对其中的点做一元线性拟合求斜率。斜率越大说明网络越拥塞。
一元线性方程:y=ax+by = ax + by=ax+b
求线性回归方程系数a:a=∑i=1n(xi−xˉ)(yi−yˉ)∑i=1n(xi−xˉ)2a=\frac{\sum_{i=1}^{n}{(x_i-\bar{x})(y_i-\bar{y})}}{\sum_{i=1}^{n}{(x_i-\bar{x})^{2}}}a=∑i=1n(xi−xˉ)2∑i=1n(xi−xˉ)(yi−yˉ)
一帧视频往往是由多个 RTP 包发送的,所以首先将 RTP 的数据按照 5ms 分组,之后对相邻的两组数据包进行计算。
5ms 是 GCC 草案中提出的:The Pacer sends a group of packets to the network every burst_time interval. RECOMMENDED value for burst_time is 5 ms.
理论上,WebRTC 是按照包组为单位进行计算的。但为理解的方便,后面将包组统一理解为一个数据包。
计算内容包括:
通过上述的三个值,可以计算:
一个包的延迟:delayi=△arrival−△timestampdelay_i = △arrival - △timestampdelayi=△arrival−△timestamp
每个包累计的延迟:accdelayi=∑delay0+delay1+...+delayiacc_{delay_i} = \sum delay_0 + delay_1 + ... + delay_iaccdelayi=∑delay0+delay1+...+delayi
WebRTC 中对累计的延迟做了平滑处理,也就是取了之前的累计延迟的 90%,取了当前包累计延迟的 10% ,从而减少了变化幅度。smodelayi=α∗smodelayi−1+(1−α)∗accdelayismo_{delay_i} = α * smo_{delay_{i-1}} + (1-α) * acc_{delay_i}smodelayi=α∗smodelayi−1+(1−α)∗accdelayi 这里 α=0.9α = 0.9α=0.9
现在有了平滑后的延迟梯度,有了每个包的到达时间。那么时间为 x 轴,延迟梯度为 y 轴。
WebRTC 中使用最小二乘算法计算出了 aaa 的值。具体计算过程,并不建议深入阅读,可能会把自己绕进去。
阿里云(WebRTC 拥塞控制 | Trendline 滤波器) 个人觉得算法这部分将的比别的好 https://developer.aliyun.com/article/781509
剩余的公式如下,最好别看:
对 包的累计延迟 和 包的平滑延迟 求平均:
xi=∑delay0+delay1+...+delayiix_i = \frac{\sum delay_0 + delay_1 + ... + delay_i}{i}xi=i∑delay0+delay1+...+delayi yi=∑smodelay0+smodelay1+...+smodelayiiy_i = \frac{\sum smo_{delay_0} + smo_{delay_1} + ... + smo_{delay_i}}{i}yi=i∑smodelay0+smodelay1+...+smodelayi
每个包组的传输时间为:transi=ti−first_arrivalitrans_i = t_i - first\_arrival_itransi=ti−first_arrivali
趋势斜率分子:numeratori=∑k=0i(transk−xk)∗(smodelayk−yk)numerator_i = \sum\limits_{k=0}^i(trans_k - x_k) * (smo_{delay_k} - y_k)numeratori=k=0∑i(transk−xk)∗(smodelayk−yk)
趋势斜率分母:denoinatori=∑k=0i(transk−xk)2denoinator_i = \sum\limits_{k=0}^i(trans_k - x_k)^2denoinatori=k=0∑i(transk−xk)2
趋势斜率为:trendlinei=numeratoridenoinatoritrendline_i = \frac{numerator_i}{denoinator_i}trendlinei=denoinatorinumeratori
上述步骤已经计算出斜率 aaa ,过载检测器就会利用 aaa 与 阈值 γγγ 进行比较,从而决策当前网络所处状态。
由于实际计算出的 aaa 非常小,所以 WebRTC 对其进行了放大,会用 a∗包组数量∗增益系数a * 包组数量 * 增益系数a∗包组数量∗增益系数 。
而 阈值 也是需要动态计算的,阈值计算公式:γi=γi−1+Δti∗ki∗(∣mi∣−γi−1)γ_i = γ_{i-1} + Δt_i * k_i * (|m_i| - γ_{i-1})γi=γi−1+Δti∗ki∗(∣mi∣−γi−1)
ΔtiΔt_iΔti 表示 距离上一次更新阈值的时间。kik_iki 表示 一个系数。mim_imi 表示 上面说的被放大后的 aaa 。
kik_iki 的取值规则如下:ki={kd=0.039∣mi∣<γi−1ku=0.0087otherwisek_i = \begin{cases} k_d=0.039&|m_i|<γ_{i-1}\\ k_u=0.0087&otherwise\\ \end{cases}ki={kd=0.039ku=0.0087∣mi∣<γi−1otherwise kdk_dkd 与 kuk_uku 分别决定阈值增加以及减小的速度。
WebRTC 将当前网络所处状态分为三个。
AIMD 的全称是 Additive Increase Multiplicative Decrease,意思是:和式增加,积式减少。直白点就是:增加的时候用加法,减少的时候用乘法。增加的时候慢一点,降低的时候快一点。
但是,AIMD 是 TCP 底层的码率调节概念,WebRTC 没有完全照搬,而是有自己一套算法。
该模块同样也维护了一个状态机:码流控制状态机。
保存当前码流改变的状态:Decrease 正在降低码率,Hold 正在保持码率,Increase 正在增加码率。
计算出当前网络状态后,根据码流控制器状态机,按照 和式增加,积式减少 的原则,估算出下一时刻发送端应该发送码流的大小。
Ar(ti)={αAr(ti−1)α=1.08σ=IncreaseβRr(ti)β=0.85σ=DecreaseAr(ti−1)σ=HoldA_r(t_i) = \begin{cases} αA_r(t_{i-1})&α = 1.08 \ σ=Increase\\ βR_r(t_i)&β=0.85 \ σ=Decrease\\ A_r(t_{i-1})&σ=Hold\\ \end{cases}Ar(ti)=⎩⎪⎨⎪⎧αAr(ti−1)βRr(ti)Ar(ti−1)α=1.08 σ=Increaseβ=0.85 σ=Decreaseσ=Hold
当前是 Increase 状态,如果吞吐量 RrR_rRr 和 链路容量(历史吞吐量的指数平滑)相差较大,则对当前码率(上次更新的码率)使用乘性增加;如果相差较小,则使用加性增加。
当前是 Decrease 状态,直接将当前吞吐量 RrR_rRr * 0.85 作为新码率,如果该码率可能仍大于上一个调整后的码率,则使用链路容量 RrR_rRr * 0.85 作为新码率。
据此,得到基于延时预估出来的码率。