diff --git a/Dockerfile b/Dockerfile index ff8ee8d..dbef87b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,10 @@ -FROM ubuntu:22.04 +FROM ubuntu:26.04 ENV DEBIAN_FRONTEND=noninteractive # Install unzip first so we can extract the large static zips RUN apt-get update && apt-get install -y unzip && rm -rf /var/lib/apt/lists/* -# Create user -RUN useradd -m -s /bin/bash ubuntu - -# Copy and extract large static files first +# Copy and extract large static files first COPY nosteam.zip /home/ubuntu/ COPY wine_user_folder.zip /home/ubuntu/ RUN cd /home/ubuntu && unzip nosteam.zip && rm nosteam.zip && unzip wine_user_folder.zip && rm wine_user_folder.zip @@ -20,21 +17,26 @@ RUN dpkg --add-architecture i386 && \ gnupg2 \ ca-certificates \ && mkdir -pm755 /etc/apt/keyrings \ - && wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key \ - && echo "deb [signed-by=/etc/apt/keyrings/winehq-archive.key] https://dl.winehq.org/wine-builds/ubuntu/ jammy main" > /etc/apt/sources.list.d/winehq.list \ + && wget -O- https://dl.winehq.org/wine-builds/winehq.key | gpg --dearmor -o /etc/apt/keyrings/winehq-archive.key \ + && echo "deb [signed-by=/etc/apt/keyrings/winehq-archive.key] https://dl.winehq.org/wine-builds/ubuntu/ resolute main" > /etc/apt/sources.list.d/winehq.list \ && apt-get update \ && apt-get install -y --install-recommends winehq-stable \ && apt-get install -y \ winetricks \ weston \ xwayland \ - pulseaudio \ + dbus-x11 \ + pipewire \ + pipewire-pulse \ + pipewire-audio-client-libraries \ + wireplumber \ pulseaudio-utils \ ffmpeg \ xdotool \ nodejs \ npm \ mesa-vulkan-drivers \ + vulkan-tools \ gstreamer1.0-tools \ gstreamer1.0-plugins-base \ gstreamer1.0-plugins-good \ @@ -42,6 +44,7 @@ RUN dpkg --add-architecture i386 && \ gstreamer1.0-plugins-ugly \ gstreamer1.0-libav \ gstreamer1.0-nice \ + gstreamer1.0-pipewire \ gir1.2-gst-plugins-bad-1.0 \ python3-gi \ python3-gst-1.0 \ @@ -49,7 +52,7 @@ RUN dpkg --add-architecture i386 && \ && rm -rf /var/lib/apt/lists/* # Environment variables -ENV VK_ICD_FILENAMES="/usr/share/vulkan/icd.d/lvp_icd.x86_64.json" +ENV VK_ICD_FILENAMES="/usr/share/vulkan/icd.d/lvp_icd.json" ENV XDG_RUNTIME_DIR=/tmp/weston-runtime ENV WAYLAND_DISPLAY=wayland-0 ENV DISPLAY=:99 @@ -61,7 +64,7 @@ ENV MESA_SHADER_CACHE_MAX_SIZE=10G COPY start.sh /home/ubuntu/start.sh COPY streaming-agent/ /home/ubuntu/streaming-agent/ RUN cd /home/ubuntu/streaming-agent && npm install -RUN pip3 install -r /home/ubuntu/streaming-agent/requirements.txt +RUN pip3 install --break-system-packages -r /home/ubuntu/streaming-agent/requirements.txt RUN chown -R ubuntu:ubuntu /home/ubuntu/ RUN chmod +x /home/ubuntu/start.sh USER ubuntu diff --git a/start.sh b/start.sh index df29b09..b64e3ff 100755 --- a/start.sh +++ b/start.sh @@ -1,44 +1,85 @@ #!/bin/bash -# Clean up stale sockets from previous run +set -euo pipefail + +# ── Clean up stale sockets from previous run ────────────────────────────────── rm -f /tmp/.X99-lock rm -f /tmp/.X11-unix/X99 -rm -rf /tmp/pulse-* -rm -f /run/user/$(id -u)/pulse/native rm -rf /tmp/weston-runtime -# Set up Weston runtime directory +# ── XDG runtime dir (required by Weston and PipeWire) ──────────────────────── export XDG_RUNTIME_DIR=/tmp/weston-runtime -mkdir -p $XDG_RUNTIME_DIR -chmod 700 $XDG_RUNTIME_DIR +mkdir -p "$XDG_RUNTIME_DIR" +chmod 700 "$XDG_RUNTIME_DIR" -# Start Weston in headless mode (handles Vulkan/DXVK rendering) +# ── D-Bus session bus (required by PipeWire / wireplumber) ─────────────────── +eval $(dbus-launch --sh-syntax) +export DBUS_SESSION_BUS_ADDRESS + +# ── Wine virtual desktop matching Weston size ──────────────────────────────── +wine reg add "HKCU\\Software\\Wine\\Explorer" /v "Desktop" /t REG_SZ /d "Default" /f +wine reg add "HKCU\\Software\\Wine\\Explorer\\Desktops" /v "Default" /t REG_SZ /d "800x600" /f +wine reg add "HKCU\\Software\\Valve\\Source\\cstrike\\Settings" /v ScreenWidth /t REG_DWORD /d 800 /f +wine reg add "HKCU\\Software\\Valve\\Source\\cstrike\\Settings" /v ScreenHeight /t REG_DWORD /d 600 /f +wine reg add "HKCU\\Software\\Wine\\WineDbg" /v ShowCrashDialog /t REG_DWORD /d 0 /f +export DXVK_CUSTOM_DISPLAY_MODES="800x600@60" + +# ── Start Weston (headless Wayland compositor) ──────────────────────────────── weston --backend=headless-backend.so --width=800 --height=600 --no-config & sleep 2 -export WAYLAND_DISPLAY=wayland-0 -# Start Xwayland — bridges Weston Wayland to X11 for ximagesrc capture -Xwayland :99 & -sleep 1 +export WAYLAND_DISPLAY=$(ls "$XDG_RUNTIME_DIR/" | grep -o 'wayland-[0-9]*' | head -1) +echo "Using WAYLAND_DISPLAY=$WAYLAND_DISPLAY" + +# ── Start Xwayland (X11 compatibility layer over Wayland) ──────────────────── +# -geometry fixes the virtual framebuffer at exactly 800x600 from the start, +# matching Weston's headless output — no xrandr mode-adding needed. +Xwayland :99 -glamor off -shm -geometry 800x600 & + +for i in $(seq 1 20); do + DISPLAY=:99 xdpyinfo > /dev/null 2>&1 && echo "Xwayland ready" && break + echo "Waiting for Xwayland... attempt $i" + sleep 1 +done export DISPLAY=:99 +echo "xrandr result: $(DISPLAY=:99 xrandr | grep '*')" -# Suppress Wine crash dialog -wine reg add "HKCU\\Software\\Wine\\WineDbg" /v ShowCrashDialog /t REG_DWORD /d 0 /f +# ── Start PipeWire stack ────────────────────────────────────────────────────── +export PULSE_RUNTIME_PATH="$XDG_RUNTIME_DIR/pulse" +rm -rf "$PULSE_RUNTIME_PATH" -# Start PulseAudio for game audio -pulseaudio --start --exit-idle-time=-1 -for i in $(seq 1 10); do - pactl info > /dev/null 2>&1 && break - echo "Waiting for PulseAudio... attempt $i" +pipewire & +sleep 1 +wireplumber & +sleep 1 +pipewire-pulse & + +# Wait for the PulseAudio-compatible socket +for i in $(seq 1 30); do + pactl info > /dev/null 2>&1 && echo "PipeWire-Pulse ready" && break + echo "Waiting for PipeWire-Pulse... attempt $i" sleep 1 done -# Virtual microphone -pactl load-module module-null-sink sink_name=mic_sink -pactl load-module module-virtual-source source_name=virtual_mic master=mic_sink.monitor -pactl set-default-source virtual_mic +export PULSE_SERVER="unix:${PULSE_RUNTIME_PATH}/native" -# Launch CSS — Wine uses Wayland driver for rendering via Weston/Vulkan +# Virtual microphone — use || true so a pre-existing module doesn't abort +# the script (set -e is active). The "Failure: No such entity" in the old +# logs was module-virtual-source failing; under PipeWire-Pulse we use +# module-null-sink + module-remap-source which is better supported. +pactl load-module module-null-sink sink_name=mic_sink sink_properties=device.description=mic_sink || true +pactl load-module module-remap-source source_name=virtual_mic master=mic_sink.monitor || true +pactl set-default-source virtual_mic || true + +# ── Launch CSS ──────────────────────────────────────────────────────────────── wine ~/Counter-Strike\ Source/revLoader.exe & -sleep 5 +for i in $(seq 1 60); do + WIN=$(DISPLAY=:99 xdotool search --name "" 2>/dev/null | head -1) + [ -n "$WIN" ] && echo "CSS window found: $WIN" && break + echo "Waiting for CSS window... attempt $i" + sleep 1 +done +sleep 3 + +# ── Start streaming agent ───────────────────────────────────────────────────── python3 ~/streaming-agent/gst_agent.py diff --git a/streaming-agent/gst_agent.py b/streaming-agent/gst_agent.py index 3a212f0..53552b0 100755 --- a/streaming-agent/gst_agent.py +++ b/streaming-agent/gst_agent.py @@ -4,14 +4,15 @@ GStreamer WebRTC Agent for CSS Streaming Two tracks published to Cloudflare Calls: - Video: ximagesrc (Xwayland :99) → VP8 → webrtcbin - - Game audio: pulsesrc → Opus → webrtcbin + - Game audio: pipewiresrc → Opus → webrtcbin CSS renders via Wine Wayland driver → Weston (Vulkan/DXVK) → Xwayland → X11 ximagesrc captures from Xwayland X11 display :99 Dependencies: apt: python3-gst-1.0 gstreamer1.0-plugins-bad gstreamer1.0-plugins-good - gstreamer1.0-plugins-ugly gstreamer1.0-libav xdotool weston xwayland + gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-pipewire + xdotool weston xwayland wireplumber pipewire pipewire-pulse pip: requests websockets """ @@ -109,19 +110,26 @@ class CSSStreamAgent: def build_pipeline(self): pipeline_str = ( - # Video: ximagesrc from Xwayland display → VP8 + # Video: ximagesrc → convert → scale → rate-enforce → VP8 + # videoconvert must come before caps negotiation; + # videoscale enforces width/height; videorate enforces framerate. f'ximagesrc display-name={DISPLAY} use-damage=0 ! ' - f'video/x-raw,framerate={CAP_FPS}/1,width={CAP_W},height={CAP_H} ! ' f'videoconvert ! ' + f'videoscale ! ' + f'videorate ! ' + f'video/x-raw,framerate={CAP_FPS}/1,width={CAP_W},height={CAP_H} ! ' f'vp8enc deadline=1 target-bitrate=1500000 keyframe-max-dist=60 ' f'error-resilient=partitions lag-in-frames=0 ! ' f'rtpvp8pay pt=96 ! ' f'application/x-rtp,media=video,encoding-name=VP8,payload=96 ! ' f'webrtcbin. ' - # Game audio: pulsesrc → Opus - f'pulsesrc ! ' - f'audio/x-raw,rate=48000,channels=2 ! ' + # Game audio: pipewiresrc → Opus + # pipewiresrc is the native PipeWire GStreamer source (Ubuntu 24+/26). + # It connects to the default audio output monitor via PipeWire directly, + # avoiding the PulseAudio compatibility layer entirely. + f'pipewiresrc ! ' + f'audio/x-raw,format=F32LE,rate=48000,channels=2 ! ' f'audioconvert ! ' f'opusenc ! ' f'rtpopuspay pt=97 ! '