/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
+ * 2004 Wim Taymans <wim@fluendo.com>
*
* gstclock.c: Clock subsystem for maintaining time sync
*
* Boston, MA 02111-1307, USA.
*/
-#include <sys/time.h>
+/**
+ * SECTION:gstclock
+ * @short_description: Abstract class for global clocks
+ * @see_also: #GstSystemClock, #GstPipeline
+ *
+ * GStreamer uses a global clock to synchronize the plugins in a pipeline.
+ * Different clock implementations are possible by implementing this abstract
+ * base class or, more conveniently, by subclassing #GstSystemClock.
+ *
+ * The #GstClock returns a monotonically increasing time with the method
+ * gst_clock_get_time(). Its accuracy and base time depend on the specific
+ * clock implementation but time is always expressed in nanoseconds. Since the
+ * baseline of the clock is undefined, the clock time returned is not
+ * meaningful in itself, what matters are the deltas between two clock times.
+ * The time returned by a clock is called the absolute time.
+ *
+ * The pipeline uses the clock to calculate the running time. Usually all
+ * renderers synchronize to the global clock using the buffer timestamps, the
+ * newsegment events and the element's base time, see #GstPipeline.
+ *
+ * A clock implementation can support periodic and single shot clock
+ * notifications both synchronous and asynchronous.
+ *
+ * One first needs to create a #GstClockID for the periodic or single shot
+ * notification using gst_clock_new_single_shot_id() or
+ * gst_clock_new_periodic_id().
+ *
+ * To perform a blocking wait for the specific time of the #GstClockID use the
+ * gst_clock_id_wait(). To receive a callback when the specific time is reached
+ * in the clock use gst_clock_id_wait_async(). Both these calls can be
+ * interrupted with the gst_clock_id_unschedule() call. If the blocking wait is
+ * unscheduled a return value of #GST_CLOCK_UNSCHEDULED is returned.
+ *
+ * Periodic callbacks scheduled async will be repeatedly called automatically
+ * until it is unscheduled. To schedule a sync periodic callback,
+ * gst_clock_id_wait() should be called repeatedly.
+ *
+ * The async callbacks can happen from any thread, either provided by the core
+ * or from a streaming thread. The application should be prepared for this.
+ *
+ * A #GstClockID that has been unscheduled cannot be used again for any wait
+ * operation, a new #GstClockID should be created and the old unscheduled one
+ * should be destroyed with gst_clock_id_unref().
+ *
+ * It is possible to perform a blocking wait on the same #GstClockID from
+ * multiple threads. However, registering the same #GstClockID for multiple
+ * async notifications is not possible, the callback will only be called for
+ * the thread registering the entry last.
+ *
+ * None of the wait operations unref the #GstClockID, the owner is responsible
+ * for unreffing the ids itself. This holds for both periodic and single shot
+ * notifications. The reason being that the owner of the #GstClockID has to
+ * keep a handle to the #GstClockID to unblock the wait on FLUSHING events or
+ * state changes and if the entry would be unreffed automatically, the handle
+ * might become invalid without any notification.
+ *
+ * These clock operations do not operate on the running time, so the callbacks
+ * will also occur when not in PLAYING state as if the clock just keeps on
+ * running. Some clocks however do not progress when the element that provided
+ * the clock is not PLAYING.
+ *
+ * When a clock has the #GST_CLOCK_FLAG_CAN_SET_MASTER flag set, it can be
+ * slaved to another #GstClock with the gst_clock_set_master(). The clock will
+ * then automatically be synchronized to this master clock by repeatedly
+ * sampling the master clock and the slave clock and recalibrating the slave
+ * clock with gst_clock_set_calibration(). This feature is mostly useful for
+ * plugins that have an internal clock but must operate with another clock
+ * selected by the #GstPipeline. They can track the offset and rate difference
+ * of their internal clock relative to the master clock by using the
+ * gst_clock_get_calibration() function.
+ *
+ * The master/slave synchronisation can be tuned with the #GstClock:timeout,
+ * #GstClock:window-size and #GstClock:window-threshold properties.
+ * The #GstClock:timeout property defines the interval to sample the master
+ * clock and run the calibration functions. #GstClock:window-size defines the
+ * number of samples to use when calibrating and #GstClock:window-threshold
+ * defines the minimum number of samples before the calibration is performed.
+ *
+ * Last reviewed on 2009-05-21 (0.10.24)
+ */
+
#include "gst_private.h"
+#include <time.h>
#include "gstclock.h"
#include "gstinfo.h"
-#include "gstmemchunk.h"
+#include "gstutils.h"
#ifndef GST_DISABLE_TRACE
/* #define GST_WITH_ALLOC_TRACE */
static GstAllocTrace *_gst_clock_entry_trace;
#endif
-#define DEFAULT_EVENT_DIFF (GST_SECOND)
-#define DEFAULT_MAX_DIFF (2 * GST_SECOND)
+/* #define DEBUGGING_ENABLED */
-enum {
- ARG_0,
- ARG_STATS,
- ARG_MAX_DIFF,
- ARG_EVENT_DIFF
+#define DEFAULT_STATS FALSE
+#define DEFAULT_WINDOW_SIZE 32
+#define DEFAULT_WINDOW_THRESHOLD 4
+#define DEFAULT_TIMEOUT GST_SECOND / 10
+
+enum
+{
+ PROP_0,
+ PROP_STATS,
+ PROP_WINDOW_SIZE,
+ PROP_WINDOW_THRESHOLD,
+ PROP_TIMEOUT
+};
+
+struct _GstClockPrivate
+{
+ gint pre_count;
+ gint post_count;
};
-static GstMemChunk *_gst_clock_entries_chunk;
+/* seqlocks */
+#define read_seqbegin(clock) \
+ g_atomic_int_get (&clock->ABI.priv->post_count);
-void gst_clock_id_unlock (GstClockID id);
+static inline gboolean
+read_seqretry (GstClock * clock, gint seq)
+{
+ /* no retry if the seqnum did not change */
+ if (G_LIKELY (seq == g_atomic_int_get (&clock->ABI.priv->pre_count)))
+ return FALSE;
+
+ /* wait for the writer to finish and retry */
+ GST_OBJECT_LOCK (clock);
+ GST_OBJECT_UNLOCK (clock);
+ return TRUE;
+}
+
+#define write_seqlock(clock) \
+G_STMT_START { \
+ GST_OBJECT_LOCK (clock); \
+ g_atomic_int_inc (&clock->ABI.priv->pre_count); \
+} G_STMT_END;
+
+#define write_sequnlock(clock) \
+G_STMT_START { \
+ g_atomic_int_inc (&clock->ABI.priv->post_count); \
+ GST_OBJECT_UNLOCK (clock); \
+} G_STMT_END;
-
-static void gst_clock_class_init (GstClockClass *klass);
-static void gst_clock_init (GstClock *clock);
-static void gst_clock_dispose (GObject *object);
+static void gst_clock_dispose (GObject * object);
+static void gst_clock_finalize (GObject * object);
-static void gst_clock_set_property (GObject *object, guint prop_id,
- const GValue *value, GParamSpec *pspec);
-static void gst_clock_get_property (GObject *object, guint prop_id,
- GValue *value, GParamSpec * pspec);
-static void gst_clock_update_stats (GstClock *clock);
+static void gst_clock_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_clock_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+static void gst_clock_update_stats (GstClock * clock);
static GstObjectClass *parent_class = NULL;
+
/* static guint gst_clock_signals[LAST_SIGNAL] = { 0 }; */
static GstClockID
-gst_clock_entry_new (GstClock *clock, GstClockTime time,
- GstClockTime interval, GstClockEntryType type)
+gst_clock_entry_new (GstClock * clock, GstClockTime time,
+ GstClockTime interval, GstClockEntryType type)
{
GstClockEntry *entry;
- entry = gst_mem_chunk_alloc (_gst_clock_entries_chunk);
+ entry = g_slice_new (GstClockEntry);
#ifndef GST_DISABLE_TRACE
gst_alloc_trace_new (_gst_clock_entry_trace, entry);
#endif
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "created entry %p, time %" GST_TIME_FORMAT, entry, GST_TIME_ARGS (time));
+ entry->refcount = 1;
entry->clock = clock;
- entry->time = time;
- entry->interval = time;
entry->type = type;
- entry->status = GST_CLOCK_ENTRY_OK;
+ entry->time = time;
+ entry->interval = interval;
+ entry->status = GST_CLOCK_OK;
+ entry->func = NULL;
+ entry->user_data = NULL;
+ entry->destroy_data = NULL;
+ entry->unscheduled = FALSE;
+ entry->woken_up = FALSE;
return (GstClockID) entry;
}
+/* WARNING : Does not modify the refcount
+ * WARNING : Do not use if a pending clock operation is happening on that entry */
+static gboolean
+gst_clock_entry_reinit (GstClock * clock, GstClockEntry * entry,
+ GstClockTime time, GstClockTime interval, GstClockEntryType type)
+{
+ g_return_val_if_fail (entry->status != GST_CLOCK_BUSY, FALSE);
+ g_return_val_if_fail (entry->clock == clock, FALSE);
+
+ entry->type = type;
+ entry->time = time;
+ entry->interval = interval;
+ entry->status = GST_CLOCK_OK;
+ entry->unscheduled = FALSE;
+ entry->woken_up = FALSE;
+
+ return TRUE;
+}
+
+/**
+ * gst_clock_single_shot_id_reinit:
+ * @clock: a #GstClock
+ * @id: a #GstClockID
+ * @time: The requested time.
+ *
+ * Reinitializes the provided single shot @id to the provided time. Does not
+ * modify the reference count.
+ *
+ * Returns: %TRUE if the GstClockID could be reinitialized to the provided
+ * @time, else %FALSE.
+ *
+ * Since: 0.10.32
+ */
+gboolean
+gst_clock_single_shot_id_reinit (GstClock * clock, GstClockID id,
+ GstClockTime time)
+{
+ return gst_clock_entry_reinit (clock, (GstClockEntry *) id, time,
+ GST_CLOCK_TIME_NONE, GST_CLOCK_ENTRY_SINGLE);
+}
+
+/**
+ * gst_clock_periodic_id_reinit:
+ * @clock: a #GstClock
+ * @id: a #GstClockID
+ * @start_time: the requested start time
+ * @interval: the requested interval
+ *
+ * Reinitializes the provided periodic @id to the provided start time and
+ * interval. Does not modify the reference count.
+ *
+ * Returns: %TRUE if the GstClockID could be reinitialized to the provided
+ * @time, else %FALSE.
+ *
+ * Since: 0.10.33
+ *
+ */
+gboolean
+gst_clock_periodic_id_reinit (GstClock * clock, GstClockID id,
+ GstClockTime start_time, GstClockTime interval)
+{
+ return gst_clock_entry_reinit (clock, (GstClockEntry *) id, start_time,
+ interval, GST_CLOCK_ENTRY_PERIODIC);
+}
+
+/**
+ * gst_clock_id_ref:
+ * @id: The #GstClockID to ref
+ *
+ * Increase the refcount of given @id.
+ *
+ * Returns: (transfer full): The same #GstClockID with increased refcount.
+ *
+ * MT safe.
+ */
+GstClockID
+gst_clock_id_ref (GstClockID id)
+{
+ g_return_val_if_fail (id != NULL, NULL);
+
+ g_atomic_int_inc (&((GstClockEntry *) id)->refcount);
+
+ return id;
+}
+
+static void
+_gst_clock_id_free (GstClockID id)
+{
+ GstClockEntry *entry;
+ g_return_if_fail (id != NULL);
+
+ GST_CAT_DEBUG (GST_CAT_CLOCK, "freed entry %p", id);
+ entry = (GstClockEntry *) id;
+ if (entry->destroy_data)
+ entry->destroy_data (entry->user_data);
+
+#ifndef GST_DISABLE_TRACE
+ gst_alloc_trace_free (_gst_clock_entry_trace, id);
+#endif
+ g_slice_free (GstClockEntry, id);
+}
+
+/**
+ * gst_clock_id_unref:
+ * @id: (transfer full): The #GstClockID to unref
+ *
+ * Unref given @id. When the refcount reaches 0 the
+ * #GstClockID will be freed.
+ *
+ * MT safe.
+ */
+void
+gst_clock_id_unref (GstClockID id)
+{
+ gint zero;
+
+ g_return_if_fail (id != NULL);
+
+ zero = g_atomic_int_dec_and_test (&((GstClockEntry *) id)->refcount);
+ /* if we ended up with the refcount at zero, free the id */
+ if (zero) {
+ _gst_clock_id_free (id);
+ }
+}
+
/**
- * gst_clock_new_single_shot_id
- * @clock: The clockid to get a single shot notification from
+ * gst_clock_new_single_shot_id:
+ * @clock: The #GstClockID to get a single shot notification from
* @time: the requested time
*
- * Get an ID from the given clock to trigger a single shot
- * notification at the requested time.
+ * Get a #GstClockID from @clock to trigger a single shot
+ * notification at the requested time. The single shot id should be
+ * unreffed after usage.
+ *
+ * Free-function: gst_clock_id_unref
+ *
+ * Returns: (transfer full): a #GstClockID that can be used to request the
+ * time notification.
*
- * Returns: An id that can be used to request the time notification.
+ * MT safe.
*/
GstClockID
-gst_clock_new_single_shot_id (GstClock *clock, GstClockTime time)
+gst_clock_new_single_shot_id (GstClock * clock, GstClockTime time)
{
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
- return gst_clock_entry_new (clock,
- time,
- GST_CLOCK_TIME_NONE,
- GST_CLOCK_ENTRY_SINGLE);
+ return gst_clock_entry_new (clock,
+ time, GST_CLOCK_TIME_NONE, GST_CLOCK_ENTRY_SINGLE);
}
/**
- * gst_clock_new_periodic_id
- * @clock: The clockid to get a periodic notification id from
+ * gst_clock_new_periodic_id:
+ * @clock: The #GstClockID to get a periodic notification id from
* @start_time: the requested start time
* @interval: the requested interval
*
- * Get an ID from the given clock to trigger a periodic notification.
- * The periodeic notifications will be start at time start_time and
- * will then be fired with the given interval.
+ * Get an ID from @clock to trigger a periodic notification.
+ * The periodic notifications will start at time @start_time and
+ * will then be fired with the given @interval. @id should be unreffed
+ * after usage.
*
- * Returns: An id that can be used to request the time notification.
+ * Free-function: gst_clock_id_unref
+ *
+ * Returns: (transfer full): a #GstClockID that can be used to request the
+ * time notification.
+ *
+ * MT safe.
*/
GstClockID
-gst_clock_new_periodic_id (GstClock *clock, GstClockTime start_time,
- GstClockTime interval)
+gst_clock_new_periodic_id (GstClock * clock, GstClockTime start_time,
+ GstClockTime interval)
{
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_time), NULL);
g_return_val_if_fail (interval != 0, NULL);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (interval), NULL);
- return gst_clock_entry_new (clock,
- start_time,
- interval,
- GST_CLOCK_ENTRY_PERIODIC);
+ return gst_clock_entry_new (clock,
+ start_time, interval, GST_CLOCK_ENTRY_PERIODIC);
+}
+
+/**
+ * gst_clock_id_compare_func
+ * @id1: A #GstClockID
+ * @id2: A #GstClockID to compare with
+ *
+ * Compares the two #GstClockID instances. This function can be used
+ * as a GCompareFunc when sorting ids.
+ *
+ * Returns: negative value if a < b; zero if a = b; positive value if a > b
+ *
+ * MT safe.
+ */
+gint
+gst_clock_id_compare_func (gconstpointer id1, gconstpointer id2)
+{
+ GstClockEntry *entry1, *entry2;
+
+ entry1 = (GstClockEntry *) id1;
+ entry2 = (GstClockEntry *) id2;
+
+ if (GST_CLOCK_ENTRY_TIME (entry1) > GST_CLOCK_ENTRY_TIME (entry2)) {
+ return 1;
+ }
+ if (GST_CLOCK_ENTRY_TIME (entry1) < GST_CLOCK_ENTRY_TIME (entry2)) {
+ return -1;
+ }
+ return 0;
}
/**
* gst_clock_id_get_time
- * @id: The clockid to query
+ * @id: The #GstClockID to query
*
* Get the time of the clock ID
*
- * Returns: the time of the given clock id
+ * Returns: the time of the given clock id.
+ *
+ * MT safe.
*/
GstClockTime
gst_clock_id_get_time (GstClockID id)
{
g_return_val_if_fail (id != NULL, GST_CLOCK_TIME_NONE);
- return GST_CLOCK_ENTRY_TIME ((GstClockEntry *)id);
+ return GST_CLOCK_ENTRY_TIME ((GstClockEntry *) id);
}
-
/**
* gst_clock_id_wait
- * @id: The clockid to wait on
- * @jitter: A pointer that will contain the jitter
+ * @id: The #GstClockID to wait on
+ * @jitter: (out) (allow-none): a pointer that will contain the jitter,
+ * can be %NULL.
+ *
+ * Perform a blocking wait on @id.
+ * @id should have been created with gst_clock_new_single_shot_id()
+ * or gst_clock_new_periodic_id() and should not have been unscheduled
+ * with a call to gst_clock_id_unschedule().
*
- * Perform a blocking wait on the given ID. The jitter arg can be
- * NULL
+ * If the @jitter argument is not %NULL and this function returns #GST_CLOCK_OK
+ * or #GST_CLOCK_EARLY, it will contain the difference
+ * against the clock and the time of @id when this method was
+ * called.
+ * Positive values indicate how late @id was relative to the clock
+ * (in which case this function will return #GST_CLOCK_EARLY).
+ * Negative values indicate how much time was spent waiting on the clock
+ * before this function returned.
*
- * Returns: the result of the blocking wait.
+ * Returns: the result of the blocking wait. #GST_CLOCK_EARLY will be returned
+ * if the current clock time is past the time of @id, #GST_CLOCK_OK if
+ * @id was scheduled in time. #GST_CLOCK_UNSCHEDULED if @id was
+ * unscheduled with gst_clock_id_unschedule().
+ *
+ * MT safe.
*/
GstClockReturn
-gst_clock_id_wait (GstClockID id, GstClockTimeDiff *jitter)
+gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter)
{
GstClockEntry *entry;
GstClock *clock;
- GstClockReturn res = GST_CLOCK_UNSUPPORTED;
+ GstClockReturn res;
GstClockTime requested;
GstClockClass *cclass;
-
+
g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR);
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
- if (! GST_CLOCK_TIME_IS_VALID (requested)) {
- GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _TIMEOUT");
- return GST_CLOCK_TIMEOUT;
- }
-
clock = GST_CLOCK_ENTRY_CLOCK (entry);
+
+ /* can't sync on invalid times */
+ if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
+ goto invalid_time;
+
cclass = GST_CLOCK_GET_CLASS (clock);
-
- if (cclass->wait) {
- GstClockTime now;
-
- GST_LOCK (clock);
- clock->entries = g_list_prepend (clock->entries, entry);
- GST_UNLOCK (clock);
-
- GST_CAT_DEBUG (GST_CAT_CLOCK, "waiting on clock");
- do {
- res = cclass->wait (clock, entry);
- }
- while (res == GST_CLOCK_ENTRY_RESTART);
- GST_CAT_DEBUG (GST_CAT_CLOCK, "done waiting");
- GST_LOCK (clock);
- clock->entries = g_list_remove (clock->entries, entry);
- GST_UNLOCK (clock);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "waiting on clock entry %p", id);
+
+ /* if we have a wait_jitter function, use that */
+ if (G_LIKELY (cclass->wait_jitter)) {
+ res = cclass->wait_jitter (clock, entry, jitter);
+ } else {
+ /* check if we have a simple _wait function otherwise. The function without
+ * the jitter arg is less optimal as we need to do an additional _get_time()
+ * which is not atomic with the _wait() and a typical _wait() function does
+ * yet another _get_time() anyway. */
+ if (G_UNLIKELY (cclass->wait == NULL))
+ goto not_supported;
if (jitter) {
- now = gst_clock_get_time (clock);
- *jitter = now - requested;
- }
+ GstClockTime now = gst_clock_get_time (clock);
- if (clock->stats) {
- gst_clock_update_stats (clock);
+ /* jitter is the diff against the clock when this entry is scheduled. Negative
+ * values mean that the entry was in time, a positive value means that the
+ * entry was too late. */
+ *jitter = GST_CLOCK_DIFF (requested, now);
}
+ res = cclass->wait (clock, entry);
}
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "done waiting entry %p, res: %d", id, res);
+
+ if (entry->type == GST_CLOCK_ENTRY_PERIODIC)
+ entry->time = requested + entry->interval;
+
+ if (G_UNLIKELY (clock->stats))
+ gst_clock_update_stats (clock);
+
return res;
+
+ /* ERRORS */
+invalid_time:
+ {
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "invalid time requested, returning _BADTIME");
+ return GST_CLOCK_BADTIME;
+ }
+not_supported:
+ {
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported");
+ return GST_CLOCK_UNSUPPORTED;
+ }
}
/**
- * gst_clock_id_wait_async:
+ * gst_clock_id_wait_async_full:
* @id: a #GstClockID to wait on
- * @func: The callback function
- * @user_data: User data passed in the calback
+ * @func: The callback function
+ * @user_data: User data passed in the callback
+ * @destroy_data: #GDestroyNotify for user_data
+ *
+ * Register a callback on the given #GstClockID @id with the given
+ * function and user_data. When passing a #GstClockID with an invalid
+ * time to this function, the callback will be called immediately
+ * with a time set to GST_CLOCK_TIME_NONE. The callback will
+ * be called when the time of @id has been reached.
*
- * Register a callback on the given clockid with the given
- * function and user_data.
+ * The callback @func can be invoked from any thread, either provided by the
+ * core or from a streaming thread. The application should be prepared for this.
*
* Returns: the result of the non blocking wait.
+ *
+ * MT safe.
+ *
+ * Since: 0.10.30
*/
GstClockReturn
-gst_clock_id_wait_async (GstClockID id,
- GstClockCallback func, gpointer user_data)
+gst_clock_id_wait_async_full (GstClockID id,
+ GstClockCallback func, gpointer user_data, GDestroyNotify destroy_data)
{
GstClockEntry *entry;
GstClock *clock;
- GstClockReturn res = GST_CLOCK_UNSUPPORTED;
+ GstClockReturn res;
GstClockClass *cclass;
-
+ GstClockTime requested;
+
g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR);
g_return_val_if_fail (func != NULL, GST_CLOCK_ERROR);
entry = (GstClockEntry *) id;
- clock = entry->clock;
+ requested = GST_CLOCK_ENTRY_TIME (entry);
+ clock = GST_CLOCK_ENTRY_CLOCK (entry);
- if (! GST_CLOCK_TIME_IS_VALID (GST_CLOCK_ENTRY_TIME (entry))) {
- (func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
- return GST_CLOCK_TIMEOUT;
- }
+ /* can't sync on invalid times */
+ if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
+ goto invalid_time;
cclass = GST_CLOCK_GET_CLASS (clock);
- if (cclass->wait_async) {
- entry->func = func;
- entry->user_data = user_data;
+ if (G_UNLIKELY (cclass->wait_async == NULL))
+ goto not_supported;
- res = cclass->wait_async (clock, entry);
- }
+ entry->func = func;
+ entry->user_data = user_data;
+ entry->destroy_data = destroy_data;
+
+ res = cclass->wait_async (clock, entry);
return res;
-}
-static void
-gst_clock_reschedule_func (GstClockEntry *entry)
-{
- entry->status = GST_CLOCK_ENTRY_OK;
-
- gst_clock_id_unlock ((GstClockID)entry);
+ /* ERRORS */
+invalid_time:
+ {
+ (func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "invalid time requested, returning _BADTIME");
+ return GST_CLOCK_BADTIME;
+ }
+not_supported:
+ {
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported");
+ return GST_CLOCK_UNSUPPORTED;
+ }
}
/**
- * gst_clock_id_unschedule:
- * @id: The id to unschedule
+ * gst_clock_id_wait_async:
+ * @id: a #GstClockID to wait on
+ * @func: The callback function
+ * @user_data: User data passed in the callback
*
- * Cancel an outstanding async notification request with the given ID.
- */
-void
-gst_clock_id_unschedule (GstClockID id)
-{
- GstClockEntry *entry;
- GstClock *clock;
- GstClockClass *cclass;
-
- g_return_if_fail (id != NULL);
-
- entry = (GstClockEntry *) id;
- clock = entry->clock;
-
- cclass = GST_CLOCK_GET_CLASS (clock);
-
- if (cclass->unschedule)
- cclass->unschedule (clock, entry);
-}
-
-/**
- * gst_clock_id_free:
- * @id: The clockid to free
+ * Register a callback on the given #GstClockID @id with the given
+ * function and user_data. When passing a #GstClockID with an invalid
+ * time to this function, the callback will be called immediately
+ * with a time set to GST_CLOCK_TIME_NONE. The callback will
+ * be called when the time of @id has been reached.
*
- * Free the resources held by the given id
+ * The callback @func can be invoked from any thread, either provided by the
+ * core or from a streaming thread. The application should be prepared for this.
+ *
+ * Returns: the result of the non blocking wait.
+ *
+ * MT safe.
*/
-void
-gst_clock_id_free (GstClockID id)
+GstClockReturn
+gst_clock_id_wait_async (GstClockID id,
+ GstClockCallback func, gpointer user_data)
{
- g_return_if_fail (id != NULL);
-
-#ifndef GST_DISABLE_TRACE
- gst_alloc_trace_free (_gst_clock_entry_trace, id);
-#endif
- gst_mem_chunk_free (_gst_clock_entries_chunk, id);
+ return gst_clock_id_wait_async_full (id, func, user_data, NULL);
}
/**
- * gst_clock_id_unlock:
- * @id: The clockid to unlock
+ * gst_clock_id_unschedule:
+ * @id: The id to unschedule
+ *
+ * Cancel an outstanding request with @id. This can either
+ * be an outstanding async notification or a pending sync notification.
+ * After this call, @id cannot be used anymore to receive sync or
+ * async notifications, you need to create a new #GstClockID.
*
- * Unlock the givan ClockID.
+ * MT safe.
*/
void
-gst_clock_id_unlock (GstClockID id)
+gst_clock_id_unschedule (GstClockID id)
{
GstClockEntry *entry;
GstClock *clock;
GstClockClass *cclass;
-
+
g_return_if_fail (id != NULL);
entry = (GstClockEntry *) id;
cclass = GST_CLOCK_GET_CLASS (clock);
- if (cclass->unlock)
- cclass->unlock (clock, entry);
+ if (G_LIKELY (cclass->unschedule))
+ cclass->unschedule (clock, entry);
}
-/**
+/*
* GstClock abstract base class implementation
*/
-GType
-gst_clock_get_type (void)
-{
- static GType clock_type = 0;
-
- if (!clock_type) {
- static const GTypeInfo clock_info = {
- sizeof (GstClockClass),
- NULL,
- NULL,
- (GClassInitFunc) gst_clock_class_init,
- NULL,
- NULL,
- sizeof (GstClock),
- 4,
- (GInstanceInitFunc) gst_clock_init,
- NULL
- };
- clock_type = g_type_register_static (GST_TYPE_OBJECT, "GstClock",
- &clock_info, G_TYPE_FLAG_ABSTRACT);
- }
- return clock_type;
-}
+G_DEFINE_TYPE (GstClock, gst_clock, GST_TYPE_OBJECT);
static void
-gst_clock_class_init (GstClockClass *klass)
+gst_clock_class_init (GstClockClass * klass)
{
- GObjectClass *gobject_class;
- GstObjectClass *gstobject_class;
-
- gobject_class = (GObjectClass*) klass;
- gstobject_class = (GstObjectClass*) klass;
-
- parent_class = g_type_class_ref (GST_TYPE_OBJECT);
-
- if (!g_thread_supported ())
- g_thread_init (NULL);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- _gst_clock_entries_chunk = gst_mem_chunk_new ("GstClockEntries",
- sizeof (GstClockEntry), sizeof (GstClockEntry) * 32,
- G_ALLOC_AND_FREE);
+ parent_class = g_type_class_peek_parent (klass);
#ifndef GST_DISABLE_TRACE
- _gst_clock_entry_trace = gst_alloc_trace_register (GST_CLOCK_ENTRY_TRACE_NAME);
+ _gst_clock_entry_trace =
+ gst_alloc_trace_register (GST_CLOCK_ENTRY_TRACE_NAME);
#endif
- gobject_class->dispose = GST_DEBUG_FUNCPTR (gst_clock_dispose);
- gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_clock_set_property);
- gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_clock_get_property);
-
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STATS,
- g_param_spec_boolean ("stats", "Stats", "Enable clock stats",
- FALSE, G_PARAM_READWRITE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MAX_DIFF,
- g_param_spec_int64 ("max-diff", "Max diff", "The maximum amount of time to wait in nanoseconds",
- 0, G_MAXINT64, DEFAULT_MAX_DIFF, G_PARAM_READWRITE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_EVENT_DIFF,
- g_param_spec_uint64 ("event-diff", "event diff",
- "The amount of time that may elapse until 2 events are treated as happening at different times",
- 0, G_MAXUINT64, DEFAULT_EVENT_DIFF, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
+ gobject_class->dispose = gst_clock_dispose;
+ gobject_class->finalize = gst_clock_finalize;
+ gobject_class->set_property = gst_clock_set_property;
+ gobject_class->get_property = gst_clock_get_property;
+
+ g_object_class_install_property (gobject_class, PROP_STATS,
+ g_param_spec_boolean ("stats", "Stats",
+ "Enable clock stats (unimplemented)", DEFAULT_STATS,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_WINDOW_SIZE,
+ g_param_spec_int ("window-size", "Window size",
+ "The size of the window used to calculate rate and offset", 2, 1024,
+ DEFAULT_WINDOW_SIZE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_WINDOW_THRESHOLD,
+ g_param_spec_int ("window-threshold", "Window threshold",
+ "The threshold to start calculating rate and offset", 2, 1024,
+ DEFAULT_WINDOW_THRESHOLD,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (gobject_class, PROP_TIMEOUT,
+ g_param_spec_uint64 ("timeout", "Timeout",
+ "The amount of time, in nanoseconds, to sample master and slave clocks",
+ 0, G_MAXUINT64, DEFAULT_TIMEOUT,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_type_class_add_private (klass, sizeof (GstClockPrivate));
}
static void
-gst_clock_init (GstClock *clock)
+gst_clock_init (GstClock * clock)
{
- clock->max_diff = DEFAULT_MAX_DIFF;
-
- clock->speed = 1.0;
- clock->active = TRUE;
- clock->start_time = 0;
clock->last_time = 0;
clock->entries = NULL;
- clock->flags = 0;
+ clock->entries_changed = g_cond_new ();
clock->stats = FALSE;
- clock->active_mutex = g_mutex_new ();
- clock->active_cond = g_cond_new ();
+ clock->ABI.priv =
+ G_TYPE_INSTANCE_GET_PRIVATE (clock, GST_TYPE_CLOCK, GstClockPrivate);
+
+ clock->internal_calibration = 0;
+ clock->external_calibration = 0;
+ clock->rate_numerator = 1;
+ clock->rate_denominator = 1;
+
+ clock->slave_lock = g_mutex_new ();
+ clock->window_size = DEFAULT_WINDOW_SIZE;
+ clock->window_threshold = DEFAULT_WINDOW_THRESHOLD;
+ clock->filling = TRUE;
+ clock->time_index = 0;
+ clock->timeout = DEFAULT_TIMEOUT;
+ clock->times = g_new0 (GstClockTime, 4 * clock->window_size);
}
static void
-gst_clock_dispose (GObject *object)
+gst_clock_dispose (GObject * object)
{
GstClock *clock = GST_CLOCK (object);
+ GstClock **master_p;
- g_mutex_free (clock->active_mutex);
- g_cond_free (clock->active_cond);
+ GST_OBJECT_LOCK (clock);
+ master_p = &clock->master;
+ gst_object_replace ((GstObject **) master_p, NULL);
+ GST_OBJECT_UNLOCK (clock);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
-/**
- * gst_clock_set_speed
- * @clock: a #GstClock to modify
- * @speed: the speed to set on the clock
- *
- * Sets the speed on the given clock. 1.0 is the default
- * speed.
- *
- * Returns: the new speed of the clock.
- */
-gdouble
-gst_clock_set_speed (GstClock *clock, gdouble speed)
+static void
+gst_clock_finalize (GObject * object)
{
- g_return_val_if_fail (GST_IS_CLOCK (clock), 0.0);
+ GstClock *clock = GST_CLOCK (object);
- GST_WARNING_OBJECT (clock, "called deprecated function");
- return clock->speed;
-}
+ GST_CLOCK_SLAVE_LOCK (clock);
+ if (clock->clockid) {
+ gst_clock_id_unschedule (clock->clockid);
+ gst_clock_id_unref (clock->clockid);
+ clock->clockid = NULL;
+ }
+ g_free (clock->times);
+ clock->times = NULL;
+ GST_CLOCK_SLAVE_UNLOCK (clock);
-/**
- * gst_clock_get_speed
- * @clock: a #GstClock to query
- *
- * Gets the speed of the given clock.
- *
- * Returns: the speed of the clock.
- */
-gdouble
-gst_clock_get_speed (GstClock *clock)
-{
- g_return_val_if_fail (GST_IS_CLOCK (clock), 0.0);
+ g_cond_free (clock->entries_changed);
+ g_mutex_free (clock->slave_lock);
- GST_WARNING_OBJECT (clock, "called deprecated function");
- return clock->speed;
+ G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* gst_clock_set_resolution
- * @clock: The clock set the resolution on
+ * @clock: a #GstClock
* @resolution: The resolution to set
*
- * Set the accuracy of the clock.
+ * Set the accuracy of the clock. Some clocks have the possibility to operate
+ * with different accuracy at the expense of more resource usage. There is
+ * normally no need to change the default resolution of a clock. The resolution
+ * of a clock can only be changed if the clock has the
+ * #GST_CLOCK_FLAG_CAN_SET_RESOLUTION flag set.
*
* Returns: the new resolution of the clock.
*/
-guint64
-gst_clock_set_resolution (GstClock *clock, guint64 resolution)
+GstClockTime
+gst_clock_set_resolution (GstClock * clock, GstClockTime resolution)
{
GstClockClass *cclass;
- g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0));
- g_return_val_if_fail (resolution != 0, G_GINT64_CONSTANT (0));
+ g_return_val_if_fail (GST_IS_CLOCK (clock), 0);
+ g_return_val_if_fail (resolution != 0, 0);
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->change_resolution)
- clock->resolution = cclass->change_resolution (clock, clock->resolution, resolution);
+ clock->resolution =
+ cclass->change_resolution (clock, clock->resolution, resolution);
return clock->resolution;
}
/**
* gst_clock_get_resolution
- * @clock: The clock get the resolution of
+ * @clock: a #GstClock
*
- * Get the accuracy of the clock.
+ * Get the accuracy of the clock. The accuracy of the clock is the granularity
+ * of the values returned by gst_clock_get_time().
*
- * Returns: the resolution of the clock in microseconds.
+ * Returns: the resolution of the clock in units of #GstClockTime.
+ *
+ * MT safe.
*/
-guint64
-gst_clock_get_resolution (GstClock *clock)
+GstClockTime
+gst_clock_get_resolution (GstClock * clock)
{
GstClockClass *cclass;
- g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0));
+ g_return_val_if_fail (GST_IS_CLOCK (clock), 0);
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->get_resolution)
return cclass->get_resolution (clock);
- return G_GINT64_CONSTANT (1);
+ return 1;
}
/**
- * gst_clock_set_active
- * @clock: a #GstClock to set state of
- * @active: flag indicating if the clock should be activated (TRUE) or deactivated
+ * gst_clock_adjust_unlocked
+ * @clock: a #GstClock to use
+ * @internal: a clock time
+ *
+ * Converts the given @internal clock time to the external time, adjusting for the
+ * rate and reference time set with gst_clock_set_calibration() and making sure
+ * that the returned time is increasing. This function should be called with the
+ * clock's OBJECT_LOCK held and is mainly used by clock subclasses.
*
- * Activates or deactivates the clock based on the active parameter.
- * As soon as the clock is activated, the time will start ticking.
+ * This function is the reverse of gst_clock_unadjust_unlocked().
+ *
+ * Returns: the converted time of the clock.
*/
-void
-gst_clock_set_active (GstClock *clock, gboolean active)
+GstClockTime
+gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal)
{
- g_return_if_fail (GST_IS_CLOCK (clock));
-
- GST_ERROR_OBJECT (clock, "called deprecated function that does nothing now.");
+ GstClockTime ret, cinternal, cexternal, cnum, cdenom;
+
+ /* get calibration values for readability */
+ cinternal = clock->internal_calibration;
+ cexternal = clock->external_calibration;
+ cnum = clock->rate_numerator;
+ cdenom = clock->rate_denominator;
+
+ /* avoid divide by 0 */
+ if (G_UNLIKELY (cdenom == 0))
+ cnum = cdenom = 1;
+
+ /* The formula is (internal - cinternal) * cnum / cdenom + cexternal
+ *
+ * Since we do math on unsigned 64-bit ints we have to special case for
+ * internal < cinternal to get the sign right. this case is not very common,
+ * though.
+ */
+ if (G_LIKELY (internal >= cinternal)) {
+ ret = internal - cinternal;
+ ret = gst_util_uint64_scale (ret, cnum, cdenom);
+ ret += cexternal;
+ } else {
+ ret = cinternal - internal;
+ ret = gst_util_uint64_scale (ret, cnum, cdenom);
+ /* clamp to 0 */
+ if (G_LIKELY (cexternal > ret))
+ ret = cexternal - ret;
+ else
+ ret = 0;
+ }
+
+ /* make sure the time is increasing */
+ clock->last_time = MAX (ret, clock->last_time);
+
+ return clock->last_time;
+}
- return;
+/**
+ * gst_clock_unadjust_unlocked
+ * @clock: a #GstClock to use
+ * @external: an external clock time
+ *
+ * Converts the given @external clock time to the internal time of @clock,
+ * using the rate and reference time set with gst_clock_set_calibration().
+ * This function should be called with the clock's OBJECT_LOCK held and
+ * is mainly used by clock subclasses.
+ *
+ * This function is the reverse of gst_clock_adjust_unlocked().
+ *
+ * Returns: the internal time of the clock corresponding to @external.
+ *
+ * Since: 0.10.13
+ */
+GstClockTime
+gst_clock_unadjust_unlocked (GstClock * clock, GstClockTime external)
+{
+ GstClockTime ret, cinternal, cexternal, cnum, cdenom;
+
+ /* get calibration values for readability */
+ cinternal = clock->internal_calibration;
+ cexternal = clock->external_calibration;
+ cnum = clock->rate_numerator;
+ cdenom = clock->rate_denominator;
+
+ /* avoid divide by 0 */
+ if (G_UNLIKELY (cnum == 0))
+ cnum = cdenom = 1;
+
+ /* The formula is (external - cexternal) * cdenom / cnum + cinternal */
+ if (G_LIKELY (external >= cexternal)) {
+ ret = external - cexternal;
+ ret = gst_util_uint64_scale (ret, cdenom, cnum);
+ ret += cinternal;
+ } else {
+ ret = cexternal - external;
+ ret = gst_util_uint64_scale (ret, cdenom, cnum);
+ if (G_LIKELY (cinternal > ret))
+ ret = cinternal - ret;
+ else
+ ret = 0;
+ }
+ return ret;
}
/**
- * gst_clock_is_active
+ * gst_clock_get_internal_time
* @clock: a #GstClock to query
*
- * Checks if the given clock is active.
- *
- * Returns: TRUE if the clock is active.
+ * Gets the current internal time of the given clock. The time is returned
+ * unadjusted for the offset and the rate.
+ *
+ * Returns: the internal time of the clock. Or GST_CLOCK_TIME_NONE when
+ * given invalid input.
+ *
+ * MT safe.
*/
-gboolean
-gst_clock_is_active (GstClock *clock)
+GstClockTime
+gst_clock_get_internal_time (GstClock * clock)
{
- g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
+ GstClockTime ret;
+ GstClockClass *cclass;
- GST_WARNING_OBJECT (clock, "called deprecated function.");
+ g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE);
- return TRUE;
+ cclass = GST_CLOCK_GET_CLASS (clock);
+
+ if (G_UNLIKELY (cclass->get_internal_time == NULL))
+ goto not_supported;
+
+ ret = cclass->get_internal_time (clock);
+
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "internal time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (ret));
+
+ return ret;
+
+ /* ERRORS */
+not_supported:
+ {
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "internal time not supported, return 0");
+ return G_GINT64_CONSTANT (0);
+ }
}
/**
- * gst_clock_reset
- * @clock: a #GstClock to reset
+ * gst_clock_get_time
+ * @clock: a #GstClock to query
+ *
+ * Gets the current time of the given clock. The time is always
+ * monotonically increasing and adjusted according to the current
+ * offset and rate.
+ *
+ * Returns: the time of the clock. Or GST_CLOCK_TIME_NONE when
+ * given invalid input.
*
- * Reset the clock to time 0.
+ * MT safe.
*/
-void
-gst_clock_reset (GstClock *clock)
+GstClockTime
+gst_clock_get_time (GstClock * clock)
{
- GstClockTime time = G_GINT64_CONSTANT (0);
- GstClockClass *cclass;
+ GstClockTime ret;
+ gint seq;
- g_return_if_fail (GST_IS_CLOCK (clock));
+ g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE);
- GST_ERROR_OBJECT (clock, "called deprecated function.");
+ do {
+ /* reget the internal time when we retry to get the most current
+ * timevalue */
+ ret = gst_clock_get_internal_time (clock);
- cclass = GST_CLOCK_GET_CLASS (clock);
-
- if (cclass->get_internal_time) {
- time = cclass->get_internal_time (clock);
- }
+ seq = read_seqbegin (clock);
+ /* this will scale for rate and offset */
+ ret = gst_clock_adjust_unlocked (clock, ret);
+ } while (read_seqretry (clock, seq));
- GST_LOCK (clock);
- //clock->active = FALSE;
- clock->start_time = time;
- clock->last_time = G_GINT64_CONSTANT (0);
- g_list_foreach (clock->entries, (GFunc) gst_clock_reschedule_func, NULL);
- GST_UNLOCK (clock);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "adjusted time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (ret));
+
+ return ret;
}
/**
- * gst_clock_handle_discont
- * @clock: a #GstClock to notify of the discontinuity
- * @time: The new time
+ * gst_clock_set_calibration
+ * @clock: a #GstClock to calibrate
+ * @internal: a reference internal time
+ * @external: a reference external time
+ * @rate_num: the numerator of the rate of the clock relative to its
+ * internal time
+ * @rate_denom: the denominator of the rate of the clock
+ *
+ * Adjusts the rate and time of @clock. A rate of 1/1 is the normal speed of
+ * the clock. Values bigger than 1/1 make the clock go faster.
+ *
+ * @internal and @external are calibration parameters that arrange that
+ * gst_clock_get_time() should have been @external at internal time @internal.
+ * This internal time should not be in the future; that is, it should be less
+ * than the value of gst_clock_get_internal_time() when this function is called.
+ *
+ * Subsequent calls to gst_clock_get_time() will return clock times computed as
+ * follows:
+ *
+ * <programlisting>
+ * time = (internal_time - internal) * rate_num / rate_denom + external
+ * </programlisting>
*
- * Notifies the clock of a discontinuity in time.
+ * This formula is implemented in gst_clock_adjust_unlocked(). Of course, it
+ * tries to do the integer arithmetic as precisely as possible.
*
- * Returns: TRUE if the clock was updated. It is possible that
- * the clock was not updated by this call because only the first
- * discontinuitity in the pipeline is honoured.
+ * Note that gst_clock_get_time() always returns increasing values so when you
+ * move the clock backwards, gst_clock_get_time() will report the previous value
+ * until the clock catches up.
+ *
+ * MT safe.
*/
-gboolean
-gst_clock_handle_discont (GstClock *clock, guint64 time)
+void
+gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime
+ external, GstClockTime rate_num, GstClockTime rate_denom)
{
- GST_ERROR_OBJECT (clock, "called deprecated function.");
-
- return FALSE;
+ g_return_if_fail (GST_IS_CLOCK (clock));
+ g_return_if_fail (rate_num != GST_CLOCK_TIME_NONE);
+ g_return_if_fail (rate_denom > 0 && rate_denom != GST_CLOCK_TIME_NONE);
+
+ write_seqlock (clock);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "internal %" GST_TIME_FORMAT " external %" GST_TIME_FORMAT " %"
+ G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT " = %f", GST_TIME_ARGS (internal),
+ GST_TIME_ARGS (external), rate_num, rate_denom,
+ gst_guint64_to_gdouble (rate_num) / gst_guint64_to_gdouble (rate_denom));
+
+ clock->internal_calibration = internal;
+ clock->external_calibration = external;
+ clock->rate_numerator = rate_num;
+ clock->rate_denominator = rate_denom;
+ write_sequnlock (clock);
}
/**
- * gst_clock_get_time
- * @clock: a #GstClock to query
+ * gst_clock_get_calibration
+ * @clock: a #GstClock
+ * @internal: (out) (allow-none): a location to store the internal time
+ * @external: (out) (allow-none): a location to store the external time
+ * @rate_num: (out) (allow-none): a location to store the rate numerator
+ * @rate_denom: (out) (allow-none): a location to store the rate denominator
*
- * Gets the current time of the given clock. The time is always
- * monotonically increasing.
+ * Gets the internal rate and reference time of @clock. See
+ * gst_clock_set_calibration() for more information.
*
- * Returns: the time of the clock.
+ * @internal, @external, @rate_num, and @rate_denom can be left %NULL if the
+ * caller is not interested in the values.
+ *
+ * MT safe.
*/
-GstClockTime
-gst_clock_get_time (GstClock *clock)
+void
+gst_clock_get_calibration (GstClock * clock, GstClockTime * internal,
+ GstClockTime * external, GstClockTime * rate_num, GstClockTime * rate_denom)
{
- GstClockTime ret = G_GINT64_CONSTANT (0);
+ gint seq;
- g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0));
+ g_return_if_fail (GST_IS_CLOCK (clock));
- GstClockClass *cclass;
+ do {
+ seq = read_seqbegin (clock);
+ if (rate_num)
+ *rate_num = clock->rate_numerator;
+ if (rate_denom)
+ *rate_denom = clock->rate_denominator;
+ if (external)
+ *external = clock->external_calibration;
+ if (internal)
+ *internal = clock->internal_calibration;
+ } while (read_seqretry (clock, seq));
+}
- cclass = GST_CLOCK_GET_CLASS (clock);
+/* will be called repeatedly to sample the master and slave clock
+ * to recalibrate the clock */
+static gboolean
+gst_clock_slave_callback (GstClock * master, GstClockTime time,
+ GstClockID id, GstClock * clock)
+{
+ GstClockTime stime, mtime;
+ gdouble r_squared;
- if (cclass->get_internal_time) {
- ret = cclass->get_internal_time (clock) - clock->start_time;
- }
- /* make sure the time is increasing, else return last_time */
- if ((gint64) ret < (gint64) clock->last_time) {
- ret = clock->last_time;
- }
- else {
- clock->last_time = ret;
- }
+ stime = gst_clock_get_internal_time (clock);
+ mtime = gst_clock_get_time (master);
- return ret;
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "master %" GST_TIME_FORMAT ", slave %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (mtime), GST_TIME_ARGS (stime));
+
+ gst_clock_add_observation (clock, stime, mtime, &r_squared);
+
+ /* FIXME, we can use the r_squared value to adjust the timeout
+ * value of the clockid */
+
+ return TRUE;
}
/**
- * gst_clock_get_event_time:
- * @clock: clock to query
+ * gst_clock_set_master
+ * @clock: a #GstClock
+ * @master: (allow-none): a master #GstClock
+ *
+ * Set @master as the master clock for @clock. @clock will be automatically
+ * calibrated so that gst_clock_get_time() reports the same time as the
+ * master clock.
*
- * Gets the "event time" of a given clock. An event on the clock happens
- * whenever this function is called. This ensures that multiple events that
- * happen shortly after each other are treated as if they happened at the same
- * time. GStreamer uses to keep state changes of multiple elements in sync.
+ * A clock provider that slaves its clock to a master can get the current
+ * calibration values with gst_clock_get_calibration().
*
- * Returns: the time of the event
+ * @master can be %NULL in which case @clock will not be slaved anymore. It will
+ * however keep reporting its time adjusted with the last configured rate
+ * and time offsets.
+ *
+ * Returns: %TRUE if the clock is capable of being slaved to a master clock.
+ * Trying to set a master on a clock without the
+ * #GST_CLOCK_FLAG_CAN_SET_MASTER flag will make this function return %FALSE.
+ *
+ * MT safe.
*/
-GstClockTime
-gst_clock_get_event_time (GstClock *clock)
+gboolean
+gst_clock_set_master (GstClock * clock, GstClock * master)
{
- GstClockTime time;
-
- g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE);
-
- time = gst_clock_get_time (clock);
+ GstClock **master_p;
- if (clock->last_event + clock->max_event_diff >= time) {
- GST_LOG_OBJECT (clock, "reporting last event time %"G_GUINT64_FORMAT,
- clock->last_event);
- } else {
- GST_LOG_OBJECT (clock, "reporting new event time %"G_GUINT64_FORMAT,
- clock->last_event);
- clock->last_event = time;
+ g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
+ g_return_val_if_fail (master != clock, FALSE);
+
+ GST_OBJECT_LOCK (clock);
+ /* we always allow setting the master to NULL */
+ if (master && !GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_CAN_SET_MASTER))
+ goto not_supported;
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "slaving %p to master clock %p", clock, master);
+ GST_OBJECT_UNLOCK (clock);
+
+ GST_CLOCK_SLAVE_LOCK (clock);
+ if (clock->clockid) {
+ gst_clock_id_unschedule (clock->clockid);
+ gst_clock_id_unref (clock->clockid);
+ clock->clockid = NULL;
+ }
+ if (master) {
+ clock->filling = TRUE;
+ clock->time_index = 0;
+ /* use the master periodic id to schedule sampling and
+ * clock calibration. */
+ clock->clockid = gst_clock_new_periodic_id (master,
+ gst_clock_get_time (master), clock->timeout);
+ gst_clock_id_wait_async_full (clock->clockid,
+ (GstClockCallback) gst_clock_slave_callback,
+ gst_object_ref (clock), (GDestroyNotify) gst_object_unref);
+ }
+ GST_CLOCK_SLAVE_UNLOCK (clock);
+
+ GST_OBJECT_LOCK (clock);
+ master_p = &clock->master;
+ gst_object_replace ((GstObject **) master_p, (GstObject *) master);
+ GST_OBJECT_UNLOCK (clock);
+
+ return TRUE;
+
+ /* ERRORS */
+not_supported:
+ {
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ "cannot be slaved to a master clock");
+ GST_OBJECT_UNLOCK (clock);
+ return FALSE;
}
-
- return clock->last_event;
}
/**
- * gst_clock_get_next_id
- * @clock: The clock to query
+ * gst_clock_get_master:
+ * @clock: a #GstClock
+ *
+ * Get the master clock that @clock is slaved to or %NULL when the clock is
+ * not slaved to any master clock.
*
- * Get the clockid of the next event.
+ * Returns: (transfer full): a master #GstClock or %NULL when this clock is
+ * not slaved to a master clock. Unref after usage.
*
- * Returns: a clockid or NULL is no event is pending.
+ * MT safe.
*/
-GstClockID
-gst_clock_get_next_id (GstClock *clock)
+GstClock *
+gst_clock_get_master (GstClock * clock)
{
- GstClockEntry *entry = NULL;
+ GstClock *result = NULL;
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
- GST_LOCK (clock);
- if (clock->entries)
- entry = GST_CLOCK_ENTRY (clock->entries->data);
- GST_UNLOCK (clock);
+ GST_OBJECT_LOCK (clock);
+ if (clock->master)
+ result = gst_object_ref (clock->master);
+ GST_OBJECT_UNLOCK (clock);
+
+ return result;
+}
+
+/* http://mathworld.wolfram.com/LeastSquaresFitting.html
+ * with SLAVE_LOCK
+ */
+static gboolean
+do_linear_regression (GstClock * clock, GstClockTime * m_num,
+ GstClockTime * m_denom, GstClockTime * b, GstClockTime * xbase,
+ gdouble * r_squared)
+{
+ GstClockTime *newx, *newy;
+ GstClockTime xmin, ymin, xbar, ybar, xbar4, ybar4;
+ GstClockTimeDiff sxx, sxy, syy;
+ GstClockTime *x, *y;
+ gint i, j;
+ guint n;
+
+ xbar = ybar = sxx = syy = sxy = 0;
+
+ x = clock->times;
+ y = clock->times + 2;
+ n = clock->filling ? clock->time_index : clock->window_size;
+
+#ifdef DEBUGGING_ENABLED
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "doing regression on:");
+ for (i = j = 0; i < n; i++, j += 4)
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, x[j], y[j]);
+#endif
+
+ xmin = ymin = G_MAXUINT64;
+ for (i = j = 0; i < n; i++, j += 4) {
+ xmin = MIN (xmin, x[j]);
+ ymin = MIN (ymin, y[j]);
+ }
+
+#ifdef DEBUGGING_ENABLED
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "min x: %" G_GUINT64_FORMAT,
+ xmin);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "min y: %" G_GUINT64_FORMAT,
+ ymin);
+#endif
+
+ newx = clock->times + 1;
+ newy = clock->times + 3;
+
+ /* strip off unnecessary bits of precision */
+ for (i = j = 0; i < n; i++, j += 4) {
+ newx[j] = x[j] - xmin;
+ newy[j] = y[j] - ymin;
+ }
+
+#ifdef DEBUGGING_ENABLED
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "reduced numbers:");
+ for (i = j = 0; i < n; i++, j += 4)
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
+ " %" G_GUINT64_FORMAT " %" G_GUINT64_FORMAT, newx[j], newy[j]);
+#endif
+
+ /* have to do this precisely otherwise the results are pretty much useless.
+ * should guarantee that none of these accumulators can overflow */
+
+ /* quantities on the order of 1e10 -> 30 bits; window size a max of 2^10, so
+ this addition could end up around 2^40 or so -- ample headroom */
+ for (i = j = 0; i < n; i++, j += 4) {
+ xbar += newx[j];
+ ybar += newy[j];
+ }
+ xbar /= n;
+ ybar /= n;
+
+#ifdef DEBUGGING_ENABLED
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbar = %" G_GUINT64_FORMAT,
+ xbar);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " ybar = %" G_GUINT64_FORMAT,
+ ybar);
+#endif
+
+ /* multiplying directly would give quantities on the order of 1e20 -> 60 bits;
+ times the window size that's 70 which is too much. Instead we (1) subtract
+ off the xbar*ybar in the loop instead of after, to avoid accumulation; (2)
+ shift off 4 bits from each multiplicand, giving an expected ceiling of 52
+ bits, which should be enough. Need to check the incoming range and domain
+ to ensure this is an appropriate loss of precision though. */
+ xbar4 = xbar >> 4;
+ ybar4 = ybar >> 4;
+ for (i = j = 0; i < n; i++, j += 4) {
+ GstClockTime newx4, newy4;
+
+ newx4 = newx[j] >> 4;
+ newy4 = newy[j] >> 4;
+
+ sxx += newx4 * newx4 - xbar4 * xbar4;
+ syy += newy4 * newy4 - ybar4 * ybar4;
+ sxy += newx4 * newy4 - xbar4 * ybar4;
+ }
+
+ if (G_UNLIKELY (sxx == 0))
+ goto invalid;
+
+ *m_num = sxy;
+ *m_denom = sxx;
+ *xbase = xmin;
+ *b = (ybar + ymin) - gst_util_uint64_scale (xbar, *m_num, *m_denom);
+ *r_squared = ((double) sxy * (double) sxy) / ((double) sxx * (double) syy);
+
+#ifdef DEBUGGING_ENABLED
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " m = %g",
+ ((double) *m_num) / *m_denom);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " b = %" G_GUINT64_FORMAT,
+ *b);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " xbase = %" G_GUINT64_FORMAT,
+ *xbase);
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, " r2 = %g", *r_squared);
+#endif
+
+ return TRUE;
+
+invalid:
+ {
+ GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "sxx == 0, regression failed");
+ return FALSE;
+ }
+}
+
+/**
+ * gst_clock_add_observation
+ * @clock: a #GstClock
+ * @slave: a time on the slave
+ * @master: a time on the master
+ * @r_squared: (out): a pointer to hold the result
+ *
+ * The time @master of the master clock and the time @slave of the slave
+ * clock are added to the list of observations. If enough observations
+ * are available, a linear regression algorithm is run on the
+ * observations and @clock is recalibrated.
+ *
+ * If this functions returns %TRUE, @r_squared will contain the
+ * correlation coefficient of the interpolation. A value of 1.0
+ * means a perfect regression was performed. This value can
+ * be used to control the sampling frequency of the master and slave
+ * clocks.
+ *
+ * Returns: %TRUE if enough observations were added to run the
+ * regression algorithm.
+ *
+ * MT safe.
+ */
+gboolean
+gst_clock_add_observation (GstClock * clock, GstClockTime slave,
+ GstClockTime master, gdouble * r_squared)
+{
+ GstClockTime m_num, m_denom, b, xbase;
+
+ g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
+ g_return_val_if_fail (r_squared != NULL, FALSE);
+
+ GST_CLOCK_SLAVE_LOCK (clock);
+
+ GST_CAT_LOG_OBJECT (GST_CAT_CLOCK, clock,
+ "adding observation slave %" GST_TIME_FORMAT ", master %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (slave), GST_TIME_ARGS (master));
+
+ clock->times[(4 * clock->time_index)] = slave;
+ clock->times[(4 * clock->time_index) + 2] = master;
+
+ clock->time_index++;
+ if (G_UNLIKELY (clock->time_index == clock->window_size)) {
+ clock->filling = FALSE;
+ clock->time_index = 0;
+ }
+
+ if (G_UNLIKELY (clock->filling
+ && clock->time_index < clock->window_threshold))
+ goto filling;
+
+ if (!do_linear_regression (clock, &m_num, &m_denom, &b, &xbase, r_squared))
+ goto invalid;
+
+ GST_CLOCK_SLAVE_UNLOCK (clock);
+
+ GST_CAT_LOG_OBJECT (GST_CAT_CLOCK, clock,
+ "adjusting clock to m=%" G_GUINT64_FORMAT "/%" G_GUINT64_FORMAT ", b=%"
+ G_GUINT64_FORMAT " (rsquared=%g)", m_num, m_denom, b, *r_squared);
- return (GstClockID *) entry;
+ /* if we have a valid regression, adjust the clock */
+ gst_clock_set_calibration (clock, xbase, b, m_num, m_denom);
+
+ return TRUE;
+
+filling:
+ {
+ GST_CLOCK_SLAVE_UNLOCK (clock);
+ return FALSE;
+ }
+invalid:
+ {
+ /* no valid regression has been done, ignore the result then */
+ GST_CLOCK_SLAVE_UNLOCK (clock);
+ return TRUE;
+ }
}
static void
-gst_clock_update_stats (GstClock *clock)
+gst_clock_update_stats (GstClock * clock)
{
}
static void
-gst_clock_set_property (GObject *object, guint prop_id,
- const GValue *value, GParamSpec *pspec)
+gst_clock_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
{
GstClock *clock;
-
+
clock = GST_CLOCK (object);
switch (prop_id) {
- case ARG_STATS:
+ case PROP_STATS:
+ GST_OBJECT_LOCK (clock);
clock->stats = g_value_get_boolean (value);
- g_object_notify (object, "stats");
+ GST_OBJECT_UNLOCK (clock);
break;
- case ARG_MAX_DIFF:
- clock->max_diff = g_value_get_int64 (value);
- g_object_notify (object, "max-diff");
+ case PROP_WINDOW_SIZE:
+ GST_CLOCK_SLAVE_LOCK (clock);
+ clock->window_size = g_value_get_int (value);
+ clock->window_threshold =
+ MIN (clock->window_threshold, clock->window_size);
+ clock->times =
+ g_renew (GstClockTime, clock->times, 4 * clock->window_size);
+ /* restart calibration */
+ clock->filling = TRUE;
+ clock->time_index = 0;
+ GST_CLOCK_SLAVE_UNLOCK (clock);
break;
- case ARG_EVENT_DIFF:
- clock->max_event_diff = g_value_get_uint64 (value);
- g_object_notify (object, "event-diff");
+ case PROP_WINDOW_THRESHOLD:
+ GST_CLOCK_SLAVE_LOCK (clock);
+ clock->window_threshold =
+ MIN (g_value_get_int (value), clock->window_size);
+ GST_CLOCK_SLAVE_UNLOCK (clock);
+ break;
+ case PROP_TIMEOUT:
+ GST_CLOCK_SLAVE_LOCK (clock);
+ clock->timeout = g_value_get_uint64 (value);
+ GST_CLOCK_SLAVE_UNLOCK (clock);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
- }
+ }
}
static void
-gst_clock_get_property (GObject *object, guint prop_id,
- GValue *value, GParamSpec * pspec)
+gst_clock_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
{
GstClock *clock;
-
+
clock = GST_CLOCK (object);
switch (prop_id) {
- case ARG_STATS:
+ case PROP_STATS:
+ GST_OBJECT_LOCK (clock);
g_value_set_boolean (value, clock->stats);
+ GST_OBJECT_UNLOCK (clock);
break;
- case ARG_MAX_DIFF:
- g_value_set_int64 (value, clock->max_diff);
+ case PROP_WINDOW_SIZE:
+ GST_CLOCK_SLAVE_LOCK (clock);
+ g_value_set_int (value, clock->window_size);
+ GST_CLOCK_SLAVE_UNLOCK (clock);
break;
- case ARG_EVENT_DIFF:
- g_value_set_uint64 (value, clock->max_event_diff);
+ case PROP_WINDOW_THRESHOLD:
+ GST_CLOCK_SLAVE_LOCK (clock);
+ g_value_set_int (value, clock->window_threshold);
+ GST_CLOCK_SLAVE_UNLOCK (clock);
+ break;
+ case PROP_TIMEOUT:
+ GST_CLOCK_SLAVE_LOCK (clock);
+ g_value_set_uint64 (value, clock->timeout);
+ GST_CLOCK_SLAVE_UNLOCK (clock);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
- }
+ }
}