aboutsummaryrefslogtreecommitdiffstats
path: root/src/audiomixer.c
diff options
context:
space:
mode:
authorAshok Sidipotu <ashok.sidipotu@collabora.com>2023-12-07 14:14:01 +0100
committerAshok Sidipotu <ashok.sidipotu@collabora.com>2023-12-08 13:12:14 +0100
commitf6eb75678f6c08c4eb3fe232f814834319611ff7 (patch)
tree0da6c467fb0e2ffcbceb0ccdfe64fc2ef05cd11e /src/audiomixer.c
parent82c1c0ab04219f9453f1b3a14a9754068e360583 (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.c233
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", &params, 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);
+}