decodebin3: Avoid parsebin usage if not needed
authorEdward Hervey <edward@centricular.com>
Wed, 9 Nov 2022 09:26:41 +0000 (10:26 +0100)
committerGStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org>
Wed, 16 Nov 2022 14:01:46 +0000 (14:01 +0000)
If the incoming streams are already parsed, there is no need to add yet-another
parsebin to process it *IF* that stream is compatible with a decoder or the
decodebin3 output caps.

This only applies if all the following conditions are met:
* The incoming stream can *NOT* do pull-based scheduling
* The incoming stream provides a `GstStream` and `GstStreamCollection`
* The caps are compatible with either the decodebin3 output caps or a decoder
  input

If all those conditions are met, a identity element is used instead of a
parsebin element and the same code paths are taken.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2784>

subprojects/gst-plugins-base/gst/playback/gstdecodebin3-parse.c
subprojects/gst-plugins-base/gst/playback/gstdecodebin3.c

index 3af3d9e..55b97f0 100644 (file)
@@ -49,7 +49,7 @@ _custom_eos_quark_get (void)
   return g_quark;
 }
 
-/* Streams that come from parsebin */
+/* Streams that come from parsebin or identity */
 /* FIXME : All this is hardcoded. Switch to tree of chains */
 struct _DecodebinInputStream
 {
@@ -59,7 +59,7 @@ struct _DecodebinInputStream
 
   DecodebinInput *input;
 
-  GstPad *srcpad;               /* From parsebin */
+  GstPad *srcpad;               /* From parsebin or identity */
 
   /* id of the pad event probe */
   gulong output_event_probe_id;
@@ -540,23 +540,31 @@ parsebin_pad_added_cb (GstElement * demux, GstPad * pad, DecodebinInput * input)
   SELECTION_UNLOCK (dbin);
 }
 
