added controller code removed dparam pc files

Original commit message from CVS:
added controller code
removed dparam pc files
This commit is contained in:
Stefan Kost 2005-08-02 21:35:34 +00:00
parent 75c955843e
commit c32233b7fc
39 changed files with 3700 additions and 47 deletions

View file

@ -1,3 +1,53 @@
2005-08-03 Stefan Kost <ensonic@users.sf.net>
* configure.ac:
* docs/libs/Makefile.am:
* docs/libs/gstreamer-libs-docs.sgml:
* docs/libs/gstreamer-libs-sections.txt:
* docs/libs/gstreamer-libs.types:
* examples/Makefile.am:
* examples/controller/.cvsignore:
* examples/controller/Makefile.am:
* examples/controller/audio-example.c: (main):
* libs/gst/Makefile.am:
* libs/gst/controller/.cvsignore:
* libs/gst/controller/Makefile.am:
* libs/gst/controller/gst-controller.c:
(on_object_controlled_property_changed), (gst_timed_value_compare),
(gst_timed_value_find),
(gst_controlled_property_set_interpolation_mode),
(gst_controlled_property_new), (gst_controlled_property_free),
(gst_controller_find_controlled_property),
(gst_controller_new_valist), (gst_controller_new),
(gst_controller_remove_properties_valist),
(gst_controller_remove_properties), (gst_controller_set),
(gst_controller_set_from_list), (gst_controller_unset),
(gst_controller_get), (gst_controller_get_all),
(gst_controller_sink_values), (gst_controller_get_value_arrays),
(gst_controller_get_value_array),
(gst_controller_set_interpolation_mode),
(_gst_controller_finalize), (_gst_controller_init),
(_gst_controller_class_init), (gst_controller_get_type):
* libs/gst/controller/gst-controller.h:
* libs/gst/controller/gst-helper.c: (g_object_control_properties),
(g_object_uncontrol_properties), (g_object_get_controller),
(g_object_set_controller), (g_object_sink_values),
(g_object_get_value_arrays), (g_object_get_value_array):
* libs/gst/controller/gst-interpolation.c:
(gst_controlled_property_find_timed_value_node),
(interpolate_none_get), (interpolate_trigger_get),
(interpolate_trigger_get_value_array):
* libs/gst/controller/lib.c: (gst_controller_init):
* pkgconfig/Makefile.am:
* pkgconfig/gstreamer-control-uninstalled.pc.in:
* pkgconfig/gstreamer-control.pc.in:
* testsuite/Makefile.am:
* testsuite/controller/.cvsignore:
* testsuite/controller/Makefile.am:
* testsuite/controller/interpolator.c: (main):
added controller code
removed dparam pc files
2005-08-01 Jan Schmidt <thaytan@mad.scientist.com>
* gst/base/gstcollectpads.c: (gst_collectpads_finalize),
(gst_collectpads_stop):

View file

@ -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,

View file

@ -223,8 +223,6 @@ Copy the event using the event specific copy function
@taglist:
@Returns:
<!-- # Unused Parameters # -->
@list:
<!-- ##### FUNCTION gst_event_parse_tag ##### -->

View file

@ -33,6 +33,9 @@ GstFakeSrc
@:
@:
@:
@:
@:
@:
<!-- ##### ARG GstFakeSrc:data ##### -->
<para>

View file

@ -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)

View file

@ -4,6 +4,7 @@
<!ENTITY % version-entities SYSTEM "version.entities">
%version-entities;
<!ENTITY GstBytestream SYSTEM "xml/gstbytestream.xml">
<!ENTITY GstController SYSTEM "xml/gstcontroller.xml">
<!ENTITY GstGetbits SYSTEM "xml/gstgetbits.xml">
<!-- has not yet been written
<!ENTITY GstPutbits SYSTEM "xml/gstputbits.xml">
@ -35,7 +36,12 @@
<!-- has not yet been written
&GstPutbits;
-->
<!--link linkend="GObject">GObject</link-->
<chapter id="gstreamer-control">
<title>gstcontrol</title>
&GstController;
<!--&GstControllerGObject; -->
</chapter>
</part>

View file

@ -111,3 +111,34 @@ gst_dp_validate_packet
<SUBSECTION Standard>
</SECTION>
<SECTION>
<FILE>gstcontroller</FILE>
<TITLE>GstController</TITLE>
<INCLUDE>libs/controller/gstcontroller.h</INCLUDE>
GstController
gst_controller_init
gst_controller_new
gst_controller_new_valist
gst_controller_remove_properties
gst_controller_remove_properties_valist
gst_controller_set
gst_controller_set_from_list
gst_controller_unset
gst_controller_get
gst_controller_get_all
gst_controller_sink_values
gst_controller_get_value_arrays
gst_controller_get_value_array
gst_controller_set_interpolation_mode
GST_PARAM_CONTROLLABLE
<SUBSECTION Standard>
GstControllerClass
GST_CONTROLLER
GST_IS_CONTROLLER
GST_CONTROLLER_CLASS
GST_IS_CONTROLLER_CLASS
GST_CONTROLLER_GET_CLASS
GST_TYPE_CONTROLLER
<SUBSECTION Private>
gst_controller_get_type
</SECTION>

View file

@ -1,3 +1,4 @@
#include <gst/gst.h>
#include <gst/controller/gst-controller.h>
gst_controller_get_type

View file

@ -11,6 +11,7 @@ dirs = \
thread \
plugins \
mixer \
controller \
cutter \
pingpong \
manual \

4
examples/controller/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
audio-example
*.bb
*.bbg
*.da

View file

@ -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

View file

@ -0,0 +1,64 @@
/*
* audio-example.c
*
* Build a pipeline with testaudiosource->alsasink
* and sweep frequency and volume
*
*/
#include <gst/gst.h>
#include <gst/controller/gst-controller.h>
gint
main (gint argc, gchar ** argv)
{
gint res = 1;
GstElement *src, *sink;
GstBin *bin;
GstController *ctrl;
GValue vol = { 0, };
gst_init (&argc, &argv);
gst_controller_init (&argc, &argv);
// build pipeline
bin = GST_BIN (gst_pipeline_new ("pipeline"));
/* TODO make this "testaudiosrc", when its ready */
src = gst_element_factory_make ("sinesrc", "gen_audio");
sink = gst_element_factory_make ("alsasink", "play_audio");
gst_bin_add_many (bin, src, sink, NULL);
// add a controller to the source
if (!(ctrl =
gst_controller_new (G_OBJECT (src), "frequency", "volume", NULL))) {
goto Error;
}
// set interpolation
gst_controller_set_interpolation_mode (ctrl, "volume",
GST_INTERPOLATE_LINEAR);
// set control values
g_value_init (&vol, G_TYPE_DOUBLE);
g_value_set_double (&vol, 0.0);
gst_controller_set (ctrl, "volume", 0 * GST_SECOND, &vol);
g_value_set_double (&vol, 1.0);
gst_controller_set (ctrl, "volume", 1 * GST_SECOND, &vol);
// iterate two seconds
/*
if(gst_element_set_state (bin, GST_STATE_PLAYING))
{
while (gst_bin_iterate (bin))
{
}
}
gst_element_set_state (bin, GST_STATE_NULL);
*/
// cleanup
g_object_unref (G_OBJECT (ctrl));
g_object_unref (G_OBJECT (bin));
res = 0;
Error:
return (res);
}

View file

@ -1 +1 @@
SUBDIRS = dataprotocol getbits
SUBDIRS = controller dataprotocol getbits

5
libs/gst/controller/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
*.bb
*.bbg
*.da
*.def
*.gcno

View file

@ -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)

View file

@ -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, &timestamp,
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;
}

View file

