返回文章列表

第十三章|基础专题五:视频编码共性

从 I/P/B 图像、GOP、帧内预测、帧间预测、运动估计、残差、变换、量化、熵编码、参考帧和 DPB 理解块式视频编码共同框架。

第十三章|基础专题五:视频编码共性

原稿学习节奏:第 5 周。

本周目标不是记住某个 H.264 字段,而是建立一套能够迁移到 H.264、H.265、VP9、AV1 等块式视频编码标准的统一模型。

核心主线只有一句话:编码器不直接描述每一帧,而是尽量描述“怎样预测这一帧,以及预测错了多少”。


本周学习安排(8~10 小时)

学习单元建议时长产出
1. 图像类型、GOP 与参考关系1 小时能画出 I/P/B 图像及参考箭头
2. 帧内预测与帧间预测1.5 小时能解释预测块从哪里来
3. 运动估计、运动补偿与亚像素插值2 小时手算 SAD、SSE 和简单运动搜索
4. 残差、变换、量化与熵编码1.5 小时手算一个微型变换和量化例子
5. 编码器重建闭环、环路滤波与 DPB1.5 小时能解释为什么编码器内部必须“自带解码器”
6. Web 场景、性能估算与面试复盘1.5~2.5 小时完成练习与系统设计题

一、学习目标

完成本周后,你应当能够:

  1. 画出传统块式视频编码器和解码器的完整数据流。
  2. 解释视频压缩如何同时利用空间冗余和时间冗余。
  3. 严格区分帧内预测、帧间预测、运动估计和运动补偿。
  4. 解释 I、P、B picture、参考帧、关键帧、IDR 和 GOP 不是同一维度的概念。
  5. 使用 SAD、SSE、SATD 和率失真代价比较候选预测模式。
  6. 解释运动矢量为什么不等于物体真实运动,也不等于光流。
  7. 解释残差为何还要经过变换、量化和熵编码。
  8. 解释为什么编码器必须使用“重建参考帧”,而不能直接使用原始参考帧。
  9. 说明环路滤波与普通显示后处理的根本区别。
  10. 估算参考帧缓冲、运动搜索和原始帧队列的资源消耗。
  11. 解释编码器为什么通常远比解码器复杂。
  12. 将 GOP、关键帧、参考帧和 lookahead 应用于 AI 视频预览、在线编辑、Seek 与低延迟设计。

二、概念地图

2.1 本周位于完整媒体系统的哪一层

原始像素帧:RGB / Y'CbCr / YUV420P / NV12

        视频编码共性:本周内容
  预测 → 残差 → 变换 → 量化 → 熵编码
        ↘ 重建 → 环路滤波 → 参考帧 ↗

   具体视频标准:H.264 / H.265 / VP9 / AV1

       基础码流:NALU / OBU / frame unit

           容器:MP4 / WebM / MOV

       浏览器:<video> / MSE / WebCodecs

本周讨论的是视频编码算法的共同骨架。H.264、H.265 等标准会规定具体块划分、预测模式、插值滤波器、变换、量化、熵编码和参考图像管理语法,但它们共享“预测 + 残差 + 重建闭环”的基本思想。ITU-T H.264 的规范目录明确包含帧间预测、分数采样插值、变换系数解码、参考图像管理和去块滤波等解码过程;H.265 也规定相应的预测、变换、参考图像和环路处理机制。12

2.2 标准规定什么,编码器实现决定什么

范畴通常由标准规定通常由编码器实现决定
合法码流语法元素、取值范围、解析顺序如何组织程序和数据结构
解码结果给定合法码流时的规范性重建过程SIMD、线程、缓存和硬件调度
预测工具可用的预测模式和插值规则搜索哪些模式、搜索多深
变换与量化允许的变换、缩放和反量化规则如何选择块大小、QP 和量化优化
参考管理解码器如何建立和维护参考关系lookahead、场景切换检测、参考帧选择
码率控制标准通常只约束输出码流CRF、ABR、VBV 分配、AQ 等策略

必须记住:视频编码标准主要保证“码流可被一致解码”,通常不会规定编码器必须使用哪一种搜索算法。 FFmpeg 文档中的 gbfrefsme_methodsubqcrfpreset 等是框架或具体编码器暴露的实现选项,不是所有视频标准共同定义的码流字段。3


三、直觉解释

3.1 为什么连续视频比 JPEG 序列更容易压缩

假设摄像机固定拍摄一个人在白墙前走动:

  • 每一帧的大部分墙面几乎不变。
  • 人的纹理在相邻帧中也没有完全消失,只是移动了位置。
  • 真正需要重新描述的,往往只是运动边缘、遮挡区域、光照变化和预测误差。

若把每一帧都独立压缩,编码器只能利用单帧内部的空间相关性。视频编码进一步利用相邻帧之间的时间相关性:

上一重建帧中的某个区域
           ↓ 平移、插值或加权组合
当前帧的预测块

当前原始块 - 预测块

只编码较小的残差

3.2 “预测 + 修正”模型

把当前块记为 X,预测块记为 P,残差记为 R

$$ R = X - P $$

解码器得到预测块和重建残差后:

$$ \hat X = P + \hat R $$

其中:

  • X:编码器看到的原始块。
  • P:帧内或帧间预测块。
  • R:原始残差。
  • \hat R:量化后再反量化、反变换得到的重建残差。
  • \hat X:编码器和解码器都能得到的重建块。

若预测准确,残差中的大量值会接近 0,经过变换和量化后会产生许多零系数,因而更容易熵编码。

3.3 编码器内部为什么必须有一条“解码路径”

                         ┌────────────码流────────────→
原始块 X → 预测 P → 残差 R → 变换 → 量化 → 熵编码
              ↑                       │
              │                       ↓
参考帧缓冲 ← 环路滤波 ← 重建块 ← 反变换 ← 反量化

关键原因是:

解码器只能得到有量化误差的重建帧,编码器后续预测也必须使用同一版本的重建帧。

若编码器用无损的原始上一帧做参考,而解码器只能用有损的重建上一帧做参考,两边从下一帧开始就会产生不同预测;误差会沿参考链持续传播,这叫 drift(漂移)

3.4 编码器与解码器的不对称

编码器面对的是一个搜索问题:

块要不要拆分?
使用帧内还是帧间?
参考哪一帧?
运动矢量是多少?
使用哪种预测模式?
变换块多大?
QP 取多少?

解码器面对的是一个执行问题:

码流已经告诉我模式、参考索引、运动矢量差和量化系数;
我按标准规定恢复预测和残差即可。

这就是“编码复杂、解码相对简单”的根本原因。


四、数学原理

4.1 残差

对一个 N×M 块:

$$ r(i,j)=x(i,j)-p(i,j) $$

若当前块与预测块分别为:

$$ X= \begin{bmatrix} 52&55\ 54&58 \end{bmatrix},\qquad P= \begin{bmatrix} 50&54\ 53&60 \end{bmatrix} $$

则:

$$ R=X-P= \begin{bmatrix} 2&1\ 1&-2 \end{bmatrix} $$

与直接编码 50~60 附近的像素相比,编码集中在 -2~2 的残差通常更有利。

4.2 SAD:绝对误差和

Sum of Absolute Differences,绝对差之和:

$$ SAD(X,P)=\sum_{i,j}|x(i,j)-p(i,j)| $$

上述例子:

$$ SAD=|2|+|1|+|1|+|-2|=6 $$

