baseparse: support reverse playback
[platform/upstream/gstreamer.git] / gst / gstclock.c
index f8dbb20..45f503e 100644 (file)
@@ -1,6 +1,7 @@
 /* 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;
@@ -322,414 +657,848 @@ gst_clock_id_unlock (GstClockID 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;
-   }
+  }
 }