gst/gstbin.*: Immediatly commit the toplevel bin state when receiving an async-done message. This enables us to avoid...

Original commit message from CVS:
* gst/gstbin.c: (gst_bin_init), (gst_bin_add_func),
(gst_bin_remove_func), (gst_bin_get_state_func),
(gst_bin_element_set_state), (gst_bin_continue_func),
(bin_push_state_continue), (bin_handle_async_start),
(bin_handle_async_done), (gst_bin_handle_message_func):
* gst/gstbin.h:
Immediatly commit the toplevel bin state when receiving an async-done
message. This enables us to avoid spawning a thread to commit the state
in some common cases and it also avoids some races.
Avoid spawning a state thread when adding/removing async elements to a
toplevel bin. Instead we immediatly update the bin state.
Get rid of iterating all the children when getting the state in the bin
because it is now always up-to-date.
Fix bug where locked elements would always return _SUCCESS even it they
returned NO_PREROLL before being locked.
Fix the order of the state_change, async-start/done messages that was
sometimes incorrect.
Mark the state_dirty field as deprecated, we don't need it anymore as we
are always up-to-date.
* gst/gstelement.c: (gst_element_get_state_func),
(gst_element_continue_state):
Small debug inprovements.
Return the previous element state return when nothing is pending instead
of blindly returning SUCCESS.
* tests/check/generic/sinks.c: (GST_START_TEST), (pad_blocked_cb),
(gst_sinks_suite):
Add a whole bunch of new testcases.
This commit is contained in:
Wim Taymans 2007-06-18 15:12:28 +00:00
parent 43efcf4222
commit 0b3153873b
5 changed files with 692 additions and 385 deletions

View file

@ -1,3 +1,35 @@
2007-06-18 Wim Taymans <wim@fluendo.com>
* gst/gstbin.c: (gst_bin_init), (gst_bin_add_func),
(gst_bin_remove_func), (gst_bin_get_state_func),
(gst_bin_element_set_state), (gst_bin_continue_func),
(bin_push_state_continue), (bin_handle_async_start),
(bin_handle_async_done), (gst_bin_handle_message_func):
* gst/gstbin.h:
Immediatly commit the toplevel bin state when receiving an async-done
message. This enables us to avoid spawning a thread to commit the state
in some common cases and it also avoids some races.
Avoid spawning a state thread when adding/removing async elements to a
toplevel bin. Instead we immediatly update the bin state.
Get rid of iterating all the children when getting the state in the bin
because it is now always up-to-date.
Fix bug where locked elements would always return _SUCCESS even it they
returned NO_PREROLL before being locked.
Fix the order of the state_change, async-start/done messages that was
sometimes incorrect.
Mark the state_dirty field as deprecated, we don't need it anymore as we
are always up-to-date.
* gst/gstelement.c: (gst_element_get_state_func),
(gst_element_continue_state):
Small debug inprovements.
Return the previous element state return when nothing is pending instead
of blindly returning SUCCESS.
* tests/check/generic/sinks.c: (GST_START_TEST), (pad_blocked_cb),
(gst_sinks_suite):
Add a whole bunch of new testcases.
2007-06-17 Thomas Vander Stichele <thomas at apestaart dot org>
* po/uk.po:

View file