特点:

  • 只需减法、绝对值和加法。
  • 速度快,适合大规模整数像素运动搜索。
  • 没有直接考虑编码该运动矢量和残差所需的比特数。

4.3 SSE:平方误差和

Sum of Squared Errors,平方误差和:

$$ SSE(X,P)=\sum_{i,j}(x(i,j)-p(i,j))^2 $$

上述例子:

$$ SSE=2^2+1^2+1^2+(-2)^2=10 $$

SSE 会更强烈地惩罚较大的单点误差。

考虑另一个候选预测:

$$ P_2= \begin{bmatrix} 51&52\ 55&57 \end{bmatrix} $$

其差值为 [1,3;-1,1]

  • SAD(P_2)=6,与第一个候选打平。
  • SSE(P_2)=12,因此 SSE 更偏好第一个候选。

4.4 SATD:变换域绝对误差和

Sum of Absolute Transformed Differences,变换后绝对差之和通常先对差值块做小型 Hadamard 变换,再累加系数绝对值:

$$ SATD(X,P)=\sum_{u,v}|H(X-P)H^T| $$

其中 H 是 Hadamard 变换矩阵。SATD 比 SAD 多一些计算,但能更好地反映残差进入变换编码后的能量分布,因此常用于较精细的模式决策。具体归一化、块大小和实现方式由编码器决定。

4.5 运动估计

设当前块为 B_t,参考帧为 F_r,候选运动矢量为 v=(v_x,v_y)

$$ v^*=\arg\min_v D\left(B_t, F_r(v)\right) $$

  • F_r(v):从参考帧按位移 v 取得的候选块。
  • D:SAD、SSE、SATD 或其他代价。
  • v^*:失真最小的候选运动矢量。

但只看失真仍不够。一个非常精确却很大的运动矢量,可能需要更多比特描述。因此实际编码决策通常使用率失真代价。

4.6 率失真优化

$$ J = D + \lambda R $$

  • J:总决策代价。
  • D:重建失真或其代理指标。
  • R:预计需要的比特数,包括模式、参考索引、运动矢量差和残差系数。
  • \lambda:失真与码率之间的权重。

例子:

候选失真 D预计比特 Rλ=2 时的 J
A10012124
B7525125

虽然 B 的预测更准,但 A 少用 13 bit;在 λ=2 时编码器会选择 A。

λ=0.5

  • J_A=106
  • J_B=87.5

此时编码器更重视质量,会选择 B。

直觉: 低码率或高 QP 时,编码器通常更不愿为小幅质量改善支付大量比特;高质量编码时则相反。

4.7 亚像素运动补偿

物体在两帧之间可能移动 0.5 或 0.25 个像素。参考帧没有直接存储“半像素”,解码器通过规范规定的插值滤波器生成这些位置的样本。

仅用于理解的线性示例:若相邻样本是 100 和 120,则中间半像素可近似为:

$$ p_{1/2}=\frac{100+120}{2}=110 $$

真实标准通常使用固定的多抽头滤波器、舍入和裁剪规则,而不是随意取平均。编码器搜索亚像素位置,解码器根据码流中的运动矢量执行同样的插值。

4.8 变换:把残差能量集中起来

以一个简化的二维正交变换说明直觉:

$$ H=\frac{1}{\sqrt2} \begin{bmatrix} 1&1\ 1&-1 \end{bmatrix},\qquad C=HRH^T $$

若残差块是:

$$ R= \begin{bmatrix} 4&4\ 4&4 \end{bmatrix} $$

则:

$$ C= \begin{bmatrix} 8&0\ 0&0 \end{bmatrix} $$

四个相同残差被集中到一个低频系数中,其余三个系数为 0。实际标准常使用整数变换或 DCT 类变换;不能笼统地说所有视频编码器都直接使用浮点 DCT。

4.9 量化

对变换系数 C(u,v)

$$ Q_C(u,v)=\operatorname{round}\left(\frac{C(u,v)}{q(u,v)}\right) $$

反量化:

$$ \hat C(u,v)=Q_C(u,v)\cdot q(u,v) $$

例子:

  • 原系数:37
  • 量化步长:8
  • 量化结果:round(37/8)=5
  • 反量化结果:5×8=40
  • 系数误差:3

量化步长越大,零系数通常越多、码率越低,但重建误差也越大。QP(Quantization Parameter,量化参数)通常用于控制量化强度,但 QP 到量化步长的精确映射由具体标准规定。

4.10 总比特预算

一个图像或帧的码率可粗略拆成:

$$ R_{total}=R_{header}+R_{mode}+R_{motion}+R_{residual}+R_{filter} $$

这解释了两个重要现象:

  1. 预测越复杂不一定越划算,因为模式和运动信息也要占比特。
  2. 残差为零不等于该块完全不花比特,仍可能需要模式或跳过标志。

五、底层数据结构

5.1 从图像到块

传统块式编码器通常可抽象为:

Sequence
 └─ Picture
     ├─ Picture / slice / tile level syntax
     └─ Block partition tree
         ├─ Prediction unit
         │   ├─ intra mode
         │   └─ 或 reference index + motion information
         ├─ Transform unit
         │   └─ quantized coefficients
         ├─ quantization information
         └─ loop-filter related information

上图是概念示意,不是某个标准的真实字段表。H.264 使用 macroblock 等术语;H.265 使用 CTU、CU、PU、TU 等层次。第 6~8 周再进入具体语法。

5.2 I、P、B picture

类型核心预测来源典型特征不能错误推断
I picture同一图像内已重建邻域随机访问候选、体积通常较大I 不一定就是 IDR
P picture参考图像列表中的一个方向或单列表预测为主常用于前向时间预测P 不等于“只参考显示顺序前一帧”
B picture可从两个参考列表选择、组合预测压缩效率通常较高,可能引入重排B 不一定永远是非参考帧

“帧类型”“是否是参考图像”“是否构成随机访问点”是不同维度。

5.3 关键帧、I picture 与 IDR

概念所属层次更准确的理解
I picture编码图像类型图像内的编码单元使用帧内预测
keyframe工程和播放器常用术语通常指可作为 Seek 起点的样本,但语义可能依上下文而异
IDRH.264/H.265 等标准中的随机访问语义阻止后续图像引用 IDR 之前的参考图像,并重置相应参考关系
reference picture解码参考关系被后续图像用于预测的重建图像

因此:

  • I picture 可能不是可靠的独立随机访问点。
  • B picture 在现代编码结构中也可能成为参考图像。
  • 容器中的“同步样本”标记与基础码流语义需要正确对应。

5.4 GOP

Group of Pictures,图像组是编码结构和工程配置中的常用概念。

简化显示顺序示例:

I  B  B  P  B  B  P  B  B  I
0  1  2  3  4  5  6  7  8  9

可能的参考关系:

I0 ─────────→ P3 ─────────→ P6 ─────────→ I9
  ↘ B1, B2     ↘ B4, B5      ↘ B7, B8

因为 B1、B2 需要 P3 作为未来显示参考,编码或解码输入顺序可能先处理 P3,再处理 B1、B2。显示顺序与解码顺序因此可能不同。

Closed GOP 与 Open GOP

  • Closed GOP: GOP 内图像不依赖前一个 GOP 的参考图像,随机访问边界更清晰。
  • Open GOP: 边界附近图像可能跨 GOP 引用,通常能提高一点压缩效率,但剪切、拼接和随机访问更复杂。

这些术语的精确行为取决于具体标准和编码器实现,不能只凭“看到 I 帧”判断是否真正闭合。

