+2005-10-10 Wim Taymans <wim@fluendo.com>
+
+ * check/Makefile.am:
+ * check/generic/states.c: (GST_START_TEST):
+ * check/gst/gstbin.c: (GST_START_TEST):
+ * check/gst/gstpipeline.c: (GST_START_TEST), (gst_pipeline_suite):
+ * check/states/sinks.c: (GST_START_TEST):
+ * check/states/sinks2.c: (GST_START_TEST), (gst_object_suite),
+ (main):
+ Check fixes, use API as stated in design docs, remove hacks.
+
+ * gst/base/gstbasesink.c: (gst_base_sink_handle_object),
+ (gst_base_sink_change_state):
+ Catch stopping our task while we're shutting down.
+
+ * gst/gstbin.c: (gst_bin_init), (gst_bin_add_func),
+ (gst_bin_remove_func), (gst_bin_get_state_func),
+ (gst_bin_recalc_state), (gst_bin_change_state_func),
+ (bin_bus_handler):
+ * gst/gstbin.h:
+ * gst/gstelement.c: (gst_element_init),
+ (gst_element_get_state_func), (gst_element_abort_state),
+ (gst_element_commit_state), (gst_element_lost_state),
+ (gst_element_set_state), (gst_element_change_state),
+ (gst_element_change_state_func):
+ * gst/gstelement.h:
+ New state change algorithm (see #318116)
+
+ * gst/gstpipeline.c: (gst_pipeline_class_init),
+ (gst_pipeline_init), (gst_pipeline_set_property),
+ (gst_pipeline_get_property), (do_pipeline_seek),
+ (gst_pipeline_change_state), (gst_pipeline_provide_clock_func):
+ * gst/gstpipeline.h:
+ Remove crude state change hacks.
+
+ * gst/gstutils.h:
+ Remove crude hacks.
+
+ * tools/gst-launch.c: (main):
+ Fixes for state change. Needs some more work to fully use the
+ new stuff.
+
2005-10-10 Andy Wingo <wingo@pobox.com>
* tests/Makefile.am (noinst_PROGRAMS): No more init.c.
pipelines/simple_launch_lines \
pipelines/cleanup \
states/sinks \
+ states/sinks2 \
gst-libs/controller \
gst-libs/gdp
gst_element_set_state (element, GST_STATE_READY);
gst_element_set_state (element, GST_STATE_PAUSED);
gst_element_set_state (element, GST_STATE_PLAYING);
- /* Sleep to give any pad tasks time to start */
- g_usleep (0.2 * G_USEC_PER_SEC);
+
gst_element_set_state (element, GST_STATE_PAUSED);
gst_element_set_state (element, GST_STATE_READY);
gst_element_set_state (element, GST_STATE_NULL);
gst_element_set_state (element, GST_STATE_PLAYING);
gst_element_set_state (element, GST_STATE_PAUSED);
gst_element_set_state (element, GST_STATE_NULL);
+
gst_object_unref (GST_OBJECT (element));
}
gst_task_cleanup_all ();
/* change state to PAUSED, spawning three messages */
GST_DEBUG ("setting pipeline to PAUSED");
ret = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
- fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
ret =
gst_element_get_state (GST_ELEMENT (pipeline), ¤t, &pending, NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
fail_unless (gst_element_link (src, sink), "could not link src and sink");
- /* change state, spawning two times three messages, minus one async */
+ /* change state, spawning two times three messages */
ret = gst_element_set_state (GST_ELEMENT (bin), GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+ ret = gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, NULL);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
- pop_messages (bus, 5);
-
- fail_unless (gst_bus_have_pending (bus) == FALSE,
- "Unexpected messages on bus");
-
- gst_bin_watch_for_state_change (GST_BIN (bin));
-
- /* should get the bin's state change message now */
- pop_messages (bus, 1);
+ pop_messages (bus, 6);
fail_unless (gst_bus_have_pending (bus) == FALSE,
"Unexpected messages on bus");
/* this one might return either SUCCESS or ASYNC, likely SUCCESS */
gst_element_set_state (GST_ELEMENT (bin), GST_STATE_PAUSED);
-
- gst_bin_watch_for_state_change (GST_BIN (bin));
+ gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, NULL);
pop_messages (bus, 3);
{
GstElement *src, *identity, *sink, *pipeline;
GstStateChangeReturn ret;
+ GstState current, pending;
GstBus *bus;
pipeline = gst_pipeline_new (NULL);
/* (1) Test state change with fakesink being a regular sink */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ fail_if (ret != GST_STATE_CHANGE_ASYNC,
+ "State change to PLAYING did not return ASYNC");
+ ret = gst_element_get_state (pipeline, ¤t, &pending, NULL);
fail_if (ret != GST_STATE_CHANGE_SUCCESS, "State change to PLAYING failed");
+ fail_if (current != GST_STATE_PLAYING, "State change to PLAYING failed");
+ fail_if (pending != GST_STATE_VOID_PENDING, "State change to PLAYING failed");
/* NULL => READY */
ASSERT_STATE_CHANGE_MSG (bus, sink, GST_STATE_NULL, GST_STATE_READY, 101);
GST_FLAG_UNSET (sink, GST_ELEMENT_IS_SINK); /* <======== */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
- fail_if (ret != GST_STATE_CHANGE_SUCCESS, "State change to PLAYING failed");
+ fail_if (ret != GST_STATE_CHANGE_ASYNC, "State change to PLAYING not ASYNC");
ret = gst_element_get_state (pipeline, ¤t, &pending, NULL);
fail_if (ret != GST_STATE_CHANGE_SUCCESS, "State change to PLAYING failed");
fail_if (current != GST_STATE_PLAYING, "State change to PLAYING failed");
pipeline = GST_PIPELINE (gst_pipeline_new (NULL));
fail_unless (pipeline != NULL, "Could not create pipeline");
- g_object_set (G_OBJECT (pipeline), "play-timeout", 0LL, NULL);
fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline),
GST_STATE_PLAYING), GST_STATE_CHANGE_SUCCESS);
pipeline = GST_PIPELINE (gst_pipeline_new (NULL));
fail_unless (pipeline != NULL, "Could not create pipeline");
- g_object_set (G_OBJECT (pipeline), "play-timeout", 0LL, NULL);
src = gst_element_factory_make ("fakesrc", NULL);
sink = gst_element_factory_make ("fakesink", NULL);
pipeline = GST_PIPELINE (gst_pipeline_new (NULL));
fail_unless (pipeline != NULL, "Could not create pipeline");
- g_object_set (G_OBJECT (pipeline), "play-timeout", 0LL, NULL);
src = gst_element_factory_make ("fakesrc", NULL);
sink = gst_element_factory_make ("fakesink", NULL);
bus = gst_pipeline_get_bus (pipeline);
- fail_unless_equals_int (gst_element_set_state_async (GST_ELEMENT (pipeline),
+ fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline),
GST_STATE_PLAYING), GST_STATE_CHANGE_ASYNC);
while (!done) {
}
}
- g_object_set (G_OBJECT (pipeline), "play-timeout", 3 * GST_SECOND, NULL);
fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline),
GST_STATE_NULL), GST_STATE_CHANGE_SUCCESS);
pipeline = gst_pipeline_new (NULL);
fail_unless (pipeline != NULL, "Could not create pipeline");
ASSERT_OBJECT_REFCOUNT (pipeline, "pipeline", 1);
- g_object_set (pipeline, "play-timeout", 0LL, NULL);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src != NULL);
ASSERT_OBJECT_REFCOUNT (pipeline, "pipeline after add_watch", 1);
ASSERT_OBJECT_REFCOUNT (bus, "bus after add_watch", 3);
- gst_element_set_state_async (pipeline, GST_STATE_PLAYING);
+ gst_element_set_state (pipeline, GST_STATE_PLAYING);
loop = g_main_loop_new (NULL, FALSE);
GST_DEBUG ("going into main loop");
g_main_loop_run (loop);
/* cleanup */
GST_DEBUG ("cleanup");
- /* current semantics require us to go step by step; this will change */
- gst_element_set_state (pipeline, GST_STATE_PAUSED);
- gst_element_set_state (pipeline, GST_STATE_READY);
gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (gst_element_get_state (pipeline, ¤t, NULL, NULL) ==
GST_STATE_CHANGE_SUCCESS);
Suite *s = suite_create ("GstPipeline");
TCase *tc_chain = tcase_create ("pipeline tests");
+ tcase_set_timeout (tc_chain, 0);
+
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_async_state_change_empty);
tcase_add_test (tc_chain, test_async_state_change_fake_ready);
ret = gst_element_get_state (sink, ¤t, &pending, &tv);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not changing state async");
- fail_unless (current == GST_STATE_PAUSED, "bad current state");
+ fail_unless (current == GST_STATE_READY, "bad current state");
fail_unless (pending == GST_STATE_PLAYING, "bad pending state");
ret = gst_element_set_state (sink, GST_STATE_PAUSED);
gst_object_unref (sinkpad);
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no async state return");
ret = gst_element_get_state (pipeline, NULL, NULL, NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no success state return");
ret = gst_element_get_state (pipeline, ¤t, &pending, &tv);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not async");
fail_unless (current == GST_STATE_PAUSED, "not paused");
- fail_unless (pending == GST_STATE_VOID_PENDING, "not playing");
-
+ fail_unless (pending == GST_STATE_PAUSED, "not paused");
}
GST_END_TEST
--- /dev/null
+/* GStreamer
+ *
+ * unit test for sinks
+ *
+ * Copyright (C) <2005> Wim Taymans <wim at fluendo dot com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include <gst/check/gstcheck.h>
+
+/* a sink should go ASYNC to PAUSE and PLAYING. */
+GST_START_TEST (test_sink)
+{
+ GstElement *sink, *src;
+ GstStateChangeReturn ret;
+ GstState current, pending;
+ GTimeVal tv;
+
+ sink = gst_element_factory_make ("fakesink", "sink");
+
+ ret = gst_element_set_state (sink, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no async state return");
+
+ GST_TIME_TO_TIMEVAL ((GstClockTime) 0, tv);
+
+ ret = gst_element_get_state (sink, ¤t, &pending, &tv);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not changing state async");
+ fail_unless (current == GST_STATE_READY, "bad current state");
+ fail_unless (pending == GST_STATE_PLAYING, "bad pending state");
+
+ src = gst_element_factory_make ("fakesrc", "src");
+ gst_element_link (src, sink);
+
+ ret = gst_element_set_state (src, GST_STATE_PLAYING);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no success state return");
+
+ /* now wait for final state */
+ ret = gst_element_get_state (sink, ¤t, &pending, NULL);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "failed to change state");
+ fail_unless (current == GST_STATE_PLAYING, "bad current state");
+ fail_unless (pending == GST_STATE_VOID_PENDING, "bad pending state");
+
+ ret = gst_element_set_state (sink, GST_STATE_NULL);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "failed to go to null");
+
+ ret = gst_element_set_state (src, GST_STATE_NULL);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "failed to go to null");
+
+ gst_object_unref (sink);
+ gst_object_unref (src);
+}
+
+GST_END_TEST
+/* test: try changing state of sinks */
+ Suite * gst_object_suite (void)
+{
+ Suite *s = suite_create ("Sinks");
+ TCase *tc_chain = tcase_create ("general");
+
+ suite_add_tcase (s, tc_chain);
+ tcase_add_test (tc_chain, test_sink);
+
+ return s;
+}
+
+int
+main (int argc, char **argv)
+{
+ int nf;
+
+ Suite *s = gst_object_suite ();
+ SRunner *sr = srunner_create (s);
+
+ gst_check_init (&argc, &argv);
+
+ srunner_run_all (sr, CK_NORMAL);
+ nf = srunner_ntests_failed (sr);
+ srunner_free (sr);
+
+ return nf;
+}
if (length == 1) {
gint t;
+ GstTask *task;
basesink->have_preroll = TRUE;
/* we are prerolling */
g_warning ("STREAM_LOCK should have been locked !!");
}
- /* now we commit our state */
- GST_STATE_LOCK (basesink);
+ /* now we commit our state, this will also automatically proceed to
+ * the next pending state. */
+ /* FIXME */
+ if ((task = GST_PAD_TASK (pad))) {
+ while (!GST_STATE_TRYLOCK (basesink)) {
+ GST_DEBUG_OBJECT (basesink,
+ "state change happening, checking shutdown");
+ GST_LOCK (pad);
+ if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad)))
+ goto task_stopped;
+ GST_UNLOCK (pad);
+ }
+ } else {
+ GST_STATE_LOCK (basesink);
+ }
GST_DEBUG_OBJECT (basesink, "commit state");
gst_element_commit_state (GST_ELEMENT (basesink));
GST_STATE_UNLOCK (basesink);
return ret;
}
+task_stopped:
+ {
+ GST_UNLOCK (pad);
+ GST_DEBUG_OBJECT (basesink, "task is stopped");
+ return GST_FLOW_WRONG_STATE;
+ }
flushing:
{
GST_UNLOCK (pad);
* we need to wait for a preroll */
GST_DEBUG_OBJECT (basesink, "have_preroll: %d, EOS: %d",
basesink->have_preroll, basesink->eos);
- if (!basesink->have_preroll && !basesink->eos) {
+ if (!basesink->have_preroll && !basesink->eos
+ && GST_STATE_PENDING (basesink) == GST_STATE_PAUSED) {
GST_DEBUG_OBJECT (basesink, "PLAYING to PAUSED, need preroll to TRUE");
basesink->need_preroll = TRUE;
ret = GST_STATE_CHANGE_ASYNC;
(guint) (bin)->child_states[2], (bin)->child_states[1], \
(bin)->child_states[0], gst_element_state_get_name (GST_STATE (bin)))
-
static GstElementDetails gst_bin_details = GST_ELEMENT_DETAILS ("Generic bin",
"Generic/Bin",
"Simple container object",
static void gst_bin_dispose (GObject * object);
+static void gst_bin_recalc_state (GstBin * bin, gboolean force);
static GstStateChangeReturn gst_bin_change_state_func (GstElement * element,
GstStateChange transition);
static GstStateChangeReturn gst_bin_get_state_func (GstElement * element,
bin->children = NULL;
bin->children_cookie = 0;
bin->eosed = NULL;
+ bin->polling = FALSE;
+ bin->state_dirty = FALSE;
/* Set up a bus for listening to child elements */
bus = g_object_new (gst_bus_get_type (), NULL);
gst_element_set_base_time (element, GST_ELEMENT (bin)->base_time);
GST_DEBUG_OBJECT (element, "setting clock %p", GST_ELEMENT_CLOCK (bin));
gst_element_set_clock (element, GST_ELEMENT_CLOCK (bin));
-
+ bin->state_dirty = TRUE;
GST_UNLOCK (bin);
/* unlink all linked pads */
GST_FLAG_UNSET (bin, GST_ELEMENT_IS_SINK);
}
}
+ bin->state_dirty = TRUE;
GST_UNLOCK (bin);
GST_CAT_INFO_OBJECT (GST_CAT_PARENTAGE, bin, "removed child \"%s\"",
return result;
}
-/* 2 phases:
- * 1) check state of all children with 0 timeout to find ERROR and
- * NO_PREROLL elements. return if found.
- * 2) perform full blocking wait with requested timeout.
- *
- * 2) cannot be performed when 1) returns results as the sinks might
- * not be able to complete the state change making 2) block forever.
- *
+/*
* MT safe
*/
static GstStateChangeReturn
GstState * pending, GTimeVal * timeout)
{
GstBin *bin = GST_BIN (element);
+
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state");
+
+ /* do a non forced recalculation of the state */
+ gst_bin_recalc_state (bin, FALSE);
+
+ return parent_class->get_state (element, state, pending, timeout);
+}
+
+static void
+gst_bin_recalc_state (GstBin * bin, gboolean force)
+{
GstStateChangeReturn ret;
GList *children;
guint32 children_cookie;
gboolean have_async;
GTimeVal tv;
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state");
-
ret = GST_STATE_CHANGE_SUCCESS;
/* lock bin, no element can be added or removed while we have this lock */
GST_LOCK (bin);
+ /* no point in scanning if nothing changed and it's no forced recalc */
+ if (!force && !bin->state_dirty)
+ goto not_dirty;
+
+ /* no point in having two scans run concurrently */
+ if (bin->polling)
+ goto was_polling;
+
+ bin->polling = TRUE;
+
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "recalc state");
restart:
have_no_preroll = FALSE;
switch (ret) {
case GST_STATE_CHANGE_FAILURE:
- /* report FAILURE immediately */
+ /* report FAILURE immediatly */
goto done;
case GST_STATE_CHANGE_NO_PREROLL:
/* we have to continue scanning as there might be
/* if we have NO_PREROLL, return that */
if (have_no_preroll) {
ret = GST_STATE_CHANGE_NO_PREROLL;
- goto done;
}
- /* else return SUCCESS if no async elements were found */
- else if (!have_async) {
- ret = GST_STATE_CHANGE_SUCCESS;
- goto done;
- }
-
- /* next we poll all children for their state to see if one of them
- * is still busy with its state change. We did not release the bin lock
- * yet so the elements are the same as the ones from the quick scan. */
- 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 the potentialy blocking wait */
- GST_UNLOCK (bin);
-
- /* ret is ASYNC if some child is still performing the state change
- * ater the timeout. */
- ret = gst_element_get_state (child, NULL, NULL, timeout);
-
- gst_object_unref (child);
-
- /* now grab the lock to iterate to the next child */
- GST_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.*/
- goto restart;
- }
-
- switch (ret) {
- case GST_STATE_CHANGE_SUCCESS:
- break;
- case GST_STATE_CHANGE_FAILURE:
- case GST_STATE_CHANGE_NO_PREROLL:
- /* report FAILURE and NO_PREROLL immediatly */
- goto done;
- case GST_STATE_CHANGE_ASYNC:
- goto done;
- default:
- g_assert_not_reached ();
- }
- children = g_list_next (children);
+ /* else return ASYNC if async elements where found. */
+ else if (have_async) {
+ ret = GST_STATE_CHANGE_ASYNC;
}
- /* if we got here, all elements can do preroll */
- have_no_preroll = FALSE;
done:
+ bin->state_dirty = FALSE;
+ bin->polling = FALSE;
GST_UNLOCK (bin);
/* now we can take the state lock, it is possible that new elements
GST_STATE_LOCK (bin);
switch (ret) {
case GST_STATE_CHANGE_SUCCESS:
- gst_element_commit_state (element);
+ case GST_STATE_CHANGE_NO_PREROLL:
+ gst_element_commit_state (GST_ELEMENT_CAST (bin));
+ break;
+ case GST_STATE_CHANGE_ASYNC:
+ gst_element_lost_state (GST_ELEMENT_CAST (bin));
break;
case GST_STATE_CHANGE_FAILURE:
- gst_element_abort_state (element);
+ gst_element_abort_state (GST_ELEMENT_CAST (bin));
break;
default:
- /* other cases are just passed along */
- break;
+ goto unknown_state;
}
-
- /* and report the state if needed */
- if (state)
- *state = GST_STATE (element);
- if (pending)
- *pending = GST_STATE_PENDING (element);
-
- GST_STATE_NO_PREROLL (element) = have_no_preroll;
-
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "state current: %s, pending: %s, error: %d, no_preroll: %d, result: %d",
- gst_element_state_get_name (GST_STATE (element)),
- gst_element_state_get_name (GST_STATE_PENDING (element)),
- GST_STATE_ERROR (element), GST_STATE_NO_PREROLL (element), ret);
-
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "return now %d",
+ GST_STATE_RETURN (bin));
GST_STATE_UNLOCK (bin);
+ return;
- return ret;
+not_dirty:
+ {
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "not dirty");
+ GST_UNLOCK (bin);
+ return;
+ }
+was_polling:
+ {
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "was polling");
+ GST_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;
+ }
}
/***********************************************
gboolean done;
/* we don't need to take the STATE_LOCK, it is already taken */
- current = GST_STATE (element);
- next = GST_STATE_PENDING (element);
+ current = GST_STATE_TRANSITION_CURRENT (transition);
+ next = 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));
- if (next == GST_STATE_VOID_PENDING)
- return GST_STATE_CHANGE_SUCCESS;
-
bin = GST_BIN_CAST (element);
/* Clear eosed element list on next PAUSED */
}
}
+ ret = parent_class->change_state (element, transition);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ goto done;
+
if (have_no_preroll) {
ret = GST_STATE_CHANGE_NO_PREROLL;
} else if (have_async) {
ret = GST_STATE_CHANGE_ASYNC;
- } else {
- ret = parent_class->change_state (element, transition);
}
done:
gst_message_unref (message);
break;
}
+ case GST_MESSAGE_STATE_CHANGED:
+ {
+ GstState old, new, pending;
+ GstObject *src;
+
+ gst_message_parse_state_changed (message, &old, &new, &pending);
+ src = GST_MESSAGE_SRC (message);
+ /* ref src, as we need it after we post the message up */
+ gst_object_ref (src);
+
+ GST_DEBUG_OBJECT (bin, "%s gave state change, %s -> %s, pending %s",
+ GST_ELEMENT_NAME (src),
+ gst_element_state_get_name (old),
+ gst_element_state_get_name (new),
+ gst_element_state_get_name (pending));
+
+ /* post message up */
+ gst_element_post_message (GST_ELEMENT_CAST (bin), message);
+
+ /* we only act on our own children */
+ GST_LOCK (bin);
+ if (!g_list_find (bin->children, src))
+ goto not_our_child;
+ GST_UNLOCK (bin);
+
+ gst_object_unref (src);
+
+ /* we can lock, either the state change is sync and we can
+ * recursively lock or the state change is async and we
+ * lock when the bin has done it state change. We can check which
+ * case it is by looking at the CHANGING_STATE flag. */
+ GST_STATE_LOCK (bin);
+ GST_DEBUG_OBJECT (bin, "locked");
+
+ if (!GST_FLAG_IS_SET (bin, GST_ELEMENT_CHANGING_STATE)) {
+ GST_DEBUG_OBJECT (bin, "got ASYNC message, forcing recalc state");
+ GST_STATE_UNLOCK (bin);
+
+ /* force bin state recalculation on async messages. */
+ gst_bin_recalc_state (bin, TRUE);
+ } else {
+ GST_STATE_UNLOCK (bin);
+ GST_DEBUG_OBJECT (bin, "got SYNC message");
+ }
+ break;
+
+ not_our_child:
+ {
+ GST_UNLOCK (bin);
+ GST_DEBUG_OBJECT (bin, "not our child");
+ gst_object_unref (src);
+ break;
+ }
+ }
default:
/* Send all other messages upward */
GST_DEBUG_OBJECT (bin, "posting message upward");
GstBus *child_bus; /* Bus we set on our children */
GList *eosed; /* list of elements that posted EOS */
+ gboolean polling;
+ gboolean state_dirty;
/*< private >*/
gpointer _gst_reserved[GST_PADDING];
};
*
* Each element has a state (see #GstState). You can get and set the state
* of an element with gst_element_get_state() and gst_element_set_state().
- * You can wait for an element to change it's state with gst_element_wait_state_change().
* To get a string representation of a #GstState, use
* gst_element_state_get_name().
*
* Note that clock slection and distribution is normally handled by the toplevel
* #GstPipeline so the clock functions are only to be used in very specific situations.
*/
+
#include "gst_private.h"
#include <glib.h>
#include <stdarg.h>
/* Element signals and args */
enum
{
- STATE_CHANGE,
NEW_PAD,
PAD_REMOVED,
NO_MORE_PADS,
GstStateChange transition);
static GstStateChangeReturn gst_element_change_state_func (GstElement * element,
GstStateChange transition);
+static GstStateChangeReturn gst_element_change_state_func (GstElement * element,
+ GstStateChange transition);
static GstStateChangeReturn gst_element_get_state_func (GstElement * element,
GstState * state, GstState * pending, GTimeVal * timeout);
static void gst_element_set_bus_func (GstElement * element, GstBus * bus);
static void
gst_element_init (GstElement * element)
{
- element->current_state = GST_STATE_NULL;
- element->pending_state = GST_STATE_VOID_PENDING;
+ GST_STATE (element) = GST_STATE_NULL;
+ GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING;
+ GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
+ GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS;
+
element->state_lock = g_new0 (GStaticRecMutex, 1);
g_static_rec_mutex_init (element->state_lock);
element->state_cond = g_cond_new ();
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state");
GST_STATE_LOCK (element);
+ ret = GST_STATE_RETURN (element);
+
/* we got an error, report immediatly */
- if (GST_STATE_NO_PREROLL (element)) {
- ret = GST_STATE_CHANGE_NO_PREROLL;
+ if (ret == GST_STATE_CHANGE_FAILURE)
goto done;
- }
- /* we got an error, report immediatly */
- if (GST_STATE_ERROR (element)) {
- ret = GST_STATE_CHANGE_FAILURE;
+ /* we got no_preroll, report immediatly */
+ if (ret == GST_STATE_CHANGE_NO_PREROLL)
+ goto done;
+
+ /* no need to wait async if we are not async */
+ if (ret != GST_STATE_CHANGE_ASYNC)
goto done;
- }
old_pending = GST_STATE_PENDING (element);
if (old_pending != GST_STATE_VOID_PENDING) {
if (timeout) {
glong add = timeout->tv_sec * G_USEC_PER_SEC + timeout->tv_usec;
+ if (add == 0)
+ goto done;
+
/* make timeout absolute */
g_get_current_time (&abstimeout);
g_time_val_add (&abstimeout, add);
*pending = GST_STATE_PENDING (element);
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "state current: %s, pending: %s, error: %d, no_preroll: %d, result: %d",
+ "state current: %s, pending: %s, result: %d",
gst_element_state_get_name (GST_STATE (element)),
- gst_element_state_get_name (GST_STATE_PENDING (element)),
- GST_STATE_ERROR (element), GST_STATE_NO_PREROLL (element), ret);
+ gst_element_state_get_name (GST_STATE_PENDING (element)), ret);
GST_STATE_UNLOCK (element);
* an error, this function returns immediatly with a return value of
* GST_STATE_CHANGE_SUCCESS or GST_STATE_CHANGE_FAILURE respectively.
*
+ * For elements that did not return ASYNC, this function returns the
+ * current and pending state immediatly.
+ *
* Returns: GST_STATE_CHANGE_SUCCESS if the element has no more pending state and
* the last state change succeeded, GST_STATE_CHANGE_ASYNC
* if the element is still performing a state change or
pending = GST_STATE_PENDING (element);
- if (pending != GST_STATE_VOID_PENDING && !GST_STATE_ERROR (element)) {
+ if (pending != GST_STATE_VOID_PENDING &&
+ GST_STATE_RETURN (element) != GST_STATE_CHANGE_FAILURE) {
#ifndef GST_DISABLE_GST_DEBUG
GstState old_state = GST_STATE (element);
#endif
-
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"aborting state from %s to %s", gst_element_state_get_name (old_state),
gst_element_state_get_name (pending));
/* flag error */
- GST_STATE_ERROR (element) = TRUE;
+ GST_STATE_RETURN (element) = GST_STATE_CHANGE_FAILURE;
GST_STATE_BROADCAST (element);
}
*
* Commit the state change of the element. This function is used
* by elements that do asynchronous state changes.
+ * The core will normally call this method automatically when an
+ * element returned SUCCESS from the state change function.
+ * Elements that return ASYNC from the change_state function should
+ * eventually call this method from the streaming thread to signal
+ * successfull state change completion.
+ *
+ * If after calling this method the element still has not reached
+ * the pending state, the next state change is performed.
*
* This function can only be called with the STATE_LOCK held.
*
+ * Returns: The result of the commit state change.
+ *
* MT safe.
*/
-void
+GstStateChangeReturn
gst_element_commit_state (GstElement * element)
{
GstState pending;
+ GstStateChangeReturn ret;
- g_return_if_fail (GST_IS_ELEMENT (element));
+ g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
+
+ ret = GST_STATE_CHANGE_SUCCESS;
pending = GST_STATE_PENDING (element);
+ /* check if there is something to commit */
if (pending != GST_STATE_VOID_PENDING) {
- GstState old_state = GST_STATE (element);
+ GstState old_state;
+ GstState current, next;
GstMessage *message;
+ GST_FLAG_SET (element, GST_ELEMENT_CHANGING_STATE);
+
+ old_state = GST_STATE (element);
+ /* this is the state we should go to next */
+ next = GST_STATE_NEXT (element);
+ /* update current state */
+ current = GST_STATE (element) = next;
+
+ /* see if we reached the final state */
+ if (pending == next) {
+ pending = GST_STATE_VOID_PENDING;
+ GST_STATE_PENDING (element) = pending;
+ GST_STATE_NEXT (element) = pending;
+ ret = GST_STATE_CHANGE_SUCCESS;
+ } else {
+ /* not there yet, will get there ASYNC */
+ ret = GST_STATE_CHANGE_ASYNC;
+ }
+
+ GST_STATE_RETURN (element) = ret;
+
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "committing state from %s to %s",
+ "committing state from %s to %s, pending %s",
gst_element_state_get_name (old_state),
+ gst_element_state_get_name (next),
gst_element_state_get_name (pending));
- GST_STATE (element) = pending;
- GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
- GST_STATE_ERROR (element) = FALSE;
-
message = gst_message_new_state_changed (GST_OBJECT (element),
- old_state, pending, pending);
+ old_state, next, pending);
gst_element_post_message (element, message);
- GST_STATE_BROADCAST (element);
+
+ if (pending != GST_STATE_VOID_PENDING) {
+ GstStateChange transition;
+
+ /* calc new next state */
+ next = GST_STATE_GET_NEXT (current, pending);
+
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+ "continue state change %s to %s, final %s",
+ gst_element_state_get_name (current),
+ gst_element_state_get_name (next),
+ gst_element_state_get_name (pending));
+
+ /* create transition */
+ transition = GST_STATE_TRANSITION (current, next);
+
+ /* perform next transition */
+ ret = gst_element_change_state (element, transition);
+ } else {
+ GST_STATE_BROADCAST (element);
+ }
+ GST_FLAG_UNSET (element, GST_ELEMENT_CHANGING_STATE);
}
+ return ret;
}
/**
g_return_if_fail (GST_IS_ELEMENT (element));
if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING &&
- !GST_STATE_ERROR (element)) {
+ GST_STATE_RETURN (element) != GST_STATE_CHANGE_FAILURE) {
GstState current_state;
GstMessage *message;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
"lost state of %s", gst_element_state_get_name (current_state));
+ GST_STATE_NEXT (element) = current_state;
GST_STATE_PENDING (element) = current_state;
- GST_STATE_ERROR (element) = FALSE;
+ GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
message = gst_message_new_state_changed (GST_OBJECT (element),
current_state, current_state, current_state);
* requested state by going through all the intermediary states and calling
* the class's state change function for each.
*
+ * This function can return GST_STATE_CHANGE_ASYNC, in which case the
+ * element will perform the remainder of the state change asynchronously.
+ * An application can use gst_element_get_state() to wait for the completion
+ * of the state change or it can wait for a state change message on the bus.
+ *
* Returns: Result of the state change using #GstStateChangeReturn.
*
* MT safe.
GstStateChangeReturn
gst_element_set_state (GstElement * element, GstState state)
{
- GstState current, old_pending;
+ GstState current, next, old_pending;
GstStateChangeReturn ret;
GstStateChange transition;
- GTimeVal tv;
+ GstStateChangeReturn old_ret;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "set_state to %s",
gst_element_state_get_name (state));
- /* get current element state, need to call the method so that
- * we call the virtual method and subclasses can implement their
- * own algorithms */
- GST_TIME_TO_TIMEVAL (0, tv);
- ret = gst_element_get_state (element, ¤t, &old_pending, &tv);
-
GST_STATE_LOCK (element);
+ GST_FLAG_SET (element, GST_ELEMENT_CHANGING_STATE);
+
+ old_ret = GST_STATE_RETURN (element);
+ /* previous state change returned an error, remove all pending
+ * and next states */
+ if (old_ret == GST_STATE_CHANGE_FAILURE) {
+ GST_STATE_NEXT (element) = GST_STATE_VOID_PENDING;
+ GST_STATE_PENDING (element) = GST_STATE_VOID_PENDING;
+ GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS;
+ }
+
+ current = GST_STATE (element);
+ next = GST_STATE_NEXT (element);
+ old_pending = GST_STATE_PENDING (element);
/* this is the (new) state we should go to */
- GST_STATE_FINAL (element) = state;
- if (ret == GST_STATE_CHANGE_ASYNC) {
- /* force next state keeping ASYNC, this is atomic as we hold
- * the STATE_LOCK */
- gst_element_commit_state (element);
- gst_element_lost_state (element);
- if (state == GST_STATE_PENDING (element))
+ GST_STATE_PENDING (element) = state;
+
+ GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+ "current %s, old_pending %s, next %s, old return %d",
+ gst_element_state_get_name (current),
+ gst_element_state_get_name (old_pending),
+ gst_element_state_get_name (next), old_ret);
+
+ /* if the element was busy doing a state change, we just update the
+ * target state, it'll get to it async then. */
+ if (old_pending != GST_STATE_VOID_PENDING) {
+ /* upwards state change will happen ASYNC */
+ if (old_pending <= state)
+ goto was_busy;
+ /* element is going to this state already */
+ else if (next == state)
goto was_busy;
+ /* element was performing an ASYNC upward state change and
+ * we request to go downward again. Start from the next pending
+ * state then. */
+ else if (next > state
+ && GST_STATE_RETURN (element) == GST_STATE_CHANGE_ASYNC) {
+ current = next;
+ }
}
+ next = GST_STATE_GET_NEXT (current, state);
- /* fixme, not right */
- transition = GST_STATE_CHANGE (element);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
+ "%s: setting state from %s to %s",
+ (next != state ? "intermediate" : "final"),
+ gst_element_state_get_name (current), gst_element_state_get_name (next));
+
+ transition = GST_STATE_TRANSITION (current, next);
ret = gst_element_change_state (element, transition);
+ GST_FLAG_UNSET (element, GST_ELEMENT_CHANGING_STATE);
GST_STATE_UNLOCK (element);
GST_DEBUG_OBJECT (element, "returned %d", ret);
was_busy:
{
+ GST_STATE_RETURN (element) = GST_STATE_CHANGE_ASYNC;
+ GST_FLAG_UNSET (element, GST_ELEMENT_CHANGING_STATE);
GST_STATE_UNLOCK (element);
GST_DEBUG_OBJECT (element, "element was busy with async state change");
return GST_STATE_CHANGE_ASYNC;
}
+
}
/* with STATE_LOCK */
{
GstElementClass *oclass;
GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS;
- GstState current, next, final;
+ GstState current;
+ GstState next;
oclass = GST_ELEMENT_GET_CLASS (element);
/* start with the current state. */
- current = GST_STATE (element);
- next = GST_STATE_PENDING (element);
- final = GST_STATE_FINAL (element);
-
- /* We always perform at least one state change, even if the
- * current state is equal to the required state. This is needed
- * for bins that sync their children. */
- do {
- GstState pending;
-
- /* calculate the pending state */
- if (current < final)
- pending = current + 1;
- else if (current > final)
- pending = current - 1;
- else
- pending = current;
+ current = GST_STATE_TRANSITION_CURRENT (transition);
+ next = GST_STATE_TRANSITION_NEXT (transition);
- /* set the pending state variable */
- GST_STATE_PENDING (element) = pending;
+ /* now we store the next state */
+ GST_STATE_NEXT (element) = next;
- GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
- "%s: setting state from %s to %s",
- (pending != final ? "intermediate" : "final"),
- gst_element_state_get_name (current),
- gst_element_state_get_name (pending));
+ /* call the state change function so it can set the state */
+ if (oclass->change_state)
+ ret = (oclass->change_state) (element, transition);
+ else
+ ret = GST_STATE_CHANGE_FAILURE;
- /* call the state change function so it can set the state */
- if (oclass->change_state)
- ret = (oclass->change_state) (element, GST_STATE_CHANGE (element));
- else
- ret = GST_STATE_CHANGE_FAILURE;
+ switch (ret) {
+ case GST_STATE_CHANGE_FAILURE:
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+ "have FAILURE change_state return");
+ /* state change failure */
+ gst_element_abort_state (element);
+ break;
+ case GST_STATE_CHANGE_ASYNC:
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+ "element will change state ASYNC");
- /* clear the error and preroll flag, we need to do that after
- * calling the virtual change_state function so that it can use the
- * old previous value. */
- GST_STATE_ERROR (element) = FALSE;
- GST_STATE_NO_PREROLL (element) = FALSE;
-
- switch (ret) {
- case GST_STATE_CHANGE_FAILURE:
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "have failed change_state return");
- /* state change failure exits the loop */
- gst_element_abort_state (element);
- goto exit;
- case GST_STATE_CHANGE_ASYNC:
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "element will change state async");
- /* an async state change exits the loop, we can only
- * go to the next state change when this one completes. */
+ /* if we go upwards, we give the app a change to wait for
+ * completion */
+ if (current < next)
goto exit;
- case GST_STATE_CHANGE_SUCCESS:
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "element changed state successfully");
- /* we can commit the state now and proceed to the next state */
- gst_element_commit_state (element);
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "committed state");
- break;
- case GST_STATE_CHANGE_NO_PREROLL:
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
- "element changed state successfully and can't preroll");
- /* we can commit the state now and proceed to the next state */
- gst_element_commit_state (element);
- GST_STATE_NO_PREROLL (element) = TRUE;
- GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "committed state");
- break;
- default:
- ret = GST_STATE_CHANGE_FAILURE;
- goto invalid_return;
- }
- /* get the current state of the element and see if we need to do more
- * state changes */
- current = GST_STATE (element);
+
+ /* else we just continue the state change downwards */
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+ "forcing commit state %s < %s",
+ gst_element_state_get_name (current),
+ gst_element_state_get_name (next));
+
+ GST_STATE_RETURN (element) = GST_STATE_CHANGE_SUCCESS;
+
+ ret = gst_element_commit_state (element);
+ break;
+ case GST_STATE_CHANGE_SUCCESS:
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+ "element changed state SUCCESS");
+ /* we can commit the state now which will proceeed to
+ * the next state */
+ ret = gst_element_commit_state (element);
+ break;
+ case GST_STATE_CHANGE_NO_PREROLL:
+ GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+ "element changed state NO_PREROLL");
+ /* we can commit the state now which will proceeed to
+ * the next state */
+ gst_element_commit_state (element);
+ ret = GST_STATE_CHANGE_NO_PREROLL;
+ break;
+ default:
+ ret = GST_STATE_CHANGE_FAILURE;
+ goto invalid_return;
}
- while (current != final);
exit:
- GST_STATE_FINAL (element) = GST_STATE_VOID_PENDING;
+ GST_STATE_RETURN (element) = ret;
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "exit state change %d", ret);
/* ERROR */
invalid_return:
{
- GST_STATE_FINAL (element) = GST_STATE_VOID_PENDING;
+ GST_STATE_RETURN (element) = ret;
/* somebody added a GST_STATE_ and forgot to do stuff here ! */
g_critical ("unknown return value %d from a state change function", ret);
return ret;
g_return_val_if_fail (GST_IS_ELEMENT (element), GST_STATE_CHANGE_FAILURE);
- state = GST_STATE (element);
- next = GST_STATE_PENDING (element);
+ state = GST_STATE_TRANSITION_CURRENT (transition);
+ next = GST_STATE_TRANSITION_NEXT (transition);
/* if the element already is in the given state, we just return success */
if (next == GST_STATE_VOID_PENDING || state == next)
GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element,
"element is already in the %s state",
gst_element_state_get_name (state));
- if (GST_STATE_NO_PREROLL (element))
- return GST_STATE_CHANGE_NO_PREROLL;
- else
- return GST_STATE_CHANGE_SUCCESS;
+ return GST_STATE_RETURN (element);
}
}
#define GST_STATE(obj) (GST_ELEMENT(obj)->current_state)
/**
+ * GST_STATE_NEXT:
+ * @obj: Element to return the next state for.
+ *
+ * This macro returns the next state of the element.
+ */
+#define GST_STATE_NEXT(obj) (GST_ELEMENT(obj)->next_state)
+
+/**
* GST_STATE_PENDING:
* @obj: Element to return the pending state for.
*
* This macro returns the currently pending state of the element.
*/
#define GST_STATE_PENDING(obj) (GST_ELEMENT(obj)->pending_state)
-#define GST_STATE_FINAL(obj) (GST_ELEMENT(obj)->final_state)
-#define GST_STATE_ERROR(obj) (GST_ELEMENT(obj)->state_error)
-#define GST_STATE_NO_PREROLL(obj) (GST_ELEMENT(obj)->no_preroll)
-
-#ifndef GST_DEBUG_STATE_CHANGE
-#define GST_STATE_CHANGE(obj) ((1<<(GST_STATE(obj)+8)) | 1<<GST_STATE_PENDING(obj))
-#else
-inline GstStateChange
-_gst_element_get_state_change (GstElement *e)
-{
- if (e->state < GST_STATE_NULL || e->state > GST_STATE_PLAYING)
- g_assert_not_reached ();
- if (e->pending_state < GST_STATE_NULL || e->pending_state > GST_STATE_PLAYING)
- g_assert_not_reached ();
- if (e->state - e->pending_state != 1 && e->pending_state - e->state != 1)
- g_assert_not_reached ();
- return (1<<(GST_STATE(obj)+8)) | 1<<GST_STATE_PENDING(obj);
-}
-#define GST_STATE_CHANGE(obj) _gst_element_get_state_change(obj)
-#endif
-
-/* FIXME: How to deal with lost_state ? */
+
+/**
+ * GST_STATE_RETURN:
+ * @obj: Element to return the last state result for.
+ *
+ * This macro returns the last state change return value.
+ */
+#define GST_STATE_RETURN(obj) (GST_ELEMENT(obj)->last_return)
+
+#define GST_SIGN(val) ((val) < 0 ? -1 : ((val) > 0 ? 1 : 0))
+#define GST_STATE_GET_NEXT(cur,pending) ((cur)+GST_SIGN ((gint)(pending)-(gint)(cur)))
+#define GST_STATE_TRANSITION(cur,next) (((cur)<<3)|(next))
+#define GST_STATE_TRANSITION_CURRENT(trans) ((trans)>>3)
+#define GST_STATE_TRANSITION_NEXT(trans) ((trans)&0x7)
+
/**
* GstStateChange:
* @GST_STATE_CHANGE_NULL_TO_READY : state change from NULL to READY
*/
typedef enum /*< flags=0 >*/
{
- GST_STATE_CHANGE_NULL_TO_READY = 1<<(GST_STATE_NULL+8) | 1<<GST_STATE_READY,
- GST_STATE_CHANGE_READY_TO_PAUSED = 1<<(GST_STATE_READY+8) | 1<<GST_STATE_PAUSED,
- GST_STATE_CHANGE_PAUSED_TO_PLAYING = 1<<(GST_STATE_PAUSED+8) | 1<<GST_STATE_PLAYING,
- GST_STATE_CHANGE_PLAYING_TO_PAUSED = 1<<(GST_STATE_PLAYING+8) | 1<<GST_STATE_PAUSED,
- GST_STATE_CHANGE_PAUSED_TO_READY = 1<<(GST_STATE_PAUSED+8) | 1<<GST_STATE_READY,
- GST_STATE_CHANGE_READY_TO_NULL = 1<<(GST_STATE_READY+8) | 1<<GST_STATE_NULL
+ GST_STATE_CHANGE_NULL_TO_READY = (GST_STATE_NULL<<3) | GST_STATE_READY,
+ GST_STATE_CHANGE_READY_TO_PAUSED = (GST_STATE_READY<<3) | GST_STATE_PAUSED,
+ GST_STATE_CHANGE_PAUSED_TO_PLAYING = (GST_STATE_PAUSED<<3) | GST_STATE_PLAYING,
+ GST_STATE_CHANGE_PLAYING_TO_PAUSED = (GST_STATE_PLAYING<<3) | GST_STATE_PAUSED,
+ GST_STATE_CHANGE_PAUSED_TO_READY = (GST_STATE_PAUSED<<3) | GST_STATE_READY,
+ GST_STATE_CHANGE_READY_TO_NULL = (GST_STATE_READY<<3) | GST_STATE_NULL
} GstStateChange;
/**
GST_ELEMENT_LOCKED_STATE = GST_OBJECT_FLAG_LAST,
GST_ELEMENT_IS_SINK,
GST_ELEMENT_UNPARENTING,
+ GST_ELEMENT_CHANGING_STATE,
GST_ELEMENT_FLAG_LAST = GST_OBJECT_FLAG_LAST + 16
} GstElementFlags;
/* element state */
GStaticRecMutex *state_lock;
GCond *state_cond;
- guint8 current_state;
- guint8 pending_state;
- guint8 final_state;
- gboolean state_error; /* Flag is set when the element has an
- * error in the last state change.
- * It is cleared when doing another
- * state change. */
- gboolean no_preroll; /* Flag is set when the element cannot
- * preroll */
+ GstState current_state;
+ GstState next_state;
+ GstState pending_state;
+ GstStateChangeReturn last_return;
+
/*< public >*/ /* with LOCK */
GstBus *bus;
GstStateChangeReturn gst_element_set_state (GstElement *element, GstState state);
void gst_element_abort_state (GstElement * element);
-void gst_element_commit_state (GstElement * element);
+GstStateChangeReturn gst_element_commit_state (GstElement * element);
void gst_element_lost_state (GstElement * element);
/* factory management */
};
#define DEFAULT_DELAY 0
-#define DEFAULT_PLAY_TIMEOUT (2*GST_SECOND)
+
enum
{
PROP_0,
PROP_DELAY,
- PROP_PLAY_TIMEOUT,
/* FILL ME */
};
"Expected delay needed for elements "
"to spin up to PLAYING in nanoseconds", 0, G_MAXUINT64, DEFAULT_DELAY,
G_PARAM_READWRITE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_PLAY_TIMEOUT,
- g_param_spec_uint64 ("play-timeout", "Play Timeout",
- "Max timeout for going to PLAYING in nanoseconds", 0, G_MAXUINT64,
- DEFAULT_PLAY_TIMEOUT, G_PARAM_READWRITE));
gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_pipeline_dispose);
GstBus *bus;
pipeline->delay = DEFAULT_DELAY;
- pipeline->play_timeout = DEFAULT_PLAY_TIMEOUT;
bus = g_object_new (gst_bus_get_type (), NULL);
gst_element_set_bus (GST_ELEMENT_CAST (pipeline), bus);
case PROP_DELAY:
pipeline->delay = g_value_get_uint64 (value);
break;
- case PROP_PLAY_TIMEOUT:
- pipeline->play_timeout = g_value_get_uint64 (value);
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case PROP_DELAY:
g_value_set_uint64 (value, pipeline->delay);
break;
- case PROP_PLAY_TIMEOUT:
- g_value_set_uint64 (value, pipeline->play_timeout);
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
gst_element_get_state (element, &state, NULL, &timeout);
was_playing = state == GST_STATE_PLAYING;
- if (was_playing)
+ if (was_playing) {
gst_element_set_state (element, GST_STATE_PAUSED);
+ }
}
res = GST_ELEMENT_CLASS (parent_class)->send_event (element, event);
if (flush && res) {
/* need to reset the stream time to 0 after a flushing seek */
gst_pipeline_set_new_stream_time (GST_PIPELINE (element), 0);
- if (was_playing) {
+ if (was_playing)
/* and continue playing */
gst_element_set_state (element, GST_STATE_PLAYING);
- }
}
return res;
}
{
GstStateChangeReturn result = GST_STATE_CHANGE_SUCCESS;
GstPipeline *pipeline = GST_PIPELINE (element);
- GstClockTime play_timeout;
GstClock *clock;
switch (transition) {
GST_UNLOCK (element);
break;
}
-
- if (result == GST_STATE_CHANGE_ASYNC) {
- GST_LOCK (pipeline);
- play_timeout = pipeline->play_timeout;
- GST_UNLOCK (pipeline);
- } else {
- play_timeout = 0;
- }
-
- /* we wait for async state changes ourselves when we are in an
- * intermediate state. */
- if (play_timeout > 0) {
- GTimeVal *timeval, timeout;
-
- GST_STATE_UNLOCK (pipeline);
-
- if (play_timeout == G_MAXUINT64) {
- timeval = NULL;
- } else {
- GST_TIME_TO_TIMEVAL (play_timeout, timeout);
- timeval = &timeout;
- }
-
- result = gst_element_get_state (element, NULL, NULL, timeval);
- if (result == GST_STATE_CHANGE_ASYNC) {
- GST_WARNING_OBJECT (pipeline,
- "timeout in PREROLL, forcing next state change");
- g_warning ("timeout in PREROLL, forcing next state change");
- result = GST_STATE_CHANGE_SUCCESS;
- }
-
- GST_STATE_LOCK (pipeline);
- }
-
return result;
}
GstClock *fixed_clock; /* fixed clock if any */
GstClockTime stream_time;
GstClockTime delay;
- GstClockTime play_timeout;
/*< private >*/
gpointer _gst_reserved[GST_PADDING];
gboolean gst_element_link_pads_filtered (GstElement * src, const gchar * srcpadname,
GstElement * dest, const gchar * destpadname,
GstCaps *filter);
-GstStateChangeReturn gst_element_set_state_async (GstElement * element, GstState state);
-
/* util elementfactory functions */
gboolean gst_element_factory_can_src_caps(GstElementFactory *factory, const GstCaps *caps);
gboolean gst_element_factory_can_sink_caps(GstElementFactory *factory, const GstCaps *caps);
/* bin functions */
void gst_bin_add_many (GstBin *bin, GstElement *element_1, ...);
void gst_bin_remove_many (GstBin *bin, GstElement *element_1, ...);
-void gst_bin_watch_for_state_change (GstBin *bin);
/* buffer functions */
GstBuffer * gst_buffer_merge (GstBuffer * buf1, GstBuffer * buf2);
if (length == 1) {
gint t;
+ GstTask *task;
basesink->have_preroll = TRUE;
/* we are prerolling */
g_warning ("STREAM_LOCK should have been locked !!");
}
- /* now we commit our state */
- GST_STATE_LOCK (basesink);
+ /* now we commit our state, this will also automatically proceed to
+ * the next pending state. */
+ /* FIXME */
+ if ((task = GST_PAD_TASK (pad))) {
+ while (!GST_STATE_TRYLOCK (basesink)) {
+ GST_DEBUG_OBJECT (basesink,
+ "state change happening, checking shutdown");
+ GST_LOCK (pad);
+ if (G_UNLIKELY (GST_PAD_IS_FLUSHING (pad)))
+ goto task_stopped;
+ GST_UNLOCK (pad);
+ }
+ } else {
+ GST_STATE_LOCK (basesink);
+ }
GST_DEBUG_OBJECT (basesink, "commit state");
gst_element_commit_state (GST_ELEMENT (basesink));
GST_STATE_UNLOCK (basesink);
return ret;
}
+task_stopped:
+ {
+ GST_UNLOCK (pad);
+ GST_DEBUG_OBJECT (basesink, "task is stopped");
+ return GST_FLOW_WRONG_STATE;
+ }
flushing:
{
GST_UNLOCK (pad);
* we need to wait for a preroll */
GST_DEBUG_OBJECT (basesink, "have_preroll: %d, EOS: %d",
basesink->have_preroll, basesink->eos);
- if (!basesink->have_preroll && !basesink->eos) {
+ if (!basesink->have_preroll && !basesink->eos
+ && GST_STATE_PENDING (basesink) == GST_STATE_PAUSED) {
GST_DEBUG_OBJECT (basesink, "PLAYING to PAUSED, need preroll to TRUE");
basesink->need_preroll = TRUE;
ret = GST_STATE_CHANGE_ASYNC;
pipelines/simple_launch_lines \
pipelines/cleanup \
states/sinks \
+ states/sinks2 \
gst-libs/controller \
gst-libs/gdp
ret = gst_element_get_state (sink, ¤t, &pending, &tv);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not changing state async");
- fail_unless (current == GST_STATE_PAUSED, "bad current state");
+ fail_unless (current == GST_STATE_READY, "bad current state");
fail_unless (pending == GST_STATE_PLAYING, "bad pending state");
ret = gst_element_set_state (sink, GST_STATE_PAUSED);
gst_object_unref (sinkpad);
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no async state return");
ret = gst_element_get_state (pipeline, NULL, NULL, NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no success state return");
ret = gst_element_get_state (pipeline, ¤t, &pending, &tv);
fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not async");
fail_unless (current == GST_STATE_PAUSED, "not paused");
- fail_unless (pending == GST_STATE_VOID_PENDING, "not playing");
-
+ fail_unless (pending == GST_STATE_PAUSED, "not paused");
}
GST_END_TEST
gst_element_set_state (element, GST_STATE_READY);
gst_element_set_state (element, GST_STATE_PAUSED);
gst_element_set_state (element, GST_STATE_PLAYING);
- /* Sleep to give any pad tasks time to start */
- g_usleep (0.2 * G_USEC_PER_SEC);
+
gst_element_set_state (element, GST_STATE_PAUSED);
gst_element_set_state (element, GST_STATE_READY);
gst_element_set_state (element, GST_STATE_NULL);
gst_element_set_state (element, GST_STATE_PLAYING);
gst_element_set_state (element, GST_STATE_PAUSED);
gst_element_set_state (element, GST_STATE_NULL);
+
gst_object_unref (GST_OBJECT (element));
}
gst_task_cleanup_all ();
/* change state to PAUSED, spawning three messages */
GST_DEBUG ("setting pipeline to PAUSED");
ret = gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PAUSED);
- fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
+ fail_unless (ret == GST_STATE_CHANGE_ASYNC);
ret =
gst_element_get_state (GST_ELEMENT (pipeline), ¤t, &pending, NULL);
fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
fail_unless (gst_element_link (src, sink), "could not link src and sink");
- /* change state, spawning two times three messages, minus one async */
+ /* change state, spawning two times three messages */
ret = gst_element_set_state (GST_ELEMENT (bin), GST_STATE_PAUSED);
fail_unless (ret == GST_STATE_CHANGE_ASYNC);
+ ret = gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, NULL);
+ fail_unless (ret == GST_STATE_CHANGE_SUCCESS);
- pop_messages (bus, 5);
-
- fail_unless (gst_bus_have_pending (bus) == FALSE,
- "Unexpected messages on bus");
-
- gst_bin_watch_for_state_change (GST_BIN (bin));
-
- /* should get the bin's state change message now */
- pop_messages (bus, 1);
+ pop_messages (bus, 6);
fail_unless (gst_bus_have_pending (bus) == FALSE,
"Unexpected messages on bus");
/* this one might return either SUCCESS or ASYNC, likely SUCCESS */
gst_element_set_state (GST_ELEMENT (bin), GST_STATE_PAUSED);
-
- gst_bin_watch_for_state_change (GST_BIN (bin));
+ gst_element_get_state (GST_ELEMENT (bin), NULL, NULL, NULL);
pop_messages (bus, 3);
{
GstElement *src, *identity, *sink, *pipeline;
GstStateChangeReturn ret;
+ GstState current, pending;
GstBus *bus;
pipeline = gst_pipeline_new (NULL);
/* (1) Test state change with fakesink being a regular sink */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+ fail_if (ret != GST_STATE_CHANGE_ASYNC,
+ "State change to PLAYING did not return ASYNC");
+ ret = gst_element_get_state (pipeline, ¤t, &pending, NULL);
fail_if (ret != GST_STATE_CHANGE_SUCCESS, "State change to PLAYING failed");
+ fail_if (current != GST_STATE_PLAYING, "State change to PLAYING failed");
+ fail_if (pending != GST_STATE_VOID_PENDING, "State change to PLAYING failed");
/* NULL => READY */
ASSERT_STATE_CHANGE_MSG (bus, sink, GST_STATE_NULL, GST_STATE_READY, 101);
GST_FLAG_UNSET (sink, GST_ELEMENT_IS_SINK); /* <======== */
ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
- fail_if (ret != GST_STATE_CHANGE_SUCCESS, "State change to PLAYING failed");
+ fail_if (ret != GST_STATE_CHANGE_ASYNC, "State change to PLAYING not ASYNC");
ret = gst_element_get_state (pipeline, ¤t, &pending, NULL);
fail_if (ret != GST_STATE_CHANGE_SUCCESS, "State change to PLAYING failed");
fail_if (current != GST_STATE_PLAYING, "State change to PLAYING failed");
pipeline = GST_PIPELINE (gst_pipeline_new (NULL));
fail_unless (pipeline != NULL, "Could not create pipeline");
- g_object_set (G_OBJECT (pipeline), "play-timeout", 0LL, NULL);
fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline),
GST_STATE_PLAYING), GST_STATE_CHANGE_SUCCESS);
pipeline = GST_PIPELINE (gst_pipeline_new (NULL));
fail_unless (pipeline != NULL, "Could not create pipeline");
- g_object_set (G_OBJECT (pipeline), "play-timeout", 0LL, NULL);
src = gst_element_factory_make ("fakesrc", NULL);
sink = gst_element_factory_make ("fakesink", NULL);
pipeline = GST_PIPELINE (gst_pipeline_new (NULL));
fail_unless (pipeline != NULL, "Could not create pipeline");
- g_object_set (G_OBJECT (pipeline), "play-timeout", 0LL, NULL);
src = gst_element_factory_make ("fakesrc", NULL);
sink = gst_element_factory_make ("fakesink", NULL);
bus = gst_pipeline_get_bus (pipeline);
- fail_unless_equals_int (gst_element_set_state_async (GST_ELEMENT (pipeline),
+ fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline),
GST_STATE_PLAYING), GST_STATE_CHANGE_ASYNC);
while (!done) {
}
}
- g_object_set (G_OBJECT (pipeline), "play-timeout", 3 * GST_SECOND, NULL);
fail_unless_equals_int (gst_element_set_state (GST_ELEMENT (pipeline),
GST_STATE_NULL), GST_STATE_CHANGE_SUCCESS);
pipeline = gst_pipeline_new (NULL);
fail_unless (pipeline != NULL, "Could not create pipeline");
ASSERT_OBJECT_REFCOUNT (pipeline, "pipeline", 1);
- g_object_set (pipeline, "play-timeout", 0LL, NULL);
src = gst_element_factory_make ("fakesrc", NULL);
fail_unless (src != NULL);
ASSERT_OBJECT_REFCOUNT (pipeline, "pipeline after add_watch", 1);
ASSERT_OBJECT_REFCOUNT (bus, "bus after add_watch", 3);
- gst_element_set_state_async (pipeline, GST_STATE_PLAYING);
+ gst_element_set_state (pipeline, GST_STATE_PLAYING);
loop = g_main_loop_new (NULL, FALSE);
GST_DEBUG ("going into main loop");
g_main_loop_run (loop);
/* cleanup */
GST_DEBUG ("cleanup");
- /* current semantics require us to go step by step; this will change */
- gst_element_set_state (pipeline, GST_STATE_PAUSED);
- gst_element_set_state (pipeline, GST_STATE_READY);
gst_element_set_state (pipeline, GST_STATE_NULL);
fail_unless (gst_element_get_state (pipeline, ¤t, NULL, NULL) ==
GST_STATE_CHANGE_SUCCESS);
Suite *s = suite_create ("GstPipeline");
TCase *tc_chain = tcase_create ("pipeline tests");
+ tcase_set_timeout (tc_chain, 0);
+
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_async_state_change_empty);
tcase_add_test (tc_chain, test_async_state_change_fake_ready);
gst_bin_add (GST_BIN (real_pipeline), pipeline);
pipeline = real_pipeline;
}
-
+#if 0
fprintf (stderr, _("PAUSE pipeline ...\n"));
ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+#endif
+ fprintf (stderr, _("PLAYING pipeline ...\n"));
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
switch (ret) {
case GST_STATE_CHANGE_FAILURE: