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..2356d5b6 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/Waylandsink_Playback.yaml @@ -0,0 +1,70 @@ +metadata: + name: gstreamer-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: + # Playback duration in seconds (default: 30) + # Priority: VIDEO_DURATION > RUNTIMESEC + VIDEO_DURATION: "30" + RUNTIMESEC: "" # if set, used as fallback + + # Test pattern for videotestsrc (default: smpte) + VIDEO_PATTERN: "smpte" # smpte|snow|black|white|red|green|blue|checkers-1|checkers-2|ball + + # Video width in pixels (default: 1920) + VIDEO_WIDTH: "1920" + + # Video height in pixels (default: 1080) + VIDEO_HEIGHT: "1080" + + # Frame rate (default: 30) + VIDEO_FRAMERATE: "30" + + # GStreamer debug level (default: 2) + # Priority: VIDEO_GST_DEBUG > GST_DEBUG_LEVEL + VIDEO_GST_DEBUG: "2" # 1-9 + GST_DEBUG_LEVEL: "" # if set, used as fallback + +run: + steps: + - REPO_PATH="$PWD" + + # Navigate to test directory + - cd Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/ + + # Export environment variables (script reads these directly) + - export VIDEO_DURATION="${VIDEO_DURATION}" + - export RUNTIMESEC="${RUNTIMESEC}" + - export VIDEO_PATTERN="${VIDEO_PATTERN}" + - export VIDEO_WIDTH="${VIDEO_WIDTH}" + - export VIDEO_HEIGHT="${VIDEO_HEIGHT}" + - export VIDEO_FRAMERATE="${VIDEO_FRAMERATE}" + - export VIDEO_GST_DEBUG="${VIDEO_GST_DEBUG}" + - export GST_DEBUG_LEVEL="${GST_DEBUG_LEVEL}" + + # Build CLI args for overrides (optional - can also rely on env vars) + - | + CMD="./run.sh" + + # Use CLI args to override defaults if needed + # Note: Script reads env vars by default, CLI args override env vars + [ -n "${VIDEO_WIDTH}" ] && [ -n "${VIDEO_HEIGHT}" ] && CMD="${CMD} --resolution ${VIDEO_WIDTH}x${VIDEO_HEIGHT}" + [ -n "${VIDEO_PATTERN}" ] && CMD="${CMD} --pattern ${VIDEO_PATTERN}" + [ -n "${VIDEO_DURATION}" ] && CMD="${CMD} --duration ${VIDEO_DURATION}" + [ -n "${VIDEO_FRAMERATE}" ] && CMD="${CMD} --framerate ${VIDEO_FRAMERATE}" + [ -n "${VIDEO_GST_DEBUG}" ] && CMD="${CMD} --gst-debug ${VIDEO_GST_DEBUG}" + + echo "[LAVA] Running: ${CMD}" + sh -c "${CMD}" || true + + # Send result to LAVA + - "${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..23ce50c4 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Display/Waylandsink_Playback/run.sh @@ -0,0 +1,476 @@ +#!/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 +)" + +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 + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 0 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" + +# 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" + +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" + +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 + +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() { + 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 + --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 $2 is empty, keep default and shift 2 + [ -n "$2" ] && 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 + +# -------------------- Pre-checks -------------------- +check_dependencies "gst-launch-1.0 gst-inspect-1.0 grep head sed" >/dev/null 2>&1 || { + log_skip "Missing required tools (gst-launch-1.0, gst-inspect-1.0, grep, head, sed)" + 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 + +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" + 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" ] && command -v adopt_wayland_env_from_socket >/dev/null 2>&1; then + log_info "Weston created Wayland socket: $sock" + if ! adopt_wayland_env_from_socket "$sock"; then + log_warn "Failed to adopt env from $sock" + 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_fail "Wayland connection test failed; cannot run ${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 -------------------- +num_buffers=$((duration * framerate)) + +pipeline="videotestsrc 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) + +if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$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 -------------------- +# Duration threshold (allow 2s slack) +min_duration=$((duration - 2)) + +# 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 + # Create temp file with last 1000 lines + tail -n 1000 "$GST_LOG" > "${GST_LOG}.tail" + if ! gstreamer_validate_log "${GST_LOG}.tail" "$TESTNAME"; then + gst_log_ok=0 + 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|137|143) # Timeout/kill signals - expected for long duration tests + result="PASS" + reason="Playback completed via ${gstRc} (SIGTERM=143, SIGKILL=137, GNU timeout=124) after ${elapsed}/${duration}s" + ;; + *) # 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 + +# -------------------- 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..adf22c5b --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md @@ -0,0 +1,644 @@ +# 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 clips converted to WebM) + +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 pre-encoded 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_320p.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 + 320_240_10fps.ivf # Downloaded VP9 clip (IVF format) + vp9_test_320p.webm # Converted VP9 clip (WebM format - used for decode test) + 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 +- `ivfparse` - IVF container parser (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) + +### VP9 Clip Conversion Pipeline + +Before decoding, the downloaded IVF file is converted to WebM (Matroska) container: + +``` +filesrc location=320_240_10fps.ivf + ! ivfparse + ! matroskamux + ! filesink location=vp9_test_320p.webm +``` + +Where: +- `ivfparse` parses the downloaded IVF container +- `matroskamux` remuxes to WebM/Matroska container +- **If conversion fails**: Test is skipped with reason "GST conversion failure" +- **No IVF fallback**: The test will not use IVF directly if conversion fails + +### Decoding Pipeline (VP9) + +``` +filesrc location=vp9_test_320p.webm + ! matroskademux + ! v4l2vp9dec + ! videoconvert + ! fakesink +``` + +Where: +- `matroskademux` parses WebM/Matroska container format +- Input file is the converted WebM file (not IVF directly) +- Resolution: 320x240 +- **Important**: Test skips if WebM conversion failed (no IVF fallback) + +--- + +## 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/320_240_10fps.ivf` +- Manually download if needed: + ```bash + cd logs/Video_Encode_Decode/ + wget https://github.com/qualcomm-linux/qcom-linux-testkit/releases/download/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz + tar -xzf video_clips_iris.tar.gz + ``` +- Check network connectivity: + ```bash + ping -c 3 github.com + ``` + +### I) VP9 decode fails with "ivfparse not found" +- Ensure `ivfparse` GStreamer plugin is installed: + ```bash + gst-inspect-1.0 ivfparse + ``` +- This is typically part of `gst-plugins-bad` package + +### J) VP9 test skips with "GST conversion failure" +- The IVF to WebM conversion failed +- Check if `matroskamux` plugin is available: + ```bash + gst-inspect-1.0 matroskamux + ``` +- Check if `ivfparse` plugin is available: + ```bash + gst-inspect-1.0 ivfparse + ``` +- Manually test the conversion: + ```bash + cd logs/Video_Encode_Decode/ + gst-launch-1.0 filesrc location=320_240_10fps.ivf ! ivfparse ! matroskamux ! filesink location=test.webm + ``` +- Check GStreamer debug output for errors: + ```bash + GST_DEBUG=3 gst-launch-1.0 filesrc location=320_240_10fps.ivf ! ivfparse ! matroskamux ! filesink location=test.webm + ``` +- **Note**: The test will NOT fall back to using IVF directly. If conversion fails, the test skips to ensure proper container format validation + +--- + +## 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: 320_240_10fps.ivf (320x240 resolution, IVF 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..5c11d51f --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml @@ -0,0 +1,85 @@ +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 + on Qualcomm Linux platforms. Supports v4l2h264enc, v4l2h265enc, v4l2h264dec, v4l2h265dec, v4l2vp9dec. + Uses videotestsrc to generate test patterns for H.264/H.265 (no external video files needed). + For VP9 decode, downloads pre-encoded IVF clips from git repo, converts them to WebM container format, + then decodes using v4l2vp9dec. Test skips if IVF to WebM conversion fails (no IVF fallback). + Tests encoding at 480p and 4K resolutions, then decodes the encoded files. + os: + - linux + scope: + - functional + +params: + # Test mode: all (encode+decode), encode (only), decode (only) (default: all) + VIDEO_TEST_MODE: "all" # all|encode|decode + + # Codecs to test (comma-separated) (default: h264,h265,vp9) + # Note: VP9 only supports decode (no encode) + VIDEO_CODECS: "h264,h265,vp9" # h264,h265,vp9 + + # Resolutions to test (comma-separated) (default: 4k) + # Supported: 480p (640x480), 720p (1280x720), 1080p (1920x1080), 4k (3840x2160) + VIDEO_RESOLUTIONS: "4k" # 480p,720p,1080p,4k + + # Encoding duration in seconds (default: 30) + # Priority: VIDEO_DURATION > RUNTIMESEC + VIDEO_DURATION: "30" # seconds + RUNTIMESEC: "" # if set, used as fallback + + # Video framerate (default: 30) + VIDEO_FRAMERATE: "30" # fps + + # Video stack selection (default: auto) + VIDEO_STACK: "auto" # auto|upstream|downstream + + # GStreamer debug level (default: 2) + # Priority: VIDEO_GST_DEBUG > GST_DEBUG_LEVEL + VIDEO_GST_DEBUG: "2" # 1-9 + GST_DEBUG_LEVEL: "" # if set, used as fallback + + # URL for VP9 clip download (default: GitHub releases) + VIDEO_CLIP_URL: "" # if set, overrides default GitHub URL + +run: + steps: + - REPO_PATH="$PWD" + + # Navigate to test directory + - cd Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/ + + # Export environment variables (script reads these directly) + - export VIDEO_TEST_MODE="${VIDEO_TEST_MODE}" + - export VIDEO_CODECS="${VIDEO_CODECS}" + - export VIDEO_RESOLUTIONS="${VIDEO_RESOLUTIONS}" + - export VIDEO_DURATION="${VIDEO_DURATION}" + - export RUNTIMESEC="${RUNTIMESEC}" + - export VIDEO_FRAMERATE="${VIDEO_FRAMERATE}" + - export VIDEO_STACK="${VIDEO_STACK}" + - export VIDEO_GST_DEBUG="${VIDEO_GST_DEBUG}" + - export GST_DEBUG_LEVEL="${GST_DEBUG_LEVEL}" + - export VIDEO_CLIP_URL="${VIDEO_CLIP_URL}" + + # Build CLI args for overrides (optional - can also rely on env vars) + - | + CMD="./run.sh" + + # Use CLI args to override defaults if needed + # Note: Script reads env vars by default, CLI args override env vars + [ -n "${VIDEO_TEST_MODE}" ] && CMD="${CMD} --mode ${VIDEO_TEST_MODE}" + [ -n "${VIDEO_CODECS}" ] && CMD="${CMD} --codecs ${VIDEO_CODECS}" + [ -n "${VIDEO_RESOLUTIONS}" ] && CMD="${CMD} --resolutions ${VIDEO_RESOLUTIONS}" + [ -n "${VIDEO_DURATION}" ] && CMD="${CMD} --duration ${VIDEO_DURATION}" + [ -n "${VIDEO_FRAMERATE}" ] && CMD="${CMD} --framerate ${VIDEO_FRAMERATE}" + [ -n "${VIDEO_STACK}" ] && CMD="${CMD} --stack ${VIDEO_STACK}" + [ -n "${VIDEO_GST_DEBUG}" ] && CMD="${CMD} --gst-debug ${VIDEO_GST_DEBUG}" + [ -n "${VIDEO_CLIP_URL}" ] && CMD="${CMD} --clip-url ${VIDEO_CLIP_URL}" + + echo "[LAVA] Running: ${CMD}" + sh -c "${CMD}" || true + + # Send result to LAVA + - "${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..12d1aaa4 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh @@ -0,0 +1,667 @@ +#!/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 +)" + +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 + +if [ -z "$INIT_ENV" ]; then + echo "[ERROR] Could not find init_env (starting at $SCRIPT_DIR)" >&2 + exit 0 +fi + +# shellcheck disable=SC1090 +. "$INIT_ENV" + +# 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" + +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" + +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}" +resolutionList="${VIDEO_RESOLUTIONS:-480p,4k}" +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/IRIS-Video-Files-v1.0/video_clips_iris.tar.gz}" + +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 + ;; + + --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 + ;; + + --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 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 + ;; + + -h|--help) + cat < + Default: all (run both encode and decode tests) + + --codecs + Comma-separated list of codecs to test + Default: h264,h265 + Note: VP9 only supports decode mode with pre-existing clips + + --resolutions <480p,4k> + Comma-separated list of resolutions to test + Default: 480p,4k (480p=640x480, 4k=3840x2160) + + --duration + Duration for encoding (in seconds) + Default: ${duration} + + --framerate + Framerate for video generation + Default: ${framerate} + + --stack + Video stack selection + Default: auto + + --gst-debug + Sets GST_DEBUG= (1-9) + Default: ${gstDebugLevel} + + --clip-url + URL to download video clips for VP9 decode tests + Default: ${clipUrl} + +Examples: + # Run all tests (encode + decode) for H264 and H265 at 480p and 4K + ./run.sh + + # Run only encoding tests + ./run.sh --mode encode + + # Run only H264 tests at 480p + ./run.sh --codecs h264 --resolutions 480p + + # Run with 10 second duration + ./run.sh --duration 10 + + # Run VP9 decode test + ./run.sh --mode decode --codecs vp9 + +EOF + 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 + +# -------------------- Pre-checks -------------------- +check_dependencies "gst-launch-1.0 gst-inspect-1.0 awk grep head sed tr stat" >/dev/null 2>&1 || { + log_skip "Missing required tools (gst-launch-1.0, gst-inspect-1.0, awk, grep, head, sed, tr, stat)" + echo "$TESTNAME SKIP" >"$RES_FILE" + exit 0 +} + +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" + +# -------------------- 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 pre-downloaded and converted WebM clip; for others, use encoded file + if [ "$codec" = "vp9" ]; then + input_file="$OUTDIR/vp9_test_320p.webm" + if [ ! -f "$input_file" ]; then + log_warn "VP9 WebM clip not found: $input_file (conversion may have failed)" + 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 download (if VP9 in codec list) -------------------- +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 DOWNLOAD & CONVERSION" + log_info "==========================================" + + vp9_clip_ivf="$OUTDIR/320_240_10fps.ivf" + vp9_clip_webm="$OUTDIR/vp9_test_320p.webm" + + # Check if WebM file already exists + if [ -f "$vp9_clip_webm" ]; then + log_info "VP9 WebM clip already exists: $vp9_clip_webm" + else + # Download IVF file if not present + if [ ! -f "$vp9_clip_ivf" ]; then + log_info "Checking network connectivity and downloading VP9 clips..." + + # Check network status first + net_rc=1 + if command -v check_network_status_rc >/dev/null 2>&1; then + check_network_status_rc + net_rc=$? + elif command -v check_network_status >/dev/null 2>&1; then + check_network_status >/dev/null 2>&1 + net_rc=$? + fi + + # If offline, try to bring network online + if [ "$net_rc" -ne 0 ]; then + if command -v bring_network_online >/dev/null 2>&1; then + log_info "Attempting to bring network online..." + bring_network_online + # Stabilization sleep after bringing network up + sleep 5 + else + log_warn "Could not establish network connectivity" + fi + else + log_info "Network already online" + # Brief stabilization sleep + sleep 2 + fi + + # Attempt download if we have connectivity + if command -v check_network_status_rc >/dev/null 2>&1; then + if check_network_status_rc; then + log_info "Downloading VP9 clips from: $clipUrl" + if extract_tar_from_url "$clipUrl" "$OUTDIR"; then + log_pass "VP9 clips downloaded and extracted successfully" + else + log_warn "Failed to download/extract VP9 clips (network online but download failed)" + fi + else + log_warn "Network still offline after connectivity attempt" + fi + else + # Fallback: attempt download without explicit network check + log_info "Downloading VP9 clips from: $clipUrl" + if extract_tar_from_url "$clipUrl" "$OUTDIR"; then + log_pass "VP9 clips downloaded and extracted successfully" + else + log_warn "Failed to download/extract VP9 clips" + fi + fi + fi + + # Verify clip exists after download attempt + if [ ! -f "$vp9_clip_ivf" ]; then + log_warn "VP9 clip not found after download attempt: $vp9_clip_ivf" + log_warn "VP9 decode tests will be skipped" + else + # Convert IVF to WebM container using GStreamer for better compatibility + if [ ! -f "$vp9_clip_webm" ]; then + log_info "Converting IVF to WebM container using GStreamer..." + + # Use GStreamer pipeline to remux IVF to WebM (Matroska container) + pipeline="filesrc location=\"$vp9_clip_ivf\" ! ivfparse ! matroskamux ! filesink location=\"$vp9_clip_webm\"" + if gstreamer_run_gstlaunch_timeout 30 "$pipeline" >/dev/null 2>&1; then + log_pass "Successfully converted IVF to WebM (320x240)" + else + log_fail "GStreamer IVF to WebM conversion failed" + log_warn "VP9 decode tests will be skipped (reason: GST conversion failure)" + rm -f "$vp9_clip_webm" 2>/dev/null || true + rm -f "$vp9_clip_ivf" 2>/dev/null || true + fi + else + log_info "WebM file already exists: $vp9_clip_webm" + 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 in this test suite)" + continue + fi + + for res in $resolutions; do + params=$(gstreamer_resolution_to_wh "$res") + set -- $params + width="$1" + height="$2" + + 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 + # For VP9, only run once (not per resolution, as we use a fixed 320p clip) + if [ "$codec" = "vp9" ]; then + total_tests=$((total_tests + 1)) + run_decode_test "$codec" "320p" || 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 "==========================================" +log_info "Total tests: $total_tests" +log_info "Passed: $pass_count" +log_info "Failed: $fail_count" +log_info "Skipped: $skip_count" + +# -------------------- Emit result -------------------- +if [ "$pass_count" -gt 0 ] && [ "$fail_count" -eq 0 ]; then + result="PASS" + reason="All tests passed ($pass_count/$total_tests)" +elif [ "$pass_count" -gt 0 ] && [ "$fail_count" -gt 0 ]; then + result="FAIL" + reason="Some tests failed (passed: $pass_count, failed: $fail_count)" +elif [ "$fail_count" -gt 0 ]; then + result="FAIL" + reason="All tests failed ($fail_count/$total_tests)" +else + result="SKIP" + reason="No tests executed or all skipped" +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 95b90f19..b6a39b2d 100755 --- a/Runner/utils/lib_gstreamer.sh +++ b/Runner/utils/lib_gstreamer.sh @@ -1,7 +1,6 @@ #!/bin/sh # Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. -# SPDX-License-Identifier: BSD-3-Clause-Clear -# +# SPDX-License-Identifier: BSD-3-Clause # Runner/utils/lib_gstreamer.sh # # GStreamer helpers. @@ -498,3 +497,389 @@ 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) + parser="" + container="matroskademux" + ;; + *) + parser="identity" + container="" + ;; + 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 container format + if [ -n "$container" ]; then + # MP4 container (h264/h265) + 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 + # VP9 with webm container + if [ -n "$decoder_params" ]; then + printf '%s\n' "filesrc location=${input_file} ! ${parser} ! ${decoder} ${decoder_params} ! videoconvert ! fakesink" + else + printf '%s\n' "filesrc location=${input_file} ! ${parser} ! ${decoder} ! videoconvert ! fakesink" + fi + fi + + return 0 +}