Notes / chrome-remote-desktoppipewireaudiodbusarch
No audio in Chrome Remote Desktop session on PipeWire: "ALSA: Couldn't open audio device: Host is down"
In a Chrome Remote Desktop X session on PipeWire, apps were silent or failed with "Host is down". CRD puts PULSE_RUNTIME_PATH, PULSE_SINK and PIPEWIRE_REMOTE into the session, pointing at its own PipeWire, which had not started. Unset them before dbus-launch, bridge system PipeWire to CRD's FIFO, check sink volume.
Symptoms #
Connected to a Linux desktop with Chrome Remote Desktop (CRD), no sound reached the CRD client. In a terminal inside the CRD session:
text$ mpg123 beep.mp3
Couldn't open SDL audio: ALSA: Couldn't open audio device: Host is down
- A GUI media player (Parole) "played" an MP3 in silence, and no sink-input for it ever
appeared in
pactl list sink-inputs. - Command-line PipeWire tools (
pw-cli,pw-dump) run in the session quietly talked to the wrong PipeWire instance. - After a first attempt at a fix, some apps started over D-Bus failed in libpulse with:
textFailed to create secure directory ()
Root cause #
Three separate problems were stacked. Each one alone is enough to leave the CRD client silent.
1. CRD's audio variables leak into the whole session #
CRD starts the remote session with three variables that point at a PipeWire instance of its own, which is supposed to write audio into a FIFO that the CRD host reads:
| Variable | Value set by CRD |
|---|---|
PULSE_RUNTIME_PATH |
/tmp/pyxdg-runtime-dir-fallback-<user>/crd_audio#<id> |
PULSE_SINK |
chrome_remote_desktop_session |
PIPEWIRE_REMOTE |
crd_audio#<id>/pipewire |
On this machine CRD's own PipeWire never came up: it failed, CRD retried it five times, then
started the host without it. (Our reading of why: the CRD session had no XDG_RUNTIME_DIR
and no systemd user bus, so that PipeWire found no runtime directory.) The variables were
still set, and everything started from the session script inherits them: the desktop
session, panel, file manager, terminals, shells, and the dbus-launch session bus.
ALSA clients going through the PipeWire ALSA plugin (mpg123's default output, GStreamer apps)
follow PIPEWIRE_REMOTE to the dead instance and fail with Host is down. Setting
PULSE_SERVER in .bashrc does not help them: it only affects libpulse clients, and only in
interactive shells.
2. Empty values pushed into the D-Bus activation environment #
dbus-launch copies its own environment into the activation environment of the session bus,
so D-Bus-activated services (for example xdg-desktop-portal*) got the bad variables too.
dbus-update-activation-environment can set variables but cannot unset them, and the first
attempt "cleared" them by setting them to empty strings. An empty PULSE_RUNTIME_PATH makes
libpulse fail with Failed to create secure directory (). We confirmed this by reproducing it.
3. The CRD sink came back at 19 % volume #
The working setup routes the system PipeWire into CRD's FIFO through a pipe-sink (see Fix). WirePlumber restores the last stored volume of a re-created sink; for this sink that was 19 % (-43 dB). So even when routing was correct, the audio was nearly silent.
Fix #
Strip the variables before dbus-launch #
In ~/.chrome-remote-desktop-session, put this at the very top, before dbus-launch and
before the desktop session starts:
bashunset PULSE_RUNTIME_PATH PULSE_SINK PIPEWIRE_REMOTE
export XDG_RUNTIME_DIR=/run/user/$(id -u)
export $(dbus-launch)
exec startlxqt # or your session
Because the session bus is started after the unset, its activation environment is clean
too. Apps now use the normal per-user PipeWire.
For shells that still inherit the variables (for example in a session started before this change), unset them in your shell startup file as well:
bashunset PULSE_RUNTIME_PATH PULSE_SINK PIPEWIRE_REMOTE
Bridge the system PipeWire into CRD's FIFO #
With CRD's own PipeWire not running, nothing writes to the FIFO the host reads. Load a pipe sink into the system PipeWire (through pipewire-pulse) pointing at that FIFO, and make it the default:
bashCRD_DIR=$(find /tmp/pyxdg-runtime-dir-fallback-$USER "${XDG_RUNTIME_DIR:-/run/user/$UID}" \
-maxdepth 1 -type d -name 'crd_audio*' 2>/dev/null | head -1)
CRD_FIFO="$CRD_DIR/fifo_output"
[ -p "$CRD_FIFO" ] || mkfifo "$CRD_FIFO"
pactl load-module module-pipe-sink sink_name=crd_sink file="$CRD_FIFO" \
rate=48000 channels=2 format=s16le
pactl set-default-sink crd_sink
pactl set-sink-volume crd_sink 100% # WirePlumber may restore an old low level
pactl set-sink-mute crd_sink 0
This has to run after the CRD host is up, because the FIFO directory belongs to the host's
session. Running it late is fine: the host picks up data from the FIFO whenever it arrives.
We start it from the session script after a fixed sleep 20. That delay is a guess at the
host's start-up time, not a measured value.
If something must update the D-Bus activation environment, set real values #
If you have a switcher script (or anything else) that fixes the activation environment of an already-running session bus, set working defaults instead of empty strings:
bashdbus-update-activation-environment \
PIPEWIRE_REMOTE=pipewire-0 \
PULSE_RUNTIME_PATH="${XDG_RUNTIME_DIR:-/run/user/$UID}/pulse"
(The script on the affected machine also passes PULSE_SINK=. Only an empty
PULSE_RUNTIME_PATH was shown to break libpulse. The repaired session ran with an empty
PULSE_SINK and played audio, but that was not tested separately.)
Repairing a session that is already running #
Processes that are already running keep the variables they started with. Without logging
out, restart each launcher (panel, desktop/file manager, app runner, hotkey daemon, window
manager, notification daemon, xdg-desktop-portal*) with a clean environment so its children
inherit it. The CRD session bus is the dbus-launch one, not /run/user/<uid>/bus, so take
its address from a session process:
bashDBUS=$(tr '\0' '\n' < /proc/$(pgrep -x lxqt-session)/environ \
| grep '^DBUS_SESSION_BUS_ADDRESS=' | cut -d= -f2-)
env -u PULSE_RUNTIME_PATH -u PULSE_SINK -u PIPEWIRE_REMOTE -u PULSE_SERVER \
DISPLAY="$DISPLAY" XDG_RUNTIME_DIR=/run/user/$(id -u) \
DBUS_SESSION_BUS_ADDRESS="$DBUS" setsid -f lxqt-panel
D-Bus-activated services such as xdg-desktop-portal* can simply be killed after the
activation environment is fixed; they restart on demand with the new environment. Kill them
by PID (pgrep -f '^/usr/lib/xdg-desktop-portal'): pkill -f xdg-desktop-portal- also
matched the command line of the shell running it and killed that shell.
What was verified #
- A beep played with
mpg123in a CRD terminal was heard in the CRD client. - The media player, relaunched with a clean environment, had a sink-input on
crd_sink, uncorked, at 100 %. - Freshly D-Bus-activated
xdg-desktop-portal*processes gotPIPEWIRE_REMOTE=pipewire-0and a validPULSE_RUNTIME_PATH.
Not yet verified: a complete new CRD session using the updated session script. The session in this write-up was repaired in place.
How it was found #
-
Look for leaked variables in any process:
bash
tr '\0' '\n' < /proc/<pid>/environ | grep -E 'PULSE|PIPEWIRE' -
PipeWire tools inside a CRD shell need the variable removed, or they talk to the dead instance:
env -u PIPEWIRE_REMOTE pw-cli .... -
busctl --userandsystemctl --user show-environmentlook at the systemd user bus, which is the wrong bus for a CRD session started withdbus-launch. -
Check that the CRD host is reading the FIFO.
lsofshould show the host with it open for reading andpipewire-pulsewith it open read/write:bash
lsof /tmp/pyxdg-runtime-dir-fallback-$USER/crd_audio*/fifo_output timeout 8 dd if=/dev/zero of=/tmp/pyxdg-runtime-dir-fallback-$USER/crd_audio*/fifo_output bs=4096 count=100The
dd(400 KiB) finished in about 1.8 s, which matches 48 kHz 16-bit stereo (192 kB/s). If it hangs, the host is not reading. -
pactl get-sink-volume crd_sinkshowed the 19 % level.