vpxenc: Add new bit-per-pixel property to select a better "default" bitrate
authorMikhail Fludkov <misha@pexip.com>
Thu, 26 Nov 2015 16:52:29 +0000 (17:52 +0100)
committerGStreamer Merge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Thu, 4 Jun 2020 20:14:03 +0000 (20:14 +0000)
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 <sebastian@centricular.com> to assume
30fps if no framerate is given in the caps instead of not calculating
any bitrate at all.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/611>

ext/vpx/gstvp9enc.c
ext/vpx/gstvpxenc.c
ext/vpx/gstvpxenc.h
tests/check/elements/vp8enc.c
tests/check/elements/vp9enc.c

index 36285647695fd356583d97c75a486b71cd450ef1..f3058081e86aded4099632f9b25e98ab8dc1d3e9 100644 (file)
@@ -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 *
index 12a781df2a6243a1d3b54432317d355fe8ec0b86..cfb69c833e30ec4dcd5e01ad0ed79a64f91e5ee0 100644 (file)
@@ -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;
 
@@ -734,6 +746,42 @@ gst_vpx_enc_finalize (GObject * object)
   G_OBJECT_CLASS (parent_class)->finalize (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));
index 5a0d5ba5378cda1fad53e69f4ba2ef3de30bc331..fbf5476ba0bc53fff06cfc3a6691951bc8d7b751 100644 (file)
@@ -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;
 
index 86b1d929d1363d895c723f801cd9c8ca0bca0e79..e9d8c071f7db5adbd24a225d3499ebee1aac4fe2 100644 (file)
@@ -17,8 +17,9 @@
  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
  * Boston, MA 02110-1301, USA.
  */
-
+#include <gst/check/gstharness.h>
 #include <gst/check/gstcheck.h>
+#include <gst/video/video.h>
 
 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;
 }
index f7be0e0fbe3e44a5dd99ce4d7bd828ea87402cd3..b7ae12fd9feca44b737479861d1b6cb0be5b4784 100644 (file)
 #include <gst/check/gstcheck.h>
 #include <gst/video/video.h>
 
+#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;
 }