gstreamer/gst/gstclock.c
Thomas Vander Stichele 5526c2dbe4 gst/gstclock.c: Commit from the Political Party For More Atomic CVS Commits, so that people don't waste too much of t...
Original commit message from CVS:
* gst/gstclock.c: (_gst_clock_id_free):
Commit from the Political Party For More Atomic CVS Commits,
so that people don't waste too much of their day fishing
out obvious leaks out of massive commits.
Oh, and fix a pretty damn obvious leak in the memchunk
removal code.
2005-09-20 09:28:56 +00:00

680 lines
16 KiB
C

/* 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
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION:gstclock
* @short_description: Abstract class for global clocks
* @see_also: #GstSystemClock
*
* GStreamer uses a global clock to synchronise the plugins in a pipeline.
* Different clock implementations are possible by implementing this abstract
* base class.
*
* The clock time is always measured in nanoseconds and always increases. The
* pipeline uses the clock to calculate the stream time.
* Usually all renderers sync to the global clock so that the clock is always
* a good measure of the current playback time in the pipeline.
*/
#include <time.h>
#include "gst_private.h"
#include "gstclock.h"
#include "gstinfo.h"
#include "gstutils.h"
#ifndef GST_DISABLE_TRACE
/* #define GST_WITH_ALLOC_TRACE */
#include "gsttrace.h"
static GstAllocTrace *_gst_clock_entry_trace;
#endif
#define DEFAULT_EVENT_DIFF (GST_SECOND)
#define DEFAULT_MAX_DIFF (2 * GST_SECOND)
enum
{
ARG_0,
ARG_STATS,
ARG_MAX_DIFF,
ARG_EVENT_DIFF
};
static void gst_clock_class_init (GstClockClass * klass);
static void gst_clock_init (GstClock * clock);
static void gst_clock_finalize (GObject * object);
static void gst_clock_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec);
static void gst_clock_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec);
static void gst_clock_update_stats (GstClock * clock);
static GstObjectClass *parent_class = NULL;
/* static guint gst_clock_signals[LAST_SIGNAL] = { 0 }; */
static GstClockID
gst_clock_entry_new (GstClock * clock, GstClockTime time,
GstClockTime interval, GstClockEntryType type)
{
GstClockEntry *entry;
entry = g_malloc0 (sizeof (GstClockEntry));
#ifndef GST_DISABLE_TRACE
gst_alloc_trace_new (_gst_clock_entry_trace, entry);
#endif
GST_CAT_DEBUG (GST_CAT_CLOCK, "created entry %p, time %" GST_TIME_FORMAT,
entry, GST_TIME_ARGS (time));
gst_atomic_int_set (&entry->refcount, 1);
entry->clock = clock;
entry->time = time;
entry->interval = interval;
entry->type = type;
entry->status = GST_CLOCK_BUSY;
return (GstClockID) entry;
}
/**
* gst_clock_id_ref:
* @id: The clockid to ref
*
* Increase the refcount of the given clockid.
*
* Returns: The same #GstClockID with increased refcount.
*
* MT safe.
*/
GstClockID
gst_clock_id_ref (GstClockID id)
{
g_return_val_if_fail (id != NULL, NULL);
g_atomic_int_inc (&((GstClockEntry *) id)->refcount);
return id;
}
static void
_gst_clock_id_free (GstClockID id)
{
g_return_if_fail (id != NULL);
GST_CAT_DEBUG (GST_CAT_CLOCK, "freed entry %p", id);
#ifndef GST_DISABLE_TRACE
gst_alloc_trace_free (_gst_clock_entry_trace, id);
#endif
g_free (id);
}
/**
* gst_clock_id_unref:
* @id: The clockid to unref
*
* Unref the given clockid. When the refcount reaches 0 the
* #GstClockID will be freed.
*
* MT safe.
*/
void
gst_clock_id_unref (GstClockID id)
{
gint zero;
g_return_if_fail (id != NULL);
zero = g_atomic_int_dec_and_test (&((GstClockEntry *) id)->refcount);
/* if we ended up with the refcount at zero, free the id */
if (zero) {
_gst_clock_id_free (id);
}
}
/**
* gst_clock_new_single_shot_id
* @clock: The clockid to get a single shot notification from
* @time: the requested time
*
* Get an ID from the given clock to trigger a single shot
* notification at the requested time. The single shot id should be
* unreffed after usage.
*
* Returns: An id that can be used to request the time notification.
*
* MT safe.
*/
GstClockID
gst_clock_new_single_shot_id (GstClock * clock, GstClockTime time)
{
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
return gst_clock_entry_new (clock,
time, GST_CLOCK_TIME_NONE, GST_CLOCK_ENTRY_SINGLE);
}
/**
* gst_clock_new_periodic_id
* @clock: The clockid to get a periodic notification id from
* @start_time: the requested start time
* @interval: the requested interval
*
* Get an ID from the given clock to trigger a periodic notification.
* The periodeic notifications will be start at time start_time and
* will then be fired with the given interval. The id should be unreffed
* after usage.
*
* Returns: An id that can be used to request the time notification.
*
* MT safe.
*/
GstClockID
gst_clock_new_periodic_id (GstClock * clock, GstClockTime start_time,
GstClockTime interval)
{
g_return_val_if_fail (GST_IS_CLOCK (clock), NULL);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (start_time), NULL);
g_return_val_if_fail (interval != 0, NULL);
return gst_clock_entry_new (clock,
start_time, interval, GST_CLOCK_ENTRY_PERIODIC);
}
/**
* gst_clock_id_compare_func
* @id1: A clockid
* @id2: A clockid to compare with
*
* Compares the two GstClockID instances. This function can be used
* as a GCompareFunc when sorting ids.
*
* Returns: negative value if a < b; zero if a = b; positive value if a > b
*
* MT safe.
*/
gint
gst_clock_id_compare_func (gconstpointer id1, gconstpointer id2)
{
GstClockEntry *entry1, *entry2;
entry1 = (GstClockEntry *) id1;
entry2 = (GstClockEntry *) id2;
if (GST_CLOCK_ENTRY_TIME (entry1) > GST_CLOCK_ENTRY_TIME (entry2)) {
return 1;
}
if (GST_CLOCK_ENTRY_TIME (entry1) < GST_CLOCK_ENTRY_TIME (entry2)) {
return -1;
}
return entry1 - entry2;
}
/**
* gst_clock_id_get_time
* @id: The clockid to query
*
* Get the time of the clock ID
*
* Returns: the time of the given clock id.
*
* MT safe.
*/
GstClockTime
gst_clock_id_get_time (GstClockID id)
{
g_return_val_if_fail (id != NULL, GST_CLOCK_TIME_NONE);
return GST_CLOCK_ENTRY_TIME ((GstClockEntry *) id);
}
/**
* gst_clock_id_wait
* @id: The clockid to wait on
* @jitter: A pointer that will contain the jitter
*
* Perform a blocking wait on the given ID. The jitter arg can be
* NULL.
*
* Returns: the result of the blocking wait.
*
* MT safe.
*/
GstClockReturn
gst_clock_id_wait (GstClockID id, GstClockTimeDiff * jitter)
{
GstClockEntry *entry;
GstClock *clock;
GstClockReturn res;
GstClockTime requested;
GstClockClass *cclass;
g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR);
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
goto invalid_time;
if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED))
goto unscheduled;
clock = GST_CLOCK_ENTRY_CLOCK (entry);
cclass = GST_CLOCK_GET_CLASS (clock);
if (G_LIKELY (cclass->wait)) {
GST_CAT_DEBUG (GST_CAT_CLOCK, "waiting on clock entry %p", id);
res = cclass->wait (clock, entry);
GST_CAT_DEBUG (GST_CAT_CLOCK, "done waiting entry %p", id);
if (jitter) {
GstClockTime now = gst_clock_get_time (clock);
*jitter = now - requested;
}
if (entry->type == GST_CLOCK_ENTRY_PERIODIC) {
entry->time += entry->interval;
}
if (clock->stats) {
gst_clock_update_stats (clock);
}
} else {
res = GST_CLOCK_UNSUPPORTED;
}
return res;
/* ERRORS */
invalid_time:
{
GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _BADTIME");
return GST_CLOCK_BADTIME;
}
unscheduled:
{
GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was unscheduled return _UNSCHEDULED");
return GST_CLOCK_UNSCHEDULED;
}
}
/**
* gst_clock_id_wait_async:
* @id: a #GstClockID to wait on
* @func: The callback function
* @user_data: User data passed in the calback
*
* Register a callback on the given clockid with the given
* function and user_data. When passing an id with an invalid
* time to this function, the callback will be called immediatly
* with a time set to GST_CLOCK_TIME_NONE. The callback will
* be called when the time of the id has been reached.
*
* Returns: the result of the non blocking wait.
*
* MT safe.
*/
GstClockReturn
gst_clock_id_wait_async (GstClockID id,
GstClockCallback func, gpointer user_data)
{
GstClockEntry *entry;
GstClock *clock;
GstClockReturn res;
GstClockClass *cclass;
GstClockTime requested;
g_return_val_if_fail (id != NULL, GST_CLOCK_ERROR);
g_return_val_if_fail (func != NULL, GST_CLOCK_ERROR);
entry = (GstClockEntry *) id;
requested = GST_CLOCK_ENTRY_TIME (entry);
clock = GST_CLOCK_ENTRY_CLOCK (entry);
if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (requested)))
goto invalid_time;
if (G_UNLIKELY (entry->status == GST_CLOCK_UNSCHEDULED))
goto unscheduled;
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->wait_async) {
entry->func = func;
entry->user_data = user_data;
res = cclass->wait_async (clock, entry);
} else {
res = GST_CLOCK_UNSUPPORTED;
}
return res;
/* ERRORS */
invalid_time:
{
(func) (clock, GST_CLOCK_TIME_NONE, id, user_data);
GST_CAT_DEBUG (GST_CAT_CLOCK, "invalid time requested, returning _BADTIME");
return GST_CLOCK_BADTIME;
}
unscheduled:
{
GST_CAT_DEBUG (GST_CAT_CLOCK, "entry was unscheduled return _UNSCHEDULED");
return GST_CLOCK_UNSCHEDULED;
}
}
/**
* gst_clock_id_unschedule:
* @id: The id to unschedule
*
* Cancel an outstanding request with the given ID. This can either
* be an outstanding async notification or a pending sync notification.
* After this call, the @id cannot be used anymore to receive sync or
* async notifications, you need to create a new GstClockID.
*
* MT safe.
*/
void
gst_clock_id_unschedule (GstClockID id)
{
GstClockEntry *entry;
GstClock *clock;
GstClockClass *cclass;
g_return_if_fail (id != NULL);
entry = (GstClockEntry *) id;
clock = entry->clock;
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->unschedule)
cclass->unschedule (clock, entry);
}
/**
* GstClock abstract base class implementation
*/
GType
gst_clock_get_type (void)
{
static GType clock_type = 0;
if (!clock_type) {
static const GTypeInfo clock_info = {
sizeof (GstClockClass),
NULL,
NULL,
(GClassInitFunc) gst_clock_class_init,
NULL,
NULL,
sizeof (GstClock),
0,
(GInstanceInitFunc) gst_clock_init,
NULL
};
clock_type = g_type_register_static (GST_TYPE_OBJECT, "GstClock",
&clock_info, G_TYPE_FLAG_ABSTRACT);
}
return clock_type;
}
static void
gst_clock_class_init (GstClockClass * klass)
{
GObjectClass *gobject_class;
GstObjectClass *gstobject_class;
gobject_class = (GObjectClass *) klass;
gstobject_class = (GstObjectClass *) klass;
parent_class = g_type_class_ref (GST_TYPE_OBJECT);
if (!g_thread_supported ())
g_thread_init (NULL);
#ifndef GST_DISABLE_TRACE
_gst_clock_entry_trace =
gst_alloc_trace_register (GST_CLOCK_ENTRY_TRACE_NAME);
#endif
gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_clock_finalize);
gobject_class->set_property = GST_DEBUG_FUNCPTR (gst_clock_set_property);
gobject_class->get_property = GST_DEBUG_FUNCPTR (gst_clock_get_property);
g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_STATS,
g_param_spec_boolean ("stats", "Stats", "Enable clock stats",
FALSE, G_PARAM_READWRITE));
}
static void
gst_clock_init (GstClock * clock)
{
clock->adjust = 0;
clock->last_time = 0;
clock->entries = NULL;
clock->entries_changed = g_cond_new ();
clock->flags = 0;
clock->stats = FALSE;
}
static void
gst_clock_finalize (GObject * object)
{
GstClock *clock = GST_CLOCK (object);
g_cond_free (clock->entries_changed);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* gst_clock_set_resolution
* @clock: The clock set the resolution on
* @resolution: The resolution to set
*
* Set the accuracy of the clock.
*
* Returns: the new resolution of the clock.
*/
guint64
gst_clock_set_resolution (GstClock * clock, guint64 resolution)
{
GstClockClass *cclass;
g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0));
g_return_val_if_fail (resolution != 0, G_GINT64_CONSTANT (0));
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->change_resolution)
clock->resolution =
cclass->change_resolution (clock, clock->resolution, resolution);
return clock->resolution;
}
/**
* gst_clock_get_resolution
* @clock: The clock get the resolution of
*
* Get the accuracy of the clock.
*
* Returns: the resolution of the clock in microseconds.
*
* MT safe.
*/
guint64
gst_clock_get_resolution (GstClock * clock)
{
GstClockClass *cclass;
g_return_val_if_fail (GST_IS_CLOCK (clock), G_GINT64_CONSTANT (0));
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->get_resolution)
return cclass->get_resolution (clock);
return G_GINT64_CONSTANT (1);
}
/**
* gst_clock_adjust_unlocked
* @clock: a #GstClock to use
* @internal: a clock time
*
* Converts the given @internal clock time to the real time, adjusting
* and making sure that the returned time is increasing.
* This function should be called with the clock lock held.
*
* Returns: the converted time of the clock.
*
* MT safe.
*/
GstClockTime
gst_clock_adjust_unlocked (GstClock * clock, GstClockTime internal)
{
GstClockTime ret;
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;
}
return ret;
}
/**
* gst_clock_get_time
* @clock: a #GstClock to query
*
* Gets the current time of the given clock. The time is always
* monotonically increasing.
*
* 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;
GstClockClass *cclass;
g_return_val_if_fail (GST_IS_CLOCK (clock), GST_CLOCK_TIME_NONE);
cclass = GST_CLOCK_GET_CLASS (clock);
if (cclass->get_internal_time) {
ret = cclass->get_internal_time (clock);
} else {
ret = G_GINT64_CONSTANT (0);
}
GST_CAT_DEBUG (GST_CAT_CLOCK, "internal time %" GST_TIME_FORMAT,
GST_TIME_ARGS (ret));
GST_LOCK (clock);
ret = gst_clock_adjust_unlocked (clock, ret);
GST_UNLOCK (clock);
GST_CAT_DEBUG (GST_CAT_CLOCK, "adjusted time %" GST_TIME_FORMAT,
GST_TIME_ARGS (ret));
return ret;
}
/**
* gst_clock_set_time_adjust
* @clock: a #GstClock to adjust
* @adjust: the adjust value
*
* 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.
*
* MT safe.
*/
void
gst_clock_set_time_adjust (GstClock * clock, GstClockTime adjust)
{
g_return_if_fail (GST_IS_CLOCK (clock));
GST_LOCK (clock);
clock->adjust = adjust;
GST_UNLOCK (clock);
}
static void
gst_clock_update_stats (GstClock * clock)
{
}
static void
gst_clock_set_property (GObject * object, guint prop_id,
const GValue * value, GParamSpec * pspec)
{
GstClock *clock;
clock = GST_CLOCK (object);
switch (prop_id) {
case ARG_STATS:
clock->stats = g_value_get_boolean (value);
g_object_notify (object, "stats");
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gst_clock_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstClock *clock;
clock = GST_CLOCK (object);
switch (prop_id) {
case ARG_STATS:
g_value_set_boolean (value, clock->stats);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}