x264代码剖析(三):主函数main()、解析函数parse()与编码函数encode()
x264代码剖析(三):主函数main()、解析函数parse()与编码函数encode()
x264的入口函数为main()。main()函数首先调用parse()解析输入的參数,然后调用encode()编码YUV数据。parse()首先调用x264_param_default()为保存參数的x264_param_t结构体赋默认值;然后在一个大循环中通过getopt_long()解析通过命令行传递来的存储在argv[]中的參数。并作对应的设置工作;最后调用select_input()和select_output()完毕输入文件格式(yuv,y4m等)和输出文件格式(裸流,mp4,mkv。FLV等)的设置。encode()首先调用x264_encoder_open()打开编码器。接着在一个循环中重复调用encode_frame()一帧一帧地进行编码;最后在编码完毕后调用x264_encoder_close()关闭编码器。encode_frame()则调用x264_encoder_encode()将存储YUV数据的x264_picture_t编码为存储H.264数据的x264_nal_t。
详细函数关系例如以下图所看到的:
以下对该函数关系图中的主函数main()、解析函数parse()与编码函数encode()做具体的分析。
1、主函数main()
主函数主要调用了两个函数:parse()和encode()。main()首先调用parse()解析输入的命令行參数,然后调用encode()进行编码。相应代码例如以下:
/******************************************************************/
/******************************************************************/
/*
======Analysed by RuiDong Fang
======Csdn Blog:http://blog.csdn.net/frd2009041510
======Date:2016.03.07
*/
/******************************************************************/
/******************************************************************/ /************====== 主函数 ======************/
/*
功能:主要调用了两个函数:parse()和encode()。 main()首先调用parse()解析输入的命令行參数。然后调用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\n" ) #ifdef _WIN32
FAIL_IF_ERROR( !get_argv_utf8( &argc, &argv ), "unable to convert command line to UTF-8\n" ) GetConsoleTitleW( org_console_title, CONSOLE_TITLE_SIZE );
_setmode( _fileno( stdin ), _O_BINARY );
_setmode( _fileno( stdout ), _O_BINARY );
_setmode( _fileno( stderr ), _O_BINARY );
#endif /* Parse command line */
if( parse( argc, argv, ¶m, &opt ) < 0 ) ///////////////////解析命令行输入,调用parse()
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 ); ///////////////////编码。调用encode() /* 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;
}
2、解析函数parse()
解析函数parse()解析输入的命令行參数,存储于argv[]中。相应的代码例如以下:
/************====== 解析函数 ======************/
/*
功能:parse()解析输入的命令行參数,存储于argv[]中
*/
static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt )
{
char *input_filename = NULL;
const char *demuxer = demuxer_names[0];
char *output_filename = NULL;
const char *muxer = muxer_names[0];
char *tcfile_name = NULL;
x264_param_t defaults; //默认值设为x264_param_t结构体
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()是一个x264的API,调用x264_param_default()为保存參数的x264_param_t结构体赋默认值
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;; )
{
//通过getopt_long()解析通过命令行传递来的存储在argv[]中的參数。并作对应的设置工作
int c = getopt_long( argc, argv, short_options, long_options, NULL ); ///////////////////getopt_long()
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; //x264_param_default_preset()是一个libx264的API,用于设置x264的preset和tune。
if( x264_param_default_preset( param, preset, tune ) < 0 ) //设置preset、tune
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 )
{
//"-h"帮助菜单
case 'h':
help( &defaults, 0 );
exit(0);
case OPT_LONGHELP:
help( &defaults, 1 );
exit(0);
case OPT_FULLHELP:
help( &defaults, 2 );
exit(0); //"-V"打印版本号信息
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; //"-o"输出文件路径
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; //"-v"
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; //"-r"
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;
}
} //解析以字符串方式输入的參数
//即选项名称和选项值都是字符串
//实质就是通过strcmp()方法
b_error |= x264_param_parse( param, long_options[long_options_index].name, optarg ); ///////////////////x264_param_parse()
}
} 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. */
//x264_param_apply_profile()是一个x264的API,该函数用于设置x264的profile.
if( x264_param_apply_profile( param, profile ) < 0 ) ///////////////////设置profile
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 ) ) ///////////////////select_output()
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 ) ) ///////////////////select_input()
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;
}
3、编码函数encode()
编码函数encode()主要用于设置正式编码前的一些參数,而且调用了encode_frame()函数,而encode_frame()又调用了x264_encoder_encode()函数进行正式编码。相应的代码例如以下:
/************====== encode函数 ======************/
/*
功能:编码(在内部有一个循环用于一帧一帧编码)
*/
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\n" )
param->i_timebase_den = param->i_fps_num * pulldown->fps_factor;
} h = x264_encoder_open( param ); /////////////////////////x264_encoder_open():打开编码器
FAIL_IF_ERROR2( !h, "x264_encoder_open failed\n" ); //获得參数
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\n" ); //计时開始
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"\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 )
{
// 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\n" ) /////////////////////////x264_encoder_headers():输出SPS。PPS,SEI等信息 //把文件头写入输出文件
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" ); /* 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")\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; //编码pic中存储的1帧YUV数据
i_frame_size = encode_frame( h, opt->hout, &pic, &last_dts ); /////////////////////////encode_frame()
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 ); /////////////////////////encode_frame()
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\n", 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, " \r" );
if( h )
x264_encoder_close( h ); /////////////////////////x264_encoder_close():关闭编码器
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_frame()内部调用x264_encoder_encode()完毕编码工作,
调用输出格式相应cli_output_t结构体的write_frame()完毕了输出工作。
*/
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 ); //////////////////////////x264_encoder_encode() FAIL_IF_ERROR( i_frame_size < 0, "x264_encoder_encode failed\n" ); 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;
}
4、总结
main()是x264控制台程序的入口函数,能够看出main()的定义非常easy,它主要调用了两个函数:parse()和encode()。main()首先调用parse()解析输入的命令行參数,然后调用encode()进行编码。
parse()用于解析命令行输入的參数(存储于argv[]中)。parse()的流程大致为:
(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…)
encode()编码YUV为H.264码流。主要流程为:
(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代码剖析(三):主函数main()、解析函数parse()与编码函数encode()的更多相关文章
- x264代码剖析(十三):核心算法之帧间预測函数x264_mb_analyse_inter_*()
x264代码剖析(十三):核心算法之帧间预測函数x264_mb_analyse_inter_*() 帧间预測是指利用视频时间域相关性,使用临近已编码图像像素预測当前图像的像素,以达到有效去除视频时域冗 ...
- x264代码剖析(八):encode()函数之x264_encoder_close()函数
x264代码剖析(八):encode()函数之x264_encoder_close()函数 encode()函数是x264的主干函数.主要包含x264_encoder_open()函数.x264_en ...
- x264代码剖析(十五):核心算法之宏块编码中的变换编码
x264代码剖析(十五):核心算法之宏块编码中的变换编码 为了进一步节省图像的传输码率.须要对图像进行压缩,通常採用变换编码及量化来消除图像中的相关性以降低图像编码的动态范围.本文主要介绍变换编码的相 ...
- HDFS集中式的缓存管理原理与代码剖析--转载
原文地址:http://yanbohappy.sinaapp.com/?p=468 Hadoop 2.3.0已经发布了,其中最大的亮点就是集中式的缓存管理(HDFS centralized cache ...
- HDFS集中式的缓存管理原理与代码剖析
转载自:http://www.infoq.com/cn/articles/hdfs-centralized-cache/ HDFS集中式的缓存管理原理与代码剖析 Hadoop 2.3.0已经发布了,其 ...
- (转)x264源码分析(1):main、parse、encode、x264_encoder_open函数代码分析
转自:http://nkwavelet.blog.163.com/blog/static/2277560382013103010312144/ x264版本: x264-snapshot-2014 ...
- 黑马程序员——JAVA基础之主函数main和静态static,静态代码块
------- android培训.java培训.期待与您交流! ---------- 主函数:是一个特殊的函数.作为程序的入口,可以被jvm调用. 主函数的定义: public:代表着该函数访问权限 ...
- 构造代码块、this关键字、静态变量、静态代码块、主函数
一.构造代码块: 作用:给对象进行初始化. 特点:对象一经运行就执行(与变量声明时赋初值同级别,此处注意 非法前向引用) 优先于构造函数的执行. 与构造函数的区别: 构造代码块是给所有对象统一初始化. ...
- .NET/C# 中你可以在代码中写多个 Main 函数,然后按需要随时切换
.NET/C# 程序从 Main 函数开始执行,基本上各种书籍资料都是这么写的.不过,我们可以写多个 Main 函数,然后在项目文件中设置应该选择哪一个 Main 函数. 你可能会觉得这样没有什么用, ...
随机推荐
- Sql Server新手学习入门
Sql Server新手学习入门 http://www.tudou.com/home/_117459337
- 利用反射实现Servlet公共类的抽取
一次请求的执行过程: 请求:发送请求地址-->到达web.xml中,找到地址对应的servlet类-->通过反射调用该类的构造函数,创建该servlet类的对象-->通过当前对象调用 ...
- wordcontent小结
gitee地址: https://gitee.com/yzpdegit/test 问题描述: 计算一个文件中所包含的单词数,字符个数,行数 需求分析: WordCount的需求可以概括为:对程序设计语 ...
- 【Linux环境编程】获取网卡的实时网速
在windows以下.我们能够看到360或者是qq安全卫士的"安全球".上面显示实时的网速情况.那么在linux里面怎样获取网卡的实时网速?事实上原理非常easy,读取须要获取网速 ...
- VTK的安装配置-使用VS2010
1.CMake的安装 CMake安装是用来对VTK编译前的配置工作.此博客中使用的是CMake2.8.CMake的下载可到https://cmake.org/站点上进行下载. 2.VTK源代码 VTK ...
- Intel TBB in OpenCASCADE
Intel TBB in OpenCASCADE eryar@163.com OpenCASCADE使用了一个开源的第三方库Intel TBB,这个并行计算库主要用于网格化.布尔操作等复杂算法,可以明 ...
- Android自己定义动态布局 — 多图片上传
Android自己定义动态布局 - 多图片上传 本文介绍Android中动态布局加入图片,多图片上传. 项目中效果图: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5 ...
- 算法题:给你一个自然数N,求[6, N]之内的全部素数中, 两两之和为偶数的那些偶数。
/* 算法题:给你一个自然数N,求[6, N]之内的全部素数中. 两两之和为偶数的那些偶数. */ #include <iostream> using namespace std; voi ...
- FocusChange-焦点变化监听事件
想要监听一个控件的焦点变化情况,发现了一个 view.setOnFocusChangeListener(new OnFocusChangeListener() { ...... } 现在写一个小dem ...
- POJ 2394 Dijkstra
题意: 思路: 裸的Dijkstra 爆敲一发模板 //By SiriusRen #include <queue> #include <cstdio> #include < ...