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

ChangeLog
gst/gstbin.c
gst/gstbin.h
gst/gstelement.c
tests/check/generic/sinks.c

index 6143768..ba7c92e 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,35 @@
+2007-06-18  Wim Taymans  <wim@fluendo.com>
+
+       * gst/gstbin.c: (gst_bin_init), (gst_bin_add_func),
+       (gst_bin_remove_func), (gst_bin_get_state_func),
+       (gst_bin_element_set_state), (gst_bin_continue_func),
+       (bin_push_state_continue), (bin_handle_async_start),
+       (bin_handle_async_done), (gst_bin_handle_message_func):
+       * gst/gstbin.h:
+       Immediatly commit the toplevel bin state when receiving an async-done
+       message. This enables us to avoid spawning a thread to commit the state
+       in some common cases and it also avoids some races.
+       Avoid spawning a state thread when adding/removing async elements to a
+       toplevel bin. Instead we immediatly update the bin state.
+       Get rid of iterating all the children when getting the state in the bin
+       because it is now always up-to-date.
+       Fix bug where locked elements would always return _SUCCESS even it they
+       returned NO_PREROLL before being locked.
+       Fix the order of the state_change, async-start/done messages that was
+       sometimes incorrect.
+       Mark the state_dirty field as deprecated, we don't need it anymore as we
+       are always up-to-date.
+
+       * gst/gstelement.c: (gst_element_get_state_func),
+       (gst_element_continue_state):
+       Small debug inprovements.
+       Return the previous element state return when nothing is pending instead
+       of blindly returning SUCCESS.
+
+       * tests/check/generic/sinks.c: (GST_START_TEST), (pad_blocked_cb),
+       (gst_sinks_suite):
+       Add a whole bunch of new testcases.
+
 2007-06-17  Thomas Vander Stichele  <thomas at apestaart dot org>
 
        * po/uk.po:
index 9442be8..c94979a 100644 (file)
@@ -196,6 +196,13 @@ struct _GstBinPrivate
   gboolean asynchandling;
 };
 
+typedef struct
+{
+  GstBin *bin;
+  guint32 cookie;
+  GstState pending;
+} BinContinueData;
+
 static void gst_bin_dispose (GObject * object);
 
 static void gst_bin_set_property (GObject * object, guint prop_id,
@@ -208,8 +215,9 @@ static GstStateChangeReturn gst_bin_change_state_func (GstElement * element,
 static GstStateChangeReturn gst_bin_get_state_func (GstElement * element,
     GstState * state, GstState * pending, GstClockTime timeout);
 static void bin_handle_async_done (GstBin * bin, GstMessage ** smessage,
-    GstMessage ** amessage);
-static void bin_push_state_continue (GstBin * bin);
+    GstMessage ** amessage, BinContinueData ** cont, GstStateChangeReturn ret);
+static void bin_handle_async_start (GstBin * bin, GstMessage ** message);
+static void bin_push_state_continue (BinContinueData * data);
 
 static gboolean gst_bin_add_func (GstBin * bin, GstElement * element);
 static gboolean gst_bin_remove_func (GstBin * bin, GstElement * element);
@@ -233,7 +241,7 @@ static void gst_bin_restore_thyself (GstObject * object, xmlNodePtr self);
 
 static void bin_remove_messages (GstBin * bin, GstObject * src,
     GstMessageType types);
-static void gst_bin_continue_func (GstBin * child, gpointer data);
+static void gst_bin_continue_func (BinContinueData * data);
 static gint bin_element_is_sink (GstElement * child, GstBin * bin);
 static gint bin_element_is_src (GstElement * child, GstBin * bin);
 
@@ -460,7 +468,6 @@ gst_bin_init (GstBin * bin)
   bin->children_cookie = 0;
   bin->messages = NULL;
   bin->provided_clock = NULL;
-  bin->state_dirty = FALSE;
   bin->clock_dirty = FALSE;
 
   /* Set up a bus for listening to child elements */
@@ -847,7 +854,12 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
   gchar *elem_name;
   GstIterator *it;
   gboolean is_sink;
-  GstMessage *clock_message = NULL;
+  GstMessage *clock_message = NULL, *async_message = NULL, *state_message =
+      NULL;
+  BinContinueData *cont = NULL;
+  GstStateChangeReturn ret;
+
+  GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element));
 
   /* we obviously can't add ourself to ourself */
   if (G_UNLIKELY (GST_ELEMENT_CAST (element) == GST_ELEMENT_CAST (bin)))
@@ -890,6 +902,32 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
   bin->numchildren++;
   bin->children_cookie++;
 
+  ret = GST_STATE_RETURN (bin);
+
+  /* no need to update the state if we are in error */
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    goto no_state_recalc;
+
+  /* update the bin state, the new element could have been an ASYNC or
+   * NO_PREROLL element */
+  ret = GST_STATE_RETURN (element);
+  GST_DEBUG_OBJECT (bin, "added %s element",
+      gst_element_state_change_return_get_name (ret));
+
+  switch (ret) {
+    case GST_STATE_CHANGE_ASYNC:
+      bin_handle_async_start (bin, &state_message);
+      break;
+    case GST_STATE_CHANGE_NO_PREROLL:
+      bin_handle_async_done (bin, &state_message, &async_message, &cont, ret);
+      break;
+    case GST_STATE_CHANGE_FAILURE:
+      break;
+    default:
+      break;
+  }
+
+no_state_recalc:
   /* distribute the bus */
   gst_element_set_bus (element, bin->child_bus);
 
