Compare commits

...

13 Commits

Author SHA1 Message Date
Daniel Nouri
1840f7c177 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
2026-01-25 17:41:03 +00:00
Mengshi Wu
bc53b6b343 bluez5: Remove unused bt_features variable in sco_create_socket. 2026-01-25 17:26:15 +00:00
Mengshi Wu
332e35039d doc: Fix bluez5.hw-offload-datapath property type in documentation 2026-01-25 17:26:15 +00:00
Mengshi Wu
254620676f bluez5: Use named constants for Bluetooth codec IDs
Replace magic numbers (0x02, 0x05) with named constants BT_CODEC_CVSD
and BT_CODEC_MSBC for better code readability. Also remove redundant
zero initialization of num_caps field since the buffer is already
memset to zero.
2026-01-25 17:26:15 +00:00
Mengshi Wu
78f16bc04b bluez5: Remove hw-offload feature flag check and associated quirks
The sco_offload_btcodec() function now returns void and only skips
offload setup when using the default datapath, simplifying the logic
and removing the need for explicit feature flag checks.
2026-01-25 17:26:15 +00:00
Mengshi Wu
2b5d21da5b bluez5: simplify SCO datapath parsing with spa_atou32 2026-01-25 17:26:15 +00:00
Mengshi Wu
2d6a7d2186 bluez5: fix format string in sco_offload_btcodec log message 2026-01-25 17:26:15 +00:00
Mengshi Wu
db7c74a042 bluez5/backend-native: Add HFP/HSP hardware offload datapath configuration
Add support for configuring the SCO hardware offload data path for
HFP/HSP profiles using the Bluetooth SIG-specified procedure. This
enables vendor-specific SCO offload integrations.

Changes:
- Add `bluez5.hw-offload-datapath` configuration property (default: 0)
- Implement `sco_offload_btcodec()` to set BT_CODEC socket option
- Add `SPA_BT_FEATURE_HW_OFFLOAD` quirk feature flag
- Apply offload configuration when creating SCO sockets if quirk enabled
- Document new property in pipewire-props.7.md

The datapath ID is configurable via device parameters and only applied
when the hardware offload feature flag is set in quirks, allowing
platform-specific SCO offload implementations.
2026-01-25 17:26:15 +00:00
Daniel Nouri
9fa0de16e9 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-31 08:22:44 +00:00
Daniel Nouri
f030e1dc03 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-31 08:22:44 +00:00
Daniel Nouri
88f8eda1e9 refactor: Remove test-alsa-path-select tool
Not run by meson test and requires specific ALSA hardware.
Fix verification already completed manually.
2025-10-31 08:22:44 +00:00
Daniel Nouri
9ad6219db3 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-31 08:22:44 +00:00
Daniel Nouri
e29d851d2c 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-31 08:22:44 +00:00
4 changed files with 110 additions and 1 deletions

View File

@ -1171,6 +1171,15 @@ in a platform-specific way. See `tests/examples/bt-pinephone.lua` in WirePlumber
Do not enable this setting if you don't know what all this means, as it won't work.
\endparblock
@PAR@ monitor-prop bluez5.hw-offload-datapath # integer
\parblock
HFP/HSP hardware offload data path ID (default: 0).
This feature configures the SCO hardwareoffload data path for HFP/HSP using the Bluetooth
SIGspecified procedure. It is intended for advanced setups and vendor integrations. Do not
edit this unless required; incorrect values can disable SCO offload.
\endparblock
@PAR@ monitor-prop bluez5.a2dp.opus.pro.channels = 3 # integer
PipeWire Opus Pro audio profile channel count.

View File

@ -1692,6 +1692,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;
@ -1783,6 +1836,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;
}
@ -2283,6 +2339,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);

View File

