9、编解码器协商:RTCRtpCodecCapability、编解码器优先级排序、H264/VP8/VP9/AV1的SDP参数

编解码器协商,说白了就是通话双方在握手阶段互相喊话:

「我支持 H264,你支持啥?」

「我支持 VP8 和 VP9,但我更想用 VP9。」

「行,那就 VP9 吧。」

这个过程看似简单,但坑不少。我做过一个跨平台项目,Android 端默认只开了 H264,iOS 端只开了 VP8,结果两边互相等对方先开口,愣是协商了 5 秒才出画面。嗯,这就是编解码器没对齐的典型问题。

9.1 RTCRtpCodecCapability:浏览器能做什么

在 WebRTC 里,RTCRtpCodecCapability 描述的是「浏览器底层能力」。你可以通过 RTCRtpSender.getCapabilities('video') 拿到它。

举个例子,我在 Chrome 上跑了一下:

const caps = RTCRtpSender.getCapabilities('video');
console.log(caps.codecs);

返回的数组里,每个元素长这样:

{
  mimeType: 'video/VP8',
  clockRate: 90000,
  channels: undefined,
  sdpFmtpLine: undefined
}

这里要注意几点:

  • mimeType:格式是 video/CODEC,比如 video/H264video/VP9
  • clockRate:视频编码器固定 90000,这是 RTP 时间戳的基准频率。
  • sdpFmtpLine:这个字段很关键,它对应 SDP 里的 a=fmtp 行。比如 H264 的 profile-level-id、packetization-mode 都在这里。
个人经验: 我习惯在建立连接前先打印 getCapabilities,看看浏览器到底支持哪些编码器。有一次发现某款国产浏览器居然不支持 VP9,排查了半天才发现是浏览器阉割了。

9.2 编解码器优先级排序:谁先谁后

协商时,Offer 方会把自己支持的编码器按优先级排好序。优先级高的写在 SDP 前面。

排序逻辑一般是:

  1. 带宽效率:同等画质下,码率越低越优先。AV1 > VP9 > H264 > VP8。
  2. 硬件支持:如果设备有硬件编码器,优先用硬件编码。
  3. 兼容性:有些老设备只支持 H264,那就得把 H264 往前放。

我做过一个视频会议项目,默认排序是 VP9 > H264 > VP8。结果发现 VP9 在低端手机上编码延迟太高,画面卡得不行。后来改成 H264 > VP9 > VP8,体验反而好了。你想想看,优先级不是越高越好,得看场景。

核心原则: 编解码器优先级 = 带宽效率 × 硬件兼容性 × 实时性要求。

9.3 H264 的 SDP 参数:profile-level-id 和 packetization-mode

H264 的 SDP 参数最复杂,因为它的配置项多。典型的 a=fmtp 行长这样:

a=fmtp:96 profile-level-id=42e01f;packetization-mode=1

我来拆解一下:

  • profile-level-id:6 位十六进制数,前两位是 profile,中间两位是约束,后两位是 level。比如 42e01f 表示 Baseline profile、level 3.1。
  • packetization-mode:0 表示单 NALU 模式,1 表示非交错模式(常用),2 表示交错模式(很少用)。

我曾经踩过一个坑:两个浏览器都支持 H264,但一个设了 packetization-mode=1,另一个没设。结果协商失败,画面一直黑屏。后来我强制在 Offer 里加上 packetization-mode=1,问题解决。

注意: H264 的 profile-level-id 必须严格匹配。如果 Offer 方写的是 42e01f,Answer 方只能回复相同或更低的 profile/level。回复更高的会被拒绝。

9.4 VP8 的 SDP 参数:简单但别忽略

VP8 的 SDP 参数相对简单,a=fmtp 行通常只有:

a=fmtp:96 max-fr=30;max-fs=3600
  • max-fr:最大帧率,单位是 1/256 帧每秒。30 表示 30fps。
  • max-fs:最大帧大小,单位是宏块数。3600 对应 720p 左右。

嗯,这里要注意:VP8 的参数虽然少,但 max-fs 如果设得太小,高分辨率画面会被截断。我见过有人设成 1200,结果 720p 视频只显示了左上角一块。

9.5 VP9 的 SDP 参数:profile-id 和 scalability

VP9 比 VP8 多了 profile 的概念。SDP 里常见:

a=fmtp:96 profile-id=0
  • profile-id:0 是基础版(8-bit 4:2:0),1 是 8-bit 4:2:2/4:4:4,2 是 10-bit 4:2:0,3 是 10-bit 4:2:2/4:4:4。

VP9 还支持 SVC(可伸缩编码),参数里会有 max-layersmax-temporal-layers 等。不过目前大多数 WebRTC 实现只用单层。

我个人建议:除非你明确需要 10-bit 色深,否则 VP9 用 profile-id=0 就够了。profile 越高,解码器要求越苛刻。

9.6 AV1 的 SDP 参数:新贵但参数少

AV1 是新生代编码器,SDP 参数目前很简单:

a=fmtp:96 profile=0;level=5;tier=0
  • profile:0 是 Main profile,1 是 High profile。
  • level:对应分辨率上限,level 5 支持 4K。
  • tier:0 是 Main tier,1 是 High tier(更高码率)。

AV1 的编码复杂度高,目前硬件支持还不多。我在项目中试过软件 AV1 编码,CPU 直接飙到 90%。嗯,现阶段还是建议当备选。

9.7 核心知识图谱

下面这张图总结了编解码器协商的完整流程:

编解码器协商流程 1. 获取能力 getCapabilities() 2. 优先级排序 带宽效率 > 硬件支持 3. 生成Offer SDP 参数填充 4. 接收Answer 匹配第一个编码器 5. 匹配检查 profile/level/参数一致? 6. 建立连接 编码器初始化完成 各编码器关键参数 H264 profile-level-id packetization-mode VP8 max-fr, max-fs VP9 profile-id max-layers (SVC) AV1 profile, level, tier

9.8 避坑指南

最后,分享几个我实际项目中遇到的坑:

  • H264 的 profile 不匹配:Offer 方写 42e01f,Answer 方只支持 42001f,协商失败。解决办法是 Offer 里多写几个 profile。
  • VP8 的 max-fs 设太小:导致 1080p 画面被裁剪。建议设成 8192 以上。
  • AV1 的 level 设太高:老设备解码器不支持 level 5,降级到 level 3 就好了。
  • 编码器顺序写反:把 VP8 写在 H264 前面,结果低端设备被迫用 VP8 软件编码,CPU 爆炸。
总结一句话: 编解码器协商不是「谁支持谁就用」,而是「在双方都支持的前提下,选一个对当前场景最优的」。参数匹配、优先级排序、硬件兼容性,三者缺一不可。

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