最近我也开始看 X264 的代码了,于是想到把我读代码的过程记录下来,因为总有很多新手问如何读代码,我这个帖子就是专为这些人写的。至于会读代码的人就完全没有必要看了。下面 当然是以 X264 为例了。JM 以及其他代码的学习方法和技巧都是完全一样的。我所用的版本是在帖子 在VS2008下编译最新版的x264连接错误 里上传的版本。最新版本的代码基本结构应该变化不大。

首先肯定是要把 X264 编译通过了,这个我就不多说了,论坛帖子 VS2008下最新X264(svn 2009.9)编译不过的解决办法(附编译通过+修改内存泄露版本) 里讲得很清楚。编译通过之后第一步就是设置编码参数,一开始尽量从最基本的学起,因此采用最简单的选项,我的参数是:-q 28 -o test.264 f:/sequences/qcif/foreman_qcif.yuv 176x144 --no-asm,在哪里设置呢?看下面的截图(在第一个图中选择 properties 后就会出现第二个图了)。至于各个参数的意义嘛,看一看 X264 的帮助信息(x264.c 里面的 Help 函数),这里说说我用的这些参数的意思。-q 28 指定采用固定 QP = 28 进行编码,-o test.264 指定输出的码流文件名为 test.264,f:/sequences/qcif/foreman_qcif.yuv 这个当然是指定原始待编码图像序列了,176x144 指定待编码图像的宽高,--no-asm 指定不采用汇编优化,全 C 代码才方便我们跟踪噻。对了,差点忘记了,要调试运行还要设置一下下面第二个截图的红色框内的内容。

1.GIF       
              
==================================================================================

2.GIF

好了,配置完了就要开始调试运行了哦。在 x264.c 文件中找到 main 函数,在 x264_param_default 函数所在这一行上下断点(即把光标移到这一行,然后按 F9),然后调试运行编码器(即按 F5),程序就会停在断点处(如下面截图)。下面开始单步调试了,除了我提到的东西,其他都不需要关心。

3.GIF

我以前多次说过,读代码首先要读框架(即粗读),任务是大致搞清楚各个函数在干什么。那么 x264_param_default 是我们第一个遇到的函数,它的功能是什么呢?顾名思义,猜测肯定是进行一些参数的设置了,因为 param 就是英文单词 parammeter 啊,按 F11 进到函数里浏览一下,果然是对一些变量赋值的操作,那么多变量啥意思?现在不必管那么多,因为你现在的任务只是搞清楚各个函数的功能,不要跑题了。 x264_param_default 里的第一个函数是 memset,这个是系统函数,你要问啥功能?去查 MSDN;第二个函数是 x264_cpu_detect,啥功能?再顾名思义,因为 X264 针对不同的平台做了优化,这个肯定是用来检测你所使用的平台的,我就不进去看了。好了,直接点 debug 工具栏上的 step out(见下面第一个截图)返回 main 函数。你在你的 VC 里没有看到 debug 工具栏?那好吧,在 VC 工具栏的空白地方点鼠标右键,选中 debug(见下面第二个截图)。

4.GIF                              

==================================================================================

5.GIF

下面按 F10,程序执行到 Parse 函数,黄色的箭头也指向它了(见下面截图),它是啥功能?上面不是有英文注释么?——/* Parse command line */,意思就是说编码器就是在这个函数里读取我们上面设置的参数的。有兴趣进去看一下,看看就行,赶紧出来,不要又被陷在里面了,我就 pass 了。继续 F10 执行到 Encode,顾名思义,这个就是编码主函数了,因为后面再也没有函数了,所以编码一定是在这个函数里面完成的(不要问我它前面的 signal 是什么功能,我一开始说过了,我没提到的不需要关心,你问了我也不知道它是干什么的)。

6.GIF

