diff --git a/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/README.md b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/README.md new file mode 100644 index 00000000..6f752886 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/README.md @@ -0,0 +1,170 @@ +# Waylandsink_Playback (GStreamer) — Runner Test + +This directory contains the **Waylandsink_Playback** validation test for Qualcomm Linux Testkit runners. + +It validates **Wayland display** using **GStreamer waylandsink** with: +- Weston/Wayland server connectivity checks +- DRM display connectivity validation +- Video playback using `waylandsink` element +- Uses `videotestsrc` to generate test patterns + +The script is designed to be **CI/LAVA-friendly**: +- Writes **PASS/FAIL/SKIP** into `Waylandsink_Playback.res` +- Always **exits 0** (even on FAIL/SKIP) +- Comprehensive Weston/Wayland environment detection +- Automatic Weston startup if needed + +--- + +## What this test does + +1. Sources framework utilities (`functestlib.sh`, `lib_gstreamer.sh`, `lib_display.sh`) +2. **Display connectivity check**: Verifies connected DRM display via sysfs +3. **Weston/Wayland server check**: + - Discovers existing Wayland socket + - Attempts to start Weston if no socket found + - Validates Wayland connection +4. **waylandsink element check**: Verifies GStreamer waylandsink is available +5. **Playback test**: Runs videotestsrc → videoconvert → waylandsink pipeline +6. **Validation**: Checks playback duration and exit code + +--- + +## PASS / FAIL / SKIP criteria + +### PASS +- Playback completes successfully (exit code 0 or 143) +- Elapsed time ≥ (duration - 2) seconds + +### FAIL +- Playback exits with error code (not 0 or 143) +- Playback exits too quickly (< duration - 2 seconds) + +### SKIP +- Missing GStreamer tools (`gst-launch-1.0`, `gst-inspect-1.0`) +- No connected DRM display found +- No Wayland socket found (and cannot start Weston) +- Wayland connection test fails +- `waylandsink` element not available + +--- + +## Dependencies + +### Required +- `gst-launch-1.0` +- `gst-inspect-1.0` +- `videotestsrc` GStreamer plugin +- `videoconvert` GStreamer plugin +- `waylandsink` GStreamer plugin + +### Display/Wayland +- Weston compositor (running or startable) +- Connected DRM display +- Wayland socket (`/run/user/*/wayland-*` or `/dev/socket/weston/wayland-*`) + +--- + +## Usage + +```bash +./run.sh [options] +``` + +### Options + +- `--resolution ` - Video resolution (e.g., 1920x1080, 3840x2160) (default: 1920x1080) +- `--duration ` - Playback duration (default: 30) +- `--pattern ` - videotestsrc pattern (default: smpte) +- `--width ` - Video width (alternative to --resolution) (default: 1920) +- `--height ` - Video height (alternative to --resolution) (default: 1080) +- `--framerate ` - Video framerate (default: 30) +- `--gst-debug ` - GStreamer debug level 1-9 (default: 2) + +--- + +## Examples + +```bash +# Run default test (1920x1080 SMPTE for 30s) +./run.sh + +# Run with custom resolution using --resolution +./run.sh --resolution 3840x2160 + +# Run with custom resolution and duration +./run.sh --resolution 3840x2160 --duration 20 + +# Run with ball pattern +./run.sh --pattern ball + +# Run with custom resolution using separate width/height +./run.sh --width 1280 --height 720 + +# Run with different framerate +./run.sh --framerate 60 + +# Run with higher debug level +./run.sh --gst-debug 5 +``` + +--- + +## Pipeline + +``` +videotestsrc num-buffers= pattern= + ! video/x-raw,width=,height=,framerate=/1 + ! videoconvert + ! waylandsink +``` + +--- + +## Logs + +``` +./Waylandsink_Playback.res +./logs/Waylandsink_Playback/ + gst.log # GStreamer debug output + run.log # Pipeline execution log +``` + +--- + +## Troubleshooting + +### "SKIP: No connected DRM display found" +- Check physical display connection +- Verify DRM drivers loaded: `ls -l /dev/dri/` + +### "SKIP: No Wayland socket found" +- Check if Weston is running: `pgrep weston` +- Try starting Weston manually +- Check `XDG_RUNTIME_DIR` and `WAYLAND_DISPLAY` environment variables + +### "SKIP: waylandsink element not available" +- Install GStreamer Wayland plugin +- Check: `gst-inspect-1.0 waylandsink` + +### "FAIL: Playback failed" +- Check logs in `logs/Waylandsink_Playback/` +- Increase debug level: `./run.sh --gst-debug 5` +- Verify Weston is running properly + +--- + +## LAVA Environment Variables + +The test supports these environment variables (can be set in LAVA job definition): + +- `VIDEO_DURATION` - Playback duration in seconds (default: 30) +- `RUNTIMESEC` - Alternative to VIDEO_DURATION +- `VIDEO_PATTERN` - videotestsrc pattern (default: smpte) +- `VIDEO_WIDTH` - Video width (default: 1920) +- `VIDEO_HEIGHT` - Video height (default: 1080) +- `VIDEO_FRAMERATE` - Video framerate (default: 30) +- `VIDEO_GST_DEBUG` - GStreamer debug level (default: 2) +- `GST_DEBUG_LEVEL` - Alternative to VIDEO_GST_DEBUG + +**Priority order for duration**: `VIDEO_DURATION` > `RUNTIMESEC` > default (30) diff --git a/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/Waylandsink_Playback.yaml b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/Waylandsink_Playback.yaml new file mode 100644 index 00000000..fcadd9b8 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/Waylandsink_Playback.yaml @@ -0,0 +1,28 @@ +metadata: + name: Waylandsink_Playback + format: "Lava-Test Test Definition 1.0" + description: > + GStreamer waylandsink playback validation with Weston/Wayland server checks + on Qualcomm Linux platforms. Uses videotestsrc to generate test patterns + and displays them via waylandsink. Validates display connectivity and + Wayland compositor functionality. + os: + - linux + scope: + - functional + +params: + VIDEO_DURATION: "30" # seconds + VIDEO_PATTERN: "smpte" # smpte|snow|black|white|red|green|blue|checkers-1|checkers-2|ball + VIDEO_WIDTH: "1920" # pixels + VIDEO_HEIGHT: "1080" # pixels + VIDEO_FRAMERATE: "30" # fps + VIDEO_GST_DEBUG: "2" # 1-9 + +run: + steps: + - REPO_PATH="$PWD" + - cd Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/ + - export VIDEO_DURATION VIDEO_PATTERN VIDEO_WIDTH VIDEO_HEIGHT VIDEO_FRAMERATE VIDEO_GST_DEBUG + - ./run.sh --resolution "${VIDEO_WIDTH}x${VIDEO_HEIGHT}" --pattern "${VIDEO_PATTERN}" --duration "${VIDEO_DURATION}" --framerate "${VIDEO_FRAMERATE}" --gst-debug "${VIDEO_GST_DEBUG}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Waylandsink_Playback.res || true diff --git a/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/run.sh b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/run.sh new file mode 100755 index 00000000..8c18ee1b --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/run.sh @@ -0,0 +1,554 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# Waylandsink Playback validation using GStreamer +# Tests video playback using waylandsink with videotestsrc +# Validates Weston/Wayland server and display connectivity +# CI/LAVA-friendly (always exits 0, writes .res file) + +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" + +TESTNAME="Waylandsink_Playback" +RES_FILE="${SCRIPT_DIR}/${TESTNAME}.res" +LOG_DIR="${SCRIPT_DIR}/logs" +OUTDIR="$LOG_DIR/$TESTNAME" +GST_LOG="$OUTDIR/gst.log" +RUN_LOG="$OUTDIR/run.log" + +mkdir -p "$OUTDIR" >/dev/null 2>&1 || true +: >"$RES_FILE" +: >"$GST_LOG" +: >"$RUN_LOG" + +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" + +INIT_ENV="" +SEARCH="$SCRIPT_DIR" +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +RES_FILE="$SCRIPT_DIR/${TESTNAME}.res" + +if [ -z "${INIT_ENV:-}" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + echo "$TESTNAME SKIP" >"$RES_FILE" 2>/dev/null || true + exit 0 +fi + +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" + +# shellcheck disable=SC1091 +. "$TOOLS/lib_gstreamer.sh" + +# shellcheck disable=SC1091 +[ -f "$TOOLS/lib_display.sh" ] && . "$TOOLS/lib_display.sh" + +result="FAIL" +reason="unknown" + +# -------------------- Defaults -------------------- +# Validate environment variables if set +if [ -n "${VIDEO_DURATION:-}" ] && ! echo "$VIDEO_DURATION" | grep -q "^[0-9]\+$"; then + log_warn "VIDEO_DURATION must be numeric (got '$VIDEO_DURATION')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi +if [ -n "${RUNTIMESEC:-}" ] && ! echo "$RUNTIMESEC" | grep -q "^[0-9]\+$"; then + log_warn "RUNTIMESEC must be numeric (got '$RUNTIMESEC')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi +if [ -n "${VIDEO_FRAMERATE:-}" ] && ! echo "$VIDEO_FRAMERATE" | grep -q "^[0-9]\+$"; then + log_warn "VIDEO_FRAMERATE must be numeric (got '$VIDEO_FRAMERATE')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi +if [ -n "${VIDEO_GST_DEBUG:-${GST_DEBUG_LEVEL:-}}" ] && ! echo "${VIDEO_GST_DEBUG:-${GST_DEBUG_LEVEL:-}}" | grep -q "^[0-9]\+$"; then + log_warn "GST debug level must be numeric (got '${VIDEO_GST_DEBUG:-${GST_DEBUG_LEVEL:-}}')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +duration="${VIDEO_DURATION:-${RUNTIMESEC:-30}}" +pattern="${VIDEO_PATTERN:-smpte}" +width="${VIDEO_WIDTH:-1920}" +height="${VIDEO_HEIGHT:-1080}" +framerate="${VIDEO_FRAMERATE:-30}" +gstDebugLevel="${VIDEO_GST_DEBUG:-${GST_DEBUG_LEVEL:-2}}" + +cleanup() { + # Best-effort: try to kill only children first; fall back to name-based kill + if ! pkill -P "$$" -x gst-launch-1.0 >/dev/null 2>&1; then + pkill -x gst-launch-1.0 >/dev/null 2>&1 || true + fi +} +trap cleanup INT TERM EXIT + +# -------------------- Arg parse -------------------- +while [ $# -gt 0 ]; do + case "$1" in + --resolution) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --resolution" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # Parse and validate WIDTHxHEIGHT format (e.g., 1920x1080) + if [ -n "$2" ]; then + # Validate format contains 'x' + if ! echo "$2" | grep -q "x"; then + log_warn "Invalid resolution format '$2' - must be WIDTHxHEIGHT" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + + width="${2%%x*}" + height="${2#*x}" + + # Validate both width and height are numeric + if ! echo "$width" | grep -q "^[0-9]\+$" || ! echo "$height" | grep -q "^[0-9]\+$"; then + log_warn "Width and height must be numeric values (got width='$width', height='$height')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + fi + shift 2 + ;; + + --duration) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --duration" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + if [ -n "$2" ]; then + if ! echo "$2" | grep -q "^[0-9]\+$"; then + log_warn "Duration must be a numeric value (got '$2')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + duration="$2" + fi + shift 2 + ;; + + --pattern) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --pattern" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If $2 is empty, keep default and shift 2 + [ -n "$2" ] && pattern="$2" + shift 2 + ;; + + --width) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --width" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # Validate width is numeric + if [ -n "$2" ]; then + if ! echo "$2" | grep -q "^[0-9]\+$"; then + log_warn "Width must be a numeric value (got '$2')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + width="$2" + fi + shift 2 + ;; + + --height) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --height" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # Validate height is numeric + if [ -n "$2" ]; then + if ! echo "$2" | grep -q "^[0-9]\+$"; then + log_warn "Height must be a numeric value (got '$2')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + height="$2" + fi + shift 2 + ;; + + --framerate) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --framerate" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + if [ -n "$2" ]; then + if ! echo "$2" | grep -q "^[0-9]\+$"; then + log_warn "Framerate must be a numeric value (got '$2')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + framerate="$2" + fi + shift 2 + ;; + + --gst-debug) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --gst-debug" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + if ! echo "$2" | grep -q "^[0-9]\+$"; then + log_warn "GST debug level must be numeric (got '$2')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + gstDebugLevel="$2" + shift 2 + ;; + + -h|--help) + cat < + Video resolution (e.g., 1920x1080, 3840x2160) + Default: ${width}x${height} + + --duration + Playback duration in seconds + Default: ${duration} + + --pattern + videotestsrc pattern + Default: ${pattern} + + --width + Video width (alternative to --resolution) + Default: ${width} + + --height + Video height (alternative to --resolution) + Default: ${height} + + --framerate + Video framerate + Default: ${framerate} + + --gst-debug + Sets GST_DEBUG= (1-9) + Default: ${gstDebugLevel} + +Examples: + # Run default test (1920x1080 SMPTE pattern for 30s) + ./run.sh + + # Run with custom resolution and duration + ./run.sh --resolution 3840x2160 --duration 20 + + # Run with different pattern + ./run.sh --pattern ball + + # Run with separate width/height + ./run.sh --width 1280 --height 720 + +EOF + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + + *) + log_warn "Unknown argument: $1" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + esac +done + +# Basic sanity +if [ "$duration" -le 0 ] || [ "$width" -le 0 ] || [ "$height" -le 0 ] || [ "$framerate" -le 0 ]; then + log_warn "Invalid parameters: duration=$duration width=$width height=$height framerate=$framerate" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# -------------------- Pre-checks -------------------- +check_dependencies "gst-launch-1.0 gst-inspect-1.0 grep head sed tail date mktemp" >/dev/null 2>&1 || { + log_skip "Missing required tools (gst-launch-1.0, gst-inspect-1.0, grep, head, sed, tail, date, mktemp)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} + +log_info "Test: $TESTNAME" +log_info "Duration: ${duration}s, Resolution: ${width}x${height}, Framerate: ${framerate}fps" +log_info "Pattern: $pattern" +log_info "GST debug: GST_DEBUG=$gstDebugLevel" +log_info "Logs: $OUTDIR" + +# -------------------- Display connectivity check -------------------- +if command -v display_debug_snapshot >/dev/null 2>&1; then + display_debug_snapshot "pre-test" +fi + +have_connector=0 +if command -v display_connected_summary >/dev/null 2>&1; then + sysfs_summary=$(display_connected_summary) + if [ -n "$sysfs_summary" ] && [ "$sysfs_summary" != "none" ]; then + have_connector=1 + log_info "Connected display (sysfs): $sysfs_summary" + fi +fi + +# Fallback: check /sys/class/drm/*/status +if [ "$have_connector" -eq 0 ]; then + drm_connected="" + for st in /sys/class/drm/card*-*/status; do + [ -f "$st" ] || continue + if grep -qi "connected" "$st"; then + conn=$(basename "$(dirname "$st")") + drm_connected="${drm_connected}${drm_connected:+,}${conn}" + fi + done + if [ -n "$drm_connected" ]; then + have_connector=1 + log_info "Connected display (drm sysfs): $drm_connected" + fi +fi + +if [ "$have_connector" -eq 0 ]; then + log_warn "No connected DRM display found, skipping ${TESTNAME}." + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# -------------------- Wayland/Weston environment check -------------------- +if command -v wayland_debug_snapshot >/dev/null 2>&1; then + wayland_debug_snapshot "${TESTNAME}: start" +fi + +sock="" + +# Try to find existing Wayland socket +if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then + sock=$(discover_wayland_socket_anywhere | head -n 1 || true) +fi + +# Adopt socket environment if found +if [ -n "$sock" ] && command -v adopt_wayland_env_from_socket >/dev/null 2>&1; then + log_info "Found existing Wayland socket: $sock" + if ! adopt_wayland_env_from_socket "$sock"; then + log_warn "Failed to adopt env from $sock" + fi +fi + +# Try starting Weston if no socket found +if [ -z "$sock" ]; then + if command -v weston_pick_env_or_start >/dev/null 2>&1; then + log_info "No usable Wayland socket; trying weston_pick_env_or_start..." + if weston_pick_env_or_start "${TESTNAME}"; then + # Re-discover socket after Weston start + if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then + sock=$(discover_wayland_socket_anywhere | head -n 1 || true) + fi + if [ -n "$sock" ]; then + log_info "Weston started successfully with socket: $sock" + if command -v adopt_wayland_env_from_socket >/dev/null 2>&1; then + adopt_wayland_env_from_socket "$sock" >/dev/null 2>&1 || true + fi + fi + else + log_warn "weston_pick_env_or_start failed" + fi + elif command -v overlay_start_weston_drm >/dev/null 2>&1; then + log_info "No usable Wayland socket; trying overlay_start_weston_drm (fallback)..." + if overlay_start_weston_drm; then + if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then + sock=$(discover_wayland_socket_anywhere | head -n 1 || true) + fi + if [ -n "$sock" ]; then + log_info "Weston created Wayland socket: $sock" + if command -v adopt_wayland_env_from_socket >/dev/null 2>&1; then + adopt_wayland_env_from_socket "$sock" >/dev/null 2>&1 || true + fi + fi + fi + else + log_warn "No Weston startup helper available (weston_pick_env_or_start or overlay_start_weston_drm)" + fi +fi + +# Final check +if [ -z "$sock" ]; then + log_warn "No Wayland socket found; skipping ${TESTNAME}." + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +# Verify Wayland connection +if command -v wayland_connection_ok >/dev/null 2>&1; then + if ! wayland_connection_ok; then + log_warn "Wayland connection test failed; skipping ${TESTNAME}." + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + log_info "Wayland connection test: OK" +fi + +# -------------------- Check waylandsink element -------------------- +if ! has_element waylandsink; then + log_warn "waylandsink element not available" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +fi + +log_info "waylandsink element: available" + +# -------------------- GStreamer debug capture -------------------- +export GST_DEBUG_NO_COLOR=1 +export GST_DEBUG="$gstDebugLevel" +export GST_DEBUG_FILE="$GST_LOG" + +# -------------------- Build and run pipeline -------------------- +# Make source real-time to match duration validation. +num_buffers=$((duration * framerate)) + +pipeline="videotestsrc is-live=true num-buffers=${num_buffers} pattern=${pattern} ! video/x-raw,width=${width},height=${height},framerate=${framerate}/1 ! videoconvert ! waylandsink" + +log_info "Pipeline: $pipeline" + +# Run with timeout +start_ts=$(date +%s) + +# Give some slack, but timeout should still be treated as a real failure for this test +timeout_sec=$((duration + 15)) + +if gstreamer_run_gstlaunch_timeout "$timeout_sec" "$pipeline" >>"$RUN_LOG" 2>&1; then + gstRc=0 +else + gstRc=$? +fi + +end_ts=$(date +%s) +elapsed=$((end_ts - start_ts)) + +log_info "Playback finished: rc=${gstRc} elapsed=${elapsed}s" + +# -------------------- Validation -------------------- +if [ "$duration" -gt 2 ]; then + min_duration=$((duration - 2)) +else + min_duration=0 +fi + +# Check for GStreamer errors in both run log and GST debug log +run_log_ok=1 +gst_log_ok=1 + +# Validate run log +if ! gstreamer_validate_log "$RUN_LOG" "$TESTNAME"; then + run_log_ok=0 +fi + +# Validate last 1000 lines of GST debug log if it exists and has content +if [ -s "$GST_LOG" ]; then + tmp_tail=$(mktemp "${OUTDIR}/gst.tail.XXXXXX" 2>/dev/null || mktemp) || tmp_tail="" + if [ -n "$tmp_tail" ]; then + tail -n 1000 "$GST_LOG" >"$tmp_tail" 2>/dev/null || true + if ! gstreamer_validate_log "$tmp_tail" "$TESTNAME"; then + gst_log_ok=0 + fi + rm -f "$tmp_tail" >/dev/null 2>&1 || true + else + # If mktemp failed, fall back to validating the full GST log + if ! gstreamer_validate_log "$GST_LOG" "$TESTNAME"; then + gst_log_ok=0 + fi + fi + rm -f "${GST_LOG}.tail" +fi + +if [ "$run_log_ok" -eq 0 ] || [ "$gst_log_ok" -eq 0 ]; then + result="FAIL" + if [ "$run_log_ok" -eq 0 ] && [ "$gst_log_ok" -eq 0 ]; then + reason="GStreamer errors detected in both run log and GST debug log" + elif [ "$run_log_ok" -eq 0 ]; then + reason="GStreamer errors detected in run log" + else + reason="GStreamer errors detected in GST debug log" + fi +else + # First check if it ran long enough + if [ "$elapsed" -ge "$min_duration" ]; then + # If it ran long enough, check exit code + case "$gstRc" in + 0) # Normal exit + result="PASS" + reason="Playback completed successfully (elapsed=${elapsed}/${duration}s)" + ;; + 124) + result="FAIL" + reason="Playback timed out (timeout=${timeout_sec}s, elapsed=${elapsed}s) - pipeline did not exit cleanly" + ;; + 137|143) + result="FAIL" + reason="Playback killed by signal (rc=$gstRc, elapsed=${elapsed}s) - unexpected termination" + ;; + *) # Unexpected return code + result="FAIL" + reason="Playback failed with unexpected exit code (rc=$gstRc, elapsed=${elapsed}/${duration}s)" + ;; + esac + else + # Didn't run long enough - always fail regardless of return code + result="FAIL" + reason="Playback exited too quickly (elapsed=${elapsed}s, minimum required=${min_duration}s)" + fi +fi + +# Helpful tails on failure (stdout visibility in CI) +if [ "$result" != "PASS" ]; then + log_info "---- gst-launch output (tail) ----" + tail -n 120 "$RUN_LOG" 2>/dev/null || true + if [ -s "$GST_LOG" ]; then + log_info "---- GST debug log (tail) ----" + tail -n 120 "$GST_LOG" 2>/dev/null || true + fi +fi + +# -------------------- Emit result -------------------- +case "$result" in + PASS) + log_pass "$TESTNAME $result: $reason" + echo "$TESTNAME PASS" >"$RES_FILE" + ;; + *) + log_fail "$TESTNAME $result: $reason" + echo "$TESTNAME FAIL" >"$RES_FILE" + ;; +esac + +exit 0 diff --git a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md new file mode 100644 index 00000000..f6da0a4b --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md @@ -0,0 +1,599 @@ +# Video_Encode_Decode (GStreamer) — Runner Test + +This directory contains the **Video_Encode_Decode** validation test for Qualcomm Linux Testkit runners. + +It validates video **encoding and decoding** using **GStreamer (`gst-launch-1.0`)** with V4L2 hardware-accelerated codecs: +- **v4l2h264enc** / **v4l2h264dec** (H.264/AVC) +- **v4l2h265enc** / **v4l2h265dec** (H.265/HEVC) +- **v4l2vp9dec** (VP9 decode only - uses pre-downloaded WebM clips) + +The script is designed to be **CI/LAVA-friendly**: +- Writes **PASS/FAIL/SKIP** into `Video_Encode_Decode.res` +- Always **exits 0** (even on FAIL/SKIP) to avoid terminating LAVA jobs early +- Logs the **final `gst-launch-1.0` command** to console and to log files +- Uses **videotestsrc** plugin to generate test patterns for H.264/H.265 (no external video files needed) +- For VP9: Downloads WebM clips from git repo (requires network connectivity) + +--- + +## Location in repo + +Expected path: + +``` +Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh +``` + +Required shared utils (sourced from `Runner/utils` via `init_env`): +- `functestlib.sh` +- `lib_gstreamer.sh` - **Contains reusable V4L2 video helpers** (see Library Functions section below) +- optional: `lib_video.sh` (for video stack management) + +--- + +## What this test does + +At a high level, the test: + +1. Finds and sources `init_env` +2. Sources: + - `$TOOLS/functestlib.sh` + - `$TOOLS/lib_gstreamer.sh` + - optionally `$TOOLS/lib_video.sh` +3. Checks for required GStreamer elements (v4l2h264enc, v4l2h265enc, v4l2h264dec, v4l2h265dec, v4l2vp9dec) +4. **Network connectivity check** (for VP9): + - Checks network connectivity using `ensure_network_online()` + - Downloads VP9 clips from git repo if not already present + - URL: https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz +5. **Encoding phase**: + - Uses `videotestsrc` to generate test video patterns (SMPTE color bars) + - Encodes to H.264 or H.265 using V4L2 hardware encoders + - Saves encoded files to `logs/Video_Encode_Decode/encoded/` + - Tests 4K resolution (3840x2160) by default +6. **Decoding phase**: + - Reads the previously encoded files (H.264/H.265) or downloaded clips (VP9) + - Decodes using V4L2 hardware decoders + - Outputs to fakesink (no display needed) +7. Collects test results and emits PASS/FAIL/SKIP + +--- + +## Test Cases + +By default, the test runs the following test cases at 4K resolution for H.264/H.265, plus VP9 decode: + +### Encoding Tests +1. **encode_h264_4k** - Encode H.264 at 3840x2160 resolution for 30 seconds +2. **encode_h265_4k** - Encode H.265 at 3840x2160 resolution for 30 seconds + +**Note:** VP9 encoding is not supported (no v4l2vp9enc available) + +### Decoding Tests +1. **decode_h264_4k** - Decode H.264 4K encoded file +2. **decode_h265_4k** - Decode H.265 4K encoded file +3. **decode_vp9_320p** - Decode VP9 pre-downloaded clip (converted to WebM: vp9_test_320p.webm) - **runs by default** + +--- + +## PASS / FAIL / SKIP criteria + +### PASS +- **Encoding**: Output file is created and has size > 1000 bytes +- **Decoding**: Pipeline completes successfully (exit code 0 or "Setting pipeline to NULL" in log) +- **Overall**: At least one test passes and no tests fail + +### FAIL +- **Encoding**: No output file created or file size too small +- **Decoding**: Pipeline fails or crashes +- **Overall**: One or more tests fail + +### SKIP +- Missing required tools (`gst-launch-1.0`, `gst-inspect-1.0`) +- Required V4L2 encoder/decoder elements not available +- For H.264/H.265 decode tests: corresponding encoded file not found (encode must run first) +- For VP9 decode tests: network connectivity unavailable, clip download failed, or IVF to WebM conversion failed + +**Note:** The test always exits `0` even for FAIL/SKIP. The `.res` file is the source of truth. + +--- + +## Logs and artifacts + +By default, logs are written relative to the script working directory: + +``` +./Video_Encode_Decode.res +./logs/Video_Encode_Decode/ + gst.log # GStreamer debug output + encode_h264_480p.log # Individual test logs + encode_h264_4k.log + encode_h265_480p.log + encode_h265_4k.log + decode_h264_480p.log + decode_h264_4k.log + decode_h265_480p.log + decode_h265_4k.log + decode_vp9_480p.log # VP9 decode test log + encoded/ # Encoded video files + encode_h264_480p.mp4 + encode_h264_4k.mp4 + encode_h265_480p.mp4 + encode_h265_4k.mp4 + VP9_640x480_10s.webm # Downloaded VP9 clip (WebM format) + dmesg/ # dmesg scan outputs (if available) +``` + +--- + +## Dependencies + +### Required +- `gst-launch-1.0` +- `gst-inspect-1.0` +- `videotestsrc` GStreamer plugin +- `videoconvert` GStreamer plugin + +### V4L2 Encoder/Decoder Elements +- `v4l2h264enc` - H.264 hardware encoder +- `v4l2h265enc` - H.265 hardware encoder +- `v4l2h264dec` - H.264 hardware decoder +- `v4l2h265dec` - H.265 hardware decoder +- `v4l2vp9dec` - VP9 hardware decoder + +### Parser Elements +- `h264parse` - H.264 stream parser +- `h265parse` - H.265 stream parser +- `matroskademux` - WebM/Matroska container demuxer (for VP9) + +### Network Requirements (for VP9) +- Network connectivity (Ethernet or WiFi) +- Access to GitHub releases: https://github.com/qualcomm-linux/qcom-linux-testkit/releases/ + +--- + +## Usage + +Run: + +```bash +./run.sh [options] +``` + +Help: + +```bash +./run.sh --help +``` + +### Options + +- `--mode ` + - Default: `all` (run both encode and decode tests) + - `encode`: Run only encoding tests + - `decode`: Run only decoding tests (requires encoded files from previous encode run) + +- `--codecs ` + - Comma-separated list of codecs to test + - Default: `h264,h265,vp9` (all three codecs run by default) + - Examples: `h264`, `h265`, `h264,h265`, `vp9`, `h264,vp9` + - Note: VP9 only supports decode (no encode) + +- `--resolutions <480p,4k>` + - Comma-separated list of resolutions to test + - Default: `480p,4k` + - Supported: `480p` (640x480), `720p` (1280x720), `1080p` (1920x1080), `4k` (3840x2160) + - Examples: `480p`, `4k`, `480p,1080p,4k` + +- `--duration ` + - Duration for encoding (in seconds) + - Default: `30` + - This determines how many frames are generated (duration × framerate) + +- `--framerate ` + - Framerate for video generation + - Default: `30` + +- `--stack ` + - Video stack selection (uses lib_video.sh if available) + - Default: `auto` + +- `--gst-debug ` + - Sets `GST_DEBUG=` (1-9) + - Values: + - `1` ERROR + - `2` WARNING (default) + - `3` FIXME + - `4` INFO + - `5` DEBUG + - `6` LOG + - `7` TRACE + - `8` MEMDUMP + - `9` MEMDUMP + - Default: `2` + +--- + +## Examples + +### 1) Run all tests (default - encode + decode for H.264/H.265/VP9 at 4K for 30 seconds) + +```bash +./run.sh +``` + +**Note:** Default behavior runs H.264, H.265, and VP9 tests at 4K resolution with 30 second duration. + +### 2) Run only encoding tests + +```bash +./run.sh --mode encode +``` + +### 3) Run only decoding tests (requires encoded files from previous run) + +```bash +./run.sh --mode decode +``` + +### 4) Test only H.264 codec + +```bash +./run.sh --codecs h264 +``` + +### 5) Test only H.265 codec at 4K resolution + +```bash +./run.sh --codecs h265 --resolutions 4k +``` + +### 6) Test all codecs at 480p only + +```bash +./run.sh --resolutions 480p +``` + +### 7) Test with longer duration (10 seconds) + +```bash +./run.sh --duration 10 +``` + +### 8) Test with higher framerate (60fps) + +```bash +./run.sh --framerate 60 +``` + +### 9) Test multiple resolutions + +```bash +./run.sh --resolutions 480p,720p,1080p,4k +``` + +### 10) Increase GStreamer debug verbosity + +```bash +./run.sh --gst-debug 5 +``` + +### 11) Quick test - H.264 only at 480p with 3 second duration + +```bash +./run.sh --codecs h264 --resolutions 480p --duration 3 +``` + +### 12) Test VP9 decode only (requires network connectivity) + +```bash +./run.sh --codecs vp9 --mode decode +``` + +### 13) Test all codecs including VP9 + +```bash +./run.sh --codecs h264,h265,vp9 +``` + +--- + +## Pipeline Details + +### Encoding Pipeline + +``` +videotestsrc num-buffers= pattern=smpte + ! video/x-raw,width=,height=,format=NV12,framerate=/1 + ! v4l2h264enc extra-controls="controls,video_bitrate=" (or v4l2h265enc) + ! h264parse (or h265parse) + ! filesink location= +``` + +Where: +- `num-buffers` = duration × framerate +- `pattern=smpte` generates SMPTE color bars test pattern +- `format=NV12` specifies the native format for V4L2 encoders (no videoconvert needed) +- `extra-controls="controls,video_bitrate="` sets encoder bitrate + - 480p: 1 Mbps (1000000) + - 720p: 2 Mbps (2000000) + - 1080p: 4 Mbps (4000000) + - 4K: 8 Mbps (8000000) +- Parser element ensures proper format negotiation + +### Decoding Pipeline (H.264/H.265) + +``` +filesrc location= + ! h264parse (or h265parse) + ! v4l2h264dec (or v4l2h265dec) + ! videoconvert + ! fakesink +``` + +Where: +- Parser ensures proper stream format +- `fakesink` discards output (no display needed for validation) + +### Decoding Pipeline (VP9) + +``` +filesrc location=VP9_640x480_10s.webm + ! matroskademux + ! v4l2vp9dec + ! videoconvert + ! fakesink +``` + +Where: +- `matroskademux` parses WebM/Matroska container format +- Input file is the downloaded WebM file +- Resolution: 640x480 + +--- + +## Troubleshooting + +### A) "SKIP: Missing gstreamer runtime" +- Ensure `gst-launch-1.0` and `gst-inspect-1.0` are installed in the image. + +### B) "Encoder not available for h264/h265" +- Check if V4L2 encoder elements are available: + ```bash + gst-inspect-1.0 v4l2h264enc + gst-inspect-1.0 v4l2h265enc + ``` +- Ensure video hardware acceleration drivers are loaded +- Check video stack configuration (upstream vs downstream) + +### C) "Decoder not available for h264/h265/vp9" +- Check if V4L2 decoder elements are available: + ```bash + gst-inspect-1.0 v4l2h264dec + gst-inspect-1.0 v4l2h265dec + gst-inspect-1.0 v4l2vp9dec + ``` + +### D) Decode tests skip with "Input file not found" +- Run encode tests first: `./run.sh --mode encode` +- Or run all tests: `./run.sh --mode all` + +### E) Encoding fails or produces small files +- Check available memory (4K encoding requires significant memory) +- Check `logs/Video_Encode_Decode/encode_*.log` for errors +- Try with lower resolution: `./run.sh --resolutions 480p` +- Increase debug level: `./run.sh --gst-debug 5` + +### F) "FAIL: file too small" +- Encoding may have failed silently +- Check individual test logs in `logs/Video_Encode_Decode/` +- Verify V4L2 video devices exist: `ls -l /dev/video*` + +### G) Video stack issues +- Check loaded modules: + ```bash + lsmod | grep -E 'iris|venus|video' + ``` +- Try forcing stack: `./run.sh --stack upstream` or `./run.sh --stack downstream` + +### H) VP9 decode fails with "Input file not found" +- Ensure network connectivity is available +- Check if clip was downloaded: `ls -l logs/Video_Encode_Decode/VP9_640x480_10s.webm` +- Manually download if needed: + ```bash + cd logs/Video_Encode_Decode/ + wget https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/GST-Video-Files-v1.0/video_clips_gst.tar.gz + tar -xzf video_clips_gst.tar.gz + ``` +### I) VP9 decode fails with "matroskademux not found" +- Ensure `matroskademux` GStreamer plugin is installed: + ```bash + gst-inspect-1.0 matroskademux + ``` +- This is typically part of `gst-plugins-good` package + +--- + +## Library Functions (Runner/utils/lib_gstreamer.sh) + +This test uses reusable helper functions from `lib_gstreamer.sh` that other GStreamer tests can leverage: + +### Resolution and Codec Helpers + +**`gstreamer_resolution_to_wh `** +- Converts resolution names to width/height +- Input: `480p`, `720p`, `1080p`, `4k` +- Output: `" "` (e.g., `"1920 1080"`) +- Example: + ```sh + params=$(gstreamer_resolution_to_wh "1080p") + width=$(printf '%s' "$params" | awk '{print $1}') # 1920 + height=$(printf '%s' "$params" | awk '{print $2}') # 1080 + ``` + +**`gstreamer_v4l2_encoder_for_codec `** +- Returns V4L2 encoder element for codec +- Input: `h264`, `h265`/`hevc` +- Output: `v4l2h264enc` or `v4l2h265enc` (or empty if not available) +- Example: + ```sh + encoder=$(gstreamer_v4l2_encoder_for_codec "h264") # v4l2h264enc + ``` + +**`gstreamer_v4l2_decoder_for_codec `** +- Returns V4L2 decoder element for codec +- Input: `h264`, `h265`/`hevc`, `vp9` +- Output: `v4l2h264dec`, `v4l2h265dec`, or `v4l2vp9dec` (or empty if not available) +- Example: + ```sh + decoder=$(gstreamer_v4l2_decoder_for_codec "vp9") # v4l2vp9dec + ``` + +**`gstreamer_container_ext_for_codec `** +- Returns file extension for codec +- Input: `h264`, `h265`, `vp9` +- Output: `mp4` (for h264/h265) or `ivf` (for vp9) +- Example: + ```sh + ext=$(gstreamer_container_ext_for_codec "h264") # mp4 + ``` + +### Bitrate and File Size Helpers + +**`gstreamer_bitrate_for_resolution `** +- Calculates recommended bitrate based on resolution +- Returns bitrate in bps +- Bitrate mapping: + - ≤640px width: 1 Mbps (1000000 bps) + - ≤1280px width: 2 Mbps (2000000 bps) + - ≤1920px width: 4 Mbps (4000000 bps) + - >1920px width: 8 Mbps (8000000 bps) +- Example: + ```sh + bitrate=$(gstreamer_bitrate_for_resolution 1920 1080) # 4000000 + ``` + +**`gstreamer_file_size_bytes `** +- Returns file size in bytes (portable across BSD/GNU stat) +- Returns `0` if file doesn't exist +- Example: + ```sh + size=$(gstreamer_file_size_bytes "/tmp/video.mp4") + if [ "$size" -gt 1000 ]; then + echo "File is valid" + fi + ``` + +### Pipeline Builders + +**`gstreamer_build_v4l2_encode_pipeline `** +- Builds complete V4L2 encode pipeline with videotestsrc +- Parameters: + - `codec`: `h264` or `h265` + - `width`, `height`: Video dimensions + - `duration`: Duration in seconds + - `framerate`: Frames per second + - `bitrate`: Bitrate in bps + - `output_file`: Output file path + - `video_stack`: `upstream` or `downstream` (adds IO mode parameters for downstream) +- Returns: Complete pipeline string (or empty if encoder not available) +- Example: + ```sh + pipeline=$(gstreamer_build_v4l2_encode_pipeline \ + "h264" 1920 1080 30 30 4000000 "/tmp/test.mp4" "upstream") + gstreamer_run_gstlaunch_timeout 40 "$pipeline" + ``` + +**`gstreamer_build_v4l2_decode_pipeline `** +- Builds complete V4L2 decode pipeline +- Parameters: + - `codec`: `h264`, `h265`, or `vp9` + - `input_file`: Input file path + - `video_stack`: `upstream` or `downstream` +- Returns: Complete pipeline string (or empty if decoder not available) +- Automatically handles: + - Container format (MP4 for h264/h265, IVF for vp9) + - Parser selection (h264parse, h265parse, ivfparse) + - IO mode parameters for downstream stack +- Example: + ```sh + pipeline=$(gstreamer_build_v4l2_decode_pipeline \ + "h264" "/tmp/test.mp4" "upstream") + gstreamer_run_gstlaunch_timeout 40 "$pipeline" + ``` + +### Usage in Other Tests + +To use these functions in your GStreamer test: + +```sh +#!/bin/sh +# Source init_env and lib_gstreamer.sh +. "$INIT_ENV" +. "$TOOLS/functestlib.sh" +. "$TOOLS/lib_gstreamer.sh" + +# Use the helpers +params=$(gstreamer_resolution_to_wh "4k") +width=$(printf '%s' "$params" | awk '{print $1}') +height=$(printf '%s' "$params" | awk '{print $2}') + +bitrate=$(gstreamer_bitrate_for_resolution "$width" "$height") + +pipeline=$(gstreamer_build_v4l2_encode_pipeline \ + "h264" "$width" "$height" 10 30 "$bitrate" "/tmp/output.mp4" "upstream") + +if [ -n "$pipeline" ]; then + gstreamer_run_gstlaunch_timeout 20 "$pipeline" +fi +``` + +### Testing Pipeline Builders + +A test script is provided to verify the pipeline builders: + +```bash +cd Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode +sh test_pipeline_builders.sh +``` + +This will output example pipelines for various codecs, resolutions, and video stacks. + +--- + +## Notes for CI / LAVA + +- The test always exits `0`. +- Use the `.res` file for result: + - `PASS` - All tests passed + - `FAIL` - One or more tests failed + - `SKIP` - No tests executed or all skipped +- Test summary is logged showing pass/fail/skip counts +- Individual test logs are available in `logs/Video_Encode_Decode/` +- Encoded files are preserved in `logs/Video_Encode_Decode/encoded/` for debugging + +### LAVA Environment Variables + +The test supports these environment variables (can be set in LAVA job definition): + +- `VIDEO_TEST_MODE` - Test mode (all/encode/decode) (default: all) +- `VIDEO_CODECS` - Comma-separated codec list (default: `h264,h265,vp9`) +- `VIDEO_RESOLUTIONS` - Comma-separated resolution list (default: `4k`) +- `VIDEO_DURATION` - Encoding duration in seconds (default: 30) +- `RUNTIMESEC` - Alternative to VIDEO_DURATION +- `VIDEO_FRAMERATE` - Video framerate (default: 30) +- `VIDEO_STACK` - Video stack selection (auto/upstream/downstream) (default: auto) +- `VIDEO_GST_DEBUG` - GStreamer debug level (default: 2) +- `GST_DEBUG_LEVEL` - Alternative to VIDEO_GST_DEBUG +- `VIDEO_CLIP_URL` - URL for VP9 clip download (default: GitHub releases) + +**Priority order for duration**: `VIDEO_DURATION` > `RUNTIMESEC` > default (30) + +### VP9-Specific Notes for CI/LAVA + +- VP9 tests require network connectivity to download clips +- The test uses `ensure_network_online()` to establish connectivity automatically +- If network is unavailable, VP9 tests will SKIP (not FAIL) +- Downloaded clips are cached in the output directory for subsequent runs +- VP9 clip: VP9_640x480_10s.webm (640x480 resolution, WebM container) + +--- diff --git a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml new file mode 100644 index 00000000..ac8260ac --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml @@ -0,0 +1,30 @@ +metadata: + name: gstreamer-video-encode-decode + format: "Lava-Test Test Definition 1.0" + description: > + Video encode/decode validation using GStreamer (gst-launch-1.0) with V4L2 hardware-accelerated codecs. + VP9 clips can be provided via VIDEO_CLIP_PATH (local dir/file/archive) or downloaded via VIDEO_CLIP_URL. + os: + - linux + scope: + - functional + +params: + VIDEO_TEST_MODE: "all" + VIDEO_CODECS: "h264,h265,vp9" + VIDEO_RESOLUTIONS: "480p" + VIDEO_DURATION: "30" + VIDEO_FRAMERATE: "30" + VIDEO_STACK: "auto" + VIDEO_GST_DEBUG: "2" + VIDEO_CLIP_URL: "" + VIDEO_CLIP_PATH: "" # dir/file/archive (tar/tar.gz/tar.xz/xz) + +run: + steps: + - REPO_PATH="$PWD" + - cd Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/ + - export VIDEO_TEST_MODE VIDEO_CODECS VIDEO_RESOLUTIONS VIDEO_DURATION VIDEO_FRAMERATE + - export VIDEO_STACK VIDEO_GST_DEBUG VIDEO_CLIP_URL VIDEO_CLIP_PATH + - ./run.sh --mode "${VIDEO_TEST_MODE}" --codecs "${VIDEO_CODECS}" --resolutions "${VIDEO_RESOLUTIONS}" --duration "${VIDEO_DURATION}" --framerate "${VIDEO_FRAMERATE}" --stack "${VIDEO_STACK}" --gst-debug "${VIDEO_GST_DEBUG}" --clip-url "${VIDEO_CLIP_URL}" --clip-path "${VIDEO_CLIP_PATH}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh Video_Encode_Decode.res || true diff --git a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh new file mode 100755 index 00000000..318136a7 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh @@ -0,0 +1,668 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause +# Video Encode/Decode validation using GStreamer with V4L2 hardware accelerated codecs +# Supports: v4l2h264dec, v4l2h265dec, v4l2h264enc, v4l2h265enc +# Uses videotestsrc for encoding, then decodes the encoded files +# Logs everything to console and also to local log files. +# PASS/FAIL/SKIP is emitted to .res. Always exits 0 (LAVA-friendly). + +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" + +TESTNAME="Video_Encode_Decode" +RES_FILE="${SCRIPT_DIR}/${TESTNAME}.res" +LOG_DIR="${SCRIPT_DIR}/logs" +OUTDIR="$LOG_DIR/$TESTNAME" +GST_LOG="$OUTDIR/gst.log" +DMESG_DIR="$OUTDIR/dmesg" +ENCODED_DIR="$OUTDIR/encoded" + +mkdir -p "$OUTDIR" "$DMESG_DIR" "$ENCODED_DIR" >/dev/null 2>&1 || true +: >"$RES_FILE" +: >"$GST_LOG" + +SCRIPT_DIR="$( + cd "$(dirname "$0")" || exit 1 + pwd +)" + +INIT_ENV="" +SEARCH="$SCRIPT_DIR" +while [ "$SEARCH" != "/" ]; do + if [ -f "$SEARCH/init_env" ]; then + INIT_ENV="$SEARCH/init_env" + break + fi + SEARCH=$(dirname "$SEARCH") +done + +RES_FILE="$SCRIPT_DIR/${TESTNAME}.res" + +if [ -z "${INIT_ENV:-}" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + echo "$TESTNAME SKIP" >"$RES_FILE" 2>/dev/null || true + exit 0 +fi + +if [ -z "${__INIT_ENV_LOADED:-}" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" + __INIT_ENV_LOADED=1 +fi + +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" + +# shellcheck disable=SC1091 +. "$TOOLS/lib_gstreamer.sh" + +# shellcheck disable=SC1091 +[ -f "$TOOLS/lib_video.sh" ] && . "$TOOLS/lib_video.sh" + +result="FAIL" +reason="unknown" +pass_count=0 +fail_count=0 +skip_count=0 +total_tests=0 + +# -------------------- Defaults (LAVA env vars -> defaults; CLI overrides) -------------------- +testMode="${VIDEO_TEST_MODE:-all}" +codecList="${VIDEO_CODECS:-h264,h265,vp9}" +resolutionList="${VIDEO_RESOLUTIONS:-480p}" +duration="${VIDEO_DURATION:-${RUNTIMESEC:-30}}" +framerate="${VIDEO_FRAMERATE:-30}" +gstDebugLevel="${VIDEO_GST_DEBUG:-${GST_DEBUG_LEVEL:-2}}" +videoStack="${VIDEO_STACK:-auto}" +clipUrl="${VIDEO_CLIP_URL:-https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/GST-Video-Files-v1.0/video_clips_gst.tar.gz}" +clipPath="${VIDEO_CLIP_PATH:-}" + +# Validate environment variables if set +# Validate numeric parameters (POSIX-safe; no indirect expansion) +for param in VIDEO_DURATION RUNTIMESEC VIDEO_FRAMERATE VIDEO_GST_DEBUG GST_DEBUG_LEVEL; do + val="" + case "$param" in + VIDEO_DURATION) val="${VIDEO_DURATION-}" ;; + RUNTIMESEC) val="${RUNTIMESEC-}" ;; + VIDEO_FRAMERATE) val="${VIDEO_FRAMERATE-}" ;; + VIDEO_GST_DEBUG) val="${VIDEO_GST_DEBUG-}" ;; + GST_DEBUG_LEVEL) val="${GST_DEBUG_LEVEL-}" ;; + esac + + if [ -n "$val" ]; then + case "$val" in + ''|*[!0-9]*) + log_warn "$param must be numeric (got '$val')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + if [ "$val" -le 0 ] 2>/dev/null; then + log_warn "$param must be positive (got '$val')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + ;; + esac + fi +done + +cleanup() { + pkill -x gst-launch-1.0 >/dev/null 2>&1 || true +} +trap cleanup INT TERM EXIT + +# -------------------- Arg parse -------------------- +while [ $# -gt 0 ]; do + case "$1" in + --mode) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --mode" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && testMode="$2" + shift 2 + ;; + + --codecs) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --codecs" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && codecList="$2" + shift 2 + ;; + + --resolutions) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --resolutions" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && resolutionList="$2" + shift 2 + ;; + + --duration) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --duration" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty or non-numeric, keep default; otherwise use provided value + if [ -n "$2" ]; then + case "$2" in + ''|*[!0-9]*) + log_warn "Invalid --duration '$2' (must be numeric)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + duration="$2" + ;; + esac + fi + shift 2 + ;; + + --framerate) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --framerate" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + if [ -n "$2" ]; then + case "$2" in + ''|*[!0-9]*) + log_warn "Invalid --framerate '$2' (must be numeric)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + if [ "$2" -le 0 ] 2>/dev/null; then + log_warn "Framerate must be positive (got '$2')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + ;; + esac + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && framerate="$2" + shift 2 + ;; + + --stack) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --stack" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && videoStack="$2" + shift 2 + ;; + + --gst-debug) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --gst-debug" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && gstDebugLevel="$2" + shift 2 + ;; + + --clip-url) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --clip-url" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + # If empty, keep default; otherwise use provided value + [ -n "$2" ] && clipUrl="$2" + shift 2 + ;; + --clip-path) + if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --clip-path" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + [ -n "$2" ] && clipPath="$2" + shift 2 + ;; + -h|--help) + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + log_warn "Unknown argument: $1" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + esac +done + +# -------------------- Validate parsed values -------------------- +case "$testMode" in all|encode|decode) : ;; *) + log_warn "Invalid --mode '$testMode'" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +case "$gstDebugLevel" in 1|2|3|4|5|6|7|8|9) : ;; *) + log_warn "Invalid --gst-debug '$gstDebugLevel' (allowed: 1-9)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +case "$duration" in + ''|*[!0-9]*) + log_warn "Invalid duration '$duration' (must be numeric)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + if [ "$duration" -le 0 ] 2>/dev/null; then + log_warn "Duration must be positive (got '$duration')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + ;; +esac + +case "$framerate" in + ''|*[!0-9]*) + log_warn "Invalid framerate '$framerate' (must be numeric)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + ;; + *) + if [ "$framerate" -le 0 ] 2>/dev/null; then + log_warn "Framerate must be positive (got '$framerate')" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 + fi + ;; +esac + +# -------------------- Pre-checks -------------------- +check_dependencies "gst-launch-1.0 gst-inspect-1.0 awk grep head sed tr stat find curl tar" >/dev/null 2>&1 || { + log_skip "Missing required tools (gst-launch-1.0, gst-inspect-1.0, awk, grep, head, sed, tr, stat, find, curl, tar)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} + +log_info "Checking dependencies: gst-launch-1.0 gst-inspect-1.0 awk grep head sed tr stat find curl tar" +log_info "Test: $TESTNAME" +log_info "Mode: $testMode" +log_info "Codecs: $codecList" +log_info "Resolutions: $resolutionList" +log_info "Duration: ${duration}s, Framerate: ${framerate}fps" +log_info "GST debug: GST_DEBUG=$gstDebugLevel" +log_info "Logs: $OUTDIR" +log_info "VP9 clip URL: $clipUrl" +if [ -n "$clipPath" ]; then + log_info "VP9 clip local path: $clipPath" +fi + +# -------------------- Video stack handling -------------------- +detected_stack="$videoStack" +if command -v video_ensure_stack >/dev/null 2>&1; then + log_info "Ensuring video stack: $videoStack" + stack_result=$(video_ensure_stack "$videoStack" "" 2>&1) + if printf '%s' "$stack_result" | grep -q "downstream"; then + detected_stack="downstream" + log_info "Detected stack: downstream" + elif printf '%s' "$stack_result" | grep -q "upstream"; then + detected_stack="upstream" + log_info "Detected stack: upstream" + else + log_info "Stack detection result: $stack_result" + fi +fi + +# -------------------- GStreamer debug capture -------------------- +export GST_DEBUG_NO_COLOR=1 +export GST_DEBUG="$gstDebugLevel" +export GST_DEBUG_FILE="$GST_LOG" + + +# -------------------- Encode test function -------------------- +run_encode_test() { + codec="$1" + resolution="$2" + width="$3" + height="$4" + + testname="encode_${codec}_${resolution}" + log_info "==========================================" + log_info "Running: $testname" + log_info "==========================================" + + # Check if encoder is available + encoder=$(gstreamer_v4l2_encoder_for_codec "$codec") + if [ -z "$encoder" ]; then + log_warn "Encoder not available for $codec" + skip_count=$((skip_count + 1)) + return 1 + fi + + ext=$(gstreamer_container_ext_for_codec "$codec") + output_file="$ENCODED_DIR/${testname}.${ext}" + test_log="$OUTDIR/${testname}.log" + + : >"$test_log" + + # Calculate bitrate based on resolution + bitrate=$(gstreamer_bitrate_for_resolution "$width" "$height") + + # Build pipeline using library function + pipeline=$(gstreamer_build_v4l2_encode_pipeline "$codec" "$width" "$height" "$duration" "$framerate" "$bitrate" "$output_file" "$detected_stack") + + if [ -z "$pipeline" ]; then + log_fail "$testname: FAIL (could not build pipeline)" + fail_count=$((fail_count + 1)) + return 1 + fi + + log_info "Pipeline: $pipeline" + + # Run encoding + if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$pipeline" >>"$test_log" 2>&1; then + gstRc=0 + else + gstRc=$? + fi + + log_info "Encode exit code: $gstRc" + + # Check for GStreamer errors in log + if ! gstreamer_validate_log "$test_log" "$testname"; then + log_fail "$testname: FAIL (GStreamer errors detected)" + fail_count=$((fail_count + 1)) + return 1 + fi + + # Check if output file was created and has content + if [ -f "$output_file" ] && [ -s "$output_file" ]; then + file_size=$(gstreamer_file_size_bytes "$output_file") + log_info "Encoded file: $output_file (size: $file_size bytes)" + + if [ "$file_size" -gt 1000 ]; then + log_pass "$testname: PASS" + pass_count=$((pass_count + 1)) + return 0 + else + log_fail "$testname: FAIL (file too small: $file_size bytes)" + fail_count=$((fail_count + 1)) + return 1 + fi + else + log_fail "$testname: FAIL (no output file created)" + fail_count=$((fail_count + 1)) + return 1 + fi +} + +# -------------------- Decode test function -------------------- +run_decode_test() { + codec="$1" + resolution="$2" + + testname="decode_${codec}_${resolution}" + log_info "==========================================" + log_info "Running: $testname" + log_info "==========================================" + + # Check if decoder is available + decoder=$(gstreamer_v4l2_decoder_for_codec "$codec") + if [ -z "$decoder" ]; then + log_warn "Decoder not available for $codec" + skip_count=$((skip_count + 1)) + return 1 + fi + + ext=$(gstreamer_container_ext_for_codec "$codec") + + # For VP9, use WebM clip directly; for others, use encoded file + if [ "$codec" = "vp9" ]; then + input_file="$OUTDIR/VP9_640x480_10s.webm" + if [ ! -f "$input_file" ]; then + log_warn "VP9 WebM clip not found: $input_file" + skip_count=$((skip_count + 1)) + return 1 + fi + else + input_file="$ENCODED_DIR/encode_${codec}_${resolution}.${ext}" + if [ ! -f "$input_file" ]; then + log_warn "Input file not found: $input_file (run encode first)" + skip_count=$((skip_count + 1)) + return 1 + fi + fi + + test_log="$OUTDIR/${testname}.log" + : >"$test_log" + + # Build pipeline using library function + pipeline=$(gstreamer_build_v4l2_decode_pipeline "$codec" "$input_file" "$detected_stack") + + if [ -z "$pipeline" ]; then + log_fail "$testname: FAIL (could not build pipeline)" + fail_count=$((fail_count + 1)) + return 1 + fi + + log_info "Pipeline: $pipeline" + + # Run decoding + if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$pipeline" >>"$test_log" 2>&1; then + gstRc=0 + else + gstRc=$? + fi + + log_info "Decode exit code: $gstRc" + + # Check for GStreamer errors in log + if ! gstreamer_validate_log "$test_log" "$testname"; then + log_fail "$testname: FAIL (GStreamer errors detected)" + fail_count=$((fail_count + 1)) + return 1 + fi + + # Check for successful completion + if [ "$gstRc" -eq 0 ]; then + log_pass "$testname: PASS" + pass_count=$((pass_count + 1)) + return 0 + else + log_fail "$testname: FAIL (rc=$gstRc)" + fail_count=$((fail_count + 1)) + return 1 + fi +} + +# -------------------- Main test execution -------------------- +log_info "Starting video encode/decode tests..." + +# Parse codec list +codecs=$(printf '%s' "$codecList" | tr ',' ' ') + +# Parse resolution list +resolutions=$(printf '%s' "$resolutionList" | tr ',' ' ') + +# -------------------- VP9 clip prep -------------------- +need_vp9_clip=0 +for codec in $codecs; do + if [ "$codec" = "vp9" ]; then + need_vp9_clip=1 + break + fi +done + +if [ "$need_vp9_clip" -eq 1 ] && [ "$testMode" != "encode" ]; then + log_info "==========================================" + log_info "VP9 CLIP PREP" + log_info "==========================================" + + vp9_clip_webm="$OUTDIR/VP9_640x480_10s.webm" + + # Check if WebM file already exists + if [ -f "$vp9_clip_webm" ]; then + log_info "VP9 WebM clip already exists: $vp9_clip_webm" + else + # Try to get WebM file from provided path or URL + if [ -n "$clipPath" ]; then + log_info "Attempting to get VP9 WebM clip from local path: $clipPath" + if [ -f "$clipPath/VP9_640x480_10s.webm" ]; then + cp "$clipPath/VP9_640x480_10s.webm" "$vp9_clip_webm" + log_info "VP9 WebM clip copied from local path" + else + log_warn "VP9 WebM clip not found in local path: $clipPath" + fi + fi + + # If not found locally, try URL download + if [ ! -f "$vp9_clip_webm" ]; then + log_info "VP9 WebM clip not found locally; attempting download from URL..." + if extract_tar_from_url "$clipUrl" "$OUTDIR"; then + # Move the extracted file from current directory to OUTDIR + if [ -f "VP9_640x480_10s.webm" ]; then + mv "VP9_640x480_10s.webm" "$vp9_clip_webm" + log_pass "VP9 WebM clip downloaded and moved successfully" + else + log_warn "VP9 WebM clip not found in downloaded content" + fi + else + log_warn "VP9 WebM clip download failed (offline or URL issue)" + fi + fi + fi +fi + +# Run encode tests (skip VP9 as it doesn't support encoding in this test) +if [ "$testMode" = "all" ] || [ "$testMode" = "encode" ]; then + log_info "==========================================" + log_info "ENCODE TESTS" + log_info "==========================================" + + for codec in $codecs; do + # Skip VP9 for encode tests (no v4l2vp9enc support in this test) + if [ "$codec" = "vp9" ]; then + log_info "Skipping VP9 encode (not supported)" + continue + fi + + for res in $resolutions; do + params=$(gstreamer_resolution_to_wh "$res") + + # ---------------- FIX: robust split independent of IFS ---------------- + width=$(printf '%s\n' "$params" | awk '{print $1}') + height=$(printf '%s\n' "$params" | awk '{print $2}') + case "$width" in ''|*[!0-9]*) width="640" ;; esac + case "$height" in ''|*[!0-9]*) height="480" ;; esac + # --------------------------------------------------------------------- + + total_tests=$((total_tests + 1)) + run_encode_test "$codec" "$res" "$width" "$height" || true + done + done +fi + +# Run decode tests +if [ "$testMode" = "all" ] || [ "$testMode" = "decode" ]; then + log_info "==========================================" + log_info "DECODE TESTS" + log_info "==========================================" + + for codec in $codecs; do + if [ "$codec" = "vp9" ]; then + total_tests=$((total_tests + 1)) + run_decode_test "$codec" "480p" || true + else + for res in $resolutions; do + total_tests=$((total_tests + 1)) + run_decode_test "$codec" "$res" || true + done + fi + done +fi + +# -------------------- Dmesg error scan -------------------- +log_info "==========================================" +log_info "DMESG ERROR SCAN" +log_info "==========================================" + +# Scan for video-related errors in dmesg +module_regex="venus|vcodec|v4l2|video|gstreamer" +exclude_regex="dummy regulator|supply [^ ]+ not found|using dummy regulator" + +if command -v scan_dmesg_errors >/dev/null 2>&1; then + scan_dmesg_errors "$DMESG_DIR" "$module_regex" "$exclude_regex" || true + + if [ -s "$DMESG_DIR/dmesg_errors.log" ]; then + log_warn "dmesg scan found video-related warnings or errors in $DMESG_DIR/dmesg_errors.log" + else + log_info "No relevant video-related errors found in dmesg" + fi +else + log_info "scan_dmesg_errors not available, skipping dmesg scan" +fi + +# -------------------- Summary -------------------- +log_info "==========================================" +log_info "TEST SUMMARY" +log_info "==========================================" +# Calculate actual total for display (sum of pass/fail/skip) +actual_total=$((pass_count + fail_count + skip_count)) +log_info "Total testcases: $actual_total" +log_info "Passed: $pass_count" +log_info "Failed: $fail_count" +log_info "Skipped: $skip_count" + +# -------------------- Emit result -------------------- +if [ "$fail_count" -eq 0 ] && [ "$pass_count" -gt 0 ]; then + result="PASS" + if [ "$skip_count" -gt 0 ]; then + reason="No failures (passed: $pass_count, failed: $fail_count, skipped: $skip_count, total: $actual_total)" + else + reason="All tests passed ($pass_count/$actual_total)" + fi +elif [ "$fail_count" -gt 0 ]; then + result="FAIL" + reason="Some tests failed (passed: $pass_count, failed: $fail_count, skipped: $skip_count, total: $actual_total)" +else + result="SKIP" + reason="No tests passed (skipped: $skip_count, total: $actual_total)" +fi + +case "$result" in + PASS) + log_pass "$TESTNAME $result: $reason" + echo "$TESTNAME PASS" >"$RES_FILE" + ;; + FAIL) + log_fail "$TESTNAME $result: $reason" + echo "$TESTNAME FAIL" >"$RES_FILE" + ;; + *) + log_warn "$TESTNAME $result: $reason" + echo "$TESTNAME SKIP" >"$RES_FILE" + ;; +esac + +exit 0 diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh index 579bb263..2af69649 100755 --- a/Runner/utils/lib_gstreamer.sh +++ b/Runner/utils/lib_gstreamer.sh @@ -497,3 +497,469 @@ gstreamer_build_playback_pipeline() { printf '%s\n' "filesrc location=${file} ! ${dec} ! audioconvert ! audioresample ! ${sinkElem}" return 0 } + +# -------------------- GStreamer error log checker -------------------- +# gstreamer_check_errors +# Returns: 0 if no critical errors found, 1 if errors found +# Checks for common GStreamer ERROR patterns that indicate failure +# Uses severity-based matching to avoid false positives on benign logs +gstreamer_check_errors() { + logfile="$1" + + [ -f "$logfile" ] || return 0 + + # Check for explicit ERROR: prefixed messages (most reliable) + if grep -q -E "^ERROR:|^0:[0-9]+:[0-9]+\.[0-9]+ [0-9]+ [^ ]+ ERROR" "$logfile" 2>/dev/null; then + return 1 + fi + + # Check for ERROR messages from GStreamer elements + if grep -q -E "ERROR: from element|gst.*ERROR" "$logfile" 2>/dev/null; then + return 1 + fi + + # Check for critical streaming errors + if grep -q -E "Internal data stream error|streaming stopped, reason not-negotiated" "$logfile" 2>/dev/null; then + return 1 + fi + + # Check for pipeline failures (more specific patterns) + if grep -q -E "pipeline doesn't want to preroll|pipeline doesn't want to play|ERROR.*pipeline" "$logfile" 2>/dev/null; then + return 1 + fi + + # Check for state change failures (require ERROR context) + if grep -q -E "ERROR.*failed to change state|ERROR.*state change failed" "$logfile" 2>/dev/null; then + return 1 + fi + + # Check for specific error patterns with proper grouping + if grep -q -E '(^ERROR:|ERROR: from element|Internal data stream error|streaming stopped, reason not-negotiated|pipeline.*failed|state change failed|Could not open resource|No such file or directory)' "$logfile" 2>/dev/null; then + return 1 + fi + + # Check for CRITICAL or FATAL level messages (keep these as they are actual severity indicators) + if grep -q -E '(^CRITICAL:|^FATAL:|gst.*(CRITICAL|FATAL))' "$logfile" 2>/dev/null; then + return 1 + fi + + return 0 +} + +# -------------------- GStreamer log validation with detailed reporting -------------------- +# gstreamer_validate_log +# Returns: 0 if validation passes, 1 if errors found +# Logs detailed error information if errors are detected +gstreamer_validate_log() { + logfile="$1" + testname="${2:-test}" + + [ -f "$logfile" ] || { + log_warn "$testname: Log file not found: $logfile" + return 1 + } + + if ! gstreamer_check_errors "$logfile"; then + log_fail "$testname: GStreamer errors detected in log" + + # Extract and log specific error messages + if grep -q "ERROR:" "$logfile" 2>/dev/null; then + log_fail "Error messages found:" + grep "ERROR:" "$logfile" 2>/dev/null | head -n 5 | while IFS= read -r line; do + log_fail " $line" + done + fi + + # Check for specific failure reasons + if grep -q "not-negotiated" "$logfile" 2>/dev/null; then + log_fail " Reason: Format negotiation failed (caps mismatch)" + fi + + if grep -q "Could not open" "$logfile" 2>/dev/null; then + log_fail " Reason: File or device access failed" + fi + + if grep -q "No such file" "$logfile" 2>/dev/null; then + log_fail " Reason: File not found" + fi + + return 1 + fi + + return 0 +} + +# -------------------- Video codec helpers (V4L2) -------------------- +# gstreamer_resolution_to_wh +# Converts resolution name to width and height +# Prints: " " +gstreamer_resolution_to_wh() { + res="$1" + # Validate input + [ -z "$res" ] && { + printf '%s %s\n' "640" "480" # Default resolution if none provided + return 0 + } + + # Convert to lowercase for case-insensitive matching + res=$(printf '%s' "$res" | tr '[:upper:]' '[:lower:]') + + case "$res" in + 480p) + printf '%s %s\n' "640" "480" + ;; + 720p) + printf '%s %s\n' "1280" "720" + ;; + 1080p|fhd) + printf '%s %s\n' "1920" "1080" + ;; + 4k|4K|2160p|uhd) + printf '%s %s\n' "3840" "2160" + ;; + # Support explicit WxH format (e.g. "1920x1080") + *x*) + w=$(printf '%s' "$res" | cut -d'x' -f1) + h=$(printf '%s' "$res" | cut -d'x' -f2) + case "$w" in + ''|*[!0-9]*) w="640" ;; # Default if invalid + esac + case "$h" in + ''|*[!0-9]*) h="480" ;; # Default if invalid + esac + printf '%s %s\n' "$w" "$h" + ;; + *) + printf '%s %s\n' "640" "480" # Default for unknown formats + ;; + esac +} + +# gstreamer_v4l2_encoder_for_codec +# Returns the V4L2 encoder element for the given codec +# Supports: H.264, H.265 (VP9 is decode-only, no encoder support) +# Prints: encoder element name or empty string if not available +gstreamer_v4l2_encoder_for_codec() { + codec="$1" + case "$codec" in + h264) + if has_element v4l2h264enc; then + printf '%s\n' "v4l2h264enc" + return 0 + fi + ;; + h265|hevc) + if has_element v4l2h265enc; then + printf '%s\n' "v4l2h265enc" + return 0 + fi + ;; + vp9) + # VP9 is decode-only, no encoder support + printf '%s\n' "" + return 1 + ;; + esac + printf '%s\n' "" + return 1 +} + +# gstreamer_v4l2_decoder_for_codec +# Returns the V4L2 decoder element for the given codec +# Prints: decoder element name or empty string if not available +gstreamer_v4l2_decoder_for_codec() { + codec="$1" + case "$codec" in + h264) + if has_element v4l2h264dec; then + printf '%s\n' "v4l2h264dec" + return 0 + fi + ;; + h265|hevc) + if has_element v4l2h265dec; then + printf '%s\n' "v4l2h265dec" + return 0 + fi + ;; + vp9) + if has_element v4l2vp9dec; then + printf '%s\n' "v4l2vp9dec" + return 0 + fi + ;; + esac + printf '%s\n' "" + return 1 +} + +# gstreamer_container_ext_for_codec +# Returns the default container file extension for the given video codec. +# This standardizes container format selection across encode/decode operations: +# - H.264/H.265: mp4 container (ISO BMFF/MP4) - encode & decode supported +# - VP9: webm container (WebM) - decode-only +# +# The encode pipeline builders (gstreamer_build_v4l2_encode_pipeline) use +# appropriate muxers (mp4mux for H.264/H.265). VP9 encoding is not supported. +# The decode pipeline builders (gstreamer_build_v4l2_decode_pipeline) use +# appropriate demuxers (qtdemux for MP4, matroskademux for WebM). +# +# Prints: file extension (without dot) - "mp4", "webm", etc. +gstreamer_container_ext_for_codec() { + codec="$1" + case "$codec" in + vp9) + # VP9 uses WebM container format (Matroska-based) + printf '%s\n' "webm" + ;; + h264|h265|hevc) + # H.264/H.265 use MP4 container format (ISO BMFF) + printf '%s\n' "mp4" + ;; + *) + # Default to MP4 for unknown codecs + printf '%s\n' "mp4" + ;; + esac +} + +# -------------------- Bitrate and file size helpers -------------------- +# gstreamer_bitrate_for_resolution +# Returns recommended bitrate in bps based on resolution +# Prints: bitrate in bps +gstreamer_bitrate_for_resolution() { + width="$1" + height="$2" + + # Default bitrate calculation + bitrate=8000000 + if [ "$width" -le 640 ]; then + bitrate=1000000 + elif [ "$width" -le 1280 ]; then + bitrate=2000000 + elif [ "$width" -le 1920 ]; then + bitrate=4000000 + fi + + printf '%s\n' "$bitrate" +} + +# gstreamer_file_size_bytes +# Returns file size in bytes (portable across BSD/GNU stat) +# Prints: file size in bytes or 0 if file doesn't exist +gstreamer_file_size_bytes() { + filepath="$1" + + [ -f "$filepath" ] || { printf '%s\n' "0"; return 1; } + + # Try BSD stat first, then GNU stat + file_size=$(stat -f%z "$filepath" 2>/dev/null || stat -c%s "$filepath" 2>/dev/null || echo 0) + printf '%s\n' "$file_size" +} + +# -------------------- V4L2 encode pipeline builder -------------------- +# gstreamer_build_v4l2_encode_pipeline +# Builds a complete V4L2 encode pipeline string +# Prints: pipeline string or empty if encoder not available +gstreamer_build_v4l2_encode_pipeline() { + codec="$1" + width="$2" + height="$3" + duration="$4" + framerate="$5" + bitrate="$6" + output_file="$7" + video_stack="${8:-upstream}" + + # Validate numeric parameters + case "$duration" in + ''|*[!0-9]*) duration=30 ;; # Default 30s for invalid/non-numeric duration + esac + + case "$framerate" in + ''|*[!0-9]*) framerate=30 ;; # Default 30fps for invalid/non-numeric framerate + esac + + encoder=$(gstreamer_v4l2_encoder_for_codec "$codec") + if [ -z "$encoder" ]; then + printf '%s\n' "" + return 1 + fi + + # Determine parser based on codec + case "$codec" in + h264) + parser="h264parse" + ;; + h265|hevc) + parser="h265parse" + ;; + *) + parser="" + ;; + esac + + # Build encoder parameters + encoder_params="extra-controls=\"controls,video_bitrate=${bitrate}\"" + if [ "$video_stack" = "downstream" ]; then + encoder_params="${encoder_params} capture-io-mode=4 output-io-mode=4" + fi + + # Calculate total frames with numeric safety + total_frames=0 + if [ "$duration" -gt 0 ] 2>/dev/null && [ "$framerate" -gt 0 ] 2>/dev/null; then + total_frames=$((duration * framerate)) + else + total_frames=900 # Default 30s * 30fps = 900 frames + fi + + # Build pipeline with mp4mux for MP4 container + if [ -n "$parser" ]; then + printf '%s\n' "videotestsrc num-buffers=${total_frames} pattern=smpte ! video/x-raw,width=${width},height=${height},format=NV12,framerate=${framerate}/1 ! ${encoder} ${encoder_params} ! ${parser} ! mp4mux ! filesink location=${output_file}" + else + printf '%s\n' "videotestsrc num-buffers=${total_frames} pattern=smpte ! video/x-raw,width=${width},height=${height},format=NV12,framerate=${framerate}/1 ! ${encoder} ${encoder_params} ! mp4mux ! filesink location=${output_file}" + fi + + return 0 +} + +# -------------------- V4L2 decode pipeline builder -------------------- +# gstreamer_build_v4l2_decode_pipeline +# Builds a complete V4L2 decode pipeline string +# Prints: pipeline string or empty if decoder not available +gstreamer_build_v4l2_decode_pipeline() { + codec="$1" + input_file="$2" + video_stack="${3:-upstream}" + + decoder=$(gstreamer_v4l2_decoder_for_codec "$codec") + if [ -z "$decoder" ]; then + printf '%s\n' "" + return 1 + fi + + # Determine parser and container based on codec + case "$codec" in + h264) + parser="h264parse" + container="qtdemux" + ;; + h265|hevc) + parser="h265parse" + container="qtdemux" + ;; + vp9) + # Try to use vp9parse if available, otherwise skip parser + if has_element vp9parse; then + parser="vp9parse" + else + parser="" + fi + container="matroskademux" + ;; + esac + + # Build decoder parameters + decoder_params="" + if [ "$video_stack" = "downstream" ]; then + decoder_params="capture-io-mode=4 output-io-mode=4" + fi + + # Build pipeline based on parser availability + # All supported formats (h264, h265, vp9) have containers (MP4 or WebM) + if [ -n "$parser" ]; then + # Use parser if available + if [ -n "$decoder_params" ]; then + printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${parser} ! ${decoder} ${decoder_params} ! videoconvert ! fakesink" + else + printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${parser} ! ${decoder} ! videoconvert ! fakesink" + fi + else + # Skip parser if not available (e.g. VP9 without vp9parse) + if [ -n "$decoder_params" ]; then + printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${decoder} ${decoder_params} ! videoconvert ! fakesink" + else + printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${decoder} ! videoconvert ! fakesink" + fi + fi + + return 0 +} + +prepare_vp9_from_local_path() { + src="$1" + outdir="$2" + ivf_out="$3" + webm_out="$4" + + [ -n "$src" ] || return 1 + [ -e "$src" ] || return 1 + + # If directory: search inside for clips + if [ -d "$src" ]; then + found_webm=$(find "$src" -type f -name '*.webm' 2>/dev/null | head -n 1 || true) + found_ivf=$(find "$src" -type f -name '*.ivf' 2>/dev/null | head -n 1 || true) + + if [ -n "$found_webm" ] && [ ! -f "$webm_out" ]; then + cp "$found_webm" "$webm_out" 2>/dev/null || true + fi + if [ -n "$found_ivf" ] && [ ! -f "$ivf_out" ]; then + cp "$found_ivf" "$ivf_out" 2>/dev/null || true + fi + + [ -f "$webm_out" ] || [ -f "$ivf_out" ] + return $? + fi + + # If file: extract to a staging dir (tar/tar.gz/tgz/tar.xz/txz supported) + if [ -f "$src" ]; then + stage="$outdir/local_clip_stage" + mkdir -p "$stage" >/dev/null 2>&1 || true + + case "$src" in + *.tar) + tar -xf "$src" -C "$stage" >/dev/null 2>&1 || return 1 + ;; + *.tar.gz|*.tgz) + tar -xzf "$src" -C "$stage" >/dev/null 2>&1 || return 1 + ;; + *.tar.xz|*.txz) + tar -xJf "$src" -C "$stage" >/dev/null 2>&1 || return 1 + ;; + *.xz) + # Could be .tar.xz already handled above, else try decompressing single file + if command -v xz >/dev/null 2>&1; then + base=$(basename "$src" .xz) + out="$stage/$base" + xz -dc "$src" >"$out" 2>/dev/null || return 1 + case "$out" in + *.tar) + tar -xf "$out" -C "$stage" >/dev/null 2>&1 || return 1 + ;; + esac + else + return 1 + fi + ;; + *) + # Unknown file type; still try as a direct clip file + stage="$src" + ;; + esac + + found_webm=$(find "$stage" -type f -name '*.webm' 2>/dev/null | head -n 1 || true) + found_ivf=$(find "$stage" -type f -name '*.ivf' 2>/dev/null | head -n 1 || true) + + if [ -n "$found_webm" ] && [ ! -f "$webm_out" ]; then + cp "$found_webm" "$webm_out" 2>/dev/null || true + fi + if [ -n "$found_ivf" ] && [ ! -f "$ivf_out" ]; then + cp "$found_ivf" "$ivf_out" 2>/dev/null || true + fi + + [ -f "$webm_out" ] || [ -f "$ivf_out" ] + return $? + fi + + return 1 +}