gstreamer/subprojects/gstreamer/gst/gstbin.c
Mathieu Duponchelle b4cf146ed0 docs: link to concrete types rather than type macros
Latest hotdoc version extended the ignored, boilerplate macros
to now include TYPE macros for records as well

Linking to the concrete type is more informative

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/913>
2021-09-25 01:29:38 +00:00

4602 lines
138 KiB
C

/* GStreamer
*
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2004 Wim Taymans <wim.taymans@gmail.com>
*
* gstbin.c: GstBin container object and support code
*
* 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:gstbin
* @title: GstBin
* @short_description: Base class and element that can contain other elements
*
* #GstBin is an element that can contain other #GstElement, allowing them to be
* managed as a group.
* Pads from the child elements can be ghosted to the bin, see #GstGhostPad.
* This makes the bin look like any other elements and enables creation of
* higher-level abstraction elements.
*
* A new #GstBin is created with gst_bin_new(). Use a #GstPipeline instead if you
* want to create a toplevel bin because a normal bin doesn't have a bus or
* handle clock distribution of its own.
*
* After the bin has been created you will typically add elements to it with
* gst_bin_add(). You can remove elements with gst_bin_remove().
*
* An element can be retrieved from a bin with gst_bin_get_by_name(), using the
* elements name. gst_bin_get_by_name_recurse_up() is mainly used for internal
* purposes and will query the parent bins when the element is not found in the
* current bin.
*
* An iterator of elements in a bin can be retrieved with
* gst_bin_iterate_elements(). Various other iterators exist to retrieve the
* elements in a bin.
*
* gst_object_unref() is used to drop your reference to the bin.
*
* The #GstBin::element-added signal is fired whenever a new element is added to
* the bin. Likewise the #GstBin::element-removed signal is fired whenever an
* element is removed from the bin.
*
* A #GstBin internally intercepts every #GstMessage posted by its children and
* implements the following default behaviour for each of them:
*
* * %GST_MESSAGE_EOS: This message is only posted by sinks in the PLAYING
* state. If all sinks posted the EOS message, this bin will post and EOS
* message upwards.
*
* * %GST_MESSAGE_SEGMENT_START: Just collected and never forwarded upwards.
* The messages are used to decide when all elements have completed playback
* of their segment.
*
* * %GST_MESSAGE_SEGMENT_DONE: Is posted by #GstBin when all elements that posted
* a SEGMENT_START have posted a SEGMENT_DONE.
*
* * %GST_MESSAGE_DURATION_CHANGED: Is posted by an element that detected a change
* in the stream duration. The duration change is posted to the
* application so that it can refetch the new duration with a duration
* query.
*
* Note that these messages can be posted before the bin is prerolled, in which
* case the duration query might fail.
*
* Note also that there might be a discrepancy (due to internal buffering/queueing)
* between the stream being currently displayed and the returned duration query.
*
* Applications might want to also query for duration (and changes) by
* listening to the %GST_MESSAGE_STREAM_START message, signaling the active start
* of a (new) stream.
*
* * %GST_MESSAGE_CLOCK_LOST: This message is posted by an element when it
* can no longer provide a clock.
*
* The default bin behaviour is to check if the lost clock was the one provided
* by the bin. If so and the bin is currently in the PLAYING state, the message
* is forwarded to the bin parent.
*
* This message is also generated when a clock provider is removed from
* the bin. If this message is received by the application, it should
* PAUSE the pipeline and set it back to PLAYING to force a new clock
* distribution.
*
* * %GST_MESSAGE_CLOCK_PROVIDE: This message is generated when an element
* can provide a clock. This mostly happens when a new clock
* provider is added to the bin.
*
* The default behaviour of the bin is to mark the currently selected clock as
* dirty, which will perform a clock recalculation the next time the bin is
* asked to provide a clock.
*
* This message is never sent to the application but is forwarded to
* the parent of the bin.
*
* * OTHERS: posted upwards.
*
* A #GstBin implements the following default behaviour for answering to a
* #GstQuery:
*
* * %GST_QUERY_DURATION: The bin will forward the query to all sink
* elements contained within and will return the maximum value.
* If no sinks are available in the bin, the query fails.
*
* * %GST_QUERY_POSITION: The query is sent to all sink elements in the bin and the
* MAXIMUM of all values is returned. If no sinks are available in the bin,
* the query fails.
*
* * OTHERS: the query is forwarded to all sink elements, the result
* of the first sink that answers the query successfully is returned. If no
* sink is in the bin, the query fails.
*
* A #GstBin will by default forward any event sent to it to all sink
* ( %GST_EVENT_TYPE_DOWNSTREAM ) or source ( %GST_EVENT_TYPE_UPSTREAM ) elements
* depending on the event type.
*
* If all the elements return %TRUE, the bin will also return %TRUE, else %FALSE
* is returned. If no elements of the required type are in the bin, the event
* handler will return %TRUE.
*/
#include "gst_private.h"
#include "gstevent.h"
#include "gstbin.h"
#include "gstinfo.h"
#include "gsterror.h"
#include "gstutils.h"
#include "gstchildproxy.h"
GST_DEBUG_CATEGORY_STATIC (bin_debug);
#define GST_CAT_DEFAULT bin_debug
/* a bin is toplevel if it has no parent or when it is configured to behave like
* a toplevel bin */
#define BIN_IS_TOPLEVEL(bin) ((GST_OBJECT_PARENT (bin) == NULL) || bin->priv->asynchandling)
struct _GstBinPrivate
{
gboolean asynchandling;
/* if we get an ASYNC_DONE message from ourselves, this means that the
* subclass will simulate ASYNC behaviour without having ASYNC children. When
* such an ASYNC_DONE message is posted while we are doing a state change, we
* have to process the message after finishing the state change even when no
* child returned GST_STATE_CHANGE_ASYNC. */
gboolean pending_async_done;
guint32 structure_cookie;
#if 0
/* cached index */
GstIndex *index;
#endif
/* forward messages from our children */
gboolean message_forward;
gboolean posted_eos;
gboolean posted_playing;
GstElementFlags suppressed_flags;
};
typedef struct
{
guint32 cookie;
GstState pending;
} BinContinueData;
static void gst_bin_dispose (GObject * object);
static void gst_bin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_bin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static GstStateChangeReturn gst_bin_change_state_func (GstElement * element,
GstStateChange transition);
static gboolean gst_bin_post_message (GstElement * element, GstMessage * msg);
static GstStateChangeReturn gst_bin_get_state_func (GstElement * element,
GstState * state, GstState * pending, GstClockTime timeout);
static void bin_handle_async_done (GstBin * bin, GstStateChangeReturn ret,
gboolean flag_pending, GstClockTime running_time);
static void bin_handle_async_start (GstBin * bin);
static void bin_push_state_continue (GstBin * bin, BinContinueData * data);
static void bin_do_eos (GstBin * bin);
static gboolean gst_bin_add_func (GstBin * bin, GstElement * element);
static gboolean gst_bin_remove_func (GstBin * bin, GstElement * element);
static void gst_bin_deep_element_added_func (GstBin * bin, GstBin * sub_bin,
GstElement * element);
static void gst_bin_deep_element_removed_func (GstBin * bin, GstBin * sub_bin,
GstElement * element);
static void gst_bin_update_context (GstBin * bin, GstContext * context);
static void gst_bin_update_context_unlocked (GstBin * bin,
GstContext * context);
#if 0
static void gst_bin_set_index_func (GstElement * element, GstIndex * index);
static GstIndex *gst_bin_get_index_func (GstElement * element);
#endif
static GstClock *gst_bin_provide_clock_func (GstElement * element);
static gboolean gst_bin_set_clock_func (GstElement * element, GstClock * clock);
static void gst_bin_handle_message_func (GstBin * bin, GstMessage * message);
static gboolean gst_bin_send_event (GstElement * element, GstEvent * event);
static GstBusSyncReply bin_bus_handler (GstBus * bus,
GstMessage * message, GstBin * bin);
static gboolean gst_bin_query (GstElement * element, GstQuery * query);
static void gst_bin_set_context (GstElement * element, GstContext * context);
static gboolean gst_bin_do_latency_func (GstBin * bin);
static void bin_remove_messages (GstBin * bin, GstObject * src,
GstMessageType types);
static void gst_bin_continue_func (GstBin * bin, BinContinueData * data);
static gint bin_element_is_sink (GstElement * child, GstBin * bin);
static gint bin_element_is_src (GstElement * child, GstBin * bin);
static GstIterator *gst_bin_sort_iterator_new (GstBin * bin);
/* Bin signals and properties */
enum
{
ELEMENT_ADDED,
ELEMENT_REMOVED,
DO_LATENCY,
DEEP_ELEMENT_ADDED,
DEEP_ELEMENT_REMOVED,
LAST_SIGNAL
};
#define DEFAULT_ASYNC_HANDLING FALSE
#define DEFAULT_MESSAGE_FORWARD FALSE
enum
{
PROP_0,
PROP_ASYNC_HANDLING,
PROP_MESSAGE_FORWARD,
PROP_LAST
};
static void gst_bin_child_proxy_init (gpointer g_iface, gpointer iface_data);
static guint gst_bin_signals[LAST_SIGNAL] = { 0 };
#define _do_init \
{ \
static const GInterfaceInfo iface_info = { \
gst_bin_child_proxy_init, \
NULL, \
NULL}; \
\
g_type_add_interface_static (g_define_type_id, GST_TYPE_CHILD_PROXY, &iface_info); \
\
GST_DEBUG_CATEGORY_INIT (bin_debug, "bin", GST_DEBUG_BOLD, \
"debugging info for the 'bin' container element"); \
\
}
#define gst_bin_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstBin, gst_bin, GST_TYPE_ELEMENT,
G_ADD_PRIVATE (GstBin)
_do_init);
static GObject *
gst_bin_child_proxy_get_child_by_index (GstChildProxy * child_proxy,
guint index)
{
GstObject *res;
GstBin *bin;
bin = GST_BIN_CAST (child_proxy);
GST_OBJECT_LOCK (bin);
if ((res = g_list_nth_data (bin->children, index)))
gst_object_ref (res);
GST_OBJECT_UNLOCK (bin);
return (GObject *) res;
}
static guint
gst_bin_child_proxy_get_children_count (GstChildProxy * child_proxy)
{
guint num;
GstBin *bin;
bin = GST_BIN_CAST (child_proxy);
GST_OBJECT_LOCK (bin);
num = bin->numchildren;
GST_OBJECT_UNLOCK (bin);
return num;
}
static void
gst_bin_child_proxy_init (gpointer g_iface, gpointer iface_data)
{
GstChildProxyInterface *iface = g_iface;
iface->get_children_count = gst_bin_child_proxy_get_children_count;
iface->get_child_by_index = gst_bin_child_proxy_get_child_by_index;
}
static gboolean
_gst_boolean_accumulator (GSignalInvocationHint * ihint,
GValue * return_accu, const GValue * handler_return, gpointer dummy)
{
gboolean myboolean;
myboolean = g_value_get_boolean (handler_return);
g_value_set_boolean (return_accu, myboolean);
GST_DEBUG ("invocation %d, %d", ihint->run_type, myboolean);
/* stop emission */
return FALSE;
}
static void
gst_bin_class_init (GstBinClass * klass)
{
GObjectClass *gobject_class;
GstElementClass *gstelement_class;
gobject_class = (GObjectClass *) klass;
gstelement_class = (GstElementClass *) klass;
gobject_class->set_property = gst_bin_set_property;
gobject_class->get_property = gst_bin_get_property;
/**
* GstBin:async-handling:
*
* If set to %TRUE, the bin will handle asynchronous state changes.
* This should be used only if the bin subclass is modifying the state
* of its children on its own.
*/
g_object_class_install_property (gobject_class, PROP_ASYNC_HANDLING,
g_param_spec_boolean ("async-handling", "Async Handling",
"The bin will handle Asynchronous state changes",
DEFAULT_ASYNC_HANDLING, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
/**
* GstBin::element-added:
* @self: the #GstBin
* @element: the #GstElement that was added to the bin
*
* Will be emitted after the element was added to the bin.
*/
gst_bin_signals[ELEMENT_ADDED] =
g_signal_new ("element-added", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstBinClass, element_added), NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
/**
* GstBin::element-removed:
* @self: the #GstBin
* @element: the #GstElement that was removed from the bin
*
* Will be emitted after the element was removed from the bin.
*/
gst_bin_signals[ELEMENT_REMOVED] =
g_signal_new ("element-removed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstBinClass, element_removed), NULL,
NULL, NULL, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
/**
* GstBin::deep-element-added:
* @self: the #GstBin
* @sub_bin: the #GstBin the element was added to
* @element: the #GstElement that was added to @sub_bin
*
* Will be emitted after the element was added to @sub_bin.
*
* Since: 1.10
*/
gst_bin_signals[DEEP_ELEMENT_ADDED] =
g_signal_new ("deep-element-added", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstBinClass, deep_element_added),
NULL, NULL, NULL, G_TYPE_NONE, 2, GST_TYPE_BIN, GST_TYPE_ELEMENT);
/**
* GstBin::deep-element-removed:
* @self: the #GstBin
* @sub_bin: the #GstBin the element was removed from
* @element: the #GstElement that was removed from @sub_bin
*
* Will be emitted after the element was removed from @sub_bin.
*
* Since: 1.10
*/
gst_bin_signals[DEEP_ELEMENT_REMOVED] =
g_signal_new ("deep-element-removed", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (GstBinClass, deep_element_removed),
NULL, NULL, NULL, G_TYPE_NONE, 2, GST_TYPE_BIN, GST_TYPE_ELEMENT);
/**
* GstBin::do-latency:
* @self: the #GstBin
*
* Will be emitted when the bin needs to perform latency calculations. This
* signal is only emitted for toplevel bins or when #GstBin:async-handling is
* enabled.
*
* Only one signal handler is invoked. If no signals are connected, the
* default handler is invoked, which will query and distribute the lowest
* possible latency to all sinks.
*
* Connect to this signal if the default latency calculations are not
* sufficient, like when you need different latencies for different sinks in
* the same pipeline.
*/
gst_bin_signals[DO_LATENCY] =
g_signal_new ("do-latency", G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (GstBinClass, do_latency),
_gst_boolean_accumulator, NULL, NULL, G_TYPE_BOOLEAN, 0, G_TYPE_NONE);
/**
* GstBin:message-forward:
*
* Forward all children messages, even those that would normally be filtered by
* the bin. This can be interesting when one wants to be notified of the EOS
* state of individual elements, for example.
*
* The messages are converted to an ELEMENT message with the bin as the
* source. The structure of the message is named `GstBinForwarded` and contains
* a field named `message` that contains the original forwarded #GstMessage.
*/
g_object_class_install_property (gobject_class, PROP_MESSAGE_FORWARD,
g_param_spec_boolean ("message-forward", "Message Forward",
"Forwards all children messages",
DEFAULT_MESSAGE_FORWARD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
gobject_class->dispose = gst_bin_dispose;
gst_element_class_set_static_metadata (gstelement_class, "Generic bin",
"Generic/Bin",
"Simple container object",
"Erik Walthinsen <omega@cse.ogi.edu>,"
"Wim Taymans <wim.taymans@gmail.com>");
gstelement_class->change_state =
GST_DEBUG_FUNCPTR (gst_bin_change_state_func);
gstelement_class->post_message = GST_DEBUG_FUNCPTR (gst_bin_post_message);
gstelement_class->get_state = GST_DEBUG_FUNCPTR (gst_bin_get_state_func);
#if 0
gstelement_class->get_index = GST_DEBUG_FUNCPTR (gst_bin_get_index_func);
gstelement_class->set_index = GST_DEBUG_FUNCPTR (gst_bin_set_index_func);
#endif
gstelement_class->provide_clock =
GST_DEBUG_FUNCPTR (gst_bin_provide_clock_func);
gstelement_class->set_clock = GST_DEBUG_FUNCPTR (gst_bin_set_clock_func);
gstelement_class->send_event = GST_DEBUG_FUNCPTR (gst_bin_send_event);
gstelement_class->query = GST_DEBUG_FUNCPTR (gst_bin_query);
gstelement_class->set_context = GST_DEBUG_FUNCPTR (gst_bin_set_context);
klass->add_element = GST_DEBUG_FUNCPTR (gst_bin_add_func);
klass->remove_element = GST_DEBUG_FUNCPTR (gst_bin_remove_func);
klass->handle_message = GST_DEBUG_FUNCPTR (gst_bin_handle_message_func);
klass->deep_element_added = gst_bin_deep_element_added_func;
klass->deep_element_removed = gst_bin_deep_element_removed_func;
klass->do_latency = GST_DEBUG_FUNCPTR (gst_bin_do_latency_func);
}
static void
gst_bin_init (GstBin * bin)
{
GstBus *bus;
bin->numchildren = 0;
bin->children = NULL;
bin->children_cookie = 0;
bin->messages = NULL;
bin->provided_clock = NULL;
bin->clock_dirty = FALSE;
/* Set up a bus for listening to child elements */
bus = g_object_new (GST_TYPE_BUS, "enable-async", FALSE, NULL);
gst_object_ref_sink (bus);
bin->child_bus = bus;
GST_DEBUG_OBJECT (bin, "using bus %" GST_PTR_FORMAT " to listen to children",
bus);
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) bin_bus_handler, bin,
NULL);
bin->priv = gst_bin_get_instance_private (bin);
bin->priv->asynchandling = DEFAULT_ASYNC_HANDLING;
bin->priv->structure_cookie = 0;
bin->priv->message_forward = DEFAULT_MESSAGE_FORWARD;
}
static void
gst_bin_dispose (GObject * object)
{
GstBin *bin = GST_BIN_CAST (object);
GstBus **child_bus_p = &bin->child_bus;
GstClock **provided_clock_p = &bin->provided_clock;
GstElement **clock_provider_p = &bin->clock_provider;
GST_CAT_DEBUG_OBJECT (GST_CAT_REFCOUNTING, object, "%p dispose", object);
GST_OBJECT_LOCK (object);
gst_object_replace ((GstObject **) child_bus_p, NULL);
gst_object_replace ((GstObject **) provided_clock_p, NULL);
gst_object_replace ((GstObject **) clock_provider_p, NULL);
bin_remove_messages (bin, NULL, GST_MESSAGE_ANY);
GST_OBJECT_UNLOCK (object);
while (bin->children) {
gst_bin_remove (bin, GST_ELEMENT_CAST (bin->children->data));
}
if (G_UNLIKELY (bin->children != NULL)) {
g_critical ("could not remove elements from bin '%s'",
GST_STR_NULL (GST_OBJECT_NAME (object)));
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
/**
* gst_bin_new:
* @name: (allow-none): the name of the new bin
*
* Creates a new bin with the given name.
*
* Returns: (transfer floating): a new #GstBin
*/
GstElement *
gst_bin_new (const gchar * name)
{
return gst_element_factory_make ("bin", name);
}
static void
gst_bin_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstBin *gstbin;
gstbin = GST_BIN_CAST (object);
switch (prop_id) {
case PROP_ASYNC_HANDLING:
GST_OBJECT_LOCK (gstbin);
gstbin->priv->asynchandling = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (gstbin);
break;
case PROP_MESSAGE_FORWARD:
GST_OBJECT_LOCK (gstbin);
gstbin->priv->message_forward = g_value_get_boolean (value);
GST_OBJECT_UNLOCK (gstbin);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_bin_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstBin *gstbin;
gstbin = GST_BIN_CAST (object);
switch (prop_id) {
case PROP_ASYNC_HANDLING:
GST_OBJECT_LOCK (gstbin);
g_value_set_boolean (value, gstbin->priv->asynchandling);
GST_OBJECT_UNLOCK (gstbin);
break;
case PROP_MESSAGE_FORWARD:
GST_OBJECT_LOCK (gstbin);
g_value_set_boolean (value, gstbin->priv->message_forward);
GST_OBJECT_UNLOCK (gstbin);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
#if 0
/* return the cached index */
static GstIndex *
gst_bin_get_index_func (GstElement * element)
{
GstBin *bin;
GstIndex *result;
bin = GST_BIN_CAST (element);
GST_OBJECT_LOCK (bin);
if ((result = bin->priv->index))
gst_object_ref (result);
GST_OBJECT_UNLOCK (bin);
return result;
}
/* set the index on all elements in this bin
*
* MT safe
*/
static void
gst_bin_set_index_func (GstElement * element, GstIndex * index)
{
GstBin *bin;
gboolean done;
GstIterator *it;
GstIndex *old;
GValue data = { 0, };
bin = GST_BIN_CAST (element);
GST_OBJECT_LOCK (bin);
old = bin->priv->index;
if (G_UNLIKELY (old == index))
goto was_set;
if (index)
gst_object_ref (index);
bin->priv->index = index;
GST_OBJECT_UNLOCK (bin);
if (old)
gst_object_unref (old);
it = gst_bin_iterate_elements (bin);
/* set the index on all elements in the bin */
done = FALSE;
while (!done) {
switch (gst_iterator_next (it, &data)) {
case GST_ITERATOR_OK:
{
GstElement *child = g_value_get_object (&data);
GST_DEBUG_OBJECT (bin, "setting index on '%s'",
GST_ELEMENT_NAME (child));
gst_element_set_index (child, index);
g_value_reset (&data);
break;
}
case GST_ITERATOR_RESYNC:
GST_DEBUG_OBJECT (bin, "iterator doing resync");
gst_iterator_resync (it);
break;
default:
case GST_ITERATOR_DONE:
GST_DEBUG_OBJECT (bin, "iterator done");
done = TRUE;
break;
}
}
g_value_unset (&data);
gst_iterator_free (it);
return;
was_set:
{
GST_DEBUG_OBJECT (bin, "index was already set");
GST_OBJECT_UNLOCK (bin);
return;
}
}
#endif
/* set the clock on all elements in this bin
*
* MT safe
*/
static gboolean
gst_bin_set_clock_func (GstElement * element, GstClock * clock)
{
GstBin *bin;
gboolean done;
GstIterator *it;
gboolean res = TRUE;
GValue data = { 0, };
bin = GST_BIN_CAST (element);
it = gst_bin_iterate_elements (bin);
done = FALSE;
while (!done) {
switch (gst_iterator_next (it, &data)) {
case GST_ITERATOR_OK:
{
GstElement *child = g_value_get_object (&data);
res &= gst_element_set_clock (child, clock);
g_value_reset (&data);
break;
}
case GST_ITERATOR_RESYNC:
GST_DEBUG_OBJECT (bin, "iterator doing resync");
gst_iterator_resync (it);
res = TRUE;
break;
default:
case GST_ITERATOR_DONE:
GST_DEBUG_OBJECT (bin, "iterator done");
done = TRUE;
break;
}
}
g_value_unset (&data);
gst_iterator_free (it);
if (res)
res = GST_ELEMENT_CLASS (parent_class)->set_clock (element, clock);
return res;
}
/* get the clock for this bin by asking all of the children in this bin
*
* The ref of the returned clock in increased so unref after usage.
*
* We loop the elements in state order and pick the last clock we can
* get. This makes sure we get a clock from the source.
*
* MT safe
*/
static GstClock *
gst_bin_provide_clock_func (GstElement * element)
{
GstClock *result = NULL;
GstElement *provider = NULL;
GstBin *bin;
GstIterator *it;
gboolean done;
GValue val = { 0, };
GstClock **provided_clock_p;
GstElement **clock_provider_p;
bin = GST_BIN_CAST (element);
GST_OBJECT_LOCK (bin);
if (!bin->clock_dirty)
goto not_dirty;
GST_DEBUG_OBJECT (bin, "finding new clock");
it = gst_bin_sort_iterator_new (bin);
GST_OBJECT_UNLOCK (bin);
done = FALSE;
while (!done) {
switch (gst_iterator_next (it, &val)) {
case GST_ITERATOR_OK:
{
GstElement *child = g_value_get_object (&val);
GstClock *clock;
clock = gst_element_provide_clock (child);
if (clock) {
GST_DEBUG_OBJECT (bin, "found candidate clock %p by element %s",
clock, GST_ELEMENT_NAME (child));
if (result) {
gst_object_unref (result);
gst_object_unref (provider);
}
result = clock;
provider = gst_object_ref (child);
}
g_value_reset (&val);
break;
}
case GST_ITERATOR_RESYNC:
gst_iterator_resync (it);
break;
default:
case GST_ITERATOR_DONE:
done = TRUE;
break;
}
}
g_value_unset (&val);
gst_iterator_free (it);
GST_OBJECT_LOCK (bin);
if (!bin->clock_dirty) {
if (provider)
gst_object_unref (provider);
if (result)
gst_object_unref (result);
result = NULL;
goto not_dirty;
}
provided_clock_p = &bin->provided_clock;
clock_provider_p = &bin->clock_provider;
gst_object_replace ((GstObject **) provided_clock_p, (GstObject *) result);
gst_object_replace ((GstObject **) clock_provider_p, (GstObject *) provider);
bin->clock_dirty = FALSE;
GST_DEBUG_OBJECT (bin,
"provided new clock %" GST_PTR_FORMAT " by provider %" GST_PTR_FORMAT,
result, provider);
/* Provider is not being returned to caller, just the result */
if (provider)
gst_object_unref (provider);
GST_OBJECT_UNLOCK (bin);
return result;
not_dirty:
{
if ((result = bin->provided_clock))
gst_object_ref (result);
GST_DEBUG_OBJECT (bin, "returning old clock %p", result);
GST_OBJECT_UNLOCK (bin);
return result;
}
}
/*
* functions for manipulating cached messages
*/
typedef struct
{
GstObject *src;
GstMessageType types;
} MessageFind;
/* check if a message is of given src and type */
static gint
message_check (GstMessage * message, MessageFind * target)
{
gboolean eq = TRUE;
if (target->src)
eq &= GST_MESSAGE_SRC (message) == target->src;
if (target->types)
eq &= (GST_MESSAGE_TYPE (message) & target->types) != 0;
GST_LOG ("looking at message %p: %d", message, eq);
return (eq ? 0 : 1);
}
static GList *
find_message (GstBin * bin, GstObject * src, GstMessageType types)
{
GList *result;
MessageFind find;
find.src = src;
find.types = types;
result = g_list_find_custom (bin->messages, &find,
(GCompareFunc) message_check);
if (result) {
GST_DEBUG_OBJECT (bin, "we found a message %p from %s matching types %08x",
result->data, GST_OBJECT_NAME (GST_MESSAGE_CAST (result->data)->src),
types);
} else {
GST_DEBUG_OBJECT (bin, "no message found matching types %08x", types);
#ifndef GST_DISABLE_GST_DEBUG
{
guint i;
for (i = 0; i < 32; i++)
if (types & (1U << i))
GST_DEBUG_OBJECT (bin, " %s", gst_message_type_get_name (1U << i));
}
#endif
}
return result;
}
/* with LOCK, returns TRUE if message had a valid SRC, takes ownership of
* the message.
*
* A message that is cached and has the same SRC and type is replaced
* by the given message.
*/
static gboolean
bin_replace_message (GstBin * bin, GstMessage * message, GstMessageType types)
{
GList *previous;
GstObject *src;
gboolean res = TRUE;
if ((src = GST_MESSAGE_SRC (message))) {
/* first find the previous message posted by this element */
if ((previous = find_message (bin, src, types))) {
GstMessage *previous_msg;
/* if we found a previous message, replace it */
previous_msg = previous->data;
previous->data = message;
GST_DEBUG_OBJECT (bin, "replace old message %s from %s with %s message",
GST_MESSAGE_TYPE_NAME (previous_msg), GST_ELEMENT_NAME (src),
GST_MESSAGE_TYPE_NAME (message));
gst_message_unref (previous_msg);
} else {
/* keep new message */
bin->messages = g_list_prepend (bin->messages, message);
GST_DEBUG_OBJECT (bin, "got new message %p, %s from %s",
message, GST_MESSAGE_TYPE_NAME (message), GST_ELEMENT_NAME (src));
}
} else {
GST_DEBUG_OBJECT (bin, "got message %s from (NULL), not processing",
GST_MESSAGE_TYPE_NAME (message));
res = FALSE;
gst_message_unref (message);
}
return res;
}
/* with LOCK. Remove all messages of given types */
static void
bin_remove_messages (GstBin * bin, GstObject * src, GstMessageType types)
{
MessageFind find;
GList *walk, *next;
find.src = src;
find.types = types;
for (walk = bin->messages; walk; walk = next) {
GstMessage *message = (GstMessage *) walk->data;
next = g_list_next (walk);
if (message_check (message, &find) == 0) {
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message),
"deleting message %p of type %s (types 0x%08x)", message,
GST_MESSAGE_TYPE_NAME (message), types);
bin->messages = g_list_delete_link (bin->messages, walk);
gst_message_unref (message);
} else {
GST_DEBUG_OBJECT (GST_MESSAGE_SRC (message),
"not deleting message %p of type 0x%08x", message,
GST_MESSAGE_TYPE (message));
}
}
}
/* Check if the bin is EOS. We do this by scanning all sinks and
* checking if they posted an EOS message.
*
* call with bin LOCK */
static gboolean
is_eos (GstBin * bin, guint32 * seqnum)
{
gboolean result;
gint n_eos = 0;
GList *walk, *msgs;
result = TRUE;
for (walk = bin->children; walk; walk = g_list_next (walk)) {
GstElement *element;
element = GST_ELEMENT_CAST (walk->data);
if (bin_element_is_sink (element, bin) == 0) {
/* check if element posted EOS */
if ((msgs =
find_message (bin, GST_OBJECT_CAST (element), GST_MESSAGE_EOS))) {
GST_DEBUG ("sink '%s' posted EOS", GST_ELEMENT_NAME (element));
*seqnum = gst_message_get_seqnum (GST_MESSAGE_CAST (msgs->data));
n_eos++;
} else {
GST_DEBUG ("sink '%s' did not post EOS yet",
GST_ELEMENT_NAME (element));
result = FALSE;
break;
}
}
}
/* FIXME: Some tests (e.g. elements/capsfilter) use
* pipelines with a dangling sinkpad but no sink element.
* These tests assume that no EOS message is ever
* posted on the bus so let's keep that behaviour.
* In valid pipelines this doesn't make a difference.
*/
return result && n_eos > 0;
}
/* Check if the bin is STREAM_START. We do this by scanning all sinks and
* checking if they posted an STREAM_START message.
*
* call with bin LOCK */
static gboolean
is_stream_start (GstBin * bin, guint32 * seqnum, gboolean * have_group_id,
guint * group_id)
{
gboolean result;
GList *walk, *msgs;
guint tmp_group_id;
gboolean first_stream_start = TRUE, first_group_id = TRUE;
gboolean same_group_id = TRUE;
*have_group_id = FALSE;
*group_id = 0;
result = FALSE;
for (walk = bin->children; walk; walk = g_list_next (walk)) {
GstElement *element;
element = GST_ELEMENT_CAST (walk->data);
if (bin_element_is_sink (element, bin) == 0) {
/* check if element posted STREAM_START */
if ((msgs =
find_message (bin, GST_OBJECT_CAST (element),
GST_MESSAGE_STREAM_START))) {
/* Only initialize to TRUE if we have any stream-start messages at
* all, otherwise it should be FALSE. */
if (first_stream_start) {
/* If any stream-start message do not contain a group id then we
* will set it to FALSE below */
*have_group_id = TRUE;
/* Similarly if any sinks did not post stream-start then we will
* set it to FALSE afterwards */
result = TRUE;
first_stream_start = FALSE;
}
GST_DEBUG ("sink '%s' posted STREAM_START", GST_ELEMENT_NAME (element));
*seqnum = gst_message_get_seqnum (GST_MESSAGE_CAST (msgs->data));
if (gst_message_parse_group_id (GST_MESSAGE_CAST (msgs->data),
&tmp_group_id)) {
if (first_group_id) {
first_group_id = FALSE;
*group_id = tmp_group_id;
} else {
if (tmp_group_id != *group_id)
same_group_id = FALSE;
}
} else {
*have_group_id = FALSE;
}
} else {
GST_DEBUG ("sink '%s' did not post STREAM_START yet",
GST_ELEMENT_NAME (element));
result = FALSE;
break;
}
}
}
/* If all have a group_id we only consider this stream started
* if all group ids were the same and all sinks posted a stream-start
* message */
if (*have_group_id)
return same_group_id && result;
/* otherwise consider this stream started after all sinks
* have reported stream-start for backward compatibility.
* FIXME 2.0: This should go away! */
return result;
}
static void
unlink_pads (const GValue * item, gpointer user_data)
{
GstPad *pad;
GstPad *peer;
pad = g_value_get_object (item);
if ((peer = gst_pad_get_peer (pad))) {
if (gst_pad_get_direction (pad) == GST_PAD_SRC)
gst_pad_unlink (pad, peer);
else
gst_pad_unlink (peer, pad);
gst_object_unref (peer);
}
}
static void
bin_deep_iterator_foreach (const GValue * item, gpointer user_data)
{
GQueue *queue = user_data;
g_queue_push_tail (queue, g_value_dup_object (item));
}
static void
gst_bin_do_deep_add_remove (GstBin * bin, gint sig_id, const gchar * sig_name,
GstElement * element)
{
g_signal_emit (bin, sig_id, 0, bin, element);
/* When removing a bin, emit deep-element-* for everything in the bin too */
if (GST_IS_BIN (element)) {
GstIterator *it;
GstIteratorResult ires;
GQueue elements = G_QUEUE_INIT;
GST_LOG_OBJECT (bin, "Recursing into bin %" GST_PTR_FORMAT " for %s",
element, sig_name);
it = gst_bin_iterate_recurse (GST_BIN_CAST (element));
do {
ires = gst_iterator_foreach (it, bin_deep_iterator_foreach, &elements);
if (ires != GST_ITERATOR_DONE) {
g_queue_foreach (&elements, (GFunc) gst_object_unref, NULL);
g_queue_clear (&elements);
}
if (ires == GST_ITERATOR_RESYNC)
gst_iterator_resync (it);
} while (ires == GST_ITERATOR_RESYNC);
if (ires != GST_ITERATOR_ERROR) {
GstElement *e;
while ((e = g_queue_pop_head (&elements))) {
GstObject *parent = gst_object_get_parent (GST_OBJECT_CAST (e));
/* an element could have removed some of its internal elements
* meanwhile, so protect against that */
if (parent) {
GST_LOG_OBJECT (bin, "calling %s for element %" GST_PTR_FORMAT
" in bin %" GST_PTR_FORMAT, sig_name, e, parent);
g_signal_emit (bin, sig_id, 0, parent, e);
gst_object_unref (parent);
gst_object_unref (e);
}
}
}
gst_iterator_free (it);
}
}
/* vmethod that adds an element to a bin
*
* MT safe
*/
static gboolean
gst_bin_add_func (GstBin * bin, GstElement * element)
{
gchar *elem_name;
GstIterator *it;
gboolean is_sink, is_source, provides_clock, requires_clock;
GstMessage *clock_message = NULL, *async_message = NULL;
GstStateChangeReturn ret;
GList *l, *elem_contexts, *need_context_messages;
GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element));
/* we obviously can't add ourself to ourself */
if (G_UNLIKELY (element == GST_ELEMENT_CAST (bin)))
goto adding_itself;
/* get the element name to make sure it is unique in this bin. */
GST_OBJECT_LOCK (element);
elem_name = g_strdup (GST_ELEMENT_NAME (element));
is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK);
is_source = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SOURCE);
provides_clock =
GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
requires_clock =
GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
GST_OBJECT_UNLOCK (element);
GST_OBJECT_LOCK (bin);
/* then check to see if the element's name is already taken in the bin,
* we can safely take the lock here. This check is probably bogus because
* you can safely change the element name after this check and before setting
* the object parent. The window is very small though... */
if (G_UNLIKELY (!gst_object_check_uniqueness (bin->children, elem_name)))
goto duplicate_name;
/* set the element's parent and add the element to the bin's list of children */
if (G_UNLIKELY (!gst_object_set_parent (GST_OBJECT_CAST (element),
GST_OBJECT_CAST (bin))))
goto had_parent;
/* if we add a sink we become a sink */
if (is_sink && !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_SINK)) {
GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, bin, "element \"%s\" was sink",
elem_name);
GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_FLAG_SINK);
}
if (is_source && !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_SOURCE)) {
GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, bin, "element \"%s\" was source",
elem_name);
GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_FLAG_SOURCE);
}
if (provides_clock
&& !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_PROVIDE_CLOCK)) {
GST_DEBUG_OBJECT (bin, "element \"%s\" can provide a clock", elem_name);
clock_message =
gst_message_new_clock_provide (GST_OBJECT_CAST (element), NULL, TRUE);
GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
}
if (requires_clock
&& !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_REQUIRE_CLOCK)) {
GST_DEBUG_OBJECT (bin, "element \"%s\" requires a clock", elem_name);
GST_OBJECT_FLAG_SET (bin, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
}
bin->children = g_list_prepend (bin->children, element);
bin->numchildren++;
bin->children_cookie++;
if (!GST_BIN_IS_NO_RESYNC (bin))
bin->priv->structure_cookie++;
/* distribute the bus */
gst_element_set_bus (element, bin->child_bus);
/* propagate the current base_time, start_time and clock */
gst_element_set_base_time (element, GST_ELEMENT_CAST (bin)->base_time);
gst_element_set_start_time (element, GST_ELEMENT_START_TIME (bin));
/* it's possible that the element did not accept the clock but
* that is not important right now. When the pipeline goes to PLAYING,
* a new clock will be selected */
gst_element_set_clock (element, GST_ELEMENT_CLOCK (bin));
/* get the element's list of contexts before propagating our own */
elem_contexts = gst_element_get_contexts (element);
for (l = GST_ELEMENT_CAST (bin)->contexts; l; l = l->next)
gst_element_set_context (element, l->data);
need_context_messages = NULL;
for (l = elem_contexts; l; l = l->next) {
GstContext *replacement, *context = l->data;
const gchar *context_type;
context_type = gst_context_get_context_type (context);
/* we already set this context above? */
replacement =
gst_element_get_context_unlocked (GST_ELEMENT (bin), context_type);
if (replacement) {
gst_context_unref (replacement);
} else {
GstMessage *msg;
GstStructure *s;
/* ask our parent for the context */
msg = gst_message_new_need_context (GST_OBJECT_CAST (bin), context_type);
s = (GstStructure *) gst_message_get_structure (msg);
gst_structure_set (s, "bin.old.context", GST_TYPE_CONTEXT, context, NULL);
need_context_messages = g_list_prepend (need_context_messages, msg);
}
}
#if 0
/* set the cached index on the children */
if (bin->priv->index)
gst_element_set_index (element, bin->priv->index);
#endif
ret = GST_STATE_RETURN (bin);
/* no need to update the state if we are in error */
if (ret == GST_STATE_CHANGE_FAILURE)
goto no_state_recalc;
/* update the bin state, the new element could have been an ASYNC or
* NO_PREROLL element */
ret = GST_STATE_RETURN (element);
GST_DEBUG_OBJECT (bin, "added %s element",
gst_element_state_change_return_get_name (ret));
switch (ret) {
case GST_STATE_CHANGE_ASYNC:
{
/* create message to track this aync element when it posts an async-done
* message */
async_message = gst_message_new_async_start (GST_OBJECT_CAST (element));
break;
}
case GST_STATE_CHANGE_NO_PREROLL:
/* ignore all async elements we might have and commit our state */
bin_handle_async_done (bin, ret, FALSE, GST_CLOCK_TIME_NONE);
break;
case GST_STATE_CHANGE_FAILURE:
break;
default:
break;
}
no_state_recalc:
GST_OBJECT_UNLOCK (bin);
for (l = need_context_messages; l; l = l->next) {
GstMessage *msg = l->data;
GstStructure *s;
const gchar *context_type;
GstContext *replacement, *context;
gst_message_parse_context_type (msg, &context_type);
GST_LOG_OBJECT (bin, "asking parent for context type: %s "
"from %" GST_PTR_FORMAT, context_type, element);
s = (GstStructure *) gst_message_get_structure (msg);
gst_structure_get (s, "bin.old.context", GST_TYPE_CONTEXT, &context, NULL);
gst_structure_remove_field (s, "bin.old.context");
/* Keep the msg around while we still need access to the context_type */
gst_element_post_message (GST_ELEMENT_CAST (bin), gst_message_ref (msg));
/* lock to avoid losing a potential write */
GST_OBJECT_LOCK (bin);
replacement =
gst_element_get_context_unlocked (GST_ELEMENT_CAST (bin), context_type);
gst_message_unref (msg);
if (replacement) {
/* we got the context set from GstElement::set_context */
gst_context_unref (replacement);
GST_OBJECT_UNLOCK (bin);
} else {
/* Propagate the element's context upwards */
GST_LOG_OBJECT (bin, "propagating existing context type: %s %p "
"from %" GST_PTR_FORMAT, context_type, context, element);
gst_bin_update_context_unlocked (bin, context);
msg =
gst_message_new_have_context (GST_OBJECT_CAST (bin),
gst_context_ref (context));
GST_OBJECT_UNLOCK (bin);
gst_element_post_message (GST_ELEMENT_CAST (bin), msg);
}
gst_context_unref (context);
}
g_list_free_full (elem_contexts, (GDestroyNotify) gst_context_unref);
g_list_free (need_context_messages);
/* post the messages on the bus of the element so that the bin can handle
* them */
if (clock_message)
gst_element_post_message (element, clock_message);
if (async_message)
gst_element_post_message (element, async_message);
/* unlink all linked pads */
it = gst_element_iterate_pads (element);
while (gst_iterator_foreach (it, (GstIteratorForeachFunction) unlink_pads,
NULL) == GST_ITERATOR_RESYNC)
gst_iterator_resync (it);
gst_iterator_free (it);
GST_CAT_DEBUG_OBJECT (GST_CAT_PARENTAGE, bin, "added element \"%s\"",
elem_name);
g_signal_emit (bin, gst_bin_signals[ELEMENT_ADDED], 0, element);
gst_child_proxy_child_added ((GstChildProxy *) bin, (GObject *) element,
elem_name);
gst_bin_do_deep_add_remove (bin, gst_bin_signals[DEEP_ELEMENT_ADDED],
"deep-element-added", element);
g_free (elem_name);
return TRUE;
/* ERROR handling here */
adding_itself:
{
GST_OBJECT_LOCK (bin);
g_warning ("Cannot add bin '%s' to itself", GST_ELEMENT_NAME (bin));
GST_OBJECT_UNLOCK (bin);
gst_object_ref_sink (element);
gst_object_unref (element);
return FALSE;
}
duplicate_name:
{
g_warning ("Name '%s' is not unique in bin '%s', not adding",
elem_name, GST_ELEMENT_NAME (bin));
GST_OBJECT_UNLOCK (bin);
g_free (elem_name);
gst_object_ref_sink (element);
gst_object_unref (element);
return FALSE;
}
had_parent:
{
g_warning ("Element '%s' already has parent", elem_name);
GST_OBJECT_UNLOCK (bin);
g_free (elem_name);
return FALSE;
}
}
/**
* gst_bin_set_suppressed_flags:
* @bin: a #GstBin
* @flags: the #GstElementFlags to suppress
*
* Suppresses the given flags on the bin. #GstElementFlags of a
* child element are propagated when it is added to the bin.
* When suppressed flags are set, those specified flags will
* not be propagated to the bin.
*
* Since: 1.10
*/
void
gst_bin_set_suppressed_flags (GstBin * bin, GstElementFlags flags)
{
g_return_if_fail (GST_IS_BIN (bin));
GST_OBJECT_LOCK (bin);
bin->priv->suppressed_flags = bin->priv->suppressed_flags | flags;
GST_OBJECT_UNLOCK (bin);
GST_DEBUG_OBJECT (bin, "Set suppressed flags(0x%x) to bin '%s'", flags,
GST_ELEMENT_NAME (bin));
}
/**
* gst_bin_get_suppressed_flags:
* @bin: a #GstBin
*
* Returns: the bin's suppressed #GstElementFlags.
*
* Since: 1.10
*/
GstElementFlags
gst_bin_get_suppressed_flags (GstBin * bin)
{
GstElementFlags res;
g_return_val_if_fail (GST_IS_BIN (bin), 0);
GST_OBJECT_LOCK (bin);
res = bin->priv->suppressed_flags;
GST_OBJECT_UNLOCK (bin);
return res;
}
/* signal vfunc, will be called when a new element was added */
static void
gst_bin_deep_element_added_func (GstBin * bin, GstBin * sub_bin,
GstElement * child)
{
GstBin *parent_bin;
parent_bin = (GstBin *) gst_object_get_parent (GST_OBJECT_CAST (bin));
if (parent_bin == NULL) {
GST_LOG_OBJECT (bin, "no parent, reached top-level");
return;
}
GST_LOG_OBJECT (parent_bin, "emitting deep-element-added for element "
"%" GST_PTR_FORMAT " which has just been added to %" GST_PTR_FORMAT,
child, sub_bin);
g_signal_emit (parent_bin, gst_bin_signals[DEEP_ELEMENT_ADDED], 0, sub_bin,
child);
gst_object_unref (parent_bin);
}
/* signal vfunc, will be called when an element was removed */
static void
gst_bin_deep_element_removed_func (GstBin * bin, GstBin * sub_bin,
GstElement * child)
{
GstBin *parent_bin;
parent_bin = (GstBin *) gst_object_get_parent (GST_OBJECT_CAST (bin));
if (parent_bin == NULL) {
GST_LOG_OBJECT (bin, "no parent, reached top-level");
return;
}
GST_LOG_OBJECT (parent_bin, "emitting deep-element-removed for element "
"%" GST_PTR_FORMAT " which has just been removed from %" GST_PTR_FORMAT,
child, sub_bin);
g_signal_emit (parent_bin, gst_bin_signals[DEEP_ELEMENT_REMOVED], 0, sub_bin,
child);
gst_object_unref (parent_bin);
}
/**
* gst_bin_add:
* @bin: a #GstBin
* @element: (transfer floating): the #GstElement to add
*
* Adds the given element to the bin. Sets the element's parent, and thus
* takes ownership of the element. An element can only be added to one bin.
*
* If the element's pads are linked to other pads, the pads will be unlinked
* before the element is added to the bin.
*
* > When you add an element to an already-running pipeline, you will have to
* > take care to set the state of the newly-added element to the desired
* > state (usually PLAYING or PAUSED, same you set the pipeline to originally)
* > with gst_element_set_state(), or use gst_element_sync_state_with_parent().
* > The bin or pipeline will not take care of this for you.
*
* Returns: %TRUE if the element could be added, %FALSE if
* the bin does not want to accept the element.
*/
gboolean
gst_bin_add (GstBin * bin, GstElement * element)
{
GstBinClass *bclass;
gboolean result;
g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (GST_ELEMENT_CAST (bin) != element, FALSE);
bclass = GST_BIN_GET_CLASS (bin);
if (G_UNLIKELY (bclass->add_element == NULL))
goto no_function;
GST_CAT_DEBUG (GST_CAT_PARENTAGE, "adding element %s to bin %s",
GST_STR_NULL (GST_ELEMENT_NAME (element)),
GST_STR_NULL (GST_ELEMENT_NAME (bin)));
GST_TRACER_BIN_ADD_PRE (bin, element);
result = bclass->add_element (bin, element);
GST_TRACER_BIN_ADD_POST (bin, element, result);
return result;
/* ERROR handling */
no_function:
{
g_warning ("adding elements to bin '%s' is not supported",
GST_ELEMENT_NAME (bin));
gst_object_ref_sink (element);
gst_object_unref (element);
return FALSE;
}
}
/* remove an element from the bin
*
* MT safe
*/
static gboolean
gst_bin_remove_func (GstBin * bin, GstElement * element)
{
gchar *elem_name;
GstIterator *it;
gboolean is_sink, is_source, provides_clock, requires_clock;
gboolean othersink, othersource, otherprovider, otherrequirer, found;
GstMessage *clock_message = NULL;
GstClock **provided_clock_p;
GstElement **clock_provider_p;
GList *walk, *next;
gboolean other_async, this_async, have_no_preroll, removed_eos;
GstStateChangeReturn ret;
GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element));
/* we obviously can't remove ourself from ourself */
if (G_UNLIKELY (element == GST_ELEMENT_CAST (bin)))
goto removing_itself;
GST_OBJECT_LOCK (bin);
GST_OBJECT_LOCK (element);
elem_name = g_strdup (GST_ELEMENT_NAME (element));
if (GST_OBJECT_PARENT (element) != GST_OBJECT_CAST (bin))
goto not_in_bin;
/* remove the parent ref */
GST_OBJECT_PARENT (element) = NULL;
/* grab element name so we can print it */
is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK);
is_source = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SOURCE);
provides_clock =
GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
requires_clock =
GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
GST_OBJECT_UNLOCK (element);
found = FALSE;
othersink = FALSE;
othersource = FALSE;
otherprovider = FALSE;
otherrequirer = FALSE;
have_no_preroll = FALSE;
/* iterate the elements, we collect which ones are async and no_preroll. We
* also remove the element when we find it. */
for (walk = bin->children; walk; walk = next) {
GstElement *child = GST_ELEMENT_CAST (walk->data);
next = g_list_next (walk);
if (child == element) {
found = TRUE;
/* remove the element */
bin->children = g_list_delete_link (bin->children, walk);
} else {
gboolean child_sink, child_source, child_provider, child_requirer;
GST_OBJECT_LOCK (child);
child_sink = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SINK);
child_source = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SOURCE);
child_provider =
GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
child_requirer =
GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
/* when we remove a sink, check if there are other sinks. */
if (is_sink && !othersink && child_sink)
othersink = TRUE;
if (is_source && !othersource && child_source)
othersource = TRUE;
if (provides_clock && !otherprovider && child_provider)
otherprovider = TRUE;
if (requires_clock && !otherrequirer && child_requirer)
otherrequirer = TRUE;
/* check if we have NO_PREROLL children */
if (GST_STATE_RETURN (child) == GST_STATE_CHANGE_NO_PREROLL)
have_no_preroll = TRUE;
GST_OBJECT_UNLOCK (child);
}
}
/* the element must have been in the bin's list of children */
if (G_UNLIKELY (!found))
goto not_in_bin;
/* we now removed the element from the list of elements, increment the cookie
* so that others can detect a change in the children list. */
bin->numchildren--;
bin->children_cookie++;
if (!GST_BIN_IS_NO_RESYNC (bin))
bin->priv->structure_cookie++;
if (is_sink && !othersink
&& !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_SINK)) {
/* we're not a sink anymore */
GST_DEBUG_OBJECT (bin, "we removed the last sink");
GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_FLAG_SINK);
}
if (is_source && !othersource
&& !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_SOURCE)) {
/* we're not a source anymore */
GST_DEBUG_OBJECT (bin, "we removed the last source");
GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_FLAG_SOURCE);
}
if (provides_clock && !otherprovider
&& !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_PROVIDE_CLOCK)) {
/* we're not a clock provider anymore */
GST_DEBUG_OBJECT (bin, "we removed the last clock provider");
GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_FLAG_PROVIDE_CLOCK);
}
if (requires_clock && !otherrequirer
&& !(bin->priv->suppressed_flags & GST_ELEMENT_FLAG_REQUIRE_CLOCK)) {
/* we're not a clock requirer anymore */
GST_DEBUG_OBJECT (bin, "we removed the last clock requirer");
GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_FLAG_REQUIRE_CLOCK);
}
/* if the clock provider for this element is removed, we lost
* the clock as well, we need to inform the parent of this
* so that it can select a new clock */
if (bin->clock_provider == element) {
GST_DEBUG_OBJECT (bin, "element \"%s\" provided the clock", elem_name);
bin->clock_dirty = TRUE;
clock_message =
gst_message_new_clock_lost (GST_OBJECT_CAST (bin), bin->provided_clock);
provided_clock_p = &bin->provided_clock;
clock_provider_p = &bin->clock_provider;
gst_object_replace ((GstObject **) provided_clock_p, NULL);
gst_object_replace ((GstObject **) clock_provider_p, NULL);
}
/* remove messages for the element, if there was a pending ASYNC_START
* message we must see if removing the element caused the bin to lose its
* async state. */
this_async = FALSE;
other_async = FALSE;
/* If we remove an EOSed element, the bin might go EOS */
removed_eos = FALSE;
for (walk = bin->messages; walk; walk = next) {
GstMessage *message = (GstMessage *) walk->data;
GstElement *src = GST_ELEMENT_CAST (GST_MESSAGE_SRC (message));
gboolean remove;
next = g_list_next (walk);
remove = FALSE;
switch (GST_MESSAGE_TYPE (message)) {
case GST_MESSAGE_ASYNC_START:
if (src == element)
this_async = TRUE;
else
other_async = TRUE;
GST_DEBUG_OBJECT (src, "looking at message %p", message);
break;
case GST_MESSAGE_STRUCTURE_CHANGE:
{
GstElement *owner;
GST_DEBUG_OBJECT (src, "looking at structure change message %p",
message);
/* it's unlikely that this message is still in the list of messages
* because this would mean that a link/unlink is busy in another thread
* while we remove the element. We still have to remove the message
* because we might not receive the done message anymore when the element
* is removed from the bin. */
gst_message_parse_structure_change (message, NULL, &owner, NULL);
if (owner == element)
remove = TRUE;
break;
}
case GST_MESSAGE_EOS:
if (src == element)
removed_eos = TRUE;
break;
default:
break;
}
if (src == element)
remove = TRUE;
if (remove) {
/* delete all message types */
GST_DEBUG_OBJECT (src, "deleting message %p of element \"%s\"",
message, elem_name);
bin->messages = g_list_delete_link (bin->messages, walk);
gst_message_unref (message);
}
}
/* get last return */
ret = GST_STATE_RETURN (bin);
/* no need to update the state if we are in error */
if (ret == GST_STATE_CHANGE_FAILURE)
goto no_state_recalc;
if (!other_async && this_async) {
/* all other elements were not async and we removed the async one,
* handle the async-done case because we are not async anymore now. */
GST_DEBUG_OBJECT (bin,
"we removed the last async element, have no_preroll %d",
have_no_preroll);
/* the current state return of the bin depends on if there are no_preroll
* elements in the pipeline or not */
if (have_no_preroll)
ret = GST_STATE_CHANGE_NO_PREROLL;
else
ret = GST_STATE_CHANGE_SUCCESS;
bin_handle_async_done (bin, ret, FALSE, GST_CLOCK_TIME_NONE);
} else {
GST_DEBUG_OBJECT (bin,
"recalc state preroll: %d, other async: %d, this async %d",
have_no_preroll, other_async, this_async);
if (have_no_preroll) {
ret = GST_STATE_CHANGE_NO_PREROLL;
} else if (other_async) {
/* there are other async elements and we were not doing an async state
* change, change our pending state and go async */
if (GST_STATE_PENDING (bin) == GST_STATE_VOID_PENDING) {
GST_STATE_NEXT (bin) = GST_STATE (bin);
GST_STATE_PENDING (bin) = GST_STATE (bin);
}
ret = GST_STATE_CHANGE_ASYNC;
}
GST_STATE_RETURN (bin) = ret;
}
no_state_recalc:
/* clear bus */
gst_element_set_bus (element, NULL);
/* Clear the clock we provided to the element */
gst_element_set_clock (element, NULL);
GST_OBJECT_UNLOCK (bin);
/* If the element was a sink that had not posted EOS,
* it might have been the last one we were waiting for,
* so check if it's time to send EOS now */
if (is_sink && !removed_eos) {
GST_DEBUG_OBJECT (bin,
"Removing sink that had not EOSed. Re-checking overall EOS status");
bin_do_eos (bin);
}
if (clock_message)
gst_element_post_message (GST_ELEMENT_CAST (bin), clock_message);
/* unlink all linked pads */
it = gst_element_iterate_pads (element);
while (gst_iterator_foreach (it, (GstIteratorForeachFunction) unlink_pads,
NULL) == GST_ITERATOR_RESYNC)
gst_iterator_resync (it);
gst_iterator_free (it);
GST_CAT_INFO_OBJECT (GST_CAT_PARENTAGE, bin, "removed child \"%s\"",
elem_name);
g_signal_emit (bin, gst_bin_signals[ELEMENT_REMOVED], 0, element);
gst_child_proxy_child_removed ((GstChildProxy *) bin, (GObject *) element,
elem_name);
gst_bin_do_deep_add_remove (bin, gst_bin_signals[DEEP_ELEMENT_REMOVED],
"deep-element-removed", element);
g_free (elem_name);
/* element is really out of our control now */
gst_object_unref (element);
return TRUE;
/* ERROR handling */
removing_itself:
{
GST_OBJECT_LOCK (bin);
g_warning ("Cannot remove bin '%s' from itself", GST_ELEMENT_NAME (bin));
GST_OBJECT_UNLOCK (bin);
return FALSE;
}
not_in_bin:
{
g_warning ("Element '%s' is not in bin '%s'", elem_name,
GST_ELEMENT_NAME (bin));
GST_OBJECT_UNLOCK (element);
GST_OBJECT_UNLOCK (bin);
g_free (elem_name);
return FALSE;
}
}
/**
* gst_bin_remove:
* @bin: a #GstBin
* @element: (transfer none): the #GstElement to remove
*
* Removes the element from the bin, unparenting it as well.
* Unparenting the element means that the element will be dereferenced,
* so if the bin holds the only reference to the element, the element
* will be freed in the process of removing it from the bin. If you
* want the element to still exist after removing, you need to call
* gst_object_ref() before removing it from the bin.
*
* If the element's pads are linked to other pads, the pads will be unlinked
* before the element is removed from the bin.
*
* Returns: %TRUE if the element could be removed, %FALSE if
* the bin does not want to remove the element.
*/
gboolean
gst_bin_remove (GstBin * bin, GstElement * element)
{
GstBinClass *bclass;
gboolean result;
g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE);
g_return_val_if_fail (GST_ELEMENT_CAST (bin) != element, FALSE);
bclass = GST_BIN_GET_CLASS (bin);
if (G_UNLIKELY (bclass->remove_element == NULL))
goto no_function;
GST_CAT_DEBUG (GST_CAT_PARENTAGE, "removing element %s from bin %s",
GST_ELEMENT_NAME (element), GST_ELEMENT_NAME (bin));
GST_TRACER_BIN_REMOVE_PRE (bin, element);
result = bclass->remove_element (bin, element);
GST_TRACER_BIN_REMOVE_POST (bin, result);
return result;
/* ERROR handling */
no_function:
{
g_warning ("removing elements from bin '%s' is not supported",
GST_ELEMENT_NAME (bin));
return FALSE;
}
}
/**
* gst_bin_iterate_elements:
* @bin: a #GstBin
*
* Gets an iterator for the elements in this bin.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
*/
GstIterator *
gst_bin_iterate_elements (GstBin * bin)
{
GstIterator *result;
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
GST_OBJECT_LOCK (bin);
result = gst_iterator_new_list (GST_TYPE_ELEMENT,
GST_OBJECT_GET_LOCK (bin),
&bin->children_cookie, &bin->children, (GObject *) bin, NULL);
GST_OBJECT_UNLOCK (bin);
return result;
}
static GstIteratorItem
iterate_child_recurse (GstIterator * it, const GValue * item)
{
GstElement *child = g_value_get_object (item);
if (GST_IS_BIN (child)) {
GstIterator *other = gst_bin_iterate_recurse (GST_BIN_CAST (child));
gst_iterator_push (it, other);
}
return GST_ITERATOR_ITEM_PASS;
}
/**
* gst_bin_iterate_recurse:
* @bin: a #GstBin
*
* Gets an iterator for the elements in this bin.
* This iterator recurses into GstBin children.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
*/
GstIterator *
gst_bin_iterate_recurse (GstBin * bin)
{
GstIterator *result;
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
GST_OBJECT_LOCK (bin);
result = gst_iterator_new_list (GST_TYPE_ELEMENT,
GST_OBJECT_GET_LOCK (bin),
&bin->children_cookie,
&bin->children,
(GObject *) bin, (GstIteratorItemFunction) iterate_child_recurse);
GST_OBJECT_UNLOCK (bin);
return result;
}
/* returns 0 when TRUE because this is a GCompareFunc */
/* MT safe */
static gint
bin_element_is_sink (GstElement * child, GstBin * bin)
{
gboolean is_sink;
/* we lock the child here for the remainder of the function to
* get its name and flag safely. */
GST_OBJECT_LOCK (child);
is_sink = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SINK);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin,
"child %s %s sink", GST_OBJECT_NAME (child), is_sink ? "is" : "is not");
GST_OBJECT_UNLOCK (child);
return is_sink ? 0 : 1;
}
static gint
sink_iterator_filter (const GValue * vchild, GValue * vbin)
{
GstBin *bin = g_value_get_object (vbin);
GstElement *child = g_value_get_object (vchild);
return (bin_element_is_sink (child, bin));
}
/**
* gst_bin_iterate_sinks:
* @bin: a #GstBin
*
* Gets an iterator for all elements in the bin that have the
* #GST_ELEMENT_FLAG_SINK flag set.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
*/
GstIterator *
gst_bin_iterate_sinks (GstBin * bin)
{
GstIterator *children;
GstIterator *result;
GValue vbin = { 0, };
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
g_value_init (&vbin, GST_TYPE_BIN);
g_value_set_object (&vbin, bin);
children = gst_bin_iterate_elements (bin);
result = gst_iterator_filter (children,
(GCompareFunc) sink_iterator_filter, &vbin);
g_value_unset (&vbin);
return result;
}
/* returns 0 when TRUE because this is a GCompareFunc */
/* MT safe */
static gint
bin_element_is_src (GstElement * child, GstBin * bin)
{
gboolean is_src;
/* we lock the child here for the remainder of the function to
* get its name and other info safely. */
GST_OBJECT_LOCK (child);
is_src = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_FLAG_SOURCE);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin,
"child %s %s src", GST_OBJECT_NAME (child), is_src ? "is" : "is not");
GST_OBJECT_UNLOCK (child);
return is_src ? 0 : 1;
}
static gint
src_iterator_filter (const GValue * vchild, GValue * vbin)
{
GstBin *bin = g_value_get_object (vbin);
GstElement *child = g_value_get_object (vchild);
return (bin_element_is_src (child, bin));
}
/**
* gst_bin_iterate_sources:
* @bin: a #GstBin
*
* Gets an iterator for all elements in the bin that have the
* #GST_ELEMENT_FLAG_SOURCE flag set.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
*/
GstIterator *
gst_bin_iterate_sources (GstBin * bin)
{
GstIterator *children;
GstIterator *result;
GValue vbin = { 0, };
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
g_value_init (&vbin, GST_TYPE_BIN);
g_value_set_object (&vbin, bin);
children = gst_bin_iterate_elements (bin);
result = gst_iterator_filter (children,
(GCompareFunc) src_iterator_filter, &vbin);
g_value_unset (&vbin);
return result;
}
/*
* MT safe
*/
static GstStateChangeReturn
gst_bin_get_state_func (GstElement * element, GstState * state,
GstState * pending, GstClockTime timeout)
{
GstStateChangeReturn ret;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state");
ret =
GST_ELEMENT_CLASS (parent_class)->get_state (element, state, pending,
timeout);
return ret;
}
/***********************************************
* Topologically sorted iterator
* see http://en.wikipedia.org/wiki/Topological_sorting
*
* For each element in the graph, an entry is kept in a HashTable
* with its number of srcpad connections (degree).
* We then change state of all elements without dependencies
* (degree 0) and decrement the degree of all elements connected
* on the sinkpads. When an element reaches degree 0, its state is
* changed next.
* When all elements are handled the algorithm stops.
*/
typedef struct _GstBinSortIterator
{
GstIterator it;
GQueue queue; /* elements queued for state change */
GstBin *bin; /* bin we iterate */
gint mode; /* adding or removing dependency */
GstElement *best; /* next element with least dependencies */
gint best_deg; /* best degree */
GHashTable *hash; /* hashtable with element dependencies */
gboolean dirty; /* we detected structure change */
} GstBinSortIterator;
static void
copy_to_queue (gpointer data, gpointer user_data)
{
GstElement *element = data;
GQueue *queue = user_data;
gst_object_ref (element);
g_queue_push_tail (queue, element);
}
static void
gst_bin_sort_iterator_copy (const GstBinSortIterator * it,
GstBinSortIterator * copy)
{
GHashTableIter iter;
gpointer key, value;
g_queue_init (&copy->queue);
g_queue_foreach ((GQueue *) & it->queue, copy_to_queue, &copy->queue);
copy->bin = gst_object_ref (it->bin);
if (it->best)
copy->best = gst_object_ref (it->best);
copy->hash = g_hash_table_new (NULL, NULL);
g_hash_table_iter_init (&iter, it->hash);
while (g_hash_table_iter_next (&iter, &key, &value))
g_hash_table_insert (copy->hash, key, value);
}
/* we add and subtract 1 to make sure we don't confuse NULL and 0 */
#define HASH_SET_DEGREE(bit, elem, deg) \
g_hash_table_replace (bit->hash, elem, GINT_TO_POINTER(deg+1))
#define HASH_GET_DEGREE(bit, elem) \
(GPOINTER_TO_INT(g_hash_table_lookup (bit->hash, elem))-1)
/* add element to queue of next elements in the iterator.
* We push at the tail to give higher priority elements a
* chance first */
static void
add_to_queue (GstBinSortIterator * bit, GstElement * element)
{
GST_DEBUG_OBJECT (bit->bin, "adding '%s' to queue",
GST_ELEMENT_NAME (element));
gst_object_ref (element);
g_queue_push_tail (&bit->queue, element);
HASH_SET_DEGREE (bit, element, -1);
}
static void
remove_from_queue (GstBinSortIterator * bit, GstElement * element)
{
GList *find;
if ((find = g_queue_find (&bit->queue, element))) {
GST_DEBUG_OBJECT (bit->bin, "removing '%s' from queue",
GST_ELEMENT_NAME (element));
g_queue_delete_link (&bit->queue, find);
gst_object_unref (element);
} else {
GST_DEBUG_OBJECT (bit->bin, "unable to remove '%s' from queue",
GST_ELEMENT_NAME (element));
}
}
/* clear the queue, unref all objects as we took a ref when
* we added them to the queue */
static void
clear_queue (GQueue * queue)
{
gpointer p;
while ((p = g_queue_pop_head (queue)))
gst_object_unref (p);
}
/* set all degrees to 0. Elements marked as a sink are
* added to the queue immediately. Since we only look at the SINK flag of the
* element, it is possible that we add non-sinks to the queue. These will be
* removed from the queue again when we can prove that it provides data for some
* other element. */
static void
reset_degree (GstElement * element, GstBinSortIterator * bit)
{
gboolean is_sink;
/* sinks are added right away */
GST_OBJECT_LOCK (element);
is_sink = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SINK);
GST_OBJECT_UNLOCK (element);
if (is_sink) {
add_to_queue (bit, element);
} else {
/* others are marked with 0 and handled when sinks are done */
HASH_SET_DEGREE (bit, element, 0);
}
}
/* adjust the degree of all elements connected to the given
* element. If a degree of an element drops to 0, it is
* added to the queue of elements to schedule next.
*
* We have to make sure not to cross the bin boundary this element
* belongs to.
*/
static void
update_degree (GstElement * element, GstBinSortIterator * bit)
{
gboolean linked = FALSE;
GST_OBJECT_LOCK (element);
/* don't touch degree if element has no sinkpads */
if (element->numsinkpads != 0) {
/* loop over all sinkpads, decrement degree for all connected
* elements in this bin */
GList *pads;
for (pads = element->sinkpads; pads; pads = g_list_next (pads)) {
GstPad *pad, *peer;
pad = GST_PAD_CAST (pads->data);
/* we're iterating over the sinkpads, check if it's busy in a link/unlink */
if (G_UNLIKELY (find_message (bit->bin, GST_OBJECT_CAST (pad),
GST_MESSAGE_STRUCTURE_CHANGE))) {
/* mark the iterator as dirty because we won't be updating the degree
* of the peer parent now. This would result in the 'loop detected'
* later on because the peer parent element could become the best next
* element with a degree > 0. We will simply continue our state
* changes and we'll eventually resync when the unlink completed and
* the iterator cookie is updated. */
bit->dirty = TRUE;
continue;
}
if ((peer = gst_pad_get_peer (pad))) {
GstElement *peer_element;
if ((peer_element = gst_pad_get_parent_element (peer))) {
GST_OBJECT_LOCK (peer_element);
/* check that we don't go outside of this bin */
if (GST_OBJECT_CAST (peer_element)->parent ==
GST_OBJECT_CAST (bit->bin)) {
gint old_deg, new_deg;
old_deg = HASH_GET_DEGREE (bit, peer_element);
/* check to see if we added an element as sink that was not really a
* sink because it was connected to some other element. */
if (old_deg == -1) {
remove_from_queue (bit, peer_element);
old_deg = 0;
}
new_deg = old_deg + bit->mode;
GST_DEBUG_OBJECT (bit->bin,
"change element %s, degree %d->%d, linked to %s",
GST_ELEMENT_NAME (peer_element), old_deg, new_deg,
GST_ELEMENT_NAME (element));
/* update degree, it is possible that an element was in 0 and
* reaches -1 here. This would mean that the element had no sinkpads
* but became linked while the state change was happening. We will
* resync on this with the structure change message. */
if (new_deg == 0) {
/* degree hit 0, add to queue */
add_to_queue (bit, peer_element);
} else {
HASH_SET_DEGREE (bit, peer_element, new_deg);
}
linked = TRUE;
}
GST_OBJECT_UNLOCK (peer_element);
gst_object_unref (peer_element);
}
gst_object_unref (peer);
}
}
}
if (!linked) {
GST_DEBUG_OBJECT (bit->bin, "element %s not linked on any sinkpads",
GST_ELEMENT_NAME (element));
}
GST_OBJECT_UNLOCK (element);
}
/* find the next best element not handled yet. This is the one
* with the lowest non-negative degree */
static void
find_element (GstElement * element, GstBinSortIterator * bit)
{
gint degree;
/* element is already handled */
if ((degree = HASH_GET_DEGREE (bit, element)) < 0)
return;
/* first element or element with smaller degree */
if (bit->best == NULL || bit->best_deg > degree) {
bit->best = element;
bit->best_deg = degree;
} else if (bit->best_deg == degree
&& GST_OBJECT_FLAG_IS_SET (bit->best, GST_ELEMENT_FLAG_SOURCE)
&& !GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_FLAG_SOURCE)) {
/* If two elements have the same degree, we want to ensure we
* return non-source elements first. */
bit->best = element;
}
}
/* get next element in iterator. */
static GstIteratorResult
gst_bin_sort_iterator_next (GstBinSortIterator * bit, GValue * result)
{
GstElement *best;
GstBin *bin = bit->bin;
/* empty queue, we have to find a next best element */
if (g_queue_is_empty (&bit->queue)) {
bit->best = NULL;
bit->best_deg = G_MAXINT;
g_list_foreach (bin->children, (GFunc) find_element, bit);
if ((best = bit->best)) {
/* when we detected an unlink, don't warn because our degrees might be
* screwed up. We will resync later */
if (bit->best_deg != 0 && !bit->dirty) {
/* we don't fail on this one yet */
GST_WARNING_OBJECT (bin, "loop dected in graph");
g_warning ("loop detected in the graph of bin '%s'!!",
GST_ELEMENT_NAME (bin));
}
/* best unhandled element, schedule as next element */
GST_DEBUG_OBJECT (bin, "queue empty, next best: %s",
GST_ELEMENT_NAME (best));
HASH_SET_DEGREE (bit, best, -1);
g_value_set_object (result, best);
} else {
GST_DEBUG_OBJECT (bin, "queue empty, elements exhausted");
/* no more unhandled elements, we are done */
return GST_ITERATOR_DONE;
}
} else {
/* everything added to the queue got reffed */
best = g_queue_pop_head (&bit->queue);
g_value_set_object (result, best);
gst_object_unref (best);
}
GST_DEBUG_OBJECT (bin, "queue head gives %s", GST_ELEMENT_NAME (best));
/* update degrees of linked elements */
update_degree (best, bit);
return GST_ITERATOR_OK;
}
/* clear queues, recalculate the degrees and restart. */
static void
gst_bin_sort_iterator_resync (GstBinSortIterator * bit)
{
GstBin *bin = bit->bin;
GST_DEBUG_OBJECT (bin, "resync");
bit->dirty = FALSE;
clear_queue (&bit->queue);
/* reset degrees */
g_list_foreach (bin->children, (GFunc) reset_degree, bit);
/* calc degrees, incrementing */
bit->mode = 1;
g_list_foreach (bin->children, (GFunc) update_degree, bit);
/* for the rest of the function we decrement the degrees */
bit->mode = -1;
}
/* clear queues, unref bin and free iterator. */
static void
gst_bin_sort_iterator_free (GstBinSortIterator * bit)
{
GstBin *bin = bit->bin;
GST_DEBUG_OBJECT (bin, "free");
clear_queue (&bit->queue);
g_hash_table_destroy (bit->hash);
gst_object_unref (bin);
}
/* should be called with the bin LOCK held */
static GstIterator *
gst_bin_sort_iterator_new (GstBin * bin)
{
GstBinSortIterator *result;
/* we don't need an ItemFunction because we ref the items in the _next
* method already */
result = (GstBinSortIterator *)
gst_iterator_new (sizeof (GstBinSortIterator),
GST_TYPE_ELEMENT,
GST_OBJECT_GET_LOCK (bin),
&bin->priv->structure_cookie,
(GstIteratorCopyFunction) gst_bin_sort_iterator_copy,
(GstIteratorNextFunction) gst_bin_sort_iterator_next,
(GstIteratorItemFunction) NULL,
(GstIteratorResyncFunction) gst_bin_sort_iterator_resync,
(GstIteratorFreeFunction) gst_bin_sort_iterator_free);
g_queue_init (&result->queue);
result->hash = g_hash_table_new (NULL, NULL);
gst_object_ref (bin);
result->bin = bin;
gst_bin_sort_iterator_resync (result);
return (GstIterator *) result;
}
/**
* gst_bin_iterate_sorted:
* @bin: a #GstBin
*
* Gets an iterator for the elements in this bin in topologically
* sorted order. This means that the elements are returned from
* the most downstream elements (sinks) to the sources.
*
* This function is used internally to perform the state changes
* of the bin elements and for clock selection.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
*/
GstIterator *
gst_bin_iterate_sorted (GstBin * bin)
{
GstIterator *result;
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
GST_OBJECT_LOCK (bin);
result = gst_bin_sort_iterator_new (bin);
GST_OBJECT_UNLOCK (bin);
return result;
}
static GstStateChangeReturn
gst_bin_element_set_state (GstBin * bin, GstElement * element,
GstClockTime base_time, GstClockTime start_time, GstState current,
GstState next)
{
GstStateChangeReturn ret;
GstState child_current, child_pending;
gboolean locked;
GList *found;
GST_STATE_LOCK (element);
GST_OBJECT_LOCK (element);
/* set base_time and start time on child */
GST_ELEMENT_START_TIME (element) = start_time;
element->base_time = base_time;
/* peel off the locked flag */
locked = GST_ELEMENT_IS_LOCKED_STATE (element);
/* Get the previous set_state result to preserve NO_PREROLL and ASYNC */
ret = GST_STATE_RETURN (element);
child_current = GST_STATE (element);
child_pending = GST_STATE_PENDING (element);
GST_OBJECT_UNLOCK (element);
/* skip locked elements */
if (G_UNLIKELY (locked))
goto locked;
/* if the element was no preroll, just start changing the state regardless
* if it had async elements (in the case of a bin) because they won't preroll
* anyway. */
if (G_UNLIKELY (ret == GST_STATE_CHANGE_NO_PREROLL)) {
GST_DEBUG_OBJECT (element, "element is NO_PREROLL, ignore async elements");
goto no_preroll;
}
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"current %s pending %s, desired next %s",
gst_element_state_get_name (child_current),
gst_element_state_get_name (child_pending),
gst_element_state_get_name (next));
/* always recurse into bins so that we can set the base time */
if (GST_IS_BIN (element))
goto do_state;
/* Try not to change the state of elements that are already in the state we're
* going to */
if (child_current == next && child_pending == GST_STATE_VOID_PENDING) {
/* child is already at the requested state, return previous return. Note that
* if the child has a pending state to next, we will still call the
* set_state function */
goto unneeded;
} else if (next > current) {
/* upward state change */
if (child_pending == GST_STATE_VOID_PENDING) {
/* .. and the child is not busy doing anything */
if (child_current > next) {
/* .. and is already past the requested state, assume it got there
* without error */
ret = GST_STATE_CHANGE_SUCCESS;
goto unneeded;
}
} else if (child_pending > child_current) {
/* .. and the child is busy going upwards */
if (child_current >= next) {
/* .. and is already past the requested state, assume it got there
* without error */
ret = GST_STATE_CHANGE_SUCCESS;
goto unneeded;
}
} else {
/* .. and the child is busy going downwards */
if (child_current > next) {
/* .. and is already past the requested state, assume it got there
* without error */
ret = GST_STATE_CHANGE_SUCCESS;
goto unneeded;
}
}
} else if (next < current) {
/* downward state change */
if (child_pending == GST_STATE_VOID_PENDING) {
/* .. and the child is not busy doing anything */
if (child_current < next) {
/* .. and is already past the requested state, assume it got there
* without error */
ret = GST_STATE_CHANGE_SUCCESS;
goto unneeded;
}
} else if (child_pending < child_current) {
/* .. and the child is busy going downwards */
if (child_current <= next) {
/* .. and is already past the requested state, assume it got there
* without error */
ret = GST_STATE_CHANGE_SUCCESS;
goto unneeded;
}
} else {
/* .. and the child is busy going upwards */
if (child_current < next) {
/* .. and is already past the requested state, assume it got there
* without error */
ret = GST_STATE_CHANGE_SUCCESS;
goto unneeded;
}
}
}
do_state:
GST_OBJECT_LOCK (bin);
/* the element was busy with an upwards async state change, we must wait for
* an ASYNC_DONE message before we attempt to change the state. */
if ((found =
find_message (bin, GST_OBJECT_CAST (element),
GST_MESSAGE_ASYNC_START))) {
#ifndef GST_DISABLE_GST_DEBUG
GstMessage *message = GST_MESSAGE_CAST (found->data);
GST_DEBUG_OBJECT (element, "element message %p, %s async busy",
message, GST_ELEMENT_NAME (GST_MESSAGE_SRC (message)));
#endif
/* only wait for upward state changes */
if (next > current) {
/* We found an async element check if we can force its state to change or
* if we have to wait for it to preroll. */
goto was_busy;
}
}
GST_OBJECT_UNLOCK (bin);
no_preroll:
GST_DEBUG_OBJECT (bin,
"setting element %s to %s, base_time %" GST_TIME_FORMAT,
GST_ELEMENT_NAME (element), gst_element_state_get_name (next),
GST_TIME_ARGS (base_time));
/* change state */
ret = gst_element_set_state (element, next);
GST_STATE_UNLOCK (element);
return ret;
locked:
{
GST_DEBUG_OBJECT (element,
"element is locked, return previous return %s",
gst_element_state_change_return_get_name (ret));
GST_STATE_UNLOCK (element);
return ret;
}
unneeded:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"skipping transition from %s to %s",
gst_element_state_get_name (child_current),
gst_element_state_get_name (next));
GST_STATE_UNLOCK (element);
return ret;
}
was_busy:
{
GST_DEBUG_OBJECT (element, "element was busy, delaying state change");
GST_OBJECT_UNLOCK (bin);
GST_STATE_UNLOCK (element);
return GST_STATE_CHANGE_ASYNC;
}
}
/* 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, 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, (GstIteratorFoldFunction) activate_pads,
&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
*/
static gboolean
gst_bin_src_pads_activate (GstBin * bin, gboolean active)
{
GstIterator *iter;
gboolean fold_ok;
GST_DEBUG_OBJECT (bin, "%s pads", active ? "activate" : "deactivate");
iter = gst_element_iterate_src_pads ((GstElement *) bin);
fold_ok = iterator_activate_fold_with_resync (iter, &active);
gst_iterator_free (iter);
if (G_UNLIKELY (!fold_ok))
goto failed;
GST_DEBUG_OBJECT (bin, "pad %sactivation successful", active ? "" : "de");
return TRUE;
/* ERRORS */
failed:
{
GST_DEBUG_OBJECT (bin, "pad %sactivation failed", active ? "" : "de");
return FALSE;
}
}
/**
* gst_bin_recalculate_latency:
* @bin: a #GstBin
*
* Queries @bin for the current latency and reconfigures this latency on all the
* elements using a LATENCY event.
*
* This method is typically called on the pipeline when a #GST_MESSAGE_LATENCY
* is posted on the bus.
*
* This function simply emits the #GstBin::do-latency signal so any custom latency
* calculations will be performed.
*
* Returns: %TRUE if the latency could be queried and reconfigured.
*/
gboolean
gst_bin_recalculate_latency (GstBin * bin)
{
gboolean res;
g_signal_emit (bin, gst_bin_signals[DO_LATENCY], 0, &res);
GST_DEBUG_OBJECT (bin, "latency returned %d", res);
return res;
}
static gboolean
gst_bin_do_latency_func (GstBin * bin)
{
GstQuery *query;
GstElement *element;
GstClockTime min_latency, max_latency;
gboolean res;
g_return_val_if_fail (GST_IS_BIN (bin), FALSE);
element = GST_ELEMENT_CAST (bin);
GST_DEBUG_OBJECT (element, "querying latency");
query = gst_query_new_latency ();
if ((res = gst_element_query (element, query))) {
gboolean live;
gst_query_parse_latency (query, &live, &min_latency, &max_latency);
GST_DEBUG_OBJECT (element,
"got min latency %" GST_TIME_FORMAT ", max latency %"
GST_TIME_FORMAT ", live %d", GST_TIME_ARGS (min_latency),
GST_TIME_ARGS (max_latency), live);
if (max_latency < min_latency) {
/* this is an impossible situation, some parts of the pipeline might not
* work correctly. We post a warning for now. */
GST_ELEMENT_WARNING (element, CORE, CLOCK, (NULL),
("Impossible to configure latency: max %" GST_TIME_FORMAT " < min %"
GST_TIME_FORMAT ". Add queues or other buffering elements.",
GST_TIME_ARGS (max_latency), GST_TIME_ARGS (min_latency)));
}
/* configure latency on elements */
res = gst_element_send_event (element, gst_event_new_latency (min_latency));
if (res) {
GST_INFO_OBJECT (element, "configured latency of %" GST_TIME_FORMAT,
GST_TIME_ARGS (min_latency));
} else {
GST_WARNING_OBJECT (element,
"did not really configure latency of %" GST_TIME_FORMAT,
GST_TIME_ARGS (min_latency));
}
} else {
/* this is not a real problem, we just don't configure any latency. */
GST_WARNING_OBJECT (element, "failed to query latency");
}
gst_query_unref (query);
return res;
}
static gboolean
gst_bin_post_message (GstElement * element, GstMessage * msg)
{
GstElementClass *pklass = (GstElementClass *) parent_class;
gboolean ret;
ret = pklass->post_message (element, gst_message_ref (msg));
if (GST_MESSAGE_TYPE (msg) == GST_MESSAGE_STATE_CHANGED &&
GST_MESSAGE_SRC (msg) == GST_OBJECT_CAST (element)) {
GstState newstate, pending;
gst_message_parse_state_changed (msg, NULL, &newstate, &pending);
if (newstate == GST_STATE_PLAYING && pending == GST_STATE_VOID_PENDING) {
GST_BIN_CAST (element)->priv->posted_playing = TRUE;
bin_do_eos (GST_BIN_CAST (element));
} else {
GST_BIN_CAST (element)->priv->posted_playing = FALSE;
}
}
gst_message_unref (msg);
return ret;
}
static void
reset_state (const GValue * data, gpointer user_data)
{
GstElement *e = g_value_get_object (data);
GstState state = GPOINTER_TO_INT (user_data);
if (gst_element_set_state (e, state) == GST_STATE_CHANGE_FAILURE)
GST_WARNING_OBJECT (e, "Failed to switch back down to %s",
gst_element_state_get_name (state));
}
static GstStateChangeReturn
gst_bin_change_state_func (GstElement * element, GstStateChange transition)
{
GstBin *bin;
GstStateChangeReturn ret;
GstState current, next;
gboolean have_async;
gboolean have_no_preroll;
GstClockTime base_time, start_time;
GstIterator *it;
gboolean done;
GValue data = { 0, };
/* we don't need to take the STATE_LOCK, it is already taken */
current = (GstState) GST_STATE_TRANSITION_CURRENT (transition);
next = (GstState) GST_STATE_TRANSITION_NEXT (transition);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"changing state of children from %s to %s",
gst_element_state_get_name (current), gst_element_state_get_name (next));
bin = GST_BIN_CAST (element);
switch (next) {
case GST_STATE_PLAYING:
{
gboolean toplevel, asynchandling;
GST_OBJECT_LOCK (bin);
toplevel = BIN_IS_TOPLEVEL (bin);
asynchandling = bin->priv->asynchandling;
GST_OBJECT_UNLOCK (bin);
if (toplevel)
gst_bin_recalculate_latency (bin);
if (asynchandling)
gst_element_post_message (element,
gst_message_new_latency (GST_OBJECT_CAST (element)));
break;
}
case GST_STATE_PAUSED:
/* Clear EOS list on next PAUSED */
GST_OBJECT_LOCK (bin);
GST_DEBUG_OBJECT (element, "clearing EOS elements");
bin_remove_messages (bin, NULL, GST_MESSAGE_EOS);
bin->priv->posted_eos = FALSE;
if (current == GST_STATE_READY)
bin_remove_messages (bin, NULL, GST_MESSAGE_STREAM_START);
GST_OBJECT_UNLOCK (bin);
if (current == GST_STATE_READY)
if (!(gst_bin_src_pads_activate (bin, TRUE)))
goto activate_failure;
break;
case GST_STATE_READY:
/* Clear message list on next READY */
GST_OBJECT_LOCK (bin);
GST_DEBUG_OBJECT (element, "clearing all cached messages");
bin_remove_messages (bin, NULL, GST_MESSAGE_ANY);
GST_OBJECT_UNLOCK (bin);
/* We might not have reached PAUSED yet due to async errors,
* make sure to always deactivate the pads nonetheless */
if (!(gst_bin_src_pads_activate (bin, FALSE)))
goto activate_failure;
break;
case GST_STATE_NULL:
/* Clear message list on next NULL */
GST_OBJECT_LOCK (bin);
GST_DEBUG_OBJECT (element, "clearing all cached messages");
bin_remove_messages (bin, NULL, GST_MESSAGE_ANY);
GST_OBJECT_UNLOCK (bin);
if (current == GST_STATE_READY) {
if (!(gst_bin_src_pads_activate (bin, FALSE)))
goto activate_failure;
}
break;
default:
break;
}
/* this flag is used to make the async state changes return immediately. We
* don't want them to interfere with this state change */
GST_OBJECT_LOCK (bin);
bin->polling = TRUE;
GST_OBJECT_UNLOCK (bin);
/* iterate in state change order */
it = gst_bin_iterate_sorted (bin);
/* mark if we've seen an ASYNC element in the bin when we did a state change.
* Note how we don't reset this value when a resync happens, the reason being
* that the async element posted ASYNC_START and we want to post ASYNC_DONE
* even after a resync when the async element is gone */
have_async = FALSE;
restart:
/* take base_time */
base_time = gst_element_get_base_time (element);
start_time = gst_element_get_start_time (element);
have_no_preroll = FALSE;
done = FALSE;
while (!done) {
switch (gst_iterator_next (it, &data)) {
case GST_ITERATOR_OK:
{
GstElement *child;
child = g_value_get_object (&data);
/* set state and base_time now */
ret = gst_bin_element_set_state (bin, child, base_time, start_time,
current, next);
switch (ret) {
case GST_STATE_CHANGE_SUCCESS:
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"child '%s' changed state to %d(%s) successfully",
GST_ELEMENT_NAME (child), next,
gst_element_state_get_name (next));
break;
case GST_STATE_CHANGE_ASYNC:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"child '%s' is changing state asynchronously to %s",
GST_ELEMENT_NAME (child), gst_element_state_get_name (next));
have_async = TRUE;
break;
}
case GST_STATE_CHANGE_FAILURE:{
GstObject *parent;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"child '%s' failed to go to state %d(%s)",
GST_ELEMENT_NAME (child),
next, gst_element_state_get_name (next));
/* Only fail if the child is still inside
* this bin. It might've been removed already
* because of the error by the bin subclass
* to ignore the error. */
parent = gst_object_get_parent (GST_OBJECT_CAST (child));
if (parent == GST_OBJECT_CAST (element)) {
/* element is still in bin, really error now */
gst_object_unref (parent);
goto undo;
}
/* child removed from bin, let the resync code redo the state
* change */
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"child '%s' was removed from the bin",
GST_ELEMENT_NAME (child));
if (parent)
gst_object_unref (parent);
break;
}
case GST_STATE_CHANGE_NO_PREROLL:
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"child '%s' changed state to %d(%s) successfully without preroll",
GST_ELEMENT_NAME (child), next,
gst_element_state_get_name (next));
have_no_preroll = TRUE;
break;
default:
g_assert_not_reached ();
break;
}
g_value_reset (&data);
break;
}
case GST_ITERATOR_RESYNC:
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "iterator doing resync");
gst_iterator_resync (it);
goto restart;
default:
case GST_ITERATOR_DONE:
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "iterator done");
done = TRUE;
break;
}
}
ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
if (G_UNLIKELY (ret == GST_STATE_CHANGE_FAILURE))
goto done;
if (have_no_preroll) {
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin,
"we have NO_PREROLL elements %s -> NO_PREROLL",
gst_element_state_change_return_get_name (ret));
ret = GST_STATE_CHANGE_NO_PREROLL;
} else if (have_async) {
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin,
"we have ASYNC elements %s -> ASYNC",
gst_element_state_change_return_get_name (ret));
ret = GST_STATE_CHANGE_ASYNC;
}
done:
g_value_unset (&data);
gst_iterator_free (it);
GST_OBJECT_LOCK (bin);
bin->polling = FALSE;
/* it's possible that we did not get ASYNC from the children while the bin is
* simulating ASYNC behaviour by posting an ASYNC_DONE message on the bus with
* itself as the source. In that case we still want to check if the state
* change completed. */
if (ret != GST_STATE_CHANGE_ASYNC && !bin->priv->pending_async_done) {
/* no element returned ASYNC and there are no pending async_done messages,
* we can just complete. */
GST_DEBUG_OBJECT (bin, "no async elements");
goto state_end;
}
/* when we get here an ASYNC element was found */
if (GST_STATE_TARGET (bin) <= GST_STATE_READY) {
/* we ignore ASYNC state changes when we go to READY or NULL */
GST_DEBUG_OBJECT (bin, "target state %s <= READY",
gst_element_state_get_name (GST_STATE_TARGET (bin)));
goto state_end;
}
GST_DEBUG_OBJECT (bin, "check async elements");
/* check if all elements managed to commit their state already */
if (!find_message (bin, NULL, GST_MESSAGE_ASYNC_START)) {
/* nothing found, remove all old ASYNC_DONE messages. This can happen when
* all the elements committed their state while we were doing the state
* change. We will still return ASYNC for consistency but we commit the
* state already so that a _get_state() will return immediately. */
bin_remove_messages (bin, NULL, GST_MESSAGE_ASYNC_DONE);
GST_DEBUG_OBJECT (bin, "async elements committed");
bin_handle_async_done (bin, GST_STATE_CHANGE_SUCCESS, FALSE,
GST_CLOCK_TIME_NONE);
}
state_end:
bin->priv->pending_async_done = FALSE;
GST_OBJECT_UNLOCK (bin);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"done changing bin's state from %s to %s, now in %s, ret %s",
gst_element_state_get_name (current),
gst_element_state_get_name (next),
gst_element_state_get_name (GST_STATE (element)),
gst_element_state_change_return_get_name (ret));
return ret;
/* ERRORS */
activate_failure:
{
GST_CAT_WARNING_OBJECT (GST_CAT_STATES, element,
"failure (de)activating src pads");
return GST_STATE_CHANGE_FAILURE;
}
undo:
{
if (current < next) {
GstIterator *it = gst_bin_iterate_sorted (GST_BIN (element));
GstIteratorResult ret;
GST_DEBUG_OBJECT (element,
"Bin failed to change state, switching children back to %s",
gst_element_state_get_name (current));
while (TRUE) {
ret =
gst_iterator_foreach (it, &reset_state, GINT_TO_POINTER (current));
if (ret != GST_ITERATOR_RESYNC)
break;
gst_iterator_resync (it);
}
gst_iterator_free (it);
}
goto done;
}
}
/*
* This function is a utility event handler. It will send the event to all sinks
* or sources and appropriate ghost pads depending on the event-direction.
*
* Applications are free to override this behaviour and implement their own
* handler, but this will work for pretty much all cases in practice.
*/
static gboolean
gst_bin_send_event (GstElement * element, GstEvent * event)
{
GstBin *bin = GST_BIN_CAST (element);
GstIterator *iter;
gboolean res = TRUE;
gboolean done = FALSE;
GValue data = { 0, };
if (GST_EVENT_IS_DOWNSTREAM (event)) {
iter = gst_bin_iterate_sources (bin);
GST_DEBUG_OBJECT (bin, "Sending %s event to src children",
GST_EVENT_TYPE_NAME (event));
} else {
iter = gst_bin_iterate_sinks (bin);
GST_DEBUG_OBJECT (bin, "Sending %s event to sink children",
GST_EVENT_TYPE_NAME (event));
}
while (!done) {
switch (gst_iterator_next (iter, &data)) {
case GST_ITERATOR_OK:
{
GstElement *child = g_value_get_object (&data);
gst_event_ref (event);
res &= gst_element_send_event (child, event);
GST_LOG_OBJECT (child, "After handling %s event: %d",
GST_EVENT_TYPE_NAME (event), res);
g_value_reset (&data);
break;
}
case GST_ITERATOR_RESYNC:
gst_iterator_resync (iter);
res = TRUE;
break;
case GST_ITERATOR_DONE:
done = TRUE;
break;
case GST_ITERATOR_ERROR:
g_assert_not_reached ();
break;
}
}
g_value_unset (&data);
gst_iterator_free (iter);
if (GST_EVENT_IS_DOWNSTREAM (event)) {
iter = gst_element_iterate_sink_pads (GST_ELEMENT (bin));
GST_DEBUG_OBJECT (bin, "Sending %s event to sink pads",
GST_EVENT_TYPE_NAME (event));
} else {
iter = gst_element_iterate_src_pads (GST_ELEMENT (bin));
GST_DEBUG_OBJECT (bin, "Sending %s event to src pads",
GST_EVENT_TYPE_NAME (event));
}
done = FALSE;
while (!done) {
switch (gst_iterator_next (iter, &data)) {
case GST_ITERATOR_OK:
{
GstPad *pad = g_value_get_object (&data);
gst_event_ref (event);
res &= gst_pad_send_event (pad, event);
GST_LOG_OBJECT (pad, "After handling %s event: %d",
GST_EVENT_TYPE_NAME (event), res);
break;
}
case GST_ITERATOR_RESYNC:
gst_iterator_resync (iter);
res = TRUE;
break;
case GST_ITERATOR_DONE:
done = TRUE;
break;
case GST_ITERATOR_ERROR:
g_assert_not_reached ();
break;
}
}
g_value_unset (&data);
gst_iterator_free (iter);
gst_event_unref (event);
return res;
}
/* this is the function called by the threadpool. When async elements commit
* their state, this function will attempt to bring the bin to the next state.
*/
static void
gst_bin_continue_func (GstBin * bin, BinContinueData * data)
{
GstState current, next, pending;
GstStateChange transition;
pending = data->pending;
GST_DEBUG_OBJECT (bin, "waiting for state lock");
GST_STATE_LOCK (bin);
GST_DEBUG_OBJECT (bin, "doing state continue");
GST_OBJECT_LOCK (bin);
/* if a new state change happened after this thread was scheduled, we return
* immediately. */
if (data->cookie != GST_ELEMENT_CAST (bin)->state_cookie)
goto interrupted;
current = GST_STATE (bin);
next = GST_STATE_GET_NEXT (current, pending);
transition = (GstStateChange) GST_STATE_TRANSITION (current, next);
GST_STATE_NEXT (bin) = next;
GST_STATE_PENDING (bin) = pending;
/* mark busy */
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
GST_OBJECT_UNLOCK (bin);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"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));
gst_element_change_state (GST_ELEMENT_CAST (bin), transition);
GST_STATE_UNLOCK (bin);
GST_DEBUG_OBJECT (bin, "state continue done");
return;
interrupted:
{
GST_OBJECT_UNLOCK (bin);
GST_STATE_UNLOCK (bin);
GST_DEBUG_OBJECT (bin, "state continue aborted due to intervening change");
return;
}
}
static GstBusSyncReply
bin_bus_handler (GstBus * bus, GstMessage * message, GstBin * bin)
{
GstBinClass *bclass;
bclass = GST_BIN_GET_CLASS (bin);
if (bclass->handle_message)
bclass->handle_message (bin, message);
else
gst_message_unref (message);
return GST_BUS_DROP;
}
static void
free_bin_continue_data (BinContinueData * data)
{
g_slice_free (BinContinueData, data);
}
static void
bin_push_state_continue (GstBin * bin, BinContinueData * data)
{
GST_DEBUG_OBJECT (bin, "pushing continue on thread pool");
gst_element_call_async (GST_ELEMENT_CAST (bin),
(GstElementCallAsyncFunc) gst_bin_continue_func, data,
(GDestroyNotify) free_bin_continue_data);
}
/* an element started an async state change, if we were not busy with a state
* change, we perform a lost state.
* This function is called with the OBJECT lock.
*/
static void
bin_handle_async_start (GstBin * bin)
{
GstState old_state, new_state;
gboolean toplevel;
GstMessage *amessage = NULL;
if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_FAILURE)
goto had_error;
/* get our toplevel state */
toplevel = BIN_IS_TOPLEVEL (bin);
/* prepare an ASYNC_START message, we always post the start message even if we
* are busy with a state change or when we are NO_PREROLL. */
if (!toplevel)
/* non toplevel bin, prepare async-start for the parent */
amessage = gst_message_new_async_start (GST_OBJECT_CAST (bin));
if (bin->polling || GST_STATE_PENDING (bin) != GST_STATE_VOID_PENDING)
goto was_busy;
/* async starts are ignored when we are NO_PREROLL */
if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_NO_PREROLL)
goto was_no_preroll;
old_state = GST_STATE (bin);
/* when we PLAYING we go back to PAUSED, when preroll happens, we go back to
* PLAYING after optionally redistributing the base_time. */
if (old_state > GST_STATE_PAUSED)
new_state = GST_STATE_PAUSED;
else
new_state = old_state;
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin,
"lost state of %s, new %s", gst_element_state_get_name (old_state),
gst_element_state_get_name (new_state));
GST_STATE (bin) = new_state;
GST_STATE_NEXT (bin) = new_state;
GST_STATE_PENDING (bin) = new_state;
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
GST_OBJECT_UNLOCK (bin);
/* post message */
_priv_gst_element_state_changed (GST_ELEMENT_CAST (bin), new_state, new_state,
new_state);
post_start:
if (amessage) {
/* post our ASYNC_START. */
GST_DEBUG_OBJECT (bin, "posting ASYNC_START to parent");
gst_element_post_message (GST_ELEMENT_CAST (bin), amessage);
}
GST_OBJECT_LOCK (bin);
return;
had_error:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "we had an error");
return;
}
was_busy:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "state change busy");
GST_OBJECT_UNLOCK (bin);
goto post_start;
}
was_no_preroll:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "ignoring, we are NO_PREROLL");
GST_OBJECT_UNLOCK (bin);
goto post_start;
}
}
/* this function is called when there are no more async elements in the bin. We
* post a state changed message and an ASYNC_DONE message.
* This function is called with the OBJECT lock.
*/
static void
bin_handle_async_done (GstBin * bin, GstStateChangeReturn ret,
gboolean flag_pending, GstClockTime running_time)
{
GstState current, pending, target;
GstStateChangeReturn old_ret;
GstState old_state, old_next;
gboolean toplevel, state_changed = FALSE;
GstMessage *amessage = NULL;
BinContinueData *cont = NULL;
if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_FAILURE)
goto had_error;
pending = GST_STATE_PENDING (bin);
if (bin->polling)
goto was_busy;
/* check if there is something to commit */
if (pending == GST_STATE_VOID_PENDING)
goto nothing_pending;
old_ret = GST_STATE_RETURN (bin);
GST_STATE_RETURN (bin) = ret;
/* move to the next target state */
target = GST_STATE_TARGET (bin);
pending = GST_STATE_PENDING (bin) = target;
amessage = gst_message_new_async_done (GST_OBJECT_CAST (bin), running_time);
old_state = GST_STATE (bin);
/* this is the state we should go to next */
old_next = GST_STATE_NEXT (bin);
if (old_next != GST_STATE_PLAYING) {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"committing state from %s to %s, old pending %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_next),
gst_element_state_get_name (pending));
/* update current state */
current = GST_STATE (bin) = old_next;
} else {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"setting state from %s to %s, pending %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_state),
gst_element_state_get_name (pending));
current = old_state;
}
/* get our toplevel state */
toplevel = BIN_IS_TOPLEVEL (bin);
/* see if we reached the final state. If we are not toplevel, we also have to
* stop here, the parent will continue our state. */
if ((pending == current) || !toplevel) {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"completed state change, pending VOID");
/* mark VOID pending */
pending = GST_STATE_VOID_PENDING;
GST_STATE_PENDING (bin) = pending;
GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
} else {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"continue state change, pending %s",
gst_element_state_get_name (pending));
cont = g_slice_new (BinContinueData);
/* cookie to detect concurrent state change */
cont->cookie = GST_ELEMENT_CAST (bin)->state_cookie;
/* pending target state */
cont->pending = pending;
/* mark busy */
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
GST_STATE_NEXT (bin) = GST_STATE_GET_NEXT (old_state, pending);
}
if (old_next != GST_STATE_PLAYING) {
if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
state_changed = TRUE;
}
}
GST_OBJECT_UNLOCK (bin);
if (state_changed) {
_priv_gst_element_state_changed (GST_ELEMENT_CAST (bin), old_state,
old_next, pending);
}
if (amessage) {
/* post our combined ASYNC_DONE when all is ASYNC_DONE. */
GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE to parent");
gst_element_post_message (GST_ELEMENT_CAST (bin), amessage);
}
GST_OBJECT_LOCK (bin);
if (cont) {
/* toplevel, start continue state */
GST_DEBUG_OBJECT (bin, "all async-done, starting state continue");
bin_push_state_continue (bin, cont);
} else {
GST_DEBUG_OBJECT (bin, "state change complete");
GST_STATE_BROADCAST (bin);
}
return;
had_error:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "we had an error");
return;
}
was_busy:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "state change busy");
/* if we were busy with a state change and we are requested to flag a
* pending async done, we do so here */
if (flag_pending)
bin->priv->pending_async_done = TRUE;
return;
}
nothing_pending:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending");
return;
}
}
static void
bin_do_eos (GstBin * bin)
{
guint32 seqnum = GST_SEQNUM_INVALID;
gboolean eos;
GST_OBJECT_LOCK (bin);
/* If all sinks are EOS, we're in PLAYING and no state change is pending
* (or we're doing playing to playing and no one else will trigger posting
* EOS for us) we forward the EOS message to the parent bin or application
*/
eos = GST_STATE (bin) == GST_STATE_PLAYING
&& (GST_STATE_PENDING (bin) == GST_STATE_VOID_PENDING ||
GST_STATE_PENDING (bin) == GST_STATE_PLAYING)
&& bin->priv->posted_playing && is_eos (bin, &seqnum);
GST_OBJECT_UNLOCK (bin);
if (eos
&& g_atomic_int_compare_and_exchange (&bin->priv->posted_eos, FALSE,
TRUE)) {
GstMessage *tmessage;
/* Clear out any further messages, and reset posted_eos so we can
detect any new EOS that happens (eg, after a seek). Since all
sinks have now posted an EOS, there will be no further EOS events
seen unless there is a new logical EOS */
GST_OBJECT_LOCK (bin);
bin_remove_messages (bin, NULL, GST_MESSAGE_EOS);
bin->priv->posted_eos = FALSE;
GST_OBJECT_UNLOCK (bin);
tmessage = gst_message_new_eos (GST_OBJECT_CAST (bin));
if (seqnum != GST_SEQNUM_INVALID)
gst_message_set_seqnum (tmessage, seqnum);
GST_DEBUG_OBJECT (bin,
"all sinks posted EOS, posting seqnum #%" G_GUINT32_FORMAT, seqnum);
gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage);
} else {
GST_LOG_OBJECT (bin, "Not forwarding EOS due to in progress state change, "
" or already posted, or waiting for more EOS");
}
}
static void
bin_do_stream_start (GstBin * bin)
{
guint32 seqnum = GST_SEQNUM_INVALID;
gboolean stream_start;
gboolean have_group_id = FALSE;
guint group_id = 0;
GST_OBJECT_LOCK (bin);
/* If all sinks are STREAM_START we forward the STREAM_START message
* to the parent bin or application
*/
stream_start = is_stream_start (bin, &seqnum, &have_group_id, &group_id);
GST_OBJECT_UNLOCK (bin);
if (stream_start) {
GstMessage *tmessage;
GST_OBJECT_LOCK (bin);
bin_remove_messages (bin, NULL, GST_MESSAGE_STREAM_START);
GST_OBJECT_UNLOCK (bin);
tmessage = gst_message_new_stream_start (GST_OBJECT_CAST (bin));
if (seqnum != GST_SEQNUM_INVALID)
gst_message_set_seqnum (tmessage, seqnum);
if (have_group_id)
gst_message_set_group_id (tmessage, group_id);
GST_DEBUG_OBJECT (bin,
"all sinks posted STREAM_START, posting seqnum #%" G_GUINT32_FORMAT,
seqnum);
gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage);
}
}
/* must be called without the object lock as it posts messages */
static void
bin_do_message_forward (GstBin * bin, GstMessage * message)
{
if (bin->priv->message_forward) {
GstMessage *forwarded;
GST_DEBUG_OBJECT (bin, "pass %s message upward",
GST_MESSAGE_TYPE_NAME (message));
/* we need to convert these messages to element messages so that our parent
* bin can easily ignore them and so that the application can easily
* distinguish between the internally forwarded and the real messages. */
forwarded = gst_message_new_element (GST_OBJECT_CAST (bin),
gst_structure_new ("GstBinForwarded",
"message", GST_TYPE_MESSAGE, message, NULL));
gst_element_post_message (GST_ELEMENT_CAST (bin), forwarded);
}
}
static void
gst_bin_update_context (GstBin * bin, GstContext * context)
{
GST_OBJECT_LOCK (bin);
gst_bin_update_context_unlocked (bin, context);
GST_OBJECT_UNLOCK (bin);
}
static void
gst_bin_update_context_unlocked (GstBin * bin, GstContext * context)
{
const gchar *context_type;
GList *l, **contexts;
contexts = &GST_ELEMENT_CAST (bin)->contexts;
context_type = gst_context_get_context_type (context);
GST_DEBUG_OBJECT (bin, "set context %p %" GST_PTR_FORMAT, context,
gst_context_get_structure (context));
for (l = *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 (strcmp (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) {
*contexts = g_list_prepend (*contexts, gst_context_ref (context));
}
}
/* handle child messages:
*
* This method is called synchronously when a child posts a message on
* the internal bus.
*
* GST_MESSAGE_EOS: This message is only posted by sinks
* in the PLAYING state. If all sinks posted the EOS message, post
* one upwards.
*
* GST_MESSAGE_STATE_DIRTY: Deprecated
*
* GST_MESSAGE_SEGMENT_START: just collect, never forward upwards. If an
* element posts segment_start twice, only the last message is kept.
*
* GST_MESSAGE_SEGMENT_DONE: replace SEGMENT_START message from same poster
* with the segment_done message. If there are no more segment_start
* messages, post segment_done message upwards.
*
* GST_MESSAGE_CLOCK_LOST: This message is posted by an element when it
* can no longer provide a clock. The default bin behaviour is to
* check if the lost clock was the one provided by the bin. If so and
* we are currently in the PLAYING state, we forward the message to
* our parent.
* This message is also generated when we remove a clock provider from
* a bin. If this message is received by the application, it should
* PAUSE the pipeline and set it back to PLAYING to force a new clock
* and a new base_time distribution.
*
* GST_MESSAGE_CLOCK_PROVIDE: This message is generated when an element
* can provide a clock. This mostly happens when we add a new clock
* provider to the bin. The default behaviour of the bin is to mark the
* currently selected clock as dirty, which will perform a clock
* recalculation the next time we are asked to provide a clock.
* This message is never sent to the application but is forwarded to
* the parent.
*
* GST_MESSAGE_ASYNC_START: Create an internal ELEMENT message that stores
* the state of the element and the fact that the element will need a
* new base_time. This message is not forwarded to the application.
*
* GST_MESSAGE_ASYNC_DONE: Find the internal ELEMENT message we kept for the
* element when it posted ASYNC_START. If all elements are done, post a
* ASYNC_DONE message to the parent.
*
* OTHER: post upwards.
*/
static void
gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
{
GstObject *src;
GstMessageType type;
GstMessage *tmessage;
guint32 seqnum;
src = GST_MESSAGE_SRC (message);
type = GST_MESSAGE_TYPE (message);
GST_DEBUG_OBJECT (bin, "[msg %p] handling child %s message of type %s",
message, src ? GST_ELEMENT_NAME (src) : "(NULL)",
GST_MESSAGE_TYPE_NAME (message));
switch (type) {
case GST_MESSAGE_ERROR:
{
GST_OBJECT_LOCK (bin);
/* flag error */
GST_DEBUG_OBJECT (bin, "got ERROR message, unlocking state change");
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_FAILURE;
GST_STATE_BROADCAST (bin);
GST_OBJECT_UNLOCK (bin);
goto forward;
}
case GST_MESSAGE_EOS:
{
/* collect all eos messages from the children */
bin_do_message_forward (bin, message);
GST_OBJECT_LOCK (bin);
/* ref message for future use */
bin_replace_message (bin, message, GST_MESSAGE_EOS);
GST_OBJECT_UNLOCK (bin);
bin_do_eos (bin);
break;
}
case GST_MESSAGE_STREAM_START:
{
/* collect all stream_start messages from the children */
GST_OBJECT_LOCK (bin);
/* ref message for future use */
bin_replace_message (bin, message, GST_MESSAGE_STREAM_START);
GST_OBJECT_UNLOCK (bin);
bin_do_stream_start (bin);
break;
}
case GST_MESSAGE_STATE_DIRTY:
{
GST_WARNING_OBJECT (bin, "received deprecated STATE_DIRTY message");
/* free message */
gst_message_unref (message);
break;
}
case GST_MESSAGE_SEGMENT_START:{
gboolean post = FALSE;
GstFormat format;
gint64 position;
gst_message_parse_segment_start (message, &format, &position);
seqnum = gst_message_get_seqnum (message);
bin_do_message_forward (bin, message);
GST_OBJECT_LOCK (bin);
/* if this is the first segment-start, post to parent but not to the
* application */
if (!find_message (bin, NULL, GST_MESSAGE_SEGMENT_START) &&
(GST_OBJECT_PARENT (bin) != NULL)) {
post = TRUE;
}
/* replace any previous segment_start message from this source
* with the new segment start message */
bin_replace_message (bin, message, GST_MESSAGE_SEGMENT_START);
GST_OBJECT_UNLOCK (bin);
if (post) {
tmessage = gst_message_new_segment_start (GST_OBJECT_CAST (bin),
format, position);
gst_message_set_seqnum (tmessage, seqnum);
/* post segment start with initial format and position. */
GST_DEBUG_OBJECT (bin, "posting SEGMENT_START (%u) bus message: %p",
seqnum, message);
gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage);
}
break;
}
case GST_MESSAGE_SEGMENT_DONE:
{
gboolean post = FALSE;
GstFormat format;
gint64 position;
gst_message_parse_segment_done (message, &format, &position);
seqnum = gst_message_get_seqnum (message);
bin_do_message_forward (bin, message);
GST_OBJECT_LOCK (bin);
bin_replace_message (bin, message, GST_MESSAGE_SEGMENT_START);
/* if there are no more segment_start messages, everybody posted
* a segment_done and we can post one on the bus. */
/* we don't care who still has a pending segment start */
if (!find_message (bin, NULL, GST_MESSAGE_SEGMENT_START)) {
/* nothing found */
post = TRUE;
/* remove all old segment_done messages */
bin_remove_messages (bin, NULL, GST_MESSAGE_SEGMENT_DONE);
}
GST_OBJECT_UNLOCK (bin);
if (post) {
tmessage = gst_message_new_segment_done (GST_OBJECT_CAST (bin),
format, position);
gst_message_set_seqnum (tmessage, seqnum);
/* post segment done with latest format and position. */
GST_DEBUG_OBJECT (bin, "posting SEGMENT_DONE (%u) bus message: %p",
seqnum, message);
gst_element_post_message (GST_ELEMENT_CAST (bin), tmessage);
}
break;
}
case GST_MESSAGE_CLOCK_LOST:
{
GstClock **provided_clock_p;
GstElement **clock_provider_p;
gboolean playing, toplevel, provided, forward;
GstClock *clock;
gst_message_parse_clock_lost (message, &clock);
GST_OBJECT_LOCK (bin);
bin->clock_dirty = TRUE;
/* if we lost the clock that we provided, post to parent but
* only if we are not a top-level bin or PLAYING.
* The reason for this is that applications should be able
* to PAUSE/PLAY if they receive this message without worrying
* about the state of the pipeline. */
provided = (clock == bin->provided_clock);
playing = (GST_STATE (bin) == GST_STATE_PLAYING);
toplevel = GST_OBJECT_PARENT (bin) == NULL;
forward = provided && (playing || !toplevel);
if (provided) {
GST_DEBUG_OBJECT (bin,
"Lost clock %" GST_PTR_FORMAT " provided by %" GST_PTR_FORMAT,
bin->provided_clock, bin->clock_provider);
provided_clock_p = &bin->provided_clock;
clock_provider_p = &bin->clock_provider;
gst_object_replace ((GstObject **) provided_clock_p, NULL);
gst_object_replace ((GstObject **) clock_provider_p, NULL);
}
GST_DEBUG_OBJECT (bin, "provided %d, playing %d, forward %d",
provided, playing, forward);
GST_OBJECT_UNLOCK (bin);
if (forward)
goto forward;
/* free message */
gst_message_unref (message);
break;
}
case GST_MESSAGE_CLOCK_PROVIDE:
{
gboolean forward;
GST_OBJECT_LOCK (bin);
bin->clock_dirty = TRUE;
/* a new clock is available, post to parent but not
* to the application */
forward = GST_OBJECT_PARENT (bin) != NULL;
GST_OBJECT_UNLOCK (bin);
if (forward)
goto forward;
/* free message */
gst_message_unref (message);
break;
}
case GST_MESSAGE_ASYNC_START:
{
GstState target;
GST_DEBUG_OBJECT (bin, "ASYNC_START message %p, %s", message,
src ? GST_OBJECT_NAME (src) : "(NULL)");
bin_do_message_forward (bin, message);
GST_OBJECT_LOCK (bin);
/* we ignore the message if we are going to <= READY */
if ((target = GST_STATE_TARGET (bin)) <= GST_STATE_READY)
goto ignore_start_message;
/* takes ownership of the message */
bin_replace_message (bin, message, GST_MESSAGE_ASYNC_START);
bin_handle_async_start (bin);
GST_OBJECT_UNLOCK (bin);
break;
ignore_start_message:
{
GST_DEBUG_OBJECT (bin, "ignoring message, target %s",
gst_element_state_get_name (target));
GST_OBJECT_UNLOCK (bin);
gst_message_unref (message);
break;
}
}
case GST_MESSAGE_ASYNC_DONE:
{
GstClockTime running_time;
GstState target;
GST_DEBUG_OBJECT (bin, "ASYNC_DONE message %p, %s", message,
src ? GST_OBJECT_NAME (src) : "(NULL)");
gst_message_parse_async_done (message, &running_time);
bin_do_message_forward (bin, message);
GST_OBJECT_LOCK (bin);
/* ignore messages if we are shutting down */
if ((target = GST_STATE_TARGET (bin)) <= GST_STATE_READY)
goto ignore_done_message;
bin_replace_message (bin, message, GST_MESSAGE_ASYNC_START);
/* if there are no more ASYNC_START messages, everybody posted
* a ASYNC_DONE and we can post one on the bus. When checking, we
* don't care who still has a pending ASYNC_START */
if (!find_message (bin, NULL, GST_MESSAGE_ASYNC_START)) {
/* nothing found, remove all old ASYNC_DONE messages */
bin_remove_messages (bin, NULL, GST_MESSAGE_ASYNC_DONE);
GST_DEBUG_OBJECT (bin, "async elements committed");
/* when we get an async done message when a state change was busy, we
* need to set the pending_done flag so that at the end of the state
* change we can see if we need to verify pending async elements, hence
* the TRUE argument here. */
bin_handle_async_done (bin, GST_STATE_CHANGE_SUCCESS, TRUE,
running_time);
} else {
GST_DEBUG_OBJECT (bin, "there are more async elements pending");
}
GST_OBJECT_UNLOCK (bin);
break;
ignore_done_message:
{
GST_DEBUG_OBJECT (bin, "ignoring message, target %s",
gst_element_state_get_name (target));
GST_OBJECT_UNLOCK (bin);
gst_message_unref (message);
break;
}
}
case GST_MESSAGE_STRUCTURE_CHANGE:
{
gboolean busy;
gst_message_parse_structure_change (message, NULL, NULL, &busy);
GST_OBJECT_LOCK (bin);
if (busy) {
/* while the pad is busy, avoid following it when doing state changes.
* Don't update the cookie yet, we will do that after the structure
* change finished and we are ready to inspect the new updated
* structure. */
bin_replace_message (bin, message, GST_MESSAGE_STRUCTURE_CHANGE);
message = NULL;
} else {
/* a pad link/unlink ended, signal the state change iterator that we
* need to resync by updating the structure_cookie. */
bin_remove_messages (bin, GST_MESSAGE_SRC (message),
GST_MESSAGE_STRUCTURE_CHANGE);
if (!GST_BIN_IS_NO_RESYNC (bin))
bin->priv->structure_cookie++;
}
GST_OBJECT_UNLOCK (bin);
if (message)
gst_message_unref (message);
break;
}
case GST_MESSAGE_NEED_CONTEXT:{
const gchar *context_type;
GList *l, *contexts;
gst_message_parse_context_type (message, &context_type);
if (src) {
GST_OBJECT_LOCK (bin);
contexts = GST_ELEMENT_CAST (bin)->contexts;
GST_LOG_OBJECT (bin, "got need-context message type: %s", context_type);
for (l = contexts; l; l = l->next) {
GstContext *tmp = l->data;
const gchar *tmp_type = gst_context_get_context_type (tmp);
if (strcmp (context_type, tmp_type) == 0) {
gst_element_set_context (GST_ELEMENT (src), l->data);
break;
}
}
GST_OBJECT_UNLOCK (bin);
/* Forward if we couldn't answer the message */
if (l == NULL) {
goto forward;
} else {
gst_message_unref (message);
}
} else {
g_warning
("Got need-context message in bin '%s' without source element, dropping",
GST_ELEMENT_NAME (bin));
gst_message_unref (message);
}
break;
}
case GST_MESSAGE_HAVE_CONTEXT:{
GstContext *context;
gst_message_parse_have_context (message, &context);
gst_bin_update_context (bin, context);
gst_context_unref (context);
goto forward;
break;
}
default:
goto forward;
}
return;
forward:
{
/* Send all other messages upward */
GST_DEBUG_OBJECT (bin, "posting message upward");
gst_element_post_message (GST_ELEMENT_CAST (bin), message);
return;
}
}
/* generic struct passed to all query fold methods */
typedef struct
{
GstQuery *query;
gint64 min;
gint64 max;
gboolean live;
} QueryFold;
typedef void (*QueryInitFunction) (GstBin * bin, QueryFold * fold);
typedef void (*QueryDoneFunction) (GstBin * bin, QueryFold * fold);
/* for duration/position we collect all durations/positions and take
* the MAX of all valid results */
static void
bin_query_min_max_init (GstBin * bin, QueryFold * fold)
{
fold->min = 0;
fold->max = -1;
fold->live = FALSE;
}
static gboolean
bin_query_duration_fold (const GValue * vitem, GValue * ret, QueryFold * fold)
{
gboolean res = FALSE;
GstObject *item = g_value_get_object (vitem);
if (GST_IS_PAD (item))
res = gst_pad_query (GST_PAD (item), fold->query);
else
res = gst_element_query (GST_ELEMENT (item), fold->query);
if (res) {
gint64 duration;
g_value_set_boolean (ret, TRUE);
gst_query_parse_duration (fold->query, NULL, &duration);
GST_DEBUG_OBJECT (item, "got duration %" G_GINT64_FORMAT, duration);
if (duration == -1) {
/* duration query succeeded, but duration is unknown */
fold->max = -1;
return FALSE;
}
if (duration > fold->max)
fold->max = duration;
}
return TRUE;
}
static void
bin_query_duration_done (GstBin * bin, QueryFold * fold)
{
GstFormat format;
gst_query_parse_duration (fold->query, &format, NULL);
/* store max in query result */
gst_query_set_duration (fold->query, format, fold->max);
GST_DEBUG_OBJECT (bin, "max duration %" G_GINT64_FORMAT, fold->max);
}
static gboolean
bin_query_position_fold (const GValue * vitem, GValue * ret, QueryFold * fold)
{
gboolean res = FALSE;
GstObject *item = g_value_get_object (vitem);
if (GST_IS_PAD (item))
res = gst_pad_query (GST_PAD (item), fold->query);
else
res = gst_element_query (GST_ELEMENT (item), fold->query);
if (res) {
gint64 position;
g_value_set_boolean (ret, TRUE);
gst_query_parse_position (fold->query, NULL, &position);
GST_DEBUG_OBJECT (item, "got position %" G_GINT64_FORMAT, position);
if (position > fold->max)
fold->max = position;
}
return TRUE;
}
static void
bin_query_position_done (GstBin * bin, QueryFold * fold)
{
GstFormat format;
gst_query_parse_position (fold->query, &format, NULL);
/* store max in query result */
gst_query_set_position (fold->query, format, fold->max);
GST_DEBUG_OBJECT (bin, "max position %" G_GINT64_FORMAT, fold->max);
}
static gboolean
bin_query_latency_fold (const GValue * vitem, GValue * ret, QueryFold * fold)
{
gboolean res = FALSE;
GstObject *item = g_value_get_object (vitem);
if (GST_IS_PAD (item))
res = gst_pad_query (GST_PAD (item), fold->query);
else
res = gst_element_query (GST_ELEMENT (item), fold->query);
if (res) {
GstClockTime min, max;
gboolean live;
gst_query_parse_latency (fold->query, &live, &min, &max);
GST_DEBUG_OBJECT (item,
"got latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
", live %d", GST_TIME_ARGS (min), GST_TIME_ARGS (max), live);
/* for the combined latency we collect the MAX of all min latencies and
* the MIN of all max latencies */
if (live) {
if (min > fold->min)
fold->min = min;
if (fold->max == -1)
fold->max = max;
else if (max < fold->max)
fold->max = max;
if (!fold->live)
fold->live = live;
}
} else {
g_value_set_boolean (ret, FALSE);
GST_DEBUG_OBJECT (item, "failed query");
}
return TRUE;
}
static void
bin_query_latency_done (GstBin * bin, QueryFold * fold)
{
/* store max in query result */
gst_query_set_latency (fold->query, fold->live, fold->min, fold->max);
GST_DEBUG_OBJECT (bin,
"latency min %" GST_TIME_FORMAT ", max %" GST_TIME_FORMAT
", live %d", GST_TIME_ARGS (fold->min), GST_TIME_ARGS (fold->max),
fold->live);
}
/* generic fold, return first valid result */
static gboolean
bin_query_generic_fold (const GValue * vitem, GValue * ret, QueryFold * fold)
{
gboolean res = FALSE;
GstObject *item = g_value_get_object (vitem);
if (GST_IS_PAD (item))
res = gst_pad_query (GST_PAD (item), fold->query);
else
res = gst_element_query (GST_ELEMENT (item), fold->query);
if (res) {
g_value_set_boolean (ret, TRUE);
GST_DEBUG_OBJECT (item, "answered query %p", fold->query);
}
/* and stop as soon as we have a valid result */
return !res;
}
/* Perform a query iteration for the given bin. The query is stored in
* QueryFold and iter should be either a GstPad iterator or a
* GstElement iterator. */
static gboolean
bin_iterate_fold (GstBin * bin, GstIterator * iter, QueryInitFunction fold_init,
QueryDoneFunction fold_done, GstIteratorFoldFunction fold_func,
QueryFold * fold_data, gboolean default_return)
{
gboolean res = default_return;
GValue ret = { 0 };
/* set the result of the query to FALSE initially */
g_value_init (&ret, G_TYPE_BOOLEAN);
g_value_set_boolean (&ret, res);
while (TRUE) {
GstIteratorResult ires;
ires = gst_iterator_fold (iter, fold_func, &ret, fold_data);
switch (ires) {
case GST_ITERATOR_RESYNC:
gst_iterator_resync (iter);
if (fold_init)
fold_init (bin, fold_data);
g_value_set_boolean (&ret, res);
break;
case GST_ITERATOR_OK:
case GST_ITERATOR_DONE:
res = g_value_get_boolean (&ret);
if (fold_done != NULL && res)
fold_done (bin, fold_data);
goto done;
default:
res = FALSE;
goto done;
}
}
done:
return res;
}
static gboolean
gst_bin_query (GstElement * element, GstQuery * query)
{
GstBin *bin = GST_BIN_CAST (element);
GstIterator *iter;
gboolean default_return = FALSE;
gboolean res = FALSE;
gboolean src_pads_query_result = FALSE;
GstIteratorFoldFunction fold_func;
QueryInitFunction fold_init = NULL;
QueryDoneFunction fold_done = NULL;
QueryFold fold_data;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_DURATION:
{
/* iterate and collect durations */
fold_func = (GstIteratorFoldFunction) bin_query_duration_fold;
fold_init = bin_query_min_max_init;
fold_done = bin_query_duration_done;
break;
}
case GST_QUERY_POSITION:
{
fold_func = (GstIteratorFoldFunction) bin_query_position_fold;
fold_init = bin_query_min_max_init;
fold_done = bin_query_position_done;
break;
}
case GST_QUERY_LATENCY:
{
fold_func = (GstIteratorFoldFunction) bin_query_latency_fold;
fold_init = bin_query_min_max_init;
fold_done = bin_query_latency_done;
default_return = TRUE;
break;
}
default:
fold_func = (GstIteratorFoldFunction) bin_query_generic_fold;
break;
}
fold_data.query = query;
iter = gst_bin_iterate_sinks (bin);
GST_DEBUG_OBJECT (bin, "Sending query %p (type %s) to sink children",
query, GST_QUERY_TYPE_NAME (query));
if (fold_init)
fold_init (bin, &fold_data);
res =
bin_iterate_fold (bin, iter, fold_init, fold_done, fold_func, &fold_data,
default_return);
gst_iterator_free (iter);
if (!res) {
/* Query the source pads of the element */
iter = gst_element_iterate_src_pads (element);
src_pads_query_result =
bin_iterate_fold (bin, iter, fold_init, fold_done, fold_func,
&fold_data, default_return);
gst_iterator_free (iter);
if (src_pads_query_result)
res = TRUE;
}
GST_DEBUG_OBJECT (bin, "query %p result %d", query, res);
return res;
}
static void
set_context (const GValue * item, gpointer user_data)
{
GstElement *element = g_value_get_object (item);
gst_element_set_context (element, user_data);
}
static void
gst_bin_set_context (GstElement * element, GstContext * context)
{
GstBin *bin;
GstIterator *children;
g_return_if_fail (GST_IS_BIN (element));
bin = GST_BIN (element);
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
children = gst_bin_iterate_elements (bin);
while (gst_iterator_foreach (children, set_context,
context) == GST_ITERATOR_RESYNC)
gst_iterator_resync (children);
gst_iterator_free (children);
}
static gint
compare_name (const GValue * velement, const gchar * name)
{
gint eq;
GstElement *element = g_value_get_object (velement);
GST_OBJECT_LOCK (element);
eq = strcmp (GST_ELEMENT_NAME (element), name);
GST_OBJECT_UNLOCK (element);
return eq;
}
/**
* gst_bin_get_by_name:
* @bin: a #GstBin
* @name: the element name to search for
*
* Gets the element with the given name from a bin. This
* function recurses into child bins.
*
* Returns: (transfer full) (nullable): the #GstElement with the given
* name
*/
GstElement *
gst_bin_get_by_name (GstBin * bin, const gchar * name)
{
GstIterator *children;
GValue result = { 0, };
GstElement *element;
gboolean found;
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
GST_CAT_INFO (GST_CAT_PARENTAGE, "[%s]: looking up child element %s",
GST_ELEMENT_NAME (bin), name);
children = gst_bin_iterate_recurse (bin);
found = gst_iterator_find_custom (children,
(GCompareFunc) compare_name, &result, (gpointer) name);
gst_iterator_free (children);
if (found) {
element = g_value_dup_object (&result);
g_value_unset (&result);
} else {
element = NULL;
}
return element;
}
/**
* gst_bin_get_by_name_recurse_up:
* @bin: a #GstBin
* @name: the element name to search for
*
* Gets the element with the given name from this bin. If the
* element is not found, a recursion is performed on the parent bin.
*
* Returns: (transfer full) (nullable): the #GstElement with the given
* name
*/
GstElement *
gst_bin_get_by_name_recurse_up (GstBin * bin, const gchar * name)
{
GstElement *result;
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
g_return_val_if_fail (name != NULL, NULL);
result = gst_bin_get_by_name (bin, name);
if (!result) {
GstObject *parent;
parent = gst_object_get_parent (GST_OBJECT_CAST (bin));
if (parent) {
if (GST_IS_BIN (parent)) {
result = gst_bin_get_by_name_recurse_up (GST_BIN_CAST (parent), name);
}
gst_object_unref (parent);
}
}
return result;
}
static gint
compare_interface (const GValue * velement, GValue * interface)
{
GstElement *element = g_value_get_object (velement);
GType interface_type = (GType) g_value_get_pointer (interface);
gint ret;
if (G_TYPE_CHECK_INSTANCE_TYPE (element, interface_type)) {
ret = 0;
} else {
ret = 1;
}
return ret;
}
/**
* gst_bin_get_by_interface:
* @bin: a #GstBin
* @iface: the #GType of an interface
*
* Looks for an element inside the bin that implements the given
* interface. If such an element is found, it returns the element.
* You can cast this element to the given interface afterwards. If you want
* all elements that implement the interface, use
* gst_bin_iterate_all_by_interface(). This function recurses into child bins.
*
* Returns: (transfer full) (nullable): A #GstElement inside the bin
* implementing the interface
*/
GstElement *
gst_bin_get_by_interface (GstBin * bin, GType iface)
{
GstIterator *children;
GValue result = { 0, };
GstElement *element;
gboolean found;
GValue viface = { 0, };
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface), NULL);
g_value_init (&viface, G_TYPE_POINTER);
g_value_set_pointer (&viface, (gpointer) iface);
children = gst_bin_iterate_recurse (bin);
found = gst_iterator_find_custom (children, (GCompareFunc) compare_interface,
&result, &viface);
gst_iterator_free (children);
if (found) {
element = g_value_dup_object (&result);
g_value_unset (&result);
} else {
element = NULL;
}
g_value_unset (&viface);
return element;
}
/**
* gst_bin_iterate_all_by_interface:
* @bin: a #GstBin
* @iface: the #GType of an interface
*
* Looks for all elements inside the bin that implements the given
* interface. You can safely cast all returned elements to the given interface.
* The function recurses inside child bins. The iterator will yield a series
* of #GstElement.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
* for all elements in the bin implementing the given interface
*/
GstIterator *
gst_bin_iterate_all_by_interface (GstBin * bin, GType iface)
{
GstIterator *children;
GstIterator *result;
GValue viface = { 0, };
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
g_return_val_if_fail (G_TYPE_IS_INTERFACE (iface), NULL);
g_value_init (&viface, G_TYPE_POINTER);
g_value_set_pointer (&viface, (gpointer) iface);
children = gst_bin_iterate_recurse (bin);
result = gst_iterator_filter (children, (GCompareFunc) compare_interface,
&viface);
g_value_unset (&viface);
return result;
}
static gint
compare_factory_names (const GValue * velement, GValue * factory_name_val)
{
GstElement *element = g_value_get_object (velement);
GstElementFactory *factory = gst_element_get_factory (element);
const gchar *factory_name = g_value_get_string (factory_name_val);
if (factory == NULL)
return -1;
return g_strcmp0 (GST_OBJECT_NAME (factory), factory_name);
}
/**
* gst_bin_iterate_all_by_element_factory_name:
* @bin: a #GstBin
* @factory_name: (not nullable): the name of the #GstElementFactory
*
* Looks for all elements inside the bin with the given element factory name.
* The function recurses inside child bins. The iterator will yield a series of
* #GstElement.
*
* Returns: (transfer full) (nullable): a #GstIterator of #GstElement
* for all elements in the bin with the given element factory name
*
* Since: 1.18
*/
GstIterator *
gst_bin_iterate_all_by_element_factory_name (GstBin * bin,
const gchar * factory_name)
{
GstIterator *children;
GstIterator *result;
GValue factory_name_val = G_VALUE_INIT;
g_return_val_if_fail (GST_IS_BIN (bin), NULL);
g_return_val_if_fail (factory_name && *factory_name, NULL);
g_value_init (&factory_name_val, G_TYPE_STRING);
g_value_set_string (&factory_name_val, factory_name);
children = gst_bin_iterate_recurse (bin);
result = gst_iterator_filter (children, (GCompareFunc) compare_factory_names,
&factory_name_val);
g_value_unset (&factory_name_val);
return result;
}