3、网络传输层抖动:UDP与TCP的选择,丢包重传策略,FEC前向纠错在H265推流中的应用

推流卡顿,很多时候不是编码器不行,也不是服务器扛不住,而是网络传输层在「搞鬼」。我见过太多团队,花大价钱优化编码参数,结果换条网络线路就原形毕露。说白了,传输层才是H265推流的「隐形瓶颈」。

这一章,我们聚焦三个核心问题:UDP还是TCP?丢包了怎么办?FEC到底有没有用?

核心观点:没有完美的传输协议,只有适合场景的取舍。UDP+丢包补偿,是H265实时推流的默认答案。

3.1 UDP vs TCP:实时推流的生死抉择

先问个问题:为什么直播推流几乎不用TCP?

TCP有重传机制,听起来很可靠对吧?但你想过没有,H265推流是实时性第一。TCP一旦丢包,它会死等重传,后面的数据包全堵在队列里。这就是典型的「队头阻塞」。我曾在一次跨国推流项目中,用TCP推H265,结果延迟飙到8秒,画面还时不时卡成PPT。

UDP就不一样了。它只管发,不管收没收到。丢包?丢了就丢了,下一帧继续。延迟能控制在200ms以内。但代价是——你得自己处理丢包。

对比维度 UDP TCP
传输模式 无连接,尽力而为 面向连接,可靠传输
丢包处理 不处理,丢就丢了 自动重传,直到收到
延迟 低(通常<200ms) 高(重传导致累积延迟)
队头阻塞 无 严重
适用场景 实时直播、视频通话 文件传输、点播

我的建议:H265实时推流,默认选UDP。如果网络环境极差(比如丢包率>10%),可以考虑在UDP之上加一层轻量ACK,但别用TCP。

3.2 丢包重传策略:ARQ的三种玩法

UDP丢包了,总得想办法补。ARQ(自动重传请求)是最直接的手段。但怎么重传,有讲究。

策略一:停等ARQ

发一个包,等ACK,收到再发下一个。简单,但效率极低。你想想,网络延迟100ms,发一个包等100ms,一秒只能发10个包。H265的码率稍微高点,直接就崩了。我从来不用这个。

策略二:回退N步ARQ

连续发一堆包,如果某个包丢了,从丢的那个开始,后面全部重传。效率比停等高,但浪费带宽。比如你发了1-10,包5丢了,5-10全部重传。其实6-10可能已经收到了,白传了。

策略三:选择性重传ARQ

只重传真正丢的那个包。这是目前最实用的方案。配合NACK(否定确认),接收端告诉发送端「我缺了包5」,发送端只补发包5。带宽利用率最高。

避坑指南:我曾经在一个项目中,ARQ超时时间设得太短(50ms),结果网络轻微抖动就触发大量重传,反而加剧了拥塞。后来调到200ms,配合抖动缓冲区,效果好了很多。记住:ARQ不是越快越好。

3.3 FEC前向纠错:用冗余换可靠性

ARQ有个硬伤——需要来回通信。延迟高的时候,等重传回来,画面早就该显示了。FEC(前向纠错)的思路是:发送端多塞一些冗余数据,接收端即使丢了一部分,也能靠冗余把原始数据算出来。

举个简单的例子:你发两个数a和b,再发一个a+b。如果a丢了,接收端可以用b和a+b算出a。这就是FEC的基本原理。

在H265推流中,常用的FEC是Reed-Solomon码。它可以把k个原始数据包,生成n个冗余包。只要收到任意k个包,就能恢复全部数据。

FEC参数 含义 典型值
k 原始数据包数量 8、16、32
n 总包数(原始+冗余) 10、20、40
冗余率 (n-k)/k 25%、50%
抗丢包能力 最多可丢n-k个包 2、4、8

关键点:FEC不是万能的。冗余率越高,抗丢包能力越强,但带宽消耗也越大。H265推流中,我一般把冗余率控制在20%-30%,既能抗住5%以内的丢包,又不至于浪费太多带宽。

3.4 FEC在H265推流中的实战配置

