mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-02-17 03:35:21 +00:00
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.
This commit is contained in:
parent
b8d13efa9d
commit
1cda8197e9
18 changed files with 880 additions and 66 deletions
30
ChangeLog
30
ChangeLog
|
@ -1,3 +1,33 @@
|
|||
2005-06-23 Wim Taymans <wim@fluendo.com>
|
||||
|
||||
* 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 <wingo@pobox.com>
|
||||
|
||||
* check/gst/gstpad.c (test_get_allowed_caps, test_refcount): Fix
|
||||
|
|
|
@ -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)
|
||||
|
|
205
check/states/sinks.c
Normal file
205
check/states/sinks.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
/* 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 "../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;
|
||||
}
|
44
docs/design/part-live-source.txt
Normal file
44
docs/design/part-live-source.txt
Normal file
|
@ -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.
|
||||
|
||||
|
||||
|
|
@ -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
|
||||
-------------------------------
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
|
|
144
gst/gstbin.c
144
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;
|
||||
|
||||
/* we cannot take the state lock yet as we might block when querying
|
||||
* the children, holding the lock too long for no reason. */
|
||||
GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "getting state");
|
||||
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 <g> */
|
||||
#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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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__ */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
205
tests/check/generic/sinks.c
Normal file
205
tests/check/generic/sinks.c
Normal file
|
@ -0,0 +1,205 @@
|
|||
/* 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 "../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;
|
||||
}
|
|
@ -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 */
|
||||
|
|
Loading…
Reference in a new issue