@@ -899,13 +937,19 @@ gst_bin_add_func (GstBin * bin, GstElement * element)
    * that is not important right now. When the pipeline goes to PLAYING,
    * a new clock will be selected */
   gst_element_set_clock (element, GST_ELEMENT_CLOCK (bin));
-  GST_DEBUG_OBJECT (bin, "marking state dirty");
-  bin->state_dirty = TRUE;
   GST_OBJECT_UNLOCK (bin);
 
-  if (clock_message) {
+  if (state_message)
+    gst_element_post_message (GST_ELEMENT_CAST (bin), state_message);
+
+  if (async_message)
+    gst_element_post_message (GST_ELEMENT_CAST (bin), async_message);
+
+  if (clock_message)
     gst_element_post_message (GST_ELEMENT_CAST (bin), clock_message);
-  }
+
+  if (cont)
+    bin_push_state_continue (cont);
 
   /* unlink all linked pads */
   it = gst_element_iterate_pads (element);
@@ -1001,11 +1045,13 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
 {
   gchar *elem_name;
   GstIterator *it;
-  gboolean is_sink;
+  gboolean is_sink, othersink, found;
   GstMessage *clock_message = NULL, *async_message = NULL, *state_message =
       NULL;
+  BinContinueData *cont = NULL;
   GList *walk, *next;
-  gboolean other_async = FALSE, this_async = FALSE, toplevel = FALSE;
+  gboolean other_async, this_async, have_no_preroll;
+  GstStateChangeReturn ret;
 
   GST_DEBUG_OBJECT (bin, "element :%s", GST_ELEMENT_NAME (element));
 
@@ -1027,26 +1073,48 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
   gst_iterator_free (it);
 
   GST_OBJECT_LOCK (bin);
-  /* the element must be in the bin's list of children */
-  if (G_UNLIKELY (g_list_find (bin->children, element) == NULL))
+  found = FALSE;
+  othersink = FALSE;
+  have_no_preroll = FALSE;
+  /* iterate the elements, we collect which ones are async and no_preroll. We
+   * also remove the element when we find it. */
+  for (walk = bin->children; walk; walk = next) {
+    GstElement *child = GST_ELEMENT_CAST (walk->data);
+
+    next = g_list_next (walk);
+
+    if (child == element) {
+      found = TRUE;
+      /* remove the element */
+      bin->children = g_list_delete_link (bin->children, walk);
+    } else {
+      gboolean child_sink;
+
+      GST_OBJECT_LOCK (child);
+      child_sink = GST_OBJECT_FLAG_IS_SET (child, GST_ELEMENT_IS_SINK);
+      /* when we remove a sink, check if there are other sinks. */
+      if (is_sink && !othersink && child_sink)
+        othersink = TRUE;
+      /* check if we have NO_PREROLL children */
+      if (GST_STATE_RETURN (child) == GST_STATE_CHANGE_NO_PREROLL)
+        have_no_preroll = TRUE;
+      GST_OBJECT_UNLOCK (child);
+    }
+  }
+
+  /* the element must have been in the bin's list of children */
+  if (G_UNLIKELY (!found))
     goto not_in_bin;
 
-  /* now remove the element from the list of elements */
-  bin->children = g_list_remove (bin->children, element);
+  /* we now removed the element from the list of elements, increment the cookie
+   * so that others can detect a change in the children list. */
   bin->numchildren--;
   bin->children_cookie++;
 
-  /* check if we removed a sink */
-  if (is_sink) {
-    GList *other_sink;
-
-    /* check if we removed the last sink */
-    other_sink = g_list_find_custom (bin->children,
-        bin, (GCompareFunc) bin_element_is_sink);
-    if (!other_sink) {
-      /* yups, we're not a sink anymore */
-      GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_IS_SINK);
-    }
+  if (is_sink && !othersink) {
+    /* we're not a sink anymore */
+    GST_DEBUG_OBJECT (bin, "we removed the last sink");
+    GST_OBJECT_FLAG_UNSET (bin, GST_ELEMENT_IS_SINK);
   }
 
   /* if the clock provider for this element is removed, we lost
@@ -1062,6 +1130,8 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
   /* remove messages for the element, if there was a pending ASYNC_START
    * message we must see if removing the element caused the bin to lose its
    * async state. */
+  this_async = FALSE;
+  other_async = FALSE;
   for (walk = bin->messages; walk; walk = next) {
     GstMessage *message = (GstMessage *) walk->data;
     GstElement *src = GST_ELEMENT_CAST (GST_MESSAGE_SRC (message));
@@ -1085,18 +1155,50 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
       gst_message_unref (message);
     }
   }
