*
* 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.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
*/
/**
* SECTION:gstclock
+ * @title: GstClock
* @short_description: Abstract class for global clocks
* @see_also: #GstSystemClock, #GstPipeline
*
* 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
+ * 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
* 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.
+ * gst_clock_get_calibration() function.
*
* The master/slave synchronisation can be tuned with the #GstClock:timeout,
* #GstClock:window-size and #GstClock:window-threshold properties.
* clock and run the calibration functions. #GstClock:window-size defines the
* number of samples to use when calibrating and #GstClock:window-threshold
* defines the minimum number of samples before the calibration is performed.
- *
- * Last reviewed on 2012-03-28 (0.11.3)
*/
-
#include "gst_private.h"
#include <time.h>
#include "gstutils.h"
#include "glib-compat-private.h"
-#ifndef GST_DISABLE_TRACE
-/* #define GST_WITH_ALLOC_TRACE */
-#include "gsttrace.h"
-static GstAllocTrace *_gst_clock_entry_trace;
-#endif
-
/* #define DEBUGGING_ENABLED */
#define DEFAULT_WINDOW_SIZE 32
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)
{
GMutex slave_lock; /* order: SLAVE_LOCK, OBJECT_LOCK */
+ GCond sync_cond;
+
/* with LOCK */
GstClockTime internal_calibration;
GstClockTime external_calibration;
gint time_index;
GstClockTime timeout;
GstClockTime *times;
+ GstClockTime *times_temp;
GstClockID clockid;
gint pre_count;
gint post_count;
+
+ gboolean synced;
};
/* seqlocks */
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_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
-/* static guint gst_clock_signals[LAST_SIGNAL] = { 0 }; */
+static guint gst_clock_signals[SIGNAL_LAST] = { 0 };
static GstClockID
gst_clock_entry_new (GstClock * clock, GstClockTime time,
GstClockEntry *entry;
entry = g_slice_new (GstClockEntry);
-#ifndef GST_DISABLE_TRACE
- _gst_alloc_trace_new (_gst_clock_entry_trace, entry);
-#endif
+
+ /* 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;
+#ifndef GST_REMOVE_DEPRECATED
+#ifndef GST_DISABLE_DEPRECATED
entry->clock = clock;
+#else
+ entry->_clock = clock;
+#endif
+#endif
+ g_weak_ref_init (&entry->ABI.abi.clock, clock);
entry->type = type;
entry->time = time;
entry->interval = interval;
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);
+ g_return_val_if_fail (gst_clock_id_uses_clock ((GstClockID) entry, clock),
+ FALSE);
entry->type = type;
entry->time = time;
*
* Returns: %TRUE if the GstClockID could be reinitialized to the provided
* @time, else %FALSE.
- *
- * Since: 0.10.32
*/
gboolean
gst_clock_single_shot_id_reinit (GstClock * clock, GstClockID id,
*
* Returns: %TRUE if the GstClockID could be reinitialized to the provided
* @time, else %FALSE.
- *
- * Since: 0.10.33
- *
*/
gboolean
gst_clock_periodic_id_reinit (GstClock * clock, GstClockID id,
if (entry->destroy_data)
entry->destroy_data (entry->user_data);
-#ifndef GST_DISABLE_TRACE
- _gst_alloc_trace_free (_gst_clock_entry_trace, id);
-#endif
+ g_weak_ref_clear (&entry->ABI.abi.clock);
+
+ /* FIXME: add tracer hook for struct allocations such as clock entries */
+
g_slice_free (GstClockEntry, id);
}
}
/**
- * gst_clock_id_compare_func
+ * gst_clock_id_compare_func:
* @id1: A #GstClockID
* @id2: A #GstClockID to compare with
*
}
/**
- * gst_clock_id_get_time
+ * gst_clock_id_get_time:
* @id: The #GstClockID to query
*
* Get the time of the clock ID
}
/**
- * gst_clock_id_wait
+ * 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.
+ * 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().
+ * 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.
+ * 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
+ * (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
+ * 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.
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
- clock = GST_CLOCK_ENTRY_CLOCK (entry);
+ clock = g_weak_ref_get (&entry->ABI.abi.clock);
+ if (G_UNLIKELY (clock == NULL))
+ goto invalid_entry;
/* can't sync on invalid times */
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
res = cclass->wait (clock, entry, jitter);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
- "done waiting entry %p, res: %d", id, res);
+ "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;
+ gst_object_unref (clock);
return res;
/* ERRORS */
{
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"invalid time requested, returning _BADTIME");
+ gst_object_unref (clock);
return GST_CLOCK_BADTIME;
}
not_supported:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported");
+ gst_object_unref (clock);
return GST_CLOCK_UNSUPPORTED;
}
+invalid_entry:
+ {
+ GST_CAT_DEBUG (GST_CAT_CLOCK, "clock entry %p lost its clock", id);
+ return GST_CLOCK_ERROR;
+ }
}
/**
- * gst_clock_id_wait_async_full:
+ * gst_clock_id_wait_async:
* @id: a #GstClockID to wait on
* @func: The callback function
* @user_data: User data passed in the callback
* Returns: the result of the non blocking wait.
*
* MT safe.
- *
- * Since: 0.10.30
*/
GstClockReturn
-gst_clock_id_wait_async_full (GstClockID id,
+gst_clock_id_wait_async (GstClockID id,
GstClockCallback func, gpointer user_data, GDestroyNotify destroy_data)
{
GstClockEntry *entry;
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
- clock = GST_CLOCK_ENTRY_CLOCK (entry);
+ clock = g_weak_ref_get (&entry->ABI.abi.clock);
+ if (G_UNLIKELY (clock == NULL))
+ goto invalid_entry;
/* can't sync on invalid times */
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
res = cclass->wait_async (clock, entry);
+ gst_object_unref (clock);
return res;
/* ERRORS */
(func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock,
"invalid time requested, returning _BADTIME");
+ gst_object_unref (clock);
return GST_CLOCK_BADTIME;
}
not_supported:
{
GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "clock wait is not supported");
+ gst_object_unref (clock);
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
- *
- * 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)
-{
- return gst_clock_id_wait_async_full (id, func, user_data, NULL);
+invalid_entry:
+ {
+ GST_CAT_DEBUG (GST_CAT_CLOCK, "clock entry %p lost its clock", id);
+ return GST_CLOCK_ERROR;
+ }
}
/**
g_return_if_fail (id != NULL);
entry = (GstClockEntry *) id;
- clock = entry->clock;
+ clock = g_weak_ref_get (&entry->ABI.abi.clock);
+ if (G_UNLIKELY (clock == NULL))
+ goto invalid_entry;
cclass = GST_CLOCK_GET_CLASS (clock);
if (G_LIKELY (cclass->unschedule))
cclass->unschedule (clock, entry);
+
+ gst_object_unref (clock);
+ return;
+
+invalid_entry:
+ {
+ GST_CAT_DEBUG (GST_CAT_CLOCK, "clock entry %p lost its clock", id);
+ return;
+ }
}
* GstClock abstract base class implementation
*/
#define gst_clock_parent_class parent_class
-G_DEFINE_TYPE (GstClock, gst_clock, GST_TYPE_OBJECT);
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GstClock, gst_clock, GST_TYPE_OBJECT);
static void
gst_clock_class_init (GstClockClass * klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
-#ifndef GST_DISABLE_TRACE
- _gst_clock_entry_trace = _gst_alloc_trace_register ("GstClockEntry", -1);
-#endif
-
gobject_class->dispose = gst_clock_dispose;
gobject_class->finalize = gst_clock_finalize;
gobject_class->set_property = gst_clock_set_property;
0, G_MAXUINT64, DEFAULT_TIMEOUT,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
- g_type_class_add_private (klass, sizeof (GstClockPrivate));
+ /**
+ * 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);
}
static void
{
GstClockPrivate *priv;
- clock->priv = priv =
- G_TYPE_INSTANCE_GET_PRIVATE (clock, GST_TYPE_CLOCK, GstClockPrivate);
+ clock->priv = priv = gst_clock_get_instance_private (clock);
priv->last_time = 0;
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;
}
static void
}
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
+ * gst_clock_set_resolution:
* @clock: a #GstClock
* @resolution: The resolution to set
*
}
/**
- * gst_clock_get_resolution
+ * gst_clock_get_resolution:
* @clock: a #GstClock
*
* Get the accuracy of the clock. The accuracy of the clock is the granularity
return 1;
}
+/* FIXME 2.0: Remove clock parameter below */
/**
- * 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().
+ * 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_unlocked (GstClock * clock, GstClockTime internal)
+gst_clock_adjust_with_calibration (GstClock * clock,
+ GstClockTime internal_target, GstClockTime cinternal,
+ GstClockTime cexternal, GstClockTime cnum, GstClockTime cdenom)
{
- 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;
+ GstClockTime ret;
/* avoid divide by 0 */
if (G_UNLIKELY (cdenom == 0))
* internal < cinternal to get the sign right. this case is not very common,
* though.
*/
- if (G_LIKELY (internal >= cinternal)) {
- ret = internal - cinternal;
+ if (G_LIKELY (internal_target >= cinternal)) {
+ ret = internal_target - cinternal;
ret = gst_util_uint64_scale (ret, cnum, cdenom);
ret += cexternal;
} else {
- ret = cinternal - internal;
+ ret = cinternal - internal_target;
ret = gst_util_uint64_scale (ret, cnum, cdenom);
/* clamp to 0 */
if (G_LIKELY (cexternal > ret))
ret = 0;
}
- /* make sure the time is increasing */
- priv->last_time = MAX (ret, priv->last_time);
-
- return priv->last_time;
+ return ret;
}
/**
- * gst_clock_unadjust_unlocked
+ * gst_clock_adjust_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.
+ * @internal: a clock time
*
- * This function is the reverse of gst_clock_adjust_unlocked().
+ * 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.
*
- * Returns: the internal time of the clock corresponding to @external.
+ * This function is the reverse of gst_clock_unadjust_unlocked().
*
- * Since: 0.10.13
+ * Returns: the converted time of the clock.
*/
GstClockTime
-gst_clock_unadjust_unlocked (GstClock * clock, GstClockTime external)
+gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal)
{
GstClockTime ret, cinternal, cexternal, cnum, cdenom;
GstClockPrivate *priv = clock->priv;
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 >= cexternal)) {
- ret = external - cexternal;
+ if (G_LIKELY (external_target >= cexternal)) {
+ ret = external_target - cexternal;
ret = gst_util_uint64_scale (ret, cdenom, cnum);
ret += cinternal;
} else {
- ret = cexternal - external;
+ 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_get_internal_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 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
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))
}
/**
- * gst_clock_get_time
+ * gst_clock_get_time:
* @clock: a #GstClock to query
*
* Gets the current time of the given clock. The time is always
}
/**
- * gst_clock_set_calibration
+ * 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
+ * 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
* Subsequent calls to gst_clock_get_time() will return clock times computed as
* follows:
*
- * <programlisting>
+ * |[
* time = (internal_time - internal) * rate_num / rate_denom + external
- * </programlisting>
+ * ]|
*
* This formula is implemented in gst_clock_adjust_unlocked(). Of course, it
* tries to do the integer arithmetic as precisely as possible.
}
/**
- * gst_clock_get_calibration
- * @clock: a #GstClock
+ * 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
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_clock_set_master
- * @clock: a #GstClock
- * @master: (allow-none): a master #GstClock
+ * 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.
- *
+ * 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
+ * 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
+ * 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.
/* 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);
* clock calibration. */
priv->clockid = gst_clock_new_periodic_id (master,
gst_clock_get_time (master), priv->timeout);
- gst_clock_id_wait_async_full (priv->clockid,
+ gst_clock_id_wait_async (priv->clockid,
(GstClockCallback) gst_clock_slave_callback,
gst_object_ref (clock), (GDestroyNotify) gst_object_unref);
}
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
+ * @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): a master #GstClock or %NULL when this clock is
- * not slaved to a master clock. Unref after usage.
+ * Returns: (transfer full) (nullable): a master #GstClock or %NULL
+ * when this clock is not slaved to a master clock. Unref after
+ * usage.
*
* MT safe.
*/
return result;
}
-/* http://mathworld.wolfram.com/LeastSquaresFitting.html
- * with SLAVE_LOCK
+/**
+ * gst_clock_id_get_clock:
+ * @id: a #GstClockID
+ *
+ * This function returns the underlying clock.
+ *
+ * Returns: (transfer full) (nullable): a #GstClock or %NULL when the
+ * underlying clock has been freed. Unref after usage.
+ *
+ * MT safe.
+ *
+ * Since: 1.16
*/
-static gboolean
-do_linear_regression (GstClock * clock, GstClockTime * m_num,
- GstClockTime * m_denom, GstClockTime * b, GstClockTime * xbase,
- gdouble * r_squared)
+GstClock *
+gst_clock_id_get_clock (GstClockID id)
{
- GstClockTime *newx, *newy;
- GstClockTime xmin, ymin, xbar, ybar, xbar4, ybar4;
- GstClockTimeDiff sxx, sxy, syy;
- GstClockTime *x, *y;
- gint i, j;
- guint n;
- GstClockPrivate *priv;
-
- xbar = ybar = sxx = syy = sxy = 0;
-
- priv = clock->priv;
-
- x = priv->times;
- y = priv->times + 2;
- n = priv->filling ? priv->time_index : priv->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 = priv->times + 1;
- newy = priv->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
+ GstClockEntry *entry;
- /* have to do this precisely otherwise the results are pretty much useless.
- * should guarantee that none of these accumulators can overflow */
+ g_return_val_if_fail (id != NULL, NULL);
- /* 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
+ entry = (GstClockEntry *) id;
+ return g_weak_ref_get (&entry->ABI.abi.clock);
+}
- /* 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;
- }
+/**
+ * gst_clock_id_uses_clock:
+ * @id: a #GstClockID to check
+ * @clock: a #GstClock to compare against
+ *
+ * This function returns whether @id uses @clock as the underlying clock.
+ * @clock can be NULL, in which case the return value indicates whether
+ * the underlying clock has been freed. If this is the case, the @id is
+ * no longer usable and should be freed.
+ *
+ * Returns: whether the clock @id uses the same underlying #GstClock @clock.
+ *
+ * MT safe.
+ *
+ * Since: 1.16
+ */
+gboolean
+gst_clock_id_uses_clock (GstClockID id, GstClock * clock)
+{
+ GstClockEntry *entry;
+ GstClock *entry_clock;
+ gboolean ret = FALSE;
- if (G_UNLIKELY (sxx == 0))
- goto invalid;
+ g_return_val_if_fail (id != NULL, FALSE);
+ g_return_val_if_fail (clock != NULL, FALSE);
- *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
+ entry = (GstClockEntry *) id;
+ entry_clock = g_weak_ref_get (&entry->ABI.abi.clock);
+ if (entry_clock == clock)
+ ret = TRUE;
- return TRUE;
+ if (G_LIKELY (entry_clock != NULL))
+ gst_object_unref (entry_clock);
-invalid:
- {
- GST_CAT_DEBUG_OBJECT (GST_CAT_CLOCK, clock, "sxx == 0, regression failed");
- return FALSE;
- }
+ return ret;
}
+
/**
- * gst_clock_add_observation
- * @clock: a #GstClock
+ * 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
* 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
+ * 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
+ * Returns: %TRUE if enough observations were added to run the
* regression algorithm.
*
* MT safe.
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);
"adding observation slave %" GST_TIME_FORMAT ", master %" GST_TIME_FORMAT,
GST_TIME_ARGS (slave), GST_TIME_ARGS (master));
- priv->times[(4 * priv->time_index)] = slave;
- priv->times[(4 * priv->time_index) + 2] = 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)) {
if (G_UNLIKELY (priv->filling && priv->time_index < priv->window_threshold))
goto filling;
- if (!do_linear_regression (clock, &m_num, &m_denom, &b, &xbase, r_squared))
+ 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);
"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);
+ if (internal)
+ *internal = xbase;
+ if (external)
+ *external = b;
+ if (rate_num)
+ *rate_num = m_num;
+ if (rate_denom)
+ *rate_denom = m_denom;
return TRUE;
{
/* no valid regression has been done, ignore the result then */
GST_CLOCK_SLAVE_UNLOCK (clock);
- return TRUE;
+ return FALSE;
}
}
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;
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);
+ }
+}