@ -0,0 +1,228 @@
/*
* gst-controller.h
*
* New dynamic properties
*/
#ifndef __GST_CONTROLLER_H__
#define __GST_CONTROLLER_H__
#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include <glib/gprintf.h>
#include <gst/gst.h>
G_BEGIN_DECLS
/**
* GST_PARAM_CONTROLLABLE:
*
* Use this flag on GstElement properties you wish to be (eventually) handled
* by a GstController.
* TODO: needs to go to gstelemnt.h (to avoid clashes on G_PARAM_USER_SHIFT)
*/
#define GST_PARAM_CONTROLLABLE (1 << (G_PARAM_USER_SHIFT + 1))
/**
* GstTimedValue:
*
* a structure for value+time
*/
typedef struct _GstTimedValue
{
GstClockTime timestamp; // timestamp of the value change
GValue value; // the new value
/* TODO what about storing the difference to next timestamp and value here
+ make calculations slightly easier and faster
- determining the GType for the value_dif is not simple
e.g. if value is G_TYPE_UCHAR value_diff needs to be G_TYPE_INT
*/
} GstTimedValue;
/**
* GstValueArray:
*
* Structure to receive multiple values at once
*/
typedef struct _GstValueArray
{
gchar *property_name;
gint nbsamples; // Number of samples requested
GstClockTime sample_interval; // Interval between each sample
gpointer *values; // pointer to the array (so it can be filled if NULL)
} GstValueArray;
/**
* GstInterpolateMode:
* @GST_INTERPOLATE_NONE: steps-like interpolation, default
* @GST_INTERPOLATE_TRIGGER: returns the default value of the property,
* except for times with specific values
* @GST_INTERPOLATE_LINEAR: linear interpolation
* @GST_INTERPOLATE_QUADRATIC: square interpolation
* @GST_INTERPOLATE_CUBIC: cubic interpolation
* @GST_INTERPOLATE_USER: user-provided interpolation
*
* The various interpolation modes available.
*/
typedef enum
{
GST_INTERPOLATE_NONE,
GST_INTERPOLATE_TRIGGER,
GST_INTERPOLATE_LINEAR,
GST_INTERPOLATE_QUADRATIC,
GST_INTERPOLATE_CUBIC,
GST_INTERPOLATE_USER
} GstInterpolateMode;
struct _GstControlledProperty;
typedef GValue *(*InterpolateGet) (struct _GstControlledProperty * prop,
GstClockTime timestamp);
typedef gboolean (*InterpolateGetValueArray) (struct _GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array);
/**
* GstInterpolateMethod:
*
* Function pointer structure to do user-defined interpolation methods
*/
typedef struct _GstInterpolateMethod
{
InterpolateGet get_int;
InterpolateGetValueArray get_int_value_array;
InterpolateGet get_long;
InterpolateGetValueArray get_long_value_array;
InterpolateGet get_float;
InterpolateGetValueArray get_float_value_array;
InterpolateGet get_double;
InterpolateGetValueArray get_double_value_array;
} GstInterpolateMethod;
/**
* GstControlledProperty:
*/
typedef struct _GstControlledProperty
{
gchar *name; // name of the property
GObject *object; // the object we control
GType type; // type of the handled property
GValue default_value; // default value for the handled property
GValue result_value; // result value location for the interpolation method
GstTimedValue last_value; // the last value a _sink call wrote
GstTimedValue live_value; // temporary value override for live input
gulong notify_handler_id; // id of the notify::<name> signal handler
GstInterpolateMode interpolation; // Interpolation mode
/* TODO instead of *method, have pointers to get() and get_value_array() here
gst_controller_set_interpolation_mode() will pick the right ones for the
properties value type
GstInterpolateMethod *method; // User-implemented handler (if interpolation == GST_INTERPOLATE_USER)
*/
InterpolateGet get;
InterpolateGetValueArray get_value_array;
GList *values; // List of GstTimedValue
/* TODO keep the last search result to be able to continue
GList *last_value; // last search result, can be used for incremental searches
*/
} GstControlledProperty;
#define GST_CONTROLLED_PROPERTY(obj) ((GstControlledProperty *)(obj))
/* type macros */
#define GST_TYPE_CONTROLLER (gst_controller_get_type ())
#define GST_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CONTROLLER, GstController))
#define GST_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CONTROLLER, GstControllerClass))
#define GST_IS_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CONTROLLER))
#define GST_IS_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CONTROLLERE))
#define GST_CONTROLLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CONTROLLER, GstControllerClass))
typedef struct _GstController GstController;
typedef struct _GstControllerClass GstControllerClass;
//typedef struct _GstControllerPrivate GstControllerPrivate;
/**
* GstController:
*
* The instance structure of GstController
*/
struct _GstController
{
GObject parent;
GList *properties; // List of GstControlledProperty
GMutex *lock; // Secure property access, elements will access from threads
};
struct _GstControllerClass
{
GObjectClass parent_class;
};
GType gst_controller_get_type (void);
/* GstController functions */
GstController *gst_controller_new_valist (GObject * object, va_list var_args);
GstController *gst_controller_new (GObject * object, ...);
gboolean gst_controller_remove_properties_valist (GstController * self,
va_list var_args);
gboolean gst_controller_remove_properties (GstController * self, ...);
gboolean gst_controller_set (GstController * self, gchar * property_name,
GstClockTime timestamp, GValue * value);
gboolean gst_controller_set_from_list (GstController * self,
gchar * property_name, GSList * timedvalues);
gboolean gst_controller_unset (GstController * self, gchar * property_name,
GstClockTime timestamp);
GValue *gst_controller_get (GstController * self, gchar * property_name,
GstClockTime timestamp);
const GList *gst_controller_get_all (GstController * self,
gchar * property_name);
gboolean gst_controller_sink_values (GstController * self,
GstClockTime timestamp);
gboolean gst_controller_get_value_arrays (GstController * self,
GstClockTime timestamp, GSList * value_arrays);
gboolean gst_controller_get_value_array (GstController * self,
GstClockTime timestamp, GstValueArray * value_array);
gboolean gst_controller_set_interpolation_mode (GstController * self,
gchar * property_name, GstInterpolateMode mode);
/* GObject convenience functions */
GstController *g_object_control_properties (GObject * object, ...);
gboolean g_object_uncontrol_properties (GObject * object, ...);
GstController *g_object_get_controller (GObject * object);
gboolean g_object_set_controller (GObject * object, GstController * controller);
gboolean g_object_sink_values (GObject * object, GstClockTime timestamp);
gboolean g_object_get_value_arrays (GObject * object,
GstClockTime timestamp, GSList * value_arrays);
gboolean g_object_get_value_array (GObject * object,
GstClockTime timestamp, GstValueArray * value_array);
/* lib init/done */
gboolean gst_controller_init (int * argc, char ***argv);
G_END_DECLS
#endif /* __GST_CONTROLLER_H__ */

