check: allow GstTestClock to handle clock notifications
authorSebastian Rasmussen <sebrn@axis.com>
Wed, 29 Aug 2012 23:58:41 +0000 (01:58 +0200)
committerTim-Philipp Müller <tim@centricular.net>
Tue, 13 Nov 2012 21:47:01 +0000 (21:47 +0000)
API: gst_test_clock_peek_id_count()
API: gst_test_clock_has_id()
API: gst_test_clock_peek_next_pending_id()
API: gst_test_clock_wait_for_next_pending_id()
API: gst_test_clock_wait_for_pending_id_count()
API: gst_test_clock_process_next_clock_id()
API: gst_test_clock_get_next_entry_time()

https://bugzilla.gnome.org/show_bug.cgi?id=683012

docs/libs/gstreamer-libs-sections.txt
libs/gst/check/Makefile.am
libs/gst/check/gsttestclock.c
libs/gst/check/gsttestclock.h
tests/check/libs/gsttestclock.c

index 3ed14c4..d9f6b9d 100644 (file)
@@ -1009,6 +1009,13 @@ gst_test_clock_new
 gst_test_clock_new_with_start_time
 gst_test_clock_set_time
 gst_test_clock_advance_time
+gst_test_clock_peek_id_count
+gst_test_clock_has_id
+gst_test_clock_peek_next_pending_id
+gst_test_clock_wait_for_next_pending_id
+gst_test_clock_wait_for_pending_id_count
+gst_test_clock_process_next_clock_id
+gst_test_clock_get_next_entry_time
 <SUBSECTION Standard>
 GST_TEST_CLOCK
 GST_IS_TEST_CLOCK
index 03fab2a..599abf7 100644 (file)
@@ -94,7 +94,14 @@ LIBGSTCHECK_EXPORTED_FUNCS = \
        gst_test_clock_new \
        gst_test_clock_new_with_start_time \
        gst_test_clock_set_time \
-       gst_test_clock_advance_time
+       gst_test_clock_advance_time \
+       gst_test_clock_peek_id_count \
+       gst_test_clock_has_id \
+       gst_test_clock_peek_next_pending_id \
+       gst_test_clock_wait_for_next_pending_id \
+       gst_test_clock_wait_for_pending_id_count \
+       gst_test_clock_process_next_clock_id \
+       gst_test_clock_get_next_entry_time
 
 LIBGSTCHECK_EXPORTED_SYMBOLS = \
        $(LIBGSTCHECK_EXPORTED_VARS) \
index 01d6003..70e8f64 100644 (file)
  *   </programlisting>
  * </example>
  *