5.5 参考帧与 DPB

**Decoded Picture Buffer,解码图像缓冲区(DPB)**用于保存:

  1. 仍可能被后续图像引用的重建图像。
  2. 已解码但尚未到显示时刻的图像。
  3. 参考标记、图像顺序和输出状态等信息。

DPB 不是简单的“最近 N 帧数组”。某些较旧图像可能继续作为长期参考,某些刚解码图像可能不作为参考,但仍需等待输出。


六、编码器流程

6.1 总流程

输入原始帧

像素格式、位深和颜色信息归一化

lookahead:复杂度、场景切换、未来参考分析

决定图像类型和参考结构

块划分与候选模式生成

帧内预测 / 帧间预测

率失真选择

残差变换与量化

熵编码语法和量化系数

输出基础码流

同时执行本地重建:
反量化 → 反变换 → 加预测 → 环路滤波 → DPB

6.2 第一步:输入准备

编码器通常需要统一:

  • 像素格式,例如 YUV420P、NV12。
  • 位深,例如 8-bit 或 10-bit。
  • 色度采样位置。
  • 宽高、stride 和 plane。
  • 颜色 primaries、transfer、matrix 与 range 元数据。
  • 时间戳、帧率或帧持续时间。

这些不直接决定运动搜索,但错误的像素格式或时间信息会让整个编码输出失真或时间轴错误。

6.3 第二步:lookahead 与图像类型决策

lookahead 会暂存未来若干帧,用于估计:

  • 是否发生场景切换。
  • 哪些帧适合作为参考帧。
  • B picture 放置位置。
  • GOP 长度和关键帧位置。
  • 各帧复杂度与码率分配。

lookahead 越深,编码器通常有更多全局信息,但会增加:

  • 首帧输出延迟。
  • 原始帧内存占用。
  • 编码决策复杂度。

低延迟场景通常缩短或关闭 lookahead。

6.4 第三步:帧内预测

帧内预测使用同一图像中已经重建并允许引用的邻域样本,构造当前块预测。

典型思路包括:

  • DC:使用邻域平均值。
  • Vertical:沿垂直方向延伸上方样本。
  • Horizontal:沿水平方向延伸左侧样本。
  • Directional:沿特定角度外推边缘纹理。
  • Planar:平滑地结合上方和左侧边界。

为什么必须使用“已重建邻域”而不是原始邻域?因为解码器没有原始图像,必须保证两边预测输入一致。

6.5 第四步:帧间预测

运动估计

运动估计只发生在编码器:

当前块

在一个或多个参考帧的搜索窗中找候选位置

计算 SAD / SATD / RDO 代价

选择参考索引、运动矢量和预测方式

运动补偿

运动补偿发生在编码器和解码器:

参考帧 + 参考索引 + 运动矢量 + 插值规则

预测块

双向或双列表预测

预测块可以由两个参考预测加权组合:

$$ P=\operatorname{clip}\left(\frac{w_0P_0+w_1P_1+o}{s}\right) $$

  • P_0P_1:两个参考预测。
  • w_0w_1:权重。
  • o:偏置或舍入项。
  • s:缩放因子。
  • clip:裁剪到合法像素范围。

具体权重和舍入由标准或码流语法规定。

6.6 运动搜索伪代码

struct MotionCandidate {
    int refIndex;
    int mvX;
    int mvY;
    double distortion;
    double estimatedBits;
    double rdCost;
};

MotionCandidate searchMotion(
    const Block& current,
    const std::vector<Frame>& references,
    int searchRange,
    double lambda
) {
    MotionCandidate best{};
    best.rdCost = std::numeric_limits<double>::infinity();

    for (int ref = 0; ref < references.size(); ++ref) {
        for (int dy = -searchRange; dy <= searchRange; ++dy) {
            for (int dx = -searchRange; dx <= searchRange; ++dx) {
                Block prediction = interpolateBlock(references[ref], dx, dy);
                double d = satd(current, prediction);
                double r = estimateMotionBits(ref, dx, dy);
                double j = d + lambda * r;

                if (j < best.rdCost) {
                    best = {ref, dx, dy, d, r, j};
                }
            }
        }
    }
    return best;
}

这段代码只表达思想。真实编码器会使用:

  • 运动矢量预测。
  • 分层搜索。
  • diamond、hexagon、uneven multi-hexagon 等快速搜索。
  • 整数像素、半像素、四分之一像素逐级细化。
  • 提前终止。
  • 多种块划分。
  • SIMD 和多线程。

6.7 第五步:残差、变换与量化

原始块 X - 预测块 P

        残差 R

       变换系数 C

     量化系数 Q_C

扫描、零游程、上下文建模或其他熵编码

编码器还可能比较:

  • 不编码残差,直接使用预测。
  • 使用不同变换块大小。
  • 使用 transform skip 或 bypass(若标准支持)。
  • 调整块级 QP 或自适应量化。

6.8 第六步:熵编码

熵编码负责压缩:

  • 图像和块类型。
  • 划分结构。
  • 帧内模式。
  • 参考索引。
  • 运动矢量差。
  • QP 差。
  • 非零系数位置、幅值和符号。
  • 环路滤波参数。

熵编码本身通常是无损的;主要有损误差来自量化和某些有损预处理。H.264 可使用 CAVLC 或 CABAC,H.265 主要使用 CABAC,但这属于后续具体标准内容。12

6.9 第七步:本地重建与参考入库

量化系数
  ↓ 反量化
重建变换系数
  ↓ 反变换
重建残差
  ↓ + 预测块
未滤波重建块
  ↓ 环路滤波
重建图像

DPB / 参考帧缓冲

环路滤波为什么在“环”内

若滤波后的重建图像会被后续图像作为参考,滤波就是编码闭环的一部分。编码器和解码器必须一致执行。

显示端额外做的锐化、降噪或超分辨率,若不参与后续参考预测,则属于环外后处理。

6.10 场景切换

硬切场景中,上一帧内容与当前帧几乎无关:

  • 帧间预测残差突然增大。
  • 运动搜索可能找到“数值接近但语义错误”的候选块。
  • 继续强行使用长参考链可能既费码率又降低质量。

编码器常通过直方图、帧差、帧内/帧间代价比或 lookahead RDO 检测场景切换,并在合适位置插入 I picture 或随机访问图像。FFmpeg 和 libx264 暴露的 scene-cut、GOP、B-frame、参考帧和运动搜索参数属于编码器策略。3


七、解码器流程

7.1 总流程

基础码流

解析图像级和块级语法

熵解码

得到预测模式、参考索引、运动信息、量化系数

构造帧内或帧间预测块

反量化和反变换

预测 + 重建残差

环路滤波

DPB 管理

按显示时序输出

7.2 解码器不做什么

解码器通常不需要:

  • 搜索最佳运动矢量。
  • 尝试所有块划分。
  • 比较多个帧内模式。
  • 运行率失真优化。
  • 做场景切换决策。
  • 运行编码码率控制。

解码器只需按码流恢复编码器已经选定的方案。

7.3 概念伪代码

