gstreamer/plugins/elements/gstoutputselector.c
Tim-Philipp Müller 65e0907798 inputselector, outputselector: add guards for wrong pads being set as active pads
Catch users wrongly setting foreign pads or wrong pads as
the selector's active pad, which leads to all kinds of
other issues. It's a programming error so handle it just
like we would if we had direct API.

https://bugzilla.gnome.org/show_bug.cgi?id=795309
2018-04-17 18:57:17 +01:00

665 lines
19 KiB
C

/* GStreamer output selector
* Copyright (C) 2008 Nokia Corporation. (contact <stefan.kost@nokia.com>)
*
* 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 <string.h>
#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 <stefan.kost@nokia.com>");
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;
}
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;
}
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 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_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;
}