H.264/AVC 编码原理详解

H.264 是目前应用最广泛的视频编码标准,理解其编码原理对优化 RTC 视频质量至关重要。本章将深入介绍 H.264 的核心技术。

1. H.264 概述

1.1 发展历史

时间 事件
2003 年 H.264 标准正式发布
2004 年 保真范围扩展(FRExt)
2007 年 可伸缩视频编码(SVC)扩展
2010 年 多视点视频编码(MVC)扩展

1.2 H.264 的设计目标

目标 说明
高压缩效率 比 H.263 提升约 50%
网络友好 适应各种网络环境
低延迟 支持实时通信场景
容错能力 内置错误恢复机制

1.3 与前代标准的对比

特性 H.263 MPEG-4 H.264
帧内预测 DC/AC 9 种方向
帧间预测 16×16 16×16 可变块大小
变换 8×8 DCT 8×8 DCT 4×4/8×8 整数变换
熵编码 VLC VLC CAVLC/CABAC
环路滤波 可选 可选 强制

2. NAL 层结构

2.1 双层结构

H.264 采用双层结构:VCL(Video Coding Layer)和 NAL(Network Abstraction Layer)。

flowchart TB
    subgraph VCL 层
        A[编码核心]
        A1[预测]
        A2[变换]
        A3[量化]
        A4[熵编码]
    end

    subgraph NAL 层
        B[网络抽象]
        B1[NAL 单元封装]
        B2[起始码]
        B3[网络适配]
    end

    A --> B
    B --> C[输出码流]

分层的目的

  • VCL:专注于编码效率,不关心传输
  • NAL:负责将 VCL 数据打包成适合网络传输的格式

2.2 NAL 单元结构

NAL 单元结构:
┌──────────────┬─────────────────────────────┐
│  NAL Header  │       RBSP Payload          │
│   (1 字节)   │         (变长)               │
└──────────────┴─────────────────────────────┘

NAL Header 格式:
|0|1|2|3|4|5|6|7|
|F|NRI|   Type   |

F (forbidden_zero_bit): 必须为 0
NRI (nal_ref_idc): 重要性标识 (00-11)
Type: NAL 单元类型 (1-31)

2.3 常用 NAL 类型

Type 名称 说明
1 非 IDR 切片 P/B 帧数据
5 IDR 切片 I 帧(关键帧)
6 SEI 补充增强信息
7 SPS 序列参数集
8 PPS 图像参数集
9 AUD 访问单元分隔符

2.4 SPS 与 PPS

SPS(Sequence Parameter Set):序列级参数

参数 说明
profile_idc Profile 标识
level_idc Level 标识
pic_width_in_mbs 图像宽度(宏块数)
pic_height_in_map_units 图像高度
max_num_ref_frames 最大参考帧数
frame_mbs_only_flag 是否只有帧宏块

PPS(Picture Parameter Set):图像级参数

参数 说明
pic_parameter_set_id PPS 标识
seq_parameter_set_id 关联的 SPS
num_slice_groups 片组数量
num_ref_idx_l0_active L0 参考帧数量
entropy_coding_mode_flag 熵编码模式

3. 宏块结构

3.1 宏块组成

一个 16×16 的宏块由以下部分组成:

宏块 (16×16):
┌─────────────────────────────────┐
│          16×16 亮度 (Y)          │
│    可分割为 16×16, 16×8, 8×16,   │
│         8×8, 8×4, 4×8, 4×4       │
├─────────────┬───────────────────┤
│   8×8 Cb    │     8×8 Cr        │
│  (色度U)    │    (色度V)         │
└─────────────┴───────────────────┘

对于 YUV420:
- 1 个 16×16 亮度块
- 2 个 8×8 色度块

3.2 宏块分割模式

帧内预测分割

帧内预测支持的块大小:
┌──────────────────┐  ┌────────┬────────┐
│                  │  │        │        │
│      16×16       │  │  16×8  │  8×16  │
│                  │  │        │        │
└──────────────────┘  └────────┴────────┘

更小的 4×4 块用于细节区域:
┌────┬────┬────┬────┐
│4×4 │4×4 │4×4 │4×4 │
├────┼────┼────┼────┤
│4×4 │4×4 │4×4 │4×4 │
├────┼────┼────┼────┤
│4×4 │4×4 │4×4 │4×4 │
├────┼────┼────┼────┤
│4×4 │4×4 │4×4 │4×4 │
└────┴────┴────┴────┘

帧间预测分割

帧间预测支持的块大小:

16×16          16×8           8×16
┌──────┐      ┌──────┐      ┌──┬───┐
│      │      │      │      │  │   │
│      │      ├──────┤      │  │   │
│      │      │      │      │  │   │
└──────┘      └──────┘      └──┴───┘

