diff --git a/.openclaw/memory/srs-codebase-map.md b/.openclaw/memory/srs-codebase-map.md index dbe7ea0d5..585dacbc8 100644 --- a/.openclaw/memory/srs-codebase-map.md +++ b/.openclaw/memory/srs-codebase-map.md @@ -353,7 +353,8 @@ How to verify SRS works correctly. - `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. +- `proxy-e2e-whip-test.sh` — WHIP (WebRTC) publish through proxy, then verify RTMP, HTTP-FLV, and HLS playback via the origin's `rtc_to_rtmp` bridge (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` and `proxy-e2e-whip-test.sh` when no suitable ffmpeg is found. **Summary: The Key Differences** diff --git a/.openclaw/skills/srs-develop/SKILL.md b/.openclaw/skills/srs-develop/SKILL.md index b1cc679b9..6602e1fe0 100644 --- a/.openclaw/skills/srs-develop/SKILL.md +++ b/.openclaw/skills/srs-develop/SKILL.md @@ -160,10 +160,14 @@ Only after the user confirms the routing do you proceed to Step 2. ``` bash scripts/proxy-e2e-transmux-test.sh ``` - - SRT proxy + transmuxing test (starts proxy + one SRS origin, publishes SRT, verifies SRT/RTMP/HTTP-FLV/HLS playback; WebRTC WHEP is a placeholder). Requires an ffmpeg built with libsrt — the default `brew install ffmpeg` does NOT include it; install via the homebrew-ffmpeg tap with `--with-srt` (see the script's pre-check error for the exact commands): + - SRT proxy + transmuxing test (starts proxy + one SRS origin, publishes SRT, verifies SRT/RTMP/HTTP-FLV/HLS playback; WebRTC WHEP is a placeholder). Requires an ffmpeg built with libsrt; the script auto-runs `scripts/setup-ffmpeg-with-whip.sh` to build one into `~/.local/` if no SRT-capable ffmpeg is found: ``` bash scripts/proxy-e2e-srt-test.sh ``` + - WHIP proxy + transmuxing test (starts proxy + one SRS origin, publishes WebRTC via WHIP, verifies RTMP/HTTP-FLV/HLS playback; WebRTC WHEP is a placeholder). Requires an ffmpeg with the `whip` muxer (built with `--enable-openssl`); the script auto-runs `scripts/setup-ffmpeg-with-whip.sh` if no suitable ffmpeg is found: + ``` + bash scripts/proxy-e2e-whip-test.sh + ``` 5. If any tests fail, fix the issues and re-run until all tests pass. All script paths are relative to this skill's directory. diff --git a/.openclaw/skills/srs-develop/scripts/proxy-e2e-whip-test.sh b/.openclaw/skills/srs-develop/scripts/proxy-e2e-whip-test.sh new file mode 100755 index 000000000..882a86d43 --- /dev/null +++ b/.openclaw/skills/srs-develop/scripts/proxy-e2e-whip-test.sh @@ -0,0 +1,297 @@ +#!/bin/bash +# E2E test for WHIP proxy: starts proxy + SRS origin, publishes a WebRTC +# stream via WHIP through the proxy, then verifies playback through the proxy +# in every form the origin can transmux from WebRTC (rtc_to_rtmp + http_remux +# + hls): +# - RTMP play (via rtc_to_rtmp on origin) +# - HTTP-FLV (HTTP remux of the bridged RTMP) +# - HLS (m3u8 + TS segments) +# - WebRTC WHEP (placeholder only, not actually verified here) +set -e + +SCRIPT_DIR="$(cd -P "$(dirname "$0")" && pwd)" +# Navigate: scripts/ -> srs-develop/ -> skills/ -> .openclaw/ -> srs +WORKSPACE="$(cd -P "$SCRIPT_DIR/../../../.." && pwd)" + +if [[ ! -f "$WORKSPACE/go.mod" ]]; then + echo "Error: go.mod not found in WORKSPACE: $WORKSPACE" >&2 + exit 1 +fi + +# Proxy ports — same layout as proxy-e2e-srt-test.sh / proxy-e2e-transmux-test.sh. +PROXY_RTMP_PORT=11935 +PROXY_HTTP_API_PORT=11985 +PROXY_HTTP_SERVER_PORT=18080 +PROXY_WEBRTC_PORT=18000 +PROXY_SRT_PORT=20080 +PROXY_SYSTEM_API_PORT=12025 + +# Origin ports (from origin1-for-proxy.conf). +ORIGIN_RTMP_PORT=19351 +ORIGIN_HTTP_PORT=8081 +ORIGIN_API_PORT=19851 +ORIGIN_RTC_PORT=8001 +ORIGIN_SRT_PORT=10081 + +SOURCE_FLV="$WORKSPACE/trunk/doc/source.flv" +SRS_BINARY="$WORKSPACE/trunk/objs/srs" +STREAM_URL="live/livestream" + +# WHIP endpoint exposed by the proxy. The proxy parses ?app=&stream= via +# utils.ConvertURLToStreamURL, then forwards the SDP exchange to the backend +# SRS origin. @see internal/proxy/api.go and internal/proxy/rtc.go. +WHIP_PUBLISH_URL="http://localhost:$PROXY_HTTP_API_PORT/rtc/v1/whip/?app=live&stream=livestream" + +# Make the SRS origin advertise a host candidate that loops back through the +# proxy. The proxy rewrites only the port in the SDP answer (origin RTC port +# -> proxy WebRTC port), so the candidate IP must already be reachable for +# the publisher; 127.0.0.1 works for all-local E2E. +ORIGIN_CANDIDATE="127.0.0.1" + +# PIDs to clean up on exit. +PROXY_PID="" +ORIGIN_PID="" +FFMPEG_PID="" + +cleanup() { + echo "" + echo "=== Cleaning up ===" + for pid in $PROXY_PID $ORIGIN_PID $FFMPEG_PID; do + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + kill "$pid" 2>/dev/null || true + fi + done + sleep 1 + for pid in $PROXY_PID $ORIGIN_PID $FFMPEG_PID; do + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + kill -9 "$pid" 2>/dev/null || true + fi + done + echo "Cleanup done." +} +trap cleanup EXIT + +probe_has_audio_video() { + local name="$1" + local url="$2" + + echo "Verifying $name playback: $url" + local output + 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." + else + echo "FAIL: $name no video stream detected." >&2 + echo "ffprobe output:" >&2 + echo "$output" >&2 + exit 1 + fi + + if echo "$output" | grep -q "codec_type=audio"; then + echo "PASS: $name audio stream detected." + else + echo "FAIL: $name no audio stream detected." >&2 + echo "ffprobe output:" >&2 + echo "$output" >&2 + exit 1 + fi +} + +wait_for_hls_playlist() { + local url="$1" + local deadline=60 + + echo "Waiting for HLS playlist to be generated (up to ${deadline}s): $url" + for ((i = 1; i <= deadline; i++)); do + if curl -fsS "$url" 2>/dev/null | grep -q "#EXTM3U"; then + echo "HLS playlist is ready." + return + fi + sleep 1 + done + + echo "FAIL: HLS playlist was not generated in ${deadline}s." >&2 + echo "Last HLS response:" >&2 + curl -v "$url" 2>&1 || true + exit 1 +} + +echo "=== E2E WHIP Proxy Test ===" +echo "Workspace: $WORKSPACE" +echo "Stream: $STREAM_URL" +echo "" + +# --- Pre-checks --- +if [[ ! -f "$SOURCE_FLV" ]]; then + echo "Error: test source not found: $SOURCE_FLV" >&2 + exit 1 +fi +if ! command -v curl &>/dev/null; then + echo "Error: curl not found in PATH" >&2 + exit 1 +fi + +# WHIP needs an ffmpeg with the `whip` muxer (added in ffmpeg 7.1, requires +# --enable-openssl at build time for DTLS-SRTP). Neither vanilla brew nor the +# homebrew-ffmpeg tap enable it. Resolution order: +# 1. Use ffmpeg/ffprobe from PATH if they include the whip muxer. +# 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_whip() { + local bin="$1" + [[ -x "$bin" ]] && "$bin" -hide_banner -muxers 2>/dev/null | grep -qw whip +} + +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_whip "$sys_ffmpeg"; then + FFMPEG_BIN="$sys_ffmpeg" + FFPROBE_BIN="$sys_ffprobe" + return 0 + fi + if [[ -x "$local_ffprobe" ]] && ffmpeg_has_whip "$local_ffmpeg"; then + FFMPEG_BIN="$local_ffmpeg" + FFPROBE_BIN="$local_ffprobe" + return 0 + fi + return 1 +} + +if ! resolve_ffmpeg; then + echo "No ffmpeg with WHIP muxer 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_whip "$FFMPEG_BIN"; then + echo "Error: ffmpeg still lacks WHIP muxer 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" +ALL_PORTS="$PROXY_RTMP_PORT $PROXY_HTTP_API_PORT $PROXY_HTTP_SERVER_PORT $PROXY_WEBRTC_PORT $PROXY_SRT_PORT $PROXY_SYSTEM_API_PORT $ORIGIN_RTMP_PORT $ORIGIN_HTTP_PORT $ORIGIN_API_PORT $ORIGIN_RTC_PORT $ORIGIN_SRT_PORT" +for port in $ALL_PORTS; do + lsof -ti :"$port" 2>/dev/null | xargs kill 2>/dev/null || true +done +sleep 1 + +# --- Step 1: Build proxy --- +echo "=== Step 1: Building proxy ===" +cd "$WORKSPACE" +make -s 2>&1 +echo "Proxy built: $WORKSPACE/bin/srs-proxy" + +# --- Step 2: Build SRS origin (if not already built) --- +if [[ ! -f "$SRS_BINARY" ]]; then + echo "=== Step 2: Building SRS origin ===" + cd "$WORKSPACE/trunk" + ./configure && make 2>&1 | tail -3 + echo "SRS origin built: $SRS_BINARY" +else + echo "=== Step 2: SRS origin already built ===" +fi + +# --- Step 3: Start proxy --- +echo "=== Step 3: Starting proxy (HTTP API :$PROXY_HTTP_API_PORT, WebRTC :$PROXY_WEBRTC_PORT) ===" +cd "$WORKSPACE" +env PROXY_RTMP_SERVER=$PROXY_RTMP_PORT \ + PROXY_HTTP_API=$PROXY_HTTP_API_PORT \ + PROXY_HTTP_SERVER=$PROXY_HTTP_SERVER_PORT \ + PROXY_WEBRTC_SERVER=$PROXY_WEBRTC_PORT \ + PROXY_SRT_SERVER=$PROXY_SRT_PORT \ + PROXY_SYSTEM_API=$PROXY_SYSTEM_API_PORT \ + PROXY_LOAD_BALANCER_TYPE=memory \ + ./bin/srs-proxy >/tmp/srs-proxy-whip-e2e.log 2>&1 & +PROXY_PID=$! +echo "Proxy PID: $PROXY_PID" +sleep 1 + +if ! kill -0 "$PROXY_PID" 2>/dev/null; then + echo "Error: proxy failed to start. Logs:" >&2 + cat /tmp/srs-proxy-whip-e2e.log >&2 + exit 1 +fi +echo "Proxy started." + +# --- Step 4: Start SRS origin (with CANDIDATE=$ORIGIN_CANDIDATE for WebRTC) --- +echo "=== Step 4: Starting SRS origin (CANDIDATE=$ORIGIN_CANDIDATE) ===" +ulimit -n 10000 2>/dev/null || true +cd "$WORKSPACE/trunk" +env CANDIDATE="$ORIGIN_CANDIDATE" \ + ./objs/srs -c conf/origin1-for-proxy.conf >/tmp/srs-origin-whip-e2e.log 2>&1 & +ORIGIN_PID=$! +echo "SRS origin PID: $ORIGIN_PID" + +# Wait for SRS to start and register with proxy (heartbeat interval is 9s). +echo "Waiting for SRS origin to register with proxy (up to 15s)..." +sleep 12 + +if ! kill -0 "$ORIGIN_PID" 2>/dev/null; then + echo "Error: SRS origin failed to start. Logs:" >&2 + cat /tmp/srs-origin-whip-e2e.log >&2 + exit 1 +fi +echo "SRS origin started and registered." + +# --- Step 5: Publish WHIP stream --- +# WebRTC requires H.264 (baseline-friendly) + Opus. source.flv is H.264 High +# profile + AAC, so transcode video to baseline and audio to Opus. Use +# zerolatency/ultrafast so the encoder keeps up with -re. +echo "=== Step 5: Publishing WHIP stream to proxy ===" +echo "Publish URL: $WHIP_PUBLISH_URL" +"$FFMPEG_BIN" -stream_loop -1 -re -i "$SOURCE_FLV" \ + -c:v libx264 -profile:v baseline -level 3.1 -pix_fmt yuv420p \ + -tune zerolatency -preset ultrafast \ + -c:a libopus -ar 48000 -ac 2 \ + -f whip "$WHIP_PUBLISH_URL" >/tmp/srs-ffmpeg-whip-e2e.log 2>&1 & +FFMPEG_PID=$! +echo "FFmpeg publisher PID: $FFMPEG_PID" + +# Wait for WHIP SDP exchange + DTLS-SRTP handshake + the origin's +# rtc_to_rtmp pipeline to spin up the bridged RTMP stream. +sleep 8 + +if ! kill -0 "$FFMPEG_PID" 2>/dev/null; then + echo "Error: FFmpeg WHIP publisher failed. Logs:" >&2 + cat /tmp/srs-ffmpeg-whip-e2e.log >&2 + exit 1 +fi +echo "Stream publishing." + +# --- Step 6: Verify RTMP playback (rtc_to_rtmp) --- +echo "=== Step 6: Verifying RTMP playback via proxy ===" +probe_has_audio_video "RTMP" "rtmp://localhost:$PROXY_RTMP_PORT/$STREAM_URL" + +# --- Step 7: Verify HTTP-FLV playback --- +echo "=== Step 7: Verifying HTTP-FLV playback via proxy ===" +probe_has_audio_video "HTTP-FLV" "http://localhost:$PROXY_HTTP_SERVER_PORT/$STREAM_URL.flv" + +# --- Step 8: Verify HLS playback --- +echo "=== Step 8: Verifying HLS playback via proxy ===" +HLS_URL="http://localhost:$PROXY_HTTP_SERVER_PORT/$STREAM_URL.m3u8" +wait_for_hls_playlist "$HLS_URL" +probe_has_audio_video "HLS" "$HLS_URL" + +# --- Step 9: WebRTC WHEP playback (placeholder) --- +echo "=== Step 9: WebRTC WHEP playback (placeholder) ===" +echo "SKIP: WebRTC WHEP playback is not verified by this script." +echo " The origin has rtmp_to_rtc enabled, so WHIP->RTMP->RTC should work end-to-end," +echo " but actual playback verification is intentionally left as a TODO here." + +echo "" +echo "=== E2E WHIP Proxy Test PASSED ==="