3、GCC算法原理(下):基于丢包的拥塞检测机制、AIMD码率调整策略、GCC与TCC的对比
上一章我们聊了GCC的延迟梯度检测,说白了就是通过包到达时间的抖动来判断网络是不是快堵了。这一章我们来看另一半——基于丢包的拥塞检测。这两条腿走路,GCC才算完整。
我个人习惯把GCC的拥塞控制比作开车:延迟检测是看前方是不是缓行,丢包检测则是看有没有追尾。两个信号一起用,才能安全又高效地跑在码率这条高速上。
3.1 基于丢包的拥塞检测机制
丢包检测的逻辑其实很朴素:如果网络开始丢包,说明链路已经过载了。但WebRTC里的实现细节,比你想的要复杂一些。
3.1.1 丢包率计算
GCC不会等收到RTCP RR(Receiver Report)才反应。它用的是实时丢包率,基于RTP序列号连续性来判断。
具体做法是这样的:
- 接收端每收到一个RTP包,检查序列号是否连续
- 如果跳号了,说明中间有包丢了
- 统计一个窗口内的丢包数,除以总包数,得到丢包率
嗯,这里要注意:GCC的丢包率计算窗口不是固定的。我记得早期版本用的是1秒窗口,后来改成了基于帧的统计。为什么?因为视频流有帧的概念,一帧丢了几个包,整帧可能就废了。按帧统计更符合实际体验。
关键参数:
fraction_lost:RTCP RR中的8位字段,表示丢包率(0-255)cumulative_lost:累计丢包数,用于长期趋势判断- GCC内部还会维护一个滑动窗口丢包率,默认窗口大小是1000个包
3.1.2 丢包阈值与状态机
有了丢包率,怎么判断该不该降码率?GCC用了一个三状态机:
| 状态 | 丢包率范围 | 码率行为 |
|---|---|---|
| Increase | 0% - 2% | 尝试增加码率 |
| Hold | 2% - 10% | 保持当前码率 |
| Decrease | > 10% | 立即降低码率 |
你可能会问:为什么2%以下的丢包不降码率?我在项目中遇到过类似困惑。其实这是GCC的设计哲学——容忍小丢包,换取更高的吞吐量。2%以下的丢包,可能是瞬时抖动,不是真正的拥塞。如果一丢包就降码率,视频质量会频繁波动,用户体验反而更差。
避坑指南:我曾经在一个弱网环境下调试,发现GCC频繁降码率。后来一查,是丢包阈值设得太敏感了。默认的2%阈值其实是个经验值,如果你做的是实时互动场景(比如在线教育),可以适当放宽到3%-5%。但如果是直播场景,建议保持2%甚至更严,因为直播对延迟容忍度更高,但对画质要求也更高。
3.2 AIMD码率调整策略
AIMD,全称是Additive Increase Multiplicative Decrease。翻译过来就是:加性增、乘性减。这是TCP拥塞控制的老祖宗,GCC把它搬到了实时通信里。
3.2.1 加性增(AI)
当网络状态良好(无丢包、延迟稳定),GCC会尝试增加码率。增加的方式不是线性的,而是基于探测的。
具体公式:
// 每收到一个RTCP RR,如果处于Increase状态
A = min(alpha * packet_size, beta) // alpha通常为0.08,beta为4000bps
new_bitrate = current_bitrate + A
说白了,每次增加的量很小,而且有上限。为什么?因为实时通信对码率波动很敏感,一次加太多,万一网络扛不住,丢包率瞬间飙升,又得降回来。这种震荡对视频编码器很不友好。
3.2.2 乘性减(MD)
一旦检测到丢包率超过10%,GCC会立即触发乘性减:
// 丢包率 > 10% 时触发
new_bitrate = current_bitrate * (1 - 0.5 * (loss_rate - 0.1) / 0.8)
// 简化版:通常直接乘以0.85或0.9
我习惯用0.85这个系数。为什么不是0.5?因为降得太狠,视频质量会断崖式下跌。0.85是个比较温和的降速,既能快速响应拥塞,又不会让画面突然糊掉。
注意:AIMD的乘性减系数不能设得太小。我曾经在一个项目中设成0.5,结果网络一波动,码率直接从2Mbps掉到1Mbps,画面瞬间变马赛克。用户投诉说"视频卡成PPT"。后来改成0.85,问题就解决了。
3.2.3 AIMD与延迟检测的协同
GCC的厉害之处在于,它把延迟检测和丢包检测的结果融合在一起决策。具体规则:
- 延迟检测给出过载/正常/欠载信号
- 丢包检测给出Increase/Hold/Decrease状态
- 最终码率取两者中的较小值
举个例子:延迟检测说可以加码率,但丢包检测说已经2%丢包了,处于Hold状态。这时候GCC会听丢包检测的,保持码率不变。说白了,丢包检测是安全底线,延迟检测是性能优化。
3.3 GCC与TCC的对比
TCC(Transport-wide Congestion Control)是Google后来推出的新一代拥塞控制算法。它和GCC有什么区别?我直接说结论:TCC是GCC的升级版,但不是替代版。
| 对比维度 | GCC | TCC |
|---|---|---|
| 反馈粒度 | 基于RTCP RR,每帧或每N个包反馈一次 | 基于TransportFeedback,每个包都有反馈 |
| 延迟检测 | 接收端计算到达时间差 | 发送端计算,基于反馈包中的时间戳 |
| 丢包检测 | 基于序列号跳号 | 基于反馈包中的丢失位图 |
| 码率调整 | AIMD + 延迟梯度 | AIMD + 延迟梯度 + 带宽探测 |
| 适用场景 | 传统WebRTC,兼容性好 | 高码率、低延迟场景,如屏幕共享 |
3.3.1 TCC的核心改进
TCC最大的变化是:把拥塞控制的决策权从接收端移到了发送端。
在GCC中,接收端计算延迟梯度,然后通过RTCP告诉发送端。在TCC中,接收端只负责反馈每个包的到达时间,发送端自己算延迟梯度。这样做的好处是:
- 发送端可以更精确地控制码率调整时机
- 避免了RTCP反馈的延迟和精度损失
- 支持更细粒度的带宽探测
我记得有一次做屏幕共享优化,GCC在高码率下(比如4Mbps)表现不稳定,画面经常卡顿。换成TCC后,码率波动明显减小,画面流畅度提升了一个档次。说白了,TCC更适合高码率、低延迟的场景。
3.3.2 什么时候该用GCC,什么时候该用TCC?
我的建议是:
- 默认用GCC:兼容性好,大部分场景够用
- 屏幕共享或高码率场景用TCC:TCC的带宽探测更激进,能榨干网络带宽
- 弱网环境用GCC:GCC的丢包检测更保守,不容易过度降码率
一句话总结:GCC是"稳",TCC是"快"。稳不一定慢,快不一定稳。选哪个,看你的业务场景。
3.4 GCC拥塞控制核心流程
下面这张图是我自己整理的GCC拥塞控制核心流程,包含了延迟检测和丢包检测两条路径的协同工作:
从这张图可以看得很清楚:GCC的拥塞控制不是单一路径,而是双通道并行。左边是延迟检测,负责在好网络下稳步提升码率;右边是丢包检测,负责在坏网络下快速降码率。最终决策取两者中的较小值,说白了就是取最保守的那个。