跳到主要内容

编解码与协商

WebRTC 在编解码上是个协商系统:发送端能编、接收端能解、双方都同意,才能用。本文给出选型矩阵、跨浏览器现状(2026),以及如何用标准 API 控制 codec 选择。

1. 主流编解码器现状(2026-06)

1.1 视频

CodecChrome / EdgeFirefoxSafari (iOS/macOS)硬件加速备注
VP8✅ 默认✅ 默认部分平台WebRTC 历史默认,兼容性最好
VP9✅(Safari 16+)部分 GPU比 VP8 省 30% 码率,支持 SVC
H.264✅ 必有几乎全平台唯一 Safari 硬解 codec,必带
H.265 / HEVC⚠️ 受限Apple 硬件专利/许可复杂,WebRTC 罕见
AV1✅(默认开启)⚠️(17.4+ 部分)新 GPU最新一代,省码率明显

1.2 音频

Codec支持备注
Opus全平台WebRTC 默认音频 codec,6~510 kbps 自适应
G.722多数VoIP 互通
PCMU/PCMA多数与传统电话互通用
iSAC/iLBC已废弃不要再用
L16受限无损,带宽高

结论

  • 音频默认 Opus,没什么可选的。
  • 视频默认 VP8 兼容稳,要互通 Safari 必带 H.264,码率敏感场景上 VP9,新业务可以试 AV1。

2. 协商怎么发生

A 端 B 端
─── createOffer ──────────────────────► setRemoteDescription
SDP 列出 A 支持的所有 codec B 比较自己的能力
生成 Answer,**只保留双方都支持的 codec**
setRemoteDescription ◄──── createAnswer

每路 m-line 的 SDP 里 m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 后面的 PT 列表,顺序就是发送端的偏好。Answer 端按自己能力裁剪并保留发送端给出的顺序。

3. 用标准 API 控制 codec(不要 munging SDP)

3.1 查询能力

const sendCaps = RTCRtpSender.getCapabilities('video');
const recvCaps = RTCRtpReceiver.getCapabilities('video');
console.log(sendCaps.codecs);
// [{mimeType: 'video/VP8', clockRate: 90000, ...}, ...]

3.2 设置偏好顺序

const tr = pc.addTransceiver('video', { direction: 'sendrecv' });

const codecs = RTCRtpSender.getCapabilities('video').codecs;
// 把 VP9 排到最前
const preferred = [
...codecs.filter(c => c.mimeType === 'video/VP9'),
...codecs.filter(c => c.mimeType === 'video/VP8'),
...codecs.filter(c => c.mimeType === 'video/H264'),
...codecs.filter(c => !['video/VP9','video/VP8','video/H264'].includes(c.mimeType)),
];
tr.setCodecPreferences(preferred);

下次 createOffer 生成的 SDP 自动按此顺序排列。

3.3 强制单一 codec(用于排障 / 兼容)

tr.setCodecPreferences(codecs.filter(c => c.mimeType === 'video/H264'));

4. H.264 的 profile 坑

H.264 在 SDP 里通过 profile-level-id 参数细分变体:

a=fmtp:97 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
profile-level-id含义适用
42e01fConstrained Baseline 3.1WebRTC 推荐,全平台兼容
42001fBaseline 3.1老设备
4d0032Main 5.0高分辨率
640032High 5.0录播质量

Safari 早期版本只支持 42e01f,跨 Safari 互通必须用这个 profile。packetization-mode=1 也是 WebRTC 强制要求。

5. AV1:什么时候用

适用:

  • 屏幕共享(AV1 对静态/低运动内容压缩比 VP9 提升 50%+)。
  • 端到端都是较新的 Chrome / Edge。
  • 业务对码率敏感(直播 CDN 成本)。

不适用:

  • 跨 Safari、老移动端互通。
  • 低端 CPU(AV1 软解功耗高)。
  • 没有硬件加速的设备。