View file

@ -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);
}

View file

@ -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;i<value_array->nbsamples;i++) { \
*values=g_value_get_##type (interpolate_none_get (prop,ts)); \
ts+=value_array->sample_interval; \
values++; \
} \
return (TRUE); \
}
DEFINE_NONE_GET (int)
DEFINE_NONE_GET (long)
DEFINE_NONE_GET (float)
DEFINE_NONE_GET (double)
static GstInterpolateMethod interpolate_none = {
interpolate_none_get,
interpolate_none_get_int_value_array,
interpolate_none_get,
interpolate_none_get_long_value_array,
interpolate_none_get,
interpolate_none_get_float_value_array,
interpolate_none_get,
interpolate_none_get_double_value_array
};
// returns the default value of the property, except for times with specific values
// needed for one-shot events, such as notes and triggers
static GValue *
interpolate_trigger_get (GstControlledProperty * prop, GstClockTime timestamp)
{
GList *node;
GstTimedValue *tv;
/* check if there is a value at the registered timestamp */
for (node = prop->values; node; node = g_list_next (node)) {
tv = node->data;
if (timestamp == tv->timestamp) {
return (&tv->value);
}
}
return (&prop->default_value);
}
static gboolean
interpolate_trigger_get_value_array (GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array)
{
return (FALSE);
}
static GstInterpolateMethod interpolate_trigger = {
interpolate_trigger_get,
interpolate_trigger_get_value_array,
interpolate_trigger_get,
NULL,
interpolate_trigger_get,
NULL,
interpolate_trigger_get,
NULL
};
// linear interpolation
// smoothes inbetween values
#define DEFINE_LINEAR_GET(type) \
static g##type \
_interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
GList *node; \
\
if ((node = gst_controlled_property_find_timed_value_node (prop, timestamp))) { \
GstTimedValue *tv1, *tv2; \
\
tv1 = node->data; \
if ((node = g_list_next (node))) { \
gdouble timediff,valuediff; \
g##type value1,value2; \
\
tv2 = node->data; \
\
timediff = (gdouble)(tv2->timestamp - tv1->timestamp); \
value1 = g_value_get_##type (&tv1->value); \
value2 = g_value_get_##type (&tv2->value); \
valuediff = (gdouble)(value2-value1); \
\
return((g##type)(value1+valuediff*((timestamp-tv1->timestamp)/timediff))); \
} \
else { \
return (g_value_get_##type (&tv1->value)); \
} \
} \
return (g_value_get_##type (&prop->default_value)); \
} \
\
static GValue * \
interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
g_value_set_##type (&prop->result_value,_interpolate_linear_get_##type (prop,timestamp)); \
return (&prop->result_value); \
} \
\
static gboolean \
interpolate_linear_get_##type##_value_array (GstControlledProperty * prop, \
GstClockTime timestamp, GstValueArray * value_array) \
{ \
gint i; \
GstClockTime ts=timestamp; \
gint *values=(gint *)value_array->values; \
\
for(i=0;i<value_array->nbsamples;i++) { \
*values=_interpolate_linear_get_##type (prop,ts); \
ts+=value_array->sample_interval; \
values++; \
} \
return (TRUE); \
}
DEFINE_LINEAR_GET (int)
DEFINE_LINEAR_GET (long)
DEFINE_LINEAR_GET (float)
DEFINE_LINEAR_GET (double)
static GstInterpolateMethod interpolate_linear = {
interpolate_linear_get_int,
interpolate_linear_get_int_value_array,
interpolate_linear_get_long,
interpolate_linear_get_long_value_array,
interpolate_linear_get_float,
interpolate_linear_get_float_value_array,
interpolate_linear_get_double,
interpolate_linear_get_double_value_array,
};
// square interpolation
// cubic interpolation
// register all interpolation methods
GstInterpolateMethod *interpolation_methods[] = {
&interpolate_none,
&interpolate_trigger,
&interpolate_linear,
NULL,
NULL
};

View file

@ -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, &timestamp,
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;
}

View file

