diff --git a/ChangeLog b/ChangeLog index ede39c7d7f..6be440e2af 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,53 @@ +2005-08-03 Stefan Kost + + * 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 * gst/base/gstcollectpads.c: (gst_collectpads_finalize), (gst_collectpads_stop): diff --git a/configure.ac b/configure.ac index 4e9b3d3760..be04d2ba94 100644 --- a/configure.ac +++ b/configure.ac @@ -630,6 +630,7 @@ gst/parse/Makefile gst/registries/Makefile libs/Makefile libs/gst/Makefile +libs/gst/controller/Makefile libs/gst/dataprotocol/Makefile libs/gst/getbits/Makefile po/Makefile.in @@ -645,6 +646,7 @@ testsuite/Makefile testsuite/bytestream/Makefile testsuite/caps/Makefile testsuite/cleanup/Makefile +testsuite/controller/Makefile testsuite/debug/Makefile testsuite/dlopen/Makefile testsuite/elements/Makefile @@ -659,6 +661,7 @@ testsuite/states/Makefile testsuite/threads/Makefile testsuite/trigger/Makefile examples/Makefile +examples/controller/Makefile examples/cutter/Makefile examples/helloworld/Makefile examples/launch/Makefile @@ -691,8 +694,8 @@ pkgconfig/gstreamer.pc 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, diff --git a/docs/gst/tmpl/gstevent.sgml b/docs/gst/tmpl/gstevent.sgml index f4575c480a..4872e40a6b 100644 --- a/docs/gst/tmpl/gstevent.sgml +++ b/docs/gst/tmpl/gstevent.sgml @@ -223,8 +223,6 @@ Copy the event using the event specific copy function @taglist: @Returns: - -@list: diff --git a/docs/gst/tmpl/gstfakesrc.sgml b/docs/gst/tmpl/gstfakesrc.sgml index 57e11cccf2..9b4080e0c6 100644 --- a/docs/gst/tmpl/gstfakesrc.sgml +++ b/docs/gst/tmpl/gstfakesrc.sgml @@ -32,6 +32,9 @@ GstFakeSrc @arg1: @: @: +@: +@: +@: @: diff --git a/docs/libs/Makefile.am b/docs/libs/Makefile.am index 4036328963..6a68742d8e 100644 --- a/docs/libs/Makefile.am +++ b/docs/libs/Makefile.am @@ -47,7 +47,7 @@ SCAN_OPTIONS=--deprecated-guards="GST_DISABLE_DEPRECATED" #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 @@ -56,13 +56,14 @@ 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 = \ @@ -80,7 +81,7 @@ extra_files = # 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) diff --git a/docs/libs/gstreamer-libs-docs.sgml b/docs/libs/gstreamer-libs-docs.sgml index 55a8120248..c978128efb 100644 --- a/docs/libs/gstreamer-libs-docs.sgml +++ b/docs/libs/gstreamer-libs-docs.sgml @@ -4,6 +4,7 @@ %version-entities; + - - + + + gstcontrol + &GstController; + + + diff --git a/docs/libs/gstreamer-libs-sections.txt b/docs/libs/gstreamer-libs-sections.txt index 8c59a4bda8..bb6ec065b1 100644 --- a/docs/libs/gstreamer-libs-sections.txt +++ b/docs/libs/gstreamer-libs-sections.txt @@ -111,3 +111,34 @@ gst_dp_validate_packet +
+gstcontroller +GstController +libs/controller/gstcontroller.h +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 + +GstControllerClass +GST_CONTROLLER +GST_IS_CONTROLLER +GST_CONTROLLER_CLASS +GST_IS_CONTROLLER_CLASS +GST_CONTROLLER_GET_CLASS +GST_TYPE_CONTROLLER + +gst_controller_get_type +
diff --git a/docs/libs/gstreamer-libs.types b/docs/libs/gstreamer-libs.types index aaaf415a81..7ee9f80e88 100644 --- a/docs/libs/gstreamer-libs.types +++ b/docs/libs/gstreamer-libs.types @@ -1,3 +1,4 @@ #include +#include - +gst_controller_get_type diff --git a/examples/Makefile.am b/examples/Makefile.am index 1d1d24723a..ec9b8b1cde 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -11,6 +11,7 @@ dirs = \ thread \ plugins \ mixer \ + controller \ cutter \ pingpong \ manual \ diff --git a/examples/controller/.gitignore b/examples/controller/.gitignore new file mode 100644 index 0000000000..228a5fa898 --- /dev/null +++ b/examples/controller/.gitignore @@ -0,0 +1,4 @@ +audio-example +*.bb +*.bbg +*.da diff --git a/examples/controller/Makefile.am b/examples/controller/Makefile.am new file mode 100644 index 0000000000..4ab183eccf --- /dev/null +++ b/examples/controller/Makefile.am @@ -0,0 +1,5 @@ +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 diff --git a/examples/controller/audio-example.c b/examples/controller/audio-example.c new file mode 100644 index 0000000000..8193ed74ad --- /dev/null +++ b/examples/controller/audio-example.c @@ -0,0 +1,64 @@ +/* + * audio-example.c + * + * Build a pipeline with testaudiosource->alsasink + * and sweep frequency and volume + * + */ + +#include +#include + +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); +} diff --git a/libs/gst/Makefile.am b/libs/gst/Makefile.am index 049515328c..481cb6e8ec 100644 --- a/libs/gst/Makefile.am +++ b/libs/gst/Makefile.am @@ -1 +1 @@ -SUBDIRS = dataprotocol getbits +SUBDIRS = controller dataprotocol getbits diff --git a/libs/gst/controller/.gitignore b/libs/gst/controller/.gitignore new file mode 100644 index 0000000000..1d7497234e --- /dev/null +++ b/libs/gst/controller/.gitignore @@ -0,0 +1,5 @@ +*.bb +*.bbg +*.da +*.def +*.gcno diff --git a/libs/gst/controller/Makefile.am b/libs/gst/controller/Makefile.am new file mode 100644 index 0000000000..52350df977 --- /dev/null +++ b/libs/gst/controller/Makefile.am @@ -0,0 +1,15 @@ +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) diff --git a/libs/gst/controller/gst-controller.c b/libs/gst/controller/gst-controller.c new file mode 100644 index 0000000000..9c4e99828a --- /dev/null +++ b/libs/gst/controller/gst-controller.c @@ -0,0 +1,929 @@ +/* + * 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; +} diff --git a/libs/gst/controller/gst-controller.h b/libs/gst/controller/gst-controller.h new file mode 100644 index 0000000000..a9472c4fa0 --- /dev/null +++ b/libs/gst/controller/gst-controller.h @@ -0,0 +1,228 @@ +/* + * gst-controller.h + * + * New dynamic properties + */ + +#ifndef __GST_CONTROLLER_H__ +#define __GST_CONTROLLER_H__ + +#include + +#include +#include +#include +#include + +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:: 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__ */ diff --git a/libs/gst/controller/gst-helper.c b/libs/gst/controller/gst-helper.c new file mode 100644 index 0000000000..de221cb63e --- /dev/null +++ b/libs/gst/controller/gst-helper.c @@ -0,0 +1,194 @@ +/* + * 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); +} diff --git a/libs/gst/controller/gst-interpolation.c b/libs/gst/controller/gst-interpolation.c new file mode 100644 index 0000000000..0c309af11f --- /dev/null +++ b/libs/gst/controller/gst-interpolation.c @@ -0,0 +1,231 @@ +/* + * 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;inbsamples;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;inbsamples;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 +}; diff --git a/libs/gst/controller/gstcontroller.c b/libs/gst/controller/gstcontroller.c new file mode 100644 index 0000000000..9c4e99828a --- /dev/null +++ b/libs/gst/controller/gstcontroller.c @@ -0,0 +1,929 @@ +/* + * 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; +} diff --git a/libs/gst/controller/gstcontroller.h b/libs/gst/controller/gstcontroller.h new file mode 100644 index 0000000000..a9472c4fa0 --- /dev/null +++ b/libs/gst/controller/gstcontroller.h @@ -0,0 +1,228 @@ +/* + * gst-controller.h + * + * New dynamic properties + */ + +#ifndef __GST_CONTROLLER_H__ +#define __GST_CONTROLLER_H__ + +#include + +#include +#include +#include +#include + +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:: 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__ */ diff --git a/libs/gst/controller/gsthelper.c b/libs/gst/controller/gsthelper.c new file mode 100644 index 0000000000..de221cb63e --- /dev/null +++ b/libs/gst/controller/gsthelper.c @@ -0,0 +1,194 @@ +/* + * 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); +} diff --git a/libs/gst/controller/gstinterpolation.c b/libs/gst/controller/gstinterpolation.c new file mode 100644 index 0000000000..0c309af11f --- /dev/null +++ b/libs/gst/controller/gstinterpolation.c @@ -0,0 +1,231 @@ +/* + * 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;inbsamples;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;inbsamples;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 +}; diff --git a/libs/gst/controller/lib.c b/libs/gst/controller/lib.c new file mode 100644 index 0000000000..e3d3af3491 --- /dev/null +++ b/libs/gst/controller/lib.c @@ -0,0 +1,33 @@ +/* + * lib.c + * + * New dynamic properties + * + */ + +#include "config.h" +#include + +/* 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; +} diff --git a/pkgconfig/Makefile.am b/pkgconfig/Makefile.am index 0179a98102..6229990629 100644 --- a/pkgconfig/Makefile.am +++ b/pkgconfig/Makefile.am @@ -2,13 +2,13 @@ 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) @@ -27,8 +27,8 @@ EXTRA_DIST = \ 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 diff --git a/pkgconfig/gstreamer-control-uninstalled.pc.in b/pkgconfig/gstreamer-control-uninstalled.pc.in deleted file mode 100644 index a2f0a4691a..0000000000 --- a/pkgconfig/gstreamer-control-uninstalled.pc.in +++ /dev/null @@ -1,14 +0,0 @@ -# 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@ diff --git a/pkgconfig/gstreamer-control.pc.in b/pkgconfig/gstreamer-control.pc.in deleted file mode 100644 index 563f6c9072..0000000000 --- a/pkgconfig/gstreamer-control.pc.in +++ /dev/null @@ -1,11 +0,0 @@ -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@ diff --git a/tests/old/examples/Makefile.am b/tests/old/examples/Makefile.am index 1d1d24723a..ec9b8b1cde 100644 --- a/tests/old/examples/Makefile.am +++ b/tests/old/examples/Makefile.am @@ -11,6 +11,7 @@ dirs = \ thread \ plugins \ mixer \ + controller \ cutter \ pingpong \ manual \ diff --git a/tests/old/examples/controller/.gitignore b/tests/old/examples/controller/.gitignore new file mode 100644 index 0000000000..228a5fa898 --- /dev/null +++ b/tests/old/examples/controller/.gitignore @@ -0,0 +1,4 @@ +audio-example +*.bb +*.bbg +*.da diff --git a/tests/old/examples/controller/Makefile.am b/tests/old/examples/controller/Makefile.am new file mode 100644 index 0000000000..4ab183eccf --- /dev/null +++ b/tests/old/examples/controller/Makefile.am @@ -0,0 +1,5 @@ +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 diff --git a/tests/old/examples/controller/audio-example.c b/tests/old/examples/controller/audio-example.c new file mode 100644 index 0000000000..8193ed74ad --- /dev/null +++ b/tests/old/examples/controller/audio-example.c @@ -0,0 +1,64 @@ +/* + * audio-example.c + * + * Build a pipeline with testaudiosource->alsasink + * and sweep frequency and volume + * + */ + +#include +#include + +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); +} diff --git a/tests/old/testsuite/Makefile.am b/tests/old/testsuite/Makefile.am index b6e96a2c34..4d4b322350 100644 --- a/tests/old/testsuite/Makefile.am +++ b/tests/old/testsuite/Makefile.am @@ -14,7 +14,7 @@ GST_DEBUG_DIRS = debug endif SUBDIRS = \ - bytestream caps cleanup \ + bytestream caps cleanup controller \ $(GST_DEBUG_DIRS) \ dlopen \ elements indexers negotiation pad \ @@ -22,7 +22,7 @@ SUBDIRS = \ plugin refcounting schedulers states threads trigger DIST_SUBDIRS = \ - bytestream caps cleanup \ + bytestream caps cleanup controller \ debug \ dlopen \ elements indexers negotiation pad \ diff --git a/tests/old/testsuite/controller/.gitignore b/tests/old/testsuite/controller/.gitignore new file mode 100644 index 0000000000..1f6c376b11 --- /dev/null +++ b/tests/old/testsuite/controller/.gitignore @@ -0,0 +1,11 @@ +Makefile +Makefile.in +*.o +*.lo +*.la +*.bb +*.bbg +*.da +.deps +.libs +interpolator diff --git a/tests/old/testsuite/controller/Makefile.am b/tests/old/testsuite/controller/Makefile.am new file mode 100644 index 0000000000..8b31b1aff0 --- /dev/null +++ b/tests/old/testsuite/controller/Makefile.am @@ -0,0 +1,9 @@ +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 diff --git a/tests/old/testsuite/controller/interpolator.c b/tests/old/testsuite/controller/interpolator.c new file mode 100644 index 0000000000..3d34a9e35c --- /dev/null +++ b/tests/old/testsuite/controller/interpolator.c @@ -0,0 +1,90 @@ +/* + * interpolator.c + * + * test interpolator methods + * + */ + +#include +#include + +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); +} diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index b6e96a2c34..4d4b322350 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -14,7 +14,7 @@ GST_DEBUG_DIRS = debug endif SUBDIRS = \ - bytestream caps cleanup \ + bytestream caps cleanup controller \ $(GST_DEBUG_DIRS) \ dlopen \ elements indexers negotiation pad \ @@ -22,7 +22,7 @@ SUBDIRS = \ plugin refcounting schedulers states threads trigger DIST_SUBDIRS = \ - bytestream caps cleanup \ + bytestream caps cleanup controller \ debug \ dlopen \ elements indexers negotiation pad \ diff --git a/testsuite/controller/.gitignore b/testsuite/controller/.gitignore new file mode 100644 index 0000000000..1f6c376b11 --- /dev/null +++ b/testsuite/controller/.gitignore @@ -0,0 +1,11 @@ +Makefile +Makefile.in +*.o +*.lo +*.la +*.bb +*.bbg +*.da +.deps +.libs +interpolator diff --git a/testsuite/controller/Makefile.am b/testsuite/controller/Makefile.am new file mode 100644 index 0000000000..8b31b1aff0 --- /dev/null +++ b/testsuite/controller/Makefile.am @@ -0,0 +1,9 @@ +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 diff --git a/testsuite/controller/interpolator.c b/testsuite/controller/interpolator.c new file mode 100644 index 0000000000..3d34a9e35c --- /dev/null +++ b/testsuite/controller/interpolator.c @@ -0,0 +1,90 @@ +/* + * interpolator.c + * + * test interpolator methods + * + */ + +#include +#include + +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); +}