19、拥塞控制与FEC/ARQ的联动:前向纠错策略、重传策略、码率与冗余的权衡、实际案例分析

说实话,拥塞控制做到一定程度,你会发现一个绕不开的问题:丢包了怎么办?

你算法再牛,网络一抖,包就丢了。这时候,光靠调整发送码率是不够的。你得有手段把丢掉的包补回来。这就是我们今天要聊的——FEC(前向纠错)和ARQ(自动重传请求)与拥塞控制的联动。

我个人习惯把这两者比作「防」和「治」。FEC是预防,提前塞冗余数据进去;ARQ是治疗,丢了再要。但问题来了:冗余多了,带宽占用高,拥塞更严重;重传多了,延迟飙升,用户体验差。怎么权衡?

嗯,这章我们就把这个权衡讲透。

19.1 前向纠错策略:FEC到底怎么用?

FEC的原理很简单:发送端在原始数据包之外,额外发送一些冗余包。接收端即使丢了一部分包,也能通过冗余包把原始数据恢复出来。

举个例子,你发送3个原始包,再生成1个冗余包(比如异或运算的结果)。接收端只要收到任意3个包,就能恢复全部数据。这就是经典的(3,4) FEC方案。

但这里有个坑——冗余率怎么定?

我在项目中遇到过,有人把冗余率设成固定20%。结果网络好的时候,白白浪费带宽;网络差的时候,20%又不够用。说白了,FEC的冗余率必须动态调整,跟着丢包率走。

核心原则:FEC冗余率 ≈ 当前丢包率 × 安全系数(通常1.1~1.3倍)。丢包率5%,冗余率就设6%左右。别多,多了就是浪费。

WebRTC里常用的FEC方案有两种:

  • ULP FEC(Uneven Level Protection):对不同重要性的数据包提供不同级别的保护。比如视频的关键帧给高冗余,非关键帧给低冗余。
  • FlexFEC:更灵活的FEC方案,可以自定义保护组的大小和冗余率。WebRTC后来主推的就是这个。

我个人更推荐FlexFEC。为什么?因为它能跟拥塞控制算法配合得更好。你可以根据带宽估计值,实时调整FEC的保护级别。带宽充裕时,多给点冗余;带宽紧张时,少给甚至不给。

19.2 重传策略:ARQ不是万能的

ARQ说白了就是「丢了再发一次」。NACK(Negative ACK)机制就是干这个的——接收端发现丢包了,发个NACK回去,发送端重新传一次。

但ARQ有个致命问题:延迟不可控

你想想看,一个包丢了,你发NACK,等发送端收到,再重传,一来一回至少一个RTT。如果网络RTT是100ms,那重传延迟就是100ms+。对于实时通信来说,这已经很明显了。

所以ARQ的使用要讲究策略:

  • 时间敏感场景(比如视频通话):丢包后如果超过某个时间阈值(比如50ms),就别重传了,直接跳过。否则等重传到了,画面已经该更新了。
  • 非时间敏感场景(比如文件传输):可以放心重传,延迟不是问题。

我曾经踩过的坑:有一次把ARQ的超时时间设得太长,结果网络抖动时,大量重传包堆积在缓冲区,导致后续新数据发不出去。整个视频流卡死。后来我把超时时间砍到30ms,配合FEC,效果好了很多。

WebRTC里ARQ的典型流程是这样的:

接收端检测到丢包 → 发送NACK(携带丢失包的序列号)
发送端收到NACK → 检查该包是否还在发送缓冲区
  如果在 → 立即重传
  如果不在(已被新数据覆盖)→ 忽略
接收端收到重传包 → 插入到正确位置 → 解码播放

注意,发送端不会无限保留旧包。WebRTC有一个发送缓冲区大小限制,通常保留最近几百毫秒的数据。超过这个范围的丢包,ARQ也救不了,只能靠FEC或者直接丢帧。

19.3 码率与冗余的权衡:一个动态博弈

这是整个章节最核心的部分。码率、冗余、延迟、质量,四者之间是一个动态平衡。

我们用一个公式来理解:

实际发送码率 = 原始码率 + FEC冗余码率 + ARQ重传码率

拥塞控制算法估计出当前可用带宽后,你要把这部分带宽分配给三部分:原始数据、FEC冗余、ARQ重传预留。

