gstreamer/libs/gst/controller/gstinterpolation.c
Sebastian Dröge bdcc0329ef libs/gst/controller/: Protect against values larger or smaller than the minimum or maximum allowed value for the prop...
Original commit message from CVS:
* libs/gst/controller/gstcontroller.c:
(gst_controlled_property_new):
* libs/gst/controller/gstcontrollerprivate.h:
* libs/gst/controller/gstinterpolation.c:
(gst_controlled_property_find_control_point_node),
(interpolate_none_get), (interpolate_none_get_enum_value_array),
(interpolate_none_get_string_value_array),
(interpolate_trigger_get),
(interpolate_trigger_get_enum_value_array),
(interpolate_trigger_get_string_value_array):
Protect against values larger or smaller than the minimum or maximum
allowed value for the property when using values that can be compared.
Optimize trigger interpolator a bit by taking the last requested value
into account instead of always looping through the complete list.
Fix coding style a bit, everywhere else we use "return foo" instead
of "return (foo)".
* tests/check/libs/controller.c: (GST_START_TEST),
(gst_controller_suite):
Add unit test for the protection against too large or too small
values.
2007-06-09 16:58:30 +00:00

638 lines
18 KiB
C

/* GStreamer
*
* Copyright (C) <2005> Stefan Kost <ensonic at users dot sf dot net>
* Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org>
*
* gstinterpolation.c: Interpolation methods for dynamic properties
*
* 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.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "gstcontrollerprivate.h"
#include "gstcontroller.h"
#define GST_CAT_DEFAULT gst_controller_debug
GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
/* common helper */
/*
* gst_controlled_property_find_control_point_node:
* @prop: the controlled property to search in
* @timestamp: the search key
*
* Find last value before given timestamp in control point list.
*
* Returns: the found #GList node or %NULL
*/
GList *
gst_controlled_property_find_control_point_node (GstControlledProperty * prop,
GstClockTime timestamp)
{
GList *prev_node = g_list_last (prop->values);
GList *node;
GstControlPoint *cp;
node = prop->values;
if (prop->last_requested_value) {
GstControlPoint *last_cp = prop->last_requested_value->data;
if (timestamp > last_cp->timestamp)
node = prop->last_requested_value;
}
/* iterate over timed value list */
for (; node; node = g_list_next (node)) {
cp = node->data;
/* this timestamp is newer that the one we look for */
if (timestamp < cp->timestamp) {
/* get previous one again */
prev_node = g_list_previous (node);
break;
}
}
if (prev_node)
prop->last_requested_value = prev_node;
return prev_node;
}
/* steps-like (no-)interpolation, default */
/* just returns the value for the most recent key-frame */
static GValue *
interpolate_none_get (GstControlledProperty * prop, GstClockTime timestamp)
{
GList *node;
if ((node =
gst_controlled_property_find_control_point_node (prop, timestamp))) {
GstControlPoint *cp = node->data;
return &cp->value;
}
return &prop->default_value;
}
#define interpolate_none_get_boolean interpolate_none_get
#define DEFINE_NONE_GET(type) \
static GValue * \
interpolate_none_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
GList *node; \
\
if ((node = \
gst_controlled_property_find_control_point_node (prop, timestamp))) { \
GstControlPoint *cp = node->data; \
g##type ret = g_value_get_##type (&cp->value); \
if (g_value_get_##type (&prop->min_value) > ret) \
return &prop->min_value; \
else if (g_value_get_##type (&prop->max_value) < ret) \
return &prop->max_value; \
return &cp->value; \
} \
return &prop->default_value; \
}
DEFINE_NONE_GET (int);
DEFINE_NONE_GET (uint);
DEFINE_NONE_GET (long);
DEFINE_NONE_GET (ulong);
DEFINE_NONE_GET (float);
DEFINE_NONE_GET (double);
#define DEFINE_NONE_GET_VALUE_ARRAY(type) \
static gboolean \
interpolate_none_get_##type##_value_array (GstControlledProperty * prop, \
GstClockTime timestamp, GstValueArray * value_array) \
{ \
gint i; \
GstClockTime ts = timestamp; \
g##type *values = (g##type *) value_array->values; \
\
for(i = 0; i < value_array->nbsamples; i++) { \
*values = g_value_get_##type (interpolate_none_get_##type (prop,ts)); \
ts += value_array->sample_interval; \
values++; \
} \
return TRUE; \
}
DEFINE_NONE_GET_VALUE_ARRAY (int);
DEFINE_NONE_GET_VALUE_ARRAY (uint);
DEFINE_NONE_GET_VALUE_ARRAY (long);
DEFINE_NONE_GET_VALUE_ARRAY (ulong);
DEFINE_NONE_GET_VALUE_ARRAY (float);
DEFINE_NONE_GET_VALUE_ARRAY (double);
DEFINE_NONE_GET_VALUE_ARRAY (boolean);
static gboolean
interpolate_none_get_enum_value_array (GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array)
{
gint i;
GstClockTime ts = timestamp;
gint *values = (gint *) value_array->values;
for (i = 0; i < value_array->nbsamples; i++) {
*values = g_value_get_enum (interpolate_none_get (prop, ts));
ts += value_array->sample_interval;
values++;
}
return TRUE;
}
static gboolean
interpolate_none_get_string_value_array (GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array)
{
gint i;
GstClockTime ts = timestamp;
gchar **values = (gchar **) value_array->values;
for (i = 0; i < value_array->nbsamples; i++) {
*values = (gchar *) g_value_get_string (interpolate_none_get (prop, ts));
ts += value_array->sample_interval;
values++;
}
return TRUE;
}
static GstInterpolateMethod interpolate_none = {
interpolate_none_get,
interpolate_none_get_int_value_array,
interpolate_none_get,
interpolate_none_get_uint_value_array,
interpolate_none_get,
interpolate_none_get_long_value_array,
interpolate_none_get,
interpolate_none_get_ulong_value_array,
interpolate_none_get,
interpolate_none_get_float_value_array,
interpolate_none_get,
interpolate_none_get_double_value_array,
interpolate_none_get,
interpolate_none_get_boolean_value_array,
interpolate_none_get,
interpolate_none_get_enum_value_array,
interpolate_none_get,
interpolate_none_get_string_value_array
};
/* returns the default value of the property, except for times with specific values */
/* needed for one-shot events, such as notes and triggers */
static GValue *
interpolate_trigger_get (GstControlledProperty * prop, GstClockTime timestamp)
{
GList *node;
GstControlPoint *cp;
/* check if there is a value at the registered timestamp */
if ((node =
gst_controlled_property_find_control_point_node (prop, timestamp))) {
cp = node->data;
if (timestamp == cp->timestamp) {
return &cp->value;
}
}
return &prop->default_value;
}
#define interpolate_trigger_get_boolean interpolate_trigger_get
#define DEFINE_TRIGGER_GET(type) \
static GValue * \
interpolate_trigger_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
GList *node; \
GstControlPoint *cp; \
\
/* check if there is a value at the registered timestamp */ \
if ((node = \
gst_controlled_property_find_control_point_node (prop, timestamp))) { \
cp = node->data; \
if (timestamp == cp->timestamp) { \
g##type ret = g_value_get_##type (&cp->value); \
if (g_value_get_##type (&prop->min_value) > ret) \
return &prop->min_value; \
else if (g_value_get_##type (&prop->max_value) < ret) \
return &prop->max_value; \
return &cp->value; \
} \
} \
\
return &prop->default_value; \
}
DEFINE_TRIGGER_GET (int);
DEFINE_TRIGGER_GET (uint);
DEFINE_TRIGGER_GET (long);
DEFINE_TRIGGER_GET (ulong);
DEFINE_TRIGGER_GET (float);
DEFINE_TRIGGER_GET (double);
#define DEFINE_TRIGGER_GET_VALUE_ARRAY(type) \
static gboolean \
interpolate_trigger_get_##type##_value_array (GstControlledProperty * prop, \
GstClockTime timestamp, GstValueArray * value_array) \
{ \
gint i; \
GstClockTime ts = timestamp; \
g##type *values = (g##type *) value_array->values; \
\
for(i = 0; i < value_array->nbsamples; i++) { \
*values = g_value_get_##type (interpolate_trigger_get_##type (prop,ts)); \
ts += value_array->sample_interval; \
values++; \
} \
return TRUE; \
}
DEFINE_TRIGGER_GET_VALUE_ARRAY (int);
DEFINE_TRIGGER_GET_VALUE_ARRAY (uint);
DEFINE_TRIGGER_GET_VALUE_ARRAY (long);
DEFINE_TRIGGER_GET_VALUE_ARRAY (ulong);
DEFINE_TRIGGER_GET_VALUE_ARRAY (float);
DEFINE_TRIGGER_GET_VALUE_ARRAY (double);
DEFINE_TRIGGER_GET_VALUE_ARRAY (boolean);
static gboolean
interpolate_trigger_get_enum_value_array (GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array)
{
gint i;
GstClockTime ts = timestamp;
gint *values = (gint *) value_array->values;
for (i = 0; i < value_array->nbsamples; i++) {
*values = g_value_get_enum (interpolate_trigger_get (prop, ts));
ts += value_array->sample_interval;
values++;
}
return TRUE;
}
static gboolean
interpolate_trigger_get_string_value_array (GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array)
{
gint i;
GstClockTime ts = timestamp;
gchar **values = (gchar **) value_array->values;
for (i = 0; i < value_array->nbsamples; i++) {
*values = (gchar *) g_value_get_string (interpolate_trigger_get (prop, ts));
ts += value_array->sample_interval;
values++;
}
return TRUE;
}
static GstInterpolateMethod interpolate_trigger = {
interpolate_trigger_get,
interpolate_trigger_get_int_value_array,
interpolate_trigger_get,
interpolate_trigger_get_uint_value_array,
interpolate_trigger_get,
interpolate_trigger_get_long_value_array,
interpolate_trigger_get,
interpolate_trigger_get_ulong_value_array,
interpolate_trigger_get,
interpolate_trigger_get_float_value_array,
interpolate_trigger_get,
interpolate_trigger_get_double_value_array,
interpolate_trigger_get,
interpolate_trigger_get_boolean_value_array,
interpolate_trigger_get,
interpolate_trigger_get_enum_value_array,
interpolate_trigger_get,
interpolate_trigger_get_string_value_array
};
/* linear interpolation */
/* smoothes inbetween values */
#define DEFINE_LINEAR_GET(type) \
static g##type \
_interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
GList *node; \
g##type ret; \
\
if ((node = gst_controlled_property_find_control_point_node (prop, timestamp))) { \
GstControlPoint *cp1, *cp2; \
\
cp1 = node->data; \
if ((node = g_list_next (node))) { \
gdouble slope; \
g##type value1,value2; \
\
cp2 = node->data; \
\
value1 = g_value_get_##type (&cp1->value); \
value2 = g_value_get_##type (&cp2->value); \
slope = (gdouble) (value2 - value1) / gst_guint64_to_gdouble (cp2->timestamp - cp1->timestamp); \
\
ret = (g##type) (value1 + gst_guint64_to_gdouble (timestamp - cp1->timestamp) * slope); \
} \
else { \
ret = g_value_get_##type (&cp1->value); \
} \
} else { \
ret = g_value_get_##type (&prop->default_value); \
} \
ret = CLAMP (ret, g_value_get_##type (&prop->min_value), g_value_get_##type (&prop->max_value)); \
return ret; \
} \
\
static GValue * \
interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
g_value_set_##type (&prop->result_value,_interpolate_linear_get_##type (prop,timestamp)); \
return &prop->result_value; \
} \
\
static gboolean \
interpolate_linear_get_##type##_value_array (GstControlledProperty * prop, \
GstClockTime timestamp, GstValueArray * value_array) \
{ \
gint i; \
GstClockTime ts = timestamp; \
g##type *values = (g##type *) value_array->values; \
\
for(i = 0; i < value_array->nbsamples; i++) { \
*values = _interpolate_linear_get_##type (prop, ts); \
ts += value_array->sample_interval; \
values++; \
} \
return TRUE; \
}
DEFINE_LINEAR_GET (int);
DEFINE_LINEAR_GET (uint);
DEFINE_LINEAR_GET (long);
DEFINE_LINEAR_GET (ulong);
DEFINE_LINEAR_GET (float);
DEFINE_LINEAR_GET (double);
static GstInterpolateMethod interpolate_linear = {
interpolate_linear_get_int,
interpolate_linear_get_int_value_array,
interpolate_linear_get_uint,
interpolate_linear_get_uint_value_array,
interpolate_linear_get_long,
interpolate_linear_get_long_value_array,
interpolate_linear_get_ulong,
interpolate_linear_get_ulong_value_array,
interpolate_linear_get_float,
interpolate_linear_get_float_value_array,
interpolate_linear_get_double,
interpolate_linear_get_double_value_array,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
/* square interpolation */
/* cubic interpolation */
/* The following functions implement a natural cubic spline interpolator.
* For details look at http://en.wikipedia.org/wiki/Spline_interpolation
*
* Instead of using a real matrix with n^2 elements for the linear system
* of equations we use three arrays o, p, q to hold the tridiagonal matrix
* as following to save memory:
*
* p[0] q[0] 0 0 0
* o[1] p[1] q[1] 0 0
* 0 o[2] p[2] q[2] .
* . . . . .
*/
#define DEFINE_CUBIC_GET(type) \
static void \
_interpolate_cubic_update_cache_##type (GstControlledProperty *prop) \
{ \
gint i, n = prop->nvalues; \
gdouble *o = g_new0 (gdouble, n); \
gdouble *p = g_new0 (gdouble, n); \
gdouble *q = g_new0 (gdouble, n); \
\
gdouble *h = g_new0 (gdouble, n); \
gdouble *b = g_new0 (gdouble, n); \
gdouble *z = g_new0 (gdouble, n); \
\
GList *node; \
GstControlPoint *cp; \
GstClockTime x_prev, x, x_next; \
g##type y_prev, y, y_next; \
\
/* Fill linear system of equations */ \
node = prop->values; \
cp = node->data; \
x = cp->timestamp; \
y = g_value_get_##type (&cp->value); \
\
p[0] = 1.0; \
\
node = node->next; \
cp = node->data; \
x_next = cp->timestamp; \
y_next = g_value_get_##type (&cp->value); \
h[0] = gst_util_guint64_to_gdouble (x_next - x); \
\
for (i = 1; i < n-1; i++) { \
/* Shuffle x and y values */ \
x_prev = x; \
y_prev = y; \
x = x_next; \
y = y_next; \
node = node->next; \
cp = node->data; \
x_next = cp->timestamp; \
y_next = g_value_get_##type (&cp->value); \
\
h[i] = gst_util_guint64_to_gdouble (x_next - x); \
o[i] = h[i-1]; \
p[i] = 2.0 * (h[i-1] + h[i]); \
q[i] = h[i]; \
b[i] = (y_next - y) / h[i] - (y - y_prev) / h[i-1]; \
} \
p[n-1] = 1.0; \
\
/* Use Gauss elimination to set everything below the \
* diagonal to zero */ \
for (i = 1; i < n-1; i++) { \
gdouble a = o[i] / p[i-1]; \
p[i] -= a * q[i-1]; \
b[i] -= a * b[i-1]; \
} \
\
/* Solve everything else from bottom to top */ \
for (i = n-2; i > 0; i--) \
z[i] = (b[i] - q[i] * z[i+1]) / p[i]; \
\
/* Save cache next in the GstControlPoint */ \
\
node = prop->values; \
for (i = 0; i < n; i++) { \
cp = node->data; \
cp->cache.cubic.h = h[i]; \
cp->cache.cubic.z = z[i]; \
node = node->next; \
} \
\
/* Free our temporary arrays */ \
g_free (o); \
g_free (p); \
g_free (q); \
g_free (h); \
g_free (b); \
g_free (z); \
} \
\
static g##type \
_interpolate_cubic_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
GList *node; \
g##type ret; \
\
if (prop->nvalues <= 2) \
return _interpolate_linear_get_##type (prop, timestamp); \
\
if (!prop->valid_cache) { \
_interpolate_cubic_update_cache_##type (prop); \
prop->valid_cache = TRUE; \
} \
\
if ((node = gst_controlled_property_find_control_point_node (prop, timestamp))) { \
GstControlPoint *cp1, *cp2; \
\
cp1 = node->data; \
if ((node = g_list_next (node))) { \
gdouble diff1, diff2; \
g##type value1,value2; \
gdouble out; \
\
cp2 = node->data; \
\
value1 = g_value_get_##type (&cp1->value); \
value2 = g_value_get_##type (&cp2->value); \
\
diff1 = gst_guint64_to_gdouble (timestamp - cp1->timestamp); \
diff2 = gst_guint64_to_gdouble (cp2->timestamp - timestamp); \
\
out = (cp2->cache.cubic.z * diff1 * diff1 * diff1 + cp1->cache.cubic.z * diff2 * diff2 * diff2) / cp1->cache.cubic.h; \
out += (value2 / cp1->cache.cubic.h - cp1->cache.cubic.h * cp2->cache.cubic.z) * diff1; \
out += (value1 / cp1->cache.cubic.h - cp1->cache.cubic.h * cp1->cache.cubic.z) * diff2; \
\
ret = (g##type) out; \
} \
else { \
ret = g_value_get_##type (&cp1->value); \
} \
} else {\
ret = g_value_get_##type (&prop->default_value); \
} \
ret = CLAMP (ret, g_value_get_##type (&prop->min_value), g_value_get_##type (&prop->max_value)); \
return ret; \
} \
\
static GValue * \
interpolate_cubic_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
g_value_set_##type (&prop->result_value,_interpolate_cubic_get_##type (prop,timestamp)); \
return &prop->result_value; \
} \
\
static gboolean \
interpolate_cubic_get_##type##_value_array (GstControlledProperty * prop, \
GstClockTime timestamp, GstValueArray * value_array) \
{ \
gint i; \
GstClockTime ts = timestamp; \
g##type *values = (g##type *) value_array->values; \
\
for(i = 0; i < value_array->nbsamples; i++) { \
*values = _interpolate_cubic_get_##type (prop, ts); \
ts += value_array->sample_interval; \
values++; \
} \
return TRUE; \
}
DEFINE_CUBIC_GET (int);
DEFINE_CUBIC_GET (uint);
DEFINE_CUBIC_GET (long);
DEFINE_CUBIC_GET (ulong);
DEFINE_CUBIC_GET (float);
DEFINE_CUBIC_GET (double);
static GstInterpolateMethod interpolate_cubic = {
interpolate_cubic_get_int,
interpolate_cubic_get_int_value_array,
interpolate_cubic_get_uint,
interpolate_cubic_get_uint_value_array,
interpolate_cubic_get_long,
interpolate_cubic_get_long_value_array,
interpolate_cubic_get_ulong,
interpolate_cubic_get_ulong_value_array,
interpolate_cubic_get_float,
interpolate_cubic_get_float_value_array,
interpolate_cubic_get_double,
interpolate_cubic_get_double_value_array,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
};
/* register all interpolation methods */
GstInterpolateMethod *interpolation_methods[] = {
&interpolate_none,
&interpolate_trigger,
&interpolate_linear,
&interpolate_cubic,
&interpolate_cubic
};
guint num_interpolation_methods = G_N_ELEMENTS (interpolation_methods);