-  /* all other elements were not async and we removed the async one,
-   * post a ASYNC_DONE message because we are not async anymore now. */
+
+  /* get last return */
+  ret = GST_STATE_RETURN (bin);
+
+  /* no need to update the state if we are in error */
+  if (ret == GST_STATE_CHANGE_FAILURE)
+    goto no_state_recalc;
+
   if (!other_async && this_async) {
-    GST_DEBUG_OBJECT (bin, "we removed the last async element");
+    GstStateChangeReturn ret;
+
+    /* all other elements were not async and we removed the async one,
+     * handle the async-done case because we are not async anymore now. */
+    GST_DEBUG_OBJECT (bin,
+        "we removed the last async element, have no_preroll %d",
+        have_no_preroll);
+
+    /* the current state return of the bin depends on if there are no_preroll
+     * elements in the pipeline or not */
+    if (have_no_preroll)
+      ret = GST_STATE_CHANGE_NO_PREROLL;
+    else
+      ret = GST_STATE_CHANGE_SUCCESS;
 
-    toplevel = ((GST_OBJECT_PARENT (bin) == NULL) || bin->priv->asynchandling);
-    if (!toplevel) {
-      bin_handle_async_done (bin, &state_message, &async_message);
+    bin_handle_async_done (bin, &state_message, &async_message, &cont, ret);
+  } else {
+    GST_DEBUG_OBJECT (bin,
+        "recalc state preroll: %d, other async: %d, this async %d",
+        have_no_preroll, other_async, this_async);
+
+    if (have_no_preroll) {
+      ret = GST_STATE_CHANGE_NO_PREROLL;
+    } else if (other_async) {
+      /* there are other async elements and we were not doing an async state
+       * change, change our pending state and go async */
+      if (GST_STATE_PENDING (bin) == GST_STATE_VOID_PENDING) {
+        GST_STATE_NEXT (bin) = GST_STATE (bin);
+        GST_STATE_PENDING (bin) = GST_STATE (bin);
+      }
+      ret = GST_STATE_CHANGE_ASYNC;
     }
+    GST_STATE_RETURN (bin) = ret;
   }
-  GST_DEBUG_OBJECT (bin, "marking state dirty");
-  bin->state_dirty = TRUE;
+no_state_recalc:
   GST_OBJECT_UNLOCK (bin);
 
   if (clock_message)
@@ -1108,8 +1210,8 @@ gst_bin_remove_func (GstBin * bin, GstElement * element)
   if (async_message)
     gst_element_post_message (GST_ELEMENT_CAST (bin), async_message);
 
-  if (toplevel)
-    bin_push_state_continue (bin);
+  if (cont)
+    bin_push_state_continue (cont);
 
   GST_CAT_INFO_OBJECT (GST_CAT_PARENTAGE, bin, "removed child \"%s\"",
       elem_name);
@@ -1423,171 +1525,6 @@ gst_bin_iterate_sources (GstBin * bin)
   return result;
 }
 
-static void
-gst_bin_recalc_state (GstBin * bin, gboolean force)
-{
-  GstStateChangeReturn ret;
-  GList *children;
-  guint32 children_cookie;
-  guint32 state_cookie;
-  gboolean have_no_preroll;
-  gboolean have_async;
-
-  ret = GST_STATE_CHANGE_SUCCESS;
-
-  /* lock bin, no element can be added or removed while we have this lock */
-  GST_OBJECT_LOCK (bin);
-  /* forced recalc, make state dirty again */
-  if (force)
-    bin->state_dirty = TRUE;
-
-  /* no point in scanning if nothing changed and it's no forced recalc */
-  if (!bin->state_dirty)
-    goto not_dirty;
-
-  /* no point in having two scans run concurrently */
-  if (bin->polling)
-    goto was_polling;
-
-  bin->polling = TRUE;
-  state_cookie = GST_ELEMENT_CAST (bin)->state_cookie;
-
-  GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "recalc state");
-
-restart:
-  /* when we leave this function, the state must not be dirty, whenever
-   * we are scanning and the state becomes dirty again, we restart. */
-  bin->state_dirty = FALSE;
-
-  have_no_preroll = FALSE;
-  have_async = FALSE;
-
-  GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "checking element states");
-
-  /* scan all element states with a zero timeout so we don't block on
-   * anything */
-  children = bin->children;
-  children_cookie = bin->children_cookie;
-  while (children) {
-    GstElement *child = GST_ELEMENT_CAST (children->data);
-
-    gst_object_ref (child);
-    /* now we release the lock to enter a non blocking wait. We
-     * release the lock anyway since we can. */
-    GST_OBJECT_UNLOCK (bin);
-
-    ret = gst_element_get_state (child, NULL, NULL, 0);
-
-    gst_object_unref (child);
-
-    /* now grab the lock to iterate to the next child */
-    GST_OBJECT_LOCK (bin);
-    if (G_UNLIKELY (children_cookie != bin->children_cookie)) {
-      /* child added/removed during state change, restart. We need
-       * to restart with the quick check as a no-preroll element could
-       * have been added here and we don't want to block on sinks then.*/
-      GST_DEBUG_OBJECT (bin, "children added or removed, restarting recalc");
-      goto restart;
-    }
-    if (state_cookie != GST_ELEMENT_CAST (bin)->state_cookie) {
-      GST_DEBUG_OBJECT (bin, "concurrent state change");
-      goto concurrent_state;
-    }
-    if (bin->state_dirty) {
-      GST_DEBUG_OBJECT (bin, "state dirty again, restarting recalc");
-      goto restart;
-    }
-
-    switch (ret) {
-      case GST_STATE_CHANGE_FAILURE:
-        /* report FAILURE  immediatly */
-        goto done;
-      case GST_STATE_CHANGE_NO_PREROLL:
-        /* we have to continue scanning as there might be
-         * ERRORS too */
-        have_no_preroll = TRUE;
-        break;
-      case GST_STATE_CHANGE_ASYNC:
-        /* we have to continue scanning as there might be
-         * ERRORS too */
-        have_async = TRUE;
-        break;
-      default:
-        break;
-    }
-    children = g_list_next (children);
-  }
-  /* if we get here, we have no FAILURES */
-
-  /* if we have NO_PREROLL, return that */
-  if (have_no_preroll) {
-    ret = GST_STATE_CHANGE_NO_PREROLL;
-  }
-  /* else return ASYNC if async elements where found. */
-  else if (have_async) {
-    ret = GST_STATE_CHANGE_ASYNC;
-  }
-
-done:
-  bin->polling = FALSE;
-  GST_STATE_RETURN (bin) = ret;
-  GST_OBJECT_UNLOCK (bin);
-
-  /* now we can take the state lock, it is possible that new elements
-   * are added now and we still report the old state. No problem though as
-   * the return is still consistent, the effect is as if the element was
-   * added after this function completed. */
-  switch (ret) {
-    case GST_STATE_CHANGE_SUCCESS:
-    case GST_STATE_CHANGE_NO_PREROLL:
-      ret = gst_element_continue_state (GST_ELEMENT_CAST (bin), ret);
-      break;
-    case GST_STATE_CHANGE_ASYNC:
-      gst_element_lost_state (GST_ELEMENT_CAST (bin));
-      break;
-    case GST_STATE_CHANGE_FAILURE:
-      gst_element_abort_state (GST_ELEMENT_CAST (bin));
-      break;
-    default:
-      goto unknown_state;
-  }
-
-  GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "bin RETURN is now %s",
-      gst_element_state_change_return_get_name (ret));
-
-  return;
-
-not_dirty:
-  {
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "not dirty");
-    GST_OBJECT_UNLOCK (bin);
-    return;
-  }
-was_polling:
-  {
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "was polling");
-    GST_OBJECT_UNLOCK (bin);
-    return;
-  }
-concurrent_state:
-  {
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "concurrent_state");
-    bin->polling = FALSE;
-    GST_OBJECT_UNLOCK (bin);
-    return;
-  }
-unknown_state:
-  {
-    /* somebody added a GST_STATE_ and forgot to do stuff here ! */
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
-        "unknown return value %d from a state change function", ret);
-    g_critical ("unknown return value %d from a state change function", ret);
-    GST_STATE_RETURN (bin) = GST_STATE_CHANGE_FAILURE;
-    GST_STATE_UNLOCK (bin);
-    return;
-  }
-}
-
 /*
  * MT safe
  */
