Запускать приложения с использованием аудио в док-контейнере

Этот вопрос вдохновлен тем, можете ли вы запускать приложения с графическим интерфейсом в Docker-контейнере?,

Основная идея - запускать приложения с аудио и пользовательским интерфейсом (vlc, firefox, skype, ...)

Я искал докер-контейнеры с использованием pulseaudio, но все контейнеры, которые я нашел, использовали Pulseaudio с потоковой передачей по tcp. (безопасная песочница приложений)

В моем случае я бы предпочел проигрывать аудио из приложения внутри контейнера непосредственно на мой хост pulseaudio. (без туннелирования ssh и раздутых образов Docker)

Pulseaudio, потому что мое приложение QT использует его;)

6 ответов

Решение

Это заняло у меня некоторое время, пока я не узнал, что нужно. (Ubuntu)

мы начнем с команды запуска Docker docker run -ti --rm myContainer sh -c "echo run something"

ALSA:
нам нужно /dev/snd и некоторый аппаратный доступ, как это выглядит. когда мы собрали это вместе, мы имеем

docker run -ti --rm \
    -v /dev/snd:/dev/snd \
    --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
    myContainer sh -c "echo run something"`

В новых версиях докера без флагов lxc вы должны использовать это:

docker run -ti --rm \
    -v /dev/snd:/dev/snd \
     --privileged \
    myContainer sh -c "echo run something"`

PulseAudio:
Здесь нам нужно в основном /dev/shm, /etc/machine-id а также /run/user/$uid/pulse, Но это еще не все (возможно, из-за Ubuntu и того, как они делали это в прошлом). Переменная окружения XDG_RUNTIME_DIR должно быть одинаковым в хост-системе и в вашем док-контейнере. Вам также может понадобиться /var/lib/dbus потому что отсюда некоторые приложения обращаются к идентификатору машины (может содержать только символическую ссылку на "реальный" идентификатор машины). И, по крайней мере, вам может понадобиться скрытая домашняя папка ~/.pulse для некоторых временных данных (я не уверен в этом).

docker run -ti --rm \
    -v /dev/shm:/dev/shm \
    -v /etc/machine-id:/etc/machine-id \
    -v /run/user/$uid/pulse:/run/user/$uid/pulse \
    -v /var/lib/dbus:/var/lib/dbus \
    -v ~/.pulse:/home/$dockerUsername/.pulse \
    myContainer sh -c "echo run something"

В новых версиях докера вам может понадобиться добавить --privileged,
Конечно, вы можете объединить оба вместе и использовать его вместе с xServer Переадресация пользовательского интерфейса, как здесь: /questions/16455501/mozhete-li-vyi-zapuskat-prilozheniya-s-graficheskim-interfejsom-v-docker-kontejnere/16455537#16455537

Просто упомянуть:

  • Вы можете обработать большую часть этого (все без используемого идентификатора) в dockerfile
  • с помощью uid=$(id -u) чтобы получить идентификатор пользователя и GID с id -g
  • создание пользователя докера с этим идентификатором

создать скрипт пользователя:

mkdir -p /home/$dockerUsername && \
echo "$dockerUsername:x:${uid}:${gid}:$dockerUsername,,,:/home/$dockerUsername:/bin/bash" >> /etc/passwd && \
echo "$dockerUsername:x:${uid}:" >> /etc/group && \
mkdir /etc/sudoers.d && \
echo "$dockerUsername ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$dockerUsername && \
chmod 0440 /etc/sudoers.d/$dockerUsername && \
chown ${uid}:${gid} -R /home/$dockerUsername

Сегодня у нас есть Pipewire, и если вы хотите, чтобы приложение внутри Docker-контейнера воспроизводило/записывало звуки с помощью вашего сервера Pipewire, работающего на хосте, вот что вы можете сделать:

  1. Прежде всего, нам, очевидно, нужен сервер Pipewire, работающий на нашем хосте. В современных дистрибутивах Pipewire запускается, когда systemd достигаетgraphical.targetи менеджер входа в систему (gdm, sddm, что угодно) выполняется. А затем, позже, когда пользователь входит в систему, Pipewire перезапускается и становится собственностью этого пользователя.

