From aa5b16f8df38d544e27bc827c267b71da66f514d Mon Sep 17 00:00:00 2001 From: George Kiagiadakis <george.kiagiadakis@collabora.com> Date: Tue, 18 Jun 2019 19:58:42 +0300 Subject: [PATCH] module-mixer: implement the "Mixer/Audio" endpoint provider This provides high level volume controls for the AGL audiomixer binding and the applications using it. --- modules/meson.build | 11 ++ modules/module-mixer.c | 282 +++++++++++++++++++++++++++++++++ modules/module-simple-policy.c | 10 +- src/wireplumber.conf | 1 + 4 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 modules/module-mixer.c diff --git a/modules/meson.build b/modules/meson.build index e9d644e1..5db780e6 100644 --- a/modules/meson.build +++ b/modules/meson.build @@ -3,6 +3,17 @@ common_c_args = [ '-DG_LOG_USE_STRUCTURED', ] +shared_library( + 'wireplumber-module-mixer', + [ + 'module-mixer.c' + ], + c_args : [common_c_args, '-DG_LOG_DOMAIN="m-mixer"'], + install : true, + install_dir : wireplumber_module_dir, + dependencies : [wp_dep], +) + shared_library( 'wireplumber-module-pipewire', [ diff --git a/modules/module-mixer.c b/modules/module-mixer.c new file mode 100644 index 00000000..1979c65d --- /dev/null +++ b/modules/module-mixer.c @@ -0,0 +1,282 @@ +/* WirePlumber + * + * Copyright © 2019 Collabora Ltd. + * @author George Kiagiadakis <george.kiagiadakis@collabora.com> + * + * SPDX-License-Identifier: MIT + */ + +#include <wp/wp.h> + +G_DECLARE_FINAL_TYPE (WpMixerEndpoint, + mixer_endpoint, WP, MIXER_ENDPOINT, WpEndpoint) + +static const char * streams[] = { + "Master" +}; +#define N_STREAMS (sizeof (streams) / sizeof (const char *)) + +enum { + CONTROL_VOLUME = 0, + CONTROL_MUTE, + N_CONTROLS, +}; + +#define MIXER_CONTROL_ID(stream_id, control_enum) \ + (stream_id * N_CONTROLS + control_enum) + +struct group +{ + WpMixerEndpoint *mixer; + const gchar *name; + guint32 mixer_stream_id; + + GWeakRef backend; + guint32 backend_ctl_ids[N_CONTROLS]; +}; + +struct _WpMixerEndpoint +{ + WpEndpoint parent; + struct group groups[N_STREAMS]; +}; + +G_DEFINE_TYPE (WpMixerEndpoint, mixer_endpoint, WP_TYPE_ENDPOINT) + +static void +backend_value_changed (WpEndpoint *backend, guint32 control_id, + struct group *group) +{ + gint i; + for (i = 0; i < N_CONTROLS; i++) { + if (control_id == group->backend_ctl_ids[i]) { + g_signal_emit_by_name (group->mixer, "notify-control-value", + MIXER_CONTROL_ID (group->mixer_stream_id, i)); + } + } +} + +static void +group_find_backend (struct group *group, WpCore *core) +{ + g_autoptr (WpEndpoint) backend = NULL; + g_autoptr (WpEndpoint) old_backend = NULL; + guint32 stream_id; + GVariantDict d; + + /* find the backend */ + g_variant_dict_init (&d, NULL); + g_variant_dict_insert (&d, "action", "s", "mixer"); + g_variant_dict_insert (&d, "media.class", "s", "Audio/Sink"); + g_variant_dict_insert (&d, "media.role", "s", group->name); + + backend = wp_policy_find_endpoint (core, g_variant_dict_end (&d), + &stream_id); + if (!backend) + return; + + /* we found the same backend as before - no need to continue */ + old_backend = g_weak_ref_get (&group->backend); + if (old_backend && old_backend == backend) + return; + + /* attach to the backend */ + g_weak_ref_set (&group->backend, backend); + group->backend_ctl_ids[CONTROL_VOLUME] = wp_endpoint_find_control (backend, + stream_id, "volume"); + group->backend_ctl_ids[CONTROL_MUTE] = wp_endpoint_find_control (backend, + stream_id, "mute"); + + /* notify of changed values */ + g_signal_emit_by_name (group->mixer, "notify-control-value", + MIXER_CONTROL_ID (group->mixer_stream_id, CONTROL_VOLUME)); + g_signal_emit_by_name (group->mixer, "notify-control-value", + MIXER_CONTROL_ID (group->mixer_stream_id, CONTROL_MUTE)); + + /* watch for further value changes in the backend */ + g_signal_connect (backend, "notify-control-value", + (GCallback) backend_value_changed, group); +} + +static void +policy_changed (WpPolicyManager *mgr, WpMixerEndpoint * self) +{ + int i; + g_autoptr (WpCore) core = wp_endpoint_get_core (WP_ENDPOINT (self)); + + for (i = 0; i < N_STREAMS; i++) { + group_find_backend (&self->groups[i], core); + } +} + +static void +mixer_endpoint_init (WpMixerEndpoint * self) +{ +} + +static void +mixer_endpoint_constructed (GObject * object) +{ + WpMixerEndpoint *self = WP_MIXER_ENDPOINT (object); + g_autoptr (WpCore) core = NULL; + g_autoptr (WpPolicyManager) policymgr = NULL; + GVariantDict d; + gint i; + + core = wp_endpoint_get_core (WP_ENDPOINT (self)); + policymgr = wp_policy_manager_get_instance (core); + g_signal_connect_object (policymgr, "policy-changed", + (GCallback) policy_changed, self, 0); + + for (i = 0; i < N_STREAMS; i++) { + g_variant_dict_init (&d, NULL); + g_variant_dict_insert (&d, "id", "u", i); + g_variant_dict_insert (&d, "name", "s", streams[i]); + wp_endpoint_register_stream (WP_ENDPOINT (self), g_variant_dict_end (&d)); + + g_variant_dict_init (&d, NULL); + g_variant_dict_insert (&d, "id", "u", MIXER_CONTROL_ID (i, CONTROL_VOLUME)); + g_variant_dict_insert (&d, "stream-id", "u", i); + g_variant_dict_insert (&d, "name", "s", "volume"); + g_variant_dict_insert (&d, "type", "s", "d"); + g_variant_dict_insert (&d, "range", "(dd)", 0.0, 1.0); + g_variant_dict_insert (&d, "default-value", "d", 1.0); + wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d)); + + g_variant_dict_init (&d, NULL); + g_variant_dict_insert (&d, "id", "u", MIXER_CONTROL_ID (i, CONTROL_MUTE)); + g_variant_dict_insert (&d, "stream-id", "u", i); + g_variant_dict_insert (&d, "name", "s", "mute"); + g_variant_dict_insert (&d, "type", "s", "b"); + g_variant_dict_insert (&d, "default-value", "b", FALSE); + wp_endpoint_register_control (WP_ENDPOINT (self), g_variant_dict_end (&d)); + + self->groups[i].mixer = self; + self->groups[i].name = streams[i]; + self->groups[i].mixer_stream_id = i; + g_weak_ref_init (&self->groups[i].backend, NULL); + + group_find_backend (&self->groups[i], core); + } + + G_OBJECT_CLASS (mixer_endpoint_parent_class)->constructed (object); +} + +static void +mixer_endpoint_finalize (GObject * object) +{ + WpMixerEndpoint *self = WP_MIXER_ENDPOINT (object); + gint i; + + for (i = 0; i < N_STREAMS; i++) { + g_autoptr (WpEndpoint) backend = g_weak_ref_get (&self->groups[i].backend); + if (backend) + g_signal_handlers_disconnect_by_data (backend, &self->groups[i]); + g_weak_ref_clear (&self->groups[i].backend); + } + + G_OBJECT_CLASS (mixer_endpoint_parent_class)->finalize (object); +} + +static GVariant * +mixer_endpoint_get_control_value (WpEndpoint * ep, guint32 control_id) +{ + WpMixerEndpoint *self = WP_MIXER_ENDPOINT (ep); + guint32 stream_id; + struct group *group; + g_autoptr (WpEndpoint) backend = NULL; + + stream_id = control_id / N_CONTROLS; + control_id = control_id % N_CONTROLS; + + if (stream_id >= N_STREAMS) { + g_warning ("Mixer:%p Invalid stream id %u", self, stream_id); + return NULL; + } + + group = &self->groups[stream_id]; + backend = g_weak_ref_get (&group->backend); + + /* if there is no backend, return the default value */ + if (!backend) { + g_warning ("Mixer:%p Cannot get control value - no backend", self); + + switch (control_id) { + case CONTROL_VOLUME: + return g_variant_new_double (1.0); + case CONTROL_MUTE: + return g_variant_new_boolean (FALSE); + default: + g_assert_not_reached (); + } + } + + /* otherwise return the value provided by the backend */ + return wp_endpoint_get_control_value (backend, + group->backend_ctl_ids[control_id]); +} + +static gboolean +mixer_endpoint_set_control_value (WpEndpoint * ep, guint32 control_id, + GVariant * value) +{ + WpMixerEndpoint *self = WP_MIXER_ENDPOINT (ep); + guint32 stream_id; + struct group *group; + g_autoptr (WpEndpoint) backend = NULL; + + stream_id = control_id / N_CONTROLS; + control_id = control_id % N_CONTROLS; + + if (stream_id >= N_STREAMS) { + g_warning ("Mixer:%p Invalid stream id %u", self, stream_id); + return FALSE; + } + + group = &self->groups[stream_id]; + backend = g_weak_ref_get (&group->backend); + + if (!backend) { + g_warning ("Mixer:%p Cannot set control value - no backend", self); + return FALSE; + } + + return wp_endpoint_set_control_value (backend, + group->backend_ctl_ids[control_id], value); +} + +static void +mixer_endpoint_class_init (WpMixerEndpointClass * klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + WpEndpointClass *endpoint_class = (WpEndpointClass *) klass; + + object_class->constructed = mixer_endpoint_constructed; + object_class->finalize = mixer_endpoint_finalize; + + endpoint_class->get_control_value = mixer_endpoint_get_control_value; + endpoint_class->set_control_value = mixer_endpoint_set_control_value; +} + +static void +remote_connected (WpRemote *remote, WpRemoteState state, WpCore *core) +{ + g_autoptr (WpEndpoint) ep = g_object_new (mixer_endpoint_get_type (), + "core", core, + "name", "Mixer", + "media-class", "Mixer/Audio", + NULL); + wp_endpoint_register (ep); +} + +void +wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args) +{ + WpRemote *remote; + + remote = wp_core_get_global (core, WP_GLOBAL_REMOTE_PIPEWIRE); + g_return_if_fail (remote != NULL); + + g_signal_connect (remote, "state-changed::connected", + (GCallback) remote_connected, core); +} diff --git a/modules/module-simple-policy.c b/modules/module-simple-policy.c index 79807bd4..9e053d67 100644 --- a/modules/module-simple-policy.c +++ b/modules/module-simple-policy.c @@ -242,12 +242,16 @@ simple_policy_find_endpoint (WpPolicy *policy, GVariant *props, { g_autoptr (WpCore) core = NULL; g_autoptr (GPtrArray) ptr_array = NULL; + const char *action = NULL; const char *media_class = NULL; + const char *role = NULL; WpEndpoint *ep; int i; core = wp_policy_get_core (policy); + g_variant_lookup (props, "action", "&s", &action); + /* Get all the endpoints with the specific media class*/ g_variant_lookup (props, "media.class", "&s", &media_class); ptr_array = wp_endpoint_find (core, media_class); @@ -256,7 +260,11 @@ simple_policy_find_endpoint (WpPolicy *policy, GVariant *props, /* TODO: for now we statically return the first stream * we should be looking into the media.role eventually */ - *stream_id = 0; + g_variant_lookup (props, "media.role", "&s", &role); + if (!g_strcmp0 (action, "mixer") && !g_strcmp0 (role, "Master")) + *stream_id = WP_STREAM_ID_NONE; + else + *stream_id = 0; /* Find and return the "selected" endpoint */ /* FIXME: fix the endpoint API, this is terrible */ diff --git a/src/wireplumber.conf b/src/wireplumber.conf index 9748aa75..75967301 100644 --- a/src/wireplumber.conf +++ b/src/wireplumber.conf @@ -2,3 +2,4 @@ load-module C libwireplumber-module-pipewire load-module C libwireplumber-module-pw-audio-softdsp-endpoint load-module C libwireplumber-module-pw-alsa-udev load-module C libwireplumber-module-simple-policy +load-module C libwireplumber-module-mixer -- GitLab