diff options
author | Rodeo <[email protected]> | 2012-03-24 19:26:12 +0000 |
---|---|---|
committer | Rodeo <[email protected]> | 2012-03-24 19:26:12 +0000 |
commit | be4abed142e5d2b9fe2b8801d129de90450fbc2e (patch) | |
tree | 6b3522c679a3de4350d56e01466017e258c9219e | |
parent | c6401fab7c5f1daf36cdb3f99c640477c19bdc01 (diff) |
Add hb_apply_h264_level(). Sets and ensures compliance with the specified H.264 level. Does not modify framerate and resolution but prints warnings when they are incompatible with the requested level.
Exposed to CLI users only via the --h264-level option. GUI support may come later, once we decide how to handle x264 presets/tunes/profiles.
git-svn-id: svn://svn.handbrake.fr/HandBrake/trunk@4534 b64f7644-9d1e-0410-96f1-a4d463321fa5
-rw-r--r-- | libhb/common.h | 4 | ||||
-rw-r--r-- | libhb/encx264.c | 155 | ||||
-rw-r--r-- | libhb/encx264.h | 10 | ||||
-rw-r--r-- | libhb/work.c | 13 | ||||
-rw-r--r-- | test/test.c | 44 |
5 files changed, 211 insertions, 15 deletions
diff --git a/libhb/common.h b/libhb/common.h index 40af25df6..6f8e3b6c5 100644 --- a/libhb/common.h +++ b/libhb/common.h @@ -294,6 +294,7 @@ struct hb_job_s char *x264_profile; char *x264_preset; char *x264_tune; + char *h264_level; int areBframes; int color_matrix_code; int color_prim; @@ -916,9 +917,10 @@ int hb_rgb2yuv(int rgb); const char * hb_subsource_name( int source ); -// x264 preset/tune/profile helpers +// x264 preset/tune/profile & h264 level helpers const char * const * hb_x264_presets(); const char * const * hb_x264_tunes(); const char * const * hb_x264_profiles(); +const char * const * hb_h264_levels(); #endif diff --git a/libhb/encx264.c b/libhb/encx264.c index 9af954d97..29037b636 100644 --- a/libhb/encx264.c +++ b/libhb/encx264.c @@ -7,8 +7,7 @@ #include <stdarg.h> #include "hb.h" - -#include "x264.h" +#include "encx264.h" int encx264Init( hb_work_object_t *, hb_job_t * ); int encx264Work( hb_work_object_t *, hb_buffer_t **, hb_buffer_t ** ); @@ -280,7 +279,7 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) } } - /* Apply profile settings after explicit settings, if present. */ + /* Apply profile and level settings last, if present. */ if( job->x264_profile ) { if( x264_param_apply_profile( ¶m, job->x264_profile ) < 0 ) @@ -290,6 +289,10 @@ int encx264Init( hb_work_object_t * w, hb_job_t * job ) return 1; } } + if( job->h264_level ) + { + hb_apply_h264_level( ¶m, job->h264_level ); + } /* B-frames are on by default.*/ job->areBframes = 1; @@ -627,6 +630,148 @@ int encx264Work( hb_work_object_t * w, hb_buffer_t ** buf_in, return HB_WORK_OK; } +/* Applies the restrictions of the requested H.264 level to the passed x264_param_t. + * Does not modify resolution/framerate but warns when they exceed level limits. + * No return value: an error or warning does not cause libhb to exit. + * Based on x264_param_apply_level and other x264 code. */ +void hb_apply_h264_level( x264_param_t * param, const char * level ) +{ + int i, h264_profile, i_mb_width, i_mb_height, mbs, max_frame_side; + x264_level_t * h264_level = NULL; + + /* find the x264_level_t corresponding to the requested level */ + for( i = 0; h264_level_names[i]; i++ ) + { + if( !strcmp( h264_level_names[i], level ) ) + { + int val = h264_level_values[i]; + for( i = 0; x264_levels[i].level_idc; i++ ) + { + if( x264_levels[i].level_idc == val ) + { + h264_level = (x264_level_t *)(x264_levels + i); + break; + } + } + break; + } + } + if( !h264_level ) + { + // error (invalid or unsupported level), abort + // this is not a failure (encoding continues) + hb_log( "hb_apply_h264_level [ERROR]: invalid level %s, ignoring", level ); + return; + } + + /* some levels do not support interlaced encoding */ + if( h264_level->frame_only && ( param->b_interlaced || param->b_fake_interlaced ) ) + { + hb_log( "hb_apply_h264_level [warning]: interlaced flag not supported for level %s, disabling", + level ); + param->b_interlaced = 0; + param->b_fake_interlaced = 0; + } + + /* frame dimensions & rate (in macroblocks) */ + i_mb_width = ( param->i_width + 15 ) / 16; + i_mb_height = ( param->i_height + 15 ) / 16; + // interlaced: i_mb_height*16 must be mod 32 + if( param->b_interlaced || param->b_fake_interlaced ) + i_mb_height = ( i_mb_height + 1 ) & ~1; + mbs = i_mb_width * i_mb_height; + + /* sanitize ref/frameref */ + if( param->i_keyint_max != 1 ) + { + int i_max_dec_frame_buffering = MAX( MIN( h264_level->dpb / (384 * mbs), 16 ), 1 ); + param->i_frame_reference = MIN( i_max_dec_frame_buffering, param->i_frame_reference ); + // some level/resolution combinations may require as little as 1 reference + // B-frames & B-pyramid are not compatible with this scenario + if( i_max_dec_frame_buffering < 2 ) + param->i_bframe = 0; + else if( i_max_dec_frame_buffering < 4 ) + param->i_bframe_pyramid = X264_B_PYRAMID_NONE; + } + + /* H.264 profile */ + if( param->rc.i_rc_method == X264_RC_CRF && param->rc.f_rf_constant < 1 ) + { + h264_profile = 2; // High 4:4:4 Predictive + } + else if( param->analyse.b_transform_8x8 || param->i_cqm_preset != X264_CQM_FLAT ) + { + h264_profile = 1; // High + } + else + { + h264_profile = 0; // less than High + } + + /* set and/or sanitize the VBV (if not lossless) */ + if( h264_profile < 2 ) + { + // High profile allows for higher VBV bufsize/maxrate + int cbp_factor = h264_profile == 1 ? 5 : 4; + if( !param->rc.i_vbv_max_bitrate) + { + param->rc.i_vbv_max_bitrate = (h264_level->bitrate * cbp_factor) / 4; + } + else + { + param->rc.i_vbv_max_bitrate = + MIN( param->rc.i_vbv_max_bitrate, (h264_level->bitrate * cbp_factor) / 4 ); + } + if( !param->rc.i_vbv_buffer_size ) + { + param->rc.i_vbv_buffer_size = (h264_level->cpb * cbp_factor) / 4; + } + else + { + param->rc.i_vbv_buffer_size = + MIN( param->rc.i_vbv_buffer_size, (h264_level->cpb * cbp_factor) / 4 ); + } + } + + /* sanitize mvrange/mv-range */ + param->analyse.i_mv_range = + MIN( param->analyse.i_mv_range, h264_level->mv_range >> !!param->b_interlaced ); + + /* TODO: check the rest of the limits */ + + /* level successfully applied, yay! */ + param->i_level_idc = h264_level->level_idc; + + /* things we can do nothing about (too late to change resolution or fps), print warnings */ + if( h264_level->frame_size < mbs ) + { + hb_log( "hb_apply_h264_level [warning]: frame size (%dx%d, %d macroblocks)", + i_mb_width*16, i_mb_height*16, mbs ); + hb_log( " too high for level %s (max. %d macroblocks)", + level, h264_level->frame_size ); + } + else if( h264_level->mbps < (int64_t)mbs * param->i_fps_num / param->i_fps_den ) + { + hb_log( "hb_apply_h264_level [warning]: framerate (%.3f) too high for level %s", + (float)param->i_fps_num/param->i_fps_den, level ); + hb_log( " at %dx%d (max. %.3f)", + param->i_width, param->i_height, (float)h264_level->mbps/mbs ); + } + // width^2 or height^2 may not exceed 8 * frame_size (in macroblocks) + // thus neither dimension may exceed sqrt(8 * frame_size) + max_frame_side = (int)sqrt( (double)h264_level->frame_size*8 ); + if( h264_level->frame_size*8 < i_mb_width * i_mb_width ) + { + hb_log( "hb_apply_h264_level [warning]: frame too wide (%d) for level %s (max. %d)", + param->i_width, level, max_frame_side*16 ); + } + if( h264_level->frame_size*8 < i_mb_height * i_mb_height ) + { + hb_log( "hb_apply_h264_level [warning]: frame too wide (%d) for level %s (max. %d)", + param->i_height, level, max_frame_side*16 ); + } +} + const char * const * hb_x264_presets() { return x264_preset_names; @@ -642,3 +787,7 @@ const char * const * hb_x264_profiles() return x264_profile_names; } +const char * const * hb_h264_levels() +{ + return h264_level_names; +} diff --git a/libhb/encx264.h b/libhb/encx264.h new file mode 100644 index 000000000..f825028eb --- /dev/null +++ b/libhb/encx264.h @@ -0,0 +1,10 @@ +/* This file is part of the HandBrake source code. + Homepage: <http://handbrake.fr/>. + It may be used under the terms of the GNU General Public License. */ + +#include "x264.h" + +static const char * const h264_level_names[] = { "1.0", "1b", "1.1", "1.2", "1.3", "2.0", "2.1", "2.2", "3.0", "3.1", "3.2", "4.0", "4.1", "4.2", "5.0", "5.1", 0 }; +static const int const h264_level_values[] = { 10, 9, 11, 12, 13, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 0 }; + +void hb_apply_h264_level( x264_param_t * param, const char * level ); diff --git a/libhb/work.c b/libhb/work.c index 530defde9..0487a07f0 100644 --- a/libhb/work.c +++ b/libhb/work.c @@ -327,23 +327,28 @@ void hb_display_job_info( hb_job_t * job ) if( job->x264_preset && *job->x264_preset && job->vcodec == HB_VCODEC_X264 ) { - hb_log( " + x264 preset: %s", job->x264_preset); + hb_log( " + x264 preset: %s", job->x264_preset ); } if( job->x264_tune && *job->x264_tune && job->vcodec == HB_VCODEC_X264 ) { - hb_log( " + x264 tune: %s", job->x264_tune); + hb_log( " + x264 tune: %s", job->x264_tune ); } if( job->advanced_opts && *job->advanced_opts && ( ( job->vcodec & HB_VCODEC_FFMPEG_MASK ) || ( job->vcodec == HB_VCODEC_X264 ) ) ) { - hb_log( " + options: %s", job->advanced_opts); + hb_log( " + options: %s", job->advanced_opts ); } if( job->x264_profile && *job->x264_profile && job->vcodec == HB_VCODEC_X264 ) { - hb_log( " + x264 profile: %s", job->x264_profile); + hb_log( " + x264 profile: %s", job->x264_profile ); + } + if( job->h264_level && *job->h264_level && + job->vcodec == HB_VCODEC_X264 ) + { + hb_log( " + h264 level: %s", job->h264_level ); } if( job->vquality >= 0 ) diff --git a/test/test.c b/test/test.c index 99161d276..0f2edc8a6 100644 --- a/test/test.c +++ b/test/test.c @@ -114,13 +114,14 @@ static int chapter_start = 0; static int chapter_end = 0; static int chapter_markers = 0; static char * marker_file = NULL; -static char *advanced_opts = NULL; -static char *advanced_opts2 = NULL; -static char *x264_profile = NULL; -static char *x264_preset = NULL; -static char *x264_tune = NULL; -static int maxHeight = 0; -static int maxWidth = 0; +static char * advanced_opts = NULL; +static char * advanced_opts2 = NULL; +static char * x264_profile = NULL; +static char * x264_preset = NULL; +static char * x264_tune = NULL; +static char * h264_level = NULL; +static int maxHeight = 0; +static int maxWidth = 0; static int turbo_opts_enabled = 0; static char * turbo_opts = "ref=1:subme=2:me=dia:analyse=none:trellis=0:no-fast-pskip=0:8x8dct=0:weightb=0"; static int largeFileSize = 0; @@ -373,6 +374,7 @@ int main( int argc, char ** argv ) free( x264_profile ); free( x264_preset ); free( x264_tune ); + free( h264_level ); // write a carriage return to stdout - avoids overlap / line wrapping when stderr is redirected fprintf( stdout, "\n" ); @@ -2378,6 +2380,7 @@ static int HandleEvents( hb_handle_t * h ) job->x264_profile = x264_profile; job->x264_preset = x264_preset; job->x264_tune = x264_tune; + job->h264_level = h264_level; if (maxWidth) job->maxWidth = maxWidth; if (maxHeight) @@ -2742,6 +2745,28 @@ static void ShowHelp() if( len ) fprintf( out, "%s\n", tmp ); fprintf( out, + " --h264-level When using x264, ensures compliance with the\n" + " <string> specified h.264 level:\n" + " "); + x264_opts = hb_h264_levels(); + tmp[0] = 0; + len = 0; + while( x264_opts && *x264_opts ) + { + strncat( tmp, *x264_opts++, 79 - len ); + if( *x264_opts ) + strcat( tmp, "/" ); + len = strlen( tmp ); + if( len > 40 && *x264_opts ) + { + fprintf( out, "%s\n ", tmp ); + len = 0; + tmp[0] = 0; + } + } + if( len ) + fprintf( out, "%s\n", tmp ); + fprintf( out, " -q, --quality <number> Set video quality\n" " -b, --vb <kb/s> Set video bitrate (default: 1000)\n" " -2, --two-pass Use two-pass mode\n" @@ -3108,6 +3133,7 @@ static int ParseOptions( int argc, char ** argv ) #define X264_PROFILE 283 #define X264_PRESET 284 #define X264_TUNE 285 + #define H264_LEVEL 286 for( ;; ) { @@ -3181,6 +3207,7 @@ static int ParseOptions( int argc, char ** argv ) { "x264-profile", required_argument, NULL, X264_PROFILE }, { "x264-preset", required_argument, NULL, X264_PRESET }, { "x264-tune", required_argument, NULL, X264_TUNE }, + { "h264-level", required_argument, NULL, H264_LEVEL }, { "turbo", no_argument, NULL, 'T' }, { "maxHeight", required_argument, NULL, 'Y' }, { "maxWidth", required_argument, NULL, 'X' }, @@ -3602,6 +3629,9 @@ static int ParseOptions( int argc, char ** argv ) case X264_TUNE: x264_tune = strdup( optarg ); break; + case H264_LEVEL: + h264_level = strdup( optarg ); + break; case 'T': turbo_opts_enabled = 1; break; |