目前国内实时互动和视频会议产品大部分还是用的h.264和hevc做为视频编解码器的方案,而很少使用vp9,即使有svc可用,是为什么呢?其根本原因是 H.264/HEVC 在硬件兼容性、产业链成熟度和编码实时性上拥有碾压性优势,而 VP9 的“免专利费”和“SVC”优势在现实场景中并未能转化为足够的竞争力。H.264最早也最成熟,几乎所有现代设备的芯片中,都内置了专门的 H.264 编解码电路。这使得编码解码效率极高、CPU占用和功耗极低。x264,openh264也经过长年优化,延时和功耗基本也没大问题。HEVC作为接替者,其硬件支持也已非常普及。而vp9硬件支持至今仍不充分,libvpx在google公司的主导下,优化并不充分。大量设备将不得不依赖效率低下的软件解码,导致CPU占用飙升、设备发热、电池快速耗尽等问题,严重影响用户体验。VP9-SVC通过一次编码生成多层视频流,能更好地适应不同网络条件。由于优化不力和业界很少有优秀的实践案例,也只能处于理想丰满,现实骨感的尴尬境地。
经过分析,vp9其实有很大的优化空间。
一、我们从libvpx的speed预设和编码性能开始讨论:
在 libvpx speed=9(最快预设)下,运动估计(ME)模块的优化策略可以概括为“一切从简、能省就省”。具体实现手段可分为“搜索策略、迭代深度、代价函数、并行粒度”四个维度:
• 搜索形状:直接采用 菱形(DIA) 或 4-步长十字(EPZS),半径从 speed=0 的 64 降到 2~4 像素。
• 早期退出:只要 SAD/SSD 小于动态阈值(与当前 QP 相关)立即终止,不再尝试更小分区。
• 关闭 1/4 像素全搜索,仅做 1/2 像素一次插值 + 中心比较,省去 Hadamard 变换。
• subpel_iters 从 3~5 次降到 1 次,且跳过 chroma 分量计算。
• 最多只搜 2 个参考帧(前一帧 + 黄金帧),后续参考直接复用 MV。
• 64×64→32×32→16×16 分裂决策改为 单层方差阈值:若 64×64 误差小于阈值,直接标记为 SKIP,不再递归。
• 代价函数从 SATD+λ*bits 简化为 SAD;关闭 Trellis、RDO 重算。
• MV 缓存共享:整帧级 hash 表缓存已算过的 MV,相同 block 直接复制。
• 所有 SAD/SSE 计算使用 AVX2/AVX-512 一次处理 32/64 像素,减少函数调用开销。
• 在 tile-row 并行框架下,ME 任务被切成 一行一个 job,避免线程同步等待。
一句话总结:speed=9 下的 ME 用“小窗口、一步亚像素、单层决策、SAD 代价”把原本最耗时的模块压到极限,换来 20× 以上的速度提升,但 RD-loss 也相应增大。
SVC L3T3:Speed=9对比x264最后测试对比,编码复杂度高30%,压缩率反而落后30%~40%。
二、libvpx的RTC编译选项问题:
由于编码复杂度问题,libvpx为RTC场景专门提供了一个编译配置选项configure –enable-realtime-only.通过开启这个配置编译后,你会发现编码压缩工具有很大的限制,在这个配置下很难提升压缩率。
从以上两点分析,要使压缩率有一定保障。至少需要关闭enable-realtime-only的编译配置选项,还需要把预设降到speed=7。
三、libvpx本身工程实现问题
Libvpx中dct和idct变换模块有重复的二次转置问题,svt-av1里同样存在。大量的simd指令集并行优化没有实现,可参考的路径是dav1d,svt-av1工程。Vp9和av1有大量同算法的模块实现,vp9更像是av1的一个子集。
四、计算去重优化
Speed=7, 在编码器流水线中,SAD 和 DCT 相关运算会存在大量重复的块级计算。这是传统混合编码架构(VP9/AV1/H.26x 等)的一个经典冗余问题。
1. 重复运算的典型场景
场景一:运动估计(ME)→ 模式决策(Mode Decision)的重复
|
阶段 |
计算内容 |
块尺寸 |
|
整像素 ME |
SAD 搜索最佳 MV |
4×4 ~ 64×64(多种划分) |
|
亚像素 ME |
SATD(类 DCT)精化 1/2、1/4、1/8 像素 |
同上 |
|
模式决策/RDO |
完整 DCT/ADST + 量化 + 熵编码代价 |
最终选定划分 |
重复点:一个候选块可能在整像素 SAD、亚像素 SATD、RDO 全变换中被计算 3 次。
场景二:码率控制(RC)→ 实际编码的重复
|
阶段 |
计算内容 |
|
RC 预分析 |
快速 DCT 或方差估计,预测比特数 |
|
正式编码 |
完整 DCT/ADST + 量化 |
重复点:RC 阶段为了估计复杂度做的"快速变换"和后续正式编码的变换是同一残差块。
场景三:多参考帧/多模式搜索
运动估计对每个参考帧、每个预测模式(单向/双向、不同块划分)都计算 SAD/SATD,最终只选一个,其余全部浪费。
Speed=7的预设实现已经做了大量的简化,导致重复运算的绝对数量已经很少,但结构性的冗余依然存在:
|
模块 |
Speed 7 当前做法 |
残余问题 |
|
整像素 ME |
仅搜索最近参考帧,MV 范围极小,SAD 用 SIMD |
同一位置在不同块划分(4×4/8×8/16×16)间重复计算 SAD |
|
亚像素 ME |
完全禁用或仅做 1/2 像素 |
无 |
|
模式决策 |
仅评估少数几种模式(SKIP、INTER、NEAREST) |
残差从像素域到变换域的搬运仍独立进行 |
|
变换/量化 |
仅评估单一 tx_size,跳过部分尺寸 |
每块仍做一次完整 DCT,无缓存 |
|
码率控制 |
简化的单遍 CBR,基于历史统计 |
无预分析,RC 估计与正式编码无联动 |
关键洞察:Speed 7 的瓶颈已从"运算复杂度"转向"数据搬运"和"分支预测失败"。重复块运算的优化应聚焦于减少内存访问和跨模块状态复用。
2.优化路径规划
路径一:块级 SAD 缓存池(整像素 ME 内)
目标:消除不同划分尺寸间的重复 SAD 计算。
原理:在 speed 7 中,一个 32×32 的 CTU 会被尝试划分为 16×16、8×8、4×4 等子块。计算 32×32 的 SAD 时,其四个 16×16 子块的 SAD 其实已经隐含在求和过程中;反之,四个 16×16 的 SAD 可以累加得到 32×32 的 SAD。
实现:
- 计算 4×4 基础 SAD → 存入缓存
- 8×8 SAD = 4 个 4×4 SAD 之和(查表 + 加法)
- 16×16 SAD = 4 个 8×8 SAD 之和
- 以此类推
收益估计:在 speed 7 的极简搜索下,ME 占比约 30-40%,此优化可减少 20-30% 的 SAD 指令数。
路径二:残差复用流水线(ME → TQ 零拷贝)
目标:消除 ME 阶段计算残差后,正式编码时重新从内存读取像素并计算残差的冗余。
现状问题:
- ME 阶段:当前块像素 – 参考块像素 = 残差(存在于寄存器/SIMD 向量中)
- 正式编码:重新从 L1/L2 缓存读取当前块和参考块像素,重新计算残差,再做 DCT
实现:
- ME 计算 SAD 时,同时将残差块写入片上缓冲区(每个 CTU 一个,大小 64×64 字节 = 4KB)
- 若该块最终被选中(通过 early decision),直接将残差指针传递给 TQ 模块
- 在 libvpx 的 vp9_encode_block 中,增加 residual_reuse 标志
- 当 speed >= 8 且 ME 已计算该块时,跳过 vp9_subtract_block,直接从预计算缓冲区读取残差
收益估计:减少一次完整的当前块+参考块内存读取(约 2×32×32 = 2KB 每块),对内存受限的嵌入式场景收益显著。
路径三:SATD/DCT 结果缓存(亚像素 & 模式决策)
目标:speed 7 虽禁用亚像素,但在某些实现中仍有 1/2 像素或模式间的快速 SATD。缓存这些结果。
实现:
- Key: (ref_frame, mv_x, mv_y, block_size, src_x, src_y)
- Value: satd_cost, estimated_bits
收益估计:在 speed 7 中 SATD 占比不高(约 5-10%),但实现成本极低,适合作为"免费优化"。
路径四:基于方差的 SKIP 模式快速决策(绕过 DCT)
目标:在正式做 DCT 之前,用像素域统计量判断该块是否极大概率是 SKIP 模式。
原理:如果当前块与参考块差异极小(SAD 接近 0,方差极低),则变换后系数几乎全为 0,SKIP 模式最优。此时无需进入 DCT。
实现:
plain
if (sad < threshold_skip) {
// threshold_skip = 4 * block_pixels (经验值,约每像素 1/4 个亮度差)
mode = SKIP;
skip_dct = 1;
estimated_bits = header_bits_only;
}
收益估计:对典型视频(如视频会议、屏幕内容),SKIP 率可达 40-60%,直接绕过 DCT 可节省大量运算。
路径五:RC 与 ME 的联合预分析(单遍编码模拟两遍)
目标:让码率控制的复杂度估计复用 ME 的 SAD 结果,避免 RC 独立做统计。
现状问题:VP9 的 single-pass CBR 在 speed 7 中,RC 模块独立维护帧复杂度统计,与 ME 的 SAD 数据无联动。
实现:
plain
// 原做法
complexity = calculate_independent_variance(frame);
// 优化后
complexity = me_stage->frame_sad_sum / (width * height);
收益估计:消除 RC 阶段对像素的二次遍历,减少 5-10% 的总 CPU 时间。
五、针对svc的参考模型,使用超分替代层间缩放运动补偿。
以SVC L3T3为例:

