Files
wireplumber/modules/module-mixer.c
George Kiagiadakis a63f2bb99b lib/wp: merge both WpRemote & WpRemotePipewire in WpCore
In practice we always create a remote and connect to pipewire.
Any other scenario is invalid, therefore, it is not justified
to be confused with so many classes for such small functionality.
This simplifies a lot the modules code.

Also, this commit exposes the pw_core and pw_remote objects
out of WpCore. This is in practice useful when dealing with low-level
pw and spa factories, which are used in the monitors. Let's not
add API wrappers for everything... Bindings will never use this
functionality anyway, since it depends on low level pipewire C API.
2019-09-07 17:55:46 +03:00

323 lines
9.2 KiB
C

/* 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)
enum {
PROP_0,
PROP_STREAMS,
};
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;
gchar *name;
guint32 mixer_stream_id;
GWeakRef backend;
guint32 backend_ctl_ids[N_CONTROLS];
};
struct _WpMixerEndpoint
{
WpEndpoint parent;
GVariant *streams;
GArray *groups;
};
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", "Alsa/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 < self->groups->len; i++) {
group_find_backend (&g_array_index (self->groups, struct group, i), core);
}
}
static void
mixer_endpoint_init (WpMixerEndpoint * self)
{
self->groups = g_array_new (FALSE, TRUE, sizeof (struct group));
}
static void
mixer_endpoint_constructed (GObject * object)
{
WpMixerEndpoint *self = WP_MIXER_ENDPOINT (object);
g_autoptr (WpCore) core = NULL;
g_autoptr (WpPolicyManager) policymgr = NULL;
struct group empty_group = {0};
GVariantDict d;
GVariantIter iter;
gint i;
gchar *stream;
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);
g_variant_iter_init (&iter, self->streams);
for (i = 0; g_variant_iter_next (&iter, "s", &stream); i++) {
struct group *group;
g_array_append_val (self->groups, empty_group);
group = &g_array_index (self->groups, struct group, i);
g_variant_dict_init (&d, NULL);
g_variant_dict_insert (&d, "id", "u", i);
g_variant_dict_insert (&d, "name", "s", stream);
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));
group->mixer = self;
group->name = stream;
group->mixer_stream_id = i;
g_weak_ref_init (&group->backend, NULL);
group_find_backend (group, 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 < self->groups->len; i++) {
struct group *group = &g_array_index (self->groups, struct group, i);
g_autoptr (WpEndpoint) backend = g_weak_ref_get (&group->backend);
if (backend)
g_signal_handlers_disconnect_by_data (backend, group);
g_weak_ref_clear (&group->backend);
g_free (group->name);
}
g_clear_pointer (&self->groups, g_array_unref);
g_clear_pointer (&self->streams, g_variant_unref);
G_OBJECT_CLASS (mixer_endpoint_parent_class)->finalize (object);
}
static void
mixer_endpoint_set_property (GObject * object, guint property_id,
const GValue * value, GParamSpec * pspec)
{
WpMixerEndpoint *self = WP_MIXER_ENDPOINT (object);
switch (property_id) {
case PROP_STREAMS:
self->streams = g_value_dup_variant (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
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 >= self->groups->len) {
g_warning ("Mixer:%p Invalid stream id %u", self, stream_id);
return NULL;
}
group = &g_array_index (self->groups, struct group, stream_id);
backend = g_weak_ref_get (&group->backend);
/* if there is no backend, return the default value */
if (!backend) {
g_debug ("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 >= self->groups->len) {
g_warning ("Mixer:%p Invalid stream id %u", self, stream_id);
return FALSE;
}
group = &g_array_index (self->groups, struct group, stream_id);
backend = g_weak_ref_get (&group->backend);
if (!backend) {
g_debug ("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->set_property = mixer_endpoint_set_property;
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;
g_object_class_install_property (object_class, PROP_STREAMS,
g_param_spec_variant ("streams", "streams",
"The stream names for the streams to create",
G_VARIANT_TYPE ("as"), NULL,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
}
static void
remote_connected (WpCore *core, WpRemoteState state, GVariant *streams)
{
g_autoptr (WpEndpoint) ep = g_object_new (mixer_endpoint_get_type (),
"core", core,
"name", "Mixer",
"media-class", "Mixer/Audio",
"streams", streams,
NULL);
wp_endpoint_register (ep);
}
void
wireplumber__module_init (WpModule * module, WpCore * core, GVariant * args)
{
GVariant *streams;
streams = g_variant_lookup_value (args, "streams", G_VARIANT_TYPE ("as"));
g_signal_connect_data (core, "remote-state-changed::connected",
(GCallback) remote_connected, streams, (GClosureNotify) g_variant_unref,
0);
}