Docker :在多个容器上共享 /dev/snd 导致 "device or resource busy"

Docker : sharing /dev/snd on multiple containers leads to "device or resource busy"

adding host device--device /dev/snd)到Docker容器时,我有时会遇到Device or resource busy错误。

例子

我用一个涉及音频的最小示例重现了这个问题 (alsa)。这是我的 Dockerfile(生成图像 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/*

我是运行下面的命令(speaker-test是一个生成音调的工具,可以用来测试音箱),与/dev/snd分享:

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

问题

当运行上一个命令时,会播放粉红噪音,但仅在某些情况下

看起来 /dev/snd 在使用时是 "locked",如果是这样,我得到以下输出(错误由最后两行表示):

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

反之亦然,如果(在容器上)播放粉红噪音,那么我就无法在我的主机上播放任何声音 (Ubuntu)。但是我的主机上的命令不会因相同的消息而失败。相反,主机上的命令(如 aplay test.wav 播放简单的声音)被无限期地阻止(即使容器随后关闭)。

我尝试通过 运行 strace aplay test.way 进行调试,但该命令似乎在 poll 系统调用中被阻止:

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

问题

如何同时播放来自 2 个(或更多)不同容器或来自我的主机和一个容器的声音?

附加信息

我已经用 /dev/snd 重现了这个问题,但我不知道在使用其他设备时是否会发生类似的事情,或者它是否只与声音设备或 alsa 有关。

另请注意,当 运行 多个 speaker-testaplay 命令同时并且 全部在我的主机上 (不涉及容器)时,然后播放所有声音。

我不知道如何用 ALSA 解决这个问题,但可以提供 2 种可能的 pulseaudio 方法。如果这些设置失败,请在图像中安装 pulseaudio 以确保依赖项已满。

ALSA 直接访问声音硬件并阻止其他客户端访问它。但是可以设置 ALSA 来服务多个客户端。那得找别人来回答了。可能需要一些 ALSA dmix plugin 设置。


  1. 带共享套接字的 Pulseaudio:

创建 pulseaudio 套接字:

pactl load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket

为 pulseaudio 客户端创建 /tmp/pulseaudio.client.conf

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

与docker共享套接字和配置文件并设置环境变量PULSE_SERVERPULSE_COOKIE。容器用户必须与主机相同:

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

cookie 将由 pulseaudio 自己创建。


  1. 基于 TCP 的 Pulseaudio:

从主机获取 IP 地址:

# either an arbitrary IPv4 address
Hostip="$(ip -4 -o a | awk '{print }' | 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 }' | cut -d/ -f1)"

运行 docker 图片。您需要一个空闲的 TCP 端口,这里使用 34567。 (TCP 端口号必须在 cat /proc/sys/net/ipv4/ip_local_port_range 范围内并且不能被使用。检查 ss -nlp | grep 34567。)

docker run --rm \
    --name pulsecontainer \
    --env PULSE_SERVER=tcp:$Hostip:34567 \
    imagename

docker run 获取容器的 IP 之后:

Containerip="$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' pulsecontainer)"

加载使用容器 IP 验证的 pulseaudio TCP 模块:

pactl load-module module-native-protocol-tcp  port=34567 auth-ip-acl=$Containerip

请注意,在容器启动 运行 之后 加载了 TCP 模块。 pulseaudio 服务器可用于容器应用程序需要一些时间。 如果 TCP 连接失败,请检查 iptablesufw 设置。


总结这些设置的方法:https://github.com/mviereck/x11docker/wiki/Container-sound:-ALSA-or-Pulseaudio