源码阅读 x264 - 命令行工具
命令行工具简介
x264 命令行工具执行流程如下:
- 首先调用 parse() 解析输入的命令行参数
- parse()首先调用 x264_param_default() 为存储参数的结构体 x264_param_t 赋默认值
- 循环中调用 getopt_long() 逐个解析输入的参数,并作相应的处理
- 调用 select_output() 解析输出文件格式(例如 raw,flv,MP4…)
- 调用 select_input() 解析输入文件格式(例如 yuv,y4m…)
- 然后调用 encode() 进行编码
- encode() 首先调用 x264_encoder_open() 打开 H.264 编码器
- 然后调用 x264_encoder_headers() 输出 H.264 码流的头信息(例如 SPS、PPS、SEI)
- 循环调用 encode_frame() 逐帧编码视频
- encode_frame()中调用了x264_encoder_encode()完成了具体的编码工作
- 调用x264_encoder_close()关闭解码器
main 函数
main() 是 x264 控制台程序的入口函数,定义如下所示。
REALIGN_STACK int main(int argc, char **argv)
{
if(argc == 4 && !strcmp( argv[1], "--autocomplete" ) )
return x264_cli_autocomplete(argv[2], argv[3] );
x264_param_t param;
cli_opt_t opt = {0};
int ret = 0;
FAIL_IF_ERROR(x264_threading_init(), "unable to initialize threading\n" );
x264_param_default(¶m);
/* Parse command line */
if(parse( argc, argv, ¶m, &opt) < 0 )
ret = -1;
/* Control-C handler */
signal(SIGINT, sigint_handler);
if(!ret)
ret = encode(¶m, &opt);
/* clean up handles */
if(filter.free)
filter.free(opt.hin);
else if(opt.hin)
cli_input.close_file(opt.hin);
if(opt.hout)
cli_output.close_file(opt.hout, 0, 0);
if(opt.tcfile_out)
fclose(opt.tcfile_out);
if(opt.qpfile)
fclose(opt.qpfile);
x264_param_cleanup(¶m);
return ret;
}
可以看出 main() 的定义很简单,它主要调用了两个函数:parse() 和 encode()。
main() 首先调用 parse() 对命令行进行解析并进行参数设置,然后调用 encode() 进行编码压缩。下面分别分析这两个函数。
parse 函数
parse() 用于解析命令行输入的参数(存储于 argv[] 中),其定义如下:
/**
* @brief 参数分析函数
* @param[in] argc 参数个数
* @param[in] argv 1 维数组指针,指向每个参数字符串首地址
* @param[in/out] param x264_param_t 结构体指针
* @param[in/out] param cli_opt_t 结构体指针
* @return # 0 执行成功
* # -1 执行失败
*/
static int parse(int argc, char **argv, x264_param_t *param, cli_opt_t *opt);
阅读源代码,可梳理出 parse() 函数的简单流程:
- 调用 x264_param_default() 为存储参数的结构体 x264_param_t 赋默认值
- 调用 x264_param_default_preset() 为 x264_param_t 赋值
- 在一个大循环中调用 getopt_long() 逐个解析输入的参数,并作相应的处理。举几个例子:
- 对于短选项,直接设置输出参数或调用其他函数进行处理。
- 对于长选项,统一调用 x264_param_parse() 进行处理。
- 调用 select_output() 解析输出文件格式(例如 raw,flv,MP4…)
- 调用 select_input() 解析输入文件格式(例如 yuv,y4m…)
关于 x264_param_t 结构体和 cli_opt_t 结构体的说明,详见:源码阅读 x264 - 常用结构体
parse() 中几个比较关键的函数:
x264_param_default
x264_param_default() 用于初始化 x264_param_t 类型变量
/**
* @brief x264_param_t 参数初始化函数
* @param[in/out] param x264_param_t 结构体指针
* @return void
* @note: fill x264_param_t with default values and do CPU detection
*/
REALIGN_STACK void x264_param_default(x264_param_t *param);
x264_param_default(x264_param_t*),在 parse() 中声明了一个 x264_param_t 类型的局部变量 default,并调用此函数进行初始化,但是目前还不知道 default 的用途。
x264_param_default_preset
/**
* @brief 根据 preset 和 tune 对 x264_param_t 参数进行初始化
* @param[in/out] param x264_param_t 结构体指针
* @param[in] preset 字符串,指示预设编码类型
* @param[in] tune 字符串,指示视频类型或视觉优化类型
* @return # 0 执行成功
* # negative 执行失败 (e.g. invalid preset/tune name)
* @note: Multiple tunings can be used if separated by a delimiter in ",./-+",
* however multiple psy tunings cannot be used.
* film, animation, grain, stillimage, psnr, and ssim are psy tunings.
*/
X264_API int x264_param_default_preset(x264_param_t *param, const char *preset, const char *tune);
--preset 的参数主要调节编码速度和质量的平衡,有 ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo 这 10 个选项,从快到慢。 --tune 的参数主要配合视频类型和视觉优化的参数,或特别的情况。如果视频的内容符合其中一个可用的调整值又或者有其中需要,则可以使用此选项,否则建议不使用。 tune 的值有: film、animation、grain、stillimage、psnr、ssim、fastdecode、zerolatency、touhou 这 9 个选项,部分选项可同时设置,可详见 param_apply_tune() 函数。
x264_param_default_preset(),会首先调用 x264_param_default() 对 param 进行初始化,然后根据 preset 和 tune 的值再对 param 进行初始化。
x264_param_parse
/**
* @brief 根据 name 和 value 对 x264_param_t 参数进行设置
* @param[in/out] param x264_param_t 结构体指针
* @param[in] name 字符串,指示参数名称
* @param[in] value 字符串,指示参数值
* @return # 0 执行成功
* #define X264_PARAM_BAD_NAME (-1) 执行失败
* #define X264_PARAM_BAD_VALUE (-2) 执行失败
* #define X264_PARAM_ALLOC_FAILED (-3) 执行失败
* @note: BAD_VALUE occurs only if it can't even parse the value,
* numerical range is not checked until x264_encoder_open() or x264_encoder_reconfig().
* value=NULL means "true" for boolean options, but is a BAD_VALUE for non-booleans.
* can allocate memory which should be freed by call of x264_param_cleanup.
*/
X264_API int x264_param_parse(x264_param_t *param, const char *name, const char *value);
x264_param_parse() 用来对命令行中的长选项进行解析,并完成 param 的设置。
x264_param_apply_fastfirstpass
/* x264_param_apply_fastfirstpass:
* If first-pass mode is set (rc.b_stat_read == 0, rc.b_stat_write == 1),
* modify the encoder settings to disable options generally not useful on
* the first pass.
*/
X264_API void x264_param_apply_fastfirstpass(x264_param_t *param);
x264_param_apply_profile
static const char * const x264_profile_names[] = {
"baseline", "main", "high", "high10", "high422", "high444", 0
};
/**
* @brief 根据 profile 对 x264_param_t 参数进行设置
* @param[in/out] param x264_param_t 结构体指针
* @param[in] profile 字符串,指示 H264 配置文件
* @return # 0 执行成功
* # negative 执行失败 (e.g. invalid profile name)
* @note: (can be NULL, in which case the function will do nothing)
* Does NOT guarantee that the given profile will be used: if the restrictions
* of "High" are applied to settings that are already Baseline-compatible, the
* stream will remain baseline. In short, it does not increase settings, only
* decrease them.
*/
X264_API int x264_param_apply_profile(x264_param_t *param, const char *profile);
x264_param_apply_profile() 根据 profile 对 param 进行设置,这里 profile 有 6 种选项,分别是 baseline/main/high/high10/high422/high444,如果选择无损压缩的话,profile 会被限制为 high444。
select_output
/**
* @brief 根据文件名的后缀确定输出的文件格式(raw H264,flv,mp4...)
* @param[in] muxer 字符串,指示输出文件的 muxer 格式
* @param[in] filename 字符串,指示输出文件的文件名
* @param[in/out] param x264_param_t 结构体指针
* @return # 0 执行成功
* # negative 执行失败
* @note: 在指定 muxer 时以 muxer 为准,muxer 为 auto 时,以输出文件拓展名为准
*/
static int select_output(const char *muxer, char *filename, x264_param_t *param);
select_output() 会从输入文件路径的字符串中提取出了扩展名,然后根据不同的扩展名设定不同的输出格式。
select_input
/**
* @brief 根据文件名的后缀确定输出的文件格式(raw H264,flv,mp4...)
* @param[in] demuxer 字符串,指示输入文件的 demuxer 格式
* @param[in] used_demuxer 字符串,指示输入文件的 demuxer 格式
* @param[in] filename 字符串,指示输入文件的文件名
* @param[out] p_handle hnd_t 结构体指针
* @param[out] info video_info_t 结构体指针,保存输入文件信息
* @param[out] opt cli_input_opt_t 结构体指针
* @return # 0 执行成功
* # negative 执行失败
* @note: 在指定 demuxer 时以 demuxer 为准,demuxer 为 auto 时,以输入文件拓展名为准
*/
static int select_input( const char *demuxer, char *used_demuxer, char *filename,
hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt )
select_input() 首先获取输入文件名的扩展名;然后根据扩展名不同调用不用的 open_file 函数来设置不同的输入格式。
encode 函数
/**
* @brief 开始进行编码
* @param[in] param x264_param_t 结构体指针
* @param[in] opt cli_opt_t 结构体指针
* @return # 0 执行成功
* # negative 执行失败
*/
static int encode( x264_param_t *param, cli_opt_t *opt );
从源代码可以梳理出来 encode() 的流程:
- 调用 x264_encoder_open() 打开 H.264 编码器。
- 调用 x264_encoder_parameters() 获得当前的参数集 x264_param_t,用于后续步骤中的一些配置。
- 调用输出格式(H.264裸流、FLV、mp4 等)对应 cli_output_t 结构体的 set_param() 方法,为输出格式的封装器设定参数。其中参数源自于上一步骤得到的 x264_param_t。
- 如果不是在每个 keyframe 前面都增加 SPS/PPS/SEI 的话,就调用 x264_encoder_headers() 在整个码流前面加 SPS/PPS/SEI。
- 进入一个循环中进行一帧一帧的将 YUV 编码为 H.264:
- 调用输入格式(YUV、Y4M 等)对应的 cli_vid_filter_t 结构体 get_frame() 方法,获取一帧 YUV 数据。
- 调用 encode_frame() 编码该帧 YUV 数据为 H.264 数据,并且输出出来。该函数内部调用 x264_encoder_encode() 完成编码工作,调用输出格式对应 cli_output_t 结构体的 write_frame() 完成了输出工作。
- 调用输入格式(YUV、Y4M 等)对应的 cli_vid_filter_t 结构体 release_frame() 方法,释放刚才获取的YUV 数据。
- 调用 print_status() 输出一些统计信息。
- 编码即将结束的时候,进入另一个循环,输出编码器中缓存的视频帧:
- 不再传递新的 YUV 数据,直接调用 encode_frame(),将编码器中缓存的剩余几帧数据编码输出出来。
- 调用 print_status() 输出一些统计信息。
- 调用 x264_encoder_close() 关闭 H.264 编码器。
encode() 的流程中涉及到 libx264 的几个关键的API,这些函数较为复杂,后续再进行分析:
- x264_encoder_open():打开H.264编码器。
- x264_encoder_headers():输出SPS/PPS/SEI。
- x264_encoder_encode():编码一帧数据。
- x264_encoder_close():关闭H.264编码器。