+static DecodebinInputStream *
+find_input_stream_for_pad (GstDecodebin3 * dbin, GstPad * pad)
+{
+  GList *tmp;
+
+  for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
+    DecodebinInputStream *cand = (DecodebinInputStream *) tmp->data;
+    if (cand->srcpad == pad)
+      return cand;
+  }
+  return NULL;
+}
+
 static void
 parsebin_pad_removed_cb (GstElement * demux, GstPad * pad, DecodebinInput * inp)
 {
   GstDecodebin3 *dbin = inp->dbin;
   DecodebinInputStream *input = NULL;
   MultiQueueSlot *slot;
-  GList *tmp;
 
-  GST_DEBUG_OBJECT (pad, "removed");
+  if (!GST_PAD_IS_SRC (pad))
+    return;
 
-  for (tmp = dbin->input_streams; tmp; tmp = tmp->next) {
-    DecodebinInputStream *cand = (DecodebinInputStream *) tmp->data;
-    if (cand->srcpad == pad) {
-      input = cand;
-      break;
-    }
-  }
+  GST_DEBUG_OBJECT (pad, "removed");
+  input = find_input_stream_for_pad (dbin, pad);
   g_assert (input);
 
   /* If there are no pending pads, this means we will definitely not need this
index 099d228..cdf5e5c 100644 (file)
 /*
  * Global design
  *
- * 1) From sink pad to elementary streams (GstParseBin)
+ * 1) From sink pad to elementary streams (GstParseBin or identity)
+ *
+ * Note : If the incoming streams are push-based-only and are compatible with
+ * either the output caps or a potential decoder, the usage of parsebin is
+ * replaced by a simple passthrough identity element.
  *
  * The input sink pads are fed to GstParseBin. GstParseBin will feed them
  * through typefind. When the caps are detected (or changed) we recursively
@@ -293,7 +297,9 @@ struct _DecodebinInput
 
   guint group_id;
 
+  /* Either parsebin or identity is used */
   GstElement *parsebin;
+  GstElement *identity;
 
   gulong pad_added_sigid;
   gulong pad_removed_sigid;
@@ -860,16 +866,30 @@ static GstPadLinkReturn
 gst_decodebin3_input_pad_link (GstPad * pad, GstObject * parent, GstPad * peer)
 {
   GstDecodebin3 *dbin = (GstDecodebin3 *) parent;
+  GstQuery *query;
+  gboolean pull_mode = FALSE;
   GstPadLinkReturn res = GST_PAD_LINK_OK;
   DecodebinInput *input = g_object_get_data (G_OBJECT (pad), "decodebin.input");
 
   g_return_val_if_fail (input, GST_PAD_LINK_REFUSED);
 
-  GST_LOG_OBJECT (parent, "Got link on input pad %" GST_PTR_FORMAT
-      ". Creating parsebin if needed", pad);
+  GST_LOG_OBJECT (parent, "Got link on input pad %" GST_PTR_FORMAT, pad);
+
+  query = gst_query_new_scheduling ();
+  if (gst_pad_query (peer, query)
+      && gst_query_has_scheduling_mode_with_flags (query, GST_PAD_MODE_PULL,
+          GST_SCHEDULING_FLAG_SEEKABLE))
+    pull_mode = TRUE;
+  gst_query_unref (query);
 
+  GST_DEBUG_OBJECT (dbin, "Upstream can do pull-based : %d", pull_mode);
+
+  /* If upstream *can* do pull-based, we always use a parsebin. If not, we will
+   * delay that decision to a later stage (caps/stream/collection event
+   * processing) to figure out if one is really needed or whether an identity
+   * element will be enough */
   INPUT_LOCK (dbin);
-  if (!ensure_input_parsebin (dbin, input))
+  if (pull_mode && !ensure_input_parsebin (dbin, input))
     res = GST_PAD_LINK_REFUSED;
   INPUT_UNLOCK (dbin);
 
@@ -909,11 +929,6 @@ gst_decodebin3_input_pad_unlink (GstPad * pad, GstObject * parent)
       ". Removing parsebin.", pad);
 
   INPUT_LOCK (dbin);
-  if (input->parsebin == NULL ||
-      GST_OBJECT_PARENT (GST_OBJECT (input->parsebin)) != GST_OBJECT (dbin)) {
-    INPUT_UNLOCK (dbin);
-    return;
-  }
 
   /* Clear stream-collection corresponding to current INPUT and post new
    * stream-collection message, if needed */
@@ -944,26 +959,40 @@ gst_decodebin3_input_pad_unlink (GstPad * pad, GstObject * parent)
   msg =
       gst_message_new_stream_collection ((GstObject *) dbin, dbin->collection);
 
-  /* Drop duration queries that the application might be doing while this message is posted */
-  probe_id = gst_pad_add_probe (input->parsebin_sink,
-      GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
-      (GstPadProbeCallback) query_duration_drop_probe, input, NULL);
+  if (input->parsebin)
+    /* Drop duration queries that the application might be doing while this message is posted */
+    probe_id = gst_pad_add_probe (input->parsebin_sink,
+        GST_PAD_PROBE_TYPE_QUERY_UPSTREAM,
+        (GstPadProbeCallback) query_duration_drop_probe, input, NULL);
 
   SELECTION_UNLOCK (dbin);
   gst_element_post_message (GST_ELEMENT_CAST (dbin), msg);
   update_requested_selection (dbin);
 
-  gst_bin_remove (GST_BIN (dbin), input->parsebin);
-  gst_element_set_state (input->parsebin, GST_STATE_NULL);
-  g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid);
-  g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid);
-  g_signal_handler_disconnect (input->parsebin, input->drained_sigid);
-  gst_pad_remove_probe (input->parsebin_sink, probe_id);
-  gst_object_unref (input->parsebin);
-  gst_object_unref (input->parsebin_sink);
+  gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink), NULL);
+  if (input->parsebin) {
+    gst_bin_remove (GST_BIN (dbin), input->parsebin);
+    gst_element_set_state (input->parsebin, GST_STATE_NULL);
+    g_signal_handler_disconnect (input->parsebin, input->pad_removed_sigid);
+    g_signal_handler_disconnect (input->parsebin, input->pad_added_sigid);
+    g_signal_handler_disconnect (input->parsebin, input->drained_sigid);
+    gst_pad_remove_probe (input->parsebin_sink, probe_id);
+    gst_object_unref (input->parsebin);
+    gst_object_unref (input->parsebin_sink);
 
-  input->parsebin = NULL;
-  input->parsebin_sink = NULL;
+    input->parsebin = NULL;
+    input->parsebin_sink = NULL;
+  }
+  if (input->identity) {
+    GstPad *idpad = gst_element_get_static_pad (input->identity, "src");
+    DecodebinInputStream *stream = find_input_stream_for_pad (dbin, idpad);
+    gst_object_unref (idpad);
+    remove_input_stream (dbin, stream);
+    gst_element_set_state (input->identity, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (dbin), input->identity);
+    gst_object_unref (input->identity);
+    input->identity = NULL;
+  }
 
   if (!input->is_main) {
     dbin->other_inputs = g_list_remove (dbin->other_inputs, input);
@@ -990,6 +1019,14 @@ free_input (GstDecodebin3 * dbin, DecodebinInput * input)
     gst_object_unref (input->parsebin);
     gst_object_unref (input->parsebin_sink);
   }
