Compare commits

...

6 commits

Author SHA1 Message Date
Daniel Nouri
51fc15bb90 Merge branch 'fix-iec958-unmute-on-activation' into 'master'
Fix HDMI/DisplayPort Audio by Enabling IEC958 Switches

Closes #3261

See merge request pipewire/pipewire!2556
2025-10-24 10:01:22 +00:00
Daniel Nouri
1cd056aa1e revert: Remove mixer path IEC958 fix
Reverts alsa-mixer.c changes from 32a3ffc74. The comprehensive ACP
layer fix (bdb82be4e) handles all scenarios including pro-audio
profiles that bypass mixer paths, making this approach redundant.
2025-10-07 17:21:05 +02:00
Daniel Nouri
b7a2fcf27e alsa: Enable IEC958 switches on device activation
IEC958 (S/PDIF, HDMI, DisplayPort) switches default to muted in ALSA
drivers, causing no audio output on digital devices.

While UCM configurations and mixer paths can handle IEC958 unmuting,
several scenarios lack coverage:
- Pro-audio profiles (bypass UCM and mixer paths by design)
- Devices without UCM configurations
- Devices with incomplete mixer path definitions
- Cards with multiple HDMI/DP outputs (indexed switches)

This ensures IEC958 switches are enabled during device activation and
port changes. The implementation uses the device mixer when available,
falls back to the card mixer for pro-audio profiles, and enables all
IEC958 switches regardless of index.

Safe for all configurations: the operation is idempotent and provides
defense-in-depth even when UCM or mixer paths handle it correctly.

Tested on AMD Rembrandt GPU with 3 HDMI outputs in pro-audio mode.
2025-10-07 17:21:05 +02:00
Daniel Nouri
87d34335f3 refactor: Remove test-alsa-path-select tool
Not run by meson test and requires specific ALSA hardware.
Fix verification already completed manually.
2025-10-07 17:21:05 +02:00
Daniel Nouri
5c3def51a4 refactor: Remove redundant ret assignments in test-alsa-path-select
The variable ret is initialized to 2 at function entry. Error paths that
want this default value can simply goto cleanup without reassigning it.
2025-10-07 17:21:05 +02:00
Daniel Nouri
571ff40704 alsa: Fix IEC958 digital output not unmuted on path activation
When selecting an HDMI/DisplayPort (IEC958) output path, the hardware
mute switch remains in kernel default state (muted), causing no audio
output despite correct software routing.

Root cause: pa_alsa_path_select() only sets mute switches when
mute_during_activation is enabled. No mixer paths enable this setting,
making the switch configuration code unreachable for IEC958 paths.

Solution: Always set mute switches to match device mute status after
path activation, regardless of mute_during_activation setting.

Testing: Added test-alsa-path-select tool to verify the fix.
- Loads mixer path and calls pa_alsa_path_select()
- Verifies switch states match expected values
- Tested on AMD Radeon HDMI and Realtek ALC257 analog

Manual verification:
- Before: IEC958 switch OFF, no audio
- After: IEC958 switch set correctly, audio works

This bug was inherited from PulseAudio's ALSA mixer path code where
HDMI path configurations lack IEC958 unmute sections.

Fixes: https://gitlab.freedesktop.org/pipewire/pipewire/-/issues/3261
See-Also: https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/issues/562
See-Also: 33be660e4b
See-Also: https://bugs.launchpad.net/hundredpapercuts/+bug/681996
2025-10-07 17:21:05 +02:00

View file

@ -1687,6 +1687,59 @@ static int device_disable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_devic
return 0;
}
/* Synchronize IEC958 digital output/input switch states.
*
* IEC958 switches default to muted in ALSA drivers. Cards with multiple
* HDMI/DP outputs have indexed switches (IEC958,0 IEC958,1 etc). We enable
* all switches since we cannot reliably map device numbers to indices.
*/
static void sync_iec958_controls(pa_alsa_device *d)
{
snd_mixer_t *mixer_handle;
snd_mixer_elem_t *elem;
pa_card *impl;
int r;
mixer_handle = d->mixer_handle;
/* Pro-audio profiles don't have per-device mixers, use card mixer */
if (!mixer_handle) {
impl = d->card;
if (!impl || impl->card.index == ACP_INVALID_INDEX)
return;
mixer_handle = pa_alsa_open_mixer(impl->ucm.mixers, impl->card.index, true);
if (!mixer_handle)
return;
}
/* Enable all IEC958 switches */
for (elem = snd_mixer_first_elem(mixer_handle); elem;
elem = snd_mixer_elem_next(elem)) {
if (snd_mixer_elem_get_type(elem) != SND_MIXER_ELEM_SIMPLE)
continue;
const char *name = snd_mixer_selem_get_name(elem);
if (!name || !pa_startswith(name, "IEC958"))
continue;
if (snd_mixer_selem_has_playback_switch(elem)) {
r = snd_mixer_selem_set_playback_switch_all(elem, 1);
if (r < 0)
pa_log_warn("Failed to enable IEC958 playback switch: %s",
pa_alsa_strerror(r));
}
if (snd_mixer_selem_has_capture_switch(elem)) {
r = snd_mixer_selem_set_capture_switch_all(elem, 1);
if (r < 0)
pa_log_warn("Failed to enable IEC958 capture switch: %s",
pa_alsa_strerror(r));
}
}
}
static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device *dev)
{
const char *mod_name;
@ -1778,6 +1831,9 @@ static int device_enable(pa_card *impl, pa_alsa_mapping *mapping, pa_alsa_device
break;
}
/* Enable IEC958 switches for digital outputs */
sync_iec958_controls(dev);
return 0;
}
@ -2278,6 +2334,7 @@ int acp_device_set_port(struct acp_device *dev, uint32_t port_index, uint32_t fl
pa_sink_suspend(s, false, PA_SUSPEND_UNAVAILABLE);
#endif
}
sync_iec958_controls(d);
if (impl->events && impl->events->port_changed)
impl->events->port_changed(impl->user_data,
old ? old->port.index : 0, p->port.index);