/* GStreamer
* Copyright (C) 1999,2000 Erik Walthinsen <omega@cse.ogi.edu>
* 2000 Wim Taymans <wtay@chello.be>
+ * 2004 Wim Taymans <wim@fluendo.com>
*
* gstclock.c: Clock subsystem for maintaining time sync
*
#include "gstclock.h"
#include "gstinfo.h"
#include "gstmemchunk.h"
+#include "gstatomic_impl.h"
#ifndef GST_DISABLE_TRACE
/* #define GST_WITH_ALLOC_TRACE */
static GstMemChunk *_gst_clock_entries_chunk;
-void gst_clock_id_unlock (GstClockID id);
-
-
static void gst_clock_class_init (GstClockClass * klass);
static void gst_clock_init (GstClock * clock);
static void gst_clock_dispose (GObject * object);
#ifndef GST_DISABLE_TRACE
gst_alloc_trace_new (_gst_clock_entry_trace, entry);
#endif
+ GST_CAT_DEBUG (GST_CAT_CLOCK, "created entry %p", entry);
+ gst_atomic_int_init (&entry->refcount, 1);
entry->clock = clock;
entry->time = time;
entry->interval = interval;
entry->type = type;
- entry->status = GST_CLOCK_ENTRY_OK;
+ 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);
+
+ gst_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
+ gst_mem_chunk_free (_gst_clock_entries_chunk, 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 = gst_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.
+ * 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)
*
* 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.
+ * 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,
}
/**
+ * 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
+ * Returns: the time of the given clock id.
+ *
+ * MT safe.
*/
GstClockTime
gst_clock_id_get_time (GstClockID id)
* @jitter: A pointer that will contain the jitter
*
* Perform a blocking wait on the given ID. The jitter arg can be
- * NULL
+ * 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 = GST_CLOCK_UNSUPPORTED;
+ GstClockReturn res;
GstClockTime requested;
GstClockClass *cclass;
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
- if (!GST_CLOCK_TIME_IS_VALID (requested)) {
- GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _TIMEOUT");
- return GST_CLOCK_TIMEOUT;
- }
+ 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 (cclass->wait) {
- GstClockTime now;
+ if (G_LIKELY (cclass->wait)) {
- GST_LOCK (clock);
- clock->entries = g_list_prepend (clock->entries, entry);
- GST_UNLOCK (clock);
-
- GST_CAT_DEBUG (GST_CAT_CLOCK, "waiting on clock");
- do {
- res = cclass->wait (clock, entry);
- }
- while (res == GST_CLOCK_ENTRY_RESTART);
- GST_CAT_DEBUG (GST_CAT_CLOCK, "done waiting");
-
- GST_LOCK (clock);
- clock->entries = g_list_remove (clock->entries, entry);
- GST_UNLOCK (clock);
+ GST_CAT_DEBUG (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) {
- now = gst_clock_get_time (clock);
+ 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;
+ }
}
/**
* @user_data: User data passed in the calback
*
* Register a callback on the given clockid with the given
- * function and user_data.
+ * 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,
{
GstClockEntry *entry;
GstClock *clock;
- GstClockReturn res = GST_CLOCK_UNSUPPORTED;
+ GstClockReturn res;
GstClockClass *cclass;
+ GstClockTime requested;
g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR);
g_return_val_if_fail (func != NULL, GST_CLOCK_ERROR);
entry = (GstClockEntry *) id;
- clock = entry->clock;
+ requested = GST_CLOCK_ENTRY_TIME (entry);
+ clock = GST_CLOCK_ENTRY_CLOCK (entry);
- if (!GST_CLOCK_TIME_IS_VALID (GST_CLOCK_ENTRY_TIME (entry))) {
- (func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
- return GST_CLOCK_TIMEOUT;
- }
+ 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);
entry->user_data = user_data;
res = cclass->wait_async (clock, entry);
+ } else {
+ res = GST_CLOCK_UNSUPPORTED;
}
-
return res;
-}
-static void
-gst_clock_reschedule_func (GstClockEntry * entry)
-{
- entry->status = GST_CLOCK_ENTRY_OK;
-
- gst_clock_id_unlock ((GstClockID) entry);
+ /* ERRORS */
+invalid_time:
+ {
+ (func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
+ GST_CAT_DEBUG (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 async notification request with the given ID.
+ * 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)
cclass->unschedule (clock, entry);
}
-/**
- * gst_clock_id_free:
- * @id: The clockid to free
- *
- * Free the resources held by the given id
- */
-void
-gst_clock_id_free (GstClockID id)
-{
- g_return_if_fail (id != NULL);
-
-#ifndef GST_DISABLE_TRACE
- gst_alloc_trace_free (_gst_clock_entry_trace, id);
-#endif
- gst_mem_chunk_free (_gst_clock_entries_chunk, id);
-}
-
-/**
- * gst_clock_id_unlock:
- * @id: The clockid to unlock
- *
- * Unlock the givan ClockID.
- */
-void
-gst_clock_id_unlock (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->unlock)
- cclass->unlock (clock, entry);
-}
-
/**
* GstClock abstract base class implementation
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STATS,
g_param_spec_boolean ("stats", "Stats", "Enable clock stats",
FALSE, G_PARAM_READWRITE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_MAX_DIFF,
- g_param_spec_int64 ("max-diff", "Max diff",
- "The maximum amount of time to wait in nanoseconds", 0, G_MAXINT64,
- DEFAULT_MAX_DIFF, G_PARAM_READWRITE));
- g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_EVENT_DIFF,
- g_param_spec_uint64 ("event-diff", "event diff",
- "The amount of time that may elapse until 2 events are treated as happening at different times",
- 0, G_MAXUINT64, DEFAULT_EVENT_DIFF,
- G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
}
static void
gst_clock_init (GstClock * clock)
{
- clock->max_diff = DEFAULT_MAX_DIFF;
-
- clock->start_time = 0;
+ clock->adjust = 0;
clock->last_time = 0;
clock->entries = NULL;
+ clock->entries_changed = g_cond_new ();
clock->flags = 0;
clock->stats = FALSE;
-
- clock->active_mutex = g_mutex_new ();
- clock->active_cond = g_cond_new ();
}
static void
{
GstClock *clock = GST_CLOCK (object);
- g_mutex_free (clock->active_mutex);
- g_cond_free (clock->active_cond);
+ g_cond_free (clock->entries_changed);
G_OBJECT_CLASS (parent_class)->dispose (object);
}
/**
- * gst_clock_set_speed
- * @clock: a #GstClock to modify
- * @speed: the speed to set on the clock
- *
- * Sets the speed on the given clock. 1.0 is the default
- * speed.
- *
- * Returns: the new speed of the clock.
- */
-gdouble
-gst_clock_set_speed (GstClock * clock, gdouble speed)
-{
- g_return_val_if_fail (GST_IS_CLOCK (clock), 0.0);
-
- GST_WARNING_OBJECT (clock, "called deprecated function");
- return 1.0;
-}
-
-/**
- * gst_clock_get_speed
- * @clock: a #GstClock to query
- *
- * Gets the speed of the given clock.
- *
- * Returns: the speed of the clock.
- */
-gdouble
-gst_clock_get_speed (GstClock * clock)
-{
- g_return_val_if_fail (GST_IS_CLOCK (clock), 0.0);
-
- GST_WARNING_OBJECT (clock, "called deprecated function");
- return 1.0;
-}
-
-/**
* gst_clock_set_resolution
* @clock: The clock set the resolution on
* @resolution: The resolution to set
* Get the accuracy of the clock.
*
* Returns: the resolution of the clock in microseconds.
+ *
+ * MT safe.
*/
guint64
gst_clock_get_resolution (GstClock * clock)
}
/**
- * gst_clock_set_active
- * @clock: a #GstClock to set state of
- * @active: flag indicating if the clock should be activated (TRUE) or deactivated
+ * gst_clock_adjust_unlocked
+ * @clock: a #GstClock to use
+ * @internal: a clock time
*
- * Activates or deactivates the clock based on the active parameter.
- * As soon as the clock is activated, the time will start ticking.
- */
-void
-gst_clock_set_active (GstClock * clock, gboolean active)
-{
- g_return_if_fail (GST_IS_CLOCK (clock));
-
- GST_ERROR_OBJECT (clock, "called deprecated function that does nothing now.");
-
- return;
-}
-
-/**
- * gst_clock_is_active
- * @clock: a #GstClock to query
+ * Converts the given @internal clock time to the real time, adjusting
+ * and making sure that the returned time is increasing.
+ * This function should be called with the clock lock held.
*
- * Checks if the given clock is active.
- *
- * Returns: TRUE if the clock is active.
- */
-gboolean
-gst_clock_is_active (GstClock * clock)
-{
- g_return_val_if_fail (GST_IS_CLOCK (clock), FALSE);
-
- GST_WARNING_OBJECT (clock, "called deprecated function.");
-
- return TRUE;
-}
-
-/**
- * gst_clock_reset
- * @clock: a #GstClock to reset
+ * Returns: the converted time of the clock.
*
- * Reset the clock to time 0.
+ * MT safe.
*/
-void
-gst_clock_reset (GstClock * clock)
+GstClockTime
+gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal)
{
- GstClockTime time = G_GINT64_CONSTANT (0);
- GstClockClass *cclass;
-
- g_return_if_fail (GST_IS_CLOCK (clock));
-
- GST_ERROR_OBJECT (clock, "called deprecated function.");
-
- cclass = GST_CLOCK_GET_CLASS (clock);
+ GstClockTime ret;
- if (cclass->get_internal_time) {
- time = cclass->get_internal_time (clock);
+ ret = internal + clock->adjust;
+ /* make sure the time is increasing, else return last_time */
+ if ((gint64) ret < (gint64) clock->last_time) {
+ ret = clock->last_time;
+ } else {
+ clock->last_time = ret;
}
-
- GST_LOCK (clock);
- //clock->active = FALSE;
- clock->start_time = time;
- clock->last_time = G_GINT64_CONSTANT (0);
- g_list_foreach (clock->entries, (GFunc) gst_clock_reschedule_func, NULL);
- GST_UNLOCK (clock);
-}
-
-/**
- * gst_clock_handle_discont
- * @clock: a #GstClock to notify of the discontinuity
- * @time: The new time
- *
- * Notifies the clock of a discontinuity in time.
- *
- * Returns: TRUE if the clock was updated. It is possible that
- * the clock was not updated by this call because only the first
- * discontinuitity in the pipeline is honoured.
- */
-gboolean
-gst_clock_handle_discont (GstClock * clock, guint64 time)
-{
- GST_ERROR_OBJECT (clock, "called deprecated function.");
-
- return FALSE;
+ return ret;
}
/**
* Gets the current time of the given clock. The time is always
* monotonically increasing.
*
- * Returns: the time of the clock.
+ * 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_GINT64_CONSTANT (0);
+ GstClockTime ret;
GstClockClass *cclass;
- g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0));
+ 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) - clock->start_time;
- }
- /* make sure the time is increasing, else return last_time */
- if ((gint64) ret < (gint64) clock->last_time) {
- ret = clock->last_time;
+ ret = cclass->get_internal_time (clock);
} else {
- clock->last_time = ret;
+ 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_event_time:
- * @clock: clock to query
- *
- * Gets the "event time" of a given clock. An event on the clock happens
- * whenever this function is called. This ensures that multiple events that
- * happen shortly after each other are treated as if they happened at the same
- * time. GStreamer uses to keep state changes of multiple elements in sync.
- *
- * Returns: the time of the event
- */
-GstClockTime
-gst_clock_get_event_time (GstClock * clock)
-{
- return gst_clock_get_event_time_delay (clock, 0);
-}
-
-/**
- * gst_clock_get_event_time_delay:
- * @clock: clock to query
- * @delay: time before the event actually occurs
- *
- * Gets the "event time" of a given clock. An event on the clock happens
- * whenever this function is called. This ensures that multiple events that
- * happen shortly after each other are treated as if they happened at the same
- * time. GStreamer uses to keep state changes of multiple elements in sync.
- *
- * When calling this function, the specified delay will be added to the current
- * time to produce the event time. This can be used for events that are
- * scheduled to happen at some point in the future.
- *
- * Returns: the time of the event
- */
-GstClockTime
-gst_clock_get_event_time_delay (GstClock * clock, GstClockTime delay)
-{
- GstClockTime time;
-
- g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE);
+ GST_LOCK (clock);
+ ret = gst_clock_adjust_unlocked (clock, ret);
+ GST_UNLOCK (clock);
- time = gst_clock_get_time (clock);
+ GST_CAT_DEBUG (GST_CAT_CLOCK, "adjusted time %" GST_TIME_FORMAT,
+ GST_TIME_ARGS (ret));
- if (ABS (GST_CLOCK_DIFF (clock->last_event, time + delay)) <
- clock->max_event_diff) {
- GST_LOG_OBJECT (clock, "reporting last event time %" G_GUINT64_FORMAT,
- clock->last_event);
- } else {
- clock->last_event = time + delay;
- GST_LOG_OBJECT (clock, "reporting new event time %" G_GUINT64_FORMAT,
- clock->last_event);
- }
-
- return clock->last_event;
+ return ret;
}
/**
- * gst_clock_get_next_id
- * @clock: The clock to query
+ * gst_clock_set_time_adjust
+ * @clock: a #GstClock to adjust
+ * @adjust: the adjust value
*
- * Get the clockid of the next event.
+ * Adjusts the current time of the clock with the adjust value.
+ * A positive value moves the clock forwards and a backwards value
+ * moves it backwards. Note that _get_time() always returns
+ * increasing values so when you move the clock backwards, _get_time()
+ * will report the previous value until the clock catches up.
*
- * Returns: a clockid or NULL is no event is pending.
+ * MT safe.
*/
-GstClockID
-gst_clock_get_next_id (GstClock * clock)
+void
+gst_clock_set_time_adjust (GstClock * clock, GstClockTime adjust)
{
- GstClockEntry *entry = NULL;
-
- g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
+ g_return_if_fail (GST_IS_CLOCK (clock));
GST_LOCK (clock);
- if (clock->entries)
- entry = GST_CLOCK_ENTRY (clock->entries->data);
+ clock->adjust = adjust;
GST_UNLOCK (clock);
-
- return (GstClockID *) entry;
}
static void
clock->stats = g_value_get_boolean (value);
g_object_notify (object, "stats");
break;
- case ARG_MAX_DIFF:
- clock->max_diff = g_value_get_int64 (value);
- g_object_notify (object, "max-diff");
- break;
- case ARG_EVENT_DIFF:
- clock->max_event_diff = g_value_get_uint64 (value);
- g_object_notify (object, "event-diff");
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
case ARG_STATS:
g_value_set_boolean (value, clock->stats);
break;
- case ARG_MAX_DIFF:
- g_value_set_int64 (value, clock->max_diff);
- break;
- case ARG_EVENT_DIFF:
- g_value_set_uint64 (value, clock->max_event_diff);
- break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;