Alright, after a lot of trial and error, I’ve figured it out as well as resolved a few other issues that cropped up after switching to PulseAudio. Enjoy!
Here’s why PulseAudio was chosen, as described by ChatGPT when we were troubleshooting:
Short version: ALSA can’t reliably play to two different hardware devices at once.
- Single-device design: ALSA’s default targets one card.
dmix mixes apps, but only within one card.
- Multi-device plugins are fragile:
tee/multi duplicate bytes, but don’t do per-device resampling, clock drift correction, or latency alignment. One sink stalls/drifts → late audio, xruns, or silence.
- Clock mismatch: HDMI and the HiFiBerry DAC run off independent clocks and often use different sample rates/buffer sizes. ALSA won’t keep them in sync.
- Control/UX: ES’s slider tends to control an ALSA mixer on one card, not two simultaneously; with ALSA-only dual-output hacks, volume control breaks or affects only one device.
- Boot timing: Early services (e.g., splash) race devices; ALSA-only duplication is more timing-sensitive than a managed audio server.
PulseAudio (and PipeWire) solve this by creating a virtual sink with:
- Per-sink resampling
- Drift/latency handling
- Unified default/volume control that ES can target
- More robust startup behavior across services and sessions
Note the twitch system mentioned in the guide is for an EmulationStation system that will play Twitch streams and has nothing to do with the original goal of getting simultaneous HDMI audio out to make the Picade Twitch-streamable (which works now). If you’re interested in getting this set up, first follow this: Verifying...
And here’s the final step-by-step document I put together based on all the findings:
🚀 Step‑by‑step setup to get both Picade X-HAT (HiFiBerry DAC) audio and HDMI audio working simultaneously
1) 🔊 Enable audio devices
- Edit
'/boot/config.txt' and ensure:
dtparam=audio=on
dtoverlay=picade
2) 🔁 Route ALSA to PulseAudio
pcm.!default { type pulse }
ctl.!default { type pulse }
3) 🧹 Clean up old PulseAudio files (if needed)
rm -rf /run/user/$(id -u)/pulse
rm -rf ~/.config/pulse/*
4) 🧩 Create the combined sink script
- File:
'/home/pi/Picade Audio Scripts/set-combined-audio-sink.sh'
#!/bin/bash
# Give PulseAudio time to start and detect sinks
#sleep 2
# Remove any previous combined sink (ignore errors)
pactl unload-module module-combine-sink 2>/dev/null
# Create a new combined sink for HDMI and Picade X-HAT
pactl load-module module-combine-sink sink_name=combined \
slaves=alsa_output.platform-bcm2835_audio.digital-stereo,alsa_output.platform-soc_sound.stereo-fallback
# Set the combined sink as default
pactl set-default-sink combined
# Set combined sink volume to 50%
# pactl set-sink-volume combined 50%
chmod +x "/home/pi/Picade Audio Scripts/set-combined-audio-sink.sh"
5) 🖥️ Create the HDMI audio sink script
- File:
'/home/pi/Picade Audio Scripts/set-hdmi-audio-sink.sh'
#!/bin/bash
# Give PulseAudio time to start and detect sinks
#sleep 2
pactl set-default-sink alsa_output.platform-bcm2835_audio.digital-stereo
chmod +x "/home/pi/Picade Audio Scripts/set-hdmi-audio-sink.sh"
6) 🎧 Create the Picade audio sink script
- File:
'/home/pi/Picade Audio Scripts/set-picade-audio-sink.sh'
#!/bin/bash
# Give PulseAudio time to start and detect sinks
#sleep 2
pactl set-default-sink alsa_output.platform-soc_sound.stereo-fallback
chmod +x "/home/pi/Picade Audio Scripts/set-picade-audio-sink.sh"
7) 🛠️ Add the systemd user service
- File:
'/home/pi/.config/systemd/user/combined-sink.service'
[Unit]
Description=Set PulseAudio Combined Sink
After=pulseaudio.service
Wants=pulseaudio.service
[Service]
Type=oneshot
ExecStart="/home/pi/Picade Audio Scripts/set-combined-audio-sink.sh"
RemainAfterExit=yes
[Install]
WantedBy=default.target
8) ✅ Enable the service and user lingering
systemctl --user daemon-reload
systemctl --user enable combined-sink.service
systemctl --user start combined-sink.service
sudo loginctl enable-linger pi
9) 🔄 Reboot and test
sudo reboot
After boot:
pactl info | grep "Default Sink" # Expect: combined
pactl list short sinks | grep combined # combined present
paplay /usr/share/sounds/alsa/Front_Center.wav
10) 🎛️ Configure EmulationStation volume slider
- EmulationStation → Main Menu → Sound Settings:
- Audio Card: Default
- Audio Device: Master
- Adjust the Volume Slider. Back out of the menu to save, go back in to verify it saved.
- Confirm audio on a game with a scraped video
11) 🖼️ Make the splash use PulseAudio combined sink (if using a video with audio)
- File:
'/opt/retropie/supplementary/splashscreen/asplashscreen.sh'
- Change
CMD to route VLC via PulseAudio:
CMD="env PULSE_SERVER=/run/user/1000/pulse/native PULSE_SINK=combined vlc --intf dummy --quiet --no-video-title-show --play-and-exit --aout=pulse"
- In
do_start(), add a short delay at the top:
# Wait a moment to ensure PulseAudio and all sinks are loaded
sleep 2
- Before launching VLC, add a readiness wait:
# Wait for PulseAudio 'combined' sink to be ready (max 10s)
for i in 1 2 3 4 5 6 7 8 9 10; do
if PULSE_SERVER=/run/user/1000/pulse/native pactl list short sinks 2>/dev/null | grep -q 'combined'; then
break
fi
sleep 1
done
sleep 0.3
12) 📺 Twitch: route MPV to the combined sink and remove sudo (if configured with a ‘twitch’ system)
- File:
'/home/pi/.twitch/twitch.sh' (ensure MPV target):
/home/pi/.venv/bin/streamlink $twitchUrl "720p60,720p,480p,best" --twitch-low-latency --twitch-disable-ads --player=mpv --player-args="--audio-device=pulse/combined"
- File:
'/home/pi/.emulationstation/es_systems.cfg' (twitch system):
<command>bash /home/pi/.twitch/twitch.sh %ROM%</command>
- Launch the Twitch system in EmulationStation and start a stream; verify audio plays from both HDMI and Picade X‑HAT outputs. 🎧🖥️
13) 🔎 Reboot and test (final verification)
sudo reboot
- On boot, if a splash screen is enabled, listen for audio to confirm VLC is routed via the
combined sink. If silent, revisit step 11 timing.
- In EmulationStation, open Main Menu → Sound Settings and verify the volume level persisted across reboot
14) 🧯 Troubleshooting (if needed)
- If PulseAudio won’t start after changes: repeat step 3
- If splash audio is late, increase the
sleep in asplashscreen.sh to 3–5s