From 1cda8197e99cb39578142865d37d61a41250c57a Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 23 Jun 2005 10:37:09 +0000 Subject: [PATCH] Added support for live sources and other elements that cannot do preroll. Original commit message from CVS: Added support for live sources and other elements that cannot do preroll. Updated design docs, added live-source design doc. Implemented live source functionality in basesrc Fix error condition in _bin_get_state() Implement live source handling in -launch. Added check for live sources. Fixed case in GstBin where elements were changed state multiple times. --- ChangeLog | 30 ++++++ check/Makefile.am | 1 + check/states/sinks.c | 205 +++++++++++++++++++++++++++++++++++++++ docs/design/part-live-source.txt | 44 +++++++++ docs/design/part-states.txt | 25 +++-- gst/base/gstbasesrc.c | 64 +++++++++++- gst/base/gstbasesrc.h | 20 ++++ gst/elements/gstfakesrc.c | 17 +++- gst/gstbin.c | 144 +++++++++++++++++++++++---- gst/gstelement.c | 59 ++++++----- gst/gstelement.h | 2 + gst/gsttypes.h | 3 +- libs/gst/base/gstbasesrc.c | 64 +++++++++++- libs/gst/base/gstbasesrc.h | 20 ++++ plugins/elements/gstfakesrc.c | 17 +++- tests/check/Makefile.am | 1 + tests/check/generic/sinks.c | 205 +++++++++++++++++++++++++++++++++++++++ tools/gst-launch.c | 25 +++-- 18 files changed, 880 insertions(+), 66 deletions(-) create mode 100644 check/states/sinks.c create mode 100644 docs/design/part-live-source.txt create mode 100644 tests/check/generic/sinks.c diff --git a/ChangeLog b/ChangeLog index d05186d..5fcde55 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,33 @@ +2005-06-23 Wim Taymans + + * check/Makefile.am: + * check/states/sinks.c: (START_TEST), (gst_object_suite), (main): + * docs/design/part-live-source.txt: + * docs/design/part-states.txt: + * gst/base/gstbasesrc.c: (gst_basesrc_init), + (gst_basesrc_set_live), (gst_basesrc_is_live), + (gst_basesrc_get_range), (gst_basesrc_activate), + (gst_basesrc_change_state): + * gst/base/gstbasesrc.h: + * gst/elements/gstfakesrc.c: (gst_fakesrc_class_init), + (gst_fakesrc_set_property), (gst_fakesrc_get_property): + * gst/gstbin.c: (gst_bin_get_state), (gst_bin_change_state): + * gst/gstelement.c: (gst_element_get_state_func), + (gst_element_set_state): + * gst/gstelement.h: + * gst/gsttypes.h: + * tools/gst-launch.c: (event_loop), (main): + Added support for live sources and other elements that + cannot do preroll. + Updated design docs, added live-source design doc. + Implemented live source functionality in basesrc + Fix error condition in _bin_get_state() + Implement live source handling in -launch. + Added check for live sources. + Fixed case in GstBin where elements were changed state + multiple times. + + 2005-06-23 Andy Wingo * check/gst/gstpad.c (test_get_allowed_caps, test_refcount): Fix diff --git a/check/Makefile.am b/check/Makefile.am index 5ceb002..01e8e6e 100644 --- a/check/Makefile.am +++ b/check/Makefile.am @@ -39,6 +39,7 @@ TESTS = $(top_builddir)/tools/gst-register \ gst/gstvalue \ pipelines/simple_launch_lines \ pipelines/cleanup \ + states/sinks \ gst-libs/gdp check_PROGRAMS = $(TESTS) diff --git a/check/states/sinks.c b/check/states/sinks.c new file mode 100644 index 0000000..9ab6af7 --- /dev/null +++ b/check/states/sinks.c @@ -0,0 +1,205 @@ +/* GStreamer + * + * unit test for sinks + * + * Copyright (C) <2005> Wim Taymans + * + * 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 "../gstcheck.h" + +/* a sink should go ASYNC to PAUSE. forcing PLAYING is possible */ +START_TEST (test_sink) +{ + GstElement *sink; + GstElementStateReturn ret; + GstElementState current, pending; + + sink = gst_element_factory_make ("fakesink", "sink"); + + ret = gst_element_set_state (sink, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_ASYNC, "no async state return"); + + ret = gst_element_set_state (sink, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_SUCCESS, "cannot force play"); + + ret = gst_element_get_state (sink, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_SUCCESS, "not playing"); + fail_unless (current == GST_STATE_PLAYING, "not playing"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); +} + +END_TEST +/* a sink should go ASYNC to PAUSE. PAUSE should complete when + * prerolled. */ +START_TEST (test_src_sink) +{ + GstElement *sink, *src, *pipeline; + GstElementStateReturn ret; + GstElementState current, pending; + GstPad *srcpad, *sinkpad; + + pipeline = gst_pipeline_new ("pipeline"); + src = gst_element_factory_make ("fakesrc", "src"); + sink = gst_element_factory_make ("fakesink", "sink"); + + gst_bin_add (GST_BIN (pipeline), src); + gst_bin_add (GST_BIN (pipeline), sink); + + srcpad = gst_element_get_pad (src, "src"); + sinkpad = gst_element_get_pad (sink, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (GST_OBJECT (srcpad)); + gst_object_unref (GST_OBJECT (sinkpad)); + + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_SUCCESS, "no success state return"); + + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_SUCCESS, "cannot start play"); + + ret = gst_element_get_state (pipeline, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_SUCCESS, "not playing"); + fail_unless (current == GST_STATE_PLAYING, "not playing"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); +} + +END_TEST +/* a pipeline with live source should return NO_PREROLL in + * PAUSE. When removing the live source it should return ASYNC + * from the sink */ +START_TEST (test_livesrc_remove) +{ + GstElement *sink, *src, *pipeline; + GstElementStateReturn ret; + GstElementState current, pending; + GstPad *srcpad, *sinkpad; + GTimeVal tv; + + 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); + + srcpad = gst_element_get_pad (src, "src"); + sinkpad = gst_element_get_pad (sink, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (GST_OBJECT (srcpad)); + gst_object_unref (GST_OBJECT (sinkpad)); + + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_NO_PREROLL, "no no_preroll state return"); + + ret = gst_element_get_state (src, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_NO_PREROLL, "not paused"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + + gst_bin_remove (GST_BIN (pipeline), src); + + GST_TIME_TO_TIMEVAL (0, tv); + ret = gst_element_get_state (pipeline, ¤t, &pending, &tv); + fail_unless (ret == GST_STATE_ASYNC, "not async"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + +} + +END_TEST +/* a sink should go ASYNC to PAUSE. PAUSE does not complete + * since we have a live source. */ +START_TEST (test_livesrc_sink) +{ + GstElement *sink, *src, *pipeline; + GstElementStateReturn ret; + GstElementState 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"); + + gst_bin_add (GST_BIN (pipeline), src); + gst_bin_add (GST_BIN (pipeline), sink); + + srcpad = gst_element_get_pad (src, "src"); + sinkpad = gst_element_get_pad (sink, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (GST_OBJECT (srcpad)); + gst_object_unref (GST_OBJECT (sinkpad)); + + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_NO_PREROLL, "no no_preroll state return"); + + ret = gst_element_get_state (src, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_NO_PREROLL, "not paused"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + + ret = gst_element_get_state (pipeline, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_NO_PREROLL, "not paused"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_SUCCESS, "cannot force play"); + + ret = gst_element_get_state (pipeline, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_SUCCESS, "not playing"); + fail_unless (current == GST_STATE_PLAYING, "not playing"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); +} + +END_TEST +/* test: try changing state of sinks */ + Suite * gst_object_suite (void) +{ + Suite *s = suite_create ("Sinks"); + TCase *tc_chain = tcase_create ("general"); + + /* turn off timeout */ + tcase_set_timeout (tc_chain, 60); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_sink); + tcase_add_test (tc_chain, test_src_sink); + tcase_add_test (tc_chain, test_livesrc_remove); + tcase_add_test (tc_chain, test_livesrc_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; +} diff --git a/docs/design/part-live-source.txt b/docs/design/part-live-source.txt new file mode 100644 index 0000000..9283bc7 --- /dev/null +++ b/docs/design/part-live-source.txt @@ -0,0 +1,44 @@ +Live sources +------------ + +A live source such as an element capturing audio or video need to be handled +in a special way. It does not make sense to start the dataflow in the PAUSED +state for those devices as the user might wait a long time between going from +PAUSED to PLAYING, making the previously captured buffers irrelevant. + +A live source therefore only produces buffers in the PLAYING state. This has +implications for sinks waiting for a buffer to complete the preroll state +since such a buffer might never arrive. + +Live sources return NO_PREROLL when going to the PAUSED state to inform the +bin/pipeline that this element will not be able to produce data in the +PAUSED state. + +When performing a get_state() on a bin with a non-zero timeout value, the +bin must be sure that there are no live sources in the pipeline because else +the get_state() function would block on the sinks. + +A gstbin therefore always performs a zero timeout get_state() on its +elements to discover the NO_PREROLL (and ERROR) elements before performing +a blocking wait on all elements. + + +Scheduling +---------- + +Live sources can not produce data in the paused state. They block in the +getrange function or in the loop function until they go to PLAYING. + + +Latency +------- + +The live source timestamps its data with the time of the clock at the +time the data was captured. Normally it will take some time to capture +the first sample of data and the last sample. This means that when the +buffer arrives at the sink, it will already be late and will be dropped. + +The latency is the time it takes to construct one buffer of data. + + + diff --git a/docs/design/part-states.txt b/docs/design/part-states.txt index a901632..d4dda06 100644 --- a/docs/design/part-states.txt +++ b/docs/design/part-states.txt @@ -60,8 +60,11 @@ The _set_state() function can return 3 possible values: change or for sinks that need to receive the first buffer before they can complete the state change (preroll). -In the case of an async state change, it is not possible to proceed to the next -state until the current state change completed. After receiving an ASYNC return + GST_STATE_NO_PREROLL: The state change is completed successfully but the element + will not be able to produce data in the PAUSED state. + +In the case of an async state change, it is possible to proceed to the next +state before the current state change completed. After receiving an ASYNC return value, you can use _element_get_state() to poll the status of the element. When setting the state of an element, the PENDING_STATE is set to the required @@ -129,15 +132,25 @@ on the elements. If after calling the state function on all children, one of the children returned ASYNC, the function returns ASYNC as well. +If after calling the state function on all children, one of the children returned +NO_PREROLL, the function returns NO_PREROLL as well. + The current state of the bin can be retrieved with _get_state(). This function will -call the _get_state() function on all the elements. If one of the children returns -FAILURE or ASYNC, the bin reports FAILURE or ASYNC respectively. The bin also -updates its state variables after polling its children, this means that the state -variables of the bin are only updated after calling _get_state() on the bin. +call the _get_state() function on all the elements. + +First the bin will perform a _get_state() on all children with a 0 timeout. This +is to find any children with an ERROR/NO_PREROLL result value. + +Then the bin performs the _get_state() with the requested timeout. The reason for +the 2 phases is that when an ERROR or NO_PREROLL result is found, a blocking +wait on the sinks might never return. The _get_state() function will be called on the children with the same timout value so the function can potentially block timeout*num_children. +The bin also updates its state variables after polling its children, this means that +the state variables of the bin are only updated after calling _get_state() on the bin. + Implementing states in elements ------------------------------- diff --git a/gst/base/gstbasesrc.c b/gst/base/gstbasesrc.c index a6bcf39..fdf7f2e 100644 --- a/gst/base/gstbasesrc.c +++ b/gst/base/gstbasesrc.c @@ -49,7 +49,7 @@ enum PROP_0, PROP_BLOCKSIZE, PROP_HAS_LOOP, - PROP_HAS_GETRANGE + PROP_HAS_GETRANGE, }; static GstElementClass *parent_class = NULL; @@ -164,6 +164,10 @@ gst_basesrc_init (GstBaseSrc * basesrc, gpointer g_class) gst_pad_set_checkgetrange_function (pad, gst_basesrc_check_get_range); + basesrc->is_live = FALSE; + basesrc->live_lock = g_mutex_new (); + basesrc->live_cond = g_cond_new (); + /* hold ref to pad */ basesrc->srcpad = pad; gst_element_add_pad (GST_ELEMENT (basesrc), pad); @@ -176,6 +180,26 @@ gst_basesrc_init (GstBaseSrc * basesrc, gpointer g_class) GST_FLAG_UNSET (basesrc, GST_BASESRC_STARTED); } +void +gst_basesrc_set_live (GstBaseSrc * src, gboolean live) +{ + GST_LIVE_LOCK (src); + src->is_live = live; + GST_LIVE_UNLOCK (src); +} + +gboolean +gst_basesrc_is_live (GstBaseSrc * src) +{ + gboolean result; + + GST_LIVE_LOCK (src); + result = src->is_live; + GST_LIVE_UNLOCK (src); + + return result; +} + static void gst_basesrc_set_dataflow_funcs (GstBaseSrc * this) { @@ -460,6 +484,16 @@ gst_basesrc_get_range (GstPad * pad, guint64 offset, guint length, src = GST_BASESRC (GST_OBJECT_PARENT (pad)); bclass = GST_BASESRC_GET_CLASS (src); + GST_LIVE_LOCK (src); + if (src->is_live) { + while (!src->live_running) { + GST_DEBUG ("live source waiting for running state"); + GST_LIVE_WAIT (src); + GST_DEBUG ("live source unlocked"); + } + } + GST_LIVE_UNLOCK (src); + if (!GST_FLAG_IS_SET (src, GST_BASESRC_STARTED)) goto not_started; @@ -725,6 +759,11 @@ gst_basesrc_activate (GstPad * pad, GstActivateMode mode) gst_basesrc_stop (basesrc); break; case GST_ACTIVATE_NONE: + GST_LIVE_LOCK (basesrc); + basesrc->live_running = TRUE; + GST_LIVE_SIGNAL (basesrc); + GST_LIVE_UNLOCK (basesrc); + /* step 1, unblock clock sync (if any) */ gst_basesrc_unlock (basesrc); @@ -746,7 +785,8 @@ static GstElementStateReturn gst_basesrc_change_state (GstElement * element) { GstBaseSrc *basesrc; - GstElementStateReturn result = GST_STATE_FAILURE; + GstElementStateReturn result = GST_STATE_SUCCESS; + GstElementStateReturn presult; GstElementState transition; basesrc = GST_BASESRC (element); @@ -757,17 +797,35 @@ gst_basesrc_change_state (GstElement * element) case GST_STATE_NULL_TO_READY: break; case GST_STATE_READY_TO_PAUSED: + GST_LIVE_LOCK (element); + if (basesrc->is_live) { + result = GST_STATE_NO_PREROLL; + basesrc->live_running = FALSE; + } + GST_LIVE_UNLOCK (element); break; case GST_STATE_PAUSED_TO_PLAYING: + GST_LIVE_LOCK (element); + basesrc->live_running = TRUE; + GST_LIVE_SIGNAL (element); + GST_LIVE_UNLOCK (element); break; default: break; } - result = GST_ELEMENT_CLASS (parent_class)->change_state (element); + if ((presult = GST_ELEMENT_CLASS (parent_class)->change_state (element)) != + GST_STATE_SUCCESS) + return presult; switch (transition) { case GST_STATE_PLAYING_TO_PAUSED: + GST_LIVE_LOCK (element); + if (basesrc->is_live) { + result = GST_STATE_NO_PREROLL; + basesrc->live_running = FALSE; + } + GST_LIVE_UNLOCK (element); break; case GST_STATE_PAUSED_TO_READY: if (!gst_basesrc_stop (basesrc)) diff --git a/gst/base/gstbasesrc.h b/gst/base/gstbasesrc.h index 98b48a9..eb39a3d 100644 --- a/gst/base/gstbasesrc.h +++ b/gst/base/gstbasesrc.h @@ -54,11 +54,28 @@ typedef struct _GstBaseSrcClass GstBaseSrcClass; #define GST_BASESRC_PAD(obj) (GST_BASESRC (obj)->srcpad) +#define GST_LIVE_GET_LOCK(elem) (GST_BASESRC(elem)->live_lock) +#define GST_LIVE_LOCK(elem) g_mutex_lock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_TRYLOCK(elem) g_mutex_trylock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_UNLOCK(elem) g_mutex_unlock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_GET_COND(elem) (GST_BASESRC(elem)->live_cond) +#define GST_LIVE_WAIT(elem) g_cond_wait (GST_LIVE_GET_COND (elem), GST_LIVE_GET_LOCK (elem)) +#define GST_LIVE_TIMED_WAIT(elem, timeval) g_cond_timed_wait (GST_LIVE_GET_COND (elem), GST_LIVE_GET_LOCK (elem),\ + timeval) +#define GST_LIVE_SIGNAL(elem) g_cond_signal (GST_LIVE_GET_COND (elem)); +#define GST_LIVE_BROADCAST(elem) g_cond_broadcast (GST_LIVE_GET_COND (elem)); + + struct _GstBaseSrc { GstElement element; GstPad *srcpad; + /*< protected >*/ /* with LIVE_LOCK */ + GMutex *live_lock; + GCond *live_cond; + gboolean is_live; + gboolean live_running; /*< protected >*/ /* with LOCK */ gint blocksize; /* size of buffers when operating push based */ gboolean has_loop; /* some scheduling properties */ @@ -114,6 +131,9 @@ struct _GstBaseSrcClass { GType gst_basesrc_get_type(void); +void gst_basesrc_set_live (GstBaseSrc *src, gboolean live); +gboolean gst_basesrc_is_live (GstBaseSrc *src); + G_END_DECLS #endif /* __GST_BASESRC_H__ */ diff --git a/gst/elements/gstfakesrc.c b/gst/elements/gstfakesrc.c index e020726..57d944d 100644 --- a/gst/elements/gstfakesrc.c +++ b/gst/elements/gstfakesrc.c @@ -90,7 +90,8 @@ enum PROP_PARENTSIZE, PROP_LAST_MESSAGE, PROP_HAS_LOOP, - PROP_HAS_GETRANGE + PROP_HAS_GETRANGE, + PROP_IS_LIVE }; #define GST_TYPE_FAKESRC_OUTPUT (gst_fakesrc_output_get_type()) @@ -284,6 +285,10 @@ gst_fakesrc_class_init (GstFakeSrcClass * klass) g_param_spec_boolean ("has-getrange", "Has getrange function", "True if the element exposes a getrange function", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_IS_LIVE, + g_param_spec_boolean ("is-live", "Is this a live source", + "True if the element cannot produce data in PAUSED", FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); gst_fakesrc_signals[SIGNAL_HANDOFF] = g_signal_new ("handoff", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, @@ -361,8 +366,10 @@ gst_fakesrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstFakeSrc *src; + GstBaseSrc *basesrc; src = GST_FAKESRC (object); + basesrc = GST_BASESRC (object); switch (prop_id) { case PROP_OUTPUT: @@ -428,6 +435,9 @@ gst_fakesrc_set_property (GObject * object, guint prop_id, const GValue * value, g_return_if_fail (!GST_FLAG_IS_SET (object, GST_BASESRC_STARTED)); src->has_getrange = g_value_get_boolean (value); break; + case PROP_IS_LIVE: + gst_basesrc_set_live (basesrc, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -439,11 +449,13 @@ gst_fakesrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstFakeSrc *src; + GstBaseSrc *basesrc; /* it's not null if we got it, but it might not be ours */ g_return_if_fail (GST_IS_FAKESRC (object)); src = GST_FAKESRC (object); + basesrc = GST_BASESRC (object); switch (prop_id) { case PROP_OUTPUT: @@ -500,6 +512,9 @@ gst_fakesrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_HAS_GETRANGE: g_value_set_boolean (value, src->has_getrange); break; + case PROP_IS_LIVE: + g_value_set_boolean (value, gst_basesrc_is_live (basesrc)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/gstbin.c b/gst/gstbin.c index 4bafa09..af4a342 100644 --- a/gst/gstbin.c +++ b/gst/gstbin.c @@ -822,8 +822,13 @@ gst_bin_iterate_sinks (GstBin * bin) return result; } -/* this functions loops over all children, as soon as one does - * not return SUCCESS, we return that value. +/* 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 */ @@ -832,18 +837,76 @@ gst_bin_get_state (GstElement * element, GstElementState * state, GstElementState * pending, GTimeVal * timeout) { GstBin *bin = GST_BIN (element); - GstElementStateReturn ret; + GstElementStateReturn ret = GST_STATE_SUCCESS; GList *children; guint32 children_cookie; + gboolean zero_timeout; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state"); - /* we cannot take the state lock yet as we might block when querying - * the children, holding the lock too long for no reason. */ + zero_timeout = timeout != NULL && timeout->tv_sec == 0 + && timeout->tv_usec == 0; + + /* if we have a non zero timeout we must make sure not to block + * on the sinks when we have NO_PREROLL elements. This is why we do + * a quick check if there are still NO_PREROLL elements. We also + * catch the error elements this way. */ + GST_STATE_LOCK (bin); + if (!zero_timeout) { + GST_LOCK (bin); + GTimeVal tv; + gboolean have_no_preroll = FALSE; + gboolean have_async = FALSE; + + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "checking for NO_PREROLL"); + /* use 0 timeout so we don't block on the sinks */ + GST_TIME_TO_TIMEVAL (0, tv); + children = bin->children; + while (children) { + GstElement *child = GST_ELEMENT_CAST (children->data); + + ret = gst_element_get_state (child, NULL, NULL, &tv); + switch (ret) { + /* report FAILURE or NO_PREROLL immediatly */ + case GST_STATE_FAILURE: + GST_UNLOCK (bin); + goto report; + case GST_STATE_NO_PREROLL: + /* we have to continue scanning as there might be + * ERRORS too */ + have_no_preroll = TRUE; + break; + case GST_STATE_ASYNC: + have_async = TRUE; + break; + default: + break; + } + children = g_list_next (children); + } + GST_UNLOCK (bin); + /* if we get here, we have no FAILURES, check for any NO_PREROLL + * elements then. */ + if (have_no_preroll) + goto report; + + /* if we get here, no NO_PREROLL elements are in the pipeline */ + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "no NO_PREROLL elements"); + GST_STATE_NO_PREROLL (element) = FALSE; + + /* if no ASYNC elements exist we don't even have to poll with a + * timeout again */ + if (!have_async) + goto report; + } + /* we have to release the state lock as we might block when querying + * the children, holding the lock for too long for no reason. */ + GST_STATE_UNLOCK (bin); /* next we poll all children for their state to see if one of them * is still busy with its state change. */ GST_LOCK (bin); restart: - ret = GST_STATE_SUCCESS; children = bin->children; children_cookie = bin->children_cookie; while (children) { @@ -863,18 +926,33 @@ restart: /* child added/removed during state change, restart */ goto restart; - if (ret != GST_STATE_SUCCESS) { - /* some child is still busy or in error, we can report that - * right away. */ - break; + switch (ret) { + case GST_STATE_SUCCESS: + break; + case GST_STATE_FAILURE: + case GST_STATE_NO_PREROLL: + /* report FAILURE and NO_PREROLL immediatly */ + goto done; + break; + case GST_STATE_ASYNC: + /* since we checked for non prerollable elements before, + * the first ASYNC return is the real return value */ + if (!zero_timeout) + goto done; + break; + default: + g_assert_not_reached (); } - children = g_list_next (children); } + /* if we got here, all elements can to preroll */ + GST_STATE_NO_PREROLL (element) = FALSE; +done: GST_UNLOCK (bin); /* now we can take the state lock */ GST_STATE_LOCK (bin); +report: switch (ret) { case GST_STATE_SUCCESS: /* we can commit the state */ @@ -895,6 +973,12 @@ restart: if (pending) *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", + 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_STATE_UNLOCK (bin); return ret; @@ -910,8 +994,9 @@ append_child (gpointer child, GQueue * queue) * as follows: * * 1) put all sink elements on the queue. - * 2) change state of elements in queue, put linked elements to queue. - * 3) while queue not empty goto 2) + * 2) put all semisink elements on the queue. + * 3) change state of elements in queue, put linked elements to queue. + * 4) while queue not empty goto 3) * * This will effectively change the state of all elements in the bin * from the sinks to the sources. We have to change the states this @@ -927,6 +1012,7 @@ gst_bin_change_state (GstElement * element) GstElementStateReturn ret; GstElementState old_state, pending; gboolean have_async = FALSE; + gboolean have_no_preroll = FALSE; GList *children; guint32 children_cookie; GQueue *elem_queue; /* list of elements waiting for a state change */ @@ -1047,9 +1133,20 @@ restart: /* see if this element is in the bin we are currently handling */ parent = gst_object_get_parent (GST_OBJECT_CAST (peer_elem)); if (parent && parent == GST_OBJECT_CAST (bin)) { + GList *oldelem; + GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, element, "adding element %s to queue", GST_ELEMENT_NAME (peer_elem)); + /* make sure we don't have duplicates */ + while ((oldelem = g_queue_find (semi_queue, peer_elem))) { + gst_object_unref (GST_OBJECT (peer_elem)); + g_queue_delete_link (semi_queue, oldelem); + } + while ((oldelem = g_queue_find (elem_queue, peer_elem))) { + gst_object_unref (GST_OBJECT (peer_elem)); + g_queue_delete_link (elem_queue, oldelem); + } /* was reffed before pushing on the queue by the * gst_object_get_parent() call we used to get the element. */ g_queue_push_tail (elem_queue, peer_elem); @@ -1101,6 +1198,13 @@ restart: /* release refcount of element we popped off the queue */ gst_object_unref (GST_OBJECT (qelement)); goto exit; + case GST_STATE_NO_PREROLL: + GST_CAT_DEBUG (GST_CAT_STATES, + "child '%s' changed state to %d(%s) successfully without preroll", + GST_ELEMENT_NAME (qelement), pending, + gst_element_state_get_name (pending)); + have_no_preroll = TRUE; + break; default: g_assert_not_reached (); break; @@ -1109,15 +1213,12 @@ restart: gst_object_unref (GST_OBJECT (qelement)); } - if (have_async) { + if (have_no_preroll) { + ret = GST_STATE_NO_PREROLL; + } else if (have_async) { ret = GST_STATE_ASYNC; } else { - if (parent_class->change_state) { - ret = parent_class->change_state (element); - } else { - ret = GST_STATE_SUCCESS; - } - if (ret == GST_STATE_SUCCESS) { + if ((ret = parent_class->change_state (element)) == GST_STATE_SUCCESS) { /* we can commit the state change now */ gst_element_commit_state (element); } @@ -1130,7 +1231,8 @@ restart: gst_element_state_get_name (GST_STATE (element))); exit: - /* release refcounts in queue, should normally be empty */ + /* release refcounts in queue, should normally be empty unless we + * had an error. */ g_queue_foreach (elem_queue, (GFunc) gst_object_unref, NULL); g_queue_free (elem_queue); g_queue_foreach (semi_queue, (GFunc) gst_object_unref, NULL); diff --git a/gst/gstelement.c b/gst/gstelement.c index decf647..6a24a80 100644 --- a/gst/gstelement.c +++ b/gst/gstelement.c @@ -1432,14 +1432,20 @@ gst_element_get_state_func (GstElement * element, GstElementStateReturn ret = GST_STATE_FAILURE; GstElementState old_pending; - g_return_val_if_fail (GST_IS_ELEMENT (element), FALSE); - GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state"); GST_STATE_LOCK (element); /* we got an error, report immediatly */ - if (GST_STATE_ERROR (element)) + if (GST_STATE_NO_PREROLL (element)) { + ret = GST_STATE_NO_PREROLL; + goto done; + } + + /* we got an error, report immediatly */ + if (GST_STATE_ERROR (element)) { + ret = GST_STATE_FAILURE; goto done; + } old_pending = GST_STATE_PENDING (element); if (old_pending != GST_STATE_VOID_PENDING) { @@ -1484,9 +1490,10 @@ done: *pending = GST_STATE_PENDING (element); GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, - "state current: %s, pending: %s", + "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_element_state_get_name (GST_STATE_PENDING (element)), + GST_STATE_ERROR (element), GST_STATE_NO_PREROLL (element), ret); GST_STATE_UNLOCK (element); @@ -1666,21 +1673,27 @@ gst_element_set_state (GstElement * element, GstElementState state) GstElementClass *oclass; GstElementState current; GstElementStateReturn return_val = GST_STATE_SUCCESS; + GstElementStateReturn ret; + GstElementState pending; + GTimeVal tv; + + + /* 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, &pending, &tv); /* get the element state lock */ GST_STATE_LOCK (element); - -#if 0 - /* a state change is pending and we are not in error, the element is busy - * with a state change and we cannot proceed. - * FIXME, does not work for a bin.*/ - if (G_UNLIKELY (GST_STATE_PENDING (element) != GST_STATE_VOID_PENDING && - !GST_STATE_ERROR (element))) - goto was_busy; -#endif + if (ret == GST_STATE_ASYNC) { + gst_element_commit_state (element); + } /* clear the error flag */ GST_STATE_ERROR (element) = FALSE; + /* clear the no_preroll flag */ + GST_STATE_NO_PREROLL (element) = FALSE; /* start with the current state */ current = GST_STATE (element); @@ -1739,6 +1752,14 @@ gst_element_set_state (GstElement * element, GstElementState state) gst_element_commit_state (element); GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "commited state"); break; + case GST_STATE_NO_PREROLL: + GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, + "element changed state successfuly 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, "commited state"); + break; default: goto invalid_return; } @@ -1756,16 +1777,6 @@ exit: return return_val; /* ERROR */ -#if 0 -was_busy: - { - GST_STATE_UNLOCK (element); - GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, - "was busy with a state change"); - - return GST_STATE_BUSY; - } -#endif invalid_return: { GST_STATE_UNLOCK (element); diff --git a/gst/gstelement.h b/gst/gstelement.h index 3b4f456..ed78adb 100644 --- a/gst/gstelement.h +++ b/gst/gstelement.h @@ -56,6 +56,7 @@ GST_EXPORT GType _gst_element_type; #define GST_STATE(obj) (GST_ELEMENT(obj)->current_state) #define GST_STATE_PENDING(obj) (GST_ELEMENT(obj)->pending_state) #define GST_STATE_ERROR(obj) (GST_ELEMENT(obj)->state_error) +#define GST_STATE_NO_PREROLL(obj) (GST_ELEMENT(obj)->no_preroll) /* Note: using 8 bit shift mostly "just because", it leaves us enough room to grow */ #define GST_STATE_TRANSITION(obj) ((GST_STATE(obj)<<8) | GST_STATE_PENDING(obj)) @@ -172,6 +173,7 @@ struct _GstElement guint8 pending_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 */ /*< public >*/ /* with LOCK */ /* element manager */ GstPipeline *manager; diff --git a/gst/gsttypes.h b/gst/gsttypes.h index 8b369a7..a00819b 100644 --- a/gst/gsttypes.h +++ b/gst/gsttypes.h @@ -55,7 +55,8 @@ typedef enum { typedef enum { GST_STATE_FAILURE = 0, GST_STATE_SUCCESS = 1, - GST_STATE_ASYNC = 2 + GST_STATE_ASYNC = 2, + GST_STATE_NO_PREROLL = 3 } GstElementStateReturn; typedef enum { diff --git a/libs/gst/base/gstbasesrc.c b/libs/gst/base/gstbasesrc.c index a6bcf39..fdf7f2e 100644 --- a/libs/gst/base/gstbasesrc.c +++ b/libs/gst/base/gstbasesrc.c @@ -49,7 +49,7 @@ enum PROP_0, PROP_BLOCKSIZE, PROP_HAS_LOOP, - PROP_HAS_GETRANGE + PROP_HAS_GETRANGE, }; static GstElementClass *parent_class = NULL; @@ -164,6 +164,10 @@ gst_basesrc_init (GstBaseSrc * basesrc, gpointer g_class) gst_pad_set_checkgetrange_function (pad, gst_basesrc_check_get_range); + basesrc->is_live = FALSE; + basesrc->live_lock = g_mutex_new (); + basesrc->live_cond = g_cond_new (); + /* hold ref to pad */ basesrc->srcpad = pad; gst_element_add_pad (GST_ELEMENT (basesrc), pad); @@ -176,6 +180,26 @@ gst_basesrc_init (GstBaseSrc * basesrc, gpointer g_class) GST_FLAG_UNSET (basesrc, GST_BASESRC_STARTED); } +void +gst_basesrc_set_live (GstBaseSrc * src, gboolean live) +{ + GST_LIVE_LOCK (src); + src->is_live = live; + GST_LIVE_UNLOCK (src); +} + +gboolean +gst_basesrc_is_live (GstBaseSrc * src) +{ + gboolean result; + + GST_LIVE_LOCK (src); + result = src->is_live; + GST_LIVE_UNLOCK (src); + + return result; +} + static void gst_basesrc_set_dataflow_funcs (GstBaseSrc * this) { @@ -460,6 +484,16 @@ gst_basesrc_get_range (GstPad * pad, guint64 offset, guint length, src = GST_BASESRC (GST_OBJECT_PARENT (pad)); bclass = GST_BASESRC_GET_CLASS (src); + GST_LIVE_LOCK (src); + if (src->is_live) { + while (!src->live_running) { + GST_DEBUG ("live source waiting for running state"); + GST_LIVE_WAIT (src); + GST_DEBUG ("live source unlocked"); + } + } + GST_LIVE_UNLOCK (src); + if (!GST_FLAG_IS_SET (src, GST_BASESRC_STARTED)) goto not_started; @@ -725,6 +759,11 @@ gst_basesrc_activate (GstPad * pad, GstActivateMode mode) gst_basesrc_stop (basesrc); break; case GST_ACTIVATE_NONE: + GST_LIVE_LOCK (basesrc); + basesrc->live_running = TRUE; + GST_LIVE_SIGNAL (basesrc); + GST_LIVE_UNLOCK (basesrc); + /* step 1, unblock clock sync (if any) */ gst_basesrc_unlock (basesrc); @@ -746,7 +785,8 @@ static GstElementStateReturn gst_basesrc_change_state (GstElement * element) { GstBaseSrc *basesrc; - GstElementStateReturn result = GST_STATE_FAILURE; + GstElementStateReturn result = GST_STATE_SUCCESS; + GstElementStateReturn presult; GstElementState transition; basesrc = GST_BASESRC (element); @@ -757,17 +797,35 @@ gst_basesrc_change_state (GstElement * element) case GST_STATE_NULL_TO_READY: break; case GST_STATE_READY_TO_PAUSED: + GST_LIVE_LOCK (element); + if (basesrc->is_live) { + result = GST_STATE_NO_PREROLL; + basesrc->live_running = FALSE; + } + GST_LIVE_UNLOCK (element); break; case GST_STATE_PAUSED_TO_PLAYING: + GST_LIVE_LOCK (element); + basesrc->live_running = TRUE; + GST_LIVE_SIGNAL (element); + GST_LIVE_UNLOCK (element); break; default: break; } - result = GST_ELEMENT_CLASS (parent_class)->change_state (element); + if ((presult = GST_ELEMENT_CLASS (parent_class)->change_state (element)) != + GST_STATE_SUCCESS) + return presult; switch (transition) { case GST_STATE_PLAYING_TO_PAUSED: + GST_LIVE_LOCK (element); + if (basesrc->is_live) { + result = GST_STATE_NO_PREROLL; + basesrc->live_running = FALSE; + } + GST_LIVE_UNLOCK (element); break; case GST_STATE_PAUSED_TO_READY: if (!gst_basesrc_stop (basesrc)) diff --git a/libs/gst/base/gstbasesrc.h b/libs/gst/base/gstbasesrc.h index 98b48a9..eb39a3d 100644 --- a/libs/gst/base/gstbasesrc.h +++ b/libs/gst/base/gstbasesrc.h @@ -54,11 +54,28 @@ typedef struct _GstBaseSrcClass GstBaseSrcClass; #define GST_BASESRC_PAD(obj) (GST_BASESRC (obj)->srcpad) +#define GST_LIVE_GET_LOCK(elem) (GST_BASESRC(elem)->live_lock) +#define GST_LIVE_LOCK(elem) g_mutex_lock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_TRYLOCK(elem) g_mutex_trylock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_UNLOCK(elem) g_mutex_unlock(GST_LIVE_GET_LOCK(elem)) +#define GST_LIVE_GET_COND(elem) (GST_BASESRC(elem)->live_cond) +#define GST_LIVE_WAIT(elem) g_cond_wait (GST_LIVE_GET_COND (elem), GST_LIVE_GET_LOCK (elem)) +#define GST_LIVE_TIMED_WAIT(elem, timeval) g_cond_timed_wait (GST_LIVE_GET_COND (elem), GST_LIVE_GET_LOCK (elem),\ + timeval) +#define GST_LIVE_SIGNAL(elem) g_cond_signal (GST_LIVE_GET_COND (elem)); +#define GST_LIVE_BROADCAST(elem) g_cond_broadcast (GST_LIVE_GET_COND (elem)); + + struct _GstBaseSrc { GstElement element; GstPad *srcpad; + /*< protected >*/ /* with LIVE_LOCK */ + GMutex *live_lock; + GCond *live_cond; + gboolean is_live; + gboolean live_running; /*< protected >*/ /* with LOCK */ gint blocksize; /* size of buffers when operating push based */ gboolean has_loop; /* some scheduling properties */ @@ -114,6 +131,9 @@ struct _GstBaseSrcClass { GType gst_basesrc_get_type(void); +void gst_basesrc_set_live (GstBaseSrc *src, gboolean live); +gboolean gst_basesrc_is_live (GstBaseSrc *src); + G_END_DECLS #endif /* __GST_BASESRC_H__ */ diff --git a/plugins/elements/gstfakesrc.c b/plugins/elements/gstfakesrc.c index e020726..57d944d 100644 --- a/plugins/elements/gstfakesrc.c +++ b/plugins/elements/gstfakesrc.c @@ -90,7 +90,8 @@ enum PROP_PARENTSIZE, PROP_LAST_MESSAGE, PROP_HAS_LOOP, - PROP_HAS_GETRANGE + PROP_HAS_GETRANGE, + PROP_IS_LIVE }; #define GST_TYPE_FAKESRC_OUTPUT (gst_fakesrc_output_get_type()) @@ -284,6 +285,10 @@ gst_fakesrc_class_init (GstFakeSrcClass * klass) g_param_spec_boolean ("has-getrange", "Has getrange function", "True if the element exposes a getrange function", TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_IS_LIVE, + g_param_spec_boolean ("is-live", "Is this a live source", + "True if the element cannot produce data in PAUSED", FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); gst_fakesrc_signals[SIGNAL_HANDOFF] = g_signal_new ("handoff", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, @@ -361,8 +366,10 @@ gst_fakesrc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstFakeSrc *src; + GstBaseSrc *basesrc; src = GST_FAKESRC (object); + basesrc = GST_BASESRC (object); switch (prop_id) { case PROP_OUTPUT: @@ -428,6 +435,9 @@ gst_fakesrc_set_property (GObject * object, guint prop_id, const GValue * value, g_return_if_fail (!GST_FLAG_IS_SET (object, GST_BASESRC_STARTED)); src->has_getrange = g_value_get_boolean (value); break; + case PROP_IS_LIVE: + gst_basesrc_set_live (basesrc, g_value_get_boolean (value)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -439,11 +449,13 @@ gst_fakesrc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstFakeSrc *src; + GstBaseSrc *basesrc; /* it's not null if we got it, but it might not be ours */ g_return_if_fail (GST_IS_FAKESRC (object)); src = GST_FAKESRC (object); + basesrc = GST_BASESRC (object); switch (prop_id) { case PROP_OUTPUT: @@ -500,6 +512,9 @@ gst_fakesrc_get_property (GObject * object, guint prop_id, GValue * value, case PROP_HAS_GETRANGE: g_value_set_boolean (value, src->has_getrange); break; + case PROP_IS_LIVE: + g_value_set_boolean (value, gst_basesrc_is_live (basesrc)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 5ceb002..01e8e6e 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -39,6 +39,7 @@ TESTS = $(top_builddir)/tools/gst-register \ gst/gstvalue \ pipelines/simple_launch_lines \ pipelines/cleanup \ + states/sinks \ gst-libs/gdp check_PROGRAMS = $(TESTS) diff --git a/tests/check/generic/sinks.c b/tests/check/generic/sinks.c new file mode 100644 index 0000000..9ab6af7 --- /dev/null +++ b/tests/check/generic/sinks.c @@ -0,0 +1,205 @@ +/* GStreamer + * + * unit test for sinks + * + * Copyright (C) <2005> Wim Taymans + * + * 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 "../gstcheck.h" + +/* a sink should go ASYNC to PAUSE. forcing PLAYING is possible */ +START_TEST (test_sink) +{ + GstElement *sink; + GstElementStateReturn ret; + GstElementState current, pending; + + sink = gst_element_factory_make ("fakesink", "sink"); + + ret = gst_element_set_state (sink, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_ASYNC, "no async state return"); + + ret = gst_element_set_state (sink, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_SUCCESS, "cannot force play"); + + ret = gst_element_get_state (sink, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_SUCCESS, "not playing"); + fail_unless (current == GST_STATE_PLAYING, "not playing"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); +} + +END_TEST +/* a sink should go ASYNC to PAUSE. PAUSE should complete when + * prerolled. */ +START_TEST (test_src_sink) +{ + GstElement *sink, *src, *pipeline; + GstElementStateReturn ret; + GstElementState current, pending; + GstPad *srcpad, *sinkpad; + + pipeline = gst_pipeline_new ("pipeline"); + src = gst_element_factory_make ("fakesrc", "src"); + sink = gst_element_factory_make ("fakesink", "sink"); + + gst_bin_add (GST_BIN (pipeline), src); + gst_bin_add (GST_BIN (pipeline), sink); + + srcpad = gst_element_get_pad (src, "src"); + sinkpad = gst_element_get_pad (sink, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (GST_OBJECT (srcpad)); + gst_object_unref (GST_OBJECT (sinkpad)); + + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_SUCCESS, "no success state return"); + + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_SUCCESS, "cannot start play"); + + ret = gst_element_get_state (pipeline, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_SUCCESS, "not playing"); + fail_unless (current == GST_STATE_PLAYING, "not playing"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); +} + +END_TEST +/* a pipeline with live source should return NO_PREROLL in + * PAUSE. When removing the live source it should return ASYNC + * from the sink */ +START_TEST (test_livesrc_remove) +{ + GstElement *sink, *src, *pipeline; + GstElementStateReturn ret; + GstElementState current, pending; + GstPad *srcpad, *sinkpad; + GTimeVal tv; + + 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); + + srcpad = gst_element_get_pad (src, "src"); + sinkpad = gst_element_get_pad (sink, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (GST_OBJECT (srcpad)); + gst_object_unref (GST_OBJECT (sinkpad)); + + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_NO_PREROLL, "no no_preroll state return"); + + ret = gst_element_get_state (src, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_NO_PREROLL, "not paused"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + + gst_bin_remove (GST_BIN (pipeline), src); + + GST_TIME_TO_TIMEVAL (0, tv); + ret = gst_element_get_state (pipeline, ¤t, &pending, &tv); + fail_unless (ret == GST_STATE_ASYNC, "not async"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + +} + +END_TEST +/* a sink should go ASYNC to PAUSE. PAUSE does not complete + * since we have a live source. */ +START_TEST (test_livesrc_sink) +{ + GstElement *sink, *src, *pipeline; + GstElementStateReturn ret; + GstElementState 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"); + + gst_bin_add (GST_BIN (pipeline), src); + gst_bin_add (GST_BIN (pipeline), sink); + + srcpad = gst_element_get_pad (src, "src"); + sinkpad = gst_element_get_pad (sink, "sink"); + gst_pad_link (srcpad, sinkpad); + gst_object_unref (GST_OBJECT (srcpad)); + gst_object_unref (GST_OBJECT (sinkpad)); + + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + fail_unless (ret == GST_STATE_NO_PREROLL, "no no_preroll state return"); + + ret = gst_element_get_state (src, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_NO_PREROLL, "not paused"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + + ret = gst_element_get_state (pipeline, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_NO_PREROLL, "not paused"); + fail_unless (current == GST_STATE_PAUSED, "not paused"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); + + ret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + fail_unless (ret == GST_STATE_SUCCESS, "cannot force play"); + + ret = gst_element_get_state (pipeline, ¤t, &pending, NULL); + fail_unless (ret == GST_STATE_SUCCESS, "not playing"); + fail_unless (current == GST_STATE_PLAYING, "not playing"); + fail_unless (pending == GST_STATE_VOID_PENDING, "not playing"); +} + +END_TEST +/* test: try changing state of sinks */ + Suite * gst_object_suite (void) +{ + Suite *s = suite_create ("Sinks"); + TCase *tc_chain = tcase_create ("general"); + + /* turn off timeout */ + tcase_set_timeout (tc_chain, 60); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_sink); + tcase_add_test (tc_chain, test_src_sink); + tcase_add_test (tc_chain, test_livesrc_remove); + tcase_add_test (tc_chain, test_livesrc_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; +} diff --git a/tools/gst-launch.c b/tools/gst-launch.c index a5325f5..425d56a 100644 --- a/tools/gst-launch.c +++ b/tools/gst-launch.c @@ -537,6 +537,7 @@ main (int argc, char *argv[]) if (!savefile) { GstElementState state, pending; + GstElementStateReturn ret; if (!GST_IS_BIN (pipeline)) { GstElement *real_pipeline = gst_element_factory_make ("pipeline", NULL); @@ -549,14 +550,26 @@ main (int argc, char *argv[]) pipeline = real_pipeline; } - fprintf (stderr, _("PREROLL pipeline ...\n")); - if (gst_element_set_state (pipeline, GST_STATE_PAUSED) == GST_STATE_FAILURE) { - fprintf (stderr, _("ERROR: pipeline doesn't want to pause.\n")); - res = -1; - goto end; + fprintf (stderr, _("PAUSE pipeline ...\n")); + ret = gst_element_set_state (pipeline, GST_STATE_PAUSED); + + switch (ret) { + case GST_STATE_FAILURE: + fprintf (stderr, _("ERROR: pipeline doesn't want to pause.\n")); + res = -1; + goto end; + case GST_STATE_NO_PREROLL: + fprintf (stderr, _("NO_PREROLL pipeline ...\n")); + break; + case GST_STATE_ASYNC: + fprintf (stderr, _("PREROLL pipeline ...\n")); + gst_element_get_state (pipeline, &state, &pending, NULL); + /* fallthrough */ + case GST_STATE_SUCCESS: + fprintf (stderr, _("PREROLLED pipeline ...\n")); + break; } - gst_element_get_state (pipeline, &state, &pending, NULL); caught_error = event_loop (pipeline, FALSE); /* see if we got any messages */ -- 2.7.4