switchbin: Initial checkin
authorJan Schmidt <jan@centricular.com>
Mon, 4 Nov 2019 14:40:04 +0000 (01:40 +1100)
committerJan Schmidt <thaytan@noraisin.net>
Wed, 13 Nov 2019 10:15:31 +0000 (10:15 +0000)
Add code from Stream Unlimited implementing a bin
which switches between different internal decoding/processing
chains based on input caps

gst/meson.build
gst/switchbin/gstswitchbin.c [new file with mode: 0644]
gst/switchbin/gstswitchbin.h [new file with mode: 0644]
gst/switchbin/meson.build [new file with mode: 0644]
gst/switchbin/plugin.c [new file with mode: 0644]
meson_options.txt

index 325492b..57d8fc1 100644 (file)
@@ -9,7 +9,7 @@ foreach plugin : ['accurip', 'adpcmdec', 'adpcmenc', 'aiff', 'asfmux',
                   'midi', 'mpegdemux', 'mpegpsmux', 'mpegtsdemux', 'mpegtsmux',
                   'mxf', 'netsim', 'onvif', 'pcapparse', 'pnm', 'proxy',
                   'rawparse', 'removesilence', 'rist', 'rtmp2', 'rtp', 'sdp',
-                  'segmentclip', 'siren', 'smooth', 'speed', 'subenc',
+                  'segmentclip', 'siren', 'smooth', 'speed', 'subenc', 'switchbin',
                   'timecode', 'transcode', 'videofilters',
                   'videoframe_audiolevel', 'videoparsers', 'videosignal',
                   'vmnc', 'y4m', 'yadif']