Чтобы приложение внутри Docker-контейнера могло использовать Pipewire, мы собираемся передать в этот контейнер дескриптор файла сокета . Для этого нам нужно знать, кто управляет сервером Pipewire на вашем хост-компьютере. Вы можете проверить это, посмотрев, под кем выполняются процессы Pipewire, а затем узнать, какой файл сокета будет правильным:

      testuser@asrock-ubuntu:~$ ps -fp $(pgrep -d, -x pipewire)

UID          PID    PPID  C STIME TTY          TIME CMD
testuser     4250    4206  1 13:32 ?        00:00:27 /usr/bin/pipewire

testuser@asrock-ubuntu:~$ id testuser

uid=1000(testuser) gid=1000(testuser) groups=1000(testuser),4(adm),6(disk),24(cdrom),27(sudo),30(dip),46(plugdev),105(input),107(kvm),122(lpadmin),134(lxd),135(sambashare),140(libvirt),64055(libvirt-qemu),997(docker)

Итак, в нашем случае я ожидаю, что должен быть файл сокета/run/user/1000/pipewire-0. Проверьте это в своей системе.

  1. Теперь мы собираемся создать облегченный докер-контейнер Alpine, передав ему файл сокета Pipewire и установив переменную XDG_RUNTIME_DIR, которая необходима для клиента Pipewire внутри контейнера.
      docker run -it -v /run/user/1000/pipewire-0:/tmp/pipewire-0 -e XDG_RUNTIME_DIR=/tmp --rm alpine /bin/ash
  1. Внутри контейнера мы устанавливаем в него несколько пакетов, чтобы все работало:
  • alsa-utils — предоставляет набор утилит ALSA, которые мы собираемся использовать в качестве примера программного обеспечения, для которого требуется ALSA.
  • Pipewire — двоичные файлы сервера (и клиента) Pipewire. Но мы не собираемся запускать полноценный сервер в контейнере, вовсе нет, нам просто нужно, чтобы там была его клиентская часть.
  • Pipewire-alsa — представляет виртуальное устройство ALSA «pipewire», которое мы собираемся использовать в нашем программном обеспечении, для которого требуется ALSA. Проще говоря, это мост между ALSA и нашим клиентом Pipewire.
      apk add pipewire-alsa pipewire alsa-utils
  1. Теперь проверим, все ли работает. Убедитесь, что вы видите устройство «pipewire» при запускеaplay -Lа затем запустите наш простой тест, чтобы воспроизвести шум через это устройство:
      speaker-test -Dpipewire -c2

В этот момент вы должны услышать приближающийся звук. Если это не так, проверьте устройство-приемник Pipewire по умолчанию на своем хосте . Если вы используете Gnome, откройте «Настройки Gnome» -> «Звук» -> «Вывод».

Теперь внутри контейнера мы можем записать волновой файл с помощью arecord и воспроизвести его с помощью чистых инструментов ALSA:

      arecord --duration=5 --device=pipewire test.wav
aplay --device=pipewire test.wav
  1. И последнее, прежде чем мы продолжим: давайте также проверим, могут ли собственные клиенты Pipewire выполняться внутри этого контейнера. Для этого мы устанавливаемpipewire-tools. Пример wav-файла, который я собираюсь воспроизвести, поставляется вместе сalsa-utilsпакет, который мы установили ранее:
      apk add pipewire-tools
pw-play /usr/share/sounds/alsa/Rear_Center.wav

Вкратце, общая установка выглядит примерно так:

