Dzisiejszy wpis jest kontynuacją wpisu sprzed dwóch lat o tytule Dockeryzacja aplikacji z graficznym interfejsem użytkownika. Wtedy poświęciłem kilka chwil na opisaniu, w jaki sposób wyświetlić obraz z kontenera, dzisiaj dodatkowo pokażę, jak usłyszeć dźwięk audio.

Na potrzeby dzisiejszego wpisu przygotujemy sobie prosty kontener oparty o obraz Ubuntu, z zainstalowaną grą Battle for Wesnoth. Polecam tę grę gorąco, jeżeli ktoś do tej pory nie miał z nią do czynienia, a jest fanem strategii turowych... na dodatek darmowych 😎.

Tworzymy sobie pusty projekt, a w nim plik o nazwie Dockerfile:

FROM docker.io/ubuntu:22.04

RUN apt-get update
RUN apt-get install -y wesnoth

Powyższy obraz to po prostu Ubuntu z zainstalowaną grą. Dodatkowo tworzymy plik konfiguracyjny docker-compose.yaml:

services:
  app:
    command: /usr/games/wesnoth -w
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      DISPLAY: host.docker.internal:0.0

Dzięki temu uruchamiając serwis, uruchomimy także bezpośrednio grę. Pewnie od razu rzuciła ci się różnica w stosunku do poprzedniego wpisu - zamiast ręcznie dodawać IP łączymy się poprzez host.docker.internal - człowiek cały czas się uczy 😊. Uruchamiam projekt na Windowsie, dlatego muszę uruchomić zainstalowaną tak jak poprzednio aplikację VcXsrv, będącą serwerem X.

⚠ Na Linuksie konfiguracja ta będzie wyglądać trochę inaczej. Przekazujemy zmienną DISPLAY z naszego hosta do kontenera i montujemy odpowiedni socket:

services:
  app:
    command: /usr/games/wesnoth -w
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      DISPLAY: $DISPLAY
    volumes:
      - /tmp/.X11-unix:/tmp/.X11-unix

Dodatkowo wykonujemy komendę, która pozwoli połączyć się do naszego wyświetlacza:

xhost +local:docker

Teraz mogę zbudować i uruchomić serwis poleceniem: docker compose up, a chwilę po tym ukaże mi się na okienko z menu gry (ale w chwili obecnej jeszcze bez dźwięku).

Okno gry uruchomionej w kontenerze Dockera
Okno gry uruchomionej w kontenerze Dockera ()

Przechwytywanie strumienia audio

Serwer X pozwala na przekazywanie obrazu wideo, jednak nie pozwala na przekazywanie dźwięku audio. Do tego należy wykorzystać podobne rozwiązanie o nazwie Pulse Audio. Podobnie jak z obrazem wideo, na maszynie hosta musi zostać uruchomiona aplikacja serwera, można ją pobrać z tej strony dla systemu Windows. Pobieram tak więc wersję dla Windows, rozpakowuję ją i modyfikuję pliki konfiguracyjne.

W pliku etc/pulse/daemon.conf w rozpakowanym folderze dodaję na końcu linię:

exit-idle-time = -1

Spowoduje to zachowanie się serwera w taki sposób, że po braku połączenia pozostanie dalej uruchomiony.

W pliku etc/pulse/default.pa dodaję na końcu linię:

load-module module-native-protocol-tcp listen=0.0.0.0 auth-anonymous=1 

Zmiana na pozwala na łączenie się do serwera z dowolnego IP. Następnie uruchamiam serwer z pliku wykonywalnego: bin/pulseaudio.exe.

W systemie Linux konfiguracja Pulse Audio jest praktycznie identyczna, łącznie ze ścieżkami.

Pulse Audio uruchomione w systemie Windows
Pulse Audio uruchomione w systemie Windows ()

Teraz pozostało skonfigurować kontener, aby przekazywał dźwięk do serwera na naszej maszynie-hoście. W pliku docker compose.yaml dodaję zmienną środowiskową PULSE_SERVER:

services:
  app:
    command: /usr/games/wesnoth -w
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      DISPLAY: host.docker.internal:0.0
      PULSE_SERVER: host.docker.internal

dla systemu Linux zmienną host.docker.internal zastępujemy odpowiadającym adresem IP:

services:
  app:
    command: /usr/games/wesnoth -w
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      DISPLAY: $DISPLAY
      PULSE_SERVER: 172.17.0.1
    volumes:
      - /tmp/.X11-unix:/tmp/.X11-unix

i uruchamiam ponownie serwis: docker-compose up. Po chwili ukazuje się okienko z grą oraz dźwiękiem audio.

To na tym etapie tyle. Ogólnie wydajność tego rozwiązania może pozostawiać wiele do życzenia - np. w moim przypadku, na starszym sprzęcie z Docker Desktop (Windows 10) wideo rozjeżdżało się z audio dość znacznie, ale na drugim, z zainstalowanym Podmanem zamiast Dockera działało dużo lepiej. Natomiast na Dockerze na Linuksie praktycznie nie zauważyłem żadnych opóźnień.