for (PictureSyntax picture : bitstream) {
    Frame reconstruction(picture.width, picture.height);

    for (BlockSyntax block : picture.blocksInDecodeOrder()) {
        Block prediction;

        if (block.isIntra()) {
            prediction = intraPredict(reconstruction, block.intraMode);
        } else {
            prediction = motionCompensate(
                dpb,
                block.referenceIndices,
                block.motionVectors,
                block.weightingParameters
            );
        }

        Coefficients qcoeff = entropyDecode(block.coefficientSyntax);
        Coefficients coeff = inverseQuantize(qcoeff, block.qp);
        Block residual = inverseTransform(coeff, block.transformType);
        reconstruction.write(block.position, clip(prediction + residual));
    }

    applyInLoopFilters(reconstruction, picture.filterParameters);
    dpb.markAndStore(reconstruction, picture.referenceCommands);
    outputWhenReady(dpb, picture.presentationOrder);
}

这仍然是跨标准抽象。真实解码顺序、边界可用性、参考列表和滤波流程必须遵循具体标准。


八、复杂度与性能

8.1 运动搜索的数量级

以 1920×1080 图像、16×16 块、整数像素搜索范围 [-64,+64] 为极端全搜索估算:

  • 每个方向候选数:129
  • 每块候选数:129×129=16,641
  • 每个候选比较样本数:256
  • 每块绝对差运算约:16,641×256≈4.26×10^6
  • 图像块数约:120×68=8,160
  • 每帧约:3.48×10^10 次样本差比较
  • 30 FPS 时约:1.04×10^12 次/秒

这是用于建立数量级直觉的上界估算,真实编码器会利用边界裁剪、快速搜索、层级金字塔、预测起点、提前终止和硬件并行大幅减少工作量。

8.2 参考帧内存

8-bit YUV420P 单帧理论数据量:

$$ Bytes = width\times height\times1.5 $$

分辨率单帧约占用4 帧8 帧16 帧
1920×10803.11 MB12.44 MB24.88 MB49.77 MB
3840×216012.44 MB49.77 MB99.53 MB199.07 MB

实际硬件表面还可能包含:

  • stride 对齐。
  • 额外 padding。
  • 10-bit 或更高位深。
  • 压缩表面或厂商私有布局。
  • 重排帧、输入队列和滤镜中间缓冲。

因此不能只按 width×height×1.5×参考帧数 估算整个解码器或编辑器内存。

8.3 主要性能瓶颈

阶段常见瓶颈
运动估计候选数、参考帧数、内存带宽、亚像素插值
模式决策块划分组合爆炸、重复变换与熵估算
变换量化小块密集计算、缓存局部性
熵编码分支、上下文依赖、串行性
环路滤波邻块依赖、跨行和跨 tile 边界
DPB大分辨率表面、参考读带宽、重排
Web 编辑JS 与媒体线程调度、GPU 上传、帧生命周期、复制

8.4 CPU、GPU 与专用硬件

  • CPU 软件编码:搜索灵活、可控性高,但功耗和延迟可能较高。
  • GPU compute:适合滤镜、缩放、合成,也可用于部分编码前处理。
  • 固定功能硬件编解码器:吞吐量和功耗好,但可用参数、格式和并发资源受设备限制。
  • 浏览器中的 WebCodecs 通常只暴露标准化接口,不保证使用何种底层实现,也不向页面暴露完整运动矢量、分块树或 RDO 过程。WebCodecs 不要求用户代理支持任何特定 codec,必须运行时探测。4

8.5 并行化

可利用的并行层次包括:

  • 多帧并行。
  • slice、tile 或行级并行。
  • 块级候选并行。
  • SIMD 向量化 SAD、插值、变换和滤波。
  • CPU 与 GPU 流水线重叠。

但预测依赖和熵编码上下文会限制完全并行。更多 tile 或 slice 可能提高并行度,却可能损失跨边界预测效率或增加头部开销。

8.6 低延迟取舍

为了降低首帧和端到端延迟,常见做法是:

  • 关闭或减少 B picture。
  • 缩短 GOP。
  • 减少参考帧。
  • 缩短或关闭 lookahead。
  • 限制重排。
  • 使用低延迟码率控制和较小缓冲。
  • 选择更快的编码 preset。

代价通常是同等质量下码率上升,或同等码率下质量下降。


九、实际场景

A. AI 生成视频网页

9.1 模型时序一致性直接影响可压缩性

AI 生成视频即使主观上“看起来相似”,若相邻帧中存在:

  • 纹理随机抖动。
  • 人脸细节持续变化。
  • 背景颗粒闪烁。
  • 物体轮廓轻微形变。
  • 光照和颜色无规则跳动。

运动补偿也难以找到稳定预测,残差会变大。此时高码率并不一定是编码器问题,而可能是生成内容缺乏时间一致性。

9.2 边生成边预览

推荐思路:

生成帧

排队与时间戳分配

低延迟编码:短 GOP、少重排、受控 lookahead

生成连续媒体分片

浏览器 MSE 或其他播放管线

关键点:

  • 每个分片边界最好与可随机访问点协调。
  • 不能让生成速度无限领先于编码、网络或播放速度。
  • 场景切换或提示词阶段切换处可考虑新的随机访问点。
  • 预览编码与最终高质量编码可以分离。

9.3 预览与最终导出分层

产品阶段优先目标编码倾向
生成中预览低首帧、低延迟较快 preset、短 GOP、少 B picture
用户挑选片段可 Seek、稳定播放代理视频、规律关键帧
最终导出质量与体积更深搜索、更多 lookahead、离线编码

B. 在线 Web 视频编辑器

9.4 Seek 与 GOP

拖动到时间 t 时:

查找 t 之前最近的随机访问样本

从该点开始送入解码器

维护参考关系并连续解码

丢弃显示时间早于 t 的输出帧

显示目标帧

GOP 越长,平均压缩效率可能越高,但最坏 Seek 解码距离通常也越长。

9.5 为什么需要代理视频

代理视频可以使用:

  • 较低分辨率。
  • 较低码率。
  • 更短、规律的 GOP。
  • 易于硬件解码的 profile 和像素格式。

它降低 Seek、预览合成、帧缓存和 GPU 上传压力。最终导出仍基于原始高质量资产。

9.6 帧精确剪切

  • 切点位于可靠随机访问点:可能直接复制完整 GOP 或样本范围。
  • 切点位于 GOP 中间:通常要从前置关键帧解码到切点。
  • 要让输出从非关键帧位置独立开始,边界附近通常需重新编码。
  • 转场、滤镜、字幕、缩放和画中画会改变像素,通常需要解码、合成和重新编码。

9.7 快速拖动时间线

必须设计取消与代际编号:

用户请求 seek #41

用户立即请求 seek #42

标记 #41 结果为过期

重置或重建解码任务

只显示 #42 对应输出

否则旧请求晚到的帧可能覆盖新位置,造成画面回跳。


十、Web 实现视角

10.1 WebCodecs 所在位置

MP4 / WebM 文件
  ↓ demux(应用自行完成或使用库)
EncodedVideoChunk
  ↓ VideoDecoder
VideoFrame
  ↓ Canvas / WebGL / WebGPU / 分析
VideoFrame
  ↓ VideoEncoder
EncodedVideoChunk
  ↓ mux(应用自行完成或使用库)
MP4 / WebM

WebCodecs 提供编解码和原始媒体接口,但不负责自动完成 MP4/WebM demux、mux、时间线编辑或文件索引。规范也不要求浏览器实现任何特定 codec。4

10.2 能力探测

const decoderSupport = await VideoDecoder.isConfigSupported({
  codec: "avc1.640028",
  codedWidth: 1920,
  codedHeight: 1080,
});

if (!decoderSupport.supported) {
  // 选择其他 profile、codec、分辨率或服务端降级路径
}

不能因为 codec string 有定义,就假设当前浏览器、操作系统和硬件一定支持该配置。

