on
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 的具体逻辑。
易混名词解释:
- codec:简而言之就是 /software/ or /hardware/,就是具体工具,例如 x264
- coding:编码格式 or 编码标准,例如 H.264
- container:容器格式,类似 mp4 等文件格式
编码最核心目的是数据压缩,本文主要聚焦于编码过程中的结构。
输入输出
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 也可以
编码流程
- 分帧处理:将视频分为宏块,并分别处理亮度和色度分量。
- 运动估计与补偿:通过运动矢量预测视频帧,减少冗余数据。
- 变换与量化:对每个宏块的亮度和色度数据进行 DCT 变换,并量化。
- 熵编码(CABAC/CAVLC):对量化后的数据进行高效编码,进一步压缩。
- 帧类型与帧间压缩:利用 I 帧、P 帧、B 帧进行帧间压缩,降低数据量。
- 数据封装与输出:将压缩后的视频数据封装成最终的输出格式。
x264 主要数据结构
GOP:Group of Picture,顾名思义,就是一个图片组,可以设置为固定长度,也可以动态,主要用于帧间压缩
Frame:一帧,可以简单理解成一张图片
Field:场,隔行扫描下,奇数场(Top Field)与偶数场(Bottom Field)组成一帧
Slice:片段,一帧包含若干个片段
Macroblock:宏块,一个片段包含若干宏块,一个宏块默认大小为 16*16,其下又包含若干子宏块,编码的基本单位
NAL:(NALU, Network Abstraction Layer Unit)
可以简单理解为:
- 一个视频包含若干 GOP
- 一个 GOP 包含若干 Frame
- 一个 Frame 包含若干 Slice
- 一个 Slice 包含若干 宏块
其实就是总分关系,做逻辑隔离,方便处理
- x264_t::mb:macroblock,宏块,分块的基本单位
- x264_image_t:待编码的图像
- x264_picture_t:x264编码视频帧(面向上层)
- x264_param_t:编码器的参数
- x264_frame_t:视频帧的核心结构(面向底层,注意与 x264_picture_t 区别)
- x264_t:
详细流程
分帧处理
大概步骤如下:
- 根据具体参数读取和解析 YUV 数据
- 将解析得到的帧据存储到 x264_picture_t、x264_frame_t 等结构中
- (默认)按 16×16 来分割帧数据进行宏块划分
- 预处理(颜色空间调整、去噪或帧缩放等操作)
- 将帧传递给编码器进行编码
其中宏块划分如下图:
即默认宏块大小为 1616,可划分为 168、816 或 88;而 88 的子宏块又可划分为 84、48 或 44。
宏块类型对应到 x264 代码则是 mb_class_e、mb_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 单元。 |
运动估计与补偿
变换与量化
熵编码
帧类型与帧间压缩
- I 帧(Intra-coded frame):完整的关键帧,包含了整个图像的所有信息,不依赖其他帧。每个 GOP 首帧为 I 帧。
- P 帧(Predicted frame):向前预测,基于前一帧(I 帧或 P 帧)来预测当前帧,只编码帧间的差异(残差)。
- B 帧(Bidirectional predicted frame):双向预测,通过前后帧(I 帧或 P 帧)预测当前帧,编码比 P 帧更小的差异。
除了上述主要三种,还有其他类型帧,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)