diff options
author | Ashok Sidipotu <ashok.sidipotu@collabora.com> | 2023-12-07 14:14:01 +0100 |
---|---|---|
committer | Ashok Sidipotu <ashok.sidipotu@collabora.com> | 2023-12-08 13:12:14 +0100 |
commit | f6eb75678f6c08c4eb3fe232f814834319611ff7 (patch) | |
tree | 0da6c467fb0e2ffcbceb0ccdfe64fc2ef05cd11e /src/audiomixer.c | |
parent | 82c1c0ab04219f9453f1b3a14a9754068e360583 (diff) |
audiomixer: Add gain controls
- Add Equalizer gain controls.
- Add a simple app to test the controls.
Bug-AGL: SPEC-4931
Change-Id: Ib33eb0e829747c401861e99acd67291462ec6a97
Signed-off-by: Ashok Sidipotu <ashok.sidipotu@collabora.com>
Diffstat (limited to 'src/audiomixer.c')
-rw-r--r-- | src/audiomixer.c | 233 |
1 files changed, 226 insertions, 7 deletions
diff --git a/src/audiomixer.c b/src/audiomixer.c index a40a38e..0351738 100644 --- a/src/audiomixer.c +++ b/src/audiomixer.c @@ -7,6 +7,7 @@ #include "audiomixer.h" #include <wp/wp.h> +#include <math.h> #include <pipewire/pipewire.h> struct audiomixer @@ -22,6 +23,8 @@ struct audiomixer gint initialized; WpObjectManager *om; + WpObjectManager *eq_om; + WpPipewireObject *eq_node; WpPlugin *default_nodes_api; WpPlugin *mixer_api; @@ -47,6 +50,11 @@ struct action guint32 id; gboolean mute; } change_mute; + struct { + guint32 id; + gchar *control; + gfloat gain; + } change_gain; }; }; @@ -60,6 +68,86 @@ get_mixer_controls (struct audiomixer * self, guint32 node_id, gdouble * vol, gb g_variant_lookup (v, "mute", "b", mute); } +static gboolean +get_gain (struct audiomixer *self, const char *name, gfloat *gain) +{ + g_autoptr (WpIterator) it = NULL; + g_auto (GValue) val = G_VALUE_INIT; + gchar param_name[20]; + gboolean ret = FALSE; + + snprintf (param_name, sizeof (param_name), "%s:%s", name, "Gain"); + + it = wp_pipewire_object_enum_params_sync (self->eq_node, "Props", NULL); + + for (; it && wp_iterator_next (it, &val); g_value_unset (&val)) { + WpSpaPod *props = g_value_get_boxed (&val); + WpSpaPod *params = NULL; + g_autoptr (WpIterator) it1 = NULL; + g_auto (GValue) val1 = G_VALUE_INIT; + gboolean param_found = FALSE; + + if (!wp_spa_pod_get_object (props, NULL, "params", "T", ¶ms, NULL)) + continue; + + if (!wp_spa_pod_is_struct (params)) + continue; + + /* iterate through the params structure */ + for (it1 = wp_spa_pod_new_iterator (params); + it1 && wp_iterator_next (it1, &val1); + g_value_unset (&val1)) { + + WpSpaPod *sparams = g_value_get_boxed (&val1); + + if (sparams && wp_spa_pod_is_string (sparams)) { + const gchar *token = NULL; + wp_spa_pod_get_string (sparams, &token); + if (g_str_equal (token, param_name)) { + /* read the next field to get the gain value */ + param_found = TRUE; + continue; + } + } + else if (sparams && param_found && wp_spa_pod_is_float (sparams)) { + if (wp_spa_pod_get_float (sparams, gain)) { + g_debug ("gain for control(%s) is %f", param_name, *gain); + ret = TRUE; + break; + } + } + + } + + if (param_found) + break; + + } + return ret; +} + +static void +add_eq_control (struct audiomixer *self, const char *name, guint32 node_id) +{ + struct mixer_control_impl *mixctl = NULL; + gfloat gain = 0.0; + + /* get current gain */ + if (!get_gain (self, name, &gain)) { + g_warning ("failed to get the gain value"); + return; + } + + /* create the control */ + mixctl = g_new0 (struct mixer_control_impl, 1); + snprintf (mixctl->pub.name, sizeof (mixctl->pub.name), "%s", name); + mixctl->pub.gain = gain; + mixctl->node_id = node_id; + g_ptr_array_add (self->mixer_controls, mixctl); + + g_debug ("added (%s) eq control and its gain is %f", mixctl->pub.name, gain); +} + static void add_control (struct audiomixer *self, const char *name, guint32 node_id) { @@ -69,7 +157,8 @@ add_control (struct audiomixer *self, const char *name, guint32 node_id) /* get current values */ if (!get_mixer_controls (self, node_id, &volume, &mute)) { - g_warning ("failed to get object controls when populating controls"); + g_warning ("failed to get object controls when populating controls for %s", + name); return; } @@ -81,7 +170,7 @@ add_control (struct audiomixer *self, const char *name, guint32 node_id) mixctl->node_id = node_id; g_ptr_array_add (self->mixer_controls, mixctl); - g_debug ("added control %s", mixctl->pub.name); + g_debug ("added control %s its volume is %f", mixctl->pub.name, volume); } static void @@ -154,6 +243,14 @@ rescan_controls (struct audiomixer * self) add_control (self, name, id); } + if (self->eq_node) { + id = wp_proxy_get_bound_id (WP_PROXY (self->eq_node)); + if (id != 0 && id != (guint32)-1) { + add_eq_control (self, "bass", id); + add_eq_control (self, "treble", id); + } + } + /* notify subscribers */ if (self->events && self->events->controls_changed) self->events->controls_changed (self->events_data); @@ -169,8 +266,10 @@ on_default_nodes_activated (WpObject * p, GAsyncResult * res, struct audiomixer } if (wp_object_get_active_features (WP_OBJECT (self->mixer_api)) - & WP_PLUGIN_FEATURE_ENABLED) - wp_core_install_object_manager (self->core, self->om); + & WP_PLUGIN_FEATURE_ENABLED) { + wp_core_install_object_manager (self->core, self->eq_om); + wp_core_install_object_manager (self->core, self->om); + } g_signal_connect_swapped (self->default_nodes_api, "changed", (GCallback) rescan_controls, self); @@ -185,16 +284,73 @@ on_mixer_activated (WpObject * p, GAsyncResult * res, struct audiomixer * self) } if (wp_object_get_active_features (WP_OBJECT (self->default_nodes_api)) - & WP_PLUGIN_FEATURE_ENABLED) - wp_core_install_object_manager (self->core, self->om); + & WP_PLUGIN_FEATURE_ENABLED) { + wp_core_install_object_manager (self->core, self->om); + wp_core_install_object_manager (self->core, self->eq_om); + } g_signal_connect_swapped (self->mixer_api, "changed", (GCallback) volume_changed, self); } static void -on_core_connected (struct audiomixer * self) +on_eq_params_changed (WpPipewireObject *obj, const gchar *param_name, + struct audiomixer * self) { + gfloat gain = 0.0; + guint32 node_id = wp_proxy_get_bound_id (WP_PROXY (obj)); + + if (!g_str_equal (param_name, "Props")) + return; + + for (guint i = 0; i < self->mixer_controls->len; i++) { + struct mixer_control_impl *ctl; + guint change_mask = 0; + + ctl = g_ptr_array_index (self->mixer_controls, i); + + if (ctl->node_id != node_id) + continue; + + if (!get_gain (self, ctl->pub.name, &gain)) { + g_warning ("failed to get cached gain value"); + return; + } + + if (!(fabs (ctl->pub.gain - gain) < 0.000001)) { + /* if gain changed */ + ctl->pub.gain = gain; + change_mask |= MIXER_CONTROL_CHANGE_FLAG_GAIN; + if (self->events && self->events->value_changed) { + self->events->value_changed (self->events_data, change_mask, &ctl->pub); + } + } + + break; + } +} +static void +on_eq_added (WpObjectManager *om, WpPipewireObject *node, + struct audiomixer *self) +{ + self->eq_node = node; + g_signal_connect (node, "params-changed", G_CALLBACK (on_eq_params_changed), + self); + rescan_controls (self); +} + +static void +on_eq_removed (WpObjectManager *om, WpPipewireObject *node, struct audiomixer + *self) +{ + self->eq_node = NULL; + rescan_controls (self); +} + +static void +on_core_connected (struct audiomixer *self) +{ + self->om = wp_object_manager_new (); wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, @@ -204,6 +360,23 @@ on_core_connected (struct audiomixer * self) g_signal_connect_swapped (self->om, "objects-changed", (GCallback) rescan_controls, self); + self->eq_node = NULL; + self->eq_om = wp_object_manager_new (); + /* + * "eq-sink" name matches with the name of sink node loaded by the equalizer + * module present in the config. + */ + wp_object_manager_add_interest (self->eq_om, WP_TYPE_NODE, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, PW_KEY_NODE_NAME, "=s", "eq-sink", + WP_CONSTRAINT_TYPE_PW_PROPERTY, PW_KEY_MEDIA_CLASS, "=s", "Audio/Sink", + NULL); + wp_object_manager_request_object_features (self->eq_om, + WP_TYPE_NODE, WP_OBJECT_FEATURES_ALL); + g_signal_connect (self->eq_om, "object-added", G_CALLBACK (on_eq_added), + self); + g_signal_connect (self->eq_om, "object-removed", G_CALLBACK (on_eq_removed), + self); + wp_object_activate (WP_OBJECT (self->default_nodes_api), WP_PLUGIN_FEATURE_ENABLED, NULL, (GAsyncReadyCallback) on_default_nodes_activated, self); @@ -218,6 +391,7 @@ on_core_disconnected (struct audiomixer * self) { g_ptr_array_set_size (self->mixer_controls, 0); g_clear_object (&self->om); + g_clear_object (&self->eq_om); g_signal_handlers_disconnect_by_data (self->default_nodes_api, self); g_signal_handlers_disconnect_by_data (self->mixer_api, self); wp_object_deactivate (WP_OBJECT (self->default_nodes_api), WP_PLUGIN_FEATURE_ENABLED); @@ -461,3 +635,48 @@ audiomixer_change_mute(struct audiomixer *self, wp_core_idle_add (self->core, NULL, (GSourceFunc) do_change_mute, action, g_free); } + +static gboolean +do_change_gain (struct action * action) +{ + struct audiomixer *self = action->audiomixer; + gboolean ret = FALSE; + gchar name[20]; + g_autoptr (WpSpaPodBuilder) s = wp_spa_pod_builder_new_struct (); + + snprintf (name, sizeof (name), "%s:%s", action->change_gain.control, "Gain"); + wp_spa_pod_builder_add_string (s, name); + wp_spa_pod_builder_add_float (s, action->change_gain.gain); + + g_autoptr (WpSpaPod) props = + wp_spa_pod_new_object ("Spa:Pod:Object:Param:Props", "Props", + "params", "P", wp_spa_pod_builder_end (s), NULL); + + ret = wp_pipewire_object_set_param (self->eq_node, "Props", 0, + g_steal_pointer (&props)); + + if(!ret) + g_warning ("set gain failed"); + + return G_SOURCE_REMOVE; +} + +void +audiomixer_change_gain(struct audiomixer *self, + const struct mixer_control *control, + float gain) +{ + const struct mixer_control_impl *impl = control; + struct action * action; + + g_return_if_fail (self->initialized == 1); + + /* schedule the action to run on the audiomixer thread */ + action = g_new0 (struct action, 1); + action->audiomixer = self; + action->change_gain.id = impl->node_id; + action->change_gain.gain = gain; + action->change_gain.control = impl->pub.name; + wp_core_idle_add (self->core, NULL, (GSourceFunc) do_change_gain, action, + g_free); +} |