x264enc: Respect Youtube bitrate recommandation upstream/master
authorThibault Saunier <tsaunier@igalia.com>
Thu, 17 Oct 2019 12:55:51 +0000 (14:55 +0200)
committerThibault Saunier <tsaunier@gnome.org>
Tue, 7 Jan 2020 21:21:42 +0000 (21:21 +0000)
Properly follow google recommendations[0] concerning bitrate when the
user wants to use the youtube profile.

[0]: https://support.google.com/youtube/answer/1722171?hl=en

ext/x264/gstencoderbitrateprofilemanager.c [new file with mode: 0644]
ext/x264/gstencoderbitrateprofilemanager.h [new file with mode: 0644]
ext/x264/gstx264enc.c
ext/x264/gstx264enc.h
ext/x264/meson.build

diff --git a/ext/x264/gstencoderbitrateprofilemanager.c b/ext/x264/gstencoderbitrateprofilemanager.c
new file mode 100644 (file)
index 0000000..ef99b0e
--- /dev/null
@@ -0,0 +1,221 @@
+/* GStreamer
+ * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * gstencoderbitrateprofilemanager.c
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "gstencoderbitrateprofilemanager.h"
+
+GST_DEBUG_CATEGORY_STATIC (encoderbitratemanager_debug);
+#define GST_CAT_DEFAULT encoderbitratemanager_debug
+
+typedef struct
+{
+  gchar *name;
+  gsize n_vals;
+  GstEncoderBitrateTargetForPixelsMap *map;
+} GstEncoderBitrateProfile;
+
+struct _GstEncoderBitrateProfileManager
+{
+  GList *profiles;
+  gchar *preset;
+  guint bitrate;
+
+  gboolean setting_preset;
+  gboolean user_bitrate;
+};
+
+/* *INDENT-OFF* */
+/* Copied from https://support.google.com/youtube/answer/1722171?hl=en */
+static const GstEncoderBitrateTargetForPixelsMap youtube_bitrate_profiles[] = {
+  {
+        .n_pixels = 3840 * 2160,
+        .low_framerate_bitrate = 40000,
+        .high_framerate_bitrate = 60000,
+  },
+  {
+        .n_pixels = 2560 * 1440,
+        .low_framerate_bitrate = 16000,
+        .high_framerate_bitrate = 24000,
+  },
+  {
+        .n_pixels = 1920 * 1080,
+        .low_framerate_bitrate = 8000,
+        .high_framerate_bitrate = 12000,
+  },
+  {
+        .n_pixels = 1080 * 720,
+        .low_framerate_bitrate = 5000,
+        .high_framerate_bitrate = 7500,
+      },
+  {
+        .n_pixels = 640 * 480,
+        .low_framerate_bitrate = 2500,
+        .high_framerate_bitrate = 4000,
+  },
+  {
+        .n_pixels = 0,
+        .low_framerate_bitrate = 2500,
+        .high_framerate_bitrate = 4000,
+  },
+  {
+        .n_pixels = 0,
+        .low_framerate_bitrate = 0,
+        .high_framerate_bitrate = 0,
+  },
+};
+/* *INDENT-ON* */
+
+static void
+gst_encoder_bitrate_profile_free (GstEncoderBitrateProfile * profile)
+{
+  g_free (profile->name);
+  g_free (profile->map);
+  g_free (profile);
+}
+
+void
+gst_encoder_bitrate_profile_manager_add_profile (GstEncoderBitrateProfileManager
+    * self, const gchar * profile_name,
+    const GstEncoderBitrateTargetForPixelsMap * map)
+{
+  gint n_vals;
+  GstEncoderBitrateProfile *profile;
+
+  for (n_vals = 0;
+      map[n_vals].low_framerate_bitrate != 0
+      && map[n_vals].high_framerate_bitrate != 0; n_vals++);
+  n_vals++;
+
+  profile = g_new0 (GstEncoderBitrateProfile, 1);
+  profile->name = g_strdup (profile_name);
+  profile->n_vals = n_vals;
+  profile->map
+      = g_memdup (map, sizeof (GstEncoderBitrateTargetForPixelsMap) * n_vals);
+  self->profiles = g_list_prepend (self->profiles, profile);
+}
+
+guint
+gst_encoder_bitrate_profile_manager_get_bitrate (GstEncoderBitrateProfileManager
+    * self, GstVideoInfo * info)
+{
+  gint i;
+  gboolean high_fps;
+  guint num_pix;
+  GList *tmp;
+
+  GstEncoderBitrateProfile *profile = NULL;
+
+  g_return_val_if_fail (self != NULL, -1);
+
+  if (!info || info->finfo == NULL
+      || info->finfo->format == GST_VIDEO_FORMAT_UNKNOWN) {
+    GST_INFO ("Video info %p not usable, returning current bitrate", info);
+    return self->bitrate;
+  }
+
+  if (!self->preset) {
+    GST_INFO ("No preset used, returning current bitrate");
+    return self->bitrate;
+
+  }
+
+  for (tmp = self->profiles; tmp; tmp = tmp->next) {
+    GstEncoderBitrateProfile *tmpprof = tmp->data;
+    if (!g_strcmp0 (tmpprof->name, self->preset)) {
+      profile = tmpprof;
+      break;
+    }
+  }
+
+  if (!profile) {
+    GST_INFO ("Could not find map for profile: %s", self->preset);
+
+    return self->bitrate;
+  }
+
+  high_fps = GST_VIDEO_INFO_FPS_N (info) / GST_VIDEO_INFO_FPS_D (info) > 30.0;
+  num_pix = GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info);
+  for (i = 0; i < profile->n_vals; i++) {
+    GstEncoderBitrateTargetForPixelsMap *bitrate_values = &profile->map[i];
+
+    if (num_pix < bitrate_values->n_pixels)
+      continue;
+
+    self->bitrate =
+        high_fps ? bitrate_values->
+        high_framerate_bitrate : bitrate_values->low_framerate_bitrate;
+    GST_INFO ("Using %s bitrate! %d", self->preset, self->bitrate);
+    return self->bitrate;
+  }
+
+  return -1;
+}
+
+void gst_encoder_bitrate_profile_manager_start_loading_preset
+    (GstEncoderBitrateProfileManager * self)
+{
+  self->setting_preset = TRUE;
+}
+
+void gst_encoder_bitrate_profile_manager_end_loading_preset
+    (GstEncoderBitrateProfileManager * self, const gchar * preset)
+{
+  self->setting_preset = FALSE;
+  g_free (self->preset);
+  self->preset = g_strdup (preset);
+}
+
+void
+gst_encoder_bitrate_profile_manager_set_bitrate (GstEncoderBitrateProfileManager
+    * self, guint bitrate)
+{
+  self->bitrate = bitrate;
+  self->user_bitrate = !self->setting_preset;
+}
+
+void
+gst_encoder_bitrate_profile_manager_free (GstEncoderBitrateProfileManager *
+    self)
+{
+  g_free (self->preset);
+  g_list_free_full (self->profiles,
+      (GDestroyNotify) gst_encoder_bitrate_profile_free);
+  g_free (self);
+}
+
+GstEncoderBitrateProfileManager *
+gst_encoder_bitrate_profile_manager_new (guint default_bitrate)
+{
+  GstEncoderBitrateProfileManager *self =
+      g_new0 (GstEncoderBitrateProfileManager, 1);
+  static volatile gsize _init = 0;
+
+  if (g_once_init_enter (&_init)) {
+    GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "encoderbitratemanager", 0,
+        "Encoder bitrate manager");
+    g_once_init_leave (&_init, 1);
+  }
+
+  self->bitrate = default_bitrate;
+  gst_encoder_bitrate_profile_manager_add_profile (self,
+      "Profile YouTube", youtube_bitrate_profiles);
+
+  return self;
+}
diff --git a/ext/x264/gstencoderbitrateprofilemanager.h b/ext/x264/gstencoderbitrateprofilemanager.h
new file mode 100644 (file)
index 0000000..d6db733
--- /dev/null
@@ -0,0 +1,46 @@
+/* GStreamer
+ * Copyright (C) 2019 Thibault Saunier <tsaunier@igalia.com>
+ *
+ * gstencoderbitrateprofilemanager.h
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#pragma once
+
+#include <gst/gst.h>
+#include <gst/video/video.h>
+
+typedef struct _GstEncoderBitrateProfileManager GstEncoderBitrateProfileManager;
+
+typedef struct _GstEncoderBitrateTargetForPixelsMap
+{
+  guint n_pixels;
+  guint low_framerate_bitrate;
+  guint high_framerate_bitrate;
+
+  gpointer _gst_reserved[GST_PADDING_LARGE];
+} GstEncoderBitrateTargetForPixelsMap;
+
+void
+gst_encoder_bitrate_profile_manager_add_profile(GstEncoderBitrateProfileManager* self,
+    const gchar* profile_name, const GstEncoderBitrateTargetForPixelsMap* map);
+guint gst_encoder_bitrate_profile_manager_get_bitrate(GstEncoderBitrateProfileManager* self, GstVideoInfo* info);
+void gst_encoder_bitrate_profile_manager_start_loading_preset (GstEncoderBitrateProfileManager* self);
+void gst_encoder_bitrate_profile_manager_end_loading_preset(GstEncoderBitrateProfileManager* self, const gchar* preset);
+void gst_encoder_bitrate_profile_manager_set_bitrate(GstEncoderBitrateProfileManager* self, guint bitrate);
+GstEncoderBitrateProfileManager* gst_encoder_bitrate_profile_manager_new(guint default_bitrate);
+void gst_encoder_bitrate_profile_manager_free(GstEncoderBitrateProfileManager* self);
\ No newline at end of file
index 7cf7fdcd4b50a8581573037bc4d36524e1fb2a49..ef1c28f26d74e3e3b49f219c215afb388beb19c5 100644 (file)
@@ -724,9 +724,36 @@ static void gst_x264_enc_set_property (GObject * object, guint prop_id,
 static void gst_x264_enc_get_property (GObject * object, guint prop_id,
     GValue * value, GParamSpec * pspec);
 
+typedef gboolean (*LoadPresetFunc) (GstPreset * preset, const gchar * name);
+
+LoadPresetFunc parent_load_preset = NULL;
+
+static gboolean
+gst_x264_enc_load_preset (GstPreset * preset, const gchar * name)
+{
+  GstX264Enc *enc = GST_X264_ENC (preset);
+  gboolean res;
+
+  gst_encoder_bitrate_profile_manager_start_loading_preset
+      (enc->bitrate_manager);
+  res = parent_load_preset (preset, name);
+  gst_encoder_bitrate_profile_manager_end_loading_preset (enc->bitrate_manager,
+      res ? name : NULL);
+
+  return res;
+}
+
+static void
+gst_x264_enc_preset_interface_init (GstPresetInterface * iface)
+{
+  parent_load_preset = iface->load_preset;
+  iface->load_preset = gst_x264_enc_load_preset;
+}
+
 #define gst_x264_enc_parent_class parent_class
 G_DEFINE_TYPE_WITH_CODE (GstX264Enc, gst_x264_enc, GST_TYPE_VIDEO_ENCODER,
-    G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET, NULL));
+    G_IMPLEMENT_INTERFACE (GST_TYPE_PRESET,
+        gst_x264_enc_preset_interface_init));
 
 /* don't forget to free the string after use */
 static const gchar *