在 VP9 空域 SVC 编码中,参考帧缩放是实现跨层预测的关键环节。由于各空间层分辨率不同,低层重建帧必须被上采样(Upsample)到当前层的分辨率,才能作为有效的参考帧用于运动补偿。以下是详细的机制说明和核心实现分析。
(一)参考帧缩放的触发条件与架构
1.1 触发场景
VP9 SVC 中参考帧缩放发生在以下场景:
|
场景 |
说明 |
|
空域层间参考 |
低空间层(如 360p)编码完成后,其重建帧需上采样到高空间层(如 720p)分辨率,供高层作为参考帧 |
|
动态分辨率切换 |
编码过程中动态改变输出分辨率时,参考帧需要重新缩放 |
|
帧率/码率自适应 |
根据网络条件调整各层参数时,可能涉及参考帧缓冲区重建 |
1.2 核心架构
VP9 编码器维护一个参考帧缓冲区池(RefCntBuffer 池),每个空间层拥有独立的编码上下文(SVC_LAYER_CONTEXT)。关键设计原则:
- 各层独立缓冲:每个空间层有自己的 cpi->svc.layer_context[sl],包含独立的参考帧列表
- 缩放按需进行:低层帧不会自动全局上采样,而是在被高层引用时通过缩放路径处理
- 引用计数管理:通过 vp9_ref_frame 和 ref_cnt 机制确保帧数据生命周期安全
(二)参考帧缩放的完整数据流
plain
┌─────────────────────────────────────────────────────────────┐
│ 低空间层 (Spatial Layer 0) 编码完成 │
│ – 分辨率: 360×640 │
│ – 重建帧存入: cpi->svc.layer_context[0].enc_frame_buffer │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 高层编码前配置参考帧 (set_svc_ref_frame_config) │
│ – 将低层帧标记为高层可用参考帧 │
│ – 设置 ref_frame_config 中的 reference layer 映射 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 高层 (Spatial Layer 1) 开始编码 │
│ – 分辨率: 720×1280 │
│ – 需要引用低层重建帧 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 分辨率匹配检查 │
│ – 比较当前层分辨率 vs 参考帧实际分辨率 │
│ – 若不一致 → 触发缩放路径 │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ 分辨率相同 │ │ 分辨率不同 │
│ 直接使用 │ │ 需要缩放 │
└─────────────┘ └─────────────┘
│
▼
┌─────────────────────────────────┐
│ 缩放处理路径 │
│ – 创建临时缩放缓冲区 │
│ – 调用 vp9_scale_if_required() │
│ – 或使用 SVC 专用缩放路径 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 上采样滤波 (8-tap/8-tap 滤波器) │
│ – 水平方向 8-tap 插值 │
│ – 垂直方向 8-tap 插值 │
│ – 生成目标分辨率参考帧 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 更新高层参考帧列表 │
│ – 将缩放后的帧加入 cm->ref_frame_map │
│ – 设置正确的 ref_idx 映射 │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ 运动补偿使用 │
│ – vp9_build_inter_predictors() │
│ – 读取缩放后参考帧像素 │
│ – 结合缩放后的 MV 进行插值 │
└─────────────────────────────────┘
Libvpx中scale_2d() — 2D 8-tap 上采样滤波,其实现质量一般,性能较差。如果换成AI超分,(目前只针对1:2放大,将来可考虑任意尺寸放大),复杂度和超分质量pnsr约35.运动补偿的残差在通常的量化下大部分会直接走skip模式。在以前的上采样滤波模式下,大部分还是帧间运动补偿,而层间运动补偿占比小。采用超分后,因为超分质量更高,帧间运动补偿占比会大量减少,而层间运动补偿会占比会更多。
综上,通过以上大量的优化,vp9-SVC在L3T3编码上压缩率和编码复杂度上有跨越式改进。使得720p30编码比传统的x264压缩率提高,复杂度也会更低。最终编码是9个等级的子流,对于rtc的抗弱网实现质的飞跃。因为SVC的码流子码率基本固定,画质也有保障。在rtc的接收端同样还可以继续使用超分提升弱网画质。


