跳到主要内容

WebRTC 全景实战 (9):音视频编解码与 Simulcast 入门

Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡
Rainy
19 MIN READ... VIEWS

Ch4 SDP 协商的核心是编解码器选择——a=rtpmap 行的背后,是数十年来视频压缩技术的积累。Ron Frederick 在 1992 年为实现 nv 工具而手写软件视频压缩Curious — RTP 历史),因为 MPEG-1 当时无法实时编码——今天 WebRTC 的 Codec 选择同样是在压缩率、延迟、专利、硬件加速之间权衡。

音频方面,Opus 的故事同样精彩:Skype 团队在收购后于 2010 年 IETF 会议上推动 Opus 标准化,Maastricht 午餐会时已完成大部分工作(Curious 历史)。Opus 继承了 Skype 的 SILK 和 Xiph 的 CELT 两条技术路线,成为 WebRTC 唯一的「指定音频编解码器」。

本章覆盖 RFC 7587 Opus、VP8/VP9/H.264/AV1 对比、Simulcast 三档发布与 RTCRtpEncodingParameters 实战配置。


零、Skype 与 Opus 的诞生

Curious 历史 记录了 Opus 标准化过程中一段有趣的插曲。2010 年 IETF 81 会议在 Maastricht 举行,Skype 工程师 Jean-Marc Valin 和 Koen Vos 带着几乎完成的 Opus 草案参会——午餐桌上就完成了大部分互操作测试

技术 lineage来源擅长场景
SILKSkype 收购前自研8–40 kbps 语音,强抗丢包
CELTXiph.Org(Ogg/Vorbis 团队)48–510 kbps 音乐,低延迟
Opus2012 合并标准化全码率覆盖,WebRTC 指定音频 Codec

Microsoft 2011 年收购 Skype 后,Skype 团队将 SILK 技术贡献给 IETF,与开源社区 CELT 合并为 Opus。RFC 7587 2015 年发布,RFC 8871 将 Opus 列为 WebRTC Mandatory to Implement (MTI) 音频编解码器——所有 WebRTC 实现必须支持 Opus。

视频侧,Ron Frederick 1992 年为 nv 工具手写软件视频压缩(因为 MPEG-1 无法实时编码),这与今天 VP8/AV1 追求「实时 + 高压缩 + 免专利」的路线一脉相承。


本篇术语表

术语英文解释
CodecCoder-Decoder编解码器,压缩/解压媒体数据
OpusWebRTC 标准音频编解码器,RFC 7587
VP8Google 开源视频编解码器,RFC 7741
VP9VP8 继任者,更高压缩率,无 royalty
H.264/AVCITU/MPEG 标准,硬件加速最广泛
AV1AOMedia 开源编解码器,最高压缩率
Simulcast同时编码发送同一视频的多个分辨率层
ridRTP Stream IDSimulcast 层的标识符(h/m/l)
SSRCSynchronization Source每层 Simulcast 分配独立 SSRC
RTCRtpEncodingParameters控制每路编码的码率、分辨率、rid
scaleResolutionDownBy相对原始分辨率的下采样倍数
maxBitrate该层编码的最大码率上限
active该层是否激活发送
KeyframeI 帧 / IDR可独立解码的完整帧
DTXDiscontinuous Transmission静音时停止发送,节省带宽
FECForward Error Correction带内前向纠错
profile-level-idH.264 的 profile 与 level 标识
contentHint提示编码器内容类型:motion / detail / text
Unified PlanWebRTC 现代 SDP 语义,Simulcast 必须使用
MTIMandatory to ImplementWebRTC 强制实现的编解码器

一、编解码在媒体管线中的位置

编解码器是 WebRTC 媒体管线中CPU/GPU 消耗最大的环节。选择 Codec 不仅影响画质,还决定了:

  • 端到端延迟(编码复杂度)
  • 带宽消耗(压缩效率)
  • 设备兼容性(硬件加速支持)
  • 专利成本(H.264 vs royalty-free)

二、音频:Opus(RFC 7587)

WebRTC 音频几乎总是 Opus——Skype 团队在 IETF 标准化,2012 年发布 RFC 6381,2015 年更新为 RFC 7587

2.1 Opus 的双模架构

Opus 是两种编解码器的结合:

特性说明
Bitrate6–510 kbps 自适应
帧长2.5ms / 5ms / 10ms / 20ms / 40ms / 60ms
FEC内置前向纠错,抗丢包
DTX静音检测,停止发送节省带宽
采样率8kHz–48kHz(WebRTC 固定 48kHz)
声道1(mono)或 2(stereo)

2.2 SDP 中的 Opus 参数

a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1;stereo=1;maxaveragebitrate=32000
fmtp 参数含义
minptime=10最小打包时长 10ms
useinbandfec=1启用带内 FEC
stereo=1声明支持立体声
maxaveragebitrate=32000限制平均码率 32kbps
cbr=1恒定码率模式
usedtx=1启用 DTX 静音检测

2.3 Opus 码率与质量

场景推荐码率帧长
窄带语音(电话)12–20 kbps20ms
宽带语音(会议)24–32 kbps20ms
高质量语音48–64 kbps20ms
音乐 / 屏幕共享音频64–128 kbps20ms
// 限制 Opus 码率
const sender = pc.getSenders().find((s) => s.track?.kind === "audio");
const params = sender.getParameters();
params.encodings[0].maxBitrate = 32_000;
await sender.setParameters(params);

2.4 RED(冗余编码)

SDP 中常见的 RED payload type 是 Opus 的冗余封装:

a=rtpmap:63 red/48000/2
a=fmtp:63 111/111/111/111/111

RED 将多个 Opus 帧打包在一个 RTP 包中,提供包级别的冗余(与 in-band FEC 互补)。WebRTC 中 RED 的使用因浏览器而异。

2.5 Opus 编码模式选择

Opus 编码器根据码率和内容自动切换 SILK/CELT/Hybrid 模式,但应用层可通过 SDP 和参数施加约束:

模式触发条件典型码率
SILK语音主导,低码率6–40 kbps
Hybrid语音 + 宽带32–64 kbps
CELT音乐/高保真64–510 kbps
// 屏幕共享时音频轨 often 是系统音——提高码率上限
const audioSender = pc.getSenders().find((s) => s.track?.kind === "audio");
const audioParams = audioSender.getParameters();
audioParams.encodings[0].maxBitrate = 128_000;
await audioSender.setParameters(audioParams);

三、视频编解码对比

3.1 详细对比表

Codec压缩率编码延迟硬件加速专利WebRTC 支持推荐场景
VP8广泛royalty-free全平台默认首选、低延迟
VP9高(比 VP8 约 30-50%)较广泛royalty-free桌面为主带宽受限、高分辨率
H.264低(硬编)最广泛有专利池全平台移动端、硬编优先
AV1最高(比 H.264 约 30-50%)高(软编)新兴royalty-freeChrome 117+未来方向、带宽极受限

3.2 VP8(RFC 7741)

Google 2010 年收购 On2 后开源 VP8,成为 WebRTC 最早的视频编解码器。

特性说明
最大分辨率16384×16384(实际受设备限制)
关键帧间隔可配置,WebRTC 默认约 3 秒或 PLI 触发
错误恢复黄金帧(Golden Frame)机制
RTP 打包单帧可拆多个 RTP 包,M bit 标记最后一包
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 transport-cc

3.3 VP9

VP9 是 VP8 的继任者,主要优势在高分辨率带宽节省