@@ -1600,11 +1537,6 @@ gst_bin_get_state_func (GstElement * element, GstState * state,
 
   GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "getting state");
 
-  /* do a non forced recalculation of the state */
-  GST_STATE_LOCK (bin);
-  gst_bin_recalc_state (bin, FALSE);
-  GST_STATE_UNLOCK (bin);
-
   ret = parent_class->get_state (element, state, pending, timeout);
 
   return ret;
@@ -1905,6 +1837,8 @@ gst_bin_element_set_state (GstBin * bin, GstElement * element,
   /* peel off the locked flag */
   GST_OBJECT_LOCK (element);
   locked = GST_OBJECT_FLAG_IS_SET (element, GST_ELEMENT_LOCKED_STATE);
+  /* get previous state return */
+  ret = GST_STATE_RETURN (element);
   GST_OBJECT_UNLOCK (element);
 
   /* skip locked elements */
@@ -1957,8 +1891,9 @@ no_latency:
 locked:
   {
     GST_DEBUG_OBJECT (element,
-        "element is locked, pretending state change succeeded");
-    return GST_STATE_CHANGE_SUCCESS;
+        "element is locked, return previous return %s",
+        gst_element_state_change_return_get_name (ret));
+    return ret;
   }
 was_busy:
   {
@@ -2264,12 +2199,14 @@ gst_bin_send_event (GstElement * element, GstEvent * event)
 }
 
 static void
-gst_bin_continue_func (GstBin * bin, gpointer data)
+gst_bin_continue_func (BinContinueData * data)
 {
-  GstState current, next, pending, target, old_state, old_next;
-  GstStateChangeReturn old_ret, ret;
-  GstStateChange transition;
-  gboolean post;
+  GstBin *bin;
+  GstState current, next, pending, transition;
+  GstStateChangeReturn ret;
+
+  bin = data->bin;
+  pending = data->pending;
 
   GST_DEBUG_OBJECT (bin, "waiting for state lock");
   GST_STATE_LOCK (bin);
@@ -2277,43 +2214,7 @@ gst_bin_continue_func (GstBin * bin, gpointer data)
   GST_DEBUG_OBJECT (bin, "doing state continue");
   GST_OBJECT_LOCK (bin);
 
-  old_ret = GST_STATE_RETURN (bin);
-  GST_STATE_RETURN (bin) = GST_STATE_CHANGE_SUCCESS;
-  target = GST_STATE_TARGET (bin);
-  pending = GST_STATE_PENDING (bin);
-
-  /* check if there is something to commit */
-  if (pending == GST_STATE_VOID_PENDING)
-    goto nothing_pending;
-
-  old_state = GST_STATE (bin);
-  /* this is the state we should go to next */
-  old_next = GST_STATE_NEXT (bin);
-
-  GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "target %s",
-      gst_element_state_get_name (target));
-
-  if (old_next == GST_STATE_PLAYING) {
-    post = FALSE;
-  } else {
-    post = TRUE;
-  }
-
-  /* we're going to PLAYING, always do the PAUSED->PLAYING state change */
-  if (target == GST_STATE_PLAYING) {
-    next = GST_STATE_PAUSED;
-    GST_STATE_PENDING (bin) = pending = target;
-  } else {
-    next = old_next;
-  }
-
-  /* update current state */
-  current = GST_STATE (bin) = next;
-
-  /* see if we reached the final state */
-  if (pending == current)
-    goto complete;
-
+  current = GST_STATE (bin);
   next = GST_STATE_GET_NEXT (current, pending);
   transition = (GstStateChange) GST_STATE_TRANSITION (current, next);
 