Вдохновленный ссылками, которые вы разместили, я смог создать следующее решение. Он настолько легкий, насколько я мог. Однако я не уверен, является ли он (1) безопасным, и (2) полностью соответствует вашему варианту использования (так как он все еще использует сеть).

  1. устанавливать paprefsв вашей хост-системе, например, используя sudo apt-get install paprefs на машине с Ubuntu.
  2. Запустите PulseAudio Preferences, перейдите на вкладку "Сетевой сервер" и установите флажок "Включить сетевой доступ к локальным звуковым устройствам" [1].
  3. Перезагрузите компьютер. (Только перезапуск Pulseaudio не работал для меня в Ubuntu 14.10)
  4. Установите Pulseaudio в свой контейнер, например sudo apt-get install -y pulseaudio
  5. В вашем контейнере запустите export "PULSE_SERVER=tcp:<host IP address>:<host Pulseaudio port>", Например, export "PULSE_SERVER=tcp:172.16.86.13:4713" [2]. Вы можете узнать свой IP-адрес, используя ifconfig и порт Pulseaudio, используя pax11publish [1].
  6. Вот и все. Шаг 5, вероятно, следует автоматизировать, если IP-адрес и порт Pulseaudio могут быть изменены. Кроме того, я не уверен, что Docker постоянно хранит переменные окружения, такие как PULSE_SERVER: Если этого не произойдет, вы должны инициализировать его после каждого запуска контейнера.

Буду очень признателен за предложения по улучшению моего подхода, поскольку в настоящее время я работаю над такой же проблемой, что и ОП.

Рекомендации:
[1] https://github.com/jlund/docker-chrome-pulseaudio
[2] https://github.com/jlund/docker-chrome-pulseaudio/blob/master/Dockerfile

ОБНОВЛЕНИЕ (и, вероятно, лучшее решение):
Это также работает с использованием сокета Unix вместо сокета TCP:

  1. Начать контейнер с -v /run/user/$UID/pulse/native:/path/to/pulseaudio/socket
  2. В контейнере беги export "PULSE_SERVER=unix:/path/to/pulseaudio/socket"

/path/to/pulseaudio/socket может быть что угодно, для тестирования я использовал /home/user/pulse,
Возможно, он даже будет работать с тем же путем, что и на хосте (с учетом части $UID) в качестве сокета по умолчанию, таким образом, окончательное решение будет -v /run/user/$UID/pulse/native:/run/user/<UID in container>/pulse; Я не проверял это однако.

Попробовав большинство описанных здесь решений, я обнаружил, что только PulseAudio по сети действительно работает. Однако вы можете сделать это безопасно, сохранив аутентификацию.

  1. Установите paprefs (на хост-машине):

    $ apt-get install paprefs
    
  2. запуск paprefs (Настройки PulseAudio) > Сетевой сервер> [X] Включить сетевой доступ к локальным звуковым устройствам.

  3. Перезапустите PulseAudio:

    $ service pulseaudio restart
    
  4. Проверьте, сработало или перезагрузите машину:

    $ (pax11publish || xprop -root PULSE_SERVER) | grep -Eo 'tcp:[^ ]*'
    tcp:myhostname:4713
    

Теперь используйте этот сокет:

$ docker run \
    -e PULSE_SERVER=tcp:$(hostname -i):4713 \
    -e PULSE_COOKIE=/run/pulse/cookie \
    -v ~/.config/pulse/cookie:/run/pulse/cookie \
    ...

Убедитесь, что пользователь, работающий внутри контейнера, имеет доступ к файлу cookie ~/.config/pulse/cookie,

Чтобы проверить это работает:

$ apt-get install mplayer
$ mplayer /usr/share/sounds/alsa/Front_Right.wav

Для получения дополнительной информации можете проверить Docker Mopidy проекта.

Предполагая, что pulseaudio установлен на хосте и в образе, можно передавать звук pulaudio через tcp всего за несколько шагов. pulseaudio не нужно перезапускать, и не нужно настраивать ни хост, ни образ. Таким образом, он включен в x11docker, без необходимости использования VNC или SSH:

