7

When adding host device (--device /dev/snd) to a Docker container, I sometimes encounter Device or resource busy errors.

Example

I have reproduced the issue with a minimal example involving audio (alsa). Here's my Dockerfile (producing an image docker-device-example) :

FROM    debian:buster

RUN     apt-get update \
 &&     apt-get install -y --no-install-recommends \
            alsa-utils \
 &&     rm -rf /var/lib/apt/lists/*

I am running the following command (speaker-test is a tool to generate a tone that can be used to test the speakers), with /dev/snd shared :

docker run --rm \
    -i -t \
    --device /dev/snd \
    docker-device-example \
    speaker-test

Issue

When running the previous command, a pink noise is played, but only under some conditions :

  • if I am not playing any sound on my host : for example, if I'm playing a video, and that even if the video is paused, the command fails
  • if I am not running another container accessing the /dev/snd device

It looks like the /dev/snd is "locked" when used, and if that is the case, I got the following output (the error is represented by the last 2 lines) :

speaker-test 1.1.6

Playback device is default
Stream parameters are 48000Hz, S16_LE, 1 channels
Using 16 octaves of pink noise
ALSA lib pcm_dmix.c:1099:(snd_pcm_dmix_open) unable to open slave
Playback open error: -16,Device or resource busy

And, vice versa, if the pink noise is played (on the container), then I cannot play any sound on my host (Ubuntu). But commands on my host does not fail with the same message. Instead, the command on the host (like aplay test.wav to play a simple sound) is blocked indefinitely (even when the container is shutdown afterwards).

I tried to debug by running strace aplay test.way, and the command seems to be blocked on the poll system call :

poll([{fd=3, events=POLLIN|POLLERR|POLLNVAL}], 1, 4294967295

Question

How can I play sounds from 2 (or more) different containers, or from my host and a container, at the same time?

Additional info

I've reproduced the issue with /dev/snd, but I don't know if similar things happen when using other devices, or if it's just related to sound devices or to alsa.

Note also that when running multiple speaker-test or aplay commands simultaneously and all on my host (no containers involved), then all sounds are played.

norbjd
  • 10,166
  • 4
  • 45
  • 80

1 Answers1

5

I can't tell how to solve this with ALSA, but can provide 2 possible ways with pulseaudio. If these setups fail, install pulseaudio in image to make sure dependencies are fullfilled.

ALSA directly accesses sound hardware and blocks access to it for other clients. But it is possible to set up ALSA to serve multiple clients. That has to be answered by someone else. Probably some ALSA dmix plugin setup is the way to go.


  1. Pulseaudio with shared socket:

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.


  1. Pulseaudio over TCP:

Get IP address from host:

# either an arbitrary IPv4 address
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 iptables and ufw settings.


A How-To summarizing these setups: https://github.com/mviereck/x11docker/wiki/Container-sound:-ALSA-or-Pulseaudio

mviereck
  • 1,309
  • 1
  • 12
  • 15
  • The first solution works perfectly, thanks! Using `pulseaudio` is not a problem for me. FI, the 2nd solution doesn't work (command for playing sound hangs and crash with a `Playback open error: -2,No such file or directory`), but since I'm running containers on the same host, the 1st solution fits better. I'll accept this answer soon if there is no more answers to my question with only ALSA. – norbjd Aug 15 '18 at 15:45
  • @norbjd I've re-checked the TCP setup, it works here. Maybe `$Hostip` needs to be checked on your system, you may have to use another IPv4 address. Be aware that it takes a short moment until `pactl load-module` is ready and accessable from container. – mviereck Aug 15 '18 at 18:20
  • I've tried to wait and use other `$Hostip`, but I'm not able to play sounds from a container with solution 2. When running `aplay -L` from the container, I've noticed that I just got `null`; while I got a `default` one (description : `Playback/recording through the PulseAudio sound server`) when doing solution 1. Maybe the issue comes from here? I've also noticed that the error when playing a sound is not immediate, the command hangs ~5s before crashing (if not setting `PULSE_SERVER`, the command crashes immediately). – norbjd Aug 16 '18 at 09:36
  • @norbjd Thanks for testing! Here `aplay -L` and `speaker-test` both work over TCP. In both modes I get `Playback/recording through the PulseAudio sound server`. Maybe `pactl load-module` fails on your system? It should print a module number. Maybe you somehow provide a wrong `$Containerip`? Anyway, if sharing the socket already fits your needs, it is ok. The difference may have to do with container system in use; I am testing with debian images. – mviereck Aug 16 '18 at 10:53
  • Also an `iptables` setup could prohibit TCP connections. – mviereck Aug 16 '18 at 11:23
  • `pactl load-module` seems to work because I am able to see it when doing `pactl list`. I also work with debian images with only `pulseaudio` and `alsa-utils` installed. I think this is just a problem of communication between container and host: from the container, I cannot connect to `$Hostip` : `telnet $Hostip $port` does not work (any `$Hostip` used), while it works on my host. Running the container with `--net=host` solves the problem, but I don't think this is a good solution. Anyway, thanks for the help! – norbjd Aug 16 '18 at 11:33
  • @norbjd One last idea: Maybe you need a different port number. It must not be in use (check `ss -lpn | grep $Port`) and must be in range of `cat /proc/sys/net/ipv4/ip_local_port_range`, here `32768 60999`. – mviereck Aug 16 '18 at 11:49
  • In fact, you were right in your last comment : packets from containers to host are blocked by `ufw`.... Thank you again for your help! :) – norbjd Aug 16 '18 at 16:39
  • @norbjd You are welcome! Good to here it works now. I've added some notes from comments to the answer. ALSA probably needs a `dmix` plugin setup. – mviereck Aug 16 '18 at 19:32
  • I tried the first solution and it works for me but the files are being delete from the /tmp directory on every reboot so I have to create the pulse audio socket everytime. Any solution to prevent deletion of the files? – node_man Mar 03 '19 at 15:45
  • @node_man The socket is intended to be temporary only. However, you can try to store it elsewhere and reuse it. But I doubt that this will work. The config file, of course, can be stored elsewhere permanently. – mviereck Mar 03 '19 at 22:14
  • I tried to store the socket somewhere else it didn't work and yes, storing the configuration file elsewhere works perfectly – node_man Mar 04 '19 at 02:43
  • So I wrote a shell script to create the socket everytime the machine restarts and it works. But when the container has a restart policy in that case it doesn't work as most of the time the container starts before the shell script is executed at startup – node_man Mar 04 '19 at 02:45
  • If there is a way to execute the pactl command with the docker run command such that pactl is executed and then the docker run will mount the directories then it will work everytime – node_man Mar 04 '19 at 02:48