@@ -2322,22 +2223,6 @@ gst_bin_continue_func (GstBin * bin, gpointer data)
   GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
   GST_OBJECT_UNLOCK (bin);
 
-  if (post) {
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
-        "committing state from %s to %s, pending %s",
-        gst_element_state_get_name (old_state),
-        gst_element_state_get_name (old_next),
-        gst_element_state_get_name (pending));
-
-    gst_element_post_message (GST_ELEMENT_CAST (bin),
-        gst_message_new_state_changed (GST_OBJECT_CAST (bin),
-            old_state, old_next, pending));
-  }
-
-  GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE");
-  gst_element_post_message (GST_ELEMENT_CAST (bin),
-      gst_message_new_async_done (GST_OBJECT_CAST (bin)));
-
   GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
       "continue state change %s to %s, final %s",
       gst_element_state_get_name (current),
@@ -2345,58 +2230,10 @@ gst_bin_continue_func (GstBin * bin, gpointer data)
 
   ret = gst_element_change_state (GST_ELEMENT_CAST (bin), transition);
 
-done:
   GST_STATE_UNLOCK (bin);
   GST_DEBUG_OBJECT (bin, "state continue done");
   gst_object_unref (bin);
-
-  return;
-
-nothing_pending:
-  {
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending");
-    GST_OBJECT_UNLOCK (bin);
-
-    GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE");
-    gst_element_post_message (GST_ELEMENT_CAST (bin),
-        gst_message_new_async_done (GST_OBJECT_CAST (bin)));
-
-    goto done;
-  }
-complete:
-  {
-    GST_STATE_PENDING (bin) = GST_STATE_VOID_PENDING;
-    GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
-
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "completed state change to %s",
-        gst_element_state_get_name (pending));
-    GST_OBJECT_UNLOCK (bin);
-
-    /* don't post silly messages with the same state. This can happen
-     * when an element state is changed to what it already was. For bins
-     * this can be the result of a lost state, which we check with the
-     * previous return value.
-     * We do signal the cond though as a _get_state() might be blocking
-     * on it. */
-    if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
-      GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
-          "changed %s to %s, VOID pending",
-          gst_element_state_get_name (old_state),
-          gst_element_state_get_name (old_next));
-
-      gst_element_post_message (GST_ELEMENT_CAST (bin),
-          gst_message_new_state_changed (GST_OBJECT_CAST (bin),
-              old_state, old_next, GST_STATE_VOID_PENDING));
-    }
-
-    GST_DEBUG_OBJECT (bin, "posting ASYNC_DONE");
-    gst_element_post_message (GST_ELEMENT_CAST (bin),
-        gst_message_new_async_done (GST_OBJECT_CAST (bin)));
-
-    GST_STATE_BROADCAST (bin);
-
-    goto done;
-  }
+  g_free (data);
 }
 
 static GstBusSyncReply
@@ -2414,16 +2251,18 @@ bin_bus_handler (GstBus * bus, GstMessage * message, GstBin * bin)
 }
 
 static void
-bin_push_state_continue (GstBin * bin)
+bin_push_state_continue (BinContinueData * data)
 {
   GstBinClass *klass;
+  GstBin *bin;
 
-  /* mark the bin dirty */
-  GST_OBJECT_LOCK (bin);
+  /* ref was taken */
+  bin = data->bin;
   klass = GST_BIN_GET_CLASS (bin);
+
+  GST_OBJECT_LOCK (bin);
   GST_DEBUG_OBJECT (bin, "pushing continue on thread pool");
-  gst_object_ref (bin);
-  g_thread_pool_push (klass->pool, bin, NULL);
+  g_thread_pool_push (klass->pool, data, NULL);
   GST_OBJECT_UNLOCK (bin);
 }
 
@@ -2441,6 +2280,10 @@ bin_handle_async_start (GstBin * bin, GstMessage ** message)
   if (GST_STATE_PENDING (bin) != GST_STATE_VOID_PENDING)
     goto was_busy;
 
+  /* async starts are ignored when we are NO_PREROLL */
+  if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_NO_PREROLL)
+    goto was_no_preroll;
+
   old_state = GST_STATE (bin);
 
   /* when we PLAYING we go back to PAUSED, when preroll happens, we go back to
@@ -2474,88 +2317,111 @@ was_busy:
     GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "state change busy");
     return;
   }
+was_no_preroll:
+  {
+    GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "ignoring, we are NO_PREROLL");
+    return;
+  }
 }
 
 /* this function is called when there are no more async elements in the bin. We
  * post a state changed message and an ASYNC_DONE message. */
 static void
 bin_handle_async_done (GstBin * bin, GstMessage ** smessage,
-    GstMessage ** amessage)
+    GstMessage ** amessage, BinContinueData ** cont, GstStateChangeReturn ret)
 {
-  GstState pending;
+  GstState current, pending, target;
   GstStateChangeReturn old_ret;
   GstState old_state, old_next;
-  GstState current, next;
   gboolean toplevel;
 
-  old_ret = GST_STATE_RETURN (bin);
-  GST_STATE_RETURN (bin) = GST_STATE_CHANGE_SUCCESS;
+  if (GST_STATE_RETURN (bin) == GST_STATE_CHANGE_FAILURE)
+    goto had_error;
+
   pending = GST_STATE_PENDING (bin);
 
   /* check if there is something to commit */
   if (pending == GST_STATE_VOID_PENDING)
     goto nothing_pending;
 
+  old_ret = GST_STATE_RETURN (bin);
+  GST_STATE_RETURN (bin) = ret;
+
+  /* move to the next target state */
+  target = GST_STATE_TARGET (bin);
+  pending = GST_STATE_PENDING (bin) = target;
+
   *amessage = gst_message_new_async_done (GST_OBJECT_CAST (bin));
 
   old_state = GST_STATE (bin);
   /* this is the state we should go to next */
   old_next = GST_STATE_NEXT (bin);
-  /* update current state */
-  current = GST_STATE (bin) = old_next;
 
-  /* see if we need to continue the state change on our own. This happens when
-   * we were asked to do so or when we are the toplevel bin. */
+  if (old_next != GST_STATE_PLAYING) {
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
+        "committing state from %s to %s, old pending %s",
+        gst_element_state_get_name (old_state),
+        gst_element_state_get_name (old_next),
+        gst_element_state_get_name (pending));
+
+    /* update current state */
+    current = GST_STATE (bin) = old_next;
+  } else {
+    current = old_state;
+  }
+
+  /* get our toplevel state */
   toplevel = ((GST_OBJECT_PARENT (bin) == NULL) || bin->priv->asynchandling);
 
