x264代码剖析(二):如何编译运行x264以及x264代码基本框架
x264工程在x265出现之前一直在更新,但是自x264-20091007(含)不再支持VC++平台,也就是说支持VC++平台的x264的最新版本是x264-20091006。接下来就以该版本为例简单介绍如何编译运行x264以及x264代码的基本框架。
首先下载x264-20091006,地址为:http://ftp.videolan.org/pub/videolan/x264/snapshots/,找到对应版本下载后直接解压缩就可以了。
1、编译运行x264
平台:win7 32bit、VS2010;
直接用VS打开“...\x264-091006\build\win32”文件夹下面的对应工程项目即可,由于VS版本与x264版本不一样,所以会自动兼容,全部采用默认兼容即可。编译过程中可能会出现一系列的问题,关于这部分将在后续进行分析解决。
已经解决:见网址:http://blog.csdn.net/frd2009041510/article/details/50822847
2、x264代码基本框架(从源码结构方面)
下面以x264-160228版本为例从源码结构方面分析其代码基本框架。
X264源码文件夹内包括的内容如下图所示:
2.1、encoder
Encoder文件夹内主要包括的内容如下图:
2.2、filters
filters文件夹内主要包括的内容如下图:
其中,video中包括:
2.3、input
input文件夹内主要包括的内容如下图:
2.4、output
output文件夹内主要包括的内容如下图:
2.5、extras
extras文件夹内主要包括的内容如下图:
2.6、doc
doc文件夹内主要包括的内容如下图:
2.7、tools和common
tools和common文件夹内包含内容较多,common文件夹内包含了一些共用的函数,如下图所示:
而tools则包含了常用的工具(个人尚未使用过),如下图所示:
3、x264代码基本框架(从编码结构方面)
下面以x264-160228版本为例从编码结构方面分析其代码基本框架。
主要参考:http://blog.csdn.net/leixiaohua1020/article/details/45536607
3.1、x264命令行程序
x264命令行程序指的是x264项目提供的控制台程序,通过这个程序可以调用libx264编码YUV为H.264码流。x264命令行工具的源码调用关系图如下图所示:
这个流程中最关键的API包括:
x264_param_default():设置参数集结构体x264_param_t的缺省值。
x264_encoder_open():打开编码器。
x264_encoder_headers():输出SPS,PPS,SEI等信息。
x264_encoder_encode():编码输出一帧图像。
x264_encoder_close():关闭编码器。
具体分析如下:
该程序的入口函数为main()。main()函数首先调用parse()解析输入的参数,然后调用encode()编码YUV数据。parse()首先调用x264_param_default()为保存参数的x264_param_t结构体赋默认值;然后在一个大循环中通过getopt_long()解析通过命令行传递来的存储在argv[]中的参数,并作相应的设置工作;最后调用select_input()和select_output()完成输入文件格式(yuv,y4m等)和输出文件格式(裸流,mp4,mkv,FLV等)的设置。encode()首先调用x264_encoder_open()打开编码器;接着在一个循环中反复调用encode_frame()一帧一帧地进行编码;最后在编码完成后调用x264_encoder_close()关闭编码器。encode_frame()则调用x264_encoder_encode()将存储YUV数据的x264_picture_t编码为存储H.264数据的x264_nal_t。
3.2、libx264类库的接口
在一个x264编码流程中,至少需要调用如下API函数:
x264_param_default():设置参数集结构体x264_param_t的缺省值。
x264_picture_alloc():为图像结构体x264_picture_t分配内存。
x264_encoder_open():打开编码器。
x264_encoder_encode():编码一帧图像。
x264_encoder_close():关闭编码器。
x264_picture_clean():释放x264_picture_alloc()申请的资源。
3.3、libx264主干函数
libx264主干函数指的是编码API之后,x264_slice_write()之前的函数,如下图所示。
从图中可以看出,x264主干部分最复杂的函数就是x264_encoder_encode(),该函数完成了编码一帧YUV为H.264码流的工作。与之配合的还有打开编码器的函数x264_encoder_open(),关闭编码器的函数x264_encoder_close(),以及输出SPS/PPS/SEI这样的头信息的x264_encoder_headers()。这一部分函数较多,暂时不详细分析,仅仅举几个例子列一下它们的功能。
3.3.1、x264_encoder_open()
x264_encoder_open()用于打开编码器,其中初始化了libx264编码所需要的各种变量。它调用了下面的函数:
x264_validate_parameters():检查输入参数(例如输入图像的宽高是否为正数)。
x264_predict_16x16_init():初始化Intra16x16帧内预测汇编函数。
x264_predict_4x4_init():初始化Intra4x4帧内预测汇编函数。
x264_pixel_init():初始化像素值计算相关的汇编函数(包括SAD、SATD、SSD等)。
x264_dct_init():初始化DCT变换和DCT反变换相关的汇编函数。
x264_mc_init():初始化运动补偿相关的汇编函数。
x264_quant_init():初始化量化和反量化相关的汇编函数。
x264_deblock_init():初始化去块效应滤波器相关的汇编函数。
x264_lookahead_init():初始化Lookahead相关的变量。
x264_ratecontrol_new():初始化码率控制模块。
3.3.2、x264_encoder_headers()
x264_encoder_headers()输出SPS/PPS/SEI这些H.264码流的头信息。它调用了下面的函数:
x264_sps_write():输出SPS
x264_pps_write():输出PPS
x264_sei_version_write():输出SEI
如下图所示:
3.3.3、x264_encoder_encode()
x264_encoder_encode()编码一帧YUV为H.264码流。它调用了下面的函数:
x264_frame_pop_unused():获取1个x264_frame_t类型结构体fenc。如果frames.unused[]队列不为空,就调用x264_frame_pop()从unused[]队列取1个现成的;否则就调用x264_frame_new()创建一个新的。
x264_frame_copy_picture():将输入的图像数据拷贝至fenc。
x264_lookahead_put_frame():将fenc放入lookahead.next.list[]队列,等待确定帧类型。
x264_lookahead_get_frames():通过lookahead分析帧类型。该函数调用了x264_slicetype_decide(),x264_slicetype_analyse()和x264_slicetype_frame_cost()等函数。经过一些列分析之后,最终确定了帧类型信息,并且将帧放入frames.current[]队列。
x264_frame_shift():从frames.current[]队列取出一帧用于编码。
x264_reference_update():更新参考帧列表。
x264_reference_reset():如果为IDR帧,调用该函数清空参考帧列表。
x264_reference_hierarchy_reset():如果是I(非IDR帧)、P帧、B帧(可做为参考帧),调用该函数。
x264_reference_build_list():创建参考帧列表list0和list1。
x264_ratecontrol_start():开启码率控制。
x264_slice_init():创建 Slice Header。
x264_slices_write():编码数据(最关键的步骤)。其中调用了x264_slice_write()完成了编码的工作(注意“x264_slices_write()”和“x264_slice_write()”名字差了一个“s”)。
x264_encoder_frame_end():编码结束后做一些后续处理,例如释放一些中间变量以及打印输出一些统计信息。其中调用了x264_frame_push_unused()将fenc重新放回frames.unused[]队列,并且调用x264_ratecontrol_end()关闭码率控制。
3.3.4、x264_encoder_close()
x264_encoder_close()用于关闭解码器,同时输出一些统计信息。它调用了下面的函数:
x264_lookahead_delete():释放Lookahead相关的变量。
x264_ratecontrol_summary():汇总码率控制信息。
x264_ratecontrol_delete():关闭码率控制。
3.4、x264_slice_write()
x264_slice_write()是x264项目的核心,它完成了编码了一个Slice的工作。根据功能的不同,该函数可以分为滤波(Filter),分析(Analysis),宏块编码(Encode)和熵编码(Entropy Encoding)几个子模块,如下图所示。
x264_slice_write()用于编码Slice。该函数中包含了一个很长的for()循环。该循环每执行一遍编码一个宏块。x264_slice_write()中以下几个函数比较重要:
x264_nal_start():开始写一个NALU。
x264_macroblock_thread_init():初始化存储宏块的重建数据缓存fdec_buf[]和编码数据缓存fenc_buf[]。
x264_slice_header_write():输出 Slice Header。
x264_fdec_filter_row():滤波模块。该模块包含了环路滤波,半像素插值,SSIM/PSNR的计算。
x264_macroblock_cache_load():将要编码的宏块的周围的宏块的信息读进来。
x264_macroblock_analyse():分析模块。该模块包含了帧内预测模式分析以及帧间运动估计等。
x264_macroblock_encode():宏块编码模块。该模块通过对残差的DCT变换、量化等方式对宏块进行编码。
x264_macroblock_write_cabac():CABAC熵编码模块。
x264_macroblock_write_cavlc():CAVLC熵编码模块。
x264_macroblock_cache_save():保存当前宏块的信息。
x264_ratecontrol_mb():码率控制。
x264_nal_end():结束写一个NALU。
3.5、滤波模块
滤波模块对应的函数是x264_fdec_filter_row()。该函数完成了环路滤波,半像素插值,SSIM/PSNR的计算的功能,如下图所示。
该函数调用了以下几个比较重要的函数:
x264_frame_deblock_row():去块效应滤波器。
x264_frame_filter():半像素插值。
x264_pixel_ssd_wxh():PSNR计算。
x264_pixel_ssim_wxh():SSIM计算。
3.6、分析模块
分析模块对应的函数是x264_macroblock_analyse()。该函数包含了帧内预测模式分析以及帧间运动估计等,如下图所示。
该函数调用了以下比较重要的函数(只列举了几个有代表性的函数):
x264_mb_analyse_init():Analysis模块初始化。
x264_mb_analyse_intra():I宏块帧内预测模式分析。
x264_macroblock_probe_pskip():分析是否是skip模式。
x264_mb_analyse_inter_p16x16():P16x16宏块帧间预测模式分析。
x264_mb_analyse_inter_p8x8():P8x8宏块帧间预测模式分析。
x264_mb_analyse_inter_p16x8():P16x8宏块帧间预测模式分析。
x264_mb_analyse_inter_b16x16():B16x16宏块帧间预测模式分析。
x264_mb_analyse_inter_b8x8():B8x8宏块帧间预测模式分析。
x264_mb_analyse_inter_b16x8():B16x8宏块帧间预测模式分析。
3.7、宏块编码模块
宏块编码模块对应的函数是x264_macroblock_encode()。该模块通过对残差的DCT变换、量化等方式对宏块进行编码。对于Intra16x16宏块,调用x264_mb_encode_i16x16()进行编码,对于Intra4x4,调用x264_mb_encode_i4x4()进行编码。对于Inter类型的宏块则直接在函数体里面编码。
从图中可以看出,宏块编码模块的x264_macroblock_encode()调用了x264_macroblock_encode_internal(),而x264_macroblock_encode_internal()完成了如下功能:
x264_macroblock_encode_skip():编码Skip类型宏块。
x264_mb_encode_i16x16():编码Intra16x16类型的宏块。该函数除了进行DCT变换之外,还对16个小块的DC系数进行了Hadamard变换。
x264_mb_encode_i4x4():编码Intra4x4类型的宏块。
帧间宏块编码:这一部分代码直接写在了函数体里面。
x264_mb_encode_chroma():编码色度块。
3.8、熵编码模块
CABAC熵编码对应的函数是x264_macroblock_write_cabac()。CAVLC熵编码对应的函数是x264_macroblock_write_cavlc(),如下图所示。
x264_macroblock_write_cavlc()调用了以下几个比较重要的函数:
x264_cavlc_mb_header_i():写入I宏块MB Header数据。包含帧内预测模式等。
x264_cavlc_mb_header_p():写入P宏块MB Header数据。包含MVD、参考帧序号等。
x264_cavlc_mb_header_b():写入B宏块MB Header数据。包含MVD、参考帧序号等。
x264_cavlc_qp_delta():写入QP。
x264_cavlc_block_residual():写入残差数据。
3.9、码率控制模块
码率控制模块函数分布在x264源代码不同的地方,包含了以下几个比较重要的函数:
x264_encoder_open()中的x264_ratecontrol_new():创建码率控制。
x264_encoder_encode()中的x264_ratecontrol_start():开始码率控制。
x264_slice_write()中的x264_ratecontrol_mb():码率控制算法。
x264_encoder_encode()中的x264_ratecontrol_end():结束码率控制。
x264_encoder_close()中的x264_ratecontrol_summary():码率控制信息。
x264_encoder_close()中的x264_ratecontrol_delete():释放码率控制。
截止到这儿,x264的基本框架就差不多完全介绍完了,具体的细节性问题以及代码分析将在后续进行不断更新。
(此处要多谢雷神提供众多的资料)