Takashi Iwai
5dabf3bcba
- 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
796 lines
30 KiB
Diff
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
|
|
|