12、音视频编解码器:WebRTC 的“翻译官”们
聊到 WebRTC 的编解码器,我脑子里第一个蹦出来的画面,是几年前一个深夜的线上会议。画面卡成了 PPT,声音像机器人说话。我盯着控制台,发现是 H264 编码器在低带宽下直接“罢工”了。嗯,从那以后,我对编解码器的敬畏心就拉满了。
说白了,编解码器就是音视频数据的“翻译官”。摄像头采集的原始视频,一秒钟的数据量能到几百 MB,不压缩根本没法传。编解码器负责把它压缩(编码),到了对端再解压(解码)。WebRTC 支持哪几个翻译官?它们各自什么脾气?怎么让它们好好干活?这就是今天要聊的。
12.1 WebRTC 支持的编解码器全家福
WebRTC 标准里,浏览器必须支持哪些编解码器,其实是有“硬性规定”的。但不同浏览器、不同设备,支持的列表会有差异。我个人习惯把常用的几个列成一张表,方便对照:
| 编解码器 | 类型 | 浏览器支持情况 | 我的评价 |
|---|---|---|---|
| VP8 | 视频 | Chrome、Firefox、Edge 全支持 | 老黄牛,兼容性最好,性能开销低 |
| VP9 | 视频 | Chrome、Firefox 支持,Safari 部分支持 | 压缩率比 VP8 高 30%-50%,但编码慢 |
| H264 | 视频 | Chrome、Safari、Edge 全支持 | 硬件编码器普及,低功耗首选 |
| AV1 | 视频 | Chrome 支持,其他浏览器逐步跟进 | 压缩率之王,但编码计算量巨大 |
| Opus | 音频 | 所有浏览器全支持 | 音频界的万金油,低延迟高音质 |
核心观点: 别盲目追求最新。AV1 压缩率虽高,但手机端编码一帧可能要几十毫秒,实时通话根本扛不住。我建议优先选 VP8 或 H264,兼容性和性能最平衡。
12.2 编解码器协商:RTCRtpCodecCapability
你想想看,两个浏览器要通话,总得先打个招呼:“我支持 VP8、H264,你支持啥?” 这个“打招呼”的过程,就是编解码器协商。WebRTC 里通过 RTCRtpCodecCapability 来描述能力。
我曾经踩过一个坑:在 Chrome 里用 getCapabilities() 查到的编码器列表,跟实际能用的不一样。原因是 Chrome 的 H264 编码器需要特定硬件支持,软件模式下可能不暴露。所以,别完全相信能力查询的结果,最好做一次实际编码测试。
代码示例:查看本地支持的视频编码器
// 获取视频发送端的能力
const capabilities = RTCRtpSender.getCapabilities('video');
console.log('支持的视频编解码器:');
capabilities.codecs.forEach(codec => {
console.log(`${codec.mimeType},时钟频率:${codec.clockRate}`);
// 输出示例:video/VP8,时钟频率:90000
// video/H264,时钟频率:90000
});
协商的底层逻辑是这样的:
- 发起方(offer)会把自己支持的编解码器列表,按优先级排序,塞进 SDP 里。
- 应答方(answer)收到后,从列表里挑一个自己也能支持的,作为最终使用的编码器。
- 如果双方没有交集,协商失败,连接建立不了。
小技巧: 如果你想强制使用某个编码器,可以在创建 offer 之前,手动修改 SDP 里的编解码器顺序,把目标编码器提到最前面。但注意,这属于“黑科技”,不同浏览器行为可能不一致。
12.3 编解码器参数配置与优化
选定了编码器,只是第一步。真正决定通话质量的,是参数配置。我见过太多人直接用默认参数,结果在弱网下画质糊成一片。
12.3.1 视频编码器核心参数
以 H264 为例,几个关键参数:
- profile(档次): baseline、main、high。baseline 兼容性最好,但压缩率低。我建议移动端用 baseline,桌面端用 high。
- level(级别): 决定了分辨率、帧率的上限。比如 level 3.1 支持 720p@30fps。
- bitrate(码率): 直接决定画质。码率太低会出马赛克,太高会卡顿。
- keyFrameInterval(关键帧间隔): 默认可能 2 秒一个关键帧。弱网下我建议缩短到 1 秒,减少丢帧后的等待时间。
配置示例:通过 RTCRtpEncodingParameters 设置
const sender = peerConnection.addTrack(videoTrack, stream);
const parameters = sender.getParameters();
// 设置编码参数
parameters.encodings[0].maxBitrate = 500000; // 500kbps
parameters.encodings[0].minBitrate = 100000; // 100kbps
parameters.encodings[0].maxFramerate = 30;
parameters.encodings[0].scaleResolutionDownBy = 1.0; // 不缩放
// 应用参数
await sender.setParameters(parameters);
注意: setParameters() 不是所有浏览器都支持动态修改。我建议在建立连接前就配置好,连接建立后尽量少改,否则可能触发编码器重启,导致短暂黑屏。
12.3.2 音频编码器 Opus 的优化
Opus 是音频界的“瑞士军刀”。它支持从 6 kbps 到 510 kbps 的码率,采样率从 8 kHz 到 48 kHz。我一般这样配置:
- 语音通话: 码率 32 kbps,采样率 16 kHz,够用且省带宽。
- 音乐场景: 码率 128 kbps,采样率 48 kHz,保证音质。
- 弱网环境: 开启 FEC(前向纠错),码率降到 24 kbps,牺牲一点音质换流畅。
配置 Opus 的示例:
const audioSender = pc.addTrack(audioTrack, stream);
const audioParams = audioSender.getParameters();
// 设置 Opus 的 FEC 和码率
audioParams.encodings[0].maxBitrate = 32000;
// 通过 codec 参数设置 FEC
audioParams.codecs[0].parameters['useinbandfec'] = '1';
audioParams.codecs[0].parameters['maxplaybackrate'] = '16000';
await audioSender.setParameters(audioParams);
12.4 知识体系:编解码器选择与协商流程
下面这张图,是我自己画的一个决策流程。每次做新项目,我都会拿出来对照一遍:
12.5 实战中的几个建议
最后,分享几个我在项目中积累的经验:
- 优先用 H264: 大部分手机和笔记本都有 H264 硬件编码器,功耗低、发热小。VP8 虽然兼容,但纯软件编码,手机发烫严重。
- 码率不要设死: 我习惯用
maxBitrate设上限,让编码器自己根据网络动态调整。设死码率,网络波动时画面直接崩。 - 音频比视频重要: 视频卡了还能忍,音频卡了通话直接中断。我会给 Opus 开 FEC,哪怕视频降到 15 帧,音频也要保证清晰。
- 测试要覆盖弱网: 我曾经在办公室测得好好的,上线后用户在农村用 3G 网络,画面全是马赛克。后来加了码率自适应,才解决问题。
一句话总结: 编解码器没有绝对的好坏,只有合不合适的场景。理解每个编码器的脾气,再根据网络和设备动态调整,才能做出流畅的通话体验。
公众号:蓝海资料掘金营,微信deep3321