diff -r 000000000000 -r 0e761a78d257 gstreamer_core/gst/gstclock.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/gstreamer_core/gst/gstclock.c Thu Dec 17 08:53:32 2009 +0200 @@ -0,0 +1,1482 @@ +/* GStreamer + * Copyright (C) 1999,2000 Erik Walthinsen + * 2000 Wim Taymans + * 2004 Wim Taymans + * + * gstclock.c: Clock subsystem for maintaining time sync + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +/** + * 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. + * + * 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 stream 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 repeadedly called automatically + * until it is unscheduled. To schedule a sync periodic callback, + * gst_clock_id_wait() should be called repeadedly. + * + * 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 wirth 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 stream 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 repeadedly + * 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 "timeout", "window-size" + * and "window-threshold" properties. The "timeout" property defines the interval + * to sample the master clock and run the calibration functions. + * "window-size" defines the number of samples to use when calibrating and + * "window-threshold" defines the minimum number of samples before the + * calibration is performed. + * + * Last reviewed on 2006-08-11 (0.10.10) + */ + + +#include "gst_private.h" +#include + +#include "gstclock.h" +#include "gstinfo.h" +#include "gstutils.h" + +#ifndef GST_DISABLE_TRACE +/* #define GST_WITH_ALLOC_TRACE */ +#include "gsttrace.h" +static GstAllocTrace *_gst_clock_entry_trace; +#endif + +#ifdef __SYMBIAN32__ +#include +#endif + +#if GLIB_CHECK_VERSION (2, 10, 0) +#define ALLOC_ENTRY() g_slice_new (GstClockEntry) +#define FREE_ENTRY(entry) g_slice_free (GstClockEntry, entry) +#else +#define ALLOC_ENTRY() g_new (GstClockEntry, 1) +#define FREE_ENTRY(entry) g_free (entry) +#endif + +/* #define DEBUGGING_ENABLED */ + +#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 +}; + +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_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 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) +{ + GstClockEntry *entry; + + entry = ALLOC_ENTRY (); +#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)); + + gst_atomic_int_set (&entry->refcount, 1); + entry->clock = clock; + entry->type = type; + entry->time = time; + entry->interval = interval; + entry->status = GST_CLOCK_BUSY; + entry->func = NULL; + entry->user_data = NULL; + + return (GstClockID) entry; +} + +/** + * gst_clock_id_ref: + * @id: The #GstClockID to ref + * + * Increase the refcount of given @id. + * + * Returns: The same #GstClockID with increased refcount. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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) +{ + g_return_if_fail (id != NULL); + + GST_CAT_DEBUG (GST_CAT_CLOCK, "freed entry %p", id); + +#ifndef GST_DISABLE_TRACE + gst_alloc_trace_free (_gst_clock_entry_trace, id); +#endif + FREE_ENTRY (id); +} + +/** + * gst_clock_id_unref: + * @id: The #GstClockID to unref + * + * Unref given @id. When the refcount reaches 0 the + * #GstClockID will be freed. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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 #GstClockID to get a single shot notification from + * @time: 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. + * + * Returns: A #GstClockID that can be used to request the time notification. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockID +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); +} + +/** + * 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 @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. @id should be unreffed + * after usage. + * + * Returns: A #GstClockID that can be used to request the time notification. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockID +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); +} + +/** + * 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. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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 #GstClockID to query + * + * Get the time of the clock ID + * + * Returns: the time of the given clock id. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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); +} + +/** + * gst_clock_id_wait + * @id: The #GstClockID to wait on + * @jitter: 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(). + * + * 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. #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. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockReturn +gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter) +{ + GstClockEntry *entry; + GstClock *clock; + 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); + + clock = GST_CLOCK_ENTRY_CLOCK (entry); + + /* can't sync on invalid times */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested))) + goto invalid_time; + + /* a previously unscheduled entry cannot be scheduled again */ + if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED)) + goto unscheduled; + + cclass = GST_CLOCK_GET_CLASS (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) { + GstClockTime now = gst_clock_get_time (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 (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; + } +unscheduled: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "entry was unscheduled return _UNSCHEDULED"); + return GST_CLOCK_UNSCHEDULED; + } +not_supported: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported"); + return GST_CLOCK_UNSUPPORTED; + } +} + +/** + * gst_clock_id_wait_async: + * @id: a #GstClockID to wait on + * @func: The callback function + * @user_data: User data passed in the calback + * + * 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 immediatly + * with a time set to GST_CLOCK_TIME_NONE. The callback will + * be called when the time of @id has been reached. + * + * Returns: the result of the non blocking wait. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockReturn +gst_clock_id_wait_async (GstClockID id, + GstClockCallback func, gpointer user_data) +{ + GstClockEntry *entry; + GstClock *clock; + 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; + requested = GST_CLOCK_ENTRY_TIME (entry); + clock = GST_CLOCK_ENTRY_CLOCK (entry); + + /* can't sync on invalid times */ + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested))) + goto invalid_time; + + /* a previously unscheduled entry cannot be scheduled again */ + if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED)) + goto unscheduled; + + cclass = GST_CLOCK_GET_CLASS (clock); + + if (G_UNLIKELY (cclass->wait_async == NULL)) + goto not_supported; + + entry->func = func; + entry->user_data = user_data; + + res = cclass->wait_async (clock, entry); + + return res; + + /* 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; + } +unscheduled: + { + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, + "entry was unscheduled return _UNSCHEDULED"); + return GST_CLOCK_UNSCHEDULED; + } +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 + * + * 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. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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 (G_LIKELY (cclass->unschedule)) + cclass->unschedule (clock, entry); +} + + +/** + * GstClock abstract base class implementation + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GType +gst_clock_get_type (void) +{ + static GType clock_type = 0; + + if (G_UNLIKELY (clock_type == 0)) { + static const GTypeInfo clock_info = { + sizeof (GstClockClass), + NULL, + NULL, + (GClassInitFunc) gst_clock_class_init, + NULL, + NULL, + sizeof (GstClock), + 0, + (GInstanceInitFunc) gst_clock_init, + NULL + }; + + clock_type = g_type_register_static (GST_TYPE_OBJECT, "GstClock", + &clock_info, G_TYPE_FLAG_ABSTRACT); + } + return clock_type; +} + +static void +gst_clock_class_init (GstClockClass * klass) +{ + GObjectClass *gobject_class; + GstObjectClass *gstobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gstobject_class = GST_OBJECT_CLASS (klass); + + parent_class = g_type_class_peek_parent (klass); + + if (!g_thread_supported ()) + g_thread_init (NULL); + +#ifndef GST_DISABLE_TRACE + _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->finalize = GST_DEBUG_FUNCPTR (gst_clock_finalize); + 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 (gobject_class, PROP_STATS, + g_param_spec_boolean ("stats", "Stats", + "Enable clock stats (unimplemented)", DEFAULT_STATS, + G_PARAM_READWRITE)); + 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_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_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)); +} + +static void +gst_clock_init (GstClock * clock) +{ + clock->last_time = 0; + clock->entries = NULL; + clock->entries_changed = g_cond_new (); + clock->stats = FALSE; + + 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) +{ + GstClock *clock = GST_CLOCK (object); + GstClock **master_p; + + 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); +} + +static void +gst_clock_finalize (GObject * object) +{ + GstClock *clock = GST_CLOCK (object); + + 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); + + g_cond_free (clock->entries_changed); + g_mutex_free (clock->slave_lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +/** + * gst_clock_set_resolution + * @clock: a #GstClock + * @resolution: The resolution to set + * + * 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. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockTime +gst_clock_set_resolution (GstClock * clock, GstClockTime resolution) +{ + GstClockClass *cclass; + + 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); + + return clock->resolution; +} + +/** + * gst_clock_get_resolution + * @clock: a #GstClock + * + * 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 units of #GstClockTime. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockTime +gst_clock_get_resolution (GstClock * clock) +{ + GstClockClass *cclass; + + 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 1; +} + +/** + * 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. + * + * This function is te reverse of gst_clock_unadjust_unlocked(). + * + * Returns: the converted time of the clock. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockTime +gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) +{ + 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 (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 + * interal < cinternal to get the sign right. this case is not very common, + * though. + */ + if (G_LIKELY (internal >= cinternal)) { + ret = gst_util_uint64_scale (internal - cinternal, cnum, cdenom); + ret += cexternal; + } else { + ret = gst_util_uint64_scale (cinternal - internal, cnum, cdenom); + /* clamp to 0 */ + if (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; +} + +/** + * 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 te reverse of gst_clock_adjust_unlocked(). + * + * Returns: the internal time of the clock corresponding to @external. + * + * Since: 0.10.13 + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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 (cnum == 0) + cnum = cdenom = 1; + + /* The formula is (external - cexternal) * cdenom / cnum + cinternal */ + if (external >= cexternal) { + ret = gst_util_uint64_scale (external - cexternal, cdenom, cnum); + ret += cinternal; + } else { + ret = gst_util_uint64_scale (cexternal - external, cdenom, cnum); + if (cinternal > ret) + ret = cinternal - ret; + else + ret = 0; + } + return ret; +} + +/** + * gst_clock_get_internal_time + * @clock: a #GstClock to query + * + * 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 + * giving wrong input. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockTime +gst_clock_get_internal_time (GstClock * clock) +{ + GstClockTime ret; + GstClockClass *cclass; + + g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE); + + 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_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 + * giving wrong input. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClockTime +gst_clock_get_time (GstClock * clock) +{ + GstClockTime ret; + + g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE); + + ret = gst_clock_get_internal_time (clock); + + GST_OBJECT_LOCK (clock); + /* this will scale for rate and offset */ + ret = gst_clock_adjust_unlocked (clock, ret); + GST_OBJECT_UNLOCK (clock); + + GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "adjusted time %" GST_TIME_FORMAT, + GST_TIME_ARGS (ret)); + + return ret; +} + +/** + * 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: + * + * + * time = (internal_time - @internal) * @rate_num / @rate_denom + @external + * + * + * This formula is implemented in gst_clock_adjust_unlocked(). Of course, it + * tries to do the integer arithmetic as precisely as possible. + * + * 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. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +void +gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime + external, GstClockTime rate_num, GstClockTime rate_denom) +{ + g_return_if_fail (GST_IS_CLOCK (clock)); + g_return_if_fail (rate_num >= 0); + g_return_if_fail (rate_denom > 0); + g_return_if_fail (internal <= gst_clock_get_internal_time (clock)); + + GST_OBJECT_LOCK (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 / rate_denom)); + + clock->internal_calibration = internal; + clock->external_calibration = external; + clock->rate_numerator = rate_num; + clock->rate_denominator = rate_denom; + GST_OBJECT_UNLOCK (clock); +} + +/** + * gst_clock_get_calibration + * @clock: a #GstClock + * @internal: a location to store the internal time + * @external: a location to store the external time + * @rate_num: a location to store the rate numerator + * @rate_denom: a location to store the rate denominator + * + * Gets the internal rate and reference time of @clock. See + * gst_clock_set_calibration() for more information. + * + * @internal, @external, @rate_num, and @rate_denom can be left NULL if the + * caller is not interested in the values. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +void +gst_clock_get_calibration (GstClock * clock, GstClockTime * internal, + GstClockTime * external, GstClockTime * rate_num, GstClockTime * rate_denom) +{ + g_return_if_fail (GST_IS_CLOCK (clock)); + + GST_OBJECT_LOCK (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; + GST_OBJECT_UNLOCK (clock); +} + +/* will be called repeadedly 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; + + stime = gst_clock_get_internal_time (clock); + mtime = gst_clock_get_time (master); + + 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_set_master + * @clock: a #GstClock + * @master: 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. + * + * A clock provider that slaves its clock to a master can get the current + * calibration values with gst_clock_get_calibration(). + * + * @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. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +gboolean +gst_clock_set_master (GstClock * clock, GstClock * master) +{ + GstClock **master_p; + + 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); + master_p = &clock->master; + gst_object_replace ((GstObject **) master_p, (GstObject *) 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 (clock->clockid, + (GstClockCallback) gst_clock_slave_callback, clock); + } + GST_CLOCK_SLAVE_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; + } +} + +/** + * 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. + * + * Returns: a master #GstClock or NULL when this clock is not slaved to a master + * clock. Unref after usage. + * + * MT safe. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +GstClock * +gst_clock_get_master (GstClock * clock) +{ + GstClock *result = NULL; + + g_return_val_if_fail (GST_IS_CLOCK (clock), NULL); + + 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: 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 interpollation. 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. + */ +#ifdef __SYMBIAN32__ +EXPORT_C +#endif + +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); + + 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); + + /* 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) +{ +} + +static void +gst_clock_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstClock *clock; + + clock = GST_CLOCK (object); + + switch (prop_id) { + case PROP_STATS: + GST_OBJECT_LOCK (clock); + clock->stats = g_value_get_boolean (value); + GST_OBJECT_UNLOCK (clock); + g_object_notify (object, "stats"); + break; + 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 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) +{ + GstClock *clock; + + clock = GST_CLOCK (object); + + switch (prop_id) { + case PROP_STATS: + GST_OBJECT_LOCK (clock); + g_value_set_boolean (value, clock->stats); + GST_OBJECT_UNLOCK (clock); + break; + case PROP_WINDOW_SIZE: + GST_CLOCK_SLAVE_LOCK (clock); + g_value_set_int (value, clock->window_size); + GST_CLOCK_SLAVE_UNLOCK (clock); + break; + 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; + } +}