@@ -1227,7 +1254,6 @@ gst_x264_enc_init (GstX264Enc * encoder)
   encoder->quantizer = ARG_QUANTIZER_DEFAULT;
   encoder->mp_cache_file = g_strdup (ARG_MULTIPASS_CACHE_FILE_DEFAULT);
   encoder->byte_stream = ARG_BYTE_STREAM_DEFAULT;
-  encoder->bitrate = ARG_BITRATE_DEFAULT;
   encoder->intra_refresh = ARG_INTRA_REFRESH_DEFAULT;
   encoder->vbv_buf_capacity = ARG_VBV_BUF_CAPACITY_DEFAULT;
   encoder->me = ARG_ME_DEFAULT;
@@ -1260,6 +1286,9 @@ gst_x264_enc_init (GstX264Enc * encoder)
   encoder->tune = ARG_TUNE_DEFAULT;
   encoder->frame_packing = ARG_FRAME_PACKING_DEFAULT;
   encoder->insert_vui = ARG_INSERT_VUI_DEFAULT;
+
+  encoder->bitrate_manager =
+      gst_encoder_bitrate_profile_manager_new (ARG_BITRATE_DEFAULT);
 }
 
 typedef struct
@@ -1384,6 +1413,7 @@ gst_x264_enc_finalize (GObject * object)
   FREE_STRING (encoder->tunings);
   FREE_STRING (encoder->option_string);
   FREE_STRING (encoder->option_string_prop);
