pulseaudio/0010-alsa-ucm-Support-Playback-CaptureVolume.patch
Takashi Iwai 5dabf3bcba Accepting request 774841 from home:tiwai:branches:multimedia:libs
- Backport upstream fixes / enhancements about alsa modules:
  mainly for UCM support (boo#1160914):
  0001-alsa-mixer-path-test-Hide-unused-functions-when-buil.patch
  0002-alsa-mixer-recognize-the-Speaker-Jack-control.patch
  0003-alsa-mixer-add-support-for-SteelSeries-Arctis-Pro-20.patch
  0004-alsa-mixer-Add-support-for-SteelSeries-Arctis-5-2019.patch
  0005-alsa-mixer-add-support-for-LucidSound-LS31-and-creat.patch
  0006-alsa-ucm-use-ucm2-name-for-the-direct-card-index-ope.patch
  0007-alsa-ucm-add-mixer-IDs-to-ucm_items.patch
  0008-alsa-mixer-handle-the-index-for-ALSA-mixer-element-i.patch
  0009-alsa-mixer-improve-alsa_id_decode-function.patch
  0010-alsa-ucm-Support-Playback-CaptureVolume.patch
  0011-alsa-ucm-Fix-volume-control-based-on-review.patch
  0012-alsa-ucm-use-the-correct-mixer-identifiers-as-first.patch
  0013-alsa-ucm-add-support-for-master-volume.patch
  0014-alsa-ucm-split-correctly-JackHWMute-device-names.patch
  0015-alsa-ucm-fix-parsing-for-JackControl.patch
  0016-alsa-ucm-add-comments-to-ucm_get_mixer_id.patch
  0017-alsa-ucm-validate-access-to-PA_DEVICE_PORT_DATA.patch
  0018-alsa-Skip-resume-PCM-if-hardware-doesn-t-support-it.patch
  0019-alsa-ucm-parse-correctly-the-device-values.patch
  0020-alsa-ucm-do-not-try-to-use-UCM-device-name-as-jack-n.patch
  0021-alsa-util-do-not-try-to-guess-the-mixer-name-from-th.patch
  0022-alsa-ucm-add-control-and-mixer-device-items.patch
  0023-alsa-ucm-get-the-mixer-names-from-ucm-don-t-guess.patch
  0024-alsa-ucm-use-the-proper-mixer-name-for-ucm-pcm-sink-.patch
  0025-alsa-mixer-handle-interface-type-CARD-PCM-for-mixer-.patch
  0026-alsa-mixer-Add-the-ability-to-pass-the-intended-role.patch
  0027-alsa-mixer-Set-the-intended-role-of-Steelseries-Arct.patch
  0028-alsa-rewrite-mixer-open-close-cache-mixer-accesses-i.patch

OBS-URL: https://build.opensuse.org/request/show/774841
OBS-URL: https://build.opensuse.org/package/show/multimedia:libs/pulseaudio?expand=0&rev=217
2020-02-17 11:47:50 +00:00

796 lines
30 KiB
Diff

From 3dfccada466bef64f73ef9be3d94eaee7b6f9a60 Mon Sep 17 00:00:00 2001
From: Arun Raghavan <git@arunraghavan.net>
Date: Tue, 3 May 2016 18:22:10 +0530
Subject: [PATCH] alsa-ucm: Support Playback/CaptureVolume
This allows us to support the PlaybackVolume and CaptureVolume commands
in UCM, specifying a mixer control to use for hardware volume control.
This only works with ports corresponding to single devices at the
moment, and doesn't support stacking controls for combination ports.
The configuration is intended to provide a control (like Headphone
Playback Volume), but we try to resolve to a simple mixer control
(Headphone) to reuse existing volume paths.
On the UCM side, this also requires that when disabling the device for
the port, the volume should be reset to some default.
When enabling/disabling combination devices, things are a bit iffy since
we have no way to reset the volume before switching to a combination
device. It would be nice to have a combination-transition-sequence
command in UCM to handle this and other similar cases.
PlaybackSwitch and CaptureSwitch are yet to be implemented.
---
src/modules/alsa/alsa-sink.c | 70 +++++++++++----
src/modules/alsa/alsa-source.c | 68 +++++++++++----
src/modules/alsa/alsa-ucm.c | 165 +++++++++++++++++++++++++++++-------
src/modules/alsa/alsa-ucm.h | 26 +++++-
src/modules/alsa/module-alsa-card.c | 4 +-
src/pulsecore/core-util.c | 26 ++++++
src/pulsecore/core-util.h | 2 +
7 files changed, 295 insertions(+), 66 deletions(-)
diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index 4b46708ce4a3..08d4d1f38b80 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -1598,7 +1598,7 @@ static void sink_set_mute_cb(pa_sink *s) {
static void mixer_volume_init(struct userdata *u) {
pa_assert(u);
- if (!u->mixer_path->has_volume) {
+ if (!u->mixer_path || !u->mixer_path->has_volume) {
pa_sink_set_write_volume_callback(u->sink, NULL);
pa_sink_set_get_volume_callback(u->sink, NULL);
pa_sink_set_set_volume_callback(u->sink, NULL);
@@ -1633,7 +1633,7 @@ static void mixer_volume_init(struct userdata *u) {
pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
}
- if (!u->mixer_path->has_mute) {
+ if (!u->mixer_path || !u->mixer_path->has_mute) {
pa_sink_set_get_mute_callback(u->sink, NULL);
pa_sink_set_set_mute_callback(u->sink, NULL);
pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@@ -1646,11 +1646,31 @@ static void mixer_volume_init(struct userdata *u) {
static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) {
struct userdata *u = s->userdata;
+ pa_alsa_ucm_port_data *data;
+
+ data = PA_DEVICE_PORT_DATA(p);
pa_assert(u);
pa_assert(p);
pa_assert(u->ucm_context);
+ u->mixer_path = data->path;
+ mixer_volume_init(u);
+
+ if (u->mixer_path) {
+ pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, s->muted);
+
+ if (s->set_mute)
+ s->set_mute(s);
+ if (s->flags & PA_SINK_DEFERRED_VOLUME) {
+ if (s->write_volume)
+ s->write_volume(s);
+ } else {
+ if (s->set_volume)
+ s->set_volume(s);
+ }
+ }
+
return pa_alsa_ucm_set_port(u->ucm_context, p, true);
}
@@ -2079,6 +2099,11 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
return;
}
+ if (u->ucm_context) {
+ /* We just want to open the device */
+ return;
+ }
+
if (element) {
if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_OUTPUT)))
@@ -2116,16 +2141,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
return 0;
if (u->sink->active_port) {
- pa_alsa_port_data *data;
+ if (!u->ucm_context) {
+ pa_alsa_port_data *data;
- /* We have a list of supported paths, so let's activate the
- * one that has been chosen as active */
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
+
+ data = PA_DEVICE_PORT_DATA(u->sink->active_port);
+ u->mixer_path = data->path;
+
+ pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
+ } else {
+ pa_alsa_ucm_port_data *data;
- data = PA_DEVICE_PORT_DATA(u->sink->active_port);
- u->mixer_path = data->path;
+ /* First activate the port on the UCM side */
+ if (pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
+ return -1;
- pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->sink->muted);
+ data = PA_DEVICE_PORT_DATA(u->sink->active_port);
+ /* Now activate volume controls, if any */
+ if (data->path) {
+ u->mixer_path = data->path;
+ pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->sink->muted);
+ }
+ }
} else {
if (!u->mixer_path && u->mixer_path_set)
@@ -2135,7 +2175,6 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
/* Hmm, we have only a single path, then let's activate it */
pa_alsa_path_select(u->mixer_path, u->mixer_path->settings, u->mixer_handle, u->sink->muted);
-
} else
return 0;
}
@@ -2466,8 +2505,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
- if (!u->ucm_context)
- find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_sink_new_data_init(&data);
data.driver = driver;
@@ -2524,7 +2562,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
}
if (u->ucm_context)
- pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card);
+ pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, true, card, u->pcm_handle, ignore_dB);
else if (u->mixer_path_set)
pa_alsa_add_ports(&data, u->mixer_path_set, card);
@@ -2598,10 +2636,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca
if (update_sw_params(u, false) < 0)
goto fail;
- if (u->ucm_context) {
- if (u->sink->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->sink->active_port, true) < 0)
- goto fail;
- } else if (setup_mixer(u, ignore_dB) < 0)
+ if (setup_mixer(u, ignore_dB) < 0)
goto fail;
pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2725,7 +2760,8 @@ static void userdata_free(struct userdata *u) {
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
- if (u->mixer_path && !u->mixer_path_set)
+ /* Only free the mixer_path if the sink owns it */
+ if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
pa_alsa_path_free(u->mixer_path);
if (u->mixer_handle)
diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c
index c8bf649e1730..657ed5aeda11 100644
--- a/src/modules/alsa/alsa-source.c
+++ b/src/modules/alsa/alsa-source.c
@@ -1469,7 +1469,7 @@ static void source_set_mute_cb(pa_source *s) {
static void mixer_volume_init(struct userdata *u) {
pa_assert(u);
- if (!u->mixer_path->has_volume) {
+ if (!u->mixer_path || !u->mixer_path->has_volume) {
pa_source_set_write_volume_callback(u->source, NULL);
pa_source_set_get_volume_callback(u->source, NULL);
pa_source_set_set_volume_callback(u->source, NULL);
@@ -1504,7 +1504,7 @@ static void mixer_volume_init(struct userdata *u) {
pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
}
- if (!u->mixer_path->has_mute) {
+ if (!u->mixer_path || !u->mixer_path->has_mute) {
pa_source_set_get_mute_callback(u->source, NULL);
pa_source_set_set_mute_callback(u->source, NULL);
pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
@@ -1517,11 +1517,31 @@ static void mixer_volume_init(struct userdata *u) {
static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) {
struct userdata *u = s->userdata;
+ pa_alsa_ucm_port_data *data;
+
+ data = PA_DEVICE_PORT_DATA(p);
pa_assert(u);
pa_assert(p);
pa_assert(u->ucm_context);
+ u->mixer_path = data->path;
+ mixer_volume_init(u);
+
+ if (u->mixer_path) {
+ pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, s->muted);
+
+ if (s->set_mute)
+ s->set_mute(s);
+ if (s->flags & PA_SOURCE_DEFERRED_VOLUME) {
+ if (s->write_volume)
+ s->write_volume(s);
+ } else {
+ if (s->set_volume)
+ s->set_volume(s);
+ }
+ }
+
return pa_alsa_ucm_set_port(u->ucm_context, p, false);
}
@@ -1785,6 +1805,11 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char
return;
}
+ if (u->ucm_context) {
+ /* We just want to open the device */
+ return;
+ }
+
if (element) {
if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT)))
@@ -1822,16 +1847,31 @@ static int setup_mixer(struct userdata *u, bool ignore_dB) {
return 0;
if (u->source->active_port) {
- pa_alsa_port_data *data;
+ if (!u->ucm_context) {
+ pa_alsa_port_data *data;
- /* We have a list of supported paths, so let's activate the
- * one that has been chosen as active */
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
- data = PA_DEVICE_PORT_DATA(u->source->active_port);
- u->mixer_path = data->path;
+ data = PA_DEVICE_PORT_DATA(u->source->active_port);
+ u->mixer_path = data->path;
- pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
+ pa_alsa_path_select(data->path, data->setting, u->mixer_handle, u->source->muted);
+ } else {
+ pa_alsa_ucm_port_data *data;
+
+ /* First activate the port on the UCM side */
+ if (pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
+ return -1;
+ data = PA_DEVICE_PORT_DATA(u->source->active_port);
+
+ /* Now activate volume controls, if any */
+ if (data->path) {
+ u->mixer_path = data->path;
+ pa_alsa_path_select(u->mixer_path, NULL, u->mixer_handle, u->source->muted);
+ }
+ }
} else {
if (!u->mixer_path && u->mixer_path_set)
@@ -2152,8 +2192,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
- if (!u->ucm_context)
- find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_source_new_data_init(&data);
data.driver = driver;
@@ -2210,7 +2249,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
}
if (u->ucm_context)
- pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card);
+ pa_alsa_ucm_add_ports(&data.ports, data.proplist, u->ucm_context, false, card, u->pcm_handle, ignore_dB);
else if (u->mixer_path_set)
pa_alsa_add_ports(&data, u->mixer_path_set, card);
@@ -2276,10 +2315,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p
if (update_sw_params(u) < 0)
goto fail;
- if (u->ucm_context) {
- if (u->source->active_port && pa_alsa_ucm_set_port(u->ucm_context, u->source->active_port, false) < 0)
- goto fail;
- } else if (setup_mixer(u, ignore_dB) < 0)
+ if (setup_mixer(u, ignore_dB) < 0)
goto fail;
pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
@@ -2368,7 +2404,7 @@ static void userdata_free(struct userdata *u) {
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
- if (u->mixer_path && !u->mixer_path_set)
+ if (u->mixer_path && !u->mixer_path_set && !u->ucm_context)
pa_alsa_path_free(u->mixer_path);
if (u->mixer_handle)
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index 14056825a25f..349a59566200 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -81,19 +81,11 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name);
-struct ucm_port {
- pa_alsa_ucm_config *ucm;
- pa_device_port *core_port;
-
- /* A single port will be associated with multiple devices if it represents
- * a combination of devices. */
- pa_dynarray *devices; /* pa_alsa_ucm_device */
-};
-static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
- pa_alsa_ucm_device **devices, unsigned n_devices);
-static void ucm_port_free(pa_device_port *port);
-static void ucm_port_update_available(struct ucm_port *port);
+static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
+ pa_alsa_ucm_device **devices, unsigned n_devices);
+static void ucm_port_data_free(pa_device_port *port);
+static void ucm_port_update_available(pa_alsa_ucm_port_data *port);
static struct ucm_items item[] = {
{"PlaybackPCM", PA_ALSA_PROP_UCM_SINK},
@@ -303,6 +295,18 @@ static int ucm_get_device_property(
else
pa_log_debug("UCM playback priority %s for device %s error", value, device_name);
}
+
+ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_VOLUME);
+ if (value) {
+ /* Try to get the simple control name, and failing that, just use the name as is */
+ char *selem;
+
+ if (!(selem = pa_str_strip_suffix(value, " Playback Volume")))
+ if (!(selem = pa_str_strip_suffix(value, " Volume")))
+ selem = pa_xstrdup(value);
+
+ pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), selem);
+ }
}
if (device->capture_channels) { /* source device */
@@ -324,6 +328,18 @@ static int ucm_get_device_property(
else
pa_log_debug("UCM capture priority %s for device %s error", value, device_name);
}
+
+ value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_VOLUME);
+ if (value) {
+ /* Try to get the simple control name, and failing that, just use the name as is */
+ char *selem;
+
+ if (!(selem = pa_str_strip_suffix(value, " Capture Volume")))
+ if (!(selem = pa_str_strip_suffix(value, " Volume")))
+ selem = pa_xstrdup(value);
+
+ pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), selem);
+ }
}
if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) {
@@ -427,6 +443,11 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
d->hw_mute_jacks = pa_dynarray_new(NULL);
d->available = PA_AVAILABLE_UNKNOWN;
+ d->playback_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+ pa_xfree);
+ d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+ pa_xfree);
+
PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
}
@@ -707,6 +728,46 @@ static int pa_alsa_ucm_device_cmp(const void *a, const void *b) {
return strcmp(pa_proplist_gets(d1->proplist, PA_ALSA_PROP_UCM_NAME), pa_proplist_gets(d2->proplist, PA_ALSA_PROP_UCM_NAME));
}
+static void probe_volumes(pa_hashmap *hash, snd_pcm_t *pcm_handle, bool ignore_dB) {
+ pa_device_port *port;
+ pa_alsa_path *path;
+ pa_alsa_ucm_port_data *data;
+ snd_mixer_t *mixer_handle;
+ const char *profile;
+ void *state, *state2;
+
+ if (!(mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL))) {
+ pa_log_error("Failed to find a working mixer device.");
+ goto fail;
+ }
+
+ PA_HASHMAP_FOREACH(port, hash, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+
+ PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) {
+ if (pa_alsa_path_probe(path, NULL, mixer_handle, ignore_dB) < 0) {
+ pa_log_warn("Could not probe path: %s, using s/w volume", data->path->name);
+ pa_hashmap_remove(data->paths, profile);
+ } else if (!path->has_volume) {
+ pa_log_warn("Path %s is not a volume control", data->path->name);
+ pa_hashmap_remove(data->paths, profile);
+ } else
+ pa_log_debug("Set up h/w volume using '%s' for %s:%s", path->name, profile, port->name);
+ }
+ }
+
+ snd_mixer_close(mixer_handle);
+
+ return;
+
+fail:
+ /* We could not probe the paths we created. Free them and revert to software volumes. */
+ PA_HASHMAP_FOREACH(port, hash, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+ pa_hashmap_remove_all(data->paths);
+ }
+}
+
static void ucm_add_port_combination(
pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context,
@@ -724,7 +785,10 @@ static void ucm_add_port_combination(
char *name, *desc;
const char *dev_name;
const char *direction;
+ const char *profile, *volume_element;
pa_alsa_ucm_device *sorted[num], *dev;
+ pa_alsa_ucm_port_data *data;
+ void *state;
for (i = 0; i < num; i++)
sorted[i] = pdevices[i];
@@ -772,8 +836,6 @@ static void ucm_add_port_combination(
port = pa_hashmap_get(ports, name);
if (!port) {
- struct ucm_port *ucm_port;
-
pa_device_port_new_data port_data;
pa_device_port_new_data_init(&port_data);
@@ -781,17 +843,33 @@ static void ucm_add_port_combination(
pa_device_port_new_data_set_description(&port_data, desc);
pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
- port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port));
- port->impl_free = ucm_port_free;
+ port = pa_device_port_new(core, &port_data, sizeof(pa_alsa_ucm_port_data));
pa_device_port_new_data_done(&port_data);
- ucm_port = PA_DEVICE_PORT_DATA(port);
- ucm_port_init(ucm_port, context->ucm, port, pdevices, num);
+ data = PA_DEVICE_PORT_DATA(port);
+ ucm_port_data_init(data, context->ucm, port, pdevices, num);
+ port->impl_free = ucm_port_data_free;
pa_hashmap_put(ports, port->name, port);
pa_log_debug("Add port %s: %s", port->name, port->description);
}
+ if (num == 1) {
+ /* To keep things simple and not worry about stacking controls, we only support hardware volumes on non-combination
+ * ports. */
+ data = PA_DEVICE_PORT_DATA(port);
+
+ PA_HASHMAP_FOREACH_KV(profile, volume_element, is_sink ? dev->playback_volumes : dev->capture_volumes, state) {
+ pa_alsa_path *path = pa_alsa_path_synthesize(volume_element,
+ is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT);
+
+ if (!path)
+ pa_log_warn("Failed to set up volume control: %s", volume_element);
+ else
+ pa_hashmap_put(data->paths, pa_xstrdup(profile), path);
+ }
+ }
+
port->priority = priority;
pa_xfree(name);
@@ -971,7 +1049,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
- pa_card *card) {
+ pa_card *card,
+ snd_pcm_t *pcm_handle,
+ bool ignore_dB) {
uint32_t idx;
char *merged_roles;
@@ -986,6 +1066,9 @@ void pa_alsa_ucm_add_ports(
/* add ports first */
pa_alsa_ucm_add_ports_combination(*p, context, is_sink, card->ports, NULL, card->core);
+ /* now set up volume paths if any */
+ probe_volumes(*p, pcm_handle, ignore_dB);
+
/* then set property PA_PROP_DEVICE_INTENDED_ROLES */
merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES));
PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
@@ -1010,10 +1093,13 @@ void pa_alsa_ucm_add_ports(
}
/* Change UCM verb and device to match selected card profile */
-int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
int ret = 0;
const char *profile;
pa_alsa_ucm_verb *verb;
+ pa_device_port *port;
+ pa_alsa_ucm_port_data *data;
+ void *state;
if (new_profile == old_profile)
return ret;
@@ -1042,6 +1128,12 @@ int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, co
}
}
+ /* select volume controls on ports */
+ PA_HASHMAP_FOREACH(port, card->ports, state) {
+ data = PA_DEVICE_PORT_DATA(port);
+ data->path = pa_hashmap_get(data->paths, new_profile);
+ }
+
return ret;
}
@@ -1650,11 +1742,18 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
if (di->ucm_ports)
pa_dynarray_free(di->ucm_ports);
+ if (di->playback_volumes)
+ pa_hashmap_free(di->playback_volumes);
+ if (di->capture_volumes)
+ pa_hashmap_free(di->capture_volumes);
+
pa_proplist_free(di->proplist);
+
if (di->conflicting_devices)
pa_idxset_free(di->conflicting_devices, NULL);
if (di->supported_devices)
pa_idxset_free(di->supported_devices, NULL);
+
pa_xfree(di);
}
@@ -1785,7 +1884,7 @@ void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_
}
}
-static void device_add_ucm_port(pa_alsa_ucm_device *device, struct ucm_port *port) {
+static void device_add_ucm_port(pa_alsa_ucm_device *device, pa_alsa_ucm_port_data *port) {
pa_assert(device);
pa_assert(port);
@@ -1813,7 +1912,7 @@ static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *ja
}
static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
- struct ucm_port *port;
+ pa_alsa_ucm_port_data *port;
unsigned idx;
pa_assert(device);
@@ -1847,8 +1946,8 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
device_set_available(device, available);
}
-static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
- pa_alsa_ucm_device **devices, unsigned n_devices) {
+static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
+ pa_alsa_ucm_device **devices, unsigned n_devices) {
unsigned i;
pa_assert(ucm);
@@ -1864,11 +1963,14 @@ static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_dev
device_add_ucm_port(devices[i], port);
}
+ port->paths = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree,
+ (pa_free_cb_t) pa_alsa_path_free);
+
ucm_port_update_available(port);
}
-static void ucm_port_free(pa_device_port *port) {
- struct ucm_port *ucm_port;
+static void ucm_port_data_free(pa_device_port *port) {
+ pa_alsa_ucm_port_data *ucm_port;
pa_assert(port);
@@ -1876,9 +1978,12 @@ static void ucm_port_free(pa_device_port *port) {
if (ucm_port->devices)
pa_dynarray_free(ucm_port->devices);
+
+ if (ucm_port->paths)
+ pa_hashmap_free(ucm_port->paths);
}
-static void ucm_port_update_available(struct ucm_port *port) {
+static void ucm_port_update_available(pa_alsa_ucm_port_data *port) {
pa_alsa_ucm_device *device;
unsigned idx;
pa_available_t available = PA_AVAILABLE_YES;
@@ -1910,7 +2015,7 @@ pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_cha
return NULL;
}
-int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile) {
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile) {
return -1;
}
@@ -1923,7 +2028,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
- pa_card *card) {
+ pa_card *card,
+ snd_pcm_t *pcm_handle,
+ bool ignore_dB) {
}
void pa_alsa_ucm_add_ports_combination(
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 4feb8c0bfc3f..2e39a09a51f3 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -113,10 +113,11 @@ typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
typedef struct pa_alsa_ucm_config pa_alsa_ucm_config;
typedef struct pa_alsa_ucm_mapping_context pa_alsa_ucm_mapping_context;
+typedef struct pa_alsa_ucm_port_data pa_alsa_ucm_port_data;
int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index);
pa_alsa_profile_set* pa_alsa_ucm_add_profile_set(pa_alsa_ucm_config *ucm, pa_channel_map *default_channel_map);
-int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, const char *new_profile, const char *old_profile);
+int pa_alsa_ucm_set_profile(pa_alsa_ucm_config *ucm, pa_card *card, const char *new_profile, const char *old_profile);
int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, const char *verb_desc, pa_alsa_ucm_verb **p_verb);
@@ -125,7 +126,9 @@ void pa_alsa_ucm_add_ports(
pa_proplist *proplist,
pa_alsa_ucm_mapping_context *context,
bool is_sink,
- pa_card *card);
+ pa_card *card,
+ snd_pcm_t *pcm_handle,
+ bool ignore_dB);
void pa_alsa_ucm_add_ports_combination(
pa_hashmap *hash,
pa_alsa_ucm_mapping_context *context,
@@ -157,6 +160,11 @@ struct pa_alsa_ucm_device {
unsigned playback_channels;
unsigned capture_channels;
+ /* These may be different per verb, so we store this as a hashmap of verb -> volume_control. We might eventually want to
+ * make this a hashmap of verb -> per-verb-device-properties-struct. */
+ pa_hashmap *playback_volumes;
+ pa_hashmap *capture_volumes;
+
pa_alsa_mapping *playback_mapping;
pa_alsa_mapping *capture_mapping;
@@ -224,4 +232,18 @@ struct pa_alsa_ucm_mapping_context {
pa_idxset *ucm_modifiers;
};
+struct pa_alsa_ucm_port_data {
+ pa_alsa_ucm_config *ucm;
+ pa_device_port *core_port;
+
+ /* A single port will be associated with multiple devices if it represents
+ * a combination of devices. */
+ pa_dynarray *devices; /* pa_alsa_ucm_device */
+
+ /* profile -> pa_alsa_path for volume control */
+ pa_hashmap *paths;
+ /* Current path, set when activating profile */
+ pa_alsa_path *path;
+};
+
#endif
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 1e1090fe024e..e2a86bc1c68d 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -241,7 +241,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) {
/* if UCM is available for this card then update the verb */
if (u->use_ucm) {
- if (pa_alsa_ucm_set_profile(&u->ucm, nd->profile ? nd->profile->name : NULL,
+ if (pa_alsa_ucm_set_profile(&u->ucm, c, nd->profile ? nd->profile->name : NULL,
od->profile ? od->profile->name : NULL) < 0) {
ret = -1;
goto finish;
@@ -294,7 +294,7 @@ static void init_profile(struct userdata *u) {
if (d->profile && u->use_ucm) {
/* Set initial verb */
- if (pa_alsa_ucm_set_profile(ucm, d->profile->name, NULL) < 0) {
+ if (pa_alsa_ucm_set_profile(ucm, u->card, d->profile->name, NULL) < 0) {
pa_log("Failed to set ucm profile %s", d->profile->name);
return;
}
diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c
index f5ec67b8f3fc..174987e4afda 100644
--- a/src/pulsecore/core-util.c
+++ b/src/pulsecore/core-util.c
@@ -2893,6 +2893,32 @@ bool pa_str_in_list_spaces(const char *haystack, const char *needle) {
return false;
}
+char* pa_str_strip_suffix(const char *str, const char *suffix) {
+ size_t str_l, suf_l, prefix;
+ char *ret;
+
+ pa_assert(str);
+ pa_assert(suffix);
+
+ str_l = strlen(str);
+ suf_l = strlen(suffix);
+
+ if (str_l < suf_l)
+ return NULL;
+
+ prefix = str_l - suf_l;
+
+ if (!pa_streq(&str[prefix], suffix))
+ return NULL;
+
+ ret = pa_xmalloc(prefix + 1);
+
+ strncpy(ret, str, prefix);
+ ret[prefix] = '\0';
+
+ return ret;
+}
+
char *pa_get_user_name_malloc(void) {
ssize_t k;
char *u;
diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h
index d1c4ae1c49aa..9440af9172ca 100644
--- a/src/pulsecore/core-util.h
+++ b/src/pulsecore/core-util.h
@@ -232,6 +232,8 @@ static inline bool pa_safe_streq(const char *a, const char *b) {
bool pa_str_in_list_spaces(const char *needle, const char *haystack);
bool pa_str_in_list(const char *haystack, const char *delimiters, const char *needle);
+char* pa_str_strip_suffix(const char *str, const char *suffix);
+
char *pa_get_host_name_malloc(void);
char *pa_get_user_name_malloc(void);
--
2.16.4