光讲理论没用,咱们直接看代码。下面是一个基于WebRTC的FEC配置示例,适用于H265推流:

// FEC配置参数
const fecConfig = {
  // 每8个原始包生成2个冗余包
  k: 8,
  n: 10,
  // 使用Reed-Solomon编码
  codec: 'rs',
  // 每个包最大大小(字节)
  maxPacketSize: 1200
};

// 初始化FEC编码器
const fecEncoder = new ReedSolomonEncoder(fecConfig);

// 发送H265 NAL单元
function sendH265Nal(nalUnit) {
  // 将NAL单元分片
  const packets = fragmentNal(nalUnit, fecConfig.maxPacketSize);
  
  // 应用FEC编码
  const fecPackets = fecEncoder.encode(packets);
  
  // 发送所有包(原始+冗余)
  for (const pkt of fecPackets) {
    udpSocket.send(pkt);
  }
}

接收端解码时,只要收到任意8个包,就能恢复完整的NAL单元。如果丢包超过2个,那就没办法了,只能丢帧。

我的经验:FEC的k值不要设太大。k=16以上时,编码延迟会明显增加,而且一旦丢包超过冗余量,恢复失败的影响面更大。我习惯用k=8,n=10,冗余率25%,在4G网络下实测效果不错。

3.5 ARQ + FEC:混合策略才是王道

ARQ和FEC各有优缺点。ARQ灵活,但依赖往返时间;FEC无延迟,但浪费带宽。实际项目中,我通常把两者结合起来:

  • 轻度丢包(<3%):只用FEC,冗余率15%-20%。ARQ关闭,避免不必要的重传。
  • 中度丢包(3%-8%):FEC冗余率提高到25%-30%,同时开启ARQ作为后备。FEC能恢复大部分丢包,剩下的靠ARQ补。
  • 重度丢包(>8%):FEC冗余率提到40%,ARQ超时缩短。但说实话,这种网络环境,推流质量已经很难保证了,建议降码率。

避坑指南:我曾经在一个项目中,ARQ和FEC同时开,结果FEC恢复了一部分包,ARQ又重传了同样的包,导致接收端重复数据堆积。后来加了去重逻辑,才解决问题。记住:混合策略需要协调,不是简单叠加。

3.6 本章知识体系图

下面这张图,把UDP/TCP选择、ARQ策略、FEC原理以及混合策略串起来了。你可以把它当作本章的「思维导图」:

H265推流传输层知识体系 H265推流传输层知识体系 传输层核心问题 UDP vs TCP ARQ重传策略 FEC前向纠错 UDP:低延迟,无重传 TCP:可靠,队头阻塞 停等ARQ(低效) 回退N步ARQ 选择性重传ARQ Reed-Solomon码 冗余率控制 ARQ + FEC 混合策略 轻度丢包→FEC为主 | 中度丢包→FEC+ARQ | 重度丢包→降码率 📌 核心结论 1. H265实时推流默认选UDP,避免TCP队头阻塞 2. ARQ推荐选择性重传,超时时间建议200ms左右 3. FEC用Reed-Solomon码,k=8/n=10,冗余率25%起步 4. 混合策略:轻度丢包FEC为主,中度丢包FEC+ARQ,重度丢包降码率

3.7 实战建议总结

最后,给你几个可以直接用的建议:

  1. 协议选择:H265推流,UDP是底线。别碰TCP,除非你做的是录播。
  2. ARQ配置:用选择性重传,超时200ms,配合NACK。别用停等ARQ,那是上个世纪的东西。
  3. FEC参数:k=8,n=10,冗余率25%。网络差的时候动态调整到k=8,n=14(冗余率75%)。
  4. 混合策略:FEC兜底,ARQ补漏。记得加去重逻辑,不然接收端会乱。
  5. 监控指标:实时关注丢包率、RTT、FEC恢复成功率。这三个指标能帮你快速定位问题。

我的习惯:每次上线新推流节点,我都会先用模拟工具打5%的丢包,看看FEC能不能扛住。扛不住就调参数,直到稳定为止。别等线上出问题再改,那时候用户已经跑了。