Во-первых, найдите бесплатный порт TCP:

read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while : ; do
  PULSE_PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
  ss -lpn | grep -q ":$PULSE_PORT " || break
done

Получить IP-адрес демона Docker. Я всегда нахожу это 172.17.42.1/16

ip -4 -o a | grep docker0 | awk '{print $4}'

Загрузите tcp модуль pulseaudio, аутентифицируйте соединение с ip докера:

PULSE_MODULE_ID=$(pactl load-module module-native-protocol-tcp port=$PULSE_PORT auth-ip-acl=172.17.42.1/16)

При запуске докера создайте переменную среды PULSE_SERVER

docker run -e PULSE_SERVER=tcp:172.17.42.1:$PULSE_PORT yourimage

После этого выгрузите модуль tcp. (Примечание: по неизвестным причинам выгрузка этого модуля может остановить демон pulseaudio на хосте):

pactl unload-module $PULSE_MODULE_ID

Мне удалось докеризировать Java-игру следующими способами, эффективно пропуская звук игры.

Этот подход требует создания образа и проверки наличия у приложения всех необходимых зависимостей, в данном случае — pulseaudio и x11. Если вы уверены, что в ваших изображениях есть все необходимое, вы можете действовать, как указано в предыдущих ответах.

Здесь нам нужно создать образ, после чего мы сможем его запустить.

      docker build -t my-unciv-image . # Run from directory where Dockerfile is
docker run --name unciv # image name\ 
  --device /dev/dri \
  -e DISPLAY=$DISPLAY \
  -e PULSE_SERVER=unix:/run/user/1000/pulse/native \
  --privileged \
  -u $(id -u):$(id -g) \
  -v /path/to/Unciv:/App \
  -v /run/user/$(id -u)/pulse:/run/user/(id -u)/pulse \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -w /App \
  my-unciv-image \
  java -jar /App/Unciv.jar

Во второй команде указывается следующее:

  • --name: контейнеру дается имя
  • --device: видеоустройство*
  • -e: требуемые переменные окружения
    • DISPLAY: отображаемый номер
    • : Сокет аудиосервера PulseAudio
  • --privileged: запустите ip привилегированный *, чтобы он мог получить доступ ко всем устройствам
  • -v: Установленные тома:
    • Путь к игре, смонтированной в /App в контейнере**
    • Разъем для аудиосервера
    • Показать сокет сервера
  • -w: Рабочий каталог

Вотdocker-compose.ymlего версия:

      # docker-compose.yml
version: '3'
services:
  unciv:
    build: .
    container_name: unciv
    devices:
      - /dev/dri:/dev/dri # * Either this
    entrypoint: java -jar /App/Unciv.jar
    environment:
      - DISPLAY=$DISPLAY
      - PULSE_SERVER=unix:/run/user/1000/pulse/native
    privileged: true # * or this
    user: 1000:1000
    volumes:
      - /path/to/game/:/App
      - /run/user/1000/pulse:/run/user/1000/pulse
      - /tmp/.X11-unix:/tmp/.X11-unix
    working_dir: /App
      FROM ubuntu:20.04

RUN apt-get update
RUN apt-get install openjdk-11-jre -y
RUN apt-get install -y xserver-xorg-video-all
RUN apt-get install -y libgl1-mesa-glx libgl1-mesa-dri
RUN apt-get install -y pulseaudio

USER unciv

Примечания:

  • *Требуется только для игр или всего, что использует openGL. Либо передавать устройства явно, либо запускать их как привилегированные, но я думаю, что достаточно передать устройство, сделать его привилегированным может быть излишним.
  • **Эта математика может быть включена в образ докера, но для демонстрации.
  • Для аудио необходимо передать переменную envPULSE_SERVERи монтаж разъема pulseaudio
Другие вопросы по тегам