| /* GStreamer |
| * |
| * Copyright (C) <2005> Stefan Kost <ensonic at users dot sf dot net> |
| * Copyright (C) 2007 Sebastian Dröge <slomo@circular-chaos.org> |
| * |
| * gstcontroller.c: dynamic parameter control subsystem |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public |
| * License along with this library; if not, write to the |
| * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
| * Boston, MA 02111-1307, USA. |
| */ |
| |
| /** |
| * SECTION:gstcontroller |
| * @short_description: dynamic parameter control subsystem |
| * |
| * The controller subsystem offers a lightweight way to adjust gobject |
| * properties over stream-time. It works by using time-stamped value pairs that |
| * are queued for element-properties. At run-time the elements continously pull |
| * values changes for the current stream-time. |
| * |
| * What needs to be changed in a #GstElement? |
| * Very little - it is just two steps to make a plugin controllable! |
| * <orderedlist> |
| * <listitem><para> |
| * mark gobject-properties paramspecs that make sense to be controlled, |
| * by GST_PARAM_CONTROLLABLE. |
| * </para></listitem> |
| * <listitem><para> |
| * when processing data (get, chain, loop function) at the beginning call |
| * gst_object_sync_values(element,timestamp). |
| * This will made the controller to update all gobject properties that are under |
| * control with the current values based on timestamp. |
| * </para></listitem> |
| * </orderedlist> |
| * |
| * What needs to be done in applications? |
| * Again its not a lot to change. |
| * <orderedlist> |
| * <listitem><para> |
| * first put some properties under control, by calling |
| * controller = gst_object_control_properties (object, "prop1", "prop2",...); |
| * </para></listitem> |
| * <listitem><para> |
| * Get a #GstControlSource for the property and set it up. |
| * csource = gst_interpolation_control_source_new (); |
| * gst_interpolation_control_source_set_interpolation_mode(csource, mode); |
| * gst_interpolation_control_source_set (csource,0 * GST_SECOND, value1); |
| * gst_interpolation_control_source_set (csource,1 * GST_SECOND, value2); |
| * </para></listitem> |
| * <listitem><para> |
| * Set the #GstControlSource in the controller. |
| * gst_controller_set_control_source (controller, "prop1", csource); |
| * </para></listitem> |
| * <listitem><para> |
| * start your pipeline |
| * </para></listitem> |
| * </orderedlist> |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include "gstcontroller.h" |
| #include "gstcontrollerprivate.h" |
| #include "gstcontrolsource.h" |
| #include "gstinterpolationcontrolsource.h" |
| |
| #define GST_CAT_DEFAULT controller_debug |
| GST_DEBUG_CATEGORY_EXTERN (GST_CAT_DEFAULT); |
| |
| static GObjectClass *parent_class = NULL; |
| GQuark priv_gst_controller_key; |
| |
| /* property ids */ |
| enum |
| { |
| PROP_CONTROL_RATE = 1 |
| }; |
| |
| struct _GstControllerPrivate |
| { |
| GstClockTime control_rate; |
| GstClockTime last_sync; |
| }; |
| |
| /* helper */ |
| |
| /* |
| * 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 witable && controlable && !construct-only */ |
| g_return_val_if_fail ((pspec->flags & (G_PARAM_WRITABLE | |
| GST_PARAM_CONTROLLABLE | G_PARAM_CONSTRUCT_ONLY)) == |
| (G_PARAM_WRITABLE | GST_PARAM_CONTROLLABLE), NULL); |
| |
| if ((prop = g_slice_new (GstControlledProperty))) { |
| prop->pspec = pspec; |
| prop->name = pspec->name; |
| prop->csource = NULL; |
| prop->disabled = FALSE; |
| memset (&prop->last_value, 0, sizeof (GValue)); |
| g_value_init (&prop->last_value, G_PARAM_SPEC_VALUE_TYPE (prop->pspec)); |
| } |
| } 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) |
| { |
| if (prop->csource) |
| g_object_unref (prop->csource); |
| g_value_unset (&prop->last_value); |
| g_slice_free (GstControlledProperty, 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; |
| /* FIXME: eventually use GQuark to speed it up */ |
| if (!strcmp (prop->name, name)) { |
| return prop; |
| } |
| } |
| GST_DEBUG ("controller does not (yet) manage property '%s'", name); |
| |
| return NULL; |
| } |
| |
| /* |
| * gst_controller_add_property: |
| * @self: the controller object or %NULL if none yet exists |
| * @object: object to bind the property |
| * @name: name of projecty in @object |
| * @ref_existing: pointer to flag that tracks if we need to ref an existing |
| * controller still |
| * |
| * Creates a new #GstControlledProperty if there is none for property @name yet. |
| * In case this is the first controlled propery, it creates the controller as |
| * well. |
| * |
| * Returns: a newly created controller object or reffed existing one with the |
| * given property bound. |
| */ |
| static GstController * |
| gst_controller_add_property (GstController * self, GObject * object, |
| const gchar * name, gboolean * ref_existing) |
| { |
| /* test if this property isn't yet controlled */ |
| if (!self || !gst_controller_find_controlled_property (self, name)) { |
| GstControlledProperty *prop; |
| |
| /* create GstControlledProperty and add to self->propeties List */ |
| if ((prop = gst_controlled_property_new (object, name))) { |
| /* if we don't have a controller object yet, now is the time to create one */ |
| if (!self) { |
| self = g_object_newv (GST_TYPE_CONTROLLER, 0, NULL); |
| self->object = g_object_ref (object); |
| /* store the controller */ |
| g_object_set_qdata (object, priv_gst_controller_key, self); |
| *ref_existing = FALSE; |
| } else { |
| /* only want one single _ref(), even for multiple properties */ |
| if (*ref_existing) { |
| g_object_ref (self); |
| *ref_existing = FALSE; |
| GST_INFO ("returning existing controller"); |
| } |
| } |
| self->properties = g_list_prepend (self->properties, prop); |
| } |
| } else { |
| GST_WARNING ("trying to control property %s again", name); |
| if (*ref_existing) { |
| g_object_ref (self); |
| *ref_existing = FALSE; |
| } |
| } |
| return self; |
| } |
| |
| /* 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; |
| gboolean ref_existing = TRUE; |
| gchar *name; |
| |
| g_return_val_if_fail (G_IS_OBJECT (object), NULL); |
| |
| GST_INFO ("setting up a new controller"); |
| |
| self = g_object_get_qdata (object, priv_gst_controller_key); |
| /* create GstControlledProperty for each property */ |
| while ((name = va_arg (var_args, gchar *))) { |
| self = gst_controller_add_property (self, object, name, &ref_existing); |
| } |
| va_end (var_args); |
| |
| if (self) |
| GST_INFO ("controller->ref_count=%d", G_OBJECT (self)->ref_count); |
| return self; |
| } |
| |
| /** |
| * gst_controller_new_list: |
| * @object: the object of which some properties should be controlled |
| * @list: 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_list (GObject * object, GList * list) |
| { |
| GstController *self; |
| gboolean ref_existing = TRUE; |
| gchar *name; |
| GList *node; |
| |
| g_return_val_if_fail (G_IS_OBJECT (object), NULL); |
| |
| GST_INFO ("setting up a new controller"); |
| |
| self = g_object_get_qdata (object, priv_gst_controller_key); |
| /* create GstControlledProperty for each property */ |
| for (node = list; node; node = g_list_next (node)) { |
| name = (gchar *) node->data; |
| self = gst_controller_add_property (self, object, name, &ref_existing); |
| } |
| |
| if (self) |
| GST_INFO ("controller->ref_count=%d", G_OBJECT (self)->ref_count); |
| 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_valist: |
| * @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); |
| //g_signal_handler_disconnect (self->object, prop->notify_handler_id); |
| gst_controlled_property_free (prop); |
| } else { |
| res = FALSE; |
| } |
| g_mutex_unlock (self->lock); |
| } |
| |
| return res; |
| } |
| |
| /** |
| * gst_controller_remove_properties_list: |
| * @self: the controller object from which some properties should be removed |
| * @list: #GList 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_list (GstController * self, GList * list) |
| { |
| gboolean res = TRUE; |
| GstControlledProperty *prop; |
| gchar *name; |
| GList *tmp; |
| |
| g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE); |
| |
| for (tmp = list; tmp; tmp = g_list_next (tmp)) { |
| name = (gchar *) tmp->data; |
| |
| /* 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); |
| //g_signal_handler_disconnect (self->object, prop->notify_handler_id); |
| 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_property_disabled: |
| * @self: the #GstController which should be disabled |
| * @property_name: property to disable |
| * @disabled: boolean that specifies whether to disable the controller |
| * or not. |
| * |
| * This function is used to disable the #GstController on a property for |
| * some time, i.e. gst_controller_sync_values() will do nothing for the |
| * property. |
| * |
| * Since: 0.10.14 |
| */ |
| |
| void |
| gst_controller_set_property_disabled (GstController * self, |
| const gchar * property_name, gboolean disabled) |
| { |
| 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))) { |
| prop->disabled = disabled; |
| } |
| g_mutex_unlock (self->lock); |
| } |
| |
| |
| /** |
| * gst_controller_set_disabled: |
| * @self: the #GstController which should be disabled |
| * @disabled: boolean that specifies whether to disable the controller |
| * or not. |
| * |
| * This function is used to disable all properties of the #GstController |
| * for some time, i.e. gst_controller_sync_values() will do nothing. |
| * |
| * Since: 0.10.14 |
| */ |
| |
| void |
| gst_controller_set_disabled (GstController * self, gboolean disabled) |
| { |
| GList *node; |
| GstControlledProperty *prop; |
| |
| g_return_if_fail (GST_IS_CONTROLLER (self)); |
| |
| g_mutex_lock (self->lock); |
| for (node = self->properties; node; node = node->next) { |
| prop = node->data; |
| prop->disabled = disabled; |
| } |
| g_mutex_unlock (self->lock); |
| } |
| |
| /** |
| * gst_controller_set_control_source: |
| * @self: the controller object |
| * @property_name: name of the property for which the #GstControlSource should be set |
| * @csource: the #GstControlSource that should be used for the property |
| * |
| * Sets the #GstControlSource for @property_name. If there already was a #GstControlSource |
| * for this property it will be unreferenced. |
| * |
| * Returns: %FALSE if the given property isn't handled by the controller or the new #GstControlSource |
| * couldn't be bound to the property, %TRUE if everything worked as expected. |
| * |
| * Since: 0.10.14 |
| */ |
| gboolean |
| gst_controller_set_control_source (GstController * self, |
| const gchar * property_name, GstControlSource * csource) |
| { |
| GstControlledProperty *prop; |
| gboolean ret = FALSE; |
| |
| g_mutex_lock (self->lock); |
| if ((prop = gst_controller_find_controlled_property (self, property_name))) { |
| GstControlSource *old = prop->csource; |
| |
| if (csource && (ret = gst_control_source_bind (csource, prop->pspec))) { |
| g_object_ref (csource); |
| prop->csource = csource; |
| } else if (!csource) { |
| ret = TRUE; |
| prop->csource = NULL; |
| } |
| |
| if (ret && old) |
| g_object_unref (old); |
| } |
| g_mutex_unlock (self->lock); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_controller_get_control_source: |
| * @self: the controller object |
| * @property_name: name of the property for which the #GstControlSource should be get |
| * |
| * Gets the corresponding #GstControlSource for the property. This should be unreferenced |
| * again after use. |
| * |
| * Returns: the #GstControlSource for @property_name or NULL if the property is not |
| * controlled by this controller or no #GstControlSource was assigned yet. |
| * |
| * Since: 0.10.14 |
| */ |
| GstControlSource * |
| gst_controller_get_control_source (GstController * self, |
| const gchar * property_name) |
| { |
| GstControlledProperty *prop; |
| GstControlSource *ret = NULL; |
| |
| 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))) { |
| ret = prop->csource; |
| } |
| g_mutex_unlock (self->lock); |
| |
| if (ret) |
| g_object_ref (ret); |
| |
| return ret; |
| } |
| |
| /** |
| * 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, const 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))) { |
| val = g_new0 (GValue, 1); |
| g_value_init (val, G_PARAM_SPEC_VALUE_TYPE (prop->pspec)); |
| if (prop->csource) { |
| gboolean res; |
| |
| /* get current value via control source */ |
| res = gst_control_source_get_value (prop->csource, timestamp, val); |
| if (!res) { |
| g_free (val); |
| val = NULL; |
| } |
| } else { |
| g_object_get_property (self->object, prop->name, val); |
| } |
| } |
| g_mutex_unlock (self->lock); |
| |
| return val; |
| } |
| |
| /** |
| * gst_controller_suggest_next_sync: |
| * @self: the controller that handles the values |
| * |
| * Returns a suggestion for timestamps where buffers should be split |
| * to get best controller results. |
| * |
| * Returns: Returns the suggested timestamp or %GST_CLOCK_TIME_NONE |
| * if no control-rate was set. |
| * |
| * Since: 0.10.13 |
| */ |
| GstClockTime |
| gst_controller_suggest_next_sync (GstController * self) |
| { |
| GstClockTime ret; |
| |
| g_return_val_if_fail (GST_IS_CONTROLLER (self), GST_CLOCK_TIME_NONE); |
| g_return_val_if_fail (self->priv->control_rate != GST_CLOCK_TIME_NONE, |
| GST_CLOCK_TIME_NONE); |
| |
| g_mutex_lock (self->lock); |
| |
| /* TODO: Implement more logic, depending on interpolation mode |
| * and control points |
| * FIXME: we need playback direction |
| */ |
| ret = self->priv->last_sync + self->priv->control_rate; |
| |
| g_mutex_unlock (self->lock); |
| |
| return ret; |
| } |
| |
| /** |
| * gst_controller_sync_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. |
| * |
| * If this function fails, it is most likely the application developers fault. |
| * Most probably the control sources are not setup correctly. |
| * |
| * Returns: %TRUE if the controller values could be applied to the object |
| * properties, %FALSE otherwise |
| */ |
| gboolean |
| gst_controller_sync_values (GstController * self, GstClockTime timestamp) |
| { |
| GstControlledProperty *prop; |
| GList *node; |
| gboolean ret = TRUE, val_ret; |
| GValue value = { 0, }; |
| |
| g_return_val_if_fail (GST_IS_CONTROLLER (self), FALSE); |
| g_return_val_if_fail (GST_CLOCK_TIME_IS_VALID (timestamp), FALSE); |
| |
| GST_LOG ("sync_values"); |
| |
| g_mutex_lock (self->lock); |
| g_object_freeze_notify (self->object); |
| /* go over the controlled properties of the controller */ |
| for (node = self->properties; node; node = g_list_next (node)) { |
| prop = node->data; |
| |
| if (!prop->csource || prop->disabled) |
| continue; |
| |
| GST_LOG ("property '%s' at ts=%" G_GUINT64_FORMAT, prop->name, timestamp); |
| |
| /* we can make this faster |
| * http://bugzilla.gnome.org/show_bug.cgi?id=536939 |
| */ |
| g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (prop->pspec)); |
| val_ret = gst_control_source_get_value (prop->csource, timestamp, &value); |
| if (G_LIKELY (val_ret)) { |
| /* always set the value for first time, but then only if it changed |
| * this should limit g_object_notify invocations. |
| * FIXME: can we detect negative playback rates? |
| */ |
| if ((timestamp < self->priv->last_sync) || |
| gst_value_compare (&value, &prop->last_value) != GST_VALUE_EQUAL) { |
| g_object_set_property (self->object, prop->name, &value); |
| g_value_copy (&value, &prop->last_value); |
| } |
| } else { |
| GST_DEBUG ("no control value for param %s", prop->name); |
| } |
| g_value_unset (&value); |
| ret &= val_ret; |
| } |
| self->priv->last_sync = timestamp; |
| g_object_thaw_notify (self->object); |
| |
| g_mutex_unlock (self->lock); |
| |
| return ret; |
| } |
| |
| /** |
| * 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. |
| * |
| * All fields of the %GstValueArray in the list must be filled correctly. |
| * Especially the GstValueArray->values arrays must be big enough to keep |
| * the requested amount of values. |
| * |
| * The types of the values in the array are the same as the property's type. |
| * |
| * <note><para>This doesn't modify the controlled GObject properties!</para></note> |
| * |
| * 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 property. |
| * |
| * All fields of @value_array must be filled correctly. Especially the |
| * @value_array->values array must be big enough to keep the requested amount |
| * of values (as indicated by the nbsamples field). |
| * |
| * The type of the values in the array is the same as the property's type. |
| * |
| * <note><para>This doesn't modify the controlled GObject property!</para></note> |
| * |
| * Returns: %TRUE if the given array 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); |
| g_return_val_if_fail (value_array->values, FALSE); |
| |
| g_mutex_lock (self->lock); |
| |
| if ((prop = |
| gst_controller_find_controlled_property (self, |
| value_array->property_name))) { |
| /* get current value_array via control source */ |
| |
| if (!prop->csource) |
| goto out; |
| |
| res = |
| gst_control_source_get_value_array (prop->csource, timestamp, |
| value_array); |
| } |
| |
| out: |
| g_mutex_unlock (self->lock); |
| return res; |
| } |
| |
| /* gobject handling */ |
| |
| static void |
| _gst_controller_get_property (GObject * object, guint property_id, |
| GValue * value, GParamSpec * pspec) |
| { |
| GstController *self = GST_CONTROLLER (object); |
| |
| switch (property_id) { |
| case PROP_CONTROL_RATE:{ |
| /* FIXME: don't change if element is playing, controller works for GObject |
| so this wont work |
| |
| GstState c_state, p_state; |
| GstStateChangeReturn ret; |
| |
| ret = gst_element_get_state (self->object, &c_state, &p_state, 0); |
| if ((ret == GST_STATE_CHANGE_SUCCESS && |
| (c_state == GST_STATE_NULL || c_state == GST_STATE_READY)) || |
| (ret == GST_STATE_CHANGE_ASYNC && |
| (p_state == GST_STATE_NULL || p_state == GST_STATE_READY))) { |
| */ |
| g_value_set_uint64 (value, self->priv->control_rate); |
| /* |
| } |
| else { |
| GST_WARNING ("Changing the control rate is only allowed if the elemnt" |
| " is in NULL or READY"); |
| } |
| */ |
| } |
| break; |
| default:{ |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| } |
| break; |
| } |
| } |
| |
| /* sets the given properties for this object */ |
| static void |
| _gst_controller_set_property (GObject * object, guint property_id, |
| const GValue * value, GParamSpec * pspec) |
| { |
| GstController *self = GST_CONTROLLER (object); |
| |
| switch (property_id) { |
| case PROP_CONTROL_RATE:{ |
| self->priv->control_rate = g_value_get_uint64 (value); |
| } |
| break; |
| default:{ |
| G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); |
| } |
| break; |
| } |
| } |
| |
| static void |
| _gst_controller_dispose (GObject * object) |
| { |
| GstController *self = GST_CONTROLLER (object); |
| |
| if (self->object != NULL) { |
| g_mutex_lock (self->lock); |
| /* free list of properties */ |
| if (self->properties) { |
| GList *node; |
| |
| for (node = self->properties; node; node = g_list_next (node)) { |
| GstControlledProperty *prop = node->data; |
| |
| gst_controlled_property_free (prop); |
| } |
| g_list_free (self->properties); |
| self->properties = NULL; |
| } |
| |
| /* remove controller from object's qdata list */ |
| g_object_set_qdata (self->object, priv_gst_controller_key, NULL); |
| g_object_unref (self->object); |
| self->object = NULL; |
| g_mutex_unlock (self->lock); |
| } |
| |
| if (G_OBJECT_CLASS (parent_class)->dispose) |
| (G_OBJECT_CLASS (parent_class)->dispose) (object); |
| } |
| |
| static void |
| _gst_controller_finalize (GObject * object) |
| { |
| GstController *self = GST_CONTROLLER (object); |
| |
| 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 (); |
| self->priv = |
| G_TYPE_INSTANCE_GET_PRIVATE (self, GST_TYPE_CONTROLLER, |
| GstControllerPrivate); |
| self->priv->last_sync = GST_CLOCK_TIME_NONE; |
| self->priv->control_rate = 100 * GST_MSECOND; |
| } |
| |
| static void |
| _gst_controller_class_init (GstControllerClass * klass) |
| { |
| GObjectClass *gobject_class = G_OBJECT_CLASS (klass); |
| |
| parent_class = g_type_class_peek_parent (klass); |
| g_type_class_add_private (klass, sizeof (GstControllerPrivate)); |
| |
| gobject_class->set_property = _gst_controller_set_property; |
| gobject_class->get_property = _gst_controller_get_property; |
| gobject_class->dispose = _gst_controller_dispose; |
| gobject_class->finalize = _gst_controller_finalize; |
| |
| priv_gst_controller_key = g_quark_from_static_string ("gst::controller"); |
| |
| /* register properties */ |
| g_object_class_install_property (gobject_class, PROP_CONTROL_RATE, |
| g_param_spec_uint64 ("control-rate", |
| "control rate", |
| "Controlled properties will be updated at least every control-rate nanoseconds", |
| 1, G_MAXUINT, 100 * GST_MSECOND, |
| G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); |
| |
| /* register signals */ |
| /* set defaults for overridable methods */ |
| } |
| |
| GType |
| gst_controller_get_type (void) |
| { |
| static volatile gsize type = 0; |
| |
| if (g_once_init_enter (&type)) { |
| GType _type; |
| 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); |
| g_once_init_leave (&type, _type); |
| } |
| return type; |
| } |