x264阅读记录-1
采用x264版本是x264-snapshot-20060316-2245。
1. main函数
x264的main函数位于x264.c中,下面是main函数调用情况:
(1)_setmode函数和_fileno函数
这两个函数是微软提供的两个库函数。
_setmode函数位于io.h文件中,主要作用是设置特定模式匹配的文件。http://msdn.microsoft.com/zh-cn/library/vstudio/tw4k6df8.aspx
_fileno函数位于stdio.h文件中,用于获取文件流所对应的描述符。http://msdn.microsoft.com/zh-cn/library/vstudio/zs6wbdhx.aspx
下面的语句主要实现的是将Windows下默认的Text模式,按需要设置为BINARY模式,
_setmode(_fileno (stdin ), _O_BINARY );
还有一点,这个语句位于预编译命令中,其中涉及到一个宏_MSC_VER。这个宏是VC编译环境的预定义宏,主要是计算为编译器的主版本号和次版本号元素。 专业数字为句点分隔的版本号的第一个元素,并且该次版本号是第二个元素。
因此,如果Visual C++编译器的版本号为15.00.20706.01,_MSC_VER 宏计算结果为1500。在 Visual Studio 2010中,_MSC_VER 定义为1600。http://msdn.microsoft.com/zh-cn/library/vstudio/b0084kay.aspx
_MSC_VER实际就是 Microsoft visual c++ version. 具体对应如下:
MS VC++ 10.0 _MSC_VER = 1600
MS VC++ 9.0 _MSC_VER = 1500
MS VC++ 8.0 _MSC_VER = 1400
MS VC++ 7.1 _MSC_VER = 1310
MS VC++ 7.0 _MSC_VER = 1300
MS VC++ 6.0 _MSC_VER = 1200
MS VC++ 5.0 _MSC_VER = 1100
其主要作用是实现版本判断,从而实现兼容性控制. http://blog.csdn.net/stpeace/article/details/8282710
(2) x264_param_default 函数
这个函数用于对编码器的一些参数进行默认设置。
(3) Parse函数
这个函数主要是进行命令行参数的解析工作。
(4) signal函数
这个是系统调用,用于设置中断通知处理。本程序中主要是对Ctrl-C信号进行处理。
/* Control-C handler */
signal( SIGINT, SigIntHandler );
采用的处理函数为SigIntHandler,即:
static void SigIntHandler( int a ) { if( b_exit_on_ctrl_c ) exit(0); b_ctrl_c = 1; } |
(5)Encode函数
这个函数就是x264具体进行编码的函数。所有的编码相关的都是在这个函数中完成的。
2. x264_param_default 函数
该函数的调用情况如下:
这个函数完成参数的设置。所有的参数都是在x264_param_t这个结构体中有定义的。最先调用的memset函数就是对这个结构体进行初始化。
(1)x264_cpu_detect函数
这个是对编码器所使用的cpu进行检测,返回的是CPU的编号,主要作用是确定CPU所支持的指令集,因为在x264中有很多代码是直接采用汇编写的。确定了CPU的型号之后就可以选择相应的汇编代码了。
在该函数中所调用的两个函数x264_cpu_cpuid_test和x264_cpu_cpuid都是直接用汇编写成的。
具体的对这个函数的注释可以看【X264中x264_cpu_detect函数注解】。
(2)x264_log_default函数
这个函数是为了设置缺省日志。其实这儿这是进行了函数指针的赋值:
/* Log */ param-> pf_log = x264_log_default ; param-> p_log_private = NULL ; param-> i_log_level = X264_LOG_INFO ; |
3. Parse函数
这个函数是对命令行参数进行分析,然后将给定的参数值赋值给x264_param_t结构体中相应的变量。
下图是该函数的调用情况,可以看到涉及到了很多函数。
查看代码可以发现,其中有很多的函数并不是直接调用,而是对一些全局的函数指针进行了赋值,即:
/* Default input file driver */ p_open_infile = open_file_yuv; p_get_frame_total = get_frame_total_yuv; p_read_frame = read_frame_yuv; p_close_infile = close_file_yuv; /* Default output file driver */ p_open_outfile = open_file_bsf; p_set_outfile_param = set_param_bsf; p_write_nalu = write_nalu_bsf; p_set_eop = set_eop_bsf; p_close_outfile = close_file_bsf; |
具体是在for循环中进行的命令行解析。
其中包括了swtich语句,对分析出的参数进行相应的设置。
(1)getopt_long函数
linux下的命令行操作非常强大,而其中支持命令行的很重要函数就是getopt_long和getopt。这两个函数都是用来对命令行进行解析。其函数调用如下:
调用的函数比较多。函数内部也挺复杂,不再做分析。
(2)Help函数
很简单,主要是打印出相应的帮助信息,即命令行参数的使用方式。
4. Encode函数
该函数完成对整个序列的编码。其调用情况如下:
(1)p_get_frame_total
用于计算序列中含有的帧的数目。
这是一个函数指针,具体应用中根据情况选择不同的函数:
A. get_frame_total_yuv:raw 420 yuv file
B. get_frame_total_avis:avs/avi input file support under cygwin
(2)x264_encoder_open
这个函数主要完成对编码器的初始化配置。这个函数也是一个比较大的函数,调用了很多其他函数。后面会有具体分析。
(3)p_set_outfile_param
设置输出文件格式,这是一个函数指针,可选择的包括mp4和mkv格式。
(4)x264_picture_alloc
这个函数主要是为一帧图像分配内存空间,由于不同的颜色空间下,一帧图像的大小是不一样的,所以在这个函数中是按颜色空间的不同来进行区分的。
(6)x264_mdate
这个函数中获取了系统时间。根据系统的不同,选用不同的系统调用来得到当前时间。
int64_t x264_mdate ( void )
{
#if !(defined (_MSC_VER ) || defined( __MINGW32__))
struct timeval tv_date;
gettimeofday( &tv_date, NULL );
return( (int64_t) tv_date.tv_sec * 1000000 + (int64_t) tv_date.tv_usec );
#else
struct _timeb tb ;
_ftime(& tb);
return (( int64_t)tb .time * (1000) + (int64_t)tb .millitm ) * (1000);
#endif
}
(7)Encode_frame
在一个for循环中通过调用Encode_frame函数来完成对序列中每一帧的编码。
通过调用p_read_frame所指向的函数来将所要编码的当前帧读入到所分配的空间中去。
Encode_frame函数是完成一帧编码的重要函数,在后面详细的分析。
当前帧编码结束后,要进行nalu打包了。根据编码时确定的nalu个数,用x264_nal_encode进行打包。
这儿需要注意的一个问题是:在for循环之后有一个do-while循环再次调用Encode_frame来进行编码。根据注释/* Flush delayed B-frames */,这是和B帧相关的,所以在进一步查看编码过程式时,要注意具体遇到B帧时是怎么处理的。(这儿的合理解释应该是在缓冲区中如果还有B帧需要编码,就调用Encode_frame进行编码。)
(8)x264_picture_clean
该函数就是释放存放一帧图像所申请的空间。
(9)x264_encoder_close关闭编码器
该函数是对编码进行收尾工作,输出一些码流相关信息,并且释放编码器开始时所申请的空间。
5. x264_encoder_open 函数
函数调用情况如下:
可以看到调用了很多函数。并且在该函数中设置了VUI高级编码属性。
(1)x264_malloc
x264_t 是一个比较大的结构体,用于保存X264编码器在编码过程中所需要的维护的信息。x264_malloc函数完成对这个结构体空间的分配。
(2)x264_validate_parameters
该函数完成对命令行参数的检查。
(3)x264_free
与x264_malloc相对应,释放所分配的空间。
(4)x264_cqm_parse_file
该函数 解析量化矩阵配置文件。
(5)x264_sps_init
此函数为序列量化集的初始化。主要对结构体x264_sps_t中参数的初始化。
(6)x264_pps_init
该函数完成对PPS参数的设置,其中大部分变量都可以在200503版标准7.4.2.2小节中找到相应的解释。
(7) x264_validate_levels
主要是检查变量的值是否合法,编码标准中的level和profile。
(8)x264_cqm_init
完成量化矩阵的初始化。
(9)x264_macroblock_cache_init
这个函数主要对cache中与宏块相关的变量进行初始化,包括为它们分配对应的内存空间。
(10)x264_rdo_init
主要是进行RDO相关的初始化。
(11)x264_predict_16x16_init, x264_predict_8x8c_init, x264_predict_8x8_init, x264_predict_4x4_init
这些函数是对预测所使用到的函数进行初始化。后面在具体进行帧间预测的时候就会使用到。
x264_predict_t是一个函数指针类型,在代码中对于不同的块大小定义了相应的预测函数,即:
/* CPU functions dependants */
x264_predict_t predict_16x16[4+3];
x264_predict_t predict_8x8c[4+3];
x264_predict8x8_t predict_8x8[9+3];
x264_predict_t predict_4x4[9+3];
此处的4个函数就是对上面的四个函数指针数组进行赋值的:
/* init CPU functions */
x264_predict_16x16_init( h->param.cpu, h->predict_16x16 );
x264_predict_8x8c_init( h->param.cpu, h->predict_8x8c );
x264_predict_8x8_init( h->param.cpu, h->predict_8x8 );
x264_predict_4x4_init( h->param.cpu, h->predict_4x4 );
因为x264中对帧内不同块大小所对应的预测模式都定义了相关的函数,所以这儿需要对相应的模式设定相应的预测函数。
(11)x264_pixel_init
这个函数也是对一些函数指针进行赋值。
这儿需要了解一下x264_pixel_function_t这个结构体:
typedef struct
{
x264_pixel_cmp_t sad[7];
x264_pixel_cmp_t ssd[7];
x264_pixel_cmp_t satd[7];
x264_pixel_cmp_t sa8d[4];
x264_pixel_cmp_t mbcmp[7]; /* either satd or sad for subpel refine and mode decision */
/* partial distortion elimination:
* terminate early if partial score is worse than a threshold.
* may be NULL, in which case just use sad instead. */
x264_pixel_cmp_pde_t sad_pde[7]; // No c Source code
} x264_pixel_function_t;
结构体中的原始都是函数指针数组。x264_pixel_cmp_t 和x264_pixel_cmp_pde_t 都是函数指针,从其成员变量的名字可以发现这些函数指针主要是用于计算像素之间的差值,包括sad,ssd和satd等。
再回过头来看x264_pixel_init函数可以发现,该函数主要是对这些函数指针数组的赋值操作。而所赋的值当然是函数。不过这儿还有一些技巧:
PIXEL_SAD_C( pixel_sad_16x16 , 16, 16 )
PIXEL_SAD_C( pixel_sad_16x8 , 16, 8 )
PIXEL_SAD_C( pixel_sad_8x16 , 8, 16 )
PIXEL_SAD_C( pixel_sad_8x8 , 8, 8 )
PIXEL_SAD_C( pixel_sad_8x4 , 8, 4 )
PIXEL_SAD_C( pixel_sad_4x8 , 4, 8 )
PIXEL_SAD_C( pixel_sad_4x4 , 4, 4 )
由于PIXEL_SAD_C是一个宏,这个宏的作用就是定义一些函数。从这儿所定义的函数名我们应该可以猜测到,这些函数就是对应于帧间预测时所需要的一些函数。
ps:这儿利用宏来定义的技巧可以学习一下。
(12)x264_dct_init
类似上面的函数,主要是对与dct操作相关的函数指针进行赋值。
这儿涉及到的是x264_dct_function_t这个结构体。这个结构体的成员都是与DCT变换相关的一些函数指针,具体可见dct.h文件。
从下图可以看到具体调用的函数:
(13)x264_mc_init
完成运动补偿相关函数指针的赋值,涉及到的结构体是x264_mc_functions_t,具体可见mc.h文件。
(14)x264_csp_init
这是不同颜色空间转换相关的一些函数。结构体x264_csp_function_t中是相关的转换函数指针。
pf->i420 = i420_to_i420;
pf->i422 = i422_to_i420;
pf->i444 = i444_to_i420;
pf->yv12 = yv12_to_i420;
pf->yuyv = yuyv_to_i420;
pf->rgb = rgb_to_i420;
pf->bgr = bgr_to_i420;
pf->bgra = bgra_to_i420;
带颜色的函数都是直接定义的,而最后的三个函数则是利用宏来定义的。
(15)x264_quant_init
量化相关的函数设置。x264_quant_function_t结构体用于保存相关的函数指针。
(16)x264_deblock_init
设置与deblocking相关的函数。
(17)x264_ratecontrol_new
这是和码率控制相关的函数。该函数创建码率控制结构体并初始化。x264_ratecontrol_t是与码率控制相关的结构体。
其调用x264_cpu_restore函数来恢复CPU的状态。
这样这个函数框架就分析完毕了。
6. Encode_frame函数解析
这个函数并不是很长,主要调用了下面的函数:
函数中涉及到了两个相关的结构体:x264_picture_t和x264_nal_t。x264_picture_t是存储输入/输出图像的结构体,x264_nal_t存储编码结束后的所有的nal包的信息。
根据该函数的代码,可以猜测其主要过程应该是先调用x264_encoder_encode对一帧图像进行编码,并且将编码完成的码率信息放入到nal列表中,然后调用x264_nal_encode对nal进行打包,最后调用p_write_nalu将码率写入文件。
(1)x264_encoder_encode
这是编码的核心函数。
int x264_encoder_encode( x264_t *h,
x264_nal_t **pp_nal, int *pi_nal,
x264_picture_t *pic_in,
x264_picture_t *pic_out )
结构体*h中是编码器相关的参数配置,**pp_nal主要用于存储nal包,*pi_nal是nal包的个数,*pic_in和*pic_out是输入和输出图像。注意pic_in的值是可以为NULL的。
这是一个大函数,后面有对其的详细分析。
(2)x264_nal_encode
int x264_nal_encode ( void *p_data , int *pi_data , int b_annexeb, x264_nal_t *nal )
这个函数就是讲nal中的数据根据H.264规定的格式进行打包,存储在p_data指向的区域。其实主要是添加起始码,NAL头和类型信息
(3)p_write_nalu
该函数将打包完成的NALU写入到文件中去。x264中给出了3种不同的类型文件:write_nalu_bsf,write_nalu_mp4和write_nalu_mkv。