接下來的幾篇博客中,具體學習下X264的實現過程。算法
源代碼的分析參考了雷神的博客,感謝雷神!博客連接:http://www.javashuo.com/article/p-uyjvuood-cg.html。windows
1.概述數組
X264編碼流程圖以下所示:app
首先咱們要建立編碼器,初始化編碼器參數,而後讀入YUV數據進行VCL視頻編碼,編碼後的數據進行NAL打包,循環全部的視頻幀,最後釋放內存,關閉編碼器。less
2.應用工程ide
(1)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 if( parse( argc, argv, ¶m, &opt ) < 0 ) //解析命令行輸入 ret = -1; #ifdef _WIN32 SetConsoleTitleW( org_console_title ); #endif signal( SIGINT, sigint_handler ); if( !ret ) ret = encode( ¶m, &opt ); //編碼 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; }
主函數的開始,首先調用parse()解析輸入的命令行參數,再調用encode()函數進行編碼。oop
(2)parse()函數用於解析命令行輸入的參數。post
static int parse(int argc, char **argv, x264_param_t *param, cli_opt_t *opt)
argc:參數個數;argv:指向參數的指針; param:參數結構體; opt:操做類型;學習
parse()函數:
//解析命令行輸入 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; 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'\n", 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'\n", 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'\n", 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'\n", 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'\n", 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\n", 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.\n", 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'\n", 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'\n", 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)\n", 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.\n" ) FAIL_IF_ERROR( timecode_input.open_file( tcfile_name, &opt->hin, &info, &input_opt ), "timecode input failed\n" ) cli_input = timecode_input; } else FAIL_IF_ERROR( !info.vfr && input_opt.timebase, "--timebase is incompatible with cfr input\n" ) /* 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\n" ); 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\n", 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\n" ) 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.\n" " If you want otherwise, use --no-interlaced or --%cff\n", 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\n" ); #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()逐個解析輸入的參數,並作相應的處理;
(4)調用select_input()解析輸出文件格式;
(5)調用select_output()解析輸入文件格式;
x264_param_default():設置x264_param_t結構體的默認值(X264的API)。
void x264_param_default(x264_param_t *param) { memset( param, 0, sizeof( x264_param_t ) ); //開闢內存空間 param->cpu = x264_cpu_detect(); //CPU自動檢測 param->i_threads = X264_THREADS_AUTO; //並行編碼線程爲0 param->b_deterministic = 1; //容許非肯定性時線程優化 param->i_sync_lookahead = X264_SYNC_LOOKAHEAD_AUTO; //自動選擇線程超前緩衝大小-1 //視頻屬性 param->i_csp = X264_CSP_I420; //設置輸入的視頻採樣的格式0x0001yuv 4:2:0 planar 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(不設置),可選:show(觀看)crop(去除) param->vui.i_vidformat = 5; //undef視頻格式 param->vui.b_fullrange = 0; //off param->vui.i_colorprim = 2; //undef原始色度格式 param->vui.i_transfer = 2; //undef 轉換方式 param->vui.i_colmatrix = 2; //undef 色度矩陣設置 param->vui.i_chroma_loc= 0; //left center 色度樣本指定,範圍0~5,默認0 param->i_fps_num = 25; //幀率 param->i_fps_den = 1; //用兩個整型的數的比值,來表示幀率 param->i_level_idc = -1; //level值的設置 param->i_slice_max_size = 0; //每片字節的最大數,包括預計的NAL開銷. param->i_slice_max_mbs = 0; //每片宏塊的最大數,重寫 i_slice_count param->i_slice_count = 0; //每幀的像條數目: 設置矩形像條. //編碼參數 param->i_frame_reference = 3; //參考幀的最大幀數。 param->i_keyint_max = 250; //在此間隔設置IDR關鍵幀 param->i_keyint_min = 25; //場景切換少於次值編碼位I, 而不是 IDR. param->i_bframe = 3; //兩個參考幀之間的B幀數目 param->i_scenecut_threshold = 40; //如何積極地插入額外的I幀 param->i_bframe_adaptive = X264_B_ADAPT_FAST; //自適應B幀斷定1 param->i_bframe_bias = 0; //控制插入B幀斷定,範圍-100~+100,越高越容易插入B幀 param->b_bframe_pyramid = 0; //容許部分B爲參考幀 param->b_deblocking_filter = 1; //去塊效應相關 param->i_deblocking_filter_alphac0 = 0; //[-6, 6] -6 亮度濾波器, 6 強 param->i_deblocking_filter_beta = 0; //[-6, 6] 同上 param->b_cabac = 1; //cabac的開關 param->i_cabac_init_idc = 0; //碼率控制 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; //平均碼率模式下,最大瞬時碼率,默認0(與-B設置相同) param->rc.i_vbv_buffer_size = 0; //碼率控制緩衝區的大小,單位kbit,默認0 param->rc.f_vbv_buffer_init = 0.9; param->rc.i_qp_constant = 23; //最小qp值 param->rc.f_rf_constant = 23; param->rc.i_qp_min = 10; //容許的最小量化值 param->rc.i_qp_max = 51; //容許的最大量化值 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; //日誌 param->pf_log = x264_log_default; param->p_log_private = NULL; param->i_log_level = X264_LOG_INFO; //默認爲「Info」 //分析 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; //運動估計算法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; //容許每一個宏塊的分區在P幀有它本身的參考號 param->analyse.b_chroma_me = 1; //亞像素色度運動估計和P幀的模式選擇 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; //快速P幀跳過檢測 param->analyse.b_weighted_bipred = 1; //爲b幀隱式加權 param->analyse.b_dct_decimate = 1; //在P-frames轉換參數域 param->analyse.b_transform_8x8 = 1; //幀間分區 param->analyse.i_trellis = 1; //Trellis量化,對每一個8x8的塊尋找合適的量化值,須要CABAC,默認0 param->analyse.i_luma_deadzone[0] = 21; //幀間亮度量化中使用的無效區大小 param->analyse.i_luma_deadzone[1] = 11; //幀內亮度量化中使用的無效區大小 param->analyse.b_psnr = 0; //是否顯示PSNR param->analyse.b_ssim = 0; //是否顯示SSIM //量化 param->i_cqm_preset = X264_CQM_FLAT; //自定義量化矩陣(CQM),初始化量化模式爲flat 0 memset( param->cqm_4iy, 16, 16 ); memset( param->cqm_4ic, 16, 16 ); memset( param->cqm_4py, 16, 16 ); memset( param->cqm_4pc, 16, 16 ); memset( param->cqm_8iy, 16, 64 ); memset( param->cqm_8py, 16, 64 ); //開闢空間 param->b_repeat_headers = 1; //在每一個關鍵幀前放置SPS/PPS param->b_aud = 0; //生成訪問單元分隔符 }
x264_param_default_preset():設置x264的preset和tune(libx264的API)。
//設置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; }
在代碼裏,調用了函數x264_param_apply_preset()和x264_param_apply_tune()分別設置preset和tune。
x264_param_apply_preset():
//設置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; //參考幀的最大幀數設爲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; //參考幀最大幀數爲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; //參考幀最大幀數設爲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; //參考幀的組大幀數設爲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; //參考幀的最大幀數設爲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; //兩個參考幀之間B幀爲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; //運動估計範圍設爲24 param->i_frame_reference = 16; //參考幀的最大幀數設爲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; //兩個參考幀之間B幀爲16 param->rc.i_lookahead = 60; } else { x264_log( NULL, X264_LOG_ERROR, "invalid preset '%s'\n", preset ); return -1; } return 0; }
經過調節preset參數能夠調整編碼速度和質量之間的平衡。它的具體參數有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、placebo。這些參數的編碼速度從快到慢,默認值是medium。
x264_param_apply_tune():
//設置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'\n", 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\n", s ); } s = strtok( NULL, ",./-+" ); } x264_free( tmp ); return 0; }
tune的參數值有:
film:電影、真人類型;
animation:動畫;
grain:須要保留大量的顆粒時使用;
stillimage:靜態圖像編碼時使用;
psnr:爲提升峯值信噪比(psnr)作優化的參數;
ssim:爲提升ssim作優化的參數(ssim:衡量兩圖圖像類似度的指標,值越大越好,最大爲1);
fastdecode:能夠快速解碼的參數;
zerolatency:零延遲,能夠減小音視頻的不一樣步;
x264_param_apply_profile():設置x264的profile(x264的API)。
//設置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\n", 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\n", 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\n", 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\n", 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\n", 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\n" ); return -1; } if( param->b_fake_interlaced ) { x264_log( NULL, X264_LOG_ERROR, "baseline profile doesn't support fake interlacing\n" ); 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; }
其函數內部調用profile_string_to_int():
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; }
(3)最後根據設定的編碼器參數,對輸入的視頻文件進行H264視頻算法編碼,編碼後的H264碼流寫入文件。
encode()函數:編碼YUV爲H264,源代碼以下:
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; int i_frame = 0; //編碼幀數目統計 int i_frame_output = 0; int64_t i_end, i_previous = 0, i_start = 0; //編碼時間統計 int64_t i_file = 0; //當前NAL打包的長度 int i_frame_size; //編碼後的碼流長度 int64_t last_dts = 0; int64_t prev_dts = 0; int64_t first_dts = 0; 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; //調試信息等級 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\n" ) param->i_timebase_den = param->i_fps_num * pulldown->fps_factor; } h = x264_encoder_open( param ); //打開編碼器 FAIL_IF_ERROR2( !h, "x264_encoder_open failed\n" ); x264_encoder_parameters( h, param ); //得到參數 FAIL_IF_ERROR2( cli_output.set_param( opt->hout, param ), "can't set outfile param\n" ); i_start = x264_mdate(); //計時,讀取當前系統時間 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"\n",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 ) { x264_nal_t *headers; int i_nal; FAIL_IF_ERROR2( x264_encoder_headers( h, &headers, &i_nal ) < 0, "x264_encoder_headers failed\n" ) FAIL_IF_ERROR2( (i_file = cli_output.write_headers( opt->hout, headers )) < 0, "error writing headers to output file\n" ); } if( opt->tcfile_out ) fprintf( opt->tcfile_out, "# timecode format v2\n" ); for( ; !b_ctrl_c && (i_frame < param->i_frame_total || !param->i_frame_total); i_frame++ ) //循環編碼全部幀 { if( filter.get_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) ) //獲取1幀YUV數據,存於cli_pic break; x264_picture_init( &pic ); //初始化x264_picture_t結構體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")\n",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\n" ); 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\n", 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; i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts ); //編碼pic中存儲的1幀YUV數據 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( filter.release_frame( opt->hin, &cli_pic, i_frame + opt->i_seek ) ) //釋放處理完的YUV數據 break; 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 ); } while( !b_ctrl_c && x264_encoder_delayed_frames( h ) ) //x264_encoder_delayed_frames()返回剩餘的幀的個數 { prev_dts = last_dts; i_frame_size = encode_frame( h, opt->hout, NULL, &last_dts ); //編碼 if( i_frame_size < 0 ) { b_ctrl_c = 1; 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\n", pts_warning_cnt-MAX_PTS_WARNING ); 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(); //獲取系統時間,計時 if( opt->b_progress ) fprintf( stderr, " \r" ); if( h ) x264_encoder_close( h ); //關閉編碼器 fprintf( stderr, "\n" ); if( b_ctrl_c ) fprintf( stderr, "aborted at input frame %d, output frame %d\n", 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\n", i_frame_output, fps,(double) i_file * 8 / ( 1000 * duration ) ); } return retval; }
encode()函數的基本流程:
調用x264_encoder_open()函數打開編碼器;
調用x264_encoder_parameters()得到當前參數集x264_param_t;
調用x264_encoder_headers()在碼流前面加入SPS/PPS/SEI信息;
調用encode_frame()進入循環一幀一幀的編碼;
調用printf_status()輸出編碼後的統計信息;
調用x264_encode_close()關閉編碼器;
encode_frame()函數:編碼一幀數據,內部調用x264_encoder_encode()函數。代碼:
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; //NAL打包指針 int i_nal; //NAL的數目 int i_frame_size = 0; i_frame_size = x264_encoder_encode( h, &nal, &i_nal, pic, &pic_out ); //編碼x264_picture_t爲x264_nal_t FAIL_IF_ERROR( i_frame_size < 0, "x264_encoder_encode failed\n" ); //編碼失敗,輸出當前信息 if( i_frame_size ) { 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; }
printf_status()函數:輸出一幀數據編碼後的統計信息。代碼:
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 ); fprintf( stderr, "%s \r", buf+5 ); x264_cli_set_console_title( buf ); fflush( stderr ); // needed in windows return i_time; }
運行編碼函數,編碼成功時,咱們能夠看到這個函數打印的一些信息。