Skip to main content

Hardware Access for Multimedia Workloads

This guide outlines best practices for building containerized workloads on the Admiral platform that require direct hardware access for High-Performance Audio, Input handling, and Fullscreen Graphics.

Unlike standard cloud containers, Admiral workloads run on physical edge devices. This environment requires specific strategies to bypass standard Linux abstractions and interface directly with hardware drivers (ALSA, DRM, Evdev).


The Challenge: Container vs. Bare Metal

When running a graphical application (like an emulator, dashboard, or media player) inside a container on bare metal, you face three primary challenges:

  1. Display Resolution: Without a Desktop Environment (GNOME/KDE), X11 defaults to safe, low resolutions (e.g., 640x480), causing apps to render in a small box.
  2. Audio Arbitration: Standard Linux distributions rely on PulseAudio or Pipewire. In a single-application container, these daemons add unnecessary complexity and often fail. Direct ALSA access is preferred but requires strict configuration.
  3. Runtime Constraints: Workloads often run in restrictive PID namespaces where standard shell features (like Process Substitution) may not behave as expected.

Audio Strategy: Robust ALSA Implementation

To guarantee sound output across diverse hardware (Intel NUCs, Embedded ARM, Industrial PCs), workloads should talk directly to the Kernel Audio drivers (ALSA).

1. The "PulseAudio Trap"

Most container base images (Ubuntu/Debian) include libasound2-plugins. This library attempts to route ALSA calls to a local PulseAudio server. If that server isn't running, audio initialization fails immediately.

Best Practice: Explicitly remove plugins to force apps to use the raw ALSA layer.

# In your Dockerfile
RUN apt-get remove -y libasound2-plugins || true

2. The "Plug" Layer (Format Conversion)

Applications often request specific audio formats (e.g., Float 32-bit @ 48kHz). Physical hardware might strictly require Integer 16-bit @ 44.1kHz. If they mismatch, the application will crash.

Solution: Use the ALSA plug plugin. It performs software sample rate and format conversion transparently.

Configuration (/etc/asound.conf):

pcm.!default {
type plug
slave {
pcm "hw:0,0" # This target must be dynamically detected
rate 48000 # Force standard rate for compatibility
}
}
ctl.!default {
type hw
card 0
}

3. Dynamic Device Detection (HDMI vs. Analog)

Linux enumerates sound cards unpredictably. card 0 might be an HDMI port (often silent on embedded screens), while speakers are on card 1.

Critical Note on Scripting: Admiral uses crun. In this environment, do not use Bash Process Substitution (e.g., while read line; do ... done < <(aplay -l)) in your startup scripts. The /dev/fd/ symlinks required for this syntax may not exist, causing silent crashes. Always use temporary files.

Robust Detection Example:

# Dump hardware list to a temp file
aplay -l > /tmp/sound_cards.txt

# Linear search for preferred keywords (Analog, Speaker, Realtek)
# We exclude HDMI unless it's the only option.
CARD_LINE=$(grep -iE "analog|speaker|headphone|conexant|realtek|generic" /tmp/sound_cards.txt | grep -vi "hdmi" | head -n 1)

# Fallback to first non-HDMI card
if [ -z "$CARD_LINE" ]; then
CARD_LINE=$(grep "^card" /tmp/sound_cards.txt | grep -vi "hdmi" | head -n 1)
fi

# Parse the card and device IDs
TARGET_CARD=$(echo "$CARD_LINE" | sed -n 's/^card \([0-9]\+\):.*/\1/p')
TARGET_DEVICE=$(echo "$CARD_LINE" | sed -n 's/.*device \([0-9]\+\):.*/\1/p')

echo "Configuring ALSA for Card $TARGET_CARD, Device $TARGET_DEVICE"

Graphics Strategy: X11 & Fullscreen Kiosk

For maximum compatibility, we recommend X11 (Xorg) over Wayland/Cage for single-app kiosks, as X11's input driver (evdev) is more permissive with containerized permission models.

1. Force Native Resolution

The X server must be told explicitly to use the connected monitor's maximum resolution; otherwise, your fullscreen app will render into a small "virtual" window centered on a black screen.

Implementation (Entrypoint Script):

OUTPUT=$(xrandr | grep " connected" | cut -d ' ' -f 1 | head -n 1)
if [ ! -z "$OUTPUT" ]; then
# Attempt to set 1920x1080 (Common Standard)
xrandr --output $OUTPUT --mode 1920x1080 || \
# If mode doesn't exist, inject it manually (Modeline for 1080p 60Hz)
xrandr --newmode "1920x1080_60.00" 173.00 1920 2048 2248 2576 1080 1083 1088 1120 -hsync +vsync && \
xrandr --addmode $OUTPUT "1920x1080_60.00" && \
xrandr --output $OUTPUT --mode "1920x1080_60.00"
fi