@ -0,0 +1,228 @@
/*
* gst-controller.h
*
* New dynamic properties
*/
#ifndef __GST_CONTROLLER_H__
#define __GST_CONTROLLER_H__
#include <string.h>
#include <glib.h>
#include <glib-object.h>
#include <glib/gprintf.h>
#include <gst/gst.h>
G_BEGIN_DECLS
/**
* GST_PARAM_CONTROLLABLE:
*
* Use this flag on GstElement properties you wish to be (eventually) handled
* by a GstController.
* TODO: needs to go to gstelemnt.h (to avoid clashes on G_PARAM_USER_SHIFT)
*/
#define GST_PARAM_CONTROLLABLE (1 << (G_PARAM_USER_SHIFT + 1))
/**
* GstTimedValue:
*
* a structure for value+time
*/
typedef struct _GstTimedValue
{
GstClockTime timestamp; // timestamp of the value change
GValue value; // the new value
/* TODO what about storing the difference to next timestamp and value here
+ make calculations slightly easier and faster
- determining the GType for the value_dif is not simple
e.g. if value is G_TYPE_UCHAR value_diff needs to be G_TYPE_INT
*/
} GstTimedValue;
/**
* GstValueArray:
*
* Structure to receive multiple values at once
*/
typedef struct _GstValueArray
{
gchar *property_name;
gint nbsamples; // Number of samples requested
GstClockTime sample_interval; // Interval between each sample
gpointer *values; // pointer to the array (so it can be filled if NULL)
} GstValueArray;
/**
* GstInterpolateMode:
* @GST_INTERPOLATE_NONE: steps-like interpolation, default
* @GST_INTERPOLATE_TRIGGER: returns the default value of the property,
* except for times with specific values
* @GST_INTERPOLATE_LINEAR: linear interpolation
* @GST_INTERPOLATE_QUADRATIC: square interpolation
* @GST_INTERPOLATE_CUBIC: cubic interpolation
* @GST_INTERPOLATE_USER: user-provided interpolation
*
* The various interpolation modes available.
*/
typedef enum
{
GST_INTERPOLATE_NONE,
GST_INTERPOLATE_TRIGGER,
GST_INTERPOLATE_LINEAR,
GST_INTERPOLATE_QUADRATIC,
GST_INTERPOLATE_CUBIC,
GST_INTERPOLATE_USER
} GstInterpolateMode;
struct _GstControlledProperty;
typedef GValue *(*InterpolateGet) (struct _GstControlledProperty * prop,
GstClockTime timestamp);
typedef gboolean (*InterpolateGetValueArray) (struct _GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array);
/**
* GstInterpolateMethod:
*
* Function pointer structure to do user-defined interpolation methods
*/
typedef struct _GstInterpolateMethod
{
InterpolateGet get_int;
InterpolateGetValueArray get_int_value_array;
InterpolateGet get_long;
InterpolateGetValueArray get_long_value_array;
InterpolateGet get_float;
InterpolateGetValueArray get_float_value_array;
InterpolateGet get_double;
InterpolateGetValueArray get_double_value_array;
} GstInterpolateMethod;
/**
* GstControlledProperty:
*/
typedef struct _GstControlledProperty
{
gchar *name; // name of the property
GObject *object; // the object we control
GType type; // type of the handled property
GValue default_value; // default value for the handled property
GValue result_value; // result value location for the interpolation method
GstTimedValue last_value; // the last value a _sink call wrote
GstTimedValue live_value; // temporary value override for live input
gulong notify_handler_id; // id of the notify::<name> signal handler
GstInterpolateMode interpolation; // Interpolation mode
/* TODO instead of *method, have pointers to get() and get_value_array() here
gst_controller_set_interpolation_mode() will pick the right ones for the
properties value type
GstInterpolateMethod *method; // User-implemented handler (if interpolation == GST_INTERPOLATE_USER)
*/
InterpolateGet get;
InterpolateGetValueArray get_value_array;
GList *values; // List of GstTimedValue
/* TODO keep the last search result to be able to continue
GList *last_value; // last search result, can be used for incremental searches
*/
} GstControlledProperty;
#define GST_CONTROLLED_PROPERTY(obj) ((GstControlledProperty *)(obj))
/* type macros */
#define GST_TYPE_CONTROLLER (gst_controller_get_type ())
#define GST_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_CONTROLLER, GstController))
#define GST_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_CONTROLLER, GstControllerClass))
#define GST_IS_CONTROLLER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_CONTROLLER))
#define GST_IS_CONTROLLER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_CONTROLLERE))
#define GST_CONTROLLER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_CONTROLLER, GstControllerClass))
typedef struct _GstController GstController;
typedef struct _GstControllerClass GstControllerClass;
//typedef struct _GstControllerPrivate GstControllerPrivate;
/**
* GstController:
*
* The instance structure of GstController
*/
struct _GstController
{
GObject parent;
GList *properties; // List of GstControlledProperty
GMutex *lock; // Secure property access, elements will access from threads
};
struct _GstControllerClass
{
GObjectClass parent_class;
};
GType gst_controller_get_type (void);
/* GstController functions */
GstController *gst_controller_new_valist (GObject * object, va_list var_args);
GstController *gst_controller_new (GObject * object, ...);
gboolean gst_controller_remove_properties_valist (GstController * self,
va_list var_args);
gboolean gst_controller_remove_properties (GstController * self, ...);
gboolean gst_controller_set (GstController * self, gchar * property_name,
GstClockTime timestamp, GValue * value);
gboolean gst_controller_set_from_list (GstController * self,
gchar * property_name, GSList * timedvalues);
gboolean gst_controller_unset (GstController * self, gchar * property_name,
GstClockTime timestamp);
GValue *gst_controller_get (GstController * self, gchar * property_name,
GstClockTime timestamp);
const GList *gst_controller_get_all (GstController * self,
gchar * property_name);
gboolean gst_controller_sink_values (GstController * self,
GstClockTime timestamp);
gboolean gst_controller_get_value_arrays (GstController * self,
GstClockTime timestamp, GSList * value_arrays);
gboolean gst_controller_get_value_array (GstController * self,
GstClockTime timestamp, GstValueArray * value_array);
gboolean gst_controller_set_interpolation_mode (GstController * self,
gchar * property_name, GstInterpolateMode mode);
/* GObject convenience functions */
GstController *g_object_control_properties (GObject * object, ...);
gboolean g_object_uncontrol_properties (GObject * object, ...);
GstController *g_object_get_controller (GObject * object);
gboolean g_object_set_controller (GObject * object, GstController * controller);
gboolean g_object_sink_values (GObject * object, GstClockTime timestamp);
gboolean g_object_get_value_arrays (GObject * object,
GstClockTime timestamp, GSList * value_arrays);
gboolean g_object_get_value_array (GObject * object,
GstClockTime timestamp, GstValueArray * value_array);
/* lib init/done */
gboolean gst_controller_init (int * argc, char ***argv);
G_END_DECLS
#endif /* __GST_CONTROLLER_H__ */