@ -196,6 +196,13 @@ struct _GstBinPrivate
gboolean asynchandling;
};
typedef struct
{
GstBin *bin;
guint32 cookie;
GstState pending;
} BinContinueData;
static void gst_bin_dispose (GObject * object);
static void gst_bin_set_property (GObject * object, guint prop_id,
@ -208,8 +215,9 @@ static GstStateChangeReturn gst_bin_change_state_func (GstElement * element,
static GstStateChangeReturn gst_bin_get_state_func (GstElement * element,
GstState * state, GstState * pending, GstClockTime timeout);
static void bin_handle_async_done (GstBin * bin, GstMessage ** smessage,
GstMessage ** amessage);
static void bin_push_state_continue (GstBin * bin);
GstMessage ** amessage, BinContinueData ** cont, GstStateChangeReturn ret);
static void bin_handle_async_start (GstBin * bin, GstMessage ** message);
static void bin_push_state_continue (BinContinueData * data);
static gboolean gst_bin_add_func (GstBin * bin, GstElement * element);
static gboolean gst_bin_remove_func (GstBin * bin, GstElement * element);
@ -233,7 +241,7 @@ static void gst_bin_restore_thyself (GstObject * object, xmlNodePtr self);
static void bin_remove_messages (GstBin * bin, GstObject * src,
GstMessageType types);
static void gst_bin_continue_func (GstBin * child, gpointer data);
static void gst_bin_continue_func (BinContinueData * data);
static gint bin_element_is_sink (GstElement * child, GstBin * bin);
static gint bin_element_is_src (GstElement * child, GstBin * bin);
@ -460,7 +468,6 @@ gst_bin_init (GstBin * bin)
bin->children_cookie = 0;
bin->messages = NULL;
bin->provided_clock = NULL;
bin->state_dirty = FALSE;
bin->clock_dirty = FALSE;
/* Set up a bus for listening to child elements */
@ -847,7 +854,12 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
gchar *elem_name;
GstIterator *it;
gboolean is_sink;
GstMessage *clock_message = NULL;
GstMessage *clock_message = NULL, *async_message = NULL, *state_message =
NULL;
BinContinueData *cont = NULL;
GstStateChangeReturn ret;
GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element));
/* we obviously can't add ourself to ourself */
if (G_UNLIKELY (GST_ELEMENT_CAST (element) == GST_ELEMENT_CAST (bin)))
@ -890,6 +902,32 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
bin->numchildren++;
bin->children_cookie++;
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:
bin_handle_async_start (bin, &state_message);
break;
case GST_STATE_CHANGE_NO_PREROLL:
bin_handle_async_done (bin, &state_message, &async_message, &cont, ret);
break;
case GST_STATE_CHANGE_FAILURE:
break;
default:
break;
}
no_state_recalc:
/* distribute the bus */
gst_element_set_bus (element, bin->child_bus);
@ -899,13 +937,19 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
* 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));
GST_DEBUG_OBJECT (bin, "marking state dirty");
bin->state_dirty = TRUE;
GST_OBJECT_UNLOCK (bin);
if (clock_message) {
if (state_message)
gst_element_post_message (GST_ELEMENT_CAST (bin), state_message);
if (async_message)
gst_element_post_message (GST_ELEMENT_CAST (bin), async_message);
if (clock_message)
gst_element_post_message (GST_ELEMENT_CAST (bin), clock_message);
}
if (cont)
bin_push_state_continue (cont);
/* unlink all linked pads */
it = gst_element_iterate_pads (element);
@ -1001,11 +1045,13 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
{
gchar *elem_name;
GstIterator *it;
gboolean is_sink;
gboolean is_sink, othersink, found;
GstMessage *clock_message = NULL, *async_message = NULL, *state_message =
NULL;
BinContinueData *cont = NULL;
GList *walk, *next;
gboolean other_async = FALSE, this_async = FALSE, toplevel = FALSE;
gboolean other_async, this_async, have_no_preroll;
GstStateChangeReturn ret;
GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element));
@ -1027,26 +1073,48 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
gst_iterator_free (it);
GST_OBJECT_LOCK (bin);
/* the element must be in the bin's list of children */
if (G_UNLIKELY (g_list_find (bin->children, element) == NULL))
found = FALSE;
othersink = 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;
GST_OBJECT_LOCK (child);
child_sink = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_IS_SINK);
/* when we remove a sink, check if there are other sinks. */
if (is_sink && !othersink && child_sink)
othersink = 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;
/* now remove the element from the list of elements */
bin->children = g_list_remove (bin->children, element);
/* 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++;
/* check if we removed a sink */
if (is_sink) {
GList *other_sink;
/* check if we removed the last sink */
other_sink = g_list_find_custom (bin->children,
bin, (GCompareFunc) bin_element_is_sink);
if (!other_sink) {
/* yups, we're not a sink anymore */
GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_IS_SINK);
}
if (is_sink && !othersink) {
/* we're not a sink anymore */
GST_DEBUG_OBJECT (bin, "we removed the last sink");
GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_IS_SINK);
}
/* if the clock provider for this element is removed, we lost
@ -1062,6 +1130,8 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
/* 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;
for (walk = bin->messages; walk; walk = next) {
GstMessage *message = (GstMessage *) walk->data;
GstElement *src = GST_ELEMENT_CAST (GST_MESSAGE_SRC (message));
@ -1085,18 +1155,50 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
gst_message_unref (message);
}
}
/* all other elements were not async and we removed the async one,
* post a ASYNC_DONE message because we are not async anymore now. */
if (!other_async && this_async) {
GST_DEBUG_OBJECT (bin, "we removed the last async element");
toplevel = ((GST_OBJECT_PARENT (bin) == NULL) || bin->priv->asynchandling);
if (!toplevel) {
bin_handle_async_done (bin, &state_message, &async_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) {
GstStateChangeReturn ret;
/* 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, &state_message, &async_message, &cont, ret);
} 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;
}
GST_DEBUG_OBJECT (bin, "marking state dirty");
bin->state_dirty = TRUE;
no_state_recalc:
GST_OBJECT_UNLOCK (bin);
if (clock_message)
@ -1108,8 +1210,8 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
if (async_message)
gst_element_post_message (GST_ELEMENT_CAST (bin), async_message);
if (toplevel)
bin_push_state_continue (bin);
if (cont)
bin_push_state_continue (cont);
GST_CAT_INFO_OBJECT (GST_CAT_PARENTAGE, bin, "removed child \"%s\"",
elem_name);
@ -1423,171 +1525,6 @@ gst_bin_iterate_sources (GstBin * bin)
return result;
}
static void
gst_bin_recalc_state (GstBin * bin, gboolean force)
{
GstStateChangeReturn ret;
GList *children;
guint32 children_cookie;
guint32 state_cookie;
gboolean have_no_preroll;
gboolean have_async;
ret = GST_STATE_CHANGE_SUCCESS;
/* lock bin, no element can be added or removed while we have this lock */
GST_OBJECT_LOCK (bin);
/* forced recalc, make state dirty again */
if (force)
bin->state_dirty = TRUE;
/* no point in scanning if nothing changed and it's no forced recalc */
if (!bin->state_dirty)
goto not_dirty;
/* no point in having two scans run concurrently */
if (bin->polling)
goto was_polling;
bin->polling = TRUE;
state_cookie = GST_ELEMENT_CAST (bin)->state_cookie;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "recalc state");
restart:
/* when we leave this function, the state must not be dirty, whenever
* we are scanning and the state becomes dirty again, we restart. */
bin->state_dirty = FALSE;
have_no_preroll = FALSE;
have_async = FALSE;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "checking element states");
/* scan all element states with a zero timeout so we don't block on
* anything */
children = bin->children;
children_cookie = bin->children_cookie;
while (children) {
GstElement *child = GST_ELEMENT_CAST (children->data);
gst_object_ref (child);
/* now we release the lock to enter a non blocking wait. We
* release the lock anyway since we can. */
GST_OBJECT_UNLOCK (bin);
ret = gst_element_get_state (child, NULL, NULL, 0);
gst_object_unref (child);
/* now grab the lock to iterate to the next child */
GST_OBJECT_LOCK (bin);
if (G_UNLIKELY (children_cookie != bin->children_cookie)) {
/* child added/removed during state change, restart. We need
* to restart with the quick check as a no-preroll element could
* have been added here and we don't want to block on sinks then.*/
GST_DEBUG_OBJECT (bin, "children added or removed, restarting recalc");
goto restart;
}
if (state_cookie != GST_ELEMENT_CAST (bin)->state_cookie) {
GST_DEBUG_OBJECT (bin, "concurrent state change");
goto concurrent_state;
}
if (bin->state_dirty) {
GST_DEBUG_OBJECT (bin, "state dirty again, restarting recalc");
goto restart;
}
switch (ret) {
case GST_STATE_CHANGE_FAILURE:
/* report FAILURE immediatly */
goto done;
case GST_STATE_CHANGE_NO_PREROLL:
/* we have to continue scanning as there might be
* ERRORS too */
have_no_preroll = TRUE;
break;
case GST_STATE_CHANGE_ASYNC:
/* we have to continue scanning as there might be
* ERRORS too */
have_async = TRUE;
break;
default:
break;
}
children = g_list_next (children);
}
/* if we get here, we have no FAILURES */
/* if we have NO_PREROLL, return that */
if (have_no_preroll) {
ret = GST_STATE_CHANGE_NO_PREROLL;
}
/* else return ASYNC if async elements where found. */
else if (have_async) {
ret = GST_STATE_CHANGE_ASYNC;
}
done:
bin->polling = FALSE;
GST_STATE_RETURN (bin) = ret;
GST_OBJECT_UNLOCK (bin);
/* now we can take the state lock, it is possible that new elements
* are added now and we still report the old state. No problem though as
* the return is still consistent, the effect is as if the element was
* added after this function completed. */
switch (ret) {
case GST_STATE_CHANGE_SUCCESS:
case GST_STATE_CHANGE_NO_PREROLL:
ret = gst_element_continue_state (GST_ELEMENT_CAST (bin), ret);
break;
case GST_STATE_CHANGE_ASYNC:
gst_element_lost_state (GST_ELEMENT_CAST (bin));
break;
case GST_STATE_CHANGE_FAILURE:
gst_element_abort_state (GST_ELEMENT_CAST (bin));
break;
default:
goto unknown_state;
}
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "bin RETURN is now %s",
gst_element_state_change_return_get_name (ret));
return;
not_dirty:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "not dirty");
GST_OBJECT_UNLOCK (bin);
return;
}
was_polling:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "was polling");
GST_OBJECT_UNLOCK (bin);
return;
}
concurrent_state:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "concurrent_state");
bin->polling = FALSE;
GST_OBJECT_UNLOCK (bin);
return;
}
unknown_state:
{
/* somebody added a GST_STATE_ and forgot to do stuff here ! */
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"unknown return value %d from a state change function", ret);
g_critical ("unknown return value %d from a state change function", ret);
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_FAILURE;
GST_STATE_UNLOCK (bin);
return;
}
}
/*
* MT safe
*/
@ -1600,11 +1537,6 @@ gst_bin_get_state_func (GstElement * element, GstState * state,
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "getting state");
/* do a non forced recalculation of the state */
GST_STATE_LOCK (bin);
gst_bin_recalc_state (bin, FALSE);
GST_STATE_UNLOCK (bin);
ret = parent_class->get_state (element, state, pending, timeout);
return ret;
@ -1905,6 +1837,8 @@ gst_bin_element_set_state (GstBin * bin, GstElement * element,
/* peel off the locked flag */
GST_OBJECT_LOCK (element);
locked = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_LOCKED_STATE);
/* get previous state return */
ret = GST_STATE_RETURN (element);
GST_OBJECT_UNLOCK (element);
/* skip locked elements */
@ -1957,8 +1891,9 @@ no_latency:
locked:
{
GST_DEBUG_OBJECT (element,
"element is locked, pretending state change succeeded");
return GST_STATE_CHANGE_SUCCESS;
"element is locked, return previous return %s",
gst_element_state_change_return_get_name (ret));
return ret;
}
was_busy:
{
@ -2264,12 +2199,14 @@ gst_bin_send_event (GstElement * element, GstEvent * event)
}
static void
gst_bin_continue_func (GstBin * bin, gpointer data)
gst_bin_continue_func (BinContinueData * data)
{
GstState current, next, pending, target, old_state, old_next;
GstStateChangeReturn old_ret, ret;
GstStateChange transition;
gboolean post;
GstBin *bin;
GstState current, next, pending, transition;
GstStateChangeReturn ret;
bin = data->bin;
pending = data->pending;
GST_DEBUG_OBJECT (bin, "waiting for state lock");
GST_STATE_LOCK (bin);
@ -2277,43 +2214,7 @@ gst_bin_continue_func (GstBin * bin, gpointer data)
GST_DEBUG_OBJECT (bin, "doing state continue");
GST_OBJECT_LOCK (bin);
old_ret = GST_STATE_RETURN (bin);
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_SUCCESS;
target = GST_STATE_TARGET (bin);
pending = GST_STATE_PENDING (bin);
/* check if there is something to commit */
if (pending == GST_STATE_VOID_PENDING)
goto nothing_pending;
old_state = GST_STATE (bin);
/* this is the state we should go to next */
old_next = GST_STATE_NEXT (bin);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "target %s",
gst_element_state_get_name (target));
if (old_next == GST_STATE_PLAYING) {
post = FALSE;
} else {
post = TRUE;
}
/* we're going to PLAYING, always do the PAUSED->PLAYING state change */
if (target == GST_STATE_PLAYING) {
next = GST_STATE_PAUSED;
GST_STATE_PENDING (bin) = pending = target;
} else {
next = old_next;
}
/* update current state */
current = GST_STATE (bin) = next;
/* see if we reached the final state */
if (pending == current)
goto complete;
current = GST_STATE (bin);
next = GST_STATE_GET_NEXT (current, pending);
transition = (GstStateChange) GST_STATE_TRANSITION (current, next);
@ -2322,22 +2223,6 @@ gst_bin_continue_func (GstBin * bin, gpointer data)
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
GST_OBJECT_UNLOCK (bin);
if (post) {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"committing state from %s to %s, pending %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_next),
gst_element_state_get_name (pending));
gst_element_post_message (GST_ELEMENT_CAST (bin),
gst_message_new_state_changed (GST_OBJECT_CAST (bin),
old_state, old_next, pending));
}
GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE");
gst_element_post_message (GST_ELEMENT_CAST (bin),
gst_message_new_async_done (GST_OBJECT_CAST (bin)));
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"continue state change %s to %s, final %s",
gst_element_state_get_name (current),
@ -2345,58 +2230,10 @@ gst_bin_continue_func (GstBin * bin, gpointer data)
ret = gst_element_change_state (GST_ELEMENT_CAST (bin), transition);
done:
GST_STATE_UNLOCK (bin);
GST_DEBUG_OBJECT (bin, "state continue done");
gst_object_unref (bin);
return;
nothing_pending:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending");
GST_OBJECT_UNLOCK (bin);
GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE");
gst_element_post_message (GST_ELEMENT_CAST (bin),
gst_message_new_async_done (GST_OBJECT_CAST (bin)));
goto done;
}
complete:
{
GST_STATE_PENDING (bin) = GST_STATE_VOID_PENDING;
GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "completed state change to %s",
gst_element_state_get_name (pending));
GST_OBJECT_UNLOCK (bin);
/* don't post silly messages with the same state. This can happen
* when an element state is changed to what it already was. For bins
* this can be the result of a lost state, which we check with the
* previous return value.
* We do signal the cond though as a _get_state() might be blocking
* on it. */
if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"changed %s to %s, VOID pending",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_next));
gst_element_post_message (GST_ELEMENT_CAST (bin),
gst_message_new_state_changed (GST_OBJECT_CAST (bin),
old_state, old_next, GST_STATE_VOID_PENDING));
}
GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE");
gst_element_post_message (GST_ELEMENT_CAST (bin),
gst_message_new_async_done (GST_OBJECT_CAST (bin)));
GST_STATE_BROADCAST (bin);
goto done;
}
g_free (data);
}
static GstBusSyncReply
@ -2414,16 +2251,18 @@ bin_bus_handler (GstBus * bus, GstMessage * message, GstBin * bin)
}
static void
bin_push_state_continue (GstBin * bin)
bin_push_state_continue (BinContinueData * data)
{
GstBinClass *klass;
GstBin *bin;
/* mark the bin dirty */
GST_OBJECT_LOCK (bin);
/* ref was taken */
bin = data->bin;
klass = GST_BIN_GET_CLASS (bin);
GST_OBJECT_LOCK (bin);
GST_DEBUG_OBJECT (bin, "pushing continue on thread pool");
gst_object_ref (bin);
g_thread_pool_push (klass->pool, bin, NULL);
g_thread_pool_push (klass->pool, data, NULL);
GST_OBJECT_UNLOCK (bin);
}
@ -2441,6 +2280,10 @@ bin_handle_async_start (GstBin * bin, GstMessage ** message)
if (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
@ -2474,88 +2317,111 @@ was_busy:
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "state change busy");
return;
}
was_no_preroll:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "ignoring, we are NO_PREROLL");
return;
}
}
/* 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. */
static void
bin_handle_async_done (GstBin * bin, GstMessage ** smessage,
GstMessage ** amessage)
GstMessage ** amessage, BinContinueData ** cont, GstStateChangeReturn ret)
{
GstState pending;
GstState current, pending, target;
GstStateChangeReturn old_ret;
GstState old_state, old_next;
GstState current, next;
gboolean toplevel;
old_ret = GST_STATE_RETURN (bin);
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_SUCCESS;
if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_FAILURE)
goto had_error;
pending = GST_STATE_PENDING (bin);
/* 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));
old_state = GST_STATE (bin);
/* this is the state we should go to next */
old_next = GST_STATE_NEXT (bin);
/* update current state */
current = GST_STATE (bin) = old_next;
/* see if we need to continue the state change on our own. This happens when
* we were asked to do so or when we are the toplevel 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 {
current = old_state;
}
/* get our toplevel state */
toplevel = ((GST_OBJECT_PARENT (bin) == NULL) || bin->priv->asynchandling);
/* see if we reached the final state. If we are not a toplevel bin we also
* must stop at this state change, the parent will set us to the required
* state eventually. */
if (pending == current || !toplevel)
goto complete;
/* 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");
next = GST_STATE_GET_NEXT (current, pending);
/* mark VOID pending */
pending = GST_STATE_VOID_PENDING;
GST_STATE_PENDING (bin) = pending;
GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
GST_STATE_NEXT (bin) = next;
/* mark busy */
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
GST_STATE_BROADCAST (bin);
} else {
BinContinueData *data;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"committing state from %s to %s, pending %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_next),
gst_element_state_get_name (pending));
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
"continue state change, pending %s",
gst_element_state_get_name (pending));
*smessage = gst_message_new_state_changed (GST_OBJECT_CAST (bin),
old_state, old_next, pending);
data = g_new0 (BinContinueData, 1);
/* ref to the bin */
data->bin = gst_object_ref (bin);
/* cookie to detect concurrent state change */
data->cookie = GST_ELEMENT_CAST (bin)->state_cookie;
/* pending target state */
data->pending = pending;
/* mark busy */
GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
*cont = data;
}
if (old_next != GST_STATE_PLAYING) {
if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
*smessage = gst_message_new_state_changed (GST_OBJECT_CAST (bin),
old_state, old_next, pending);
}
}
return;
had_error:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "we had an error");
return;
}
nothing_pending:
{
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending");
return;
}
complete:
{
GST_STATE_PENDING (bin) = GST_STATE_VOID_PENDING;
GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "completed state change");
/* don't post silly messages with the same state. This can happen
* when an element state is changed to what it already was. For bins
* this can be the result of a lost state, which we check with the
* previous return value.
* We do signal the cond though as a _get_state() might be blocking
* on it. */
if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
*smessage = gst_message_new_state_changed (GST_OBJECT_CAST (bin),
old_state, old_next, GST_STATE_VOID_PENDING);
}
GST_STATE_BROADCAST (bin);
return;
}
}
@ -2772,6 +2638,7 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
if (target <= 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, &smessage);
@ -2786,6 +2653,7 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
message =
gst_message_new_async_start (GST_OBJECT_CAST (bin), new_base_time);
} else {
/* toplevel bin, we don't post async start */
message = NULL;
}
GST_OBJECT_UNLOCK (bin);
@ -2810,9 +2678,9 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
}
case GST_MESSAGE_ASYNC_DONE:
{
gboolean toplevel = FALSE;
GstState target;
GstMessage *smessage = NULL, *amessage = NULL;
BinContinueData *cont = NULL;
GST_DEBUG_OBJECT (bin, "ASYNC_DONE message %p, %s", message,
GST_OBJECT_NAME (src));
@ -2831,10 +2699,8 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
/* nothing found, remove all old ASYNC_DONE messages */
bin_remove_messages (bin, NULL, GST_MESSAGE_ASYNC_DONE);
toplevel = ((GST_OBJECT_PARENT (bin) == NULL)
|| bin->priv->asynchandling);
if (!toplevel)
bin_handle_async_done (bin, &smessage, &amessage);
bin_handle_async_done (bin, &smessage, &amessage, &cont,
GST_STATE_CHANGE_SUCCESS);
}
GST_OBJECT_UNLOCK (bin);
@ -2848,10 +2714,10 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
gst_element_post_message (GST_ELEMENT_CAST (bin), amessage);
}
if (toplevel) {
if (cont) {
/* toplevel, start continue state */
GST_DEBUG_OBJECT (bin, "all async-done, starting state continue");
bin_push_state_continue (bin);
bin_push_state_continue (cont);
}
break;

View file

@ -86,7 +86,7 @@ typedef struct _GstBinPrivate GstBinPrivate;
* @child_bus: internal bus for handling child messages
* @messages: queued and cached messages
* @polling: the bin is currently calculating its state
* @state_dirty: the bin needs to recalculate its state
* @state_dirty: the bin needs to recalculate its state (deprecated)
* @clock_dirty: the bin needs to select a new clock
* @provided_clock: the last clock selected
* @clock_provider: the element that provided @provided_clock

View file

@ -1858,11 +1858,11 @@ gst_element_get_state_func (GstElement * element,
ret = GST_STATE_CHANGE_FAILURE;
}
}
}
/* if nothing is pending anymore we can return SUCCESS */
if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) {
GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "nothing pending");
ret = GST_STATE_CHANGE_SUCCESS;
/* if nothing is pending anymore we can return SUCCESS */
if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) {
GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "nothing pending");
ret = GST_STATE_CHANGE_SUCCESS;
}
}
done:
@ -1887,7 +1887,7 @@ interrupted:
if (pending)
*pending = GST_STATE_VOID_PENDING;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "get_state() interruped");
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "interruped");
GST_OBJECT_UNLOCK (element);
@ -2100,8 +2100,13 @@ complete:
* We do signal the cond though as a _get_state() might be blocking
* on it. */
if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
message = gst_message_new_state_changed (GST_OBJECT_CAST (element),
old_state, old_next, GST_STATE_VOID_PENDING);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"posting state-changed %s to %s",
gst_element_state_get_name (old_state),
gst_element_state_get_name (old_next));
message =
gst_message_new_state_changed (GST_OBJECT_CAST (element), old_state,
old_next, GST_STATE_VOID_PENDING);
gst_element_post_message (element, message);
}
@ -2891,7 +2896,8 @@ gst_element_set_bus (GstElement * element, GstBus * bus)
* gst_element_get_bus:
* @element: a #GstElement to get the bus of.
*
* Returns the bus of the element.
* Returns the bus of the element. Note that only a #GstPipeline will provide a
* bus for the application.
*
* Returns: the element's #GstBus. unref after usage.
*

