1 /* GStreamer
2  *
3  * Copyright (C) 2007,2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
4  *
5  * gstinterpolationcontrolsource.c: Control source that provides several
6  *                                  interpolation methods
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU Library General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
21  * Boston, MA 02110-1301, USA.
22  */
23 
24 /**
25  * SECTION:gstinterpolationcontrolsource
26  * @title: GstInterpolationControlSource
27  * @short_description: interpolation control source
28  *
29  * #GstInterpolationControlSource is a #GstControlSource, that interpolates values between user-given
30  * control points. It supports several interpolation modes and property types.
31  *
32  * To use #GstInterpolationControlSource get a new instance by calling
33  * gst_interpolation_control_source_new(), bind it to a #GParamSpec and set some
34  * control points by calling gst_timed_value_control_source_set().
35  *
36  * All functions are MT-safe.
37  *
38  */
39 #ifdef HAVE_CONFIG_H
40 #include "config.h"
41 #endif
42 
43 #include <glib-object.h>
44 #include <gst/gst.h>
45 
46 #include "gstinterpolationcontrolsource.h"
47 #include "gst/glib-compat-private.h"
48 #include "gst/math-compat.h"
49 
50 #define GST_CAT_DEFAULT controller_debug
51 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
52 
53 /* helper functions */
54 
55 static inline gboolean
_get_nearest_control_points(GstTimedValueControlSource * self,GstClockTime ts,GstControlPoint ** cp1,GstControlPoint ** cp2)56 _get_nearest_control_points (GstTimedValueControlSource * self,
57     GstClockTime ts, GstControlPoint ** cp1, GstControlPoint ** cp2)
58 {
59   GSequenceIter *iter;
60 
61   iter = gst_timed_value_control_source_find_control_point_iter (self, ts);
62   if (iter) {
63     *cp1 = g_sequence_get (iter);
64     iter = g_sequence_iter_next (iter);
65     if (iter && !g_sequence_iter_is_end (iter)) {
66       *cp2 = g_sequence_get (iter);
67     } else {
68       *cp2 = NULL;
69     }
70     return TRUE;
71   }
72   return FALSE;
73 }
74 
75 static inline void
_get_nearest_control_points2(GstTimedValueControlSource * self,GstClockTime ts,GstControlPoint ** cp1,GstControlPoint ** cp2,GstClockTime * next_ts)76 _get_nearest_control_points2 (GstTimedValueControlSource * self,
77     GstClockTime ts, GstControlPoint ** cp1, GstControlPoint ** cp2,
78     GstClockTime * next_ts)
79 {
80   GSequenceIter *iter1, *iter2 = NULL;
81 
82   *cp1 = *cp2 = NULL;
83   iter1 = gst_timed_value_control_source_find_control_point_iter (self, ts);
84   if (iter1) {
85     *cp1 = g_sequence_get (iter1);
86     iter2 = g_sequence_iter_next (iter1);
87   } else {
88     if (G_LIKELY (self->values)) {
89       /* all values in the control point list come after the given timestamp */
90       iter2 = g_sequence_get_begin_iter (self->values);
91       /* why this? if !cp1 we don't interpolate anyway
92        * if we can eliminate this, we can also use _get_nearest_control_points()
93        * here, is this just to set next_ts? */
94     } else {
95       /* no values */
96       iter2 = NULL;
97     }
98   }
99 
100   if (iter2 && !g_sequence_iter_is_end (iter2)) {
101     *cp2 = g_sequence_get (iter2);
102     *next_ts = (*cp2)->timestamp;
103   } else {
104     *next_ts = GST_CLOCK_TIME_NONE;
105   }
106 }
107 
108 
109 /*  steps-like (no-)interpolation, default */
110 /*  just returns the value for the most recent key-frame */
111 static inline gdouble
_interpolate_none(GstTimedValueControlSource * self,GstControlPoint * cp)112 _interpolate_none (GstTimedValueControlSource * self, GstControlPoint * cp)
113 {
114   return cp->value;
115 }
116 
117 static gboolean
interpolate_none_get(GstTimedValueControlSource * self,GstClockTime timestamp,gdouble * value)118 interpolate_none_get (GstTimedValueControlSource * self, GstClockTime timestamp,
119     gdouble * value)
120 {
121   gboolean ret = FALSE;
122   GSequenceIter *iter;
123   GstControlPoint *cp;
124 
125   g_mutex_lock (&self->lock);
126 
127   iter =
128       gst_timed_value_control_source_find_control_point_iter (self, timestamp);
129   if (iter) {
130     cp = g_sequence_get (iter);
131     *value = _interpolate_none (self, cp);
132     ret = TRUE;
133   }
134   g_mutex_unlock (&self->lock);
135   return ret;
136 }
137 
138 static gboolean
interpolate_none_get_value_array(GstTimedValueControlSource * self,GstClockTime timestamp,GstClockTime interval,guint n_values,gdouble * values)139 interpolate_none_get_value_array (GstTimedValueControlSource * self,
140     GstClockTime timestamp, GstClockTime interval, guint n_values,
141     gdouble * values)
142 {
143   gboolean ret = FALSE;
144   guint i;
145   GstClockTime ts = timestamp;
146   GstClockTime next_ts = 0;
147   GstControlPoint *cp1 = NULL, *cp2 = NULL;
148 
149   g_mutex_lock (&self->lock);
150 
151   for (i = 0; i < n_values; i++) {
152     GST_LOG ("values[%3d] : ts=%" GST_TIME_FORMAT ", next_ts=%" GST_TIME_FORMAT,
153         i, GST_TIME_ARGS (ts), GST_TIME_ARGS (next_ts));
154     if (ts >= next_ts) {
155       _get_nearest_control_points2 (self, ts, &cp1, &cp2, &next_ts);
156     }
157     if (cp1) {
158       *values = _interpolate_none (self, cp1);
159       ret = TRUE;
160       GST_LOG ("values[%3d]=%lf", i, *values);
161     } else {
162       *values = NAN;
163       GST_LOG ("values[%3d]=-", i);
164     }
165     ts += interval;
166     values++;
167   }
168   g_mutex_unlock (&self->lock);
169   return ret;
170 }
171 
172 
173 
174 /*  linear interpolation */
175 /*  smoothes in between values */
176 static inline gdouble
_interpolate_linear(GstClockTime timestamp1,gdouble value1,GstClockTime timestamp2,gdouble value2,GstClockTime timestamp)177 _interpolate_linear (GstClockTime timestamp1, gdouble value1,
178     GstClockTime timestamp2, gdouble value2, GstClockTime timestamp)
179 {
180   if (GST_CLOCK_TIME_IS_VALID (timestamp2)) {
181     gdouble slope;
182 
183     slope =
184         (value2 - value1) / gst_guint64_to_gdouble (timestamp2 - timestamp1);
185     return value1 + (gst_guint64_to_gdouble (timestamp - timestamp1) * slope);
186   } else {
187     return value1;
188   }
189 }
190 
191 static gboolean
interpolate_linear_get(GstTimedValueControlSource * self,GstClockTime timestamp,gdouble * value)192 interpolate_linear_get (GstTimedValueControlSource * self,
193     GstClockTime timestamp, gdouble * value)
194 {
195   gboolean ret = FALSE;
196   GstControlPoint *cp1, *cp2;
197 
198   g_mutex_lock (&self->lock);
199 
200   if (_get_nearest_control_points (self, timestamp, &cp1, &cp2)) {
201     *value = _interpolate_linear (cp1->timestamp, cp1->value,
202         (cp2 ? cp2->timestamp : GST_CLOCK_TIME_NONE),
203         (cp2 ? cp2->value : 0.0), timestamp);
204     ret = TRUE;
205   }
206   g_mutex_unlock (&self->lock);
207   return ret;
208 }
209 
210 static gboolean
interpolate_linear_get_value_array(GstTimedValueControlSource * self,GstClockTime timestamp,GstClockTime interval,guint n_values,gdouble * values)211 interpolate_linear_get_value_array (GstTimedValueControlSource * self,
212     GstClockTime timestamp, GstClockTime interval, guint n_values,
213     gdouble * values)
214 {
215   gboolean ret = FALSE;
216   guint i;
217   GstClockTime ts = timestamp;
218   GstClockTime next_ts = 0;
219   GstControlPoint *cp1 = NULL, *cp2 = NULL;
220 
221   g_mutex_lock (&self->lock);
222 
223   for (i = 0; i < n_values; i++) {
224     GST_LOG ("values[%3d] : ts=%" GST_TIME_FORMAT ", next_ts=%" GST_TIME_FORMAT,
225         i, GST_TIME_ARGS (ts), GST_TIME_ARGS (next_ts));
226     if (ts >= next_ts) {
227       _get_nearest_control_points2 (self, ts, &cp1, &cp2, &next_ts);
228     }
229     if (cp1) {
230       *values = _interpolate_linear (cp1->timestamp, cp1->value,
231           (cp2 ? cp2->timestamp : GST_CLOCK_TIME_NONE),
232           (cp2 ? cp2->value : 0.0), ts);
233       ret = TRUE;
234       GST_LOG ("values[%3d]=%lf", i, *values);
235     } else {
236       *values = NAN;
237       GST_LOG ("values[%3d]=-", i);
238     }
239     ts += interval;
240     values++;
241   }
242   g_mutex_unlock (&self->lock);
243   return ret;
244 }
245 
246 
247 
248 /*  cubic interpolation */
249 
250 /* The following functions implement a natural cubic spline interpolator.
251  * For details look at http://en.wikipedia.org/wiki/Spline_interpolation
252  *
253  * Instead of using a real matrix with n^2 elements for the linear system
254  * of equations we use three arrays o, p, q to hold the tridiagonal matrix
255  * as following to save memory:
256  *
257  * p[0] q[0]    0    0    0
258  * o[1] p[1] q[1]    0    0
259  *    0 o[2] p[2] q[2]    .
260  *    .    .    .    .    .
261  */
262 
263 static void
_interpolate_cubic_update_cache(GstTimedValueControlSource * self)264 _interpolate_cubic_update_cache (GstTimedValueControlSource * self)
265 {
266   gint i, n = self->nvalues;
267   gdouble *o = g_new0 (gdouble, n);
268   gdouble *p = g_new0 (gdouble, n);
269   gdouble *q = g_new0 (gdouble, n);
270 
271   gdouble *h = g_new0 (gdouble, n);
272   gdouble *b = g_new0 (gdouble, n);
273   gdouble *z = g_new0 (gdouble, n);
274 
275   GSequenceIter *iter;
276   GstControlPoint *cp;
277   GstClockTime x, x_next;
278   gdouble y_prev, y, y_next;
279 
280   /* Fill linear system of equations */
281   iter = g_sequence_get_begin_iter (self->values);
282   cp = g_sequence_get (iter);
283   x = cp->timestamp;
284   y = cp->value;
285 
286   p[0] = 1.0;
287 
288   iter = g_sequence_iter_next (iter);
289   cp = g_sequence_get (iter);
290   x_next = cp->timestamp;
291   y_next = cp->value;
292   h[0] = gst_guint64_to_gdouble (x_next - x);
293 
294   for (i = 1; i < n - 1; i++) {
295     /* Shuffle x and y values */
296     y_prev = y;
297     x = x_next;
298     y = y_next;
299     iter = g_sequence_iter_next (iter);
300     cp = g_sequence_get (iter);
301     x_next = cp->timestamp;
302     y_next = cp->value;
303 
304     h[i] = gst_guint64_to_gdouble (x_next - x);
305     o[i] = h[i - 1];
306     p[i] = 2.0 * (h[i - 1] + h[i]);
307     q[i] = h[i];
308     b[i] = (y_next - y) / h[i] - (y - y_prev) / h[i - 1];
309   }
310   p[n - 1] = 1.0;
311 
312   /* Use Gauss elimination to set everything below the diagonal to zero */
313   for (i = 1; i < n - 1; i++) {
314     gdouble a = o[i] / p[i - 1];
315     p[i] -= a * q[i - 1];
316     b[i] -= a * b[i - 1];
317   }
318 
319   /* Solve everything else from bottom to top */
320   for (i = n - 2; i > 0; i--)
321     z[i] = (b[i] - q[i] * z[i + 1]) / p[i];
322 
323   /* Save cache next in the GstControlPoint */
324 
325   iter = g_sequence_get_begin_iter (self->values);
326   for (i = 0; i < n; i++) {
327     cp = g_sequence_get (iter);
328     cp->cache.cubic.h = h[i];
329     cp->cache.cubic.z = z[i];
330     iter = g_sequence_iter_next (iter);
331   }
332 
333   /* Free our temporary arrays */
334   g_free (o);
335   g_free (p);
336   g_free (q);
337   g_free (h);
338   g_free (b);
339   g_free (z);
340 }
341 
342 static inline gdouble
_interpolate_cubic(GstTimedValueControlSource * self,GstControlPoint * cp1,gdouble value1,GstControlPoint * cp2,gdouble value2,GstClockTime timestamp)343 _interpolate_cubic (GstTimedValueControlSource * self, GstControlPoint * cp1,
344     gdouble value1, GstControlPoint * cp2, gdouble value2,
345     GstClockTime timestamp)
346 {
347   if (!self->valid_cache) {
348     _interpolate_cubic_update_cache (self);
349     self->valid_cache = TRUE;
350   }
351 
352   if (cp2) {
353     gdouble diff1, diff2;
354     gdouble out;
355 
356     diff1 = gst_guint64_to_gdouble (timestamp - cp1->timestamp);
357     diff2 = gst_guint64_to_gdouble (cp2->timestamp - timestamp);
358 
359     out =
360         (cp2->cache.cubic.z * diff1 * diff1 * diff1 +
361         cp1->cache.cubic.z * diff2 * diff2 * diff2) / cp1->cache.cubic.h;
362     out +=
363         (value2 / cp1->cache.cubic.h -
364         cp1->cache.cubic.h * cp2->cache.cubic.z) * diff1;
365     out +=
366         (value1 / cp1->cache.cubic.h -
367         cp1->cache.cubic.h * cp1->cache.cubic.z) * diff2;
368     return out;
369   } else {
370     return value1;
371   }
372 }
373 
374 static gboolean
interpolate_cubic_get(GstTimedValueControlSource * self,GstClockTime timestamp,gdouble * value)375 interpolate_cubic_get (GstTimedValueControlSource * self,
376     GstClockTime timestamp, gdouble * value)
377 {
378   gboolean ret = FALSE;
379   GstControlPoint *cp1, *cp2 = NULL;
380 
381   if (self->nvalues <= 2)
382     return interpolate_linear_get (self, timestamp, value);
383 
384   g_mutex_lock (&self->lock);
385 
386   if (_get_nearest_control_points (self, timestamp, &cp1, &cp2)) {
387     *value = _interpolate_cubic (self, cp1, cp1->value, cp2,
388         (cp2 ? cp2->value : 0.0), timestamp);
389     ret = TRUE;
390   }
391   g_mutex_unlock (&self->lock);
392   return ret;
393 }
394 
395 static gboolean
interpolate_cubic_get_value_array(GstTimedValueControlSource * self,GstClockTime timestamp,GstClockTime interval,guint n_values,gdouble * values)396 interpolate_cubic_get_value_array (GstTimedValueControlSource * self,
397     GstClockTime timestamp, GstClockTime interval, guint n_values,
398     gdouble * values)
399 {
400   gboolean ret = FALSE;
401   guint i;
402   GstClockTime ts = timestamp;
403   GstClockTime next_ts = 0;
404   GstControlPoint *cp1 = NULL, *cp2 = NULL;
405 
406   if (self->nvalues <= 2)
407     return interpolate_linear_get_value_array (self, timestamp, interval,
408         n_values, values);
409 
410   g_mutex_lock (&self->lock);
411 
412   for (i = 0; i < n_values; i++) {
413     GST_LOG ("values[%3d] : ts=%" GST_TIME_FORMAT ", next_ts=%" GST_TIME_FORMAT,
414         i, GST_TIME_ARGS (ts), GST_TIME_ARGS (next_ts));
415     if (ts >= next_ts) {
416       _get_nearest_control_points2 (self, ts, &cp1, &cp2, &next_ts);
417     }
418     if (cp1) {
419       *values = _interpolate_cubic (self, cp1, cp1->value, cp2,
420           (cp2 ? cp2->value : 0.0), ts);
421       ret = TRUE;
422       GST_LOG ("values[%3d]=%lf", i, *values);
423     } else {
424       *values = NAN;
425       GST_LOG ("values[%3d]=-", i);
426     }
427     ts += interval;
428     values++;
429   }
430   g_mutex_unlock (&self->lock);
431   return ret;
432 }
433 
434 
435 /*  monotonic cubic interpolation */
436 
437 /* the following functions implement monotonic cubic spline interpolation.
438  * For details: http://en.wikipedia.org/wiki/Monotone_cubic_interpolation
439  *
440  * In contrast to the previous cubic mode, the values won't overshoot.
441  */
442 
443 static void
_interpolate_cubic_monotonic_update_cache(GstTimedValueControlSource * self)444 _interpolate_cubic_monotonic_update_cache (GstTimedValueControlSource * self)
445 {
446   gint i, n = self->nvalues;
447   gdouble *dxs = g_new0 (gdouble, n);
448   gdouble *dys = g_new0 (gdouble, n);
449   gdouble *ms = g_new0 (gdouble, n);
450   gdouble *c1s = g_new0 (gdouble, n);
451 
452   GSequenceIter *iter;
453   GstControlPoint *cp;
454   GstClockTime x, x_next, dx;
455   gdouble y, y_next, dy;
456 
457   /* Get consecutive differences and slopes */
458   iter = g_sequence_get_begin_iter (self->values);
459   cp = g_sequence_get (iter);
460   x_next = cp->timestamp;
461   y_next = cp->value;
462   for (i = 0; i < n - 1; i++) {
463     x = x_next;
464     y = y_next;
465     iter = g_sequence_iter_next (iter);
466     cp = g_sequence_get (iter);
467     x_next = cp->timestamp;
468     y_next = cp->value;
469 
470     dx = gst_guint64_to_gdouble (x_next - x);
471     dy = y_next - y;
472     dxs[i] = dx;
473     dys[i] = dy;
474     ms[i] = dy / dx;
475   }
476 
477   /* Get degree-1 coefficients */
478   c1s[0] = ms[0];
479   for (i = 1; i < n; i++) {
480     gdouble m = ms[i - 1];
481     gdouble m_next = ms[i];
482 
483     if (m * m_next <= 0) {
484       c1s[i] = 0.0;
485     } else {
486       gdouble dx_next, dx_sum;
487 
488       dx = dxs[i], dx_next = dxs[i + 1], dx_sum = dx + dx_next;
489       c1s[i] = 3.0 * dx_sum / ((dx_sum + dx_next) / m + (dx_sum + dx) / m_next);
490     }
491   }
492   c1s[n - 1] = ms[n - 1];
493 
494   /* Get degree-2 and degree-3 coefficients */
495   iter = g_sequence_get_begin_iter (self->values);
496   for (i = 0; i < n - 1; i++) {
497     gdouble c1, m, inv_dx, common;
498     cp = g_sequence_get (iter);
499 
500     c1 = c1s[i];
501     m = ms[i];
502     inv_dx = 1.0 / dxs[i];
503     common = c1 + c1s[i + 1] - m - m;
504 
505     cp->cache.cubic_monotonic.c1s = c1;
506     cp->cache.cubic_monotonic.c2s = (m - c1 - common) * inv_dx;
507     cp->cache.cubic_monotonic.c3s = common * inv_dx * inv_dx;
508 
509     iter = g_sequence_iter_next (iter);
510   }
511 
512   /* Free our temporary arrays */
513   g_free (dxs);
514   g_free (dys);
515   g_free (ms);
516   g_free (c1s);
517 }
518 
519 static inline gdouble
_interpolate_cubic_monotonic(GstTimedValueControlSource * self,GstControlPoint * cp1,gdouble value1,GstControlPoint * cp2,gdouble value2,GstClockTime timestamp)520 _interpolate_cubic_monotonic (GstTimedValueControlSource * self,
521     GstControlPoint * cp1, gdouble value1, GstControlPoint * cp2,
522     gdouble value2, GstClockTime timestamp)
523 {
524   if (!self->valid_cache) {
525     _interpolate_cubic_monotonic_update_cache (self);
526     self->valid_cache = TRUE;
527   }
528 
529   if (cp2) {
530     gdouble diff = gst_guint64_to_gdouble (timestamp - cp1->timestamp);
531     gdouble diff2 = diff * diff;
532     gdouble out;
533 
534     out = value1 + cp1->cache.cubic_monotonic.c1s * diff;
535     out += cp1->cache.cubic_monotonic.c2s * diff2;
536     out += cp1->cache.cubic_monotonic.c3s * diff * diff2;
537     return out;
538   } else {
539     return value1;
540   }
541 }
542 
543 static gboolean
interpolate_cubic_monotonic_get(GstTimedValueControlSource * self,GstClockTime timestamp,gdouble * value)544 interpolate_cubic_monotonic_get (GstTimedValueControlSource * self,
545     GstClockTime timestamp, gdouble * value)
546 {
547   gboolean ret = FALSE;
548   GstControlPoint *cp1, *cp2 = NULL;
549 
550   if (self->nvalues <= 2)
551     return interpolate_linear_get (self, timestamp, value);
552 
553   g_mutex_lock (&self->lock);
554 
555   if (_get_nearest_control_points (self, timestamp, &cp1, &cp2)) {
556     *value = _interpolate_cubic_monotonic (self, cp1, cp1->value, cp2,
557         (cp2 ? cp2->value : 0.0), timestamp);
558     ret = TRUE;
559   }
560   g_mutex_unlock (&self->lock);
561   return ret;
562 }
563 
564 static gboolean
interpolate_cubic_monotonic_get_value_array(GstTimedValueControlSource * self,GstClockTime timestamp,GstClockTime interval,guint n_values,gdouble * values)565 interpolate_cubic_monotonic_get_value_array (GstTimedValueControlSource * self,
566     GstClockTime timestamp, GstClockTime interval, guint n_values,
567     gdouble * values)
568 {
569   gboolean ret = FALSE;
570   guint i;
571   GstClockTime ts = timestamp;
572   GstClockTime next_ts = 0;
573   GstControlPoint *cp1 = NULL, *cp2 = NULL;
574 
575   if (self->nvalues <= 2)
576     return interpolate_linear_get_value_array (self, timestamp, interval,
577         n_values, values);
578 
579   g_mutex_lock (&self->lock);
580 
581   for (i = 0; i < n_values; i++) {
582     GST_LOG ("values[%3d] : ts=%" GST_TIME_FORMAT ", next_ts=%" GST_TIME_FORMAT,
583         i, GST_TIME_ARGS (ts), GST_TIME_ARGS (next_ts));
584     if (ts >= next_ts) {
585       _get_nearest_control_points2 (self, ts, &cp1, &cp2, &next_ts);
586     }
587     if (cp1) {
588       *values = _interpolate_cubic_monotonic (self, cp1, cp1->value, cp2,
589           (cp2 ? cp2->value : 0.0), ts);
590       ret = TRUE;
591       GST_LOG ("values[%3d]=%lf", i, *values);
592     } else {
593       *values = NAN;
594       GST_LOG ("values[%3d]=-", i);
595     }
596     ts += interval;
597     values++;
598   }
599   g_mutex_unlock (&self->lock);
600   return ret;
601 }
602 
603 
604 static struct
605 {
606   GstControlSourceGetValue get;
607   GstControlSourceGetValueArray get_value_array;
608 } interpolation_modes[] = {
609   {
610   (GstControlSourceGetValue) interpolate_none_get,
611         (GstControlSourceGetValueArray) interpolate_none_get_value_array}, {
612   (GstControlSourceGetValue) interpolate_linear_get,
613         (GstControlSourceGetValueArray) interpolate_linear_get_value_array}, {
614   (GstControlSourceGetValue) interpolate_cubic_get,
615         (GstControlSourceGetValueArray) interpolate_cubic_get_value_array}, {
616     (GstControlSourceGetValue) interpolate_cubic_monotonic_get,
617         (GstControlSourceGetValueArray)
618 interpolate_cubic_monotonic_get_value_array}};
619 
620 static const guint num_interpolation_modes = G_N_ELEMENTS (interpolation_modes);
621 
622 enum
623 {
624   PROP_MODE = 1
625 };
626 
627 struct _GstInterpolationControlSourcePrivate
628 {
629   GstInterpolationMode interpolation_mode;
630 };
631 
632 #define _do_init \
633   GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "interpolation control source", 0, \
634     "timeline value interpolating control source")
635 
636 G_DEFINE_TYPE_WITH_CODE (GstInterpolationControlSource,
637     gst_interpolation_control_source, GST_TYPE_TIMED_VALUE_CONTROL_SOURCE,
638     G_ADD_PRIVATE (GstInterpolationControlSource)
639     _do_init);
640 
641 /**
642  * gst_interpolation_control_source_new:
643  *
644  * This returns a new, unbound #GstInterpolationControlSource.
645  *
646  * Returns: (transfer full): a new, unbound #GstInterpolationControlSource.
647  */
648 GstControlSource *
gst_interpolation_control_source_new(void)649 gst_interpolation_control_source_new (void)
650 {
651   GstControlSource *csource =
652       g_object_new (GST_TYPE_INTERPOLATION_CONTROL_SOURCE, NULL);
653 
654   /* Clear floating flag */
655   gst_object_ref_sink (csource);
656 
657   return csource;
658 }
659 
660 static gboolean
gst_interpolation_control_source_set_interpolation_mode(GstInterpolationControlSource * self,GstInterpolationMode mode)661     gst_interpolation_control_source_set_interpolation_mode
662     (GstInterpolationControlSource * self, GstInterpolationMode mode)
663 {
664   GstControlSource *csource = GST_CONTROL_SOURCE (self);
665 
666   if (mode >= num_interpolation_modes || (int) mode < 0) {
667     GST_WARNING ("interpolation mode %d invalid or not implemented yet", mode);
668     return FALSE;
669   }
670 
671   GST_TIMED_VALUE_CONTROL_SOURCE_LOCK (self);
672   csource->get_value = interpolation_modes[mode].get;
673   csource->get_value_array = interpolation_modes[mode].get_value_array;
674 
675   gst_timed_value_control_invalidate_cache ((GstTimedValueControlSource *)
676       csource);
677   self->priv->interpolation_mode = mode;
678 
679   GST_TIMED_VALUE_CONTROL_SOURCE_UNLOCK (self);
680 
681   return TRUE;
682 }
683 
684 static void
gst_interpolation_control_source_init(GstInterpolationControlSource * self)685 gst_interpolation_control_source_init (GstInterpolationControlSource * self)
686 {
687   self->priv = gst_interpolation_control_source_get_instance_private (self);
688   gst_interpolation_control_source_set_interpolation_mode (self,
689       GST_INTERPOLATION_MODE_NONE);
690 }
691 
692 static void
gst_interpolation_control_source_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)693 gst_interpolation_control_source_set_property (GObject * object, guint prop_id,
694     const GValue * value, GParamSpec * pspec)
695 {
696   GstInterpolationControlSource *self =
697       GST_INTERPOLATION_CONTROL_SOURCE (object);
698 
699   switch (prop_id) {
700     case PROP_MODE:
701       gst_interpolation_control_source_set_interpolation_mode (self,
702           (GstInterpolationMode) g_value_get_enum (value));
703       break;
704     default:
705       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
706       break;
707   }
708 }
709 
710 static void
gst_interpolation_control_source_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)711 gst_interpolation_control_source_get_property (GObject * object, guint prop_id,
712     GValue * value, GParamSpec * pspec)
713 {
714   GstInterpolationControlSource *self =
715       GST_INTERPOLATION_CONTROL_SOURCE (object);
716 
717   switch (prop_id) {
718     case PROP_MODE:
719       g_value_set_enum (value, self->priv->interpolation_mode);
720       break;
721     default:
722       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
723       break;
724   }
725 }
726 
727 static void
gst_interpolation_control_source_class_init(GstInterpolationControlSourceClass * klass)728 gst_interpolation_control_source_class_init (GstInterpolationControlSourceClass
729     * klass)
730 {
731   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
732 
733   gobject_class->set_property = gst_interpolation_control_source_set_property;
734   gobject_class->get_property = gst_interpolation_control_source_get_property;
735 
736   g_object_class_install_property (gobject_class, PROP_MODE,
737       g_param_spec_enum ("mode", "Mode", "Interpolation mode",
738           GST_TYPE_INTERPOLATION_MODE, GST_INTERPOLATION_MODE_NONE,
739           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
740 }
741