summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libhb/common.h4
-rw-r--r--libhb/encx264.c155
-rw-r--r--libhb/encx264.h10
-rw-r--r--libhb/work.c13
-rw-r--r--test/test.c44
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( &param, 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( &param, 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;