=====================================================
H.264源代码分析文章列表:
【编码 - x264】
x264源代码简单分析:x264命令行工具(x264.exe)
x264源代码简单分析:x264_slice_write()
x264源代码简单分析:宏块分析(Analysis)部分-帧内宏块(Intra)
x264源代码简单分析:宏块分析(Analysis)部分-帧间宏块(Inter)
x264源代码简单分析:熵编码(Entropy Encoding)部分
【解码 - libavcodec H.264 解码器】
FFmpeg的H.264解码器源代码简单分析:解析器(Parser)部分
FFmpeg的H.264解码器源代码简单分析:解码器主干部分
FFmpeg的H.264解码器源代码简单分析:熵解码(EntropyDecoding)部分
FFmpeg的H.264解码器源代码简单分析:宏块解码(Decode)部分-帧内宏块(Intra)
FFmpeg的H.264解码器源代码简单分析:宏块解码(Decode)部分-帧间宏块(Inter)
FFmpeg的H.264解码器源代码简单分析:环路滤波(Loop Filter)部分
=====================================================
本文简单分析x264项目中的命令行工具(x264.exe)的源代码。该命令行工具可以调用libx264将YUV格式像素数据编码为H.264码流。
函数调用关系图
X264命令行工具的源代码在x264中的位置如下图所示。
X264命令行工具的源代码的调用关系如下图所示。
从图中可以看出,X264命令行工具调用了libx264的几个API完成了H.264编码工作。使用libx264的API进行编码可以参考《 最简单的视频编码器:基于libx264(编码YUV为H.264)》,这个流程中最关键的API包括:
x264_param_default():设置参数集结构体x264_param_t的缺省值。
x264_encoder_open():打开编码器。
x264_encoder_headers():输出SPS,PPS,SEI等信息。
x264_encoder_encode():编码输出一帧图像。
x264_encoder_close():关闭编码器。
在X264命令行工具中,main()首先调用parse()解析输入的命令行参数,然后调用encode()进行编码。parse()首先调用x264_param_default()为存储参数的结构体x264_param_t赋默认值;然后在一个大循环中调用getopt_long()逐个解析输入的参数,并作相应的处理;最后调用select_input()和select_output()解析输入文件格式(例如yuv,y4m…)和输出文件格式(例如raw,flv,MP4…)。encode()首先调用x264_encoder_open()打开H.264编码器,然后调用x264_encoder_headers()输出H.264码流的头信息(例如SPS、PPS、SEI),接着进入一个循环并且调用encode_frame()逐帧编码视频,最后调用x264_encoder_close()关闭解码器。其中encode_frame()中又调用了x264_encoder_encode()完成了具体的编码工作。下文将会对上述流程展开分析。
main()
main()是x264控制台程序的入口函数,定义如下所示。//主函数int main( int argc, char **argv ){ //参数集 x264_param_t param; cli_opt_t opt = {
0}; int ret = 0; FAIL_IF_ERROR( x264_threading_init(), "unable to initialize threading\n" )#ifdef _WIN32 FAIL_IF_ERROR( !get_argv_utf8( &argc, &argv ), "unable to convert command line to UTF-8\n" ) GetConsoleTitleW( org_console_title, CONSOLE_TITLE_SIZE ); _setmode( _fileno( stdin ), _O_BINARY ); _setmode( _fileno( stdout ), _O_BINARY ); _setmode( _fileno( stderr ), _O_BINARY );#endif /* Parse command line */ //解析命令行输入 if( parse( argc, argv, ¶m, &opt ) < 0 ) ret = -1;#ifdef _WIN32 /* Restore title; it can be changed by input modules */ SetConsoleTitleW( org_console_title );#endif /* 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 );#ifdef _WIN32 SetConsoleTitleW( org_console_title ); free( argv );#endif return ret;}
可以看出main()的定义很简单,它主要调用了两个函数:parse()和encode()。main()首先调用parse()解析输入的命令行参数,然后调用encode()进行编码。下面分别分析这两个函数。
parse()
parse()用于解析命令行输入的参数(存储于argv[]中)。它的定义如下所示。//解析命令行输入static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ){ char *input_filename = NULL; const char *demuxer = demuxer_names[0]; char *output_filename = NULL; const char *muxer = muxer_names[0]; char *tcfile_name = NULL; x264_param_t defaults; char *profile = NULL; char *vid_filters = NULL; int b_thread_input = 0; int b_turbo = 1; int b_user_ref = 0; int b_user_fps = 0; int b_user_interlaced = 0; cli_input_opt_t input_opt; cli_output_opt_t output_opt; char *preset = NULL; char *tune = NULL; //初始化参数默认值 x264_param_default( &defaults ); cli_log_level = defaults.i_log_level; memset( &input_opt, 0, sizeof(cli_input_opt_t) ); memset( &output_opt, 0, sizeof(cli_output_opt_t) ); input_opt.bit_depth = 8; input_opt.input_range = input_opt.output_range = param->vui.b_fullrange = RANGE_AUTO; int output_csp = defaults.i_csp; opt->b_progress = 1; /* Presets are applied before all other options. */ for( optind = 0;; ) { int c = getopt_long( argc, argv, short_options, long_options, NULL ); if( c == -1 ) break; if( c == OPT_PRESET ) preset = optarg; if( c == OPT_TUNE ) tune = optarg; else if( c == '?' ) return -1; } if( preset && !strcasecmp( preset, "placebo" ) ) b_turbo = 0; //设置preset,tune if( x264_param_default_preset( param, preset, tune ) < 0 ) return -1; /* Parse command line options */ //解析命令行选项 for( optind = 0;; ) { int b_error = 0; int long_options_index = -1; int c = getopt_long( argc, argv, short_options, long_options, &long_options_index ); if( c == -1 ) { break; } //不同的选项做不同的处理 switch( c ) { case 'h': help( &defaults, 0 );//"-h"帮助菜单 exit(0); case OPT_LONGHELP: help( &defaults, 1 ); exit(0); case OPT_FULLHELP: help( &defaults, 2 ); exit(0); case 'V': print_version_info();//打印版本信息 exit(0); case OPT_FRAMES: param->i_frame_total = X264_MAX( atoi( optarg ), 0 ); break; case OPT_SEEK: opt->i_seek = X264_MAX( atoi( optarg ), 0 ); break; case 'o': output_filename = optarg;//输出文件路径 break; case OPT_MUXER: FAIL_IF_ERROR( parse_enum_name( optarg, muxer_names, &muxer ), "Unknown muxer `%s'\n", optarg ) break; case OPT_DEMUXER: FAIL_IF_ERROR( parse_enum_name( optarg, demuxer_names, &demuxer ), "Unknown demuxer `%s'\n", optarg ) break; case OPT_INDEX: input_opt.index_file = optarg; break; case OPT_QPFILE: opt->qpfile = x264_fopen( optarg, "rb" ); FAIL_IF_ERROR( !opt->qpfile, "can't open qpfile `%s'\n", optarg ) if( !x264_is_regular_file( opt->qpfile ) ) { x264_cli_log( "x264", X264_LOG_ERROR, "qpfile incompatible with non-regular file `%s'\n", optarg ); fclose( opt->qpfile ); return -1; } break; case OPT_THREAD_INPUT: b_thread_input = 1; break; case OPT_QUIET: cli_log_level = param->i_log_level = X264_LOG_NONE;//设置log级别 break; case 'v': cli_log_level = param->i_log_level = X264_LOG_DEBUG;//设置log级别 break; case OPT_LOG_LEVEL: if( !parse_enum_value( optarg, log_level_names, &cli_log_level ) ) cli_log_level += X264_LOG_NONE; else cli_log_level = atoi( optarg ); param->i_log_level = cli_log_level;//设置log级别 break; case OPT_NOPROGRESS: opt->b_progress = 0; break; case OPT_TUNE: case OPT_PRESET: break; case OPT_PROFILE: profile = optarg; break; case OPT_SLOWFIRSTPASS: b_turbo = 0; break; case 'r': b_user_ref = 1; goto generic_option; case OPT_FPS: b_user_fps = 1; param->b_vfr_input = 0; goto generic_option; case OPT_INTERLACED: b_user_interlaced = 1; goto generic_option;