View file

@ -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);
}

View file

@ -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;i<value_array->nbsamples;i++) { \
*values=g_value_get_##type (interpolate_none_get (prop,ts)); \
ts+=value_array->sample_interval; \
values++; \
} \
return (TRUE); \
}
DEFINE_NONE_GET (int)
DEFINE_NONE_GET (long)
DEFINE_NONE_GET (float)
DEFINE_NONE_GET (double)
static GstInterpolateMethod interpolate_none = {
interpolate_none_get,
interpolate_none_get_int_value_array,
interpolate_none_get,
interpolate_none_get_long_value_array,
interpolate_none_get,
interpolate_none_get_float_value_array,
interpolate_none_get,
interpolate_none_get_double_value_array
};
// returns the default value of the property, except for times with specific values
// needed for one-shot events, such as notes and triggers
static GValue *
interpolate_trigger_get (GstControlledProperty * prop, GstClockTime timestamp)
{
GList *node;
GstTimedValue *tv;
/* check if there is a value at the registered timestamp */
for (node = prop->values; node; node = g_list_next (node)) {
tv = node->data;
if (timestamp == tv->timestamp) {
return (&tv->value);
}
}
return (&prop->default_value);
}
static gboolean
interpolate_trigger_get_value_array (GstControlledProperty * prop,
GstClockTime timestamp, GstValueArray * value_array)
{
return (FALSE);
}
static GstInterpolateMethod interpolate_trigger = {
interpolate_trigger_get,
interpolate_trigger_get_value_array,
interpolate_trigger_get,
NULL,
interpolate_trigger_get,
NULL,
interpolate_trigger_get,
NULL
};
// linear interpolation
// smoothes inbetween values
#define DEFINE_LINEAR_GET(type) \
static g##type \
_interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
GList *node; \
\
if ((node = gst_controlled_property_find_timed_value_node (prop, timestamp))) { \
GstTimedValue *tv1, *tv2; \
\
tv1 = node->data; \
if ((node = g_list_next (node))) { \
gdouble timediff,valuediff; \
g##type value1,value2; \
\
tv2 = node->data; \
\
timediff = (gdouble)(tv2->timestamp - tv1->timestamp); \
value1 = g_value_get_##type (&tv1->value); \
value2 = g_value_get_##type (&tv2->value); \
valuediff = (gdouble)(value2-value1); \
\
return((g##type)(value1+valuediff*((timestamp-tv1->timestamp)/timediff))); \
} \
else { \
return (g_value_get_##type (&tv1->value)); \
} \
} \
return (g_value_get_##type (&prop->default_value)); \
} \
\
static GValue * \
interpolate_linear_get_##type (GstControlledProperty * prop, GstClockTime timestamp) \
{ \
g_value_set_##type (&prop->result_value,_interpolate_linear_get_##type (prop,timestamp)); \
return (&prop->result_value); \
} \
\
static gboolean \
interpolate_linear_get_##type##_value_array (GstControlledProperty * prop, \
GstClockTime timestamp, GstValueArray * value_array) \
{ \
gint i; \
GstClockTime ts=timestamp; \
gint *values=(gint *)value_array->values; \
\
for(i=0;i<value_array->nbsamples;i++) { \
*values=_interpolate_linear_get_##type (prop,ts); \
ts+=value_array->sample_interval; \
values++; \
} \
return (TRUE); \
}
DEFINE_LINEAR_GET (int)
DEFINE_LINEAR_GET (long)
DEFINE_LINEAR_GET (float)
DEFINE_LINEAR_GET (double)
static GstInterpolateMethod interpolate_linear = {
interpolate_linear_get_int,
interpolate_linear_get_int_value_array,
interpolate_linear_get_long,
interpolate_linear_get_long_value_array,
interpolate_linear_get_float,
interpolate_linear_get_float_value_array,
interpolate_linear_get_double,
interpolate_linear_get_double_value_array,
};
// square interpolation
// cubic interpolation
// register all interpolation methods
GstInterpolateMethod *interpolation_methods[] = {
&interpolate_none,
&interpolate_trigger,
&interpolate_linear,
NULL,
NULL
};

33
libs/gst/controller/lib.c Normal file
View file

@ -0,0 +1,33 @@
/*
* lib.c
*
* New dynamic properties
*
*/
#include "config.h"
#include <gst/gst.h>
/* library initialisation */
#define GST_CAT_DEFAULT gst_controller_debug
GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
/**
* gst_controller_init:
* @argc: pointer to the commandline argument count
* @argv: pointer to the commandline argument values
*
* Initializes the use of the controller library. Suggested to be called right
* after gst_init().
*
* Returns: the %TRUE for success.
*/
gboolean
gst_controller_init (int *argc, char ***argv)
{
GST_DEBUG_CATEGORY_INIT (GST_CAT_DEFAULT, "gstcontroller", 0,
"dynamic parameter control for gstreamer elements");
return TRUE;
}

View file

@ -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

View file

@ -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@

View file

@ -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@

View file

@ -11,6 +11,7 @@ dirs = \
thread \
plugins \
mixer \
controller \
cutter \
pingpong \
manual \

View file

@ -0,0 +1,4 @@
audio-example
*.bb
*.bbg
*.da

View file

@ -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

View file

@ -0,0 +1,64 @@
/*
* audio-example.c
*
* Build a pipeline with testaudiosource->alsasink
* and sweep frequency and volume
*
*/
#include <gst/gst.h>
#include <gst/controller/gst-controller.h>
gint
main (gint argc, gchar ** argv)
{
gint res = 1;
GstElement *src, *sink;
GstBin *bin;
GstController *ctrl;
GValue vol = { 0, };
gst_init (&argc, &argv);
gst_controller_init (&argc, &argv);
// build pipeline
bin = GST_BIN (gst_pipeline_new ("pipeline"));
/* TODO make this "testaudiosrc", when its ready */
src = gst_element_factory_make ("sinesrc", "gen_audio");
sink = gst_element_factory_make ("alsasink", "play_audio");
gst_bin_add_many (bin, src, sink, NULL);
// add a controller to the source
if (!(ctrl =
gst_controller_new (G_OBJECT (src), "frequency", "volume", NULL))) {
goto Error;
}
// set interpolation
gst_controller_set_interpolation_mode (ctrl, "volume",
GST_INTERPOLATE_LINEAR);
// set control values
g_value_init (&vol, G_TYPE_DOUBLE);
g_value_set_double (&vol, 0.0);
gst_controller_set (ctrl, "volume", 0 * GST_SECOND, &vol);
g_value_set_double (&vol, 1.0);
gst_controller_set (ctrl, "volume", 1 * GST_SECOND, &vol);
// iterate two seconds
/*
if(gst_element_set_state (bin, GST_STATE_PLAYING))
{
while (gst_bin_iterate (bin))
{
}
}
gst_element_set_state (bin, GST_STATE_NULL);
*/
// cleanup
g_object_unref (G_OBJECT (ctrl));
g_object_unref (G_OBJECT (bin));
res = 0;
Error:
return (res);
}

View file

@ -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 \

View file

@ -0,0 +1,11 @@
Makefile
Makefile.in
*.o
*.lo
*.la
*.bb
*.bbg
*.da
.deps
.libs
interpolator

View file

@ -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

View file

@ -0,0 +1,90 @@
/*
* interpolator.c
*
* test interpolator methods
*
*/
#include <gst/gst.h>
#include <gst/controller/gst-controller.h>
extern GstInterpolateMethod *interpolation_methods[];
gint
main (gint argc, gchar ** argv)
{
gint res = 1;
GstControlledProperty *prop = NULL;
GType type = G_TYPE_INT;
GstTimedValue tv1 = { 0, }, tv2 = {
0,}, tv3 = {
0,};
GValue *val;
gint i;
gst_init (&argc, &argv);
gst_controller_init (&argc, &argv);
// build fake controlled property
if ((prop = g_new0 (GstControlledProperty, 1))) {
prop->name = "test";
//prop->parent_type = G_OBJECT_TYPE (object);
prop->type = type;
g_value_init (&prop->default_value, type);
g_value_set_int (&prop->default_value, 0);
g_value_init (&prop->result_value, type);
// set timed values
tv1.timestamp = 0;
g_value_init (&tv1.value, type);
g_value_set_int (&tv1.value, 0);
prop->values = g_list_append (prop->values, &tv1);
tv2.timestamp = 10 * GST_SECOND;
g_value_init (&tv2.value, type);
g_value_set_int (&tv2.value, 100);
prop->values = g_list_append (prop->values, &tv2);
tv3.timestamp = 20 * GST_SECOND;
g_value_init (&tv3.value, type);
g_value_set_int (&tv3.value, 50);
prop->values = g_list_append (prop->values, &tv3);
g_print ("# time trig none line\n");
// test interpolator
for (i = 0; i < 25; i++) {
g_print (" %4d", i);
prop->interpolation = GST_INTERPOLATE_TRIGGER;
prop->get = interpolation_methods[prop->interpolation]->get_int;
prop->get_value_array =
interpolation_methods[prop->interpolation]->get_int_value_array;
val = prop->get (prop, i * GST_SECOND);
g_print (" %4d", (val ? g_value_get_int (val) : 0));
prop->interpolation = GST_INTERPOLATE_NONE;
prop->get = interpolation_methods[prop->interpolation]->get_int;
prop->get_value_array =
interpolation_methods[prop->interpolation]->get_int_value_array;
val = prop->get (prop, i * GST_SECOND);
g_print (" %4d", (val ? g_value_get_int (val) : 0));
prop->interpolation = GST_INTERPOLATE_LINEAR;
prop->get = interpolation_methods[prop->interpolation]->get_int;
prop->get_value_array =
interpolation_methods[prop->interpolation]->get_int_value_array;
val = prop->get (prop, i * GST_SECOND);
g_print (" %4d", (val ? g_value_get_int (val) : 0));
g_print ("\n");
}
g_list_free (prop->values);
g_free (prop);
res = 0;
}
return (res);
}

View file

@ -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 \

11
testsuite/controller/.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
Makefile
Makefile.in
*.o
*.lo
*.la
*.bb
*.bbg
*.da
.deps
.libs
interpolator

View file

@ -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

View file

@ -0,0 +1,90 @@
/*
* interpolator.c
*
* test interpolator methods
*
*/
#include <gst/gst.h>
#include <gst/controller/gst-controller.h>
extern GstInterpolateMethod *interpolation_methods[];
gint
main (gint argc, gchar ** argv)
{
gint res = 1;
GstControlledProperty *prop = NULL;
GType type = G_TYPE_INT;
GstTimedValue tv1 = { 0, }, tv2 = {
0,}, tv3 = {
0,};
GValue *val;
gint i;
gst_init (&argc, &argv);
gst_controller_init (&argc, &argv);
// build fake controlled property
if ((prop = g_new0 (GstControlledProperty, 1))) {
prop->name = "test";
//prop->parent_type = G_OBJECT_TYPE (object);
prop->type = type;
g_value_init (&prop->default_value, type);
g_value_set_int (&prop->default_value, 0);
g_value_init (&prop->result_value, type);
// set timed values
tv1.timestamp = 0;
g_value_init (&tv1.value, type);
g_value_set_int (&tv1.value, 0);
prop->values = g_list_append (prop->values, &tv1);
tv2.timestamp = 10 * GST_SECOND;
g_value_init (&tv2.value, type);
g_value_set_int (&tv2.value, 100);
prop->values = g_list_append (prop->values, &tv2);
tv3.timestamp = 20 * GST_SECOND;
g_value_init (&tv3.value, type);
g_value_set_int (&tv3.value, 50);
prop->values = g_list_append (prop->values, &tv3);
g_print ("# time trig none line\n");
// test interpolator
for (i = 0; i < 25; i++) {
g_print (" %4d", i);
prop->interpolation = GST_INTERPOLATE_TRIGGER;
prop->get = interpolation_methods[prop->interpolation]->get_int;
prop->get_value_array =
interpolation_methods[prop->interpolation]->get_int_value_array;
val = prop->get (prop, i * GST_SECOND);
g_print (" %4d", (val ? g_value_get_int (val) : 0));
prop->interpolation = GST_INTERPOLATE_NONE;
prop->get = interpolation_methods[prop->interpolation]->get_int;
prop->get_value_array =
interpolation_methods[prop->interpolation]->get_int_value_array;
val = prop->get (prop, i * GST_SECOND);
g_print (" %4d", (val ? g_value_get_int (val) : 0));
prop->interpolation = GST_INTERPOLATE_LINEAR;
prop->get = interpolation_methods[prop->interpolation]->get_int;
prop->get_value_array =
interpolation_methods[prop->interpolation]->get_int_value_array;
val = prop->get (prop, i * GST_SECOND);
g_print (" %4d", (val ? g_value_get_int (val) : 0));
g_print ("\n");
}
g_list_free (prop->values);
g_free (prop);
res = 0;
}
return (res);
}