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

数据封装与输出

/* 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 单元不仅对视频数据的传输和存储进行优化,还允许视频编码器在不同的环境中有效地处理视频流,包括流媒体、视频会议和广播等场景。

x264.h

H264基本原理 H264 基础原理介绍

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