8×8 可进一步分割:
┌──┬──┐
│8×8│8×8│    每个 8×8 可分为: 8×4, 4×8, 4×4
├──┼──┤
│8×8│8×8│
└──┴──┘

3.3 分割选择策略

场景 推荐分割 原因
平坦区域 16×16 大块预测效率高
边缘区域 4×4 小块更精确
运动物体 匹配物体大小 减少残差
纹理复杂 小块 更好匹配纹理

4. 帧内预测

4.1 帧内预测原理

帧内预测利用同一帧内已编码像素预测当前块:

帧内预测示意:
              已编码像素 (上方)
    ┌─────────────────────┐
    │  A  B  C  D  E  F   │
左  ├──┌─────────────────┤
侧  │  │                 │
已  │I │   当前块        │
编  │J │   (待预测)      │
码  │  │                 │
    └──┴─────────────────┘

预测值由 A-F 和 I-J 计算得出

4.2 4×4 亮度预测模式

H.264 为 4×4 块提供 9 种预测模式:

9 种帧内预测方向:

模式 0 (垂直)      模式 1 (水平)     模式 2 (DC)
┌─────────┐       ┌─────────┐       ┌─────────┐
│  │  │  │ │       │─────────│       │ 平均值  │
│  │  │  │ │       │─────────│       │ 平均值  │
│  │  │  │ │       │─────────│       │ 平均值  │
│  │  │  │ │       │─────────│       │ 平均值  │
└─────────┘       └─────────┘       └─────────┘

模式 3-8 (对角方向):
模式 3: 左下45°   模式 4: 右下45°   模式 5: 垂直偏右
模式 6: 水平偏下  模式 7: 垂直偏左  模式 8: 水平偏上

4.3 16×16 亮度预测模式

对于大块,H.264 提供 4 种简化的预测模式:

模式 名称 适用场景
0 垂直 垂直边缘
1 水平 水平边缘
2 DC 平坦区域
3 平面 渐变区域

4.4 色度预测

色度块(8×8)也有 4 种预测模式,与 16×16 亮度类似:

模式 名称
0 DC
1 水平
2 垂直
3 平面

4.5 模式选择

编码器通过率失真优化(RDO)选择最佳模式:

代价计算:
Cost = D + λ × R

D: 失真(原始块与重建块的差异)
R: 码率(编码该模式需要的比特数)
λ: 拉格朗日乘子(由 QP 决定)

选择 Cost 最小的模式

5. 帧间预测

5.1 帧间预测原理

帧间预测从参考帧中寻找匹配块:

flowchart LR
    subgraph 当前帧
        A[当前块<br>待编码]
    end

    subgraph 参考帧
        B[搜索窗口]
        C[最佳匹配块]
    end

    A --> |运动估计| B
    B --> |找到| C
    C --> |运动矢量 MV| D[编码]
    C --> |残差| D

5.2 运动估计

整像素搜索

搜索范围示例 (±16 像素):
参考帧:
┌─────────────────────────────┐
│                             │
│    ┌─────────────┐          │
│    │ 搜索窗口    │          │
│    │  32×32     │          │
│    │   ┌──┐     │          │
│    │   │最│     │          │
│    │   │佳│     │          │
│    │   │匹│     │          │
│    │   │配│     │          │
│    │   └──┘     │          │
│    └─────────────┘          │
│                             │
└─────────────────────────────┘

亚像素精度

亚像素插值:

整像素位置:    ●   ●   ●   ●
               |
1/2 像素插值:  ● × ● × ● × ●
               |
1/4 像素插值:  ●○×○●○×○●○×○●

● = 整像素
× = 1/2 像素
○ = 1/4 像素

H.264 支持 1/4 像素精度

5.3 运动矢量

运动矢量结构

运动矢量 (MV):
┌──────────────────────────┐
│  水平分量 (mvx)  │  垂直分量 (mvy)  │
│     (有符号)     │     (有符号)      │
└──────────────────────────┘

示例:
当前块位置: (100, 200)
匹配块位置:  (105, 198)
运动矢量:    (5, -2)

表示当前块相对于参考帧向右移动5像素,向上移动2像素

运动矢量预测

由于相邻块的运动通常具有空间相关性(同一物体的运动在相邻块间相似),H.264 不直接编码运动矢量,而是编码预测差值。

利用空间相关性预测 MV:

    B (上方块)      已编码
      │
C ─── A (左块) ─── 当前块 (待编码)
(左上)   已编码

MV_A = 左边已编码块的运动矢量
MV_B = 上边已编码块的运动矢量
MV_C = 左上或右上已编码块的运动矢量

预测值: MV_pred = median(MV_A, MV_B, MV_C)  // 取中值
实际编码: MVD = MV - MV_pred  // 只传差值

