11、音视频编解码:常用编解码器、编解码协商过程、Simulcast与SVC、码率自适应

聊到WebRTC,编解码这块是绕不开的核心。说白了,没有编解码,摄像头采集的原始视频数据根本传不出去——一秒钟几MB甚至几十MB的数据量,网络扛不住。我刚开始接触WebRTC时,总觉得编解码是底层的事,上层调API就行。后来踩了几次坑才发现,不理解编解码的协商逻辑,连个视频通话都调不通。

常用编解码器:VP8、VP9、H.264、AV1

WebRTC标准里强制要求支持的是VP8和H.264。VP9和AV1是可选,但浏览器支持度越来越高。

编解码器 特点 浏览器支持 我的经验
VP8 开源、低延迟、兼容性好 Chrome、Firefox、Edge 最稳的选择,几乎不出错
H.264 硬件编码支持好、带宽利用率高 Chrome、Safari、Edge iOS端必选,否则黑屏
VP9 压缩率比VP8高30%-50% Chrome、Firefox 适合低带宽场景,但编码慢
AV1 压缩率最高,但编码开销大 Chrome(部分版本) 目前还不太实用,等硬件普及
我的建议: 如果做跨平台应用,优先支持VP8和H.264。VP8用于Chrome和Firefox,H.264用于Safari和iOS。VP9和AV1可以作为锦上添花的选项,但别依赖它们。

编解码协商过程:SDP Offer/Answer 模型

编解码协商,说白了就是两端互相告诉对方「我能解码什么格式,你发什么格式给我」。这个过程通过SDP(Session Description Protocol)完成。

我记得第一次调试时,发现视频一直黑屏。查了半天,原来是SDP里编解码顺序写错了。浏览器会优先选择SDP里排第一的编解码器。

协商流程大致是这样的:

  1. 发起方创建Offer,里面列出支持的编解码器列表(按优先级排序)
  2. 接收方收到Offer后,创建Answer,从Offer的列表里选一个自己支持的
  3. 双方确认后,开始传输

代码层面,你不需要手动拼SDP。WebRTC API帮你做了大部分工作:

// 创建PeerConnection
const pc = new RTCPeerConnection();

// 创建Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

// 发送offer给远端
// 远端收到后创建Answer
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
注意: 我曾经遇到过一个问题——在Safari上H.264编码的视频花屏。排查后发现是SDP里H.264的profile-level-id参数不匹配。Safari默认用高profile,而Chrome用baseline。解决方案是在createOffer之前手动设置编解码参数。

Simulcast与SVC:多流与分层编码

这两个概念容易混淆。我简单解释一下:

  • Simulcast(同时广播): 发送端同时编码多个不同分辨率的视频流(比如720p、480p、360p),接收端根据网络情况选择订阅哪个流。
  • SVC(可伸缩视频编码): 只编码一个流,但这个流包含多个层(基础层+增强层)。网络差时只传基础层,网络好时叠加增强层提高质量。

我个人更倾向于Simulcast,因为实现简单,兼容性好。SVC虽然理论上更优雅,但浏览器支持有限,而且编码开销大。

Simulcast的典型应用场景: 视频会议中,主讲人发送720p流,观众席的人网络差就订阅360p流。这样既保证了主讲人的清晰度,又照顾了低带宽用户。

开启Simulcast的代码示例:

// 在创建Offer之前设置Simulcast
const transceiver = pc.addTransceiver('video', {
    direction: 'sendrecv',
    sendEncodings: [
        { rid: 'high', maxBitrate: 900000, scaleResolutionDownBy: 1.0 },
        { rid: 'medium', maxBitrate: 300000, scaleResolutionDownBy: 2.0 },
        { rid: 'low', maxBitrate: 100000, scaleResolutionDownBy: 4.0 }
    ]
});

这里rid是流的标识,maxBitrate是最大码率,scaleResolutionDownBy是分辨率缩放比例。比如scaleResolutionDownBy: 2.0表示分辨率缩小到原来的一半。

码率自适应:让视频流畅不卡顿

码率自适应,说白了就是根据网络状况动态调整视频质量。网络好时用高码率,网络差时降低码率避免卡顿。

WebRTC内置了码率自适应算法,叫GCC(Google Congestion Control)。它会根据丢包率、RTT(往返时延)等指标动态调整发送码率。

但内置算法不一定适合所有场景。我在做直播项目时,发现GCC在弱网环境下反应太慢,导致视频卡顿好几秒才恢复。后来我们手动实现了码率自适应:

// 监听网络状态
pc.oniceconnectionstatechange = () => {
    if (pc.iceConnectionState === 'connected') {
        // 网络恢复,提高码率
        setCodecBitrate(800000);
    } else if (pc.iceConnectionState === 'disconnected') {
        // 网络断开,降低码率
        setCodecBitrate(200000);
    }
};

// 手动设置编码器码率
function setCodecBitrate(bitrate) {
    const sender = pc.getSenders().find(s => s.track.kind === 'video');
    const parameters = sender.getParameters();
    parameters.encodings[0].maxBitrate = bitrate;
    sender.setParameters(parameters);
}
避坑指南: 我曾经在弱网环境下直接降低码率,结果画面反而更卡了。原因是码率降得太低,编码器为了维持帧率,把分辨率也降了,导致画面模糊。后来我改成先降帧率再降码率,效果好了很多。

知识体系总览

下面这张图概括了本章的核心逻辑:

音视频编解码知识体系 编解码核心 常用编解码器 SDP协商过程 Simulcast 码率自适应 VP8 / H.264 VP9 / AV1 Offer / Answer profile-level-id 多分辨率流 SVC分层编码 GCC算法 动态调整码率 降帧率 vs 降码率 核心目标:在有限带宽下提供最佳用户体验

嗯,编解码这块内容确实不少。但说白了,你只要掌握三个核心点:

  • 选对编解码器: 根据平台和场景选择VP8或H.264
  • 理解协商流程: SDP Offer/Answer是基础,参数匹配是关键
  • 做好码率控制: Simulcast或SVC选一个,配合码率自适应算法

我在实际项目中,通常先用VP8做原型,稳定后再根据需求加入H.264和Simulcast。码率自适应这块,内置的GCC够用就别自己折腾,除非你有特殊场景。

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