gstreamer/subprojects/gstreamer/gst/gstelement.c
Michael Gruner 53c145a158 gstelement: protect async state changes against spurious wake ups
When a pipeline is pre-rolling, it waits for all sink elements to report
they have received a buffer before completing the transition to paused.
This async wait is done using a state condition variable. The way this
waits are currently implemented do not protect against spurious conditional
wake ups, which may happen due to external factors in the kernel.

This change implements the wait within a loop that iterates over the protected
variable to reinitiates the wait if the wakeup was spurious. More details in
the [GCond docs](https://docs.gtk.org/glib/struct.Cond.html).

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/4086>
2023-04-04 07:24:57 +00:00

3924 lines
116 KiB
C

/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2004 Wim Taymans <wim@fluendo.com>
*
* gstelement.c: The base element, all elements derive from this
*
* 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:gstelement
* @title: GstElement
* @short_description: Abstract base class for all pipeline elements
* @see_also: #GstElementFactory, #GstPad
* @symbols:
* - GST_ELEMENT_METADATA_LONGNAME
* - GST_ELEMENT_METADATA_KLASS
* - GST_ELEMENT_METADATA_DESCRIPTION
* - GST_ELEMENT_METADATA_AUTHOR
* - GST_ELEMENT_METADATA_DOC_URI
* - GST_ELEMENT_METADATA_ICON_NAME
*
* GstElement is the abstract base class needed to construct an element that
* can be used in a GStreamer pipeline. Please refer to the plugin writers
* guide for more information on creating #GstElement subclasses.
*
* The name of a #GstElement can be get with gst_element_get_name() and set with
* gst_element_set_name(). For speed, GST_ELEMENT_NAME() can be used in the
* core when using the appropriate locking. Do not use this in plug-ins or
* applications in order to retain ABI compatibility.
*
* Elements can have pads (of the type #GstPad). These pads link to pads on
* other elements. #GstBuffer flow between these linked pads.
* A #GstElement has a #GList of #GstPad structures for all their input (or sink)
* and output (or source) pads.
* Core and plug-in writers can add and remove pads with gst_element_add_pad()
* and gst_element_remove_pad().
*
* An existing pad of an element can be retrieved by name with
* gst_element_get_static_pad(). A new dynamic pad can be created using
* gst_element_request_pad() with a #GstPadTemplate.
* An iterator of all pads can be retrieved with gst_element_iterate_pads().
*
* Elements can be linked through their pads.
* If the link is straightforward, use the gst_element_link()
* convenience function to link two elements, or gst_element_link_many()
* for more elements in a row.
* Use gst_element_link_filtered() to link two elements constrained by
* a specified set of #GstCaps.
* For finer control, use gst_element_link_pads() and
* gst_element_link_pads_filtered() to specify the pads to link on
* each element by name.
*
* Each element has a state (see #GstState). You can get and set the state
* of an element with gst_element_get_state() and gst_element_set_state().
* Setting a state triggers a #GstStateChange. To get a string representation
* of a #GstState, use gst_element_state_get_name().
*
* You can get and set a #GstClock on an element using gst_element_get_clock()
* and gst_element_set_clock().
* Some elements can provide a clock for the pipeline if
* the #GST_ELEMENT_FLAG_PROVIDE_CLOCK flag is set. With the
* gst_element_provide_clock() method one can retrieve the clock provided by
* such an element.
* Not all elements require a clock to operate correctly. If the
* #GST_ELEMENT_FLAG_REQUIRE_CLOCK() flag is set, a clock should be set on the
* element with gst_element_set_clock().
*
* Note that clock selection and distribution is normally handled by the
* toplevel #GstPipeline so the clock functions are only to be used in very
* specific situations.
*/
#include "gst_private.h"
#include <glib.h>
#include <stdarg.h>
#include <gobject/gvaluecollector.h>
#include "gstelement.h"
#include "gstelementmetadata.h"
#include "gstenumtypes.h"
#include "gstbus.h"
#include "gsterror.h"
#include "gstevent.h"
#include "gstghostpad.h"
#include "gstutils.h"
#include "gstinfo.h"
#include "gstquark.h"
#include "gsttracerutils.h"
#include "gstvalue.h"
#include <glib/gi18n-lib.h>
#include "glib-compat-private.h"
#include "printf/printf.h"
/* Element signals and args */
enum
{
PAD_ADDED,
PAD_REMOVED,
NO_MORE_PADS,
/* add more above */
LAST_SIGNAL
};
enum
{
ARG_0
/* FILL ME */
};
static void gst_element_class_init (GstElementClass * klass);
static void gst_element_init (GstElement * element);
static void gst_element_base_class_init (gpointer g_class);
static void gst_element_constructed (GObject * object);
static void gst_element_dispose (GObject * object);
static void gst_element_finalize (GObject * object);
static GstStateChangeReturn gst_element_change_state_func (GstElement * element,
GstStateChange transition);
static GstStateChangeReturn gst_element_get_state_func (GstElement * element,
GstState * state, GstState * pending, GstClockTime timeout);
static GstStateChangeReturn gst_element_set_state_func (GstElement * element,
GstState state);
static gboolean gst_element_set_clock_func (GstElement * element,
GstClock * clock);
static void gst_element_set_bus_func (GstElement * element, GstBus * bus);
static gboolean gst_element_post_message_default (GstElement * element,
GstMessage * message);
static void gst_element_set_context_default (GstElement * element,
GstContext * context);
static gboolean gst_element_default_send_event (GstElement * element,
GstEvent * event);
static gboolean gst_element_default_query (GstElement * element,
GstQuery * query);
static GstPadTemplate
* gst_element_class_request_pad_simple_template (GstElementClass *
element_class, const gchar * name);
static void gst_element_call_async_func (gpointer data, gpointer user_data);
static GstObjectClass *parent_class = NULL;
static guint gst_element_signals[LAST_SIGNAL] = { 0 };
static GMutex _element_pool_lock;
static GThreadPool *gst_element_pool = NULL;
/* this is used in gstelementfactory.c:gst_element_register() */
GQuark __gst_elementclass_factory = 0;
/* used for gst_element_type_set_skip_documentation() and
* gst_element_factory_get_skip_documentation() */
GQuark __gst_elementclass_skip_doc = 0;
GType
gst_element_get_type (void)
{
static gsize gst_element_type = 0;
if (g_once_init_enter (&gst_element_type)) {
GType _type;
static const GTypeInfo element_info = {
sizeof (GstElementClass),
gst_element_base_class_init,
NULL, /* base_class_finalize */
(GClassInitFunc) gst_element_class_init,
NULL,
NULL,
sizeof (GstElement),
0,
(GInstanceInitFunc) gst_element_init,
NULL
};
_type = g_type_register_static (GST_TYPE_OBJECT, "GstElement",
&element_info, G_TYPE_FLAG_ABSTRACT);
__gst_elementclass_factory =
g_quark_from_static_string ("GST_ELEMENTCLASS_FACTORY");
__gst_elementclass_skip_doc =
g_quark_from_static_string ("GST_ELEMENTCLASS_SKIP_DOCUMENTATION");
g_once_init_leave (&gst_element_type, _type);
}
return gst_element_type;
}
static GThreadPool *
gst_element_setup_thread_pool (void)
{
GError *err = NULL;
GThreadPool *pool;
GST_DEBUG ("creating element thread pool");
pool =
g_thread_pool_new ((GFunc) gst_element_call_async_func, NULL, -1, FALSE,
&err);
if (err != NULL) {
g_critical ("could not alloc threadpool %s", err->message);
g_clear_error (&err);
}
return pool;
}
static void
gst_element_class_init (GstElementClass * klass)
{
GObjectClass *gobject_class;
gobject_class = (GObjectClass *) klass;
parent_class = g_type_class_peek_parent (klass);
/**
* GstElement::pad-added:
* @gstelement: the object which received the signal
* @new_pad: the pad that has been added
*
* a new #GstPad has been added to the element. Note that this signal will
* usually be emitted from the context of the streaming thread. Also keep in
* mind that if you add new elements to the pipeline in the signal handler
* you will need to set them to the desired target state with
* gst_element_set_state() or gst_element_sync_state_with_parent().
*/
gst_element_signals[PAD_ADDED] =
g_signal_new ("pad-added", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstElementClass, pad_added), NULL, NULL,
NULL, G_TYPE_NONE, 1, GST_TYPE_PAD);
/**
* GstElement::pad-removed:
* @gstelement: the object which received the signal
* @old_pad: the pad that has been removed
*
* a #GstPad has been removed from the element
*/
gst_element_signals[PAD_REMOVED] =
g_signal_new ("pad-removed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GstElementClass, pad_removed), NULL, NULL,
NULL, G_TYPE_NONE, 1, GST_TYPE_PAD);
/**
* GstElement::no-more-pads:
* @gstelement: the object which received the signal
*
* This signals that the element will not generate more dynamic pads.
* Note that this signal will usually be emitted from the context of
* the streaming thread.
*/
gst_element_signals[NO_MORE_PADS] =
g_signal_new ("no-more-pads", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstElementClass, no_more_pads), NULL,
NULL, NULL, G_TYPE_NONE, 0);
gobject_class->dispose = gst_element_dispose;
gobject_class->finalize = gst_element_finalize;
gobject_class->constructed = gst_element_constructed;
klass->change_state = GST_DEBUG_FUNCPTR (gst_element_change_state_func);
klass->set_state = GST_DEBUG_FUNCPTR (gst_element_set_state_func);
klass->get_state = GST_DEBUG_FUNCPTR (gst_element_get_state_func);
klass->set_clock = GST_DEBUG_FUNCPTR (gst_element_set_clock_func);
klass->set_bus = GST_DEBUG_FUNCPTR (gst_element_set_bus_func);
klass->query = GST_DEBUG_FUNCPTR (gst_element_default_query);
klass->send_event = GST_DEBUG_FUNCPTR (gst_element_default_send_event);
klass->numpadtemplates = 0;
klass->post_message = GST_DEBUG_FUNCPTR (gst_element_post_message_default);
klass->set_context = GST_DEBUG_FUNCPTR (gst_element_set_context_default);
klass->elementfactory = NULL;
}
static void
gst_element_base_class_init (gpointer g_class)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
GList *node, *padtemplates;
/* Copy the element details here so elements can inherit the
* details from their base class and classes only need to set
* the details in class_init instead of base_init */
element_class->metadata =
element_class->metadata ? gst_structure_copy (element_class->metadata) :
gst_structure_new_empty ("metadata");
/* Copy the pad templates so elements inherit them
* from their base class but elements can add pad templates in class_init
* instead of base_init.
*/
padtemplates = g_list_copy (element_class->padtemplates);
for (node = padtemplates; node != NULL; node = node->next) {
GstPadTemplate *tmpl = (GstPadTemplate *) node->data;
gst_object_ref (tmpl);
}
element_class->padtemplates = padtemplates;
/* set the factory, see gst_element_register() */
element_class->elementfactory =
g_type_get_qdata (G_TYPE_FROM_CLASS (element_class),
__gst_elementclass_factory);
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "type %s : factory %p",
G_OBJECT_CLASS_NAME (element_class), element_class->elementfactory);
}
static void
gst_element_init (GstElement * element)
{
GST_STATE (element) = GST_STATE_NULL;
GST_STATE_TARGET (element) = GST_STATE_NULL;
GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING;
GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS;
g_rec_mutex_init (&element->state_lock);
g_cond_init (&element->state_cond);
}
static void
gst_element_constructed (GObject * object)
{
GST_TRACER_ELEMENT_NEW (GST_ELEMENT_CAST (object));
G_OBJECT_CLASS (parent_class)->constructed (object);
}
/**
* gst_element_release_request_pad:
* @element: a #GstElement to release the request pad of.
* @pad: the #GstPad to release.
*
* Makes the element free the previously requested pad as obtained
* with gst_element_request_pad().
*
* This does not unref the pad. If the pad was created by using
* gst_element_request_pad(), gst_element_release_request_pad() needs to be
* followed by gst_object_unref() to free the @pad.
*
* MT safe.
*/
void
gst_element_release_request_pad (GstElement * element, GstPad * pad)
{
GstElementClass *oclass;
g_return_if_fail (GST_IS_ELEMENT (element));
g_return_if_fail (GST_IS_PAD (pad));
g_return_if_fail (GST_PAD_PAD_TEMPLATE (pad) == NULL ||
GST_PAD_TEMPLATE_PRESENCE (GST_PAD_PAD_TEMPLATE (pad)) ==
GST_PAD_REQUEST);
g_return_if_fail (GST_PAD_PARENT (pad) == element);
oclass = GST_ELEMENT_GET_CLASS (element);
/* if the element implements a custom release function we call that, else we
* simply remove the pad from the element */
if (oclass->release_pad)
oclass->release_pad (element, pad);
else
gst_element_remove_pad (element, pad);
}
/**
* gst_element_provide_clock:
* @element: a #GstElement to query
*
* Get the clock provided by the given element.
* > An element is only required to provide a clock in the PAUSED
* > state. Some elements can provide a clock in other states.
*
* Returns: (transfer full) (nullable): the GstClock provided by the
* element or %NULL if no clock could be provided. Unref after usage.
*
* MT safe.
*/
GstClock *
gst_element_provide_clock (GstElement * element)
{
GstClock *result = NULL;
GstElementClass *oclass;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
oclass = GST_ELEMENT_GET_CLASS (element);
if (oclass->provide_clock)
result = oclass->provide_clock (element);
return result;
}
static gboolean
gst_element_set_clock_func (GstElement * element, GstClock * clock)
{
GstClock **clock_p;
GST_OBJECT_LOCK (element);
clock_p = &element->clock;
gst_object_replace ((GstObject **) clock_p, (GstObject *) clock);
GST_OBJECT_UNLOCK (element);
return TRUE;
}
/**
* gst_element_set_clock:
* @element: a #GstElement to set the clock for.
* @clock: (transfer none) (nullable): the #GstClock to set for the element.
*
* Sets the clock for the element. This function increases the
* refcount on the clock. Any previously set clock on the object
* is unreffed.
*
* Returns: %TRUE if the element accepted the clock. An element can refuse a
* clock when it, for example, is not able to slave its internal clock to the
* @clock or when it requires a specific clock to operate.
*
* MT safe.
*/
gboolean
gst_element_set_clock (GstElement * element, GstClock * clock)
{
GstElementClass *oclass;
gboolean res = FALSE;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (clock == NULL || GST_IS_CLOCK (clock), FALSE);
oclass = GST_ELEMENT_GET_CLASS (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, element, "setting clock %p", clock);
if (oclass->set_clock)
res = oclass->set_clock (element, clock);
return res;
}
/**
* gst_element_get_clock:
* @element: a #GstElement to get the clock of.
*
* Gets the currently configured clock of the element. This is the clock as was
* last set with gst_element_set_clock().
*
* Elements in a pipeline will only have their clock set when the
* pipeline is in the PLAYING state.
*
* Returns: (transfer full) (nullable): the #GstClock of the element. unref after usage.
*
* MT safe.
*/
GstClock *
gst_element_get_clock (GstElement * element)
{
GstClock *result;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
GST_OBJECT_LOCK (element);
if ((result = element->clock))
gst_object_ref (result);
GST_OBJECT_UNLOCK (element);
return result;
}
/**
* gst_element_set_base_time:
* @element: a #GstElement.
* @time: the base time to set.
*
* Set the base time of an element. See gst_element_get_base_time().
*
* MT safe.
*/
void
gst_element_set_base_time (GstElement * element, GstClockTime time)
{
GstClockTime old;
g_return_if_fail (GST_IS_ELEMENT (element));
g_return_if_fail (GST_CLOCK_TIME_IS_VALID (time));
GST_OBJECT_LOCK (element);
old = element->base_time;
element->base_time = time;
GST_OBJECT_UNLOCK (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, element,
"set base_time=%" GST_TIME_FORMAT ", old %" GST_TIME_FORMAT,
GST_TIME_ARGS (time), GST_TIME_ARGS (old));
}
/**
* gst_element_get_base_time:
* @element: a #GstElement.
*
* Returns the base time of the element. The base time is the
* absolute time of the clock when this element was last put to
* PLAYING. Subtracting the base time from the clock time gives
* the running time of the element.
*
* Returns: the base time of the element.
*
* MT safe.
*/
GstClockTime
gst_element_get_base_time (GstElement * element)
{
GstClockTime result;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_CLOCK_TIME_NONE);
GST_OBJECT_LOCK (element);
result = element->base_time;
GST_OBJECT_UNLOCK (element);
return result;
}
/**
* gst_element_set_start_time:
* @element: a #GstElement.
* @time: the base time to set.
*
* Set the start time of an element. The start time of the element is the
* running time of the element when it last went to the PAUSED state. In READY
* or after a flushing seek, it is set to 0.
*
* Toplevel elements like #GstPipeline will manage the start_time and
* base_time on its children. Setting the start_time to #GST_CLOCK_TIME_NONE
* on such a toplevel element will disable the distribution of the base_time to
* the children and can be useful if the application manages the base_time
* itself, for example if you want to synchronize capture from multiple
* pipelines, and you can also ensure that the pipelines have the same clock.
*
* MT safe.
*/
void
gst_element_set_start_time (GstElement * element, GstClockTime time)
{
GstClockTime old;
g_return_if_fail (GST_IS_ELEMENT (element));
GST_OBJECT_LOCK (element);
old = GST_ELEMENT_START_TIME (element);
GST_ELEMENT_START_TIME (element) = time;
GST_OBJECT_UNLOCK (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, element,
"set start_time=%" GST_TIME_FORMAT ", old %" GST_TIME_FORMAT,
GST_TIME_ARGS (time), GST_TIME_ARGS (old));
}
/**
* gst_element_get_start_time:
* @element: a #GstElement.
*
* Returns the start time of the element. The start time is the
* running time of the clock when this element was last put to PAUSED.
*
* Usually the start_time is managed by a toplevel element such as
* #GstPipeline.
*
* MT safe.
*
* Returns: the start time of the element.
*/
GstClockTime
gst_element_get_start_time (GstElement * element)
{
GstClockTime result;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_CLOCK_TIME_NONE);
GST_OBJECT_LOCK (element);
result = GST_ELEMENT_START_TIME (element);
GST_OBJECT_UNLOCK (element);
return result;
}
/**
* gst_element_get_current_running_time:
* @element: a #GstElement.
*
* Returns the running time of the element. The running time is the
* element's clock time minus its base time. Will return GST_CLOCK_TIME_NONE
* if the element has no clock, or if its base time has not been set.
*
* Returns: the running time of the element, or GST_CLOCK_TIME_NONE if the
* element has no clock or its base time has not been set.
*
* Since: 1.18
*/
GstClockTime
gst_element_get_current_running_time (GstElement * element)
{
GstClockTime base_time, clock_time;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_CLOCK_TIME_NONE);
base_time = gst_element_get_base_time (element);
if (!GST_CLOCK_TIME_IS_VALID (base_time)) {
GST_DEBUG_OBJECT (element, "Could not determine base time");
return GST_CLOCK_TIME_NONE;
}
clock_time = gst_element_get_current_clock_time (element);
if (!GST_CLOCK_TIME_IS_VALID (clock_time)) {
return GST_CLOCK_TIME_NONE;
}
if (clock_time < base_time) {
GST_DEBUG_OBJECT (element, "Got negative current running time");
return GST_CLOCK_TIME_NONE;
}
return clock_time - base_time;
}
/**
* gst_element_get_current_clock_time:
* @element: a #GstElement.
*
* Returns the current clock time of the element, as in, the time of the
* element's clock, or GST_CLOCK_TIME_NONE if there is no clock.
*
* Returns: the clock time of the element, or GST_CLOCK_TIME_NONE if there is
* no clock.
*
* Since: 1.18
*/
GstClockTime
gst_element_get_current_clock_time (GstElement * element)
{
GstClock *clock = NULL;
GstClockTime ret;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_CLOCK_TIME_NONE);
clock = gst_element_get_clock (element);
if (!clock) {
GST_DEBUG_OBJECT (element, "Element has no clock");
return GST_CLOCK_TIME_NONE;
}
ret = gst_clock_get_time (clock);
gst_object_unref (clock);
return ret;
}
#if 0
/**
* gst_element_set_index:
* @element: a #GstElement.
* @index: (transfer none): a #GstIndex.
*
* Set @index on the element. The refcount of the index
* will be increased, any previously set index is unreffed.
*
* MT safe.
*/
void
gst_element_set_index (GstElement * element, GstIndex * index)
{
GstElementClass *oclass;
g_return_if_fail (GST_IS_ELEMENT (element));
g_return_if_fail (index == NULL || GST_IS_INDEX (index));
oclass = GST_ELEMENT_GET_CLASS (element);
if (oclass->set_index)
oclass->set_index (element, index);
}
/**
* gst_element_get_index:
* @element: a #GstElement.
*
* Gets the index from the element.
*
* Returns: (transfer full) (nullable): a #GstIndex or %NULL when no
* index was set on the element. unref after usage.
*
* MT safe.
*/
GstIndex *
gst_element_get_index (GstElement * element)
{
GstElementClass *oclass;
GstIndex *result = NULL;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
oclass = GST_ELEMENT_GET_CLASS (element);
if (oclass->get_index)
result = oclass->get_index (element);
return result;
}
#endif
/**
* gst_element_add_pad:
* @element: a #GstElement to add the pad to.
* @pad: (transfer floating): the #GstPad to add to the element.
*
* Adds a pad (link point) to @element. @pad's parent will be set to @element;
* see gst_object_set_parent() for refcounting information.
*
* Pads are automatically activated when added in the PAUSED or PLAYING
* state.
*
* The pad and the element should be unlocked when calling this function.
*
* This function will emit the #GstElement::pad-added signal on the element.
*
* Returns: %TRUE if the pad could be added. This function can fail when
* a pad with the same name already existed or the pad already had another
* parent.
*
* MT safe.
*/
gboolean
gst_element_add_pad (GstElement * element, GstPad * pad)
{
gchar *pad_name;
gboolean active;
gboolean should_activate;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (GST_IS_PAD (pad), FALSE);
/* locking pad to look at the name */
GST_OBJECT_LOCK (pad);
pad_name = g_strdup (GST_PAD_NAME (pad));
GST_CAT_INFO_OBJECT (GST_CAT_ELEMENT_PADS, element, "adding pad '%s'",
GST_STR_NULL (pad_name));
active = GST_PAD_IS_ACTIVE (pad);
GST_OBJECT_FLAG_SET (pad, GST_PAD_FLAG_NEED_PARENT);
GST_OBJECT_UNLOCK (pad);
/* then check to see if there's already a pad by that name here */
GST_OBJECT_LOCK (element);
if (G_UNLIKELY (!gst_object_check_uniqueness (element->pads, pad_name)))
goto name_exists;
/* try to set the pad's parent */
if (G_UNLIKELY (!gst_object_set_parent (GST_OBJECT_CAST (pad),
GST_OBJECT_CAST (element))))
goto had_parent;
/* check for active pads */
should_activate = !active && (GST_STATE (element) > GST_STATE_READY ||
GST_STATE_NEXT (element) == GST_STATE_PAUSED);
g_free (pad_name);
/* add it to the list */
switch (gst_pad_get_direction (pad)) {
case GST_PAD_SRC:
element->srcpads = g_list_append (element->srcpads, pad);
element->numsrcpads++;
break;
case GST_PAD_SINK:
element->sinkpads = g_list_append (element->sinkpads, pad);
element->numsinkpads++;
break;
default:
goto no_direction;
}
element->pads = g_list_append (element->pads, pad);
element->numpads++;
element->pads_cookie++;
GST_OBJECT_UNLOCK (element);
if (should_activate)
gst_pad_set_active (pad, TRUE);
/* emit the PAD_ADDED signal */
g_signal_emit (element, gst_element_signals[PAD_ADDED], 0, pad);
GST_TRACER_ELEMENT_ADD_PAD (element, pad);
return TRUE;
/* ERROR cases */
name_exists:
{
g_critical ("Padname %s is not unique in element %s, not adding",
pad_name, GST_ELEMENT_NAME (element));
GST_OBJECT_UNLOCK (element);
g_free (pad_name);
gst_object_ref_sink (pad);
gst_object_unref (pad);
return FALSE;
}
had_parent:
{
g_critical
("Pad %s already has parent when trying to add to element %s",
pad_name, GST_ELEMENT_NAME (element));
GST_OBJECT_UNLOCK (element);
g_free (pad_name);
return FALSE;
}
no_direction:
{
GST_OBJECT_LOCK (pad);
g_critical
("Trying to add pad %s to element %s, but it has no direction",
GST_OBJECT_NAME (pad), GST_ELEMENT_NAME (element));
GST_OBJECT_UNLOCK (pad);
GST_OBJECT_UNLOCK (element);
return FALSE;
}
}
/**
* gst_element_remove_pad:
* @element: a #GstElement to remove pad from.
* @pad: (transfer none): the #GstPad to remove from the element.
*
* Removes @pad from @element. @pad will be destroyed if it has not been
* referenced elsewhere using gst_object_unparent().
*
* This function is used by plugin developers and should not be used
* by applications. Pads that were dynamically requested from elements
* with gst_element_request_pad() should be released with the
* gst_element_release_request_pad() function instead.
*
* Pads are not automatically deactivated so elements should perform the needed
* steps to deactivate the pad in case this pad is removed in the PAUSED or
* PLAYING state. See gst_pad_set_active() for more information about
* deactivating pads.
*
* The pad and the element should be unlocked when calling this function.
*
* This function will emit the #GstElement::pad-removed signal on the element.
*
* Returns: %TRUE if the pad could be removed. Can return %FALSE if the
* pad does not belong to the provided element.
*
* MT safe.
*/
gboolean
gst_element_remove_pad (GstElement * element, GstPad * pad)
{
GstPad *peer;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (GST_IS_PAD (pad), FALSE);
/* locking pad to look at the name and parent */
GST_OBJECT_LOCK (pad);
GST_CAT_INFO_OBJECT (GST_CAT_ELEMENT_PADS, element, "removing pad '%s'",
GST_STR_NULL (GST_PAD_NAME (pad)));
if (G_UNLIKELY (GST_PAD_PARENT (pad) != element))
goto not_our_pad;
GST_OBJECT_UNLOCK (pad);
/* unlink */
if ((peer = gst_pad_get_peer (pad))) {
/* window for MT unsafeness, someone else could unlink here
* and then we call unlink with wrong pads. The unlink
* function would catch this and safely return failed. */
if (GST_PAD_IS_SRC (pad))
gst_pad_unlink (pad, peer);
else
gst_pad_unlink (peer, pad);
gst_object_unref (peer);
}
/* if this is a ghost pad we also need to unset the target or it
* will stay linked although not allowed according to the topology.
*
* FIXME 2.0: Do this generically somehow from inside GstGhostPad
* when it gets unparented.
*/
if (GST_IS_GHOST_PAD (pad)) {
gst_ghost_pad_set_target (GST_GHOST_PAD (pad), NULL);
}
GST_OBJECT_LOCK (element);
/* remove it from the list */
switch (gst_pad_get_direction (pad)) {
case GST_PAD_SRC:
element->srcpads = g_list_remove (element->srcpads, pad);
element->numsrcpads--;
break;
case GST_PAD_SINK:
element->sinkpads = g_list_remove (element->sinkpads, pad);
element->numsinkpads--;
break;
default:
g_critical ("Removing pad without direction???");
break;
}
element->pads = g_list_remove (element->pads, pad);
element->numpads--;
element->pads_cookie++;
GST_OBJECT_UNLOCK (element);
/* emit the PAD_REMOVED signal before unparenting and losing the last ref. */
g_signal_emit (element, gst_element_signals[PAD_REMOVED], 0, pad);
GST_TRACER_ELEMENT_REMOVE_PAD (element, pad);
gst_object_unparent (GST_OBJECT_CAST (pad));
return TRUE;
/* ERRORS */
not_our_pad:
{
/* locking order is element > pad */
GST_OBJECT_UNLOCK (pad);
GST_OBJECT_LOCK (element);
GST_OBJECT_LOCK (pad);
g_critical ("Padname %s:%s does not belong to element %s when removing",
GST_DEBUG_PAD_NAME (pad), GST_ELEMENT_NAME (element));
GST_OBJECT_UNLOCK (pad);
GST_OBJECT_UNLOCK (element);
return FALSE;
}
}
/**
* gst_element_no_more_pads:
* @element: a #GstElement
*
* Use this function to signal that the element does not expect any more pads
* to show up in the current pipeline. This function should be called whenever
* pads have been added by the element itself. Elements with #GST_PAD_SOMETIMES
* pad templates use this in combination with autopluggers to figure out that
* the element is done initializing its pads.
*
* This function emits the #GstElement::no-more-pads signal.
*
* MT safe.
*/
void
gst_element_no_more_pads (GstElement * element)
{
g_return_if_fail (GST_IS_ELEMENT (element));
g_signal_emit (element, gst_element_signals[NO_MORE_PADS], 0);
}
static gint
pad_compare_name (GstPad * pad1, const gchar * name)
{
gint result;
GST_OBJECT_LOCK (pad1);
result = strcmp (GST_PAD_NAME (pad1), name);
GST_OBJECT_UNLOCK (pad1);
return result;
}
/**
* gst_element_get_static_pad:
* @element: a #GstElement to find a static pad of.
* @name: the name of the static #GstPad to retrieve.
*
* Retrieves a pad from @element by name. This version only retrieves
* already-existing (i.e. 'static') pads.
*
* Returns: (transfer full) (nullable): the requested #GstPad if
* found, otherwise %NULL. unref after usage.
*
* MT safe.
*/
GstPad *
gst_element_get_static_pad (GstElement * element, const gchar * name)
{
GList *find;
GstPad *result = NULL;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
g_return_val_if_fail (name != NULL, NULL);
GST_OBJECT_LOCK (element);
find =
g_list_find_custom (element->pads, name, (GCompareFunc) pad_compare_name);
if (find) {
result = GST_PAD_CAST (find->data);
gst_object_ref (result);
}
if (result == NULL) {
GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "no such pad '%s' in element \"%s\"",
name, GST_ELEMENT_NAME (element));
} else {
GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "found pad %s:%s",
GST_ELEMENT_NAME (element), name);
}
GST_OBJECT_UNLOCK (element);
return result;
}
static gboolean
gst_element_is_valid_request_template_name (const gchar * templ_name,
const gchar * name)
{
gchar *endptr;
const gchar *templ_name_ptr, *name_ptr;
gboolean next_specifier;
guint templ_postfix_len = 0, name_postfix_len = 0;
g_return_val_if_fail (templ_name != NULL, FALSE);
g_return_val_if_fail (name != NULL, FALSE);
/* Is this the template name? */
if (strcmp (templ_name, name) == 0)
return TRUE;
/* otherwise check all the specifiers */
do {
/* Because of sanity checks in gst_pad_template_new(), we know that %s
* and %d and %u, occurring at the template_name */
templ_name_ptr = strchr (templ_name, '%');
/* check characters ahead of the specifier */
if (!templ_name_ptr || strlen (name) <= templ_name_ptr - templ_name
|| strncmp (templ_name, name, templ_name_ptr - templ_name) != 0) {
return FALSE;
}
/* %s is not allowed for multiple specifiers, just a single specifier can be
* accepted in gst_pad_template_new() and can not be mixed with other
* specifier '%u' and '%d' */
if (*(templ_name_ptr + 1) == 's') {
return TRUE;
}
name_ptr = name + (templ_name_ptr - templ_name);
/* search next specifier, each of specifier should be separated by '_' */
templ_name = strchr (templ_name_ptr, '_');
name = strchr (name_ptr, '_');
/* don't match the number of specifiers */
if ((templ_name && !name) || (!templ_name && name))
return FALSE;
if (templ_name && name)
next_specifier = TRUE;
else
next_specifier = FALSE;
/* check characters followed by the specifier */
if (*(templ_name_ptr + 2) != '\0' && *(templ_name_ptr + 2) != '_') {
if (next_specifier) {
templ_postfix_len = templ_name - (templ_name_ptr + 2);
name_postfix_len = name - name_ptr;
} else {
templ_postfix_len = strlen (templ_name_ptr + 2);
name_postfix_len = strlen (name_ptr);
}
if (strncmp (templ_name_ptr + 2,
name_ptr + name_postfix_len - templ_postfix_len,
templ_postfix_len) != 0) {
return FALSE;
}
}
/* verify the specifier */
if (*(name_ptr) == '%') {
guint len;
len = (next_specifier) ? name - name_ptr : strlen (name_ptr);
if (strncmp (name_ptr, templ_name_ptr, len) != 0)
return FALSE;
} else {
const gchar *specifier;
gchar *target = NULL;
/* extract specifier when it has postfix characters */
if (name_postfix_len > templ_postfix_len) {
target = g_strndup (name_ptr, name_postfix_len - templ_postfix_len);
}
specifier = target ? target : name_ptr;
if (*(templ_name_ptr + 1) == 'd') {
gint64 tmp;
/* it's an int */
tmp = g_ascii_strtoll (specifier, &endptr, 10);
if (tmp < G_MININT || tmp > G_MAXINT || (*endptr != '\0'
&& *endptr != '_'))
return FALSE;
} else if (*(templ_name_ptr + 1) == 'u') {
guint64 tmp;
/* it's an int */
tmp = g_ascii_strtoull (specifier, &endptr, 10);
if (tmp > G_MAXUINT || (*endptr != '\0' && *endptr != '_'))
return FALSE;
}
g_free (target);
}
/* otherwise we increment these from NULL to 1 */
if (next_specifier) {
templ_name++;
name++;
}
} while (next_specifier);
return TRUE;
}
static GstPad *
_gst_element_request_pad (GstElement * element, GstPadTemplate * templ,
const gchar * name, const GstCaps * caps)
{
GstPad *newpad = NULL;
GstElementClass *oclass;
oclass = GST_ELEMENT_GET_CLASS (element);
#ifndef G_DISABLE_CHECKS
/* Some sanity checking here */
if (name) {
GstPad *pad;
g_return_val_if_fail (gst_element_is_valid_request_template_name
(templ->name_template, name), NULL);
pad = gst_element_get_static_pad (element, name);
if (pad) {
gst_object_unref (pad);
/* FIXME 2.0: Change this to g_return_val_if_fail() */
g_critical ("Element %s already has a pad named %s, the behaviour of "
" gst_element_get_request_pad() for existing pads is undefined!",
GST_ELEMENT_NAME (element), name);
}
}
#endif
#ifdef GST_ENABLE_EXTRA_CHECKS
{
if (!g_list_find (oclass->padtemplates, templ)) {
/* FIXME 2.0: Change this to g_return_val_if_fail() */
g_critical ("Element type %s does not have a pad template %s (%p)",
g_type_name (G_OBJECT_TYPE (element)), templ->name_template, templ);
}
}
#endif
if (oclass->request_new_pad)
newpad = (oclass->request_new_pad) (element, templ, name, caps);
if (newpad)
gst_object_ref (newpad);
return newpad;
}
#ifndef GST_REMOVE_DEPRECATED
/**
* gst_element_get_request_pad:
* @element: a #GstElement to find a request pad of.
* @name: the name of the request #GstPad to retrieve.
*
* The name of this function is confusing to people learning GStreamer.
* gst_element_request_pad_simple() aims at making it more explicit it is
* a simplified gst_element_request_pad().
*
* Deprecated: 1.20: Prefer using gst_element_request_pad_simple() which
* provides the exact same functionality.
*
* Returns: (transfer full) (nullable): requested #GstPad if found,
* otherwise %NULL. Release after usage.
*/
GstPad *
gst_element_get_request_pad (GstElement * element, const gchar * name)
{
return gst_element_request_pad_simple (element, name);
}
#endif
/**
* gst_element_request_pad_simple:
* @element: a #GstElement to find a request pad of.
* @name: the name of the request #GstPad to retrieve.
*
* Retrieves a pad from the element by name (e.g. "src_\%d"). This version only
* retrieves request pads. The pad should be released with
* gst_element_release_request_pad().
*
* This method is slower than manually getting the pad template and calling
* gst_element_request_pad() if the pads should have a specific name (e.g.
* @name is "src_1" instead of "src_\%u").
*
* Note that this function was introduced in GStreamer 1.20 in order to provide
* a better name to gst_element_get_request_pad(). Prior to 1.20, users
* should use gst_element_get_request_pad() which provides the same
* functionality.
*
* Returns: (transfer full) (nullable): requested #GstPad if found,
* otherwise %NULL. Release after usage.
*
* Since: 1.20
*/
GstPad *
gst_element_request_pad_simple (GstElement * element, const gchar * name)
{
GstPadTemplate *templ = NULL;
GstPad *pad;
const gchar *req_name = NULL;
gboolean templ_found = FALSE;
GList *list;
GstElementClass *class;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
g_return_val_if_fail (name != NULL, NULL);
class = GST_ELEMENT_GET_CLASS (element);
templ = gst_element_class_request_pad_simple_template (class, name);
if (templ) {
req_name = strstr (name, "%") ? NULL : name;
templ_found = TRUE;
} else {
/* there is no % in the name, try to find a matching template */
list = class->padtemplates;
while (!templ_found && list) {
templ = (GstPadTemplate *) list->data;
if (templ->presence == GST_PAD_REQUEST) {
GST_CAT_DEBUG (GST_CAT_PADS, "comparing %s to %s", name,
templ->name_template);
if (gst_element_is_valid_request_template_name (templ->name_template,
name)) {
templ_found = TRUE;
req_name = name;
break;
}
}
list = list->next;
}
}
if (!templ_found)
return NULL;
pad = _gst_element_request_pad (element, templ, req_name, NULL);
return pad;
}
/**
* gst_element_request_pad: (virtual request_new_pad)
* @element: a #GstElement to find a request pad of.
* @templ: a #GstPadTemplate of which we want a pad of.
* @name: (transfer none) (allow-none): the name of the request #GstPad
* to retrieve. Can be %NULL.
* @caps: (transfer none) (allow-none): the caps of the pad we want to
* request. Can be %NULL.
*
* Retrieves a request pad from the element according to the provided template.
* Pad templates can be looked up using
* gst_element_factory_get_static_pad_templates().
*
* The pad should be released with gst_element_release_request_pad().
*
* Returns: (transfer full) (nullable): requested #GstPad if found,
* otherwise %NULL. Release after usage.
*/
GstPad *
gst_element_request_pad (GstElement * element,
GstPadTemplate * templ, const gchar * name, const GstCaps * caps)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
g_return_val_if_fail (templ != NULL, NULL);
g_return_val_if_fail (templ->presence == GST_PAD_REQUEST, NULL);
return _gst_element_request_pad (element, templ, name, caps);
}
static GstIterator *
gst_element_iterate_pad_list (GstElement * element, GList ** padlist)
{
GstIterator *result;
GST_OBJECT_LOCK (element);
result = gst_iterator_new_list (GST_TYPE_PAD,
GST_OBJECT_GET_LOCK (element),
&element->pads_cookie, padlist, (GObject *) element, NULL);
GST_OBJECT_UNLOCK (element);
return result;
}
/**
* gst_element_iterate_pads:
* @element: a #GstElement to iterate pads of.
*
* Retrieves an iterator of @element's pads. The iterator should
* be freed after usage. Also more specialized iterators exists such as
* gst_element_iterate_src_pads() or gst_element_iterate_sink_pads().
*
* The order of pads returned by the iterator will be the order in which
* the pads were added to the element.
*
* Returns: (transfer full): the #GstIterator of #GstPad.
*
* MT safe.
*/
GstIterator *
gst_element_iterate_pads (GstElement * element)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
return gst_element_iterate_pad_list (element, &element->pads);
}
/**
* gst_element_iterate_src_pads:
* @element: a #GstElement.
*
* Retrieves an iterator of @element's source pads.
*
* The order of pads returned by the iterator will be the order in which
* the pads were added to the element.
*
* Returns: (transfer full): the #GstIterator of #GstPad.
*
* MT safe.
*/
GstIterator *
gst_element_iterate_src_pads (GstElement * element)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
return gst_element_iterate_pad_list (element, &element->srcpads);
}
/**
* gst_element_iterate_sink_pads:
* @element: a #GstElement.
*
* Retrieves an iterator of @element's sink pads.
*
* The order of pads returned by the iterator will be the order in which
* the pads were added to the element.
*
* Returns: (transfer full): the #GstIterator of #GstPad.
*
* MT safe.
*/
GstIterator *
gst_element_iterate_sink_pads (GstElement * element)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
return gst_element_iterate_pad_list (element, &element->sinkpads);
}
static gboolean
gst_element_do_foreach_pad (GstElement * element,
GstElementForeachPadFunc func, gpointer user_data,
GList ** p_pads, guint16 * p_npads)
{
gboolean ret = TRUE;
GstPad **pads;
guint n_pads, i;
GList *l;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (func != NULL, FALSE);
GST_OBJECT_LOCK (element);
n_pads = *p_npads;
pads = g_newa (GstPad *, n_pads + 1);
for (l = *p_pads, i = 0; l != NULL; l = l->next) {
g_assert (i < n_pads);
pads[i++] = gst_object_ref (l->data);
}
GST_OBJECT_UNLOCK (element);
if (n_pads == 0)
return FALSE;
for (i = 0; i < n_pads; ++i) {
ret = func (element, pads[i], user_data);
if (!ret)
break;
}
for (i = 0; i < n_pads; ++i)
gst_object_unref (pads[i]);
return ret;
}
/**
* gst_element_foreach_sink_pad:
* @element: a #GstElement to iterate sink pads of
* @func: (scope call): function to call for each sink pad
* @user_data: (closure): user data passed to @func
*
* Call @func with @user_data for each of @element's sink pads. @func will be
* called exactly once for each sink pad that exists at the time of this call,
* unless one of the calls to @func returns %FALSE in which case we will stop
* iterating pads and return early. If new sink pads are added or sink pads
* are removed while the sink pads are being iterated, this will not be taken
* into account until next time this function is used.
*
* Returns: %FALSE if @element had no sink pads or if one of the calls to @func
* returned %FALSE.
*
* Since: 1.14
*/
gboolean
gst_element_foreach_sink_pad (GstElement * element,
GstElementForeachPadFunc func, gpointer user_data)
{
return gst_element_do_foreach_pad (element, func, user_data,
&element->sinkpads, &element->numsinkpads);
}
/**
* gst_element_foreach_src_pad:
* @element: a #GstElement to iterate source pads of
* @func: (scope call): function to call for each source pad
* @user_data: (closure): user data passed to @func
*
* Call @func with @user_data for each of @element's source pads. @func will be
* called exactly once for each source pad that exists at the time of this call,
* unless one of the calls to @func returns %FALSE in which case we will stop
* iterating pads and return early. If new source pads are added or source pads
* are removed while the source pads are being iterated, this will not be taken
* into account until next time this function is used.
*
* Returns: %FALSE if @element had no source pads or if one of the calls
* to @func returned %FALSE.
*
* Since: 1.14
*/
gboolean
gst_element_foreach_src_pad (GstElement * element,
GstElementForeachPadFunc func, gpointer user_data)
{
return gst_element_do_foreach_pad (element, func, user_data,
&element->srcpads, &element->numsrcpads);
}
/**
* gst_element_foreach_pad:
* @element: a #GstElement to iterate pads of
* @func: (scope call): function to call for each pad
* @user_data: (closure): user data passed to @func
*
* Call @func with @user_data for each of @element's pads. @func will be called
* exactly once for each pad that exists at the time of this call, unless
* one of the calls to @func returns %FALSE in which case we will stop
* iterating pads and return early. If new pads are added or pads are removed
* while pads are being iterated, this will not be taken into account until
* next time this function is used.
*
* Returns: %FALSE if @element had no pads or if one of the calls to @func
* returned %FALSE.
*
* Since: 1.14
*/
gboolean
gst_element_foreach_pad (GstElement * element, GstElementForeachPadFunc func,
gpointer user_data)
{
return gst_element_do_foreach_pad (element, func, user_data,
&element->pads, &element->numpads);
}
/**
* gst_element_class_add_pad_template:
* @klass: the #GstElementClass to add the pad template to.
* @templ: (transfer floating): a #GstPadTemplate to add to the element class.
*
* Adds a padtemplate to an element class. This is mainly used in the _class_init
* functions of classes. If a pad template with the same name as an already
* existing one is added the old one is replaced by the new one.
*
* @templ's reference count will be incremented, and any floating
* reference will be removed (see gst_object_ref_sink())
*
*/
void
gst_element_class_add_pad_template (GstElementClass * klass,
GstPadTemplate * templ)
{
GList *template_list = klass->padtemplates;
g_return_if_fail (GST_IS_ELEMENT_CLASS (klass));
g_return_if_fail (GST_IS_PAD_TEMPLATE (templ));
/* If we already have a pad template with the same name replace the
* old one. */
while (template_list) {
GstPadTemplate *padtempl = (GstPadTemplate *) template_list->data;
/* Found pad with the same name, replace and return */
if (strcmp (templ->name_template, padtempl->name_template) == 0) {
gst_object_ref_sink (padtempl);
gst_object_unref (padtempl);
template_list->data = templ;
return;
}
template_list = g_list_next (template_list);
}
/* Take ownership of the floating ref */
gst_object_ref_sink (templ);
klass->padtemplates = g_list_append (klass->padtemplates, templ);
klass->numpadtemplates++;
}
/**
* gst_element_class_add_static_pad_template:
* @klass: the #GstElementClass to add the pad template to.
* @static_templ: #GstStaticPadTemplate to add as pad template to the element class.
*
* Adds a pad template to an element class based on the static pad template
* @templ. This is mainly used in the _class_init functions of element
* implementations. If a pad template with the same name already exists,
* the old one is replaced by the new one.
*
* Since: 1.8
*/
void
gst_element_class_add_static_pad_template (GstElementClass * klass,
GstStaticPadTemplate * static_templ)
{
gst_element_class_add_pad_template (klass,
gst_static_pad_template_get (static_templ));
}
/**
* gst_element_class_add_static_pad_template_with_gtype:
* @klass: the #GstElementClass to add the pad template to.
* @static_templ: #GstStaticPadTemplate to add as pad template to the element class.
* @pad_type: The #GType of the pad to create
*
* Adds a pad template to an element class based on the static pad template
* @templ. This is mainly used in the _class_init functions of element
* implementations. If a pad template with the same name already exists,
* the old one is replaced by the new one.
*
* Since: 1.14
*/
void
gst_element_class_add_static_pad_template_with_gtype (GstElementClass * klass,
GstStaticPadTemplate * static_templ, GType pad_type)
{
gst_element_class_add_pad_template (klass,
gst_pad_template_new_from_static_pad_template_with_gtype (static_templ,
pad_type));
}
/**
* gst_element_class_add_metadata:
* @klass: class to set metadata for
* @key: the key to set
* @value: the value to set
*
* Set @key with @value as metadata in @klass.
*/
void
gst_element_class_add_metadata (GstElementClass * klass,
const gchar * key, const gchar * value)
{
g_return_if_fail (GST_IS_ELEMENT_CLASS (klass));
g_return_if_fail (key != NULL);
g_return_if_fail (value != NULL);
gst_structure_set ((GstStructure *) klass->metadata,
key, G_TYPE_STRING, value, NULL);
}
/**
* gst_element_class_add_static_metadata:
* @klass: class to set metadata for
* @key: the key to set
* @value: the value to set
*
* Set @key with @value as metadata in @klass.
*
* Same as gst_element_class_add_metadata(), but @value must be a static string
* or an inlined string, as it will not be copied. (GStreamer plugins will
* be made resident once loaded, so this function can be used even from
* dynamically loaded plugins.)
*/
void
gst_element_class_add_static_metadata (GstElementClass * klass,
const gchar * key, const gchar * value)
{
GValue val = G_VALUE_INIT;
g_return_if_fail (GST_IS_ELEMENT_CLASS (klass));
g_return_if_fail (key != NULL);
g_return_if_fail (value != NULL);
g_value_init (&val, G_TYPE_STRING);
g_value_set_static_string (&val, value);
gst_structure_take_value ((GstStructure *) klass->metadata, key, &val);
}
/**
* gst_element_class_set_metadata:
* @klass: class to set metadata for
* @longname: The long English name of the element. E.g. "File Sink"
* @classification: String describing the type of element, as an unordered list
* separated with slashes ('/'). See draft-klass.txt of the design docs
* for more details and common types. E.g: "Sink/File"
* @description: Sentence describing the purpose of the element.
* E.g: "Write stream to a file"
* @author: Name and contact details of the author(s). Use \n to separate
* multiple author metadata. E.g: "Joe Bloggs &lt;joe.blogs at foo.com&gt;"
*
* Sets the detailed information for a #GstElementClass.
* > This function is for use in _class_init functions only.
*/
void
gst_element_class_set_metadata (GstElementClass * klass,
const gchar * longname, const gchar * classification,
const gchar * description, const gchar * author)
{
g_return_if_fail (GST_IS_ELEMENT_CLASS (klass));
g_return_if_fail (longname != NULL && *longname != '\0');
g_return_if_fail (classification != NULL && *classification != '\0');
g_return_if_fail (description != NULL && *description != '\0');
g_return_if_fail (author != NULL && *author != '\0');
gst_structure_id_set ((GstStructure *) klass->metadata,
GST_QUARK (ELEMENT_METADATA_LONGNAME), G_TYPE_STRING, longname,
GST_QUARK (ELEMENT_METADATA_KLASS), G_TYPE_STRING, classification,
GST_QUARK (ELEMENT_METADATA_DESCRIPTION), G_TYPE_STRING, description,
GST_QUARK (ELEMENT_METADATA_AUTHOR), G_TYPE_STRING, author, NULL);
}
/**
* gst_element_class_set_static_metadata:
* @klass: class to set metadata for
* @longname: The long English name of the element. E.g. "File Sink"
* @classification: String describing the type of element, as an unordered list
* separated with slashes ('/'). See draft-klass.txt of the design docs
* for more details and common types. E.g: "Sink/File"
* @description: Sentence describing the purpose of the element.
* E.g: "Write stream to a file"
* @author: Name and contact details of the author(s). Use \n to separate
* multiple author metadata. E.g: "Joe Bloggs &lt;joe.blogs at foo.com&gt;"
*
* Sets the detailed information for a #GstElementClass.
*
* > This function is for use in _class_init functions only.
*
* Same as gst_element_class_set_metadata(), but @longname, @classification,
* @description, and @author must be static strings or inlined strings, as
* they will not be copied. (GStreamer plugins will be made resident once
* loaded, so this function can be used even from dynamically loaded plugins.)
*/
void
gst_element_class_set_static_metadata (GstElementClass * klass,
const gchar * longname, const gchar * classification,
const gchar * description, const gchar * author)
{
GstStructure *s = (GstStructure *) klass->metadata;
GValue val = G_VALUE_INIT;
g_return_if_fail (GST_IS_ELEMENT_CLASS (klass));
g_return_if_fail (longname != NULL && *longname != '\0');
g_return_if_fail (classification != NULL && *classification != '\0');
g_return_if_fail (description != NULL && *description != '\0');
g_return_if_fail (author != NULL && *author != '\0');
g_value_init (&val, G_TYPE_STRING);
g_value_set_static_string (&val, longname);
gst_structure_id_set_value (s, GST_QUARK (ELEMENT_METADATA_LONGNAME), &val);
g_value_set_static_string (&val, classification);
gst_structure_id_set_value (s, GST_QUARK (ELEMENT_METADATA_KLASS), &val);
g_value_set_static_string (&val, description);
gst_structure_id_set_value (s, GST_QUARK (ELEMENT_METADATA_DESCRIPTION),
&val);
g_value_set_static_string (&val, author);
gst_structure_id_take_value (s, GST_QUARK (ELEMENT_METADATA_AUTHOR), &val);
}
/**
* gst_element_class_get_metadata:
* @klass: class to get metadata for
* @key: the key to get
*
* Get metadata with @key in @klass.
*
* Returns: the metadata for @key.
*/
const gchar *
gst_element_class_get_metadata (GstElementClass * klass, const gchar * key)
{
g_return_val_if_fail (GST_IS_ELEMENT_CLASS (klass), NULL);
g_return_val_if_fail (key != NULL, NULL);
return gst_structure_get_string ((GstStructure *) klass->metadata, key);
}
/**
* gst_element_get_metadata:
* @element: class to get metadata for
* @key: the key to get
*
* Get metadata with @key in @klass.
*
* Returns: the metadata for @key.
*
* Since: 1.14
*/
const gchar *
gst_element_get_metadata (GstElement * element, const gchar * key)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
g_return_val_if_fail (key != NULL, NULL);
return gst_element_class_get_metadata (GST_ELEMENT_GET_CLASS (element), key);
}
/**
* gst_element_class_get_pad_template_list:
* @element_class: a #GstElementClass to get pad templates of.
*
* Retrieves a list of the pad templates associated with @element_class. The
* list must not be modified by the calling code.
* > If you use this function in the GInstanceInitFunc of an object class
* > that has subclasses, make sure to pass the g_class parameter of the
* > GInstanceInitFunc here.
*
* Returns: (transfer none) (element-type Gst.PadTemplate): the #GList of
* pad templates.
*/
GList *
gst_element_class_get_pad_template_list (GstElementClass * element_class)
{
g_return_val_if_fail (GST_IS_ELEMENT_CLASS (element_class), NULL);
return element_class->padtemplates;
}
/**
* gst_element_get_pad_template_list:
* @element: a #GstElement to get pad templates of.
*
* Retrieves a list of the pad templates associated with @element. The
* list must not be modified by the calling code.
*
* Returns: (transfer none) (element-type Gst.PadTemplate): the #GList of
* pad templates.
*
* Since: 1.14
*/
GList *
gst_element_get_pad_template_list (GstElement * element)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
return
gst_element_class_get_pad_template_list (GST_ELEMENT_GET_CLASS (element));
}
/**
* gst_element_class_get_pad_template:
* @element_class: a #GstElementClass to get the pad template of.
* @name: the name of the #GstPadTemplate to get.
*
* Retrieves a padtemplate from @element_class with the given name.
* > If you use this function in the GInstanceInitFunc of an object class
* > that has subclasses, make sure to pass the g_class parameter of the
* > GInstanceInitFunc here.
*
* Returns: (transfer none) (nullable): the #GstPadTemplate with the
* given name, or %NULL if none was found. No unreferencing is
* necessary.
*/
GstPadTemplate *
gst_element_class_get_pad_template (GstElementClass *
element_class, const gchar * name)
{
GList *padlist;
g_return_val_if_fail (GST_IS_ELEMENT_CLASS (element_class), NULL);
g_return_val_if_fail (name != NULL, NULL);
padlist = element_class->padtemplates;
while (padlist) {
GstPadTemplate *padtempl = (GstPadTemplate *) padlist->data;
if (strcmp (padtempl->name_template, name) == 0)
return padtempl;
padlist = g_list_next (padlist);
}
return NULL;
}
/**
* gst_element_get_pad_template:
* @element: a #GstElement to get the pad template of.
* @name: the name of the #GstPadTemplate to get.
*
* Retrieves a padtemplate from @element with the given name.
*
* Returns: (transfer none) (nullable): the #GstPadTemplate with the
* given name, or %NULL if none was found. No unreferencing is
* necessary.
*
* Since: 1.14
*/
GstPadTemplate *
gst_element_get_pad_template (GstElement * element, const gchar * name)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
g_return_val_if_fail (name != NULL, NULL);
return gst_element_class_get_pad_template (GST_ELEMENT_GET_CLASS (element),
name);
}
static GstPadTemplate *
gst_element_class_request_pad_simple_template (GstElementClass *
element_class, const gchar * name)
{
GstPadTemplate *tmpl;
tmpl = gst_element_class_get_pad_template (element_class, name);
if (tmpl != NULL && tmpl->presence == GST_PAD_REQUEST)
return tmpl;
return NULL;
}
/* get a random pad on element of the given direction.
* The pad is random in a sense that it is the first pad that is (optionally) linked.
*/
static GstPad *
gst_element_get_random_pad (GstElement * element,
gboolean need_linked, GstPadDirection dir)
{
GstPad *result = NULL;
GList *pads;
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "getting a random pad");
switch (dir) {
case GST_PAD_SRC:
GST_OBJECT_LOCK (element);
pads = element->srcpads;
break;
case GST_PAD_SINK:
GST_OBJECT_LOCK (element);
pads = element->sinkpads;
break;
default:
goto wrong_direction;
}
for (; pads; pads = g_list_next (pads)) {
GstPad *pad = GST_PAD_CAST (pads->data);
GST_OBJECT_LOCK (pad);
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "checking pad %s:%s",
GST_DEBUG_PAD_NAME (pad));
if (need_linked && !GST_PAD_IS_LINKED (pad)) {
/* if we require a linked pad, and it is not linked, continue the
* search */
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "pad %s:%s is not linked",
GST_DEBUG_PAD_NAME (pad));
GST_OBJECT_UNLOCK (pad);
continue;
} else {
/* found a pad, stop search */
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "found pad %s:%s",
GST_DEBUG_PAD_NAME (pad));
GST_OBJECT_UNLOCK (pad);
result = pad;
break;
}
}
if (result)
gst_object_ref (result);
GST_OBJECT_UNLOCK (element);
return result;
/* ERROR handling */
wrong_direction:
{
g_warning ("unknown pad direction %d", dir);
return NULL;
}
}
static gboolean
gst_element_default_send_event (GstElement * element, GstEvent * event)
{
gboolean result = FALSE;
GstPad *pad;
pad = GST_EVENT_IS_DOWNSTREAM (event) ?
gst_element_get_random_pad (element, TRUE, GST_PAD_SINK) :
gst_element_get_random_pad (element, TRUE, GST_PAD_SRC);
if (pad) {
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS,
"pushing %s event to random %s pad %s:%s",
GST_EVENT_TYPE_NAME (event),
(GST_PAD_DIRECTION (pad) == GST_PAD_SRC ? "src" : "sink"),
GST_DEBUG_PAD_NAME (pad));
result = gst_pad_send_event (pad, event);
gst_object_unref (pad);
} else {
GST_CAT_INFO (GST_CAT_ELEMENT_PADS, "can't send %s event on element %s",
GST_EVENT_TYPE_NAME (event), GST_ELEMENT_NAME (element));
gst_event_unref (event);
}
return result;
}
/**
* gst_element_send_event:
* @element: a #GstElement to send the event to.
* @event: (transfer full): the #GstEvent to send to the element.
*
* Sends an event to an element. If the element doesn't implement an
* event handler, the event will be pushed on a random linked sink pad for
* downstream events or a random linked source pad for upstream events.
*
* This function takes ownership of the provided event so you should
* gst_event_ref() it if you want to reuse the event after this call.
*
* MT safe.
*
* Returns: %TRUE if the event was handled. Events that trigger a preroll (such
* as flushing seeks and steps) will emit %GST_MESSAGE_ASYNC_DONE.
*/
gboolean
gst_element_send_event (GstElement * element, GstEvent * event)
{
GstElementClass *oclass;
gboolean result = FALSE;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (event != NULL, FALSE);
oclass = GST_ELEMENT_GET_CLASS (element);
GST_STATE_LOCK (element);
if (oclass->send_event) {
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "send %s event on element %s",
GST_EVENT_TYPE_NAME (event), GST_ELEMENT_NAME (element));
result = oclass->send_event (element, event);
} else {
gst_event_unref (event);
}
GST_STATE_UNLOCK (element);
return result;
}
/**
* gst_element_seek:
* @element: a #GstElement to send the event to.
* @rate: The new playback rate
* @format: The format of the seek values
* @flags: The optional seek flags.
* @start_type: The type and flags for the new start position
* @start: The value of the new start position
* @stop_type: The type and flags for the new stop position
* @stop: The value of the new stop position
*
* Sends a seek event to an element. See gst_event_new_seek() for the details of
* the parameters. The seek event is sent to the element using
* gst_element_send_event().
*
* MT safe.
*
* Returns: %TRUE if the event was handled. Flushing seeks will trigger a
* preroll, which will emit %GST_MESSAGE_ASYNC_DONE.
*/
gboolean
gst_element_seek (GstElement * element, gdouble rate, GstFormat format,
GstSeekFlags flags, GstSeekType start_type, gint64 start,
GstSeekType stop_type, gint64 stop)
{
GstEvent *event;
gboolean result;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
event =
gst_event_new_seek (rate, format, flags, start_type, start, stop_type,
stop);
result = gst_element_send_event (element, event);
return result;
}
static gboolean
gst_element_default_query (GstElement * element, GstQuery * query)
{
gboolean result = FALSE;
GstPad *pad;
pad = gst_element_get_random_pad (element, FALSE, GST_PAD_SRC);
if (pad) {
result = gst_pad_query (pad, query);
gst_object_unref (pad);
} else {
pad = gst_element_get_random_pad (element, TRUE, GST_PAD_SINK);
if (pad) {
GstPad *peer = gst_pad_get_peer (pad);
if (peer) {
result = gst_pad_query (peer, query);
gst_object_unref (peer);
}
gst_object_unref (pad);
}
}
return result;
}
/**
* gst_element_query:
* @element: a #GstElement to perform the query on.
* @query: (transfer none): the #GstQuery.
*
* Performs a query on the given element.
*
* For elements that don't implement a query handler, this function
* forwards the query to a random srcpad or to the peer of a
* random linked sinkpad of this element.
*
* Please note that some queries might need a running pipeline to work.
*
* Returns: %TRUE if the query could be performed.
*
* MT safe.
*/
gboolean
gst_element_query (GstElement * element, GstQuery * query)
{
GstElementClass *klass;
gboolean res = FALSE;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (query != NULL, FALSE);
GST_TRACER_ELEMENT_QUERY_PRE (element, query);
klass = GST_ELEMENT_GET_CLASS (element);
if (klass->query) {
GST_CAT_DEBUG (GST_CAT_ELEMENT_PADS, "send query on element %s",
GST_ELEMENT_NAME (element));
res = klass->query (element, query);
}
GST_TRACER_ELEMENT_QUERY_POST (element, query, res);
return res;
}
static gboolean
gst_element_post_message_default (GstElement * element, GstMessage * message)
{
GstBus *bus;
gboolean result = FALSE;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (message != NULL, FALSE);
GST_OBJECT_LOCK (element);
bus = element->bus;
if (G_UNLIKELY (bus == NULL))
goto no_bus;
gst_object_ref (bus);
GST_OBJECT_UNLOCK (element);
/* we release the element lock when posting the message so that any
* (synchronous) message handlers can operate on the element */
result = gst_bus_post (bus, message);
gst_object_unref (bus);
return result;
/* ERRORS */
no_bus:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_MESSAGE, element,
"not posting message %p: no bus", message);
GST_OBJECT_UNLOCK (element);
gst_message_unref (message);
return FALSE;
}
}
/**
* gst_element_post_message:
* @element: a #GstElement posting the message
* @message: (transfer full): a #GstMessage to post
*
* Post a message on the element's #GstBus. This function takes ownership of the
* message; if you want to access the message after this call, you should add an
* additional reference before calling.
*
* Returns: %TRUE if the message was successfully posted. The function returns
* %FALSE if the element did not have a bus.
*
* MT safe.
*/
gboolean
gst_element_post_message (GstElement * element, GstMessage * message)
{
GstElementClass *klass;
gboolean res = FALSE;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (message != NULL, FALSE);
GST_TRACER_ELEMENT_POST_MESSAGE_PRE (element, message);
klass = GST_ELEMENT_GET_CLASS (element);
if (klass->post_message)
res = klass->post_message (element, message);
else
gst_message_unref (message);
GST_TRACER_ELEMENT_POST_MESSAGE_POST (element, res);
return res;
}
/**
* _gst_element_error_printf:
* @format: (allow-none): the printf-like format to use, or %NULL
*
* This function is only used internally by the gst_element_error() macro.
*
* Returns: (transfer full) (nullable): a newly allocated string, or
* %NULL if the format was %NULL or ""
*
* MT safe.
*/
gchar *
_gst_element_error_printf (const gchar * format, ...)
{
va_list args;
gchar *buffer;
int len;
if (format == NULL)
return NULL;
if (format[0] == 0)
return NULL;
va_start (args, format);
len = __gst_vasprintf (&buffer, format, args);
va_end (args);
if (len < 0)
buffer = NULL;
return buffer;
}
/**
* gst_element_message_full_with_details:
* @element: a #GstElement to send message from
* @type: the #GstMessageType
* @domain: the GStreamer GError domain this message belongs to
* @code: the GError code belonging to the domain
* @text: (allow-none) (transfer full): an allocated text string to be used
* as a replacement for the default message connected to code,
* or %NULL
* @debug: (allow-none) (transfer full): an allocated debug message to be
* used as a replacement for the default debugging information,
* or %NULL
* @file: the source code file where the error was generated
* @function: the source code function where the error was generated
* @line: the source code line where the error was generated
* @structure:(transfer full): optional details structure
*
* Post an error, warning or info message on the bus from inside an element.
*
* @type must be of #GST_MESSAGE_ERROR, #GST_MESSAGE_WARNING or
* #GST_MESSAGE_INFO.
*
* Since: 1.10
*/
void gst_element_message_full_with_details
(GstElement * element, GstMessageType type,
GQuark domain, gint code, gchar * text,
gchar * debug, const gchar * file, const gchar * function, gint line,
GstStructure * structure)
{
GError *gerror = NULL;
gchar *name;
gchar *sent_text;
gchar *sent_debug;
gboolean has_debug = TRUE;
GstMessage *message = NULL;
/* checks */
GST_CAT_DEBUG_OBJECT (GST_CAT_MESSAGE, element, "start");
g_return_if_fail (GST_IS_ELEMENT (element));
g_return_if_fail ((type == GST_MESSAGE_ERROR) ||
(type == GST_MESSAGE_WARNING) || (type == GST_MESSAGE_INFO));
/* check if we send the given text or the default error text */
if ((text == NULL) || (text[0] == 0)) {
/* text could have come from g_strdup_printf (""); */
g_free (text);
sent_text = gst_error_get_message (domain, code);
} else
sent_text = text;
/* construct a sent_debug with extra information from source */
if ((debug == NULL) || (debug[0] == 0)) {
/* debug could have come from g_strdup_printf (""); */
has_debug = FALSE;
}
name = gst_object_get_path_string (GST_OBJECT_CAST (element));
if (has_debug)
sent_debug = g_strdup_printf ("%s(%d): %s (): %s:\n%s",
file, line, function, name, debug);
else
sent_debug = g_strdup_printf ("%s(%d): %s (): %s",
file, line, function, name);
g_free (name);
g_free (debug);
/* create gerror and post message */
GST_CAT_INFO_OBJECT (GST_CAT_ERROR_SYSTEM, element, "posting message: %s",
sent_text);
gerror = g_error_new_literal (domain, code, sent_text);
switch (type) {
case GST_MESSAGE_ERROR:
message =
gst_message_new_error_with_details (GST_OBJECT_CAST (element), gerror,
sent_debug, structure);
break;
case GST_MESSAGE_WARNING:
message =
gst_message_new_warning_with_details (GST_OBJECT_CAST (element),
gerror, sent_debug, structure);
break;
case GST_MESSAGE_INFO:
message =
gst_message_new_info_with_details (GST_OBJECT_CAST (element), gerror,
sent_debug, structure);
break;
default:
g_assert_not_reached ();
break;
}
gst_element_post_message (element, message);
GST_CAT_INFO_OBJECT (GST_CAT_ERROR_SYSTEM, element, "posted %s message: %s",
(type == GST_MESSAGE_ERROR ? "error" : "warning"), sent_text);
/* cleanup */
g_error_free (gerror);
g_free (sent_debug);
g_free (sent_text);
}
/**
* gst_element_message_full:
* @element: a #GstElement to send message from
* @type: the #GstMessageType
* @domain: the GStreamer GError domain this message belongs to
* @code: the GError code belonging to the domain
* @text: (allow-none) (transfer full): an allocated text string to be used
* as a replacement for the default message connected to code,
* or %NULL
* @debug: (allow-none) (transfer full): an allocated debug message to be
* used as a replacement for the default debugging information,
* or %NULL
* @file: the source code file where the error was generated
* @function: the source code function where the error was generated
* @line: the source code line where the error was generated
*
* Post an error, warning or info message on the bus from inside an element.
*
* @type must be of #GST_MESSAGE_ERROR, #GST_MESSAGE_WARNING or
* #GST_MESSAGE_INFO.
*
* MT safe.
*/
void gst_element_message_full
(GstElement * element, GstMessageType type,
GQuark domain, gint code, gchar * text,
gchar * debug, const gchar * file, const gchar * function, gint line)
{
gst_element_message_full_with_details (element, type, domain, code, text,
debug, file, function, line, NULL);
}
/**
* gst_element_is_locked_state:
* @element: a #GstElement.
*
* Checks if the state of an element is locked.
* If the state of an element is locked, state changes of the parent don't
* affect the element.
* This way you can leave currently unused elements inside bins. Just lock their
* state before changing the state from #GST_STATE_NULL.
*
* MT safe.
*
* Returns: %TRUE, if the element's state is locked.
*/
gboolean
gst_element_is_locked_state (GstElement * element)
{
gboolean result;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
GST_OBJECT_LOCK (element);
result = GST_ELEMENT_IS_LOCKED_STATE (element);
GST_OBJECT_UNLOCK (element);
return result;
}
/**
* gst_element_set_locked_state:
* @element: a #GstElement
* @locked_state: %TRUE to lock the element's state
*
* Locks the state of an element, so state changes of the parent don't affect
* this element anymore.
*
* Note that this is racy if the state lock of the parent bin is not taken.
* The parent bin might've just checked the flag in another thread and as the
* next step proceed to change the child element's state.
*
* MT safe.
*
* Returns: %TRUE if the state was changed, %FALSE if bad parameters were given
* or the elements state-locking needed no change.
*/
gboolean
gst_element_set_locked_state (GstElement * element, gboolean locked_state)
{
gboolean old;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
GST_OBJECT_LOCK (element);
old = GST_ELEMENT_IS_LOCKED_STATE (element);
if (G_UNLIKELY (old == locked_state))
goto was_ok;
if (locked_state) {
GST_CAT_DEBUG (GST_CAT_STATES, "locking state of element %s",
GST_ELEMENT_NAME (element));
GST_OBJECT_FLAG_SET (element, GST_ELEMENT_FLAG_LOCKED_STATE);
} else {
GST_CAT_DEBUG (GST_CAT_STATES, "unlocking state of element %s",
GST_ELEMENT_NAME (element));
GST_OBJECT_FLAG_UNSET (element, GST_ELEMENT_FLAG_LOCKED_STATE);
}
GST_OBJECT_UNLOCK (element);
return TRUE;
was_ok:
{
GST_CAT_DEBUG (GST_CAT_STATES,
"elements %s was already in locked state %d",
GST_ELEMENT_NAME (element), old);
GST_OBJECT_UNLOCK (element);
return FALSE;
}
}
/**
* gst_element_sync_state_with_parent:
* @element: a #GstElement.
*
* Tries to change the state of the element to the same as its parent.
* If this function returns %FALSE, the state of element is undefined.
*
* Returns: %TRUE, if the element's state could be synced to the parent's state.
*
* MT safe.
*/
gboolean
gst_element_sync_state_with_parent (GstElement * element)
{
GstElement *parent;
GstState target;
GstStateChangeReturn ret;
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
if ((parent = GST_ELEMENT_CAST (gst_element_get_parent (element)))) {
GstState parent_current, parent_pending;
GST_OBJECT_LOCK (parent);
parent_current = GST_STATE (parent);
parent_pending = GST_STATE_PENDING (parent);
GST_OBJECT_UNLOCK (parent);
/* set to pending if there is one, else we set it to the current state of
* the parent */
if (parent_pending != GST_STATE_VOID_PENDING)
target = parent_pending;
else
target = parent_current;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"syncing state (%s) to parent %s %s (%s, %s)",
gst_element_state_get_name (GST_STATE (element)),
GST_ELEMENT_NAME (parent), gst_element_state_get_name (target),
gst_element_state_get_name (parent_current),
gst_element_state_get_name (parent_pending));
ret = gst_element_set_state (element, target);
if (ret == GST_STATE_CHANGE_FAILURE)
goto failed;
gst_object_unref (parent);
return TRUE;
} else {
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "element has no parent");
}
return FALSE;
/* ERROR */
failed:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"syncing state failed (%s)",
gst_element_state_change_return_get_name (ret));
gst_object_unref (parent);
return FALSE;
}
}
/* MT safe */
static GstStateChangeReturn
gst_element_get_state_func (GstElement * element,
GstState * state, GstState * pending, GstClockTime timeout)
{
GstStateChangeReturn ret = GST_STATE_CHANGE_FAILURE;
GstState old_pending;
gint64 end_time;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "getting state, timeout %"
GST_TIME_FORMAT, GST_TIME_ARGS (timeout));
GST_OBJECT_LOCK (element);
if (timeout != GST_CLOCK_TIME_NONE) {
/* make timeout absolute */
end_time = g_get_monotonic_time () + (timeout / 1000);
}
do {
ret = GST_STATE_RETURN (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "RETURN is %s",
gst_element_state_change_return_get_name (ret));
/* we got an error, report immediately */
if (ret == GST_STATE_CHANGE_FAILURE)
goto done;
/* we got no_preroll, report immediately */
if (ret == GST_STATE_CHANGE_NO_PREROLL)
goto done;
/* no need to wait async if we are not async */
if (ret != GST_STATE_CHANGE_ASYNC)
goto done;
old_pending = GST_STATE_PENDING (element);
if (old_pending != GST_STATE_VOID_PENDING) {
gboolean signaled = TRUE;
guint32 cookie;
/* get cookie to detect state changes during waiting */
cookie = element->state_cookie;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"waiting for element to commit state");
/* we have a pending state change, wait for it to complete or for
an interruption */
if (timeout != GST_CLOCK_TIME_NONE) {
signaled = GST_STATE_WAIT_UNTIL (element, end_time);
} else {
GST_STATE_WAIT (element);
signaled = TRUE;
}
if (!signaled) {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "timed out");
/* timeout triggered */
ret = GST_STATE_CHANGE_ASYNC;
goto done;
} else {
if (cookie != element->state_cookie)
goto interrupted;
/* could be success or failure */
if (old_pending == GST_STATE (element)) {
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "got success");
ret = GST_STATE_CHANGE_SUCCESS;
} else {
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "got failure");
ret = GST_STATE_CHANGE_FAILURE;
}
}
/* if nothing is pending anymore we can return SUCCESS */
if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) {
GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "nothing pending");
ret = GST_STATE_CHANGE_SUCCESS;
}
}
} while (old_pending != GST_STATE (element));
done:
if (state)
*state = GST_STATE (element);
if (pending)
*pending = GST_STATE_PENDING (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"state current: %s, pending: %s, result: %s",
gst_element_state_get_name (GST_STATE (element)),
gst_element_state_get_name (GST_STATE_PENDING (element)),
gst_element_state_change_return_get_name (ret));
GST_OBJECT_UNLOCK (element);
return ret;
interrupted:
{
if (state)
*state = GST_STATE_VOID_PENDING;
if (pending)
*pending = GST_STATE_VOID_PENDING;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "interrupted");
GST_OBJECT_UNLOCK (element);
return GST_STATE_CHANGE_FAILURE;
}
}
/**
* gst_element_get_state:
* @element: a #GstElement to get the state of.
* @state: (out) (allow-none): a pointer to #GstState to hold the state.
* Can be %NULL.
* @pending: (out) (allow-none): a pointer to #GstState to hold the pending
* state. Can be %NULL.
* @timeout: a #GstClockTime to specify the timeout for an async
* state change or %GST_CLOCK_TIME_NONE for infinite timeout.
*
* Gets the state of the element.
*
* For elements that performed an ASYNC state change, as reported by
* gst_element_set_state(), this function will block up to the
* specified timeout value for the state change to complete.
* If the element completes the state change or goes into
* an error, this function returns immediately with a return value of
* %GST_STATE_CHANGE_SUCCESS or %GST_STATE_CHANGE_FAILURE respectively.
*
* For elements that did not return %GST_STATE_CHANGE_ASYNC, this function
* returns the current and pending state immediately.
*
* This function returns %GST_STATE_CHANGE_NO_PREROLL if the element
* successfully changed its state but is not able to provide data yet.
* This mostly happens for live sources that only produce data in
* %GST_STATE_PLAYING. While the state change return is equivalent to
* %GST_STATE_CHANGE_SUCCESS, it is returned to the application to signal that
* some sink elements might not be able to complete their state change because
* an element is not producing data to complete the preroll. When setting the
* element to playing, the preroll will complete and playback will start.
*
* Returns: %GST_STATE_CHANGE_SUCCESS if the element has no more pending state
* and the last state change succeeded, %GST_STATE_CHANGE_ASYNC if the
* element is still performing a state change or
* %GST_STATE_CHANGE_FAILURE if the last state change failed.
*
* MT safe.
*/
GstStateChangeReturn
gst_element_get_state (GstElement * element,
GstState * state, GstState * pending, GstClockTime timeout)
{
GstElementClass *oclass;
GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
oclass = GST_ELEMENT_GET_CLASS (element);
if (oclass->get_state)
result = (oclass->get_state) (element, state, pending, timeout);
return result;
}
/**
* gst_element_abort_state:
* @element: a #GstElement to abort the state of.
*
* Abort the state change of the element. This function is used
* by elements that do asynchronous state changes and find out
* something is wrong.
*
* This function should be called with the STATE_LOCK held.
*
* MT safe.
*/
void
gst_element_abort_state (GstElement * element)
{
GstState pending;
#ifndef GST_DISABLE_GST_DEBUG
GstState old_state;
#endif
g_return_if_fail (GST_IS_ELEMENT (element));
GST_OBJECT_LOCK (element);
pending = GST_STATE_PENDING (element);
if (pending == GST_STATE_VOID_PENDING ||
GST_STATE_RETURN (element) == GST_STATE_CHANGE_FAILURE)
goto nothing_aborted;
#ifndef GST_DISABLE_GST_DEBUG
old_state = GST_STATE (element);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"aborting state from %s to %s", gst_element_state_get_name (old_state),
gst_element_state_get_name (pending));
#endif
/* flag error */
GST_STATE_RETURN (element) = GST_STATE_CHANGE_FAILURE;
GST_STATE_BROADCAST (element);
GST_OBJECT_UNLOCK (element);
return;
nothing_aborted:
{
GST_OBJECT_UNLOCK (element);
return;
}
}
/* Not static because GstBin has manual state handling too */
void
_priv_gst_element_state_changed (GstElement * element, GstState oldstate,
GstState newstate, GstState pending)
{
GstElementClass *klass = GST_ELEMENT_GET_CLASS (element);
GstMessage *message;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"notifying about state-changed %s to %s (%s pending)",
gst_element_state_get_name (oldstate),
gst_element_state_get_name (newstate),
gst_element_state_get_name (pending));
if (klass->state_changed)
klass->state_changed (element, oldstate, newstate, pending);
message = gst_message_new_state_changed (GST_OBJECT_CAST (element),
oldstate, newstate, pending);
gst_element_post_message (element, message);
}
/**
* gst_element_continue_state:
* @element: a #GstElement to continue the state change of.
* @ret: The previous state return value
*
* Commit the state change of the element and proceed to the next
* pending state if any. This function is used
* by elements that do asynchronous state changes.
* The core will normally call this method automatically when an
* element returned %GST_STATE_CHANGE_SUCCESS from the state change function.
*
* If after calling this method the element still has not reached
* the pending state, the next state change is performed.
*
* This method is used internally and should normally not be called by plugins
* or applications.
*
* This function must be called with STATE_LOCK held.
*
* Returns: The result of the commit state change.
*
* MT safe.
*/
GstStateChangeReturn
gst_element_continue_state (GstElement * element, GstStateChangeReturn ret)
{
GstStateChangeReturn old_ret;
GstState old_state, old_next;
GstState current, next, pending;
GstStateChange transition;
GST_OBJECT_LOCK (element);
old_ret = GST_STATE_RETURN (element);
GST_STATE_RETURN (element) = ret;
pending = GST_STATE_PENDING (element);
/* check if there is something to commit */
if (pending == GST_STATE_VOID_PENDING)
goto nothing_pending;
old_state = GST_STATE (element);
/* this is the state we should go to next */
old_next = GST_STATE_NEXT (element);
/* update current state */
current = GST_STATE (element) = old_next;
/* see if we reached the final state */
if (pending == current)
goto complete;
next = GST_STATE_GET_NEXT (current, pending);
transition = (GstStateChange) GST_STATE_TRANSITION (current, next);
GST_STATE_NEXT (element) = next;
/* mark busy */
GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
GST_OBJECT_UNLOCK (element);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"committing state from %s to %s, pending %s, next %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_next),
gst_element_state_get_name (pending), gst_element_state_get_name (next));
_priv_gst_element_state_changed (element, old_state, old_next, pending);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"continue state change %s to %s, final %s",
gst_element_state_get_name (current),
gst_element_state_get_name (next), gst_element_state_get_name (pending));
ret = gst_element_change_state (element, transition);
return ret;
nothing_pending:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "nothing pending");
GST_OBJECT_UNLOCK (element);
return ret;
}
complete:
{
GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"completed state change to %s", gst_element_state_get_name (pending));
GST_OBJECT_UNLOCK (element);
/* don't post silly messages with the same state. This can happen
* when an element state is changed to what it already was. For bins
* this can be the result of a lost state, which we check with the
* previous return value.
* We do signal the cond though as a _get_state() might be blocking
* on it. */
if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC)
_priv_gst_element_state_changed (element, old_state, old_next,
GST_STATE_VOID_PENDING);
GST_STATE_BROADCAST (element);
return ret;
}
}
/**
* gst_element_lost_state:
* @element: a #GstElement the state is lost of
*
* Brings the element to the lost state. The current state of the
* element is copied to the pending state so that any call to
* gst_element_get_state() will return %GST_STATE_CHANGE_ASYNC.
*
* An ASYNC_START message is posted. If the element was PLAYING, it will
* go to PAUSED. The element will be restored to its PLAYING state by
* the parent pipeline when it prerolls again.
*
* This is mostly used for elements that lost their preroll buffer
* in the %GST_STATE_PAUSED or %GST_STATE_PLAYING state after a flush,
* they will go to their pending state again when a new preroll buffer is
* queued. This function can only be called when the element is currently
* not in error or an async state change.
*
* This function is used internally and should normally not be called from
* plugins or applications.
*/
void
gst_element_lost_state (GstElement * element)
{
GstState old_state, new_state;
GstMessage *message;
g_return_if_fail (GST_IS_ELEMENT (element));
GST_OBJECT_LOCK (element);
if (GST_STATE_RETURN (element) == GST_STATE_CHANGE_FAILURE)
goto nothing_lost;
if (GST_STATE_PENDING (element) != GST_STATE_VOID_PENDING)
goto only_async_start;
old_state = GST_STATE (element);
/* when we were PLAYING, the new state is PAUSED. We will also not
* automatically go to PLAYING but let the parent bin(s) set us to PLAYING
* when we preroll. */
if (old_state > GST_STATE_PAUSED)
new_state = GST_STATE_PAUSED;
else
new_state = old_state;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"lost state of %s to %s", gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
GST_STATE (element) = new_state;
GST_STATE_NEXT (element) = new_state;
GST_STATE_PENDING (element) = new_state;
GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
GST_OBJECT_UNLOCK (element);
_priv_gst_element_state_changed (element, new_state, new_state, new_state);
message = gst_message_new_async_start (GST_OBJECT_CAST (element));
gst_element_post_message (element, message);
return;
nothing_lost:
{
GST_OBJECT_UNLOCK (element);
return;
}
only_async_start:
{
GST_OBJECT_UNLOCK (element);
message = gst_message_new_async_start (GST_OBJECT_CAST (element));
gst_element_post_message (element, message);
return;
}
}
/**
* gst_element_set_state:
* @element: a #GstElement to change state of.
* @state: the element's new #GstState.
*
* Sets the state of the element. This function will try to set the
* requested state by going through all the intermediary states and calling
* the class's state change function for each.
*
* This function can return #GST_STATE_CHANGE_ASYNC, in which case the
* element will perform the remainder of the state change asynchronously in
* another thread.
* An application can use gst_element_get_state() to wait for the completion
* of the state change or it can wait for a %GST_MESSAGE_ASYNC_DONE or
* %GST_MESSAGE_STATE_CHANGED on the bus.
*
* State changes to %GST_STATE_READY or %GST_STATE_NULL never return
* #GST_STATE_CHANGE_ASYNC.
*
* Returns: Result of the state change using #GstStateChangeReturn.
*
* MT safe.
*/
GstStateChangeReturn
gst_element_set_state (GstElement * element, GstState state)
{
GstElementClass *oclass;
GstStateChangeReturn result = GST_STATE_CHANGE_FAILURE;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
oclass = GST_ELEMENT_GET_CLASS (element);
if (oclass->set_state)
result = (oclass->set_state) (element, state);
return result;
}
/*
* default set state function, calculates the next state based
* on current state and calls the change_state function
*/
static GstStateChangeReturn
gst_element_set_state_func (GstElement * element, GstState state)
{
GstState current, next, old_pending;
GstStateChangeReturn ret;
GstStateChange transition;
GstStateChangeReturn old_ret;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "set_state to %s",
gst_element_state_get_name (state));
/* state lock is taken to protect the set_state() and get_state()
* procedures, it does not lock any variables. */
GST_STATE_LOCK (element);
/* now calculate how to get to the new state */
GST_OBJECT_LOCK (element);
old_ret = GST_STATE_RETURN (element);
/* previous state change returned an error, remove all pending
* and next states */
if (old_ret == GST_STATE_CHANGE_FAILURE) {
GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING;
GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS;
}
current = GST_STATE (element);
next = GST_STATE_NEXT (element);
old_pending = GST_STATE_PENDING (element);
/* this is the (new) state we should go to. TARGET is the last state we set on
* the element. */
if (state != GST_STATE_TARGET (element)) {
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"setting target state to %s", gst_element_state_get_name (state));
GST_STATE_TARGET (element) = state;
/* increment state cookie so that we can track each state change. We only do
* this if this is actually a new state change. */
element->state_cookie++;
}
GST_STATE_PENDING (element) = state;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"current %s, old_pending %s, next %s, old return %s",
gst_element_state_get_name (current),
gst_element_state_get_name (old_pending),
gst_element_state_get_name (next),
gst_element_state_change_return_get_name (old_ret));
/* if the element was busy doing a state change, we just update the
* target state, it'll get to it async then. */
if (old_pending != GST_STATE_VOID_PENDING) {
/* upwards state change will happen ASYNC */
if (old_pending <= state)
goto was_busy;
/* element is going to this state already */
else if (next == state)
goto was_busy;
/* element was performing an ASYNC upward state change and
* we request to go downward again. Start from the next pending
* state then. */
else if (next > state
&& GST_STATE_RETURN (element) == GST_STATE_CHANGE_ASYNC) {
current = next;
}
}
next = GST_STATE_GET_NEXT (current, state);
/* now we store the next state */
GST_STATE_NEXT (element) = next;
/* mark busy, we need to check that there is actually a state change
* to be done else we could accidentally override SUCCESS/NO_PREROLL and
* the default element change_state function has no way to know what the
* old value was... could consider this a FIXME...*/
if (current != next)
GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
transition = (GstStateChange) GST_STATE_TRANSITION (current, next);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"%s: setting state from %s to %s",
(next != state ? "intermediate" : "final"),
gst_element_state_get_name (current), gst_element_state_get_name (next));
/* now signal any waiters, they will error since the cookie was incremented */
GST_STATE_BROADCAST (element);
GST_OBJECT_UNLOCK (element);
ret = gst_element_change_state (element, transition);
GST_STATE_UNLOCK (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "returned %s",
gst_element_state_change_return_get_name (ret));
return ret;
was_busy:
{
GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"element was busy with async state change");
GST_OBJECT_UNLOCK (element);
GST_STATE_UNLOCK (element);
return GST_STATE_CHANGE_ASYNC;
}
}
/**
* gst_element_change_state:
* @element: a #GstElement
* @transition: the requested transition
*
* Perform @transition on @element.
*
* This function must be called with STATE_LOCK held and is mainly used
* internally.
*
* Returns: the #GstStateChangeReturn of the state transition.
*/
GstStateChangeReturn
gst_element_change_state (GstElement * element, GstStateChange transition)
{
GstElementClass *oclass;
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
oclass = GST_ELEMENT_GET_CLASS (element);
GST_TRACER_ELEMENT_CHANGE_STATE_PRE (element, transition);
/* call the state change function so it can set the state */
if (oclass->change_state)
ret = (oclass->change_state) (element, transition);
else
ret = GST_STATE_CHANGE_FAILURE;
GST_TRACER_ELEMENT_CHANGE_STATE_POST (element, transition, ret);
switch (ret) {
case GST_STATE_CHANGE_FAILURE:
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"have FAILURE change_state return");
/* state change failure */
gst_element_abort_state (element);
break;
case GST_STATE_CHANGE_ASYNC:
{
GstState target;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"element will change state ASYNC");
target = GST_STATE_TARGET (element);
if (target > GST_STATE_READY)
goto async;
/* else we just continue the state change downwards */
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"forcing commit state %s <= %s",
gst_element_state_get_name (target),
gst_element_state_get_name (GST_STATE_READY));
ret = gst_element_continue_state (element, GST_STATE_CHANGE_SUCCESS);
break;
}
case GST_STATE_CHANGE_SUCCESS:
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"element changed state SUCCESS");
/* we can commit the state now which will proceed to
* the next state */
ret = gst_element_continue_state (element, ret);
break;
case GST_STATE_CHANGE_NO_PREROLL:
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"element changed state NO_PREROLL");
/* we can commit the state now which will proceed to
* the next state */
ret = gst_element_continue_state (element, ret);
break;
default:
goto invalid_return;
}
GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "exit state change %d", ret);
return ret;
async:
GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "exit async state change %d",
ret);
return ret;
/* ERROR */
invalid_return:
{
GST_OBJECT_LOCK (element);
/* somebody added a GST_STATE_ and forgot to do stuff here ! */
g_critical ("%s: unknown return value %d from a state change function",
GST_ELEMENT_NAME (element), ret);
/* we are in error now */
ret = GST_STATE_CHANGE_FAILURE;
GST_STATE_RETURN (element) = ret;
GST_OBJECT_UNLOCK (element);
return ret;
}
}
/* gst_iterator_fold functions for pads_activate
* Stop the iterator if activating one pad failed, but only if that pad
* has not been removed from the element. */
static gboolean
activate_pads (const GValue * vpad, GValue * ret, gboolean * active)
{
GstPad *pad = g_value_get_object (vpad);
gboolean cont = TRUE;
if (!gst_pad_set_active (pad, *active)) {
if (GST_PAD_PARENT (pad) != NULL) {
cont = FALSE;
g_value_set_boolean (ret, FALSE);
}
}
return cont;
}
/* returns false on error or early cutout of the fold, true if all
* pads in @iter were (de)activated successfully. */
static gboolean
iterator_activate_fold_with_resync (GstIterator * iter,
GstIteratorFoldFunction func, gpointer user_data)
{
GstIteratorResult ires;
GValue ret = { 0 };
/* no need to unset this later, it's just a boolean */
g_value_init (&ret, G_TYPE_BOOLEAN);
g_value_set_boolean (&ret, TRUE);
while (1) {
ires = gst_iterator_fold (iter, func, &ret, user_data);
switch (ires) {
case GST_ITERATOR_RESYNC:
/* need to reset the result again */
g_value_set_boolean (&ret, TRUE);
gst_iterator_resync (iter);
break;
case GST_ITERATOR_DONE:
/* all pads iterated, return collected value */
goto done;
default:
/* iterator returned _ERROR or premature end with _OK,
* mark an error and exit */
g_value_set_boolean (&ret, FALSE);
goto done;
}
}
done:
/* return collected value */
return g_value_get_boolean (&ret);
}
/* is called with STATE_LOCK
*
* Pads are activated from source pads to sinkpads.
*/
static gboolean
gst_element_pads_activate (GstElement * element, gboolean active)
{
GstIterator *iter;
gboolean res;
GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element,
"%s pads", active ? "activate" : "deactivate");
iter = gst_element_iterate_src_pads (element);
res =
iterator_activate_fold_with_resync (iter,
(GstIteratorFoldFunction) activate_pads, &active);
gst_iterator_free (iter);
if (G_UNLIKELY (!res))
goto src_failed;
iter = gst_element_iterate_sink_pads (element);
res =
iterator_activate_fold_with_resync (iter,
(GstIteratorFoldFunction) activate_pads, &active);
gst_iterator_free (iter);
if (G_UNLIKELY (!res))
goto sink_failed;
GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element,
"pad %sactivation successful", active ? "" : "de");
return TRUE;
/* ERRORS */
src_failed:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element,
"pad %sactivation failed", active ? "" : "de");
return FALSE;
}
sink_failed:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element,
"sink pads_activate failed");
return FALSE;
}
}
/* is called with STATE_LOCK */
static GstStateChangeReturn
gst_element_change_state_func (GstElement * element, GstStateChange transition)
{
GstState state, next;
GstStateChangeReturn result = GST_STATE_CHANGE_SUCCESS;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
state = (GstState) GST_STATE_TRANSITION_CURRENT (transition);
next = GST_STATE_TRANSITION_NEXT (transition);
/* if the element already is in the given state, we just return success */
if (next == GST_STATE_VOID_PENDING || state == next)
goto was_ok;
GST_CAT_LOG_OBJECT (GST_CAT_STATES, element,
"default handler tries setting state from %s to %s (%04x)",
gst_element_state_get_name (state),
gst_element_state_get_name (next), transition);
switch (transition) {
case GST_STATE_CHANGE_NULL_TO_READY:
break;
case GST_STATE_CHANGE_READY_TO_PAUSED:
if (!gst_element_pads_activate (element, TRUE)) {
result = GST_STATE_CHANGE_FAILURE;
}
break;
case GST_STATE_CHANGE_PAUSED_TO_PLAYING:
break;
case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
break;
case GST_STATE_CHANGE_PAUSED_TO_READY:
case GST_STATE_CHANGE_READY_TO_NULL:{
GList *l;
/* deactivate pads in both cases, since they are activated on
ready->paused but the element might not have made it to paused */
if (!gst_element_pads_activate (element, FALSE)) {
result = GST_STATE_CHANGE_FAILURE;
}
/* Remove all non-persistent contexts */
GST_OBJECT_LOCK (element);
for (l = element->contexts; l;) {
GstContext *context = l->data;
if (!gst_context_is_persistent (context)) {
GList *next;
gst_context_unref (context);
next = l->next;
element->contexts = g_list_delete_link (element->contexts, l);
l = next;
} else {
l = l->next;
}
}
GST_OBJECT_UNLOCK (element);
break;
}
default:
/* this will catch real but unhandled state changes;
* can only be caused by:
* - a new state was added
* - somehow the element was asked to jump across an intermediate state
*/
g_warning ("Unhandled state change from %s to %s",
gst_element_state_get_name (state),
gst_element_state_get_name (next));
break;
}
return result;
was_ok:
{
GST_OBJECT_LOCK (element);
result = GST_STATE_RETURN (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"element is already in the %s state",
gst_element_state_get_name (state));
GST_OBJECT_UNLOCK (element);
return result;
}
}
/**
* gst_element_get_factory:
* @element: a #GstElement to request the element factory of.
*
* Retrieves the factory that was used to create this element.
*
* Returns: (transfer none) (nullable): the #GstElementFactory used for creating this
* element or %NULL if element has not been registered (static element). no refcounting is needed.
*/
GstElementFactory *
gst_element_get_factory (GstElement * element)
{
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
return GST_ELEMENT_GET_CLASS (element)->elementfactory;
}
static void
gst_element_dispose (GObject * object)
{
GstElement *element = GST_ELEMENT_CAST (object);
GstClock **clock_p;
GstBus **bus_p;
GstElementClass *oclass;
GList *walk;
oclass = GST_ELEMENT_GET_CLASS (element);
GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "%p dispose", element);
if (GST_STATE (element) != GST_STATE_NULL)
goto not_null;
/* start by releasing all request pads, this might also remove some dynamic
* pads */
walk = element->pads;
while (walk) {
GstPad *pad = GST_PAD_CAST (walk->data);
walk = walk->next;
if (oclass->release_pad && GST_PAD_PAD_TEMPLATE (pad) &&
GST_PAD_TEMPLATE_PRESENCE (GST_PAD_PAD_TEMPLATE (pad))
== GST_PAD_REQUEST) {
GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element,
"removing request pad %s:%s", GST_DEBUG_PAD_NAME (pad));
oclass->release_pad (element, pad);
/* in case the release_pad function removed the next pad too */
if (walk && g_list_position (element->pads, walk) == -1)
walk = element->pads;
}
}
/* remove the remaining pads */
while (element->pads) {
GstPad *pad = GST_PAD_CAST (element->pads->data);
GST_CAT_DEBUG_OBJECT (GST_CAT_ELEMENT_PADS, element,
"removing pad %s:%s", GST_DEBUG_PAD_NAME (pad));
if (!gst_element_remove_pad (element, pad)) {
/* only happens when someone unparented our pad.. */
g_critical ("failed to remove pad %s:%s", GST_DEBUG_PAD_NAME (pad));
break;
}
}
GST_OBJECT_LOCK (element);
clock_p = &element->clock;
bus_p = &element->bus;
gst_object_replace ((GstObject **) clock_p, NULL);
gst_object_replace ((GstObject **) bus_p, NULL);
g_list_free_full (element->contexts, (GDestroyNotify) gst_context_unref);
element->contexts = NULL;
GST_OBJECT_UNLOCK (element);
GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "%p parent class dispose",
element);
G_OBJECT_CLASS (parent_class)->dispose (object);
return;
/* ERRORS */
not_null:
{
gboolean is_locked;
is_locked = GST_ELEMENT_IS_LOCKED_STATE (element);
g_critical
("\nTrying to dispose element %s, but it is in %s%s instead of the NULL"
" state.\n"
"You need to explicitly set elements to the NULL state before\n"
"dropping the final reference, to allow them to clean up.\n"
"This problem may also be caused by a refcounting bug in the\n"
"application or some element.\n",
GST_OBJECT_NAME (element),
gst_element_state_get_name (GST_STATE (element)),
is_locked ? " (locked)" : "");
return;
}
}
static void
gst_element_finalize (GObject * object)
{
GstElement *element = GST_ELEMENT_CAST (object);
GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "%p finalize", element);
g_cond_clear (&element->state_cond);
g_rec_mutex_clear (&element->state_lock);
GST_CAT_INFO_OBJECT (GST_CAT_REFCOUNTING, element, "%p finalize parent",
element);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
gst_element_set_bus_func (GstElement * element, GstBus * bus)
{
GstBus **bus_p;
g_return_if_fail (GST_IS_ELEMENT (element));
GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, element, "setting bus to %p", bus);
GST_OBJECT_LOCK (element);
bus_p = &GST_ELEMENT_BUS (element);
gst_object_replace ((GstObject **) bus_p, GST_OBJECT_CAST (bus));
GST_OBJECT_UNLOCK (element);
}
/**
* gst_element_set_bus:
* @element: a #GstElement to set the bus of.
* @bus: (transfer none) (allow-none): the #GstBus to set.
*
* Sets the bus of the element. Increases the refcount on the bus.
* For internal use only, unless you're testing elements.
*
* MT safe.
*/
void
gst_element_set_bus (GstElement * element, GstBus * bus)
{
GstElementClass *oclass;
g_return_if_fail (GST_IS_ELEMENT (element));
oclass = GST_ELEMENT_GET_CLASS (element);
if (oclass->set_bus)
oclass->set_bus (element, bus);
}
/**
* gst_element_get_bus:
* @element: a #GstElement to get the bus of.
*
* Returns the bus of the element. Note that only a #GstPipeline will provide a
* bus for the application.
*
* Returns: (transfer full) (nullable): the element's #GstBus. unref after
* usage.
*
* MT safe.
*/
GstBus *
gst_element_get_bus (GstElement * element)
{
GstBus *result = NULL;
g_return_val_if_fail (GST_IS_ELEMENT (element), result);
GST_OBJECT_LOCK (element);
if ((result = GST_ELEMENT_BUS (element)))
gst_object_ref (result);
GST_OBJECT_UNLOCK (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_BUS, element, "got bus %" GST_PTR_FORMAT,
result);
return result;
}
static void
gst_element_set_context_default (GstElement * element, GstContext * context)
{
const gchar *context_type;
GList *l;
g_return_if_fail (GST_IS_CONTEXT (context));
context_type = gst_context_get_context_type (context);
g_return_if_fail (context_type != NULL);
GST_OBJECT_LOCK (element);
for (l = element->contexts; l; l = l->next) {
GstContext *tmp = l->data;
const gchar *tmp_type = gst_context_get_context_type (tmp);
/* Always store newest context but never replace
* a persistent one by a non-persistent one */
if (g_strcmp0 (context_type, tmp_type) == 0 &&
(gst_context_is_persistent (context) ||
!gst_context_is_persistent (tmp))) {
gst_context_replace ((GstContext **) & l->data, context);
break;
}
}
/* Not found? Add */
if (l == NULL) {
element->contexts =
g_list_prepend (element->contexts, gst_context_ref (context));
}
GST_OBJECT_UNLOCK (element);
}
/**
* gst_element_set_context:
* @element: a #GstElement to set the context of.
* @context: (transfer none): the #GstContext to set.
*
* Sets the context of the element. Increases the refcount of the context.
*
* MT safe.
*/
void
gst_element_set_context (GstElement * element, GstContext * context)
{
GstElementClass *oclass;
g_return_if_fail (GST_IS_ELEMENT (element));
g_return_if_fail (GST_IS_CONTEXT (context));
oclass = GST_ELEMENT_GET_CLASS (element);
GST_CAT_DEBUG_OBJECT (GST_CAT_CONTEXT, element,
"set context %p %" GST_PTR_FORMAT, context,
gst_context_get_structure (context));
if (oclass->set_context)
oclass->set_context (element, context);
}
/**
* gst_element_get_contexts:
* @element: a #GstElement to set the context of.
*
* Gets the contexts set on the element.
*
* MT safe.
*
* Returns: (element-type Gst.Context) (transfer full): List of #GstContext
*
* Since: 1.8
*/
GList *
gst_element_get_contexts (GstElement * element)
{
GList *ret;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
GST_OBJECT_LOCK (element);
ret = g_list_copy_deep (element->contexts, (GCopyFunc) gst_context_ref, NULL);
GST_OBJECT_UNLOCK (element);
return ret;
}
static gint
_match_context_type (GstContext * c1, const gchar * context_type)
{
const gchar *c1_type;
c1_type = gst_context_get_context_type (c1);
return g_strcmp0 (c1_type, context_type);
}
/**
* gst_element_get_context_unlocked:
* @element: a #GstElement to get the context of.
* @context_type: a name of a context to retrieve
*
* Gets the context with @context_type set on the element or NULL.
*
* Returns: (transfer full) (nullable): A #GstContext or NULL
*
* Since: 1.8
*/
GstContext *
gst_element_get_context_unlocked (GstElement * element,
const gchar * context_type)
{
GstContext *ret = NULL;
GList *node;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
node =
g_list_find_custom (element->contexts, context_type,
(GCompareFunc) _match_context_type);
if (node && node->data)
ret = gst_context_ref (node->data);
return ret;
}
/**
* gst_element_get_context:
* @element: a #GstElement to get the context of.
* @context_type: a name of a context to retrieve
*
* Gets the context with @context_type set on the element or NULL.
*
* MT safe.
*
* Returns: (transfer full) (nullable): A #GstContext or NULL
*
* Since: 1.8
*/
GstContext *
gst_element_get_context (GstElement * element, const gchar * context_type)
{
GstContext *ret = NULL;
g_return_val_if_fail (GST_IS_ELEMENT (element), NULL);
GST_OBJECT_LOCK (element);
ret = gst_element_get_context_unlocked (element, context_type);
GST_OBJECT_UNLOCK (element);
return ret;
}
static void
gst_element_property_post_notify_msg (GstElement * element, GObject * obj,
GParamSpec * pspec, gboolean include_value)
{
GValue val = G_VALUE_INIT;
GValue *v;
GST_LOG_OBJECT (element, "property '%s' of object %" GST_PTR_FORMAT " has "
"changed, posting message with%s value", pspec->name, obj,
include_value ? "" : "out");
if (include_value && (pspec->flags & G_PARAM_READABLE) != 0) {
g_value_init (&val, pspec->value_type);
g_object_get_property (obj, pspec->name, &val);
v = &val;
} else {
v = NULL;
}
gst_element_post_message (element,
gst_message_new_property_notify (GST_OBJECT_CAST (obj), pspec->name, v));
}
static void
gst_element_property_deep_notify_cb (GstElement * element, GObject * prop_obj,
GParamSpec * pspec, gpointer user_data)
{
gboolean include_value = GPOINTER_TO_INT (user_data);
gst_element_property_post_notify_msg (element, prop_obj, pspec,
include_value);
}
static void
gst_element_property_notify_cb (GObject * obj, GParamSpec * pspec,
gpointer user_data)
{
gboolean include_value = GPOINTER_TO_INT (user_data);
gst_element_property_post_notify_msg (GST_ELEMENT_CAST (obj), obj, pspec,
include_value);
}
/**
* gst_element_add_property_notify_watch:
* @element: a #GstElement to watch for property changes
* @property_name: (allow-none): name of property to watch for changes, or
* NULL to watch all properties
* @include_value: whether to include the new property value in the message
*
* Returns: a watch id, which can be used in connection with
* gst_element_remove_property_notify_watch() to remove the watch again.
*
* Since: 1.10
*/
gulong
gst_element_add_property_notify_watch (GstElement * element,
const gchar * property_name, gboolean include_value)
{
const gchar *sep;
gchar *signal_name;
gulong id;
g_return_val_if_fail (GST_IS_ELEMENT (element), 0);
sep = (property_name != NULL) ? "::" : NULL;
signal_name = g_strconcat ("notify", sep, property_name, NULL);
id = g_signal_connect (element, signal_name,
G_CALLBACK (gst_element_property_notify_cb),
GINT_TO_POINTER (include_value));
g_free (signal_name);
return id;
}
/**
* gst_element_add_property_deep_notify_watch:
* @element: a #GstElement to watch (recursively) for property changes
* @property_name: (allow-none): name of property to watch for changes, or
* NULL to watch all properties
* @include_value: whether to include the new property value in the message
*
* Returns: a watch id, which can be used in connection with
* gst_element_remove_property_notify_watch() to remove the watch again.
*
* Since: 1.10
*/
gulong
gst_element_add_property_deep_notify_watch (GstElement * element,
const gchar * property_name, gboolean include_value)
{
const gchar *sep;
gchar *signal_name;
gulong id;
g_return_val_if_fail (GST_IS_ELEMENT (element), 0);
sep = (property_name != NULL) ? "::" : NULL;
signal_name = g_strconcat ("deep-notify", sep, property_name, NULL);
id = g_signal_connect (element, signal_name,
G_CALLBACK (gst_element_property_deep_notify_cb),
GINT_TO_POINTER (include_value));
g_free (signal_name);
return id;
}
/**
* gst_element_remove_property_notify_watch:
* @element: a #GstElement being watched for property changes
* @watch_id: watch id to remove
*
* Since: 1.10
*/
void
gst_element_remove_property_notify_watch (GstElement * element, gulong watch_id)
{
g_signal_handler_disconnect (element, watch_id);
}
typedef struct
{
GstElement *element;
GstElementCallAsyncFunc func;
gpointer user_data;
GDestroyNotify destroy_notify;
} GstElementCallAsyncData;
static void
gst_element_call_async_func (gpointer data, gpointer user_data)
{
GstElementCallAsyncData *async_data = data;
async_data->func (async_data->element, async_data->user_data);
if (async_data->destroy_notify)
async_data->destroy_notify (async_data->user_data);
gst_object_unref (async_data->element);
g_free (async_data);
}
/**
* gst_element_call_async:
* @element: a #GstElement
* @func: Function to call asynchronously from another thread
* @user_data: Data to pass to @func
* @destroy_notify: GDestroyNotify for @user_data
*
* Calls @func from another thread and passes @user_data to it. This is to be
* used for cases when a state change has to be performed from a streaming
* thread, directly via gst_element_set_state() or indirectly e.g. via SEEK
* events.
*
* Calling those functions directly from the streaming thread will cause
* deadlocks in many situations, as they might involve waiting for the
* streaming thread to shut down from this very streaming thread.
*
* MT safe.
*
* Since: 1.10
*/
void
gst_element_call_async (GstElement * element, GstElementCallAsyncFunc func,
gpointer user_data, GDestroyNotify destroy_notify)
{
GstElementCallAsyncData *async_data;
g_return_if_fail (GST_IS_ELEMENT (element));
async_data = g_new0 (GstElementCallAsyncData, 1);
async_data->element = gst_object_ref (element);
async_data->func = func;
async_data->user_data = user_data;
async_data->destroy_notify = destroy_notify;
g_mutex_lock (&_element_pool_lock);
if (G_UNLIKELY (gst_element_pool == NULL))
gst_element_pool = gst_element_setup_thread_pool ();
g_thread_pool_push ((GThreadPool *) gst_element_pool, async_data, NULL);
g_mutex_unlock (&_element_pool_lock);
}
void
_priv_gst_element_cleanup (void)
{
g_mutex_lock (&_element_pool_lock);
if (gst_element_pool) {
g_thread_pool_free ((GThreadPool *) gst_element_pool, FALSE, TRUE);
gst_element_pool = NULL;
}
g_mutex_unlock (&_element_pool_lock);
}
/**
* gst_make_element_message_details:
* @name: Name of the first field to set
* @...: variable arguments in the same form as #GstStructure
*
* Create a #GstStructure to be used with #gst_element_message_full_with_details.
* %NULL terminator required.
*
* Since: 1.10
*/
GstStructure *
gst_make_element_message_details (const char *name, ...)
{
GstStructure *structure;
va_list varargs;
if (name == NULL)
return NULL;
va_start (varargs, name);
structure = gst_structure_new_valist ("details", name, varargs);
va_end (varargs);
return structure;
}