+ * #GstClock allows for setting up single shot or periodic clock notifications
+ * as well as waiting for these notifications synchronously (using
+ * gst_clock_id_wait()) or asynchronously (using gst_clock_id_wait_async() or
+ * gst_clock_id_wait_async_full()). This is used by many GStreamer elements,
+ * among them #GstBaseSrc and #GstBaseSink.
+ *
+ * #GstTestClock keeps track of these clock notifications. By calling
+ * gst_test_clock_wait_for_next_pending_id() or
+ * gst_test_clock_wait_for_pending_id_count() a unit tests may wait for the
+ * next one or several clock notifications to be requested. Additionally unit
+ * tests may release blocked waits in a controlled fashion by calling
+ * gst_test_clock_process_next_clock_id(). This way a unit test can control the
+ * inaccuracy (jitter) of clock notifications, since the test can decide to
+ * release blocked waits when the clock time has advanced exactly to, or past,
+ * the requested clock notification time.
+ *
+ * There are also interfaces for determining if a notification belongs to a
+ * #GstTestClock or not, as well as getting the number of requested clock
+ * notifications so far.
+ *
+ * N.B.: When a unit test waits for a certain amount of clock notifications to
+ * be requested in gst_test_clock_wait_for_next_pending_id() or
+ * gst_test_clock_wait_for_pending_id_count() then these functions may block
+ * for a long time. If they block forever then the expected clock notifications
+ * were never requested from #GstTestClock, and so the assumptions in the code
+ * of the unit test are wrong. The unit test case runner in #GstCheck is
+ * expected to catch these cases either by the default test case timeout or the
+ * one set for the unit test by calling tcase_set_timeout().
+ *
+ * The sample code below assumes that the element under test will delay a
+ * buffer pushed on the source pad by some latency until it arrives on the sink
+ * pad. Moreover it is assumed that the element will at some point call
+ * gst_clock_id_wait() to synchronously wait for a specific time. The first
+ * buffer sent will arrive exactly on time only delayed by the latency. The
+ * second buffer will arrive a little late (7ms) due to simulated jitter in the
+ * clock notification.
+ *
+ * <example>
+ * <title>Demonstration of how to work with clock notifications and #GstTestClock</title>
+ *   <programlisting language="c">
+ *   #include &lt;gst/gst.h&gt;
+ *   #include &lt;gst/check/gstcheck.h&gt;
+ *   #include &lt;gst/check/gsttestclock.h&gt;
+ *
+ *   GstClockTime latency;
+ *   GstElement *element;
+ *   GstPad *srcpad;
+ *   GstClock *clock;
+ *   GstTestClock *test_clock;
+ *   GstBuffer buf;
+ *   GstClockID pending_id;
+ *   GstClockID processed_id;
+ *
+ *   latency = 42 * GST_MSECOND;
+ *   element = create_element (latency, ...);
+ *   srcpad = get_source_pad (element);
+ *
+ *   clock = gst_test_clock_new ();
+ *   test_clock = GST_TEST_CLOCK (clock);
+ *   gst_element_set_clock (element, clock);
+ *
+ *   GST_INFO ("Set time, create and push the first buffer\n");
+ *   gst_test_clock_set_time (test_clock, 0);
+ *   buf = create_test_buffer (gst_clock_get_time (clock), ...);
+ *   gst_assert_cmpint (gst_pad_push (srcpad, buf), ==, GST_FLOW_OK);
+ *
+ *   GST_INFO ("Block until element is waiting for a clock notification\n");
+ *   gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+ *   GST_INFO ("Advance to the requested time of the clock notification\n");
+ *   gst_test_clock_advance_time (test_clock, latency);
+ *   GST_INFO ("Release the next blocking wait and make sure it is the one from element\n");
+ *   processed_id = gst_test_clock_process_next_clock_id (test_clock);
+ *   g_assert (processed_id == pending_id);
+ *   g_assert_cmpint (GST_CLOCK_ENTRY_STATUS (processed_id), ==, GST_CLOCK_OK);
+ *   gst_clock_id_unref (pending_id);
+ *   gst_clock_id_unref (processed_id);
+ *
+ *   GST_INFO ("Validate that element produced an output buffer and check its timestamp\n");
+ *   g_assert_cmpint (get_number_of_output_buffer (...), ==, 1);
+ *   buf = get_buffer_pushed_by_element (element, ...);
+ *   g_assert_cmpint (GST_BUFFER_TIMESTAMP (buf), ==, latency);
+ *   gst_buffer_unref (buf);
+ *   GST_INFO ("Check that element does not wait for any clock notification\n");
+ *   g_assert (gst_test_clock_peek_next_pending_id (test_clock, NULL) == FALSE);
+ *
+ *   GST_INFO ("Set time, create and push the second buffer\n");
+ *   gst_test_clock_advance_time (test_clock, 10 * GST_SECOND);
+ *   buf = create_test_buffer (gst_clock_get_time (clock), ...);
+ *   gst_assert_cmpint (gst_pad_push (srcpad, buf), ==, GST_FLOW_OK);
+ *
+ *   GST_INFO ("Block until element is waiting for a new clock notification\n");
+ *   (gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+ *   GST_INFO ("Advance past 7ms beyond the requested time of the clock notification\n");
+ *   gst_test_clock_advance_time (test_clock, latency + 7 * GST_MSECOND);
+ *   GST_INFO ("Release the next blocking wait and make sure it is the one from element\n");
+ *   processed_id = gst_test_clock_process_next_clock_id (test_clock);
+ *   g_assert (processed_id == pending_id);
+ *   g_assert_cmpint (GST_CLOCK_ENTRY_STATUS (processed_id), ==, GST_CLOCK_OK);
+ *   gst_clock_id_unref (pending_id);
+ *   gst_clock_id_unref (processed_id);
+ *
+ *   GST_INFO ("Validate that element produced an output buffer and check its timestamp\n");
+ *   g_assert_cmpint (get_number_of_output_buffer (...), ==, 1);
+ *   buf = get_buffer_pushed_by_element (element, ...);
+ *   g_assert_cmpint (GST_BUFFER_TIMESTAMP (buf), ==,
+ *       10 * GST_SECOND + latency + 7 * GST_MSECOND);
+ *   gst_buffer_unref (buf);
+ *   GST_INFO ("Check that element does not wait for any clock notification\n");
+ *   g_assert (gst_test_clock_peek_next_pending_id (test_clock, NULL) == FALSE);
+ *   ...
+ *   </programlisting>
+ * </example>
+ *
  * Since #GstTestClock is only supposed to be used in unit tests it calls
  * g_assert(), g_assert_cmpint() or g_assert_cmpuint() to validate all function
  * arguments. This will highlight any issues with the unit test code itself.
@@ -80,6 +193,9 @@ struct _GstTestClockPrivate
 {
   GstClockTime start_time;
   GstClockTime internal_time;
+  GList *entry_contexts;
+  GCond *entry_added_cond;
+  GCond *entry_processed_cond;
 };
 
 #define GST_TEST_CLOCK_GET_PRIVATE(obj) ((GST_TEST_CLOCK_CAST (obj))->priv)
@@ -108,6 +224,25 @@ static void gst_test_clock_set_property (GObject * object, guint property_id,
 
 static GstClockTime gst_test_clock_get_resolution (GstClock * clock);
 static GstClockTime gst_test_clock_get_internal_time (GstClock * clock);
+static GstClockReturn gst_test_clock_wait (GstClock * clock,
+    GstClockEntry * entry, GstClockTimeDiff * jitter);
+static GstClockReturn gst_test_clock_wait_async (GstClock * clock,
+    GstClockEntry * entry);
+static void gst_test_clock_unschedule (GstClock * clock, GstClockEntry * entry);
+
+static gboolean gst_test_clock_peek_next_pending_id_unlocked (GstTestClock *
+    test_clock, GstClockID * pending_id);
+static guint gst_test_clock_peek_id_count_unlocked (GstTestClock * test_clock);
+
+static void gst_test_clock_add_entry (GstTestClock * test_clock,
+    GstClockEntry * entry, GstClockTimeDiff * jitter);
+static void gst_test_clock_remove_entry (GstTestClock * test_clock,
+    GstClockEntry * entry);
+static GstClockEntryContext *gst_test_clock_lookup_entry_context (
+    GstTestClock * test_clock, GstClockEntry * clock_entry);
+
+static gint gst_clock_entry_context_compare_func (gconstpointer a,
+    gconstpointer b);
 
 static void
 gst_test_clock_class_init (GstTestClockClass * klass)
@@ -130,6 +265,9 @@ gst_test_clock_class_init (GstTestClockClass * klass)
       gst_test_clock_get_resolution);
   gstclock_class->get_internal_time = GST_DEBUG_FUNCPTR (
       gst_test_clock_get_internal_time);
+  gstclock_class->wait = GST_DEBUG_FUNCPTR (gst_test_clock_wait);
+  gstclock_class->wait_async = GST_DEBUG_FUNCPTR (gst_test_clock_wait_async);
+  gstclock_class->unschedule = GST_DEBUG_FUNCPTR (gst_test_clock_unschedule);
 
   /**
    * GstTestClock:start-time
@@ -149,9 +287,16 @@ gst_test_clock_class_init (GstTestClockClass * klass)
 static void
 gst_test_clock_init (GstTestClock * test_clock)
 {
+  GstTestClockPrivate *priv;
+
   test_clock->priv = G_TYPE_INSTANCE_GET_PRIVATE (test_clock,
       GST_TYPE_TEST_CLOCK, GstTestClockPrivate);
 
+  priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  priv->entry_added_cond = g_cond_new ();
+  priv->entry_processed_cond = g_cond_new ();
+
   GST_OBJECT_FLAG_SET (test_clock,
       GST_CLOCK_FLAG_CAN_DO_SINGLE_SYNC |
       GST_CLOCK_FLAG_CAN_DO_SINGLE_ASYNC |
@@ -166,17 +311,37 @@ gst_test_clock_constructed (GObject * object)
   GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
 
   priv->internal_time = priv->start_time;
+
+  G_OBJECT_CLASS (parent_class)->constructed (object);
 }
 
 static void
 gst_test_clock_dispose (GObject * object)
 {
+  GstTestClock *test_clock = GST_TEST_CLOCK (object);
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  GST_OBJECT_LOCK (test_clock);
+
+  while (priv->entry_contexts != NULL) {
+    GstClockEntryContext *ctx = priv->entry_contexts->data;
+    gst_test_clock_remove_entry (test_clock, ctx->clock_entry);
+  }
+
+  GST_OBJECT_UNLOCK (test_clock);
+
   G_OBJECT_CLASS (parent_class)->dispose (object);
 }
 
 static void
 gst_test_clock_finalize (GObject * object)
 {
+  GstTestClock *test_clock = GST_TEST_CLOCK (object);
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  g_cond_free (priv->entry_added_cond);
+  g_cond_free (priv->entry_processed_cond);
+
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
@@ -244,6 +409,166 @@ gst_test_clock_get_internal_time (GstClock * clock)
   return result;
 }
 
+static GstClockReturn
+gst_test_clock_wait (GstClock * clock,
+    GstClockEntry * entry, GstClockTimeDiff * jitter)
+{
+  GstTestClock *test_clock = GST_TEST_CLOCK (clock);
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  GST_OBJECT_LOCK (test_clock);
+
+  GST_CAT_DEBUG_OBJECT (GST_CAT_TEST_CLOCK, test_clock,
+      "requesting synchronous clock notification at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (GST_CLOCK_ENTRY_TIME (entry)));
+
+  if (gst_test_clock_lookup_entry_context (test_clock, entry) == NULL)
+    gst_test_clock_add_entry (test_clock, entry, jitter);
+
+  GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_BUSY;
+
+  while (GST_CLOCK_ENTRY_STATUS (entry) == GST_CLOCK_BUSY)
+    g_cond_wait (priv->entry_processed_cond,
+        GST_OBJECT_GET_LOCK (test_clock));
+
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return GST_CLOCK_ENTRY_STATUS (entry);
+}
+
+static GstClockReturn
+gst_test_clock_wait_async (GstClock * clock, GstClockEntry * entry)
+{
+  GstTestClock *test_clock = GST_TEST_CLOCK (clock);
+
+  GST_OBJECT_LOCK (test_clock);
+
+  GST_CAT_DEBUG_OBJECT (GST_CAT_TEST_CLOCK, test_clock,
+      "requesting asynchronous clock notification at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (GST_CLOCK_ENTRY_TIME (entry)));
+
+  gst_test_clock_add_entry (test_clock, entry, NULL);
+
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return GST_CLOCK_OK;
+}
+
+static void
+gst_test_clock_unschedule (GstClock * clock, GstClockEntry * entry)
+{
+  GstTestClock *test_clock = GST_TEST_CLOCK (clock);
+
+  GST_OBJECT_LOCK (test_clock);
+
+  GST_CAT_DEBUG_OBJECT (GST_CAT_TEST_CLOCK, test_clock,
+      "unscheduling requested clock notification at %" GST_TIME_FORMAT,
+      GST_TIME_ARGS (GST_CLOCK_ENTRY_TIME (entry)));
+
+  GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_UNSCHEDULED;
+  gst_test_clock_remove_entry (test_clock, entry);
+
+  GST_OBJECT_UNLOCK (test_clock);
+}
+
+static gboolean
+gst_test_clock_peek_next_pending_id_unlocked (GstTestClock * test_clock,
+    GstClockID * pending_id)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+  GList *imminent_clock_id = g_list_first (priv->entry_contexts);
+  gboolean result = FALSE;
+
+  if (imminent_clock_id != NULL) {
+    GstClockEntryContext *ctx = imminent_clock_id->data;
+
+    if (pending_id != NULL) {
+      *pending_id = gst_clock_id_ref (ctx->clock_entry);
+    }
+
+    result = TRUE;
+  }
+
+  return result;
+}
+
+static guint
+gst_test_clock_peek_id_count_unlocked (GstTestClock * test_clock)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  return g_list_length (priv->entry_contexts);
+}
+
+static void
+gst_test_clock_add_entry (GstTestClock * test_clock,
+    GstClockEntry * entry, GstClockTimeDiff * jitter)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+  GstClockTime now;
+  GstClockEntryContext *ctx;
+
+  now = gst_clock_adjust_unlocked (GST_CLOCK (test_clock),
+      priv->internal_time);
+
+  if (jitter != NULL)
+    *jitter = GST_CLOCK_DIFF (GST_CLOCK_ENTRY_TIME (entry), now);
+
+  ctx = g_slice_new (GstClockEntryContext);
+  ctx->clock_entry = GST_CLOCK_ENTRY (gst_clock_id_ref (entry));
+  ctx->time_diff = GST_CLOCK_DIFF (now, GST_CLOCK_ENTRY_TIME (entry));
+
+  priv->entry_contexts = g_list_insert_sorted (priv->entry_contexts, ctx,
+      gst_clock_entry_context_compare_func);
+
+  g_cond_broadcast (priv->entry_added_cond);
+}
+
+static void
+gst_test_clock_remove_entry (GstTestClock * test_clock, GstClockEntry * entry)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+  GstClockEntryContext *ctx;
+
+  ctx = gst_test_clock_lookup_entry_context (test_clock, entry);
+  if (ctx != NULL) {
+    gst_clock_id_unref (ctx->clock_entry);
+    priv->entry_contexts = g_list_remove (priv->entry_contexts, ctx);
+    g_slice_free (GstClockEntryContext, ctx);
+
+    g_cond_broadcast (priv->entry_processed_cond);
+  }
+}
+
+static GstClockEntryContext *
+gst_test_clock_lookup_entry_context (GstTestClock * test_clock,
+    GstClockEntry * clock_entry)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+  GstClockEntryContext *result = NULL;
+  GList *cur;
+
+  for (cur = priv->entry_contexts; cur != NULL; cur = cur->next) {
+    GstClockEntryContext *ctx = cur->data;
+
+    if (ctx->clock_entry == clock_entry) {
+      result = ctx;
+      break;
+    }
+  }
+
+  return result;
+}
+
+static gint
+gst_clock_entry_context_compare_func (gconstpointer a, gconstpointer b)
+{
+  const GstClockEntryContext *ctx_a = a;
+  const GstClockEntryContext *ctx_b = b;
+
+  return gst_clock_id_compare_func (ctx_a->clock_entry, ctx_b->clock_entry);
+}
+
 /**
  * gst_test_clock_new:
  *
@@ -337,3 +662,238 @@ gst_test_clock_advance_time (GstTestClock * test_clock,
 
   GST_OBJECT_UNLOCK (test_clock);
 }
+
+/**
+ * gst_test_clock_peek_id_count:
+ * @test_clock: a #GstTestClock for which to count notifications
+ *
+ * Determine the number of pending clock notifications that have been
+ * requested from the @test_clock.
+ *
+ * MT safe.
+ *
+ * Returns: the number of pending clock notifications.
+ */
+guint
+gst_test_clock_peek_id_count (GstTestClock * test_clock)
+{
+  guint result;
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+
+  GST_OBJECT_LOCK (test_clock);
+  result = gst_test_clock_peek_id_count_unlocked (test_clock);
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return result;
+}
+
+/**
+ * gst_test_clock_has_id:
+ * @test_clock: a #GstTestClock to ask if it provided the notification
+ * @id: (transfer none): a #GstClockID clock notification
+ *
+ * Checks whether @test_clock was requested to provide the clock notification
+ * given by @id.
+ *
+ * MT safe.
+ *
+ * Returns: %TRUE if the clock has been asked to provide the given clock
+ * notification, %FALSE otherwise.
+ */
+gboolean
+gst_test_clock_has_id (GstTestClock * test_clock, GstClockID id)
+{
+  gboolean result;
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+  g_assert (id != NULL);
+
+  GST_OBJECT_LOCK (test_clock);
+  result = gst_test_clock_lookup_entry_context (test_clock, id) != NULL;
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return result;
+}
+
+/**
+ * gst_test_clock_peek_next_pending_id:
+ * @test_clock: a #GstTestClock to check the clock notifications for
+ * @pending_id: (allow-none) (out) (transfer full): a #GstClockID clock
+ * notification to look for
+ *
+ * Determines if the @pending_id is the next clock notification scheduled to
+ * be triggered given the current time of the @test_clock.
+ *
+ * MT safe.
+ *
+ * Return: %TRUE if @pending_id is the next clock notification to be
+ * triggered, %FALSE otherwise.
+ */
+gboolean
+gst_test_clock_peek_next_pending_id (GstTestClock * test_clock,
+    GstClockID * pending_id)
+{
+  gboolean result;
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+
+  GST_OBJECT_LOCK (test_clock);
+  result = gst_test_clock_peek_next_pending_id_unlocked (test_clock,
+      pending_id);
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return result;
+}
+
+/**
+ * gst_test_clock_wait_for_next_pending_id:
+ * @test_clock: #GstTestClock for which to get the pending clock notification
+ * @pending_id: (allow-none) (out) (transfer full): #GstClockID
+ * with information about the pending clock notification
+ *
+ * Waits until a clock notification is requested from @test_clock. There is no
+ * timeout for this wait, see the main description of #GstTestClock. A reference
+ * to the pending clock notification is stored in @pending_id.
+ *
+ * MT safe.
+ */
+void
+gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock,
+    GstClockID * pending_id)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+
+  GST_OBJECT_LOCK (test_clock);
+
+  while (priv->entry_contexts == NULL)
+    g_cond_wait (priv->entry_added_cond, GST_OBJECT_GET_LOCK (test_clock));
+
+  g_assert (gst_test_clock_peek_next_pending_id_unlocked (test_clock, pending_id));
+
+  GST_OBJECT_UNLOCK (test_clock);
+}
+
+/**
+ * gst_test_clock_wait_for_pending_id_count:
+ * @test_clock: #GstTestClock for which to await having enough pending clock
+ * @count: the number of pending clock notifications to wait for
+ *
+ * Blocks until at least @count clock notifications have been requested from
+ * @test_clock. There is no timeout for this wait, see the main description of
+ * #GstTestClock.
+ *
+ * MT safe.
+ */
+void
+gst_test_clock_wait_for_pending_id_count (GstTestClock * test_clock, guint count)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+
+  GST_OBJECT_LOCK (test_clock);
+
+  while (gst_test_clock_peek_id_count_unlocked (test_clock) < count)
+    g_cond_wait (priv->entry_added_cond, GST_OBJECT_GET_LOCK (test_clock));
+
+  GST_OBJECT_UNLOCK (test_clock);
+}
+
+/**
+ * gst_test_clock_process_next_clock_id:
+ * @test_clock: a #GstTestClock for which to retrive the next pending clock
+ * notification
+ *
+ * MT safe.
+ *
+ * Returns: (transfer full): a #GstClockID containing the next pending clock
+ * notification.
+ */
+GstClockID
+gst_test_clock_process_next_clock_id (GstTestClock * test_clock)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+  GstClockID result = NULL;
+  GstClockEntryContext *ctx = NULL;
+  GList *cur;
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+
+  GST_OBJECT_LOCK (test_clock);
+
+  for (cur = priv->entry_contexts; cur != NULL && result == NULL;
+      cur = cur->next) {
+    ctx = cur->data;
+
+    if (priv->internal_time >= GST_CLOCK_ENTRY_TIME (ctx->clock_entry))
+      result = gst_clock_id_ref (ctx->clock_entry);
+  }
+
+  if (result != NULL) {
+    GstClockEntry *entry = ctx->clock_entry;
+
+    if (ctx->time_diff >= 0)
+      GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_OK;
+    else
+      GST_CLOCK_ENTRY_STATUS (entry) = GST_CLOCK_EARLY;
+
+    if (entry->func != NULL) {
+      GST_OBJECT_UNLOCK (test_clock);
+      entry->func (GST_CLOCK (test_clock), priv->internal_time, entry,
+          entry->user_data);
+      GST_OBJECT_LOCK (test_clock);
+    }
+
+    gst_test_clock_remove_entry (test_clock, entry);
+
+    if (GST_CLOCK_ENTRY_TYPE (entry) == GST_CLOCK_ENTRY_PERIODIC) {
+      GST_CLOCK_ENTRY_TIME (entry) += GST_CLOCK_ENTRY_INTERVAL (entry);
+
+      if (entry->func != NULL)
+        gst_test_clock_add_entry (test_clock, entry, NULL);
+    }
+  }
+
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return result;
+}
+
+/**
+ * gst_test_clock_get_next_entry_time:
+ * @test_clock: a #GstTestClock to fetch the next clock notification time for
+ *
+ * Retrieve the requested time for the next pending clock notification.
+ *
+ * MT safe.
+ *
+ * Returns: a #GstClockTime set to the time of the next pending clock
+ * notification. If no clock notifications have been requested
+ * %GST_CLOCK_TIME_NONE will be returned.
+ */
+GstClockTime
+gst_test_clock_get_next_entry_time (GstTestClock * test_clock)
+{
+  GstTestClockPrivate *priv = GST_TEST_CLOCK_GET_PRIVATE (test_clock);
+  GstClockTime result = GST_CLOCK_TIME_NONE;
+  GList *imminent_clock_id;
+
+  g_assert (GST_IS_TEST_CLOCK (test_clock));
+
+  GST_OBJECT_LOCK (test_clock);
+
+  /* The list of pending clock notifications is sorted by time,
+     so the most imminent one is the first one in the list. */
+  imminent_clock_id = g_list_first (priv->entry_contexts);
+  if (imminent_clock_id != NULL) {
+    GstClockEntryContext *ctx = imminent_clock_id->data;
+    result = GST_CLOCK_ENTRY_TIME (ctx->clock_entry);
+  }
+
+  GST_OBJECT_UNLOCK (test_clock);
+
+  return result;
+}
index 6d47e63..d5ef652 100644 (file)
@@ -79,6 +79,18 @@ void gst_test_clock_set_time (GstTestClock * test_clock, GstClockTime new_time);
 void gst_test_clock_advance_time (GstTestClock * test_clock,
     GstClockTimeDiff delta);
 
