From 4eeb471e11b35bdf0e9aa95ea354219036035d76 Mon Sep 17 00:00:00 2001 From: Sebastian Rasmussen Date: Thu, 30 Aug 2012 01:58:41 +0200 Subject: [PATCH] check: allow GstTestClock to handle clock notifications 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 | 7 + libs/gst/check/Makefile.am | 9 +- libs/gst/check/gsttestclock.c | 560 +++++++++++++++++ libs/gst/check/gsttestclock.h | 12 + tests/check/libs/gsttestclock.c | 831 ++++++++++++++++++++++++++ 5 files changed, 1418 insertions(+), 1 deletion(-) diff --git a/docs/libs/gstreamer-libs-sections.txt b/docs/libs/gstreamer-libs-sections.txt index 3ed14c4d05..d9f6b9d033 100644 --- a/docs/libs/gstreamer-libs-sections.txt +++ b/docs/libs/gstreamer-libs-sections.txt @@ -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 GST_TEST_CLOCK GST_IS_TEST_CLOCK diff --git a/libs/gst/check/Makefile.am b/libs/gst/check/Makefile.am index 03fab2ae8d..599abf7cb2 100644 --- a/libs/gst/check/Makefile.am +++ b/libs/gst/check/Makefile.am @@ -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) \ diff --git a/libs/gst/check/gsttestclock.c b/libs/gst/check/gsttestclock.c index 01d60032bb..70e8f64145 100644 --- a/libs/gst/check/gsttestclock.c +++ b/libs/gst/check/gsttestclock.c @@ -55,6 +55,119 @@ * * * + * #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. + * + * + * Demonstration of how to work with clock notifications and #GstTestClock + * + * #include <gst/gst.h> + * #include <gst/check/gstcheck.h> + * #include <gst/check/gsttestclock.h> + * + * 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); + * ... + * + * + * * 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; +} diff --git a/libs/gst/check/gsttestclock.h b/libs/gst/check/gsttestclock.h index 6d47e63aae..d5ef6522d1 100644 --- a/libs/gst/check/gsttestclock.h +++ b/libs/gst/check/gsttestclock.h @@ -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__ */ diff --git a/tests/check/libs/gsttestclock.c b/tests/check/libs/gsttestclock.c index 0c8678f1f8..19f9cb097d 100644 --- a/tests/check/libs/gsttestclock.c +++ b/tests/check/libs/gsttestclock.c @@ -23,6 +23,175 @@ #include #include +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; }