diff --git a/common/common.c b/common/common.c index 6c88556..90307f6 100644 --- a/common/common.c +++ b/common/common.c @@ -25,6 +25,7 @@ *****************************************************************************/ #include "common.h" +#include "encoder/set.h" #include #include @@ -66,7 +67,7 @@ void x264_param_default( x264_param_t *param ) 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_level_idc = X264_LEVEL_IDC_AUTO; param->i_slice_max_size = 0; param->i_slice_max_mbs = 0; param->i_slice_count = 0; @@ -428,15 +429,15 @@ int x264_param_apply_profile( x264_param_t *param, const char *profile ) return 0; #if BIT_DEPTH > 8 - if( !strcasecmp( profile, "baseline" ) || !strcasecmp( profile, "main" ) || - !strcasecmp( profile, "high" ) ) + if( x264_get_profile( profile ) == PROFILE_BASELINE || x264_get_profile( profile == PROFILE_MAIN ) || + x264_get_profile( profile ) == PROFILE_HIGH ) { x264_log( NULL, X264_LOG_ERROR, "%s profile doesn't support a bit depth of %d.\n", profile, BIT_DEPTH ); return -1; } #endif - if( !strcasecmp( profile, "baseline" ) ) + if( x264_get_profile( profile ) == PROFILE_BASELINE ) { param->analyse.b_transform_8x8 = 0; param->b_cabac = 0; @@ -454,12 +455,13 @@ int x264_param_apply_profile( x264_param_t *param, const char *profile ) return -1; } } - else if( !strcasecmp( profile, "main" ) ) + else if( x264_get_profile( profile ) == PROFILE_MAIN ) { param->analyse.b_transform_8x8 = 0; param->i_cqm_preset = X264_CQM_FLAT; } - else if( !strcasecmp( profile, "high" ) || !strcasecmp( profile, "high10" ) ) + else if( x264_get_profile( profile ) == PROFILE_HIGH || + x264_get_profile( profile ) == PROFILE_HIGH10 ) { /* Default */ } @@ -477,6 +479,66 @@ int x264_param_apply_profile( x264_param_t *param, const char *profile ) return 0; } +int x264_param_apply_level( x264_param_t *param, const char *profile ) +{ + int level_idc = param->i_level_idc; + if( level_idc == X264_LEVEL_IDC_AUTO ) + return 0; + + int prof = BIT_DEPTH == 8 ? PROFILE_HIGH : PROFILE_HIGH10; + prof = x264_get_profile( profile ); + + const x264_level_t *l = x264_get_level( level_idc ); + if( !l ) + { + x264_log( NULL, X264_LOG_ERROR, "Invalid level_idc: %d\n", level_idc ); + return -1; + } + + char level_name[5]; + snprintf( level_name, sizeof(level_name), "%d.%d", level_idc/10, level_idc%10 ); + if( level_idc == 9 ) + strcpy( level_name, "1b" ); + int cbp_factor = prof==PROFILE_HIGH10 ? 12 : + prof==PROFILE_HIGH ? 5 : 4; + int mb_size = 384*BIT_DEPTH/8; + int mbs = ((param->i_width+15)/16) * ((param->i_height+15)/16); + while( 1 ) + { + int num_reorder_frames = param->i_bframe_pyramid ? 2 : param->i_bframe ? 1 : 0; + int max_dec_frame_buffering = X264_MIN(X264_REF_MAX, X264_MAX4(param->i_frame_reference, 1 + num_reorder_frames, + param->i_bframe_pyramid ? 4 : 1, param->i_dpb_size)); + int dpb = max_dec_frame_buffering * mbs * mb_size; + + if( dpb <= l->dpb ) + break; + + if( max_dec_frame_buffering == 4 && param->i_bframe_pyramid ) + param->i_bframe_pyramid = 0; + else if( max_dec_frame_buffering == 2 && param->i_bframe ) + param->i_bframe = 0; + else if( max_dec_frame_buffering == 1 ) + { + x264_log( NULL, X264_LOG_ERROR, "Impossible DPB size %d required by level %s. Lowest\n", l->dpb, level_name ); + x264_log( NULL, X264_LOG_ERROR, "possible is %d; try a higher level or lower resolution.\n", dpb ); + return -1; + } + else if( param->i_dpb_size > param->i_frame_reference ) + param->i_dpb_size--; + else + param->i_frame_reference--; + } + +#define LIMIT( name, limit, val ) \ + if( (val) > (limit) || (val) <= 0 ) \ + val = limit; + + LIMIT( "VBV bitrate", l->bitrate * cbp_factor / 4, param->rc.i_vbv_max_bitrate ); + LIMIT( "VBV buffer", l->cpb * cbp_factor / 4, param->rc.i_vbv_buffer_size ); + LIMIT( "MV range", l->mv_range, param->analyse.i_mv_range ); + return 0; +} + static int parse_enum( const char *arg, const char * const *names, int *dst ) { for( int i = 0; names[i]; i++ ) diff --git a/encoder/encoder.c b/encoder/encoder.c index 8b14b41..51617d0 100644 --- a/encoder/encoder.c +++ b/encoder/encoder.c @@ -742,7 +742,7 @@ static int x264_validate_parameters( x264_t *h ) { const x264_level_t *l = x264_levels; - if( h->param.i_level_idc < 0 ) + if( h->param.i_level_idc == X264_LEVEL_IDC_AUTO ) { int maxrate_bak = h->param.rc.i_vbv_max_bitrate; if( h->param.rc.i_rc_method == X264_RC_ABR && h->param.rc.i_vbv_buffer_size <= 0 ) diff --git a/encoder/set.c b/encoder/set.c index 6cf0c6a..0a8a110 100644 --- a/encoder/set.c +++ b/encoder/set.c @@ -115,6 +115,18 @@ void x264_sps_init( x264_sps_t *sps, int i_id, x264_param_t *param ) else sps->i_profile_idc = PROFILE_BASELINE; + /* Hack: if the user specifies a High-compatible VBV, but not a Main-compatible one, + * switch to High profile. Prevents spurious errors on the first pass. */ + const x264_level_t *l = x264_get_level( param->i_level_idc ); + if( l && sps->i_profile_idc <= PROFILE_MAIN && + l->bitrate * 5/4 >= param->rc.i_vbv_max_bitrate && + l->cpb * 5/4 >= param->rc.i_vbv_buffer_size && + (l->bitrate < param->rc.i_vbv_max_bitrate || l->cpb < param->rc.i_vbv_buffer_size) ) + { + sps->i_profile_idc = PROFILE_HIGH; + } + + sps->b_constraint_set0 = sps->i_profile_idc == PROFILE_BASELINE; /* x264 doesn't support the features that are in Baseline and not in Main, * namely arbitrary_slice_order and slice_groups. */ @@ -670,6 +682,16 @@ const x264_level_t x264_levels[] = { 0 } }; +const x264_level_t *x264_get_level( int level_idc ) +{ + const x264_level_t *l = x264_levels; + while( l->level_idc != 0 && l->level_idc != level_idc ) + l++; + if( l->level_idc ) + return l; + return NULL; +} + #define ERROR(...)\ {\ if( verbose )\ @@ -681,7 +703,8 @@ int x264_validate_levels( x264_t *h, int verbose ) { int ret = 0; int mbs = h->sps->i_mb_width * h->sps->i_mb_height; - int dpb = mbs * 384 * h->sps->vui.i_max_dec_frame_buffering; + int mb_size = 384*BIT_DEPTH/8; + int dpb = mbs * mb_size * h->sps->vui.i_max_dec_frame_buffering; int cbp_factor = h->sps->i_profile_idc==PROFILE_HIGH10 ? 12 : h->sps->i_profile_idc==PROFILE_HIGH ? 5 : 4; @@ -696,7 +719,7 @@ int x264_validate_levels( x264_t *h, int verbose ) h->sps->i_mb_width, h->sps->i_mb_height, l->frame_size ); if( dpb > l->dpb ) ERROR( "DPB size (%d frames, %d bytes) > level limit (%d frames, %d bytes)\n", - h->sps->vui.i_max_dec_frame_buffering, dpb, (int)(l->dpb / (384*mbs)), l->dpb ); + h->sps->vui.i_max_dec_frame_buffering, dpb, (int)(l->dpb / (mb_size*mbs)), l->dpb ); #define CHECK( name, limit, val ) \ if( (val) > (limit) ) \ diff --git a/encoder/set.h b/encoder/set.h index 6d70e96..d034a69 100644 --- a/encoder/set.h +++ b/encoder/set.h @@ -33,6 +33,7 @@ void x264_pps_init( x264_pps_t *pps, int i_id, x264_param_t *param, x264_sps_t * void x264_pps_write( bs_t *s, x264_pps_t *pps ); void x264_sei_recovery_point_write( x264_t *h, bs_t *s, int recovery_frame_cnt ); int x264_sei_version_write( x264_t *h, bs_t *s ); +const x264_level_t *x264_get_level( int level_idc ); int x264_validate_levels( x264_t *h, int verbose ); void x264_sei_buffering_period_write( x264_t *h, bs_t *s ); void x264_sei_pic_timing_write( x264_t *h, bs_t *s ); diff --git a/x264.c b/x264.c index bba17b8..72f2e29 100644 --- a/x264.c +++ b/x264.c @@ -770,7 +770,8 @@ enum OPT_INPUT_RES, OPT_INPUT_CSP, OPT_INPUT_DEPTH, - OPT_DTS_COMPRESSION + OPT_DTS_COMPRESSION, + OPT_DEVICE } OptionsOPT; static char short_options[] = "8A:B:b:f:hI:i:m:o:p:q:r:t:Vvw"; @@ -781,6 +782,7 @@ static struct option long_options[] = { "fullhelp", no_argument, NULL, OPT_FULLHELP }, { "version", no_argument, NULL, 'V' }, { "profile", required_argument, NULL, OPT_PROFILE }, + { "device", required_argument, NULL, OPT_DEVICE }, { "preset", required_argument, NULL, OPT_PRESET }, { "tune", required_argument, NULL, OPT_TUNE }, { "slow-firstpass", no_argument, NULL, OPT_SLOWFIRSTPASS }, @@ -928,6 +930,116 @@ static struct option long_options[] = {0, 0, 0, 0} }; +const x264_device_t x264_device_psp = {480, 272, 8, "main", {{0, 0}}}; +const x264_device_t x264_device_ipod = {320, 240, 8, "baseline", {{0, 0}}}; +const x264_device_t x264_device_ps3 = {1920, 1080, 12, "high", {{0, 0}}}; +const x264_device_t x264_device_iphone = {480, 320, 8, "baseline", {{0, 0}}}; +const x264_device_t x264_device_iphone4 = {960, 640, 8, "main", {{0, 0}}}; +const x264_device_t x264_device_xbox360 = {1920, 1080, 12, "high", {{0, 0}}}; +const x264_device_t x264_device_ipad = {1024, 768, 9, "main", {{0, 0}}}; +const x264_device_t x264_device_wdtv = {1920, 1080, 12, "high", {{0, 0}}}; + +const x264_device_t *x264_get_device( const char *device ) +{ + if( !strcasecmp( device, "psp" ) ) return &x264_device_psp; + else if( !strcasecmp( device, "ipod" ) ) return &x264_device_ipod; + else if( !strcasecmp( device, "ps3" ) ) return &x264_device_ps3; + else if( !strcasecmp( device, "iphone" ) ) return &x264_device_iphone; + else if( !strcasecmp( device, "iphone4" ) ) return &x264_device_iphone4; + else if( !strcasecmp( device, "xbox360" ) ) return &x264_device_xbox360; + else if( !strcasecmp( device, "ipad" ) ) return &x264_device_ipad; + else if( !strcasecmp( device, "wdtv" ) ) return &x264_device_wdtv; + else + { + x264_log( NULL, X264_LOG_ERROR, "invalid device: %s\n", device ); + return NULL; + } +} + +const int x264_get_profile( const char *p_profile ) +{ + if( !strcmp( p_profile, "baseline" ) ) return PROFILE_BASELINE; + else if( !strcmp( p_profile, "main" ) ) return PROFILE_MAIN; + else if( !strcmp( p_profile, "high" ) ) return PROFILE_HIGH; + else if( !strcmp( p_profile, "high10" ) ) return PROFILE_HIGH10; + else x264_log( NULL, X264_LOG_ERROR, "invalid profile: %s\n", p_profile ); + return 0; +} + +x264_param_t *x264_param_apply_device( x264_param_t *param, const char *device) +{ + const x264_device_t *_device; + int height = param->i_height; + int width = param->i_width; + if( !( _device = x264_get_device( device ) ) ) return NULL; + if( _device->i_level_idc < param->i_level_idc ) + { + x264_level_t level = x264_levels[ _device->i_level_idc ]; + double ratio = ( double )param->i_width / ( double )param->i_height; + double max_area = level.frame_size * 256; + width = sqrt( max_area * ratio ); + height = width / ratio; + } + if ( width % 2 ) width--; + if ( height % 2 ) height--; + param->i_width = width; + param->i_height = height; + param->i_level_idc = _device->i_level_idc; + for( int i = 0;; i++ ) + { + if( _device->confstring[i][0] == 0 ) break; + x264_param_parse( param, _device->confstring[i][0], _device->confstring[i][1] ); + } + x264_param_apply_profile( param, _device->p_profile ); + x264_param_apply_level( param, _device->p_profile ); + return param; +} + +int x264_parse_device( x264_param_t *param, const char *device ) +{ + if( !device ) return -1; + char *tmp = x264_malloc( strlen( device ) + 1 ); + tmp = strcpy( tmp, device ); + char *s = strtok( tmp, ",./-+" ); + while( s ) + { + if( !strcasecmp( s, "psp" ) ) + { + if( !x264_param_apply_device( param, "psp" ) ) return -1; + } + else if( !strcasecmp( s, "ipod" ) ) + { + if( !x264_param_apply_device( param, "ipod" ) ) return -1; + } + else if( !strcasecmp( s, "ps3" ) ) + { + if( !x264_param_apply_device( param, "ps3" ) ) return -1; + } + else if( !strcasecmp( s, "iphone" ) ) + { + if( !x264_param_apply_device( param, "iphone" ) ) return -1; + } + else if ( !strcasecmp( s, "iphone4" ) ) + { + if( !x264_param_apply_device( param, "iphone4" ) ) return -1; + } + else if( !strcasecmp( s, "xbox360" ) ) + { + if( !x264_param_apply_device( param, "xbox360" ) ) return -1; + } + else if( !strcasecmp( s, "ipad" ) ) + { + if( !x264_param_apply_device( param, "ipad" ) ) return -1;; + } + else if( !strcasecmp( s, "wdtv" ) ) + { + if( !x264_param_apply_device( param, "wdtv" ) ) return -1;; + } + s = strtok( NULL, ",./-+" ); + } + x264_free( tmp ); + return 0; +} static int select_output( const char *muxer, char *filename, x264_param_t *param ) { const char *ext = get_filename_extension( filename ); @@ -975,6 +1087,7 @@ static int select_input( const char *demuxer, char *used_demuxer, char *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 ) { @@ -1127,6 +1240,7 @@ static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ) char *tcfile_name = NULL; x264_param_t defaults; char *profile = NULL; + char *devices = NULL; char *vid_filters = NULL; int b_thread_input = 0; int b_turbo = 1; @@ -1296,6 +1410,9 @@ static int parse( int argc, char **argv, x264_param_t *param, cli_opt_t *opt ) case OPT_DTS_COMPRESSION: output_opt.use_dts_compress = 1; break; + case OPT_DEVICE: + devices = optarg; + break; default: generic_option: { @@ -1458,23 +1575,10 @@ generic_option: param->b_tff = !!info.tff; } - /* 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 * 384 * param->i_frame_reference > x264_levels[i].dpb && - param->i_frame_reference > 1 ) - { - param->i_frame_reference--; - } - break; - } - } - + if( x264_param_apply_level( param, profile ) < 0 ) + return -1; + + x264_parse_device( param, devices ); return 0; } @@ -1653,7 +1757,6 @@ static int encode( x264_param_t *param, cli_opt_t *opt ) break; x264_picture_init( &pic ); convert_cli_to_lib_pic( &pic, &cli_pic ); - if( !param->b_vfr_input ) pic.i_pts = i_frame; diff --git a/x264.h b/x264.h index ce79d40..034d077 100644 --- a/x264.h +++ b/x264.h @@ -161,6 +161,7 @@ typedef struct #define X264_OPEN_GOP_NONE 0 #define X264_OPEN_GOP_NORMAL 1 #define X264_OPEN_GOP_BLURAY 2 +#define X264_LEVEL_IDC_AUTO (-1) static const char * const x264_direct_pred_names[] = { "none", "spatial", "temporal", "auto", 0 }; static const char * const x264_motion_est_names[] = { "dia", "hex", "umh", "esa", "tesa", 0 }; @@ -223,6 +224,15 @@ typedef struct struct x264_param_t *param; } x264_zone_t; +typedef struct +{ + int i_screen_width; + int i_screen_height; + int i_level_idc; + char *p_profile; + char *confstring[5][3]; +} x264_device_t; + typedef struct x264_param_t { /* CPU flags */ @@ -524,6 +534,7 @@ int x264_param_parse( x264_param_t *, const char *name, const char *value ); * 2) Custom user options (via param_parse or directly assigned variables) * 3) x264_param_apply_fastfirstpass * 4) x264_param_apply_profile + * 5) x264_param_apply_level * * Additionally, x264CLI does not apply step 3 if the preset chosen is "placebo" * or --slow-firstpass is set. */ @@ -576,6 +587,51 @@ static const char * const x264_profile_names[] = { "baseline", "main", "high", " * 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_level: + * Applies the restrictions of the level set in the passed x264_param_t to + * the passed x264_param_t, using the passed profile. + * Uses the level_idc values from x264_levels[]. + * + * Does nothing (but is considered success) if the level is set to + * X264_LEVEL_IDC_AUTO (default). + * + * x264_param_apply_level does not check or modify parameters related + * to properties of the source video that may violate levels, e.g. + * resolution, framerate, interlacing. x264_param_apply_level is not + * intended as a compatibility check; this is done inside x264 at the + * start of encoding. + * + * i_width and i_height must be initialized for x264_param_apply_level + * to work properly. + * + * returns 0 on success, negative on failure (e.g. invalid level). */ +int x264_param_apply_level( x264_param_t *, const char *profile ); + +/* x264_get_device: + * Gets the x264_device_t that matches the passed device name. Returns a NULL + * pointer and logs an error if one does not exist. +*/ +const x264_device_t *x264_get_device( const char *device ); + +/* x264_get_profile: + * Returns the #defined PROFILE_* relevant to the profile name in p_profile. + * If one does not exist, it returns 0 and logs an error. +*/ +const int x264_get_profile( const char *p_profile ); + +/* x264_param_apply_devices: + * Applies the device constraints listed in the passed x264_device_t to the + * passed x264_param_t. Returns the passed x264_param_t if successful or a + * NULL pointer if the device could not be found. +*/ +x264_param_t *x264_param_apply_devices( x264_param_t *param, const char *device ); + +/* x264_parse_device: + * Parses the comma-seperated devices string supplied with the commandline + * arguments. Returns 0 if successful. +*/ +int x264_parse_device( x264_param_t *param, const char *device ); + /**************************************************************************** * Picture structures and functions ****************************************************************************/