@ -63,6 +63,9 @@ SPA_LOG_TOPIC_DEFINE_STATIC(log_topic, "spa.bluez5.native");
#define RFCOMM_MESSAGE_MAX_LENGTH 256
#define BT_CODEC_CVSD 0x02
#define BT_CODEC_MSBC 0x05
enum {
HFP_AG_INITIAL_CODEC_SETUP_NONE = 0,
HFP_AG_INITIAL_CODEC_SETUP_SEND,
@ -112,6 +115,7 @@ struct impl {
int hfp_default_speaker_volume;
struct spa_source sco;
unsigned int hfphsp_sco_datapath;
const struct spa_bt_quirks *quirks;
@ -297,6 +301,33 @@ static const struct media_codec *codec_list_best(struct impl *backend, struct sp
return NULL;
}
static void sco_offload_btcodec(struct impl *backend, int sock, bool msbc)
{
int err;
char buffer[255];
struct bt_codecs *codecs;
if (backend->hfphsp_sco_datapath == HFP_SCO_DEFAULT_DATAPATH)
return;
spa_log_info(backend->log, "sock(%d) msbc(%d)", sock, msbc);
memset(buffer, 0, sizeof(buffer));
codecs = (void *)buffer;
if (msbc)
codecs->codecs[0].id = BT_CODEC_MSBC;
else
codecs->codecs[0].id = BT_CODEC_CVSD;
codecs->num_codecs = 1;
codecs->codecs[0].data_path_id = backend->hfphsp_sco_datapath;
err = setsockopt(sock, SOL_BLUETOOTH, BT_CODEC, codecs, sizeof(buffer));
if (err < 0)
spa_log_error(backend->log, "ERROR: %s (%d)", strerror(errno), errno);
else
spa_log_info(backend->log, "set offload codec succeeded");
}
static DBusHandlerResult profile_release(DBusConnection *conn, DBusMessage *m, void *userdata)
{
if (!reply_with_error(conn, m, BLUEZ_PROFILE_INTERFACE ".Error.NotImplemented", "Method not implemented"))
@ -2595,6 +2626,8 @@ static int sco_create_socket(struct impl *backend, struct spa_bt_adapter *adapte
}
}
sco_offload_btcodec(backend, sock, transparent);
return spa_steal_fd(sock);
}
@ -4101,6 +4134,14 @@ static void parse_hfp_default_volumes(struct impl *backend, const struct spa_dic
backend->hfp_default_speaker_volume = SPA_BT_VOLUME_HS_MAX;
}
static void parse_sco_datapath(struct impl *backend, const struct spa_dict *info)
{
backend->hfphsp_sco_datapath = HFP_SCO_DEFAULT_DATAPATH;
spa_atou32(spa_dict_lookup(info, "bluez5.hw-offload-datapath"),
&backend->hfphsp_sco_datapath, 10);
}
static const struct spa_bt_backend_implementation backend_impl = {
SPA_VERSION_BT_BACKEND_IMPLEMENTATION,
.free = backend_native_free,
@ -4163,6 +4204,7 @@ struct spa_bt_backend *backend_native_new(struct spa_bt_monitor *monitor,
parse_hfp_disable_nrec(backend, info);
parse_hfp_default_volumes(backend, info);
parse_hfp_pts(backend, info);
parse_sco_datapath(backend, info);
#ifdef HAVE_BLUEZ_5_BACKEND_HSP_NATIVE
if (!dbus_connection_register_object_path(backend->conn,

View File

@ -135,7 +135,8 @@ extern "C" {
#define PROFILE_HFP_AG "/Profile/HFPAG"
#define PROFILE_HFP_HF "/Profile/HFPHF"
#define HSP_HS_DEFAULT_CHANNEL 3
#define HSP_HS_DEFAULT_CHANNEL 3
#define HFP_SCO_DEFAULT_DATAPATH 0
#define SOURCE_ID_BLUETOOTH 0x1 /* Bluetooth SIG */
#define SOURCE_ID_USB 0x2 /* USB Implementer's Forum */