/* 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 * * 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 depends on the specific clock * implementation but time is always expessed in nanoseconds. Since the * baseline of the clock is undefined, the clock time returned is not * meaningfull in itself, what matters are the deltas between two clock * times. * * 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. * * The time of the clock in itself is not very useful for an application. * * 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 an async 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. * * 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 once. * * 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 we unref it automatically, the handle might be * invalid. * * 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. * * Last reviewed on 2005-10-28 (0.9.4) */ #include #include "gst_private.h" #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 #define DEFAULT_EVENT_DIFF (GST_SECOND) #define DEFAULT_MAX_DIFF (2 * GST_SECOND) enum { ARG_0, ARG_STATS, ARG_MAX_DIFF, ARG_EVENT_DIFF }; static void gst_clock_class_init (GstClockClass * klass); static void gst_clock_init (GstClock * clock); 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 = g_malloc0 (sizeof (GstClockEntry)); #ifndef GST_DISABLE_TRACE gst_alloc_trace_new (_gst_clock_entry_trace, entry); #endif GST_CAT_DEBUG (GST_CAT_CLOCK, "created entry %p, time %" GST_TIME_FORMAT, entry, GST_TIME_ARGS (time)); gst_atomic_int_set (&entry->refcount, 1); entry->clock = clock; entry->time = time; entry->interval = interval; entry->type = type; entry->status = GST_CLOCK_BUSY; return (GstClockID) entry; } /** * gst_clock_id_ref: * @id: The clockid to ref * * Increase the refcount of the given clockid. * * Returns: 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) { 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 g_free (id); } /** * gst_clock_id_unref: * @id: The clockid to unref * * Unref the given clockid. 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 * @time: the requested time * * Get an ID from the given clock to trigger a single shot * notification at the requested time. The single shot id should be * unreffed after usage. * * 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) { 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 clockid 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. The id should be unreffed * after usage. * * Returns: An id that can be used to request the time notification. * * MT safe. */ 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); return gst_clock_entry_new (clock, start_time, interval, GST_CLOCK_ENTRY_PERIODIC); } /** * gst_clock_id_compare_func * @id1: A clockid * @id2: A clockid 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 entry1 - entry2; } /** * gst_clock_id_get_time * @id: The clockid to query * * Get the time of the 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); } /** * gst_clock_id_wait * @id: The clockid to wait on * @jitter: A pointer that will contain the jitter * * Perform a blocking wait on the given ID. The jitter arg can be * NULL. * * Returns: the result of the blocking wait. * * MT safe. */ 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); if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested))) goto invalid_time; if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED)) goto unscheduled; clock = GST_CLOCK_ENTRY_CLOCK (entry); cclass = GST_CLOCK_GET_CLASS (clock); if (G_LIKELY (cclass->wait)) { GST_CAT_DEBUG (GST_CAT_CLOCK, "waiting on clock entry %p", id); res = cclass->wait (clock, entry); GST_CAT_DEBUG (GST_CAT_CLOCK, "done waiting entry %p", id); if (jitter) { GstClockTime now = gst_clock_get_time (clock); *jitter = now - requested; } if (entry->type == GST_CLOCK_ENTRY_PERIODIC) { entry->time += entry->interval; } if (clock->stats) { gst_clock_update_stats (clock); } } else { res = GST_CLOCK_UNSUPPORTED; } return res; /* ERRORS */ invalid_time: { GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _BADTIME"); return GST_CLOCK_BADTIME; } unscheduled: { GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was unscheduled return _UNSCHEDULED"); return GST_CLOCK_UNSCHEDULED; } } /** * 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 clockid with the given * function and user_data. When passing an id 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 the id has been reached. * * Returns: the result of the non blocking wait. * * MT safe. */ 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); if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested))) goto invalid_time; if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED)) goto unscheduled; cclass = GST_CLOCK_GET_CLASS (clock); if (cclass->wait_async) { entry->func = func; entry->user_data = user_data; res = cclass->wait_async (clock, entry); } else { res = GST_CLOCK_UNSUPPORTED; } return res; /* ERRORS */ invalid_time: { (func) (clock, GST_CLOCK_TIME_NONE, id, user_data); GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _BADTIME"); return GST_CLOCK_BADTIME; } unscheduled: { GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was unscheduled return _UNSCHEDULED"); return GST_CLOCK_UNSCHEDULED; } } /** * gst_clock_id_unschedule: * @id: The id to unschedule * * Cancel an outstanding request with the given ID. This can either * be an outstanding async notification or a pending sync notification. * After this call, the @id cannot be used anymore to receive sync or * async notifications, you need to create a new GstClockID. * * MT safe. */ 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); } /** * 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), 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 = (GObjectClass *) klass; gstobject_class = (GstObjectClass *) klass; parent_class = g_type_class_ref (GST_TYPE_OBJECT); 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->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 (G_OBJECT_CLASS (klass), ARG_STATS, g_param_spec_boolean ("stats", "Stats", "Enable clock stats", FALSE, G_PARAM_READWRITE)); } static void gst_clock_init (GstClock * clock) { clock->last_time = 0; clock->entries = NULL; clock->entries_changed = g_cond_new (); clock->flags = 0; clock->stats = FALSE; clock->internal_calibration = 0; clock->external_calibration = 0; clock->rate = 1.0; } static void gst_clock_finalize (GObject * object) { GstClock *clock = GST_CLOCK (object); g_cond_free (clock->entries_changed); G_OBJECT_CLASS (parent_class)->finalize (object); } /** * gst_clock_set_resolution * @clock: The clock set the resolution on * @resolution: The resolution to set * * Set the accuracy of the clock. * * Returns: the new resolution of the clock. */ guint64 gst_clock_set_resolution (GstClock * clock, guint64 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)); 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: The clock get the resolution of * * Get the accuracy of the clock. * * Returns: the resolution of the clock in microseconds. * * MT safe. */ guint64 gst_clock_get_resolution (GstClock * clock) { GstClockClass *cclass; g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0)); cclass = GST_CLOCK_GET_CLASS (clock); if (cclass->get_resolution) return cclass->get_resolution (clock); return G_GINT64_CONSTANT (1); } /** * gst_clock_adjust_unlocked * @clock: a #GstClock to use * @internal: a clock time * * Converts the given @internal clock time to the real 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 LOCK held and is mainly used by clock subclasses. * * Returns: the converted time of the clock. * * MT safe. */ GstClockTime gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) { GstClockTime ret; ret = (internal - clock->internal_calibration) * clock->rate; ret += clock->external_calibration; /* make sure the time is increasing */ clock->last_time = MAX (ret, clock->last_time); return clock->last_time; } /** * 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. */ 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 (cclass->get_internal_time) { ret = cclass->get_internal_time (clock); } else { ret = G_GINT64_CONSTANT (0); } GST_CAT_DEBUG (GST_CAT_CLOCK, "internal time %" GST_TIME_FORMAT, GST_TIME_ARGS (ret)); return ret; } /** * 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. */ 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_LOCK (clock); /* this will scale for rate and offset */ ret = gst_clock_adjust_unlocked (clock, ret); GST_UNLOCK (clock); GST_CAT_DEBUG (GST_CAT_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: the rate of the clock relative to its internal time * * Adjusts the rate and time of @clock. A @rate of 1.0 is the normal speed of * the clock. Values bigger than 1.0 make the clock go faster. @rate must be * positive. * * @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 + @external * * * 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. */ void gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime external, gdouble rate) { g_return_if_fail (GST_IS_CLOCK (clock)); g_return_if_fail (rate > 0.0); g_return_if_fail (internal <= gst_clock_get_internal_time (clock)); GST_LOCK (clock); clock->internal_calibration = internal; clock->external_calibration = external; clock->rate = rate; GST_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: a location to store the rate * * Gets the internal rate and reference time of @clock. See * gst_clock_set_calibration() for more information. * * @internal, @external and @rate can be left NULL if the caller * is not interested in the values. * * MT safe. */ void gst_clock_get_calibration (GstClock * clock, GstClockTime * internal, GstClockTime * external, gdouble * rate) { g_return_if_fail (GST_IS_CLOCK (clock)); GST_LOCK (clock); if (rate) *rate = clock->rate; if (external) *external = clock->external_calibration; if (internal) *internal = clock->internal_calibration; GST_UNLOCK (clock); } 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 ARG_STATS: clock->stats = g_value_get_boolean (value); g_object_notify (object, "stats"); 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 ARG_STATS: g_value_set_boolean (value, clock->stats); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } }