本文簡單分析x264項目中的命令行工具(x264.exe)的源代碼。該命令行工具可以調用libx264將YUV格式像素數據編碼為H.264碼流。
X264命令行工具的源代碼在x264中的位置如下圖所示。
單擊查看更清晰的圖片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()完成了具體的編碼工作。下文將會對上述流程展開分析。
//主函數 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 ) #ifdef _WIN32 FAIL_IF_ERROR( !get_argv_utf8( &argc, &argv ), unable to convert command line to UTF-8 ) 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; }
//解析命令行輸入 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' , optarg ) break; case OPT_DEMUXER: FAIL_IF_ERROR( parse_enum_name( optarg, demuxer_names, &demuxer ), Unknown demuxer `%s' , 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' , optarg ) if( !x264_is_regular_file( opt->qpfile ) ) { x264_cli_log( x264, X264_LOG_ERROR, qpfile incompatible with non-regular file `%s' , 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; case OPT_TCFILE_IN: tcfile_name = optarg; break; case OPT_TCFILE_OUT: opt->tcfile_out = x264_fopen( optarg, wb ); FAIL_IF_ERROR( !opt->tcfile_out, can't open `%s' , optarg ) break; case OPT_TIMEBASE: input_opt.timebase = optarg; break; case OPT_PULLDOWN: FAIL_IF_ERROR( parse_enum_value( optarg, pulldown_names, &opt->i_pulldown ), Unknown pulldown `%s' , optarg ) break; case OPT_VIDEO_FILTER: vid_filters = optarg; break; case OPT_INPUT_FMT: input_opt.format = optarg;//輸入文件格式 break; case OPT_INPUT_RES: input_opt.resolution = optarg;//輸入分辨率 break; case OPT_INPUT_CSP: input_opt.colorspace = optarg;//輸入色域 break; case OPT_INPUT_DEPTH: input_opt.bit_depth = atoi( optarg );//輸入顏色位深 break; case OPT_DTS_COMPRESSION: output_opt.use_dts_compress = 1; break; case OPT_OUTPUT_CSP: FAIL_IF_ERROR( parse_enum_value( optarg, output_csp_names, &output_csp ), Unknown output csp `%s' , optarg ) // correct the parsed value to the libx264 csp value #if X264_CHROMA_FORMAT static const uint8_t output_csp_fix[] = { X264_CHROMA_FORMAT, X264_CSP_RGB }; #else static const uint8_t output_csp_fix[] = { X264_CSP_I420, X264_CSP_I422, X264_CSP_I444, X264_CSP_RGB }; #endif param->i_csp = output_csp = output_csp_fix[output_csp]; break; case OPT_INPUT_RANGE: FAIL_IF_ERROR( parse_enum_value( optarg, range_names, &input_opt.input_range ), Unknown input range `%s' , optarg ) input_opt.input_range += RANGE_AUTO; break; case OPT_RANGE: FAIL_IF_ERROR( parse_enum_value( optarg, range_names, ¶m->vui.b_fullrange ), Unknown range `%s' , optarg ); input_opt.output_range = param->vui.b_fullrange += RANGE_AUTO; break; default: generic_option: { if( long_options_index < 0 ) { for( int i = 0; long_options[i].name; i++ ) if( long_options[i].val == c ) { long_options_index = i; break; } if( long_options_index < 0 ) { /* getopt_long already printed an error message */ return -1; } } //解析以字符串方式輸入的參數 //即選項名稱和選項值都是字符串 b_error |= x264_param_parse( param, long_options[long_options_index].name, optarg ); } } if( b_error ) { const char *name = long_options_index > 0 ? long_options[long_options_index].name : argv[optind-2]; x264_cli_log( x264, X264_LOG_ERROR, invalid argument: %s = %s , name, optarg ); return -1; } } /* If first pass mode is used, apply faster settings. */ if( b_turbo ) x264_param_apply_fastfirstpass( param ); /* Apply profile restrictions. */ //設置profile if( x264_param_apply_profile( param, profile ) < 0 ) return -1; /* Get the file name */ FAIL_IF_ERROR( optind > argc - 1 || !output_filename, No %s file. Run x264 --help for a list of options. , optind > argc - 1 ? input : output ) //根據文件名的後綴確定輸出的文件格式(raw H264,flv,mp4...) if( select_output( muxer, output_filename, param ) ) return -1; FAIL_IF_ERROR( cli_output.open_file( output_filename, &opt->hout, &output_opt ), could not open output file `%s' , output_filename ) //輸入文件路徑 input_filename = argv[optind++]; video_info_t info = {0}; char demuxername[5]; /* set info flags to be overwritten by demuxer as necessary. */ //設置info結構體 info.csp = param->i_csp; info.fps_num = param->i_fps_num; info.fps_den = param->i_fps_den; info.fullrange = input_opt.input_range == RANGE_PC; info.interlaced = param->b_interlaced; if( param->vui.i_sar_width > 0 && param->vui.i_sar_height > 0 ) { info.sar_width = param->vui.i_sar_width; info.sar_height = param->vui.i_sar_height; } info.tff = param->b_tff; info.vfr = param->b_vfr_input; input_opt.seek = opt->i_seek; input_opt.progress = opt->b_progress; input_opt.output_csp = output_csp; //設置輸入文件的格式(yuv,y4m...) if( select_input( demuxer, demuxername, input_filename, &opt->hin, &info, &input_opt ) ) return -1; FAIL_IF_ERROR( !opt->hin && cli_input.open_file( input_filename, &opt->hin, &info, &input_opt ), could not open input file `%s' , input_filename ) x264_reduce_fraction( &info.sar_width, &info.sar_height ); x264_reduce_fraction( &info.fps_num, &info.fps_den ); x264_cli_log( demuxername, X264_LOG_INFO, %dx%d%c %u:%u @ %u/%u fps (%cfr) , info.width, info.height, info.interlaced ? 'i' : 'p', info.sar_width, info.sar_height, info.fps_num, info.fps_den, info.vfr ? 'v' : 'c' ); if( tcfile_name ) { FAIL_IF_ERROR( b_user_fps, --fps + --tcfile-in is incompatible. ) FAIL_IF_ERROR( timecode_input.open_file( tcfile_name, &opt->hin, &info, &input_opt ), timecode input failed ) cli_input = timecode_input; } else FAIL_IF_ERROR( !info.vfr && input_opt.timebase, --timebase is incompatible with cfr input ) /* init threaded input while the information about the input video is unaltered by filtering */ #if HAVE_THREAD if( info.thread_safe && (b_thread_input || param->i_threads > 1 || (param->i_threads == X264_THREADS_AUTO && x264_cpu_num_processors() > 1)) ) { if( thread_input.open_file( NULL, &opt->hin, &info, NULL ) ) { fprintf( stderr, x264 [error]: threaded input failed ); return -1; } cli_input = thread_input; } #endif /* override detected values by those specified by the user */ if( param->vui.i_sar_width > 0 && param->vui.i_sar_height > 0 ) { info.sar_width = param->vui.i_sar_width; info.sar_height = param->vui.i_sar_height; } if( b_user_fps ) { info.fps_num = param->i_fps_num; info.fps_den = param->i_fps_den; } if( !info.vfr ) { info.timebase_num = info.fps_den; info.timebase_den = info.fps_num; } if( !tcfile_name && input_opt.timebase ) { uint64_t i_user_timebase_num; uint64_t i_user_timebase_den; int ret = sscanf( input_opt.timebase, %SCNu64/%SCNu64, &i_user_timebase_num, &i_user_timebase_den ); FAIL_IF_ERROR( !ret, invalid argument: timebase = %s , input_opt.timebase ) else if( ret == 1 ) { i_user_timebase_num = info.timebase_num; i_user_timebase_den = strtoul( input_opt.timebase, NULL, 10 ); } FAIL_IF_ERROR( i_user_timebase_num > UINT32_MAX || i_user_timebase_den > UINT32_MAX, timebase you specified exceeds H.264 maximum ) opt->timebase_convert_multiplier = ((double)i_user_timebase_den / info.timebase_den) * ((double)info.timebase_num / i_user_timebase_num); info.timebase_num = i_user_timebase_num; info.timebase_den = i_user_timebase_den; info.vfr = 1; } if( b_user_interlaced ) { info.interlaced = param->b_interlaced; info.tff = param->b_tff; } if( input_opt.input_range != RANGE_AUTO ) info.fullrange = input_opt.input_range; //初始化濾鏡filter //filter可以認為是一種“擴展”了的輸入源 if( init_vid_filters( vid_filters, &opt->hin, &info, param, output_csp ) ) return -1; /* set param flags from the post-filtered video */ param->b_vfr_input = info.vfr; param->i_fps_num = info.fps_num; param->i_fps_den = info.fps_den; param->i_timebase_num = info.timebase_num; param->i_timebase_den = info.timebase_den; param->vui.i_sar_width = info.sar_width; param->vui.i_sar_height = info.sar_height; info.num_frames = X264_MAX( info.num_frames - opt->i_seek, 0 ); if( (!info.num_frames || param->i_frame_total < info.num_frames) && param->i_frame_total > 0 ) info.num_frames = param->i_frame_total; param->i_frame_total = info.num_frames; if( !b_user_interlaced && info.interlaced ) { #if HAVE_INTERLACED x264_cli_log( x264, X264_LOG_WARNING, input appears to be interlaced, enabling %cff interlaced mode. If you want otherwise, use --no-interlaced or --%cff , info.tff ? 't' : 'b', info.tff ? 'b' : 't' ); param->b_interlaced = 1; param->b_tff = !!info.tff; #else x264_cli_log( x264, X264_LOG_WARNING, input appears to be interlaced, but not compiled with interlaced support ); #endif } /* if the user never specified the output range and the input is now rgb, default it to pc */ int csp = param->i_csp & X264_CSP_MASK; if( csp >= X264_CSP_BGR && csp <= X264_CSP_RGB ) { if( input_opt.output_range == RANGE_AUTO ) param->vui.b_fullrange = RANGE_PC; /* otherwise fail if they specified tv */ FAIL_IF_ERROR( !param->vui.b_fullrange, RGB must be PC range ) } /* Automatically reduce reference frame count to match the user's target level * if the user didn't explicitly set a reference frame count. */ if( !b_user_ref ) { int mbs = (((param->i_width)+15)>>4) * (((param->i_height)+15)>>4); for( int i = 0; x264_levels[i].level_idc != 0; i++ ) if( param->i_level_idc == x264_levels[i].level_idc ) { while( mbs * param->i_frame_reference > x264_levels[i].dpb && param->i_frame_reference > 1 ) param->i_frame_reference--; break; } } return 0; }
(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…)
下文按照順序記錄parse()中涉及到的函數:
x264_param_default()
x264_param_default_preset()
help()
print_version_info()
x264_param_parse()
select_input()
select_output()
/* x264_param_default: * fill x264_param_t with default values and do CPU detection */ void x264_param_default( x264_param_t * );x264_param_default()的定義如下所示。
/**************************************************************************** * x264_param_default: ****************************************************************************/ //初始化參數默認值 void x264_param_default( x264_param_t *param ) { /* */ memset( param, 0, sizeof( x264_param_t ) ); /* CPU autodetect */ param->cpu = x264_cpu_detect(); param->i_threads = X264_THREADS_AUTO; param->i_lookahead_threads = X264_THREADS_AUTO; param->b_deterministic = 1; param->i_sync_lookahead = X264_SYNC_LOOKAHEAD_AUTO; /* Video properties */ param->i_csp = X264_CHROMA_FORMAT ? X264_CHROMA_FORMAT : X264_CSP_I420; param->i_width = 0; param->i_height = 0; param->vui.i_sar_width = 0; param->vui.i_sar_height= 0; param->vui.i_overscan = 0; /* undef */ param->vui.i_vidformat = 5; /* undef */ param->vui.b_fullrange = -1; /* default depends on input */ param->vui.i_colorprim = 2; /* undef */ param->vui.i_transfer = 2; /* undef */ param->vui.i_colmatrix = -1; /* default depends on input */ param->vui.i_chroma_loc= 0; /* left center */ param->i_fps_num = 25; param->i_fps_den = 1; param->i_level_idc = -1; param->i_slice_max_size = 0; param->i_slice_max_mbs = 0; param->i_slice_count = 0; /* Encoder parameters */ //編碼參數--最常見 param->i_frame_reference = 3; param->i_keyint_max = 250; param->i_keyint_min = X264_KEYINT_MIN_AUTO; param->i_bframe = 3; param->i_scenecut_threshold = 40; param->i_bframe_adaptive = X264_B_ADAPT_FAST; param->i_bframe_bias = 0; param->i_bframe_pyramid = X264_B_PYRAMID_NORMAL; param->b_interlaced = 0; param->b_constrained_intra = 0; param->b_deblocking_filter = 1; param->i_deblocking_filter_alphac0 = 0; param->i_deblocking_filter_beta = 0; param->b_cabac = 1; param->i_cabac_init_idc = 0; //碼率控制模塊 Rate Control param->rc.i_rc_method = X264_RC_CRF; param->rc.i_bitrate = 0; param->rc.f_rate_tolerance = 1.0; param->rc.i_vbv_max_bitrate = 0; param->rc.i_vbv_buffer_size = 0; param->rc.f_vbv_buffer_init = 0.9; param->rc.i_qp_constant = 23 + QP_BD_OFFSET; param->rc.f_rf_constant = 23; param->rc.i_qp_min = 0; param->rc.i_qp_max = QP_MAX; param->rc.i_qp_step = 4; param->rc.f_ip_factor = 1.4; param->rc.f_pb_factor = 1.3; param->rc.i_aq_mode = X264_AQ_VARIANCE; param->rc.f_aq_strength = 1.0; param->rc.i_lookahead = 40; param->rc.b_stat_write = 0; param->rc.psz_stat_out = x264_2pass.log; param->rc.b_stat_read = 0; param->rc.psz_stat_in = x264_2pass.log; param->rc.f_qcompress = 0.6; param->rc.f_qblur = 0.5; param->rc.f_complexity_blur = 20; param->rc.i_zones = 0; param->rc.b_mb_tree = 1; /* Log */ //日志模塊 param->pf_log = x264_log_default; param->p_log_private = NULL; param->i_log_level = X264_LOG_INFO; /* */ //分析模塊 Analysis param->analyse.intra = X264_ANALYSE_I4x4 | X264_ANALYSE_I8x8; param->analyse.inter = X264_ANALYSE_I4x4 | X264_ANALYSE_I8x8 | X264_ANALYSE_PSUB16x16 | X264_ANALYSE_BSUB16x16; param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_SPATIAL; param->analyse.i_me_method = X264_ME_HEX; param->analyse.f_psy_rd = 1.0; param->analyse.b_psy = 1; param->analyse.f_psy_trellis = 0; param->analyse.i_me_range = 16; param->analyse.i_subpel_refine = 7; param->analyse.b_mixed_references = 1; param->analyse.b_chroma_me = 1; param->analyse.i_mv_range_thread = -1; param->analyse.i_mv_range = -1; // set from level_idc param->analyse.i_chroma_qp_offset = 0; param->analyse.b_fast_pskip = 1; param->analyse.b_weighted_bipred = 1; param->analyse.i_weighted_pred = X264_WEIGHTP_SMART; param->analyse.b_dct_decimate = 1; param->analyse.b_transform_8x8 = 1; param->analyse.i_trellis = 1; param->analyse.i_luma_deadzone[0] = 21; param->analyse.i_luma_deadzone[1] = 11; param->analyse.b_psnr = 0; param->analyse.b_ssim = 0; param->i_cqm_preset = X264_CQM_FLAT; memset( param->cqm_4iy, 16, sizeof( param->cqm_4iy ) ); memset( param->cqm_4py, 16, sizeof( param->cqm_4py ) ); memset( param->cqm_4ic, 16, sizeof( param->cqm_4ic ) ); memset( param->cqm_4pc, 16, sizeof( param->cqm_4pc ) ); memset( param->cqm_8iy, 16, sizeof( param->cqm_8iy ) ); memset( param->cqm_8py, 16, sizeof( param->cqm_8py ) ); memset( param->cqm_8ic, 16, sizeof( param->cqm_8ic ) ); memset( param->cqm_8pc, 16, sizeof( param->cqm_8pc ) ); param->b_repeat_headers = 1; param->b_annexb = 1; param->b_aud = 0; param->b_vfr_input = 1; param->i_nal_hrd = X264_NAL_HRD_NONE; param->b_tff = 1; param->b_pic_struct = 0; param->b_fake_interlaced = 0; param->i_frame_packing = -1; param->b_opencl = 0; param->i_opencl_device = 0; param->opencl_device_id = NULL; param->psz_clbin_file = NULL; }
/* 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. * * returns 0 on success, negative on failure (e.g. invalid preset/tune name). */ int x264_param_default_preset( x264_param_t *, const char *preset, const char *tune );x264_param_default_preset()的定義如下所示。
//設置preset,tune int x264_param_default_preset( x264_param_t *param, const char *preset, const char *tune ) { x264_param_default( param ); //設置preset if( preset && x264_param_apply_preset( param, preset ) < 0 ) return -1; //設置tune if( tune && x264_param_apply_tune( param, tune ) < 0 ) return -1; return 0; }
//設置preset static int x264_param_apply_preset( x264_param_t *param, const char *preset ) { char *end; int i = strtol( preset, &end, 10 ); if( *end == 0 && i >= 0 && i < sizeof(x264_preset_names)/sizeof(*x264_preset_names)-1 ) preset = x264_preset_names[i]; //幾種不同的preset設置不同的參數 if( !strcasecmp( preset, ultrafast ) ) { param->i_frame_reference = 1; param->i_scenecut_threshold = 0; param->b_deblocking_filter = 0;//不使用去塊濾波 param->b_cabac = 0;//不使用CABAC param->i_bframe = 0;//不使用B幀 param->analyse.intra = 0; param->analyse.inter = 0; param->analyse.b_transform_8x8 = 0;//不使用8x8DCT param->analyse.i_me_method = X264_ME_DIA;//運動搜索方法使用“Diamond” param->analyse.i_subpel_refine = 0; param->rc.i_aq_mode = 0; param->analyse.b_mixed_references = 0; param->analyse.i_trellis = 0; param->i_bframe_adaptive = X264_B_ADAPT_NONE; param->rc.b_mb_tree = 0; param->analyse.i_weighted_pred = X264_WEIGHTP_NONE;//不使用加權 param->analyse.b_weighted_bipred = 0; param->rc.i_lookahead = 0; } else if( !strcasecmp( preset, superfast ) ) { param->analyse.inter = X264_ANALYSE_I8x8|X264_ANALYSE_I4x4; param->analyse.i_me_method = X264_ME_DIA;//鑽石模板 param->analyse.i_subpel_refine = 1;//亞像素運動估計質量為1 param->i_frame_reference = 1; param->analyse.b_mixed_references = 0; param->analyse.i_trellis = 0; param->rc.b_mb_tree = 0; param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE; param->rc.i_lookahead = 0; } else if( !strcasecmp( preset, veryfast ) ) { param->analyse.i_me_method = X264_ME_HEX;//六邊形模板 param->analyse.i_subpel_refine = 2; param->i_frame_reference = 1; param->analyse.b_mixed_references = 0; param->analyse.i_trellis = 0; param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE; param->rc.i_lookahead = 10; } else if( !strcasecmp( preset, faster ) ) { param->analyse.b_mixed_references = 0; param->i_frame_reference = 2; param->analyse.i_subpel_refine = 4; param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE; param->rc.i_lookahead = 20; } else if( !strcasecmp( preset, fast ) ) { param->i_frame_reference = 2; param->analyse.i_subpel_refine = 6; param->analyse.i_weighted_pred = X264_WEIGHTP_SIMPLE; param->rc.i_lookahead = 30; } else if( !strcasecmp( preset, medium ) ) { /* Default is medium */ } else if( !strcasecmp( preset, slow ) ) { param->analyse.i_me_method = X264_ME_UMH;//UMH相對復雜 param->analyse.i_subpel_refine = 8;//亞像素運動估計質量為8 param->i_frame_reference = 5; param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS; param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO; param->rc.i_lookahead = 50; } else if( !strcasecmp( preset, slower ) ) { param->analyse.i_me_method = X264_ME_UMH; param->analyse.i_subpel_refine = 9; param->i_frame_reference = 8; param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS; param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO; param->analyse.inter |= X264_ANALYSE_PSUB8x8; param->analyse.i_trellis = 2; param->rc.i_lookahead = 60; } else if( !strcasecmp( preset, veryslow ) ) { param->analyse.i_me_method = X264_ME_UMH; param->analyse.i_subpel_refine = 10; param->analyse.i_me_range = 24; param->i_frame_reference = 16; param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS; param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO; param->analyse.inter |= X264_ANALYSE_PSUB8x8; param->analyse.i_trellis = 2; param->i_bframe = 8; param->rc.i_lookahead = 60; } else if( !strcasecmp( preset, placebo ) ) { param->analyse.i_me_method = X264_ME_TESA;//TESA很慢 param->analyse.i_subpel_refine = 11; param->analyse.i_me_range = 24; param->i_frame_reference = 16; param->i_bframe_adaptive = X264_B_ADAPT_TRELLIS; param->analyse.i_direct_mv_pred = X264_DIRECT_PRED_AUTO; param->analyse.inter |= X264_ANALYSE_PSUB8x8; param->analyse.b_fast_pskip = 0; param->analyse.i_trellis = 2; param->i_bframe = 16; param->rc.i_lookahead = 60; } else { x264_log( NULL, X264_LOG_ERROR, invalid preset '%s' , preset ); return -1; } return 0; }
//設置tune static int x264_param_apply_tune( x264_param_t *param, const char *tune ) { char *tmp = x264_malloc( strlen( tune ) + 1 ); if( !tmp ) return -1; tmp = strcpy( tmp, tune ); //分解一個字符串為一個字符串數組。第2個參數為分隔符 char *s = strtok( tmp, ,./-+ ); int psy_tuning_used = 0; //設置 //這裡是循環的,可以設置多次 while( s ) { if( !strncasecmp( s, film, 4 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->i_deblocking_filter_alphac0 = -1; param->i_deblocking_filter_beta = -1; param->analyse.f_psy_trellis = 0.15; } else if( !strncasecmp( s, animation, 9 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->i_frame_reference = param->i_frame_reference > 1 ? param->i_frame_reference*2 : 1; param->i_deblocking_filter_alphac0 = 1; param->i_deblocking_filter_beta = 1; param->analyse.f_psy_rd = 0.4; param->rc.f_aq_strength = 0.6; param->i_bframe += 2; } else if( !strncasecmp( s, grain, 5 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->i_deblocking_filter_alphac0 = -2; param->i_deblocking_filter_beta = -2; param->analyse.f_psy_trellis = 0.25; param->analyse.b_dct_decimate = 0; param->rc.f_pb_factor = 1.1; param->rc.f_ip_factor = 1.1; param->rc.f_aq_strength = 0.5; param->analyse.i_luma_deadzone[0] = 6; param->analyse.i_luma_deadzone[1] = 6; param->rc.f_qcompress = 0.8; } else if( !strncasecmp( s, stillimage, 10 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->i_deblocking_filter_alphac0 = -3; param->i_deblocking_filter_beta = -3; param->analyse.f_psy_rd = 2.0; param->analyse.f_psy_trellis = 0.7; param->rc.f_aq_strength = 1.2; } else if( !strncasecmp( s, psnr, 4 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->rc.i_aq_mode = X264_AQ_NONE; param->analyse.b_psy = 0; } else if( !strncasecmp( s, ssim, 4 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->rc.i_aq_mode = X264_AQ_AUTOVARIANCE; param->analyse.b_psy = 0; } else if( !strncasecmp( s, fastdecode, 10 ) ) { param->b_deblocking_filter = 0; param->b_cabac = 0; param->analyse.b_weighted_bipred = 0; param->analyse.i_weighted_pred = X264_WEIGHTP_NONE; } else if( !strncasecmp( s, zerolatency, 11 ) ) { //zerolatency速度快 param->rc.i_lookahead = 0; param->i_sync_lookahead = 0; param->i_bframe = 0;//不使用B幀 param->b_sliced_threads = 1; param->b_vfr_input = 0; param->rc.b_mb_tree = 0; } else if( !strncasecmp( s, touhou, 6 ) ) { if( psy_tuning_used++ ) goto psy_failure; param->i_frame_reference = param->i_frame_reference > 1 ? param->i_frame_reference*2 : 1; param->i_deblocking_filter_alphac0 = -1; param->i_deblocking_filter_beta = -1; param->analyse.f_psy_trellis = 0.2; param->rc.f_aq_strength = 1.3; if( param->analyse.inter & X264_ANALYSE_PSUB16x16 ) param->analyse.inter |= X264_ANALYSE_PSUB8x8; } else { x264_log( NULL, X264_LOG_ERROR, invalid tune '%s' , s ); x264_free( tmp ); return -1; } if( 0 ) { psy_failure: x264_log( NULL, X264_LOG_WARNING, only 1 psy tuning can be used: ignoring tune %s , s ); } s = strtok( NULL, ,./-+ ); } x264_free( tmp ); return 0; }
//幫助菜單 //longhelp標識是否展開更長的幫助菜單 static void help( x264_param_t *defaults, int longhelp ) { char buf[50]; //H0(),H1(),H2()都是printf() //H1(),H2()只有“長幫助菜單”的情況下才會調用printf() #define H0 printf #define H1 if(longhelp>=1) printf #define H2 if(longhelp==2) printf H0( x264 core:%d%s Syntax: x264 [options] -o outfile infile Infile can be raw (in which case resolution is required), or YUV4MPEG (*.y4m), or Avisynth if compiled with support (%s). or libav* formats if compiled with lavf support (%s) or ffms support (%s). Outfile type is selected by filename: .264 -> Raw bytestream .mkv -> Matroska .flv -> Flash Video .mp4 -> MP4 if compiled with GPAC or L-SMASH support (%s) Output bit depth: %d (configured at compile time) Options: -h, --help List basic options --longhelp List more options --fullhelp List all options , X264_BUILD, X264_VERSION, #if HAVE_AVS yes, #else no, #endif #if HAVE_LAVF yes, #else no, #endif #if HAVE_FFMS yes, #else no, #endif #if HAVE_GPAC gpac, #elif HAVE_LSMASH lsmash, #else no, #endif x264_bit_depth ); H0( Example usage: ); H0( ); H0( Constant quality mode: ); H0( x264 --crf 24 -o
//打印版本信息 static void print_version_info( void ) { #ifdef X264_POINTVER printf( x264 X264_POINTVER ); #else printf( x264 0.%d.X , X264_BUILD ); #endif #if HAVE_SWSCALE printf( (libswscale %d.%d.%d) , LIBSWSCALE_VERSION_MAJOR, LIBSWSCALE_VERSION_MINOR, LIBSWSCALE_VERSION_MICRO ); #endif #if HAVE_LAVF printf( (libavformat %d.%d.%d) , LIBAVFORMAT_VERSION_MAJOR, LIBAVFORMAT_VERSION_MINOR, LIBAVFORMAT_VERSION_MICRO ); #endif #if HAVE_FFMS printf( (ffmpegsource %d.%d.%d.%d) , FFMS_VERSION >> 24, (FFMS_VERSION & 0xff0000) >> 16, (FFMS_VERSION & 0xff00) >> 8, FFMS_VERSION & 0xff ); #endif printf( built on __DATE__ , ); #ifdef __INTEL_COMPILER printf( intel: %.2f (%d) , __INTEL_COMPILER / 100.f, __INTEL_COMPILER_BUILD_DATE ); #elif defined(__GNUC__) printf( gcc: __VERSION__ ); #elif defined(_MSC_FULL_VER) printf( msvc: %.2f (%u) , _MSC_VER / 100.f, _MSC_FULL_VER ); #else printf( using an unknown compiler ); #endif printf( configuration: --bit-depth=%d --chroma-format=%s , x264_bit_depth, X264_CHROMA_FORMAT ? (output_csp_names[0]+1) : all ); printf( x264 license: ); #if HAVE_GPL printf( GPL version 2 or later ); #else printf( Non-GPL commercial ); #endif #if HAVE_SWSCALE const char *license = swscale_license(); printf( libswscale%s%s license: %s , HAVE_LAVF ? /libavformat : , HAVE_FFMS ? /ffmpegsource : , license ); if( !strcmp( license, nonfree and unredistributable ) || (!HAVE_GPL && (!strcmp( license, GPL version 2 or later ) || !strcmp( license, GPL version 3 or later )))) printf( WARNING: This binary is unredistributable! ); #endif }
/* x264_param_parse: * set one parameter by name. * returns 0 on success, or returns one of the following errors. * 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. */ int x264_param_parse( x264_param_t *, const char *name, const char *value );x264_param_parse()的定義如下所示。
//解析以字符串方式輸入的參數 //即選項名稱和選項值都是字符串 //實質就是通過strcmp()方法 int x264_param_parse( x264_param_t *p, const char *name, const char *value ) { char *name_buf = NULL; int b_error = 0; int name_was_bool; int value_was_null = !value; int i; if( !name ) return X264_PARAM_BAD_NAME; if( !value ) value = true; if( value[0] == '=' ) value++; if( strchr( name, '_' ) ) // s/_/-/g { char *c; name_buf = strdup(name); while( (c = strchr( name_buf, '_' )) ) *c = '-'; name = name_buf; } if( (!strncmp( name, no-, 3 ) && (i = 3)) || (!strncmp( name, no, 2 ) && (i = 2)) ) { name += i; value = atobool(value) ? false : true; } name_was_bool = 0; #define OPT(STR) else if( !strcmp( name, STR ) ) #define OPT2(STR0, STR1) else if( !strcmp( name, STR0 ) || !strcmp( name, STR1 ) ) if(0); //OPT()實際上就是strcmp() OPT(asm) { p->cpu = isdigit(value[0]) ? atoi(value) : !strcasecmp(value, auto) || atobool(value) ? x264_cpu_detect() : 0; if( b_error ) { char *buf = strdup(value); char *tok, UNUSED *saveptr=NULL, *init; b_error = 0; p->cpu = 0; for( init=buf; (tok=strtok_r(init, ,, &saveptr)); init=NULL ) { for( i=0; x264_cpu_names[i].flags && strcasecmp(tok, x264_cpu_names[i].name); i++ ); p->cpu |= x264_cpu_names[i].flags; if( !x264_cpu_names[i].flags ) b_error = 1; } free( buf ); if( (p->cpu&X264_CPU_SSSE3) && !(p->cpu&X264_CPU_SSE2_IS_SLOW) ) p->cpu |= X264_CPU_SSE2_IS_FAST; } } OPT(threads) { if( !strcasecmp(value, auto) ) p->i_threads = X264_THREADS_AUTO; else p->i_threads = atoi(value); } OPT(lookahead-threads) { if( !strcasecmp(value, auto) ) p->i_lookahead_threads = X264_THREADS_AUTO; else p->i_lookahead_threads = atoi(value); } OPT(sliced-threads) p->b_sliced_threads = atobool(value); OPT(sync-lookahead) { if( !strcasecmp(value, auto) ) p->i_sync_lookahead = X264_SYNC_LOOKAHEAD_AUTO; else p->i_sync_lookahead = atoi(value); } OPT2(deterministic, n-deterministic) p->b_deterministic = atobool(value); OPT(cpu-independent) p->b_cpu_independent = atobool(value); OPT2(level, level-idc) { if( !strcmp(value, 1b) ) p->i_level_idc = 9; else if( atof(value) < 6 ) p->i_level_idc = (int)(10*atof(value)+.5); else p->i_level_idc = atoi(value); } OPT(bluray-compat) p->b_bluray_compat = atobool(value); OPT(avcintra-class) p->i_avcintra_class = atoi(value); OPT(sar) { b_error = ( 2 != sscanf( value, %d:%d, &p->vui.i_sar_width, &p->vui.i_sar_height ) && 2 != sscanf( value, %d/%d, &p->vui.i_sar_width, &p->vui.i_sar_height ) ); } OPT(overscan) b_error |= parse_enum( value, x264_overscan_names, &p->vui.i_overscan ); OPT(videoformat) b_error |= parse_enum( value, x264_vidformat_names, &p->vui.i_vidformat ); OPT(fullrange) b_error |= parse_enum( value, x264_fullrange_names, &p->vui.b_fullrange ); OPT(colorprim) b_error |= parse_enum( value, x264_colorprim_names, &p->vui.i_colorprim ); OPT(transfer) b_error |= parse_enum( value, x264_transfer_names, &p->vui.i_transfer ); OPT(colormatrix) b_error |= parse_enum( value, x264_colmatrix_names, &p->vui.i_colmatrix ); OPT(chromaloc) { p->vui.i_chroma_loc = atoi(value); b_error = ( p->vui.i_chroma_loc < 0 || p->vui.i_chroma_loc > 5 ); } OPT(fps) { if( sscanf( value, %u/%u, &p->i_fps_num, &p->i_fps_den ) == 2 ) ; else { float fps = atof(value); if( fps > 0 && fps <= INT_MAX/1000 ) { p->i_fps_num = (int)(fps * 1000 + .5); p->i_fps_den = 1000; } else { p->i_fps_num = atoi(value); p->i_fps_den = 1; } } } OPT2(ref, frameref) p->i_frame_reference = atoi(value); OPT(dpb-size) p->i_dpb_size = atoi(value); OPT(keyint) { if( strstr( value, infinite ) ) p->i_keyint_max = X264_KEYINT_MAX_INFINITE; else p->i_keyint_max = atoi(value); } OPT2(min-keyint, keyint-min) { p->i_keyint_min = atoi(value); if( p->i_keyint_max < p->i_keyint_min ) p->i_keyint_max = p->i_keyint_min; } OPT(scenecut) { p->i_scenecut_threshold = atobool(value); if( b_error || p->i_scenecut_threshold ) { b_error = 0; p->i_scenecut_threshold = atoi(value); } } OPT(intra-refresh) p->b_intra_refresh = atobool(value); OPT(bframes) p->i_bframe = atoi(value); OPT(b-adapt) { p->i_bframe_adaptive = atobool(value); if( b_error ) { b_error = 0; p->i_bframe_adaptive = atoi(value); } } OPT(b-bias) p->i_bframe_bias = atoi(value); OPT(b-pyramid) { b_error |= parse_enum( value, x264_b_pyramid_names, &p->i_bframe_pyramid ); if( b_error ) { b_error = 0; p->i_bframe_pyramid = atoi(value); } } OPT(open-gop) p->b_open_gop = atobool(value); OPT(nf) p->b_deblocking_filter = !atobool(value); OPT2(filter, deblock) { if( 2 == sscanf( value, %d:%d, &p->i_deblocking_filter_alphac0, &p->i_deblocking_filter_beta ) || 2 == sscanf( value, %d,%d, &p->i_deblocking_filter_alphac0, &p->i_deblocking_filter_beta ) ) { p->b_deblocking_filter = 1; } else if( sscanf( value, %d, &p->i_deblocking_filter_alphac0 ) ) { p->b_deblocking_filter = 1; p->i_deblocking_filter_beta = p->i_deblocking_filter_alphac0; } else p->b_deblocking_filter = atobool(value); } OPT(slice-max-size) p->i_slice_max_size = atoi(value); OPT(slice-max-mbs) p->i_slice_max_mbs = atoi(value); OPT(slice-min-mbs) p->i_slice_min_mbs = atoi(value); OPT(slices) p->i_slice_count = atoi(value); OPT(slices-max) p->i_slice_count_max = atoi(value); OPT(cabac) p->b_cabac = atobool(value); OPT(cabac-idc) p->i_cabac_init_idc = atoi(value); OPT(interlaced) p->b_interlaced = atobool(value); OPT(tff) p->b_interlaced = p->b_tff = atobool(value); OPT(bff) { p->b_interlaced = atobool(value); p->b_tff = !p->b_interlaced; } OPT(constrained-intra) p->b_constrained_intra = atobool(value); OPT(cqm) { if( strstr( value, flat ) ) p->i_cqm_preset = X264_CQM_FLAT; else if( strstr( value, jvt ) ) p->i_cqm_preset = X264_CQM_JVT; else p->psz_cqm_file = strdup(value); } OPT(cqmfile) p->psz_cqm_file = strdup(value); OPT(cqm4) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4iy, 16 ); b_error |= parse_cqm( value, p->cqm_4py, 16 ); b_error |= parse_cqm( value, p->cqm_4ic, 16 ); b_error |= parse_cqm( value, p->cqm_4pc, 16 ); } OPT(cqm8) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_8iy, 64 ); b_error |= parse_cqm( value, p->cqm_8py, 64 ); b_error |= parse_cqm( value, p->cqm_8ic, 64 ); b_error |= parse_cqm( value, p->cqm_8pc, 64 ); } OPT(cqm4i) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4iy, 16 ); b_error |= parse_cqm( value, p->cqm_4ic, 16 ); } OPT(cqm4p) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4py, 16 ); b_error |= parse_cqm( value, p->cqm_4pc, 16 ); } OPT(cqm4iy) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4iy, 16 ); } OPT(cqm4ic) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4ic, 16 ); } OPT(cqm4py) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4py, 16 ); } OPT(cqm4pc) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_4pc, 16 ); } OPT(cqm8i) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_8iy, 64 ); b_error |= parse_cqm( value, p->cqm_8ic, 64 ); } OPT(cqm8p) { p->i_cqm_preset = X264_CQM_CUSTOM; b_error |= parse_cqm( value, p->cqm_8py, 64 ); b_error |= parse_cqm( value, p->cqm_8pc, 64 ); } OPT(log) p->i_log_level = atoi(value); OPT(dump-yuv) p->psz_dump_yuv = strdup(value); OPT2(analyse, partitions) { p->analyse.inter = 0; if( strstr( value, none ) ) p->analyse.inter = 0; if( strstr( value, all ) ) p->analyse.inter = ~0; if( strstr( value, i4x4 ) ) p->analyse.inter |= X264_ANALYSE_I4x4; if( strstr( value, i8x8 ) ) p->analyse.inter |= X264_ANALYSE_I8x8; if( strstr( value, p8x8 ) ) p->analyse.inter |= X264_ANALYSE_PSUB16x16; if( strstr( value, p4x4 ) ) p->analyse.inter |= X264_ANALYSE_PSUB8x8; if( strstr( value, b8x8 ) ) p->analyse.inter |= X264_ANALYSE_BSUB16x16; } OPT(8x8dct) p->analyse.b_transform_8x8 = atobool(value); OPT2(weightb, weight-b) p->analyse.b_weighted_bipred = atobool(value); OPT(weightp) p->analyse.i_weighted_pred = atoi(value); OPT2(direct, direct-pred) b_error |= parse_enum( value, x264_direct_pred_names, &p->analyse.i_direct_mv_pred ); OPT(chroma-qp-offset) p->analyse.i_chroma_qp_offset = atoi(value); OPT(me) b_error |= parse_enum( value, x264_motion_est_names, &p->analyse.i_me_method ); OPT2(merange, me-range) p->analyse.i_me_range = atoi(value); OPT2(mvrange, mv-range) p->analyse.i_mv_range = atoi(value); OPT2(mvrange-thread, mv-range-thread) p->analyse.i_mv_range_thread = atoi(value); OPT2(subme, subq) p->analyse.i_subpel_refine = atoi(value); OPT(psy-rd) { if( 2 == sscanf( value, %f:%f, &p->analyse.f_psy_rd, &p->analyse.f_psy_trellis ) || 2 == sscanf( value, %f,%f, &p->analyse.f_psy_rd, &p->analyse.f_psy_trellis ) || 2 == sscanf( value, %f|%f, &p->analyse.f_psy_rd, &p->analyse.f_psy_trellis )) { } else if( sscanf( value, %f, &p->analyse.f_psy_rd ) ) { p->analyse.f_psy_trellis = 0; } else { p->analyse.f_psy_rd = 0; p->analyse.f_psy_trellis = 0; } } OPT(psy) p->analyse.b_psy = atobool(value); OPT(chroma-me) p->analyse.b_chroma_me = atobool(value); OPT(mixed-refs) p->analyse.b_mixed_references = atobool(value); OPT(trellis) p->analyse.i_trellis = atoi(value); OPT(fast-pskip) p->analyse.b_fast_pskip = atobool(value); OPT(dct-decimate) p->analyse.b_dct_decimate = atobool(value); OPT(deadzone-inter) p->analyse.i_luma_deadzone[0] = atoi(value); OPT(deadzone-intra) p->analyse.i_luma_deadzone[1] = atoi(value); OPT(nr) p->analyse.i_noise_reduction = atoi(value); OPT(bitrate) { p->rc.i_bitrate = atoi(value); p->rc.i_rc_method = X264_RC_ABR; } OPT2(qp, qp_constant) { p->rc.i_qp_constant = atoi(value); p->rc.i_rc_method = X264_RC_CQP; } OPT(crf) { p->rc.f_rf_constant = atof(value); p->rc.i_rc_method = X264_RC_CRF; } OPT(crf-max) p->rc.f_rf_constant_max = atof(value); OPT(rc-lookahead) p->rc.i_lookahead = atoi(value); OPT2(qpmin, qp-min) p->rc.i_qp_min = atoi(value); OPT2(qpmax, qp-max) p->rc.i_qp_max = atoi(value); OPT2(qpstep, qp-step) p->rc.i_qp_step = atoi(value); OPT(ratetol) p->rc.f_rate_tolerance = !strncmp(inf, value, 3) ? 1e9 : atof(value); OPT(vbv-maxrate) p->rc.i_vbv_max_bitrate = atoi(value); OPT(vbv-bufsize) p->rc.i_vbv_buffer_size = atoi(value); OPT(vbv-init) p->rc.f_vbv_buffer_init = atof(value); OPT2(ipratio, ip-factor) p->rc.f_ip_factor = atof(value); OPT2(pbratio, pb-factor) p->rc.f_pb_factor = atof(value); OPT(aq-mode) p->rc.i_aq_mode = atoi(value); OPT(aq-strength) p->rc.f_aq_strength = atof(value); OPT(pass) { int pass = x264_clip3( atoi(value), 0, 3 ); p->rc.b_stat_write = pass & 1; p->rc.b_stat_read = pass & 2; } OPT(stats) { p->rc.psz_stat_in = strdup(value); p->rc.psz_stat_out = strdup(value); } OPT(qcomp) p->rc.f_qcompress = atof(value); OPT(mbtree) p->rc.b_mb_tree = atobool(value); OPT(qblur) p->rc.f_qblur = atof(value); OPT2(cplxblur, cplx-blur) p->rc.f_complexity_blur = atof(value); OPT(zones) p->rc.psz_zones = strdup(value); OPT(crop-rect) b_error |= sscanf( value, %u,%u,%u,%u, &p->crop_rect.i_left, &p->crop_rect.i_top, &p->crop_rect.i_right, &p->crop_rect.i_bottom ) != 4; OPT(psnr) p->analyse.b_psnr = atobool(value); OPT(ssim) p->analyse.b_ssim = atobool(value); OPT(aud) p->b_aud = atobool(value); OPT(sps-id) p->i_sps_id = atoi(value); OPT(global-header) p->b_repeat_headers = !atobool(value); OPT(repeat-headers) p->b_repeat_headers = atobool(value); OPT(annexb) p->b_annexb = atobool(value); OPT(force-cfr) p->b_vfr_input = !atobool(value); OPT(nal-hrd) b_error |= parse_enum( value, x264_nal_hrd_names, &p->i_nal_hrd ); OPT(filler) p->rc.b_filler = atobool(value); OPT(pic-struct) p->b_pic_struct = atobool(value); OPT(fake-interlaced) p->b_fake_interlaced = atobool(value); OPT(frame-packing) p->i_frame_packing = atoi(value); OPT(stitchable) p->b_stitchable = atobool(value); OPT(opencl) p->b_opencl = atobool( value ); OPT(opencl-clbin) p->psz_clbin_file = strdup( value ); OPT(opencl-device) p->i_opencl_device = atoi( value ); else return X264_PARAM_BAD_NAME; #undef OPT #undef OPT2 #undef atobool #undef atoi #undef atof if( name_buf ) free( name_buf ); b_error |= value_was_null && !name_was_bool; return b_error ? X264_PARAM_BAD_VALUE : 0; }
/* (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. * * returns 0 on success, negative on failure (e.g. invalid profile name). */ int x264_param_apply_profile( x264_param_t *, const char *profile );x264_param_apply_profile()的定義如下所示。
//設置profile int x264_param_apply_profile( x264_param_t *param, const char *profile ) { if( !profile ) return 0; //字符串到整型 int p = profile_string_to_int( profile ); //檢查profile設置是否正確 if( p < 0 ) { x264_log( NULL, X264_LOG_ERROR, invalid profile: %s , profile ); return -1; } if( p < PROFILE_HIGH444_PREDICTIVE && ((param->rc.i_rc_method == X264_RC_CQP && param->rc.i_qp_constant <= 0) || (param->rc.i_rc_method == X264_RC_CRF && (int)(param->rc.f_rf_constant + QP_BD_OFFSET) <= 0)) ) { x264_log( NULL, X264_LOG_ERROR, %s profile doesn't support lossless , profile ); return -1; } if( p < PROFILE_HIGH444_PREDICTIVE && (param->i_csp & X264_CSP_MASK) >= X264_CSP_I444 ) { x264_log( NULL, X264_LOG_ERROR, %s profile doesn't support 4:4:4 , profile ); return -1; } if( p < PROFILE_HIGH422 && (param->i_csp & X264_CSP_MASK) >= X264_CSP_I422 ) { x264_log( NULL, X264_LOG_ERROR, %s profile doesn't support 4:2:2 , profile ); return -1; } if( p < PROFILE_HIGH10 && BIT_DEPTH > 8 ) { x264_log( NULL, X264_LOG_ERROR, %s profile doesn't support a bit depth of %d , profile, BIT_DEPTH ); return -1; } //根據不同的Profile做設置 //Baseline基本型 if( p == PROFILE_BASELINE ) { //不支持DCT8x8 param->analyse.b_transform_8x8 = 0; //不使用CABAC param->b_cabac = 0; param->i_cqm_preset = X264_CQM_FLAT; param->psz_cqm_file = NULL; //沒有B幀 param->i_bframe = 0; //沒有加權 param->analyse.i_weighted_pred = X264_WEIGHTP_NONE; //不支持隔行掃描 if( param->b_interlaced ) { x264_log( NULL, X264_LOG_ERROR, baseline profile doesn't support interlacing ); return -1; } if( param->b_fake_interlaced ) { x264_log( NULL, X264_LOG_ERROR, baseline profile doesn't support fake interlacing ); return -1; } } //Main主型 else if( p == PROFILE_MAIN ) { //不支持DCT8x8 param->analyse.b_transform_8x8 = 0; param->i_cqm_preset = X264_CQM_FLAT; param->psz_cqm_file = NULL; } return 0; }
static int profile_string_to_int( const char *str ) { if( !strcasecmp( str, baseline ) ) return PROFILE_BASELINE; if( !strcasecmp( str, main ) ) return PROFILE_MAIN; if( !strcasecmp( str, high ) ) return PROFILE_HIGH; if( !strcasecmp( str, high10 ) ) return PROFILE_HIGH10; if( !strcasecmp( str, high422 ) ) return PROFILE_HIGH422; if( !strcasecmp( str, high444 ) ) return PROFILE_HIGH444_PREDICTIVE; return -1; }從定義可以看出profile_string_to_int()根據輸入的字符串str返回不同的整型變量。
//根據文件名的後綴確定輸出的文件格式(raw H264,flv,mp4...) static int select_output( const char *muxer, char *filename, x264_param_t *param ) { //從文件路徑字符串中解析出擴展名,存入ext //解析的方式就是反向搜索字符“.” const char *ext = get_filename_extension( filename ); //strcasecmp(char *s1, char *s2)用於忽略大小寫比較字符串. //參數s1和s2字符串相等則返回0。s1大於s2則返回大於0 的值,s1 小於s2 則返回小於0的值。 if( !strcmp( filename, - ) || strcasecmp( muxer, auto ) ) ext = muxer; //後綴為“mp4” if( !strcasecmp( ext, mp4 ) ) { #if HAVE_GPAC || HAVE_LSMASH cli_output = mp4_output; param->b_annexb = 0; param->b_repeat_headers = 0; if( param->i_nal_hrd == X264_NAL_HRD_CBR ) { x264_cli_log( x264, X264_LOG_WARNING, cbr nal-hrd is not compatible with mp4 ); param->i_nal_hrd = X264_NAL_HRD_VBR; } #else x264_cli_log( x264, X264_LOG_ERROR, not compiled with MP4 output support ); return -1; #endif } else if( !strcasecmp( ext, mkv ) ) { //設定cli_output_t cli_output = mkv_output; //不加起始碼0x00000001 param->b_annexb = 0; //不再每個Keyframe前面加SPS和PPS param->b_repeat_headers = 0; } else if( !strcasecmp( ext, flv ) ) { cli_output = flv_output; param->b_annexb = 0; param->b_repeat_headers = 0; } else cli_output = raw_output;//不符合上述後綴,則輸出裸流 return 0; }
//根據“.”確定文件後綴 static inline char *get_filename_extension( char *filename ) { char *ext = filename + strlen( filename ); while( *ext != '.' && ext > filename ) ext--; ext += *ext == '.'; return ext; }可以看出get_filename_extension()從字符串的末尾開始向前搜索點符號“.”,並且將“.”後面的內容作為提取出來的擴展名。
//設置輸入文件的格式(yuv,y4m...) 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 ) { int b_auto = !strcasecmp( demuxer, auto ); //從文件路徑字符串中解析出擴展名,存入ext //解析的方式就是反向搜索字符“.” const char *ext = b_auto ? get_filename_extension( filename ) : ; int b_regular = strcmp( filename, - ); if( !b_regular && b_auto ) ext = raw; b_regular = b_regular && x264_is_regular_file_path( filename ); if( b_regular ) { FILE *f = x264_fopen( filename, r ); if( f ) { b_regular = x264_is_regular_file( f ); fclose( f ); } } const char *module = b_auto ? ext : demuxer; //strcasecmp(char *s1, char *s2)用於忽略大小寫比較字符串. //參數s1和s2字符串相等則返回0。s1大於s2則返回大於0 的值,s1 小於s2 則返回小於0的值。 if( !strcasecmp( module, avs ) || !strcasecmp( ext, d2v ) || !strcasecmp( ext, dga ) ) { #if HAVE_AVS cli_input = avs_input; module = avs; #else x264_cli_log( x264, X264_LOG_ERROR, not compiled with AVS input support ); return -1; #endif } else if( !strcasecmp( module, y4m ) ) cli_input = y4m_input; else if( !strcasecmp( module, raw ) || !strcasecmp( ext, yuv ) ) cli_input = raw_input; else { #if HAVE_FFMS if( b_regular && (b_auto || !strcasecmp( demuxer, ffms )) && !ffms_input.open_file( filename, p_handle, info, opt ) ) { module = ffms; b_auto = 0; cli_input = ffms_input; } #endif #if HAVE_LAVF if( (b_auto || !strcasecmp( demuxer, lavf )) && !lavf_input.open_file( filename, p_handle, info, opt ) ) { module = lavf; b_auto = 0; cli_input = lavf_input; } #endif #if HAVE_AVS if( b_regular && (b_auto || !strcasecmp( demuxer, avs )) && !avs_input.open_file( filename, p_handle, info, opt ) ) { module = avs; b_auto = 0; cli_input = avs_input; } #endif if( b_auto && !raw_input.open_file( filename, p_handle, info, opt ) ) { module = raw; b_auto = 0; cli_input = raw_input; } FAIL_IF_ERROR( !(*p_handle), could not open input file `%s' via any method! , filename ) } strcpy( used_demuxer, module ); return 0; }
//編碼(在內部有一個循環用於一幀一幀編碼) static int encode( x264_param_t *param, cli_opt_t *opt ) { x264_t *h = NULL; x264_picture_t pic; cli_pic_t cli_pic; const cli_pulldown_t *pulldown = NULL; // shut up gcc int i_frame = 0; int i_frame_output = 0; int64_t i_end, i_previous = 0, i_start = 0; int64_t i_file = 0; int i_frame_size; int64_t last_dts = 0; int64_t prev_dts = 0; int64_t first_dts = 0; # define MAX_PTS_WARNING 3 /* arbitrary */ int pts_warning_cnt = 0; int64_t largest_pts = -1; int64_t second_largest_pts = -1; int64_t ticks_per_frame; double duration; double pulldown_pts = 0; int retval = 0; opt->b_progress &= param->i_log_level < X264_LOG_DEBUG; /* set up pulldown */ if( opt->i_pulldown && !param->b_vfr_input ) { param->b_pulldown = 1; param->b_pic_struct = 1; pulldown = &pulldown_values[opt->i_pulldown]; param->i_timebase_num = param->i_fps_den; FAIL_IF_ERROR2( fmod( param->i_fps_num * pulldown->fps_factor, 1 ), unsupported framerate for chosen pulldown ) param->i_timebase_den = param->i_fps_num * pulldown->fps_factor; } //打開編碼器 h = x264_encoder_open( param ); FAIL_IF_ERROR2( !h, x264_encoder_open failed ); //獲得參數 x264_encoder_parameters( h, param ); //一些不是裸流的封轉格式(FLV,MP4等)需要一些參數,例如寬高等等 //cli_output_t是代表輸出媒體文件的結構體 FAIL_IF_ERROR2( cli_output.set_param( opt->hout, param ), can't set outfile param ); //計時 i_start = x264_mdate(); /* ticks/frame = ticks/second / frames/second */ ticks_per_frame = (int64_t)param->i_timebase_den * param->i_fps_den / param->i_timebase_num / param->i_fps_num; FAIL_IF_ERROR2( ticks_per_frame < 1 && !param->b_vfr_input, ticks_per_frame invalid: %PRId64 , ticks_per_frame ) ticks_per_frame = X264_MAX( ticks_per_frame, 1 ); //如果不是在每個keyframe前面都增加SPS/PPS/SEI的話,就在整個碼流前面加SPS/PPS/SEI //Header指的就是SPS/PPS/SEI if( !param->b_repeat_headers ) { // Write SPS/PPS/SEI x264_nal_t *headers; int i_nal; //獲得文件頭(SPS、PPS、SEI) FAIL_IF_ERROR2( x264_encoder_headers( h, &headers, &i_nal ) < 0, x264_encoder_headers failed ) //把文件頭寫入輸出文件 FAIL_IF_ERROR2( (i_file = cli_output.write_headers( opt->hout, headers )) < 0, error writing headers to output file ); } if( opt->tcfile_out ) fprintf( opt->tcfile_out, # timecode format v2 ); /* Encode frames */ //循環進行編碼 for( ; !b_ctrl_c && (i_frame < param->i_frame_total || !param->i_frame_total); i_frame++ ) { //從輸入源中獲取1幀YUV數據,存於cli_pic //cli_vid_filter_t可以認為是x264一種“擴展”後的輸入源,可以在像素域對圖像進行拉伸裁剪等工作。 //原本代表輸入源的結構體是cli_input_t if( filter.get_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) ) break; //初始化x264_picture_t結構體pic x264_picture_init( &pic ); //cli_pic到pic convert_cli_to_lib_pic( &pic, &cli_pic ); if( !param->b_vfr_input ) pic.i_pts = i_frame; if( opt->i_pulldown && !param->b_vfr_input ) { pic.i_pic_struct = pulldown->pattern[ i_frame % pulldown->mod ]; pic.i_pts = (int64_t)( pulldown_pts + 0.5 ); pulldown_pts += pulldown_frame_duration[pic.i_pic_struct]; } else if( opt->timebase_convert_multiplier ) pic.i_pts = (int64_t)( pic.i_pts * opt->timebase_convert_multiplier + 0.5 ); if( pic.i_pts <= largest_pts ) { if( cli_log_level >= X264_LOG_DEBUG || pts_warning_cnt < MAX_PTS_WARNING ) x264_cli_log( x264, X264_LOG_WARNING, non-strictly-monotonic pts at frame %d (%PRId64 <= %PRId64) , i_frame, pic.i_pts, largest_pts ); else if( pts_warning_cnt == MAX_PTS_WARNING ) x264_cli_log( x264, X264_LOG_WARNING, too many nonmonotonic pts warnings, suppressing further ones ); pts_warning_cnt++; pic.i_pts = largest_pts + ticks_per_frame; } second_largest_pts = largest_pts; largest_pts = pic.i_pts; if( opt->tcfile_out ) fprintf( opt->tcfile_out, %.6f , pic.i_pts * ((double)param->i_timebase_num / param->i_timebase_den) * 1e3 ); if( opt->qpfile ) parse_qpfile( opt, &pic, i_frame + opt->i_seek ); prev_dts = last_dts; //編碼pic中存儲的1幀YUV數據 i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts ); if( i_frame_size < 0 ) { b_ctrl_c = 1; /* lie to exit the loop */ retval = -1; } else if( i_frame_size ) { i_file += i_frame_size; i_frame_output++; if( i_frame_output == 1 ) first_dts = prev_dts = last_dts; } //釋放處理完的YUV數據 if( filter.release_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) ) break; /* update status line (up to 1000 times per input file) */ if( opt->b_progress && i_frame_output ) i_previous = print_status( i_start, i_previous, i_frame_output, param->i_frame_total, i_file, param, 2 * last_dts - prev_dts - first_dts ); } /* Flush delayed frames */ //輸出編碼器中剩余的幀 //x264_encoder_delayed_frames()返回剩余的幀的個數 while( !b_ctrl_c && x264_encoder_delayed_frames( h ) ) { prev_dts = last_dts; //編碼 //注意第3個參數為NULL i_frame_size = encode_frame( h, opt->hout, NULL, &last_dts ); if( i_frame_size < 0 ) { b_ctrl_c = 1; /* lie to exit the loop */ retval = -1; } else if( i_frame_size ) { i_file += i_frame_size; i_frame_output++; if( i_frame_output == 1 ) first_dts = prev_dts = last_dts; } //輸出一些統計信息 if( opt->b_progress && i_frame_output ) i_previous = print_status( i_start, i_previous, i_frame_output, param->i_frame_total, i_file, param, 2 * last_dts - prev_dts - first_dts ); } fail: if( pts_warning_cnt >= MAX_PTS_WARNING && cli_log_level < X264_LOG_DEBUG ) x264_cli_log( x264, X264_LOG_WARNING, %d suppressed nonmonotonic pts warnings , pts_warning_cnt-MAX_PTS_WARNING ); /* duration algorithm fails when only 1 frame is output */ if( i_frame_output == 1 ) duration = (double)param->i_fps_den / param->i_fps_num; else if( b_ctrl_c ) duration = (double)(2 * last_dts - prev_dts - first_dts) * param->i_timebase_num / param->i_timebase_den; else duration = (double)(2 * largest_pts - second_largest_pts) * param->i_timebase_num / param->i_timebase_den; //計時 i_end = x264_mdate(); /* Erase progress indicator before printing encoding stats. */ if( opt->b_progress ) fprintf( stderr, ); //關閉編碼器 if( h ) x264_encoder_close( h ); fprintf( stderr, ); if( b_ctrl_c ) fprintf( stderr, aborted at input frame %d, output frame %d , opt->i_seek + i_frame, i_frame_output ); //關閉輸出文件 cli_output.close_file( opt->hout, largest_pts, second_largest_pts ); opt->hout = NULL; if( i_frame_output > 0 ) { double fps = (double)i_frame_output * (double)1000000 / (double)( i_end - i_start ); fprintf( stderr, encoded %d frames, %.2f fps, %.2f kb/s , i_frame_output, fps, (double) i_file * 8 / ( 1000 * duration ) ); } return retval; }
(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編碼器。
x264_encoder_open():打開H.264編碼器。
x264_encoder_headers():輸出SPS/PPS/SEI。
x264_encoder_encode():編碼一幀數據。
x264_encoder_close():關閉H.264編碼器。
//編碼1幀 static int encode_frame( x264_t *h, hnd_t hout, x264_picture_t *pic, int64_t *last_dts ) { x264_picture_t pic_out; x264_nal_t *nal; int i_nal; int i_frame_size = 0; //編碼API //編碼x264_picture_t為x264_nal_t i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ); FAIL_IF_ERROR( i_frame_size < 0, x264_encoder_encode failed ); if( i_frame_size ) { //通過cli_output_t中的方法輸出 //輸出raw H.264流的話,等同於直接fwrite() //其他封裝格式,則還需進行一定的封裝 i_frame_size = cli_output.write_frame( hout, nal[0].p_payload, i_frame_size, &pic_out ); *last_dts = pic_out.i_dts; } return i_frame_size; }
//打印一些和時間有關的統計信息 static int64_t print_status( int64_t i_start, int64_t i_previous, int i_frame, int i_frame_total, int64_t i_file, x264_param_t *param, int64_t last_ts ) { char buf[200]; int64_t i_time = x264_mdate(); if( i_previous && i_time - i_previous < UPDATE_INTERVAL ) return i_previous; int64_t i_elapsed = i_time - i_start; double fps = i_elapsed > 0 ? i_frame * 1000000. / i_elapsed : 0; double bitrate; if( last_ts ) bitrate = (double) i_file * 8 / ( (double) last_ts * 1000 * param->i_timebase_num / param->i_timebase_den ); else bitrate = (double) i_file * 8 / ( (double) 1000 * param->i_fps_den / param->i_fps_num ); if( i_frame_total ) { //形成輸出的字符串 int eta = i_elapsed * (i_frame_total - i_frame) / ((int64_t)i_frame * 1000000); sprintf( buf, x264 [%.1f%%] %d/%d frames, %.2f fps, %.2f kb/s, eta %d:%02d:%02d, 100. * i_frame / i_frame_total, i_frame, i_frame_total, fps, bitrate, eta/3600, (eta/60)%60, eta%60 ); } else sprintf( buf, x264 %d frames: %.2f fps, %.2f kb/s, i_frame, fps, bitrate ); //輸出到stderr fprintf( stderr, %s , buf+5 ); //設置到標題欄? x264_cli_set_console_title( buf ); fflush( stderr ); // needed in windows return i_time; }
cli_output_t:輸出格式對應的結構體。輸出格式一般為H.264裸流、FLV、MP4等。在x264的編碼過程中,調用cli_vid_filter_t結構體的get_frame()讀取YUV數據,調用cli_output_t的write_frame()寫入數據。下面簡單分析一下它們之間的關系。
cli_input_t:輸入格式對應的結構體。輸入格式一般為純YUV像素數據,Y4M格式數據等。
cli_vid_filter_t:輸入格式濾鏡結構體。濾鏡可以對輸入數據做一些簡單的處理,例如拉伸、裁剪等等(當然濾鏡也可以不作任何處理,直接讀取輸入數據)。
typedef struct { int (*open_file)( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt ); int (*set_param)( hnd_t handle, x264_param_t *p_param ); int (*write_headers)( hnd_t handle, x264_nal_t *p_nal ); int (*write_frame)( hnd_t handle, uint8_t *p_nal, int i_size, x264_picture_t *p_picture ); int (*close_file)( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts ); } cli_output_t; extern const cli_output_t raw_output; extern const cli_output_t mkv_output; extern const cli_output_t mp4_output; extern const cli_output_t flv_output;
#include output.h static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt ) { if( !strcmp( psz_filename, - ) ) *p_handle = stdout; else if( !(*p_handle = x264_fopen( psz_filename, w+b )) ) return -1; return 0; } static int set_param( hnd_t handle, x264_param_t *p_param ) { return 0; } static int write_headers( hnd_t handle, x264_nal_t *p_nal ) { int size = p_nal[0].i_payload + p_nal[1].i_payload + p_nal[2].i_payload; if( fwrite( p_nal[0].p_payload, size, 1, (FILE*)handle ) ) return size; return -1; } static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture ) { if( fwrite( p_nalu, i_size, 1, (FILE*)handle ) ) return i_size; return -1; } static int close_file( hnd_t handle, int64_t largest_pts, int64_t second_largest_pts ) { if( !handle || handle == stdout ) return 0; return fclose( (FILE*)handle ); } const cli_output_t raw_output = { open_file, set_param, write_headers, write_frame, close_file };
const cli_output_t flv_output = { open_file, set_param, write_headers, write_frame, close_file };該文件內容比較多,只舉例看一下其中的兩個函數:open_file()和write_frame()。
static int write_header( flv_buffer *c ) { flv_put_tag( c, FLV ); // Signature flv_put_byte( c, 1 ); // Version flv_put_byte( c, 1 ); // Video Only flv_put_be32( c, 9 ); // DataOffset flv_put_be32( c, 0 ); // PreviousTagSize0 return flv_flush_data( c ); } static int open_file( char *psz_filename, hnd_t *p_handle, cli_output_opt_t *opt ) { *p_handle = NULL; flv_hnd_t *p_flv = calloc( 1, sizeof(flv_hnd_t) ); if( !p_flv ) return -1; p_flv->b_dts_compress = opt->use_dts_compress; p_flv->c = flv_create_writer( psz_filename ); if( !p_flv->c ) return -1; CHECK( write_header( p_flv->c ) ); *p_handle = p_flv; return 0; }可以看出flv_output 中的open_file()中完成了FLV封裝格式文件頭的創建。
static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture ) { flv_hnd_t *p_flv = handle; flv_buffer *c = p_flv->c; #define convert_timebase_ms( timestamp, timebase ) (int64_t)((timestamp) * (timebase) * 1000 + 0.5) if( !p_flv->i_framenum ) { p_flv->i_delay_time = p_picture->i_dts * -1; if( !p_flv->b_dts_compress && p_flv->i_delay_time ) x264_cli_log( flv, X264_LOG_INFO, initial delay %PRId64 ms , convert_timebase_ms( p_picture->i_pts + p_flv->i_delay_time, p_flv->d_timebase ) ); } int64_t dts; int64_t cts; int64_t offset; if( p_flv->b_dts_compress ) { if( p_flv->i_framenum == 1 ) p_flv->i_init_delta = convert_timebase_ms( p_picture->i_dts + p_flv->i_delay_time, p_flv->d_timebase ); dts = p_flv->i_framenum > p_flv->i_delay_frames ? convert_timebase_ms( p_picture->i_dts, p_flv->d_timebase ) : p_flv->i_framenum * p_flv->i_init_delta / (p_flv->i_delay_frames + 1); cts = convert_timebase_ms( p_picture->i_pts, p_flv->d_timebase ); } else { dts = convert_timebase_ms( p_picture->i_dts + p_flv->i_delay_time, p_flv->d_timebase ); cts = convert_timebase_ms( p_picture->i_pts + p_flv->i_delay_time, p_flv->d_timebase ); } offset = cts - dts; if( p_flv->i_framenum ) { if( p_flv->i_prev_dts == dts ) x264_cli_log( flv, X264_LOG_WARNING, duplicate DTS %PRId64 generated by rounding decoding framerate cannot exceed 1000fps , dts ); if( p_flv->i_prev_cts == cts ) x264_cli_log( flv, X264_LOG_WARNING, duplicate CTS %PRId64 generated by rounding composition framerate cannot exceed 1000fps , cts ); } p_flv->i_prev_dts = dts; p_flv->i_prev_cts = cts; // A new frame - write packet header flv_put_byte( c, FLV_TAG_TYPE_VIDEO ); flv_put_be24( c, 0 ); // calculated later flv_put_be24( c, dts ); flv_put_byte( c, dts >> 24 ); flv_put_be24( c, 0 ); p_flv->start = c->d_cur; flv_put_byte( c, p_picture->b_keyframe ? FLV_FRAME_KEY : FLV_FRAME_INTER ); flv_put_byte( c, 1 ); // AVC NALU flv_put_be24( c, offset ); if( p_flv->sei ) { flv_append_data( c, p_flv->sei, p_flv->sei_len ); free( p_flv->sei ); p_flv->sei = NULL; } flv_append_data( c, p_nalu, i_size ); unsigned length = c->d_cur - p_flv->start; flv_rewrite_amf_be24( c, length, p_flv->start - 10 ); flv_put_be32( c, 11 + length ); // Last tag size CHECK( flv_flush_data( c ) ); p_flv->i_framenum++; return i_size; }
typedef struct { int (*open_file)( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt ); int (*picture_alloc)( cli_pic_t *pic, int csp, int width, int height ); int (*read_frame)( cli_pic_t *pic, hnd_t handle, int i_frame ); int (*release_frame)( cli_pic_t *pic, hnd_t handle ); void (*picture_clean)( cli_pic_t *pic ); int (*close_file)( hnd_t handle ); } cli_input_t; extern const cli_input_t raw_input; extern const cli_input_t y4m_input; extern const cli_input_t avs_input; extern const cli_input_t lavf_input; extern const cli_input_t ffms_input;
#include input.h #define FAIL_IF_ERROR( cond, ... ) FAIL_IF_ERR( cond, raw, __VA_ARGS__ ) typedef struct { FILE *fh; int next_frame; uint64_t plane_size[4]; uint64_t frame_size; int bit_depth; } raw_hnd_t; //打開raw YUV格式文件 static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt ) { raw_hnd_t *h = calloc( 1, sizeof(raw_hnd_t) ); if( !h ) return -1; if( !opt->resolution ) { //如果沒有設置分辨率 //嘗試從文件名中解析分辨率 /* try to parse the file name */ for( char *p = psz_filename; *p; p++ ) if( *p >= '0' && *p <= '9' && sscanf( p, %dx%d, &info->width, &info->height ) == 2 ) break; } else sscanf( opt->resolution, %dx%d, &info->width, &info->height ); //沒有分辨率信息的話,會彈出錯誤信息 FAIL_IF_ERROR( !info->width || !info->height, raw input requires a resolution. ) //設置顏色空間 if( opt->colorspace ) { for( info->csp = X264_CSP_CLI_MAX-1; info->csp > X264_CSP_NONE; info->csp-- ) { if( x264_cli_csps[info->csp].name && !strcasecmp( x264_cli_csps[info->csp].name, opt->colorspace ) ) break; } FAIL_IF_ERROR( info->csp == X264_CSP_NONE, unsupported colorspace `%s' , opt->colorspace ); } else /* default */ info->csp = X264_CSP_I420;//默認為YUV420P //顏色位深 h->bit_depth = opt->bit_depth; FAIL_IF_ERROR( h->bit_depth < 8 || h->bit_depth > 16, unsupported bit depth `%d' , h->bit_depth ); if( h->bit_depth > 8 ) info->csp |= X264_CSP_HIGH_DEPTH; if( !strcmp( psz_filename, - ) ) h->fh = stdin; //從管道輸入 else h->fh = x264_fopen( psz_filename, rb ); //打開文件 if( h->fh == NULL ) return -1; info->thread_safe = 1; info->num_frames = 0; info->vfr = 0; const x264_cli_csp_t *csp = x264_cli_get_csp( info->csp ); for( int i = 0; i < csp->planes; i++ ) { h->plane_size[i] = x264_cli_pic_plane_size( info->csp, info->width, info->height, i ); h->frame_size += h->plane_size[i]; /* x264_cli_pic_plane_size returns the size in bytes, we need the value in pixels from here on */ h->plane_size[i] /= x264_cli_csp_depth_factor( info->csp ); } if( x264_is_regular_file( h->fh ) ) { fseek( h->fh, 0, SEEK_END ); uint64_t size = ftell( h->fh ); fseek( h->fh, 0, SEEK_SET ); info->num_frames = size / h->frame_size; } *p_handle = h; return 0; } //讀取一幀數據-內部 static int read_frame_internal( cli_pic_t *pic, raw_hnd_t *h, int bit_depth_uc ) { int error = 0; int pixel_depth = x264_cli_csp_depth_factor( pic->img.csp ); //一個分量一個分量讀 for( int i = 0; i < pic->img.planes && !error; i++ ) { //fread()讀取 error |= fread( pic->img.plane[i], pixel_depth, h->plane_size[i], h->fh ) != h->plane_size[i]; if( bit_depth_uc ) { /* upconvert non 16bit high depth planes to 16bit using the same * algorithm as used in the depth filter. */ uint16_t *plane = (uint16_t*)pic->img.plane[i]; uint64_t pixel_count = h->plane_size[i]; int lshift = 16 - h->bit_depth; for( uint64_t j = 0; j < pixel_count; j++ ) plane[j] = plane[j] << lshift; } } return error; } //讀取一幀數據 static int read_frame( cli_pic_t *pic, hnd_t handle, int i_frame ) { raw_hnd_t *h = handle; if( i_frame > h->next_frame ) { if( x264_is_regular_file( h->fh ) ) fseek( h->fh, i_frame * h->frame_size, SEEK_SET ); //fseek()。偏移量=幀序號*幀大小。 else while( i_frame > h->next_frame ) { //讀取一幀數據-內部 if( read_frame_internal( pic, h, 0 ) ) return -1; h->next_frame++; } } if( read_frame_internal( pic, h, h->bit_depth & 7 ) ) return -1; h->next_frame = i_frame+1; return 0; } //關閉文件 static int close_file( hnd_t handle ) { raw_hnd_t *h = handle; if( !h || !h->fh ) return 0; //fclose()關閉文件 fclose( h->fh ); free( h ); return 0; } //raw格式對應的數組 const cli_input_t raw_input = { open_file, x264_cli_pic_alloc, read_frame, NULL, x264_cli_pic_clean, close_file };
從源代碼中可以看出,raw_input 中的open_file()函數在打開YUV像素數據的時候,會首先判斷是否設置了寬和高(YUV是純像素數據,沒有寬和高信息),如果沒有設置,則會嘗試從文件路徑中解析寬和高信息。如果成功完成上述步驟,open_file()就會調用x264_fopen()打開輸入文件。其他的函數在源代碼中都寫了注釋,就不再重復記錄了。
y4m_input(Y4M格式的cli_input_t結構體)
const cli_input_t y4m_input = { open_file, x264_cli_pic_alloc, read_frame, NULL, x264_cli_pic_clean, close_file };該文件內容較多,不再進行詳細分析。在這裡看一個打開文件的函數open_file()。該函數的定義如下所示。
typedef struct { FILE *fh; int next_frame; int seq_header_len; int frame_header_len; uint64_t frame_size; uint64_t plane_size[3]; int bit_depth; } y4m_hnd_t; #define Y4M_MAGIC YUV4MPEG2 #define MAX_YUV4_HEADER 80 #define Y4M_FRAME_MAGIC FRAME #define MAX_FRAME_HEADER 80 static int parse_csp_and_depth( char *csp_name, int *bit_depth ) { int csp = X264_CSP_MAX; /* Set colorspace from known variants */ if( !strncmp( 420, csp_name, 3 ) ) csp = X264_CSP_I420; else if( !strncmp( 422, csp_name, 3 ) ) csp = X264_CSP_I422; else if( !strncmp( 444, csp_name, 3 ) && strncmp( 444alpha, csp_name, 8 ) ) // only accept alphaless 4:4:4 csp = X264_CSP_I444; /* Set high bit depth from known extensions */ if( sscanf( csp_name, %*d%*[pP]%d, bit_depth ) != 1 ) *bit_depth = 8; return csp; } static int open_file( char *psz_filename, hnd_t *p_handle, video_info_t *info, cli_input_opt_t *opt ) { y4m_hnd_t *h = malloc( sizeof(y4m_hnd_t) ); int i; uint32_t n, d; char header[MAX_YUV4_HEADER+10]; char *tokend, *header_end; int colorspace = X264_CSP_NONE; int alt_colorspace = X264_CSP_NONE; int alt_bit_depth = 8; if( !h ) return -1; h->next_frame = 0; info->vfr = 0; if( !strcmp( psz_filename, - ) ) h->fh = stdin; else h->fh = x264_fopen(psz_filename, rb); if( h->fh == NULL ) return -1; h->frame_header_len = strlen( Y4M_FRAME_MAGIC )+1; /* Read header */ //解析Y4M格式的文件頭 for( i = 0; i < MAX_YUV4_HEADER; i++ ) { header[i] = fgetc( h->fh ); if( header[i] == ' ' )www.Bkjia.com { /* Add a space after last option. Makes parsing 444 vs 444alpha easier. */ header[i+1] = 0x20; header[i+2] = 0; break; } } if( i == MAX_YUV4_HEADER || strncmp( header, Y4M_MAGIC, strlen( Y4M_MAGIC ) ) ) return -1; /* Scan properties */ header_end = &header[i+1]; /* Include space */ h->seq_header_len = i+1; for( char *tokstart = &header[strlen( Y4M_MAGIC )+1]; tokstart < header_end; tokstart++ ) { if( *tokstart == 0x20 ) continue; switch( *tokstart++ ) { case 'W': /* Width. Required. */ info->width = strtol( tokstart, &tokend, 10 ); tokstart=tokend; break; case 'H': /* Height. Required. */ info->height = strtol( tokstart, &tokend, 10 ); tokstart=tokend; break; case 'C': /* Color space */ colorspace = parse_csp_and_depth( tokstart, &h->bit_depth ); tokstart = strchr( tokstart, 0x20 ); break; case 'I': /* Interlace type */ switch( *tokstart++ ) { case 't': info->interlaced = 1; info->tff = 1; break; case 'b': info->interlaced = 1; info->tff = 0; break; case 'm': info->interlaced = 1; break; //case '?': //case 'p': default: break; } break; case 'F': /* Frame rate - 0:0 if unknown */ if( sscanf( tokstart, %u:%u, &n, &d ) == 2 && n && d ) { x264_reduce_fraction( &n, &d ); info->fps_num = n; info->fps_den = d; } tokstart = strchr( tokstart, 0x20 ); break; case 'A': /* Pixel aspect - 0:0 if unknown */ /* Don't override the aspect ratio if sar has been explicitly set on the commandline. */ if( sscanf( tokstart, %u:%u, &n, &d ) == 2 && n && d ) { x264_reduce_fraction( &n, &d ); info->sar_width = n; info->sar_height = d; } tokstart = strchr( tokstart, 0x20 ); break; case 'X': /* Vendor extensions */ if( !strncmp( YSCSS=, tokstart, 6 ) ) { /* Older nonstandard pixel format representation */ tokstart += 6; alt_colorspace = parse_csp_and_depth( tokstart, &alt_bit_depth ); } tokstart = strchr( tokstart, 0x20 ); break; } } if( colorspace == X264_CSP_NONE ) { colorspace = alt_colorspace; h->bit_depth = alt_bit_depth; } // default to 8bit 4:2:0 if nothing is specified if( colorspace == X264_CSP_NONE ) { colorspace = X264_CSP_I420; h->bit_depth = 8; } FAIL_IF_ERROR( colorspace <= X264_CSP_NONE || colorspace >= X264_CSP_MAX, colorspace unhandled ) FAIL_IF_ERROR( h->bit_depth < 8 || h->bit_depth > 16, unsupported bit depth `%d' , h->bit_depth ); info->thread_safe = 1; info->num_frames = 0; info->csp = colorspace; h->frame_size = h->frame_header_len; if( h->bit_depth > 8 ) info->csp |= X264_CSP_HIGH_DEPTH; const x264_cli_csp_t *csp = x264_cli_get_csp( info->csp ); for( i = 0; i < csp->planes; i++ ) { h->plane_size[i] = x264_cli_pic_plane_size( info->csp, info->width, info->height, i ); h->frame_size += h->plane_size[i]; /* x264_cli_pic_plane_size returns the size in bytes, we need the value in pixels from here on */ h->plane_size[i] /= x264_cli_csp_depth_factor( info->csp ); } /* Most common case: frame_header = FRAME */ if( x264_is_regular_file( h->fh ) ) { uint64_t init_pos = ftell( h->fh ); fseek( h->fh, 0, SEEK_END ); uint64_t i_size = ftell( h->fh ); fseek( h->fh, init_pos, SEEK_SET ); info->num_frames = (i_size - h->seq_header_len) / h->frame_size; } *p_handle = h; return 0; }
struct cli_vid_filter_t { /* name of the filter */ const char *name; /* help: a short message on what the filter does and how to use it. * this should only be implemented by filters directly accessible by the user */ void (*help)( int longhelp ); /* init: initializes the filter given the input clip properties and parameter to adjust them as necessary * with the given options provided by the user. * returns 0 on success, nonzero on error. */ int (*init)( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string ); /* get_frame: given the storage for the output frame and desired frame number, generate the frame accordingly. * the image data returned by get_frame should be treated as const and not be altered. * returns 0 on success, nonzero on error. */ int (*get_frame)( hnd_t handle, cli_pic_t *output, int frame ); /* release_frame: frame is done being used and is signaled for cleanup. * returns 0 on succeess, nonzero on error. */ int (*release_frame)( hnd_t handle, cli_pic_t *pic, int frame ); /* free: run filter cleanup procedures. */ void (*free)( hnd_t handle ); /* next registered filter, unused by filters themselves */ cli_vid_filter_t *next; };
source_filter:不作任何處理。
resize_filter:拉伸。
#include video.h /* This filter converts the demuxer API into the filtering API for video frames. * Backseeking is prohibited here as not all demuxers are capable of doing so. */ typedef struct { cli_pic_t pic; hnd_t hin; int cur_frame; } source_hnd_t; cli_vid_filter_t source_filter; static int init( hnd_t *handle, cli_vid_filter_t *filter, video_info_t *info, x264_param_t *param, char *opt_string ) { source_hnd_t *h = calloc( 1, sizeof(source_hnd_t) ); if( !h ) return -1; h->cur_frame = -1; if( cli_input.picture_alloc( &h->pic, info->csp, info->width, info->height ) ) return -1; h->hin = *handle; *handle = h; *filter = source_filter; return 0; } static int get_frame( hnd_t handle, cli_pic_t *output, int frame ) { source_hnd_t *h = handle; /* do not allow requesting of frames from before the current position */ if( frame <= h->cur_frame || cli_input.read_frame( &h->pic, h->hin, frame ) ) return -1; h->cur_frame = frame; *output = h->pic; return 0; } static int release_frame( hnd_t handle, cli_pic_t *pic, int frame ) { source_hnd_t *h = handle; if( cli_input.release_frame && cli_input.release_frame( &h->pic, h->hin ) ) return -1; return 0; } static void free_filter( hnd_t handle ) { source_hnd_t *h = handle; cli_input.picture_clean( &h->pic ); cli_input.close_file( h->hin ); free( h ); } cli_vid_filter_t source_filter = { source, NULL, init, get_frame, release_frame, free_filter, NULL };
cli_vid_filter_t resize_filter = { NAME, help, init, get_frame, release_frame, free_filter, NULL };由於resize_filter涉及到的代碼比較多,在這裡僅看一下它的get_frame()的定義。
static int get_frame( hnd_t handle, cli_pic_t *output, int frame ) { resizer_hnd_t *h = handle; if( h->prev_filter.get_frame( h->prev_hnd, output, frame ) ) return -1; if( h->variable_input && check_resizer( h, output ) ) return -1; h->working = 1; if( h->pre_swap_chroma ) XCHG( uint8_t*, output->img.plane[1], output->img.plane[2] ); if( h->ctx ) { sws_scale( h->ctx, (const uint8_t* const*)output->img.plane, output->img.stride, 0, output->img.height, h->buffer.img.plane, h->buffer.img.stride ); output->img = h->buffer.img; /* copy img data */ } else output->img.csp = h->dst_csp; if( h->post_swap_chroma ) XCHG( uint8_t*, output->img.plane[1], output->img.plane[2] ); return 0; }