10.3 Backpressure

VideoDecoder.decodeQueueSizeVideoEncoder.encodeQueueSize 可用于观察待处理请求数量;规范还定义了队列减少时的 dequeue 事件。4

概念代码:

function once(target: EventTarget, type: string): Promise<Event> {
  return new Promise(resolve => {
    target.addEventListener(type, resolve, { once: true });
  });
}

async function submitChunk(
  decoder: VideoDecoder,
  chunk: EncodedVideoChunk,
  maxQueue = 8,
): Promise<void> {
  while (decoder.decodeQueueSize >= maxQueue) {
    await once(decoder, "dequeue");
  }
  decoder.decode(chunk);
}

队列阈值不是统一标准答案,应根据:

  • 解码延迟。
  • 分辨率。
  • 帧率。
  • 设备能力。
  • 用户是否正在播放或拖动。
  • 允许的内存上限。

动态调整。

10.4 VideoFrame.close()

解码媒体可能占用大量系统或 GPU 资源。WebCodecs 的资源模型要求应用通过 close() 释放不再需要的 VideoFrame 引用,并明确建议尽快关闭。4

const decoder = new VideoDecoder({
  output(frame) {
    try {
      // drawImage、复制到目标缓冲,或把所有权明确传给下游
      renderSynchronously(frame);
    } finally {
      frame.close();
    }
  },
  error(error) {
    console.error("decode failed", error);
  },
});

若下游异步使用该帧,应先完成转移、克隆或复制,再关闭原对象;不能在消费者仍依赖资源时过早关闭。

10.5 浏览器通常看不到编码器内部决策

WebCodecs 页面层通常可以控制:

  • codec、分辨率、码率、帧率等配置。
  • 某帧是否请求 key frame。
  • 输入时间戳。
  • 编码和解码队列。

但通常不能直接读取或指定:

  • 每个块的运动矢量。
  • 完整分块树。
  • 每个候选的 RDO 代价。
  • 精确运动搜索算法。
  • 所有参考帧选择细节。

若产品需要这些分析数据,通常要使用自有 WASM 编码器、服务端编码器、研究解码器或专门的码流分析工具。

10.6 Worker 与线程划分

推荐将以下任务移出主线程:

  • demux 和索引解析。
  • 编解码调度。
  • 时间线帧请求。
  • 缩略图生成。
  • 部分 Canvas 或 WebGL/WebGPU 渲染。

主线程重点处理交互与轻量状态更新。即使底层编解码器在独立媒体线程工作,页面代码仍可能因频繁复制、回调风暴或大对象生命周期管理不当而卡顿。


十一、易错点

  1. 运动估计不等于运动补偿。 前者是编码器搜索;后者是根据已知运动信息生成预测。
  2. 运动矢量不等于物体真实速度。 它是使编码代价较小的块对应关系。
  3. I picture 不等于 IDR。 帧内编码不自动保证完整随机访问语义。
  4. 关键帧不是严格统一的基础码流术语。 容器、播放器和编码器语境可能不同。
  5. B picture 不一定是非参考图像。 现代编码结构可以让 B picture 被后续图像引用。
  6. P picture 不等于只参考紧邻上一显示帧。 参考列表和重排可能更复杂。
  7. GOP 长度不等于固定的 I 帧间隔。 场景切换、open GOP 和编码器策略都会影响结构。
  8. 编码器不能用原始参考帧替代重建参考帧。 否则编码器与解码器会漂移。
  9. 残差小不代表码率一定小。 模式、运动信息和系数分布也影响比特数。
  10. SAD 最小不代表最终码率质量最优。 应考虑 D+λR
  11. QP 不是“质量百分比”。 它是控制量化强度的参数,映射由标准和编码器实现共同决定。
  12. 变换不产生主要有损误差。 正交或整数可逆近似本身不是主要损失源,量化通常才是核心有损环节。
  13. 熵编码不是有损压缩。 它无损地压缩已经选定的语法符号和量化系数。
  14. 去块滤波不一定只是播放器美化。 作为 in-loop filter 时,它影响后续参考预测。
  15. 参考帧数增加不总能提高质量。 还会增加搜索、信令、DPB 和内存带宽成本。
  16. lookahead 不是解码器需求。 它是编码器为了未来决策暂存原始帧的策略。
  17. 短 GOP 不一定画质更高。 它改善随机访问,却可能增加帧内编码开销。
  18. 长 GOP 不一定适合编辑。 Seek 和边界剪切成本会提高。
  19. WebCodecs 编码输出不是完整 MP4。 还需组织时间戳并 mux。
  20. 有 WebCodecs 接口不等于配置一定可用。 必须运行时探测。
  21. decodeQueueSize 不是内存占用字节数。 它只是待处理请求数量的一个信号。
  22. 调用 flush() 不是日常背压机制。 频繁 flush 可能破坏流水线效率。
  23. VideoFrame.close() 不能随意延迟到 GC。 大量媒体资源会造成显著内存压力。
  24. AI 视频高码率不一定是编码器差。 时序闪烁和形变会显著破坏帧间预测。

十二、面试题

下面每题给出“标准回答、追问、常见错误、高级回答”。回答以本周通用框架为主,不提前陷入某个标准的全部语法细节。

12.1 基础题(10 题)

1. 视频编码为什么需要预测?

  • 标准回答: 相邻像素和相邻帧高度相关。预测把原始块转换为更接近零的残差,便于变换、量化和熵编码。
  • 追问: 预测越准是否一定越好?
  • 常见错误: “预测后就不用编码残差了。”
  • 高级回答: 应比较率失真代价;更准的预测可能需要更多模式或运动比特。

2. 帧内预测和帧间预测有什么区别?

  • 标准回答: 帧内预测使用同一图像已重建邻域;帧间预测使用 DPB 中的重建参考图像。
  • 追问: 为什么都强调“重建”?
  • 常见错误: “帧内就是 I 帧,帧间就是 P 帧。”
  • 高级回答: 图像类型和块级模式并非总是一一对应;关键是预测样本的来源和标准允许的模式。

3. 运动估计和运动补偿有什么区别?

  • 标准回答: 运动估计在编码器中搜索运动信息;运动补偿在编码器和解码器中根据运动信息生成预测块。
  • 追问: 解码器需要做全搜索吗?
  • 常见错误: 把两者都说成“寻找运动矢量”。
  • 高级回答: 编码器会把运动矢量与预测值的差进行信令,解码器只需恢复并执行插值。

4. I、P、B picture 分别是什么?

  • 标准回答: I 主要使用图像内预测;P 主要使用一个参考列表方向的帧间预测;B 可使用两个参考列表和组合预测。
  • 追问: B picture 能否作为参考?
  • 常见错误: “B 一定不被参考。”
  • 高级回答: picture type、reference status 和 random-access status 应分开讨论。

5. GOP 是什么?

  • 标准回答: GOP 是一组具有特定预测和随机访问结构的连续编码图像,常用于描述关键帧间隔和参考结构。
  • 追问: 长 GOP 与短 GOP 如何取舍?
  • 常见错误: “GOP 就是两个 I 帧之间的帧数,所有编码器都严格固定。”
  • 高级回答: 还要区分 open/closed GOP、场景切换和 IDR 周期。

6. 什么是参考帧?

  • 标准回答: 被后续图像用于帧间预测的重建图像。
  • 追问: 所有已解码帧都会成为参考帧吗?
  • 常见错误: “参考帧就是上一帧。”
  • 高级回答: DPB 需要同时处理参考标记与输出重排,两者生命周期不同。

