15、SVC可伸缩编码:时间可伸缩、空间可伸缩、质量可伸缩的配置与切换
SVC,全称是Scalable Video Coding。说白了,就是让视频流能「伸缩」。
你想想看,一个视频会议里,有人用5G手机,有人用WiFi,还有人在地铁上信号断断续续。如果大家都收同一路码流,要么高清用户浪费带宽,要么低端用户卡成PPT。SVC就是来解决这个问题的。
我个人习惯把SVC比作「俄罗斯套娃」。一个视频流里,包含了多个子层。接收端可以根据自己的网络状况,只解码其中一部分,就能看到视频。网络好了,多拿几层,画质更清晰;网络差了,少拿几层,至少画面不卡。
15.1 三种可伸缩维度
SVC定义了三个维度的伸缩性。我在项目中遇到过不少同学把这仨搞混,咱们一个一个说清楚。
| 维度 | 英文 | 通俗理解 | 典型场景 |
|---|---|---|---|
| 时间可伸缩 | Temporal Scalability | 帧率变化。比如从30fps降到15fps | 弱网时降低帧率,保证流畅度 |
| 空间可伸缩 | Spatial Scalability | 分辨率变化。比如从1080p降到720p | 小屏设备或带宽不足时降分辨率 |
| 质量可伸缩 | Quality Scalability | 画质变化。SNR信噪比不同 | 相同分辨率下,调整压缩程度 |
15.2 时间可伸缩(Temporal Scalability)
这个最容易理解。视频是由一帧帧图片组成的,时间可伸缩就是决定「哪些帧要传,哪些帧可以丢」。
在WebRTC里,我们通常用VP8/VP9或H264 SVC来实现。以VP9为例,它通过层级化P帧(Hierarchical P-frame)来构建时间层。
// 配置VP9时间可伸缩,3层结构
// 层0: 关键帧 + 基础P帧 (帧率基准)
// 层1: 中间P帧 (帧率翻倍)
// 层2: 最高P帧 (全帧率)
const videoEncoderConfig = {
codec: 'VP9',
scalabilityMode: 'L3T3', // 3层空间 × 3层时间
// 时间层配置
temporalLayers: [
{ targetBitrate: 100000 }, // 层0: 100kbps
{ targetBitrate: 200000 }, // 层1: 200kbps
{ targetBitrate: 400000 } // 层2: 400kbps
]
};
15.3 空间可伸缩(Spatial Scalability)
空间可伸缩,说白了就是「同一个画面,存多个分辨率版本」。比如基础层是360p,增强层是720p,再往上还有1080p。
接收端如果带宽够,就收1080p的包;如果不够,就只收360p的包。注意,这里不是重新编码,而是从同一个码流里提取不同分辨率的子流。
// 配置VP9空间可伸缩,2层分辨率
// 层0: 640x360 (基础层)
// 层1: 1280x720 (增强层)
const spatialConfig = {
scalabilityMode: 'L2T2', // 2层空间 × 2层时间
spatialLayers: [
{
width: 640,
height: 360,
maxBitrate: 300000
},
{
width: 1280,
height: 720,
maxBitrate: 800000,
// 依赖基础层
dependency: [0]
}
]
};
15.4 质量可伸缩(Quality Scalability)
质量可伸缩,也叫SNR可伸缩。它不改变分辨率,也不改变帧率,而是改变每一帧的编码质量。
举个例子:基础层用较粗的量化参数(QP值大),画面有块状感;增强层叠加细节信息,画面变清晰。接收端可以只收基础层,也可以收基础层+增强层。
// 质量可伸缩的典型配置
// 基础层: QP=40,码率低,画面粗糙
// 增强层: QP=20,码率高,画面精细
const qualityConfig = {
scalabilityMode: 'L1T3', // 1层空间 × 3层时间
qualityLayers: [
{
qp: 40,
targetBitrate: 150000
},
{
qp: 28,
targetBitrate: 350000,
dependency: [0]
},
{
qp: 20,
targetBitrate: 600000,
dependency: [0, 1]
}
]
};
15.5 动态切换策略
光配置好还不够,关键是怎么在运行时动态切换。我见过不少项目,SVC配好了,但切换逻辑写得很糙,导致画面频繁抖动。
我个人建议的切换策略:
- 先降时间层:帧率降低对用户感知最小。从30fps降到15fps,大部分人感觉不到。
- 再降质量层:如果带宽还不够,降低画质。画面模糊一点,但分辨率不变,UI布局不会乱。
- 最后降空间层:降分辨率是最后手段。因为分辨率一变,整个渲染布局都要调整,用户能明显感觉到「画面变小了」。
// 动态切换示例:根据带宽调整SVC层
function onBandwidthEstimate(bandwidthKbps) {
if (bandwidthKbps > 800) {
// 带宽充足:全开
setSpatialLayer(2); // 1080p
setTemporalLayer(2); // 30fps
setQualityLayer(2); // 高质量
} else if (bandwidthKbps > 400) {
// 中等带宽:降时间层
setSpatialLayer(2); // 保持1080p
setTemporalLayer(1); // 降到15fps
setQualityLayer(1); // 中等质量
} else if (bandwidthKbps > 200) {
// 低带宽:降质量+时间
setSpatialLayer(1); // 降到720p
setTemporalLayer(1); // 15fps
setQualityLayer(0); // 基础质量
} else {
// 极低带宽:保底
setSpatialLayer(0); // 360p
setTemporalLayer(0); // 7.5fps
setQualityLayer(0); // 最低质量
}
}
15.6 SVC vs Simulcast
说到这,你可能想问:SVC和Simulcast( simulcast是同时发送多个独立码流)有什么区别?
嗯,这是个好问题。简单说:
- Simulcast:同时编码多个独立流(比如360p、720p、1080p各一路)。接收端选一路收。优点是切换快,缺点是带宽浪费(同时传多路)。
- SVC:只编码一个流,但内嵌多层。接收端决定收几层。优点是带宽利用率高,缺点是编码复杂度高,解码器支持有限。
我个人在项目中更倾向于SVC,尤其是VP9的SVC实现已经比较成熟了。但如果你要兼容老设备,Simulcast可能更稳妥。
15.7 实战配置清单
最后,给你一份我常用的SVC配置清单。照着这个配,基本不会出大问题。
| 参数 | 推荐值 | 说明 |
|---|---|---|
| scalabilityMode | L3T3 | 3层空间 × 3层时间,覆盖大部分场景 |
| 时间层数 | 3 | 层0: 7.5fps, 层1: 15fps, 层2: 30fps |
| 空间层数 | 2-3 | 层0: 360p, 层1: 720p, 层2: 1080p |
| 切换滞回区间 | 200kbps | 降级阈值和升级阈值之间留200kbps缓冲 |
| 编码器 | VP9 | VP9的SVC支持最好,H264 SVC需要额外配置 |