2. The Window Manager (Openbox)

Even with command-line flags like -fullscreen, some applications will fail to maximize correctly without a Window Manager. Openbox is extremely lightweight and can be configured via XML to strip borders and force maximization for all windows.

Config (/root/.config/openbox/rc.xml):

<openbox_config xmlns="http://openbox.org/3.4/rc">
<applications>
<application class="*">
<decorations>no</decorations>
<maximized>yes</maximized>
<fullscreen>yes</fullscreen>
</application>
</applications>
</openbox_config>

Input Handling

Use the evdev driver for X11. It reads directly from /dev/input/event*, bypassing complex "Seat" logic found in Wayland.

Dockerfile Config (/etc/X11/xorg.conf.d/10-input.conf):

Section "InputClass"
Identifier "evdev keyboard catchall"
MatchIsKeyboard "on"
MatchDevicePath "/dev/input/event*"
Driver "evdev"
Option "GrabDevice" "true"
EndSection

Runtime Permissions: Ensure your entrypoint script grants access to input nodes before X starts.

chmod 666 /dev/input/* 2>/dev/null || true

Reference Implementation

Below is a complete reference for a Dockerfile and entrypoint.sh that implements Audio Detection, Resolution Forcing, and Fullscreen enforcement.

5.1 Dockerfile

FROM ubuntu:noble

ENV DEBIAN_FRONTEND=noninteractive
ENV DISPLAY=:0

# 1. Install Bare Metal Dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
x11-xserver-utils xserver-xorg-core xinit openbox \
xserver-xorg-input-evdev \
alsa-utils alsa-ucm-conf libasound2t64 \
udev procps grep sed \
&& rm -rf /var/lib/apt/lists/*

# 2. Prevent Audio Hijacking
RUN apt-get remove -y libasound2-plugins || true

# 3. Configure Input
RUN mkdir -p /etc/X11/xorg.conf.d && \
echo 'Section "InputClass"\nIdentifier "evdev catchall"\nMatchDevicePath "/dev/input/event*"\nDriver "evdev"\nEndSection' > /etc/X11/xorg.conf.d/10-input.conf

# 4. Configure Window Manager (Force Fullscreen)
RUN mkdir -p /root/.config/openbox && \
echo '<openbox_config xmlns="http://openbox.org/3.4/rc"><applications><application class="*"><decorations>no</decorations><maximized>yes</maximized><fullscreen>yes</fullscreen></application></applications></openbox_config>' > /root/.config/openbox/rc.xml

COPY entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/entrypoint.sh

CMD ["/usr/local/bin/entrypoint.sh"]

5.2 entrypoint.sh

#!/bin/bash

# A. Init Hardware
/usr/lib/systemd/systemd-udevd --daemon
udevadm trigger
udevadm settle
chmod 666 /dev/input/* 2>/dev/null || true
chmod 666 /dev/snd/* 2>/dev/null || true

# B. Audio Detection (Linear Approach)
aplay -l > /tmp/sound_cards.txt
# Find analog/speaker, exclude HDMI
CARD_LINE=$(grep -iE "analog|speaker|headphone|realtek" /tmp/sound_cards.txt | grep -vi "hdmi" | head -n 1)
# Fallback to any non-HDMI
if [ -z "$CARD_LINE" ]; then
CARD_LINE=$(grep "^card" /tmp/sound_cards.txt | grep -vi "hdmi" | head -n 1)
fi

TARGET_CARD=$(echo "$CARD_LINE" | sed -n 's/^card \([0-9]\+\):.*/\1/p')
TARGET_DEVICE=$(echo "$CARD_LINE" | sed -n 's/.*device \([0-9]\+\):.*/\1/p')
[ -z "$TARGET_CARD" ] && TARGET_CARD=0 && TARGET_DEVICE=0

# C. Generate ALSA Config
cat > /etc/asound.conf << EOF
pcm.!default {
type plug
slave {
pcm "hw:$TARGET_CARD,$TARGET_DEVICE"
rate 48000
}
}
ctl.!default {
type hw
card $TARGET_CARD
}
EOF

# D. Create X11 Launch Script
cat > /root/.xinitrc << 'EOF'
#!/bin/sh
# 1. Disable Power Management
xset s off -dpms

# 2. Resolution Force (Dynamic)
OUTPUT=$(xrandr | grep " connected" | cut -d ' ' -f 1 | head -n 1)
if [ ! -z "$OUTPUT" ]; then
xrandr --output $OUTPUT --mode 1920x1080 || true
fi

# 3. Start Window Manager
openbox &
sleep 1

# 4. Start Application (Replace with your workload)
exec your-application-binary
EOF
chmod +x /root/.xinitrc

# E. Start X Server
exec xinit /root/.xinitrc -- /usr/bin/X :0 -ac -nocursor -s 0 -dpms -allowMouseOpenFail