Ziqi (Katrina) Ding

Back

The Problem#

Home Assistant can generate audio (TTS, alerts), but getting it to actually come out of a USB speaker on a Linux box can be weirdly painful—mostly because audio services love fighting over devices and permissions.

In this post, I’ll show how I routed HA audio through a USB speaker using MPD, how to find the right output device, and how to fix the classic “device busy” / “PipeWire connection refused” traps so sound finally plays where it should.

The Solution#

HA talks to MPD (port 6600), and MPD plays through PipeWire’s PulseAudio-compatible socket. Two things go wrong:

  1. MPD can’t find PipeWire. MPD runs as a system service, but PipeWire runs inside your user session. A system service has no XDG_RUNTIME_DIR / PULSE_SERVER, so it doesn’t know where your PipeWire socket is → Connection refused. Fix: tell it where the socket is.
  2. MPD plays to the wrong device. If you have more than one USB audio device, PipeWire’s default sink may not be your speaker. Fix: pin the speaker’s sink name in mpd.conf.

How to Do It#

1. Find your speaker#

katrina@homeserver:~$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Device [USB Audio Device], device 0: USB Audio [USB Audio]     # <== This one
  Subdevices: 1/1
card 1: HID [USB Audio and HID], device 0: USB Audio [USB Audio]       # Another USB device
  Subdevices: 1/1
card 2: PCH [HDA Intel PCH], device 3: HDMI 0 [HDMI 0]                 # HDMI (note: device 3, not 0)
  ...
sh
katrina@homeserver:~$ cat /proc/asound/cards
 0 [Device         ]: USB-Audio - USB Audio Device    # <== This one
                      GeneralPlus USB Audio Device at usb-0000:00:14.0-3, full speed
 1 [HID            ]: USB-Audio - USB Audio and HID
                      CSCTEK USB Audio and HID at usb-0000:00:14.0-6, full speed
sh

What we actually need is the PipeWire sink name, which is stable:

katrina@homeserver:~$ pactl list short sinks
47   alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo             PipeWire  s16le 2ch 48000Hz  SUSPENDED
49   alsa_output.usb-CSCTEK_USB_Audio_and_HID_A34004801402-00.analog-stereo    PipeWire  s16le 2ch 48000Hz  SUSPENDED
sh

SUSPENDED just means idle. Copy the name of your speaker’s sink (the alsa_output.usb-... string). Not sure which one it is? Play a tone on one sink at a time and listen:

paplay --device=alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo /usr/share/sounds/alsa/Front_Center.wav
sh

paplay needs a WAV file. /usr/share/sounds/alsa/Front_Center.wav ships with alsa-utils. If you don’t have it, any short WAV works.

2. Ensure MPD is installed and running#

katrina@homeserver:~$ systemctl status mpd
● mpd.service - Music Player Daemon
     Loaded: loaded (/lib/systemd/system/mpd.service; enabled; preset: enabled)
     Active: active (running) since ...
sh

If it isn’t running:

sudo systemctl enable --now mpd
sh

3. Configure MPD#

Open the config: sudo nano /etc/mpd.conf

# Listen on all interfaces so HA can reach it
bind_to_address                 "0.0.0.0"

# Port HA connects to
port                            "6600"

# Output through PipeWire (via its PulseAudio-compatible socket)
audio_output {
    type        "pulse"
    name        "PipeWire Output"
    sink        "alsa_output.usb-GeneralPlus_USB_Audio_Device-00.analog-stereo"   # <== your sink name from step 1
}
sh

Also check the user line near the top of the file:

grep -n '^\s*#\?\s*user' /etc/mpd.conf
sh
  • #user "mpd" (commented out) → the system service runs as root. This is what the fix in step 4 was tested with.
  • user "mpd" (active) → runs as the mpd user, which cannot enter /run/user/1000/ (that directory is drwx------, owned by you), so step 4 won’t work. Either comment the line out, or use the user-service alternative below.

Restart and check:

sudo systemctl restart mpd
systemctl status mpd
ss -tuln | grep 6600     # port should be listening
sh

4. Fix “Connection refused” (the main fix)#

Run a quick test first:

mpc clear
mpc add https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3
mpc play
sleep 3
mpc status
sh

If you see this, you’re in the trap:

[paused]  #1/1   0:00/6:13 (0%)
ERROR: Failed to enable output "PipeWire Output" (pulse); pa_context_connect() has failed: Connection refused
sh

