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)
数据封装与输出
/* x264_encoder_encode:
* encode one picture.
* *pi_nal is the number of NAL units outputted in pp_nal.
* returns the number of bytes in the returned NALs.
* returns negative on error and zero if no NAL units returned.
* the payloads of all output NALs are guaranteed to be sequential in memory. */
X264_API int x264_encoder_encode( x264_t *, x264_nal_t **pp_nal, int *pi_nal, x264_picture_t *pic_in, x264_picture_t *pic_out);
在 x264_encoder_encode 中,将输入的 x264_picture_t 转换为 x264_frame_t,并存储到帧缓冲区。
x264_frame_t 视频帧的核心结构(面向底层,注意与 x264_picture_t 区别)
typedef struct x264_frame
{
int i_poc; //帧的显示顺序编号(Picture Order Count)
int i_type; // 帧类型(I、P、B)
x264_param_t *param;
int i_frame; /* Presentation frame number */
int i_coded; /* Coded frame number */
int64_t i_field_cnt; /* Presentation field count */
int i_frame_num; /* 7.4.3 frame_num,帧在 GOP 中的编码顺序编号 */
/* YUV buffer */
int i_csp; /* Internal csp */
int i_plane;
int i_stride[3]; // 每个平面的行跨度
int i_width[3];
int i_lines[3];
pixel *plane[3]; // 帧的像素数据(内部存储)
pixel *plane_fld[3];
pixel *filtered[3][4]; /* plane[0], H, V, HV */
pixel *filtered_fld[3][4];
pixel *lowres[4]; /* half-size copy of input frame: Orig, H, V, HV */
uint16_t *integral;
int8_t *ref[2]; //存储引用帧的指针,供帧间预测使用
/* user frame properties */
uint8_t *mb_info;
void (*mb_info_free)( void* );
#if HAVE_OPENCL
x264_frame_opencl_t opencl;
#endif
} x264_frame_t;
/* x264_picture_alloc:
* alloc data for a picture. You must call x264_picture_clean on it.
* returns 0 on success, or -1 on malloc failure or invalid colorspace. */
X264_API int x264_picture_alloc( x264_picture_t *pic, int i_csp, int i_width, int i_height );
x264_picture_alloc 用于分配帧数据所需的内存并初始化帧结构。
x264_frame_t
x264_nal_t: x264_zone_t
x264_image_properties_t
x264_reference_frame_t x264_t x264_level_t
x264_slice_header_t x264_dct_function_t x264_deblock_function_t
NAL 在编码后的封装阶段起作用:
• 运动估计、DCT 变换、量化、熵编码等步骤处理的是原始的像素数据、运动矢量和频域信息,而 NAL 单元是在这些处理后生成的压缩数据的封装格式,用于高效传输和存储。 NAL(Network Abstraction Layer) 在 H.264 编码过程中主要出现在 编码后的封装阶段,它负责将编码后的数据(如 I 帧、P 帧、运动矢量等)组织成适合网络传输和存储的 NAL 单元。NAL 单元不仅对视频数据的传输和存储进行优化,还允许视频编码器在不同的环境中有效地处理视频流,包括流媒体、视频会议和广播等场景。
https://lazybing.github.io/blog/2017/06/23/x264-paraments-illustra/ https://juejin.cn/post/6844903511386243079#heading-5 https://blog.csdn.net/android_lee/article/details/6200423 https://juejin.cn/post/6844903783156154382 https://juejin.cn/post/7083679329183334407 https://blog.csdn.net/qq_28258885/article/details/119416168 https://cloud.tencent.com/developer/article/1608800 https://www.ctyun.cn/developer/article/460972599935045