好了,按 F11 进入 Encode 函数。一进去就有一些赋值语句,你要问那些变量是啥意思?——你跑题了!Encode 里第一个函数 p_get_frame_total 啥功能,顾名思义,我猜测是计算待编码图像序列一共有多少帧,要验证猜测的自己按 F11 跟进去看代码,我就 pass 了。继续 F10,到了函数 x264_encoder_open,它都做了些什么?F11 进去看看:memset,memcpy 是系统函数,F10 执行到 x264_validate_parameters,它在干什么?先想想 validate 是啥意思?然后 F11 进去,然后 F10 浏览一下好像还是在做一些参数的赋值和参数检查,浏览过程中对遇到的每个函数顾名思义一下其功能,自己考虑一下要不要跟到里面去。当我 F10 到 x264_sps_init 的时候,它的函数名让我有兴趣想知道它的具体功能——因为 SPS 跟编码直接关系。顾名思义SPS 初始化,F11 进去之后会发现是在对 SPS 结构体里的变量进行赋值。这些变量大部分在 200503 版标准 7.4.2.1 小节都能找到相应的解释。好了 step out 返回 x264_validate_parameters,继续 F10,好像其他没什么函数是我有兴趣的了,直接step out  返回 x264_encoder_open(或者在 x264_validate_parameters 里一直按 F10,最后也会返回到 x264_encoder_open)

好了,继续 F10(注意:x264_cqm_parse_file 这个函数是停不住的,它的功能顾名思义是解析量化矩阵配置文件,我们的编码选项里没有采用量化矩阵,因此它是不会被执行到的,你说我咋顾名思义知道它是跟 量化矩阵相关的,因为 cqm 就是 custom quant matrix,你再要问我咋知道 cqm 是 custom quant matrix,我猜的,真是猜的!),到 x264_reduce_fraction 函数,这回我顾名思义不出来它在干什么了,那就 F11 进去,看见一小段计算,也不知道在干什么。但是再看看 x264_reduce_fraction 的输入参数,i_fps_num,顾名思义是帧率,此时把鼠标移动到这个变量上显示它的值是 25,见下面截图(要想知道它在哪里被赋值的,回头去找噻,还记得我们在 main 里遇到的第一个函数是 x264_param_default 么,当时顾名思义它的功能是什么?如果你回去找了,你会发现里面有个语句是:param->i_fps_num = 25;那到底是不是它呢?很简单,把这里 25 改成其他值,例如 27,再重新调试执行到 x264_reduce_fraction,看 h->param.i_fps_num 是否等于 27 就知道了。)

7.GIF

