From 7b390a8bbd0da33199e3c083f13bf5cd1a825a96 Mon Sep 17 00:00:00 2001 From: Mikhail Fludkov Date: Thu, 26 Nov 2015 17:52:29 +0100 Subject: [PATCH] vpxenc: Add new bit-per-pixel property to select a better "default" bitrate MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit As part of this also change the default bitrate value to 0. The default value was 256000 previously. In reality, if the property was not set the bitrate value would be scaled according to the resolution which is not very intuitive behavior. It is better to use 0 for this purpose. Now together with newly introduced property "bits-per-pixel" 0 means to assign the bitrate according to resolution/framerate. The default bitrates are now - 1.2Mbps for VP8 720p@30fps - 0.8Mbps for VP9 720p@30fps and scaled accordingly for different resolutions/framerates. Previously the default bitrate was also not scaled according to the framerate but only took the resolution into account. This also fixes the side effect of setting bitrate to 0. Previously encoder would not produce any data at all. Addition from Sebastian Dröge to assume 30fps if no framerate is given in the caps instead of not calculating any bitrate at all. Part-of: --- ext/vpx/gstvp9enc.c | 3 ++ ext/vpx/gstvpxenc.c | 87 ++++++++++++++++++++++++++++------ ext/vpx/gstvpxenc.h | 5 +- tests/check/elements/vp8enc.c | 106 +++++++++++++++++++++++++++++++++++++++++- tests/check/elements/vp9enc.c | 46 ++++++++++++++++++ 5 files changed, 231 insertions(+), 16 deletions(-) diff --git a/ext/vpx/gstvp9enc.c b/ext/vpx/gstvp9enc.c index 3628564..f305808 100644 --- a/ext/vpx/gstvp9enc.c +++ b/ext/vpx/gstvp9enc.c @@ -101,6 +101,8 @@ static GstFlowReturn gst_vp9_enc_handle_invisible_frame_buffer (GstVPXEnc * enc, static void gst_vp9_enc_set_frame_user_data (GstVPXEnc * enc, GstVideoCodecFrame * frame, vpx_image_t * image); +#define DEFAULT_BITS_PER_PIXEL 0.0289 + static void gst_vp9_enc_class_init (GstVP9EncClass * klass) { @@ -152,6 +154,7 @@ gst_vp9_enc_init (GstVP9Enc * gst_vp9_enc) } else { gst_vpx_enc->have_default_config = TRUE; } + gst_vpx_enc->bits_per_pixel = DEFAULT_BITS_PER_PIXEL; } static vpx_codec_iface_t * diff --git a/ext/vpx/gstvpxenc.c b/ext/vpx/gstvpxenc.c index 12a781d..cfb69c8 100644 --- a/ext/vpx/gstvpxenc.c +++ b/ext/vpx/gstvpxenc.c @@ -47,7 +47,7 @@ GST_DEBUG_CATEGORY_STATIC (gst_vpxenc_debug); #define DEFAULT_PROFILE 0 #define DEFAULT_RC_END_USAGE VPX_VBR -#define DEFAULT_RC_TARGET_BITRATE 256000 +#define DEFAULT_RC_TARGET_BITRATE 0 #define DEFAULT_RC_MIN_QUANTIZER 4 #define DEFAULT_RC_MAX_QUANTIZER 63 @@ -102,6 +102,8 @@ GST_DEBUG_CATEGORY_STATIC (gst_vpxenc_debug); #define DEFAULT_TIMEBASE_N 0 #define DEFAULT_TIMEBASE_D 1 +#define DEFAULT_BITS_PER_PIXEL 0.0434 + enum { PROP_0, @@ -148,7 +150,8 @@ enum PROP_TUNING, PROP_CQ_LEVEL, PROP_MAX_INTRA_BITRATE_PCT, - PROP_TIMEBASE + PROP_TIMEBASE, + PROP_BITS_PER_PIXEL }; @@ -368,7 +371,8 @@ gst_vpx_enc_class_init (GstVPXEncClass * klass) g_object_class_install_property (gobject_class, PROP_RC_TARGET_BITRATE, g_param_spec_int ("target-bitrate", "Target bitrate", - "Target bitrate (in bits/sec)", + "Target bitrate (in bits/sec) (0: auto - bitrate depends on " + "resolution, see \"bits-per-pixel\" property for more info)", 0, G_MAXINT, DEFAULT_RC_TARGET_BITRATE, (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); @@ -638,6 +642,13 @@ gst_vpx_enc_class_init (GstVPXEncClass * klass) 0, 1, G_MAXINT, 1, DEFAULT_TIMEBASE_N, DEFAULT_TIMEBASE_D, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, PROP_BITS_PER_PIXEL, + g_param_spec_float ("bits-per-pixel", "Bits per pixel", + "Factor to convert number of pixels to bitrate value " + "(only has an effect if target-bitrate=0)", + 0.0, G_MAXFLOAT, DEFAULT_BITS_PER_PIXEL, + (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS))); + GST_DEBUG_CATEGORY_INIT (gst_vpxenc_debug, "vpxenc", 0, "VPX Encoder"); gst_type_mark_as_plugin_api (GST_VPX_ENC_END_USAGE_TYPE); @@ -657,7 +668,7 @@ gst_vpx_enc_init (GstVPXEnc * gst_vpx_enc) gst_vpx_enc->cfg.rc_end_usage = DEFAULT_RC_END_USAGE; gst_vpx_enc->cfg.rc_target_bitrate = DEFAULT_RC_TARGET_BITRATE / 1000; - gst_vpx_enc->rc_target_bitrate_set = FALSE; + gst_vpx_enc->rc_target_bitrate_auto = DEFAULT_RC_TARGET_BITRATE == 0; gst_vpx_enc->cfg.rc_min_quantizer = DEFAULT_RC_MIN_QUANTIZER; gst_vpx_enc->cfg.rc_max_quantizer = DEFAULT_RC_MAX_QUANTIZER; gst_vpx_enc->cfg.rc_dropframe_thresh = DEFAULT_RC_DROPFRAME_THRESH; @@ -705,6 +716,7 @@ gst_vpx_enc_init (GstVPXEnc * gst_vpx_enc) gst_vpx_enc->max_intra_bitrate_pct = DEFAULT_MAX_INTRA_BITRATE_PCT; gst_vpx_enc->timebase_n = DEFAULT_TIMEBASE_N; gst_vpx_enc->timebase_d = DEFAULT_TIMEBASE_D; + gst_vpx_enc->bits_per_pixel = DEFAULT_BITS_PER_PIXEL; gst_vpx_enc->cfg.g_profile = DEFAULT_PROFILE; @@ -735,6 +747,42 @@ gst_vpx_enc_finalize (GObject * object) } static void +gst_vpx_enc_set_auto_bitrate (GstVPXEnc * encoder) +{ + if (encoder->input_state != NULL) { + guint size; + guint pixels_per_sec; + guint target_bitrate; + guint fps_n, fps_d; + + if (GST_VIDEO_INFO_FPS_D (&encoder->input_state->info) != 0) { + fps_n = GST_VIDEO_INFO_FPS_N (&encoder->input_state->info); + fps_d = GST_VIDEO_INFO_FPS_D (&encoder->input_state->info); + } else { + /* otherwise assume 30 frames per second as a fallback */ + fps_n = 30; + fps_d = 1; + } + + size = + GST_VIDEO_INFO_WIDTH (&encoder->input_state->info) * + GST_VIDEO_INFO_HEIGHT (&encoder->input_state->info); + pixels_per_sec = size * fps_n / fps_d; + target_bitrate = pixels_per_sec * encoder->bits_per_pixel; + + GST_DEBUG_OBJECT (encoder, + "Setting autobitrate for %ux%ux @ %u/%ufps %.4f = %ubps", + GST_VIDEO_INFO_WIDTH (&encoder->input_state->info), + GST_VIDEO_INFO_HEIGHT (&encoder->input_state->info), + GST_VIDEO_INFO_FPS_N (&encoder->input_state->info), + GST_VIDEO_INFO_FPS_D (&encoder->input_state->info), + encoder->bits_per_pixel, target_bitrate); + + encoder->cfg.rc_target_bitrate = target_bitrate / 1000; + } +} + +static void gst_vpx_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { @@ -753,8 +801,13 @@ gst_vpx_enc_set_property (GObject * object, guint prop_id, global = TRUE; break; case PROP_RC_TARGET_BITRATE: - gst_vpx_enc->cfg.rc_target_bitrate = g_value_get_int (value) / 1000; - gst_vpx_enc->rc_target_bitrate_set = TRUE; + if (g_value_get_int (value) == 0) { + gst_vpx_enc_set_auto_bitrate (gst_vpx_enc); + gst_vpx_enc->rc_target_bitrate_auto = TRUE; + } else { + gst_vpx_enc->cfg.rc_target_bitrate = g_value_get_int (value) / 1000; + gst_vpx_enc->rc_target_bitrate_auto = FALSE; + } global = TRUE; break; case PROP_RC_MIN_QUANTIZER: @@ -1097,6 +1150,13 @@ gst_vpx_enc_set_property (GObject * object, guint prop_id, gst_vpx_enc->timebase_n = gst_value_get_fraction_numerator (value); gst_vpx_enc->timebase_d = gst_value_get_fraction_denominator (value); break; + case PROP_BITS_PER_PIXEL: + gst_vpx_enc->bits_per_pixel = g_value_get_float (value); + if (gst_vpx_enc->rc_target_bitrate_auto) { + gst_vpx_enc_set_auto_bitrate (gst_vpx_enc); + global = TRUE; + } + break; default: break; } @@ -1318,6 +1378,9 @@ gst_vpx_enc_get_property (GObject * object, guint prop_id, GValue * value, gst_value_set_fraction (value, gst_vpx_enc->timebase_n, gst_vpx_enc->timebase_d); break; + case PROP_BITS_PER_PIXEL: + g_value_set_float (value, gst_vpx_enc->bits_per_pixel); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -1452,14 +1515,6 @@ gst_vpx_enc_set_format (GstVideoEncoder * video_encoder, } encoder->cfg.g_profile = gst_vpx_enc_get_downstream_profile (encoder); - - /* Scale default bitrate to our size */ - if (!encoder->rc_target_bitrate_set) - encoder->cfg.rc_target_bitrate = - gst_util_uint64_scale (DEFAULT_RC_TARGET_BITRATE, - GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info), - 320 * 240 * 1000); - encoder->cfg.g_w = GST_VIDEO_INFO_WIDTH (info); encoder->cfg.g_h = GST_VIDEO_INFO_HEIGHT (info); @@ -1641,6 +1696,10 @@ gst_vpx_enc_set_format (GstVideoEncoder * video_encoder, gst_video_codec_state_unref (encoder->input_state); encoder->input_state = gst_video_codec_state_ref (state); + /* Scale default bitrate to our size */ + if (encoder->rc_target_bitrate_auto) + gst_vpx_enc_set_auto_bitrate (encoder); + /* prepare cached image buffer setup */ image = &encoder->image; memset (image, 0, sizeof (*image)); diff --git a/ext/vpx/gstvpxenc.h b/ext/vpx/gstvpxenc.h index 5a0d5ba..fbf5476 100644 --- a/ext/vpx/gstvpxenc.h +++ b/ext/vpx/gstvpxenc.h @@ -68,7 +68,7 @@ struct _GstVPXEnc /* properties */ vpx_codec_enc_cfg_t cfg; gboolean have_default_config; - gboolean rc_target_bitrate_set; + gboolean rc_target_bitrate_auto; gint n_ts_target_bitrate; gint n_ts_rate_decimator; gint n_ts_layer_id; @@ -100,6 +100,9 @@ struct _GstVPXEnc unsigned int timebase_n; unsigned int timebase_d; + /* Bits per Pixel */ + gfloat bits_per_pixel; + /* state */ gboolean inited; diff --git a/tests/check/elements/vp8enc.c b/tests/check/elements/vp8enc.c index 86b1d92..e9d8c07 100644 --- a/tests/check/elements/vp8enc.c +++ b/tests/check/elements/vp8enc.c @@ -17,8 +17,9 @@ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ - +#include #include +#include static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, @@ -90,6 +91,7 @@ cleanup_vp8enc (GstElement * vp8enc) gst_check_teardown_element (vp8enc); } + GST_START_TEST (test_encode_simple) { GstElement *vp8enc; @@ -152,6 +154,106 @@ GST_START_TEST (test_encode_simple) GST_END_TEST; +#define gst_caps_new_i420(w, h) gst_caps_new_i420_full (w, h, 30, 1, 1, 1) +static GstCaps * +gst_caps_new_i420_full (gint width, gint height, gint fps_n, gint fps_d, + gint par_n, gint par_d) +{ + GstVideoInfo info; + gst_video_info_init (&info); + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, width, height); + GST_VIDEO_INFO_FPS_N (&info) = fps_n; + GST_VIDEO_INFO_FPS_D (&info) = fps_d; + GST_VIDEO_INFO_PAR_N (&info) = par_n; + GST_VIDEO_INFO_PAR_D (&info) = par_d; + return gst_video_info_to_caps (&info); +} + +static GstBuffer * +gst_harness_create_video_buffer_from_info (GstHarness * h, gint value, + GstVideoInfo * info, GstClockTime timestamp, GstClockTime duration) +{ + GstBuffer *buf; + gsize size; + + size = GST_VIDEO_INFO_SIZE (info); + + buf = gst_harness_create_buffer (h, size); + gst_buffer_memset (buf, 0, value, size); + g_assert (buf != NULL); + + gst_buffer_add_video_meta_full (buf, + GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (info), + GST_VIDEO_INFO_WIDTH (info), + GST_VIDEO_INFO_HEIGHT (info), + GST_VIDEO_INFO_N_PLANES (info), info->offset, info->stride); + + GST_BUFFER_PTS (buf) = timestamp; + GST_BUFFER_DURATION (buf) = duration; + + return buf; +} + +static GstBuffer * +gst_harness_create_video_buffer_full (GstHarness * h, gint value, + guint width, guint height, GstClockTime timestamp, GstClockTime duration) +{ + GstVideoInfo info; + + gst_video_info_init (&info); + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, width, height); + + return gst_harness_create_video_buffer_from_info (h, value, &info, + timestamp, duration); +} + +GST_START_TEST (test_encode_simple_when_bitrate_set_to_zero) +{ + GstHarness *h = gst_harness_new_parse ("vp8enc target-bitrate=0"); + GstBuffer *buf; + + gst_harness_set_src_caps (h, gst_caps_new_i420 (320, 240)); + + buf = gst_harness_create_video_buffer_full (h, 0x42, + 320, 240, 0, gst_util_uint64_scale (GST_SECOND, 1, 30)); + gst_harness_push (h, buf); + gst_buffer_unref (gst_harness_pull (h)); + gst_harness_teardown (h); +} + +GST_END_TEST; + +GST_START_TEST (test_autobitrate_changes_with_caps) +{ + gint bitrate = 0; + GstHarness *h = gst_harness_new ("vp8enc"); + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 30, 1, 1, 1)); + + /* Default settings for 720p @ 30fps ~1.2Mbps */ + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 1199000); + + /* Change bits-per-pixel 0.036 to give us ~1Mbps */ + g_object_set (h->element, "bits-per-pixel", 0.037, NULL); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 1022000); + + /* Halving the framerate should halve the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 511000); + + /* Halving the resolution should quarter the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (640, 360, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 127000); + + gst_harness_teardown (h); +} + +GST_END_TEST; + static Suite * vp8enc_suite (void) { @@ -161,6 +263,8 @@ vp8enc_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_encode_simple); + tcase_add_test (tc_chain, test_encode_simple_when_bitrate_set_to_zero); + tcase_add_test (tc_chain, test_autobitrate_changes_with_caps); return s; } diff --git a/tests/check/elements/vp9enc.c b/tests/check/elements/vp9enc.c index f7be0e0..b7ae12f 100644 --- a/tests/check/elements/vp9enc.c +++ b/tests/check/elements/vp9enc.c @@ -21,6 +21,21 @@ #include #include +#define gst_caps_new_i420(w, h) gst_caps_new_i420_full (w, h, 30, 1, 1, 1) +static GstCaps * +gst_caps_new_i420_full (gint width, gint height, gint fps_n, gint fps_d, + gint par_n, gint par_d) +{ + GstVideoInfo info; + gst_video_info_init (&info); + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, width, height); + GST_VIDEO_INFO_FPS_N (&info) = fps_n; + GST_VIDEO_INFO_FPS_D (&info) = fps_d; + GST_VIDEO_INFO_PAR_N (&info) = par_n; + GST_VIDEO_INFO_PAR_D (&info) = par_d; + return gst_video_info_to_caps (&info); +} + GST_START_TEST (test_encode_lag_in_frames) { GstHarness *h = gst_harness_new_parse ("vp9enc lag-in-frames=5 cpu-used=8 " @@ -62,6 +77,36 @@ GST_START_TEST (test_encode_lag_in_frames) GST_END_TEST; +GST_START_TEST (test_autobitrate_changes_with_caps) +{ + gint bitrate = 0; + GstHarness *h = gst_harness_new ("vp9enc"); + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 30, 1, 1, 1)); + + /* Default settings for 720p @ 30fps ~0.8Mbps */ + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 799000); + + /* Change bits-per-pixel 0.036 to give us ~1Mbps */ + g_object_set (h->element, "bits-per-pixel", 0.037, NULL); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 1022000); + + /* Halving the framerate should halve the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (1280, 720, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 511000); + + /* Halving the resolution should quarter the auto bitrate */ + gst_harness_set_src_caps (h, gst_caps_new_i420_full (640, 360, 15, 1, 1, 1)); + g_object_get (h->element, "target-bitrate", &bitrate, NULL); + fail_unless_equals_int (bitrate, 127000); + + gst_harness_teardown (h); +} + +GST_END_TEST; + static Suite * vp9enc_suite (void) { @@ -71,6 +116,7 @@ vp9enc_suite (void) suite_add_tcase (s, tc_chain); tcase_add_test (tc_chain, test_encode_lag_in_frames); + tcase_add_test (tc_chain, test_autobitrate_changes_with_caps); return s; } -- 2.7.4