7. 什么是残差?

  • 标准回答: 原始块与预测块之差,编码器通常编码预测信息和量化残差,而非直接编码原像素。
  • 追问: 残差为什么还要变换?
  • 常见错误: “残差就是编码误差。”
  • 高级回答: 原始残差和量化后的重建误差不是同一个概念。

8. QP 越大通常意味着什么?

  • 标准回答: 通常意味着更粗的量化、更低码率和更大失真。
  • 追问: QP 每增加 1,质量是否线性下降?
  • 常见错误: “QP 是 0~100 的质量百分比。”
  • 高级回答: 需区分标准定义的 QP、量化步长映射、编码器 AQ 和感知优化。

9. 为什么需要去块滤波?

  • 标准回答: 块独立预测和量化可能导致边界不连续;去块滤波降低块效应,并在 in-loop 场景中改善后续参考质量。
  • 追问: 能否只在显示时滤波?
  • 常见错误: “只是播放器锐化。”
  • 高级回答: 环路内滤波会改变参考图像,必须由编码器和解码器一致执行。

10. 为什么编码器比解码器复杂?

  • 标准回答: 编码器要搜索块划分、预测模式、参考帧、运动矢量和 QP;解码器只执行码流已确定的选择。
  • 追问: 慢 preset 会增加解码复杂度吗?
  • 常见错误: “因为编码器做 DCT,解码器不做。”
  • 高级回答: 慢 preset 主要增加编码搜索,但某些选择也可能影响解码负载和并行性,不能绝对化。

12.2 底层实现题(10 题)

11. 为什么编码器必须本地重建?

  • 标准回答: 保证后续预测使用与解码器完全一致的参考图像,避免量化误差造成漂移。
  • 追问: 漂移如何传播?
  • 常见错误: “只是为了预览编码结果。”
  • 高级回答: 漂移会沿参考依赖图扩散,直到随机访问或参考刷新边界切断依赖。

12. 为什么运动矢量常编码成差值?

  • 标准回答: 邻近块的运动通常相关,使用预测运动矢量后只编码 MVD,分布更集中。
  • 追问: 预测运动矢量从哪里来?
  • 常见错误: “直接把绝对坐标写进码流。”
  • 高级回答: 候选可来自空间邻块、时间同位块或 merge/AMVP 类机制,具体由标准规定。

13. 亚像素运动补偿为什么有用?

  • 标准回答: 实际运动不一定是整数像素,亚像素插值能降低预测残差。
  • 追问: 半像素样本存在哪里?
  • 常见错误: “参考帧预先保存了所有半像素。”
  • 高级回答: 通常按规范滤波按需生成或缓存,工程上需权衡计算与内存。

14. 块为什么要有多种大小?

  • 标准回答: 大块适合平坦或一致运动区域,小块适合边缘、复杂运动和遮挡边界。
  • 追问: 为什么不全部用最小块?
  • 常见错误: “块越小质量一定越高。”
  • 高级回答: 小块会增加划分、模式和运动信令,并放大搜索复杂度。

15. skip 或 merge 类模式解决什么问题?

  • 标准回答: 当邻域已有合适运动信息且残差很小,可减少重复信令。
  • 追问: skip 是否代表像素完全不变?
  • 常见错误: “skip 就是复制上一帧同位置。”
  • 高级回答: 预测候选、参考索引和是否编码残差取决于具体标准模式。

16. 为什么环路滤波必须在参考帧入 DPB 前完成?

  • 标准回答: 后续运动补偿应引用规范规定的滤波后重建图像。
  • 追问: 编码器与解码器滤波结果差一位会怎样?
  • 常见错误: “滤波只影响当前显示。”
  • 高级回答: 任何不一致都可能沿参考链扩散为持续漂移。

17. DPB 为什么不等于简单 FIFO?

  • 标准回答: 图像是否仍作参考、是否已输出、显示顺序和长期参考状态可能不同。
  • 追问: B picture 带来什么额外状态?
  • 常见错误: “解码完就按输入顺序显示。”
  • 高级回答: 需要同时管理 reference lifecycle 与 output reordering。

18. 场景切换时编码器为什么常插入帧内图像?

  • 标准回答: 跨场景帧间相关性弱,继续预测会产生大量残差和无效运动搜索。
  • 追问: 是否每个硬切都必须 IDR?
  • 常见错误: “检测到色差就固定插 IDR。”
  • 高级回答: 应结合随机访问需求、VBV、GOP 约束和 open-GOP 策略决定。

19. 为什么 SATD 常比 SAD 更适合精细模式决策?

  • 标准回答: SATD 对差值做变换,更接近后续变换编码的能量与可编码性。
  • 追问: 为什么不对所有候选做完整熵编码?
  • 常见错误: “SATD 一定等于最终码流大小。”
  • 高级回答: 编码器采用分层筛选:快速指标淘汰,再对少量候选做更精确 RDO。

20. 为什么参考帧越多不一定越好?

  • 标准回答: 更多参考可能找到更好预测,但增加搜索时间、参考索引比特、DPB 和带宽。
  • 追问: 哪类内容可能更受益?
  • 常见错误: “把 refs 拉满就能免费提升质量。”
  • 高级回答: 周期运动、遮挡后重现和镜头静止可能受益,但需看码率和复杂度预算。

12.3 数学题(10 题)

21. 手算 SAD 与 SSE

  • 题目: 差值块为 [2,1;1,-2]
  • 标准回答: SAD=6SSE=10
  • 追问: 哪个更惩罚离群误差?
  • 常见错误: 忘记绝对值或平方。
  • 高级回答: 说明二者只是失真代理,不含码率项。

22. 用 RDO 选择候选

  • 题目: A 的 D=100,R=12,B 的 D=75,R=25λ=2
  • 标准回答: J_A=124J_B=125,选 A。
  • 追问: λ 变小时会怎样?
  • 常见错误: 只选 D 最小的 B。
  • 高级回答: λ 与量化强度、slice type 和编码器模型通常相关。

23. 半像素线性插值

  • 题目: 相邻样本 100 和 120,简化半像素值是多少?
  • 标准回答: 110。
  • 追问: 标准是否一定用简单平均?
  • 常见错误: 认为半像素不存在所以只能四舍五入到整数位置。
  • 高级回答: 真实规范采用固定滤波器、舍入与裁剪,确保跨实现一致。

24. 量化误差

  • 题目: 系数 37、步长 8。
  • 标准回答: 量化为 5,反量化为 40,误差 3。
  • 追问: 为什么大量小系数会变成 0?
  • 常见错误: 反量化后还能精确恢复 37。
  • 高级回答: 零系数提高扫描和熵编码效率,但会造成纹理丢失和振铃。

25. 1080p 参考帧内存

  • 题目: 1920×1080、8-bit YUV420P、8 个表面。
  • 标准回答: 理论像素数据约 1920×1080×1.5×8≈24.88 MB
  • 追问: 为什么实际更高?
  • 常见错误: 用 RGB24 的 3 bytes/pixel。
  • 高级回答: 加上 stride、对齐、重排、输入和 GPU 私有表面。

26. 全搜索候选数

  • 题目: 搜索范围 [-16,+16]
  • 标准回答: 每方向 33 个位置,共 33²=1089 个整数像素候选。
  • 追问: 加四分之一像素后能否简单乘 16?
  • 常见错误: 写成 32²
  • 高级回答: 实际通常先整数搜索,再对最优附近做局部亚像素细化。