View file

@ -450,6 +450,402 @@ GST_START_TEST (test_livesrc3_sink)
GST_END_TEST;
GST_START_TEST (test_locked_sink)
{
GstElement *sink, *src, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
sink = gst_element_factory_make ("fakesink", "sink");
gst_bin_add (GST_BIN (pipeline), src);
gst_bin_add (GST_BIN (pipeline), sink);
/* we don't link the elements */
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not no_preroll");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
/* the sink is now async going from ready to paused */
ret = gst_element_get_state (sink, &current, &pending, 0);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not async");
fail_unless (current == GST_STATE_READY, "not ready");
fail_unless (pending == GST_STATE_PAUSED, "not paused");
/* lock the sink */
gst_element_set_locked_state (sink, TRUE);
/* move to PlAYING, the sink should remain ASYNC. The pipeline
* returns ASYNC */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* back to PAUSED, we should get NO_PREROLL again */
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
/* unlock the sink */
gst_element_set_locked_state (sink, FALSE);
/* and now everything back down */
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no success state return");
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_unlinked_live)
{
GstElement *sink, *src, *lsrc, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
GstPad *srcpad, *sinkpad;
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
lsrc = gst_element_factory_make ("fakesrc", "lsrc");
g_object_set (G_OBJECT (lsrc), "is-live", TRUE, NULL);
sink = gst_element_factory_make ("fakesink", "sink");
gst_bin_add (GST_BIN (pipeline), src);
gst_bin_add (GST_BIN (pipeline), lsrc);
gst_bin_add (GST_BIN (pipeline), sink);
/* link non live source to sink */
srcpad = gst_element_get_pad (src, "src");
sinkpad = gst_element_get_pad (sink, "sink");
gst_pad_link (srcpad, sinkpad);
gst_object_unref (srcpad);
gst_object_unref (sinkpad);
/* we don't link the srcpad of the live source, it will not contribute to the
* NO_PREROLL. */
/* set state to PAUSED, this should return NO_PREROLL because there is a live
* source. since the only sink in this pipeline is linked to a non-live
* source, it will preroll eventually. */
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
/* wait till the sink is prerolled */
ret = gst_element_get_state (sink, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not success");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_VOID_PENDING, "have playing");
/* the pipeline should still return NO_PREROLL */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not no_preroll");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_VOID_PENDING, "have playing");
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_delayed_async)
{
GstElement *sink, *src, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
GstPad *srcpad, *sinkpad;
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
sink = gst_element_factory_make ("fakesink", "sink");
/* add source, don't add sink yet */
gst_bin_add (GST_BIN (pipeline), src);
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
/* add sink now and set to PAUSED */
gst_bin_add (GST_BIN (pipeline), sink);
/* This will make the bin notice an ASYNC element. */
ret = gst_element_set_state (sink, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* we should still be NO_PREROLL now although there is an async element in the
* pipeline. */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
/* link live source to sink */
srcpad = gst_element_get_pad (src, "src");
sinkpad = gst_element_get_pad (sink, "sink");
gst_pad_link (srcpad, sinkpad);
gst_object_unref (srcpad);
gst_object_unref (sinkpad);
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* we should get SUCCESS now */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not NO_PREROLL");
fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_added_async)
{
GstElement *sink, *src, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
GstPad *srcpad, *sinkpad;
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
sink = gst_element_factory_make ("fakesink", "sink");
/* add source, don't add sink yet */
gst_bin_add (GST_BIN (pipeline), src);
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
/* set sink to PAUSED without adding it to the pipeline */
ret = gst_element_set_state (sink, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* add sink now, pipeline should notice the async element */
gst_bin_add (GST_BIN (pipeline), sink);
/* we should still be NO_PREROLL now although there is an async element in the
* pipeline. */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
/* link live source to sink */
srcpad = gst_element_get_pad (src, "src");
sinkpad = gst_element_get_pad (sink, "sink");
gst_pad_link (srcpad, sinkpad);
gst_object_unref (srcpad);
gst_object_unref (sinkpad);
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* we should get SUCCESS now */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not NO_PREROLL");
fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_added_async2)
{
GstElement *sink, *src, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
sink = gst_element_factory_make ("fakesink", "sink");
/* add source, don't add sink yet */
gst_bin_add (GST_BIN (pipeline), src);
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
/* set sink to PAUSED without adding it to the pipeline */
ret = gst_element_set_state (sink, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* add sink now, pipeline should notice the async element */
gst_bin_add (GST_BIN (pipeline), sink);
/* we should be ASYNC now because there is an async element in the
* pipeline. */
ret = gst_element_get_state (pipeline, &current, &pending, 0);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not ASYNC");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_PAUSED, "not paused");
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
gst_object_unref (pipeline);
}
GST_END_TEST;
GST_START_TEST (test_add_live)
{
GstElement *sink, *src, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
sink = gst_element_factory_make ("fakesink", "sink");
/* add sink, don't add sourc3 yet */
gst_bin_add (GST_BIN (pipeline), sink);
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
/* set source to PAUSED without adding it to the pipeline */
ret = gst_element_set_state (src, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
/* add source now, pipeline should notice the NO_PREROLL element */
gst_bin_add (GST_BIN (pipeline), src);
/* we should be NO_PREROLL now because there is a NO_PREROLL element in the
* pipeline. */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
fail_unless (current == GST_STATE_PAUSED, "not paused");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
gst_object_unref (pipeline);
}
GST_END_TEST;
static GMutex *blocked_lock;
static GCond *blocked_cond;
static void
pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data)
{
g_mutex_lock (blocked_lock);
GST_DEBUG ("srcpad blocked: %d, sending signal", blocked);
g_cond_signal (blocked_cond);
g_mutex_unlock (blocked_lock);
}
GST_START_TEST (test_add_live2)
{
GstElement *sink, *src, *pipeline;
GstStateChangeReturn ret;
GstState current, pending;
GstPad *srcpad, *sinkpad;
blocked_lock = g_mutex_new ();
blocked_cond = g_cond_new ();
pipeline = gst_pipeline_new ("pipeline");
src = gst_element_factory_make ("fakesrc", "src");
g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
sink = gst_element_factory_make ("fakesink", "sink");
/* add sink, don't add source yet */
gst_bin_add (GST_BIN (pipeline), sink);
/* set the pipeline to PLAYING. This will return ASYNC on READY->PAUSED */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
g_mutex_lock (blocked_lock);
GST_DEBUG ("blocking srcpad");
/* block source pad */
srcpad = gst_element_get_pad (src, "src");
gst_pad_set_blocked_async (srcpad, TRUE, pad_blocked_cb, NULL);
/* set source to PAUSED without adding it to the pipeline */
ret = gst_element_set_state (src, GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
"no NO_PREROLL state return");
/* add source now, pipeline should notice the NO_PREROLL element. This
* should trigger as commit of the ASYNC pipeline and make it continue
* to PLAYING. We blocked the source pad so that we don't get an unlinked
* error. */
gst_bin_add (GST_BIN (pipeline), src);
/* wait for pad blocked, this means the source is now PLAYING. */
g_cond_wait (blocked_cond, blocked_lock);
g_mutex_unlock (blocked_lock);
GST_DEBUG ("linking pads");
/* link to sink */
sinkpad = gst_element_get_pad (sink, "sink");
gst_pad_link (srcpad, sinkpad);
gst_object_unref (srcpad);
gst_object_unref (sinkpad);
GST_DEBUG ("unblocking srcpad");
/* and unblock */
gst_pad_set_blocked_async (srcpad, FALSE, pad_blocked_cb, NULL);
GST_DEBUG ("getting state");
/* we should be SUCCESS now and PLAYING */
ret =
gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
ret = gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
g_cond_free (blocked_cond);
g_mutex_free (blocked_lock);
gst_object_unref (pipeline);
}
GST_END_TEST;
/* test: try changing state of sinks */
Suite *
gst_sinks_suite (void)
@ -465,6 +861,13 @@ gst_sinks_suite (void)
tcase_add_test (tc_chain, test_livesrc_sink);
tcase_add_test (tc_chain, test_livesrc2_sink);
tcase_add_test (tc_chain, test_livesrc3_sink);
tcase_add_test (tc_chain, test_locked_sink);
tcase_add_test (tc_chain, test_unlinked_live);
tcase_add_test (tc_chain, test_delayed_async);
tcase_add_test (tc_chain, test_added_async);
tcase_add_test (tc_chain, test_added_async2);
tcase_add_test (tc_chain, test_add_live);
tcase_add_test (tc_chain, test_add_live2);
return s;
}