+  gst_encoder_bitrate_profile_manager_free (encoder->bitrate_manager);
 
 #undef FREE_STRING
 
@@ -1497,6 +1527,7 @@ gst_x264_enc_init_encoder (GstX264Enc * encoder)
 {
   guint pass = 0;
   GstVideoInfo *info;
+  guint bitrate;
 
   if (!encoder->input_state) {
     GST_DEBUG_OBJECT (encoder, "Have no input state yet");
@@ -1661,6 +1692,10 @@ skip_vui_parameters:
 
   encoder->x264param.analyse.b_psnr = 0;
 
+  bitrate =
+      gst_encoder_bitrate_profile_manager_get_bitrate (encoder->bitrate_manager,
+      encoder->input_state ? &encoder->input_state->info : NULL);
+
   /* FIXME 2.0 make configuration more sane and consistent with x264 cmdline:
    * + split pass property into a pass property (pass1/2/3 enum) and rc-method
    * + bitrate property should only be used in case of CBR method
@@ -1677,7 +1712,7 @@ skip_vui_parameters:
     case GST_X264_ENC_PASS_QUAL:
       encoder->x264param.rc.i_rc_method = X264_RC_CRF;
       encoder->x264param.rc.f_rf_constant = encoder->quantizer;
-      encoder->x264param.rc.i_vbv_max_bitrate = encoder->bitrate;
+      encoder->x264param.rc.i_vbv_max_bitrate = bitrate;
       encoder->x264param.rc.i_vbv_buffer_size
           = encoder->x264param.rc.i_vbv_max_bitrate
           * encoder->vbv_buf_capacity / 1000;
@@ -1688,8 +1723,8 @@ skip_vui_parameters:
     case GST_X264_ENC_PASS_PASS3:
     default:
       encoder->x264param.rc.i_rc_method = X264_RC_ABR;
-      encoder->x264param.rc.i_bitrate = encoder->bitrate;
-      encoder->x264param.rc.i_vbv_max_bitrate = encoder->bitrate;
+      encoder->x264param.rc.i_bitrate = bitrate;
+      encoder->x264param.rc.i_vbv_max_bitrate = bitrate;
       encoder->x264param.rc.i_vbv_buffer_size =
           encoder->x264param.rc.i_vbv_max_bitrate
           * encoder->vbv_buf_capacity / 1000;
@@ -2031,6 +2066,9 @@ gst_x264_enc_set_src_caps (GstX264Enc * encoder, GstCaps * caps)
   GstStructure *structure;
   GstVideoCodecState *state;
   GstTagList *tags;
+  guint bitrate =
+      gst_encoder_bitrate_profile_manager_get_bitrate (encoder->bitrate_manager,
+      encoder->input_state ? &encoder->input_state->info : NULL);
 
   outcaps = gst_caps_new_empty_simple ("video/x-h264");
   structure = gst_caps_get_structure (outcaps, 0);
@@ -2099,8 +2137,8 @@ gst_x264_enc_set_src_caps (GstX264Enc * encoder, GstCaps * caps)
   tags = gst_tag_list_new_empty ();
   gst_tag_list_add (tags, GST_TAG_MERGE_REPLACE, GST_TAG_ENCODER, "x264",
       GST_TAG_ENCODER_VERSION, X264_BUILD,
-      GST_TAG_MAXIMUM_BITRATE, encoder->bitrate * 1024,
-      GST_TAG_NOMINAL_BITRATE, encoder->bitrate * 1024, NULL);
+      GST_TAG_MAXIMUM_BITRATE, bitrate * 1024,
+      GST_TAG_NOMINAL_BITRATE, bitrate * 1024, NULL);
   gst_video_encoder_merge_tags (GST_VIDEO_ENCODER (encoder), tags,
       GST_TAG_MERGE_REPLACE);
   gst_tag_list_unref (tags);
@@ -2545,13 +2583,18 @@ gst_x264_enc_flush_frames (GstX264Enc * encoder, gboolean send)
 static void
 gst_x264_enc_reconfig (GstX264Enc * encoder)
 {
+  guint bitrate;
+
   if (!encoder->vtable)
     return;
 
+  bitrate =
+      gst_encoder_bitrate_profile_manager_get_bitrate (encoder->bitrate_manager,
+      encoder->input_state ? &encoder->input_state->info : NULL);
   switch (encoder->pass) {
     case GST_X264_ENC_PASS_QUAL:
       encoder->x264param.rc.f_rf_constant = encoder->quantizer;
-      encoder->x264param.rc.i_vbv_max_bitrate = encoder->bitrate;
+      encoder->x264param.rc.i_vbv_max_bitrate = bitrate;
       encoder->x264param.rc.i_vbv_buffer_size
           = encoder->x264param.rc.i_vbv_max_bitrate
           * encoder->vbv_buf_capacity / 1000;
@@ -2561,8 +2604,8 @@ gst_x264_enc_reconfig (GstX264Enc * encoder)
     case GST_X264_ENC_PASS_PASS2:
     case GST_X264_ENC_PASS_PASS3:
     default:
-      encoder->x264param.rc.i_bitrate = encoder->bitrate;
-      encoder->x264param.rc.i_vbv_max_bitrate = encoder->bitrate;
+      encoder->x264param.rc.i_bitrate = bitrate;
+      encoder->x264param.rc.i_vbv_max_bitrate = bitrate;
       encoder->x264param.rc.i_vbv_buffer_size
           = encoder->x264param.rc.i_vbv_max_bitrate
           * encoder->vbv_buf_capacity / 1000;
@@ -2601,7 +2644,8 @@ gst_x264_enc_set_property (GObject * object, guint prop_id,
       gst_x264_enc_reconfig (encoder);
       break;
     case ARG_BITRATE:
-      encoder->bitrate = g_value_get_uint (value);
+      gst_encoder_bitrate_profile_manager_set_bitrate (encoder->bitrate_manager,
+          g_value_get_uint (value));
       gst_x264_enc_reconfig (encoder);
       break;
     case ARG_VBV_BUF_CAPACITY:
@@ -2820,7 +2864,9 @@ gst_x264_enc_get_property (GObject * object, guint prop_id,
       g_value_set_boolean (value, encoder->byte_stream);
       break;
     case ARG_BITRATE:
-      g_value_set_uint (value, encoder->bitrate);
+      g_value_set_uint (value,
+          gst_encoder_bitrate_profile_manager_get_bitrate
+          (encoder->bitrate_manager, NULL));
       break;
     case ARG_INTRA_REFRESH:
       g_value_set_boolean (value, encoder->intra_refresh);
index c7de48cf37640d5c1be243b1618542bb2d765e3a..e1a091fcf5f337db3606d8de08ff31f27aad31ba 100644 (file)
@@ -25,6 +25,7 @@
 #include <gst/gst.h>
 #include <gst/video/video.h>
 #include <gst/video/gstvideoencoder.h>
+#include "gstencoderbitrateprofilemanager.h"
 
 #ifdef HAVE_STDINT_H
 #include <stdint.h>
@@ -126,6 +127,8 @@ struct _GstX264Enc
 
   /* cached values to set x264_picture_t */
   gint x264_nplanes;
+
+  GstEncoderBitrateProfileManager *bitrate_manager;
 };
 
 struct _GstX264EncClass
index 99a692028fe79cdfa17f2e7a423adee33a0c9590..fdbfeebce5c628af0e809a3d79040526881fa37c 100644 (file)
@@ -1,5 +1,6 @@
 x264_sources = [
   'gstx264enc.c',
+  'gstencoderbitrateprofilemanager.c',
 ]
 
 x264_dep = dependency('x264', required : get_option('x264'),