27. 残差全为常数时变换有什么特点?

  • 标准回答: 能量集中在 DC 或最低频系数,其他系数接近 0。
  • 追问: 这为何利于压缩?
  • 常见错误: “变换直接删除了高频。”
  • 高级回答: 变换重分布能量,真正丢弃通常发生在量化。

28. λ 增大对模式选择有什么影响?

  • 标准回答: 更重视比特成本,倾向选择信令更省、残差更简单的模式。
  • 追问: 是否必然选择更大块?
  • 常见错误: “λ 越大质量越高。”
  • 高级回答: 只是概率趋势,最终仍由每个候选的 D 和 R 决定。

29. 预测完全准确时码率是否为 0?

  • 标准回答: 不一定;仍需编码块模式、参考索引、运动信息或 skip 标志等语法。
  • 追问: 什么机制可进一步减少这些比特?
  • 常见错误: “残差为 0 就完全无数据。”
  • 高级回答: 利用 merge、skip、上下文概率和运动预测压缩语法。

30. 30 FPS、GOP=60 的名义关键帧间隔是多少?

  • 标准回答: 若严格每 60 帧一个关键帧,则约 2 秒。
  • 追问: 为什么实际可能不是精确 2 秒?
  • 常见错误: 把 GOP 当作毫秒。
  • 高级回答: 可变帧率、场景切换、最小关键帧间隔和强制关键帧都会改变结果。

12.4 Web 实战题(10 题)

31. WebCodecs 解码为什么要做 backpressure?

  • 标准回答: 页面若持续送入 chunk,解码请求、输出帧和 GPU 资源会堆积;应根据队列和消费速度限流。
  • 追问: 只看 decodeQueueSize 足够吗?
  • 常见错误: 每次都 flush() 控制队列。
  • 高级回答: 同时观察输出帧缓存、渲染延迟、内存和代际取消状态。

32. VideoFrame.close() 为什么重要?

  • 标准回答: VideoFrame 可能引用大块系统或 GPU 媒体资源,应在下游完成使用后立即释放。
  • 追问: 能否依赖 GC?
  • 常见错误: draw 后永不 close。
  • 高级回答: 设计明确的所有权模型,跨 Worker 使用 transfer 或 clone 时也要处理原对象生命周期。

33. 如何实现时间线拖动到任意时间?

  • 标准回答: 查找前置随机访问样本,从该处解码并丢弃目标前输出。
  • 追问: 为什么不能只送目标 sample?
  • 常见错误: 按文件字节比例 seek。
  • 高级回答: 建立 sample 时间索引、同步样本索引、解码代际和最近窗口缓存。

34. 为什么 Web 编辑器需要代理视频?

  • 标准回答: 降低解码、Seek、帧缓存、GPU 合成和网络压力。
  • 追问: 代理应如何设计 GOP?
  • 常见错误: 代理只降低码率,不降低分辨率和 GOP。
  • 高级回答: 代理需保持与源素材可靠的时间映射、旋转和颜色元数据。

35. WebCodecs 能否直接导出 MP4?

  • 标准回答: VideoEncoder 输出编码 chunk,不自动生成完整 MP4;仍需 mux、sample 表和时间戳组织。
  • 追问: 配置数据放在哪里?
  • 常见错误: 把 chunk 按顺序拼接后命名为 .mp4
  • 高级回答: 正确处理 decoder configuration、关键帧标记、DTS/PTS、duration 和 fragmented MP4 组织。

36. 快速连续 Seek 如何避免旧帧覆盖新帧?

  • 标准回答: 使用请求 generation id,取消或 reset 旧解码任务,只接受当前代际输出。
  • 追问: reset 后第一块应满足什么条件?
  • 常见错误: 只在 UI 层忽略旧 Promise,不处理解码器队列。
  • 高级回答: 将 demux、decode、render 都绑定到同一代际并建立可中断调度器。

37. 如何探测浏览器编码能力?

  • 标准回答: 使用 VideoEncoder.isConfigSupported() 测试具体 codec、profile、分辨率和参数。
  • 追问: 返回 supported 是否保证持续实时?
  • 常见错误: 根据浏览器品牌硬编码支持表。
  • 高级回答: 还要做小样本运行测试、监测输出速度,并准备服务端降级。

38. AI 视频边生成边播放如何选择 GOP?

  • 标准回答: 预览偏向短 GOP、少重排和可控分片边界,以换取低延迟和错误恢复。
  • 追问: 最终导出是否沿用同一设置?
  • 常见错误: 生成每一帧都设为关键帧。
  • 高级回答: 采用快速预览编码和离线高质量重编码两条管线。

39. 页面内存持续增长如何排查?

  • 标准回答: 检查 VideoFrame 生命周期、解码输出缓存、Canvas/GPU 纹理、未取消任务和 demux 缓冲。
  • 追问: decodeQueueSize=0 是否说明没有泄漏?
  • 常见错误: 只看 JS heap。
  • 高级回答: 还要观察 GPU/process memory、帧引用图、transfer 与 clone 数量。

40. 为什么 WebCodecs 页面通常拿不到运动矢量?

  • 标准回答: WebCodecs 抽象的是编解码输入输出,不是标准码流分析和编码器决策调试接口。
  • 追问: 产品确实需要怎么办?
  • 常见错误:EncodedVideoChunk 属性直接读取 MV。
  • 高级回答: 使用服务端分析、WASM 解析器、研究解码器或编码时旁路生成元数据。

12.5 系统设计题(5 题)

41. 设计 AI 视频边生成边预览系统

  • 标准回答: 生成帧 → 时间戳队列 → 低延迟编码 → 连续分片 → MSE 播放;全链路有 backpressure。
  • 追问: 生成速度快于播放速度怎么办?
  • 常见错误: 把每帧 PNG 直接无限推给浏览器。
  • 高级回答: 设计预览质量档位、分片确认、缓冲上限、断点恢复和最终离线重编码。

42. 设计支持流畅拖动的在线编辑器预览管线

  • 标准回答: 导入时建索引和代理;Seek 从前置同步样本解码;窗口化帧缓存;GPU 合成;旧请求可取消。
  • 追问: 多轨同时可见时如何调度?
  • 常见错误: 每次鼠标移动都从源文件头开始解码。
  • 高级回答: 按可见贡献和层级优先级分配解码预算,复用共享资产解码结果。

43. 设计帧精确剪切与快速导出

  • 标准回答: 中间完整 GOP 尽量码流复制,边界 GOP 解码并重新编码,再正确 mux 和时间戳重写。
  • 追问: open GOP 如何处理?
  • 常见错误: 任意 sample 边界都直接 copy。
  • 高级回答: 建依赖分析,必要时扩大重编码边界,并验证参数集和参考完整性。

44. 设计 4K 多轨 Web 编辑器的内存控制

  • 标准回答: 使用代理、窗口缓存、帧对象所有权、及时 close、GPU 纹理池和全局内存预算。
  • 追问: 何时淘汰缓存?
  • 常见错误: 每条轨道缓存整段解码帧。
  • 高级回答: 结合播放方向、时间距离、重解码成本和资产共享度做加权淘汰。

45. 设计客户端预览、服务端最终导出的分工

  • 标准回答: 客户端维护非破坏性时间线并做低分辨率交互预览;服务端按同一时间模型做高质量解码、合成、编码和 mux。
  • 追问: 如何保证两端画面一致?
  • 常见错误: 只把最终 Canvas 录屏上传。
  • 高级回答: 版本化效果参数、字体与色彩配置,建立确定性渲染契约和抽帧对比测试。

