H.264 解析

简介

H.264(Advanced Video Coding),目前(2024)为止应用最广泛的视频压缩标准。具体协议本身在 ISO/IEC 14496-10 (MPEG-4 Part 10) 中。 关于 MPEG 的详述可以参看 MP4 解析 。14496-10 协议本身是付费的,最新版本大家自己搜索吧,这里提供一个 14496-10:2004 版本。

x.264 是 H.264 的一个开源实现,源码参看 videolan - x264,本文也会引用部分源码用于展示 H.254 的具体逻辑。

易混名词解释:

编码最核心目的是数据压缩,本文主要聚焦于编码过程中的结构

输入输出

x264 命令行示例:

x264 --input-res 1920x1080 --fps 30 -o output.mp4 yuv420.yuv

由上可以看出,输入为 yuv420.yuv,输出为 output.mp4,可以简单理解成 H.264 的(编码部分)内容就是将原始视频压缩为 mp4(由 mp4box 封装) 格式(或其他格式)。

(几乎所有)视频编码(如 H.264、H.265 等)的国际标准都使用 YUV 作为基础色彩空间,并对其进行了高度优化。至于为什么用 YUV 而不是 RGB,大家可以自行 chatgpt。

关于 x264 的参数可参阅 X264 参数详解,或者直接 man 也可以

编码流程

  1. 分帧处理:将视频分为宏块,并分别处理亮度和色度分量。
  2. 运动估计与补偿:通过运动矢量预测视频帧,减少冗余数据。
  3. 变换与量化:对每个宏块的亮度和色度数据进行 DCT 变换,并量化。
  4. 熵编码(CABAC/CAVLC):对量化后的数据进行高效编码,进一步压缩。
  5. 帧类型与帧间压缩:利用 I 帧、P 帧、B 帧进行帧间压缩,降低数据量。
  6. 数据封装与输出:将压缩后的视频数据封装成最终的输出格式。

x264 主要数据结构

GOP:Group of Picture,顾名思义,就是一个图片组,可以设置为固定长度,也可以动态,主要用于帧间压缩
Frame:一帧,可以简单理解成一张图片
Field:场,隔行扫描下,奇数场(Top Field)与偶数场(Bottom Field)组成一帧
Slice:片段,一帧包含若干个片段
Macroblock:宏块,一个片段包含若干宏块,一个宏块默认大小为 16*16,其下又包含若干子宏块,编码的基本单位
NAL:(NALU, Network Abstraction Layer Unit)

可以简单理解为:

其实就是总分关系,做逻辑隔离,方便处理

详细流程

分帧处理

大概步骤如下:

  1. 根据具体参数读取和解析 YUV 数据
  2. 将解析得到的帧据存储到 x264_picture_t、x264_frame_t 等结构中
  3. (默认)按 16×16 来分割帧数据进行宏块划分
  4. 预处理(颜色空间调整、去噪或帧缩放等操作)
  5. 将帧传递给编码器进行编码

其中宏块划分如下图:

MBSplit.png

即默认宏块大小为 1616,可划分为 168、816 或 88;而 88 的子宏块又可划分为 84、48 或 44。

宏块类型对应到 x264 代码则是 mb_class_emb_partition_e

各步骤的关键函数如下图,具体也可以看 example.c 了解函数调用时序:

步骤 关键文件 关键函数 描述
读取和解析 YUV 数据 common/frame.c x264_picture_alloc, 外部读取逻辑 分配内存,初始化 YUV 数据。
填充到帧结构 common/frame.h x264_encoder_encode 将 YUV 数据转化为帧结构。
宏块划分 encoder/macroblock.c x264_macroblock_cache_load, x264_macroblock_analyse 宏块数据划分与加载。
预处理和时间戳关联 common/mc.c x264_frame_filter 边缘扩展、滤波和时间戳管理。
传递到编码器 encoder/encoder.c x264_encoder_encode 帧传递并完成编码,输出 NAL 单元。

运动估计与补偿

变换与量化

熵编码

帧类型与帧间压缩

除了上述主要三种,还有其他类型帧,x264 代码如下:

/* Slice type */
#define X264_TYPE_AUTO          0x0000  /* Let x264 choose the right type */
#define X264_TYPE_IDR           0x0001
#define X264_TYPE_I             0x0002
#define X264_TYPE_P             0x0003
#define X264_TYPE_BREF          0x0004  /* Non-disposable B-frame */
#define X264_TYPE_B             0x0005
#define X264_TYPE_KEYFRAME      0x0006  /* IDR or I depending on b_open_gop option */
#define IS_X264_TYPE_I(x) ((x)==X264_TYPE_I || (x)==X264_TYPE_IDR || (x)==X264_TYPE_KEYFRAME)
#define IS_X264_TYPE_B(x) ((x)==X264_TYPE_B || (x)==X264_TYPE_BREF)

Slice type

数据封装与输出