怎么分?我建议遵循以下优先级:

  1. 先保证原始码率:这是用户体验的基础。视频不能太糊,音频不能太差。
  2. 再分配FEC冗余:根据丢包率动态调整。丢包率低时,FEC可以少给甚至不给。
  3. 最后预留ARQ带宽:ARQ是应急用的,不需要一直预留太多。但也不能完全不预留,否则重传时带宽不够,会进一步加剧拥塞。

一个小技巧:我习惯把ARQ重传的带宽预留设为总带宽的5%~10%。当实际发生重传时,从FEC冗余的带宽里临时借用。这样既保证了原始码率,又不会因为重传导致带宽超标。

下面这张图展示了FEC、ARQ和拥塞控制的联动关系:

拥塞控制与FEC/ARQ联动架构 拥塞控制算法 (GCC / BBR / NADA) 带宽分配器 原始码率 视频/音频编码器 FEC冗余 动态冗余率 ARQ重传预留 NACK处理 反馈回路:丢包率 / RTT / 接收端报告 → 拥塞控制算法 动态调整

你看,整个系统是一个闭环。拥塞控制输出带宽估计值,带宽分配器把它分给三部分,然后接收端反馈丢包率和RTT,拥塞控制再根据这些反馈调整带宽估计。循环往复。

19.4 实际案例分析:一个典型的WebRTC通话场景

我们来看一个实际案例。假设一个视频通话场景:

  • 目标码率:1.5 Mbps
  • 当前丢包率:3%
  • RTT:50ms
  • 可用带宽:2 Mbps

怎么配置FEC和ARQ?

我的做法是这样的:

  1. 先算FEC冗余:丢包率3%,安全系数1.2,冗余率 = 3% × 1.2 = 3.6%。取整4%。所以FEC冗余码率 = 1.5 Mbps × 4% = 60 Kbps。
  2. 再算ARQ预留:预留5%的带宽,即 2 Mbps × 5% = 100 Kbps。
  3. 总发送码率:1.5 Mbps + 60 Kbps + 100 Kbps = 1.66 Mbps。低于可用带宽2 Mbps,没问题。

但如果丢包率突然上升到10%呢?

这时候FEC冗余率要调到12%(10% × 1.2),FEC码率变成 1.5 Mbps × 12% = 180 Kbps。总发送码率 = 1.5 + 0.18 + 0.1 = 1.78 Mbps。还在2 Mbps以内,可以接受。

但如果丢包率继续上升到20%呢?

FEC冗余率24%,FEC码率360 Kbps。总发送码率 = 1.5 + 0.36 + 0.1 = 1.96 Mbps。快接近2 Mbps了。这时候我会考虑降低原始码率到1.2 Mbps,给FEC和ARQ留出空间。

关键决策点:当丢包率超过15%时,单纯靠FEC和ARQ已经不够了。必须降低原始码率,牺牲画质来保证流畅度。这是拥塞控制和FEC/ARQ联动的核心——降码率是第一优先级

我记得有一次在弱网环境下测试,丢包率飙到30%。FEC冗余率设到36%,结果带宽全被冗余占用了,原始码率被压缩到极低,画面全是马赛克。后来我调整策略:丢包率超过20%时,直接关闭FEC,全部依赖ARQ+降码率。虽然延迟高了点,但画面至少还能看。

19.5 避坑指南:我踩过的几个坑

做FEC和ARQ的联动,有几个坑我替你们踩过了:

  • 不要同时用高冗余FEC和大量ARQ:两者都占用带宽,同时开会导致带宽爆炸。我的经验是:丢包率低时用ARQ,丢包率高时用FEC,不要同时高配。
  • FEC保护组不要太大:保护组越大,计算复杂度越高,延迟越大。我一般控制在8~16个包一组。
  • ARQ超时时间要跟RTT联动:RTT大时,超时时间也要相应增大。但最大不要超过200ms,否则重传包到了也没意义。
  • 注意FEC和编码器的配合:有些编码器(比如VP8)内部也有FEC机制,跟WebRTC的FEC可能会冲突。建议只开一层的FEC。

特别提醒:FEC不是万能的。它只能应对随机丢包,对连续丢包(比如网络断流)无能为力。连续丢包超过FEC的保护能力时,还是要靠ARQ或者直接丢帧。

好了,这一章的内容就到这里。FEC和ARQ的联动,说白了就是在「防丢包」和「治丢包」之间找一个平衡点。没有银弹,只有根据网络状况动态调整的策略。


公众号:蓝海资料掘金营,微信deep3321