-  /* see if we reached the final state. If we are not a toplevel bin we also
-   * must stop at this state change, the parent will set us to the required
-   * state eventually. */
-  if (pending == current || !toplevel)
-    goto complete;
+  /* see if we reached the final state. If we are not toplevel, we also have to
+   * stop here, the parent will continue our state. */
+  if ((pending == current) || !toplevel) {
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
+        "completed state change, pending VOID");
 
-  next = GST_STATE_GET_NEXT (current, pending);
+    /* mark VOID pending */
+    pending = GST_STATE_VOID_PENDING;
+    GST_STATE_PENDING (bin) = pending;
+    GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
 
-  GST_STATE_NEXT (bin) = next;
-  /* mark busy */
-  GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
+    GST_STATE_BROADCAST (bin);
+  } else {
+    BinContinueData *data;
 
-  GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
-      "committing state from %s to %s, pending %s",
-      gst_element_state_get_name (old_state),
-      gst_element_state_get_name (old_next),
-      gst_element_state_get_name (pending));
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin,
+        "continue state change, pending %s",
+        gst_element_state_get_name (pending));
 
-  *smessage = gst_message_new_state_changed (GST_OBJECT_CAST (bin),
-      old_state, old_next, pending);
+    data = g_new0 (BinContinueData, 1);
 
-  return;
+    /* ref to the bin */
+    data->bin = gst_object_ref (bin);
+    /* cookie to detect concurrent state change */
+    data->cookie = GST_ELEMENT_CAST (bin)->state_cookie;
+    /* pending target state */
+    data->pending = pending;
+    /* mark busy */
+    GST_STATE_RETURN (bin) = GST_STATE_CHANGE_ASYNC;
 
-nothing_pending:
-  {
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending");
-    return;
+    *cont = data;
   }
-complete:
-  {
-    GST_STATE_PENDING (bin) = GST_STATE_VOID_PENDING;
-    GST_STATE_NEXT (bin) = GST_STATE_VOID_PENDING;
-
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "completed state change");
 
-    /* don't post silly messages with the same state. This can happen
-     * when an element state is changed to what it already was. For bins
-     * this can be the result of a lost state, which we check with the
-     * previous return value.
-     * We do signal the cond though as a _get_state() might be blocking
-     * on it. */
+  if (old_next != GST_STATE_PLAYING) {
     if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
       *smessage = gst_message_new_state_changed (GST_OBJECT_CAST (bin),
-          old_state, old_next, GST_STATE_VOID_PENDING);
+          old_state, old_next, pending);
     }
+  }
+  return;
 
-    GST_STATE_BROADCAST (bin);
-
+had_error:
+  {
+    GST_CAT_DEBUG_OBJECT (GST_CAT_STATES, bin, "we had an error");
+    return;
+  }
+nothing_pending:
+  {
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, bin, "nothing pending");
     return;
   }
 }
@@ -2772,6 +2638,7 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
       if (target <= GST_STATE_READY)
         goto ignore_start_message;
 
+      /* takes ownership of the message */
       bin_replace_message (bin, message, GST_MESSAGE_ASYNC_START);
 
       bin_handle_async_start (bin, &smessage);
@@ -2786,6 +2653,7 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
         message =
             gst_message_new_async_start (GST_OBJECT_CAST (bin), new_base_time);
       } else {
+        /* toplevel bin, we don't post async start */
         message = NULL;
       }
       GST_OBJECT_UNLOCK (bin);
@@ -2810,9 +2678,9 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
     }
     case GST_MESSAGE_ASYNC_DONE:
     {
-      gboolean toplevel = FALSE;
       GstState target;
       GstMessage *smessage = NULL, *amessage = NULL;
+      BinContinueData *cont = NULL;
 
       GST_DEBUG_OBJECT (bin, "ASYNC_DONE message %p, %s", message,
           GST_OBJECT_NAME (src));
@@ -2831,10 +2699,8 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
         /* nothing found, remove all old ASYNC_DONE messages */
         bin_remove_messages (bin, NULL, GST_MESSAGE_ASYNC_DONE);
 
-        toplevel = ((GST_OBJECT_PARENT (bin) == NULL)
-            || bin->priv->asynchandling);
-        if (!toplevel)
-          bin_handle_async_done (bin, &smessage, &amessage);
+        bin_handle_async_done (bin, &smessage, &amessage, &cont,
+            GST_STATE_CHANGE_SUCCESS);
       }
       GST_OBJECT_UNLOCK (bin);
 
