/* 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. */ /** * SECTION:element-switchbin * @title: switchbin * * switchbin is a helper element which chooses between a set of * processing chains (paths) based on input caps, and changes if new caps * arrive. Paths are child objects, which are accessed by the * #GstChildProxy interface. * * Whenever new input caps are encountered at the switchbin's sinkpad, * the * first path with matching caps is picked. The paths are looked at * in order: path \#0's caps are looked at first, checked against the new * input caps with gst_caps_can_intersect(), and if its return value * is TRUE, path \#0 is picked. Otherwise, path \#1's caps are looked at etc. * If no path matches, an error is reported. * * <refsect2> * <title>Example launch line</title> * * In this example, if the data is raw PCM audio with 44.1 kHz, a volume * element is used for reducing the audio volume to 10%. Otherwise, it is * just passed through. So, a 44.1 kHz MP3 will sound quiet, a 48 kHz MP3 * will be at full volume. * * |[ * gst-launch-1.0 uridecodebin uri=<URI> ! switchbin num-paths=2 \ * path0::element="audioconvert ! volume volume=0.1" path0::caps="audio/x-raw, rate=44100" \ * path1::element="identity" path1::caps="ANY" ! \ * autoaudiosink * ]| * * This example's path \#1 is a fallback "catch-all" path. Its caps are "ANY" caps, * so any input caps will match against this. A catch-all path with an identity element * is useful for cases where certain kinds of processing should only be done for specific * formats, like the example above (it applies volume only to 44.1 kHz PCM audio). * </refsect2> * */ #include <string.h> #include <stdio.h> #include "gstswitchbin.h" GST_DEBUG_CATEGORY (switch_bin_debug); #define GST_CAT_DEFAULT switch_bin_debug enum { PROP_0, PROP_NUM_PATHS, PROP_CURRENT_PATH, PROP_LAST }; #define DEFAULT_NUM_PATHS 0 GParamSpec *switchbin_props[PROP_LAST]; #define PATH_LOCK(obj) g_mutex_lock(&(GST_SWITCH_BIN_CAST (obj)->path_mutex)) #define PATH_UNLOCK(obj) g_mutex_unlock(&(GST_SWITCH_BIN_CAST (obj)->path_mutex)) #define PATH_UNLOCK_AND_CHECK(obj) gst_switch_bin_unlock_paths_and_notify(GST_SWITCH_BIN_CAST (obj)); 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_unlock_paths_and_notify (GstSwitchBin * switchbin); static void gst_switch_bin_dispose (GObject * object); 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_unlock_paths_and_notify (GstSwitchBin * switchbin) { gboolean do_notify = switchbin->path_changed; switchbin->path_changed = FALSE; PATH_UNLOCK (switchbin); if (do_notify) g_object_notify_by_pspec (G_OBJECT (switchbin), switchbin_props[PROP_CURRENT_PATH]); } 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->dispose = GST_DEBUG_FUNCPTR (gst_switch_bin_dispose); 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); /** * GstSwitchBin:num-paths * * Configure how many paths the switchbin will be choosing between. Attempting * to configure a path outside the range 0..(n-1) will fail. Reducing the * number of paths will release any paths outside the new range, which might * trigger activation of a new path by re-assessing the current caps. */ switchbin_props[PROP_NUM_PATHS] = g_param_spec_uint ("num-paths", "Number of paths", "Number of paths", 0, G_MAXUINT - 1, DEFAULT_NUM_PATHS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_NUM_PATHS, switchbin_props[PROP_NUM_PATHS]); /** * GstSwitchBin:current-path * * Returns the currently selected path number. If there is no current * path (due to no caps, or unsupported caps), the value is #G_MAXUINT. Read-only. */ switchbin_props[PROP_CURRENT_PATH] = g_param_spec_uint ("current-path", "Current Path", "Currently selected path", 0, G_MAXUINT, 0, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_CURRENT_PATH, switchbin_props[PROP_CURRENT_PATH]); 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_dispose (GObject * object) { GstSwitchBin *switch_bin = GST_SWITCH_BIN (object); guint i; /* Chaining up will release all children of the bin, * invalidating any pointer to elements in the paths, so make sure * and clear those first */ PATH_LOCK (switch_bin); for (i = 0; i < switch_bin->num_paths; ++i) { if (switch_bin->paths[i]) switch_bin->paths[i]->element = NULL; } PATH_UNLOCK (switch_bin); G_OBJECT_CLASS (gst_switch_bin_parent_class)->dispose (object); } static void gst_switch_bin_finalize (GObject * object) { GstSwitchBin *switch_bin = GST_SWITCH_BIN (object); guint i; 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); for (i = 0; i < switch_bin->num_paths; ++i) { gst_object_unparent (GST_OBJECT (switch_bin->paths[i])); } 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_AND_CHECK (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_AND_CHECK (switch_bin); break; case PROP_CURRENT_PATH: PATH_LOCK (switch_bin); if (switch_bin->current_path) { guint i; for (i = 0; i < switch_bin->num_paths; ++i) { if (switch_bin->paths[i] == switch_bin->current_path) { g_value_set_uint (value, i); break; } } } else { /* No valid path, return MAXUINT */ g_value_set_uint (value, G_MAXUINT); } 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_AND_CHECK (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); } switch_bin->paths = g_realloc (switch_bin->paths, sizeof (GstObject *) * new_num_paths); } 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; switch_bin->path_changed = TRUE; } /* 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; switch_bin->path_changed = TRUE; /* 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_AND_CHECK (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; }