From d1fc8c12538913df71f719e557d64abe0a4ca689 Mon Sep 17 00:00:00 2001 From: winlin Date: Sun, 10 May 2026 19:33:32 -0400 Subject: [PATCH] Proxy: Add ffmpeg-from-source builder; auto-fallback in SRT E2E test. Co-Authored-By: Claude Opus 4.7 (1M context) --- .openclaw/memory/srs-codebase-map.md | 3 +- .../srs-develop/scripts/proxy-e2e-srt-test.sh | 72 ++++-- .../scripts/setup-ffmpeg-with-whip.sh | 232 ++++++++++++++++++ 3 files changed, 283 insertions(+), 24 deletions(-) create mode 100755 .openclaw/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh diff --git a/.openclaw/memory/srs-codebase-map.md b/.openclaw/memory/srs-codebase-map.md index 8dfb1ab5c..dbe7ea0d5 100644 --- a/.openclaw/memory/srs-codebase-map.md +++ b/.openclaw/memory/srs-codebase-map.md @@ -346,13 +346,14 @@ How to verify SRS works correctly. - Reconnecting Load Test - Janus -`.openclaw/skills/srs-develop/scripts/` — Go proxy verification scripts: +`.openclaw/skills/srs-develop/scripts/` — Go proxy verification and setup scripts: - `proxy-utest.sh` — Runs Go proxy unit tests with optional coverage. - `proxy-e2e-test.sh` — Single-origin RTMP proxy E2E test. - `proxy-e2e-cluster-test.sh` — Multi-origin memory load-balancer E2E test. - `proxy-e2e-redis-test.sh` — Multi-proxy Redis load-balancer E2E test. - `proxy-e2e-transmux-test.sh` — RTMP publish through proxy, then verify RTMP, HTTP-FLV, HLS, and WebRTC playback. - `proxy-e2e-srt-test.sh` — SRT publish through proxy, then verify SRT, RTMP, HTTP-FLV, and HLS playback (WebRTC WHEP is a placeholder). +- `setup-ffmpeg-with-whip.sh` — macOS-only: build ffmpeg from source into `~/.local/` with WHIP (openssl DTLS) and SRT support; auto-invoked by `proxy-e2e-srt-test.sh` when no SRT-capable ffmpeg is found. **Summary: The Key Differences** diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-srt-test.sh b/.openclaw/skills/srs-develop/scripts/proxy-e2e-srt-test.sh index d117b98f3..3bd387e8a 100755 --- a/.openclaw/skills/srs-develop/scripts/proxy-e2e-srt-test.sh +++ b/.openclaw/skills/srs-develop/scripts/proxy-e2e-srt-test.sh @@ -71,7 +71,7 @@ probe_has_audio_video() { echo "Verifying $name playback: $url" local output - output=$(ffprobe -v error -show_streams "$url" 2>&1 || true) + output=$("$FFPROBE_BIN" -v error -show_streams "$url" 2>&1 || true) if echo "$output" | grep -q "codec_type=video"; then echo "PASS: $name video stream detected." @@ -121,33 +121,59 @@ if [[ ! -f "$SOURCE_FLV" ]]; then echo "Error: test source not found: $SOURCE_FLV" >&2 exit 1 fi -if ! command -v ffmpeg &>/dev/null; then - echo "Error: ffmpeg not found in PATH" >&2 - exit 1 -fi -if ! command -v ffprobe &>/dev/null; then - echo "Error: ffprobe not found in PATH" >&2 - exit 1 -fi if ! command -v curl &>/dev/null; then echo "Error: curl not found in PATH" >&2 exit 1 fi + # SRT URLs need libsrt compiled into ffmpeg/ffprobe. The default Homebrew -# ffmpeg formula does NOT include libsrt — install the homebrew-ffmpeg tap -# build with the explicit --with-srt option instead: -# brew tap homebrew-ffmpeg/ffmpeg -# brew uninstall ffmpeg # if vanilla ffmpeg is already installed -# brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-srt -if ! ffmpeg -hide_banner -protocols 2>/dev/null | grep -qw srt; then - echo "Error: ffmpeg was built without SRT protocol support." >&2 - echo "The default 'brew install ffmpeg' does NOT include libsrt." >&2 - echo "Install the homebrew-ffmpeg tap build with --with-srt instead:" >&2 - echo " brew tap homebrew-ffmpeg/ffmpeg" >&2 - echo " brew uninstall ffmpeg # if vanilla ffmpeg is already installed" >&2 - echo " brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-srt" >&2 - exit 1 +# ffmpeg formula does NOT include libsrt. Resolution order: +# 1. Use ffmpeg/ffprobe from PATH if they support SRT. +# 2. Otherwise, use ~/.local/bin/ffmpeg/ffprobe if previously built there. +# 3. Otherwise, build from source via setup-ffmpeg-with-whip.sh (installs +# into ~/.local/) and use the freshly built binaries. +ffmpeg_has_srt() { + local bin="$1" + [[ -x "$bin" ]] && "$bin" -hide_banner -protocols 2>/dev/null | grep -qw srt +} + +resolve_ffmpeg() { + local sys_ffmpeg sys_ffprobe local_ffmpeg local_ffprobe + sys_ffmpeg="$(command -v ffmpeg || true)" + sys_ffprobe="$(command -v ffprobe || true)" + local_ffmpeg="$HOME/.local/bin/ffmpeg" + local_ffprobe="$HOME/.local/bin/ffprobe" + + if [[ -n "$sys_ffprobe" ]] && ffmpeg_has_srt "$sys_ffmpeg"; then + FFMPEG_BIN="$sys_ffmpeg" + FFPROBE_BIN="$sys_ffprobe" + return 0 + fi + if [[ -x "$local_ffprobe" ]] && ffmpeg_has_srt "$local_ffmpeg"; then + FFMPEG_BIN="$local_ffmpeg" + FFPROBE_BIN="$local_ffprobe" + return 0 + fi + return 1 +} + +if ! resolve_ffmpeg; then + echo "No ffmpeg with SRT support found on PATH or in ~/.local/bin." + echo "Building ffmpeg from source via setup-ffmpeg-with-whip.sh — this can take several minutes." + bash "$SCRIPT_DIR/setup-ffmpeg-with-whip.sh" + FFMPEG_BIN="$HOME/.local/bin/ffmpeg" + FFPROBE_BIN="$HOME/.local/bin/ffprobe" + if ! ffmpeg_has_srt "$FFMPEG_BIN"; then + echo "Error: ffmpeg still lacks SRT support after running setup-ffmpeg-with-whip.sh." >&2 + exit 1 + fi + if [[ ! -x "$FFPROBE_BIN" ]]; then + echo "Error: ffprobe missing at $FFPROBE_BIN after running setup-ffmpeg-with-whip.sh." >&2 + exit 1 + fi fi +echo "ffmpeg : $FFMPEG_BIN" +echo "ffprobe: $FFPROBE_BIN" # --- Step 0: Clean up stale state --- rm -f "$WORKSPACE/trunk/objs/origin1.pid" @@ -217,7 +243,7 @@ echo "SRS origin started and registered." # --- Step 5: Publish SRT stream --- echo "=== Step 5: Publishing SRT stream to proxy ===" echo "Publish URL: $SRT_PUBLISH_URL" -ffmpeg -stream_loop -1 -re -i "$SOURCE_FLV" -c copy -f mpegts \ +"$FFMPEG_BIN" -stream_loop -1 -re -i "$SOURCE_FLV" -c copy -f mpegts \ "$SRT_PUBLISH_URL" >/tmp/srs-ffmpeg-srt-e2e.log 2>&1 & FFMPEG_PID=$! echo "FFmpeg publisher PID: $FFMPEG_PID" diff --git a/.openclaw/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh b/.openclaw/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh new file mode 100755 index 000000000..bd50eff14 --- /dev/null +++ b/.openclaw/skills/srs-develop/scripts/setup-ffmpeg-with-whip.sh @@ -0,0 +1,232 @@ +#!/bin/bash +# Build ffmpeg from source with the codecs and protocols the SRS proxy E2E +# tests need — in particular WHIP (WebRTC-HTTP Ingestion Protocol), which the +# default Homebrew formulas (vanilla + homebrew-ffmpeg tap) do not enable. +# +# Modelled on the ossrs/dev-docker ubuntu20 base images: +# https://github.com/ossrs/dev-docker/blob/ubuntu20/Dockerfile.base +# https://github.com/ossrs/dev-docker/blob/ubuntu20/Dockerfile.base2 +# https://github.com/ossrs/dev-docker/blob/ubuntu20/Dockerfile.base3 +# The Dockerfiles describe *which* libraries and configure flags to enable; +# on macOS we install those deps via Homebrew (shared libs) instead of +# rebuilding each one from tarballs. ffmpeg itself is built from source so we +# can pass --enable-libsrtp / --enable-openssl, which neither Homebrew formula +# turns on. +# +# Output: +# ~/.local/src/ffmpeg (git clone) +# ~/.local/bin/{ffmpeg,ffprobe,ffplay} +# ~/.local/lib, ~/.local/share, ... (ffmpeg --prefix tree) +# +# Re-running is safe: deps already installed are skipped, the git clone is +# reused (fetch + checkout), and ffmpeg is rebuilt incrementally. +set -e + +FFMPEG_TAG="${FFMPEG_TAG:-n8.1.1}" +PREFIX="${PREFIX:-$HOME/.local}" +SRC_DIR="${SRC_DIR:-$HOME/.local/src/ffmpeg}" +JOBS="${JOBS:-$(sysctl -n hw.ncpu 2>/dev/null || echo 4)}" + +REQUIRED_BREW_PKGS=( + # build toolchain (Homebrew renamed pkg-config → pkgconf in 2024) + pkgconf nasm + # WHIP needs DTLS (openssl). ffmpeg's WHIP muxer uses ffmpeg's *internal* + # SRTP implementation (srtp_protocol_select="rtp_protocol srtp" in configure) + # — no external libsrtp dependency, so no extra brew package here. + openssl@3 + # SRT + srt + # video codecs (matches Dockerfile.base / .base2) + x264 x265 libvpx + # audio codecs (fdk-aac requires --enable-nonfree below) + fdk-aac lame opus + # subtitle / font stack (matches Dockerfile.base3) + freetype fontconfig harfbuzz fribidi libass +) + +log() { printf '\n=== %s ===\n' "$*"; } + +# --- Pre-checks --------------------------------------------------------------- + +if [[ "$(uname -s)" != "Darwin" ]]; then + echo "Error: this script targets macOS. For Linux, follow Dockerfile.base/2/3 directly." >&2 + exit 1 +fi + +if [[ "$(id -u)" -eq 0 ]]; then + echo "Error: do not run as root. Homebrew refuses, and the prefix is your \$HOME." >&2 + exit 1 +fi + +if ! command -v brew &>/dev/null; then + echo "Error: Homebrew not found. Install from https://brew.sh and retry." >&2 + exit 1 +fi + +if ! command -v git &>/dev/null; then + echo "Error: git not found in PATH." >&2 + exit 1 +fi + +log "Configuration" +echo "FFmpeg tag : $FFMPEG_TAG" +echo "Prefix : $PREFIX" +echo "Source dir : $SRC_DIR" +echo "Parallel : $JOBS jobs" + +# --- Step 1: Install Homebrew deps ------------------------------------------- + +log "Step 1: Installing Homebrew dependencies" +INSTALLED="$(brew list --formula 2>/dev/null || true)" +TO_INSTALL=() +for pkg in "${REQUIRED_BREW_PKGS[@]}"; do + if echo "$INSTALLED" | grep -qx "$pkg"; then + echo " ok $pkg" + else + echo " miss $pkg" + TO_INSTALL+=("$pkg") + fi +done + +if [[ ${#TO_INSTALL[@]} -gt 0 ]]; then + echo "" + echo "Installing missing packages: ${TO_INSTALL[*]}" + brew install "${TO_INSTALL[@]}" +else + echo "" + echo "All required Homebrew packages already installed." +fi + +# --- Step 2: Clone or refresh ffmpeg source ---------------------------------- + +log "Step 2: Fetching ffmpeg source at $FFMPEG_TAG" +mkdir -p "$(dirname "$SRC_DIR")" +if [[ ! -d "$SRC_DIR/.git" ]]; then + # Shallow clone of just the tag we want — full history is ~600 MB and + # takes minutes to index; this drops to ~80 MB and seconds. + git clone --depth 1 --branch "$FFMPEG_TAG" \ + https://github.com/FFmpeg/FFmpeg.git "$SRC_DIR" +else + cd "$SRC_DIR" + git fetch --depth 1 --tags --quiet origin "$FFMPEG_TAG" +fi +cd "$SRC_DIR" +git checkout --quiet "$FFMPEG_TAG" +echo "Checked out: $(git describe --tags --always)" + +# --- Step 3: Configure -------------------------------------------------------- + +log "Step 3: Configuring ffmpeg" + +# openssl@3 is keg-only in Homebrew, so its .pc files are not on the default +# PKG_CONFIG_PATH. Other deps (lame in particular) ship no .pc at all, so +# also pass --extra-cflags / --extra-ldflags pointing at the Homebrew prefix +# (works for both Apple Silicon /opt/homebrew and Intel /usr/local). +BREW_PREFIX="$(brew --prefix)" +OPENSSL_PREFIX="$(brew --prefix openssl@3)" +export PKG_CONFIG_PATH="$OPENSSL_PREFIX/lib/pkgconfig:${PKG_CONFIG_PATH:-}" +echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH" + +# --enable-nonfree : required by --enable-libfdk-aac +# --enable-gpl : required by --enable-libx264 / --enable-libx265 / --enable-libass +# --enable-version3 : allow (L)GPLv3 components alongside GPL/nonfree +# --enable-openssl : DTLS backend; the WHIP muxer needs this for the +# DTLS-SRTP handshake. ffmpeg's WHIP muxer uses ffmpeg's +# *internal* SRTP (libavformat/srtp.c), not external +# libsrtp — so no --enable-libsrtp flag exists/is needed. +# --enable-libsrt : SRT protocol (publish/play) +./configure \ + --prefix="$PREFIX" \ + --extra-cflags="-I$BREW_PREFIX/include" \ + --extra-ldflags="-L$BREW_PREFIX/lib" \ + --enable-gpl \ + --enable-nonfree \ + --enable-version3 \ + --enable-openssl \ + --enable-libsrt \ + --enable-libx264 \ + --enable-libx265 \ + --enable-libvpx \ + --enable-libfdk-aac \ + --enable-libmp3lame \ + --enable-libopus \ + --enable-libass \ + --enable-libfreetype \ + --enable-libfontconfig \ + --enable-libharfbuzz \ + --enable-libfribidi \ + --enable-videotoolbox \ + --enable-audiotoolbox \ + --disable-debug + +# --- Step 4: Build and install ----------------------------------------------- + +log "Step 4: Building ffmpeg ($JOBS jobs)" +make -j"$JOBS" + +log "Step 5: Installing to $PREFIX" +make install + +# --- Step 5: Verify ----------------------------------------------------------- + +log "Step 6: Verifying installed binary" +FFMPEG_BIN="$PREFIX/bin/ffmpeg" +if [[ ! -x "$FFMPEG_BIN" ]]; then + echo "Error: $FFMPEG_BIN missing after install." >&2 + exit 1 +fi + +echo "" +"$FFMPEG_BIN" -version | head -2 +echo "" + +if "$FFMPEG_BIN" -hide_banner -muxers 2>/dev/null | grep -qw whip; then + echo "PASS: WHIP muxer is available." +else + echo "FAIL: WHIP muxer is NOT in the installed ffmpeg." >&2 + exit 1 +fi + +if "$FFMPEG_BIN" -hide_banner -protocols 2>/dev/null | grep -qw srt; then + echo "PASS: SRT protocol is available." +else + echo "FAIL: SRT protocol is NOT in the installed ffmpeg." >&2 + exit 1 +fi + +if "$FFMPEG_BIN" -hide_banner -codecs 2>/dev/null | grep -E "libx264|libx265" | grep -q libx264; then + echo "PASS: libx264 encoder is available." +else + echo "FAIL: libx264 encoder missing." >&2 + exit 1 +fi + +if "$FFMPEG_BIN" -hide_banner -codecs 2>/dev/null | grep -q libx265; then + echo "PASS: libx265 encoder is available." +else + echo "FAIL: libx265 encoder missing." >&2 + exit 1 +fi + +echo "" +log "Done" +echo "Binary: $FFMPEG_BIN" +echo "Version: $("$FFMPEG_BIN" -version | head -1)" +echo "" + +# Warn if PATH won't pick up the new binary. +if ! echo ":$PATH:" | grep -q ":$PREFIX/bin:"; then + echo "NOTE: $PREFIX/bin is not on your PATH." + echo " Add this to ~/.zshrc (or your shell rc):" + echo " export PATH=\"\$HOME/.local/bin:\$PATH\"" + echo " Then 'which ffmpeg' should resolve to $FFMPEG_BIN." +else + RESOLVED="$(command -v ffmpeg || true)" + if [[ "$RESOLVED" == "$FFMPEG_BIN" ]]; then + echo "PATH check: 'ffmpeg' resolves to $RESOLVED ✓" + else + echo "NOTE: $PREFIX/bin is on PATH but 'ffmpeg' currently resolves to:" + echo " $RESOLVED" + echo " Reorder PATH so $PREFIX/bin comes before $(dirname "$RESOLVED")." + fi +fi