14、带宽估计与自适应:WebRTC的带宽估计算法(GCC、REMB、Transport-CC)、码率自适应调整、拥塞控制策略
带宽估计,说白了就是让WebRTC自己判断「当前网络能跑多快」。我刚开始接触这个模块时,觉得它就是个简单的限速器。后来在项目中遇到一次严重的卡顿问题,排查了三天才发现是带宽估计过于激进,导致频繁丢包重传。嗯,从那以后我再也不敢小看这个模块了。
14.1 为什么需要带宽估计?
实时通信和普通下载不一样。下载文件时,网络慢就慢点,大不了多等几秒。但视频通话不行——你不可能让用户等网络好了再说话。WebRTC必须实时感知网络状态,动态调整发送速率。
核心目标就三个:
- 不浪费带宽:网络好的时候尽量高清
- 不压垮网络:网络差的时候主动降速
- 平滑过渡:变化不要太突兀,否则用户体验很差
关键认知:带宽估计不是「测」出来的,而是「猜」出来的。你永远无法精确知道网络还剩多少容量,只能通过丢包、延迟、抖动这些间接指标去推断。
14.2 三大带宽估计算法
WebRTC历史上出现过三种主流算法。我按出现顺序给你捋一遍。
14.2.1 GCC(Google Congestion Control)
这是最经典的算法,也是WebRTC的默认方案。它分两部分:
- 基于丢包:发送端统计丢包率,丢包多了就降码率
- 基于延迟:接收端计算延迟梯度,发现排队延迟增加就降速
我记得第一次看GCC源码时,被那个「延迟梯度」的计算绕晕了。说白了就是:如果相邻两个包的到达间隔比发送间隔大,说明网络开始排队了,这就是拥塞的前兆。
我的经验:GCC在WiFi环境下表现不错,但在4G/5G这种带宽波动剧烈的场景下,丢包检测往往滞后。我曾经在一个移动端项目中,把丢包阈值从10%调到了5%,效果反而更好——因为早点降速比晚降速更稳定。
14.2.2 REMB(Remote Estimated Maximum Bitrate)
REMB是接收端主导的算法。接收端根据收到的包计算可用带宽,然后通过RTCP反馈告诉发送端。
流程大概是:
- 接收端统计到达速率和延迟
- 用自适应阈值判断是否拥塞
- 生成REMB消息(包含估计带宽值)
- 发送端收到后调整编码器码率
REMB的好处是接收端能看到全局情况,但坏处是反馈有延迟。我遇到过一个问题:接收端刚发完REMB说「带宽充足」,下一秒网络就断了,发送端还按高速率发,结果大量丢包。
避坑指南:我曾经在弱网环境下发现REMB和GCC互相打架——发送端按GCC降了码率,接收端REMB又认为带宽够了让升回去,导致码率来回震荡。解决方案是只启用一种算法,或者做好优先级逻辑。
14.2.3 Transport-CC(Transport-wide Congestion Control)
这是目前WebRTC推荐的最新方案。它把拥塞控制从「每个流单独做」变成了「所有流统一做」。
核心变化:
- 每个RTP包都带一个传输序列号(transport-wide sequence number)
- 接收端反馈时,不是反馈每个流的状况,而是反馈整个传输通道的状况
- 发送端根据统一的反馈调整所有流的码率
你想想看,如果同时有视频流和音频流,GCC和REMB需要各自维护一套状态。Transport-CC就简单了——一个通道一个状态,所有流共享。
技术要点:Transport-CC使用传输级拥塞控制扩展,每个包增加一个字节的序列号。接收端通过RTCP反馈包到达情况,发送端用Kalman滤波器估计带宽。
14.3 码率自适应调整
带宽估计出来之后,怎么让编码器听话?这就是码率自适应要做的事。
WebRTC的码率控制分三层:
| 层级 | 控制对象 | 调整粒度 | 响应速度 |
|---|---|---|---|
| 编码器内部 | 量化参数(QP) | 帧级别 | 毫秒级 |
| 码率控制器 | 目标码率 | 秒级别 | 秒级 |
| 带宽估计器 | 发送速率 | RTT级别 | 百毫秒级 |
实际流程是这样的:
- 带宽估计器算出当前可用带宽(比如1Mbps)
- 码率控制器告诉编码器「目标码率设为900Kbps」(留10%余量)
- 编码器调整QP,让输出码率接近900Kbps
- 如果网络变差,带宽估计器降到500Kbps,码率控制器再通知编码器降
这里有个坑:编码器调整码率不是瞬时的。我遇到过编码器还在输出高码率,带宽估计器已经降了,导致中间几帧被丢弃。解决方案是让码率控制器提前预判,不要等带宽降了再通知编码器。
我的习惯:在码率控制器和编码器之间加一个平滑层。比如带宽从1Mbps降到500Kbps,不要一步到位,而是分三步降:800Kbps→650Kbps→500Kbps。这样编码器有足够时间调整,画面不会突然变糊。
14.4 拥塞控制策略
拥塞控制不是「发现拥塞再处理」,而是「预防拥塞发生」。WebRTC的策略可以总结为四个字:先降后升。
14.4.1 降速策略
当检测到拥塞时:
- 丢包触发:丢包率超过阈值(默认10%),立即降码率到当前值的0.85倍
- 延迟触发:延迟梯度持续增加,按比例降码率(通常每次降5-10%)
- 超时触发:如果连续几个RTT没有收到反馈,直接降到最低码率
降速要快,但不能太狠。我见过一个极端案例:丢包率刚超过阈值,码率直接从2Mbps降到200Kbps,画面瞬间变成马赛克。后来我们改成了分步降:先降到1.5Mbps,如果还丢包再降到1Mbps。
14.4.2 升速策略
升速要慢,这是铁律。为什么?因为网络变好可能是暂时的,升太快容易再次拥塞。
WebRTC的升速策略:
- 使用「加性增」模式:每个RTT增加约8%的码率
- 或者使用「基于探测」模式:发送探测包,看网络能不能承受更高码率
我个人更倾向于探测模式。因为加性增在带宽充裕时太慢,用户可能等很久才能看到高清画面。探测模式可以在几秒内找到新的带宽上限。
避坑指南:我曾经在探测模式中犯过一个错误——探测包发得太猛,直接把网络打崩了。后来我限制探测包不超过当前码率的1.2倍,并且只在连续3个RTT没有拥塞时才触发探测。
14.5 知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到从网络状态到最终码率调整的完整链路。
14.6 实际项目中的选择建议
说了这么多,到底该用哪个?我根据项目经验给你个参考:
- 标准WebRTC应用:直接用GCC,稳定可靠,社区支持最好
- 多流场景(同时传视频+屏幕共享):推荐Transport-CC,统一管理更高效
- 接收端算力有限:用GCC,因为REMB需要接收端做较多计算
- 移动端弱网环境:Transport-CC + 自定义降速策略,我一般把丢包阈值降到5%
最后说一句:带宽估计没有银弹。我见过很多团队花大量时间调参数,最后发现还不如默认配置好。我的建议是:先用默认配置跑通,然后针对你的特定网络场景做微调。不要一开始就想着「优化」,先保证「能用」。