/* GStreamer output selector * Copyright (C) 2008 Nokia Corporation. (contact ) * * 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-output-selector * @title: output-selector * @see_also: #GstOutputSelector, #GstInputSelector * * Direct input stream to one out of N output pads. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "gstoutputselector.h" GST_DEBUG_CATEGORY_STATIC (output_selector_debug); #define GST_CAT_DEFAULT output_selector_debug static GstStaticPadTemplate gst_output_selector_sink_factory = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS_ANY); static GstStaticPadTemplate gst_output_selector_src_factory = GST_STATIC_PAD_TEMPLATE ("src_%u", GST_PAD_SRC, GST_PAD_REQUEST, GST_STATIC_CAPS_ANY); #define GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE (gst_output_selector_pad_negotiation_mode_get_type()) static GType gst_output_selector_pad_negotiation_mode_get_type (void) { static GType pad_negotiation_mode_type = 0; static const GEnumValue pad_negotiation_modes[] = { {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE, "None", "none"}, {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL, "All", "all"}, {GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ACTIVE, "Active", "active"}, {0, NULL, NULL} }; if (!pad_negotiation_mode_type) { pad_negotiation_mode_type = g_enum_register_static ("GstOutputSelectorPadNegotiationMode", pad_negotiation_modes); } return pad_negotiation_mode_type; } enum { PROP_0, PROP_ACTIVE_PAD, PROP_RESEND_LATEST, PROP_PAD_NEGOTIATION_MODE }; #define DEFAULT_PAD_NEGOTIATION_MODE GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL #define _do_init \ GST_DEBUG_CATEGORY_INIT (output_selector_debug, \ "output-selector", 0, "Output stream selector"); #define gst_output_selector_parent_class parent_class G_DEFINE_TYPE_WITH_CODE (GstOutputSelector, gst_output_selector, GST_TYPE_ELEMENT, _do_init); static void gst_output_selector_dispose (GObject * object); static void gst_output_selector_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_output_selector_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static GstPad *gst_output_selector_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * unused, const GstCaps * caps); static void gst_output_selector_release_pad (GstElement * element, GstPad * pad); static GstFlowReturn gst_output_selector_chain (GstPad * pad, GstObject * parent, GstBuffer * buf); static GstStateChangeReturn gst_output_selector_change_state (GstElement * element, GstStateChange transition); static gboolean gst_output_selector_event (GstPad * pad, GstObject * parent, GstEvent * event); static gboolean gst_output_selector_query (GstPad * pad, GstObject * parent, GstQuery * query); static void gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector * sel, gint mode); static void gst_output_selector_class_init (GstOutputSelectorClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); gobject_class->dispose = gst_output_selector_dispose; gobject_class->set_property = gst_output_selector_set_property; gobject_class->get_property = gst_output_selector_get_property; g_object_class_install_property (gobject_class, PROP_ACTIVE_PAD, g_param_spec_object ("active-pad", "Active pad", "Currently active src pad", GST_TYPE_PAD, G_PARAM_READWRITE | GST_PARAM_MUTABLE_PLAYING | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_RESEND_LATEST, g_param_spec_boolean ("resend-latest", "Resend latest buffer", "Resend latest buffer after a switch to a new pad", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_PAD_NEGOTIATION_MODE, g_param_spec_enum ("pad-negotiation-mode", "Pad negotiation mode", "The mode to be used for pad negotiation", GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE, DEFAULT_PAD_NEGOTIATION_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); gst_element_class_set_static_metadata (gstelement_class, "Output selector", "Generic", "1-to-N output stream selector", "Stefan Kost "); gst_element_class_add_static_pad_template (gstelement_class, &gst_output_selector_sink_factory); gst_element_class_add_static_pad_template (gstelement_class, &gst_output_selector_src_factory); gstelement_class->request_new_pad = GST_DEBUG_FUNCPTR (gst_output_selector_request_new_pad); gstelement_class->release_pad = GST_DEBUG_FUNCPTR (gst_output_selector_release_pad); gstelement_class->change_state = gst_output_selector_change_state; gst_type_mark_as_plugin_api (GST_TYPE_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE); } static void gst_output_selector_init (GstOutputSelector * sel) { sel->sinkpad = gst_pad_new_from_static_template (&gst_output_selector_sink_factory, "sink"); gst_pad_set_chain_function (sel->sinkpad, GST_DEBUG_FUNCPTR (gst_output_selector_chain)); gst_pad_set_event_function (sel->sinkpad, GST_DEBUG_FUNCPTR (gst_output_selector_event)); gst_pad_set_query_function (sel->sinkpad, GST_DEBUG_FUNCPTR (gst_output_selector_query)); gst_element_add_pad (GST_ELEMENT (sel), sel->sinkpad); /* srcpad management */ sel->active_srcpad = NULL; sel->nb_srcpads = 0; gst_segment_init (&sel->segment, GST_FORMAT_UNDEFINED); sel->pending_srcpad = NULL; sel->resend_latest = FALSE; sel->latest_buffer = NULL; gst_output_selector_switch_pad_negotiation_mode (sel, DEFAULT_PAD_NEGOTIATION_MODE); } static void gst_output_selector_reset (GstOutputSelector * osel) { GST_OBJECT_LOCK (osel); if (osel->pending_srcpad != NULL) { gst_object_unref (osel->pending_srcpad); osel->pending_srcpad = NULL; } if (osel->latest_buffer != NULL) { gst_buffer_unref (osel->latest_buffer); osel->latest_buffer = NULL; } osel->segment_seqnum = GST_SEQNUM_INVALID; GST_OBJECT_UNLOCK (osel); gst_segment_init (&osel->segment, GST_FORMAT_UNDEFINED); } static void gst_output_selector_dispose (GObject * object) { GstOutputSelector *osel = GST_OUTPUT_SELECTOR (object); gst_output_selector_reset (osel); G_OBJECT_CLASS (parent_class)->dispose (object); } static void gst_output_selector_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object); switch (prop_id) { case PROP_ACTIVE_PAD: { GstPad *next_pad; next_pad = g_value_get_object (value); GST_INFO_OBJECT (sel, "Activating pad %s:%s", GST_DEBUG_PAD_NAME (next_pad)); /* guard against users setting a sink pad or foreign pad as active pad */ if (next_pad != NULL) { g_return_if_fail (GST_PAD_IS_SRC (next_pad)); g_return_if_fail (GST_PAD_PARENT (next_pad) == GST_ELEMENT_CAST (sel)); } GST_OBJECT_LOCK (object); if (next_pad != sel->active_srcpad) { /* switch to new srcpad in next chain run */ if (sel->pending_srcpad != NULL) { GST_INFO ("replacing pending switch"); gst_object_unref (sel->pending_srcpad); } if (next_pad) gst_object_ref (next_pad); sel->pending_srcpad = next_pad; } else { GST_INFO ("pad already active"); if (sel->pending_srcpad != NULL) { gst_object_unref (sel->pending_srcpad); sel->pending_srcpad = NULL; } } GST_OBJECT_UNLOCK (object); break; } case PROP_RESEND_LATEST:{ sel->resend_latest = g_value_get_boolean (value); break; } case PROP_PAD_NEGOTIATION_MODE:{ gst_output_selector_switch_pad_negotiation_mode (sel, g_value_get_enum (value)); break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_output_selector_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstOutputSelector *sel = GST_OUTPUT_SELECTOR (object); switch (prop_id) { case PROP_ACTIVE_PAD: GST_OBJECT_LOCK (object); g_value_set_object (value, sel->pending_srcpad ? sel->pending_srcpad : sel->active_srcpad); GST_OBJECT_UNLOCK (object); break; case PROP_RESEND_LATEST:{ GST_OBJECT_LOCK (object); g_value_set_boolean (value, sel->resend_latest); GST_OBJECT_UNLOCK (object); break; } case PROP_PAD_NEGOTIATION_MODE: g_value_set_enum (value, sel->pad_negotiation_mode); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static GstPad * gst_output_selector_get_active (GstOutputSelector * sel) { GstPad *active = NULL; GST_OBJECT_LOCK (sel); if (sel->pending_srcpad) active = gst_object_ref (sel->pending_srcpad); else if (sel->active_srcpad) active = gst_object_ref (sel->active_srcpad); GST_OBJECT_UNLOCK (sel); return active; } static void gst_output_selector_switch_pad_negotiation_mode (GstOutputSelector * sel, gint mode) { sel->pad_negotiation_mode = mode; } static gboolean forward_sticky_events (GstPad * pad, GstEvent ** event, gpointer user_data) { GstPad *srcpad = GST_PAD_CAST (user_data); gst_pad_push_event (srcpad, gst_event_ref (*event)); return TRUE; } static gboolean gst_output_selector_srcpad_event_func (GstPad * pad, GstObject * parent, GstEvent * event) { GstOutputSelector *osel = GST_OUTPUT_SELECTOR (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK: { guint32 seqnum = gst_event_get_seqnum (event); GST_OBJECT_LOCK (osel); if (seqnum == osel->segment_seqnum) { GST_OBJECT_UNLOCK (osel); GST_DEBUG_OBJECT (pad, "Drop duplicated SEEK event seqnum %" G_GUINT32_FORMAT, seqnum); gst_event_unref (event); return TRUE; } osel->segment_seqnum = seqnum; GST_OBJECT_UNLOCK (osel); break; } default: break; } return gst_pad_event_default (pad, parent, event); } static GstPad * gst_output_selector_request_new_pad (GstElement * element, GstPadTemplate * templ, const gchar * name, const GstCaps * caps) { gchar *padname; GstPad *srcpad; GstOutputSelector *osel; osel = GST_OUTPUT_SELECTOR (element); GST_DEBUG_OBJECT (osel, "requesting pad"); GST_OBJECT_LOCK (osel); padname = g_strdup_printf ("src_%u", osel->nb_srcpads++); srcpad = gst_pad_new_from_template (templ, padname); GST_OBJECT_UNLOCK (osel); gst_pad_set_event_function (srcpad, gst_output_selector_srcpad_event_func); gst_pad_set_active (srcpad, TRUE); /* Forward sticky events to the new srcpad */ gst_pad_sticky_events_foreach (osel->sinkpad, forward_sticky_events, srcpad); gst_element_add_pad (GST_ELEMENT (osel), srcpad); /* Set the first requested src pad as active by default */ GST_OBJECT_LOCK (osel); if (osel->active_srcpad == NULL) { osel->active_srcpad = srcpad; GST_OBJECT_UNLOCK (osel); g_object_notify (G_OBJECT (osel), "active-pad"); } else { GST_OBJECT_UNLOCK (osel); } g_free (padname); return srcpad; } static void gst_output_selector_release_pad (GstElement * element, GstPad * pad) { GstOutputSelector *osel; osel = GST_OUTPUT_SELECTOR (element); GST_DEBUG_OBJECT (osel, "releasing pad"); /* Disable active pad if it's the to be removed pad */ GST_OBJECT_LOCK (osel); if (osel->active_srcpad == pad) { osel->active_srcpad = NULL; GST_OBJECT_UNLOCK (osel); g_object_notify (G_OBJECT (osel), "active-pad"); } else { GST_OBJECT_UNLOCK (osel); } gst_pad_set_active (pad, FALSE); gst_element_remove_pad (GST_ELEMENT_CAST (osel), pad); } static gboolean gst_output_selector_switch (GstOutputSelector * osel) { gboolean res = FALSE; GstEvent *ev = NULL; GstSegment *seg = NULL; GstPad *active_srcpad; /* Switch */ GST_OBJECT_LOCK (osel); GST_INFO_OBJECT (osel, "switching to pad %" GST_PTR_FORMAT, osel->pending_srcpad); if (!osel->pending_srcpad) { GST_OBJECT_UNLOCK (osel); return TRUE; } if (gst_pad_is_linked (osel->pending_srcpad)) { osel->active_srcpad = osel->pending_srcpad; res = TRUE; } gst_object_unref (osel->pending_srcpad); osel->pending_srcpad = NULL; active_srcpad = res ? gst_object_ref (osel->active_srcpad) : NULL; GST_OBJECT_UNLOCK (osel); /* Send SEGMENT event and latest buffer if switching succeeded * and we already have a valid segment configured */ if (res) { GstBuffer *latest_buffer; g_object_notify (G_OBJECT (osel), "active-pad"); GST_OBJECT_LOCK (osel); latest_buffer = osel->latest_buffer ? gst_buffer_ref (osel->latest_buffer) : NULL; GST_OBJECT_UNLOCK (osel); gst_pad_sticky_events_foreach (osel->sinkpad, forward_sticky_events, active_srcpad); /* update segment if required */ if (osel->segment.format != GST_FORMAT_UNDEFINED) { /* Send SEGMENT to the pad we are going to switch to */ seg = &osel->segment; /* If resending then mark segment start and position accordingly */ if (osel->resend_latest && latest_buffer && GST_BUFFER_TIMESTAMP_IS_VALID (latest_buffer)) { seg->position = GST_BUFFER_TIMESTAMP (latest_buffer); } ev = gst_event_new_segment (seg); if (!gst_pad_push_event (active_srcpad, ev)) { GST_WARNING_OBJECT (osel, "newsegment handling failed in %" GST_PTR_FORMAT, active_srcpad); } } /* Resend latest buffer to newly switched pad */ if (osel->resend_latest && latest_buffer) { GST_INFO ("resending latest buffer"); gst_pad_push (active_srcpad, latest_buffer); } else if (latest_buffer) { gst_buffer_unref (latest_buffer); } gst_object_unref (active_srcpad); } else { GST_WARNING_OBJECT (osel, "switch failed, pad not linked"); } return res; } static GstFlowReturn gst_output_selector_chain (GstPad * pad, GstObject * parent, GstBuffer * buf) { GstFlowReturn res; GstOutputSelector *osel; GstClockTime position, duration; GstPad *active_srcpad; osel = GST_OUTPUT_SELECTOR (parent); /* * The _switch function might push a buffer if 'resend-latest' is true. * * Elements/Applications (e.g. camerabin) might use pad probes to * switch output-selector's active pad. If we simply switch and don't * recheck any pending pad switch the following codepath could end * up pushing a buffer on a non-active pad. This is bad. * * So we always should check the pending_srcpad before going further down * the chain and pushing the new buffer */ while (osel->pending_srcpad) { /* Do the switch */ gst_output_selector_switch (osel); } active_srcpad = gst_output_selector_get_active (osel); if (!active_srcpad) { GST_DEBUG_OBJECT (osel, "No active srcpad"); gst_buffer_unref (buf); return GST_FLOW_OK; } GST_OBJECT_LOCK (osel); if (osel->latest_buffer) { gst_buffer_unref (osel->latest_buffer); osel->latest_buffer = NULL; } if (osel->resend_latest) { /* Keep reference to latest buffer to resend it after switch */ osel->latest_buffer = gst_buffer_ref (buf); } GST_OBJECT_UNLOCK (osel); /* Keep track of last stop and use it in SEGMENT start after switching to a new src pad */ position = GST_BUFFER_TIMESTAMP (buf); if (GST_CLOCK_TIME_IS_VALID (position)) { duration = GST_BUFFER_DURATION (buf); if (GST_CLOCK_TIME_IS_VALID (duration)) { position += duration; } GST_LOG_OBJECT (osel, "setting last stop %" GST_TIME_FORMAT, GST_TIME_ARGS (position)); osel->segment.position = position; } GST_LOG_OBJECT (osel, "pushing buffer to %" GST_PTR_FORMAT, active_srcpad); res = gst_pad_push (active_srcpad, buf); gst_object_unref (active_srcpad); return res; } static GstStateChangeReturn gst_output_selector_change_state (GstElement * element, GstStateChange transition) { GstOutputSelector *sel; GstStateChangeReturn result; sel = GST_OUTPUT_SELECTOR (element); switch (transition) { case GST_STATE_CHANGE_READY_TO_PAUSED: break; default: break; } result = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); switch (transition) { case GST_STATE_CHANGE_PAUSED_TO_READY: gst_output_selector_reset (sel); break; default: break; } return result; } static gboolean gst_output_selector_forward_event (GstOutputSelector * sel, GstEvent * event) { gboolean res = TRUE; GstPad *active; switch (sel->pad_negotiation_mode) { case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL: /* Send to all src pads */ res = gst_pad_event_default (sel->sinkpad, GST_OBJECT_CAST (sel), event); break; case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE: gst_event_unref (event); break; default: active = gst_output_selector_get_active (sel); if (active) { res = gst_pad_push_event (active, event); gst_object_unref (active); } else { gst_event_unref (event); } break; } return res; } static gboolean gst_output_selector_event (GstPad * pad, GstObject * parent, GstEvent * event) { gboolean res = TRUE; GstOutputSelector *sel; GstPad *active = NULL; sel = GST_OUTPUT_SELECTOR (parent); switch (GST_EVENT_TYPE (event)) { case GST_EVENT_EOS: { res = gst_output_selector_forward_event (sel, event); break; } case GST_EVENT_SEGMENT: { gst_event_copy_segment (event, &sel->segment); GST_DEBUG_OBJECT (sel, "configured SEGMENT %" GST_SEGMENT_FORMAT, &sel->segment); /* fall through */ } default: { active = gst_output_selector_get_active (sel); if (active) { res = gst_pad_push_event (active, event); gst_object_unref (active); } else { gst_event_unref (event); } break; } } return res; } static gboolean gst_output_selector_query (GstPad * pad, GstObject * parent, GstQuery * query) { gboolean res = TRUE; GstOutputSelector *sel; GstPad *active = NULL; sel = GST_OUTPUT_SELECTOR (parent); switch (GST_QUERY_TYPE (query)) { case GST_QUERY_CAPS: { switch (sel->pad_negotiation_mode) { case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_ALL: /* Send caps to all src pads */ res = gst_pad_proxy_query_caps (pad, query); break; case GST_OUTPUT_SELECTOR_PAD_NEGOTIATION_MODE_NONE: res = FALSE; break; default: active = gst_output_selector_get_active (sel); if (active) { res = gst_pad_peer_query (active, query); gst_object_unref (active); } else { res = FALSE; } break; } break; } case GST_QUERY_DRAIN: if (sel->latest_buffer) { gst_buffer_unref (sel->latest_buffer); sel->latest_buffer = NULL; } /* fall through */ default: res = gst_pad_query_default (pad, parent, query); break; } return res; }