7、速率控制器(Rate Controller):AIMD策略、增加与减少因子、码率更新公式、平滑处理
好,咱们接着聊。前面几章我们把带宽探测、丢包检测、延迟梯度这些“感知层”的东西讲透了。现在终于到了核心决策层——速率控制器。
说白了,前面那些模块都是“眼睛”和“耳朵”,负责收集网络状态。而速率控制器就是“大脑”,它要根据这些信息,决定下一秒该用多大的码率去发送数据。
我个人习惯把速率控制器比作一个“油门踏板”。路况好(网络通畅),我就踩深一点;路况差(拥塞了),我就松一松。但这个“踩”和“松”的力度、时机,就是今天要聊的AIMD策略。
7.1 AIMD策略:加性增、乘性减
AIMD,全称 Additive Increase Multiplicative Decrease。翻译过来就是“加性增、乘性减”。
这名字起得挺直白,但背后是TCP拥塞控制几十年的智慧结晶。WebRTC里的GCC(Google Congestion Control)算法,核心骨架就是AIMD。
为什么用AIMD?你想想看,网络拥塞控制本质上是一个“探测-反馈-调整”的闭环。我们永远不知道网络的真实容量是多少,只能通过“增加码率直到出现拥塞”这种方式来逼近上限。
加性增的意思是:当网络状态良好时,码率线性增长。每次增加一个固定的步长,比如每秒增加几十kbps。这样增长是缓慢且保守的,不会一下子把网络冲垮。
乘性减的意思是:当检测到拥塞时,码率按比例下降。比如乘以0.85或0.5。下降幅度很大,目的是快速从拥塞状态中退出来,给网络一个喘息的机会。
我在项目中遇到过一种情况:有同事觉得“乘性减”太粗暴,改成“减性减”,每次只降固定值。结果呢?一旦遇到突发拥塞,码率降得太慢,缓冲区迅速填满,延迟飙升,最后视频卡成PPT。嗯,从那以后,我再也不敢乱改乘性减了。
7.2 增加因子与减少因子
既然是AIMD,那“增加多少”和“减少多少”就是两个关键参数。在WebRTC的GCC算法中,这两个因子有明确的定义。
增加因子(Additive Increase Factor)
GCC里,码率增加不是简单的“每秒加X kbps”,而是跟当前码率有关。具体公式是:
// 每收到一个拥塞反馈周期(通常200ms),码率增加:
A = alpha * (target_bitrate / frame_size) * (1 / frame_rate)
// 简化后,实际代码中常用:
A = max(1000, 0.08 * target_bitrate) // 单位:bps
这个公式看着有点绕,我解释一下:
- alpha:通常取0.08~0.1,控制增长速率
- target_bitrate:当前目标码率
- frame_size 和 frame_rate:视频帧大小和帧率,用于归一化
说白了,码率越高,每次增加的绝对值也越大。这很合理——你跑在5Mbps时,每次加50kbps感觉不大;但你跑在100kbps时,加50kbps就是50%的增长,太猛了。
减少因子(Multiplicative Decrease Factor)
减少因子就简单多了。GCC中,当检测到拥塞时,码率直接乘以一个小于1的系数:
// 拥塞时,码率更新:
new_bitrate = current_bitrate * beta
// 其中 beta 通常取 0.85 或 0.5
这里有两个流派:
| 流派 | beta值 | 特点 |
|---|---|---|
| 保守派 | 0.85 | 下降幅度小,适合轻微拥塞 |
| 激进派 | 0.5 | 下降幅度大,快速缓解拥塞 |
WebRTC默认用的是0.85。为什么?因为实时通信对码率波动很敏感,降太多会导致视频质量骤降,用户能明显感觉到画面变糊。0.85是一个折中值。
7.3 码率更新公式
好了,有了增加因子和减少因子,我们来看看完整的码率更新公式。GCC的速率控制器维护一个目标码率 A,每个周期(通常100~200ms)更新一次:
// 伪代码:GCC码率更新逻辑
if (overuse_detected) {
// 拥塞:乘性减
A = A * beta;
} else if (underuse_detected) {
// 带宽未充分利用:加性增
A = A + alpha * (A / frame_size) * (1 / frame_rate);
} else {
// 正常状态:保持或微调
// 通常不做大幅调整
}
这个公式看起来简单,但实际工程中要考虑很多边界情况:
- 码率下限:不能低于某个最小值(比如30kbps),否则视频完全不可用
- 码率上限:不能超过发送端编码能力或接收端解码能力
- 码率变化率限制:单次变化不能超过50%,防止剧烈抖动
我记得有一次调试,发现码率在1Mbps和500kbps之间来回震荡。查了半天,原来是增加因子和减少因子不匹配——增得太快,减得太慢,形成了振荡。后来把alpha调小了一点,beta也调小了一点,振荡就消失了。
7.4 平滑处理
直接使用AIMD算出来的码率,会有个问题:锯齿状波动。
你想想看,每200ms更新一次码率,每次更新要么加一点,要么减一大块。如果网络状态频繁切换,码率就会像锯齿一样上下跳动。这种跳动反映到视频上,就是画面一会清晰一会模糊,用户体验极差。
所以,我们需要平滑处理。
WebRTC里常用的平滑手段有两种:
- 指数移动平均(EMA):对计算出的目标码率做平滑,公式是
smoothed_rate = (1 - k) * smoothed_rate + k * target_rate,其中k是平滑系数,通常取0.1~0.3。 - 码率变化率限制:限制单次码率变化的最大百分比,比如不超过20%。
我个人更倾向于第二种。为什么?因为EMA虽然平滑,但会引入延迟——码率变化要经过好几个周期才能完全反映出来。在实时通信中,延迟就是敌人。
码率变化率限制的实现很简单:
// 限制码率变化率不超过20%
double max_change = current_bitrate * 0.2;
double delta = new_bitrate - current_bitrate;
if (delta > max_change) {
delta = max_change;
} else if (delta < -max_change) {
delta = -max_change;
}
final_bitrate = current_bitrate + delta;
这样既避免了剧烈波动,又保证了码率能快速响应网络变化。
7.5 知识体系图
下面这张图总结了速率控制器的核心逻辑,从输入到输出,一目了然:
从图中可以看到,速率控制器接收三个输入:带宽探测、丢包检测、延迟梯度。经过AIMD决策引擎计算目标码率,再经过平滑处理,最终输出一个稳定的目标码率给视频编码器。
每个环节都很重要,但平滑处理往往是容易被忽视的一环。很多初学者把AIMD公式实现出来就跑,结果码率波动大得吓人。记住:平滑不是可选项,是必选项。
好了,这一章的内容就到这里。速率控制器是WebRTC拥塞控制的核心,AIMD策略、增加减少因子、码率更新公式、平滑处理,这四个知识点环环相扣。理解了它们,你就掌握了GCC算法的精髓。
公众号:蓝海资料掘金营,微信deep3321