From 5dabf3bcbaaec3714516e56ee36ae2ecd1bcce2160adf071a40f046db3ea5e2a Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Mon, 17 Feb 2020 11:47:50 +0000 Subject: [PATCH] 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 --- ...test-Hide-unused-functions-when-buil.patch | 48 + ...r-recognize-the-Speaker-Jack-control.patch | 30 + ...upport-for-SteelSeries-Arctis-Pro-20.patch | 104 +++ ...upport-for-SteelSeries-Arctis-5-2019.patch | 29 + ...upport-for-LucidSound-LS31-and-creat.patch | 199 +++++ ...2-name-for-the-direct-card-index-ope.patch | 61 ++ ...-alsa-ucm-add-mixer-IDs-to-ucm_items.patch | 72 ++ ...e-the-index-for-ALSA-mixer-element-i.patch | 820 ++++++++++++++++++ ...ixer-improve-alsa_id_decode-function.patch | 51 ++ ...a-ucm-Support-Playback-CaptureVolume.patch | 795 +++++++++++++++++ ...m-Fix-volume-control-based-on-review.patch | 242 ++++++ ...e-correct-mixer-identifiers-as-first.patch | 120 +++ ...sa-ucm-add-support-for-master-volume.patch | 330 +++++++ ...it-correctly-JackHWMute-device-names.patch | 59 ++ ...alsa-ucm-fix-parsing-for-JackControl.patch | 40 + ...ucm-add-comments-to-ucm_get_mixer_id.patch | 45 + ...lidate-access-to-PA_DEVICE_PORT_DATA.patch | 81 ++ ...e-PCM-if-hardware-doesn-t-support-it.patch | 65 ++ ...cm-parse-correctly-the-device-values.patch | 125 +++ ...try-to-use-UCM-device-name-as-jack-n.patch | 44 + ...-try-to-guess-the-mixer-name-from-th.patch | 84 ++ ...m-add-control-and-mixer-device-items.patch | 99 +++ ...the-mixer-names-from-ucm-don-t-guess.patch | 244 ++++++ ...-proper-mixer-name-for-ucm-pcm-sink-.patch | 93 ++ ...e-interface-type-CARD-PCM-for-mixer-.patch | 125 +++ ...he-ability-to-pass-the-intended-role.patch | 51 ++ ...he-intended-role-of-Steelseries-Arct.patch | 39 + ...er-open-close-cache-mixer-accesses-i.patch | 761 ++++++++++++++++ 0029-alsa-ucm-add-support-for-HDMI-ELD.patch | 264 ++++++ ...e-quick-card-number-lookup-to-save-m.patch | 83 ++ ...ve-check-for-the-empty-path-set-for-.patch | 134 +++ ...o-set-profile-priority-from-UCM-valu.patch | 128 +++ pulseaudio.changes | 38 + pulseaudio.spec | 65 ++ 34 files changed, 5568 insertions(+) create mode 100644 0001-alsa-mixer-path-test-Hide-unused-functions-when-buil.patch create mode 100644 0002-alsa-mixer-recognize-the-Speaker-Jack-control.patch create mode 100644 0003-alsa-mixer-add-support-for-SteelSeries-Arctis-Pro-20.patch create mode 100644 0004-alsa-mixer-Add-support-for-SteelSeries-Arctis-5-2019.patch create mode 100644 0005-alsa-mixer-add-support-for-LucidSound-LS31-and-creat.patch create mode 100644 0006-alsa-ucm-use-ucm2-name-for-the-direct-card-index-ope.patch create mode 100644 0007-alsa-ucm-add-mixer-IDs-to-ucm_items.patch create mode 100644 0008-alsa-mixer-handle-the-index-for-ALSA-mixer-element-i.patch create mode 100644 0009-alsa-mixer-improve-alsa_id_decode-function.patch create mode 100644 0010-alsa-ucm-Support-Playback-CaptureVolume.patch create mode 100644 0011-alsa-ucm-Fix-volume-control-based-on-review.patch create mode 100644 0012-alsa-ucm-use-the-correct-mixer-identifiers-as-first.patch create mode 100644 0013-alsa-ucm-add-support-for-master-volume.patch create mode 100644 0014-alsa-ucm-split-correctly-JackHWMute-device-names.patch create mode 100644 0015-alsa-ucm-fix-parsing-for-JackControl.patch create mode 100644 0016-alsa-ucm-add-comments-to-ucm_get_mixer_id.patch create mode 100644 0017-alsa-ucm-validate-access-to-PA_DEVICE_PORT_DATA.patch create mode 100644 0018-alsa-Skip-resume-PCM-if-hardware-doesn-t-support-it.patch create mode 100644 0019-alsa-ucm-parse-correctly-the-device-values.patch create mode 100644 0020-alsa-ucm-do-not-try-to-use-UCM-device-name-as-jack-n.patch create mode 100644 0021-alsa-util-do-not-try-to-guess-the-mixer-name-from-th.patch create mode 100644 0022-alsa-ucm-add-control-and-mixer-device-items.patch create mode 100644 0023-alsa-ucm-get-the-mixer-names-from-ucm-don-t-guess.patch create mode 100644 0024-alsa-ucm-use-the-proper-mixer-name-for-ucm-pcm-sink-.patch create mode 100644 0025-alsa-mixer-handle-interface-type-CARD-PCM-for-mixer-.patch create mode 100644 0026-alsa-mixer-Add-the-ability-to-pass-the-intended-role.patch create mode 100644 0027-alsa-mixer-Set-the-intended-role-of-Steelseries-Arct.patch create mode 100644 0028-alsa-rewrite-mixer-open-close-cache-mixer-accesses-i.patch create mode 100644 0029-alsa-ucm-add-support-for-HDMI-ELD.patch create mode 100644 0030-alsa-mixer-do-the-quick-card-number-lookup-to-save-m.patch create mode 100644 0031-alsa-mixer-improve-check-for-the-empty-path-set-for-.patch create mode 100644 0032-alsa-ucm-allow-to-set-profile-priority-from-UCM-valu.patch diff --git a/0001-alsa-mixer-path-test-Hide-unused-functions-when-buil.patch b/0001-alsa-mixer-path-test-Hide-unused-functions-when-buil.patch new file mode 100644 index 0000000..64af2fc --- /dev/null +++ b/0001-alsa-mixer-path-test-Hide-unused-functions-when-buil.patch @@ -0,0 +1,48 @@ +From 3dff31e19ca627fc4e0a8f13aeb44923118ecfa1 Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Fri, 20 Sep 2019 17:09:40 +0300 +Subject: [PATCH] alsa-mixer-path-test: Hide unused functions when building + with Meson +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Silences these warnings: + +[509/574] Compiling C object 'src/tests/a4ccf2d@@alsa-mixer-path-test@exe/alsa-mixer-path-test.c.o'. +../src/tests/alsa-mixer-path-test.c:24:20: warning: ‘load_makefile’ defined but not used [-Wunused-function] + static pa_strlist *load_makefile() { + ^~~~~~~~~~~~~ +../src/tests/alsa-mixer-path-test.c:17:20: warning: ‘get_default_paths_dir’ defined but not used [-Wunused-function] + static const char *get_default_paths_dir(void) { + ^~~~~~~~~~~~~~~~~~~~~ +--- + src/tests/alsa-mixer-path-test.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/src/tests/alsa-mixer-path-test.c b/src/tests/alsa-mixer-path-test.c +index ee40587b7b13..75cf086138cc 100644 +--- a/src/tests/alsa-mixer-path-test.c ++++ b/src/tests/alsa-mixer-path-test.c +@@ -13,6 +13,10 @@ + #include + #include + ++/* This test inspects the Makefile, so this is not applicable when using ++ * Meson. */ ++#ifndef MESON_BUILD ++ + /* This function was copied from alsa-mixer.c */ + static const char *get_default_paths_dir(void) { + if (pa_run_from_build_tree()) +@@ -52,6 +56,7 @@ static pa_strlist *load_makefile() { + fclose(f); + return result; + } ++#endif /* end of #ifndef MESON_BUILD */ + + START_TEST (mixer_path_test) { + #ifdef MESON_BUILD +-- +2.16.4 + diff --git a/0002-alsa-mixer-recognize-the-Speaker-Jack-control.patch b/0002-alsa-mixer-recognize-the-Speaker-Jack-control.patch new file mode 100644 index 0000000..fb9f363 --- /dev/null +++ b/0002-alsa-mixer-recognize-the-Speaker-Jack-control.patch @@ -0,0 +1,30 @@ +From 248a77c7fd2f9682c58d3d3dd6f3c2ba3ad2c111 Mon Sep 17 00:00:00 2001 +From: Tanu Kaskinen +Date: Sat, 2 Sep 2017 15:35:01 +0300 +Subject: [PATCH] alsa-mixer: recognize the "Speaker Jack" control + +This control has been seen in the wild: +https://lists.freedesktop.org/archives/pulseaudio-discuss/2017-August/028595.html +(The pastebin link in that mail might not work anymore, but the paste +just shows that there's a Speaker Jack control). +--- + src/modules/alsa/mixer/paths/analog-output-speaker.conf | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/modules/alsa/mixer/paths/analog-output-speaker.conf b/src/modules/alsa/mixer/paths/analog-output-speaker.conf +index 9f4dac414134..6f9968e1f537 100644 +--- a/src/modules/alsa/mixer/paths/analog-output-speaker.conf ++++ b/src/modules/alsa/mixer/paths/analog-output-speaker.conf +@@ -56,6 +56,9 @@ state.unplugged = unknown + state.plugged = no + state.unplugged = unknown + ++[Jack Speaker] ++required-any = any ++ + [Jack Speaker Phantom] + required-any = any + state.plugged = unknown +-- +2.16.4 + diff --git a/0003-alsa-mixer-add-support-for-SteelSeries-Arctis-Pro-20.patch b/0003-alsa-mixer-add-support-for-SteelSeries-Arctis-Pro-20.patch new file mode 100644 index 0000000..260472e --- /dev/null +++ b/0003-alsa-mixer-add-support-for-SteelSeries-Arctis-Pro-20.patch @@ -0,0 +1,104 @@ +From 7259e8c22fb97b1ae80ac4909713e51b293afc4a Mon Sep 17 00:00:00 2001 +From: Josh +Date: Sat, 25 May 2019 02:35:01 -0700 +Subject: [PATCH] alsa-mixer: add support for SteelSeries Arctis Pro 2019 + headset + +Signed-off-by: Dave Chiluk +--- + src/Makefile.am | 6 +++--- + src/modules/alsa/90-pulseaudio.rules | 10 +++++++--- + ...ut-chat.conf => steelseries-arctis-output-chat-common.conf} | 0 + ...ut-game.conf => steelseries-arctis-output-game-common.conf} | 0 + ...usb-audio.conf => steelseries-arctis-common-usb-audio.conf} | 4 ++-- + 5 files changed, 12 insertions(+), 8 deletions(-) + rename src/modules/alsa/mixer/paths/{steelseries-arctis-5-output-chat.conf => steelseries-arctis-output-chat-common.conf} (100%) + rename src/modules/alsa/mixer/paths/{steelseries-arctis-5-output-game.conf => steelseries-arctis-output-game-common.conf} (100%) + rename src/modules/alsa/mixer/profile-sets/{steelseries-arctis-5-usb-audio.conf => steelseries-arctis-common-usb-audio.conf} (80%) + +diff --git a/src/Makefile.am b/src/Makefile.am +index e3baf587a8a3..3db921a07e1b 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -1353,7 +1353,7 @@ dist_alsaprofilesets_DATA = \ + modules/alsa/mixer/profile-sets/native-instruments-korecontroller.conf \ + modules/alsa/mixer/profile-sets/kinect-audio.conf \ + modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf \ +- modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf \ ++ modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf \ + modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf \ + modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf \ + modules/alsa/mixer/profile-sets/cmedia-high-speed-true-hdaudio.conf +@@ -1399,8 +1399,8 @@ dist_alsapaths_DATA = \ + modules/alsa/mixer/paths/hdmi-output-5.conf \ + modules/alsa/mixer/paths/hdmi-output-6.conf \ + modules/alsa/mixer/paths/hdmi-output-7.conf \ +- modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf \ +- modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf \ ++ modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf \ ++ modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf \ + modules/alsa/mixer/paths/steelseries-arctis-7-input.conf \ + modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf \ + modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf +diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules +index d85763917d76..9d34e6dfdd38 100644 +--- a/src/modules/alsa/90-pulseaudio.rules ++++ b/src/modules/alsa/90-pulseaudio.rules +@@ -109,16 +109,20 @@ ATTRS{idVendor}=="0763", ATTRS{idProduct}=="2012", ENV{PULSE_PROFILE_SET}="maudi + ATTRS{idVendor}=="045e", ATTRS{idProduct}=="02bb", ENV{PULSE_PROFILE_SET}="kinect-audio.conf" + ATTRS{idVendor}=="041e", ATTRS{idProduct}=="322c", ENV{PULSE_PROFILE_SET}="sb-omni-surround-5.1.conf" + ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell-dock-tb16-usb-audio.conf" +-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-5-usb-audio.conf" ++ + + # ID 1038:12ad is for the 2018 refresh of the Arctis 7. +-# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 +-# configuration). ++# ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" + ++# ID 1038:1250 is for the Arctis 5 ++# ID 1038:1252 is for Arctis Pro 2019 edition ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1252", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ++ + ATTRS{idVendor}=="147a", ATTRS{idProduct}=="e055", ENV{PULSE_PROFILE_SET}="cmedia-high-speed-true-hdaudio.conf" + + GOTO="pulseaudio_end" +diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf +similarity index 100% +rename from src/modules/alsa/mixer/paths/steelseries-arctis-5-output-chat.conf +rename to src/modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf +diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf b/src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf +similarity index 100% +rename from src/modules/alsa/mixer/paths/steelseries-arctis-5-output-game.conf +rename to src/modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf +diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf +similarity index 80% +rename from src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf +rename to src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf +index fe353c38f09c..80c33707aea4 100644 +--- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-5-usb-audio.conf ++++ b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf +@@ -6,13 +6,13 @@ description = Chat + device-strings = hw:%f,0,0 + channel-map = left,right + paths-input = analog-input-mic +-paths-output = steelseries-arctis-5-output-chat ++paths-output = steelseries-arctis-output-chat-common + + [Mapping analog-game] + description = Game + device-strings = hw:%f,1,0 + channel-map = left,right +-paths-output = steelseries-arctis-5-output-game ++paths-output = steelseries-arctis-output-game-common + direction = output + + [Profile output:analog-chat+output:analog-game+input:analog-chat] +-- +2.16.4 + diff --git a/0004-alsa-mixer-Add-support-for-SteelSeries-Arctis-5-2019.patch b/0004-alsa-mixer-Add-support-for-SteelSeries-Arctis-5-2019.patch new file mode 100644 index 0000000..dd9ac0a --- /dev/null +++ b/0004-alsa-mixer-Add-support-for-SteelSeries-Arctis-5-2019.patch @@ -0,0 +1,29 @@ +From 79d3b99ba4773651c4ebc082233f9d0b5f68bfad Mon Sep 17 00:00:00 2001 +From: Krzysztof Stasiowski +Date: Tue, 18 Jun 2019 11:50:35 +0000 +Subject: [PATCH] alsa-mixer: Add support for SteelSeries Arctis 5 2019 headset + +Signed-off-by: Dave Chiluk +--- + src/modules/alsa/90-pulseaudio.rules | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules +index 9d34e6dfdd38..61e7692543e8 100644 +--- a/src/modules/alsa/90-pulseaudio.rules ++++ b/src/modules/alsa/90-pulseaudio.rules +@@ -119,8 +119,10 @@ ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="steel + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" + + # ID 1038:1250 is for the Arctis 5 +-# ID 1038:1252 is for Arctis Pro 2019 edition ++# ID 1037:12aa is for the Arctis 5 2019 ++# ID 1038:1252 is for the Arctis Pro 2019 edition + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1250", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12aa", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" + ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1252", ENV{PULSE_PROFILE_SET}="steelseries-arctis-common-usb-audio.conf" + + ATTRS{idVendor}=="147a", ATTRS{idProduct}=="e055", ENV{PULSE_PROFILE_SET}="cmedia-high-speed-true-hdaudio.conf" +-- +2.16.4 + diff --git a/0005-alsa-mixer-add-support-for-LucidSound-LS31-and-creat.patch b/0005-alsa-mixer-add-support-for-LucidSound-LS31-and-creat.patch new file mode 100644 index 0000000..4953533 --- /dev/null +++ b/0005-alsa-mixer-add-support-for-LucidSound-LS31-and-creat.patch @@ -0,0 +1,199 @@ +From 1ee1f749e154d2f64b4661f833eebaa18ae1a081 Mon Sep 17 00:00:00 2001 +From: Dave Chiluk +Date: Thu, 8 Aug 2019 23:10:01 -0500 +Subject: [PATCH] alsa-mixer: add support for LucidSound LS31, and create + usb-gaming-headset profile + +--- + src/Makefile.am | 8 ++++---- + src/modules/alsa/90-pulseaudio.rules | 10 ++++++---- + ...tis-7-input.conf => usb-gaming-headset-input.conf} | 12 ++++++++++-- + ...-mono.conf => usb-gaming-headset-output-mono.conf} | 15 ++++++++++----- + ...reo.conf => usb-gaming-headset-output-stereo.conf} | 15 ++++++++++----- + ...rctis-7-usb-audio.conf => usb-gaming-headset.conf} | 19 ++++++++++++------- + 6 files changed, 52 insertions(+), 27 deletions(-) + rename src/modules/alsa/mixer/paths/{steelseries-arctis-7-input.conf => usb-gaming-headset-input.conf} (66%) + rename src/modules/alsa/mixer/paths/{steelseries-arctis-7-output-mono.conf => usb-gaming-headset-output-mono.conf} (66%) + rename src/modules/alsa/mixer/paths/{steelseries-arctis-7-output-stereo.conf => usb-gaming-headset-output-stereo.conf} (68%) + rename src/modules/alsa/mixer/profile-sets/{steelseries-arctis-7-usb-audio.conf => usb-gaming-headset.conf} (79%) + +diff --git a/src/Makefile.am b/src/Makefile.am +index 3db921a07e1b..0f1ded7f96c5 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -1354,7 +1354,7 @@ dist_alsaprofilesets_DATA = \ + modules/alsa/mixer/profile-sets/kinect-audio.conf \ + modules/alsa/mixer/profile-sets/sb-omni-surround-5.1.conf \ + modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf \ +- modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf \ ++ modules/alsa/mixer/profile-sets/usb-gaming-headset.conf \ + modules/alsa/mixer/profile-sets/dell-dock-tb16-usb-audio.conf \ + modules/alsa/mixer/profile-sets/cmedia-high-speed-true-hdaudio.conf + +@@ -1401,9 +1401,9 @@ dist_alsapaths_DATA = \ + modules/alsa/mixer/paths/hdmi-output-7.conf \ + modules/alsa/mixer/paths/steelseries-arctis-output-chat-common.conf \ + modules/alsa/mixer/paths/steelseries-arctis-output-game-common.conf \ +- modules/alsa/mixer/paths/steelseries-arctis-7-input.conf \ +- modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf \ +- modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf ++ modules/alsa/mixer/paths/usb-gaming-headset-input.conf \ ++ modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf \ ++ modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf + + endif + +diff --git a/src/modules/alsa/90-pulseaudio.rules b/src/modules/alsa/90-pulseaudio.rules +index 61e7692543e8..fa43cb802a8e 100644 +--- a/src/modules/alsa/90-pulseaudio.rules ++++ b/src/modules/alsa/90-pulseaudio.rules +@@ -113,10 +113,12 @@ ATTRS{idVendor}=="0bda", ATTRS{idProduct}=="4014", ENV{PULSE_PROFILE_SET}="dell- + + # ID 1038:12ad is for the 2018 refresh of the Arctis 7. + # ID 1038:1294 is for Arctis Pro Wireless (which works with the Arctis 7 configuration). +-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" +-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" +-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" +-ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="steelseries-arctis-7-usb-audio.conf" ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1260", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="12ad", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1294", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" ++ATTRS{idVendor}=="1038", ATTRS{idProduct}=="1730", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" ++# Lucidsound LS31 ++ATTRS{idVendor}=="2f12", ATTRS{idProduct}=="0109", ENV{PULSE_PROFILE_SET}="usb-gaming-headset.conf" + + # ID 1038:1250 is for the Arctis 5 + # ID 1037:12aa is for the Arctis 5 2019 +diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf +similarity index 66% +rename from src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf +rename to src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf +index 3fa36e9385a5..9fa7fe908587 100644 +--- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-input.conf ++++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-input.conf +@@ -13,8 +13,16 @@ + # You should have received a copy of the GNU Lesser General Public License + # along with PulseAudio; if not, see . + +-; Steelseries Arctis 7 USB headset microphone path. Works also with Arctis Pro +-; Wireless. ++; USB gaming headset microphone input path. These headsets usually have two ++; output devices. The first one is mono, meant for voice audio, and the second ++; one is stereo, meant for everything else. The purpose of this unusual design ++; is to provide separate volume controls for voice and other audio, which can ++; be useful in gaming. ++; ++; Works with: ++; Steelseries Arctis 7 ++; Steelseries Arctis Pro Wireless. ++; Lucidsound LS31 + + [General] + description-key = analog-input-microphone-headset +diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf +similarity index 66% +rename from src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf +rename to src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf +index d8b24a2fbdf2..6df662f069e5 100644 +--- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-mono.conf ++++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-mono.conf +@@ -13,11 +13,16 @@ + # You should have received a copy of the GNU Lesser General Public License + # along with PulseAudio; if not, see . + +-; Steelseries Arctis 7 USB headset mono output path. Works also with Arctis Pro +-; Wireless. The headset has two output devices. The first one is mono, meant +-; for voice audio, and the second one is stereo, meant for everything else. The +-; purpose of this unusual design is to provide separate volume controls for +-; voice and other audio, which can be useful in gaming. ++; USB gaming headset mono output path. These headsets usually have two ++; output devices. The first one is mono, meant for voice audio, and the second ++; one is stereo, meant for everything else. The purpose of this unusual design ++; is to provide separate volume controls for voice and other audio, which can ++; be useful in gaming. ++; ++; Works with: ++; Steelseries Arctis 7 ++; Steelseries Arctis Pro Wireless. ++; Lucidsound LS31 + + [General] + description-key = analog-output-headphones-mono +diff --git a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf +similarity index 68% +rename from src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf +rename to src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf +index fcc58a033e2f..e3f91cd6cd2e 100644 +--- a/src/modules/alsa/mixer/paths/steelseries-arctis-7-output-stereo.conf ++++ b/src/modules/alsa/mixer/paths/usb-gaming-headset-output-stereo.conf +@@ -13,11 +13,16 @@ + # You should have received a copy of the GNU Lesser General Public License + # along with PulseAudio; if not, see . + +-; Steelseries Arctis 7 USB headset stereo output path. Works also with Arctis +-; Pro Wireless. The headset has two output devices. The first one is mono, +-; meant for voice audio, and the second one is stereo, meant for everything +-; else. The purpose of this unusual design is to provide separate volume +-; controls for voice and other audio, which can be useful in gaming. ++; USB gaming headset mono output path. These headsets usually have two ++; output devices. The first one is mono, meant for voice audio, and the second ++; one is stereo, meant for everything else. The purpose of this unusual design ++; is to provide separate volume controls for voice and other audio, which can ++; be useful in gaming. ++; ++; Works with: ++; Steelseries Arctis 7 ++; Steelseries Arctis Pro Wireless. ++; Lucidsound LS31 + ; + ; This path doesn't provide hardware volume control, because the stereo + ; output is controlled by the PCM element with index 1, and currently +diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf +similarity index 79% +rename from src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf +rename to src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf +index e1394dcfcc3b..01ecf864bcf2 100644 +--- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-7-usb-audio.conf ++++ b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf +@@ -13,12 +13,17 @@ + # You should have received a copy of the GNU Lesser General Public License + # along with PulseAudio; if not, see . + +-; Steelseries Arctis 7 USB and Arctis Pro Wireless USB headset. These headsets +-; have a microphone and two output devices. The first output device is mono, +-; meant for voice audio, and the second one is stereo, meant for everything +-; else. The purpose of this unusual design is to provide separate volume ++; USB gaming headset. ++; These headsets usually have two output devices. The first one is mono, ++; meant for voice audio, and the second one is stereo, meant for everything ++; else. The purpose of this unusual design is to provide separate volume + ; controls for voice and other audio, which can be useful in gaming. + ; ++; Works with: ++; Steelseries Arctis 7 ++; Steelseries Arctis Pro Wireless. ++; Lucidsound LS31 ++; + ; See default.conf for an explanation on the directives used here. + + [General] +@@ -27,13 +32,13 @@ auto-profiles = yes + [Mapping analog-mono] + device-strings = hw:%f,0,0 + channel-map = mono +-paths-output = steelseries-arctis-7-output-mono +-paths-input = steelseries-arctis-7-input ++paths-output = usb-gaming-headset-output-mono ++paths-input = usb-gaming-headset-input + + [Mapping analog-stereo] + device-strings = hw:%f,1,0 + channel-map = left,right +-paths-output = steelseries-arctis-7-output-stereo ++paths-output = usb-gaming-headset-output-stereo + direction = output + + [Profile output:analog-mono+output:analog-stereo+input:analog-mono] +-- +2.16.4 + diff --git a/0006-alsa-ucm-use-ucm2-name-for-the-direct-card-index-ope.patch b/0006-alsa-ucm-use-ucm2-name-for-the-direct-card-index-ope.patch new file mode 100644 index 0000000..63e6cc4 --- /dev/null +++ b/0006-alsa-ucm-use-ucm2-name-for-the-direct-card-index-ope.patch @@ -0,0 +1,61 @@ +From c8f065250dde966825f171ff817f7301f423a42e Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 23 Nov 2019 15:17:30 +0100 +Subject: [PATCH] alsa-ucm: use ucm2 name for the direct card index open + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 28 ++++++++++++++++++---------- + 1 file changed, 18 insertions(+), 10 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 0a40ca8fe654..aeb4e59e323c 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -576,17 +576,25 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) { + const char **verb_list; + int num_verbs, i, err = 0; + +- /* is UCM available for this card ? */ +- err = snd_card_get_name(card_index, &card_name); +- if (err < 0) { +- pa_log("Card can't get card_name from card_index %d", card_index); +- goto name_fail; +- } +- ++ /* support multiple card instances, address card directly by index */ ++ card_name = pa_sprintf_malloc("hw:%i", card_index); ++ if (card_name == NULL) ++ return -ENOMEM; + err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); + if (err < 0) { +- pa_log_info("UCM not available for card %s", card_name); +- goto ucm_mgr_fail; ++ /* fallback longname: is UCM available for this card ? */ ++ pa_xfree(card_name); ++ err = snd_card_get_name(card_index, &card_name); ++ if (err < 0) { ++ pa_log("Card can't get card_name from card_index %d", card_index); ++ goto name_fail; ++ } ++ ++ err = snd_use_case_mgr_open(&ucm->ucm_mgr, card_name); ++ if (err < 0) { ++ pa_log_info("UCM not available for card %s", card_name); ++ goto ucm_mgr_fail; ++ } + } + + pa_log_info("UCM available for card %s", card_name); +@@ -626,7 +634,7 @@ ucm_verb_fail: + } + + ucm_mgr_fail: +- free(card_name); ++ pa_xfree(card_name); + + name_fail: + return err; +-- +2.16.4 + diff --git a/0007-alsa-ucm-add-mixer-IDs-to-ucm_items.patch b/0007-alsa-ucm-add-mixer-IDs-to-ucm_items.patch new file mode 100644 index 0000000..8a0005f --- /dev/null +++ b/0007-alsa-ucm-add-mixer-IDs-to-ucm_items.patch @@ -0,0 +1,72 @@ +From ab5be56a10a83bfdfd7f40c02245db039e6eb730 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 23 Nov 2019 15:50:29 +0100 +Subject: [PATCH] alsa-ucm: add mixer IDs to ucm_items + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 6 ++++++ + src/modules/alsa/alsa-ucm.h | 18 ++++++++++++++++++ + 2 files changed, 24 insertions(+) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index aeb4e59e323c..14056825a25f 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -100,11 +100,17 @@ static struct ucm_items item[] = { + {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE}, + {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME}, + {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH}, ++ {"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM}, ++ {"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM}, ++ {"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE}, + {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY}, + {"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE}, + {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS}, + {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME}, + {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH}, ++ {"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM}, ++ {"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM}, ++ {"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE}, + {"CapturePriority", PA_ALSA_PROP_UCM_CAPTURE_PRIORITY}, + {"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE}, + {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS}, +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index c926f3cc39a6..4feb8c0bfc3f 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -51,6 +51,15 @@ typedef void snd_use_case_mgr_t; + /** For devices: Playback switch e.g PlaybackSwitch */ + #define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH "alsa.ucm.playback.switch" + ++/** For devices: Playback mixer identifier */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM "alsa.ucm.playback.mixer.element" ++ ++/** For devices: Playback mixer master identifier */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM "alsa.ucm.playback.master.element" ++ ++/** For devices: Playback mixer master type */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" ++ + /** For devices: Playback priority */ + #define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY "alsa.ucm.playback.priority" + +@@ -69,6 +78,15 @@ typedef void snd_use_case_mgr_t; + /** For devices: Capture switch e.g CaptureSwitch */ + #define PA_ALSA_PROP_UCM_CAPTURE_SWITCH "alsa.ucm.capture.switch" + ++/** For devices: Capture mixer identifier */ ++#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM "alsa.ucm.capture.mixer.element" ++ ++/** For devices: Capture mixer identifier */ ++#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM "alsa.ucm.capture.master.element" ++ ++/** For devices: Capture mixer identifier */ ++#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" ++ + /** For devices: Capture priority */ + #define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY "alsa.ucm.capture.priority" + +-- +2.16.4 + diff --git a/0008-alsa-mixer-handle-the-index-for-ALSA-mixer-element-i.patch b/0008-alsa-mixer-handle-the-index-for-ALSA-mixer-element-i.patch new file mode 100644 index 0000000..27a664e --- /dev/null +++ b/0008-alsa-mixer-handle-the-index-for-ALSA-mixer-element-i.patch @@ -0,0 +1,820 @@ +From 7f4b8e1a7c2c6a873ddb0207d9b407605bd3e7d6 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 23 Nov 2019 20:59:24 +0100 +Subject: [PATCH] alsa-mixer: handle the index for ALSA mixer element + identifiers + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.c | 286 ++++++++++++++++++++++++++++++------------ + src/modules/alsa/alsa-mixer.h | 12 +- + 2 files changed, 217 insertions(+), 81 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index 1309cec5d8f8..38ace783a997 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -107,6 +107,33 @@ struct description_map { + const char *description; + }; + ++static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { ++ if (id->index > 0) { ++ snprintf(dst, dst_len, "'%s',%d", id->name, id->index); ++ } else { ++ snprintf(dst, dst_len, "'%s'", id->name); ++ } ++ return dst; ++} ++ ++static int alsa_id_decode(const char *src, char *name, int *index) { ++ char *idx; ++ ++ *index = 0; ++ strcpy(name, src); ++ idx = strchr(name, ','); ++ if (idx == NULL) ++ return 0; ++ *idx = '\0'; ++ idx++; ++ if (*idx < '0' || *idx > '9') { ++ pa_log("Element %s: index value is invalid", src); ++ return 1; ++ } ++ *index = atoi(idx); ++ return 0; ++} ++ + pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) { + pa_alsa_jack *jack; + +@@ -641,6 +668,7 @@ static void decibel_fix_free(pa_alsa_decibel_fix *db_fix) { + pa_xfree(db_fix->name); + pa_xfree(db_fix->db_values); + ++ pa_xfree(db_fix->key); + pa_xfree(db_fix); + } + +@@ -656,7 +684,7 @@ static void element_free(pa_alsa_element *e) { + if (e->db_fix) + decibel_fix_free(e->db_fix); + +- pa_xfree(e->alsa_name); ++ pa_xfree(e->alsa_id.name); + pa_xfree(e); + } + +@@ -717,11 +745,11 @@ static pa_volume_t from_alsa_volume(long v, long min, long max) { + return (pa_volume_t) round(((double) (v - min) * PA_VOLUME_NORM) / (double) (max - min)); + } + +-#define SELEM_INIT(sid, name) \ +- do { \ +- snd_mixer_selem_id_alloca(&(sid)); \ +- snd_mixer_selem_id_set_name((sid), (name)); \ +- snd_mixer_selem_id_set_index((sid), 0); \ ++#define SELEM_INIT(sid, aid) \ ++ do { \ ++ snd_mixer_selem_id_alloca(&(sid)); \ ++ snd_mixer_selem_id_set_name((sid), (aid)->name); \ ++ snd_mixer_selem_id_set_index((sid), (aid)->index); \ + } while(false) + + static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v) { +@@ -729,6 +757,7 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann + snd_mixer_elem_t *me; + snd_mixer_selem_channel_id_t c; + pa_channel_position_mask_t mask = 0; ++ char buf[64]; + unsigned k; + + pa_assert(m); +@@ -736,9 +765,10 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann + pa_assert(cm); + pa_assert(v); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return -1; + } + +@@ -763,14 +793,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann + if (value < e->db_fix->min_step) { + value = e->db_fix->min_step; + snd_mixer_selem_set_playback_volume(me, c, value); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_debug("Playback volume for element %s channel %i was below the dB fix limit. " +- "Volume reset to %0.2f dB.", e->alsa_name, c, ++ "Volume reset to %0.2f dB.", buf, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } else if (value > e->db_fix->max_step) { + value = e->db_fix->max_step; + snd_mixer_selem_set_playback_volume(me, c, value); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_debug("Playback volume for element %s channel %i was over the dB fix limit. " +- "Volume reset to %0.2f dB.", e->alsa_name, c, ++ "Volume reset to %0.2f dB.", buf, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } + +@@ -791,14 +823,16 @@ static int element_get_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann + if (value < e->db_fix->min_step) { + value = e->db_fix->min_step; + snd_mixer_selem_set_capture_volume(me, c, value); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_debug("Capture volume for element %s channel %i was below the dB fix limit. " +- "Volume reset to %0.2f dB.", e->alsa_name, c, ++ "Volume reset to %0.2f dB.", buf, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } else if (value > e->db_fix->max_step) { + value = e->db_fix->max_step; + snd_mixer_selem_set_capture_volume(me, c, value); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_debug("Capture volume for element %s channel %i was over the dB fix limit. " +- "Volume reset to %0.2f dB.", e->alsa_name, c, ++ "Volume reset to %0.2f dB.", buf, c, + e->db_fix->db_values[value - e->db_fix->min_step] / 100.0); + } + +@@ -896,14 +930,16 @@ static int element_get_switch(pa_alsa_element *e, snd_mixer_t *m, bool *b) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + snd_mixer_selem_channel_id_t c; ++ char buf[64]; + + pa_assert(m); + pa_assert(e); + pa_assert(b); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return -1; + } + +@@ -1057,6 +1093,7 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann + snd_mixer_elem_t *me; + snd_mixer_selem_channel_id_t c; + pa_channel_position_mask_t mask = 0; ++ char buf[64]; + unsigned k; + + pa_assert(m); +@@ -1065,9 +1102,10 @@ static int element_set_volume(pa_alsa_element *e, snd_mixer_t *m, const pa_chann + pa_assert(v); + pa_assert(pa_cvolume_compatible_with_channel_map(v, cm)); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return -1; + } + +@@ -1250,14 +1288,16 @@ int pa_alsa_path_set_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_ma + static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { + snd_mixer_elem_t *me; + snd_mixer_selem_id_t *sid; ++ char buf[64]; + int r; + + pa_assert(m); + pa_assert(e); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return -1; + } + +@@ -1266,8 +1306,10 @@ static int element_set_switch(pa_alsa_element *e, snd_mixer_t *m, bool b) { + else + r = snd_mixer_selem_set_capture_switch_all(me, b); + +- if (r < 0) +- pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); ++ if (r < 0) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); ++ } + + return r; + } +@@ -1302,13 +1344,15 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { + int r = 0; + long volume = -1; + bool volume_set = false; ++ char buf[64]; + + pa_assert(m); + pa_assert(e); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return -1; + } + +@@ -1351,8 +1395,10 @@ static int element_set_constant_volume(pa_alsa_element *e, snd_mixer_t *m) { + r = snd_mixer_selem_set_capture_dB_all(me, 0, -1); + } + +- if (r < 0) +- pa_log_warn("Failed to set volume of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); ++ if (r < 0) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to set volume of %s: %s", buf, pa_alsa_strerror(errno)); ++ } + + return r; + } +@@ -1530,6 +1576,7 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + int r; + bool is_mono; + pa_channel_position_t p; ++ char buf[64]; + + if (e->direction == PA_ALSA_DIRECTION_OUTPUT) { + if (!snd_mixer_selem_has_playback_volume(me)) { +@@ -1555,29 +1602,33 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + r = snd_mixer_selem_get_capture_volume_range(me, &e->min_volume, &e->max_volume); + + if (r < 0) { +- pa_log_warn("Failed to get volume range of %s: %s", e->alsa_name, pa_alsa_strerror(r)); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to get volume range of %s: %s", buf, pa_alsa_strerror(r)); + return false; + } + + if (e->min_volume >= e->max_volume) { +- pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", +- e->min_volume, e->max_volume); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Your kernel driver is broken for element %s: it reports a volume range from %li to %li which makes no sense.", ++ buf, e->min_volume, e->max_volume); + return false; + } + if (e->volume_use == PA_ALSA_VOLUME_CONSTANT && (e->min_volume > e->constant_volume || e->max_volume < e->constant_volume)) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Constant volume %li configured for element %s, but the available range is from %li to %li.", +- e->constant_volume, e->alsa_name, e->min_volume, e->max_volume); ++ e->constant_volume, buf, e->min_volume, e->max_volume); + return false; + } + + + if (e->db_fix && ((e->min_volume > e->db_fix->min_step) || (e->max_volume < e->db_fix->max_step))) { +- pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " +- "real hardware range (%li-%li). Disabling the decibel fix.", e->alsa_name, +- e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("The step range of the decibel fix for element %s (%li-%li) doesn't fit to the " ++ "real hardware range (%li-%li). Disabling the decibel fix.", buf, ++ e->db_fix->min_step, e->db_fix->max_step, e->min_volume, e->max_volume); + +- decibel_fix_free(e->db_fix); +- e->db_fix = NULL; ++ decibel_fix_free(e->db_fix); ++ e->db_fix = NULL; + } + + if (e->db_fix) { +@@ -1598,19 +1649,22 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + long max_dB_checked = 0; + + if (element_ask_vol_dB(me, e->direction, e->min_volume, &min_dB_checked) < 0) { +- pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->min_volume); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->min_volume); + return false; + } + + if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB_checked) < 0) { +- pa_log_warn("Failed to query the dB value for %s at volume level %li", e->alsa_name, e->max_volume); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to query the dB value for %s at volume level %li", buf, e->max_volume); + return false; + } + + if (min_dB != min_dB_checked || max_dB != max_dB_checked) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Your kernel driver is broken: the reported dB range for %s (from %0.2f dB to %0.2f dB) " + "doesn't match the dB values at minimum and maximum volume levels: %0.2f dB at level %li, " +- "%0.2f dB at level %li.", e->alsa_name, min_dB / 100.0, max_dB / 100.0, ++ "%0.2f dB at level %li.", buf, min_dB / 100.0, max_dB / 100.0, + min_dB_checked / 100.0, e->min_volume, max_dB_checked / 100.0, e->max_volume); + return false; + } +@@ -1629,11 +1683,12 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + } + + if (e->volume_limit >= 0) { +- if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) ++ if (e->volume_limit <= e->min_volume || e->volume_limit > e->max_volume) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_warn("Volume limit for element %s of path %s is invalid: %li isn't within the valid range " + "%li-%li. The volume limit is ignored.", +- e->alsa_name, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); +- else { ++ buf, e->path->name, e->volume_limit, e->min_volume + 1, e->max_volume); ++ } else { + e->max_volume = e->volume_limit; + + if (e->has_dB) { +@@ -1641,7 +1696,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + e->db_fix->max_step = e->max_volume; + e->max_dB = ((double) e->db_fix->db_values[e->db_fix->max_step - e->db_fix->min_step]) / 100.0; + } else if (element_ask_vol_dB(me, e->direction, e->max_volume, &max_dB) < 0) { +- pa_log_warn("Failed to get dB value of %s: %s", e->alsa_name, pa_alsa_strerror(r)); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to get dB value of %s: %s", buf, pa_alsa_strerror(r)); + e->has_dB = false; + } else + e->max_dB = ((double) max_dB) / 100.0; +@@ -1681,7 +1737,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + } + + if (e->n_channels <= 0) { +- pa_log_warn("Volume element %s with no channels?", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Volume element %s with no channels?", buf); + return false; + } else if (e->n_channels > 2) { + /* FIXME: In some places code like this is used: +@@ -1695,7 +1752,8 @@ static bool element_probe_volume(pa_alsa_element *e, snd_mixer_elem_t *me) { + * Since the array size is fixed at 2, we obviously + * don't support elements with more than two + * channels... */ +- pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", e->alsa_name, e->n_channels); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Volume element %s has %u channels. That's too much! I can't handle that!", buf, e->n_channels); + return false; + } + +@@ -1733,7 +1791,7 @@ static int element_probe(pa_alsa_element *e, snd_mixer_t *m) { + pa_assert(e); + pa_assert(e->path); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + + if (!(me = snd_mixer_find_selem(m, sid))) { + +@@ -1854,6 +1912,8 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) + + static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool prefixed) { + pa_alsa_element *e; ++ char *name; ++ int index; + + pa_assert(p); + pa_assert(section); +@@ -1869,16 +1929,22 @@ static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool p + if (strchr(section, ':')) + return NULL; + +- if (p->last_element && pa_streq(p->last_element->alsa_name, section)) ++ name = alloca(strlen(section) + 1); ++ if (alsa_id_decode(section, name, &index)) ++ return NULL; ++ ++ if (p->last_element && pa_streq(p->last_element->alsa_id.name, name) && ++ p->last_element->alsa_id.index == index) + return p->last_element; + + PA_LLIST_FOREACH(e, p->elements) +- if (pa_streq(e->alsa_name, section)) ++ if (pa_streq(e->alsa_id.name, name) && e->alsa_id.index == index) + goto finish; + + e = pa_xnew0(pa_alsa_element, 1); + e->path = p; +- e->alsa_name = pa_xstrdup(section); ++ e->alsa_id.name = pa_xstrdup(name); ++ e->alsa_id.index = index; + e->direction = p->direction; + e->volume_limit = -1; + +@@ -1912,10 +1978,12 @@ finish: + } + + static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { +- char *en; ++ char *en, *name; + const char *on; + pa_alsa_option *o; + pa_alsa_element *e; ++ size_t len; ++ int index; + + if (!pa_startswith(section, "Option ")) + return NULL; +@@ -1926,18 +1994,25 @@ static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { + if (!(on = strchr(section, ':'))) + return NULL; + +- en = pa_xstrndup(section, on - section); ++ len = on - section; ++ en = alloca(len + 1); ++ strncpy(en, section, len); ++ en[len] = '\0'; ++ ++ name = alloca(strlen(en) + 1); ++ if (alsa_id_decode(en, name, &index)) ++ return NULL; ++ + on++; + + if (p->last_option && +- pa_streq(p->last_option->element->alsa_name, en) && ++ pa_streq(p->last_option->element->alsa_id.name, name) && ++ p->last_option->element->alsa_id.index == index && + pa_streq(p->last_option->alsa_name, on)) { +- pa_xfree(en); + return p->last_option; + } + + pa_assert_se(e = element_get(p, en, false)); +- pa_xfree(en); + + PA_LLIST_FOREACH(o, e->options) + if (pa_streq(o->alsa_name, on)) +@@ -2393,14 +2468,16 @@ static int jack_parse_append_pcm_to_name(pa_config_parser_state *state) { + static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; ++ char buf[64]; + int r; + + pa_assert(e); + pa_assert(m); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return -1; + } + +@@ -2411,14 +2488,18 @@ static int element_set_option(pa_alsa_element *e, snd_mixer_t *m, int alsa_idx) + else + r = snd_mixer_selem_set_capture_switch_all(me, alsa_idx); + +- if (r < 0) +- pa_log_warn("Failed to set switch of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); ++ if (r < 0) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to set switch of %s: %s", buf, pa_alsa_strerror(errno)); ++ } + + } else { + pa_assert(e->enumeration_use == PA_ALSA_ENUMERATION_SELECT); + +- if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) +- pa_log_warn("Failed to set enumeration of %s: %s", e->alsa_name, pa_alsa_strerror(errno)); ++ if ((r = snd_mixer_selem_set_enum_item(me, 0, alsa_idx)) < 0) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Failed to set enumeration of %s: %s", buf, pa_alsa_strerror(errno)); ++ } + } + + return r; +@@ -2462,6 +2543,7 @@ static int option_verify(pa_alsa_option *o) { + { "output-speaker", N_("Speaker") }, + { "output-headphones", N_("Headphones") } + }; ++ char buf[64]; + + pa_assert(o); + +@@ -2472,14 +2554,16 @@ static int option_verify(pa_alsa_option *o) { + + if (o->element->enumeration_use != PA_ALSA_ENUMERATION_SELECT && + o->element->switch_use != PA_ALSA_SWITCH_SELECT) { +- pa_log("Element %s of option %s not set for select.", o->element->alsa_name, o->name); ++ alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); ++ pa_log("Element %s of option %s not set for select.", buf, o->name); + return -1; + } + + if (o->element->switch_use == PA_ALSA_SWITCH_SELECT && + !pa_streq(o->alsa_name, "on") && + !pa_streq(o->alsa_name, "off")) { +- pa_log("Switch %s options need be named off or on ", o->element->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &o->element->alsa_id); ++ pa_log("Switch %s options need be named off or on ", buf); + return -1; + } + +@@ -2495,6 +2579,7 @@ static int option_verify(pa_alsa_option *o) { + + static int element_verify(pa_alsa_element *e) { + pa_alsa_option *o; ++ char buf[64]; + + pa_assert(e); + +@@ -2503,12 +2588,14 @@ static int element_verify(pa_alsa_element *e) { + (e->required_any != PA_ALSA_REQUIRED_IGNORE && e->required_any == e->required_absent) || + (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required_any != PA_ALSA_REQUIRED_IGNORE) || + (e->required_absent == PA_ALSA_REQUIRED_ANY && e->required != PA_ALSA_REQUIRED_IGNORE)) { +- pa_log("Element %s cannot be required and absent at the same time.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log("Element %s cannot be required and absent at the same time.", buf); + return -1; + } + + if (e->switch_use == PA_ALSA_SWITCH_SELECT && e->enumeration_use == PA_ALSA_ENUMERATION_SELECT) { +- pa_log("Element %s cannot set select for both switch and enumeration.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log("Element %s cannot set select for both switch and enumeration.", buf); + return -1; + } + +@@ -2660,9 +2747,15 @@ fail: + pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction) { + pa_alsa_path *p; + pa_alsa_element *e; ++ char *name; ++ int index; + + pa_assert(element); + ++ name = alloca(strlen(element) + 1); ++ if (alsa_id_decode(element, name, &index)) ++ return NULL; ++ + p = pa_xnew0(pa_alsa_path, 1); + p->name = pa_xstrdup(element); + p->direction = direction; +@@ -2670,7 +2763,8 @@ pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t d + + e = pa_xnew0(pa_alsa_element, 1); + e->path = p; +- e->alsa_name = pa_xstrdup(element); ++ e->alsa_id.name = pa_xstrdup(name); ++ e->alsa_id.index = index; + e->direction = direction; + e->volume_limit = -1; + +@@ -2820,6 +2914,7 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m + double min_dB[PA_CHANNEL_POSITION_MAX], max_dB[PA_CHANNEL_POSITION_MAX]; + pa_channel_position_t t; + pa_channel_position_mask_t path_volume_channels = 0; ++ char buf[64]; + + pa_assert(p); + pa_assert(m); +@@ -2843,12 +2938,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m + } + + PA_LLIST_FOREACH(e, p->elements) { ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + if (element_probe(e, m) < 0) { + p->supported = false; +- pa_log_debug("Probe of element '%s' failed.", e->alsa_name); ++ pa_log_debug("Probe of element %s failed.", buf); + return -1; + } +- pa_log_debug("Probe of element '%s' succeeded (volume=%d, switch=%d, enumeration=%d).", e->alsa_name, e->volume_use, e->switch_use, e->enumeration_use); ++ pa_log_debug("Probe of element %s succeeded (volume=%d, switch=%d, enumeration=%d).", buf, e->volume_use, e->switch_use, e->enumeration_use); + + if (ignore_dB) + e->has_dB = false; +@@ -2884,13 +2980,13 @@ int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m + * which cannot do dB volumes, so we we need + * to 'neutralize' this slider */ + e->volume_use = PA_ALSA_VOLUME_ZERO; +- pa_log_info("Zeroing volume of '%s' on path '%s'", e->alsa_name, p->name); ++ pa_log_info("Zeroing volume of %s on path '%s'", buf, p->name); + } + } + } else if (p->has_volume) { + /* We can't use this volume, so let's ignore it */ + e->volume_use = PA_ALSA_VOLUME_IGNORE; +- pa_log_info("Ignoring volume of '%s' on path '%s' (missing dB info)", e->alsa_name, p->name); ++ pa_log_info("Ignoring volume of %s on path '%s' (missing dB info)", buf, p->name); + } + p->has_volume = true; + } +@@ -2954,11 +3050,14 @@ void pa_alsa_option_dump(pa_alsa_option *o) { + } + + void pa_alsa_element_dump(pa_alsa_element *e) { ++ char buf[64]; ++ + pa_alsa_option *o; + pa_assert(e); + ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); + pa_log_debug("Element %s, direction=%i, switch=%i, volume=%i, volume_limit=%li, enumeration=%i, required=%i, required_any=%i, required_absent=%i, mask=0x%llx, n_channels=%u, override_map=%s", +- e->alsa_name, ++ buf, + e->direction, + e->switch_use, + e->volume_use, +@@ -3008,14 +3107,16 @@ void pa_alsa_path_dump(pa_alsa_path *p) { + static void element_set_callback(pa_alsa_element *e, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata) { + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; ++ char buf[64]; + + pa_assert(e); + pa_assert(m); + pa_assert(cb); + +- SELEM_INIT(sid, e->alsa_name); ++ SELEM_INIT(sid, &e->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", e->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &e->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return; + } + +@@ -3081,6 +3182,8 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d + char **pn = NULL, **en = NULL, **ie; + pa_alsa_decibel_fix *db_fix; + void *state, *state2; ++ char name[64]; ++ int index; + + pa_assert(m); + pa_assert(m->profile_set); +@@ -3160,9 +3263,18 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d + if (je == ie) + continue; + ++ if (strlen(*je) + 1 >= sizeof(name)) { ++ pa_log("Element identifier %s is too long!", *je); ++ continue; ++ } ++ ++ if (alsa_id_decode(*je, name, &index)) ++ continue; ++ + e = pa_xnew0(pa_alsa_element, 1); + e->path = p; +- e->alsa_name = pa_xstrdup(*je); ++ e->alsa_id.name = pa_xstrdup(name); ++ e->alsa_id.index = index; + e->direction = direction; + e->required_absent = PA_ALSA_REQUIRED_ANY; + e->volume_limit = -1; +@@ -3183,7 +3295,8 @@ finish: + pa_alsa_element *e; + + PA_LLIST_FOREACH(e, p->elements) { +- if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_name)) { ++ if (e->volume_use != PA_ALSA_VOLUME_IGNORE && pa_streq(db_fix->name, e->alsa_id.name) && ++ db_fix->index == e->alsa_id.index) { + /* The profile set that contains the dB fix may be freed + * before the element, so we have to copy the dB fix + * object. */ +@@ -3256,6 +3369,8 @@ static bool enumeration_is_subset(pa_alsa_option *a_options, pa_alsa_option *b_o + * Compares two elements to see if a is a subset of b + */ + static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_t *m) { ++ char buf[64]; ++ + pa_assert(a); + pa_assert(b); + pa_assert(m); +@@ -3293,9 +3408,10 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ + snd_mixer_selem_id_t *sid; + snd_mixer_elem_t *me; + +- SELEM_INIT(sid, a->alsa_name); ++ SELEM_INIT(sid, &a->alsa_id); + if (!(me = snd_mixer_find_selem(m, sid))) { +- pa_log_warn("Element %s seems to have disappeared.", a->alsa_name); ++ alsa_id_str(buf, sizeof(buf), &a->alsa_id); ++ pa_log_warn("Element %s seems to have disappeared.", buf); + return false; + } + +@@ -3325,8 +3441,9 @@ static bool element_is_subset(pa_alsa_element *a, pa_alsa_element *b, snd_mixer_ + return false; + for (s = 0; s <= SND_MIXER_SCHN_LAST; s++) + if (a->masks[s][a->n_channels-1] != b->masks[s][b->n_channels-1]) { ++ alsa_id_str(buf, sizeof(buf), &a->alsa_id); + pa_log_debug("Element %s is not a subset - mask a: 0x%" PRIx64 ", mask b: 0x%" PRIx64 ", at channel %d", +- a->alsa_name, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); ++ buf, a->masks[s][a->n_channels-1], b->masks[s][b->n_channels-1], s); + return false; + } + } +@@ -3423,7 +3540,8 @@ static void path_set_condense(pa_alsa_path_set *ps, snd_mixer_t *m) { + break; + + PA_LLIST_FOREACH(eb, p2->elements) { +- if (pa_streq(ea->alsa_name, eb->alsa_name)) { ++ if (pa_streq(ea->alsa_id.name, eb->alsa_id.name) && ++ ea->alsa_id.index == eb->alsa_id.index) { + found_matching_element = true; + is_subset = element_is_subset(ea, eb, m); + break; +@@ -3600,22 +3718,30 @@ static pa_alsa_profile *profile_get(pa_alsa_profile_set *ps, const char *name) { + return p; + } + +-static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *name) { ++static pa_alsa_decibel_fix *decibel_fix_get(pa_alsa_profile_set *ps, const char *alsa_id) { + pa_alsa_decibel_fix *db_fix; ++ char *name; ++ int index; + +- if (!pa_startswith(name, "DecibelFix ")) ++ if (!pa_startswith(alsa_id, "DecibelFix ")) + return NULL; + +- name += 11; ++ alsa_id += 11; + +- if ((db_fix = pa_hashmap_get(ps->decibel_fixes, name))) ++ if ((db_fix = pa_hashmap_get(ps->decibel_fixes, alsa_id))) + return db_fix; + ++ name = alloca(strlen(alsa_id) + 1); ++ if (alsa_id_decode(alsa_id, name, &index)) ++ return NULL; ++ + db_fix = pa_xnew0(pa_alsa_decibel_fix, 1); + db_fix->profile_set = ps; + db_fix->name = pa_xstrdup(name); ++ db_fix->index = index; ++ db_fix->key = pa_xstrdup(alsa_id); + +- pa_hashmap_put(ps->decibel_fixes, db_fix->name, db_fix); ++ pa_hashmap_put(ps->decibel_fixes, db_fix->key, db_fix); + + return db_fix; + } +diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h +index 65b071165c6b..709f270fbfa5 100644 +--- a/src/modules/alsa/alsa-mixer.h ++++ b/src/modules/alsa/alsa-mixer.h +@@ -34,6 +34,7 @@ + typedef struct pa_alsa_fdlist pa_alsa_fdlist; + typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata; + typedef struct pa_alsa_setting pa_alsa_setting; ++typedef struct pa_alsa_mixer_id pa_alsa_mixer_id; + typedef struct pa_alsa_option pa_alsa_option; + typedef struct pa_alsa_element pa_alsa_element; + typedef struct pa_alsa_jack pa_alsa_jack; +@@ -97,6 +98,12 @@ struct pa_alsa_setting { + unsigned priority; + }; + ++/* ALSA mixer element identifier */ ++struct pa_alsa_mixer_id { ++ char *name; ++ int index; ++}; ++ + /* An option belongs to an element and refers to one enumeration item + * of the element is an enumeration item, or a switch status if the + * element is a switch item. */ +@@ -123,7 +130,7 @@ struct pa_alsa_element { + pa_alsa_path *path; + PA_LLIST_FIELDS(pa_alsa_element); + +- char *alsa_name; ++ struct pa_alsa_mixer_id alsa_id; + pa_alsa_direction_t direction; + + pa_alsa_switch_use_t switch_use; +@@ -315,9 +322,12 @@ struct pa_alsa_profile { + }; + + struct pa_alsa_decibel_fix { ++ char *key; ++ + pa_alsa_profile_set *profile_set; + + char *name; /* Alsa volume element name. */ ++ int index; /* Alsa volume element index. */ + long min_step; + long max_step; + +-- +2.16.4 + diff --git a/0009-alsa-mixer-improve-alsa_id_decode-function.patch b/0009-alsa-mixer-improve-alsa_id_decode-function.patch new file mode 100644 index 0000000..048a2ed --- /dev/null +++ b/0009-alsa-mixer-improve-alsa_id_decode-function.patch @@ -0,0 +1,51 @@ +From 1c240b7a12e9e2f7c2266d18cbb74130bb81277e Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Tue, 26 Nov 2019 10:35:14 +0100 +Subject: [PATCH] alsa-mixer: improve alsa_id_decode() function + +Accept those identifiers: + + Speaker,1 + 'Speaker',1 + "Speaker",1 + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index 38ace783a997..f57aabe5d885 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -117,11 +117,24 @@ static char *alsa_id_str(char *dst, size_t dst_len, pa_alsa_mixer_id *id) { + } + + static int alsa_id_decode(const char *src, char *name, int *index) { +- char *idx; ++ char *idx, c; ++ int i; + + *index = 0; +- strcpy(name, src); +- idx = strchr(name, ','); ++ c = src[0]; ++ /* Strip quotes in entries such as 'Speaker',1 or "Speaker",1 */ ++ if (c == '\'' || c == '"') { ++ strcpy(name, src + 1); ++ for (i = 0; name[i] != '\0' && name[i] != c; i++); ++ idx = NULL; ++ if (name[i]) { ++ name[i] = '\0'; ++ idx = strchr(name + i + 1, ','); ++ } ++ } else { ++ strcpy(name, src); ++ idx = strchr(name, ','); ++ } + if (idx == NULL) + return 0; + *idx = '\0'; +-- +2.16.4 + diff --git a/0010-alsa-ucm-Support-Playback-CaptureVolume.patch b/0010-alsa-ucm-Support-Playback-CaptureVolume.patch new file mode 100644 index 0000000..debf743 --- /dev/null +++ b/0010-alsa-ucm-Support-Playback-CaptureVolume.patch @@ -0,0 +1,795 @@ +From 3dfccada466bef64f73ef9be3d94eaee7b6f9a60 Mon Sep 17 00:00:00 2001 +From: Arun Raghavan +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 + diff --git a/0011-alsa-ucm-Fix-volume-control-based-on-review.patch b/0011-alsa-ucm-Fix-volume-control-based-on-review.patch new file mode 100644 index 0000000..e4f912b --- /dev/null +++ b/0011-alsa-ucm-Fix-volume-control-based-on-review.patch @@ -0,0 +1,242 @@ +From 9acacd9ba3b9f4df0957e9ddaacbcee00396175c Mon Sep 17 00:00:00 2001 +From: Jaska Uimonen +Date: Tue, 1 Oct 2019 18:34:17 +0300 +Subject: [PATCH] alsa-ucm: Fix volume control based on review + +- sync mixer logic added +- mixer path creation, empty set in mapping creation, paths added in path creation +- path creation moved inside new port creation as it might be called twice otherwise +- some comments added +--- + src/modules/alsa/alsa-sink.c | 31 ++++++++-------------------- + src/modules/alsa/alsa-source.c | 29 ++++++++------------------ + src/modules/alsa/alsa-ucm.c | 47 +++++++++++++++++++++++++++++------------- + src/modules/alsa/alsa-ucm.h | 2 +- + 4 files changed, 52 insertions(+), 57 deletions(-) + +diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c +index 08d4d1f38b80..0a5c92529af3 100644 +--- a/src/modules/alsa/alsa-sink.c ++++ b/src/modules/alsa/alsa-sink.c +@@ -1266,7 +1266,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) { + + /* port may be NULL, because if we use a synthesized mixer path, then the + * sink has no ports. */ +- if (port) { ++ if (port && !u->ucm_context) { + pa_alsa_port_data *data; + + data = PA_DEVICE_PORT_DATA(port); +@@ -1648,28 +1648,19 @@ 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->mixer_handle); + pa_assert(u->ucm_context); + +- u->mixer_path = data->path; ++ data = PA_DEVICE_PORT_DATA(p); ++ pa_assert_se(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); +- } +- } ++ if (s->flags & PA_SINK_DEFERRED_VOLUME) ++ pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_SYNC_MIXER, p, 0, NULL); ++ else ++ sync_mixer(u, p); + + return pa_alsa_ucm_set_port(u->ucm_context, p, true); + } +@@ -2091,6 +2082,7 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de + } + + static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) { ++ + if (!mapping && !element) + return; + +@@ -2099,11 +2091,6 @@ 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))) +diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c +index 657ed5aeda11..d186101720b8 100644 +--- a/src/modules/alsa/alsa-source.c ++++ b/src/modules/alsa/alsa-source.c +@@ -1137,7 +1137,7 @@ static void sync_mixer(struct userdata *u, pa_device_port *port) { + + /* port may be NULL, because if we use a synthesized mixer path, then the + * source has no ports. */ +- if (port) { ++ if (port && !u->ucm_context) { + pa_alsa_port_data *data; + + data = PA_DEVICE_PORT_DATA(port); +@@ -1523,24 +1523,17 @@ static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) { + + pa_assert(u); + pa_assert(p); ++ pa_assert(u->mixer_handle); + pa_assert(u->ucm_context); + +- u->mixer_path = data->path; ++ data = PA_DEVICE_PORT_DATA(p); ++ pa_assert_se(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); +- } +- } ++ if (s->flags & PA_SOURCE_DEFERRED_VOLUME) ++ pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_SYNC_MIXER, p, 0, NULL); ++ else ++ sync_mixer(u, p); + + return pa_alsa_ucm_set_port(u->ucm_context, p, false); + } +@@ -1805,11 +1798,6 @@ 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))) +@@ -2404,6 +2392,7 @@ static void userdata_free(struct userdata *u) { + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + ++ /* 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); + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 349a59566200..a812b52f449e 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -852,21 +852,29 @@ static void ucm_add_port_combination( + + 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); ++ 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); ++ ++ /* Add path also to already created empty path set */ ++ dev = sorted[0]; ++ if (is_sink) ++ pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(volume_element), path); ++ else ++ pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(volume_element), path); ++ } ++ } + } + } + +@@ -1185,16 +1193,27 @@ int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *p + + static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) { + ++ pa_alsa_path_set *ps; ++ ++ /* create empty path set for the future path additions */ ++ ps = pa_xnew0(pa_alsa_path_set, 1); ++ ps->direction = m->direction; ++ ps->paths = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); ++ + switch (m->direction) { + case PA_ALSA_DIRECTION_ANY: + pa_idxset_put(p->output_mappings, m, NULL); + pa_idxset_put(p->input_mappings, m, NULL); ++ m->output_path_set = ps; ++ m->input_path_set = ps; + break; + case PA_ALSA_DIRECTION_OUTPUT: + pa_idxset_put(p->output_mappings, m, NULL); ++ m->output_path_set = ps; + break; + case PA_ALSA_DIRECTION_INPUT: + pa_idxset_put(p->input_mappings, m, NULL); ++ m->input_path_set = ps; + break; + } + } +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index 2e39a09a51f3..d8507a83615c 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -240,7 +240,7 @@ struct pa_alsa_ucm_port_data { + * a combination of devices. */ + pa_dynarray *devices; /* pa_alsa_ucm_device */ + +- /* profile -> pa_alsa_path for volume control */ ++ /* profile name -> pa_alsa_path for volume control */ + pa_hashmap *paths; + /* Current path, set when activating profile */ + pa_alsa_path *path; +-- +2.16.4 + diff --git a/0012-alsa-ucm-use-the-correct-mixer-identifiers-as-first.patch b/0012-alsa-ucm-use-the-correct-mixer-identifiers-as-first.patch new file mode 100644 index 0000000..7edbdde --- /dev/null +++ b/0012-alsa-ucm-use-the-correct-mixer-identifiers-as-first.patch @@ -0,0 +1,120 @@ +From dc9dc70fcc1b06788d08b5e7997c9053a13cbce2 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Tue, 26 Nov 2019 10:54:15 +0100 +Subject: [PATCH] alsa-ucm: use the correct mixer identifiers as first + +The mixer identifiers should be used for snd_mixer_selem API. +Use them as first, then try to fallback to the raw control +identifiers. + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 78 ++++++++++++++++++++++++++++++++------------- + 1 file changed, 56 insertions(+), 22 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index a812b52f449e..65ec6941e8de 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -200,6 +200,50 @@ static void ucm_add_devices_to_idxset( + } + } + ++/* Get the volume identifier */ ++static char *ucm_get_mixer_id( ++ pa_alsa_ucm_device *device, ++ const char *mprop, ++ const char *cprop, ++ const char *cid) ++{ ++#if SND_LIB_VERSION >= 0x10201 ++ snd_ctl_elem_id_t *ctl; ++ int err; ++#endif ++ const char *value; ++ char *value2; ++ int index; ++ ++ value = pa_proplist_gets(device->proplist, mprop); ++ if (value) ++ return pa_xstrdup(value); ++ value = pa_proplist_gets(device->proplist, cprop); ++ if (value == NULL) ++ return NULL; ++#if SND_LIB_VERSION >= 0x10201 ++ snd_ctl_elem_id_alloca(&ctl); ++ err = snd_use_case_parse_ctl_elem_id(ctl, cid, value); ++ if (err < 0) ++ return NULL; ++ value = snd_ctl_elem_id_get_name(ctl); ++ index = snd_ctl_elem_id_get_index(ctl); ++#else ++#warning "Upgrade to alsa-lib 1.2.1!" ++ index = 0; ++#endif ++ if (!(value2 = pa_str_strip_suffix(value, " Playback Volume"))) ++ if (!(value2 = pa_str_strip_suffix(value, " Capture Volume"))) ++ if (!(value2 = pa_str_strip_suffix(value, " Volume"))) ++ value2 = pa_xstrdup(value); ++ if (index > 0) { ++ char *mix = pa_sprintf_malloc("'%s',%d", value2, index); ++ pa_xfree(value2); ++ return mix; ++ } ++ return value2; ++} ++ + /* Create a property list for this ucm device */ + static int ucm_get_device_property( + pa_alsa_ucm_device *device, +@@ -296,17 +340,12 @@ static int ucm_get_device_property( + 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); +- } ++ value = ucm_get_mixer_id(device, ++ PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM, ++ PA_ALSA_PROP_UCM_PLAYBACK_VOLUME, ++ "PlaybackVolume"); ++ if (value) ++ pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), (void *)value); + } + + if (device->capture_channels) { /* source device */ +@@ -329,17 +368,12 @@ static int ucm_get_device_property( + 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); +- } ++ value = ucm_get_mixer_id(device, ++ PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM, ++ PA_ALSA_PROP_UCM_CAPTURE_VOLUME, ++ "CaptureVolume"); ++ if (value) ++ pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), (void *)value); + } + + if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { +-- +2.16.4 + diff --git a/0013-alsa-ucm-add-support-for-master-volume.patch b/0013-alsa-ucm-add-support-for-master-volume.patch new file mode 100644 index 0000000..01df5ed --- /dev/null +++ b/0013-alsa-ucm-add-support-for-master-volume.patch @@ -0,0 +1,330 @@ +From 6d830bf0f08c7f92418c2d8b0e73c0415dca03c8 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Wed, 27 Nov 2019 11:34:49 +0100 +Subject: [PATCH] alsa-ucm: add support for master volume + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.c | 20 +++++----- + src/modules/alsa/alsa-mixer.h | 1 + + src/modules/alsa/alsa-ucm.c | 86 +++++++++++++++++++++++++++++++++---------- + src/modules/alsa/alsa-ucm.h | 19 ++++++++++ + 4 files changed, 96 insertions(+), 30 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index f57aabe5d885..ed06f42d86da 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -1923,7 +1923,7 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) + return 0; + } + +-static pa_alsa_element* element_get(pa_alsa_path *p, const char *section, bool prefixed) { ++pa_alsa_element * pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed) { + pa_alsa_element *e; + char *name; + int index; +@@ -2025,7 +2025,7 @@ static pa_alsa_option* option_get(pa_alsa_path *p, const char *section) { + return p->last_option; + } + +- pa_assert_se(e = element_get(p, en, false)); ++ pa_assert_se(e = pa_alsa_element_get(p, en, false)); + + PA_LLIST_FOREACH(o, e->options) + if (pa_streq(o->alsa_name, on)) +@@ -2054,7 +2054,7 @@ static int element_parse_switch(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] Switch makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +@@ -2085,7 +2085,7 @@ static int element_parse_volume(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] Volume makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +@@ -2121,7 +2121,7 @@ static int element_parse_enumeration(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] Enumeration makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +@@ -2213,7 +2213,7 @@ static int element_parse_required(pa_config_parser_state *state) { + + p = state->userdata; + +- e = element_get(p, state->section, true); ++ e = pa_alsa_element_get(p, state->section, true); + o = option_get(p, state->section); + j = jack_get(p, state->section); + if (!e && !o && !j) { +@@ -2279,7 +2279,7 @@ static int element_parse_direction(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +@@ -2305,7 +2305,7 @@ static int element_parse_direction_try_other(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] Direction makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +@@ -2328,7 +2328,7 @@ static int element_parse_volume_limit(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] volume-limit makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +@@ -2386,7 +2386,7 @@ static int element_parse_override_map(pa_config_parser_state *state) { + + p = state->userdata; + +- if (!(e = element_get(p, state->section, true))) { ++ if (!(e = pa_alsa_element_get(p, state->section, true))) { + pa_log("[%s:%u] Override map makes no sense in '%s'", state->filename, state->lineno, state->section); + return -1; + } +diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h +index 709f270fbfa5..0b634f2f2be1 100644 +--- a/src/modules/alsa/alsa-mixer.h ++++ b/src/modules/alsa/alsa-mixer.h +@@ -244,6 +244,7 @@ void pa_alsa_element_dump(pa_alsa_element *e); + + pa_alsa_path *pa_alsa_path_new(const char *paths_dir, const char *fname, pa_alsa_direction_t direction); + pa_alsa_path *pa_alsa_path_synthesize(const char *element, pa_alsa_direction_t direction); ++pa_alsa_element *pa_alsa_element_get(pa_alsa_path *p, const char *section, bool prefixed); + int pa_alsa_path_probe(pa_alsa_path *p, pa_alsa_mapping *mapping, snd_mixer_t *m, bool ignore_dB); + void pa_alsa_path_dump(pa_alsa_path *p); + int pa_alsa_path_get_volume(pa_alsa_path *p, snd_mixer_t *m, const pa_channel_map *cm, pa_cvolume *v); +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 65ec6941e8de..46d016541ddf 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -200,6 +200,14 @@ static void ucm_add_devices_to_idxset( + } + } + ++static void ucm_volume_free(pa_alsa_ucm_volume *vol) { ++ pa_assert(vol); ++ pa_xfree(vol->mixer_elem); ++ pa_xfree(vol->master_elem); ++ pa_xfree(vol->master_type); ++ pa_xfree(vol); ++} ++ + /* Get the volume identifier */ + static char *ucm_get_mixer_id( + pa_alsa_ucm_device *device, +@@ -244,6 +252,32 @@ static char *ucm_get_mixer_id( + return value2; + } + ++/* Get the volume identifier */ ++static pa_alsa_ucm_volume *ucm_get_mixer_volume( ++ pa_alsa_ucm_device *device, ++ const char *mprop, ++ const char *cprop, ++ const char *cid, ++ const char *masterid, ++ const char *mastertype) ++{ ++ pa_alsa_ucm_volume *vol; ++ char *mixer_elem; ++ ++ mixer_elem = ucm_get_mixer_id(device, mprop, cprop, cid); ++ if (mixer_elem == NULL) ++ return NULL; ++ vol = pa_xnew0(pa_alsa_ucm_volume, 1); ++ if (vol == NULL) { ++ pa_xfree(mixer_elem); ++ return NULL; ++ } ++ vol->mixer_elem = mixer_elem; ++ vol->master_elem = pa_xstrdup(pa_proplist_gets(device->proplist, masterid)); ++ vol->master_type = pa_xstrdup(pa_proplist_gets(device->proplist, mastertype)); ++ return vol; ++} ++ + /* Create a property list for this ucm device */ + static int ucm_get_device_property( + pa_alsa_ucm_device *device, +@@ -258,6 +292,7 @@ static int ucm_get_device_property( + int err; + uint32_t ui; + int n_confdev, n_suppdev; ++ pa_alsa_ucm_volume *vol; + + for (i = 0; item[i].id; i++) { + id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name); +@@ -340,12 +375,14 @@ static int ucm_get_device_property( + pa_log_debug("UCM playback priority %s for device %s error", value, device_name); + } + +- value = ucm_get_mixer_id(device, +- PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM, +- PA_ALSA_PROP_UCM_PLAYBACK_VOLUME, +- "PlaybackVolume"); +- if (value) +- pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), (void *)value); ++ vol = ucm_get_mixer_volume(device, ++ PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM, ++ PA_ALSA_PROP_UCM_PLAYBACK_VOLUME, ++ "PlaybackVolume", ++ PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM, ++ PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE); ++ if (vol) ++ pa_hashmap_put(device->playback_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); + } + + if (device->capture_channels) { /* source device */ +@@ -368,12 +405,14 @@ static int ucm_get_device_property( + pa_log_debug("UCM capture priority %s for device %s error", value, device_name); + } + +- value = ucm_get_mixer_id(device, +- PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM, +- PA_ALSA_PROP_UCM_CAPTURE_VOLUME, +- "CaptureVolume"); +- if (value) +- pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), (void *)value); ++ vol = ucm_get_mixer_volume(device, ++ PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM, ++ PA_ALSA_PROP_UCM_CAPTURE_VOLUME, ++ "CaptureVolume", ++ PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM, ++ PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE); ++ if (vol) ++ pa_hashmap_put(device->capture_volumes, pa_xstrdup(pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_NAME)), vol); + } + + if (PA_UCM_PLAYBACK_PRIORITY_UNSET(device) || PA_UCM_CAPTURE_PRIORITY_UNSET(device)) { +@@ -478,9 +517,9 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) { + 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); ++ (pa_free_cb_t) ucm_volume_free); + d->capture_volumes = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, pa_xfree, +- pa_xfree); ++ (pa_free_cb_t) ucm_volume_free); + + PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d); + } +@@ -819,9 +858,10 @@ static void ucm_add_port_combination( + char *name, *desc; + const char *dev_name; + const char *direction; +- const char *profile, *volume_element; ++ const char *profile; + pa_alsa_ucm_device *sorted[num], *dev; + pa_alsa_ucm_port_data *data; ++ pa_alsa_ucm_volume *vol; + void *state; + + for (i = 0; i < num; i++) +@@ -892,21 +932,27 @@ static void ucm_add_port_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, ++ PA_HASHMAP_FOREACH_KV(profile, vol, is_sink ? dev->playback_volumes : dev->capture_volumes, state) { ++ pa_alsa_path *path = pa_alsa_path_synthesize(vol->mixer_elem, + is_sink ? PA_ALSA_DIRECTION_OUTPUT : PA_ALSA_DIRECTION_INPUT); + + if (!path) +- pa_log_warn("Failed to set up volume control: %s", volume_element); ++ pa_log_warn("Failed to set up volume control: %s", vol->mixer_elem); + else { ++ if (vol->master_elem) { ++ pa_alsa_element *e = pa_alsa_element_get(path, vol->master_elem, false); ++ e->switch_use = PA_ALSA_SWITCH_MUTE; ++ e->volume_use = PA_ALSA_VOLUME_MERGE; ++ } ++ + pa_hashmap_put(data->paths, pa_xstrdup(profile), path); + + /* Add path also to already created empty path set */ + dev = sorted[0]; + if (is_sink) +- pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(volume_element), path); ++ pa_hashmap_put(dev->playback_mapping->output_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + else +- pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(volume_element), path); ++ pa_hashmap_put(dev->capture_mapping->input_path_set->paths, pa_xstrdup(vol->mixer_elem), path); + } + } + } +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index d8507a83615c..a6863abc05a6 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -60,6 +60,12 @@ typedef void snd_use_case_mgr_t; + /** For devices: Playback mixer master type */ + #define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" + ++/** For devices: Playback mixer master identifier */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ID "alsa.ucm.playback.master.id" ++ ++/** For devices: Playback mixer master type */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE "alsa.ucm.playback.master.type" ++ + /** For devices: Playback priority */ + #define PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY "alsa.ucm.playback.priority" + +@@ -87,6 +93,12 @@ typedef void snd_use_case_mgr_t; + /** For devices: Capture mixer identifier */ + #define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" + ++/** For devices: Capture mixer identifier */ ++#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_ID "alsa.ucm.capture.master.id" ++ ++/** For devices: Capture mixer identifier */ ++#define PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE "alsa.ucm.capture.master.type" ++ + /** For devices: Capture priority */ + #define PA_ALSA_PROP_UCM_CAPTURE_PRIORITY "alsa.ucm.capture.priority" + +@@ -114,6 +126,7 @@ 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; ++typedef struct pa_alsa_ucm_volume pa_alsa_ucm_volume; + + 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); +@@ -246,4 +259,10 @@ struct pa_alsa_ucm_port_data { + pa_alsa_path *path; + }; + ++struct pa_alsa_ucm_volume { ++ char *mixer_elem; /* mixer element identifier */ ++ char *master_elem; /* master mixer element identifier */ ++ char *master_type; ++}; ++ + #endif +-- +2.16.4 + diff --git a/0014-alsa-ucm-split-correctly-JackHWMute-device-names.patch b/0014-alsa-ucm-split-correctly-JackHWMute-device-names.patch new file mode 100644 index 0000000..e16ce07 --- /dev/null +++ b/0014-alsa-ucm-split-correctly-JackHWMute-device-names.patch @@ -0,0 +1,59 @@ +From 156bd7742490d68701688572ec06f2c608f33db6 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Tue, 3 Dec 2019 14:52:08 +0100 +Subject: [PATCH] alsa-ucm: split correctly JackHWMute device names + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 28 +++++++++++++++++++++++++++- + 1 file changed, 27 insertions(+), 1 deletion(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 46d016541ddf..65b786caf95e 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -200,6 +200,32 @@ static void ucm_add_devices_to_idxset( + } + } + ++/* Split a string into words. Like pa_split_spaces() but handle '' and "". */ ++static char *ucm_split_devnames(const char *c, const char **state) { ++ const char *current = *state ? *state : c; ++ char h; ++ size_t l; ++ ++ if (!*current || *c == 0) ++ return NULL; ++ ++ current += strspn(current, "\n\r \t"); ++ h = *current; ++ if (h == '\'' || h =='"') { ++ c = ++current; ++ for (l = 0; *c && *c != h; l++) c++; ++ if (*c != h) ++ return NULL; ++ *state = c + 1; ++ } else { ++ l = strcspn(current, "\n\r \t"); ++ *state = current+l; ++ } ++ ++ return pa_xstrndup(current, l); ++} ++ ++ + static void ucm_volume_free(pa_alsa_ucm_volume *vol) { + pa_assert(vol); + pa_xfree(vol->mixer_elem); +@@ -1607,7 +1633,7 @@ static int ucm_create_profile( + char *hw_mute_device_name; + const char *state = NULL; + +- while ((hw_mute_device_name = pa_split_spaces(jack_hw_mute, &state))) { ++ while ((hw_mute_device_name = ucm_split_devnames(jack_hw_mute, &state))) { + pa_alsa_ucm_verb *verb2; + bool device_found = false; + +-- +2.16.4 + diff --git a/0015-alsa-ucm-fix-parsing-for-JackControl.patch b/0015-alsa-ucm-fix-parsing-for-JackControl.patch new file mode 100644 index 0000000..b73ac9f --- /dev/null +++ b/0015-alsa-ucm-fix-parsing-for-JackControl.patch @@ -0,0 +1,40 @@ +From e04f14ebf3a0898dd2f665434524cc34cb267ddd Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Tue, 3 Dec 2019 15:13:48 +0100 +Subject: [PATCH] alsa-ucm: fix parsing for JackControl + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 65b786caf95e..221fed719284 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -1534,6 +1534,22 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d + + jack_control = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_JACK_CONTROL); + if (jack_control) { ++#if SND_LIB_VERSION >= 0x10201 ++ snd_ctl_elem_id_t *ctl; ++ int err, index; ++ snd_ctl_elem_id_alloca(&ctl); ++ err = snd_use_case_parse_ctl_elem_id(ctl, "JackControl", jack_control); ++ if (err < 0) ++ return NULL; ++ jack_control = snd_ctl_elem_id_get_name(ctl); ++ index = snd_ctl_elem_id_get_index(ctl); ++ if (index > 0) { ++ pa_log("[%s] Invalid JackControl index value: \"%s\",%d", device_name, jack_control, index); ++ return NULL; ++ } ++#else ++#warning "Upgrade to alsa-lib 1.2.1!" ++#endif + if (!pa_endswith(jack_control, " Jack")) { + pa_log("[%s] Invalid JackControl value: \"%s\"", device_name, jack_control); + return NULL; +-- +2.16.4 + diff --git a/0016-alsa-ucm-add-comments-to-ucm_get_mixer_id.patch b/0016-alsa-ucm-add-comments-to-ucm_get_mixer_id.patch new file mode 100644 index 0000000..e7f57bc --- /dev/null +++ b/0016-alsa-ucm-add-comments-to-ucm_get_mixer_id.patch @@ -0,0 +1,45 @@ +From f5c02dfcd821ab77fc7f91da985254a7bdb658ad Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Wed, 4 Dec 2019 16:29:51 +0100 +Subject: [PATCH] alsa-ucm: add comments to ucm_get_mixer_id() + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 221fed719284..3ee271845c19 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -241,7 +241,7 @@ static char *ucm_get_mixer_id( + const char *cprop, + const char *cid) + { +-#if SND_LIB_VERSION >= 0x10201 ++#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */ + snd_ctl_elem_id_t *ctl; + int err; + #endif +@@ -249,13 +249,17 @@ static char *ucm_get_mixer_id( + char *value2; + int index; + ++ /* mixer element as first, if it's found, return it without modifications */ + value = pa_proplist_gets(device->proplist, mprop); + if (value) + return pa_xstrdup(value); ++ /* fallback, get the control element identifier */ ++ /* and try to do some heuristic to determine the mixer element name */ + value = pa_proplist_gets(device->proplist, cprop); + if (value == NULL) + return NULL; +-#if SND_LIB_VERSION >= 0x10201 ++#if SND_LIB_VERSION >= 0x10201 /* alsa-lib-1.2.1+ check */ ++ /* The new parser may return also element index. */ + snd_ctl_elem_id_alloca(&ctl); + err = snd_use_case_parse_ctl_elem_id(ctl, cid, value); + if (err < 0) +-- +2.16.4 + diff --git a/0017-alsa-ucm-validate-access-to-PA_DEVICE_PORT_DATA.patch b/0017-alsa-ucm-validate-access-to-PA_DEVICE_PORT_DATA.patch new file mode 100644 index 0000000..cbc674d --- /dev/null +++ b/0017-alsa-ucm-validate-access-to-PA_DEVICE_PORT_DATA.patch @@ -0,0 +1,81 @@ +From e6779ad229d5858f90f5f10c3796c9778f05c3fa Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Wed, 4 Dec 2019 19:33:01 +0100 +Subject: [PATCH] alsa-ucm: validate access to PA_DEVICE_PORT_DATA() + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-sink.c | 3 ++- + src/modules/alsa/alsa-source.c | 3 +-- + src/modules/alsa/module-alsa-card.c | 6 +++++- + 3 files changed, 8 insertions(+), 4 deletions(-) + +diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c +index 0a5c92529af3..19e9efc3674b 100644 +--- a/src/modules/alsa/alsa-sink.c ++++ b/src/modules/alsa/alsa-sink.c +@@ -1672,6 +1672,7 @@ static int sink_set_port_cb(pa_sink *s, pa_device_port *p) { + pa_assert(u); + pa_assert(p); + pa_assert(u->mixer_handle); ++ pa_assert(!u->ucm_context); + + data = PA_DEVICE_PORT_DATA(p); + pa_assert_se(u->mixer_path = data->path); +@@ -2688,7 +2689,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca + * pa_sink_suspend() between pa_sink_new() and pa_sink_put() would + * otherwise work, but currently pa_sink_suspend() will crash if + * pa_sink_put() hasn't been called. */ +- if (u->sink->active_port) { ++ if (u->sink->active_port && !u->ucm_context) { + pa_alsa_port_data *port_data; + + port_data = PA_DEVICE_PORT_DATA(u->sink->active_port); +diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c +index d186101720b8..34946b74c77f 100644 +--- a/src/modules/alsa/alsa-source.c ++++ b/src/modules/alsa/alsa-source.c +@@ -1519,8 +1519,6 @@ 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->mixer_handle); +@@ -1545,6 +1543,7 @@ static int source_set_port_cb(pa_source *s, pa_device_port *p) { + pa_assert(u); + pa_assert(p); + pa_assert(u->mixer_handle); ++ pa_assert(!u->ucm_context); + + data = PA_DEVICE_PORT_DATA(p); + pa_assert_se(u->mixer_path = data->path); +diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c +index e2a86bc1c68d..be260e4badab 100644 +--- a/src/modules/alsa/module-alsa-card.c ++++ b/src/modules/alsa/module-alsa-card.c +@@ -538,6 +538,9 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + ++ if (u->use_ucm) ++ return 0; ++ + p = find_port_with_eld_device(u->card->ports, device); + if (p == NULL) { + pa_log_error("Invalid device changed in ALSA: %d", device); +@@ -900,7 +903,8 @@ int pa__init(pa_module *m) { + * results in an infinite loop of "fill buffer, handle underrun". To work + * around this issue, the suspend_when_unavailable flag is used to stop + * playback when the HDMI cable is unplugged. */ +- if (pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) { ++ if (!u->use_ucm && ++ pa_safe_streq(pa_proplist_gets(data.proplist, "alsa.driver_name"), "snd_hdmi_lpe_audio")) { + pa_device_port *port; + void *state; + +-- +2.16.4 + diff --git a/0018-alsa-Skip-resume-PCM-if-hardware-doesn-t-support-it.patch b/0018-alsa-Skip-resume-PCM-if-hardware-doesn-t-support-it.patch new file mode 100644 index 0000000..7df8880 --- /dev/null +++ b/0018-alsa-Skip-resume-PCM-if-hardware-doesn-t-support-it.patch @@ -0,0 +1,65 @@ +From 734a00c849815a45697970d593068c301a04ebbb Mon Sep 17 00:00:00 2001 +From: Kai-Heng Feng +Date: Tue, 10 Dec 2019 16:16:18 +0800 +Subject: [PATCH] alsa: Skip resume PCM if hardware doesn't support it + +Hardwares without SNDRV_PCM_INFO_RESUME capability, like USB Audio, +don't support snd_pcm_resume() when it's in suspended state. + +Let's use snd_pcm_hw_params_can_resume() to check hardware's capability +before snd_pcm_resume() attempt. If it doesn't support resume, just go +to snd_pcm_drop() to leave suspended state directly. +--- + src/modules/alsa/alsa-util.c | 28 +++++++++++++++++++--------- + 1 file changed, 19 insertions(+), 9 deletions(-) + +diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c +index bd0a47e5072c..a14b061118e6 100644 +--- a/src/modules/alsa/alsa-util.c ++++ b/src/modules/alsa/alsa-util.c +@@ -1066,6 +1066,7 @@ void pa_alsa_init_proplist_ctl(pa_proplist *p, const char *name) { + + int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { + snd_pcm_state_t state; ++ snd_pcm_hw_params_t *hwparams; + int err; + + pa_assert(pcm); +@@ -1103,16 +1104,25 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { + break; + + case SND_PCM_STATE_SUSPENDED: +- /* Retry resume 3 times before giving up, then fallback to restarting the stream. */ +- for (int i = 0; i < 3; i++) { +- if ((err = snd_pcm_resume(pcm)) == 0) +- return 0; +- if (err != -EAGAIN) +- break; +- pa_msleep(25); ++ snd_pcm_hw_params_alloca(&hwparams); ++ ++ if ((err = snd_pcm_hw_params_any(pcm, hwparams)) < 0) { ++ pa_log_debug("snd_pcm_hw_params_any() failed: %s", pa_alsa_strerror(err)); ++ return -1; + } +- pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM"); +- /* Fall through */ ++ ++ if (snd_pcm_hw_params_can_resume(hwparams)) { ++ /* Retry resume 3 times before giving up, then fallback to restarting the stream. */ ++ for (int i = 0; i < 3; i++) { ++ if ((err = snd_pcm_resume(pcm)) == 0) ++ return 0; ++ if (err != -EAGAIN) ++ break; ++ pa_msleep(25); ++ } ++ pa_log_warn("Could not recover alsa device from SUSPENDED state, trying to restart PCM"); ++ } ++ /* Fall through */ + + default: + +-- +2.16.4 + diff --git a/0019-alsa-ucm-parse-correctly-the-device-values.patch b/0019-alsa-ucm-parse-correctly-the-device-values.patch new file mode 100644 index 0000000..8b761a4 --- /dev/null +++ b/0019-alsa-ucm-parse-correctly-the-device-values.patch @@ -0,0 +1,125 @@ +From 4c64f73c97c7f77426ee838f47fc7bad6a4563b6 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Fri, 6 Dec 2019 21:51:47 +0100 +Subject: [PATCH] alsa-ucm: parse correctly the device values + +The UCM library is used to get the fallback values from the verbs +and the defaults section. There is no reason to duplicate this code +inside application. + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 55 ++++++--------------------------------------- + 1 file changed, 7 insertions(+), 48 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 3ee271845c19..ac1b71e94022 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -141,30 +141,6 @@ static struct ucm_info dev_info[] = { + {NULL, 0} + }; + +-/* UCM profile properties - The verb data is store so it can be used to fill +- * the new profiles properties */ +-static int ucm_get_property(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr, const char *verb_name) { +- const char *value; +- char *id; +- int i; +- +- for (i = 0; item[i].id; i++) { +- int err; +- +- id = pa_sprintf_malloc("=%s//%s", item[i].id, verb_name); +- err = snd_use_case_get(uc_mgr, id, &value); +- pa_xfree(id); +- if (err < 0) +- continue; +- +- pa_log_debug("Got %s for verb %s: %s", item[i].id, verb_name, value); +- pa_proplist_sets(verb->proplist, item[i].property, value); +- free((void*)value); +- } +- +- return 0; +-}; +- + static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) { + pa_alsa_ucm_device *d; + uint32_t idx; +@@ -325,7 +301,7 @@ static int ucm_get_device_property( + pa_alsa_ucm_volume *vol; + + for (i = 0; item[i].id; i++) { +- id = pa_sprintf_malloc("=%s/%s", item[i].id, device_name); ++ id = pa_sprintf_malloc("%s/%s", item[i].id, device_name); + err = snd_use_case_get(uc_mgr, id, &value); + pa_xfree(id); + if (err < 0) +@@ -347,14 +323,8 @@ static int ucm_get_device_property( + + /* get pcm */ + value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SINK); +- if (!value) { /* take pcm from verb playback default */ +- value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SINK); +- if (value) { +- pa_log_debug("UCM playback device %s fetch pcm from verb default %s", device_name, value); +- pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SINK, value); +- } else +- pa_log("UCM playback device %s fetch pcm failed", device_name); +- } ++ if (!value) /* take pcm from verb playback default */ ++ pa_log("UCM playback device %s fetch pcm failed", device_name); + } + + value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_CHANNELS); +@@ -367,14 +337,8 @@ static int ucm_get_device_property( + + /* get pcm */ + value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_SOURCE); +- if (!value) { /* take pcm from verb capture default */ +- value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_SOURCE); +- if (value) { +- pa_log_debug("UCM capture device %s fetch pcm from verb default %s", device_name, value); +- pa_proplist_sets(device->proplist, PA_ALSA_PROP_UCM_SOURCE, value); +- } else +- pa_log("UCM capture device %s fetch pcm failed", device_name); +- } ++ if (!value) /* take pcm from verb capture default */ ++ pa_log("UCM capture device %s fetch pcm failed", device_name); + } + + if (device->playback_channels == 0 && device->capture_channels == 0) { +@@ -387,8 +351,7 @@ static int ucm_get_device_property( + /* get rate and priority of device */ + if (device->playback_channels) { /* sink device */ + /* get rate */ +- if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE)) || +- (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) { ++ if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_PLAYBACK_RATE))) { + if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) { + pa_log_debug("UCM playback device %s rate %d", device_name, ui); + device->playback_rate = ui; +@@ -417,8 +380,7 @@ static int ucm_get_device_property( + + if (device->capture_channels) { /* source device */ + /* get rate */ +- if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE)) || +- (value = pa_proplist_gets(verb->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) { ++ if ((value = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_CAPTURE_RATE))) { + if (pa_atou(value, &ui) == 0 && pa_sample_rate_valid(ui)) { + pa_log_debug("UCM capture device %s rate %d", device_name, ui); + device->capture_rate = ui; +@@ -796,9 +758,6 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons + if (err < 0) + pa_log("No UCM modifiers for verb %s", verb_name); + +- /* Verb properties */ +- ucm_get_property(verb, uc_mgr, verb_name); +- + PA_LLIST_FOREACH(d, verb->devices) { + const char *dev_name = pa_proplist_gets(d->proplist, PA_ALSA_PROP_UCM_NAME); + +-- +2.16.4 + diff --git a/0020-alsa-ucm-do-not-try-to-use-UCM-device-name-as-jack-n.patch b/0020-alsa-ucm-do-not-try-to-use-UCM-device-name-as-jack-n.patch new file mode 100644 index 0000000..8ecddf0 --- /dev/null +++ b/0020-alsa-ucm-do-not-try-to-use-UCM-device-name-as-jack-n.patch @@ -0,0 +1,44 @@ +From ef1df946274a0499e1fa631a8b6680c23c4eb723 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Fri, 6 Dec 2019 15:43:04 +0100 +Subject: [PATCH] alsa-ucm: do not try to use UCM device name as jack name by + default + +Remove the implicit rule. It is perfectly ok to have the jack with +the same name for another I/O in the driver. Trust only the +value obtained from UCM. + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index ac1b71e94022..95f1a47f8b61 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -1522,9 +1522,8 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d + * end, so drop the trailing " Jack". */ + name = pa_xstrndup(jack_control, strlen(jack_control) - 5); + } else { +- /* The jack control hasn't been explicitly configured - try a jack name +- * that is the same as the device name. */ +- name = pa_xstrdup(device_name); ++ /* The jack control hasn't been explicitly configured, fail. */ ++ return NULL; + } + + PA_LLIST_FOREACH(j, ucm->jacks) +@@ -1603,7 +1602,8 @@ static int ucm_create_profile( + ucm_create_mapping(ucm, ps, p, dev, verb_name, name, sink, source); + + jack = ucm_get_jack(ucm, dev); +- device_set_jack(dev, jack); ++ if (jack) ++ device_set_jack(dev, jack); + + /* JackHWMute contains a list of device names. Each listed device must + * be associated with the jack object that we just created. */ +-- +2.16.4 + diff --git a/0021-alsa-util-do-not-try-to-guess-the-mixer-name-from-th.patch b/0021-alsa-util-do-not-try-to-guess-the-mixer-name-from-th.patch new file mode 100644 index 0000000..bdd39bf --- /dev/null +++ b/0021-alsa-util-do-not-try-to-guess-the-mixer-name-from-th.patch @@ -0,0 +1,84 @@ +From d8200ee805ed6b508a8174031080b1d98a7c27b3 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Fri, 6 Dec 2019 16:05:07 +0100 +Subject: [PATCH] alsa-util: do not try to guess the mixer name from the PCM + name + +This is just invalid. It results to an error in almost all cases. +The hw: scheme is sufficient to get the right card mixer. + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 2 ++ + src/modules/alsa/alsa-util.c | 26 +++++++------------------- + 2 files changed, 9 insertions(+), 19 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 95f1a47f8b61..45eb83085b38 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -1725,6 +1725,8 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { + PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { + bool has_control; + ++ if (!dev->jack) ++ continue; + has_control = pa_alsa_mixer_find(mixer_handle, dev->jack->alsa_name, 0) != NULL; + pa_alsa_jack_set_has_control(dev->jack, has_control); + pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); +diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c +index a14b061118e6..54fe1361148a 100644 +--- a/src/modules/alsa/alsa-util.c ++++ b/src/modules/alsa/alsa-util.c +@@ -1743,7 +1743,6 @@ snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device) { + snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { + int err; + snd_mixer_t *m; +- const char *dev; + snd_pcm_info_t* info; + snd_pcm_info_alloca(&info); + +@@ -1754,15 +1753,6 @@ snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { + return NULL; + } + +- /* First, try by name */ +- if ((dev = snd_pcm_name(pcm))) +- if (prepare_mixer(m, dev) >= 0) { +- if (ctl_device) +- *ctl_device = pa_xstrdup(dev); +- +- return m; +- } +- + /* Then, try by card index */ + if (snd_pcm_info(pcm, info) >= 0) { + char *md; +@@ -1771,17 +1761,15 @@ snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { + if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card_idx); ++ if (prepare_mixer(m, md) >= 0) { + +- if (!dev || !pa_streq(dev, md)) +- if (prepare_mixer(m, md) >= 0) { ++ if (ctl_device) ++ *ctl_device = md; ++ else ++ pa_xfree(md); + +- if (ctl_device) +- *ctl_device = md; +- else +- pa_xfree(md); +- +- return m; +- } ++ return m; ++ } + + pa_xfree(md); + } +-- +2.16.4 + diff --git a/0022-alsa-ucm-add-control-and-mixer-device-items.patch b/0022-alsa-ucm-add-control-and-mixer-device-items.patch new file mode 100644 index 0000000..cfbfaeb --- /dev/null +++ b/0022-alsa-ucm-add-control-and-mixer-device-items.patch @@ -0,0 +1,99 @@ +From ddd0fdb9970b920ef95e33cfe50a1e492be9d60b Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Fri, 6 Dec 2019 20:33:45 +0100 +Subject: [PATCH] alsa-ucm: add control and mixer device items + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 5 +++++ + src/modules/alsa/alsa-ucm.h | 15 +++++++++++++++ + 2 files changed, 20 insertions(+) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 45eb83085b38..2a594b4df546 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -90,16 +90,20 @@ static void ucm_port_update_available(pa_alsa_ucm_port_data *port); + static struct ucm_items item[] = { + {"PlaybackPCM", PA_ALSA_PROP_UCM_SINK}, + {"CapturePCM", PA_ALSA_PROP_UCM_SOURCE}, ++ {"PlaybackCTL", PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE}, + {"PlaybackVolume", PA_ALSA_PROP_UCM_PLAYBACK_VOLUME}, + {"PlaybackSwitch", PA_ALSA_PROP_UCM_PLAYBACK_SWITCH}, ++ {"PlaybackMixer", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE}, + {"PlaybackMixerElem", PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM}, + {"PlaybackMasterElem", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_ELEM}, + {"PlaybackMasterType", PA_ALSA_PROP_UCM_PLAYBACK_MASTER_TYPE}, + {"PlaybackPriority", PA_ALSA_PROP_UCM_PLAYBACK_PRIORITY}, + {"PlaybackRate", PA_ALSA_PROP_UCM_PLAYBACK_RATE}, + {"PlaybackChannels", PA_ALSA_PROP_UCM_PLAYBACK_CHANNELS}, ++ {"CaptureCTL", PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE}, + {"CaptureVolume", PA_ALSA_PROP_UCM_CAPTURE_VOLUME}, + {"CaptureSwitch", PA_ALSA_PROP_UCM_CAPTURE_SWITCH}, ++ {"CaptureMixer", PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE}, + {"CaptureMixerElem", PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM}, + {"CaptureMasterElem", PA_ALSA_PROP_UCM_CAPTURE_MASTER_ELEM}, + {"CaptureMasterType", PA_ALSA_PROP_UCM_CAPTURE_MASTER_TYPE}, +@@ -107,6 +111,7 @@ static struct ucm_items item[] = { + {"CaptureRate", PA_ALSA_PROP_UCM_CAPTURE_RATE}, + {"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS}, + {"TQ", PA_ALSA_PROP_UCM_QOS}, ++ {"JackCTL", PA_ALSA_PROP_UCM_JACK_DEVICE}, + {"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL}, + {"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE}, + {NULL, NULL}, +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index a6863abc05a6..014bc334ad67 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -45,12 +45,18 @@ typedef void snd_use_case_mgr_t; + /** For devices: Playback roles */ + #define PA_ALSA_PROP_UCM_PLAYBACK_ROLES "alsa.ucm.playback.roles" + ++/** For devices: Playback control device name */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE "alsa.ucm.playback.ctldev" ++ + /** For devices: Playback control volume ID string. e.g PlaybackVolume */ + #define PA_ALSA_PROP_UCM_PLAYBACK_VOLUME "alsa.ucm.playback.volume" + + /** For devices: Playback switch e.g PlaybackSwitch */ + #define PA_ALSA_PROP_UCM_PLAYBACK_SWITCH "alsa.ucm.playback.switch" + ++/** For devices: Playback mixer device name */ ++#define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE "alsa.ucm.playback.mixer.device" ++ + /** For devices: Playback mixer identifier */ + #define PA_ALSA_PROP_UCM_PLAYBACK_MIXER_ELEM "alsa.ucm.playback.mixer.element" + +@@ -78,12 +84,18 @@ typedef void snd_use_case_mgr_t; + /** For devices: Capture roles */ + #define PA_ALSA_PROP_UCM_CAPTURE_ROLES "alsa.ucm.capture.roles" + ++/** For devices: Capture control device name */ ++#define PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE "alsa.ucm.capture.ctldev" ++ + /** For devices: Capture controls volume ID string. e.g CaptureVolume */ + #define PA_ALSA_PROP_UCM_CAPTURE_VOLUME "alsa.ucm.capture.volume" + + /** For devices: Capture switch e.g CaptureSwitch */ + #define PA_ALSA_PROP_UCM_CAPTURE_SWITCH "alsa.ucm.capture.switch" + ++/** For devices: Capture mixer device name */ ++#define PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE "alsa.ucm.capture.mixer.device" ++ + /** For devices: Capture mixer identifier */ + #define PA_ALSA_PROP_UCM_CAPTURE_MIXER_ELEM "alsa.ucm.capture.mixer.element" + +@@ -114,6 +126,9 @@ typedef void snd_use_case_mgr_t; + /** For devices: The modifier (if any) that this device corresponds to */ + #define PA_ALSA_PROP_UCM_MODIFIER "alsa.ucm.modifier" + ++/* Corresponds to the "JackCTL" UCM value. */ ++#define PA_ALSA_PROP_UCM_JACK_DEVICE "alsa.ucm.jack_device" ++ + /* Corresponds to the "JackControl" UCM value. */ + #define PA_ALSA_PROP_UCM_JACK_CONTROL "alsa.ucm.jack_control" + +-- +2.16.4 + diff --git a/0023-alsa-ucm-get-the-mixer-names-from-ucm-don-t-guess.patch b/0023-alsa-ucm-get-the-mixer-names-from-ucm-don-t-guess.patch new file mode 100644 index 0000000..bbe539b --- /dev/null +++ b/0023-alsa-ucm-get-the-mixer-names-from-ucm-don-t-guess.patch @@ -0,0 +1,244 @@ +From e438382a51f7e0d04fb9439da2f45c183e958e0f Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Fri, 6 Dec 2019 21:32:21 +0100 +Subject: [PATCH] alsa-ucm: get the mixer names from ucm, don't guess + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.h | 1 + + src/modules/alsa/alsa-ucm.c | 96 +++++++++++++++++++++++++++++++++++-------- + src/modules/alsa/alsa-util.c | 26 ++++++++---- + 3 files changed, 97 insertions(+), 26 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h +index 0b634f2f2be1..7864a2c1252f 100644 +--- a/src/modules/alsa/alsa-mixer.h ++++ b/src/modules/alsa/alsa-mixer.h +@@ -364,6 +364,7 @@ void pa_alsa_profile_set_free(pa_alsa_profile_set *s); + void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); + void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s); + ++snd_mixer_t *pa_alsa_open_mixer_by_name(const char *dev); + snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device); + + pa_alsa_fdlist *pa_alsa_fdlist_new(void); +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 2a594b4df546..d02adab650fe 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -289,6 +289,31 @@ static pa_alsa_ucm_volume *ucm_get_mixer_volume( + return vol; + } + ++/* Get the ALSA mixer device for the UCM device */ ++static const char *get_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) ++{ ++ const char *dev_name; ++ ++ if (is_sink) { ++ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_MIXER_DEVICE); ++ if (!dev_name) ++ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_PLAYBACK_CTL_DEVICE); ++ } else { ++ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_MIXER_DEVICE); ++ if (!dev_name) ++ dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_CAPTURE_CTL_DEVICE); ++ } ++ return dev_name; ++} ++ ++/* Get the ALSA mixer device for the UCM jack */ ++static const char *get_jack_mixer_device(pa_alsa_ucm_device *dev, bool is_sink) { ++ const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_DEVICE); ++ if (!dev_name) ++ return get_mixer_device(dev, is_sink); ++ return dev_name; ++} ++ + /* Create a property list for this ucm device */ + static int ucm_get_device_property( + pa_alsa_ucm_device *device, +@@ -795,22 +820,41 @@ 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) { ++static void probe_volumes(pa_hashmap *hash, bool is_sink, 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; ++ pa_alsa_ucm_device *dev; ++ snd_mixer_t *mixer_handle = NULL; ++ const char *profile, *mdev_opened = NULL, *mdev, *mdev2; + 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; +- } ++ int idx; + + PA_HASHMAP_FOREACH(port, hash, state) { + data = PA_DEVICE_PORT_DATA(port); + ++ mdev = NULL; ++ PA_DYNARRAY_FOREACH(dev, data->devices, idx) { ++ mdev2 = get_mixer_device(dev, is_sink); ++ if (mdev && !pa_streq(mdev, mdev2)) { ++ pa_log_error("Two mixer device names found ('%s', '%s'), using s/w volume", mdev, mdev2); ++ goto fail; ++ } ++ mdev = mdev2; ++ } ++ ++ if (!mdev_opened || !pa_streq(mdev_opened, mdev)) { ++ if (mixer_handle) { ++ snd_mixer_close(mixer_handle); ++ mdev_opened = NULL; ++ } ++ if (!(mixer_handle = pa_alsa_open_mixer_by_name(mdev))) { ++ pa_log_error("Failed to find a working mixer device (%s).", mdev); ++ goto fail; ++ } ++ mdev_opened = mdev; ++ } ++ + 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); +@@ -823,7 +867,8 @@ static void probe_volumes(pa_hashmap *hash, snd_pcm_t *pcm_handle, bool ignore_d + } + } + +- snd_mixer_close(mixer_handle); ++ if (mixer_handle) ++ snd_mixer_close(mixer_handle); + + return; + +@@ -1149,7 +1194,7 @@ void pa_alsa_ucm_add_ports( + 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); ++ probe_volumes(*p, is_sink, 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)); +@@ -1716,28 +1761,43 @@ static void profile_finalize_probing(pa_alsa_profile *p) { + } + + static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { +- snd_pcm_t *pcm_handle; +- snd_mixer_t *mixer_handle; ++ snd_mixer_t *mixer_handle = NULL; + pa_alsa_ucm_mapping_context *context = &m->ucm_context; + pa_alsa_ucm_device *dev; ++ bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; ++ const char *mdev_opened = NULL, *mdev; + uint32_t idx; + +- pcm_handle = m->direction == PA_ALSA_DIRECTION_OUTPUT ? m->output_pcm : m->input_pcm; +- mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL); +- if (!mixer_handle) +- return; +- + PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { + bool has_control; + + if (!dev->jack) + continue; ++ ++ mdev = get_jack_mixer_device(dev, is_sink); ++ if (mdev == NULL) { ++ pa_log_error("Unable to determine mixer device for jack %s", dev->jack->name); ++ continue; ++ } ++ ++ if (!mdev_opened || !pa_streq(mdev_opened, mdev)) { ++ if (mixer_handle) { ++ snd_mixer_close(mixer_handle); ++ mdev_opened = NULL; ++ } ++ mixer_handle = pa_alsa_open_mixer_by_name(mdev); ++ if (!mixer_handle) ++ continue; ++ mdev_opened = mdev; ++ } ++ + has_control = pa_alsa_mixer_find(mixer_handle, dev->jack->alsa_name, 0) != NULL; + pa_alsa_jack_set_has_control(dev->jack, has_control); + pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); + } + +- snd_mixer_close(mixer_handle); ++ if (mixer_handle) ++ snd_mixer_close(mixer_handle); + } + + static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { +diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c +index 54fe1361148a..2df2258d85af 100644 +--- a/src/modules/alsa/alsa-util.c ++++ b/src/modules/alsa/alsa-util.c +@@ -1740,20 +1740,31 @@ snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device) { + return NULL; + } + +-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { ++snd_mixer_t *pa_alsa_open_mixer_by_name(const char *dev) { + int err; + snd_mixer_t *m; +- snd_pcm_info_t* info; +- snd_pcm_info_alloca(&info); + +- pa_assert(pcm); ++ pa_assert(dev); + + if ((err = snd_mixer_open(&m, 0)) < 0) { + pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); + return NULL; + } + +- /* Then, try by card index */ ++ if (prepare_mixer(m, dev) >= 0) ++ return m; ++ ++ snd_mixer_close(m); ++ return NULL; ++} ++ ++snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { ++ snd_mixer_t *m; ++ snd_pcm_info_t* info; ++ snd_pcm_info_alloca(&info); ++ ++ pa_assert(pcm); ++ + if (snd_pcm_info(pcm, info) >= 0) { + char *md; + int card_idx; +@@ -1761,8 +1772,8 @@ snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { + if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { + + md = pa_sprintf_malloc("hw:%i", card_idx); +- if (prepare_mixer(m, md) >= 0) { +- ++ m = pa_alsa_open_mixer_by_name(md); ++ if (m) { + if (ctl_device) + *ctl_device = md; + else +@@ -1775,7 +1786,6 @@ snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { + } + } + +- snd_mixer_close(m); + return NULL; + } + +-- +2.16.4 + diff --git a/0024-alsa-ucm-use-the-proper-mixer-name-for-ucm-pcm-sink-.patch b/0024-alsa-ucm-use-the-proper-mixer-name-for-ucm-pcm-sink-.patch new file mode 100644 index 0000000..3765860 --- /dev/null +++ b/0024-alsa-ucm-use-the-proper-mixer-name-for-ucm-pcm-sink-.patch @@ -0,0 +1,93 @@ +From dacfcbb09c9d91ca20dedfa449da37f0f7e3953f Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 7 Dec 2019 11:50:13 +0100 +Subject: [PATCH] alsa-ucm: use the proper mixer name for ucm pcm sink/source + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-sink.c | 9 ++++++++- + src/modules/alsa/alsa-source.c | 10 +++++++++- + src/modules/alsa/alsa-ucm.c | 9 +++++++-- + 3 files changed, 24 insertions(+), 4 deletions(-) + +diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c +index 19e9efc3674b..5ea4b2597449 100644 +--- a/src/modules/alsa/alsa-sink.c ++++ b/src/modules/alsa/alsa-sink.c +@@ -2083,11 +2083,18 @@ static void set_sink_name(pa_sink_new_data *data, pa_modargs *ma, const char *de + } + + static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) { ++ const char *mdev; + + if (!mapping && !element) + return; + +- if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { ++ mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); ++ if (mdev) { ++ u->mixer_handle = pa_alsa_open_mixer_by_name(mdev); ++ } else { ++ u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device); ++ } ++ if (!mdev) { + pa_log_info("Failed to find a working mixer device."); + return; + } +diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c +index 34946b74c77f..bd78d45e2a5b 100644 +--- a/src/modules/alsa/alsa-source.c ++++ b/src/modules/alsa/alsa-source.c +@@ -1789,10 +1789,18 @@ static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char + } + + static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, bool ignore_dB) { ++ const char *mdev; ++ + if (!mapping && !element) + return; + +- if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) { ++ mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); ++ if (mdev) { ++ u->mixer_handle = pa_alsa_open_mixer_by_name(mdev); ++ } else { ++ u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device); ++ } ++ if (!mdev) { + pa_log_info("Failed to find a working mixer device."); + return; + } +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index d02adab650fe..d1ca62d28619 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -1339,7 +1339,8 @@ static void ucm_add_mapping(pa_alsa_profile *p, pa_alsa_mapping *m) { + + static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device *device) { + char *cur_desc; +- const char *new_desc; ++ const char *new_desc, *mdev; ++ bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; + + pa_idxset_put(m->ucm_context.ucm_devices, device, NULL); + +@@ -1355,10 +1356,14 @@ static void alsa_mapping_add_ucm_device(pa_alsa_mapping *m, pa_alsa_ucm_device * + m->description = m->description ? m->description : pa_xstrdup(""); + + /* save mapping to ucm device */ +- if (m->direction == PA_ALSA_DIRECTION_OUTPUT) ++ if (is_sink) + device->playback_mapping = m; + else + device->capture_mapping = m; ++ ++ mdev = get_mixer_device(device, is_sink); ++ if (mdev) ++ pa_proplist_sets(m->proplist, "alsa.mixer_device", mdev); + } + + static void alsa_mapping_add_ucm_modifier(pa_alsa_mapping *m, pa_alsa_ucm_modifier *modifier) { +-- +2.16.4 + diff --git a/0025-alsa-mixer-handle-interface-type-CARD-PCM-for-mixer-.patch b/0025-alsa-mixer-handle-interface-type-CARD-PCM-for-mixer-.patch new file mode 100644 index 0000000..953fc64 --- /dev/null +++ b/0025-alsa-mixer-handle-interface-type-CARD-PCM-for-mixer-.patch @@ -0,0 +1,125 @@ +From f18b0c3402f5e1f7db9d0a42c6e10cfe1f212da3 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 7 Dec 2019 17:54:04 +0100 +Subject: [PATCH] alsa-mixer: handle interface type (CARD,PCM) for mixer + element lookups + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.c | 2 +- + src/modules/alsa/alsa-ucm.c | 2 +- + src/modules/alsa/alsa-util.c | 18 +++++++++++++++++- + src/modules/alsa/alsa-util.h | 3 ++- + src/modules/alsa/module-alsa-card.c | 4 ++-- + 5 files changed, 23 insertions(+), 6 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index ed06f42d86da..094cff817df7 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -1907,7 +1907,7 @@ static int jack_probe(pa_alsa_jack *j, pa_alsa_mapping *mapping, snd_mixer_t *m) + j->append_pcm_to_name = false; + } + +- has_control = pa_alsa_mixer_find(m, j->alsa_name, 0) != NULL; ++ has_control = pa_alsa_mixer_find_card(m, j->alsa_name, 0) != NULL; + pa_alsa_jack_set_has_control(j, has_control); + + if (j->has_control) { +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index d1ca62d28619..a64505a0c781 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -1796,7 +1796,7 @@ static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { + mdev_opened = mdev; + } + +- has_control = pa_alsa_mixer_find(mixer_handle, dev->jack->alsa_name, 0) != NULL; ++ has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL; + pa_alsa_jack_set_has_control(dev->jack, has_control); + pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); + } +diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c +index 2df2258d85af..08a144782394 100644 +--- a/src/modules/alsa/alsa-util.c ++++ b/src/modules/alsa/alsa-util.c +@@ -1610,7 +1610,11 @@ bool pa_alsa_may_tsched(bool want) { + + #define SND_MIXER_ELEM_PULSEAUDIO (SND_MIXER_ELEM_LAST + 10) + +-snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsigned int device) { ++static snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, ++ snd_ctl_elem_iface_t iface, ++ const char *name, ++ unsigned int index, ++ unsigned int device) { + snd_mixer_elem_t *elem; + + for (elem = snd_mixer_first_elem(mixer); elem; elem = snd_mixer_elem_next(elem)) { +@@ -1618,8 +1622,12 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig + if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_PULSEAUDIO) + continue; + helem = snd_mixer_elem_get_private(elem); ++ if (snd_hctl_elem_get_interface(helem) != iface) ++ continue; + if (!pa_streq(snd_hctl_elem_get_name(helem), name)) + continue; ++ if (snd_hctl_elem_get_index(helem) != index) ++ continue; + if (snd_hctl_elem_get_device(helem) != device) + continue; + return elem; +@@ -1627,6 +1635,14 @@ snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsig + return NULL; + } + ++snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device) { ++ return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_CARD, name, 0, device); ++} ++ ++snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device) { ++ return pa_alsa_mixer_find(mixer, SND_CTL_ELEM_IFACE_PCM, name, 0, device); ++} ++ + static int mixer_class_compare(const snd_mixer_elem_t *c1, const snd_mixer_elem_t *c2) + { + /* Dummy compare function */ +diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h +index 4ceaa06ee480..ceca48809400 100644 +--- a/src/modules/alsa/alsa-util.h ++++ b/src/modules/alsa/alsa-util.h +@@ -141,7 +141,8 @@ const char* pa_alsa_strerror(int errnum); + + bool pa_alsa_may_tsched(bool want); + +-snd_mixer_elem_t *pa_alsa_mixer_find(snd_mixer_t *mixer, const char *name, unsigned int device); ++snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device); ++snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device); + + snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device); + +diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c +index be260e4badab..ba7a19a38983 100644 +--- a/src/modules/alsa/module-alsa-card.c ++++ b/src/modules/alsa/module-alsa-card.c +@@ -588,7 +588,7 @@ static void init_eld_ctls(struct userdata *u) { + if (device < 0) + continue; + +- melem = pa_alsa_mixer_find(u->mixer_handle, "ELD", device); ++ melem = pa_alsa_mixer_find_pcm(u->mixer_handle, "ELD", device); + if (melem) { + snd_mixer_elem_set_callback(melem, hdmi_eld_changed); + snd_mixer_elem_set_callback_private(melem, u); +@@ -635,7 +635,7 @@ static void init_jacks(struct userdata *u) { + u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL); + if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) >= 0) { + PA_HASHMAP_FOREACH(jack, u->jacks, state) { +- jack->melem = pa_alsa_mixer_find(u->mixer_handle, jack->alsa_name, 0); ++ jack->melem = pa_alsa_mixer_find_card(u->mixer_handle, jack->alsa_name, 0); + if (!jack->melem) { + pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); + pa_alsa_jack_set_has_control(jack, false); +-- +2.16.4 + diff --git a/0026-alsa-mixer-Add-the-ability-to-pass-the-intended-role.patch b/0026-alsa-mixer-Add-the-ability-to-pass-the-intended-role.patch new file mode 100644 index 0000000..856fd86 --- /dev/null +++ b/0026-alsa-mixer-Add-the-ability-to-pass-the-intended-role.patch @@ -0,0 +1,51 @@ +From c6a0665618975eedc98bdf23e4140935a1af38c2 Mon Sep 17 00:00:00 2001 +From: Laurent Bigonville +Date: Thu, 7 Mar 2019 11:35:30 +0100 +Subject: [PATCH] alsa-mixer: Add the ability to pass the intended-role to the + mapping + +https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/640 +--- + src/modules/alsa/alsa-mixer.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index 094cff817df7..7956371b604b 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -3978,6 +3978,24 @@ static int mapping_parse_fallback(pa_config_parser_state *state) { + return 0; + } + ++static int mapping_parse_intended_roles(pa_config_parser_state *state) { ++ pa_alsa_profile_set *ps; ++ pa_alsa_mapping *m; ++ ++ pa_assert(state); ++ ++ ps = state->userdata; ++ ++ if (!(m = pa_alsa_mapping_get(ps, state->section))) { ++ pa_log("[%s:%u] %s invalid in section %s", state->filename, state->lineno, state->lvalue, state->section); ++ return -1; ++ } ++ ++ pa_proplist_sets(m->proplist, PA_PROP_DEVICE_INTENDED_ROLES, state->rvalue); ++ ++ return 0; ++} ++ + + static int profile_parse_mappings(pa_config_parser_state *state) { + pa_alsa_profile_set *ps; +@@ -4569,6 +4587,7 @@ pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel + { "element-output", mapping_parse_element, NULL, NULL }, + { "direction", mapping_parse_direction, NULL, NULL }, + { "exact-channels", mapping_parse_exact_channels, NULL, NULL }, ++ { "intended-roles", mapping_parse_intended_roles, NULL, NULL }, + + /* Shared by [Mapping ...] and [Profile ...] */ + { "description", mapping_parse_description, NULL, NULL }, +-- +2.16.4 + diff --git a/0027-alsa-mixer-Set-the-intended-role-of-Steelseries-Arct.patch b/0027-alsa-mixer-Set-the-intended-role-of-Steelseries-Arct.patch new file mode 100644 index 0000000..ac38101 --- /dev/null +++ b/0027-alsa-mixer-Set-the-intended-role-of-Steelseries-Arct.patch @@ -0,0 +1,39 @@ +From 6438e5c46dc449e4726ec9312859cc70388d2851 Mon Sep 17 00:00:00 2001 +From: Laurent Bigonville +Date: Thu, 7 Mar 2019 11:36:02 +0100 +Subject: [PATCH] alsa-mixer: Set the intended-role of Steelseries Arctis 5/7 + headset as phone + +Fixes: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/issues/640 +--- + .../alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf | 1 + + src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf +index 80c33707aea4..5f11ed1e26ef 100644 +--- a/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf ++++ b/src/modules/alsa/mixer/profile-sets/steelseries-arctis-common-usb-audio.conf +@@ -7,6 +7,7 @@ device-strings = hw:%f,0,0 + channel-map = left,right + paths-input = analog-input-mic + paths-output = steelseries-arctis-output-chat-common ++intended-roles = phone + + [Mapping analog-game] + description = Game +diff --git a/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf +index 01ecf864bcf2..f48b44f03767 100644 +--- a/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf ++++ b/src/modules/alsa/mixer/profile-sets/usb-gaming-headset.conf +@@ -34,6 +34,7 @@ device-strings = hw:%f,0,0 + channel-map = mono + paths-output = usb-gaming-headset-output-mono + paths-input = usb-gaming-headset-input ++intended-roles = phone + + [Mapping analog-stereo] + device-strings = hw:%f,1,0 +-- +2.16.4 + diff --git a/0028-alsa-rewrite-mixer-open-close-cache-mixer-accesses-i.patch b/0028-alsa-rewrite-mixer-open-close-cache-mixer-accesses-i.patch new file mode 100644 index 0000000..b8db228 --- /dev/null +++ b/0028-alsa-rewrite-mixer-open-close-cache-mixer-accesses-i.patch @@ -0,0 +1,761 @@ +From 3bd7c70c518d66707cbfde138ab7dcc505e463ac Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 7 Dec 2019 20:39:21 +0100 +Subject: [PATCH] alsa: rewrite mixer open/close, cache mixer accesses in probe + +The ALSA mixer can be opened multiple times (especially for UCM +in the probe). This adds a simple mixer cache to prevent +multiple open calls. + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.c | 19 ++++---- + src/modules/alsa/alsa-mixer.h | 19 ++++++-- + src/modules/alsa/alsa-sink.c | 21 ++++---- + src/modules/alsa/alsa-source.c | 21 ++++---- + src/modules/alsa/alsa-ucm.c | 66 ++++++++++---------------- + src/modules/alsa/alsa-ucm.h | 1 + + src/modules/alsa/alsa-util.c | 95 +++++++++++++++++++------------------ + src/modules/alsa/alsa-util.h | 6 ++- + src/modules/alsa/module-alsa-card.c | 76 ++++++++++++++++++----------- + 9 files changed, 175 insertions(+), 149 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index 7956371b604b..a3c998b654e9 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -147,13 +147,14 @@ static int alsa_id_decode(const char *src, char *name, int *index) { + return 0; + } + +-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) { ++pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name) { + pa_alsa_jack *jack; + + pa_assert(name); + + jack = pa_xnew0(pa_alsa_jack, 1); + jack->path = path; ++ jack->mixer_device_name = pa_xstrdup(mixer_device_name); + jack->name = pa_xstrdup(name); + jack->alsa_name = pa_sprintf_malloc("%s Jack", name); + jack->state_unplugged = PA_AVAILABLE_NO; +@@ -172,6 +173,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack) { + + pa_xfree(jack->alsa_name); + pa_xfree(jack->name); ++ pa_xfree(jack->mixer_device_name); + pa_xfree(jack); + } + +@@ -1982,7 +1984,7 @@ static pa_alsa_jack* jack_get(pa_alsa_path *p, const char *section) { + if (pa_streq(j->name, section)) + goto finish; + +- j = pa_alsa_jack_new(p, section); ++ j = pa_alsa_jack_new(p, NULL, section); + PA_LLIST_INSERT_AFTER(pa_alsa_jack, p->jacks, p->last_jack, j); + + finish: +@@ -4160,7 +4162,8 @@ fail: + } + + static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, +- pa_alsa_direction_t direction, pa_hashmap *used_paths) { ++ pa_alsa_direction_t direction, pa_hashmap *used_paths, ++ pa_hashmap *mixers) { + + pa_alsa_path *p; + void *state; +@@ -4185,7 +4188,7 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, + + pa_assert(pcm_handle); + +- mixer_handle = pa_alsa_open_mixer_for_pcm(pcm_handle, NULL); ++ mixer_handle = pa_alsa_open_mixer_for_pcm(mixers, pcm_handle, true); + if (!mixer_handle) { + /* Cannot open mixer, remove all entries */ + pa_hashmap_remove_all(ps->paths); +@@ -4203,9 +4206,6 @@ static void mapping_paths_probe(pa_alsa_mapping *m, pa_alsa_profile *profile, + path_set_condense(ps, mixer_handle); + path_set_make_path_descriptions_unique(ps); + +- if (mixer_handle) +- snd_mixer_close(mixer_handle); +- + PA_HASHMAP_FOREACH(p, ps->paths, state) + pa_hashmap_put(used_paths, p, p); + +@@ -4785,6 +4785,7 @@ static void mapping_query_hw_device(pa_alsa_mapping *mapping, snd_pcm_t *pcm) { + + void pa_alsa_profile_set_probe( + pa_alsa_profile_set *ps, ++ pa_hashmap *mixers, + const char *dev_id, + const pa_sample_spec *ss, + unsigned default_n_fragments, +@@ -4914,14 +4915,14 @@ void pa_alsa_profile_set_probe( + PA_IDXSET_FOREACH(m, p->output_mappings, idx) + if (m->output_pcm) { + found_output |= !p->fallback_output; +- mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths); ++ mapping_paths_probe(m, p, PA_ALSA_DIRECTION_OUTPUT, used_paths, mixers); + } + + if (p->input_mappings) + PA_IDXSET_FOREACH(m, p->input_mappings, idx) + if (m->input_pcm) { + found_input |= !p->fallback_input; +- mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths); ++ mapping_paths_probe(m, p, PA_ALSA_DIRECTION_INPUT, used_paths, mixers); + } + } + +diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h +index 7864a2c1252f..325b0c998b02 100644 +--- a/src/modules/alsa/alsa-mixer.h ++++ b/src/modules/alsa/alsa-mixer.h +@@ -32,6 +32,7 @@ + #include + + typedef struct pa_alsa_fdlist pa_alsa_fdlist; ++typedef struct pa_alsa_mixer pa_alsa_mixer; + typedef struct pa_alsa_mixer_pdata pa_alsa_mixer_pdata; + typedef struct pa_alsa_setting pa_alsa_setting; + typedef struct pa_alsa_mixer_id pa_alsa_mixer_id; +@@ -98,6 +99,13 @@ struct pa_alsa_setting { + unsigned priority; + }; + ++/* An entry for one ALSA mixer */ ++struct pa_alsa_mixer { ++ snd_mixer_t *mixer_handle; ++ pa_alsa_fdlist *fdl; ++ bool used_for_probe_only:1; ++}; ++ + /* ALSA mixer element identifier */ + struct pa_alsa_mixer_id { + char *name; +@@ -165,6 +173,9 @@ struct pa_alsa_jack { + pa_alsa_path *path; + PA_LLIST_FIELDS(pa_alsa_jack); + ++ snd_mixer_t *mixer_handle; ++ char *mixer_device_name; ++ + char *name; /* E g "Headphone" */ + char *alsa_name; /* E g "Headphone Jack" */ + bool has_control; /* is the jack itself present? */ +@@ -182,7 +193,7 @@ struct pa_alsa_jack { + bool append_pcm_to_name; + }; + +-pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name); ++pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *mixer_device_name, const char *name); + void pa_alsa_jack_free(pa_alsa_jack *jack); + void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control); + void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in); +@@ -201,6 +212,7 @@ struct pa_alsa_path { + char *description; + unsigned priority; + bool autodetect_eld_device; ++ pa_alsa_mixer *eld_mixer_handle; + int eld_device; + pa_proplist *proplist; + +@@ -359,14 +371,11 @@ void pa_alsa_decibel_fix_dump(pa_alsa_decibel_fix *db_fix); + pa_alsa_mapping *pa_alsa_mapping_get(pa_alsa_profile_set *ps, const char *name); + + pa_alsa_profile_set* pa_alsa_profile_set_new(const char *fname, const pa_channel_map *bonus); +-void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); ++void pa_alsa_profile_set_probe(pa_alsa_profile_set *ps, pa_hashmap *mixers, const char *dev_id, const pa_sample_spec *ss, unsigned default_n_fragments, unsigned default_fragment_size_msec); + void pa_alsa_profile_set_free(pa_alsa_profile_set *s); + void pa_alsa_profile_set_dump(pa_alsa_profile_set *s); + void pa_alsa_profile_set_drop_unsupported(pa_alsa_profile_set *s); + +-snd_mixer_t *pa_alsa_open_mixer_by_name(const char *dev); +-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device); +- + pa_alsa_fdlist *pa_alsa_fdlist_new(void); + void pa_alsa_fdlist_free(pa_alsa_fdlist *fdl); + int pa_alsa_fdlist_set_handle(pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, snd_hctl_t *hctl_handle, pa_mainloop_api* m); +diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c +index 5ea4b2597449..363b4be2fa25 100644 +--- a/src/modules/alsa/alsa-sink.c ++++ b/src/modules/alsa/alsa-sink.c +@@ -105,6 +105,7 @@ struct userdata { + char *paths_dir; + pa_alsa_fdlist *mixer_fdl; + pa_alsa_mixer_pdata *mixer_pd; ++ pa_hashmap *mixers; + snd_mixer_t *mixer_handle; + pa_alsa_path_set *mixer_path_set; + pa_alsa_path *mixer_path; +@@ -2088,13 +2089,16 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char + if (!mapping && !element) + return; + ++ u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, ++ NULL, (pa_free_cb_t) pa_alsa_mixer_free); ++ + mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); + if (mdev) { +- u->mixer_handle = pa_alsa_open_mixer_by_name(mdev); ++ u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, true); + } else { +- u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device); ++ u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->mixers, u->pcm_handle, true); + } +- if (!mdev) { ++ if (!u->mixer_handle) { + pa_log_info("Failed to find a working mixer device."); + return; + } +@@ -2121,10 +2125,9 @@ fail: + u->mixer_path = NULL; + } + +- if (u->mixer_handle) { +- snd_mixer_close(u->mixer_handle); +- u->mixer_handle = NULL; +- } ++ u->mixer_handle = NULL; ++ pa_hashmap_free(u->mixers); ++ u->mixers = NULL; + } + + static int setup_mixer(struct userdata *u, bool ignore_dB) { +@@ -2759,8 +2762,8 @@ static void userdata_free(struct userdata *u) { + if (u->mixer_path && !u->mixer_path_set && !u->ucm_context) + pa_alsa_path_free(u->mixer_path); + +- if (u->mixer_handle) +- snd_mixer_close(u->mixer_handle); ++ if (u->mixers) ++ pa_hashmap_free(u->mixers); + + if (u->smoother) + pa_smoother_free(u->smoother); +diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c +index bd78d45e2a5b..b46e845cc5a7 100644 +--- a/src/modules/alsa/alsa-source.c ++++ b/src/modules/alsa/alsa-source.c +@@ -93,6 +93,7 @@ struct userdata { + char *paths_dir; + pa_alsa_fdlist *mixer_fdl; + pa_alsa_mixer_pdata *mixer_pd; ++ pa_hashmap *mixers; + snd_mixer_t *mixer_handle; + pa_alsa_path_set *mixer_path_set; + pa_alsa_path *mixer_path; +@@ -1794,13 +1795,16 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char + if (!mapping && !element) + return; + ++ u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, ++ NULL, (pa_free_cb_t) pa_alsa_mixer_free); ++ + mdev = pa_proplist_gets(mapping->proplist, "alsa.mixer_device"); + if (mdev) { +- u->mixer_handle = pa_alsa_open_mixer_by_name(mdev); ++ u->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, mdev, false); + } else { +- u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device); ++ u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->mixers, u->pcm_handle, false); + } +- if (!mdev) { ++ if (!u->mixer_handle) { + pa_log_info("Failed to find a working mixer device."); + return; + } +@@ -1827,10 +1831,9 @@ fail: + u->mixer_path = NULL; + } + +- if (u->mixer_handle) { +- snd_mixer_close(u->mixer_handle); +- u->mixer_handle = NULL; +- } ++ u->mixer_handle = NULL; ++ pa_hashmap_free(u->mixers); ++ u->mixers = NULL; + } + + static int setup_mixer(struct userdata *u, bool ignore_dB) { +@@ -2403,8 +2406,8 @@ static void userdata_free(struct userdata *u) { + if (u->mixer_path && !u->mixer_path_set && !u->ucm_context) + pa_alsa_path_free(u->mixer_path); + +- if (u->mixer_handle) +- snd_mixer_close(u->mixer_handle); ++ if (u->mixers) ++ pa_hashmap_free(u->mixers); + + if (u->smoother) + pa_smoother_free(u->smoother); +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index a64505a0c781..b9a4b8c4b436 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -820,13 +820,13 @@ 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, bool is_sink, snd_pcm_t *pcm_handle, bool ignore_dB) { ++static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_hashmap *mixers, bool ignore_dB) { + pa_device_port *port; + pa_alsa_path *path; + pa_alsa_ucm_port_data *data; + pa_alsa_ucm_device *dev; +- snd_mixer_t *mixer_handle = NULL; +- const char *profile, *mdev_opened = NULL, *mdev, *mdev2; ++ snd_mixer_t *mixer_handle; ++ const char *profile, *mdev, *mdev2; + void *state, *state2; + int idx; + +@@ -843,16 +843,9 @@ static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, + mdev = mdev2; + } + +- if (!mdev_opened || !pa_streq(mdev_opened, mdev)) { +- if (mixer_handle) { +- snd_mixer_close(mixer_handle); +- mdev_opened = NULL; +- } +- if (!(mixer_handle = pa_alsa_open_mixer_by_name(mdev))) { +- pa_log_error("Failed to find a working mixer device (%s).", mdev); +- goto fail; +- } +- mdev_opened = mdev; ++ if (!(mixer_handle = pa_alsa_open_mixer_by_name(mixers, mdev, true))) { ++ pa_log_error("Failed to find a working mixer device (%s).", mdev); ++ goto fail; + } + + PA_HASHMAP_FOREACH_KV(profile, path, data->paths, state2) { +@@ -867,9 +860,6 @@ static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, + } + } + +- if (mixer_handle) +- snd_mixer_close(mixer_handle); +- + return; + + fail: +@@ -1194,7 +1184,7 @@ void pa_alsa_ucm_add_ports( + 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, is_sink, pcm_handle, ignore_dB); ++ probe_volumes(*p, is_sink, pcm_handle, context->ucm->mixers, ignore_dB); + + /* then set property PA_PROP_DEVICE_INTENDED_ROLES */ + merged_roles = pa_xstrdup(pa_proplist_gets(proplist, PA_PROP_DEVICE_INTENDED_ROLES)); +@@ -1543,6 +1533,7 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d + pa_alsa_jack *j; + const char *device_name; + const char *jack_control; ++ const char *mixer_device_name; + char *name; + + pa_assert(ucm); +@@ -1585,7 +1576,14 @@ static pa_alsa_jack* ucm_get_jack(pa_alsa_ucm_config *ucm, pa_alsa_ucm_device *d + if (pa_streq(j->name, name)) + goto finish; + +- j = pa_alsa_jack_new(NULL, name); ++ mixer_device_name = get_jack_mixer_device(device, true); ++ if (!mixer_device_name) ++ mixer_device_name = get_jack_mixer_device(device, false); ++ if (!mixer_device_name) { ++ pa_log("[%s] No mixer device name for JackControl \"%s\"", device_name, jack_control); ++ return NULL; ++ } ++ j = pa_alsa_jack_new(NULL, mixer_device_name, name); + PA_LLIST_PREPEND(pa_alsa_jack, ucm->jacks, j); + + finish: +@@ -1765,44 +1763,28 @@ static void profile_finalize_probing(pa_alsa_profile *p) { + } + } + +-static void ucm_mapping_jack_probe(pa_alsa_mapping *m) { +- snd_mixer_t *mixer_handle = NULL; ++static void ucm_mapping_jack_probe(pa_alsa_mapping *m, pa_hashmap *mixers) { ++ snd_mixer_t *mixer_handle; + pa_alsa_ucm_mapping_context *context = &m->ucm_context; + pa_alsa_ucm_device *dev; +- bool is_sink = m->direction == PA_ALSA_DIRECTION_OUTPUT; +- const char *mdev_opened = NULL, *mdev; + uint32_t idx; + + PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { + bool has_control; + +- if (!dev->jack) ++ if (!dev->jack || !dev->jack->mixer_device_name) + continue; + +- mdev = get_jack_mixer_device(dev, is_sink); +- if (mdev == NULL) { +- pa_log_error("Unable to determine mixer device for jack %s", dev->jack->name); ++ mixer_handle = pa_alsa_open_mixer_by_name(mixers, dev->jack->mixer_device_name, true); ++ if (!mixer_handle) { ++ pa_log_error("Unable to determine open mixer device '%s' for jack %s", dev->jack->mixer_device_name, dev->jack->name); + continue; + } + +- if (!mdev_opened || !pa_streq(mdev_opened, mdev)) { +- if (mixer_handle) { +- snd_mixer_close(mixer_handle); +- mdev_opened = NULL; +- } +- mixer_handle = pa_alsa_open_mixer_by_name(mdev); +- if (!mixer_handle) +- continue; +- mdev_opened = mdev; +- } +- + has_control = pa_alsa_mixer_find_card(mixer_handle, dev->jack->alsa_name, 0) != NULL; + pa_alsa_jack_set_has_control(dev->jack, has_control); + pa_log_info("UCM jack %s has_control=%d", dev->jack->name, dev->jack->has_control); + } +- +- if (mixer_handle) +- snd_mixer_close(mixer_handle); + } + + static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set *ps) { +@@ -1860,11 +1842,11 @@ static void ucm_probe_profile_set(pa_alsa_ucm_config *ucm, pa_alsa_profile_set * + + PA_IDXSET_FOREACH(m, p->output_mappings, idx) + if (!PA_UCM_IS_MODIFIER_MAPPING(m)) +- ucm_mapping_jack_probe(m); ++ ucm_mapping_jack_probe(m, ucm->mixers); + + PA_IDXSET_FOREACH(m, p->input_mappings, idx) + if (!PA_UCM_IS_MODIFIER_MAPPING(m)) +- ucm_mapping_jack_probe(m); ++ ucm_mapping_jack_probe(m, ucm->mixers); + + profile_finalize_probing(p); + } +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index 014bc334ad67..49362992865b 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -248,6 +248,7 @@ struct pa_alsa_ucm_config { + snd_use_case_mgr_t *ucm_mgr; + pa_alsa_ucm_verb *active_verb; + ++ pa_hashmap *mixers; + PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs); + PA_LLIST_HEAD(pa_alsa_jack, jacks); + }; +diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c +index 08a144782394..34930c9fa155 100644 +--- a/src/modules/alsa/alsa-util.c ++++ b/src/modules/alsa/alsa-util.c +@@ -1726,83 +1726,86 @@ static int prepare_mixer(snd_mixer_t *mixer, const char *dev) { + return 0; + } + +-snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device) { +- int err; +- snd_mixer_t *m; +- char *md; +- snd_pcm_info_t* info; +- snd_pcm_info_alloca(&info); +- +- if ((err = snd_mixer_open(&m, 0)) < 0) { +- pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); +- return NULL; +- } +- +- /* Then, try by card index */ +- md = pa_sprintf_malloc("hw:%i", alsa_card_index); +- if (prepare_mixer(m, md) >= 0) { +- +- if (ctl_device) +- *ctl_device = md; +- else +- pa_xfree(md); +- +- return m; +- } +- ++snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe) { ++ char *md = pa_sprintf_malloc("hw:%i", alsa_card_index); ++ snd_mixer_t *m = pa_alsa_open_mixer_by_name(mixers, md, probe); + pa_xfree(md); +- +- snd_mixer_close(m); +- return NULL; ++ return m; + } + +-snd_mixer_t *pa_alsa_open_mixer_by_name(const char *dev) { ++snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe) { + int err; + snd_mixer_t *m; ++ pa_alsa_mixer *pm; + ++ pa_assert(mixers); + pa_assert(dev); + ++ pm = pa_hashmap_get(mixers, dev); ++ if (pm) { ++ if (!probe) ++ pm->used_for_probe_only = false; ++ return pm->mixer_handle; ++ } ++ + if ((err = snd_mixer_open(&m, 0)) < 0) { + pa_log("Error opening mixer: %s", pa_alsa_strerror(err)); + return NULL; + } + +- if (prepare_mixer(m, dev) >= 0) +- return m; ++ if (prepare_mixer(m, dev) >= 0) { ++ pm = pa_xnew0(pa_alsa_mixer, 1); ++ if (pm) { ++ pm->used_for_probe_only = probe; ++ pm->mixer_handle = m; ++ pa_hashmap_put(mixers, pa_xstrdup(dev), pm); ++ return m; ++ } ++ } + + snd_mixer_close(m); + return NULL; + } + +-snd_mixer_t *pa_alsa_open_mixer_for_pcm(snd_pcm_t *pcm, char **ctl_device) { +- snd_mixer_t *m; ++snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe) { + snd_pcm_info_t* info; + snd_pcm_info_alloca(&info); + + pa_assert(pcm); + + if (snd_pcm_info(pcm, info) >= 0) { +- char *md; + int card_idx; + +- if ((card_idx = snd_pcm_info_get_card(info)) >= 0) { ++ if ((card_idx = snd_pcm_info_get_card(info)) >= 0) ++ return pa_alsa_open_mixer(mixers, card_idx, probe); ++ } + +- md = pa_sprintf_malloc("hw:%i", card_idx); +- m = pa_alsa_open_mixer_by_name(md); +- if (m) { +- if (ctl_device) +- *ctl_device = md; +- else +- pa_xfree(md); ++ return NULL; ++} + +- return m; +- } ++void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer_handle, pa_mainloop_api *ml) ++{ ++ pa_alsa_mixer *pm; ++ void *state; + +- pa_xfree(md); ++ PA_HASHMAP_FOREACH(pm, mixers, state) ++ if (pm->mixer_handle == mixer_handle) { ++ pm->used_for_probe_only = false; ++ if (!pm->fdl) { ++ pm->fdl = pa_alsa_fdlist_new(); ++ if (pm->fdl) ++ pa_alsa_fdlist_set_handle(pm->fdl, pm->mixer_handle, NULL, ml); ++ } + } +- } ++} + +- return NULL; ++void pa_alsa_mixer_free(pa_alsa_mixer *mixer) ++{ ++ if (mixer->fdl) ++ pa_alsa_fdlist_free(mixer->fdl); ++ if (mixer->mixer_handle) ++ snd_mixer_close(mixer->mixer_handle); ++ pa_xfree(mixer); + } + + int pa_alsa_get_hdmi_eld(snd_hctl_elem_t *elem, pa_hdmi_eld *eld) { +diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h +index ceca48809400..0d3d5af13370 100644 +--- a/src/modules/alsa/alsa-util.h ++++ b/src/modules/alsa/alsa-util.h +@@ -144,7 +144,11 @@ bool pa_alsa_may_tsched(bool want); + snd_mixer_elem_t *pa_alsa_mixer_find_card(snd_mixer_t *mixer, const char *name, unsigned int device); + snd_mixer_elem_t *pa_alsa_mixer_find_pcm(snd_mixer_t *mixer, const char *name, unsigned int device); + +-snd_mixer_t *pa_alsa_open_mixer(int alsa_card_index, char **ctl_device); ++snd_mixer_t *pa_alsa_open_mixer(pa_hashmap *mixers, int alsa_card_index, bool probe); ++snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, bool probe); ++snd_mixer_t *pa_alsa_open_mixer_for_pcm(pa_hashmap *mixers, snd_pcm_t *pcm, bool probe); ++void pa_alsa_mixer_set_fdlist(pa_hashmap *mixers, snd_mixer_t *mixer, pa_mainloop_api *ml); ++void pa_alsa_mixer_free(pa_alsa_mixer *mixer); + + typedef struct pa_hdmi_eld pa_hdmi_eld; + struct pa_hdmi_eld { +diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c +index ba7a19a38983..3b2f99fc4e54 100644 +--- a/src/modules/alsa/module-alsa-card.c ++++ b/src/modules/alsa/module-alsa-card.c +@@ -111,9 +111,8 @@ struct userdata { + char *device_id; + int alsa_card_index; + +- snd_mixer_t *mixer_handle; ++ pa_hashmap *mixers; + pa_hashmap *jacks; +- pa_alsa_fdlist *mixer_fdl; + + pa_card *card; + +@@ -569,9 +568,6 @@ static void init_eld_ctls(struct userdata *u) { + void *state; + pa_device_port *port; + +- if (!u->mixer_handle) +- return; +- + /* The code in this function expects ports to have a pa_alsa_port_data + * struct as their data, but in UCM mode ports don't have any data. Hence, + * the ELD controls can't currently be used in UCM mode. */ +@@ -580,6 +576,7 @@ static void init_eld_ctls(struct userdata *u) { + + PA_HASHMAP_FOREACH(port, u->card->ports, state) { + pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); ++ snd_mixer_t *mixer_handle; + snd_mixer_elem_t* melem; + int device; + +@@ -588,8 +585,13 @@ static void init_eld_ctls(struct userdata *u) { + if (device < 0) + continue; + +- melem = pa_alsa_mixer_find_pcm(u->mixer_handle, "ELD", device); ++ mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, true); ++ if (!mixer_handle) ++ continue; ++ ++ melem = pa_alsa_mixer_find_pcm(mixer_handle, "ELD", device); + if (melem) { ++ pa_alsa_mixer_set_fdlist(u->mixers, mixer_handle, u->core->mainloop); + snd_mixer_elem_set_callback(melem, hdmi_eld_changed); + snd_mixer_elem_set_callback_private(melem, u); + hdmi_eld_changed(melem, 0); +@@ -630,25 +632,31 @@ static void init_jacks(struct userdata *u) { + if (pa_hashmap_size(u->jacks) == 0) + return; + +- u->mixer_fdl = pa_alsa_fdlist_new(); +- +- u->mixer_handle = pa_alsa_open_mixer(u->alsa_card_index, NULL); +- if (u->mixer_handle && pa_alsa_fdlist_set_handle(u->mixer_fdl, u->mixer_handle, NULL, u->core->mainloop) >= 0) { +- PA_HASHMAP_FOREACH(jack, u->jacks, state) { +- jack->melem = pa_alsa_mixer_find_card(u->mixer_handle, jack->alsa_name, 0); +- if (!jack->melem) { +- pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); +- pa_alsa_jack_set_has_control(jack, false); +- continue; ++ PA_HASHMAP_FOREACH(jack, u->jacks, state) { ++ if (!jack->mixer_device_name) { ++ jack->mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, false); ++ if (!jack->mixer_handle) { ++ pa_log("Failed to open mixer for card %d for jack detection", u->alsa_card_index); ++ continue; ++ } ++ } else { ++ jack->mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, jack->mixer_device_name, false); ++ if (!jack->mixer_handle) { ++ pa_log("Failed to open mixer '%s' for jack detection", jack->mixer_device_name); ++ continue; + } +- snd_mixer_elem_set_callback(jack->melem, report_jack_state); +- snd_mixer_elem_set_callback_private(jack->melem, u); +- report_jack_state(jack->melem, 0); + } +- +- } else +- pa_log("Failed to open mixer for jack detection"); +- ++ pa_alsa_mixer_set_fdlist(u->mixers, jack->mixer_handle, u->core->mainloop); ++ jack->melem = pa_alsa_mixer_find_card(jack->mixer_handle, jack->alsa_name, 0); ++ if (!jack->melem) { ++ pa_log_warn("Jack '%s' seems to have disappeared.", jack->alsa_name); ++ pa_alsa_jack_set_has_control(jack, false); ++ continue; ++ } ++ snd_mixer_elem_set_callback(jack->melem, report_jack_state); ++ snd_mixer_elem_set_callback_private(jack->melem, u); ++ report_jack_state(jack->melem, 0); ++ } + } + + static void set_card_name(pa_card_new_data *data, pa_modargs *ma, const char *device_id) { +@@ -772,6 +780,10 @@ int pa__init(pa_module *m) { + u->use_ucm = true; + u->ucm.core = m->core; + ++ u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, ++ pa_xfree, (pa_free_cb_t) pa_alsa_mixer_free); ++ u->ucm.mixers = u->mixers; /* alias */ ++ + if (!(u->modargs = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; +@@ -852,7 +864,7 @@ int pa__init(pa_module *m) { + + u->profile_set->ignore_dB = ignore_dB; + +- pa_alsa_profile_set_probe(u->profile_set, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); ++ pa_alsa_profile_set_probe(u->profile_set, u->mixers, u->device_id, &m->core->default_sample_spec, m->core->default_n_fragments, m->core->default_fragment_size_msec); + pa_alsa_profile_set_dump(u->profile_set); + + pa_card_new_data_init(&data); +@@ -952,6 +964,16 @@ int pa__init(pa_module *m) { + init_profile(u); + init_eld_ctls(u); + ++ /* Remove all probe only mixers */ ++ if (u->mixers) { ++ const char *devname; ++ pa_alsa_mixer *pm; ++ void *state; ++ PA_HASHMAP_FOREACH_KV(devname, pm, u->mixers, state) ++ if (pm->used_for_probe_only) ++ pa_hashmap_remove_and_free(u->mixers, devname); ++ } ++ + if (reserve) + pa_reserve_wrapper_unref(reserve); + +@@ -1002,10 +1024,8 @@ void pa__done(pa_module*m) { + if (!(u = m->userdata)) + goto finish; + +- if (u->mixer_fdl) +- pa_alsa_fdlist_free(u->mixer_fdl); +- if (u->mixer_handle) +- snd_mixer_close(u->mixer_handle); ++ if (u->mixers) ++ pa_hashmap_free(u->mixers); + if (u->jacks) + pa_hashmap_free(u->jacks); + +-- +2.16.4 + diff --git a/0029-alsa-ucm-add-support-for-HDMI-ELD.patch b/0029-alsa-ucm-add-support-for-HDMI-ELD.patch new file mode 100644 index 0000000..ec3db18 --- /dev/null +++ b/0029-alsa-ucm-add-support-for-HDMI-ELD.patch @@ -0,0 +1,264 @@ +From 3ceff8bb3f697ec16dc5c72e658b10ac40bc19f5 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sat, 7 Dec 2019 23:22:33 +0100 +Subject: [PATCH] alsa-ucm: add support for HDMI ELD + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 72 +++++++++++++++++++++++++++++++++++-- + src/modules/alsa/alsa-ucm.h | 7 ++++ + src/modules/alsa/module-alsa-card.c | 56 ++++++++++++++++++----------- + 3 files changed, 113 insertions(+), 22 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index b9a4b8c4b436..5695840abaf1 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -820,6 +820,36 @@ 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 set_eld_devices(pa_hashmap *hash) ++{ ++ pa_device_port *port; ++ pa_alsa_ucm_port_data *data; ++ pa_alsa_ucm_device *dev; ++ const char *eld_mixer_device_name; ++ void *state; ++ int idx, eld_device; ++ ++ PA_HASHMAP_FOREACH(port, hash, state) { ++ data = PA_DEVICE_PORT_DATA(port); ++ eld_mixer_device_name = NULL; ++ eld_device = -1; ++ PA_DYNARRAY_FOREACH(dev, data->devices, idx) { ++ if (dev->eld_device >= 0 && dev->eld_mixer_device_name) { ++ if (eld_device >= 0 && eld_device != dev->eld_device) { ++ pa_log_error("The ELD device is already set!"); ++ } else if (eld_mixer_device_name && pa_streq(dev->eld_mixer_device_name, eld_mixer_device_name)) { ++ pa_log_error("The ELD mixer device is already set (%s, %s)!", dev->eld_mixer_device_name, dev->eld_mixer_device_name); ++ } else { ++ eld_mixer_device_name = dev->eld_mixer_device_name; ++ eld_device = dev->eld_device; ++ } ++ } ++ } ++ data->eld_device = eld_device; ++ data->eld_mixer_device_name = pa_xstrdup(eld_mixer_device_name); ++ } ++} ++ + static void probe_volumes(pa_hashmap *hash, bool is_sink, snd_pcm_t *pcm_handle, pa_hashmap *mixers, bool ignore_dB) { + pa_device_port *port; + pa_alsa_path *path; +@@ -1159,6 +1189,9 @@ void pa_alsa_ucm_add_ports_combination( + ucm_add_ports_combination(p, context, is_sink, pdevices, 0, PA_IDXSET_INVALID, ports, cp, core); + pa_xfree(pdevices); + } ++ ++ /* ELD devices */ ++ set_eld_devices(ports); + } + + void pa_alsa_ucm_add_ports( +@@ -1709,6 +1742,33 @@ static int ucm_create_profile( + return 0; + } + ++static void mapping_init_eld(pa_alsa_mapping *m, snd_pcm_t *pcm) ++{ ++ pa_alsa_ucm_mapping_context *context = &m->ucm_context; ++ pa_alsa_ucm_device *dev; ++ uint32_t idx; ++ char *mdev; ++ snd_pcm_info_t *info; ++ int pcm_card, pcm_device; ++ ++ snd_pcm_info_alloca(&info); ++ if (snd_pcm_info(pcm, info) < 0) ++ return; ++ ++ if ((pcm_card = snd_pcm_info_get_card(info)) < 0) ++ return; ++ if ((pcm_device = snd_pcm_info_get_device(info)) < 0) ++ return; ++ ++ PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) { ++ mdev = pa_sprintf_malloc("hw:%i", pcm_card); ++ if (mdev == NULL) ++ continue; ++ dev->eld_mixer_device_name = mdev; ++ dev->eld_device = pcm_device; ++ } ++} ++ + static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, int mode) { + snd_pcm_t* pcm; + pa_sample_spec try_ss = ucm->core->default_sample_spec; +@@ -1730,8 +1790,11 @@ static snd_pcm_t* mapping_open_pcm(pa_alsa_ucm_config *ucm, pa_alsa_mapping *m, + pcm = pa_alsa_open_by_device_string(m->device_strings[0], NULL, &try_ss, + &try_map, mode, &try_period_size, &try_buffer_size, 0, NULL, NULL, exact_channels); + +- if (pcm && !exact_channels) +- m->channel_map = try_map; ++ if (pcm) { ++ if (!exact_channels) ++ m->channel_map = try_map; ++ mapping_init_eld(m, pcm); ++ } + + return pcm; + } +@@ -1912,6 +1975,8 @@ static void free_verb(pa_alsa_ucm_verb *verb) { + if (di->supported_devices) + pa_idxset_free(di->supported_devices, NULL); + ++ pa_xfree(di->eld_mixer_device_name); ++ + pa_xfree(di); + } + +@@ -2115,6 +2180,7 @@ static void ucm_port_data_init(pa_alsa_ucm_port_data *port, pa_alsa_ucm_config * + port->ucm = ucm; + port->core_port = core_port; + port->devices = pa_dynarray_new(NULL); ++ port->eld_device = -1; + + for (i = 0; i < n_devices; i++) { + pa_dynarray_append(port->devices, devices[i]); +@@ -2139,6 +2205,8 @@ static void ucm_port_data_free(pa_device_port *port) { + + if (ucm_port->paths) + pa_hashmap_free(ucm_port->paths); ++ ++ pa_xfree(ucm_port->eld_mixer_device_name); + } + + static void ucm_port_update_available(pa_alsa_ucm_port_data *port) { +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index 49362992865b..48fd9db3f79f 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -207,6 +207,9 @@ struct pa_alsa_ucm_device { + pa_alsa_jack *jack; + pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */ + pa_available_t available; ++ ++ char *eld_mixer_device_name; ++ int eld_device; + }; + + void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device); +@@ -273,6 +276,10 @@ struct pa_alsa_ucm_port_data { + pa_hashmap *paths; + /* Current path, set when activating profile */ + pa_alsa_path *path; ++ ++ /* ELD info */ ++ char *eld_mixer_device_name; ++ int eld_device; /* PCM device number */ + }; + + struct pa_alsa_ucm_volume { +diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c +index 3b2f99fc4e54..c5852b43d844 100644 +--- a/src/modules/alsa/module-alsa-card.c ++++ b/src/modules/alsa/module-alsa-card.c +@@ -512,15 +512,24 @@ static int report_jack_state(snd_mixer_elem_t *melem, unsigned int mask) { + return 0; + } + +-static pa_device_port* find_port_with_eld_device(pa_hashmap *ports, int device) { ++static pa_device_port* find_port_with_eld_device(struct userdata *u, int device) { + void *state; + pa_device_port *p; + +- PA_HASHMAP_FOREACH(p, ports, state) { +- pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p); +- pa_assert(data->path); +- if (device == data->path->eld_device) +- return p; ++ if (u->use_ucm) { ++ PA_HASHMAP_FOREACH(p, u->card->ports, state) { ++ pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(p); ++ pa_assert(data->eld_mixer_device_name); ++ if (device == data->eld_device) ++ return p; ++ } ++ } else { ++ PA_HASHMAP_FOREACH(p, u->card->ports, state) { ++ pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(p); ++ pa_assert(data->path); ++ if (device == data->path->eld_device) ++ return p; ++ } + } + return NULL; + } +@@ -537,10 +546,7 @@ static int hdmi_eld_changed(snd_mixer_elem_t *melem, unsigned int mask) { + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + +- if (u->use_ucm) +- return 0; +- +- p = find_port_with_eld_device(u->card->ports, device); ++ p = find_port_with_eld_device(u, device); + if (p == NULL) { + pa_log_error("Invalid device changed in ALSA: %d", device); + return 0; +@@ -571,21 +577,30 @@ static void init_eld_ctls(struct userdata *u) { + /* The code in this function expects ports to have a pa_alsa_port_data + * struct as their data, but in UCM mode ports don't have any data. Hence, + * the ELD controls can't currently be used in UCM mode. */ +- if (u->use_ucm) +- return; +- + PA_HASHMAP_FOREACH(port, u->card->ports, state) { +- pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); + snd_mixer_t *mixer_handle; + snd_mixer_elem_t* melem; + int device; + +- pa_assert(data->path); +- device = data->path->eld_device; +- if (device < 0) +- continue; ++ if (u->use_ucm) { ++ pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(port); ++ device = data->eld_device; ++ if (device < 0 || !data->eld_mixer_device_name) ++ continue; ++ ++ mixer_handle = pa_alsa_open_mixer_by_name(u->mixers, data->eld_mixer_device_name, true); ++ } else { ++ pa_alsa_port_data *data = PA_DEVICE_PORT_DATA(port); ++ ++ pa_assert(data->path); ++ ++ device = data->path->eld_device; ++ if (device < 0) ++ continue; ++ ++ mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, true); ++ } + +- mixer_handle = pa_alsa_open_mixer(u->mixers, u->alsa_card_index, true); + if (!mixer_handle) + continue; + +@@ -595,9 +610,10 @@ static void init_eld_ctls(struct userdata *u) { + snd_mixer_elem_set_callback(melem, hdmi_eld_changed); + snd_mixer_elem_set_callback_private(melem, u); + hdmi_eld_changed(melem, 0); ++ pa_log_info("ELD device found for port %s (%d).", port->name, device); + } + else +- pa_log_debug("No ELD device found for port %s.", port->name); ++ pa_log_debug("No ELD device found for port %s (%d).", port->name, device); + } + } + +-- +2.16.4 + diff --git a/0030-alsa-mixer-do-the-quick-card-number-lookup-to-save-m.patch b/0030-alsa-mixer-do-the-quick-card-number-lookup-to-save-m.patch new file mode 100644 index 0000000..4c263af --- /dev/null +++ b/0030-alsa-mixer-do-the-quick-card-number-lookup-to-save-m.patch @@ -0,0 +1,83 @@ +From 8837c90b7fe6b7c3cbbaeda0a29e2f5d311c3d14 Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sun, 8 Dec 2019 22:48:45 +0100 +Subject: [PATCH] alsa-mixer: do the quick card number lookup to save mixer + instances + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.h | 1 + + src/modules/alsa/alsa-util.c | 33 +++++++++++++++++++++++++++++++++ + 2 files changed, 34 insertions(+) + +diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h +index 325b0c998b02..d740a78c8dce 100644 +--- a/src/modules/alsa/alsa-mixer.h ++++ b/src/modules/alsa/alsa-mixer.h +@@ -102,6 +102,7 @@ struct pa_alsa_setting { + /* An entry for one ALSA mixer */ + struct pa_alsa_mixer { + snd_mixer_t *mixer_handle; ++ int card_index; + pa_alsa_fdlist *fdl; + bool used_for_probe_only:1; + }; +diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c +index 34930c9fa155..d86f43c10098 100644 +--- a/src/modules/alsa/alsa-util.c ++++ b/src/modules/alsa/alsa-util.c +@@ -1737,11 +1737,33 @@ snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, boo + int err; + snd_mixer_t *m; + pa_alsa_mixer *pm; ++ char *dev2; ++ void *state; + + pa_assert(mixers); + pa_assert(dev); + + pm = pa_hashmap_get(mixers, dev); ++ ++ /* The quick card number/index lookup (hw:#) ++ * We already know the card number/index, thus use the mixer ++ * from the cache at first. ++ */ ++ if (!pm && pa_strneq(dev, "hw:", 3)) { ++ const char *s = dev + 3; ++ int card_index; ++ while (*s && *s >= 0 && *s <= '9') s++; ++ if (*s == '\0' && pa_atoi(dev + 3, &card_index) >= 0) { ++ PA_HASHMAP_FOREACH_KV(dev2, pm, mixers, state) { ++ if (pm->card_index == card_index) { ++ dev = dev2; ++ pm = pa_hashmap_get(mixers, dev); ++ break; ++ } ++ } ++ } ++ } ++ + if (pm) { + if (!probe) + pm->used_for_probe_only = false; +@@ -1756,6 +1778,17 @@ snd_mixer_t *pa_alsa_open_mixer_by_name(pa_hashmap *mixers, const char *dev, boo + if (prepare_mixer(m, dev) >= 0) { + pm = pa_xnew0(pa_alsa_mixer, 1); + if (pm) { ++ snd_hctl_t *hctl; ++ pm->card_index = -1; ++ /* determine the ALSA card number (index) and store it to card_index */ ++ err = snd_mixer_get_hctl(m, dev, &hctl); ++ if (err >= 0) { ++ snd_ctl_card_info_t *info; ++ snd_ctl_card_info_alloca(&info); ++ err = snd_ctl_card_info(snd_hctl_ctl(hctl), info); ++ if (err >= 0) ++ pm->card_index = snd_ctl_card_info_get_card(info); ++ } + pm->used_for_probe_only = probe; + pm->mixer_handle = m; + pa_hashmap_put(mixers, pa_xstrdup(dev), pm); +-- +2.16.4 + diff --git a/0031-alsa-mixer-improve-check-for-the-empty-path-set-for-.patch b/0031-alsa-mixer-improve-check-for-the-empty-path-set-for-.patch new file mode 100644 index 0000000..a11ad13 --- /dev/null +++ b/0031-alsa-mixer-improve-check-for-the-empty-path-set-for-.patch @@ -0,0 +1,134 @@ +From d7dbd0cbe3191661c02ac89d108b36c79474de3c Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Sun, 8 Dec 2019 23:17:32 +0100 +Subject: [PATCH] alsa-mixer: improve check for the empty path set for + sink/source + +The unused mixer instances are created without this code. + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-mixer.c | 6 ++++++ + src/modules/alsa/alsa-mixer.h | 1 + + src/modules/alsa/alsa-sink.c | 19 +++++++++++++------ + src/modules/alsa/alsa-source.c | 9 ++++++--- + 4 files changed, 26 insertions(+), 9 deletions(-) + +diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c +index a3c998b654e9..d184aec7aee7 100644 +--- a/src/modules/alsa/alsa-mixer.c ++++ b/src/modules/alsa/alsa-mixer.c +@@ -741,6 +741,12 @@ void pa_alsa_path_set_free(pa_alsa_path_set *ps) { + pa_xfree(ps); + } + ++int pa_alsa_path_set_is_empty(pa_alsa_path_set *ps) { ++ if (ps && !pa_hashmap_isempty(ps->paths)) ++ return 0; ++ return 1; ++} ++ + static long to_alsa_dB(pa_volume_t v) { + return lround(pa_sw_volume_to_dB(v) * 100.0); + } +diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h +index d740a78c8dce..df739cc2e0f3 100644 +--- a/src/modules/alsa/alsa-mixer.h ++++ b/src/modules/alsa/alsa-mixer.h +@@ -272,6 +272,7 @@ pa_alsa_path_set *pa_alsa_path_set_new(pa_alsa_mapping *m, pa_alsa_direction_t d + void pa_alsa_path_set_dump(pa_alsa_path_set *s); + void pa_alsa_path_set_set_callback(pa_alsa_path_set *ps, snd_mixer_t *m, snd_mixer_elem_callback_t cb, void *userdata); + void pa_alsa_path_set_free(pa_alsa_path_set *s); ++int pa_alsa_path_set_is_empty(pa_alsa_path_set *s); + + struct pa_alsa_mapping { + pa_alsa_profile_set *profile_set; +diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c +index 363b4be2fa25..042d4dfd9c89 100644 +--- a/src/modules/alsa/alsa-sink.c ++++ b/src/modules/alsa/alsa-sink.c +@@ -1651,7 +1651,6 @@ static int sink_set_port_ucm_cb(pa_sink *s, pa_device_port *p) { + + pa_assert(u); + pa_assert(p); +- pa_assert(u->mixer_handle); + pa_assert(u->ucm_context); + + data = PA_DEVICE_PORT_DATA(p); +@@ -2089,6 +2088,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char + if (!mapping && !element) + return; + ++ if (!element && mapping && pa_alsa_path_set_is_empty(mapping->output_path_set)) ++ return; ++ + u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + NULL, (pa_free_cb_t) pa_alsa_mixer_free); + +@@ -2113,8 +2115,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char + + pa_log_debug("Probed mixer path %s:", u->mixer_path->name); + pa_alsa_path_dump(u->mixer_path); +- } else if (!(u->mixer_path_set = mapping->output_path_set)) +- goto fail; ++ } else { ++ u->mixer_path_set = mapping->output_path_set; ++ } + + return; + +@@ -2559,10 +2562,14 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca + goto fail; + } + +- if (u->ucm_context) ++ if (u->ucm_context) { + 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); ++ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); ++ } else { ++ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB); ++ if (u->mixer_path_set) ++ pa_alsa_add_ports(&data, u->mixer_path_set, card); ++ } + + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE | PA_SINK_LATENCY | (u->use_tsched ? PA_SINK_DYNAMIC_LATENCY : 0) | + (set_formats ? PA_SINK_SET_FORMATS : 0)); +diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c +index b46e845cc5a7..104de4e266dd 100644 +--- a/src/modules/alsa/alsa-source.c ++++ b/src/modules/alsa/alsa-source.c +@@ -1522,7 +1522,6 @@ static int source_set_port_ucm_cb(pa_source *s, pa_device_port *p) { + + pa_assert(u); + pa_assert(p); +- pa_assert(u->mixer_handle); + pa_assert(u->ucm_context); + + data = PA_DEVICE_PORT_DATA(p); +@@ -1795,6 +1794,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char + if (!mapping && !element) + return; + ++ if (!element && mapping && pa_alsa_path_set_is_empty(mapping->input_path_set)) ++ return; ++ + u->mixers = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, + NULL, (pa_free_cb_t) pa_alsa_mixer_free); + +@@ -1819,8 +1821,9 @@ static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char + + pa_log_debug("Probed mixer path %s:", u->mixer_path->name); + pa_alsa_path_dump(u->mixer_path); +- } else if (!(u->mixer_path_set = mapping->input_path_set)) +- goto fail; ++ } else { ++ u->mixer_path_set = mapping->input_path_set; ++ } + + return; + +-- +2.16.4 + diff --git a/0032-alsa-ucm-allow-to-set-profile-priority-from-UCM-valu.patch b/0032-alsa-ucm-allow-to-set-profile-priority-from-UCM-valu.patch new file mode 100644 index 0000000..d618fe6 --- /dev/null +++ b/0032-alsa-ucm-allow-to-set-profile-priority-from-UCM-valu.patch @@ -0,0 +1,128 @@ +From cd4a69374cefe7c720bd6916f06ff7151da9892a Mon Sep 17 00:00:00 2001 +From: Jaroslav Kysela +Date: Tue, 10 Dec 2019 12:34:19 +0100 +Subject: [PATCH] alsa-ucm: allow to set profile priority from UCM value + +Signed-off-by: Jaroslav Kysela +--- + src/modules/alsa/alsa-ucm.c | 59 +++++++++++++++++++++++++++++++++------------ + src/modules/alsa/alsa-ucm.h | 1 + + 2 files changed, 45 insertions(+), 15 deletions(-) + +diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c +index 5695840abaf1..a57be6d22497 100644 +--- a/src/modules/alsa/alsa-ucm.c ++++ b/src/modules/alsa/alsa-ucm.c +@@ -146,6 +146,26 @@ static struct ucm_info dev_info[] = { + {NULL, 0} + }; + ++ ++static char *ucm_verb_value( ++ snd_use_case_mgr_t *uc_mgr, ++ const char *verb_name, ++ const char *id) { ++ ++ const char *value; ++ char *_id = pa_sprintf_malloc("=%s//%s", id, verb_name); ++ int err = snd_use_case_get(uc_mgr, _id, &value); ++ pa_xfree(_id); ++ if (err < 0) ++ return NULL; ++ pa_log_debug("Got %s for verb %s: %s", id, verb_name, value); ++ /* Use the cast here to allow free() call without casting for callers. ++ * The snd_use_case_get() returns mallocated string. ++ * See the Note: in use-case.h for snd_use_case_get(). ++ */ ++ return (char *)value; ++} ++ + static int ucm_device_exists(pa_idxset *idxset, pa_alsa_ucm_device *dev) { + pa_alsa_ucm_device *d; + uint32_t idx; +@@ -766,6 +786,8 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons + pa_alsa_ucm_device *d; + pa_alsa_ucm_modifier *mod; + pa_alsa_ucm_verb *verb; ++ char *value; ++ unsigned ui; + int err = 0; + + *p_verb = NULL; +@@ -780,6 +802,11 @@ int pa_alsa_ucm_get_verb(snd_use_case_mgr_t *uc_mgr, const char *verb_name, cons + pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(verb_name)); + pa_proplist_sets(verb->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(verb_desc)); + ++ value = ucm_verb_value(uc_mgr, verb_name, "Priority"); ++ if (value && !pa_atou(value, &ui)) ++ verb->priority = ui > 10000 ? 10000 : ui; ++ free(value); ++ + err = ucm_get_devices(verb, uc_mgr); + if (err < 0) + pa_log("No UCM devices for verb %s", verb_name); +@@ -1637,7 +1664,7 @@ static int ucm_create_profile( + pa_alsa_ucm_modifier *mod; + int i = 0; + const char *name, *sink, *source; +- char *verb_cmp, *c; ++ unsigned int priority; + + pa_assert(ps); + +@@ -1657,24 +1684,26 @@ static int ucm_create_profile( + p->supported = true; + pa_hashmap_put(ps->profiles, p->name, p); + +- /* TODO: get profile priority from ucm info or policy management */ +- c = verb_cmp = pa_xstrdup(verb_name); +- while (*c) { +- if (*c == '_') *c = ' '; +- c++; +- } ++ /* TODO: get profile priority from policy management */ ++ priority = verb->priority; + +- for (i = 0; verb_info[i].id; i++) { +- if (strcasecmp(verb_info[i].id, verb_cmp) == 0) { +- p->priority = verb_info[i].priority; +- break; ++ if (priority == 0) { ++ char *verb_cmp, *c; ++ c = verb_cmp = pa_xstrdup(verb_name); ++ while (*c) { ++ if (*c == '_') *c = ' '; ++ c++; ++ } ++ for (i = 0; verb_info[i].id; i++) { ++ if (strcasecmp(verb_info[i].id, verb_cmp) == 0) { ++ priority = verb_info[i].priority; ++ break; ++ } + } ++ pa_xfree(verb_cmp); + } + +- pa_xfree(verb_cmp); +- +- if (verb_info[i].id == NULL) +- p->priority = 1000; ++ p->priority = priority; + + PA_LLIST_FOREACH(dev, verb->devices) { + pa_alsa_jack *jack; +diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h +index 48fd9db3f79f..e7a795cedeb7 100644 +--- a/src/modules/alsa/alsa-ucm.h ++++ b/src/modules/alsa/alsa-ucm.h +@@ -241,6 +241,7 @@ struct pa_alsa_ucm_verb { + PA_LLIST_FIELDS(pa_alsa_ucm_verb); + + pa_proplist *proplist; ++ unsigned priority; + + PA_LLIST_HEAD(pa_alsa_ucm_device, devices); + PA_LLIST_HEAD(pa_alsa_ucm_modifier, modifiers); +-- +2.16.4 + diff --git a/pulseaudio.changes b/pulseaudio.changes index 33fe716..4cac70d 100644 --- a/pulseaudio.changes +++ b/pulseaudio.changes @@ -1,3 +1,41 @@ +------------------------------------------------------------------- +Wed Feb 12 22:01:05 CET 2020 - tiwai@suse.de + +- 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 + 0029-alsa-ucm-add-support-for-HDMI-ELD.patch + 0030-alsa-mixer-do-the-quick-card-number-lookup-to-save-m.patch + 0031-alsa-mixer-improve-check-for-the-empty-path-set-for-.patch + 0032-alsa-ucm-allow-to-set-profile-priority-from-UCM-valu.patch + ------------------------------------------------------------------- Mon Feb 3 07:36:31 UTC 2020 - Bjørn Lie diff --git a/pulseaudio.spec b/pulseaudio.spec index b0a8018..69dea03 100644 --- a/pulseaudio.spec +++ b/pulseaudio.spec @@ -50,6 +50,39 @@ Patch2: pulseaudio-wrong-memset.patch Patch5: qpaeq-shebang.patch # PATCH-FIX-OPENSUSE Workaround for old systemd on Leap 15.x Patch6: pulseaudio-old-systemd-workaround.patch +# PATCH-FIX-UPSTREAM +Patch1001: 0001-alsa-mixer-path-test-Hide-unused-functions-when-buil.patch +Patch1002: 0002-alsa-mixer-recognize-the-Speaker-Jack-control.patch +Patch1003: 0003-alsa-mixer-add-support-for-SteelSeries-Arctis-Pro-20.patch +Patch1004: 0004-alsa-mixer-Add-support-for-SteelSeries-Arctis-5-2019.patch +Patch1005: 0005-alsa-mixer-add-support-for-LucidSound-LS31-and-creat.patch +Patch1006: 0006-alsa-ucm-use-ucm2-name-for-the-direct-card-index-ope.patch +Patch1007: 0007-alsa-ucm-add-mixer-IDs-to-ucm_items.patch +Patch1008: 0008-alsa-mixer-handle-the-index-for-ALSA-mixer-element-i.patch +Patch1009: 0009-alsa-mixer-improve-alsa_id_decode-function.patch +Patch1010: 0010-alsa-ucm-Support-Playback-CaptureVolume.patch +Patch1011: 0011-alsa-ucm-Fix-volume-control-based-on-review.patch +Patch1012: 0012-alsa-ucm-use-the-correct-mixer-identifiers-as-first.patch +Patch1013: 0013-alsa-ucm-add-support-for-master-volume.patch +Patch1014: 0014-alsa-ucm-split-correctly-JackHWMute-device-names.patch +Patch1015: 0015-alsa-ucm-fix-parsing-for-JackControl.patch +Patch1016: 0016-alsa-ucm-add-comments-to-ucm_get_mixer_id.patch +Patch1017: 0017-alsa-ucm-validate-access-to-PA_DEVICE_PORT_DATA.patch +Patch1018: 0018-alsa-Skip-resume-PCM-if-hardware-doesn-t-support-it.patch +Patch1019: 0019-alsa-ucm-parse-correctly-the-device-values.patch +Patch1020: 0020-alsa-ucm-do-not-try-to-use-UCM-device-name-as-jack-n.patch +Patch1021: 0021-alsa-util-do-not-try-to-guess-the-mixer-name-from-th.patch +Patch1022: 0022-alsa-ucm-add-control-and-mixer-device-items.patch +Patch1023: 0023-alsa-ucm-get-the-mixer-names-from-ucm-don-t-guess.patch +Patch1024: 0024-alsa-ucm-use-the-proper-mixer-name-for-ucm-pcm-sink-.patch +Patch1025: 0025-alsa-mixer-handle-interface-type-CARD-PCM-for-mixer-.patch +Patch1026: 0026-alsa-mixer-Add-the-ability-to-pass-the-intended-role.patch +Patch1027: 0027-alsa-mixer-Set-the-intended-role-of-Steelseries-Arct.patch +Patch1028: 0028-alsa-rewrite-mixer-open-close-cache-mixer-accesses-i.patch +Patch1029: 0029-alsa-ucm-add-support-for-HDMI-ELD.patch +Patch1030: 0030-alsa-mixer-do-the-quick-card-number-lookup-to-save-m.patch +Patch1031: 0031-alsa-mixer-improve-check-for-the-empty-path-set-for-.patch +Patch1032: 0032-alsa-ucm-allow-to-set-profile-priority-from-UCM-valu.patch BuildRequires: alsa-devel >= 1.0.19 BuildRequires: bluez-devel >= 5 BuildRequires: doxygen @@ -338,6 +371,38 @@ Optional dependency offering zsh completion for various PulseAudio utilities %prep %setup -q -T -b0 %patch0 +%patch1001 -p1 +%patch1002 -p1 +%patch1003 -p1 +%patch1004 -p1 +%patch1005 -p1 +%patch1006 -p1 +%patch1007 -p1 +%patch1008 -p1 +%patch1009 -p1 +%patch1010 -p1 +%patch1011 -p1 +%patch1012 -p1 +%patch1013 -p1 +%patch1014 -p1 +%patch1015 -p1 +%patch1016 -p1 +%patch1017 -p1 +%patch1018 -p1 +%patch1019 -p1 +%patch1020 -p1 +%patch1021 -p1 +%patch1022 -p1 +%patch1023 -p1 +%patch1024 -p1 +%patch1025 -p1 +%patch1026 -p1 +%patch1027 -p1 +%patch1028 -p1 +%patch1029 -p1 +%patch1030 -p1 +%patch1031 -p1 +%patch1032 -p1 %patch1 -p1 %patch2 %patch5