预测编码的优势

  • 相邻块 MV 相似时,MVD 接近零,编码比特少
  • 中值预测对异常值不敏感,稳定性好
  • 典型可节省 30-50% 的运动矢量编码比特
    ```

5.4 参考帧管理

多参考帧

H.264 支持最多 16 个参考帧:

参考帧列表:

List 0 (前向参考):
┌───┬───┬───┬───┬───┐
│-5 │-4 │-3 │-2 │-1 │ ← 当前帧
└───┴───┴───┴───┴───┘

List 1 (后向参考,用于 B 帧):
                              当前帧 →
┌───┬───┬───┬───┬───┐
│+1 │+2 │+3 │+4 │+5 │
└───┴───┴───┴───┴───┘

参考帧选择的好处

  • 场景切换时选择最佳参考
  • 周期性场景可选择更远的参考
  • 提高压缩效率

5.5 B 帧预测

B 帧可以同时使用前向和后向参考:

B 帧预测模式:

时间轴:  ────────────────────────────>
         │         │         │
        I帧       B帧        P帧
      (参考1)   (当前)    (参考2)

B 帧可使用的预测:
1. 前向预测: 从 I 帧预测
2. 后向预测: 从 P 帧预测
3. 双向预测: 从 I 帧和 P 帧加权平均

双向预测示例:
预测值 = 0.5 × 前向预测值 + 0.5 × 后向预测值


## 6. 变换与量化

### 6.1 整数变换

H.264 使用 4×4 整数变换替代传统 DCT:

**整数变换的优势**:
- 无舍入误差
- 计算简单(只用加法和移位)
- 逆变换精确可逆

**变换过程**:


4×4 残差块 → 整数变换 → 量化

变换核 (简化):
┌───────────┐
│ 1  1  1  1│
│ 2  1 -1 -2│
│ 1 -1 -1  1│
│ 1 -2  2 -1│
└───────────┘

计算复杂度低,适合硬件实现


### 6.2 量化

**量化公式**:


量化:
|Coeff| = (|残差| × QScale + Offset) >> QBits

反量化:
残差' = |Coeff| × QScale

QP 范围: 0-51
QStep 随 QP 指数增长


**QP 与 QStep 的关系**:

| QP | QStep | 相对精度 |
|----|-------|----------|
| 0 | 0.625 | 最高 |
| 12 | 2.5 | 高 |
| 24 | 10 | 中 |
| 36 | 40 | 低 |
| 51 | 224 | 最低 |

### 6.3 变换系数扫描

**之字形扫描**(用于 4×4 块):


系数扫描顺序:
┌───┬───┬───┬───┐
│ 0 │ 1 │ 5 │ 6 │
├───┼───┼───┼───┤
│ 2 │ 4 │ 7 │12 │
├───┼───┼───┼───┤
│ 3 │ 8 │11 │13 │
├───┼───┼───┼───┤
│ 9 │10 │14 │15 │
└───┴───┴───┴───┘

从低频(0)到高频(15)排序
便于后续熵编码


## 7. 熵编码

### 7.1 CAVLC

**Context-Adaptive Variable Length Coding**

CAVLC 用于 Baseline Profile,特点:
- 基于上下文自适应选择码表
- 利用系数分布特性

**编码流程**:


残差系数编码:

1. 编码非零系数个数和拖尾1个数
   (coeff_token, 根据相邻块选择码表)

2. 编码拖尾1的符号
   (每个拖尾1用1bit表示符号)

3. 编码剩余非零系数的幅值
   (level_prefix + level_suffix)

4. 编码最后一个非零系数前的零个数
   (total_zeros)

5. 编码每个非零系数前的连续零个数
   (run_before)


### 7.2 CABAC

**Context-Based Adaptive Binary Arithmetic Coding**

CABAC 用于 Main/High Profile,压缩效率更高:


CABAC 编码流程:

┌──────────────┐
│  语法元素    │
└──────┬───────┘
       ↓
┌──────────────┐
│ 二值化        │  ← 将语法元素转为二进制串
└──────┬───────┘
       ↓
┌──────────────┐
│ 上下文建模    │  ← 根据上下文选择概率模型
└──────┬───────┘
       ↓
┌──────────────┐
│ 算术编码      │  ← 根据概率进行算术编码
└──────┬───────┘
       ↓
┌──────────────┐
│  输出比特流   │
└──────────────┘


**CABAC 的优势**:

| 特性 | 说明 |
|------|------|
| 自适应概率 | 根据已编码数据动态调整概率 |
| 上下文模型 | 利用相邻块的相关性 |
| 算术编码 | 可以分配小数比特 |

**CAVLC vs CABAC**:

| 特性 | CAVLC | CABAC |
|------|-------|-------|
| 复杂度 | 低 | 高 |
| 压缩效率 | 基准 | +5-15% |
| 并行性 | 好 | 差 |
| 适用场景 | 移动设备 | 高清视频 |

## 8. 环路滤波

### 8.1 去块滤波

**块效应产生原因**:
- 块边界使用不同的预测模式
- 量化导致块边界的系数失真

**滤波强度**:


边界强度 (Boundary Strength, BS):

BS = 4: 边界两边都是帧内预测
BS = 3: 边界一边是帧内预测
BS = 2: 参考帧不同或运动矢量差 ≥ 1 像素
BS = 1: 参考帧相同且运动矢量差 < 1 像素
BS = 0: 不需要滤波

BS 越大,滤波强度越高


**滤波条件**:

只有满足以下条件才滤波:

|p0 - q0| < α(QP) AND
|p1 - p0| < β(QP) AND
|q1 - q0| < β(QP)


其中 α 和 β 由 QP 决定,QP 越大,阈值越大(高压缩时允许更强滤波)。

**滤波效果**:

滤波前: 滤波后:
┌───┬───┐ ┌───────┐
│ A │ B │ 边界 │ 平滑 │
├───┼───┤ ────> │ 过渡 │
│ C │ D │ │ │
└───┴───┘ └───────┘


#### 滤波顺序


宏块滤波顺序:

1. 先滤波垂直边界 (从左到右)
   ┌─┼─┼─┼─┐
   │ │ │ │ │
   └─┴─┴─┴─┘

2. 再滤波水平边界 (从上到下)
   ┌───┬───┬───┬───┐
   ├───┼───┼───┼───┤
   ├───┼───┼───┼───┤
   └───┴───┴───┴───┘

3. 亮度先滤波,色度后滤波


## 9. Profile 与 Level

### 9.1 Profile 分类

| Profile | 特性 | 应用 |
|---------|------|------|
| Baseline | 无 B 帧、CABAC | 移动设备、视频通话 |
| Constrained Baseline | Baseline 子集 | WebRTC |
| Main | B 帧、CABAC | 广播 |
| High | 8×8 变换、量化矩阵 | 高清视频 |

**WebRTC 选择 Constrained Baseline 的原因**:
- 无 B 帧,保证低延迟
- 无 CABAC,解码复杂度低
- 广泛的硬件支持

### 9.2 Level 定义

| Level | 最大分辨率 | 最大帧率 | 最大码率 | 最大参考帧 |
|-------|-----------|----------|----------|-----------|
| 3.0 | 720p | 30fps | 10 Mbps | 4 |
| 3.1 | 720p | 30fps | 14 Mbps | 4 |
| 4.0 | 1080p | 30fps | 20 Mbps | 4 |
| 4.1 | 1080p | 30fps | 50 Mbps | 4 |
| 5.0 | 4K | 30fps | 135 Mbps | 16 |

### 9.3 Profile-Level-ID

在 SDP 中通过 profile-level-id 协商:


profile-level-id = 42e01f

解析:
0x42 = 66 = Baseline Profile
0xe0 = 约束标志
0x1f = 31 = Level 3.1


## 10. 编码工具选择

### 10.1 实时通信推荐配置

| 参数 | 推荐值 | 原因 |
|------|--------|------|
| Profile | Constrained Baseline | 低延迟、广泛支持 |
| B 帧 | 不使用 | 降低延迟 |
| 参考帧数 | 1-3 | 平衡效率与延迟 |
| 熵编码 | CAVLC | 低复杂度 |
| GOP 大小 | 大(按需请求关键帧) | 避免带宽突发 |

### 10.2 码率控制

| 场景 | 推荐策略 |
|------|----------|
| 网络稳定 | CBR(恒定码率) |
| 网络波动 | ABR(自适应码率) |
| 低延迟 | 固定 QP 或简单 ABR |

### 10.3 错误恢复策略

| 策略 | 说明 |
|------|------|
| 定期关键帧 | 简单但带宽浪费 |
| 按需关键帧 | 通过 PLI 请求 |
| NACK 重传 | 请求丢失的包 |
| FEC | 前向纠错 |

## 11. 总结

### 11.1 H.264 核心技术

| 技术 | 作用 | 关键点 |
|------|------|--------|
| 帧内预测 | 去除空间冗余 | 多方向预测 |
| 帧间预测 | 去除时间冗余 | 运动估计、多参考帧 |
| 整数变换 | 能量集中 | 4×4 块、无舍入误差 |
| 量化 | 去除视觉冗余 | QP 控制 |
| 熵编码 | 去除编码冗余 | CAVLC/CABAC |
| 环路滤波 | 消除块效应 | 去块滤波 |

### 11.2 关键优化点

1. **宏块分割**:根据内容选择合适的块大小
2. **运动估计精度**:1/4 像素精度平衡效率与复杂度
3. **参考帧管理**:合理利用多参考帧
4. **码率控制**:自适应码率应对网络波动
5. **错误恢复**:按需请求关键帧 + NACK + FEC

下一章将介绍音频编码技术。