+  if (input->identity) {
+    GstPad *idpad = gst_element_get_static_pad (input->identity, "src");
+    DecodebinInputStream *stream = find_input_stream_for_pad (dbin, idpad);
+    gst_object_unref (idpad);
+    remove_input_stream (dbin, stream);
+    gst_element_set_state (input->identity, GST_STATE_NULL);
+    gst_object_unref (input->identity);
+  }
   if (input->collection)
     gst_object_unref (input->collection);
   g_free (input);
@@ -1025,6 +1062,80 @@ sink_query_function (GstPad * sinkpad, GstDecodebin3 * dbin, GstQuery * query)
 }
 
 static gboolean
+is_parsebin_required_for_input (GstDecodebin3 * dbin, DecodebinInput * input,
+    GstCaps * newcaps, GstPad * sinkpad)
+{
+  gboolean parsebin_needed = TRUE;
+  GstStream *stream;
+
+  stream = gst_pad_get_stream (sinkpad);
+
+  if (stream == NULL) {
+    /* If upstream didn't provide a `GstStream` we will need to create a
+     * parsebin to handle that stream */
+    GST_DEBUG_OBJECT (sinkpad,
+        "Need to create parsebin since upstream doesn't provide GstStream");
+  } else if (gst_caps_can_intersect (newcaps, dbin->caps)) {
+    /* If the incoming caps match decodebin3 output, no processing is needed */
+    GST_FIXME_OBJECT (sinkpad, "parsebin not needed (matches output caps) !");
+    parsebin_needed = FALSE;
+  } else {
+    GList *decoder_list;
+    /* If the incoming caps are compatible with a decoder, we don't need to
+     * process it before */
+    g_mutex_lock (&dbin->factories_lock);
+    gst_decode_bin_update_factories_list (dbin);
+    decoder_list =
+        gst_element_factory_list_filter (dbin->decoder_factories, newcaps,
+        GST_PAD_SINK, TRUE);
+    g_mutex_unlock (&dbin->factories_lock);
+    if (decoder_list) {
+      GST_FIXME_OBJECT (sinkpad, "parsebin not needed (available decoders) !");
+      gst_plugin_feature_list_free (decoder_list);
+      parsebin_needed = FALSE;
+    }
+  }
+  if (stream)
+    gst_object_unref (stream);
+
+  return parsebin_needed;
+}
+
+static void
+setup_identify_for_input (GstDecodebin3 * dbin, DecodebinInput * input,
+    GstPad * sinkpad)
+{
+  GstPad *idsrc, *idsink;
+  DecodebinInputStream *inputstream;
+
+  GST_DEBUG_OBJECT (sinkpad, "Adding identity for new input stream");
+
+  input->identity = gst_element_factory_make ("identity", NULL);
+  /* We drop allocation queries due to our usage of multiqueue just
+   * afterwards. It is just too dangerous.
+   *
+   * If application users want to have optimal raw source <=> sink allocations
+   * they should not use decodebin3
+   */
+  g_object_set (input->identity, "drop-allocation", TRUE, NULL);
+  input->identity = gst_object_ref (input->identity);
+  idsink = gst_element_get_static_pad (input->identity, "sink");
+  idsrc = gst_element_get_static_pad (input->identity, "src");
+  gst_bin_add (GST_BIN (dbin), input->identity);
+
+  SELECTION_LOCK (dbin);
+  inputstream = create_input_stream (dbin, idsrc, input);
+  /* Forward any existing GstStream directly on the input stream */
+  inputstream->active_stream = gst_pad_get_stream (sinkpad);
+  SELECTION_UNLOCK (dbin);
+
+  gst_object_unref (idsrc);
+  gst_object_unref (idsink);
+  gst_ghost_pad_set_target (GST_GHOST_PAD (input->ghost_sink), idsink);
+  gst_element_sync_state_with_parent (input->identity);
+}
+
+static gboolean
 sink_event_function (GstPad * sinkpad, GstDecodebin3 * dbin, GstEvent * event)
 {
   DecodebinInput *input =
@@ -1059,6 +1170,7 @@ sink_event_function (GstPad * sinkpad, GstDecodebin3 * dbin, GstEvent * event)
     case GST_EVENT_STREAM_COLLECTION:
     {
       GstStreamCollection *collection = NULL;
+
       gst_event_parse_stream_collection (event, &collection);
       if (collection) {
         INPUT_LOCK (dbin);
@@ -1078,30 +1190,58 @@ sink_event_function (GstPad * sinkpad, GstDecodebin3 * dbin, GstEvent * event)
         } else
           SELECTION_UNLOCK (dbin);
       }
+
+      /* If we are waiting to create an identity passthrough, do it now */
+      if (!input->parsebin && !input->identity)
+        setup_identify_for_input (dbin, input, sinkpad);
       break;
     }
     case GST_EVENT_CAPS:
     {
+      GstCaps *newcaps = NULL;
+
+      gst_event_parse_caps (event, &newcaps);
+      if (!newcaps)
+        break;
+      GST_DEBUG_OBJECT (sinkpad, "new caps %" GST_PTR_FORMAT, newcaps);
+
+      /* No parsebin or identity present, check if we can avoid creating one */
+      if (!input->parsebin && !input->identity) {
+        if (is_parsebin_required_for_input (dbin, input, newcaps, sinkpad)) {
+          GST_DEBUG_OBJECT (sinkpad, "parsebin is required for input");
+          ensure_input_parsebin (dbin, input);
+          break;
+        }
+        GST_DEBUG_OBJECT (sinkpad,
+            "parsebin not required. Will create identity passthrough element once we get the collection");
+        break;
+      }
+
+      if (input->identity) {
+        if (is_parsebin_required_for_input (dbin, input, newcaps, sinkpad)) {
+          GST_ERROR_OBJECT (sinkpad,
+              "Switching from passthrough to parsebin on inputs is not supported !");
+          gst_event_unref (event);
+          return FALSE;
+        }
+        /* Nothing else to do here */
+        break;
+      }
+
+      /* Check if the parsebin present can handle the new caps */
+      g_assert (input->parsebin);
       GST_DEBUG_OBJECT (sinkpad,
           "New caps, checking if they are compatible with existing parsebin");
-      if (input->parsebin_sink) {
-        GstCaps *newcaps = NULL;
-
-        gst_event_parse_caps (event, &newcaps);
-        GST_DEBUG_OBJECT (sinkpad, "new caps %" GST_PTR_FORMAT, newcaps);
-
-        if (newcaps
-            && !gst_pad_query_accept_caps (input->parsebin_sink, newcaps)) {
-          GST_DEBUG_OBJECT (sinkpad,
-              "Parsebin doesn't accept the new caps %" GST_PTR_FORMAT, newcaps);
-          GST_STATE_LOCK (dbin);
-          /* Reset parsebin so that it reconfigures itself for the new stream format */
-          gst_element_set_state (input->parsebin, GST_STATE_NULL);
-          gst_element_sync_state_with_parent (input->parsebin);
-          GST_STATE_UNLOCK (dbin);
-        } else {
-          GST_DEBUG_OBJECT (sinkpad, "Parsebin accepts new caps");
-        }
+      if (!gst_pad_query_accept_caps (input->parsebin_sink, newcaps)) {
+        GST_DEBUG_OBJECT (sinkpad,
+            "Parsebin doesn't accept the new caps %" GST_PTR_FORMAT, newcaps);
+        GST_STATE_LOCK (dbin);
+        /* Reset parsebin so that it reconfigures itself for the new stream format */
+        gst_element_set_state (input->parsebin, GST_STATE_NULL);
+        gst_element_sync_state_with_parent (input->parsebin);
+        GST_STATE_UNLOCK (dbin);
+      } else {
+        GST_DEBUG_OBJECT (sinkpad, "Parsebin accepts new caps");
       }
       break;
     }