弱网对抗:NACK与FEC机制、带宽估计、Simulcast分层编码

各位同学,今天我们来聊聊视频会议中最让人头疼的问题——网络不好怎么办。

说实话,我在做WebRTC开发之前,一直觉得视频卡顿就是网络差,没什么好办法。直到深入研究了WebRTC的弱网对抗机制,才发现这里面门道真多。WebRTC能成为实时通信的事实标准,靠的就是这套组合拳。

弱网对抗,说白了就是三件事:丢包了怎么补、带宽变了怎么调、不同终端怎么适配。对应到技术层面,就是NACK/FEC、带宽估计、Simulcast分层编码。我们一个一个来看。

一、NACK与FEC:丢包了怎么办?

网络丢包是常态,不是异常。尤其是在WiFi环境下,丢包率1%-5%都很正常。WebRTC提供了两种丢包恢复手段:NACK和FEC。

1. NACK(否定确认)

NACK的原理很简单:接收端发现某个包丢了,就告诉发送端“这个包我没收到,再发一次”。

我刚开始接触时觉得这太基础了,不就是TCP那套吗?但实际用起来坑不少。

NACK的工作流程:

  • 接收端检测到序列号不连续
  • 构造NACK消息,包含丢失包的序列号
  • 发送端收到后,从缓存中取出对应包重传

这里有个关键点:发送端必须保留已发送包的缓存。缓存多久?我一般建议300-500ms。太短了来不及重传,太长了内存吃不消。

我的经验:NACK不是万能的。如果丢包率超过20%,NACK会导致大量重传,反而加剧网络拥塞。这时候就该FEC上场了。

2. FEC(前向纠错)

FEC的思路完全不同——我不等你告诉我丢了,我提前发一些冗余数据。即使丢了一部分,接收端也能自己恢复。

WebRTC用的是ULP FEC(Uneven Level Protection),说白了就是给重要数据多加几层保护。

// FEC配置示例
// 设置FEC保护比例,0.2表示20%的冗余
rtpConfig.SetFecParams(SdpFecMode.kFlexFec, 0.2f);

// 动态调整FEC比例
if (packetLossRate > 0.1f) {
    fecRate = Math.min(0.5f, fecRate + 0.05f);
} else {
    fecRate = Math.max(0.1f, fecRate - 0.02f);
}

注意:FEC会增加带宽消耗。20%的冗余意味着你要多传20%的数据。我见过有人把FEC开到50%,结果网络直接崩了。FEC和NACK要配合使用,不是非此即彼。

二、带宽估计:网络到底能跑多快?

带宽估计是WebRTC最核心的模块之一。没有准确的带宽估计,编码器不知道该压到多少码率,结果就是要么卡顿,要么模糊。

WebRTC的带宽估计主要分两种:

算法 原理 适用场景
GCC(Google Congestion Control) 基于延迟梯度和丢包率 大多数场景
REMBB(Remote Bitrate Estimation) 基于接收端反馈 对称网络

我个人习惯用GCC,因为它对网络变化的响应更灵敏。GCC的核心思想是:延迟增加说明网络开始拥塞,丢包增加说明网络已经过载

具体实现上,GCC会维护一个带宽估计值,然后根据网络状态动态调整:

// GCC带宽估计的简化逻辑
if (delayGradient > threshold) {
    // 延迟在增加,降低带宽
    estimatedBitrate *= 0.85f;
} else if (packetLossRate > 0.1f) {
    // 丢包严重,大幅降低
    estimatedBitrate *= 0.7f;
} else if (packetLossRate < 0.02f) {
    // 网络良好,尝试增加
    estimatedBitrate *= 1.08f;
}

嗯,这里要注意:带宽估计不能太激进。我曾经把增加系数设成1.15,结果网络稍微好一点就猛涨,然后马上又跌回来,画面忽好忽坏,用户体验极差。

三、Simulcast分层编码:不同终端各取所需

你有没有想过一个问题:一个1080p的视频流,手机端和PC端都接收同样的分辨率?显然不合理。手机屏幕小,带宽有限,根本不需要那么高的分辨率。

Simulcast就是解决这个问题的。它让发送端同时编码多个不同分辨率的流,接收端根据自己的情况选择最合适的。

Simulcast的典型分层:

  • 高分辨率层:1080p,用于PC端
  • 中分辨率层:720p,用于平板
  • 低分辨率层:360p,用于手机

实现Simulcast需要在SDP协商时声明多个RTP流:

// Simulcast SDP示例
a=simulcast:send rid=high;mid;low
a=rid:high send pt=96;97 width=1920;height=1080
a=rid:mid send pt=96;97 width=1280;height=720  
a=rid:low send pt=96;97 width=640;height=360

接收端收到后,会根据带宽和屏幕大小选择订阅哪一层。带宽不够时,可以动态切换到低层。

避坑指南:Simulcast会增加编码器的负担。我曾经在一个低端手机上开Simulcast,结果CPU直接飙到90%,画面反而更卡了。建议只在高端设备上启用Simulcast,或者限制最多2层。

知识体系总览

说了这么多,我们来画个图,把这三者的关系理清楚:

WebRTC弱网对抗知识体系 NACK与FEC 带宽估计 Simulcast 丢包恢复 码率自适应 多分辨率编码 NACK: 选择性重传 FEC: 前向纠错 GCC: 延迟+丢包 REMBB: 接收端反馈 高/中/低三层 动态切换 三者协同:丢包恢复 + 带宽自适应 + 分层编码 = 流畅体验

从图上可以看得很清楚:NACK/FEC解决丢包问题,带宽估计解决码率适配问题,Simulcast解决多终端适配问题。三者缺一不可。

实际项目中,我一般这样配置:

  • 丢包率 < 5%:只用NACK,带宽开销最小
  • 丢包率 5%-15%:NACK + FEC(10%冗余),平衡恢复速度和带宽
  • 丢包率 > 15%:加大FEC到20%,同时降低视频分辨率

最后说一句,弱网对抗没有银弹。每个网络环境都不一样,最好的办法是在实际场景中测试,然后调整参数。我见过太多人照着网上的配置抄,结果在自己的网络环境下反而更差。

好了,这一章的内容就到这里。记住这三板斧,你的视频会议APP在网络差的时候也能保持基本可用。


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