18、WebRTC的视频处理:视频JitterBuffer、NACK与FEC、视频SVC与Simulcast
视频通话卡成幻灯片,画面碎成马赛克——这几乎是每个做实时音视频的人都会遇到的噩梦。我早期做海外会议系统时,用户从新加坡连到法兰克福,延迟400ms,丢包率15%,那画面简直没法看。后来我才真正搞明白,WebRTC里那套视频处理机制,才是保命的底牌。
今天咱们就聊聊视频链路上的四个核心武器:JitterBuffer、NACK、FEC,以及SVC与Simulcast。它们各自解决什么问题?怎么配合?踩过哪些坑?我尽量一次说透。
18.1 视频JitterBuffer:把乱序的包排好队
网络不是一根直管子。包会乱序、会延迟、会一股脑涌过来。JitterBuffer 就是接收端的一个缓冲区,专门干两件事:排序和抗抖动。
我见过不少新手直接把收到的包往解码器里塞,结果画面一顿一顿的。为什么?因为包到达的时间不均匀。有的早到50ms,有的晚到200ms。解码器需要稳定的帧率输入,JitterBuffer 就是那个“蓄水池”。
核心机制:
- 每个视频包都有 RTP 时间戳和序列号。JitterBuffer 按序列号重排。
- 缓冲区会维持一个目标延迟(比如 100ms),动态调整。
- 如果某个帧的包一直没到齐,超时后直接丢帧——总比卡住好。
我的经验:JitterBuffer 的延迟不能设死。我曾经在 Wi-Fi 环境下把缓冲设成 50ms,结果频繁丢帧。后来改成自适应算法,根据最近 500ms 的抖动统计动态调整,效果好了很多。WebRTC 内部用的是 NetEq 算法,音频视频各有一套,但思路相通。
18.2 NACK:丢了包,我喊你重传
NACK(Negative Acknowledgment)就是“我没收到,再发一次”。接收端发现某个序列号缺失,就通过 RTCP 反馈给发送端,发送端重新发送那个包。
听起来简单,但坑不少。
- 延迟问题:一个 RTT 的代价。如果 RTT 是 200ms,重传回来至少 200ms,对于实时视频来说已经很久了。
- 风暴问题:如果丢包率突然升高,接收端会疯狂发 NACK,发送端疯狂重传,网络更堵。WebRTC 里有限制:每个包最多重传 3 次,而且有 NACK 频率控制。
- 关键帧保护:我习惯对关键帧(IDR)的 NACK 优先级调高。因为丢了关键帧,后面一堆 P 帧都解不出来,画面会一直花屏直到下一个关键帧到来。
注意:NACK 不是万能的。在丢包率超过 20% 的网络里,重传只会让情况更糟。这时候需要 FEC 上场。
18.3 FEC:提前给你冗余包
FEC(Forward Error Correction)是发送端提前算好一些冗余数据,跟原始数据一起发出去。接收端即使丢了一部分包,也能靠冗余包把原始数据恢复出来。
WebRTC 里常用的 FEC 是 ULP FEC(Uneven Level Protection)和 FlexFEC。说白了就是 XOR 运算:把几个包异或一下,生成一个冗余包。丢了一个,用剩下的 XOR 回来。
| 特性 | NACK | FEC |
|---|---|---|
| 延迟 | 至少 1 RTT | 无额外延迟(接收端直接恢复) |
| 带宽开销 | 丢包时才产生 | 始终存在(冗余率固定或动态) |
| 适用场景 | 低丢包、低延迟要求 | 高丢包、低延迟要求 |
| 恢复能力 | 100%(只要重传成功) | 有限(取决于冗余率) |
实际项目中,我通常把两者结合使用。丢包率低于 5% 时只用 NACK,高于 10% 时开启 FEC,冗余率设在 20% 左右。WebRTC 的 VideoSendStream 配置里可以设置 fec_params,建议根据网络探测结果动态调整。
避坑指南:我曾经在 FEC 冗余率上吃过亏。设了 50% 冗余,结果带宽不够,反而加剧了丢包。记住:FEC 不是越多越好,它会吃掉带宽。建议用 REMBB 带宽估计来动态调整 FEC 和编码码率的比例。
18.4 视频SVC与Simulcast:让不同带宽的人都能看
一个会议里,有人用 5G,有人用 3G,还有人用 2G 热点。你不能让所有人都收同一路高清流。SVC 和 Simulcast 就是解决这个问题的。
Simulcast:同时发多路流
发送端同时编码出多个分辨率的流,比如 1080p、720p、360p。接收端根据自己的带宽选一路来收。
- 优点:每路流独立编码,质量好。接收端切换流时延迟低。
- 缺点:发送端编码压力大,带宽开销大(同时发多路)。
我在做 SFU(选择性转发单元)时,Simulcast 是最常用的方案。SFU 只需要根据订阅者的带宽,转发对应质量的流即可,不需要转码,性能很高。
SVC:一层流,多种质量
SVC(Scalable Video Coding)把视频编码成多层:基础层(低分辨率/低帧率)加上增强层。接收端可以只收基础层,也可以收基础层+增强层来提升质量。
- 优点:只发一路流,带宽利用率高。接收端可以灵活选择层数。
- 缺点:编码复杂度高,增强层丢包会影响上层质量。浏览器支持不如 Simulcast 好。
我的选择:如果是浏览器端,我优先用 Simulcast。Chrome 和 Firefox 对 Simulcast 支持很成熟。如果是自研客户端或者硬件编码器,SVC 更省带宽。VP9 的 SVC 实现比 H.264 的 SVC 更实用,我最近的项目就在用 VP9 SVC,配合带宽估计,效果很稳。
18.5 四者如何配合?
一个典型的视频接收流程是这样的:
- 网络包到达,先进入 JitterBuffer,排序并等待。
- 如果发现丢包,先尝试 FEC 恢复。恢复不了,再发 NACK 请求重传。
- 如果重传超时,直接丢帧,解码器跳过这一帧。
- 同时,带宽估计模块在后台跑,根据网络状况决定是否切换 Simulcast 流或调整 SVC 层数。
这套机制不是孤立的。我见过有人只开 NACK 不开 FEC,结果高丢包时画面卡死。也有人只开 FEC 不开 NACK,结果带宽浪费严重。记住:JitterBuffer 是基础,NACK 和 FEC 互补,SVC/Simulcast 是上层策略。
一句话总结:JitterBuffer 让视频不乱不卡,NACK 和 FEC 让视频不丢不花,SVC 和 Simulcast 让视频适应各种网络。四者缺一不可。