跳到主要内容

WebRTC 全景实战 (11):Simulcast、SVC 与选择性订阅

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

"从多播幻想到 packet shufflers——SFU 是 WebRTC 多人会议的必然归宿。" — WebRTC for the Curious 历史

Ch9 Simulcast 入门 介绍了三档发布。本章深入 SFU 如何根据订阅者带宽选择转发层——这是从 P2P 跃迁到多人会议的核心机制。

Marratech 早期押注 IP 多播(Multicast),Serge Lachapelle 在 Curious 访谈 中回忆:公网多播从未真正落地,行业最终转向 packet shufflers(SFU)——Simulcast + 选择性订阅是 SFU 的标配能力。Google 2010 年收购 Marratech 后,这套架构在 Meet 中大规模验证。

配套 Lab:examples/webrtc-lab/client/ch02-p2p-basic 扩展 Simulcast + client/ch12-sfu-client(LiveKit)。


本篇术语表

术语英文解释
Simulcast联播同一视频源独立编码多个分辨率/码率层,同时发送
SVCScalable Video Coding可伸缩视频编码,单流内含多层,层间有依赖关系
RIDRTP Stream IdentifierSimulcast 层的标识符(h / m / l
SSRCSynchronization SourceRTP 同步源标识,Simulcast 每层独立 SSRC
MIDMedia IdentificationUnified Plan 下 m-line 与 transceiver 的绑定标识
LayerSelector层选择器SFU 根据订阅者带宽选择转发的 Simulcast/SVC 层
Dynacast动态联播LiveKit 按需发布——无订阅者时不发送高层
Dependency Descriptor依赖描述符AV1/VP9 SVC 的层依赖关系 RTP 头扩展
Temporal Layer时间层SVC 中按帧率分层的子流(T0/T1/T2)
Spatial Layer空间层SVC 中按分辨率分层的子流(L0/L1/L2)
Selective Subscription选择性订阅订阅者或 SFU 按需求选择接收的层/Track
Adaptive Stream自适应流LiveKit 客户端自动调整订阅分辨率
Content Hint内容提示motion / detail / text 影响编码策略
Pause Video暂停视频带宽不足时 SFU 停止转发视频但保持音频
Active Speaker活跃说话者大会议中优先订阅当前发言人的高清层

一、为什么需要多层视频?

Ch10 GCC 可以在单流内降码率,但无法在不转码的情况下降分辨率。Simulcast/SVC 让 SFU 在不做转码的前提下,为不同带宽的订阅者转发不同质量的层——这是 SFU 的核心价值。

Serge Lachapelle 在 Curious 访谈中解释:Meet 早期尝试过 MCU 混流,但转码延迟和 CPU 成本不可接受;packet shuffler + Simulcast 才是可扩展路径。


二、Simulcast vs SVC 架构对比

特性SimulcastSVC
编码次数N 次独立编码1 次编码,多层输出
上行带宽高(各层码率之和)低(仅最高层码率)
下行灵活性SFU 选 SSRC/rid 转发SFU 解析 Dependency Descriptor 选层
SFU 复杂度低(按 SSRC 切换)中(需理解层依赖)
编解码器VP8/H.264/VP9/AV1 均可VP9 / AV1 为主
CPU 消耗高(多编码器)低(单编码器)
典型框架mediasoup / JanusLiveKit Dynacast

2.1 选型决策树


三、Simulcast 的 SDP 与 RTP 细节

3.1 Unified Plan + RID

SDP 关键行:

a=simulcast:send h;m;l
a=rid:h send
a=rid:m send
a=rid:l send
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:5 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
字段作用
a=simulcast:send h;m;l声明发送三个 Simulcast 层
a=rid:h send定义 rid 标识与方向
mid:0绑定到 Unified Plan 的 transceiver
rtp-stream-idRTP 头扩展携带 rid

3.2 发布端代码

// examples/webrtc-lab/client/ch02-p2p-basic 扩展
const transceiver = pc.addTransceiver("video", {
direction: "sendonly",
sendEncodings: [
{ rid: "h", maxBitrate: 1_500_000, scaleResolutionDownBy: 1, maxFramerate: 30 },
{ rid: "m", maxBitrate: 500_000, scaleResolutionDownBy: 2, maxFramerate: 24 },
{ rid: "l", maxBitrate: 150_000, scaleResolutionDownBy: 4, maxFramerate: 15 },
],
});

// 验证 Simulcast 是否生效
const sender = transceiver.sender;
const params = sender.getParameters();
console.log("Encodings:", params.encodings.map((e) => e.rid));
// 期望: ["h", "m", "l"]
Simulcast 必须配合 Unified Plan

plan-b 已废弃。确保 RTCPeerConnection 使用默认 Unified Plan,且 addTransceiver 而非 addStream

3.3 屏幕共享 vs 摄像头 Simulcast

// 屏幕共享:更高码率,较少层
const screenTransceiver = pc.addTransceiver(screenTrack, {
direction: "sendonly",
sendEncodings: [
{ rid: "h", maxBitrate: 3_000_000, maxFramerate: 15 },
{ rid: "l", maxBitrate: 500_000, maxFramerate: 5 },
],
});
screenTrack.contentHint = "detail"; // 保文字清晰

四、SVC 层结构与 Dependency Descriptor

SVC 的关键优势:丢弃高层不影响低层解码。SFU 可以在 RTP 级别丢弃 L2/T2 包,订阅者仍能解码 L0/T0。

4.1 VP9 SVC 模式

模式空间层时间层典型用途
L1T111最低开销
L1T313帧率自适应
L3T333全自适应(LiveKit 默认)

4.2 AV1 Dependency Descriptor

AV1 的 Dependency Descriptor(DD)头扩展让 SFU 无需解码即可知道每个 RTP 包属于哪一层:

a=extmap:11 https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension

五、SFU 选择性转发流程

5.1 SFU Forwarder 内部逻辑

Pion(Go)和 LiveKit(Go + Pion)都实现了 Forwarder + StreamTracker 管线。LiveKit 的 LayerSelector 会综合考虑:

  1. 订阅者的 TWCC 带宽估计(Ch10
  2. 订阅者请求的视频质量(HIGH / MEDIUM / LOW
  3. 发布者当前可用的 Simulcast 层
  4. CPU/带宽保护阈值

5.2 层切换时序

层切换不是瞬时的——SFU 需要等待新层 Keyframe(PLI/FIR 触发)。


六、Dynacast:按需发布

LiveKit 的 Dynacast 进一步优化上行带宽:若无人订阅高层,停止发送该层:

Dynacast 对大型会议(100+ 参与者,大部分静音/小窗)节省的上行带宽非常可观。详见 LiveKit 介绍

6.1 Dynacast 与 Active Speaker


七、大会议带宽模型

架构6 人会议每人连接数6 人每人上行6 人每人下行
Full Mesh5 条 P2P5 × 码率5 × 码率
SFU1 条到 SFU1 × Simulcast 码率(N-1) × 单流码率
MCU1 条到 MCU1 × 码率1 × 混流码率

7.1 带宽估算公式

Simulcast 上行 = rid_h + rid_m + rid_l(无 Dynacast)
≈ rid_active_layers(有 Dynacast)

SFU 下行/订阅者 = Σ(每个远程 participant 的选中层码率)
SFU 总转发量 = Σ(每个订阅者的下行之和)

示例:10 人会议,3 档 Simulcast(1.5M + 0.5M + 0.15M = 2.15M 上行/人),SFU 为每个订阅者转发 9 路 × 选中层(假设平均 500kbps)= 4.5Mbps/订阅者。

7.2 100 人会议优化策略

策略节省实现
Active Speaker 高清下行 80%+仅 1 人 rid=h
视频 Pause下行 50%+非可见 tile 不订阅
Dynacast上行 60%+无订阅者停发高层
音频 always-on音频独立 Track,~64kbps/人

八、代码:Simulcast 发布与订阅控制

8.1 发布端完整示例

async function publishWithSimulcast(room, localTrack) {
await room.localParticipant.publishTrack(localTrack, {
simulcast: true,
videoEncoding: {
maxBitrate: 1_500_000,
maxFramerate: 30,
},
videoSimulcastLayers: [
{ rid: "h", scaleResolutionDownBy: 1, maxBitrate: 1_500_000 },
{ rid: "m", scaleResolutionDownBy: 2, maxBitrate: 500_000 },
{ rid: "l", scaleResolutionDownBy: 4, maxBitrate: 150_000 },
],
});
}

LiveKit SDK 封装了 room.setVideoQuality(participant, quality) 等高层 API,见 LiveKit 介绍

8.2 订阅质量控制

import { VideoQuality } from "livekit-client";

// 为特定参与者设置订阅质量
room.setVideoQuality(remoteParticipant.identity, VideoQuality.HIGH);
room.setVideoQuality(otherParticipant.identity, VideoQuality.LOW);

// 暂停某参与者视频(保留音频)
room.setVideoSubscription(remoteParticipant.identity, false);

8.3 原生 WebRTC 订阅层控制

const receiver = pc.getReceivers().find((r) => r.track?.kind === "video");
const params = receiver.getParameters();
params.degradationPreference = "maintain-framerate";
await receiver.setParameters(params);

8.4 getStats 验证 Simulcast 层

async function logSimulcastStats(pc) {
const stats = await pc.getStats();
stats.forEach((r) => {
if (r.type === "outbound-rtp" && r.kind === "video") {
console.log({
rid: r.rid,
ssrc: r.ssrc,
bytesSent: r.bytesSent,
framesEncoded: r.framesEncoded,
targetBitrate: r.targetBitrate,
frameWidth: r.frameWidth,
frameHeight: r.frameHeight,
});
}
});
}
// 期望看到 3 个 outbound-rtp,rid 分别为 h/m/l

九、Marratech → Google Meet 的架构启示

Serge Lachapelle 在 Curious 访谈中的核心观点:SFU 不是对 P2P 的妥协,而是公网多人会议的唯一可行架构。Simulcast 解决了 SFU「不转码就无法适配不同带宽」的问题;SVC 和 Dynacast 则是 Simulcast 上行带宽代价的后续优化。


十、常见陷阱

#陷阱现象修复
1Simulcast 未在 SDP 生效仅 1 个 SSRC检查 Unified Plan + sendEncodings
2rid 命名不规范SFU 无法选层使用 h/m/lf/h/q 约定
3三层码率之和超上行全部层丢包启用 Dynacast 或降低 maxBitrate
4SVC 层依赖解析错误订阅者花屏检查 DD 头扩展协商
5忽略音频独立转发视频降层但音频占带宽音频始终独立 Track
6SFU 转码误用延迟高、CPU 爆SFU 应只转发不转码
7大会议全量订阅下行带宽爆炸仅订阅 active speaker + 可见 tile
8层切换无 Keyframe切换后黑屏 2sSFU 发 PLI 请求 I 帧
9H.264 Simulcast profile 不一致解码失败统一 profile-level-id
10屏幕共享用摄像头三档文字模糊/带宽浪费屏幕共享单独 2 档配置

十一、实战 Lab

Lab 1:验证 Simulcast 三档

cd examples/webrtc-lab/signaling && npm start
npx serve examples/webrtc-lab/client/ch02-p2p-basic
  1. 修改 main.js 添加 sendEncodings 三档
  2. 通话后运行 logSimulcastStats(pc)
  3. 确认 3 个 outbound-rtp 报告

Lab 2:带宽限制与层切换

  1. Chrome DevTools 限速 300kbps
  2. 观察 rid=lbytesSent 持续增长,rid=h/m 停滞
  3. 取消限速,观察 rid=h 恢复

Lab 3:LiveKit SFU 选择性订阅

livekit-server --dev
# 使用 examples/webrtc-lab/client/ch12-sfu-client
  1. 3 人加入同一 Room
  2. 订阅者 A 设置 VideoQuality.HIGH,订阅者 B 设置 VideoQuality.LOW
  3. 在 LiveKit Dashboard 观察不同下行码率

Lab 4:Simulcast vs SVC 上行对比

  1. 发布者 A:Simulcast 三档 VP8
  2. 发布者 B:SVC VP9(LiveKit 默认)
  3. 对比 getStats 中总 bytesSent 速率

Lab 5:Dynacast 验证

  1. 发布者加入 Room 但不订阅任何远程 Track
  2. 观察 LiveKit 是否停止发送 h/m 层(通过发布者 getStats

Lab 6:层切换延迟测量

  1. 通话中 DevTools 从 Unlimited 切到 300kbps
  2. 记录 rid 变化时间戳
  3. 对比 SFU 层切换 vs 单流 GCC 降分辨率的速度

Lab 7:屏幕共享 Simulcast

  1. 发布屏幕共享 + contentHint: "detail"
  2. 订阅者限速 500kbps
  3. 确认文字仍可读(l 层保分辨率)

十二、本章小结

概念要点
Simulcast独立编码多层,SFU 按 rid 转发,上行代价高
SVC单流多层,上行高效,SFU 需解析层依赖
Dynacast按需发布,节省上行
SFU 选层TWCC BWE + 订阅偏好 → LayerSelector
大会议Active Speaker + Pause + Dynacast 三件套
历史Marratech 多播 → packet shuffler → Meet/LiveKit

下一篇(Ch12)SFU 架构与 Pion 实战


系列导航

章节主题状态
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)
版权所有。
系统正常