+2005-08-03 Stefan Kost <ensonic@users.sf.net>
+
+ * configure.ac:
+ * docs/libs/Makefile.am:
+ * docs/libs/gstreamer-libs-docs.sgml:
+ * docs/libs/gstreamer-libs-sections.txt:
+ * docs/libs/gstreamer-libs.types:
+ * examples/Makefile.am:
+ * examples/controller/.cvsignore:
+ * examples/controller/Makefile.am:
+ * examples/controller/audio-example.c: (main):
+ * libs/gst/Makefile.am:
+ * libs/gst/controller/.cvsignore:
+ * libs/gst/controller/Makefile.am:
+ * libs/gst/controller/gst-controller.c:
+ (on_object_controlled_property_changed), (gst_timed_value_compare),
+ (gst_timed_value_find),
+ (gst_controlled_property_set_interpolation_mode),
+ (gst_controlled_property_new), (gst_controlled_property_free),
+ (gst_controller_find_controlled_property),
+ (gst_controller_new_valist), (gst_controller_new),
+ (gst_controller_remove_properties_valist),
+ (gst_controller_remove_properties), (gst_controller_set),
+ (gst_controller_set_from_list), (gst_controller_unset),
+ (gst_controller_get), (gst_controller_get_all),
+ (gst_controller_sink_values), (gst_controller_get_value_arrays),
+ (gst_controller_get_value_array),
+ (gst_controller_set_interpolation_mode),
+ (_gst_controller_finalize), (_gst_controller_init),
+ (_gst_controller_class_init), (gst_controller_get_type):
+ * libs/gst/controller/gst-controller.h:
+ * libs/gst/controller/gst-helper.c: (g_object_control_properties),
+ (g_object_uncontrol_properties), (g_object_get_controller),
+ (g_object_set_controller), (g_object_sink_values),
+ (g_object_get_value_arrays), (g_object_get_value_array):
+ * libs/gst/controller/gst-interpolation.c:
+ (gst_controlled_property_find_timed_value_node),
+ (interpolate_none_get), (interpolate_trigger_get),
+ (interpolate_trigger_get_value_array):
+ * libs/gst/controller/lib.c: (gst_controller_init):
+ * pkgconfig/Makefile.am:
+ * pkgconfig/gstreamer-control-uninstalled.pc.in:
+ * pkgconfig/gstreamer-control.pc.in:
+ * testsuite/Makefile.am:
+ * testsuite/controller/.cvsignore:
+ * testsuite/controller/Makefile.am:
+ * testsuite/controller/interpolator.c: (main):
+ added controller code
+ removed dparam pc files
+
2005-08-01 Jan Schmidt <thaytan@mad.scientist.com>
* gst/base/gstcollectpads.c: (gst_collectpads_finalize),
(gst_collectpads_stop):
gst/registries/Makefile
libs/Makefile
libs/gst/Makefile
+libs/gst/controller/Makefile
libs/gst/dataprotocol/Makefile
libs/gst/getbits/Makefile
po/Makefile.in
testsuite/bytestream/Makefile
testsuite/caps/Makefile
testsuite/cleanup/Makefile
+testsuite/controller/Makefile
testsuite/debug/Makefile
testsuite/dlopen/Makefile
testsuite/elements/Makefile
testsuite/threads/Makefile
testsuite/trigger/Makefile
examples/Makefile
+examples/controller/Makefile
examples/cutter/Makefile
examples/helloworld/Makefile
examples/launch/Makefile
pkgconfig/gstreamer-uninstalled.pc
pkgconfig/gstreamer-base.pc
pkgconfig/gstreamer-base-uninstalled.pc
-pkgconfig/gstreamer-control.pc
-pkgconfig/gstreamer-control-uninstalled.pc
+pkgconfig/gstreamer-controller.pc
+pkgconfig/gstreamer-controller-uninstalled.pc
pkgconfig/gstreamer-dataprotocol.pc
pkgconfig/gstreamer-dataprotocol-uninstalled.pc
gstreamer.spec,
@taglist:
@Returns:
-<!-- # Unused Parameters # -->
-@list:
<!-- ##### FUNCTION gst_event_parse_tag ##### -->
@arg1:
@:
@:
+@:
+@:
+@:
@:
<!-- ##### ARG GstFakeSrc:data ##### -->
#EXTRA_DIST = gstreamer.types.in gstreamer.hierarchy $(DOC_MODULE)-sections.txt gstreamer-sections.txt $(DOC_MAIN_SGML_FILE)
# Extra options to supply to gtkdoc-mkdb.
-MKDB_OPTIONS=--sgml-mode --ignore-files=trio
+MKDB_OPTIONS= --output-format=xml --sgml-mode --ignore-files=trio
# Extra options to supply to gtkdoc-fixref.
FIXXREF_OPTIONS=--extra-dir=../gst/html
HFILE_GLOB=$(DOC_SOURCE_DIR)/*/*.h
CFILE_GLOB=$(DOC_SOURCE_DIR)/*/*.c
-# this is a wingo addition
-# thomasvs: another nice wingo addition would be an explanation on why
-# this is useful ;)
-
+# Dependencies for the intermediate scanobj tool
#SCANOBJ_DEPS = $(top_builddir)/gst/elements/libgstelements.la \
# $(top_builddir)/gst/schedulers/libgstbasicomegascheduler.la \
# $(top_builddir)/libs/gst/control/libgstcontrol-@GST_MAJORMINOR@.la
+SCANOBJ_DEPS = $(top_builddir)/libs/gst/controller/libgstcontroller-@GST_MAJORMINOR@.la
+
+# Extra options to pass to gtkdoc-scanobj or gtkdoc-scangobj.
+SCANOBJ_OPTIONS=--type-init-func="g_type_init();gst_init(&argc,&argv)"
# Header files to ignore when scanning.
IGNORE_HFILES = \
# CFLAGS and LDFLAGS for compiling scan program. Only needed if your app/lib
# contains GtkObjects/GObjects and you want to document signals and properties.
-GTKDOC_CFLAGS = $(GST_OBJ_CFLAGS) $(POPT_CFLAGS) -I$(top_builddir)
+GTKDOC_CFLAGS = $(GST_OBJ_CFLAGS) $(POPT_CFLAGS) -I$(top_builddir) -I$(top_builddir)/libs
GTKDOC_LIBS = $(GST_OBJ_LIBS) $(POPT_LIBS) $(SCANOBJ_DEPS)
GTKDOC_CC=$(LIBTOOL) --mode=compile $(CC)
<!ENTITY % version-entities SYSTEM "version.entities">
%version-entities;
<!ENTITY GstBytestream SYSTEM "xml/gstbytestream.xml">
+<!ENTITY GstController SYSTEM "xml/gstcontroller.xml">
<!ENTITY GstGetbits SYSTEM "xml/gstgetbits.xml">
<!-- has not yet been written
<!ENTITY GstPutbits SYSTEM "xml/gstputbits.xml">
<!-- has not yet been written
&GstPutbits;
-->
- <!--link linkend="GObject">GObject</link-->
-
+
+ <chapter id="gstreamer-control">
+ <title>gstcontrol</title>
+ &GstController;
+ <!--&GstControllerGObject; -->
+ </chapter>
+
</part>
<part id="gstreamer-libs-hierarchy">
<SUBSECTION Standard>
</SECTION>
+<SECTION>
+<FILE>gstcontroller</FILE>
+<TITLE>GstController</TITLE>
+<INCLUDE>libs/controller/gstcontroller.h</INCLUDE>
+GstController
+gst_controller_init
+gst_controller_new
+gst_controller_new_valist
+gst_controller_remove_properties
+gst_controller_remove_properties_valist
+gst_controller_set
+gst_controller_set_from_list
+gst_controller_unset
+gst_controller_get
+gst_controller_get_all
+gst_controller_sink_values
+gst_controller_get_value_arrays
+gst_controller_get_value_array
+gst_controller_set_interpolation_mode
+GST_PARAM_CONTROLLABLE
+<SUBSECTION Standard>
+GstControllerClass
+GST_CONTROLLER
+GST_IS_CONTROLLER
+GST_CONTROLLER_CLASS
+GST_IS_CONTROLLER_CLASS
+GST_CONTROLLER_GET_CLASS
+GST_TYPE_CONTROLLER
+<SUBSECTION Private>
+gst_controller_get_type
+</SECTION>
#include <gst/gst.h>
+#include <gst/controller/gst-controller.h>
-
+gst_controller_get_type
thread \
plugins \
mixer \
+ controller \
cutter \
pingpong \
manual \
--- /dev/null
+audio-example
+*.bb
+*.bbg
+*.da
--- /dev/null
+noinst_PROGRAMS = audio-example
+
+audio_example_CFLAGS = $(GST_OBJ_CFLAGS) -I$(top_builddir)/libs
+audio_example_LDADD = $(GST_OBJ_LIBS)
+audio_example_LDFLAGS = $(top_builddir)/libs/gst/controller/libgstcontroller-@GST_MAJORMINOR@.la
--- /dev/null
+/*
+ * audio-example.c
+ *
+ * Build a pipeline with testaudiosource->alsasink
+ * and sweep frequency and volume
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/controller/gst-controller.h>
+
+gint
+main (gint argc, gchar ** argv)
+{
+ gint res = 1;
+ GstElement *src, *sink;
+ GstBin *bin;
+ GstController *ctrl;
+ GValue vol = { 0, };
+
+ gst_init (&argc, &argv);
+ gst_controller_init (&argc, &argv);
+
+ // build pipeline
+ bin = GST_BIN (gst_pipeline_new ("pipeline"));
+ /* TODO make this "testaudiosrc", when its ready */
+ src = gst_element_factory_make ("sinesrc", "gen_audio");
+ sink = gst_element_factory_make ("alsasink", "play_audio");
+ gst_bin_add_many (bin, src, sink, NULL);
+
+ // add a controller to the source
+ if (!(ctrl =
+ gst_controller_new (G_OBJECT (src), "frequency", "volume", NULL))) {
+ goto Error;
+ }
+ // set interpolation
+ gst_controller_set_interpolation_mode (ctrl, "volume",
+ GST_INTERPOLATE_LINEAR);
+
+ // set control values
+ g_value_init (&vol, G_TYPE_DOUBLE);
+ g_value_set_double (&vol, 0.0);
+ gst_controller_set (ctrl, "volume", 0 * GST_SECOND, &vol);
+ g_value_set_double (&vol, 1.0);
+ gst_controller_set (ctrl, "volume", 1 * GST_SECOND, &vol);
+
+ // iterate two seconds
+ /*
+ if(gst_element_set_state (bin, GST_STATE_PLAYING))
+ {
+ while (gst_bin_iterate (bin))
+ {
+ }
+ }
+ gst_element_set_state (bin, GST_STATE_NULL);
+ */
+
+ // cleanup
+ g_object_unref (G_OBJECT (ctrl));
+ g_object_unref (G_OBJECT (bin));
+ res = 0;
+Error:
+ return (res);
+}
-SUBDIRS = dataprotocol getbits
+SUBDIRS = controller dataprotocol getbits
--- /dev/null
+*.bb
+*.bbg
+*.da
+*.def
+*.gcno
--- /dev/null
+lib_LTLIBRARIES = libgstcontroller-@GST_MAJORMINOR@.la
+
+libgstcontroller_@GST_MAJORMINOR@_includedir = $(includedir)/gstreamer-@GST_MAJORMINOR@/gst/controller
+libgstcontroller_@GST_MAJORMINOR@_include_HEADERS = \
+ gst-controller.h
+
+libgstcontroller_@GST_MAJORMINOR@_la_SOURCES = \
+ lib.c \
+ gst-controller.c \
+ gst-interpolation.c \
+ gst-helper.c
+
+libgstcontroller_@GST_MAJORMINOR@_la_CFLAGS = $(GST_OBJ_CFLAGS) -I$(top_srcdir)/libs
+libgstcontroller_@GST_MAJORMINOR@_la_LDFLAGS = @GST_LIB_LDFLAGS@
+libgstcontroller_@GST_MAJORMINOR@_la_LIBADD = $(GST_OBJ_LIBS)
--- /dev/null
+/*
+ * gst-controller.c
+ *
+ * New dynamic properties
+ *
+ */
+
+/* What needs to be done in plugins?
+Very little - it is just two steps to make a plugin controllable!
+
+1) Just mark gobject-properties that make sense to be controlled,
+ by GST_PARAM_CONTROLLABLE for a start.
+
+2) When processing data (get, chain, loop function) at the beginning call
+ gst_element_sink_values(element,timestamp).
+ This will made the controller to update all gobject properties that are under
+ control with the current values based on timestamp.
+*/
+
+/* What needs to be done in applications?
+
+1) First put some properties under control, by calling
+ controller=g_object_control_properties(object, "prop1", "prop2",...);
+
+2) Set how the controller will smooth inbetween values.
+ gst_controller_set_interpolation_mode(controller,"prop1",mode);
+
+3) Set key values
+ gst_controller_set(controller,"prop1",0*GST_SECOND,value1);
+ gst_controller_set(controller,"prop1",1*GST_SECOND,value2);
+
+4) Start your pipeline ;-)
+
+5) Live control params from the GUI
+ g_object_set_live_value(object, "prop1", timestamp, value);
+*/
+
+#include "config.h"
+#include "gst-controller.h"
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
+
+static GObjectClass *parent_class = NULL;
+GQuark controller_key;
+
+
+/* imports from gst-interpolation.c */
+
+extern GList
+ *gst_controlled_property_find_timed_value_node (GstControlledProperty *
+ prop, GstClockTime timestamp);
+extern GstInterpolateMethod *interpolation_methods[];
+
+/* callbacks */
+
+void
+on_object_controlled_property_changed (const GObject * object, GParamSpec * arg,
+ gpointer user_data)
+{
+ GstControlledProperty *prop = GST_CONTROLLED_PROPERTY (user_data);
+ GstController *ctrl;
+
+ GST_INFO ("notify for '%s'", prop->name);
+
+ ctrl = g_object_get_qdata (G_OBJECT (object), controller_key);
+ g_return_if_fail (ctrl);
+
+ if (g_mutex_trylock (ctrl->lock)) {
+ if (!G_IS_VALUE (&prop->live_value.value)) {
+ //g_value_unset (&prop->live_value.value);
+ g_value_init (&prop->live_value.value, prop->type);
+ }
+ g_object_get_property (G_OBJECT (object), prop->name,
+ &prop->live_value.value);
+ prop->live_value.timestamp = prop->last_value.timestamp;
+ g_mutex_unlock (ctrl->lock);
+ GST_DEBUG ("-> is live update : ts=%" G_GUINT64_FORMAT,
+ prop->live_value.timestamp);
+ }
+ //else {
+ //GST_DEBUG ("-> is control change");
+ //}
+}
+
+/* helper */
+
+/*
+ * gst_timed_value_compare:
+ * @p1: a pointer to a #GstTimedValue
+ * @p2: a pointer to a #GstTimedValue
+ *
+ * Compare function for g_list operations that operates on two #GstTimedValue
+ * parameters.
+ */
+static gint
+gst_timed_value_compare (gconstpointer p1, gconstpointer p2)
+{
+ GstClockTime ct1 = ((GstTimedValue *) p1)->timestamp;
+ GstClockTime ct2 = ((GstTimedValue *) p2)->timestamp;
+
+ return ((ct1 < ct2) ? -1 : ((ct1 == ct2) ? 0 : 1));
+/* this does not produce an gint :(
+ return ((ct1 - ct2));
+*/
+}
+
+/*
+ * gst_timed_value_find:
+ * @p1: a pointer to a #GstTimedValue
+ * @p2: a pointer to a #GstClockTime
+ *
+ * Compare function for g_list operations that operates on a #GstTimedValue and
+ * a #GstClockTime.
+ */
+static gint
+gst_timed_value_find (gconstpointer p1, gconstpointer p2)
+{
+ GstClockTime ct1 = ((GstTimedValue *) p1)->timestamp;
+ GstClockTime ct2 = *(GstClockTime *) p2;
+
+ return ((ct1 < ct2) ? -1 : ((ct1 == ct2) ? 0 : 1));
+/* this does not produce an gint :(
+ return ((ct1 - ct2));
+*/
+}
+
+/*
+ * gst_controlled_property_set_interpolation_mode:
+ * @self: the controlled property object to change
+ * @mode: the new interpolation mode
+ *
+ * Sets the given Interpolation mode for the controlled property and activates
+ * the respective interpolation hooks.
+ */
+static gboolean
+gst_controlled_property_set_interpolation_mode (GstControlledProperty * self,
+ GstInterpolateMode mode)
+{
+ self->interpolation = mode;
+ if (mode != GST_INTERPOLATE_USER) {
+ switch (self->type) {
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ self->get = interpolation_methods[mode]->get_int;
+ self->get_value_array =
+ interpolation_methods[mode]->get_int_value_array;
+ break;
+ case G_TYPE_LONG:
+ case G_TYPE_ULONG:
+ self->get = interpolation_methods[mode]->get_long;
+ self->get_value_array =
+ interpolation_methods[mode]->get_long_value_array;
+ break;
+ case G_TYPE_FLOAT:
+ self->get = interpolation_methods[mode]->get_float;
+ self->get_value_array =
+ interpolation_methods[mode]->get_float_value_array;
+ break;
+ case G_TYPE_DOUBLE:
+ self->get = interpolation_methods[mode]->get_double;
+ self->get_value_array =
+ interpolation_methods[mode]->get_double_value_array;
+ break;
+ default:
+ self->get = NULL;
+ self->get_value_array = NULL;
+ GST_WARNING ("incomplete implementation for type '%d'", self->type);
+ }
+ } else {
+ /* TODO shouldn't this also get a GstInterpolateMethod *user_method
+ for the case mode==GST_INTERPOLATE_USER
+ */
+ }
+ return (TRUE);
+}
+
+/*
+ * gst_controlled_property_new:
+ * @object: for which object the controlled property should be set up
+ * @name: the name of the property to be controlled
+ *
+ * Private method which initializes the fields of a new controlled property
+ * structure.
+ *
+ * Returns: a freshly allocated structure or %NULL
+ */
+static GstControlledProperty *
+gst_controlled_property_new (GObject * object, const gchar * name)
+{
+ GstControlledProperty *prop = NULL;
+ GParamSpec *pspec;
+
+ GST_INFO ("trying to put property '%s' under control", name);
+
+ // check if the object has a property of that name
+ if ((pspec =
+ g_object_class_find_property (G_OBJECT_GET_CLASS (object), name))) {
+ GST_DEBUG (" psec->flags : 0x%08x", pspec->flags);
+
+ // check if this param is controlable
+ g_return_val_if_fail (!(pspec->
+ flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)), NULL);
+ //g_return_val_if_fail((pspec->flags&GST_PARAM_CONTROLLABLE),NULL);
+ /* TODO do sanity checks
+ we don't control some pspec->value_type:
+ G_TYPE_PARAM_BOXED
+ G_TYPE_PARAM_ENUM
+ G_TYPE_PARAM_FLAGS
+ G_TYPE_PARAM_OBJECT
+ G_TYPE_PARAM_PARAM
+ G_TYPE_PARAM_POINTER
+ G_TYPE_PARAM_STRING
+ */
+
+ if ((prop = g_new0 (GstControlledProperty, 1))) {
+ gchar *signal_name;
+
+ prop->name = pspec->name; // so we don't use the same mem twice
+ prop->object = object;
+ prop->type = G_PARAM_SPEC_VALUE_TYPE (pspec);
+ gst_controlled_property_set_interpolation_mode (prop,
+ GST_INTERPOLATE_NONE);
+ /* prepare our gvalues */
+ g_value_init (&prop->default_value, prop->type);
+ g_value_init (&prop->result_value, prop->type);
+ g_value_init (&prop->last_value.value, prop->type);
+ switch (prop->type) {
+ case G_TYPE_INT:{
+ GParamSpecInt *tpspec = G_PARAM_SPEC_INT (pspec);
+
+ g_value_set_int (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_UINT:{
+ GParamSpecUInt *tpspec = G_PARAM_SPEC_UINT (pspec);
+
+ g_value_set_uint (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_LONG:{
+ GParamSpecLong *tpspec = G_PARAM_SPEC_LONG (pspec);
+
+ g_value_set_long (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_ULONG:{
+ GParamSpecULong *tpspec = G_PARAM_SPEC_ULONG (pspec);
+
+ g_value_set_ulong (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_FLOAT:{
+ GParamSpecFloat *tpspec = G_PARAM_SPEC_FLOAT (pspec);
+
+ g_value_set_float (&prop->default_value, tpspec->default_value);
+ }
+ case G_TYPE_DOUBLE:{
+ GParamSpecDouble *tpspec = G_PARAM_SPEC_DOUBLE (pspec);
+
+ g_value_set_double (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ default:
+ GST_WARNING ("incomplete implementation for paramspec type '%s'",
+ G_PARAM_SPEC_TYPE_NAME (pspec));
+ }
+ /* TODO what about adding a timedval with timestamp=0 and value=default
+ + a bit easier for interpolators, example:
+ * first timestamp is at 5
+ * requested value if for timestamp=3
+ * LINEAR and Co. would need to interpolate from default value
+ to value at timestamp 5
+ */
+ signal_name = g_alloca (8 + 1 + strlen (name));
+ g_sprintf (signal_name, "notify::%s", name);
+ prop->notify_handler_id =
+ g_signal_connect (object, signal_name,
+ G_CALLBACK (on_object_controlled_property_changed), (gpointer) prop);
+ }
+ } else {
+ GST_WARNING ("class '%s' has no property '%s'", G_OBJECT_TYPE_NAME (object),
+ name);
+ }
+
+ return (prop);
+}
+
+/*
+ * gst_controlled_property_free:
+ * @prop: the object to free
+ *
+ * Private method which frees all data allocated by a #GstControlledProperty
+ * instance.
+ */
+static void
+gst_controlled_property_free (GstControlledProperty * prop)
+{
+ GList *node;
+
+ g_signal_handler_disconnect (prop->object, prop->notify_handler_id);
+ for (node = prop->values; node; node = g_list_next (node)) {
+ g_free (node->data);
+ }
+ g_list_free (prop->values);
+ g_free (prop);
+}
+
+/*
+ * gst_controller_find_controlled_property:
+ * @self: the controller object to search for a property in
+ * @name: the gobject property name to look for
+ *
+ * Searches the list of properties under control.
+ *
+ * Returns: a #GstControlledProperty object of %NULL if the property is not
+ * being controlled.
+ */
+static GstControlledProperty *
+gst_controller_find_controlled_property (GstController * self,
+ const gchar * name)
+{
+ GstControlledProperty *prop;
+ GList *node;
+
+ for (node = self->properties; node; node = g_list_next (node)) {
+ prop = node->data;
+ if (!strcmp (prop->name, name)) {
+ return (prop);
+ }
+ }
+ GST_WARNING ("controller does not manage property '%s'", name);
+
+ return (NULL);
+}
+
+/* methods */
+
+/**
+ * gst_controller_new_valist:
+ * @object: the object of which some properties should be controlled
+ * @var_args: %NULL terminated list of property names that should be controlled
+ *
+ * Creates a new GstController for the given object's properties
+ *
+ * Returns: the new controller.
+ */
+GstController *
+gst_controller_new_valist (GObject * object, va_list var_args)
+{
+ GstController *self;
+ GstControlledProperty *prop;
+ gchar *name;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+ GST_INFO ("setting up a new controller");
+
+ /* TODO should this method check if the given object implements GstParent and
+ if so instantiate a GstParentController ?
+
+ BilboEd: This is too specific to be put here, don't we want
+ GstController to be as generic as possible ?
+
+ Ensonic: So we will have gst_parent_controller_new as well and maybe a
+ convinience function that automatically chooses the right one (how to name it)?
+ GstParent will be in core after all.
+ */
+
+ self = g_object_get_qdata (object, controller_key);
+ if (!self) {
+ self = g_object_new (GST_TYPE_CONTROLLER, NULL);
+ self->lock = g_mutex_new ();
+ // store the controller
+ g_object_set_qdata (object, controller_key, self);
+ }
+ // create GstControlledProperty for each property
+ while ((name = va_arg (var_args, gchar *))) {
+ // create GstControlledProperty and add to self->propeties List
+ if ((prop = gst_controlled_property_new (object, name)))
+ self->properties = g_list_prepend (self->properties, prop);
+ }
+ va_end (var_args);
+
+ return (self);
+}
+
+/**
+ * gst_controller_new:
+ * @object: the object of which some properties should be controlled
+ * @...: %NULL terminated list of property names that should be controlled
+ *
+ * Creates a new GstController for the given object's properties
+ *
+ * Returns: the new controller.
+ */
+GstController *
+gst_controller_new (GObject * object, ...)
+{
+ GstController *self;
+ va_list var_args;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+ va_start (var_args, object);
+ self = gst_controller_new_valist (object, var_args);
+ va_end (var_args);
+
+ return (self);
+}
+
+/**
+ * gst_controller_remove_properties:
+ * @self: the controller object from which some properties should be removed
+ * @var_args: %NULL terminated list of property names that should be removed
+ *
+ * Removes the given object properties from the controller
+ *
+ * Returns: %FALSE if one of the given property isn't handled by the controller, %TRUE otherwise
+ */
+gboolean
+gst_controller_remove_properties_valist (GstController * self, va_list var_args)
+{
+ gboolean res = TRUE;
+ GstControlledProperty *prop;
+ gchar *name;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+
+ while ((name = va_arg (var_args, gchar *))) {
+ // find the property in the properties list of the controller, remove and free it
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, name))) {
+ self->properties = g_list_remove (self->properties, prop);
+ gst_controlled_property_free (prop);
+ } else {
+ res = FALSE;
+ }
+ g_mutex_unlock (self->lock);
+ }
+
+ return (res);
+}
+
+/**
+ * gst_controller_remove_properties:
+ * @self: the controller object from which some properties should be removed
+ * @...: %NULL terminated list of property names that should be removed
+ *
+ * Removes the given object properties from the controller
+ *
+ * Returns: %FALSE if one of the given property isn't handled by the controller, %TRUE otherwise
+ */
+gboolean
+gst_controller_remove_properties (GstController * self, ...)
+{
+ gboolean res;
+ va_list var_args;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+
+ va_start (var_args, self);
+ res = gst_controller_remove_properties_valist (self, var_args);
+ va_end (var_args);
+
+ return (res);
+}
+
+/**
+ * gst_controller_set:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to set
+ * @timestamp: the time the control-change is schedules 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 (ex : properties not handled by controller), TRUE otherwise
+ */
+gboolean
+gst_controller_set (GstController * self, gchar * property_name,
+ GstClockTime timestamp, GValue * value)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+ g_return_val_if_fail (G_IS_VALUE (value), FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ if (G_VALUE_TYPE (value) == prop->type) {
+ GstTimedValue *tv;
+ GList *node;
+
+ // check if a timed_value for the timestamp already exists
+ if ((node = g_list_find_custom (prop->values, ×tamp,
+ gst_timed_value_find))) {
+ tv = node->data;
+ memcpy (&tv->value, value, sizeof (GValue));
+ } else {
+ // create a new GstTimedValue
+ tv = g_new (GstTimedValue, 1);
+ tv->timestamp = timestamp;
+ memcpy (&tv->value, value, sizeof (GValue));
+ // and sort it into the prop->values list
+ prop->values =
+ g_list_insert_sorted (prop->values, tv, gst_timed_value_compare);
+ }
+ res = TRUE;
+ } else {
+ GST_WARNING ("incompatible value type for property '%s'", prop->name);
+ }
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_set_from_list:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to set
+ * @timedvalues: a list with #GstTimedValue items
+ *
+ * Sets multiple timed values at once.
+ *
+ * Returns: %FALSE if the values couldn't be set (ex : properties not handled by controller), %TRUE otherwise
+ */
+
+gboolean
+gst_controller_set_from_list (GstController * self, gchar * property_name,
+ GSList * timedvalues)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+ GSList *node;
+ GstTimedValue *tv;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ for (node = timedvalues; node; node = g_slist_next (node)) {
+ tv = node->data;
+ if (G_VALUE_TYPE (&tv->value) == prop->type) {
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (tv->timestamp), FALSE);
+ /* TODO copy the timed value or just link in? */
+ prop->values =
+ g_list_insert_sorted (prop->values, tv, gst_timed_value_compare);
+ res = TRUE;
+ } else {
+ GST_WARNING ("incompatible value type for property '%s'", prop->name);
+ }
+ }
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_unset:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to unset
+ * @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 values couldn't be unset (ex : properties not handled by controller), %TRUE otherwise
+ */
+gboolean
+gst_controller_unset (GstController * self, gchar * property_name,
+ GstClockTime timestamp)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ prop->values = g_list_remove (prop->values, prop);
+ res = TRUE;
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_get:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to get
+ * timestamp: the time the control-change should be read from
+ *
+ * Gets the value for the given controller-handled property at the requested
+ * time.
+ *
+ * Returns: the GValue of the property at the given time, or %NULL if the property isn't handled by the controller
+ */
+
+GValue *
+gst_controller_get (GstController * self, gchar * property_name,
+ GstClockTime timestamp)
+{
+ GstControlledProperty *prop;
+ GValue *val = NULL;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), NULL);
+ g_return_val_if_fail (property_name, NULL);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), NULL);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ //get current value via interpolator
+ val = prop->get (prop, timestamp);
+ }
+ g_mutex_unlock (self->lock);
+
+ return (val);
+}
+
+/**
+ * gst_controller_get_all:
+ * @self: the controller to get the list from
+ * @property_name: the name of the property to get the list for
+ *
+ * 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
+ */
+const GList *
+gst_controller_get_all (GstController * self, gchar * property_name)
+{
+ GList *res = NULL;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), NULL);
+ g_return_val_if_fail (property_name, NULL);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ res = g_list_copy (prop->values);
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_sink_values:
+ * @self: the controller that handles the values
+ * @timestamp: the time that should be processed
+ *
+ * Sets the properties of the element, according to the controller that (maybe)
+ * handles them and for the given timestamp.
+ *
+ * Returns: %TRUE if the controller values could be applied to the object
+ * properties, %FALSE otherwise
+ */
+gboolean
+gst_controller_sink_values (GstController * self, GstClockTime timestamp)
+{
+ GstControlledProperty *prop;
+ GList *node;
+ GValue *value;
+ gboolean live;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ GST_INFO ("sink_values");
+
+ g_mutex_lock (self->lock);
+ // go over the controlled properties of the controller
+ for (node = self->properties; node; node = g_list_next (node)) {
+ prop = node->data;
+ GST_DEBUG (" property '%s' at ts=%" G_GUINT64_FORMAT, prop->name,
+ timestamp);
+
+ live = FALSE;
+ if (G_IS_VALUE (&prop->live_value.value)) {
+ GList *lnode =
+ gst_controlled_property_find_timed_value_node (prop, timestamp);
+ if (!lnode) {
+ GST_DEBUG (" no control changes in the queue");
+ live = TRUE;
+ } else {
+ GstTimedValue *tv = lnode->data;
+
+ //GST_DEBUG ("live.ts %"G_UINT64_FORMAT" <-> now %"G_UINT64_FORMAT, prop->live_value.timestamp, tv->timestamp);
+ if (prop->live_value.timestamp < tv->timestamp) {
+ g_value_unset (&prop->live_value.value);
+ GST_DEBUG (" live value resetted");
+ } else if (prop->live_value.timestamp < timestamp) {
+ live = TRUE;
+ }
+ }
+ }
+ if (!live) {
+ //get current value via interpolator
+ value = prop->get (prop, timestamp);
+ prop->last_value.timestamp = timestamp;
+ g_value_copy (value, &prop->last_value.value);
+ g_object_set_property (prop->object, prop->name, value);
+ }
+ }
+ g_mutex_unlock (self->lock);
+ /* TODO what can here go wrong, to return FALSE ?
+ BilboEd : Nothing I guess, as long as all the checks are made when creating the controller,
+ adding/removing controlled properties, etc...
+ */
+
+ return (TRUE);
+}
+
+/**
+ * gst_controller_get_value_arrays:
+ * @self: the controller that handles the values
+ * @timestamp: the time that should be processed
+ * @value_arrays: list to return the control-values in
+ *
+ * Function to be able to get an array of values for one or more given element
+ * properties.
+ *
+ * If the GstValueArray->values array in list nodes is NULL, it will be created
+ * by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+gst_controller_get_value_arrays (GstController * self,
+ GstClockTime timestamp, GSList * value_arrays)
+{
+ gboolean res = TRUE;
+ GSList *node;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+ g_return_val_if_fail (value_arrays, FALSE);
+
+ for (node = value_arrays; (res && node); node = g_slist_next (node)) {
+ res = gst_controller_get_value_array (self, timestamp, node->data);
+ }
+
+ return (res);
+}
+
+/**
+ * gst_controller_get_value_array:
+ * @self: the controller that handles the values
+ * @timestamp: the time that should be processed
+ * @value_array: array to put control-values in
+ *
+ * Function to be able to get an array of values for one element properties
+ *
+ * If the GstValueArray->values array is NULL, it will be created by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+gst_controller_get_value_array (GstController * self, GstClockTime timestamp,
+ GstValueArray * value_array)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+ g_return_val_if_fail (value_array, FALSE);
+ g_return_val_if_fail (value_array->property_name, FALSE);
+
+ /* TODO and if GstValueArray->values is not NULL, the caller is resposible that
+ is is big enough for nbsamples values, right?
+ */
+
+ g_mutex_lock (self->lock);
+ if ((prop =
+ gst_controller_find_controlled_property (self,
+ value_array->property_name))) {
+ if (!value_array->values) {
+ /* TODO from where to get the base size
+ value_array->values=g_new(sizeof(???),nbsamples);
+ */
+ }
+ //get current value_array via interpolator
+ res = prop->get_value_array (prop, timestamp, value_array);
+ }
+ g_mutex_unlock (self->lock);
+ return (res);
+}
+
+/**
+ * gst_controller_set_interpolation_mode:
+ * @controller:
+ * @property_name:
+ * @mode: interpolation mode
+ *
+ * Sets the given interpolation mode on the given property.
+ *
+ * Returns: %TRUE if the property is handled by the controller, %FALSE otherwise
+ */
+gboolean
+gst_controller_set_interpolation_mode (GstController * self,
+ gchar * property_name, GstInterpolateMode mode)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ /* TODO shouldn't this also get a GstInterpolateMethod *user_method
+ for the case mode==GST_INTERPOLATE_USER
+ */
+ gst_controlled_property_set_interpolation_mode (prop, mode);
+ res = TRUE;
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/*
+void
+gst_controller_set_live_value(GstController * self, gchar *property_name,
+ GstClockTime timestamp, GValue *value)
+{
+ GstControlledProperty *prop;
+
+ g_return_if_fail (GST_IS_CONTROLLER (self));
+ g_return_if_fail (property_name);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ g_value_unset (&prop->live_value.value);
+ g_value_init (&prop->live_value.value, prop->type);
+ g_value_copy (value, &prop->live_value.value);
+ prop->live_value.timestamp = timestamp;
+ }
+ g_mutex_unlock (self->lock);
+}
+
+*/
+
+/* gobject handling */
+
+static void
+_gst_controller_finalize (GObject * object)
+{
+ GstController *self = GST_CONTROLLER (object);
+ GList *node;
+
+ // free list of properties
+ if (self->properties) {
+ for (node = self->properties; node; node = g_list_next (node)) {
+ gst_controlled_property_free (node->data);
+ }
+ g_list_free (self->properties);
+ self->properties = NULL;
+ }
+ g_mutex_free (self->lock);
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+_gst_controller_init (GTypeInstance * instance, gpointer g_class)
+{
+ GstController *self = GST_CONTROLLER (instance);
+
+ self->lock = g_mutex_new ();
+
+}
+
+static void
+_gst_controller_class_init (GstControllerClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_ref (G_TYPE_OBJECT);
+
+ gobject_class->finalize = _gst_controller_finalize;
+
+ controller_key = g_quark_from_string ("gst::controller");
+
+ // register properties
+ // register signals
+ // set defaults for overridable methods
+ /* TODO which of theses do we need ?
+ BilboEd : none :)
+ */
+}
+
+GType
+gst_controller_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof (GstControllerClass),
+ NULL, // base_init
+ NULL, // base_finalize
+ (GClassInitFunc) _gst_controller_class_init, // class_init
+ NULL, // class_finalize
+ NULL, // class_data
+ sizeof (GstController),
+ 0, // n_preallocs
+ (GInstanceInitFunc) _gst_controller_init, // instance_init
+ NULL // value_table
+ };
+ type = g_type_register_static (G_TYPE_OBJECT, "GstController", &info, 0);
+ }
+ return type;
+}
--- /dev/null
+/*
+ * gst-controller.h
+ *
+ * New dynamic properties
+ */
+
+#ifndef __GST_CONTROLLER_H__
+#define __GST_CONTROLLER_H__
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GST_PARAM_CONTROLLABLE:
+ *
+ * Use this flag on GstElement properties you wish to be (eventually) handled
+ * by a GstController.
+ * TODO: needs to go to gstelemnt.h (to avoid clashes on G_PARAM_USER_SHIFT)
+ */
+#define GST_PARAM_CONTROLLABLE (1 << (G_PARAM_USER_SHIFT + 1))
+
+
+/**
+ * GstTimedValue:
+ *
+ * a structure for value+time
+ */
+typedef struct _GstTimedValue
+{
+ GstClockTime timestamp; // timestamp of the value change
+ GValue value; // the new value
+ /* TODO what about storing the difference to next timestamp and value here
+ + make calculations slightly easier and faster
+ - determining the GType for the value_dif is not simple
+ e.g. if value is G_TYPE_UCHAR value_diff needs to be G_TYPE_INT
+ */
+} GstTimedValue;
+
+
+/**
+ * GstValueArray:
+ *
+ * Structure to receive multiple values at once
+ */
+typedef struct _GstValueArray
+{
+ gchar *property_name;
+ gint nbsamples; // Number of samples requested
+ GstClockTime sample_interval; // Interval between each sample
+ gpointer *values; // pointer to the array (so it can be filled if NULL)
+} GstValueArray;
+
+
+/**
+ * GstInterpolateMode:
+ * @GST_INTERPOLATE_NONE: steps-like interpolation, default
+ * @GST_INTERPOLATE_TRIGGER: returns the default value of the property,
+ * except for times with specific values
+ * @GST_INTERPOLATE_LINEAR: linear interpolation
+ * @GST_INTERPOLATE_QUADRATIC: square interpolation
+ * @GST_INTERPOLATE_CUBIC: cubic interpolation
+ * @GST_INTERPOLATE_USER: user-provided interpolation
+ *
+ * The various interpolation modes available.
+ */
+typedef enum
+{
+ GST_INTERPOLATE_NONE,
+ GST_INTERPOLATE_TRIGGER,
+ GST_INTERPOLATE_LINEAR,
+ GST_INTERPOLATE_QUADRATIC,
+ GST_INTERPOLATE_CUBIC,
+ GST_INTERPOLATE_USER
+} GstInterpolateMode;
+
+
+struct _GstControlledProperty;
+
+typedef GValue *(*InterpolateGet) (struct _GstControlledProperty * prop,
+ GstClockTime timestamp);
+typedef gboolean (*InterpolateGetValueArray) (struct _GstControlledProperty * prop,
+ GstClockTime timestamp, GstValueArray * value_array);
+
+/**
+ * GstInterpolateMethod:
+ *
+ * Function pointer structure to do user-defined interpolation methods
+ */
+typedef struct _GstInterpolateMethod
+{
+ InterpolateGet get_int;
+ InterpolateGetValueArray get_int_value_array;
+ InterpolateGet get_long;
+ InterpolateGetValueArray get_long_value_array;
+ InterpolateGet get_float;
+ InterpolateGetValueArray get_float_value_array;
+ InterpolateGet get_double;
+ InterpolateGetValueArray get_double_value_array;
+} GstInterpolateMethod;
+
+/**
+ * GstControlledProperty:
+ */
+typedef struct _GstControlledProperty
+{
+ gchar *name; // name of the property
+ GObject *object; // the object we control
+ GType type; // type of the handled property
+ GValue default_value; // default value for the handled property
+ GValue result_value; // result value location for the interpolation method
+ GstTimedValue last_value; // the last value a _sink call wrote
+ GstTimedValue live_value; // temporary value override for live input
+ gulong notify_handler_id; // id of the notify::<name> signal handler
+ GstInterpolateMode interpolation; // Interpolation mode
+ /* TODO instead of *method, have pointers to get() and get_value_array() here
+ gst_controller_set_interpolation_mode() will pick the right ones for the
+ properties value type
+ GstInterpolateMethod *method; // User-implemented handler (if interpolation == GST_INTERPOLATE_USER)
+ */
+ InterpolateGet get;
+ InterpolateGetValueArray get_value_array;
+
+ GList *values; // List of GstTimedValue
+ /* TODO keep the last search result to be able to continue
+ GList *last_value; // last search result, can be used for incremental searches
+ */
+} GstControlledProperty;
+
+#define GST_CONTROLLED_PROPERTY(obj) ((GstControlledProperty *)(obj))
+
+/* type macros */
+
+#define GST_TYPE_CONTROLLER (gst_controller_get_type ())
+#define GST_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CONTROLLER, GstController))
+#define GST_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CONTROLLER, GstControllerClass))
+#define GST_IS_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CONTROLLER))
+#define GST_IS_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CONTROLLERE))
+#define GST_CONTROLLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CONTROLLER, GstControllerClass))
+
+typedef struct _GstController GstController;
+typedef struct _GstControllerClass GstControllerClass;
+
+//typedef struct _GstControllerPrivate GstControllerPrivate;
+
+/**
+ * GstController:
+ *
+ * The instance structure of GstController
+ */
+
+struct _GstController
+{
+ GObject parent;
+
+ GList *properties; // List of GstControlledProperty
+ GMutex *lock; // Secure property access, elements will access from threads
+};
+
+struct _GstControllerClass
+{
+ GObjectClass parent_class;
+};
+
+GType gst_controller_get_type (void);
+
+/* GstController functions */
+
+GstController *gst_controller_new_valist (GObject * object, va_list var_args);
+GstController *gst_controller_new (GObject * object, ...);
+
+gboolean gst_controller_remove_properties_valist (GstController * self,
+ va_list var_args);
+gboolean gst_controller_remove_properties (GstController * self, ...);
+
+gboolean gst_controller_set (GstController * self, gchar * property_name,
+ GstClockTime timestamp, GValue * value);
+gboolean gst_controller_set_from_list (GstController * self,
+ gchar * property_name, GSList * timedvalues);
+
+gboolean gst_controller_unset (GstController * self, gchar * property_name,
+ GstClockTime timestamp);
+
+
+GValue *gst_controller_get (GstController * self, gchar * property_name,
+ GstClockTime timestamp);
+const GList *gst_controller_get_all (GstController * self,
+ gchar * property_name);
+
+
+gboolean gst_controller_sink_values (GstController * self,
+ GstClockTime timestamp);
+
+gboolean gst_controller_get_value_arrays (GstController * self,
+ GstClockTime timestamp, GSList * value_arrays);
+gboolean gst_controller_get_value_array (GstController * self,
+ GstClockTime timestamp, GstValueArray * value_array);
+
+gboolean gst_controller_set_interpolation_mode (GstController * self,
+ gchar * property_name, GstInterpolateMode mode);
+
+
+/* GObject convenience functions */
+
+GstController *g_object_control_properties (GObject * object, ...);
+gboolean g_object_uncontrol_properties (GObject * object, ...);
+
+GstController *g_object_get_controller (GObject * object);
+gboolean g_object_set_controller (GObject * object, GstController * controller);
+
+gboolean g_object_sink_values (GObject * object, GstClockTime timestamp);
+
+gboolean g_object_get_value_arrays (GObject * object,
+ GstClockTime timestamp, GSList * value_arrays);
+gboolean g_object_get_value_array (GObject * object,
+ GstClockTime timestamp, GstValueArray * value_array);
+
+/* lib init/done */
+
+gboolean gst_controller_init (int * argc, char ***argv);
+
+G_END_DECLS
+#endif /* __GST_CONTROLLER_H__ */
--- /dev/null
+/*
+ * gst-helper.c
+ *
+ * GObject convinience methods for using dynamic properties
+ *
+ */
+
+#include "config.h"
+#include "gst-controller.h"
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
+
+extern GQuark controller_key;
+
+/**
+ * g_object_control_properties:
+ * @object: the object of which some properties should be controlled
+ * @var_args: %NULL terminated list of property names that should be controlled
+ *
+ * Convenience function for GObject
+ *
+ * Creates a GstController that allows you to dynamically control one, or more, GObject properties.
+ * If the given GObject already has a GstController, it adds the given properties to the existing
+ * controller and returns that controller.
+ *
+ * Returns: The GstController with which the user can control the given properties dynamically or NULL if
+ * one or more of the given properties aren't available, or cannot be controlled, for the given element.
+ */
+GstController *
+g_object_control_properties (GObject * object, ...)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+ va_list var_args;
+
+ va_start (var_args, object);
+ ctrl = gst_controller_new_valist (object, var_args);
+ va_end (var_args);
+ return (ctrl);
+}
+
+/**
+ * g_object_uncontrol_properties:
+ * @object: the object of which some properties should not be controlled anymore
+ * @var_args: %NULL terminated list of property names that should be controlled
+ *
+ * Convenience function for GObject
+ *
+ * Removes the given element's properties from it's controller
+ *
+ * Returns: %FALSE if one of the given property names isn't handled by the
+ * controller, %TRUE otherwise
+ */
+gboolean
+g_object_uncontrol_properties (GObject * object, ...)
+{
+ gboolean res = FALSE;
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+ if ((ctrl = g_object_get_qdata (object, controller_key))) {
+ va_list var_args;
+
+ va_start (var_args, object);
+ res = gst_controller_remove_properties_valist (ctrl, var_args);
+ va_end (var_args);
+ }
+ return (res);
+}
+
+/**
+ * g_object_get_controller:
+ * @object: the object that has controlled properties
+ *
+ * Returns: the controller handling some of the given element's properties,
+ * %NULL if no controller
+ */
+GstController *
+g_object_get_controller (GObject * object)
+{
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+ return (g_object_get_qdata (object, controller_key));
+}
+
+/**
+ * g_object_set_controller:
+ * @object: the object that should get the controller
+ * @controller: the controller object to plug in
+ *
+ * Sets the controller on the given GObject
+ *
+ * Returns: %FALSE if the GObject already has an controller, %TRUE otherwise
+ */
+gboolean
+g_object_set_controller (GObject * object, GstController * controller)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (controller, FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (!ctrl, FALSE);
+ g_object_set_qdata (object, controller_key, controller);
+ return (TRUE);
+}
+
+/**
+ * g_object_sink_values:
+ * @object: the object that has controlled properties
+ * @timestamp: the time that should be processed
+ *
+ * Convenience function for GObject
+ *
+ * Returns: same thing as gst_controller_sink_values()
+ */
+gboolean
+g_object_sink_values (GObject * object, GstClockTime timestamp)
+{
+ GstController *ctrl = NULL;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (ctrl, FALSE);
+ return gst_controller_sink_values (ctrl, timestamp);
+}
+
+/**
+ * g_object_get_value_arrays:
+ * @object: the object that has controlled properties
+ * @timestamp: the time that should be processed
+ * @value_arrays: list to return the control-values in
+ *
+ * Function to be able to get an array of values for one or more given element
+ * properties.
+ *
+ * If the GstValueArray->values array in list nodes is NULL, it will be created
+ * by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * The g_object_* functions are just convenience functions for GObject
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+g_object_get_value_arrays (GObject * object, GstClockTime timestamp,
+ GSList * value_arrays)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (ctrl, FALSE);
+ return gst_controller_get_value_arrays (ctrl, timestamp, value_arrays);
+}
+
+/**
+ * g_object_get_value_array:
+ * @self: the object that has controlled properties
+ * @timestamp: the time that should be processed
+ * @value_array: array to put control-values in
+ *
+ * Function to be able to get an array of values for one element properties
+ *
+ * If the GstValueArray->values array is NULL, it will be created by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * The g_object_* functions are just convenience functions for GObject
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+g_object_get_value_array (GObject * object, GstClockTime timestamp,
+ GstValueArray * value_array)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (ctrl, FALSE);
+
+ return gst_controller_get_value_array (ctrl, timestamp, value_array);
+}
--- /dev/null
+/*
+ * gst-interpolation.h
+ *
+ * Interpolation methodws for dynamic properties
+ */
+
+#include "config.h"
+#include "gst-controller.h"
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
+
+// common helper
+
+/*
+ * gst_controlled_property_find_timed_value_node:
+ * @prop: the controlled property to search in
+ * @timestamp: the search key
+ *
+ * Find last value before given timestamp in timed value list.
+ *
+ * Returns: the found #GList node or %NULL
+ */
+GList *
+gst_controlled_property_find_timed_value_node (GstControlledProperty * prop,
+ GstClockTime timestamp)
+{
+ //GList *prev_node = NULL;
+ GList *prev_node = g_list_last (prop->values);
+ GList *node;
+ GstTimedValue *tv;
+
+ /*
+ if((prop->last_value) &&
+ (timestamp>((GstTimedValue *)(prop->last_value->data))->timestamp)) {
+ node=prop->last_value;
+ }
+ else {
+ node=prop->values;
+ }
+ */
+
+ /* iterate over timed value list */
+ for (node = prop->values; node; node = g_list_next (node)) {
+ tv = node->data;
+ /* this timestamp is newer that the one we look for */
+ if (timestamp < tv->timestamp) {
+ /* get previous one again */
+ prev_node = g_list_previous (node);
+ break;
+ }
+ }
+ /*
+ if(node) {
+ prop->last_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_timed_value_node (prop, timestamp))) {
+ GstTimedValue *tv = node->data;
+
+ return (&tv->value);
+ }
+ return (&prop->default_value);
+}
+
+#define DEFINE_NONE_GET(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 (prop,ts)); \
+ ts+=value_array->sample_interval; \
+ values++; \
+ } \
+ return (TRUE); \
+}
+
+DEFINE_NONE_GET (int)
+ DEFINE_NONE_GET (long)
+DEFINE_NONE_GET (float)
+DEFINE_NONE_GET (double)
+
+ static GstInterpolateMethod interpolate_none = {
+ interpolate_none_get,
+ interpolate_none_get_int_value_array,
+ interpolate_none_get,
+ interpolate_none_get_long_value_array,
+ interpolate_none_get,
+ interpolate_none_get_float_value_array,
+ interpolate_none_get,
+ interpolate_none_get_double_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;
+ GstTimedValue *tv;
+
+ /* check if there is a value at the registered timestamp */
+ for (node = prop->values; node; node = g_list_next (node)) {
+ tv = node->data;
+ if (timestamp == tv->timestamp) {
+ return (&tv->value);
+ }
+ }
+
+ return (&prop->default_value);
+}
+
+static gboolean
+interpolate_trigger_get_value_array (GstControlledProperty * prop,
+ GstClockTime timestamp, GstValueArray * value_array)
+{
+ return (FALSE);
+}
+
+static GstInterpolateMethod interpolate_trigger = {
+ interpolate_trigger_get,
+ interpolate_trigger_get_value_array,
+ interpolate_trigger_get,
+ NULL,
+ interpolate_trigger_get,
+ NULL,
+ interpolate_trigger_get,
+ NULL
+};
+
+// linear interpolation
+// smoothes inbetween values
+
+#define DEFINE_LINEAR_GET(type) \
+static g##type \
+_interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
+{ \
+ GList *node; \
+ \
+ if ((node = gst_controlled_property_find_timed_value_node (prop, timestamp))) { \
+ GstTimedValue *tv1, *tv2; \
+ \
+ tv1 = node->data; \
+ if ((node = g_list_next (node))) { \
+ gdouble timediff,valuediff; \
+ g##type value1,value2; \
+ \
+ tv2 = node->data; \
+ \
+ timediff = (gdouble)(tv2->timestamp - tv1->timestamp); \
+ value1 = g_value_get_##type (&tv1->value); \
+ value2 = g_value_get_##type (&tv2->value); \
+ valuediff = (gdouble)(value2-value1); \
+ \
+ return((g##type)(value1+valuediff*((timestamp-tv1->timestamp)/timediff))); \
+ } \
+ else { \
+ return (g_value_get_##type (&tv1->value)); \
+ } \
+ } \
+ return (g_value_get_##type (&prop->default_value)); \
+} \
+\
+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; \
+ gint *values=(gint *)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 (long)
+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_long,
+ interpolate_linear_get_long_value_array,
+ interpolate_linear_get_float,
+ interpolate_linear_get_float_value_array,
+ interpolate_linear_get_double,
+ interpolate_linear_get_double_value_array,
+ };
+
+// square interpolation
+
+// cubic interpolation
+
+// register all interpolation methods
+GstInterpolateMethod *interpolation_methods[] = {
+ &interpolate_none,
+ &interpolate_trigger,
+ &interpolate_linear,
+ NULL,
+ NULL
+};
--- /dev/null
+/*
+ * gst-controller.c
+ *
+ * New dynamic properties
+ *
+ */
+
+/* What needs to be done in plugins?
+Very little - it is just two steps to make a plugin controllable!
+
+1) Just mark gobject-properties that make sense to be controlled,
+ by GST_PARAM_CONTROLLABLE for a start.
+
+2) When processing data (get, chain, loop function) at the beginning call
+ gst_element_sink_values(element,timestamp).
+ This will made the controller to update all gobject properties that are under
+ control with the current values based on timestamp.
+*/
+
+/* What needs to be done in applications?
+
+1) First put some properties under control, by calling
+ controller=g_object_control_properties(object, "prop1", "prop2",...);
+
+2) Set how the controller will smooth inbetween values.
+ gst_controller_set_interpolation_mode(controller,"prop1",mode);
+
+3) Set key values
+ gst_controller_set(controller,"prop1",0*GST_SECOND,value1);
+ gst_controller_set(controller,"prop1",1*GST_SECOND,value2);
+
+4) Start your pipeline ;-)
+
+5) Live control params from the GUI
+ g_object_set_live_value(object, "prop1", timestamp, value);
+*/
+
+#include "config.h"
+#include "gst-controller.h"
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
+
+static GObjectClass *parent_class = NULL;
+GQuark controller_key;
+
+
+/* imports from gst-interpolation.c */
+
+extern GList
+ *gst_controlled_property_find_timed_value_node (GstControlledProperty *
+ prop, GstClockTime timestamp);
+extern GstInterpolateMethod *interpolation_methods[];
+
+/* callbacks */
+
+void
+on_object_controlled_property_changed (const GObject * object, GParamSpec * arg,
+ gpointer user_data)
+{
+ GstControlledProperty *prop = GST_CONTROLLED_PROPERTY (user_data);
+ GstController *ctrl;
+
+ GST_INFO ("notify for '%s'", prop->name);
+
+ ctrl = g_object_get_qdata (G_OBJECT (object), controller_key);
+ g_return_if_fail (ctrl);
+
+ if (g_mutex_trylock (ctrl->lock)) {
+ if (!G_IS_VALUE (&prop->live_value.value)) {
+ //g_value_unset (&prop->live_value.value);
+ g_value_init (&prop->live_value.value, prop->type);
+ }
+ g_object_get_property (G_OBJECT (object), prop->name,
+ &prop->live_value.value);
+ prop->live_value.timestamp = prop->last_value.timestamp;
+ g_mutex_unlock (ctrl->lock);
+ GST_DEBUG ("-> is live update : ts=%" G_GUINT64_FORMAT,
+ prop->live_value.timestamp);
+ }
+ //else {
+ //GST_DEBUG ("-> is control change");
+ //}
+}
+
+/* helper */
+
+/*
+ * gst_timed_value_compare:
+ * @p1: a pointer to a #GstTimedValue
+ * @p2: a pointer to a #GstTimedValue
+ *
+ * Compare function for g_list operations that operates on two #GstTimedValue
+ * parameters.
+ */
+static gint
+gst_timed_value_compare (gconstpointer p1, gconstpointer p2)
+{
+ GstClockTime ct1 = ((GstTimedValue *) p1)->timestamp;
+ GstClockTime ct2 = ((GstTimedValue *) p2)->timestamp;
+
+ return ((ct1 < ct2) ? -1 : ((ct1 == ct2) ? 0 : 1));
+/* this does not produce an gint :(
+ return ((ct1 - ct2));
+*/
+}
+
+/*
+ * gst_timed_value_find:
+ * @p1: a pointer to a #GstTimedValue
+ * @p2: a pointer to a #GstClockTime
+ *
+ * Compare function for g_list operations that operates on a #GstTimedValue and
+ * a #GstClockTime.
+ */
+static gint
+gst_timed_value_find (gconstpointer p1, gconstpointer p2)
+{
+ GstClockTime ct1 = ((GstTimedValue *) p1)->timestamp;
+ GstClockTime ct2 = *(GstClockTime *) p2;
+
+ return ((ct1 < ct2) ? -1 : ((ct1 == ct2) ? 0 : 1));
+/* this does not produce an gint :(
+ return ((ct1 - ct2));
+*/
+}
+
+/*
+ * gst_controlled_property_set_interpolation_mode:
+ * @self: the controlled property object to change
+ * @mode: the new interpolation mode
+ *
+ * Sets the given Interpolation mode for the controlled property and activates
+ * the respective interpolation hooks.
+ */
+static gboolean
+gst_controlled_property_set_interpolation_mode (GstControlledProperty * self,
+ GstInterpolateMode mode)
+{
+ self->interpolation = mode;
+ if (mode != GST_INTERPOLATE_USER) {
+ switch (self->type) {
+ case G_TYPE_INT:
+ case G_TYPE_UINT:
+ self->get = interpolation_methods[mode]->get_int;
+ self->get_value_array =
+ interpolation_methods[mode]->get_int_value_array;
+ break;
+ case G_TYPE_LONG:
+ case G_TYPE_ULONG:
+ self->get = interpolation_methods[mode]->get_long;
+ self->get_value_array =
+ interpolation_methods[mode]->get_long_value_array;
+ break;
+ case G_TYPE_FLOAT:
+ self->get = interpolation_methods[mode]->get_float;
+ self->get_value_array =
+ interpolation_methods[mode]->get_float_value_array;
+ break;
+ case G_TYPE_DOUBLE:
+ self->get = interpolation_methods[mode]->get_double;
+ self->get_value_array =
+ interpolation_methods[mode]->get_double_value_array;
+ break;
+ default:
+ self->get = NULL;
+ self->get_value_array = NULL;
+ GST_WARNING ("incomplete implementation for type '%d'", self->type);
+ }
+ } else {
+ /* TODO shouldn't this also get a GstInterpolateMethod *user_method
+ for the case mode==GST_INTERPOLATE_USER
+ */
+ }
+ return (TRUE);
+}
+
+/*
+ * gst_controlled_property_new:
+ * @object: for which object the controlled property should be set up
+ * @name: the name of the property to be controlled
+ *
+ * Private method which initializes the fields of a new controlled property
+ * structure.
+ *
+ * Returns: a freshly allocated structure or %NULL
+ */
+static GstControlledProperty *
+gst_controlled_property_new (GObject * object, const gchar * name)
+{
+ GstControlledProperty *prop = NULL;
+ GParamSpec *pspec;
+
+ GST_INFO ("trying to put property '%s' under control", name);
+
+ // check if the object has a property of that name
+ if ((pspec =
+ g_object_class_find_property (G_OBJECT_GET_CLASS (object), name))) {
+ GST_DEBUG (" psec->flags : 0x%08x", pspec->flags);
+
+ // check if this param is controlable
+ g_return_val_if_fail (!(pspec->
+ flags & (G_PARAM_CONSTRUCT | G_PARAM_CONSTRUCT_ONLY)), NULL);
+ //g_return_val_if_fail((pspec->flags&GST_PARAM_CONTROLLABLE),NULL);
+ /* TODO do sanity checks
+ we don't control some pspec->value_type:
+ G_TYPE_PARAM_BOXED
+ G_TYPE_PARAM_ENUM
+ G_TYPE_PARAM_FLAGS
+ G_TYPE_PARAM_OBJECT
+ G_TYPE_PARAM_PARAM
+ G_TYPE_PARAM_POINTER
+ G_TYPE_PARAM_STRING
+ */
+
+ if ((prop = g_new0 (GstControlledProperty, 1))) {
+ gchar *signal_name;
+
+ prop->name = pspec->name; // so we don't use the same mem twice
+ prop->object = object;
+ prop->type = G_PARAM_SPEC_VALUE_TYPE (pspec);
+ gst_controlled_property_set_interpolation_mode (prop,
+ GST_INTERPOLATE_NONE);
+ /* prepare our gvalues */
+ g_value_init (&prop->default_value, prop->type);
+ g_value_init (&prop->result_value, prop->type);
+ g_value_init (&prop->last_value.value, prop->type);
+ switch (prop->type) {
+ case G_TYPE_INT:{
+ GParamSpecInt *tpspec = G_PARAM_SPEC_INT (pspec);
+
+ g_value_set_int (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_UINT:{
+ GParamSpecUInt *tpspec = G_PARAM_SPEC_UINT (pspec);
+
+ g_value_set_uint (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_LONG:{
+ GParamSpecLong *tpspec = G_PARAM_SPEC_LONG (pspec);
+
+ g_value_set_long (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_ULONG:{
+ GParamSpecULong *tpspec = G_PARAM_SPEC_ULONG (pspec);
+
+ g_value_set_ulong (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ case G_TYPE_FLOAT:{
+ GParamSpecFloat *tpspec = G_PARAM_SPEC_FLOAT (pspec);
+
+ g_value_set_float (&prop->default_value, tpspec->default_value);
+ }
+ case G_TYPE_DOUBLE:{
+ GParamSpecDouble *tpspec = G_PARAM_SPEC_DOUBLE (pspec);
+
+ g_value_set_double (&prop->default_value, tpspec->default_value);
+ }
+ break;
+ default:
+ GST_WARNING ("incomplete implementation for paramspec type '%s'",
+ G_PARAM_SPEC_TYPE_NAME (pspec));
+ }
+ /* TODO what about adding a timedval with timestamp=0 and value=default
+ + a bit easier for interpolators, example:
+ * first timestamp is at 5
+ * requested value if for timestamp=3
+ * LINEAR and Co. would need to interpolate from default value
+ to value at timestamp 5
+ */
+ signal_name = g_alloca (8 + 1 + strlen (name));
+ g_sprintf (signal_name, "notify::%s", name);
+ prop->notify_handler_id =
+ g_signal_connect (object, signal_name,
+ G_CALLBACK (on_object_controlled_property_changed), (gpointer) prop);
+ }
+ } else {
+ GST_WARNING ("class '%s' has no property '%s'", G_OBJECT_TYPE_NAME (object),
+ name);
+ }
+
+ return (prop);
+}
+
+/*
+ * gst_controlled_property_free:
+ * @prop: the object to free
+ *
+ * Private method which frees all data allocated by a #GstControlledProperty
+ * instance.
+ */
+static void
+gst_controlled_property_free (GstControlledProperty * prop)
+{
+ GList *node;
+
+ g_signal_handler_disconnect (prop->object, prop->notify_handler_id);
+ for (node = prop->values; node; node = g_list_next (node)) {
+ g_free (node->data);
+ }
+ g_list_free (prop->values);
+ g_free (prop);
+}
+
+/*
+ * gst_controller_find_controlled_property:
+ * @self: the controller object to search for a property in
+ * @name: the gobject property name to look for
+ *
+ * Searches the list of properties under control.
+ *
+ * Returns: a #GstControlledProperty object of %NULL if the property is not
+ * being controlled.
+ */
+static GstControlledProperty *
+gst_controller_find_controlled_property (GstController * self,
+ const gchar * name)
+{
+ GstControlledProperty *prop;
+ GList *node;
+
+ for (node = self->properties; node; node = g_list_next (node)) {
+ prop = node->data;
+ if (!strcmp (prop->name, name)) {
+ return (prop);
+ }
+ }
+ GST_WARNING ("controller does not manage property '%s'", name);
+
+ return (NULL);
+}
+
+/* methods */
+
+/**
+ * gst_controller_new_valist:
+ * @object: the object of which some properties should be controlled
+ * @var_args: %NULL terminated list of property names that should be controlled
+ *
+ * Creates a new GstController for the given object's properties
+ *
+ * Returns: the new controller.
+ */
+GstController *
+gst_controller_new_valist (GObject * object, va_list var_args)
+{
+ GstController *self;
+ GstControlledProperty *prop;
+ gchar *name;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+ GST_INFO ("setting up a new controller");
+
+ /* TODO should this method check if the given object implements GstParent and
+ if so instantiate a GstParentController ?
+
+ BilboEd: This is too specific to be put here, don't we want
+ GstController to be as generic as possible ?
+
+ Ensonic: So we will have gst_parent_controller_new as well and maybe a
+ convinience function that automatically chooses the right one (how to name it)?
+ GstParent will be in core after all.
+ */
+
+ self = g_object_get_qdata (object, controller_key);
+ if (!self) {
+ self = g_object_new (GST_TYPE_CONTROLLER, NULL);
+ self->lock = g_mutex_new ();
+ // store the controller
+ g_object_set_qdata (object, controller_key, self);
+ }
+ // create GstControlledProperty for each property
+ while ((name = va_arg (var_args, gchar *))) {
+ // create GstControlledProperty and add to self->propeties List
+ if ((prop = gst_controlled_property_new (object, name)))
+ self->properties = g_list_prepend (self->properties, prop);
+ }
+ va_end (var_args);
+
+ return (self);
+}
+
+/**
+ * gst_controller_new:
+ * @object: the object of which some properties should be controlled
+ * @...: %NULL terminated list of property names that should be controlled
+ *
+ * Creates a new GstController for the given object's properties
+ *
+ * Returns: the new controller.
+ */
+GstController *
+gst_controller_new (GObject * object, ...)
+{
+ GstController *self;
+ va_list var_args;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), NULL);
+
+ va_start (var_args, object);
+ self = gst_controller_new_valist (object, var_args);
+ va_end (var_args);
+
+ return (self);
+}
+
+/**
+ * gst_controller_remove_properties:
+ * @self: the controller object from which some properties should be removed
+ * @var_args: %NULL terminated list of property names that should be removed
+ *
+ * Removes the given object properties from the controller
+ *
+ * Returns: %FALSE if one of the given property isn't handled by the controller, %TRUE otherwise
+ */
+gboolean
+gst_controller_remove_properties_valist (GstController * self, va_list var_args)
+{
+ gboolean res = TRUE;
+ GstControlledProperty *prop;
+ gchar *name;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+
+ while ((name = va_arg (var_args, gchar *))) {
+ // find the property in the properties list of the controller, remove and free it
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, name))) {
+ self->properties = g_list_remove (self->properties, prop);
+ gst_controlled_property_free (prop);
+ } else {
+ res = FALSE;
+ }
+ g_mutex_unlock (self->lock);
+ }
+
+ return (res);
+}
+
+/**
+ * gst_controller_remove_properties:
+ * @self: the controller object from which some properties should be removed
+ * @...: %NULL terminated list of property names that should be removed
+ *
+ * Removes the given object properties from the controller
+ *
+ * Returns: %FALSE if one of the given property isn't handled by the controller, %TRUE otherwise
+ */
+gboolean
+gst_controller_remove_properties (GstController * self, ...)
+{
+ gboolean res;
+ va_list var_args;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+
+ va_start (var_args, self);
+ res = gst_controller_remove_properties_valist (self, var_args);
+ va_end (var_args);
+
+ return (res);
+}
+
+/**
+ * gst_controller_set:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to set
+ * @timestamp: the time the control-change is schedules 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 (ex : properties not handled by controller), TRUE otherwise
+ */
+gboolean
+gst_controller_set (GstController * self, gchar * property_name,
+ GstClockTime timestamp, GValue * value)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+ g_return_val_if_fail (G_IS_VALUE (value), FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ if (G_VALUE_TYPE (value) == prop->type) {
+ GstTimedValue *tv;
+ GList *node;
+
+ // check if a timed_value for the timestamp already exists
+ if ((node = g_list_find_custom (prop->values, ×tamp,
+ gst_timed_value_find))) {
+ tv = node->data;
+ memcpy (&tv->value, value, sizeof (GValue));
+ } else {
+ // create a new GstTimedValue
+ tv = g_new (GstTimedValue, 1);
+ tv->timestamp = timestamp;
+ memcpy (&tv->value, value, sizeof (GValue));
+ // and sort it into the prop->values list
+ prop->values =
+ g_list_insert_sorted (prop->values, tv, gst_timed_value_compare);
+ }
+ res = TRUE;
+ } else {
+ GST_WARNING ("incompatible value type for property '%s'", prop->name);
+ }
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_set_from_list:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to set
+ * @timedvalues: a list with #GstTimedValue items
+ *
+ * Sets multiple timed values at once.
+ *
+ * Returns: %FALSE if the values couldn't be set (ex : properties not handled by controller), %TRUE otherwise
+ */
+
+gboolean
+gst_controller_set_from_list (GstController * self, gchar * property_name,
+ GSList * timedvalues)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+ GSList *node;
+ GstTimedValue *tv;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ for (node = timedvalues; node; node = g_slist_next (node)) {
+ tv = node->data;
+ if (G_VALUE_TYPE (&tv->value) == prop->type) {
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (tv->timestamp), FALSE);
+ /* TODO copy the timed value or just link in? */
+ prop->values =
+ g_list_insert_sorted (prop->values, tv, gst_timed_value_compare);
+ res = TRUE;
+ } else {
+ GST_WARNING ("incompatible value type for property '%s'", prop->name);
+ }
+ }
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_unset:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to unset
+ * @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 values couldn't be unset (ex : properties not handled by controller), %TRUE otherwise
+ */
+gboolean
+gst_controller_unset (GstController * self, gchar * property_name,
+ GstClockTime timestamp)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ prop->values = g_list_remove (prop->values, prop);
+ res = TRUE;
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_get:
+ * @self: the controller object which handles the properties
+ * @property_name: the name of the property to get
+ * timestamp: the time the control-change should be read from
+ *
+ * Gets the value for the given controller-handled property at the requested
+ * time.
+ *
+ * Returns: the GValue of the property at the given time, or %NULL if the property isn't handled by the controller
+ */
+
+GValue *
+gst_controller_get (GstController * self, gchar * property_name,
+ GstClockTime timestamp)
+{
+ GstControlledProperty *prop;
+ GValue *val = NULL;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), NULL);
+ g_return_val_if_fail (property_name, NULL);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), NULL);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ //get current value via interpolator
+ val = prop->get (prop, timestamp);
+ }
+ g_mutex_unlock (self->lock);
+
+ return (val);
+}
+
+/**
+ * gst_controller_get_all:
+ * @self: the controller to get the list from
+ * @property_name: the name of the property to get the list for
+ *
+ * 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
+ */
+const GList *
+gst_controller_get_all (GstController * self, gchar * property_name)
+{
+ GList *res = NULL;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), NULL);
+ g_return_val_if_fail (property_name, NULL);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ res = g_list_copy (prop->values);
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/**
+ * gst_controller_sink_values:
+ * @self: the controller that handles the values
+ * @timestamp: the time that should be processed
+ *
+ * Sets the properties of the element, according to the controller that (maybe)
+ * handles them and for the given timestamp.
+ *
+ * Returns: %TRUE if the controller values could be applied to the object
+ * properties, %FALSE otherwise
+ */
+gboolean
+gst_controller_sink_values (GstController * self, GstClockTime timestamp)
+{
+ GstControlledProperty *prop;
+ GList *node;
+ GValue *value;
+ gboolean live;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ GST_INFO ("sink_values");
+
+ g_mutex_lock (self->lock);
+ // go over the controlled properties of the controller
+ for (node = self->properties; node; node = g_list_next (node)) {
+ prop = node->data;
+ GST_DEBUG (" property '%s' at ts=%" G_GUINT64_FORMAT, prop->name,
+ timestamp);
+
+ live = FALSE;
+ if (G_IS_VALUE (&prop->live_value.value)) {
+ GList *lnode =
+ gst_controlled_property_find_timed_value_node (prop, timestamp);
+ if (!lnode) {
+ GST_DEBUG (" no control changes in the queue");
+ live = TRUE;
+ } else {
+ GstTimedValue *tv = lnode->data;
+
+ //GST_DEBUG ("live.ts %"G_UINT64_FORMAT" <-> now %"G_UINT64_FORMAT, prop->live_value.timestamp, tv->timestamp);
+ if (prop->live_value.timestamp < tv->timestamp) {
+ g_value_unset (&prop->live_value.value);
+ GST_DEBUG (" live value resetted");
+ } else if (prop->live_value.timestamp < timestamp) {
+ live = TRUE;
+ }
+ }
+ }
+ if (!live) {
+ //get current value via interpolator
+ value = prop->get (prop, timestamp);
+ prop->last_value.timestamp = timestamp;
+ g_value_copy (value, &prop->last_value.value);
+ g_object_set_property (prop->object, prop->name, value);
+ }
+ }
+ g_mutex_unlock (self->lock);
+ /* TODO what can here go wrong, to return FALSE ?
+ BilboEd : Nothing I guess, as long as all the checks are made when creating the controller,
+ adding/removing controlled properties, etc...
+ */
+
+ return (TRUE);
+}
+
+/**
+ * gst_controller_get_value_arrays:
+ * @self: the controller that handles the values
+ * @timestamp: the time that should be processed
+ * @value_arrays: list to return the control-values in
+ *
+ * Function to be able to get an array of values for one or more given element
+ * properties.
+ *
+ * If the GstValueArray->values array in list nodes is NULL, it will be created
+ * by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+gst_controller_get_value_arrays (GstController * self,
+ GstClockTime timestamp, GSList * value_arrays)
+{
+ gboolean res = TRUE;
+ GSList *node;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+ g_return_val_if_fail (value_arrays, FALSE);
+
+ for (node = value_arrays; (res && node); node = g_slist_next (node)) {
+ res = gst_controller_get_value_array (self, timestamp, node->data);
+ }
+
+ return (res);
+}
+
+/**
+ * gst_controller_get_value_array:
+ * @self: the controller that handles the values
+ * @timestamp: the time that should be processed
+ * @value_array: array to put control-values in
+ *
+ * Function to be able to get an array of values for one element properties
+ *
+ * If the GstValueArray->values array is NULL, it will be created by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+gst_controller_get_value_array (GstController * self, GstClockTime timestamp,
+ GstValueArray * value_array)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+ g_return_val_if_fail (value_array, FALSE);
+ g_return_val_if_fail (value_array->property_name, FALSE);
+
+ /* TODO and if GstValueArray->values is not NULL, the caller is resposible that
+ is is big enough for nbsamples values, right?
+ */
+
+ g_mutex_lock (self->lock);
+ if ((prop =
+ gst_controller_find_controlled_property (self,
+ value_array->property_name))) {
+ if (!value_array->values) {
+ /* TODO from where to get the base size
+ value_array->values=g_new(sizeof(???),nbsamples);
+ */
+ }
+ //get current value_array via interpolator
+ res = prop->get_value_array (prop, timestamp, value_array);
+ }
+ g_mutex_unlock (self->lock);
+ return (res);
+}
+
+/**
+ * gst_controller_set_interpolation_mode:
+ * @controller:
+ * @property_name:
+ * @mode: interpolation mode
+ *
+ * Sets the given interpolation mode on the given property.
+ *
+ * Returns: %TRUE if the property is handled by the controller, %FALSE otherwise
+ */
+gboolean
+gst_controller_set_interpolation_mode (GstController * self,
+ gchar * property_name, GstInterpolateMode mode)
+{
+ gboolean res = FALSE;
+ GstControlledProperty *prop;
+
+ g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE);
+ g_return_val_if_fail (property_name, FALSE);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ /* TODO shouldn't this also get a GstInterpolateMethod *user_method
+ for the case mode==GST_INTERPOLATE_USER
+ */
+ gst_controlled_property_set_interpolation_mode (prop, mode);
+ res = TRUE;
+ }
+ g_mutex_unlock (self->lock);
+
+ return (res);
+}
+
+/*
+void
+gst_controller_set_live_value(GstController * self, gchar *property_name,
+ GstClockTime timestamp, GValue *value)
+{
+ GstControlledProperty *prop;
+
+ g_return_if_fail (GST_IS_CONTROLLER (self));
+ g_return_if_fail (property_name);
+
+ g_mutex_lock (self->lock);
+ if ((prop = gst_controller_find_controlled_property (self, property_name))) {
+ g_value_unset (&prop->live_value.value);
+ g_value_init (&prop->live_value.value, prop->type);
+ g_value_copy (value, &prop->live_value.value);
+ prop->live_value.timestamp = timestamp;
+ }
+ g_mutex_unlock (self->lock);
+}
+
+*/
+
+/* gobject handling */
+
+static void
+_gst_controller_finalize (GObject * object)
+{
+ GstController *self = GST_CONTROLLER (object);
+ GList *node;
+
+ // free list of properties
+ if (self->properties) {
+ for (node = self->properties; node; node = g_list_next (node)) {
+ gst_controlled_property_free (node->data);
+ }
+ g_list_free (self->properties);
+ self->properties = NULL;
+ }
+ g_mutex_free (self->lock);
+
+ if (G_OBJECT_CLASS (parent_class)->finalize)
+ (G_OBJECT_CLASS (parent_class)->finalize) (object);
+}
+
+static void
+_gst_controller_init (GTypeInstance * instance, gpointer g_class)
+{
+ GstController *self = GST_CONTROLLER (instance);
+
+ self->lock = g_mutex_new ();
+
+}
+
+static void
+_gst_controller_class_init (GstControllerClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ parent_class = g_type_class_ref (G_TYPE_OBJECT);
+
+ gobject_class->finalize = _gst_controller_finalize;
+
+ controller_key = g_quark_from_string ("gst::controller");
+
+ // register properties
+ // register signals
+ // set defaults for overridable methods
+ /* TODO which of theses do we need ?
+ BilboEd : none :)
+ */
+}
+
+GType
+gst_controller_get_type (void)
+{
+ static GType type = 0;
+
+ if (type == 0) {
+ static const GTypeInfo info = {
+ sizeof (GstControllerClass),
+ NULL, // base_init
+ NULL, // base_finalize
+ (GClassInitFunc) _gst_controller_class_init, // class_init
+ NULL, // class_finalize
+ NULL, // class_data
+ sizeof (GstController),
+ 0, // n_preallocs
+ (GInstanceInitFunc) _gst_controller_init, // instance_init
+ NULL // value_table
+ };
+ type = g_type_register_static (G_TYPE_OBJECT, "GstController", &info, 0);
+ }
+ return type;
+}
--- /dev/null
+/*
+ * gst-controller.h
+ *
+ * New dynamic properties
+ */
+
+#ifndef __GST_CONTROLLER_H__
+#define __GST_CONTROLLER_H__
+
+#include <string.h>
+
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gprintf.h>
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+/**
+ * GST_PARAM_CONTROLLABLE:
+ *
+ * Use this flag on GstElement properties you wish to be (eventually) handled
+ * by a GstController.
+ * TODO: needs to go to gstelemnt.h (to avoid clashes on G_PARAM_USER_SHIFT)
+ */
+#define GST_PARAM_CONTROLLABLE (1 << (G_PARAM_USER_SHIFT + 1))
+
+
+/**
+ * GstTimedValue:
+ *
+ * a structure for value+time
+ */
+typedef struct _GstTimedValue
+{
+ GstClockTime timestamp; // timestamp of the value change
+ GValue value; // the new value
+ /* TODO what about storing the difference to next timestamp and value here
+ + make calculations slightly easier and faster
+ - determining the GType for the value_dif is not simple
+ e.g. if value is G_TYPE_UCHAR value_diff needs to be G_TYPE_INT
+ */
+} GstTimedValue;
+
+
+/**
+ * GstValueArray:
+ *
+ * Structure to receive multiple values at once
+ */
+typedef struct _GstValueArray
+{
+ gchar *property_name;
+ gint nbsamples; // Number of samples requested
+ GstClockTime sample_interval; // Interval between each sample
+ gpointer *values; // pointer to the array (so it can be filled if NULL)
+} GstValueArray;
+
+
+/**
+ * GstInterpolateMode:
+ * @GST_INTERPOLATE_NONE: steps-like interpolation, default
+ * @GST_INTERPOLATE_TRIGGER: returns the default value of the property,
+ * except for times with specific values
+ * @GST_INTERPOLATE_LINEAR: linear interpolation
+ * @GST_INTERPOLATE_QUADRATIC: square interpolation
+ * @GST_INTERPOLATE_CUBIC: cubic interpolation
+ * @GST_INTERPOLATE_USER: user-provided interpolation
+ *
+ * The various interpolation modes available.
+ */
+typedef enum
+{
+ GST_INTERPOLATE_NONE,
+ GST_INTERPOLATE_TRIGGER,
+ GST_INTERPOLATE_LINEAR,
+ GST_INTERPOLATE_QUADRATIC,
+ GST_INTERPOLATE_CUBIC,
+ GST_INTERPOLATE_USER
+} GstInterpolateMode;
+
+
+struct _GstControlledProperty;
+
+typedef GValue *(*InterpolateGet) (struct _GstControlledProperty * prop,
+ GstClockTime timestamp);
+typedef gboolean (*InterpolateGetValueArray) (struct _GstControlledProperty * prop,
+ GstClockTime timestamp, GstValueArray * value_array);
+
+/**
+ * GstInterpolateMethod:
+ *
+ * Function pointer structure to do user-defined interpolation methods
+ */
+typedef struct _GstInterpolateMethod
+{
+ InterpolateGet get_int;
+ InterpolateGetValueArray get_int_value_array;
+ InterpolateGet get_long;
+ InterpolateGetValueArray get_long_value_array;
+ InterpolateGet get_float;
+ InterpolateGetValueArray get_float_value_array;
+ InterpolateGet get_double;
+ InterpolateGetValueArray get_double_value_array;
+} GstInterpolateMethod;
+
+/**
+ * GstControlledProperty:
+ */
+typedef struct _GstControlledProperty
+{
+ gchar *name; // name of the property
+ GObject *object; // the object we control
+ GType type; // type of the handled property
+ GValue default_value; // default value for the handled property
+ GValue result_value; // result value location for the interpolation method
+ GstTimedValue last_value; // the last value a _sink call wrote
+ GstTimedValue live_value; // temporary value override for live input
+ gulong notify_handler_id; // id of the notify::<name> signal handler
+ GstInterpolateMode interpolation; // Interpolation mode
+ /* TODO instead of *method, have pointers to get() and get_value_array() here
+ gst_controller_set_interpolation_mode() will pick the right ones for the
+ properties value type
+ GstInterpolateMethod *method; // User-implemented handler (if interpolation == GST_INTERPOLATE_USER)
+ */
+ InterpolateGet get;
+ InterpolateGetValueArray get_value_array;
+
+ GList *values; // List of GstTimedValue
+ /* TODO keep the last search result to be able to continue
+ GList *last_value; // last search result, can be used for incremental searches
+ */
+} GstControlledProperty;
+
+#define GST_CONTROLLED_PROPERTY(obj) ((GstControlledProperty *)(obj))
+
+/* type macros */
+
+#define GST_TYPE_CONTROLLER (gst_controller_get_type ())
+#define GST_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CONTROLLER, GstController))
+#define GST_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CONTROLLER, GstControllerClass))
+#define GST_IS_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CONTROLLER))
+#define GST_IS_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CONTROLLERE))
+#define GST_CONTROLLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CONTROLLER, GstControllerClass))
+
+typedef struct _GstController GstController;
+typedef struct _GstControllerClass GstControllerClass;
+
+//typedef struct _GstControllerPrivate GstControllerPrivate;
+
+/**
+ * GstController:
+ *
+ * The instance structure of GstController
+ */
+
+struct _GstController
+{
+ GObject parent;
+
+ GList *properties; // List of GstControlledProperty
+ GMutex *lock; // Secure property access, elements will access from threads
+};
+
+struct _GstControllerClass
+{
+ GObjectClass parent_class;
+};
+
+GType gst_controller_get_type (void);
+
+/* GstController functions */
+
+GstController *gst_controller_new_valist (GObject * object, va_list var_args);
+GstController *gst_controller_new (GObject * object, ...);
+
+gboolean gst_controller_remove_properties_valist (GstController * self,
+ va_list var_args);
+gboolean gst_controller_remove_properties (GstController * self, ...);
+
+gboolean gst_controller_set (GstController * self, gchar * property_name,
+ GstClockTime timestamp, GValue * value);
+gboolean gst_controller_set_from_list (GstController * self,
+ gchar * property_name, GSList * timedvalues);
+
+gboolean gst_controller_unset (GstController * self, gchar * property_name,
+ GstClockTime timestamp);
+
+
+GValue *gst_controller_get (GstController * self, gchar * property_name,
+ GstClockTime timestamp);
+const GList *gst_controller_get_all (GstController * self,
+ gchar * property_name);
+
+
+gboolean gst_controller_sink_values (GstController * self,
+ GstClockTime timestamp);
+
+gboolean gst_controller_get_value_arrays (GstController * self,
+ GstClockTime timestamp, GSList * value_arrays);
+gboolean gst_controller_get_value_array (GstController * self,
+ GstClockTime timestamp, GstValueArray * value_array);
+
+gboolean gst_controller_set_interpolation_mode (GstController * self,
+ gchar * property_name, GstInterpolateMode mode);
+
+
+/* GObject convenience functions */
+
+GstController *g_object_control_properties (GObject * object, ...);
+gboolean g_object_uncontrol_properties (GObject * object, ...);
+
+GstController *g_object_get_controller (GObject * object);
+gboolean g_object_set_controller (GObject * object, GstController * controller);
+
+gboolean g_object_sink_values (GObject * object, GstClockTime timestamp);
+
+gboolean g_object_get_value_arrays (GObject * object,
+ GstClockTime timestamp, GSList * value_arrays);
+gboolean g_object_get_value_array (GObject * object,
+ GstClockTime timestamp, GstValueArray * value_array);
+
+/* lib init/done */
+
+gboolean gst_controller_init (int * argc, char ***argv);
+
+G_END_DECLS
+#endif /* __GST_CONTROLLER_H__ */
--- /dev/null
+/*
+ * gst-helper.c
+ *
+ * GObject convinience methods for using dynamic properties
+ *
+ */
+
+#include "config.h"
+#include "gst-controller.h"
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
+
+extern GQuark controller_key;
+
+/**
+ * g_object_control_properties:
+ * @object: the object of which some properties should be controlled
+ * @var_args: %NULL terminated list of property names that should be controlled
+ *
+ * Convenience function for GObject
+ *
+ * Creates a GstController that allows you to dynamically control one, or more, GObject properties.
+ * If the given GObject already has a GstController, it adds the given properties to the existing
+ * controller and returns that controller.
+ *
+ * Returns: The GstController with which the user can control the given properties dynamically or NULL if
+ * one or more of the given properties aren't available, or cannot be controlled, for the given element.
+ */
+GstController *
+g_object_control_properties (GObject * object, ...)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+ va_list var_args;
+
+ va_start (var_args, object);
+ ctrl = gst_controller_new_valist (object, var_args);
+ va_end (var_args);
+ return (ctrl);
+}
+
+/**
+ * g_object_uncontrol_properties:
+ * @object: the object of which some properties should not be controlled anymore
+ * @var_args: %NULL terminated list of property names that should be controlled
+ *
+ * Convenience function for GObject
+ *
+ * Removes the given element's properties from it's controller
+ *
+ * Returns: %FALSE if one of the given property names isn't handled by the
+ * controller, %TRUE otherwise
+ */
+gboolean
+g_object_uncontrol_properties (GObject * object, ...)
+{
+ gboolean res = FALSE;
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+ if ((ctrl = g_object_get_qdata (object, controller_key))) {
+ va_list var_args;
+
+ va_start (var_args, object);
+ res = gst_controller_remove_properties_valist (ctrl, var_args);
+ va_end (var_args);
+ }
+ return (res);
+}
+
+/**
+ * g_object_get_controller:
+ * @object: the object that has controlled properties
+ *
+ * Returns: the controller handling some of the given element's properties,
+ * %NULL if no controller
+ */
+GstController *
+g_object_get_controller (GObject * object)
+{
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+
+ return (g_object_get_qdata (object, controller_key));
+}
+
+/**
+ * g_object_set_controller:
+ * @object: the object that should get the controller
+ * @controller: the controller object to plug in
+ *
+ * Sets the controller on the given GObject
+ *
+ * Returns: %FALSE if the GObject already has an controller, %TRUE otherwise
+ */
+gboolean
+g_object_set_controller (GObject * object, GstController * controller)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (controller, FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (!ctrl, FALSE);
+ g_object_set_qdata (object, controller_key, controller);
+ return (TRUE);
+}
+
+/**
+ * g_object_sink_values:
+ * @object: the object that has controlled properties
+ * @timestamp: the time that should be processed
+ *
+ * Convenience function for GObject
+ *
+ * Returns: same thing as gst_controller_sink_values()
+ */
+gboolean
+g_object_sink_values (GObject * object, GstClockTime timestamp)
+{
+ GstController *ctrl = NULL;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (ctrl, FALSE);
+ return gst_controller_sink_values (ctrl, timestamp);
+}
+
+/**
+ * g_object_get_value_arrays:
+ * @object: the object that has controlled properties
+ * @timestamp: the time that should be processed
+ * @value_arrays: list to return the control-values in
+ *
+ * Function to be able to get an array of values for one or more given element
+ * properties.
+ *
+ * If the GstValueArray->values array in list nodes is NULL, it will be created
+ * by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * The g_object_* functions are just convenience functions for GObject
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+g_object_get_value_arrays (GObject * object, GstClockTime timestamp,
+ GSList * value_arrays)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (ctrl, FALSE);
+ return gst_controller_get_value_arrays (ctrl, timestamp, value_arrays);
+}
+
+/**
+ * g_object_get_value_array:
+ * @self: the object that has controlled properties
+ * @timestamp: the time that should be processed
+ * @value_array: array to put control-values in
+ *
+ * Function to be able to get an array of values for one element properties
+ *
+ * If the GstValueArray->values array is NULL, it will be created by the function.
+ * The type of the values in the array are the same as the property's type.
+ *
+ * The g_object_* functions are just convenience functions for GObject
+ *
+ * Returns: %TRUE if the given array(s) could be filled, %FALSE otherwise
+ */
+gboolean
+g_object_get_value_array (GObject * object, GstClockTime timestamp,
+ GstValueArray * value_array)
+{
+ GstController *ctrl;
+
+ g_return_val_if_fail (G_IS_OBJECT (object), FALSE);
+ g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE);
+
+ ctrl = g_object_get_qdata (object, controller_key);
+ g_return_val_if_fail (ctrl, FALSE);
+
+ return gst_controller_get_value_array (ctrl, timestamp, value_array);
+}
--- /dev/null
+/*
+ * gst-interpolation.h
+ *
+ * Interpolation methodws for dynamic properties
+ */
+
+#include "config.h"
+#include "gst-controller.h"
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT);
+
+// common helper
+
+/*
+ * gst_controlled_property_find_timed_value_node:
+ * @prop: the controlled property to search in
+ * @timestamp: the search key
+ *
+ * Find last value before given timestamp in timed value list.
+ *
+ * Returns: the found #GList node or %NULL
+ */
+GList *
+gst_controlled_property_find_timed_value_node (GstControlledProperty * prop,
+ GstClockTime timestamp)
+{
+ //GList *prev_node = NULL;
+ GList *prev_node = g_list_last (prop->values);
+ GList *node;
+ GstTimedValue *tv;
+
+ /*
+ if((prop->last_value) &&
+ (timestamp>((GstTimedValue *)(prop->last_value->data))->timestamp)) {
+ node=prop->last_value;
+ }
+ else {
+ node=prop->values;
+ }
+ */
+
+ /* iterate over timed value list */
+ for (node = prop->values; node; node = g_list_next (node)) {
+ tv = node->data;
+ /* this timestamp is newer that the one we look for */
+ if (timestamp < tv->timestamp) {
+ /* get previous one again */
+ prev_node = g_list_previous (node);
+ break;
+ }
+ }
+ /*
+ if(node) {
+ prop->last_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_timed_value_node (prop, timestamp))) {
+ GstTimedValue *tv = node->data;
+
+ return (&tv->value);
+ }
+ return (&prop->default_value);
+}
+
+#define DEFINE_NONE_GET(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 (prop,ts)); \
+ ts+=value_array->sample_interval; \
+ values++; \
+ } \
+ return (TRUE); \
+}
+
+DEFINE_NONE_GET (int)
+ DEFINE_NONE_GET (long)
+DEFINE_NONE_GET (float)
+DEFINE_NONE_GET (double)
+
+ static GstInterpolateMethod interpolate_none = {
+ interpolate_none_get,
+ interpolate_none_get_int_value_array,
+ interpolate_none_get,
+ interpolate_none_get_long_value_array,
+ interpolate_none_get,
+ interpolate_none_get_float_value_array,
+ interpolate_none_get,
+ interpolate_none_get_double_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;
+ GstTimedValue *tv;
+
+ /* check if there is a value at the registered timestamp */
+ for (node = prop->values; node; node = g_list_next (node)) {
+ tv = node->data;
+ if (timestamp == tv->timestamp) {
+ return (&tv->value);
+ }
+ }
+
+ return (&prop->default_value);
+}
+
+static gboolean
+interpolate_trigger_get_value_array (GstControlledProperty * prop,
+ GstClockTime timestamp, GstValueArray * value_array)
+{
+ return (FALSE);
+}
+
+static GstInterpolateMethod interpolate_trigger = {
+ interpolate_trigger_get,
+ interpolate_trigger_get_value_array,
+ interpolate_trigger_get,
+ NULL,
+ interpolate_trigger_get,
+ NULL,
+ interpolate_trigger_get,
+ NULL
+};
+
+// linear interpolation
+// smoothes inbetween values
+
+#define DEFINE_LINEAR_GET(type) \
+static g##type \
+_interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
+{ \
+ GList *node; \
+ \
+ if ((node = gst_controlled_property_find_timed_value_node (prop, timestamp))) { \
+ GstTimedValue *tv1, *tv2; \
+ \
+ tv1 = node->data; \
+ if ((node = g_list_next (node))) { \
+ gdouble timediff,valuediff; \
+ g##type value1,value2; \
+ \
+ tv2 = node->data; \
+ \
+ timediff = (gdouble)(tv2->timestamp - tv1->timestamp); \
+ value1 = g_value_get_##type (&tv1->value); \
+ value2 = g_value_get_##type (&tv2->value); \
+ valuediff = (gdouble)(value2-value1); \
+ \
+ return((g##type)(value1+valuediff*((timestamp-tv1->timestamp)/timediff))); \
+ } \
+ else { \
+ return (g_value_get_##type (&tv1->value)); \
+ } \
+ } \
+ return (g_value_get_##type (&prop->default_value)); \
+} \
+\
+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; \
+ gint *values=(gint *)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 (long)
+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_long,
+ interpolate_linear_get_long_value_array,
+ interpolate_linear_get_float,
+ interpolate_linear_get_float_value_array,
+ interpolate_linear_get_double,
+ interpolate_linear_get_double_value_array,
+ };
+
+// square interpolation
+
+// cubic interpolation
+
+// register all interpolation methods
+GstInterpolateMethod *interpolation_methods[] = {
+ &interpolate_none,
+ &interpolate_trigger,
+ &interpolate_linear,
+ NULL,
+ NULL
+};
--- /dev/null
+/*
+ * lib.c
+ *
+ * New dynamic properties
+ *
+ */
+
+#include "config.h"
+#include <gst/gst.h>
+
+/* library initialisation */
+
+#define GST_CAT_DEFAULT gst_controller_debug
+GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
+
+/**
+ * gst_controller_init:
+ * @argc: pointer to the commandline argument count
+ * @argv: pointer to the commandline argument values
+ *
+ * Initializes the use of the controller library. Suggested to be called right
+ * after gst_init().
+ *
+ * Returns: the %TRUE for success.
+ */
+gboolean
+gst_controller_init (int *argc, char ***argv)
+{
+ GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gstcontroller", 0,
+ "dynamic parameter control for gstreamer elements");
+
+ return TRUE;
+}
pcfiles = \
gstreamer-@GST_MAJORMINOR@.pc \
gstreamer-base-@GST_MAJORMINOR@.pc \
- gstreamer-control-@GST_MAJORMINOR@.pc \
+ gstreamer-controller-@GST_MAJORMINOR@.pc \
gstreamer-dataprotocol-@GST_MAJORMINOR@.pc
pcfiles_uninstalled = \
gstreamer-@GST_MAJORMINOR@-uninstalled.pc \
gstreamer-base-@GST_MAJORMINOR@-uninstalled.pc \
- gstreamer-control-@GST_MAJORMINOR@-uninstalled.pc \
+ gstreamer-controller-@GST_MAJORMINOR@-uninstalled.pc \
gstreamer-dataprotocol-@GST_MAJORMINOR@-uninstalled.pc
all-local: $(pcfiles) $(pcfiles_uninstalled)
gstreamer-uninstalled.pc.in \
gstreamer-base.pc.in \
gstreamer-base-uninstalled.pc.in \
- gstreamer-control.pc.in \
- gstreamer-control-uninstalled.pc.in \
+ gstreamer-controller.pc.in \
+ gstreamer-controller-uninstalled.pc.in \
gstreamer-dataprotocol.pc.in \
gstreamer-dataprotocol-uninstalled.pc.in
+++ /dev/null
-# the standard variables don't make sense for an uninstalled copy
-prefix=
-exec_prefix=
-libdir=${pcfiledir}/../libs
-includedir=${pcfiledir}/..
-gstcontrol_libs=-lgstcontrol-@GST_MAJORMINOR@
-
-Name: GStreamer control library, uninstalled
-Description: Dynamic parameters for plug-ins
-Requires: gstreamer-@GST_MAJORMINOR@ = @VERSION@
-Version: @VERSION@
-
-Libs: ${libdir}/gst/control/libgstcontrol-@GST_MAJORMINOR@.la
-Cflags: -I${includedir} -I${includedir}/libs @GST_PKG_CFLAGS@
+++ /dev/null
-prefix=@prefix@
-exec_prefix=@exec_prefix@
-libdir=@libdir@
-includedir=@includedir@/gstreamer-@GST_MAJORMINOR@
-
-Name: GStreamer control library
-Description: Dynamic parameters for plug-ins
-Requires: gstreamer-@GST_MAJORMINOR@
-Version: @VERSION@
-Libs: -L${libdir} -lgstcontrol-@GST_MAJORMINOR@
-Cflags: -I${includedir} @GST_PKG_CFLAGS@
thread \
plugins \
mixer \
+ controller \
cutter \
pingpong \
manual \
--- /dev/null
+audio-example
+*.bb
+*.bbg
+*.da
--- /dev/null
+noinst_PROGRAMS = audio-example
+
+audio_example_CFLAGS = $(GST_OBJ_CFLAGS) -I$(top_builddir)/libs
+audio_example_LDADD = $(GST_OBJ_LIBS)
+audio_example_LDFLAGS = $(top_builddir)/libs/gst/controller/libgstcontroller-@GST_MAJORMINOR@.la
--- /dev/null
+/*
+ * audio-example.c
+ *
+ * Build a pipeline with testaudiosource->alsasink
+ * and sweep frequency and volume
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/controller/gst-controller.h>
+
+gint
+main (gint argc, gchar ** argv)
+{
+ gint res = 1;
+ GstElement *src, *sink;
+ GstBin *bin;
+ GstController *ctrl;
+ GValue vol = { 0, };
+
+ gst_init (&argc, &argv);
+ gst_controller_init (&argc, &argv);
+
+ // build pipeline
+ bin = GST_BIN (gst_pipeline_new ("pipeline"));
+ /* TODO make this "testaudiosrc", when its ready */
+ src = gst_element_factory_make ("sinesrc", "gen_audio");
+ sink = gst_element_factory_make ("alsasink", "play_audio");
+ gst_bin_add_many (bin, src, sink, NULL);
+
+ // add a controller to the source
+ if (!(ctrl =
+ gst_controller_new (G_OBJECT (src), "frequency", "volume", NULL))) {
+ goto Error;
+ }
+ // set interpolation
+ gst_controller_set_interpolation_mode (ctrl, "volume",
+ GST_INTERPOLATE_LINEAR);
+
+ // set control values
+ g_value_init (&vol, G_TYPE_DOUBLE);
+ g_value_set_double (&vol, 0.0);
+ gst_controller_set (ctrl, "volume", 0 * GST_SECOND, &vol);
+ g_value_set_double (&vol, 1.0);
+ gst_controller_set (ctrl, "volume", 1 * GST_SECOND, &vol);
+
+ // iterate two seconds
+ /*
+ if(gst_element_set_state (bin, GST_STATE_PLAYING))
+ {
+ while (gst_bin_iterate (bin))
+ {
+ }
+ }
+ gst_element_set_state (bin, GST_STATE_NULL);
+ */
+
+ // cleanup
+ g_object_unref (G_OBJECT (ctrl));
+ g_object_unref (G_OBJECT (bin));
+ res = 0;
+Error:
+ return (res);
+}
endif
SUBDIRS = \
- bytestream caps cleanup \
+ bytestream caps cleanup controller \
$(GST_DEBUG_DIRS) \
dlopen \
elements indexers negotiation pad \
plugin refcounting schedulers states threads trigger
DIST_SUBDIRS = \
- bytestream caps cleanup \
+ bytestream caps cleanup controller \
debug \
dlopen \
elements indexers negotiation pad \
--- /dev/null
+Makefile
+Makefile.in
+*.o
+*.lo
+*.la
+*.bb
+*.bbg
+*.da
+.deps
+.libs
+interpolator
--- /dev/null
+include ../Rules
+
+tests_pass = interpolator
+tests_fail =
+tests_ignore =
+
+interpolator_SOURCES = interpolator.c
+interpolator_CFLAGS = $(GST_OBJ_CFLAGS) -I$(top_builddir)/libs
+interpolator_LDFLAGS = $(top_builddir)/libs/gst/controller/libgstcontroller-@GST_MAJORMINOR@.la
--- /dev/null
+/*
+ * interpolator.c
+ *
+ * test interpolator methods
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/controller/gst-controller.h>
+
+extern GstInterpolateMethod *interpolation_methods[];
+
+gint
+main (gint argc, gchar ** argv)
+{
+ gint res = 1;
+ GstControlledProperty *prop = NULL;
+ GType type = G_TYPE_INT;
+ GstTimedValue tv1 = { 0, }, tv2 = {
+ 0,}, tv3 = {
+ 0,};
+ GValue *val;
+ gint i;
+
+ gst_init (&argc, &argv);
+ gst_controller_init (&argc, &argv);
+
+ // build fake controlled property
+
+ if ((prop = g_new0 (GstControlledProperty, 1))) {
+ prop->name = "test";
+ //prop->parent_type = G_OBJECT_TYPE (object);
+ prop->type = type;
+
+ g_value_init (&prop->default_value, type);
+ g_value_set_int (&prop->default_value, 0);
+ g_value_init (&prop->result_value, type);
+
+ // set timed values
+ tv1.timestamp = 0;
+ g_value_init (&tv1.value, type);
+ g_value_set_int (&tv1.value, 0);
+ prop->values = g_list_append (prop->values, &tv1);
+
+ tv2.timestamp = 10 * GST_SECOND;
+ g_value_init (&tv2.value, type);
+ g_value_set_int (&tv2.value, 100);
+ prop->values = g_list_append (prop->values, &tv2);
+
+ tv3.timestamp = 20 * GST_SECOND;
+ g_value_init (&tv3.value, type);
+ g_value_set_int (&tv3.value, 50);
+ prop->values = g_list_append (prop->values, &tv3);
+
+ g_print ("# time trig none line\n");
+
+ // test interpolator
+ for (i = 0; i < 25; i++) {
+ g_print (" %4d", i);
+
+ prop->interpolation = GST_INTERPOLATE_TRIGGER;
+ prop->get = interpolation_methods[prop->interpolation]->get_int;
+ prop->get_value_array =
+ interpolation_methods[prop->interpolation]->get_int_value_array;
+ val = prop->get (prop, i * GST_SECOND);
+ g_print (" %4d", (val ? g_value_get_int (val) : 0));
+
+ prop->interpolation = GST_INTERPOLATE_NONE;
+ prop->get = interpolation_methods[prop->interpolation]->get_int;
+ prop->get_value_array =
+ interpolation_methods[prop->interpolation]->get_int_value_array;
+ val = prop->get (prop, i * GST_SECOND);
+ g_print (" %4d", (val ? g_value_get_int (val) : 0));
+
+ prop->interpolation = GST_INTERPOLATE_LINEAR;
+ prop->get = interpolation_methods[prop->interpolation]->get_int;
+ prop->get_value_array =
+ interpolation_methods[prop->interpolation]->get_int_value_array;
+ val = prop->get (prop, i * GST_SECOND);
+ g_print (" %4d", (val ? g_value_get_int (val) : 0));
+
+ g_print ("\n");
+ }
+
+ g_list_free (prop->values);
+ g_free (prop);
+ res = 0;
+ }
+ return (res);
+}
endif
SUBDIRS = \
- bytestream caps cleanup \
+ bytestream caps cleanup controller \
$(GST_DEBUG_DIRS) \
dlopen \
elements indexers negotiation pad \
plugin refcounting schedulers states threads trigger
DIST_SUBDIRS = \
- bytestream caps cleanup \
+ bytestream caps cleanup controller \
debug \
dlopen \
elements indexers negotiation pad \
--- /dev/null
+Makefile
+Makefile.in
+*.o
+*.lo
+*.la
+*.bb
+*.bbg
+*.da
+.deps
+.libs
+interpolator
--- /dev/null
+include ../Rules
+
+tests_pass = interpolator
+tests_fail =
+tests_ignore =
+
+interpolator_SOURCES = interpolator.c
+interpolator_CFLAGS = $(GST_OBJ_CFLAGS) -I$(top_builddir)/libs
+interpolator_LDFLAGS = $(top_builddir)/libs/gst/controller/libgstcontroller-@GST_MAJORMINOR@.la
--- /dev/null
+/*
+ * interpolator.c
+ *
+ * test interpolator methods
+ *
+ */
+
+#include <gst/gst.h>
+#include <gst/controller/gst-controller.h>
+
+extern GstInterpolateMethod *interpolation_methods[];
+
+gint
+main (gint argc, gchar ** argv)
+{
+ gint res = 1;
+ GstControlledProperty *prop = NULL;
+ GType type = G_TYPE_INT;
+ GstTimedValue tv1 = { 0, }, tv2 = {
+ 0,}, tv3 = {
+ 0,};
+ GValue *val;
+ gint i;
+
+ gst_init (&argc, &argv);
+ gst_controller_init (&argc, &argv);
+
+ // build fake controlled property
+
+ if ((prop = g_new0 (GstControlledProperty, 1))) {
+ prop->name = "test";
+ //prop->parent_type = G_OBJECT_TYPE (object);
+ prop->type = type;
+
+ g_value_init (&prop->default_value, type);
+ g_value_set_int (&prop->default_value, 0);
+ g_value_init (&prop->result_value, type);
+
+ // set timed values
+ tv1.timestamp = 0;
+ g_value_init (&tv1.value, type);
+ g_value_set_int (&tv1.value, 0);
+ prop->values = g_list_append (prop->values, &tv1);
+
+ tv2.timestamp = 10 * GST_SECOND;
+ g_value_init (&tv2.value, type);
+ g_value_set_int (&tv2.value, 100);
+ prop->values = g_list_append (prop->values, &tv2);
+
+ tv3.timestamp = 20 * GST_SECOND;
+ g_value_init (&tv3.value, type);
+ g_value_set_int (&tv3.value, 50);
+ prop->values = g_list_append (prop->values, &tv3);
+
+ g_print ("# time trig none line\n");
+
+ // test interpolator
+ for (i = 0; i < 25; i++) {
+ g_print (" %4d", i);
+
+ prop->interpolation = GST_INTERPOLATE_TRIGGER;
+ prop->get = interpolation_methods[prop->interpolation]->get_int;
+ prop->get_value_array =
+ interpolation_methods[prop->interpolation]->get_int_value_array;
+ val = prop->get (prop, i * GST_SECOND);
+ g_print (" %4d", (val ? g_value_get_int (val) : 0));
+
+ prop->interpolation = GST_INTERPOLATE_NONE;
+ prop->get = interpolation_methods[prop->interpolation]->get_int;
+ prop->get_value_array =
+ interpolation_methods[prop->interpolation]->get_int_value_array;
+ val = prop->get (prop, i * GST_SECOND);
+ g_print (" %4d", (val ? g_value_get_int (val) : 0));
+
+ prop->interpolation = GST_INTERPOLATE_LINEAR;
+ prop->get = interpolation_methods[prop->interpolation]->get_int;
+ prop->get_value_array =
+ interpolation_methods[prop->interpolation]->get_int_value_array;
+ val = prop->get (prop, i * GST_SECOND);
+ g_print (" %4d", (val ? g_value_get_int (val) : 0));
+
+ g_print ("\n");
+ }
+
+ g_list_free (prop->values);
+ g_free (prop);
+ res = 0;
+ }
+ return (res);
+}