Added support for live sources and other elements that cannot do preroll.
authorWim Taymans <wim.taymans@gmail.com>
Thu, 23 Jun 2005 10:37:09 +0000 (10:37 +0000)
committerWim Taymans <wim.taymans@gmail.com>
Thu, 23 Jun 2005 10:37:09 +0000 (10:37 +0000)
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.

18 files changed:
ChangeLog
check/Makefile.am
check/states/sinks.c [new file with mode: 0644]
docs/design/part-live-source.txt [new file with mode: 0644]
docs/design/part-states.txt
gst/base/gstbasesrc.c
gst/base/gstbasesrc.h
gst/elements/gstfakesrc.c
gst/gstbin.c
gst/gstelement.c
gst/gstelement.h
gst/gsttypes.h
libs/gst/base/gstbasesrc.c
libs/gst/base/gstbasesrc.h
plugins/elements/gstfakesrc.c
tests/check/Makefile.am
tests/check/generic/sinks.c [new file with mode: 0644]
tools/gst-launch.c

index d05186d..5fcde55 100644 (file)
--- a/ChangeLog
+++ b/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
index 5ceb002..01e8e6e 100644 (file)
@@ -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 (file)
index 0000000..9ab6af7
--- /dev/null
@@ -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, &current, &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, &current, &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, &current, &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, &current, &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, &current, &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, &current, &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, &current, &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 (file)
index 0000000..9283bc7
--- /dev/null
@@ -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.
+
+
+
index a901632..d4dda06 100644 (file)
@@ -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
 -------------------------------
index a6bcf39..fdf7f2e 100644 (file)
@@ -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))
index 98b48a9..eb39a3d 100644 (file)
@@ -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__ */
index e020726..57d944d 100644 (file)
@@ -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;
index 4bafa09..af4a342 100644 (file)
@@ -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);
index decf647..6a24a80 100644 (file)
@@ -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, &current, &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);
index 3b4f456..ed78adb 100644 (file)
@@ -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;
index 8b369a7..a00819b 100644 (file)
@@ -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 {
index a6bcf39..fdf7f2e 100644 (file)
@@ -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))
index 98b48a9..eb39a3d 100644 (file)
@@ -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__ */
index e020726..57d944d 100644 (file)
@@ -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;
index 5ceb002..01e8e6e 100644 (file)
@@ -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 (file)
index 0000000..9ab6af7
--- /dev/null
@@ -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, &current, &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, &current, &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, &current, &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, &current, &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, &current, &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, &current, &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, &current, &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;
+}
index a5325f5..425d56a 100644 (file)
@@ -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 */