diff --git a/gst/switchbin/gstswitchbin.c b/gst/switchbin/gstswitchbin.c
new file mode 100644 (file)
index 0000000..ef160f2
--- /dev/null
@@ -0,0 +1,1062 @@
+/* switchbin
+ * Copyright (C) 2016  Carlos Rafael Giani
+ *
+ * gstswitchbin.c: Element for switching between paths based on input caps
+ *
+ * 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 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#include <string.h>
+#include <stdio.h>
+#include "gstswitchbin.h"
+
+
+GST_DEBUG_CATEGORY (switch_bin_debug);
+#define GST_CAT_DEFAULT switch_bin_debug
+
+
+#define PATH_LOCK(obj) g_mutex_lock(&(((GstSwitchBin *)(obj))->path_mutex))
+#define PATH_UNLOCK(obj) g_mutex_unlock(&(((GstSwitchBin *)(obj))->path_mutex))
+
+
+enum
+{
+  PROP_0,
+  PROP_NUM_PATHS
+};
+
+
+#define DEFAULT_NUM_PATHS 0
+
+
+static GstStaticPadTemplate static_sink_template =
+GST_STATIC_PAD_TEMPLATE ("sink",
+    GST_PAD_SINK,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+
+static GstStaticPadTemplate static_src_template =
+GST_STATIC_PAD_TEMPLATE ("src",
+    GST_PAD_SRC,
+    GST_PAD_ALWAYS,
+    GST_STATIC_CAPS_ANY);
+
+
+
+
+static void gst_switch_bin_child_proxy_iface_init (gpointer iface,
+    gpointer iface_data);
+static GObject *gst_switch_bin_child_proxy_get_child_by_index (GstChildProxy *
+    child_proxy, guint index);
+static guint gst_switch_bin_child_proxy_get_children_count (GstChildProxy *
+    child_proxy);
+
+
+G_DEFINE_TYPE_WITH_CODE (GstSwitchBin,
+    gst_switch_bin,
+    GST_TYPE_BIN,
+    G_IMPLEMENT_INTERFACE (GST_TYPE_CHILD_PROXY,
+        gst_switch_bin_child_proxy_iface_init)
+    );
+
+
+
+static void gst_switch_bin_finalize (GObject * object);
+static void gst_switch_bin_set_property (GObject * object, guint prop_id,
+    GValue const *value, GParamSpec * pspec);
+static void gst_switch_bin_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static gboolean gst_switch_bin_sink_event (GstPad * pad,
+    GstObject * parent, GstEvent * event);
+static gboolean gst_switch_bin_sink_query (GstPad * pad,
+    GstObject * parent, GstQuery * query);
+static gboolean gst_switch_bin_src_query (GstPad * pad, GstObject * parent,
+    GstQuery * query);
+
+static gboolean gst_switch_bin_set_num_paths (GstSwitchBin * switch_bin,
+    guint new_num_paths);
+static gboolean gst_switch_bin_select_path_for_caps (GstSwitchBin *
+    switch_bin, GstCaps * caps);
+static gboolean gst_switch_bin_switch_to_path (GstSwitchBin * switch_bin,
+    GstSwitchBinPath * switch_bin_path);
+static GstSwitchBinPath *gst_switch_bin_find_matching_path (GstSwitchBin *
+    switch_bin, GstCaps const *caps);
+
+static void gst_switch_bin_set_sinkpad_block (GstSwitchBin * switch_bin,
+    gboolean do_block);
+static GstPadProbeReturn gst_switch_bin_blocking_pad_probe (GstPad * pad,
+    GstPadProbeInfo * info, gpointer user_data);
+
+static GstCaps *gst_switch_bin_get_allowed_caps (GstSwitchBin * switch_bin,
+    gchar const *pad_name, GstCaps * filter);
+static gboolean gst_switch_bin_are_caps_acceptable (GstSwitchBin *
+    switch_bin, GstCaps const *caps);
+
+
+
+static void
+gst_switch_bin_child_proxy_iface_init (gpointer iface,
+    G_GNUC_UNUSED gpointer iface_data)
+{
+  GstChildProxyInterface *child_proxy_iface = iface;
+
+  child_proxy_iface->get_child_by_index =
+      GST_DEBUG_FUNCPTR (gst_switch_bin_child_proxy_get_child_by_index);
+  child_proxy_iface->get_children_count =
+      GST_DEBUG_FUNCPTR (gst_switch_bin_child_proxy_get_children_count);
+}
+
+
+static GObject *
+gst_switch_bin_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
+    guint index)
+{
+  GObject *result;
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (child_proxy);
+
+  PATH_LOCK (switch_bin);
+
+  if (G_UNLIKELY (index >= switch_bin->num_paths))
+    result = NULL;
+  else
+    result = g_object_ref (G_OBJECT (switch_bin->paths[index]));
+
+  PATH_UNLOCK (switch_bin);
+
+  return result;
+}
+
+
+static guint
+gst_switch_bin_child_proxy_get_children_count (GstChildProxy * child_proxy)
+{
+  guint result;
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (child_proxy);
+
+  PATH_LOCK (switch_bin);
+  result = switch_bin->num_paths;
+  PATH_UNLOCK (switch_bin);
+
+  return result;
+}
+
+
+
+static void
+gst_switch_bin_class_init (GstSwitchBinClass * klass)
+{
+  GObjectClass *object_class;
+  GstElementClass *element_class;
+
+  GST_DEBUG_CATEGORY_INIT (switch_bin_debug, "switchbin", 0, "switch bin");
+
+  object_class = G_OBJECT_CLASS (klass);
+  element_class = GST_ELEMENT_CLASS (klass);
+
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&static_sink_template));
+  gst_element_class_add_pad_template (element_class,
+      gst_static_pad_template_get (&static_src_template));
+
+  object_class->finalize = GST_DEBUG_FUNCPTR (gst_switch_bin_finalize);
+  object_class->set_property = GST_DEBUG_FUNCPTR (gst_switch_bin_set_property);
+  object_class->get_property = GST_DEBUG_FUNCPTR (gst_switch_bin_get_property);
+
+  g_object_class_install_property (object_class,
+      PROP_NUM_PATHS,
+      g_param_spec_uint ("num-paths",
+          "Number of paths",
+          "Number of paths",
+          0, G_MAXUINT,
+          DEFAULT_NUM_PATHS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+
+  gst_element_class_set_static_metadata (element_class,
+      "switchbin",
+      "Generic/Bin",
+      "Switch between sub-pipelines (paths) based on input caps",
+      "Carlos Rafael Giani <dv@pseudoterminal.org>");
+}
+
+
+static void
+gst_switch_bin_init (GstSwitchBin * switch_bin)
+{
+  GstPad *pad;
+
+  switch_bin->num_paths = DEFAULT_NUM_PATHS;
+  switch_bin->paths = NULL;
+  switch_bin->current_path = NULL;
+  switch_bin->last_stream_start = NULL;
+  switch_bin->blocking_probe_id = 0;
+  switch_bin->drop_probe_id = 0;
+  switch_bin->last_caps = NULL;
+
+  switch_bin->sinkpad = gst_ghost_pad_new_no_target_from_template ("sink",
+      gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (switch_bin),
+          "sink")
+      );
+  gst_element_add_pad (GST_ELEMENT (switch_bin), switch_bin->sinkpad);
+
+  switch_bin->srcpad = gst_ghost_pad_new_no_target_from_template ("src",
+      gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (switch_bin),
+          "src")
+      );
+  gst_element_add_pad (GST_ELEMENT (switch_bin), switch_bin->srcpad);
+
+  gst_pad_set_event_function (switch_bin->sinkpad, gst_switch_bin_sink_event);
+  gst_pad_set_query_function (switch_bin->sinkpad, gst_switch_bin_sink_query);
+  gst_pad_set_query_function (switch_bin->srcpad, gst_switch_bin_src_query);
+
+  switch_bin->input_identity =
+      gst_element_factory_make ("identity", "input-identity");
+
+  gst_bin_add (GST_BIN (switch_bin), switch_bin->input_identity);
+  pad = gst_element_get_static_pad (switch_bin->input_identity, "sink");
+  gst_ghost_pad_set_target (GST_GHOST_PAD (switch_bin->sinkpad), pad);
+  gst_object_unref (GST_OBJECT (pad));
+}
+
+static void
+gst_switch_bin_finalize (GObject * object)
+{
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (object);
+
+  if (switch_bin->last_caps != NULL)
+    gst_caps_unref (switch_bin->last_caps);
+  if (switch_bin->last_stream_start != NULL)
+    gst_event_unref (switch_bin->last_stream_start);
+
+  g_free (switch_bin->paths);
+
+  G_OBJECT_CLASS (gst_switch_bin_parent_class)->finalize (object);
+}
+
+
+static void
+gst_switch_bin_set_property (GObject * object, guint prop_id,
+    GValue const *value, GParamSpec * pspec)
+{
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (object);
+  switch (prop_id) {
+    case PROP_NUM_PATHS:
+      PATH_LOCK (switch_bin);
+      gst_switch_bin_set_num_paths (switch_bin, g_value_get_uint (value));
+      PATH_UNLOCK (switch_bin);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+gst_switch_bin_get_property (GObject * object, guint prop_id, GValue * value,
+    GParamSpec * pspec)
+{
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (object);
+  switch (prop_id) {
+    case PROP_NUM_PATHS:
+      PATH_LOCK (switch_bin);
+      g_value_set_uint (value, switch_bin->num_paths);
+      PATH_UNLOCK (switch_bin);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static gboolean
+gst_switch_bin_sink_event (GstPad * pad, GstObject * parent, GstEvent * event)
+{
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (parent);
+
+  switch (GST_EVENT_TYPE (event)) {
+    case GST_EVENT_STREAM_START:
+    {
+      GST_DEBUG_OBJECT (switch_bin,
+          "stream-start event observed; copying it for later use");
+      gst_event_replace (&(switch_bin->last_stream_start), event);
+
+      return gst_pad_event_default (pad, parent, event);
+    }
+
+    case GST_EVENT_CAPS:
+    {
+      /* Intercept the caps event to switch to an appropriate path, then
+       * resume default caps event processing */
+
+      GstCaps *caps;
+      gboolean ret;
+
+      gst_event_parse_caps (event, &caps);
+      GST_DEBUG_OBJECT (switch_bin,
+          "sink pad got caps event with caps %" GST_PTR_FORMAT
+          " ; looking for matching path", (gpointer) caps);
+
+      PATH_LOCK (switch_bin);
+      ret = gst_switch_bin_select_path_for_caps (switch_bin, caps);
+      PATH_UNLOCK (switch_bin);
+
+      if (!ret) {
+        gst_event_unref (event);
+        return FALSE;
+      } else
+        return gst_pad_event_default (pad, parent, event);
+    }
+
+    default:
+      GST_DEBUG_OBJECT (switch_bin, "sink event: %s",
+          gst_event_type_get_name (GST_EVENT_TYPE (event)));
+      return gst_pad_event_default (pad, parent, event);
+  }
+}
+
+
+static gboolean
+gst_switch_bin_handle_query (GstPad * pad, GstObject * parent, GstQuery * query,
+    char const *pad_name)
+{
+  GstSwitchBin *switch_bin = GST_SWITCH_BIN (parent);
+
+  switch (GST_QUERY_TYPE (query)) {
+    case GST_QUERY_CAPS:
+    {
+      GstCaps *filter, *caps;
+
+      gst_query_parse_caps (query, &filter);
+
+      PATH_LOCK (switch_bin);
+
+      if (switch_bin->num_paths == 0) {
+        /* No paths exist - cannot return any caps */
+        caps = NULL;
+      } else if ((switch_bin->current_path == NULL)
+          || (switch_bin->current_path->element == NULL)) {
+        /* Paths exist, but there is no current path (or the path is a dropping path,
+         * so no element exists) - just return all allowed caps */
+        caps = gst_switch_bin_get_allowed_caps (switch_bin, pad_name, filter);
+      } else {
+        /* Paths exist and there is a current path
+         * Forward the query to its element */
+
+        GstQuery *caps_query = gst_query_new_caps (NULL);
+        GstPad *element_pad =
+            gst_element_get_static_pad (switch_bin->current_path->element,
+            pad_name);
+
+        caps = NULL;
+        if (gst_pad_query (element_pad, caps_query)) {
+          GstCaps *query_caps;
+          gst_query_parse_caps_result (caps_query, &query_caps);
+          caps = gst_caps_copy (query_caps);
+        }
+
+        gst_query_unref (caps_query);
+        gst_object_unref (GST_OBJECT (element_pad));
+      }
+
+
+      PATH_UNLOCK (switch_bin);
+
+      if (caps != NULL) {
+        GST_DEBUG_OBJECT (switch_bin, "%s caps query:  caps: %" GST_PTR_FORMAT,
+            pad_name, (gpointer) caps);
+        gst_query_set_caps_result (query, caps);
+        gst_caps_unref (caps);
+        return TRUE;
+      } else
+        return FALSE;
+    }
+
+    case GST_QUERY_ACCEPT_CAPS:
+    {
+      GstCaps *caps;
+      gboolean acceptable;
+
+      gst_query_parse_accept_caps (query, &caps);
+      PATH_LOCK (switch_bin);
+      acceptable = gst_switch_bin_are_caps_acceptable (switch_bin, caps);
+      PATH_UNLOCK (switch_bin);
+      GST_DEBUG_OBJECT (switch_bin,
+          "%s accept_caps query:  acceptable: %d  caps: %" GST_PTR_FORMAT,
+          pad_name, (gint) acceptable, (gpointer) caps);
+
+      gst_query_set_accept_caps_result (query, acceptable);
+      return TRUE;
+    }
+
+    default:
+      return gst_pad_query_default (pad, parent, query);
+  }
+}
+
+
+static gboolean
+gst_switch_bin_sink_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+  return gst_switch_bin_handle_query (pad, parent, query, "sink");
+}
+
+
+static gboolean
+gst_switch_bin_src_query (GstPad * pad, GstObject * parent, GstQuery * query)
+{
+  return gst_switch_bin_handle_query (pad, parent, query, "src");
+}
+
+
+static gboolean
+gst_switch_bin_set_num_paths (GstSwitchBin * switch_bin, guint new_num_paths)
+{
+  guint i;
+  gboolean cur_path_removed = FALSE;
+
+  /* must be called with path lock held */
+
+  if (switch_bin->num_paths == new_num_paths) {
+    GST_DEBUG_OBJECT (switch_bin,
+        "no change in number of paths - ignoring call");
+    return TRUE;
+  } else if (switch_bin->num_paths < new_num_paths) {
+    /* New number of paths is larger -> N new paths need to be created & added,
+     * where N = new_num_paths - switch_bin->num_paths. */
+
+    GST_DEBUG_OBJECT (switch_bin, "adding %u new paths",
+        new_num_paths - switch_bin->num_paths);
+
+    switch_bin->paths =
+        g_realloc (switch_bin->paths, sizeof (GstObject *) * new_num_paths);
+
+    for (i = switch_bin->num_paths; i < new_num_paths; ++i) {
+      GstSwitchBinPath *path;
+      gchar *path_name;
+
+      /* default names would be something like "switchbinpath0" */
+      path_name = g_strdup_printf ("path%u", i);
+      switch_bin->paths[i] = path =
+          g_object_new (gst_switch_bin_path_get_type (), "name", path_name,
+          NULL);
+      path->bin = switch_bin;
+
+      gst_object_set_parent (GST_OBJECT (path), GST_OBJECT (switch_bin));
+      gst_child_proxy_child_added (GST_CHILD_PROXY (switch_bin),
+          G_OBJECT (path), path_name);
+
+      GST_DEBUG_OBJECT (switch_bin, "added path #%u \"%s\" (%p)", i, path_name,
+          (gpointer) path);
+      g_free (path_name);
+    }
+  } else {
+    /* New number of paths is smaller -> the last N paths need to be removed,
+     * where N = switch_bin->num_paths - new_num_paths. If one of the paths
+     * that are being removed is the current path, then a new current path
+     * is selected. */
+    gchar *path_name;
+
+    GST_DEBUG_OBJECT (switch_bin, "removing the last %u paths",
+        switch_bin->num_paths - new_num_paths);
+
+    for (i = new_num_paths; i < switch_bin->num_paths; ++i) {
+      GstSwitchBinPath *path = switch_bin->paths[i];
+      path_name = g_strdup (GST_OBJECT_NAME (path));
+
+      if (path == switch_bin->current_path) {
+        cur_path_removed = TRUE;
+        gst_switch_bin_switch_to_path (switch_bin, NULL);
+
+        GST_DEBUG_OBJECT (switch_bin,
+            "path #%u \"%s\" (%p) is the current path - selecting a new current path will be necessary",
+            i, path_name, (gpointer) (switch_bin->paths[i]));
+      }
+
+      gst_child_proxy_child_removed (GST_CHILD_PROXY (switch_bin),
+          G_OBJECT (path), path_name);
+      gst_object_unparent (GST_OBJECT (switch_bin->paths[i]));
+
+      GST_DEBUG_OBJECT (switch_bin, "removed path #%u \"%s\" (%p)", i,
+          path_name, (gpointer) (switch_bin->paths[i]));
+      g_free (path_name);
+    }
+
+    if (new_num_paths != 0)
+      switch_bin->paths =
+          g_realloc (switch_bin->paths, sizeof (GstObject *) * new_num_paths);
+    else
+      switch_bin->paths = 0;
+  }
+
+  switch_bin->num_paths = new_num_paths;
+
+  if (new_num_paths > 0) {
+    if (cur_path_removed) {
+      /* Select a new current path if the previous one was removed above */
+
+      if (switch_bin->last_caps != NULL) {
+        GST_DEBUG_OBJECT (switch_bin,
+            "current path was removed earlier - need to select a new one based on the last caps %"
+            GST_PTR_FORMAT, (gpointer) (switch_bin->last_caps));
+        return gst_switch_bin_select_path_for_caps (switch_bin,
+            switch_bin->last_caps);
+      } else {
+        /* This should not happen. Every time a current path is selected, the
+         * caps that were used for the selection are copied as the last_caps.
+         * So, if a current path exists, but last_caps is NULL, it indicates
+         * a bug. For example, if the current path was selected without calling
+         * gst_switch_bin_select_path_for_caps(). */
+        g_assert_not_reached ();
+        return FALSE;           /* shuts up compiler warning */
+      }
+    } else
+      return TRUE;
+  } else
+    return gst_switch_bin_switch_to_path (switch_bin, NULL);
+}
+
+
+static gboolean
+gst_switch_bin_select_path_for_caps (GstSwitchBin * switch_bin, GstCaps * caps)
+{
+  /* must be called with path lock held */
+
+  gboolean ret;
+  GstSwitchBinPath *path;
+
+  path = gst_switch_bin_find_matching_path (switch_bin, caps);
+  if (path == NULL) {
+    /* No matching path found, the caps are incompatible. Report this and exit. */
+
+    GST_ELEMENT_ERROR (switch_bin, STREAM, WRONG_TYPE,
+        ("could not find compatible path"), ("sink caps: %" GST_PTR_FORMAT,
+            (gpointer) caps));
+    ret = FALSE;
+  } else {
+    /* Matching path found. Try to switch to it. */
+
+    GST_DEBUG_OBJECT (switch_bin, "found matching path \"%s\" (%p) - switching",
+        GST_OBJECT_NAME (path), (gpointer) path);
+    ret = gst_switch_bin_switch_to_path (switch_bin, path);
+  }
+
+  if (ret && (caps != switch_bin->last_caps))
+    gst_caps_replace (&(switch_bin->last_caps), caps);
+
+  return ret;
+}
+
+
+static gboolean
+gst_switch_bin_switch_to_path (GstSwitchBin * switch_bin,
+    GstSwitchBinPath * switch_bin_path)
+{
+  /* must be called with path lock held */
+
+  gboolean ret = TRUE;
+
+  if (switch_bin_path != NULL)
+    GST_DEBUG_OBJECT (switch_bin, "switching to path \"%s\" (%p)",
+        GST_OBJECT_NAME (switch_bin_path), (gpointer) switch_bin_path);
+  else
+    GST_DEBUG_OBJECT (switch_bin,
+        "switching to NULL path (= disabling current path)");
+
+  /* No current path set and no path is to be set -> nothing to do */
+  if ((switch_bin_path == NULL) && (switch_bin->current_path == NULL))
+    return TRUE;
+
+  /* If this path is already the current one, do nothing */
+  if (switch_bin->current_path == switch_bin_path)
+    return TRUE;
+
+  /* Block incoming data to be able to safely switch */
+  gst_switch_bin_set_sinkpad_block (switch_bin, TRUE);
+
+  /* Unlink the current path's element (if there is a current path) */
+  if (switch_bin->current_path != NULL) {
+    GstSwitchBinPath *cur_path = switch_bin->current_path;
+
+    if (cur_path->element != NULL) {
+      gst_element_set_state (cur_path->element, GST_STATE_NULL);
+      gst_element_unlink (switch_bin->input_identity, cur_path->element);
+    }
+
+    gst_ghost_pad_set_target (GST_GHOST_PAD (switch_bin->srcpad), NULL);
+
+    switch_bin->current_path = NULL;
+  }
+
+  /* Link the new path's element (if a new path is specified) */
+  if (switch_bin_path != NULL) {
+    if (switch_bin_path->element != NULL) {
+      GstPad *pad;
+
+      /* There is a path element. Link it into the pipeline. Data passes through
+       * it now, since its associated path just became the current one. */
+
+      /* TODO: currently, only elements with one "src" "sink" always-pad are supported;
+       * add support for request and sometimes pads */
+
+      pad = gst_element_get_static_pad (switch_bin_path->element, "src");
+      if (pad == NULL) {
+        GST_ERROR_OBJECT (switch_bin,
+            "path element has no static srcpad - cannot link");
+        ret = FALSE;
+        goto finish;
+      }
+
+      if (!gst_ghost_pad_set_target (GST_GHOST_PAD (switch_bin->srcpad), pad)) {
+        GST_ERROR_OBJECT (switch_bin,
+            "could not set the path element's srcpad as the ghost srcpad's target");
+        ret = FALSE;
+      }
+
+      gst_object_unref (GST_OBJECT (pad));
+
+      if (!ret)
+        goto finish;
+
+      if (!gst_element_link (switch_bin->input_identity,
+              switch_bin_path->element)) {
+        GST_ERROR_OBJECT (switch_bin,
+            "linking the path element's sinkpad failed ; check if the path element's sink caps and the upstream elements connected to the switchbin's sinkpad match");
+        ret = FALSE;
+        goto finish;
+      }
+
+      /* Unlock the element's state in case it was locked earlier
+       * so its state can be synced to the switchbin's */
+      gst_element_set_locked_state (switch_bin_path->element, FALSE);
+      if (!gst_element_sync_state_with_parent (switch_bin_path->element)) {
+        GST_ERROR_OBJECT (switch_bin,
+            "could not sync the path element's state with that of the switchbin");
+        ret = FALSE;
+        goto finish;
+      }
+    } else {
+      GstPad *srcpad;
+
+      /* There is no path element. This will probably yield an error
+       * into the pipeline unless we're shutting down */
+      GST_DEBUG_OBJECT (switch_bin, "path has no element ; will drop data");
+
+      srcpad = gst_element_get_static_pad (switch_bin->input_identity, "src");
+
+      g_assert (srcpad != NULL);
+
+      if (!gst_ghost_pad_set_target (GST_GHOST_PAD (switch_bin->srcpad),
+              srcpad)) {
+        GST_ERROR_OBJECT (switch_bin,
+            "could not set the path element's srcpad as the ghost srcpad's target");
+        ret = FALSE;
+      }
+
+      GST_DEBUG_OBJECT (switch_bin,
+          "pushing stream-start downstream before disabling");
+      gst_element_send_event (switch_bin->input_identity,
+          gst_event_ref (switch_bin->last_stream_start));
+
+      gst_object_unref (GST_OBJECT (srcpad));
+    }
+  }
+
+  switch_bin->current_path = switch_bin_path;
+
+  /* If there is a new path to use, unblock the input */
+  if (switch_bin_path != NULL)
+    gst_switch_bin_set_sinkpad_block (switch_bin, FALSE);
+
+finish:
+  return ret;
+}
+
+
+static GstSwitchBinPath *
+gst_switch_bin_find_matching_path (GstSwitchBin * switch_bin,
+    GstCaps const *caps)
+{
+  /* must be called with path lock held */
+
+  guint i;
+
+  for (i = 0; i < switch_bin->num_paths; ++i) {
+    GstSwitchBinPath *path = switch_bin->paths[i];
+    if (gst_caps_can_intersect (caps, path->caps))
+      return path;
+  }
+
+  return NULL;
+}
+
+
+static GstCaps *
+gst_switch_bin_get_allowed_caps (GstSwitchBin * switch_bin,
+    G_GNUC_UNUSED gchar const *pad_name, GstCaps * filter)
+{
+  /* must be called with path lock held */
+
+  guint i;
+  GstCaps *total_path_caps;
+
+  /* The allowed caps are a combination of the caps of all paths, the
+   * filter caps, and the allowed caps as indicated by the result
+   * of the CAPS query on the current path's element.
+   * Since the CAPS query result can be influenced by an element's
+   * current state and link to other elements, the non-current
+   * path elements are not queried.
+   *
+   * In theory, it would be enough to just append all path caps. However,
+   * to refine this a bit further, in case of the current path, the
+   * path caps are first intersected with the result of the CAPS query.
+   * This narrows down the acceptable caps for this current path,
+   * hopefully providing better quality caps. */
+
+  if (switch_bin->num_paths == 0) {
+    /* No paths exist, so nothing can be returned */
+    GST_ELEMENT_ERROR (switch_bin, STREAM, FAILED, ("no paths defined"),
+        ("there must be at least one path in order for switchbin to do anything"));
+    return NULL;
+  }
+
+  total_path_caps = gst_caps_new_empty ();
+
+  for (i = 0; i < switch_bin->num_paths; ++i) {
+    GstSwitchBinPath *path = switch_bin->paths[i];
+
+    if ((path->element != NULL) && (path == switch_bin->current_path)) {
+      GstPad *pad;
+      GstCaps *caps, *intersected_caps;
+      GstQuery *caps_query = NULL;
+
+      pad = gst_element_get_static_pad (path->element, pad_name);
+      caps_query = gst_query_new_caps (NULL);
+
+      /* Query the path element for allowed caps. If this is
+       * successful, intersect the returned caps with the path caps,
+       * and append the result of the intersection to the total_path_caps. */
+      if (gst_pad_query (pad, caps_query)) {
+        gst_query_parse_caps_result (caps_query, &caps);
+        intersected_caps = gst_caps_intersect (caps, path->caps);
+        gst_caps_append (total_path_caps, intersected_caps);
+      } else
+        gst_caps_append (total_path_caps, gst_caps_ref (path->caps));
+
+      gst_object_unref (GST_OBJECT (pad));
+      gst_query_unref (caps_query);
+    } else {
+      /* Either this is the current path and it has no element (= is a dropping path),
+       * or it is not the current path. In both cases, no caps query can be performed.
+       * Just append the path caps then. */
+      gst_caps_append (total_path_caps, gst_caps_ref (path->caps));
+    }
+  }
+
+  /* Apply filter caps if present */
+  if (filter != NULL) {
+    GstCaps *tmp_caps = total_path_caps;
+    total_path_caps = gst_caps_intersect (tmp_caps, filter);
+    gst_caps_unref (tmp_caps);
+  }
+
+  return total_path_caps;
+}
+
+
+static gboolean
+gst_switch_bin_are_caps_acceptable (GstSwitchBin * switch_bin,
+    GstCaps const *caps)
+{
+  /* must be called with path lock held */
+
+  return (gst_switch_bin_find_matching_path (switch_bin, caps) != NULL);
+}
+
+
+static void
+gst_switch_bin_set_sinkpad_block (GstSwitchBin * switch_bin, gboolean do_block)
+{
+  GstPad *pad;
+
+  if ((do_block && (switch_bin->blocking_probe_id != 0)) || (!do_block
+          && (switch_bin->blocking_probe_id == 0)))
+    return;
+
+  pad = gst_element_get_static_pad (switch_bin->input_identity, "sink");
+
+  if (do_block) {
+    switch_bin->blocking_probe_id =
+        gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BLOCK_DOWNSTREAM,
+        gst_switch_bin_blocking_pad_probe, NULL, NULL);
+  } else {
+    gst_pad_remove_probe (pad, switch_bin->blocking_probe_id);
+    switch_bin->blocking_probe_id = 0;
+  }
+
+  GST_DEBUG_OBJECT (switch_bin, "sinkpad block enabled: %d", do_block);
+
+  gst_object_unref (GST_OBJECT (pad));
+}
+
+
+static GstPadProbeReturn
+gst_switch_bin_blocking_pad_probe (G_GNUC_UNUSED GstPad * pad,
+    GstPadProbeInfo * info, G_GNUC_UNUSED gpointer user_data)
+{
+  if (GST_PAD_PROBE_INFO_TYPE (info) & GST_PAD_PROBE_TYPE_EVENT_DOWNSTREAM) {
+    GstEvent *event = GST_PAD_PROBE_INFO_EVENT (info);
+    switch (GST_EVENT_TYPE (event)) {
+      case GST_EVENT_CAPS:
+      case GST_EVENT_STREAM_START:
+        return GST_PAD_PROBE_PASS;
+      default:
+        break;
+    }
+  }
+
+  return GST_PAD_PROBE_OK;
+}
+
+/************ GstSwitchBinPath ************/
+
+
+typedef struct _GstSwitchBinPathClass GstSwitchBinPathClass;
+
+
+#define GST_TYPE_SWITCH_BIN_PATH             (gst_switch_bin_path_get_type())
+#define GST_SWITCH_BIN_PATH(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_SWITCH_BIN_PATH, GstSwitchBinPath))
+#define GST_SWITCH_BIN_PATH_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_SWITCH_BIN_PATH, GstSwitchBinPathClass))
+#define GST_SWITCH_BIN_PATH_CAST(obj)        ((GstSwitchBinPath *)(obj))
+#define GST_IS_SWITCH_BIN_PATH(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_SWITCH_BIN_PATH))
+#define GST_IS_SWITCH_BIN_PATH_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_SWITCH_BIN_PATH))
+
+
+struct _GstSwitchBinPathClass
+{
+  GstObjectClass parent_class;
+};
+
+
+enum
+{
+  PROP_PATH_0,
+  PROP_ELEMENT,
+  PROP_PATH_CAPS
+};
+
+
+G_DEFINE_TYPE (GstSwitchBinPath, gst_switch_bin_path, GST_TYPE_OBJECT);
+
+
+static void gst_switch_bin_path_dispose (GObject * object);
+static void gst_switch_bin_path_set_property (GObject * object,
+    guint prop_id, GValue const *value, GParamSpec * pspec);
+static void gst_switch_bin_path_get_property (GObject * object,
+    guint prop_id, GValue * value, GParamSpec * pspec);
+
+static gboolean gst_switch_bin_path_use_new_element (GstSwitchBinPath *
+    switch_bin_path, GstElement * new_element);
+
+
+
+static void
+gst_switch_bin_path_class_init (GstSwitchBinPathClass * klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = GST_DEBUG_FUNCPTR (gst_switch_bin_path_dispose);
+  object_class->set_property =
+      GST_DEBUG_FUNCPTR (gst_switch_bin_path_set_property);
+  object_class->get_property =
+      GST_DEBUG_FUNCPTR (gst_switch_bin_path_get_property);
+
+  g_object_class_install_property (object_class,
+      PROP_ELEMENT,
+      g_param_spec_object ("element",
+          "Element",
+          "The path's element (if set to NULL, this path will drop any incoming data)",
+          GST_TYPE_ELEMENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+  g_object_class_install_property (object_class,
+      PROP_PATH_CAPS,
+      g_param_spec_boxed ("caps",
+          "Caps",
+          "Caps which, if they are a subset of the input caps, select this path as the active one",
+          GST_TYPE_CAPS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)
+      );
+}
+
+
+static void
+gst_switch_bin_path_init (GstSwitchBinPath * switch_bin_path)
+{
+  switch_bin_path->caps = gst_caps_new_any ();
+  switch_bin_path->element = NULL;
+  switch_bin_path->bin = NULL;
+}
+
+
+static void
+gst_switch_bin_path_dispose (GObject * object)
+{
+  GstSwitchBinPath *switch_bin_path = GST_SWITCH_BIN_PATH (object);
+
+  if (switch_bin_path->caps != NULL) {
+    gst_caps_unref (switch_bin_path->caps);
+    switch_bin_path->caps = NULL;
+  }
+
+  if (switch_bin_path->element != NULL) {
+    gst_switch_bin_path_use_new_element (switch_bin_path, NULL);
+  }
+
+  /* element is managed by the bin itself */
+
+  G_OBJECT_CLASS (gst_switch_bin_path_parent_class)->dispose (object);
+}
+
+
+static void
+gst_switch_bin_path_set_property (GObject * object, guint prop_id,
+    GValue const *value, GParamSpec * pspec)
+{
+  GstSwitchBinPath *switch_bin_path = GST_SWITCH_BIN_PATH (object);
+  switch (prop_id) {
+    case PROP_ELEMENT:
+    {
+      /* Get the object without modifying the refcount */
+      GstElement *new_element = GST_ELEMENT (g_value_get_object (value));
+
+      GST_OBJECT_LOCK (switch_bin_path);
+      PATH_LOCK (switch_bin_path->bin);
+      gst_switch_bin_path_use_new_element (switch_bin_path, new_element);
+      PATH_UNLOCK (switch_bin_path->bin);
+      GST_OBJECT_UNLOCK (switch_bin_path);
+
+      break;
+    }
+
+    case PROP_PATH_CAPS:
+    {
+      GstCaps *old_caps;
+      GstCaps const *new_caps = gst_value_get_caps (value);
+
+      GST_OBJECT_LOCK (switch_bin_path);
+      old_caps = switch_bin_path->caps;
+      if (new_caps == NULL) {
+        /* NULL caps are interpreted as ANY */
+        switch_bin_path->caps = gst_caps_new_any ();
+      } else
+        switch_bin_path->caps = gst_caps_copy (new_caps);
+      GST_OBJECT_UNLOCK (switch_bin_path);
+
+      if (old_caps != NULL)
+        gst_caps_unref (old_caps);
+
+      /* the new caps do not get applied right away
+       * they only start to be used with the next stream */
+
+      break;
+    }
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static void
+gst_switch_bin_path_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstSwitchBinPath *switch_bin_path = GST_SWITCH_BIN_PATH (object);
+  switch (prop_id) {
+    case PROP_ELEMENT:
+      /* If a path element exists, increase its refcount first. This is
+       * necessary because the code that called g_object_get() to fetch this
+       * element will also unref it when it is finished with it. */
+      if (switch_bin_path->element != NULL)
+        gst_object_ref (GST_OBJECT (switch_bin_path->element));
+
+      /* Use g_value_take_object() instead of g_value_set_object() as the latter
+       * increases the element's refcount for the duration of the GValue's lifetime */
+      g_value_take_object (value, switch_bin_path->element);
+
+      break;
+
+    case PROP_PATH_CAPS:
+      GST_OBJECT_LOCK (switch_bin_path);
+      gst_value_set_caps (value, switch_bin_path->caps);
+      GST_OBJECT_UNLOCK (switch_bin_path);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+static gboolean
+gst_switch_bin_path_use_new_element (GstSwitchBinPath * switch_bin_path,
+    GstElement * new_element)
+{
+  /* Must be called with lock */
+
+  GstSwitchBinPath *current_path = switch_bin_path->bin->current_path;
+  gboolean is_current_path = (current_path == switch_bin_path);
+
+  /* Before switching the element, make sure it is not linked,
+   * which is the case if this is the current path. */
+  if (is_current_path)
+    gst_switch_bin_switch_to_path (switch_bin_path->bin, NULL);
+
+  /* Remove any present path element prior to using the new one */
+  if (switch_bin_path->element != NULL) {
+    gst_element_set_state (switch_bin_path->element, GST_STATE_NULL);
+    /* gst_bin_remove automatically unrefs the path element */
+    gst_bin_remove (GST_BIN (switch_bin_path->bin), switch_bin_path->element);
+    switch_bin_path->element = NULL;
+  }
+
+  /* If there *is* a new element, use it. new_element == NULL is a valid case;
+   * a NULL element is used in dropping paths, which will just use the drop probe
+   * to drop buffers if they become the current path. */
+  if (new_element != NULL) {
+    gst_bin_add (GST_BIN (switch_bin_path->bin), new_element);
+    switch_bin_path->element = new_element;
+
+    /* Lock the element's state in case. This prevents freezes, which can happen
+     * when an element from a not-current path tries to follow a state change,
+     * but is unable to do so as long as it isn't linked. By locking the state,
+     * it won't follow state changes, so the freeze does not happen. */
+    gst_element_set_locked_state (new_element, TRUE);
+  }
+
+  /* We are done. Switch back to the path if it is the current one,
+   * since we switched away from it earlier. */
+  if (is_current_path)
+    return gst_switch_bin_switch_to_path (switch_bin_path->bin, current_path);
+  else
+    return TRUE;
+}
diff --git a/gst/switchbin/gstswitchbin.h b/gst/switchbin/gstswitchbin.h
new file mode 100644 (file)
index 0000000..8779a79
--- /dev/null
@@ -0,0 +1,87 @@
+/* switchbin
+ * Copyright (C) 2016  Carlos Rafael Giani
+ *
+ * gstswitchbin.h: Header for GstSwitchBin object
+ *
+ * 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 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+
+#ifndef GSTSWITCHBIN_H___
+#define GSTSWITCHBIN_H___
+
+#include <gst/gst.h>
+
+
+G_BEGIN_DECLS
+
+
+typedef struct _GstSwitchBin GstSwitchBin;
+typedef struct _GstSwitchBinClass GstSwitchBinClass;
+typedef struct _GstSwitchBinPath GstSwitchBinPath;
+
+
+#define GST_TYPE_SWITCH_BIN             (gst_switch_bin_get_type())
+#define GST_SWITCH_BIN(obj)             (G_TYPE_CHECK_INSTANCE_CAST((obj), GST_TYPE_SWITCH_BIN, GstSwitchBin))
+#define GST_SWITCH_BIN_CLASS(klass)     (G_TYPE_CHECK_CLASS_CAST((klass), GST_TYPE_SWITCH_BIN, GstSwitchBinClass))
+#define GST_SWITCH_BIN_CAST(obj)        ((GstSwitchBin *)(obj))
+#define GST_IS_SWITCH_BIN(obj)          (G_TYPE_CHECK_INSTANCE_TYPE((obj), GST_TYPE_SWITCH_BIN))
+#define GST_IS_SWITCH_BIN_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE((klass), GST_TYPE_SWITCH_BIN))
+
+
+struct _GstSwitchBin
+{
+       GstBin parent;
+
+       GMutex path_mutex;
+
+       GstSwitchBinPath **paths;
+       GstSwitchBinPath *current_path;
+       guint num_paths;
+
+       GstElement *input_identity;
+       GstEvent *last_stream_start;
+       GstPad *sinkpad, *srcpad;
+       gulong blocking_probe_id, drop_probe_id;
+
+       GstCaps *last_caps;
+};
+
+
+struct _GstSwitchBinClass
+{
+       GstBinClass parent_class;
+};
+
+
+struct _GstSwitchBinPath
+{
+       GstObject parent;
+
+       GstElement *element;
+       GstCaps *caps;
+       GstSwitchBin *bin;
+};
+
+
+GType gst_switch_bin_get_type(void);
+GType gst_switch_bin_path_get_type(void);
+
+
+G_END_DECLS
+
+
+#endif
diff --git a/gst/switchbin/meson.build b/gst/switchbin/meson.build
new file mode 100644 (file)
index 0000000..8903fc7
--- /dev/null
@@ -0,0 +1,14 @@
+switchbin_sources = [
+  'gstswitchbin.c', 'plugin.c'
+]
+
+gstswitchbin = library('gstswitchbin',
+  switchbin_sources,
+  c_args : gst_plugins_bad_args,
+  include_directories : [configinc],
+  dependencies : [gst_dep],
+  install : true,
+  install_dir : plugins_install_dir,
+)
+pkgconfig.generate(gstswitchbin, install_dir : plugins_pkgconfig_install_dir)
+plugins += [gstswitchbin]
diff --git a/gst/switchbin/plugin.c b/gst/switchbin/plugin.c
new file mode 100644 (file)
index 0000000..a784b24
--- /dev/null
@@ -0,0 +1,43 @@
+/* switchbin
+ * Copyright (C) 2016  Carlos Rafael Giani
+ *
+ * gstswitchbin.c: Element for switching between paths based on input caps
+ *
+ * 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 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., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gstswitchbin.h"
+
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+  gboolean ret = TRUE;
+  ret = ret
+      && gst_element_register (plugin, "switchbin", GST_RANK_NONE,
+      gst_switch_bin_get_type ());
+  return ret;
+}
+
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+    GST_VERSION_MINOR,
+    switchbin,
+    "switchbin",
+    plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
index 7e9e543..b64c1ce 100644 (file)
@@ -57,6 +57,7 @@ option('siren', type : 'feature', value : 'auto')
 option('smooth', type : 'feature', value : 'auto')
 option('speed', type : 'feature', value : 'auto')
 option('subenc', type : 'feature', value : 'auto')
+option('switchbin', type : 'feature', value : 'auto')
 option('timecode', type : 'feature', value : 'auto')
 option('videofilters', type : 'feature', value : 'auto')
 option('videoframe_audiolevel', type : 'feature', value : 'auto')