/* 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., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, 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 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. */ #include "gst_private.h" #include #include "gstclock.h" #include "gstinfo.h" #include "gstutils.h" #include "glib-compat-private.h" /* #define DEBUGGING_ENABLED */ #define DEFAULT_WINDOW_SIZE 32 #define DEFAULT_WINDOW_THRESHOLD 4 #define DEFAULT_TIMEOUT GST_SECOND / 10 enum { PROP_0, PROP_WINDOW_SIZE, PROP_WINDOW_THRESHOLD, PROP_TIMEOUT }; enum { SIGNAL_SYNCED, SIGNAL_LAST }; #define GST_CLOCK_SLAVE_LOCK(clock) g_mutex_lock (&GST_CLOCK_CAST (clock)->priv->slave_lock) #define GST_CLOCK_SLAVE_UNLOCK(clock) g_mutex_unlock (&GST_CLOCK_CAST (clock)->priv->slave_lock) struct _GstClockPrivate { GMutex slave_lock; /* order: SLAVE_LOCK, OBJECT_LOCK */ GCond sync_cond; /* with LOCK */ GstClockTime internal_calibration; GstClockTime external_calibration; GstClockTime rate_numerator; GstClockTime rate_denominator; GstClockTime last_time; /* with LOCK */ GstClockTime resolution; /* for master/slave clocks */ GstClock *master; /* with SLAVE_LOCK */ gboolean filling; gint window_size; gint window_threshold; gint time_index; GstClockTime timeout; GstClockTime *times; GstClockTime *times_temp; GstClockID clockid; gint pre_count; gint post_count; gboolean synced; }; /* seqlocks */ #define read_seqbegin(clock) \ g_atomic_int_get (&clock->priv->post_count); 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->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->priv->pre_count); \ } G_STMT_END; #define write_sequnlock(clock) \ G_STMT_START { \ g_atomic_int_inc (&clock->priv->post_count); \ GST_OBJECT_UNLOCK (clock); \ } G_STMT_END; #ifndef GST_DISABLE_GST_DEBUG static const gchar * gst_clock_return_get_name (GstClockReturn ret) { switch (ret) { case GST_CLOCK_OK: return "ok"; case GST_CLOCK_EARLY: return "early"; case GST_CLOCK_UNSCHEDULED: return "unscheduled"; case GST_CLOCK_BUSY: return "busy"; case GST_CLOCK_BADTIME: return "bad-time"; case GST_CLOCK_ERROR: return "error"; case GST_CLOCK_UNSUPPORTED: return "unsupported"; case GST_CLOCK_DONE: return "done"; default: break; } return "unknown"; } #endif /* GST_DISABLE_GST_DEBUG */ 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 guint gst_clock_signals[SIGNAL_LAST] = { 0 }; static GstClockID gst_clock_entry_new (GstClock * clock, GstClockTime time, GstClockTime interval, GstClockEntryType type) { GstClockEntry *entry; entry = g_slice_new (GstClockEntry); /* FIXME: add tracer hook for struct allocations such as clock entries */ 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->type = type; 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. */ 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. */ 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); /* FIXME: add tracer hook for struct allocations such as clock entries */ 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 #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. * * 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_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 periodic notifications will start at time @start_time and * will then be fired with the given @interval. @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. * * 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); 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. */ 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. */ 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: (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(). * * 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. */ 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; 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_UNLIKELY (cclass->wait == NULL)) goto not_supported; res = cclass->wait (clock, entry, jitter); GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "done waiting entry %p, res: %d (%s)", id, res, gst_clock_return_get_name (res)); if (entry->type == GST_CLOCK_ENTRY_PERIODIC) entry->time = requested + entry->interval; 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: * @id: a #GstClockID to wait on * @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. * * 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. */ GstClockReturn gst_clock_id_wait_async (GstClockID id, GstClockCallback func, gpointer user_data, GDestroyNotify destroy_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; cclass = GST_CLOCK_GET_CLASS (clock); if (G_UNLIKELY (cclass->wait_async == NULL)) goto not_supported; entry->func = func; entry->user_data = user_data; entry->destroy_data = destroy_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; } 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. */ 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 */ #define gst_clock_parent_class parent_class G_DEFINE_ABSTRACT_TYPE (GstClock, gst_clock, GST_TYPE_OBJECT); static void gst_clock_class_init (GstClockClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); 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_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)); /** * GstClock::synced: * @clock: the clock * @synced: if the clock is synced now * * Signaled on clocks with GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC set once * the clock is synchronized, or when it completely lost synchronization. * This signal will not be emitted on clocks without the flag. * * This signal will be emitted from an arbitrary thread, most likely not * the application's main thread. * * Since: 1.6 */ gst_clock_signals[SIGNAL_SYNCED] = g_signal_new ("synced", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic, G_TYPE_NONE, 1, G_TYPE_BOOLEAN); g_type_class_add_private (klass, sizeof (GstClockPrivate)); } static void gst_clock_init (GstClock * clock) { GstClockPrivate *priv; clock->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (clock, GST_TYPE_CLOCK, GstClockPrivate); priv->last_time = 0; priv->internal_calibration = 0; priv->external_calibration = 0; priv->rate_numerator = 1; priv->rate_denominator = 1; g_mutex_init (&priv->slave_lock); g_cond_init (&priv->sync_cond); priv->window_size = DEFAULT_WINDOW_SIZE; priv->window_threshold = DEFAULT_WINDOW_THRESHOLD; priv->filling = TRUE; priv->time_index = 0; priv->timeout = DEFAULT_TIMEOUT; priv->times = g_new0 (GstClockTime, 4 * priv->window_size); priv->times_temp = priv->times + 2 * priv->window_size; /* clear floating flag */ gst_object_ref_sink (clock); } static void gst_clock_dispose (GObject * object) { GstClock *clock = GST_CLOCK (object); GstClock **master_p; GST_OBJECT_LOCK (clock); master_p = &clock->priv->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->priv->clockid) { gst_clock_id_unschedule (clock->priv->clockid); gst_clock_id_unref (clock->priv->clockid); clock->priv->clockid = NULL; } g_free (clock->priv->times); clock->priv->times = NULL; clock->priv->times_temp = NULL; GST_CLOCK_SLAVE_UNLOCK (clock); g_mutex_clear (&clock->priv->slave_lock); g_cond_clear (&clock->priv->sync_cond); 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. */ GstClockTime gst_clock_set_resolution (GstClock * clock, GstClockTime resolution) { GstClockPrivate *priv; 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); priv = clock->priv; if (cclass->change_resolution) priv->resolution = cclass->change_resolution (clock, priv->resolution, resolution); return priv->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. */ 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; } /* FIXME 2.0: Remove clock parameter below */ /** * gst_clock_adjust_with_calibration: * @clock: (allow-none): a #GstClock to use * @internal_target: a clock time * @cinternal: a reference internal time * @cexternal: a reference external time * @cnum: the numerator of the rate of the clock relative to its * internal time * @cdenom: the denominator of the rate of the clock * * Converts the given @internal_target clock time to the external time, * using the passed calibration parameters. This function performs the * same calculation as gst_clock_adjust_unlocked() when called using the * current calibration parameters, but doesn't ensure a monotonically * increasing result as gst_clock_adjust_unlocked() does. * * Note: The @clock parameter is unused and can be NULL * * Returns: the converted time of the clock. * * Since: 1.6 */ GstClockTime gst_clock_adjust_with_calibration (GstClock * clock, GstClockTime internal_target, GstClockTime cinternal, GstClockTime cexternal, GstClockTime cnum, GstClockTime cdenom) { GstClockTime ret; /* 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_target >= cinternal)) { ret = internal_target - cinternal; ret = gst_util_uint64_scale (ret, cnum, cdenom); ret += cexternal; } else { ret = cinternal - internal_target; ret = gst_util_uint64_scale (ret, cnum, cdenom); /* clamp to 0 */ if (G_LIKELY (cexternal > ret)) ret = cexternal - ret; else ret = 0; } return ret; } /** * 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 the reverse of gst_clock_unadjust_unlocked(). * * Returns: the converted time of the clock. */ GstClockTime gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal) { GstClockTime ret, cinternal, cexternal, cnum, cdenom; GstClockPrivate *priv = clock->priv; /* get calibration values for readability */ cinternal = priv->internal_calibration; cexternal = priv->external_calibration; cnum = priv->rate_numerator; cdenom = priv->rate_denominator; ret = gst_clock_adjust_with_calibration (clock, internal, cinternal, cexternal, cnum, cdenom); /* make sure the time is increasing */ priv->last_time = MAX (ret, priv->last_time); return priv->last_time; } /* FIXME 2.0: Remove clock parameter below */ /** * gst_clock_unadjust_with_calibration: * @clock: (allow-none): a #GstClock to use * @external_target: a clock time * @cinternal: a reference internal time * @cexternal: a reference external time * @cnum: the numerator of the rate of the clock relative to its * internal time * @cdenom: the denominator of the rate of the clock * * Converts the given @external_target clock time to the internal time, * using the passed calibration parameters. This function performs the * same calculation as gst_clock_unadjust_unlocked() when called using the * current calibration parameters. * * Note: The @clock parameter is unused and can be NULL * * Returns: the converted time of the clock. * * Since: 1.8 */ GstClockTime gst_clock_unadjust_with_calibration (GstClock * clock, GstClockTime external_target, GstClockTime cinternal, GstClockTime cexternal, GstClockTime cnum, GstClockTime cdenom) { GstClockTime ret; /* avoid divide by 0 */ if (G_UNLIKELY (cnum == 0)) cnum = cdenom = 1; /* The formula is (external - cexternal) * cdenom / cnum + cinternal */ if (G_LIKELY (external_target >= cexternal)) { ret = external_target - cexternal; ret = gst_util_uint64_scale (ret, cdenom, cnum); ret += cinternal; } else { ret = cexternal - external_target; ret = gst_util_uint64_scale (ret, cdenom, cnum); if (G_LIKELY (cinternal > ret)) ret = cinternal - ret; else ret = 0; } return ret; } /** * 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. */ GstClockTime gst_clock_unadjust_unlocked (GstClock * clock, GstClockTime external) { GstClockTime cinternal, cexternal, cnum, cdenom; GstClockPrivate *priv = clock->priv; /* get calibration values for readability */ cinternal = priv->internal_calibration; cexternal = priv->external_calibration; cnum = priv->rate_numerator; cdenom = priv->rate_denominator; return gst_clock_unadjust_with_calibration (clock, external, cinternal, cexternal, cnum, cdenom); } /** * 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 * given invalid 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); if (G_UNLIKELY (GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC) && !clock->priv->synced)) GST_CAT_WARNING_OBJECT (GST_CAT_CLOCK, clock, "clock is not synchronized yet"); 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 * given invalid input. * * MT safe. */ GstClockTime gst_clock_get_time (GstClock * clock) { GstClockTime ret; gint seq; g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE); do { /* reget the internal time when we retry to get the most current * timevalue */ ret = gst_clock_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_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. */ void gst_clock_set_calibration (GstClock * clock, GstClockTime internal, GstClockTime external, GstClockTime rate_num, GstClockTime rate_denom) { GstClockPrivate *priv; 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); priv = clock->priv; 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)); priv->internal_calibration = internal; priv->external_calibration = external; priv->rate_numerator = rate_num; priv->rate_denominator = rate_denom; write_sequnlock (clock); } /** * 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 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. */ void gst_clock_get_calibration (GstClock * clock, GstClockTime * internal, GstClockTime * external, GstClockTime * rate_num, GstClockTime * rate_denom) { gint seq; GstClockPrivate *priv; g_return_if_fail (GST_IS_CLOCK (clock)); priv = clock->priv; do { seq = read_seqbegin (clock); if (rate_num) *rate_num = priv->rate_numerator; if (rate_denom) *rate_denom = priv->rate_denominator; if (external) *external = priv->external_calibration; if (internal) *internal = priv->internal_calibration; } while (read_seqretry (clock, seq)); } /* 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 (!gst_clock_is_synced (clock)) { GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "Slave clock is not synced yet"); return TRUE; } 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: (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. * * 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. */ gboolean gst_clock_set_master (GstClock * clock, GstClock * master) { GstClock **master_p; GstClockPrivate *priv; 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; if (master && !gst_clock_is_synced (master)) goto master_not_synced; GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "slaving %p to master clock %p", clock, master); GST_OBJECT_UNLOCK (clock); priv = clock->priv; GST_CLOCK_SLAVE_LOCK (clock); if (priv->clockid) { gst_clock_id_unschedule (priv->clockid); gst_clock_id_unref (priv->clockid); priv->clockid = NULL; } if (master) { priv->filling = TRUE; priv->time_index = 0; /* use the master periodic id to schedule sampling and * clock calibration. */ priv->clockid = gst_clock_new_periodic_id (master, gst_clock_get_time (master), priv->timeout); gst_clock_id_wait_async (priv->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 = &priv->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; } master_not_synced: { GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, master, "master clock is not synced yet"); 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: (transfer full) (nullable): a master #GstClock or %NULL * when this clock is not slaved to a master clock. Unref after * usage. * * MT safe. */ GstClock * gst_clock_get_master (GstClock * clock) { GstClock *result = NULL; GstClockPrivate *priv; g_return_val_if_fail (GST_IS_CLOCK (clock), NULL); priv = clock->priv; GST_OBJECT_LOCK (clock); if (priv->master) result = gst_object_ref (priv->master); GST_OBJECT_UNLOCK (clock); return result; } /** * 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; if (!gst_clock_add_observation_unapplied (clock, slave, master, r_squared, &xbase, &b, &m_num, &m_denom)) return FALSE; /* if we have a valid regression, adjust the clock */ gst_clock_set_calibration (clock, xbase, b, m_num, m_denom); return TRUE; } /** * gst_clock_add_observation_unapplied: * @clock: a #GstClock * @slave: a time on the slave * @master: a time on the master * @r_squared: (out): a pointer to hold the result * @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 * * Add a clock observation to the internal slaving algorithm the same as * gst_clock_add_observation(), and return the result of the master clock * estimation, without updating the internal calibration. * * The caller can then take the results and call gst_clock_set_calibration() * with the values, or some modified version of them. * * Since: 1.6 */ gboolean gst_clock_add_observation_unapplied (GstClock * clock, GstClockTime slave, GstClockTime master, gdouble * r_squared, GstClockTime * internal, GstClockTime * external, GstClockTime * rate_num, GstClockTime * rate_denom) { GstClockTime m_num, m_denom, b, xbase; GstClockPrivate *priv; guint n; g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE); g_return_val_if_fail (r_squared != NULL, FALSE); priv = clock->priv; 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)); priv->times[(2 * priv->time_index)] = slave; priv->times[(2 * priv->time_index) + 1] = master; priv->time_index++; if (G_UNLIKELY (priv->time_index == priv->window_size)) { priv->filling = FALSE; priv->time_index = 0; } if (G_UNLIKELY (priv->filling && priv->time_index < priv->window_threshold)) goto filling; n = priv->filling ? priv->time_index : priv->window_size; if (!gst_calculate_linear_regression (priv->times, priv->times_temp, n, &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 (internal) *internal = xbase; if (external) *external = b; if (rate_num) *rate_num = m_num; if (rate_denom) *rate_denom = 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 FALSE; } } /** * gst_clock_set_timeout: * @clock: a #GstClock * @timeout: a timeout * * Set the amount of time, in nanoseconds, to sample master and slave * clocks */ void gst_clock_set_timeout (GstClock * clock, GstClockTime timeout) { g_return_if_fail (GST_IS_CLOCK (clock)); GST_CLOCK_SLAVE_LOCK (clock); clock->priv->timeout = timeout; GST_CLOCK_SLAVE_UNLOCK (clock); } /** * gst_clock_get_timeout: * @clock: a #GstClock * * Get the amount of time that master and slave clocks are sampled. * * Returns: the interval between samples. */ GstClockTime gst_clock_get_timeout (GstClock * clock) { GstClockTime result; g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE); GST_CLOCK_SLAVE_LOCK (clock); result = clock->priv->timeout; GST_CLOCK_SLAVE_UNLOCK (clock); return result; } static void gst_clock_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstClock *clock; GstClockPrivate *priv; clock = GST_CLOCK (object); priv = clock->priv; switch (prop_id) { case PROP_WINDOW_SIZE: GST_CLOCK_SLAVE_LOCK (clock); priv->window_size = g_value_get_int (value); priv->window_threshold = MIN (priv->window_threshold, priv->window_size); priv->times = g_renew (GstClockTime, priv->times, 4 * priv->window_size); priv->times_temp = priv->times + 2 * priv->window_size; /* restart calibration */ priv->filling = TRUE; priv->time_index = 0; GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_WINDOW_THRESHOLD: GST_CLOCK_SLAVE_LOCK (clock); priv->window_threshold = MIN (g_value_get_int (value), priv->window_size); GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_TIMEOUT: gst_clock_set_timeout (clock, g_value_get_uint64 (value)); 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; GstClockPrivate *priv; clock = GST_CLOCK (object); priv = clock->priv; switch (prop_id) { case PROP_WINDOW_SIZE: GST_CLOCK_SLAVE_LOCK (clock); g_value_set_int (value, priv->window_size); GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_WINDOW_THRESHOLD: GST_CLOCK_SLAVE_LOCK (clock); g_value_set_int (value, priv->window_threshold); GST_CLOCK_SLAVE_UNLOCK (clock); break; case PROP_TIMEOUT: g_value_set_uint64 (value, gst_clock_get_timeout (clock)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } /** * gst_clock_wait_for_sync: * @clock: a GstClock * @timeout: timeout for waiting or %GST_CLOCK_TIME_NONE * * Waits until @clock is synced for reporting the current time. If @timeout * is %GST_CLOCK_TIME_NONE it will wait forever, otherwise it will time out * after @timeout nanoseconds. * * For asynchronous waiting, the GstClock::synced signal can be used. * * * This returns immediately with TRUE if GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC * is not set on the clock, or if the clock is already synced. * * Returns: %TRUE if waiting was successful, or %FALSE on timeout * * Since: 1.6 */ gboolean gst_clock_wait_for_sync (GstClock * clock, GstClockTime timeout) { gboolean timed_out = FALSE; g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE); GST_OBJECT_LOCK (clock); if (!GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC) || clock->priv->synced) { GST_OBJECT_UNLOCK (clock); return TRUE; } if (timeout != GST_CLOCK_TIME_NONE) { gint64 end_time = g_get_monotonic_time () + gst_util_uint64_scale (timeout, G_TIME_SPAN_SECOND, GST_SECOND); while (!clock->priv->synced && !timed_out) { timed_out = !g_cond_wait_until (&clock->priv->sync_cond, GST_OBJECT_GET_LOCK (clock), end_time); } } else { timed_out = FALSE; while (!clock->priv->synced) { g_cond_wait (&clock->priv->sync_cond, GST_OBJECT_GET_LOCK (clock)); } } GST_OBJECT_UNLOCK (clock); return !timed_out; } /** * gst_clock_is_synced: * @clock: a GstClock * * Checks if the clock is currently synced. * * This returns if GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC is not set on the clock. * * Returns: %TRUE if the clock is currently synced * * Since: 1.6 */ gboolean gst_clock_is_synced (GstClock * clock) { g_return_val_if_fail (GST_IS_CLOCK (clock), TRUE); return !GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC) || clock->priv->synced; } /** * gst_clock_set_synced: * @clock: a GstClock * @synced: if the clock is synced * * Sets @clock to synced and emits the GstClock::synced signal, and wakes up any * thread waiting in gst_clock_wait_for_sync(). * * This function must only be called if GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC * is set on the clock, and is intended to be called by subclasses only. * * Since: 1.6 */ void gst_clock_set_synced (GstClock * clock, gboolean synced) { g_return_if_fail (GST_IS_CLOCK (clock)); g_return_if_fail (GST_OBJECT_FLAG_IS_SET (clock, GST_CLOCK_FLAG_NEEDS_STARTUP_SYNC)); GST_OBJECT_LOCK (clock); if (clock->priv->synced != ! !synced) { clock->priv->synced = ! !synced; g_cond_signal (&clock->priv->sync_cond); GST_OBJECT_UNLOCK (clock); g_signal_emit (clock, gst_clock_signals[SIGNAL_SYNCED], 0, ! !synced); } else { GST_OBJECT_UNLOCK (clock); } }