x264源代码框架分析

X264编码器主*分的函数调用关系如下图所示。
x264源代码框架分析

从图中可以看出,x264主*分最复杂的函数就是x264_encoder_encode(),该函数完成了编码一帧YUV为H.264码流的工作。与之配合的还有打开编码器的函数x264_encoder_open(),关闭编码器的函数x264_encoder_close(),以及输出SPS/PPS/SEI这样的头信息的x264_encoder_headers()。实际中x264_encoder_headers()函数好像并没有执行,因为param->b_repeat_headers为1,即每个GOP前都重复填充pps与sps

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():初始化码率控制相关的变量。

x264_encoder_headers()输出SPS/PPS/SEI这些H.264码流的头信息。它调用了下面的函数:
x264_sps_write():输出SPS
x264_pps_write():输出PPS
x264_sei_version_write():输出SEI

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[]队列取出1帧用于编码。
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()关闭码率控制。

x264_encoder_close()用于关闭解码器,同时输出一些统计信息。它调用了下面的函数:
   x264_lookahead_delete():释放Lookahead相关的变量。
   x264_ratecontrol_summary():汇总码率控制信息。
   x264_ratecontrol_delete():关闭码率控制。

x264_encoder_close()是libx264的一个API函数。该函数用于关闭编码器,同时输出一些统计信息。该函数执行的时候输出的统计信息如下图所示。

 x264源代码框架分析

 

main()是x264控制台程序的入口函数,可以看出main()的定义很简单,它主要调用了两个函数:parse()和encode()。main()首先调用parse()解析输入的命令行参数,然后调用encode()进行编码。

        parse()用于解析命令行输入的参数(存储于argv[]中)。parse()的流程大致为:
(1)调用x264_param_default()为存储参数的结构体x264_param_t赋默认值;
(2)调用x264_param_default_preset()为x264_param_t赋值;
(3)在一个大循环中调用getopt_long()逐个解析输入的参数,并作相应的处理。举几个例子:
         a)“-h”:调用help()打开帮助菜单。
         b)“-V”调用print_version_info()打印版本信息。
         c)对于长选项,调用x264_param_parse()进行处理。
(4)调用select_input()解析输出文件格式(例如raw,flv,MP4…)
(5)调用select_output()解析输入文件格式(例如yuv,y4m…)

        encode()编码YUV为H.264码流,主要流程为:

(1)调用x264_encoder_open()打开H.264编码器;
(2)调用x264_encoder_parameters()获得当前的参数集x264_param_t,用于后续步骤中的一些配置;
(3)调用输出格式(H.264裸流、FLV、mp4等)对应cli_output_t结构体的set_param()方法,为输出格式的封装器设定参数。其中参数源自于上一步骤得到的x264_param_t;
(4)如果不是在每个keyframe前面都增加SPS/PPS/SEI的话,就调用x264_encoder_headers()在整个码流前面加SPS/PPS/SEI;
(5)进入一个循环中进行一帧一帧的将YUV编码为H.264:
        a)调用输入格式(YUV、Y4M等)对应的cli_vid_filter_t结构体get_frame()方法,获取一帧YUV数据。
        b)调用encode_frame()编码该帧YUV数据为H.264数据,并且输出出来。该函数内部调用x264_encoder_encode()完成编码工作,调用输出格式对应cli_output_t结构体的write_frame()完成了输出工作。
       c)调用输入格式(YUV、Y4M等)对应的cli_vid_filter_t结构体release_frame()方法,释放刚才获取的YUV数据。
       d)调用print_status()输出一些统计信息。
(6)编码即将结束的时候,进入另一个循环,输出编码器中缓存的视频帧:
       a)不再传递新的YUV数据,直接调用encode_frame(),将编码器中缓存的剩余几帧数据编码输出出来。
       b)调用print_status()输出一些统计信息。
(7)调用x264_encoder_close()关闭H.264编码器。