Extend Local Audio Out provider with PulseAudio support#3724
Extend Local Audio Out provider with PulseAudio support#3724iVolt1 wants to merge 125 commits intomusic-assistant:devfrom
Conversation
The fix wraps stream.close() in asyncio.shield() so cancellation of the parent task can't interrupt the executor call. The suppress(Exception) wrapper is still there as a safety net.
In HA's hassio_audio environment module-suspend-on-idle isn't loaded so DACs stay powered once played to regardless of stream state. Maybe your pipewire-pulse loads the module. The asyncio.shield fix is included in my PR so feel free to test it. |
|
I will verify tomorrow. With me it was partially the local host config of the usb .... but when corked processes stayed open it never shut down too, only with a restart of pipewire (after playing ended) or a shutdown of MA it went down after x seconds. Thanks for the effort so far, as said back tomorrow :) |
The MA hassio_audio pulse audio plugin does not include suspend_on_idle so that is likely why devices are not going to Idle or Suspend. It may depend on your linux distribution whether that is included or not. |
|
@marcelveldt, @MarvinSchenkel Is there anything else besides the Security Check holding back this PR from being merged to dev? I would really like to see it move forward since it is how I play audio here every day and it seems really solid. Also local audio is a much requested feature for whole house audio using line outputs with multiple DACS or 5.1/7.1 stereo pairs so I think many users will be happy to see this implemented. Thanks, iVolt1. |
Nope...it is the processes that stay open, see my screenshot above, as soon as I reset pw (i.e. processes gone) or close MA container (ditto), USB goes down in seconds and after that also my dac suspends. Pipe/pulse is not proprely closing processes so some code needed to push to do so . Alsa devices no problem |
Ditto, I would like to see this go live too so next steps can be planned. |
There was a problem hiding this comment.
Pull request overview
Extends the built-in Local Audio Out player provider to add Linux PulseAudio sink discovery/output, while updating Sendspin bridge format handling so bridge players can advertise per-device PCM formats.
Changes:
- Add Linux PulseAudio sink enumeration/playback plumbing in
local_audio, including a newpa_simple.pyhelper and bridge/player changes. - Update Sendspin bridge format selection so bridge players can expose sink-specific sample rate/bit depth.
- Add Linux-only
pulsectldependency and refresh Local Audio docs/metadata.
Note: the supplied diff did not include the Dockerfile.base or bundled binary/library changes mentioned in the PR description, so those parts could not be reviewed here.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| requirements_all.txt | Adds Linux-only pulsectl to aggregate requirements. |
| music_assistant/providers/sendspin/playback.py | Uses bridge-declared output format for member format selection. |
| music_assistant/providers/sendspin/bridge_role.py | Parameterizes bridge audio requirements and exposes the configured format. |
| music_assistant/providers/local_audio/sendspin_bridge.py | Reworks the bridge for Linux PulseAudio output and per-device format advertising. |
| music_assistant/providers/local_audio/README.md | Updates provider documentation for Linux PulseAudio and macOS behavior. |
| music_assistant/providers/local_audio/provider.py | Adds a Linux libpulse-simple availability check at init. |
| music_assistant/providers/local_audio/player.py | Changes player volume/mute persistence and PulseAudio sink volume handling. |
| music_assistant/providers/local_audio/pa_simple.py | Adds ctypes-based PulseAudio streaming and sink enumeration helpers. |
| music_assistant/providers/local_audio/manifest.json | Adds Linux-only pulsectl requirement and updates codeowners. |
| music_assistant/providers/local_audio/constants.py | Updates local audio defaults, cache constants, and UUID namespace. |
| music_assistant/providers/local_audio/init.py | Removes provider config entries and keeps basic provider setup exports. |
Bit depth parser replaced with explicit format lookup dict — handles s24-32le correctly as 32-bit instead of producing 2432 pactl discovery now prefers system binary first, bundled binary is the fallback
restore_state() now called before register_or_update() so correct volume/mute values are in the initial PLAYER_ADDED event
display_name from device.get("description", device_name) used for MA player display, while stable PA sink name is used for UUID generation
removed bin/pactl and bin/lib/ from the file structure table, updated pactl dependency to say "requires pulseaudio-utils", updated the notes bullet to match.

Extend Local Audio Out provider with PulseAudio support
Summary
Extends the existing
local_audioplayer provider to support PulseAudio on Linux, while preserving the existing PortAudio/CoreAudio path on macOS. Each PulseAudio sink is registered as an external Sendspin bridge client, enabling synchronized multi-room playback alongside existing Sendspin players. Audio is written directly to PulseAudio via a minimallibpulse-simplectypes wrapper. The separatepulse_audioprovider is superseded by this change and removed.Motivation
The existing
local_audioprovider uses PortAudio/sounddevice which cannot enumerate PulseAudio virtual sinks such asmodule-remap-sinkstereo pairs. On Linux, PulseAudio sits on top of ALSA and owns the hardware devices, so PortAudio can only see physical ALSA devices — not virtual sinks. This change targets PulseAudio directly on Linux viapactlandlibpulse-simple, correctly discovering and playing to all sinks including remap sinks, combined sinks, S/PDIF, and HDMI outputs, while preserving full macOS functionality.Changes
Modified —
music_assistant/providers/local_audio/Dependencies
pulseaudio-utilsinDockerfile.base, with bundled binary as fallbackdepends_on: sendspin)Expanding Outputs with Stereo Pair Remap Sinks
Multi-channel sound cards (5.1, 7.1 surround) expose a single multi-channel PulseAudio sink by default. To use each channel pair as an independent MA player, PulseAudio
module-remap-sinkcan split a multi-channel sink into individual stereo sinks — one per channel pair (front, rear, side, center/LFE). The Local Audio Out provider discovers and registers all remap sinks automatically alongside physical sinks, so no additional configuration is needed in MA once the remap sinks exist.For Home Assistant OS users, the companion addon Pulse Audio Stereo Pairs automates this setup. It runs as a lightweight HA addon that creates the remap sinks on startup and reacts to audio device hot-plug and unplug events, removing the need to configure remap sinks manually via pactl. Once both the addon and this provider are running, each channel pair of every multi-channel card appears as a separate player in Music Assistant.
# Extend Local Audio Out provider with PulseAudio support