+guint gst_test_clock_peek_id_count (GstTestClock * test_clock);
+gboolean gst_test_clock_has_id (GstTestClock * test_clock, GstClockID id);
+gboolean gst_test_clock_peek_next_pending_id (GstTestClock * test_clock,
+    GstClockID * pending_id);
+void gst_test_clock_wait_for_next_pending_id (GstTestClock * test_clock,
+    GstClockID * pending_id);
+void gst_test_clock_wait_for_pending_id_count (GstTestClock * test_clock,
+    guint count);
+
+GstClockID gst_test_clock_process_next_clock_id (GstTestClock * test_clock);
+GstClockTime gst_test_clock_get_next_entry_time (GstTestClock * test_clock);
+
 G_END_DECLS
 
 #endif /* __GST_TEST_CLOCK_H__ */
index 0c8678f..19f9cb0 100644 (file)
 #include <gst/check/gstcheck.h>
 #include <gst/check/gsttestclock.h>
 
+typedef struct
+{
+  GstTestClock *test_clock;
+  GstClockID id;
+  GstClockTime reference;
+} GtuClockWaitContext;
+
+typedef struct
+{
+  GstClockID clock_id;
+  GstClockTimeDiff jitter;
+} SyncClockWaitContext;
+
+#define assert_pending_id(pending_id, id, type, time) \
+G_STMT_START { \
+  GstClockEntry *entry = GST_CLOCK_ENTRY (pending_id); \
+  g_assert (entry == (id)); \
+  g_assert (GST_CLOCK_ENTRY_TYPE (entry) == (type)); \
+  g_assert_cmpuint (GST_CLOCK_ENTRY_TIME (entry), ==, (time)); \
+} G_STMT_END
+
+#define assert_processed_id(processed_id, id, type, time) \
+G_STMT_START { \
+  GstClockEntry *entry = GST_CLOCK_ENTRY (processed_id); \
+  g_assert (entry == (id)); \
+  g_assert (GST_CLOCK_ENTRY_TYPE (entry) == (type)); \
+  g_assert_cmpuint (GST_CLOCK_ENTRY_STATUS (entry), ==, (time)); \
+} G_STMT_END
+
+static gpointer test_wait_pending_single_shot_id_sync_worker (gpointer data);
+static gpointer test_wait_pending_single_shot_id_async_worker (gpointer data);
+static gpointer test_wait_pending_periodic_id_waiter_thread (gpointer data);
+static gboolean test_async_wait_cb (GstClock * clock, GstClockTime time,
+    GstClockID id, gpointer user_data);
+
+static GtuClockWaitContext *gst_test_util_wait_for_clock_id_begin (GstTestClock
+    * clock, GstClockID id, GstClockTimeDiff * jitter);
+static GstClockReturn gst_test_util_wait_for_clock_id_end (GtuClockWaitContext *
+    wait_ctx);
+static gboolean
+gst_test_util_clock_wait_context_has_completed (GtuClockWaitContext * wait_ctx);
+
+static gpointer
+test_wait_pending_single_shot_id_sync_worker (gpointer data)
+{
+  SyncClockWaitContext *ctx = data;
+
+  gst_clock_id_wait (ctx->clock_id, &ctx->jitter);
+
+  return NULL;
+}
+
+static gpointer
+test_wait_pending_single_shot_id_async_worker (gpointer data)
+{
+  GstClockID clock_id = data;
+
+  g_usleep (G_USEC_PER_SEC / 10);
+  gst_clock_id_wait_async (clock_id, test_async_wait_cb, NULL, NULL);
+
+  return NULL;
+}
+
+static gpointer
+test_wait_pending_periodic_id_waiter_thread (gpointer data)
+{
+  GstClockID clock_id = data;
+  gst_clock_id_wait (clock_id, NULL);
+  return NULL;
+}
+
+static gboolean
+test_async_wait_cb (GstClock * clock,
+    GstClockTime time, GstClockID id, gpointer user_data)
+{
+
+  gboolean *wait_complete = user_data;
+
+  if (wait_complete != NULL)
+    *wait_complete = TRUE;
+
+  return TRUE;
+}
+
+static GtuClockWaitContext *
+gst_test_util_wait_for_clock_id_begin (GstTestClock * test_clock, GstClockID id,
+    GstClockTimeDiff * jitter)
+{
+  GtuClockWaitContext *wait_ctx;
+
+  wait_ctx = g_slice_new (GtuClockWaitContext);
+  wait_ctx->test_clock = gst_object_ref (test_clock);
+  wait_ctx->reference = gst_clock_get_time (GST_CLOCK (wait_ctx->test_clock));
+  wait_ctx->id = gst_clock_id_ref (id);
+
+  if (jitter) {
+    GstClockEntry *entry = GST_CLOCK_ENTRY (wait_ctx->id);
+    GstClockTime requested = GST_CLOCK_ENTRY_TIME (entry);
+    GstClockTime reference = wait_ctx->reference;
+
+    *jitter = GST_CLOCK_DIFF (requested, reference);
+  }
+
+  if (!gst_test_clock_has_id (wait_ctx->test_clock, wait_ctx->id)) {
+    GstClockClass *klass = GST_CLOCK_GET_CLASS (wait_ctx->test_clock);
+    GstClock *clock = GST_CLOCK (wait_ctx->test_clock);
+    g_assert (klass->wait_async (clock, wait_ctx->id) == GST_CLOCK_OK);
+  }
+
+  g_assert (gst_test_clock_has_id (wait_ctx->test_clock, wait_ctx->id));
+  g_assert_cmpint (gst_test_clock_peek_id_count (wait_ctx->test_clock), >, 0);
+
+  return wait_ctx;
+}
+
+static GstClockReturn
+gst_test_util_wait_for_clock_id_end (GtuClockWaitContext * wait_ctx)
+{
+  GstClockReturn status = GST_CLOCK_ERROR;
+  GstClockEntry *entry = GST_CLOCK_ENTRY (wait_ctx->id);
+
+  if (G_UNLIKELY (GST_CLOCK_ENTRY_STATUS (entry) == GST_CLOCK_UNSCHEDULED)) {
+    status = GST_CLOCK_UNSCHEDULED;
+  } else {
+    GstClockTime requested = GST_CLOCK_ENTRY_TIME (entry);
+    GstClockTimeDiff diff;
+
+    g_assert (gst_test_clock_has_id (wait_ctx->test_clock, wait_ctx->id));
+
+    diff = GST_CLOCK_DIFF (requested, wait_ctx->reference);
+
+    if (diff > 0) {
+      status = GST_CLOCK_EARLY;
+    } else {
+      status = GST_CLOCK_OK;
+    }
+
+    g_atomic_int_set (&GST_CLOCK_ENTRY_STATUS (entry), status);
+  }
+
+  if (GST_CLOCK_ENTRY_TYPE (entry) == GST_CLOCK_ENTRY_SINGLE) {
+    GstClockClass *klass = GST_CLOCK_GET_CLASS (wait_ctx->test_clock);
+    GstClock *clock = GST_CLOCK (wait_ctx->test_clock);
+
+    klass->unschedule (clock, wait_ctx->id);
+    g_assert (!gst_test_clock_has_id (wait_ctx->test_clock, wait_ctx->id));
+  } else {
+    GST_CLOCK_ENTRY_TIME (entry) += GST_CLOCK_ENTRY_INTERVAL (entry);
+    g_assert (gst_test_clock_has_id (wait_ctx->test_clock, wait_ctx->id));
+  }
+
+  gst_clock_id_unref (wait_ctx->id);
+  gst_object_unref (wait_ctx->test_clock);
+  g_slice_free (GtuClockWaitContext, wait_ctx);
+
+  return status;
+}
+
+static gboolean
+gst_test_util_clock_wait_context_has_completed (GtuClockWaitContext * wait_ctx)
+{
+  GstClock *clock = GST_CLOCK (wait_ctx->test_clock);
+  GstClockEntry *entry = GST_CLOCK_ENTRY (wait_ctx->id);
+  GstClockTime requested = GST_CLOCK_ENTRY_TIME (entry);
+  GstClockTime now = gst_clock_get_time (clock);
+
+  return requested < now;
+}
+
 GST_START_TEST (test_object_flags)
 {
   GstClock *clock = gst_test_clock_new ();
@@ -91,6 +260,651 @@ GST_START_TEST (test_advance_time)
 
 GST_END_TEST;
 
+GST_START_TEST (test_wait_synchronous_no_timeout)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GThread *worker_thread;
+  GstClockID pending_id;
+  GstClockID processed_id;
+  SyncClockWaitContext context;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND - 1);
+  context.clock_id = gst_clock_id_ref (clock_id);
+  context.jitter = 0;
+  worker_thread = g_thread_create (test_wait_pending_single_shot_id_sync_worker,
+      &context, TRUE, NULL);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_SECOND - 1);
+  gst_clock_id_unref (pending_id);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_EARLY);
+  gst_clock_id_unref (processed_id);
+  g_thread_join (worker_thread);
+  g_assert_cmpuint (context.jitter, ==, 1);
+  gst_clock_id_unref (context.clock_id);
+  gst_clock_id_unref (clock_id);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND);
+  context.clock_id = gst_clock_id_ref (clock_id);
+  context.jitter = 0;
+  worker_thread = g_thread_create (test_wait_pending_single_shot_id_sync_worker,
+      &context, TRUE, NULL);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_SECOND);
+  gst_clock_id_unref (pending_id);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  g_thread_join (worker_thread);
+  g_assert_cmpuint (context.jitter, ==, 0);
+  gst_clock_id_unref (context.clock_id);
+  gst_clock_id_unref (clock_id);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND + 1);
+  context.clock_id = gst_clock_id_ref (clock_id);
+  context.jitter = 0;
+  worker_thread = g_thread_create (test_wait_pending_single_shot_id_sync_worker,
+      &context, TRUE, NULL);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_SECOND + 1);
+  gst_clock_id_unref (pending_id);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  g_assert (processed_id == NULL);
+  gst_test_clock_advance_time (test_clock, 1);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  g_thread_join (worker_thread);
+  g_assert_cmpuint (context.jitter, ==, -1);
+  gst_clock_id_unref (context.clock_id);
+  gst_clock_id_unref (clock_id);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_wait_pending_single_shot_id)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GstClockID processed_id;
+  GThread *worker_thread;
+  GstClockID pending_id;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND);
+  gst_clock_id_wait_async (clock_id, test_async_wait_cb, NULL, NULL);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_SECOND);
+  gst_clock_id_unref (pending_id);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  gst_clock_id_unref (clock_id);
+
+  clock_id = gst_clock_new_single_shot_id (clock, 2 * GST_SECOND);
+  worker_thread = g_thread_create (test_wait_pending_single_shot_id_async_worker,
+      clock_id, TRUE, NULL);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id, GST_CLOCK_ENTRY_SINGLE, 2 * GST_SECOND);
+  gst_clock_id_unref (pending_id);
+  g_thread_join (worker_thread);
+  gst_clock_id_unref (clock_id);
+
+  clock_id = gst_clock_new_single_shot_id (clock, 3 * GST_SECOND);
+  worker_thread = g_thread_create (test_wait_pending_single_shot_id_async_worker,
+      clock_id, TRUE, NULL);
+  gst_test_clock_wait_for_next_pending_id (test_clock, NULL);
+  g_thread_join (worker_thread);
+  gst_clock_id_unref (clock_id);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_wait_pending_periodic_id)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GstClockID processed_id;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+  clock_id = gst_clock_new_periodic_id (clock, GST_SECOND, GST_MSECOND);
+
+  {
+    GThread *waiter_thread;
+
+    waiter_thread =
+        g_thread_create (test_wait_pending_periodic_id_waiter_thread, clock_id,
+        TRUE, NULL);
+
+    gst_test_clock_wait_for_next_pending_id (test_clock, NULL);
+    gst_test_clock_set_time (test_clock, GST_SECOND);
+    processed_id = gst_test_clock_process_next_clock_id (test_clock);
+    assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_PERIODIC, GST_CLOCK_OK);
+    gst_clock_id_unref (processed_id);
+
+    g_thread_join (waiter_thread);
+  }
+
+  {
+    guint i;
+    GThread *waiter_thread;
+
+    for (i = 0; i < 3; i++) {
+      g_assert (!gst_test_clock_peek_next_pending_id (test_clock, NULL));
+      g_usleep (G_USEC_PER_SEC / 10 / 10);
+    }
+
+    waiter_thread =
+        g_thread_create (test_wait_pending_periodic_id_waiter_thread, clock_id,
+        TRUE, NULL);
+
+    gst_test_clock_wait_for_next_pending_id (test_clock, NULL);
+    gst_clock_id_unschedule (clock_id);
+
+    g_thread_join (waiter_thread);
+  }
+
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_past)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GstClockTimeDiff jitter;
+  GtuClockWaitContext *wait_ctx;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND - 1);
+  wait_ctx =
+      gst_test_util_wait_for_clock_id_begin (test_clock, clock_id, &jitter);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx) == GST_CLOCK_EARLY);
+  g_assert_cmpint (jitter, ==, 1);
+  gst_clock_id_unref (clock_id);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_present)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GstClockTimeDiff jitter;
+  GtuClockWaitContext *wait_ctx;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND);
+  wait_ctx =
+      gst_test_util_wait_for_clock_id_begin (test_clock, clock_id, &jitter);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx) == GST_CLOCK_OK);
+  g_assert_cmpint (jitter, ==, 0);
+  gst_clock_id_unref (clock_id);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_future)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GstClockTimeDiff jitter;
+  GtuClockWaitContext *wait_ctx;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_single_shot_id (clock, 2 * GST_SECOND);
+  wait_ctx =
+      gst_test_util_wait_for_clock_id_begin (test_clock, clock_id, &jitter);
+  gst_test_clock_advance_time (test_clock, GST_SECOND);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx) == GST_CLOCK_OK);
+  g_assert_cmpint (jitter, ==, -GST_SECOND);
+  gst_clock_id_unref (clock_id);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_unschedule)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  GtuClockWaitContext *wait_ctx;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND);
+  gst_clock_id_unschedule (clock_id);
+  gst_clock_id_unref (clock_id);
+
+  clock_id = gst_clock_new_single_shot_id (clock, 2 * GST_SECOND);
+  wait_ctx = gst_test_util_wait_for_clock_id_begin (test_clock, clock_id, NULL);
+  gst_clock_id_unschedule (clock_id);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx)
+      == GST_CLOCK_UNSCHEDULED);
+  gst_clock_id_unref (clock_id);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_ordering)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id_a, clock_id_b;
+  GtuClockWaitContext *wait_ctx_a, *wait_ctx_b;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id_a = gst_clock_new_single_shot_id (clock, 3 * GST_SECOND);
+  wait_ctx_a =
+      gst_test_util_wait_for_clock_id_begin (test_clock, clock_id_a, NULL);
+
+  gst_test_clock_advance_time (test_clock, GST_SECOND);
+
+  clock_id_b = gst_clock_new_single_shot_id (clock, 2 * GST_SECOND);
+  wait_ctx_b =
+      gst_test_util_wait_for_clock_id_begin (test_clock, clock_id_b, NULL);
+
+  gst_test_clock_advance_time (test_clock, GST_SECOND);
+
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx_b) == GST_CLOCK_OK);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx_a) == GST_CLOCK_OK);
+
+  gst_clock_id_unref (clock_id_b);
+  gst_clock_id_unref (clock_id_a);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_ordering_parallel)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id_a, clock_id_b;
+  GtuClockWaitContext *wait_ctx_a, *wait_ctx_b;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id_a = gst_clock_new_single_shot_id (clock, 3 * GST_SECOND);
+  clock_id_b = gst_clock_new_single_shot_id (clock, 2 * GST_SECOND);
+  wait_ctx_a = gst_test_util_wait_for_clock_id_begin (test_clock, clock_id_a,
+      NULL);
+  wait_ctx_b = gst_test_util_wait_for_clock_id_begin (test_clock, clock_id_b,
+      NULL);
+
+  g_assert_cmpuint (gst_test_clock_get_next_entry_time (test_clock), ==,
+      2 * GST_SECOND);
+  gst_test_clock_advance_time (test_clock, GST_SECOND);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx_b) == GST_CLOCK_OK);
+
+  g_assert_cmpuint (gst_test_clock_get_next_entry_time (test_clock), ==,
+      3 * GST_SECOND);
+  gst_test_clock_advance_time (test_clock, GST_SECOND);
+  g_assert (gst_test_util_wait_for_clock_id_end (wait_ctx_a) == GST_CLOCK_OK);
+
+  gst_clock_id_unref (clock_id_b);
+  gst_clock_id_unref (clock_id_a);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_sync_simultaneous_no_timeout)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id_a;
+  GstClockID clock_id_b;
+  SyncClockWaitContext context_a;
+  SyncClockWaitContext context_b;
+  GThread *worker_thread_a;
+  GThread *worker_thread_b;
+  GstClockID processed_id;
+  GstClockID pending_id;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id_a = gst_clock_new_single_shot_id (clock, 5 * GST_SECOND);
+  clock_id_b = gst_clock_new_single_shot_id (clock, 6 * GST_SECOND);
+
+  context_a.clock_id = gst_clock_id_ref (clock_id_a);
+  context_a.jitter = 0;
+  context_b.clock_id = gst_clock_id_ref (clock_id_b);
+  context_b.jitter = 0;
+
+  gst_test_clock_wait_for_pending_id_count (test_clock, 0);
+
+  worker_thread_b = g_thread_create (test_wait_pending_single_shot_id_sync_worker,
+      &context_b, TRUE, NULL);
+
+  gst_test_clock_wait_for_pending_id_count (test_clock, 1);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE, 6 * GST_SECOND);
+  gst_clock_id_unref (pending_id);
+
+  worker_thread_a = g_thread_create (test_wait_pending_single_shot_id_sync_worker,
+      &context_a, TRUE, NULL);
+
+  gst_test_clock_wait_for_pending_id_count (test_clock, 2);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id_a, GST_CLOCK_ENTRY_SINGLE, 5 * GST_SECOND);
+  gst_clock_id_unref (pending_id);
+
+  g_assert_cmpuint (gst_test_clock_get_next_entry_time (test_clock), ==,
+      5 * GST_SECOND);
+  gst_test_clock_advance_time (test_clock, 5 * GST_SECOND);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  assert_processed_id (processed_id, clock_id_a, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+
+  gst_test_clock_wait_for_pending_id_count (test_clock, 1);
+  gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+  assert_pending_id (pending_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE, 6 * GST_SECOND);
+  gst_clock_id_unref (pending_id);
+
+  g_assert_cmpuint (gst_test_clock_get_next_entry_time (test_clock), ==,
+      6 * GST_SECOND);
+  gst_test_clock_advance_time (test_clock, 6 * GST_SECOND);
+  processed_id = gst_test_clock_process_next_clock_id (test_clock);
+  assert_processed_id (processed_id, clock_id_b, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+
+  gst_test_clock_wait_for_pending_id_count (test_clock, 0);
+
+  g_thread_join (worker_thread_a);
+  g_thread_join (worker_thread_b);
+
+  g_assert_cmpuint (context_a.jitter, ==, -4 * GST_SECOND);
+  g_assert_cmpuint (context_b.jitter, ==, -5 * GST_SECOND);
+
+  gst_clock_id_unref (context_a.clock_id);
+  gst_clock_id_unref (context_b.clock_id);
+
+  gst_clock_id_unref (clock_id_a);
+  gst_clock_id_unref (clock_id_b);
+
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_async_past)
+{
+  GstClock *clock;
+  GstClockID clock_id;
+  GstClockID processed_id;
+  gboolean wait_complete = FALSE;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND - 1);
+  g_assert (gst_clock_id_wait_async (clock_id, test_async_wait_cb,
+          &wait_complete, NULL) == GST_CLOCK_OK);
+  g_assert (!wait_complete);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (wait_complete);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_EARLY);
+  gst_clock_id_unref (processed_id);
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_async_present)
+{
+  GstClock *clock;
+  GstClockID clock_id;
+  GstClockID processed_id;
+  gboolean wait_complete = FALSE;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  clock_id = gst_clock_new_single_shot_id (clock, GST_SECOND);
+  g_assert (gst_clock_id_wait_async (clock_id, test_async_wait_cb,
+          &wait_complete, NULL) == GST_CLOCK_OK);
+  g_assert (!wait_complete);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (wait_complete);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_async_future)
+{
+  GstClock *clock;
+  GstClockID clock_id;
+  GstClockID processed_id;
+  gboolean wait_complete = FALSE;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+  clock_id = gst_clock_new_single_shot_id (clock, 2 * GST_SECOND);
+  g_assert (gst_clock_id_wait_async (clock_id, test_async_wait_cb,
+          &wait_complete, NULL) == GST_CLOCK_OK);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (processed_id == NULL);
+  g_assert (!wait_complete);
+  g_assert (GST_CLOCK_ENTRY_STATUS (GST_CLOCK_ENTRY (clock_id))
+      == GST_CLOCK_OK);
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), GST_SECOND - 1);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (processed_id == NULL);
+  g_assert (!wait_complete);
+  g_assert (GST_CLOCK_ENTRY_STATUS (GST_CLOCK_ENTRY (clock_id))
+      == GST_CLOCK_OK);
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), 1);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (wait_complete);
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_SINGLE, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  g_assert (GST_CLOCK_ENTRY_STATUS (GST_CLOCK_ENTRY (clock_id))
+      == GST_CLOCK_OK);
+
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_single_shot_async_unschedule)
+{
+  GstClock *clock;
+  GstClockID clock_id;
+  gboolean wait_complete = FALSE;
+
+  clock = gst_test_clock_new_with_start_time (GST_SECOND);
+
+  clock_id = gst_clock_new_single_shot_id (clock, 3 * GST_SECOND);
+  g_assert (gst_clock_id_wait_async (clock_id, test_async_wait_cb,
+          &wait_complete, NULL) == GST_CLOCK_OK);
+
+  gst_clock_id_unschedule (clock_id);
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), 2 * GST_SECOND);
+  g_assert (gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock))
+      == NULL);
+  g_assert (!wait_complete);
+
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_periodic_sync)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  guint i;
+  const GstClockTime interval = 4 * GST_MSECOND;
+
+  clock = gst_test_clock_new ();
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_periodic_id (clock, GST_SECOND, interval);
+
+  for (i = 0; i < 3; i++) {
+    GtuClockWaitContext *wait_ctx;
+    GstClockID pending_id;
+    guint j;
+
+    wait_ctx =
+        gst_test_util_wait_for_clock_id_begin (test_clock, clock_id, NULL);
+
+    gst_test_clock_wait_for_next_pending_id (test_clock, &pending_id);
+    assert_pending_id (pending_id, clock_id, GST_CLOCK_ENTRY_PERIODIC, GST_SECOND + (i * interval));
+    gst_clock_id_unref (pending_id);
+
+    for (j = 0; j < 10; j++) {
+      g_usleep (G_USEC_PER_SEC / 10 / 10);
+      g_assert (!gst_test_util_clock_wait_context_has_completed (wait_ctx));
+    }
+
+    if (i == 0)
+      gst_test_clock_advance_time (test_clock, GST_SECOND);
+    else
+      gst_test_clock_advance_time (test_clock, interval);
+
+    gst_test_util_wait_for_clock_id_end (wait_ctx);
+  }
+
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_periodic_async)
+{
+  GstClock *clock;
+  GstClockID clock_id;
+  GstClockID processed_id;
+  gboolean wait_complete = FALSE;
+  const GstClockTime interval = 4 * GST_MSECOND;
+
+  clock = gst_test_clock_new ();
+  clock_id = gst_clock_new_periodic_id (clock, gst_clock_get_time (clock),
+      interval);
+  g_assert (gst_clock_id_wait_async (clock_id, test_async_wait_cb,
+          &wait_complete, NULL) == GST_CLOCK_OK);
+
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_PERIODIC, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+
+  g_assert (wait_complete);
+  wait_complete = FALSE;
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), interval - 1);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (processed_id == NULL);
+  g_assert (!wait_complete);
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), 1);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_PERIODIC, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  g_assert (wait_complete);
+  wait_complete = FALSE;
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), interval - 1);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  g_assert (processed_id == NULL);
+  g_assert (!wait_complete);
+
+  gst_test_clock_advance_time (GST_TEST_CLOCK (clock), 1);
+  processed_id = gst_test_clock_process_next_clock_id (GST_TEST_CLOCK (clock));
+  assert_processed_id (processed_id, clock_id, GST_CLOCK_ENTRY_PERIODIC, GST_CLOCK_OK);
+  gst_clock_id_unref (processed_id);
+  g_assert (wait_complete);
+  wait_complete = FALSE;
+
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_periodic_uniqueness)
+{
+  GstClock *clock;
+  GstTestClock *test_clock;
+  GstClockID clock_id;
+  guint i;
+  const GstClockTime interval = 4 * GST_MSECOND;
+
+  clock = gst_test_clock_new ();
+  test_clock = GST_TEST_CLOCK (clock);
+
+  clock_id = gst_clock_new_periodic_id (clock, 0, interval);
+
+  for (i = 0; i < 3; i++) {
+    GtuClockWaitContext *wait_ctx;
+    guint j;
+
+    wait_ctx =
+        gst_test_util_wait_for_clock_id_begin (test_clock, clock_id, NULL);
+
+    for (j = 0; j < 10; j++) {
+      g_usleep (G_USEC_PER_SEC / 10 / 10);
+      g_assert_cmpuint (gst_test_clock_peek_id_count (test_clock), ==, 1);
+    }
+
+    gst_test_clock_advance_time (test_clock, interval);
+    gst_test_util_wait_for_clock_id_end (wait_ctx);
+  }
+
+  gst_clock_id_unref (clock_id);
+  gst_object_unref (clock);
+}
+
+GST_END_TEST;
+
 static Suite *
 gst_test_clock_suite (void)
 {
@@ -104,6 +918,23 @@ gst_test_clock_suite (void)
   tcase_add_test (tc_chain, test_start_time);
   tcase_add_test (tc_chain, test_set_time);
   tcase_add_test (tc_chain, test_advance_time);
+  tcase_add_test (tc_chain, test_wait_synchronous_no_timeout);
+  tcase_add_test (tc_chain, test_wait_pending_single_shot_id);
+  tcase_add_test (tc_chain, test_wait_pending_periodic_id);
+  tcase_add_test (tc_chain, test_single_shot_sync_simultaneous_no_timeout);
+  tcase_add_test (tc_chain, test_single_shot_sync_past);
+  tcase_add_test (tc_chain, test_single_shot_sync_present);
+  tcase_add_test (tc_chain, test_single_shot_sync_future);
+  tcase_add_test (tc_chain, test_single_shot_sync_unschedule);
+  tcase_add_test (tc_chain, test_single_shot_sync_ordering);
+  tcase_add_test (tc_chain, test_single_shot_sync_ordering_parallel);
+  tcase_add_test (tc_chain, test_single_shot_async_past);
+  tcase_add_test (tc_chain, test_single_shot_async_present);
+  tcase_add_test (tc_chain, test_single_shot_async_future);
+  tcase_add_test (tc_chain, test_single_shot_async_unschedule);
+  tcase_add_test (tc_chain, test_periodic_sync);
+  tcase_add_test (tc_chain, test_periodic_async);
+  tcase_add_test (tc_chain, test_periodic_uniqueness);
 
   return s;
 }