一、Simulcast 到底是什么?
Simulcast,中文叫「联播」。说白了,就是一个发送端同时发出多路不同分辨率的视频流。
我记得第一次接触这个概念是在做视频会议项目的时候。当时有个客户反馈:「你们这个会议,有人用 4K 显示器,有人用手机,画质怎么调都不对。」嗯,这就是典型的单流问题——你只能发一种分辨率,要么高清的让手机端卡死,要么低清的让大屏用户骂娘。
Simulcast 的解决方案很简单:发送端同时编码三路流,比如 1080p、720p、360p。接收端根据自己的屏幕和带宽,选最合适的那一路。
核心思想: 一次编码,多路输出,按需订阅。
二、Simulcast 的工作原理
2.1 编码端:三路并行编码
发送端拿到摄像头采集的原始帧后,不是只编一路。而是同时送进三个编码器实例,分别输出不同分辨率。
// 伪代码示意
const encodings = [
{ rid: 'h', scaleResolutionDownBy: 1.0, maxBitrate: 2_500_000 }, // 1080p
{ rid: 'm', scaleResolutionDownBy: 2.0, maxBitrate: 1_000_000 }, // 540p
{ rid: 'l', scaleResolutionDownBy: 4.0, maxBitrate: 300_000 } // 270p
];
const sender = pc.addTrack(track);
sender.setParameters({ encodings });
这里有个坑,我踩过。Chrome 的 Simulcast 要求 rid 必须是 'h'、'm'、'l' 这三个值,大小写敏感。你写个 'high'、'medium'、'low',浏览器直接忽略,只发一路。
2.2 传输层:RTP 流标识
每一路流在 RTP 层面用不同的 SSRC 标识。接收端通过 SSRC 来区分哪一路是高清,哪一路是标清。
实际抓包时你会看到类似这样的结构:
SSRC 0x12345678 → 高清流 (rid=h)
SSRC 0x87654321 → 标清流 (rid=m)
SSRC 0x11223344 → 低清流 (rid=l)
2.3 接收端:动态切换
接收端不是三路全收。它只订阅最适合自己的那一路。网络变差了?从高清切到标清。屏幕变大了?从标清切回高清。
这个切换过程是无感知的,因为 SFU 在中间做了转发控制。
我的经验: 切换时建议加一个 200ms 的缓冲。不要因为网络瞬间抖动就频繁切换,否则用户会看到画面忽大忽小,体验反而更差。
三、Simulcast vs SVC:到底选哪个?
很多新手会问:Simulcast 和 SVC 不都是做分层吗?有什么区别?
嗯,这个问题我当年也纠结过。直接上对比表:
| 对比维度 | Simulcast | SVC |
|---|---|---|
| 编码方式 | 多路独立编码 | 单路分层编码 |
| 码率开销 | 较高(约 1.5x - 2x) | 较低(约 1.1x - 1.3x) |
| 解码复杂度 | 低(标准解码器即可) | 高(需要 SVC 解码器) |
| 浏览器支持 | Chrome、Firefox 原生支持 | 仅 Safari 部分支持 |
| 灵活性 | 每路独立码率、帧率 | 依赖层间依赖关系 |
| 适用场景 | 多人会议、直播 | 点播、存储优化 |
我个人习惯:实时通信场景无脑选 Simulcast。为什么?因为浏览器兼容性摆在那里。你想想看,SVC 在 Chrome 上至今不支持,你总不能要求所有用户都换 Safari 吧?
避坑指南: 我曾经在一个项目中尝试用 SVC 做分层,结果发现 Firefox 根本不认。最后不得不回退到 Simulcast,白白浪费了两周时间。所以,除非你确定所有客户端都支持 SVC,否则别碰。
四、Simulcast 的配置与优化
4.1 码率配置:别照搬默认值
WebRTC 的默认码率配置其实挺保守的。1080p 默认只给 2500kbps,这在静态场景下够用,但如果是屏幕共享或者运动画面,画质会明显下降。
我建议的配置方案:
const encodings = [
{ rid: 'h', scaleResolutionDownBy: 1.0, maxBitrate: 4_000_000 }, // 1080p: 4Mbps
{ rid: 'm', scaleResolutionDownBy: 2.0, maxBitrate: 1_500_000 }, // 540p: 1.5Mbps
{ rid: 'l', scaleResolutionDownBy: 4.0, maxBitrate: 500_000 } // 270p: 500Kbps
];
注意:maxBitrate 是上限,不是固定值。实际码率会根据网络状况动态调整。
4.2 分辨率选择:别盲目追求 4K
很多人觉得 Simulcast 就是分辨率越高越好。其实不然。你想想看,如果发送端是 4K 摄像头,你强行编三路 4K、2K、1080p,CPU 直接爆炸。
我的经验:最高路不要超过 1080p。原因很简单:
- 1080p 在 95% 的屏幕上已经够用
- 再高的分辨率对 CPU 和带宽都是浪费
- 移动端根本用不到 4K
4.3 帧率控制:别让低清流也跑 30fps
低清流的主要作用是保底。网络差的时候,用户能看清画面就行,不需要流畅。
我一般这样配:
const encodings = [
{ rid: 'h', scaleResolutionDownBy: 1.0, maxBitrate: 4_000_000, maxFramerate: 30 },
{ rid: 'm', scaleResolutionDownBy: 2.0, maxBitrate: 1_500_000, maxFramerate: 20 },
{ rid: 'l', scaleResolutionDownBy: 4.0, maxBitrate: 500_000, maxFramerate: 10 }
];
低清流 10fps 就够了。这样能省下不少带宽给高清流。
4.4 动态调整:别写死
Simulcast 的配置不是一成不变的。用户网络在变,屏幕在变,你的配置也得跟着变。
我常用的策略:
- 上行带宽充足:开启三路流,高清流给满码率
- 上行带宽不足:关闭高清流,只发中低两路
- 上行带宽极差:只发低清流,甚至降帧率到 5fps
这个逻辑可以通过 RTCRtpSender.setParameters() 动态调整,不需要重新协商。
小技巧: 我习惯在发送端加一个带宽探测模块。每 5 秒检测一次上行带宽,然后动态调整 Simulcast 的路数。这样既能保证画质,又不会把网络打满。
五、Simulcast 的架构图
下面这张图展示了 Simulcast 的完整工作流程:
六、总结
Simulcast 是 WebRTC 中解决多端适配问题的核心方案。它的核心逻辑就一句话:多路编码,按需订阅。
配置时记住三点:
- 码率别照搬默认值,根据场景调高
- 最高路分辨率控制在 1080p 以内
- 低清流帧率可以降到 10fps 省带宽
至于 SVC,除非你确定所有客户端都支持,否则别碰。我在项目里吃过这个亏,不想你再踩一遍。