-
Notifications
You must be signed in to change notification settings - Fork 417
Container sound: ALSA or Pulseaudio
This page describes some possible ways how to set up sound for docker containers without x11docker.
Quite common is the easy ALSA setup. A bit more advanced, but with better integration in most desktop environments are setup with PipeWire or Pulseaudio.
Pulseaudio is a sound server that has been standard on Linux desktops for several years.
It can be provided with shared unix sockets or with a TCP connection.
Both ways need Pulseaudio server on host and Pulseaudio client library (Debian: libpulse0) in image. x11docker supports both ways with --pulseaudio=socket or --pulseaudio=tcp.
PipeWire is the successor of Pulseaudio. It becomes the standard sound system on Linux desktops. The setup here is done with two shared sockets. x11docker provides it with option --pipewire.
JACK is a professional sound server system for Linux.
For ALSA sound just share sound devices with --device /dev/snd. You would not need the more advanced Pulseaudio setup, but will have trouble if more than one application tries to access the sound hardware. x11docker provides this setup with option --alsa.
- If you have an unprivileged user in container, add him to group
audiowith--group-add=audio. - If you can't hear sound, you might need to set environment variable
--env ALSA_CARD=Genericor another card name that shows up inaplay -l. - To check ALSA sound in container, install
alsa-utilsin image and runspeaker-test:docker run --rm --device /dev/snd ALSAIMAGE speaker-test - Applications that only support Pulseaudio can be fooled with apulse. Example:
apulse firefoxsets up a fake Pulseaudio environment, but forwards the sound signals to ALSA.
Create pulseaudio socket:
pactl load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket
Create /tmp/pulseaudio.client.conf for pulseaudio clients:
default-server = unix:/tmp/pulseaudio.socket
# Prevent a server running in the container
autospawn = no
daemon-binary = /bin/true
# Prevent the use of shared memory
enable-shm = false
Share socket and config file with docker and set environment variables PULSE_SERVER and PULSE_COOKIE. Container user must be same as on host:
docker run --rm \
--env PULSE_SERVER=unix:/tmp/pulseaudio.socket \
--env PULSE_COOKIE=/tmp/pulseaudio.cookie \
--volume /tmp/pulseaudio.socket:/tmp/pulseaudio.socket \
--volume /tmp/pulseaudio.client.conf:/etc/pulse/client.conf \
--user $(id -u):$(id -g) \
imagename
The cookie will be created by pulseaudio itself.
Get IP address from host:
# either an arbitrary IPv4 address from host
Hostip="$(ip -4 -o a | awk '{print $4}' | cut -d/ -f1 | grep -v 127.0.0.1 | head -n1)"
# or especially IP from docker daemon
Hostip="$(ip -4 -o a| grep docker0 | awk '{print $4}' | cut -d/ -f1)"
Run docker image. You need a free TCP port, here 34567 is used. (TCP port number must be in range of cat /proc/sys/net/ipv4/ip_local_port_range and must not be in use. Check with ss -nlp | grep 34567.)
docker run --rm \
--name pulsecontainer \
--env PULSE_SERVER=tcp:$Hostip:34567 \
imagename
After docker run [...] get IP of container with:
Containerip="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' pulsecontainer)"
Load pulseaudio TCP module authenticated with container IP:
pactl load-module module-native-protocol-tcp port=34567 auth-ip-acl=$Containerip
Be aware that the TCP module is loaded after container is up and running. It takes a moment until pulseaudio server is available for container applications. If TCP connection fails, check for possible iptables and ufw settings that might prohibit the connection.
If pulseaudio runs as a system wide daemon on host, LC_ALL=C pactl info shows Server String: /var/run/pulse/native and User Name: pulse.
Note that such a setup is discouraged.
- By default pulseaudio runs under an unprivileged user.
- If pulseaudio runs as a system wide daemon, the TCP setup works.
- To share the system socket
/var/run/pulse/native, the container user must be added to grouppulse-accesswith--group-add pulse-access.PULSE_COOKIEis not needed in that case.
A container gets access to PipeWire sharing the PipeWire unix socket from host. Usually it is $XDG_RUNTIME_DIR/pipewire-0.
- The pipewire socket must either be in
XDG_RUNTIME_DIRof container, or its localization can be indicated with environment variablePIPEWIRE_RUNTIME_DIR. - For ALSA client support the container needs the PipeWire ALSA module.
- For Pulseaudio client support host and container need the PipeWire Pulseaudio module.
- The pulseaudio socket location on host can be achieved from
pactl --info. - The Pulseaudio socket created by PipeWire has to be shared. Usually it it
$XDG_RUNTIME_DIR/pulse/native. - The environment variable
PULSE_SERVERmust point to the pulseaudio socket in container.
- The pulseaudio socket location on host can be achieved from
- For package names of modules compare wiki: Dependencies in image.
Example:
docker run --rm \
-v $XDG_RUNTIME_DIR/pipewire-0:/tmp/pipewire-0 \
-e PIPEWIRE_RUNTIME_DIR=/tmp \
-v $XDG_RUNTIME_DIR/pulse/native:/tmp/pulse.socket \
-e PULSE_SERVER=/tmp/pulse.socket
...
Instead of sharing the host Pipewire socket it is possible to create a restricted socket in /tmp with pw-container.
It is a bit difficult to catch the output of pw-container. This solution writes the output to a file and than reads from it:
echo "#! /bin/bash
# helper script to get socket path. Reading output of pw-container directly somehow not possible.
echo \$PIPEWIRE_REMOTE >/tmp/PIPEWIRE_REMOTE
while :; do
sleep 1000
done
" >/tmp/pw_script
bash /tmp/pw_script &
PIPEWIRE_REMOTE="$(cat /tmp/PIPEWIRE_REMOTE)"
Use it like:
docker run --rm \
-v $PIPEWIRE_REMOTE:$PIPEWIRE_REMOTE \
-e PIPEWIRE_REMOTE=$PIPEWIRE_REMOTE
...
This should work with a PipeWire JACK module on host and in image. Compare chapter PipeWire.
Look at this contribution of @domichel: Howto get JACK audio working with a docker container #544