十三、练习

练习 1:手算运动候选

当前块:

$$ X=\begin{bmatrix}52&55\54&58\end{bmatrix} $$

候选 A:

$$ P_A=\begin{bmatrix}50&54\53&60\end{bmatrix} $$

候选 B:

$$ P_B=\begin{bmatrix}51&52\55&57\end{bmatrix} $$

任务:

  1. 计算两个候选的 SAD 和 SSE。
  2. 若 A 的运动信息为 8 bit,B 为 3 bit,设 λ=1,分别用 SAD 和 SSE 作为 D 做一次选择。
  3. 解释为何不同失真指标可能给出不同排序。

答案要点: A:SAD 6、SSE 10;B:SAD 6、SSE 12。加入运动比特后 B 可能因信令更省而胜出。

练习 2:证明原始参考会漂移

构造一维例子:

  • 原始参考样本 X0=100
  • 量化重建后 X̂0=98
  • 下一帧真实样本 X1=105

分别计算:

  1. 编码器错误使用原始参考时的残差。
  2. 解码器使用重建参考恢复出的结果。
  3. 编码器正确使用重建参考时的残差。

答案要点: 错误路径编码残差 5,解码器得到 98+5=103;正确路径残差应为 7,解码器得到 105

练习 3:运动搜索伪代码

实现一个只支持灰度 8-bit、整数像素、固定 8×8 块、搜索范围 ±8 的 SAD 全搜索函数。要求:

  • 处理参考帧边界。
  • 返回 mvXmvY 和 SAD。
  • 对当前块越界、stride 异常做检查。
  • 统计实际比较的候选数。

进阶:增加运动矢量比特估算和 J=D+λR

练习 4:复杂度估算

计算 1280×720、16×16 块、搜索范围 ±32 时的:

  1. 每块整数候选数。
  2. 每帧块数。
  3. 全搜索样本差运算上界。
  4. 60 FPS 的每秒数量级。

然后说明 diamond search 为什么可能快很多,但为何不保证全局最优。

练习 5:DPB 内存预算

为以下配置估算理论像素内存:

  • 3840×2160。
  • 10-bit 4:2:0,以 16-bit 容器存放每个样本。
  • 8 个参考/重排表面。
  • 额外 4 个输入与滤镜表面。

提示:按存储容器计算时,每个 4:2:0 样本平均占 3 bytes/pixel,而不是 8-bit 时的 1.5 bytes/pixel

练习 6:GOP 与显示顺序

给定显示顺序:

I0 B1 B2 P3 B4 B5 P6

画出一种可能的:

  • 参考关系。
  • 解码输入顺序。
  • 显示输出顺序。
  • DPB 中等待输出的帧。

说明为什么只保存一个“上一帧”无法完成该结构的解码。

练习 7:在线编辑器 Seek 设计

设计一个函数:

seek(assetId: string, targetUs: number, generation: number): Promise<VideoFrame>

至少说明:

  • 如何查前置同步样本。
  • 如何重建 decoder config。
  • 如何送入 chunk。
  • 如何丢弃目标时间前帧。
  • 如何处理新的 generation 抢占旧请求。
  • 谁负责关闭未返回的 VideoFrame

练习 8:AI 视频压缩 Debug

同样 1080p、30 FPS、同一编码参数,两段 AI 视频码率差一倍。设计排查顺序:

  1. 检查实际帧率、分辨率和像素格式。
  2. 对比场景切换和镜头运动。
  3. 对比帧间差分与时序闪烁。
  4. 对比关键帧频率和 GOP。
  5. 对比 grain/noise、文字和细纹理。
  6. 最后再检查编码 preset、参考帧、B picture 和码率控制。

十四、本章速查表

14.1 名词

名词一句话解释
Intra prediction从同一图像已重建邻域预测当前块
Inter prediction从重建参考图像预测当前块
Motion estimation编码器搜索参考索引和运动矢量
Motion compensation按运动信息和插值规则生成预测块
Residual原始块减预测块
Reference picture被后续图像用于预测的重建图像
DPB管理参考图像和输出重排的解码图像缓冲区
GOP一组具有特定参考和随机访问结构的图像
QP控制量化强度的参数
RDO使用 D+λR 在质量和码率之间做决策
Lookahead编码器查看未来原始帧以优化结构和码率
In-loop filter结果进入参考闭环的滤波处理
Scene cut相邻图像内容关系突然中断的镜头切换

14.2 公式

$$ R=X-P $$

$$ SAD=\sum|X-P| $$

$$ SSE=\sum(X-P)^2 $$

$$ v^*=\arg\min_v D(B_t,F_r(v)) $$

$$ J=D+\lambda R $$

$$ Q_C=\operatorname{round}(C/q) $$

14.3 编码流程

输入帧
→ lookahead / 图像类型
→ 块划分
→ 帧内或帧间预测
→ RDO
→ 残差
→ 变换
→ 量化
→ 熵编码
→ 码流

本地闭环:
反量化 → 反变换 → 加预测 → 环路滤波 → DPB

14.4 解码流程

解析与熵解码
→ 预测模式、参考和运动信息
→ 反量化与反变换
→ 预测 + 残差
→ 环路滤波
→ DPB
→ 按显示顺序输出

14.5 面试结论

  1. 编码器搜索方案,解码器执行方案。
  2. 运动估计仅在编码器;运动补偿两端都有。
  3. 编码器必须引用重建帧,否则产生漂移。
  4. I picture、IDR、keyframe、reference picture 不能混为一谈。
  5. B picture 可能造成解码顺序和显示顺序不同,也可能作为参考。
  6. 最小 SAD 不一定是最优,应考虑 D+λR
  7. 量化通常是主要有损环节,熵编码通常无损。
  8. in-loop filter 的结果会进入参考预测闭环。
  9. 长 GOP 偏压缩效率,短 GOP 偏 Seek、编辑和恢复。
  10. AI 视频时序不一致会直接降低帧间压缩效率。
  11. WebCodecs 不是容器封装器,也不保证特定 codec 可用。
  12. Web 视频管线必须同时控制队列、帧生命周期、取消和内存。

十五、参考资料


本周完成标准

在不看资料的情况下,完成以下四项即可进入第 6 周:

  1. 在白板上画出编码器主路径和本地重建闭环。
  2. 用一个 2×2 块手算残差、SAD、SSE、量化和简单 RDO。
  3. 用三分钟解释 I/P/B、参考帧、GOP、关键帧和 IDR 的区别。
  4. 设计一个支持取消、backpressure、帧释放和前置关键帧 Seek 的 Web 解码流程。

Footnotes

  1. ITU-T Recommendation H.264, Advanced video coding for generic audiovisual services. https://www.itu.int/rec/t-rec-h.264 2

  2. ITU-T Recommendation H.265, High efficiency video coding. https://www.itu.int/rec/t-rec-h.265 2

  3. FFmpeg, Codecs Documentation. 文档列出 GOP、B-frames、reference frames、motion estimation、scene-cut、CRF、preset 等框架或编码器选项;这些选项不应误认为标准统一语法。https://ffmpeg.org/ffmpeg-codecs.html 2

  4. W3C, WebCodecs. 规范定义 VideoDecoderVideoEncoder、队列属性、配置探测和 VideoFrame 资源生命周期,同时不要求实现任何特定 codec。https://www.w3.org/TR/webcodecs/ 2 3 4