pactl list short sinks works for you because your shell knows where PipeWire lives. MPD, as a system service, does not. Confirm the cause:

katrina@homeserver:~$ ps -o user,pid,cmd -C mpd
USER         PID CMD
root      218263 /usr/bin/mpd --systemd     # system service, no PULSE_SERVER in its environment
katrina@homeserver:~$ ls -ld /run/user/1000/pulse
drwx------ 2 katrina katrina 80 ... /run/user/1000/pulse   # only katrina can enter
sh

Fix: give the service a systemd drop-in that points at your socket. Write it with a heredoc (safer to paste than a long one-liner):

sudo mkdir -p /etc/systemd/system/mpd.service.d
sudo tee /etc/systemd/system/mpd.service.d/pulse.conf <<'EOF'
[Unit]
After=user@1000.service

[Service]
Environment=PULSE_SERVER=unix:/run/user/1000/pulse/native
EOF
sh
  • PULSE_SERVER=... tells MPD where PipeWire’s socket is. 1000 is katrina’s UID (check with id -u).
  • After=user@1000.service makes MPD start after your user session (and PipeWire) at boot. This relies on lingering being enabled: loginctl show-user katrina | grep Linger should say Linger=yes.

Apply and verify:

sudo systemctl daemon-reload
sudo systemctl restart mpd
systemctl cat mpd | grep PULSE      # should print the Environment= line
sh

5. Test#

mpc clear
mpc add https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3
mpc play
sleep 3
mpc status
sh

Expected: [playing] with the time advancing and no pa_context_connect error, and sound from the speaker. Then trigger a TTS / media play from Home Assistant.

Alternative: run MPD as a user service#

Cleaner than a root system service, because MPD then lives inside your session and gets XDG_RUNTIME_DIR for free, but I haven’t tested this on my machine yet.

# 1. Stop and disable the system service AND its socket
sudo systemctl disable --now mpd mpd.socket

# 2. Create a user config (copy the system one, then edit paths)
mkdir -p ~/.config/mpd
cp /etc/mpd.conf ~/.config/mpd/mpd.conf
#    - remove/comment the `user` line
#    - point music_directory, playlist_directory, db_file, state_file at paths under your home
#    - keep bind_to_address, port and the audio_output block

# 3. Enable the user unit shipped by Debian
systemctl --user enable --now mpd
sh

Lingering must be on so it starts at boot without you logging in (sudo loginctl enable-linger katrina). Note that systemctl disable mpd alone isn’t enough: mpd.socket will re-launch the system service on demand, so disable the socket too.

Troubleshooting#

SymptomCauseFix
pa_context_connect() ... Connection refusedMPD (system service) can’t find PipeWire’s socketStep 4 drop-in
Sound plays, but from the wrong devicePipeWire default sink isn’t your speakerAdd sink "..." to the audio_output block; or wpctl set-default <id> (wpctl status shows ids)
speaker-test -D hw:N,0: -2 No such file or directoryN isn’t a USB card. HDMI devices are numbered 3, 7, 8, 9, not 0Check aplay -l for the real card/device numbers
speaker-test: Channels count (1) not availableUSB speaker only supports stereoUse -c 2
speaker-test: Device or resource busyPipeWire is holding the ALSA deviceTest through PipeWire instead: speaker-test -D pulse -c 2, or paplay --device=<sink> file.wav
mpc status: CURL failed: ... 404 on an /api/tts_proxy/...mp3 URLHA’s TTS URLs are short-lived. MPD got a stale oneNot an audio problem. Trigger a fresh TTS from HA
mpc volume shows n/aThe pulse output has no hardware mixerSet volume in PipeWire: wpctl set-volume <sink-id> 0.5
Works, then breaks after rebootMPD started before your user session/PipeWireConfirm the drop-in has After=user@1000.service and lingering is on; sudo systemctl restart mpd

Summary#

Home Assistant audio plays through a USB speaker by routing output through MPD to PipeWire. The two fixes that matter: point the MPD system service at your user’s PipeWire socket with PULSE_SERVER (a systemd drop-in), and pin the speaker’s sink name in mpd.conf so it doesn’t depend on PipeWire’s default device or on USB card numbering.

Use USB Speaker as Audio Output
https://katrina-ziqi-ding.com/blog/use-usb-speaker-as-audio-output
Author Ziqi (Katrina) Ding
Published at 22-08-2025