@@ -2848,10 +2714,10 @@ gst_bin_handle_message_func (GstBin * bin, GstMessage * message)
         gst_element_post_message (GST_ELEMENT_CAST (bin), amessage);
       }
 
-      if (toplevel) {
+      if (cont) {
         /* toplevel, start continue state */
         GST_DEBUG_OBJECT (bin, "all async-done, starting state continue");
-        bin_push_state_continue (bin);
+        bin_push_state_continue (cont);
       }
       break;
 
index 93606f3..1a2714d 100644 (file)
@@ -86,7 +86,7 @@ typedef struct _GstBinPrivate GstBinPrivate;
  * @child_bus: internal bus for handling child messages
  * @messages: queued and cached messages
  * @polling: the bin is currently calculating its state
- * @state_dirty: the bin needs to recalculate its state
+ * @state_dirty: the bin needs to recalculate its state (deprecated)
  * @clock_dirty: the bin needs to select a new clock
  * @provided_clock: the last clock selected
  * @clock_provider: the element that provided @provided_clock
index 56e0c6c..d135202 100644 (file)
@@ -1858,11 +1858,11 @@ gst_element_get_state_func (GstElement * element,
         ret = GST_STATE_CHANGE_FAILURE;
       }
     }
-  }
-  /* if nothing is pending anymore we can return SUCCESS */
-  if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) {
-    GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "nothing pending");
-    ret = GST_STATE_CHANGE_SUCCESS;
+    /* if nothing is pending anymore we can return SUCCESS */
+    if (GST_STATE_PENDING (element) == GST_STATE_VOID_PENDING) {
+      GST_CAT_LOG_OBJECT (GST_CAT_STATES, element, "nothing pending");
+      ret = GST_STATE_CHANGE_SUCCESS;
+    }
   }
 
 done:
@@ -1887,7 +1887,7 @@ interrupted:
     if (pending)
       *pending = GST_STATE_VOID_PENDING;
 
-    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "get_state() interruped");
+    GST_CAT_INFO_OBJECT (GST_CAT_STATES, element, "interruped");
 
     GST_OBJECT_UNLOCK (element);
 
@@ -2100,8 +2100,13 @@ complete:
      * We do signal the cond though as a _get_state() might be blocking
      * on it. */
     if (old_state != old_next || old_ret == GST_STATE_CHANGE_ASYNC) {
-      message = gst_message_new_state_changed (GST_OBJECT_CAST (element),
-          old_state, old_next, GST_STATE_VOID_PENDING);
+      GST_CAT_INFO_OBJECT (GST_CAT_STATES, element,
+          "posting state-changed %s to %s",
+          gst_element_state_get_name (old_state),
+          gst_element_state_get_name (old_next));
+      message =
+          gst_message_new_state_changed (GST_OBJECT_CAST (element), old_state,
+          old_next, GST_STATE_VOID_PENDING);
       gst_element_post_message (element, message);
     }
 
@@ -2891,7 +2896,8 @@ gst_element_set_bus (GstElement * element, GstBus * bus)
  * gst_element_get_bus:
  * @element: a #GstElement to get the bus of.
  *
- * Returns the bus of the element.
+ * Returns the bus of the element. Note that only a #GstPipeline will provide a
+ * bus for the application.
  *
  * Returns: the element's #GstBus. unref after usage.
  *
index bacfd7f..acfad82 100644 (file)
@@ -450,6 +450,402 @@ GST_START_TEST (test_livesrc3_sink)
 
 GST_END_TEST;
 
