gstreamer/libs/gst/controller/gstinterpolationcontrolsource.c
Stefan Sauer 9a27b9c056 controller: move to core/gstobject
Move the controller to gstobject as a simple delegate. The controller and
controlsource are not classes in core. The controlsources stay separate as a lib
for now. This way we can avoid the qdata lookups.

Also remove controller_init(). There is no more need to link to controller for
elements.

Also sanitize the API. We now have functions to add properties like we had
methods to remove that. That avoids then ref count hacks we had in _new.
2011-11-04 20:11:51 +01:00

702 lines
22 KiB
C

/* GStreamer
*
* Copyright (C) 2007,2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
*
* gstinterpolationcontrolsource.c: Control source that provides several
* interpolation methods
*
* 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:gstinterpolationcontrolsource
* @short_description: interpolation control source
*
* #GstInterpolationControlSource is a #GstControlSource, that interpolates values between user-given
* control points. It supports several interpolation modes and property types.
*
* To use #GstInterpolationControlSource get a new instance by calling
* gst_interpolation_control_source_new(), bind it to a #GParamSpec, select a interpolation mode with
* gst_interpolation_control_source_set_interpolation_mode() and set some control points by calling
* gst_interpolation_control_source_set().
*
* All functions are MT-safe.
*
*/
#include <glib-object.h>
#include <gst/gst.h>
#include "gstinterpolationcontrolsource.h"
#include "gstinterpolationcontrolsourceprivate.h"
#define GST_CAT_DEFAULT controller_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define _do_init \
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "interpolation control source", 0, "timeline value interpolating control source")
G_DEFINE_TYPE_WITH_CODE (GstInterpolationControlSource,
gst_interpolation_control_source, GST_TYPE_CONTROL_SOURCE, _do_init);
static GObjectClass *parent_class = NULL;
/*
* gst_control_point_free:
* @prop: the object to free
*
* Private method which frees all data allocated by a #GstControlPoint
* instance.
*/
static void
gst_control_point_free (GstControlPoint * cp)
{
g_return_if_fail (cp);
g_value_unset (&cp->value);
g_slice_free (GstControlPoint, cp);
}
static void
gst_interpolation_control_source_reset (GstInterpolationControlSource * self)
{
GstControlSource *csource = (GstControlSource *) self;
csource->get_value = NULL;
csource->get_value_array = NULL;
self->priv->type = self->priv->base = G_TYPE_INVALID;
if (G_IS_VALUE (&self->priv->default_value))
g_value_unset (&self->priv->default_value);
if (G_IS_VALUE (&self->priv->minimum_value))
g_value_unset (&self->priv->minimum_value);
if (G_IS_VALUE (&self->priv->maximum_value))
g_value_unset (&self->priv->maximum_value);
if (self->priv->values) {
g_sequence_free (self->priv->values);
self->priv->values = NULL;
}
self->priv->nvalues = 0;
self->priv->valid_cache = FALSE;
}
/**
* gst_interpolation_control_source_new:
*
* This returns a new, unbound #GstInterpolationControlSource.
*
* Returns: a new, unbound #GstInterpolationControlSource.
*/
GstInterpolationControlSource *
gst_interpolation_control_source_new (void)
{
return g_object_newv (GST_TYPE_INTERPOLATION_CONTROL_SOURCE, 0, NULL);
}
/**
* gst_interpolation_control_source_set_interpolation_mode:
* @self: the #GstInterpolationControlSource object
* @mode: interpolation mode
*
* Sets the given interpolation mode.
*
* <note><para>User interpolation is not yet available and quadratic interpolation
* is deprecated and maps to cubic interpolation.</para></note>
*
* Returns: %TRUE if the interpolation mode could be set, %FALSE otherwise
*/
/* *INDENT-OFF* */
gboolean
gst_interpolation_control_source_set_interpolation_mode (
GstInterpolationControlSource * self, GstInterpolateMode mode)
/* *INDENT-ON* */
{
gboolean ret = TRUE;
GstControlSource *csource = GST_CONTROL_SOURCE (self);
if (mode >= priv_gst_num_interpolation_methods
|| priv_gst_interpolation_methods[mode] == NULL) {
GST_WARNING ("interpolation mode %d invalid or not implemented yet", mode);
return FALSE;
}
if (mode == GST_INTERPOLATE_QUADRATIC) {
GST_WARNING ("Quadratic interpolation mode is deprecated, using cubic"
"interpolation mode");
}
if (mode == GST_INTERPOLATE_USER) {
GST_WARNING ("User interpolation mode is not implemented yet");
return FALSE;
}
g_mutex_lock (self->lock);
switch (self->priv->base) {
case G_TYPE_INT:
csource->get_value = priv_gst_interpolation_methods[mode]->get_int;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_int_value_array;
break;
case G_TYPE_UINT:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_uint;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_uint_value_array;
break;
}
case G_TYPE_LONG:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_long;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_long_value_array;
break;
}
case G_TYPE_ULONG:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_ulong;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_ulong_value_array;
break;
}
case G_TYPE_INT64:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_int64;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_int64_value_array;
break;
}
case G_TYPE_UINT64:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_uint64;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_uint64_value_array;
break;
}
case G_TYPE_FLOAT:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_float;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_float_value_array;
break;
}
case G_TYPE_DOUBLE:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_double;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_double_value_array;
break;
}
case G_TYPE_BOOLEAN:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_boolean;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_boolean_value_array;
break;
}
case G_TYPE_ENUM:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_enum;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_enum_value_array;
break;
}
case G_TYPE_STRING:{
csource->get_value = priv_gst_interpolation_methods[mode]->get_string;
csource->get_value_array =
priv_gst_interpolation_methods[mode]->get_string_value_array;
break;
}
default:
ret = FALSE;
break;
}
/* Incomplete implementation */
if (!ret || !csource->get_value || !csource->get_value_array) {
gst_interpolation_control_source_reset (self);
ret = FALSE;
}
self->priv->valid_cache = FALSE;
self->priv->interpolation_mode = mode;
g_mutex_unlock (self->lock);
return ret;
}
static gboolean
gst_interpolation_control_source_bind (GstControlSource * source,
GParamSpec * pspec)
{
GType type, base;
GstInterpolationControlSource *self =
(GstInterpolationControlSource *) source;
gboolean ret = TRUE;
/* get the fundamental base type */
self->priv->type = base = type = G_PARAM_SPEC_VALUE_TYPE (pspec);
while ((type = g_type_parent (type)))
base = type;
self->priv->base = base;
/* restore type */
type = self->priv->type;
if (!gst_interpolation_control_source_set_interpolation_mode (self,
self->priv->interpolation_mode))
return FALSE;
switch (base) {
case G_TYPE_INT:{
GParamSpecInt *tpspec = G_PARAM_SPEC_INT (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_int (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_int (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_int (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_UINT:{
GParamSpecUInt *tpspec = G_PARAM_SPEC_UINT (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_uint (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_uint (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_uint (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_LONG:{
GParamSpecLong *tpspec = G_PARAM_SPEC_LONG (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_long (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_long (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_long (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_ULONG:{
GParamSpecULong *tpspec = G_PARAM_SPEC_ULONG (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_ulong (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_ulong (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_ulong (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_INT64:{
GParamSpecInt64 *tpspec = G_PARAM_SPEC_INT64 (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_int64 (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_int64 (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_int64 (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_UINT64:{
GParamSpecUInt64 *tpspec = G_PARAM_SPEC_UINT64 (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_uint64 (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_uint64 (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_uint64 (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_FLOAT:{
GParamSpecFloat *tpspec = G_PARAM_SPEC_FLOAT (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_float (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_float (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_float (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_DOUBLE:{
GParamSpecDouble *tpspec = G_PARAM_SPEC_DOUBLE (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_double (&self->priv->default_value, tpspec->default_value);
g_value_init (&self->priv->minimum_value, type);
g_value_set_double (&self->priv->minimum_value, tpspec->minimum);
g_value_init (&self->priv->maximum_value, type);
g_value_set_double (&self->priv->maximum_value, tpspec->maximum);
break;
}
case G_TYPE_BOOLEAN:{
GParamSpecBoolean *tpspec = G_PARAM_SPEC_BOOLEAN (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_boolean (&self->priv->default_value, tpspec->default_value);
break;
}
case G_TYPE_ENUM:{
GParamSpecEnum *tpspec = G_PARAM_SPEC_ENUM (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_enum (&self->priv->default_value, tpspec->default_value);
break;
}
case G_TYPE_STRING:{
GParamSpecString *tpspec = G_PARAM_SPEC_STRING (pspec);
g_value_init (&self->priv->default_value, type);
g_value_set_string (&self->priv->default_value, tpspec->default_value);
break;
}
default:
GST_WARNING ("incomplete implementation for paramspec type '%s'",
G_PARAM_SPEC_TYPE_NAME (pspec));
ret = FALSE;
break;
}
if (ret) {
self->priv->valid_cache = FALSE;
self->priv->nvalues = 0;
} else {
gst_interpolation_control_source_reset (self);
}
return ret;
}
/*
* gst_control_point_compare:
* @p1: a pointer to a #GstControlPoint
* @p2: a pointer to a #GstControlPoint
*
* Compare function for g_list operations that operates on two #GstControlPoint
* parameters.
*/
static gint
gst_control_point_compare (gconstpointer p1, gconstpointer p2)
{
GstClockTime ct1 = ((GstControlPoint *) p1)->timestamp;
GstClockTime ct2 = ((GstControlPoint *) p2)->timestamp;
return ((ct1 < ct2) ? -1 : ((ct1 == ct2) ? 0 : 1));
}
/*
* gst_control_point_find:
* @p1: a pointer to a #GstControlPoint
* @p2: a pointer to a #GstClockTime
*
* Compare function for g_list operations that operates on a #GstControlPoint and
* a #GstClockTime.
*/
static gint
gst_control_point_find (gconstpointer p1, gconstpointer p2)
{
GstClockTime ct1 = ((GstControlPoint *) p1)->timestamp;
GstClockTime ct2 = *(GstClockTime *) p2;
return ((ct1 < ct2) ? -1 : ((ct1 == ct2) ? 0 : 1));
}
static GstControlPoint *
_make_new_cp (GstInterpolationControlSource * self, GstClockTime timestamp,
const GValue * value)
{
GstControlPoint *cp;
/* create a new GstControlPoint */
cp = g_slice_new0 (GstControlPoint);
cp->timestamp = timestamp;
g_value_init (&cp->value, self->priv->type);
g_value_copy (value, &cp->value);
return cp;
}
static void
gst_interpolation_control_source_set_internal (GstInterpolationControlSource *
self, GstClockTime timestamp, const GValue * value)
{
GSequenceIter *iter;
/* check if a control point for the timestamp already exists */
/* iter contains the iter right *after* timestamp */
if (G_LIKELY (self->priv->values)) {
iter =
g_sequence_search (self->priv->values, &timestamp,
(GCompareDataFunc) gst_control_point_find, NULL);
if (iter) {
GSequenceIter *prev = g_sequence_iter_prev (iter);
GstControlPoint *cp = g_sequence_get (prev);
/* If the timestamp is the same just update the control point value */
if (cp->timestamp == timestamp) {
/* update control point */
g_value_reset (&cp->value);
g_value_copy (value, &cp->value);
goto done;
}
}
} else {
self->priv->values =
g_sequence_new ((GDestroyNotify) gst_control_point_free);
}
/* sort new cp into the prop->values list */
g_sequence_insert_sorted (self->priv->values, _make_new_cp (self, timestamp,
value), (GCompareDataFunc) gst_control_point_compare, NULL);
self->priv->nvalues++;
done:
self->priv->valid_cache = FALSE;
}
/**
* gst_interpolation_control_source_set:
* @self: the #GstInterpolationControlSource object
* @timestamp: the time the control-change is scheduled for
* @value: the control-value
*
* Set the value of given controller-handled property at a certain time.
*
* Returns: FALSE if the values couldn't be set, TRUE otherwise.
*/
gboolean
gst_interpolation_control_source_set (GstInterpolationControlSource * self,
GstClockTime timestamp, const GValue * value)
{
g_return_val_if_fail (GST_IS_INTERPOLATION_CONTROL_SOURCE (self), FALSE);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
g_return_val_if_fail (G_IS_VALUE (value), FALSE);
g_return_val_if_fail (G_VALUE_TYPE (value) == self->priv->type, FALSE);
g_mutex_lock (self->lock);
gst_interpolation_control_source_set_internal (self, timestamp, value);
g_mutex_unlock (self->lock);
return TRUE;
}
/**
* gst_interpolation_control_source_set_from_list:
* @self: the #GstInterpolationControlSource object
* @timedvalues: (transfer none) (element-type GstController.TimedValue): a list
* with #GstTimedValue items
*
* Sets multiple timed values at once.
*
* Returns: FALSE if the values couldn't be set, TRUE otherwise.
*/
gboolean
gst_interpolation_control_source_set_from_list (GstInterpolationControlSource *
self, const GSList * timedvalues)
{
const GSList *node;
GstTimedValue *tv;
gboolean res = FALSE;
g_return_val_if_fail (GST_IS_INTERPOLATION_CONTROL_SOURCE (self), FALSE);
for (node = timedvalues; node; node = g_slist_next (node)) {
tv = node->data;
if (!GST_CLOCK_TIME_IS_VALID (tv->timestamp)) {
GST_WARNING ("GstTimedValued with invalid timestamp passed to %s",
GST_FUNCTION);
} else if (!G_IS_VALUE (&tv->value)) {
GST_WARNING ("GstTimedValued with invalid value passed to %s",
GST_FUNCTION);
} else if (G_VALUE_TYPE (&tv->value) != self->priv->type) {
GST_WARNING ("incompatible value type for property");
} else {
g_mutex_lock (self->lock);
gst_interpolation_control_source_set_internal (self, tv->timestamp,
&tv->value);
g_mutex_unlock (self->lock);
res = TRUE;
}
}
return res;
}
/**
* gst_interpolation_control_source_unset:
* @self: the #GstInterpolationControlSource object
* @timestamp: the time the control-change should be removed from
*
* Used to remove the value of given controller-handled property at a certain
* time.
*
* Returns: FALSE if the value couldn't be unset (i.e. not found, TRUE otherwise.
*/
gboolean
gst_interpolation_control_source_unset (GstInterpolationControlSource * self,
GstClockTime timestamp)
{
GSequenceIter *iter;
gboolean res = FALSE;
g_return_val_if_fail (GST_IS_INTERPOLATION_CONTROL_SOURCE (self), FALSE);
g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
g_mutex_lock (self->lock);
/* check if a control point for the timestamp exists */
if (G_LIKELY (self->priv->values) && (iter =
g_sequence_search (self->priv->values, &timestamp,
(GCompareDataFunc) gst_control_point_find, NULL))) {
GstControlPoint *cp;
/* Iter contains the iter right after timestamp, i.e.
* we need to get the previous one and check the timestamp
*/
iter = g_sequence_iter_prev (iter);
cp = g_sequence_get (iter);
if (cp->timestamp == timestamp) {
g_sequence_remove (iter);
self->priv->nvalues--;
self->priv->valid_cache = FALSE;
res = TRUE;
}
}
g_mutex_unlock (self->lock);
return res;
}
/**
* gst_interpolation_control_source_unset_all:
* @self: the #GstInterpolationControlSource object
*
* Used to remove all time-stamped values of given controller-handled property
*
*/
void
gst_interpolation_control_source_unset_all (GstInterpolationControlSource *
self)
{
g_return_if_fail (GST_IS_INTERPOLATION_CONTROL_SOURCE (self));
g_mutex_lock (self->lock);
/* free GstControlPoint structures */
if (self->priv->values) {
g_sequence_free (self->priv->values);
self->priv->values = NULL;
}
self->priv->nvalues = 0;
self->priv->valid_cache = FALSE;
g_mutex_unlock (self->lock);
}
static void
_append_control_point (GstControlPoint * cp, GList ** l)
{
*l = g_list_prepend (*l, cp);
}
/**
* gst_interpolation_control_source_get_all:
* @self: the #GstInterpolationControlSource to get the list from
*
* Returns a read-only copy of the list of #GstTimedValue for the given property.
* Free the list after done with it.
*
* Returns: (transfer container) (element-type GstController.TimedValue): a copy
* of the list, or %NULL if the property isn't handled by the controller
*/
GList *
gst_interpolation_control_source_get_all (GstInterpolationControlSource * self)
{
GList *res = NULL;
g_return_val_if_fail (GST_IS_INTERPOLATION_CONTROL_SOURCE (self), NULL);
g_mutex_lock (self->lock);
if (G_LIKELY (self->priv->values))
g_sequence_foreach (self->priv->values, (GFunc) _append_control_point,
&res);
g_mutex_unlock (self->lock);
return g_list_reverse (res);
}
/**
* gst_interpolation_control_source_get_count:
* @self: the #GstInterpolationControlSource to get the number of values from
*
* Returns the number of control points that are set.
*
* Returns: the number of control points that are set.
*
*/
gint
gst_interpolation_control_source_get_count (GstInterpolationControlSource *
self)
{
g_return_val_if_fail (GST_IS_INTERPOLATION_CONTROL_SOURCE (self), 0);
return self->priv->nvalues;
}
static void
gst_interpolation_control_source_init (GstInterpolationControlSource * self)
{
self->lock = g_mutex_new ();
self->priv =
G_TYPE_INSTANCE_GET_PRIVATE (self, GST_TYPE_INTERPOLATION_CONTROL_SOURCE,
GstInterpolationControlSourcePrivate);
self->priv->interpolation_mode = GST_INTERPOLATE_NONE;
}
static void
gst_interpolation_control_source_finalize (GObject * obj)
{
GstInterpolationControlSource *self = GST_INTERPOLATION_CONTROL_SOURCE (obj);
g_mutex_lock (self->lock);
gst_interpolation_control_source_reset (self);
g_mutex_unlock (self->lock);
g_mutex_free (self->lock);
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
static void
gst_interpolation_control_source_dispose (GObject * obj)
{
G_OBJECT_CLASS (parent_class)->dispose (obj);
}
static void
gst_interpolation_control_source_class_init (GstInterpolationControlSourceClass
* klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GstControlSourceClass *csource_class = GST_CONTROL_SOURCE_CLASS (klass);
parent_class = g_type_class_peek_parent (klass);
g_type_class_add_private (klass,
sizeof (GstInterpolationControlSourcePrivate));
gobject_class->finalize = gst_interpolation_control_source_finalize;
gobject_class->dispose = gst_interpolation_control_source_dispose;
csource_class->bind = gst_interpolation_control_source_bind;
}