// 只给屏幕共享通道用 AV1,摄像头通道用 VP9
const screenTr = pc.addTransceiver(screenTrack);
screenTr.setCodecPreferences(prefer('AV1'));

const camTr = pc.addTransceiver(cameraTrack);
camTr.setCodecPreferences(prefer('VP9'));

6. RTX、FEC、扩展头

每个视频 codec 通常配套:

a=rtpmap:96 VP8/90000
a=rtpmap:97 rtx/90000 # 重传 codec
a=fmtp:97 apt=96
a=rtpmap:98 red/90000 # 冗余编码
a=rtpmap:99 ulpfec/90000 # 前向纠错
  • RTX:NACK 重传走的独立 SSRC,把重传包和原始流分开,方便丢包分析。
  • RED + ULPFEC:丢包率高时启用,预先发冗余包让接收端不靠重传也能恢复。Chrome 默认对音频开 RED,对视频通常关。

详见 04-媒体引擎与QoS/抗丢包NACK-RTX-FEC.md

7. 编码参数控制

const sender = pc.getSenders().find(s => s.track?.kind === 'video');
const params = sender.getParameters();
params.encodings[0].maxBitrate = 1_500_000;
params.encodings[0].maxFramerate = 30;
params.encodings[0].priority = 'high'; // 'very-low' / 'low' / 'medium' / 'high'
params.encodings[0].networkPriority= 'high';
params.encodings[0].scaleResolutionDownBy = 1; // 2 = 降一半分辨率
params.encodings[0].active = true;
params.degradationPreference = 'maintain-framerate'; // 拥塞时优先保帧率
await sender.setParameters(params);

degradationPreference

拥塞时策略
maintain-framerate优先保帧率,降分辨率
maintain-resolution优先保分辨率,降帧率
balanced(默认)平衡

经验:屏幕共享用 maintain-resolution,会议视频用 maintain-framerate

8. Opus 调参

// 通过 SDP 的 fmtp 设置 Opus(这一类是少数推荐显式 munging 的场景,
// 浏览器目前没有完整 API 暴露所有 Opus 参数)
function setOpusFec(sdp) {
return sdp.replace(/(a=fmtp:111 [^\r\n]+)/, '$1;useinbandfec=1;usedtx=1');
}
参数含义推荐
useinbandfec=1启用前向纠错通话场景必开
usedtx=1静音时降码率通话场景开
stereo=1立体声音乐场景开,通话场景
maxaveragebitrate=128000最大平均码率高质量场景

注意:Opus 参数是少数浏览器没有完整 API 的领域,社区容忍对 SDP 的 fmtp 行做精确替换,但仍要做版本回归。

9. 跨浏览器兼容矩阵速记

场景配方
1v1 通话,浏览器都未知VP8(首选)+ H.264(兜底)+ Opus
全 Chrome 内部系统VP9 / AV1 + Opus
必须支持 iOS SafariH.264 (42e01f, packetization-mode=1) + Opus
屏幕共享AV1(同 Chrome)/ VP9(混合)+ Opus DTX
与传统电话互通G.722 / PCMU + H.264

10. 排查 codec 问题

// 看实际协商出的 codec
const stats = await pc.getStats();
for (const r of stats.values()) {
if (r.type === 'codec') {
console.log(r.mimeType, r.payloadType, r.sdpFmtpLine);
}
}

// 看实际收发的 codec
for (const r of stats.values()) {
if (r.type === 'inbound-rtp' && r.kind === 'video') {
const codec = stats.get(r.codecId);
console.log('对端发的 codec:', codec?.mimeType);
}
}

11. 反模式

反模式后果修复
用正则改 m-line 里 codec 顺序浏览器升级即坏setCodecPreferences
H.264 不指定 42e01fSafari 互通失败只发 baseline 兼容 profile
全场景默认开 AV1iOS 用户看不到按平台动态选
强制 stereo Opus 做语音浪费一倍带宽通话场景 mono
关闭 FEC 想省带宽弱网用户体验崩FEC 是雪中送炭

12. 权威资料