在底层 UDP 传输协议中,单纯依靠 FEC(前向纠错)会浪费带宽,单纯依靠 ARQ(丢包重传)会带来巨大的延迟。ARQ+FEC 混合方案(Hybrid ARQ) 是目前实时音视频通信(如 WebRTC、Zoom)的主流方案。
以下是一个完整的底层实现逻辑框架:
1. 整体架构设计
混合方案的核心逻辑是:用 FEC 覆盖随机的小量丢包(零延迟),用 ARQ 覆盖大面积的丢包或突发丢包(低带宽消耗)。
2. 发送端 (Sender) 实现流程
发送端需要维护一个滑动窗口(Sliding Window),用于存储已发送但尚未确认的包。
A. 封包与缓存
- 给每个包打上全局唯一的序列号 (SN)。
- 将原始音频包放入
Send_Window缓存,等待可能的 ARQ 请求。
B. FEC 编码策略
- 分组 (Group):每K个原始包组成一个矩阵。
- 计算 (Encode):生成M个冗余包(通常使用 Reed-Solomon 算法)。
- 动态冗余率:根据接收端反馈的丢包率 L动态调整M的值。
- 若 ‘L < 5%’ M=1(低冗余)。
- 若 ‘L > 15%’ M =K*0.4(高冗余)。
C. 响应 ARQ 请求
- 监听来自接收端的
NACK(Negative Acknowledgment) 包。 - 从
Send_Window中检索指定的 SN,重新打包发送。 - 优先级控制:ARQ 的重传包优先级应高于新的 FEC 包。
3. 接收端 (Receiver) 实现流程
接收端的核心是 Jitter Buffer(抖动缓冲区),它负责排序、解码和触发请求。
A. 处理逻辑流水线
- 收包入库:收到包后根据 SN 放入 Buffer。
- FEC 尝试(即时):
- 检查当前组 (Group) 是否有空缺。
-
如果缺失的包数小于收到冗余包数,立即执行矩阵解码,恢复原始包。
-
ARQ 触发(定时/间隙):
-
如果 FEC 无法恢复(丢包太多),且时间允许(尚未到达播放时间),发送
NACK给发送端。 -
递交给解码器:
- 按 SN 顺序取出包,交给 Opus/AAC 解码。
4. 关键算法与协议细节
数据包结构定义 (C++)
enum PacketType { MEDIA = 0, FEC = 1, NACK = 2 };
struct Packet {
uint16_t sn; // 序列号
uint8_t type; // 包类型
uint16_t group_id; // FEC 组 ID
uint32_t ts; // 时间戳(用于计算抖动)
uint8_t payload[MAX_SIZE];
};
ARQ 的“限制性策略”
在实时音频中,不能无限次重传。
- RTT 判定:如果 (当前时间 – 包发送时间) > 2.5倍 RTT,通常放弃重传。
- 过期判定:如果包的 SN 已经落后于当前播放指针,直接丢弃 NACK 请求。
5. ARQ 与 FEC 的协同触发算法
如何平衡两者?这里推荐一个常用的工业级逻辑:
| 丢包情况 | 处理策略 | 理由 |
|---|---|---|
| 单随机丢包 | FEC 恢复 | 延迟为 0,不产生额外往返时间。 |
| 突发丢包 (Burst) | ARQ 请求 | FEC 难以覆盖连续 10 个以上的丢包,重传更节省带宽。 |
| 极高 RTT 网络 | 加大 FEC 权重 | RTT 太大(如 >300ms)时,ARQ 重传回来也过时了,只能靠 FEC 硬扛。 |
| 极低带宽网络 | 减小 FEC,开启 ARQ | FEC 产生的冗余流量会加剧拥塞,ARQ 按需索取更精准。 |
6. 实现建议
- 引入 RTT 估算:你需要实现类似 TCP 的 RTT 采样(通过发送包和接收 ACK 的时间差),这是判断 ARQ 是否还有效的前提。
- 使用成熟的 RS 库:推荐使用
multi-platform FEC library(如 OpenFEC) 或Jerasure库处理矩阵运算,不要手写有限域算术。 - 带宽估算 (BWE):如果可能,集成 GCC (Google Congestion Control) 算法,它能告诉你当前网络能跑多少码率,从而决定音频码率 + FEC 冗余量的总和。
WebRTC 的 ARQ 和 FEC 实现机制
WebRTC 是 ARQ+FEC 混合方案的典型实现,它综合使用了多种丢包恢复技术,实现了业界领先的实时通信质量。
WebRTC ARQ 实现
RTP NACK(Negative Acknowledgment)
实现机制:
– 协议基础:基于 RTP/RTCP 协议(RFC 3550、RFC 4585)
– 丢包检测:接收端通过序列号间隙检测丢包
– 反馈机制:通过 RTCP Feedback 包(RTCP NACK)通知发送端
– 批量请求:一次 NACK 可以请求多个丢失的包(序列号范围)
关键参数:
– NACK 间隔:通常每 5-20ms 发送一次 NACK
– 最大重传次数:每个包最多重传 2-3 次
– RTT 阈值:如果 RTT > 200ms,降低 NACK 频率或禁用
实现细节:
// WebRTC NACK 包结构(简化)
struct RTCPNACK {
uint32_t sender_ssrc; // 发送端 SSRC
uint32_t media_ssrc; // 媒体流 SSRC
uint16_t pid; // 第一个丢失包的序列号
uint16_t blp; // Bitmask,表示后续丢失的包
};
RTX(RTP Retransmission)
实现机制:
– 独立流:使用独立的 RTP 流(RTX 流)传输重传包
– SSRC 分离:原始流和重传流使用不同的 SSRC
– 序列号映射:重传包使用 RTX 序列号,通过 RTP Header Extension 携带原始序列号
优势:
– 可以区分原始包和重传包,便于统计和监控
– 支持不同的传输优先级
– 兼容性更好,符合 RTP 标准
实现细节:
// RTX 包结构
struct RTXPacket {
uint16_t rtx_seq; // RTX 流序列号
uint16_t original_seq; // 原始序列号(通过 Header Extension)
uint8_t payload[];
};
NACK 触发策略
触发条件:
1. 序列号间隙检测:发现序列号不连续
2. 定时触发:每 5-20ms 检查一次,批量发送 NACK
3. RTT 判断:如果 RTT 过大,可能放弃 NACK
优化策略:
– 延迟发送:等待一小段时间(5-10ms),可能收到乱序包
– 批量处理:一次 NACK 请求多个丢失的包
– 优先级控制:关键帧(I帧)优先重传
WebRTC FEC 实现
ULPFEC(Uneven Level Protection FEC)
实现机制:
– 协议标准:RFC 5109
– 编码算法:基于 XOR 运算的简单 FEC
– 保护模式:可以保护多个 RTP 包(通常 2-8 个)
– 多级保护:不同重要性的包可以使用不同的 FEC 保护级别
编码过程:
1. 分组:将连续的 K 个媒体包分为一组
2. 编码:对这 K 个包进行 XOR 运算,生成 FEC 包
3. 发送:媒体包和 FEC 包一起发送
解码过程:
1. 检测丢包:检查组内是否有包丢失
2. 判断可恢复性:如果丢失包数 ≤ FEC 包数,可以恢复
3. XOR 解码:使用收到的包和 FEC 包进行 XOR 运算恢复丢失的包
关键参数:
– 保护包数:通常保护 2-8 个包
– 冗余率:根据丢包率动态调整(5%-40%)
– 延迟:零延迟,无需等待往返时间
FlexFEC
实现机制:
– 协议标准:RFC 8627
– 编码算法:支持多种编码方式(XOR、Reed-Solomon 等)
– 保护模式:支持 1D(一维)和 2D(二维)保护模式
– 灵活性:可以灵活配置保护参数
1D 保护模式:
– 对连续的包进行一维保护
– 适合随机丢包场景
2D 保护模式:
– 对包矩阵进行二维保护
– 可以同时保护行和列
– 适合突发丢包场景
配置示例:
// FlexFEC 配置
struct FlexFECConfig {
uint8_t protection_mode; // 1D 或 2D
uint8_t row_k; // 行保护包数
uint8_t row_n; // 行总包数
uint8_t col_k; // 列保护包数(2D模式)
uint8_t col_n; // 列总包数(2D模式)
};
WebRTC 协同机制
自适应策略
WebRTC 根据网络状况动态调整 ARQ 和 FEC 的使用:
网络质量好(丢包率 < 2%):
– 降低 FEC 冗余度(5-10%)
– 主要依赖 NACK 重传
– 减少带宽开销
网络质量中等(丢包率 2-10%):
– 中等 FEC 冗余度(15-25%)
– NACK + FEC 组合使用
– 平衡延迟和带宽
网络质量差(丢包率 > 10%):
– 提高 FEC 冗余度(30-40%)
– 如果 RTT 高,减少 NACK 依赖
– 优先保证流畅度
RTT 自适应
低 RTT(< 50ms):
– 优先使用 NACK,延迟低
– FEC 冗余度较低
中等 RTT(50-200ms):
– NACK + FEC 组合
– 根据 RTT 调整 NACK 超时时间
高 RTT(> 200ms):
– 主要依赖 FEC
– 减少或禁用 NACK(重传回来也过时了)
带宽自适应
带宽充足:
– 可以使用较高的 FEC 冗余度
– 保证更好的质量
带宽受限:
– 降低 FEC 冗余度
– 优先使用 NACK(按需重传)
– 根据带宽估算(BWE)调整码率和冗余度
WebRTC 关键实现细节
发送端实现
包发送流程:
1. 媒体包发送:发送原始媒体包,记录到发送窗口
2. FEC 编码:对媒体包进行 FEC 编码,生成冗余包
3. FEC 包发送:发送 FEC 包
4. NACK 处理:收到 NACK 后,通过 RTX 流重传
优先级控制:
– RTX 重传包优先级最高
– 关键帧(I帧)优先级高于普通帧
– FEC 包优先级低于媒体包
接收端实现
包接收流程:
1. 包接收:收到包后放入 Jitter Buffer
2. FEC 尝试:立即尝试使用 FEC 恢复丢包
3. NACK 触发:如果 FEC 无法恢复,且时间允许,发送 NACK
4. 包排序:按序列号排序,递交给解码器
Jitter Buffer 管理:
– 最小延迟:通常 30-50ms
– 最大延迟:根据网络状况动态调整(50-200ms)
– 包过期:超过播放时间的包直接丢弃
统计和反馈
关键指标:
– 丢包率:通过 RTCP Receiver Report 反馈
– RTT:通过 RTCP Sender Report 和 Receiver Report 计算
– 带宽估算:使用 GCC(Google Congestion Control)算法
反馈机制:
– RTCP RR:接收端报告,包含丢包率、延迟等信息
– RTCP NACK:丢包重传请求
– RTCP PLI/FIR:关键帧请求
WebRTC 配置建议
视频通话场景:
– FEC 冗余度:15-25%
– NACK 启用:是
– RTX 启用:是
– 保护模式:ULPFEC 或 FlexFEC 1D
直播推流场景:
– FEC 冗余度:20-30%
– NACK 启用:根据延迟要求(低延迟启用,高延迟禁用)
– 保护模式:FlexFEC 2D(适合突发丢包)
点播场景:
– FEC 冗余度:10-15%
– NACK 启用:是(延迟要求不严格)
– 保护模式:ULPFEC
总结
WebRTC 的 ARQ+FEC 混合方案通过以下机制实现了高效的丢包恢复:
- 多层次的 ARQ:NACK + RTX,实现快速重传
- 灵活的 FEC:ULPFEC + FlexFEC,支持多种保护模式
- 自适应策略:根据网络状况动态调整 ARQ 和 FEC 的比例
- 优先级控制:关键数据优先保护,保证用户体验
- 完善的反馈机制:通过 RTCP 实现网络状况的实时反馈
这使得 WebRTC 能够在各种网络条件下提供稳定、低延迟的实时通信体验。
