diff options
-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; |