4. 带宽估计与码率控制:GCC、REMB与TWCC
带宽估计与码率控制,说白了就是让RTP流“看路开车”。
网络带宽是动态的,忽快忽慢。如果没有一套机制去感知和调整,视频就会卡顿、花屏,甚至断连。我早期做WebRTC项目时,就吃过这个亏——带宽估计没配好,用户一多,整个会议就崩了。
这一章,我们聚焦Google Congestion Control(GCC),以及它依赖的两个核心协议:REMB和TWCC。我会把发送端和接收端带宽估计的逻辑拆开讲,再结合我踩过的坑,给你一些实战建议。
4.1 GCC的整体架构
GCC是WebRTC默认的拥塞控制算法。它不是一个单一模块,而是一套协作流程。
我习惯把它分成两条腿走路:
- 接收端带宽估计:基于到达时间差,计算延迟梯度,判断网络是否拥塞。
- 发送端带宽估计:基于丢包率,做更激进的降码率决策。
两条腿互相配合,最终输出一个“目标码率”,喂给编码器和RTP发送模块。
核心原则:延迟信号用于早期预警,丢包信号用于紧急刹车。两者不能互相替代。
4.2 接收端带宽估计:延迟梯度与REMB
接收端带宽估计的核心,是计算数据包到达时间的“延迟梯度”。
为什么看延迟?因为网络一旦开始排队,延迟就会增加。这个信号比丢包来得早。我遇到过很多案例,丢包率还没涨,延迟已经翻倍了。
4.2.1 到达时间差与延迟梯度
接收端会记录每个RTP包的到达时间。然后,它计算一组包的“到达时间差”与“发送时间差”的差值。
- 如果差值 > 0,说明网络排队了,延迟在增加。
- 如果差值 < 0,说明网络在释放,延迟在减少。
这个差值经过卡尔曼滤波或趋势线拟合,得到一个“延迟梯度”指标。
我的经验:延迟梯度对突发流量特别敏感。我曾经在一个弱网测试中,发现延迟梯度在500ms内就从0飙升到200ms,而丢包率还是0%。这时候如果不降码率,接下来必然丢包。
4.2.2 REMB:接收端反馈带宽估计
REMB(Receiver Estimated Maximum Bitrate)是RTCP的一种扩展反馈报文。接收端算出一个“建议码率”,通过REMB告诉发送端。
REMB报文格式很简单:
// REMB报文结构(简化)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|V=2|P| FMT=15 | PT=206 | length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of packet sender |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC of media source |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unique identifier 'R' 'E' 'M' 'B' |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Num SSRC | br Exp | br Mantissa |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| SSRC feedback |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
关键字段:
- br Exp + br Mantissa:表示建议码率,单位是bps。
- SSRC feedback:这个码率是针对哪个媒体流的。
注意:REMB是接收端单方面建议。发送端可以采纳,也可以忽略。我见过一些实现,发送端完全不听REMB,只靠自己算丢包,结果延迟爆炸。
4.3 发送端带宽估计:丢包率与码率决策
发送端带宽估计,主要依赖丢包率。为什么?因为丢包是网络过载的“铁证”。
GCC在发送端维护一个状态机:
| 状态 | 触发条件 | 码率调整策略 |
|---|---|---|
| 增加 | 丢包率 < 2% | 按比例增加码率(如+8%) |
| 保持 | 2% ≤ 丢包率 < 10% | 维持当前码率 |
| 减少 | 丢包率 ≥ 10% | 按比例减少码率(如-20%) |
这个状态机看起来简单,但实际调参很讲究。我曾经在一个项目中,把“减少”阈值设成了5%,结果网络稍微波动就降码率,视频质量忽高忽低。后来改回10%,稳定多了。
4.3.1 丢包率计算
发送端通过RTCP RR(Receiver Report)获取丢包率。RR报文里有一个“fraction lost”字段,表示丢包比例。
// RTCP RR中的丢包率字段
// fraction lost: 8 bits, 单位是 1/256
// 例如:fraction lost = 25,表示丢包率 ≈ 9.8%
避坑指南:我曾经遇到一个问题,fraction lost一直为0,但视频明显卡顿。后来发现是接收端RTCP RR发送间隔太长(默认5秒),丢包信息滞后了。我建议把RTCP间隔缩短到1秒以内,尤其是对实时性要求高的场景。
4.4 TWCC:更精细的传输反馈
TWCC(Transport Wide Congestion Control)是GCC的“增强版”反馈机制。它不像REMB那样只给一个建议码率,而是把每个数据包的到达情况都反馈回来。
TWCC的核心是:
- 发送端给每个RTP包打上一个“传输序列号”。
- 接收端收到后,记录每个包的到达时间。
- 接收端通过RTCP反馈报文,把“包序号+到达时间”列表发给发送端。
- 发送端自己算延迟梯度,自己做带宽估计。
这样做的好处是:发送端掌握全部信息,决策更精准。坏处是:反馈报文变大,带宽开销增加。
| 特性 | REMB | TWCC |
|---|---|---|
| 反馈内容 | 建议码率 | 每个包的到达时间 |
| 计算位置 | 接收端 | 发送端 |
| 带宽开销 | 低(约20字节/报文) | 高(取决于包数量) |
| 精度 | 中等 | 高 |
| 适用场景 | 传统WebRTC | 新版WebRTC、低延迟场景 |
我的建议:如果你的网络带宽充裕,优先用TWCC。它能让发送端更早感知到拥塞,码率控制更平滑。如果带宽紧张(比如移动网络),用REMB更省流量。
4.5 实战:GCC参数调优
GCC虽然默认参数能用,但想跑得好,必须调。我总结几个关键参数:
- RTCP反馈间隔:默认5秒太长,建议设为200ms-1秒。
- 丢包率阈值:减少阈值建议10%,增加阈值建议2%。
- 码率增加比例:建议8%-15%,太快容易震荡。
- 码率减少比例:建议20%-30%,太慢无法快速响应拥塞。
- 延迟梯度敏感度:如果网络抖动大,可以适当降低敏感度。
曾经踩过的坑:我把码率增加比例设成了25%,结果网络稍微好转,码率就猛涨,然后立刻触发丢包,又猛降。视频质量像过山车。后来改成10%,平滑多了。
4.6 小结
带宽估计与码率控制,是RTP部署中最容易出问题的地方,也是最值得优化的地方。
GCC通过接收端延迟梯度(REMB/TWCC)和发送端丢包率,共同决策目标码率。REMB适合带宽受限场景,TWCC适合精度要求高的场景。
调参时,记住一句话:延迟是预警,丢包是铁证。两者结合,才能让RTP流在网络波动中稳如老狗。