15、SVC可伸缩编码:时间可伸缩、空间可伸缩、质量可伸缩的配置与切换

SVC,全称是Scalable Video Coding。说白了,就是让视频流能「伸缩」。

你想想看,一个视频会议里,有人用5G手机,有人用WiFi,还有人在地铁上信号断断续续。如果大家都收同一路码流,要么高清用户浪费带宽,要么低端用户卡成PPT。SVC就是来解决这个问题的。

我个人习惯把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
  ]
};
我的经验: 时间层数不是越多越好。3层已经能覆盖大部分场景。我曾经试过5层,结果编码器开销太大,反而得不偿失。

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配好了,但切换逻辑写得很糙,导致画面频繁抖动。

我个人建议的切换策略:

  1. 先降时间层:帧率降低对用户感知最小。从30fps降到15fps,大部分人感觉不到。
  2. 再降质量层:如果带宽还不够,降低画质。画面模糊一点,但分辨率不变,UI布局不会乱。
  3. 最后降空间层:降分辨率是最后手段。因为分辨率一变,整个渲染布局都要调整,用户能明显感觉到「画面变小了」。
// 动态切换示例:根据带宽调整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);  // 最低质量
  }
}
避坑指南: 我曾经犯过一个错误——切换太频繁。带宽刚掉到400kbps,我立刻降层;结果带宽又回升到500kbps,我又升回去。这样反复切换,用户看到的画面一直在「跳」。后来我加了滞回区间(hysteresis),比如降到400kbps触发降级,但需要回升到600kbps才触发升级。这样稳定多了。

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需要额外配置
总结一句话: SVC的核心是「分层」。时间层保流畅,空间层保分辨率,质量层保画质。动态切换时,先降时间,再降质量,最后降空间。别忘了加滞回区间。
SVC可伸缩编码核心结构 原始视频流 SVC编码器 时间可伸缩 帧率: 30fps → 15fps → 7.5fps 空间可伸缩 分辨率: 1080p → 720p → 360p 质量可伸缩 画质: QP=20 → QP=28 → QP=40 动态切换策略:先降时间 → 再降质量 → 最后降空间 关键:切换时加入滞回区间(200kbps),避免频繁抖动
公众号:蓝海资料掘金营,微信deep3321