中间来了个插曲打乱了叙述的逻辑节奏。刚才我们是在考虑 x264_reduce_fraction 的功能。F11 进去没什么收获,但从输入参数上我们可以猜测到它应该是在做跟帧率相关的计算。好了,就先了解这么多吧。继续 F10,到了 x264_sps_init,咦,刚才我们不是遇到过一个 x264_sps_init 了么?为什么这里又有一个呢?读者自己试着去猜测一下原因。
继续 F10,到了 x264_pps_init,顾名思义,猜测是对 PPS 的参数设置,其中大部分变量在 200503 版标准 7.4.2.2 小节都能找到相应的解释,有兴趣的进去浏览一下。继续 F10,到 x264_validate_levels,顾名思义是检查一些变量的值是否合理。
继续 F10,执行到 x264_cqm_init,顾名思义量化矩阵初始化(记心好的朋友一定记得前面有个跟 cqm 相关的函数,那个是解析量化矩阵文件,功能不一样哈)。
继续 F10,到 x264_rdo_init,顾名思义进行 RDO 的一些初始化操作,F11 进去之后根据里面的函数顾名思义猜测是在进行 CABAC 表的初始化。
继续 F10,到 x264_predict_16x16_init,顾名思义是进行与 16*16 预测相关的一些初始化,F11 进去对里面用到的变量顾名思义发现是在设置一些帧内 16*16 预测函数的函数指针。
继续 F10,到 x264_predict_8x8c_init,顾名思义是与色度预测相关的初始化操作,F11 进去对里面用到的变量顾名思义猜测是在设置一些帧内色度预测函数的函数指针。
继续 F10,到 x264_predict_8x8_init,顾名思义是与 8*8 预测相关的初始化操作,F11 进去对里面用到的变量顾名思义猜测是在设置一些帧内 8*8 预测函数的函数指针。
继续 F10,到 x264_predict_4x4_init,顾名思义是与 4*4 预测相关的初始化操作,F11 进去对里面用到的变量顾名思义猜测是在设置一些帧内 4*4 预测函数的函数指针。
继续 F10,到 x264_init_vlc_tables,顾名思义是与 CAVLC 相关的初始化操作,F11 进去浏览下代码会知道是在进行 VLC 表初始化。同时可以从这里猜测到 h->param.b_cabac 这个变量的含义表示是否采用 CABAC 编码,因为该变量为 0 才进行 CAVLC 表初始化。
继续 F10,到 x264_pixel_init,顾名思义是像素初始化,不懂具体啥意思,一头雾水,那就 F11 进去看看,根据里面大量出现 SAD/SATD 猜测是在设置运动估计时候计算代价用的一些函数指针,因为计算代价会用到 SAD/SATD。
继续 F10,到 x264_dct_init,顾名思义是 DCT 相关的初始化,F11 进去对里面用到的变量顾名思义猜测是在设置 DCT 变换函数的函数指针。
继续 F10,到 x264_zigzag_init,顾名思义是 zigzag 扫描相关的初始化,F11 进去对里面用到的变量顾名思义猜测是在设置 zigzag 扫描函数的函数指针。
继续 F10,到 x264_mc_init,顾名思义是运动补偿(MC)相关的初始化。
继续 F10,到 x264_quant_init,顾名思义是量化相关的初始化,F11 进去会发现里面是在设置量化相关的函数的函数指针。
继续 F10,到 x264_deblock_init,顾名思义是去块滤波相关的初始化,F11 进去会发现里面是在设置去块滤波相关的函数的函数指针。
继续 F10,到 x264_dct_init_weights,顾名思义我也没猜出是啥意思,F11 进去也没获得进一步信息,那就先把里面用到的变量在脑袋里拍个快照。等以后用到这些变量的时候再根据上下文来理解。
继续 F10,到 mbcmp_init,顾名思义我也没猜出是啥意思,F11 进去看见有个宏定义 X264_ME_TESA,猜测是跟运动估计相关的操作。
继续 F10,到 x264_frame_pop_unused,F11 进去,读一下里面的代码就知道是取得一个 x264_frame_t 结构体,猜测这个结构体是用于存放当前编码帧的信息的。
继续 F10,到 h->thread[ i ]->out.p_bitstream = x264_malloc( h->out.i_bitstream );,根据 p_bitstream 和 x264_malloc 顾名思义是开辟码流存储空间的。
继续 F10,到 x264_macroblock_cache_init,F11 进去浏览下代码,猜测是开辟宏块信息存放空间,以及进行一些宏块信息初始化设置的。
继续 F10,到 x264_ratecontrol_new,顾名思义是码率控制相关的函数。

好了,x264_encoder_open 函数就这么浏览完了,各个函数的大概功能也知道了。返回到 Encode 函数,F10 继续,到了 p_set_outfile_param,F11 进去发现是个空函数。F10 继续,到了 x264_picture_alloc,结合注释并顾名思义是分配解码过程中图像要用到的存储空间的。F10 继续,到 x264_mdate,从函数名不知道什么意思,F11 进去就知道了,是获取当前时间的。继续 F10 就到了 for 循环,从 for 循环的代码以及注释可以知道这个 for 循环的功能就是循环编码每一帧了。继续 F10,到 p_read_frame,顾名思义是在读入当前帧待编码图像。继续 F10,到 Encode_frame,顾名思义,这就是执行编码一帧图像的函数了,最核心的编码代码肯定就在这里面了。这个 for 循环之后还有个 do-while 循环,其中也调用了 Encode_frame 函数,根据注释猜测是进行 B 帧编码的。x264_mdate 刚才说了是在获取当前时间,x264_picture_clean 顾名思义是在释放图像的占用内存,x264_encoder_close 顾名思义是执行关闭编码器的一些操作,F11 进去后浏览代码会发现包括输出信息、释放内存等一些操作,x264_free 也释放内存,p_close_infile 和 p_close_outfile 顾名思义是关闭输入输出文件。

至此 X264 编码器第一层函数结构就分析完了。下面接着的就是采用同样的方法分析编码主函数 Encode_frame 了。经过上面的叙述,相信不会阅读代码的朋友一定已经掌握了阅读代码的入门技巧。当然越到深层函数的分析要求对 H.264 标准和 H.264 编码原理越熟悉,所以分析编码主函数的难度也比较大,耗时也比较长。因此学习 X264 代码是需要将 H.264 的编码知识与代码实现结合起来的,这样才能更容易读懂代码,这一点在上面的叙述中也有体现。

好了,暂时写到这里吧。打这么多字,真累啊!希望你们看着不会觉得累。呵呵~~~