#!/usr/bin/env bash set -euo pipefail ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)" BUILD_DIR="${BUILD_DIR:-$ROOT_DIR/build}" STREAMER_BIN="${STREAMER_BIN:-$BUILD_DIR/cvmmap_streamer}" SRS_ROOT="${SRS_ROOT:-$HOME/Code/srs}" SRS_BIN="${SRS_BIN:-$SRS_ROOT/trunk/objs/srs}" SRS_CONF="${SRS_CONF:-$SRS_ROOT/trunk/conf/srs.conf}" FFPROBE_BIN="${FFPROBE_BIN:-ffprobe}" FFMPEG_BIN="${FFMPEG_BIN:-ffmpeg}" CURL_BIN="${CURL_BIN:-curl}" INPUT_URI="${INPUT_URI:-}" STREAM_NAME="${STREAM_NAME:-}" CODEC="${CODEC:-h264}" ENCODER_BACKEND="${ENCODER_BACKEND:-ffmpeg}" ENCODER_DEVICE="${ENCODER_DEVICE:-nvidia}" RTMP_TRANSPORT="${RTMP_TRANSPORT:-libavformat}" INGEST_MAX_FRAMES="${INGEST_MAX_FRAMES:-120}" PROBE_TIMEOUT_S="${PROBE_TIMEOUT_S:-20}" DECODE_FRAMES="${DECODE_FRAMES:-15}" SRS_MAX_CONNECTIONS="${SRS_MAX_CONNECTIONS:-64}" RUN_DIR="${RUN_DIR:-$BUILD_DIR/live_srs_forward_smoke_$(date +%Y%m%d_%H%M%S)}" mkdir -p "$RUN_DIR" SRS_LOG="$RUN_DIR/srs.log" STREAMER_LOG="$RUN_DIR/streamer.log" HTTP_PROBE_LOG="$RUN_DIR/ffprobe_httpflv.log" DECODE_LOG="$RUN_DIR/ffmpeg_decode_httpflv.log" API_LOG="$RUN_DIR/srs_api_streams.json" SRS_TEMP_CONF="$RUN_DIR/srs.conf" RTMP_URL="rtmp://127.0.0.1/live/${STREAM_NAME}" HTTP_FLV_URL="http://127.0.0.1:8080/live/${STREAM_NAME}.flv" SRS_PID="" STREAMER_PID="" STARTED_SRS=0 require_bin() { local path="$1" local label="$2" if ! command -v "$path" >/dev/null 2>&1 && [[ ! -x "$path" ]]; then echo "$label not found: $path" >&2 exit 1 fi } usage() { cat <<'EOF' Usage: live_srs_forward_smoke.sh [stream-name] Environment overrides: INPUT_URI cvmmap source URI, if positional argument is omitted STREAM_NAME RTMP/HTTP-FLV stream name, default derived from INPUT_URI CODEC h264|h265 ENCODER_BACKEND auto|ffmpeg ENCODER_DEVICE auto|nvidia|software RTMP_TRANSPORT libavformat|ffmpeg_process INGEST_MAX_FRAMES bounded frame count for the smoke DECODE_FRAMES frames to decode from HTTP-FLV after probe SRS_ROOT local SRS checkout, default ~/Code/srs EOF } derive_stream_name() { local uri="$1" local body="${uri#cvmmap://}" local instance="${body%%@*}" instance="${instance%%\?*}" instance="${instance%%/*}" if [[ -z "$instance" || "$instance" == "$uri" ]]; then instance="stream" fi instance="${instance//[^a-zA-Z0-9_-]/_}" printf '%s_live' "$instance" } wait_for_http() { local url="$1" local attempts="$2" for _ in $(seq 1 "$attempts"); do if "${CURL_BIN}" -fsS "$url" >/dev/null 2>&1; then return 0 fi sleep 0.25 done return 1 } wait_for_http_flv() { local attempts="$1" for _ in $(seq 1 "$attempts"); do if "${FFPROBE_BIN}" \ -v error \ -select_streams v:0 \ -show_entries stream=index,codec_name,width,height,avg_frame_rate \ -of default=noprint_wrappers=1 \ "$HTTP_FLV_URL" >"$HTTP_PROBE_LOG" 2>&1; then return 0 fi if [[ -n "$STREAMER_PID" ]] && ! kill -0 "$STREAMER_PID" >/dev/null 2>&1; then return 1 fi sleep 0.25 done return 1 } print_failure_context() { echo "--- artifacts ---" >&2 echo "RUN_DIR=$RUN_DIR" >&2 echo "--- streamer log tail ---" >&2 tail -n 120 "$STREAMER_LOG" >&2 || true echo "--- srs log tail ---" >&2 tail -n 120 "$SRS_LOG" >&2 || true } cleanup() { if [[ -n "$STREAMER_PID" ]] && kill -0 "$STREAMER_PID" >/dev/null 2>&1; then kill "$STREAMER_PID" >/dev/null 2>&1 || true wait "$STREAMER_PID" >/dev/null 2>&1 || true fi if [[ "$STARTED_SRS" -eq 1 ]] && [[ -n "$SRS_PID" ]] && kill -0 "$SRS_PID" >/dev/null 2>&1; then kill "$SRS_PID" >/dev/null 2>&1 || true wait "$SRS_PID" >/dev/null 2>&1 || true fi } trap cleanup EXIT require_bin "$STREAMER_BIN" "streamer binary" require_bin "$SRS_BIN" "SRS binary" require_bin "$FFPROBE_BIN" "ffprobe" require_bin "$FFMPEG_BIN" "ffmpeg" require_bin "$CURL_BIN" "curl" if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then usage exit 0 fi if [[ -n "${1:-}" ]]; then INPUT_URI="$1" fi if [[ -n "${2:-}" ]]; then STREAM_NAME="$2" fi if [[ -z "$INPUT_URI" ]]; then echo "input URI is required" >&2 usage >&2 exit 1 fi if [[ -z "$STREAM_NAME" ]]; then STREAM_NAME="$(derive_stream_name "$INPUT_URI")" fi RTMP_URL="rtmp://127.0.0.1/live/${STREAM_NAME}" HTTP_FLV_URL="http://127.0.0.1:8080/live/${STREAM_NAME}.flv" if [[ ! -f "$SRS_CONF" ]]; then echo "SRS config not found: $SRS_CONF" >&2 exit 1 fi if wait_for_http "http://127.0.0.1:1985/api/v1/versions" 4; then echo "Reusing running SRS on 127.0.0.1:1935" else sed \ -e 's/^daemon[[:space:]]\+on;/daemon off;/' \ -e "s/^max_connections[[:space:]]\\+[0-9]\\+;/max_connections ${SRS_MAX_CONNECTIONS};/" \ "$SRS_CONF" >"$SRS_TEMP_CONF" ( cd "$SRS_ROOT/trunk" "$SRS_BIN" -c "$SRS_TEMP_CONF" >"$SRS_LOG" 2>&1 & echo $! >"$RUN_DIR/srs.pid" ) SRS_PID="$(cat "$RUN_DIR/srs.pid")" STARTED_SRS=1 if ! wait_for_http "http://127.0.0.1:1985/api/v1/versions" 40; then echo "SRS failed to become ready on 127.0.0.1:1985" >&2 print_failure_context exit 1 fi fi "$STREAMER_BIN" \ --run-mode pipeline \ --input-uri "$INPUT_URI" \ --codec "$CODEC" \ --encoder-backend "$ENCODER_BACKEND" \ --encoder-device "$ENCODER_DEVICE" \ --rtmp \ --rtmp-url "$RTMP_URL" \ --rtmp-transport "$RTMP_TRANSPORT" \ --ingest-max-frames "$INGEST_MAX_FRAMES" \ >"$STREAMER_LOG" 2>&1 & STREAMER_PID=$! if ! wait_for_http_flv $((PROBE_TIMEOUT_S * 4)); then echo "HTTP-FLV stream never became probeable: $HTTP_FLV_URL" >&2 print_failure_context exit 1 fi if ! "$FFMPEG_BIN" -v error -i "$HTTP_FLV_URL" -an -frames:v "$DECODE_FRAMES" -f null - >"$DECODE_LOG" 2>&1; then echo "warning: HTTP-FLV decode sample failed, see $DECODE_LOG" >&2 fi "$CURL_BIN" -fsSL "http://127.0.0.1:1985/api/v1/streams/" >"$API_LOG" 2>/dev/null || true wait "$STREAMER_PID" STREAMER_RC=$? STREAMER_PID="" echo "RUN_DIR=$RUN_DIR" echo "STREAMER_RC=$STREAMER_RC" echo "INPUT_URI=$INPUT_URI" echo "RTMP_URL=$RTMP_URL" echo "HTTP_FLV_URL=$HTTP_FLV_URL" echo "--- http-flv ffprobe ---" cat "$HTTP_PROBE_LOG" echo "--- http-flv decode log ---" cat "$DECODE_LOG" echo "--- srs api streams ---" cat "$API_LOG" 2>/dev/null || true echo "--- streamer log tail ---" tail -n 80 "$STREAMER_LOG"