对比 VP8VP9
同画质码率降低 30-50%
编码 CPU高约 2-3x
SVC 支持原生 SVC(Ch11
硬件加速Android 较广泛,iOS 不支持
a=rtpmap:98 VP9/90000
a=fmtp:98 profile-id=0

3.4 H.264/AVC

H.264 是 ITU-T 和 MPEG 联合标准,硬件加速支持最广泛——几乎所有手机芯片都有 H.264 硬编硬解。

a=rtpmap:102 H264/90000
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
fmtp 参数含义
profile-level-id=42e01fBaseline Profile, Level 3.1
packetization-mode=1非交错模式(WebRTC 要求)
level-asymmetry-allowed=1允许双方 level 不对称

profile-level-id 解码

42e01f →
42 = Baseline Profile (0x42)
e0 = constraint_set flags
1f = Level 3.1 (0x1f = 31)
H.264 Profile 匹配

双方 profile-level-id 必须兼容。Offer 中列出多个 H.264 PT(不同 profile),Answer 选择双方都支持的那个。profile 不匹配是 H.264 协商失败的首要原因。

3.5 AV1

AV1 由 AOMedia(Google/Mozilla/Netflix 等)开发,压缩率最高但编码复杂度也最高。

特性说明
压缩率比 H.264 高 30-50%,比 VP9 高 20-30%
编码速度软编极慢(实时场景需硬编)
硬件加速Intel/AMD/NVIDIA 新一代 GPU 开始支持
WebRTCChrome 117+ 支持,Safari/Firefox 逐步跟进
a=rtpmap:41 AV1/90000
a=fmtp:41 profile=0;level-idx=5;tier=0

3.6 Codec 选择策略

// 设置 Codec 偏好
const transceiver = pc.addTransceiver("video", { direction: "sendrecv" });
const caps = RTCRtpSender.getCapabilities("video");

// 优先 H.264,其次 VP8
const preferred = caps.codecs.sort((a, b) => {
const order = { "video/H264": 0, "video/VP8": 1, "video/VP9": 2, "video/AV1": 3 };
return (order[a.mimeType] ?? 99) - (order[b.mimeType] ?? 99);
});
transceiver.setCodecPreferences(preferred);

3.7 屏幕共享 vs 摄像头:contentHint

屏幕共享(getDisplayMedia)与摄像头的内容特性截然不同——静态文字需要 sharp edges,摄像头需要运动平滑:

const screenTrack = screenStream.getVideoTracks()[0];
screenTrack.contentHint = "detail"; // 或 "text" / "motion"

const cameraTrack = cameraStream.getVideoTracks()[0];
cameraTrack.contentHint = "motion";
contentHint编码器行为适用
motion优先帧率,允许模糊摄像头、游戏
detail优先清晰度,保文字边缘屏幕共享
text最高清晰度,低帧率可接受文档/代码演示

3.8 H.264 与 Simulcast 的限制

H.264 硬件编码器通常不支持 Simulcast 多路独立编码——同一时刻只能输出一路。Chrome 在 H.264 Simulcast 场景可能回退到软编或只发送单层。生产会议系统常见策略:

策略说明
VP8/VP9 Simulcast桌面端默认,多层无专利顾虑
H.264 单流 + SFU 转码移动端发送 H.264,SFU 转码给其他订阅者
SVC(VP9/AV1)单编码多分层,见 Ch11

四、Simulcast 三档发布

Simulcast = 同时编码并发送同一视频的多个分辨率/码率层,SFU 按订阅者带宽选择转发哪一层(Ch11 详解 SFU 侧路由)。

4.1 为什么需要 Simulcast?

场景无 Simulcast有 Simulcast
100 人会议,带宽各异所有人收到相同质量每人按带宽收不同层
大屏 + 小窗小窗浪费高分辨率带宽小窗收 l 层
带宽波动重协商 Codec/分辨率SFU 无缝切换层

Simulcast 的代价是发送端 CPU/带宽:三档意味着编码器运行 3 次。但发送端只需上行 1.5Mbps(最高层),而非 100 × 1.5Mbps——这就是 SFU 架构的优势。

Unified Plan 是前提

Simulcast 要求 Unified Plan SDP 语义(现代浏览器默认)。Plan B 已废弃——若 pc.getConfiguration().sdpSemantics !== 'unified-plan',Simulcast 不会生效。每个 rid 对应 RTCRtpSender 的一个 encoding,而非独立 m-line。

4.2 RTCRtpEncodingParameters 实战

const transceiver = pc.addTransceiver("video", {
direction: "sendonly",
sendEncodings: [
{
rid: "h",
maxBitrate: 1_500_000,
maxFramerate: 30,
scaleResolutionDownBy: 1.0,
active: true,
},
{
rid: "m",
maxBitrate: 500_000,
maxFramerate: 15,
scaleResolutionDownBy: 2.0,
active: true,
},
{
rid: "l",
maxBitrate: 150_000,
maxFramerate: 7.5,
scaleResolutionDownBy: 4.0,
active: true,
},
],
});

4.3 EncodingParameters 字段详解

字段类型含义示例
ridstringRTP Stream ID,标识 Simulcast 层"h" / "m" / "l"
activeboolean是否激活该层发送true
maxBitratenumber最大码率(bps)1500000
minBitratenumber最小码率(bps)30000
maxFrameratenumber最大帧率30
scaleResolutionDownBynumber下采样倍数(相对原始)2.0 = 宽高各减半
prioritystring层优先级"high" / "low"
networkPrioritystring网络优先级"high" / "low"
ssrcnumber手动指定 SSRC(通常自动生成)

4.4 SDP 中的 Simulcast 协商

Offer 中 Simulcast 相关属性:

a=simulcast:send h;m;l
a=rid:1 send h
a=rid:2 send m
a=rid:3 send l
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
属性含义
a=simulcast:send h;m;l发送三档,rid 分别为 h/m/l
a=rid:1 send hrid=h 映射到 mid 内的层 1
rtp-stream-id extmapRTP 头扩展携带 rid
repaired-rtp-stream-idRTX 重传时关联原始 rid

4.5 动态层管理

// 运行时关闭低质量层(节省 CPU)
const sender = pc.getSenders().find((s) => s.track?.kind === "video");
const params = sender.getParameters();

params.encodings.forEach((enc) => {
if (enc.rid === "l") {
enc.active = false; // 关闭 l 层
}
});
await sender.setParameters(params);

// 运行时调整码率
params.encodings.forEach((enc) => {
if (enc.rid === "h") {
enc.maxBitrate = 2_000_000; // 提升 h 层到 2Mbps
}
});
await sender.setParameters(params);

4.6 Simulcast vs SVC

特性SimulcastSVC(Ch11
编码次数N 次(每层独立)1 次(分层编码)
CPU 消耗
SFU 复杂度按 SSRC 选层按 temporal/spatial layer 选层
兼容性全平台需要 Codec 支持 SVC(VP9/AV1)
灵活性每层独立参数层间有依赖关系

五、码率控制

WebRTC 的码率控制是多层协作的:

5.1 应用层码率限制

const sender = pc.getSenders().find((s) => s.track?.kind === "video");
const params = sender.getParameters();

// 限制所有层
params.encodings.forEach((enc) => {
enc.maxBitrate = 500_000;
});
await sender.setParameters(params);

5.2 GCC 自适应

GCC(Ch10)会在 maxBitrate 之下进一步自适应:

实际发送码率 = min(maxBitrate, GCC估计带宽, CPU允许码率)

getStats()qualityLimitationReason 会告诉你瓶颈在哪:

含义
bandwidthGCC 降低了码率
cpu编码器跟不上,主动降质
none无限制

5.3 初始码率与快速启动

// 部分浏览器支持 degradationPreference
const params = sender.getParameters();
params.degradationPreference = "maintain-framerate";
// 其他选项: "maintain-resolution" | "balanced"
await sender.setParameters(params);
degradationPreference行为
maintain-framerate带宽不足时降分辨率,保帧率
maintain-resolution带宽不足时降帧率,保分辨率
balanced帧率和分辨率等比降低

六、编解码器协商过程

6.1 查看浏览器能力

const audioCodecs = RTCRtpSender.getCapabilities("audio").codecs;
const videoCodecs = RTCRtpSender.getCapabilities("video").codecs;

console.log("Audio:", audioCodecs.map((c) => c.mimeType));
console.log("Video:", videoCodecs.map((c) =>
`${c.mimeType} pt=${c.preferredPayloadType} hw=${c.hardwareAccelerated}`
));

6.2 强制特定 Codec

const transceiver = pc.addTransceiver("video", { direction: "sendrecv" });
const caps = RTCRtpSender.getCapabilities("video");

// 只保留 VP8
const vp8Only = caps.codecs.filter((c) => c.mimeType === "video/VP8");
transceiver.setCodecPreferences(vp8Only);

七、常见问题与排查

问题原因解决
无 Simulcast 层未用 Unified Plan + sendEncodingsaddTransceiver + rid
SDP 无 a=simulcast浏览器不支持或只有 1 层确认 Chrome 72+ / Firefox 66+
H.264 协商失败profile-level-id 不匹配检查 fmtp 参数兼容性
画质模糊码率过低或 scaleResolutionDownBy 过大提高 maxBitrate
编码 CPU 过高Simulcast 3 层 + 软编减少层数或启用硬编
AV1 不生效浏览器/设备不支持回退 VP9/VP8
音频断断续续Opus 码率过低提高 maxBitrate 到 32kbps+
setParameters 失败参数不合法或顺序错误先 getParameters 再修改

7.1 Simulcast 不生效排查

// 1. 确认 SDP 中有 simulcast 属性
console.log(pc.localDescription.sdp.includes("simulcast"));

// 2. 确认有 3 个 outbound-rtp
const stats = await pc.getStats();
let outboundCount = 0;
stats.forEach((r) => {
if (r.type === "outbound-rtp" && r.kind === "video") outboundCount++;
});
console.log("Outbound video streams:", outboundCount); // 应为 3

// 3. 确认 rid 存在
stats.forEach((r) => {
if (r.type === "outbound-rtp" && r.kind === "video") {
console.log("rid:", r.rid, "ssrc:", r.ssrc, "bitrate:", r.targetBitrate);
}
});

八、实战 Lab

Lab 1:Codec 能力探测

  1. 在控制台运行 RTCRtpSender.getCapabilities("video")
  2. 列出所有支持的 mimeType 和 hardwareAccelerated 标志
  3. 对比 Chrome 桌面 vs Chrome Android 的差异

Lab 2:Simulcast 三档验证

  1. addTransceiver 配置 h/m/l 三档
  2. createOffer() 后搜索 SDP 中的 a=simulcasta=rid
  3. getStats() 确认 3 个 outbound-rtp 报告,各有不同 rid 和 SSRC

Lab 3:带宽降层观察

  1. 建立 Simulcast 通话
  2. Chrome DevTools 限速 300 kbps
  3. 观察 SFU 或 P2P 对端收到的层从 h → m → l 切换
  4. 记录切换时 inbound-rtp 的 SSRC 变化

Lab 4:VP8 vs H.264 画质对比

// 测试 1:强制 VP8
transceiver.setCodecPreferences(
caps.codecs.filter((c) => c.mimeType === "video/VP8")
);
// 测试 2:强制 H.264
transceiver.setCodecPreferences(
caps.codecs.filter((c) => c.mimeType === "video/H264")
);
// 在相同 maxBitrate 下对比主观画质和 CPU 占用

Lab 5:动态层管理

  1. 开启 3 层 Simulcast
  2. 30 秒后关闭 l 层:enc.active = false
  3. getStats() 确认 outbound-rtp 从 3 个变为 2 个
  4. 重新开启 l 层,确认恢复

Lab 6:Opus 码率与质量

  1. 分别设置 maxBitrate 为 16k / 32k / 64k
  2. 播放音乐片段,记录主观质量差异
  3. 启用 useinbandfec=1,10% 丢包下对比有无 FEC 的 concealedSamples

九、本章小结

要点内容
音频Opus 是唯一标准,注意 FEC/DTX/码率配置
视频VP8 默认,H.264 移动端,VP9/AV1 高压缩
Simulcastrid + sendEncodings,每层独立 SSRC
码率应用层 maxBitrate + GCC 自适应
选型兼容性 > 压缩率 > 专利自由度

从 Ron Frederick 1992 年手写软件视频压缩,到 Skype 团队标准化 Opus,再到今天 AV1 的硬件加速普及——编解码技术的演进直接塑造了 WebRTC 的能力边界。理解 Codec 特性,才能在延迟、画质、兼容性之间做出正确权衡。

下一篇(Ch10)带宽估计与 GCC——TWCC 反馈如何驱动发送端码率自适应。


系列导航

章节主题状态
0架构全景与协议栈地图✅ 已发布
1浏览器媒体 API 与设备管理✅ 已发布
2第一个 P2P 视频通话✅ 已发布
3信令服务器设计与会话状态机✅ 已发布
4SDP 解剖与媒体协商✅ 已发布
5ICE、STUN、TURN 与 NAT 穿透✅ 已发布
6Data Channel 与 SCTP over DTLS✅ 已发布
7DTLS 握手与 SRTP 加密体系✅ 已发布
8RTP/RTCP 媒体传输与 QoS✅ 已发布
9音视频编解码与 Simulcast 入门✅ 已发布
10带宽估计与拥塞控制 GCC✅ 已发布
11Simulcast、SVC 与选择性订阅✅ 已发布
12SFU/MCU/Mesh 架构与 Pion 实战✅ 已发布
13调试工具链与可观测性✅ 已发布
14TURN 集群部署与多区域扩展✅ 已发布
15Capstone 生产级视频会议系统✅ 已发布

References

Logo
RainLib

探索技术、设计与分布式系统的边界。构建面向未来的开发者工具。

留言与建议

© 2026 RainLib. 为未来构建。(Built for the Future)
版权所有。
系统正常