+GST_START_TEST (test_locked_sink)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  gst_bin_add (GST_BIN (pipeline), src);
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* we don't link the elements */
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not no_preroll");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  /* the sink is now async going from ready to paused */
+  ret = gst_element_get_state (sink, &current, &pending, 0);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not async");
+  fail_unless (current == GST_STATE_READY, "not ready");
+  fail_unless (pending == GST_STATE_PAUSED, "not paused");
+
+  /* lock the sink */
+  gst_element_set_locked_state (sink, TRUE);
+
+  /* move to PlAYING, the sink should remain ASYNC. The pipeline
+   * returns ASYNC */
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* back to PAUSED, we should get NO_PREROLL again */
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* unlock the sink */
+  gst_element_set_locked_state (sink, FALSE);
+
+  /* and now everything back down */
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no success state return");
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_unlinked_live)
+{
+  GstElement *sink, *src, *lsrc, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  lsrc = gst_element_factory_make ("fakesrc", "lsrc");
+  g_object_set (G_OBJECT (lsrc), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  gst_bin_add (GST_BIN (pipeline), src);
+  gst_bin_add (GST_BIN (pipeline), lsrc);
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* link non live source to sink */
+  srcpad = gst_element_get_pad (src, "src");
+  sinkpad = gst_element_get_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  /* we don't link the srcpad of the live source, it will not contribute to the
+   * NO_PREROLL. */
+
+  /* set state to PAUSED, this should return NO_PREROLL because there is a live
+   * source. since the only sink in this pipeline is linked to a non-live
+   * source, it will preroll eventually. */
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* wait till the sink is prerolled */
+  ret = gst_element_get_state (sink, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not success");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have playing");
+
+  /* the pipeline should still return NO_PREROLL */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not no_preroll");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have playing");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_delayed_async)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add source, don't add sink yet */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* add sink now and set to PAUSED */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* This will make the bin notice an ASYNC element. */
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* we should still be NO_PREROLL now although there is an async element in the
+   * pipeline. */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  /* link live source to sink */
+  srcpad = gst_element_get_pad (src, "src");
+  sinkpad = gst_element_get_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* we should get SUCCESS now */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_added_async)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add source, don't add sink yet */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* set sink to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* add sink now, pipeline should notice the async element */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* we should still be NO_PREROLL now although there is an async element in the
+   * pipeline. */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  /* link live source to sink */
+  srcpad = gst_element_get_pad (src, "src");
+  sinkpad = gst_element_get_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* we should get SUCCESS now */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_added_async2)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add source, don't add sink yet */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "no SUCCESS state return");
+
+  /* set sink to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (sink, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* add sink now, pipeline should notice the async element */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* we should be ASYNC now because there is an async element in the
+   * pipeline. */
+  ret = gst_element_get_state (pipeline, &current, &pending, 0);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "not ASYNC");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_PAUSED, "not paused");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_add_live)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add sink, don't add sourc3 yet */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  ret = gst_element_set_state (pipeline, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  /* set source to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (src, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* add source now, pipeline should notice the NO_PREROLL element */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  /* we should be NO_PREROLL now because there is a NO_PREROLL element in the
+   * pipeline. */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL, "not NO_PREROLL");
+  fail_unless (current == GST_STATE_PAUSED, "not paused");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
+static GMutex *blocked_lock;
+static GCond *blocked_cond;
+
+static void
+pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data)
+{
+  g_mutex_lock (blocked_lock);
+  GST_DEBUG ("srcpad blocked: %d, sending signal", blocked);
+  g_cond_signal (blocked_cond);
+  g_mutex_unlock (blocked_lock);
+}
+
+GST_START_TEST (test_add_live2)
+{
+  GstElement *sink, *src, *pipeline;
+  GstStateChangeReturn ret;
+  GstState current, pending;
+  GstPad *srcpad, *sinkpad;
+
+  blocked_lock = g_mutex_new ();
+  blocked_cond = g_cond_new ();
+
+  pipeline = gst_pipeline_new ("pipeline");
+  src = gst_element_factory_make ("fakesrc", "src");
+  g_object_set (G_OBJECT (src), "is-live", TRUE, NULL);
+  sink = gst_element_factory_make ("fakesink", "sink");
+
+  /* add sink, don't add source yet */
+  gst_bin_add (GST_BIN (pipeline), sink);
+
+  /* set the pipeline to PLAYING. This will return ASYNC on READY->PAUSED */
+  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+  fail_unless (ret == GST_STATE_CHANGE_ASYNC, "no ASYNC state return");
+
+  g_mutex_lock (blocked_lock);
+
+  GST_DEBUG ("blocking srcpad");
+  /* block source pad */
+  srcpad = gst_element_get_pad (src, "src");
+  gst_pad_set_blocked_async (srcpad, TRUE, pad_blocked_cb, NULL);
+
+  /* set source to PAUSED without adding it to the pipeline */
+  ret = gst_element_set_state (src, GST_STATE_PAUSED);
+  fail_unless (ret == GST_STATE_CHANGE_NO_PREROLL,
+      "no NO_PREROLL state return");
+
+  /* add source now, pipeline should notice the NO_PREROLL element. This
+   * should trigger as commit of the ASYNC pipeline and make it continue
+   * to PLAYING. We blocked the source pad so that we don't get an unlinked
+   * error. */
+  gst_bin_add (GST_BIN (pipeline), src);
+
+  /* wait for pad blocked, this means the source is now PLAYING. */
+  g_cond_wait (blocked_cond, blocked_lock);
+  g_mutex_unlock (blocked_lock);
+
+  GST_DEBUG ("linking pads");
+
+  /* link to sink */
+  sinkpad = gst_element_get_pad (sink, "sink");
+  gst_pad_link (srcpad, sinkpad);
+  gst_object_unref (srcpad);
+  gst_object_unref (sinkpad);
+
+  GST_DEBUG ("unblocking srcpad");
+
+  /* and unblock */
+  gst_pad_set_blocked_async (srcpad, FALSE, pad_blocked_cb, NULL);
+
+  GST_DEBUG ("getting state");
+
+  /* we should be SUCCESS now and PLAYING */
+  ret =
+      gst_element_get_state (pipeline, &current, &pending, GST_CLOCK_TIME_NONE);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+  fail_unless (current == GST_STATE_PLAYING, "not PLAYING");
+  fail_unless (pending == GST_STATE_VOID_PENDING, "have pending");
+
+  ret = gst_element_set_state (pipeline, GST_STATE_NULL);
+  fail_unless (ret == GST_STATE_CHANGE_SUCCESS, "not SUCCESS");
+
+  g_cond_free (blocked_cond);
+  g_mutex_free (blocked_lock);
+  gst_object_unref (pipeline);
+}
+
+GST_END_TEST;
+
 /* test: try changing state of sinks */
 Suite *
 gst_sinks_suite (void)
@@ -465,6 +861,13 @@ gst_sinks_suite (void)
   tcase_add_test (tc_chain, test_livesrc_sink);
   tcase_add_test (tc_chain, test_livesrc2_sink);
   tcase_add_test (tc_chain, test_livesrc3_sink);
+  tcase_add_test (tc_chain, test_locked_sink);
+  tcase_add_test (tc_chain, test_unlinked_live);
+  tcase_add_test (tc_chain, test_delayed_async);
+  tcase_add_test (tc_chain, test_added_async);
+  tcase_add_test (tc_chain, test_added_async2);
+  tcase_add_test (tc_chain, test_add_live);
+  tcase_add_test (tc_chain, test_add_live2);
 
   return s;
 }