/* GStreamer * * Copyright (C) 2007,2009 Sebastian Dröge * * 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 #include #include "gstcontrolsource.h" #include "gstinterpolationcontrolsource.h" #include "gstinterpolationcontrolsourceprivate.h" #include "gst/glib-compat-private.h" #define GST_CAT_DEFAULT controller_debug GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT); G_DEFINE_TYPE (GstInterpolationControlSource, gst_interpolation_control_source, GST_TYPE_CONTROL_SOURCE); /* * 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. * * User interpolation is not yet available and quadratic interpolation * is deprecated and maps to cubic interpolation. * * 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, ×tamp, (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: 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, ×tamp, (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, GQueue * res) { g_queue_push_tail (res, 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: 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) { GQueue res = G_QUEUE_INIT; 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 res.head; } /** * 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 (gst_interpolation_control_source_parent_class)->finalize (obj); } static void gst_interpolation_control_source_dispose (GObject * obj) { G_OBJECT_CLASS (gst_interpolation_control_source_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); 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; }