From c1513344c45c8d0cb1525062a63fc42a513a0445 Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Sat, 7 Feb 2026 23:59:39 +0530 Subject: [PATCH 1/8] Add GStreamer AI_ML test suite Signed-off-by: Vij Patel --- .../Multimedia/GSTreamer/AI_ML/AI_ML.yaml | 20 ++ .../Multimedia/GSTreamer/AI_ML/Readme.md | 192 ++++++++++++ .../suites/Multimedia/GSTreamer/AI_ML/run.sh | 285 ++++++++++++++++++ Runner/utils/lib_gstreamer.sh | 225 ++++++++++++++ 4 files changed, 722 insertions(+) create mode 100644 Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml create mode 100644 Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md create mode 100644 Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml b/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml new file mode 100644 index 00000000..14b654c3 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml @@ -0,0 +1,20 @@ +metadata: + name: gstreamer-ai-ml + format: "Lava-Test Test Definition 1.0" + description: "Validates AI/ML inference pipelines using GStreamer (gst-launch-1.0) with Qualcomm AI RT (QAIRT) runtime components for image classification and object detection use cases." + os: + - linux + scope: + - functional + +params: + TIMEOUT: 60 # Timeout in seconds for each pipeline (default in script is 60) + GST_DEBUG_LEVEL: 2 # GStreamer debug level (default in script is 2) + OUTPUT_VIDEO_PATH: "/opt/video_out.mp4" # Path for encoded output video + +run: + steps: + - REPO_PATH=$PWD + - cd Runner/suites/Multimedia/GSTreamer/AI_ML/ + - ./run.sh --timeout "${TIMEOUT}" --gstdebug "${GST_DEBUG_LEVEL}" --output-video "${OUTPUT_VIDEO_PATH}" || true + - $REPO_PATH/Runner/utils/send-to-lava.sh AI_ML.res || true \ No newline at end of file diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md b/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md new file mode 100644 index 00000000..9f4b566f --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md @@ -0,0 +1,192 @@ +# AI_ML — Runner Test + +This directory contains the **AI_ML** validation test for Qualcomm Linux Testkit runners. + +It validates **AI/ML inference pipelines** using **GStreamer (`gst-launch-1.0`)** with Qualcomm AI RT (QAIRT) runtime components for computer vision use cases: +- **Image Classification** +Streams a video (`video.mp4`) through **Inception‑v3** TensorFlow‑Lite quantized model. + The frames are decoded, and the top‑5 class scores are over‑laid on‑frame. The pipeline re‑encodes the video and writes the result to the path supplied via `--output-video`. + +- **Object Detection** \ +Streams the video (`video.mp4`) through **YoloX** TensorFlow‑Lite quantized model. After decoding, each frame is sent to model and detected bounding boxes are composited onto the video and displayed full‑screen on Wayland socket. + +The script is designed to be **CI/LAVA-friendly**: +- Writes **PASS/FAIL** into `AI_ML.res` +- Always **exits 0** (even on FAIL) to avoid terminating LAVA jobs early +- Logs detailed output for each pipeline test +- Downloads required AI models automatically when connected to WiFi + +--- + +## Location in repo + +Expected path: + +``` +Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh +``` + +Required shared utils (sourced from `Runner/utils` via `init_env`): +- `functestlib.sh` +- `lib_gstreamer.sh` + +--- + +## What this test does + +At a high level, the test: + +1. Finds and sources `init_env` +2. Sources `$TOOLS/functestlib.sh` and `$TOOLS/lib_gstreamer.sh` +3. Sets Wayland environment variables for proper display +4. Connects to Ethernet network to download required AI models +5. Defines and validates **2 different AI pipeline tests** covering: + - Image Classification (Inception-v3) + - Object Detection (YoloX) +6. For each pipeline: + - Checks if all required GStreamer elements are available + - Runs the pipeline with a timeout + - Validates successful execution based on GStreamer state changes + - Logs detailed output for diagnostics +7. Generates a comprehensive report with pass/fail status for each pipeline +8. Produces a final PASS/FAIL result for the entire suite + +--- + +## PASS / FAIL criteria + +### PASS +- **All pipelines** successfully run to PLAYING state without critical errors +- Output file contains `PASS AI_ML` +- Exit code 0 + +### FAIL +- **Any pipeline** fails to reach PLAYING state or encounters critical errors +- Output file contains `FAIL AI_ML` +- Exit code 1 + +**Note:** The test always exits `0` even for FAIL. The `.res` file is the source of truth. + +--- + +## Logs and artifacts + +By default, logs are written relative to the script working directory: + +``` +./AI_ML.res +./logs/ + object_detection_console.log + object_detection_gst_debug.log + image_classification_console.log + image_classification_gst_debug.log +``` + +Each pipeline log contains detailed GStreamer debug output that can be examined for failures. + +--- + +## Dependencies + +### Required +- `gst-launch-1.0` +- `gst-inspect-1.0` +- `curl`, `unzip` (for model downloads) +- Internet connectivity (to download models) +- Proper QAIRT runtime installation (QNN) + +### Required AI Models (downloaded automatically) +- `inception_v3_quantized.tflite` +- `yolox_quantized.tflite` +- `video.mp4` +- Labels from `labels.zip` (containing classification and detection labels) + +--- + +## Pipeline validation behavior + +The test validates pipelines by: +1. First checking if all required GStreamer elements are available using `gst-inspect-1.0` +2. Running each pipeline with a timeout (default 60 seconds) +3. Checking for the "Setting pipeline to PLAYING" message in logs +4. Ensuring no critical errors occur after reaching PLAYING state +5. Special handling for timeout: if pipeline reached PLAYING before timeout, it's still considered a PASS + +--- + +## Usage + +Run: + +``` +./run.sh [options] +``` + +### Options + +- `--timeout ` + - Sets pipeline execution timeout (default: 60 seconds) +- `--gstdebug ` + - Sets GStreamer debug level (default: 2) +- `--output-video ` + - Path for the encoded video generated by the classification pipeline (default: /opt/video_out.mp4) + +--- + +## Examples + +### 1) Basic execution with default timeout + +``` +./run.sh +``` + +### 2) Increase timeout to 120 seconds and set debug level + +``` +./run.sh --timeout 120 --gstdebug 3 +``` + +### 3) Specify custom output video path + +``` +./run.sh --output-video /tmp/custom_video.mp4 +``` + +--- + +## Troubleshooting + +### A) "FAIL: Test directory not found" +- Ensure the test is located in the expected directory structure within Runner + +### B) "Could not find init_env" +- Verify Runner environment is properly set up +- Check if `init_env` exists in parent directories + +### C) Internet connection failures +- Confirm internet interface is available and functional +- The script uses `ensure_network_online` which automatically connects to available networks +- Verify internet connectivity with ping test + +### D) Pipeline element missing +- Verify all required GStreamer plugins are installed +- Check for proper QAIRT GStreamer plugin integration + +### E) Model download failures +- Confirm internet connectivity +- Verify sufficient storage space for model downloads + +--- + +## Notes for CI / LAVA + +- The test always exits `0` even for FAIL conditions +- Use the `.res` file for result determination: + - `PASS AI_ML` + - `FAIL AI_ML` +- Requires Internet connectivity to download models +- Test duration depends on platform performance and number of successful pipelines +- Each pipeline test has its own timeout to prevent hangs + +--- \ No newline at end of file diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh new file mode 100644 index 00000000..22383107 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh @@ -0,0 +1,285 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear + +#Source init_env and functestlib.sh +SCRIPT_DIR="$(cd "$(dirname "$0")" && 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 1 +fi + +# Only source if not already loaded (idempotent) +if [ -z "$__INIT_ENV_LOADED" ]; then + # shellcheck disable=SC1090 + . "$INIT_ENV" +fi +# ---------------------------------------------------------------------- +# Source the environment and helper libraries. +# ---------------------------------------------------------------------- +# shellcheck disable=SC1090 +. "$INIT_ENV" + +# shellcheck disable=SC1091 +. "$TOOLS/functestlib.sh" + +# shellcheck disable=SC1091 +. "$TOOLS/lib_gstreamer.sh" + +# ---------------------------------------------------------------------- +# Constants & defaults (can be overridden via CLI) +# ---------------------------------------------------------------------- +TESTNAME="AI_ML" +RES_FILE="./${TESTNAME}.res" + +TIMEOUT=60 # seconds +ENCODED_VIDEO_PATH="/opt/video_out.mp4" +GST_DEBUG_LEVEL=2 + +# ---------------------------------------------------------------------- +# GStreamer pipelines (kept on a single line for readability) +# ---------------------------------------------------------------------- +DETECTION_PIPELINE='gst-launch-1.0 -e filesrc location=/opt/video.mp4 ! \ + qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 \ + ! queue ! tee name=split \ + split. ! queue ! qtivcomposer name=mixer ! queue ! waylandsink sync=true fullscreen=true \ + split. ! queue ! qtimlvconverter ! queue ! qtimltflite delegate=external \ + external-delegate-path=libQnnTFLiteDelegate.so \ + external-delegate-options="QNNExternalDelegate,backend_type=htp;" \ + model=/opt/yolox_quantized.tflite ! queue ! qtimlpostprocess \ + module=yolov8 labels=/opt/labels/yolox.json settings="{\"confidence\": 50.0}" \ + ! video/x-raw,format=BGRA,width=640,height=360 ! queue ! mixer.' + +CLASSIFICATION_PIPELINE='gst-launch-1.0 -e filesrc location=/opt/video.mp4 ! \ + qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 \ + ! video/x-raw,format=NV12_Q08C ! queue ! tee name=split \ + ! queue ! qtimetamux name=metamux ! queue ! qtivoverlay ! queue \ + ! v4l2h264enc capture-io-mode=4 output-io-mode=5 ! h264parse ! queue \ + ! mp4mux ! queue ! filesink location='"${ENCODED_VIDEO_PATH}"' \ + split. ! queue ! qtimlvconverter ! queue ! qtimltflite delegate=external \ + external-delegate-path=libQnnTFLiteDelegate.so \ + external-delegate-options="QNNExternalDelegate,backend_type=htp;" \ + model=/opt/inception_v3_quantized.tflite ! queue ! qtimlpostprocess \ + results=5 module=mobilenet-softmax labels=/opt/labels/imagenet_labels.txt \ + settings="{\"confidence\": 31.0}" ! text/x-raw ! queue ! metamux.' + +# ---------------------------------------------------------------------- +# Helper functions +# ---------------------------------------------------------------------- +usage() { + exit_code=0 + if [ "$#" -gt 0 ]; then + printf 'Error: %s\n\n' "$1" >&2 + exit_code=2 + fi + cat <<'EOF' +Usage: run.sh [options] + +Options: + --timeout Maximum time a GStreamer pipeline may run. + Default: 60 seconds + --gstdebug GST_DEBUG level (numeric). Default: 2 + --output-video Path for the encoded video generated by the + classification pipeline. + Default: /opt/video_out.mp4 + -h, --help Show this help message and exit. +EOF + exit "${exit_code}" +} + +# ---------------------------------------------------------------------- +# Pull all required TFLite models / assets. +# ---------------------------------------------------------------------- +download_model_artifacts() { + # ------------------------------------------------------------------ + # Optional: make sure we have network connectivity (kept from the + # original script) + # ------------------------------------------------------------------ + if command -v ensure_network_online >/dev/null 2>&1; then + if ! ensure_network_online; then + log_skip "Network offline/limited; cannot fetch assets" + return 1 + fi + fi + + log_info "Network is online, proceed to download!" + out="/opt" + mkdir -p "${out}" + + # ------------------------------------------------------------------ + # Download the regular (non-zip) assets with download_resource + # ------------------------------------------------------------------ + log_info "Downloading Inception-v3 model..." + download_resource \ + "https://huggingface.co/qualcomm/Inception-v3/resolve/ba8121b0a74c7e28b45b250064c26efc7e7da29e/Inception-v3_w8a8.tflite" \ + "${out}/inception_v3_quantized.tflite" >/dev/null + + log_info "Downloading YoloX model..." + download_resource \ + "https://huggingface.co/qualcomm/Yolo-X/resolve/v0.30.5/Yolo-X_w8a8.tflite" \ + "${out}/yolox_quantized.tflite" >/dev/null + + log_info "Downloading sample video..." + download_resource \ + "https://raw.githubusercontent.com/quic/sample-apps-for-qualcomm-linux/refs/heads/main/artifacts/videos/video.mp4" \ + "${out}/video.mp4" >/dev/null + + # ------------------------------------------------------------------ + # Download zip-files (labels) and extract them + # ------------------------------------------------------------------ + log_info "Downloading labels (set 1)…" + zip_path=$(download_resource \ + "https://github.com/quic/sample-apps-for-qualcomm-linux/releases/download/GA1.5-rel/labels.zip" \ + "${out}") # dest is a directory → original name kept (labels.zip) + extract_zip_to_dir "${zip_path}" "${out}" + + log_info "Downloading labels (set 2)…" + zip_path=$(download_resource \ + "https://github.com/quic/sample-apps-for-qualcomm-linux/releases/download/GA1.6-labels/labels.zip" \ + "${out}") + extract_zip_to_dir "${zip_path}" "${out}" +} + +# ---------------------------------------------------------------------- +# Argument parsing +# ---------------------------------------------------------------------- +while [ "$#" -gt 0 ]; do + case "$1" in + --timeout) + shift + TIMEOUT=$1 + ;; + --gstdebug) + shift + GST_DEBUG_LEVEL=$1 + ;; + --output-video) + shift + ENCODED_VIDEO_PATH=$1 + ;; + -h|--help) + usage + ;; + *) + usage "Unknown argument: $1" + ;; + esac + shift +done + +# ---------------------------------------------------------------------- +# Wayland environment handling (if available) +# ---------------------------------------------------------------------- +log_info "Setting up Wayland environment (if available)" +if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then + socket=$(discover_wayland_socket_anywhere | head -n1 || true) +fi + +if [ -n "${socket:-}" ] && command -v adopt_wayland_env_from_socket >/dev/null 2>&1; then + log_info "Found Wayland socket: ${socket}" + if ! adopt_wayland_env_from_socket "${socket}"; then + log_fail "Failed to adopt Wayland env from ${socket}" + printf 'FAIL %s\n' "${TESTNAME}" > "${RES_FILE}" + exit 1 + fi +fi + +# ---------------------------------------------------------------------- +# Locate test case directory +# ---------------------------------------------------------------------- +test_path=$(find_test_case_by_name "${TESTNAME}") +if ! cd "${test_path}"; then + log_error "Unable to cd to test directory: ${test_path}" + printf 'FAIL %s\n' "${TESTNAME}" > "${RES_FILE}" + exit 1 +fi + +# ---------------------------------------------------------------------- +# Start of test execution +# ---------------------------------------------------------------------- +log_info "--------------------------------------------------------------------------" +log_info "------------------- Starting ${TESTNAME} Testcase ------------------------" + +# ---------------------------------------------------------------------- +# Verify required GStreamer elements for both pipelines +# ---------------------------------------------------------------------- +if ! check_pipeline_elements "${DETECTION_PIPELINE}"; then + log_skip "${TESTNAME} SKIP - Object-Detection pipeline missing elements" + printf '%s SKIP\n' "${TESTNAME}" > "${RES_FILE}" + exit 0 +fi + +if ! check_pipeline_elements "${CLASSIFICATION_PIPELINE}"; then + log_skip "${TESTNAME} SKIP - Image-Classification pipeline missing elements" + printf '%s SKIP\n' "${TESTNAME}" > "${RES_FILE}" + exit 0 +fi + +# ---------------------------------------------------------------------- +# Pull required model / media assets +# ---------------------------------------------------------------------- +log_info "Downloading required models/assets" +if ! download_model_artifacts; then + log_skip "${TESTNAME} SKIP - Failed fetching assets online!" + printf '%s SKIP\n' "${TESTNAME}" > "${RES_FILE}" + exit 0 +fi + +# ---------------------------------------------------------------------- +# Set GStreamer debug level (environment variable) +# ---------------------------------------------------------------------- +export GST_DEBUG="${GST_DEBUG_LEVEL}" +log_info "GStreamer debug level set to ${GST_DEBUG_LEVEL}" +log_info "Setting timeout to ${TIMEOUT} seconds" + +# ---------------------------------------------------------------------- +# Run both pipelines (object detection + image classification) +# ---------------------------------------------------------------------- +overall_success=1 + +if run_pipeline_with_logs "object_detection" "${DETECTION_PIPELINE}" "${TIMEOUT}"; then + log_pass "Object-Detection pipeline completed successfully" +else + overall_success=0 + log_fail "Object-Detection pipeline failed" +fi + +if run_pipeline_with_logs "image_classification" "${CLASSIFICATION_PIPELINE}" "${TIMEOUT}"; then + log_pass "Image-Classification pipeline completed successfully" + + # Verify that the encoded video file was produced + log_info "Checking encoded video file: ${ENCODED_VIDEO_PATH}" + expected=10485760 + if check_file_size "${ENCODED_VIDEO_PATH}" "$expected"; then + log_pass "${ENCODED_VIDEO_PATH} is present and non-empty" + else + overall_success=0 + log_fail "${ENCODED_VIDEO_PATH} is empty or cannot be accessed" + fi +else + overall_success=0 + log_fail "Image-Classification pipeline failed" +fi + +# ---------------------------------------------------------------------- +# Final result handling +# ---------------------------------------------------------------------- +if [ "$overall_success" -eq 1 ]; then + log_pass "Both pipelines executed successfully." + printf 'PASS %s\n' "${TESTNAME}" > "${RES_FILE}" + exit 0 +else + log_fail "One or more pipelines failed." + printf 'FAIL %s\n' "${TESTNAME}" > "${RES_FILE}" + exit 1 +fi \ No newline at end of file diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh index 95b90f19..787d8a94 100755 --- a/Runner/utils/lib_gstreamer.sh +++ b/Runner/utils/lib_gstreamer.sh @@ -498,3 +498,228 @@ gstreamer_build_playback_pipeline() { printf '%s\n' "filesrc location=${file} ! ${dec} ! audioconvert ! audioresample ! ${sinkElem}" return 0 } + +# -------------------------------------------------------------- +# download_resource +# $1 url – URL to download +# $2 dest – Either a file name or an existing directory. +# Prints the full path of the downloaded file on stdout. +# -------------------------------------------------------------- +download_resource() { + url=$1 + dest=$2 + + if [ -d "${dest}" ]; then + filename=$(basename "${url}") + dest="${dest%/}/${filename}" + fi + + mkdir -p "$(dirname "${dest}")" + + if command -v curl >/dev/null 2>&1; then + curl -fkL "${url}" -o "${dest}" + elif command -v wget >/dev/null 2>&1; then + wget -q "${url}" -O "${dest}" + else + echo "Error: neither 'curl' nor 'wget' is installed." >&2 + return 1 + fi + + if command -v realpath >/dev/null 2>&1; then + realpath "${dest}" + else + case "${dest}" in + ./*) echo "${dest#./}" ;; + *) echo "${dest}" ;; + esac + fi +} +# -------------------------------------------------------------- +# extract_zip_to_dir +# -------------------------------------------------------------- +extract_zip_to_dir() { + zip_path=$1 + dest_dir=$2 + + tmp_dir=$(mktemp -d -t "$(basename "${zip_path%.*}")_XXXX") + + if ! unzip -o "${zip_path}" -d "${tmp_dir}" >/dev/null; then + echo "Unzip of ${zip_path} failed" >&2 + rm -rf "${tmp_dir}" + return 1 + fi + + mkdir -p "${dest_dir}" + cp -r "${tmp_dir}"/* "${dest_dir}/" + + # Clean up the temporary folder; + rm -rf "${tmp_dir}" "${zip_path}" +} +# ------------------------------------------------------------------------- +# check_pipeline_elements +# Verify that every GStreamer element that appears in a gst-launch +# pipeline is installed on the system (via `has_element`). +# Returns: +# 0 – all elements are present +# 1 – at least one element is missing +# ------------------------------------------------------------------------- +check_pipeline_elements() { + pipeline="${1:?missing pipeline argument}" + missing_count=0 + missing_list="" + total_elements=0 + + log_info "Checking elements in pipeline" + + # --------------------------------------------------------- + # Normalise the pipeline string + # --------------------------------------------------------- + pipeline=$(printf '%s' "$pipeline" | tr -d '\\\n') + pipeline=${pipeline#gst-launch-1.0* } + # Remove the literal "gst-launch-1.0" if present + pipeline=${pipeline#gst-launch-1.0} + # Trim any leading whitespace left by the previous step + pipeline=${pipeline#"${pipeline%%[![:space:]]*}"} + # Drop leading option tokens (e.g. "-e", "-v", "--no-fault") + while [[ $pipeline == -* ]]; do + # Remove the first token (option) and any following whitespace + pipeline=${pipeline#* } + pipeline=${pipeline#"${pipeline%%[![:space:]]*}"} + done + + # --------------------------------------------------------- + # Write the token list to a temporary file + # --------------------------------------------------------- + tmpfile=$(mktemp) || exit 1 + printf '%s' "$pipeline" | tr '!' '\n' >"$tmpfile" + + # --------------------------------------------------------- + # Read the file line‑by‑line – this runs in the *current* + # shell, so variable updates survive. + # --------------------------------------------------------- + while IFS= read -r element_spec; do + # ---- NEW ---- + # Strip surrounding whitespace; skip blank lines + element_spec=$(printf '%s' "$element_spec" | xargs) + [[ -z $element_spec ]] && continue + # -------------- + + element_name=$(printf '%s' "$element_spec" | cut -d' ' -f1) + + case "$element_name" in + *.) log_info "Skipping element reference: $element_name" ; continue ;; + name=*) log_info "Skipping property assignment: $element_name" ; continue ;; + *_::*) log_info "Skipping property assignment: $element_name" ; continue ;; + video/*|audio/*|application/*|text/*|image/*) + log_info "Skipping caps filter: $element_name" ; continue ;; + *) + total_elements=$(( total_elements + 1 )) + log_info "Checking element: $element_name" + if ! has_element "$element_name"; then + missing_count=$(( missing_count + 1 )) + missing_list="${missing_list}${element_name} " + log_error "Required element missing: $element_name" + else + log_info "Element found: $element_name" + fi + ;; + esac + done <"$tmpfile" + # Clean up the temporary file + rm -f "$tmpfile" + + if [ "$missing_count" -eq 0 ]; then + log_pass "All $total_elements elements in pipeline are available" + return 0 + else + log_fail "Missing $missing_count/$total_elements elements: $missing_list" + return 1 + fi +} +# ---------------------------------------------------------------------- +# Run a pipeline with timeout, capture console output and GST debug logs. +# ---------------------------------------------------------------------- +run_pipeline_with_logs() { + name=$1 + cmd=$2 + timeout=$3 + TIMEOUT=${timeout:-60} # default 60 seconds + + console_log="${name}_console.log" + gst_debug_log="${name}_gst_debug.log" + + export GST_DEBUG_FILE="${gst_debug_log}" + + log_info "Running ${name} (timeout=${TIMEOUT}s)" + timeout "${TIMEOUT}" sh -c "$cmd" >"$console_log" 2>&1 + rc=$? + + # Look for a successful PLAYING state and the absence of ERROR messages. + playing=$(grep -c "Setting pipeline to PLAYING" "$console_log" || true) + error_present=$(grep -c "ERROR:" "$console_log" || true) + + if [ "$playing" -gt 0 ] && [ "$error_present" -eq 0 ]; then + log_pass "${name} PASS" + return 0 + fi + + # Special case: timeout (rc = 124) but PLAYING was already reached. + if [ "$rc" -eq 124 ] && [ "$playing" -gt 0 ]; then + log_pass "${name} PASS (completed before timeout)" + return 0 + fi + + # Anything else is a failure. + log_fail "${name} FAIL (rc=${rc})" + log_info "=== ERROR DETAILS ===" + if [ "$error_present" -gt 0 ]; then + grep -A10 -B5 "ERROR:" "$console_log" | tail -n 30 | + while IFS= read -r line; do log_info "$line"; done + else + tail -n 30 "$console_log" | + while IFS= read -r line; do log_info "$line"; done + fi + log_info "=====================" + return 1 +} +# ------------------------------------------------------------------ +# Function: check_file_size +# Purpose : Check that a file exists and its size > 0. +# Returns : 0 → file size > 0 (success) +# 1 → file missing, unreadable, or size == 0 (failure) +# Requires: GNU coreutils (stat -c %s) +# ------------------------------------------------------------------ +check_file_size() { + input_file_path="$1" + expected_file_size="$2" + + if [ -z "$input_file_path" ]; then + log_fail "No input file path provided" + return 1 + fi + if [ ! -e "$input_file_path" ]; then + log_fail "Encoded video file does not exist: $input_file_path" + return 1 + fi + + # ---- Ensure we have `stat` ------------------------------------------------ + if ! command -v stat >/dev/null 2>&1; then + log_fail "`stat` command not found – cannot determine file size" + return 1 + fi + + # ---- Get the actual size ------------------------------------------------- + size_in_bytes=$(stat -c %s "$input_file_path" 2>/dev/null) || { + log_fail "Unable to read size of file: $input_file_path" + return 1 + } + + # ---- Compare with the expected size -------------------------------------- + if (( size_in_bytes >= expected_file_size )); then + log_pass "File OK (size ${size_in_bytes} bytes ≥ ${expected_file_size} bytes): $input_file_path" + return 0 + else + log_info "File too small (size ${size_in_bytes} bytes < ${expected_file_size} bytes): $input_file_path" + return 1 + fi +} From 52d925a2fb59cfce856514a6ebf29e9a8d72c787 Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Mon, 9 Feb 2026 12:22:41 +0530 Subject: [PATCH 2/8] Fix: Add execute permission to run.sh Signed-off-by: Vij Patel --- Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh old mode 100644 new mode 100755 From ecb24e7468dc3ccb8e89333221b581fecdbd70c8 Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Mon, 9 Feb 2026 14:09:12 +0530 Subject: [PATCH 3/8] shellcheck: fix warnings in lib_gstreamer.sh Signed-off-by: Vij Patel --- Runner/utils/lib_gstreamer.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) mode change 100755 => 100644 Runner/utils/lib_gstreamer.sh diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh old mode 100755 new mode 100644 index 787d8a94..48975931 --- a/Runner/utils/lib_gstreamer.sh +++ b/Runner/utils/lib_gstreamer.sh @@ -581,7 +581,7 @@ check_pipeline_elements() { # Trim any leading whitespace left by the previous step pipeline=${pipeline#"${pipeline%%[![:space:]]*}"} # Drop leading option tokens (e.g. "-e", "-v", "--no-fault") - while [[ $pipeline == -* ]]; do + while [ "${pipeline#-}" != "$pipeline" ]; do # Remove the first token (option) and any following whitespace pipeline=${pipeline#* } pipeline=${pipeline#"${pipeline%%[![:space:]]*}"} @@ -601,7 +601,7 @@ check_pipeline_elements() { # ---- NEW ---- # Strip surrounding whitespace; skip blank lines element_spec=$(printf '%s' "$element_spec" | xargs) - [[ -z $element_spec ]] && continue + [ -z "$element_spec" ] && continue # -------------- element_name=$(printf '%s' "$element_spec" | cut -d' ' -f1) @@ -704,7 +704,7 @@ check_file_size() { # ---- Ensure we have `stat` ------------------------------------------------ if ! command -v stat >/dev/null 2>&1; then - log_fail "`stat` command not found – cannot determine file size" + log_fail "stat command not found – cannot determine file size" return 1 fi @@ -715,7 +715,7 @@ check_file_size() { } # ---- Compare with the expected size -------------------------------------- - if (( size_in_bytes >= expected_file_size )); then + if [ "$size_in_bytes" -ge "$expected_file_size" ]; then log_pass "File OK (size ${size_in_bytes} bytes ≥ ${expected_file_size} bytes): $input_file_path" return 0 else From 64796577eb99478fd91bb22fbbe8c37adc568644 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 13 Feb 2026 11:51:35 +0000 Subject: [PATCH 4/8] fix: keep consistent mode on lib_gstreamer.sh Signed-off-by: root --- Runner/utils/lib_gstreamer.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 Runner/utils/lib_gstreamer.sh diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh old mode 100644 new mode 100755 From d6463a76dbed7c65e3802309e04fb809ef0e05af Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Mon, 16 Feb 2026 00:22:23 +0530 Subject: [PATCH 5/8] fix: Add consistent .res file output to Readme.md Signed-off-by: Vij Patel --- Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md b/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md index 9f4b566f..4cc56c89 100644 --- a/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/Readme.md @@ -57,12 +57,12 @@ At a high level, the test: ### PASS - **All pipelines** successfully run to PLAYING state without critical errors -- Output file contains `PASS AI_ML` +- Output file contains `AI_ML PASS` - Exit code 0 ### FAIL - **Any pipeline** fails to reach PLAYING state or encounters critical errors -- Output file contains `FAIL AI_ML` +- Output file contains `AI_ML FAIL` - Exit code 1 **Note:** The test always exits `0` even for FAIL. The `.res` file is the source of truth. @@ -129,7 +129,7 @@ Run: - `--gstdebug ` - Sets GStreamer debug level (default: 2) - `--output-video ` - - Path for the encoded video generated by the classification pipeline (default: /opt/video_out.mp4) + - Path for the encoded video generated by the classification pipeline (default: ./video_out.mp4) --- @@ -183,8 +183,8 @@ Run: - The test always exits `0` even for FAIL conditions - Use the `.res` file for result determination: - - `PASS AI_ML` - - `FAIL AI_ML` + - `AI_ML PASS` + - `AI_ML FAIL` - Requires Internet connectivity to download models - Test duration depends on platform performance and number of successful pipelines - Each pipeline test has its own timeout to prevent hangs From 20ed61bb5ade0bf26d38cd7faaa6ba15d97305a6 Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Mon, 16 Feb 2026 00:37:06 +0530 Subject: [PATCH 6/8] fix: Changes to output path in AI_ML.yaml Signed-off-by: Vij Patel --- Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml b/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml index 14b654c3..d7f4dbaf 100644 --- a/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/AI_ML.yaml @@ -10,7 +10,7 @@ metadata: params: TIMEOUT: 60 # Timeout in seconds for each pipeline (default in script is 60) GST_DEBUG_LEVEL: 2 # GStreamer debug level (default in script is 2) - OUTPUT_VIDEO_PATH: "/opt/video_out.mp4" # Path for encoded output video + OUTPUT_VIDEO_PATH: "./video_out.mp4" # Path for encoded output video run: steps: From 81d0af26861ffc382ff801383a85337a975b6517 Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Tue, 17 Feb 2026 17:46:12 +0530 Subject: [PATCH 7/8] fix: Changes to run.sh revision 1 Signed-off-by: Vij Patel --- Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh index 22383107..81bcece0 100755 --- a/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh @@ -157,14 +157,23 @@ while [ "$#" -gt 0 ]; do case "$1" in --timeout) shift + if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then + usage "Missing value for --timeout" + fi TIMEOUT=$1 ;; --gstdebug) shift + if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then + usage "Missing value for --gstdebug" + fi GST_DEBUG_LEVEL=$1 ;; --output-video) shift + if [ "$#" -eq 0 ] || [ "${1#-}" != "$1" ]; then + usage "Missing value for --output-video" + fi ENCODED_VIDEO_PATH=$1 ;; -h|--help) From 0c8a0f9ac75dd520601a3227e6dea8c7774be346 Mon Sep 17 00:00:00 2001 From: Vij Patel Date: Tue, 17 Feb 2026 17:51:40 +0530 Subject: [PATCH 8/8] fix: Changes to lib_gstreamer.sh revision 1 Signed-off-by: Vij Patel --- .../suites/Multimedia/GSTreamer/AI_ML/run.sh | 110 +++++++----------- Runner/utils/lib_gstreamer.sh | 71 +++++++---- 2 files changed, 90 insertions(+), 91 deletions(-) diff --git a/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh index 81bcece0..114d82ab 100755 --- a/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh +++ b/Runner/suites/Multimedia/GSTreamer/AI_ML/run.sh @@ -23,13 +23,11 @@ fi if [ -z "$__INIT_ENV_LOADED" ]; then # shellcheck disable=SC1090 . "$INIT_ENV" + __INIT_ENV_LOADED=1 fi # ---------------------------------------------------------------------- # Source the environment and helper libraries. # ---------------------------------------------------------------------- -# shellcheck disable=SC1090 -. "$INIT_ENV" - # shellcheck disable=SC1091 . "$TOOLS/functestlib.sh" @@ -41,37 +39,26 @@ fi # ---------------------------------------------------------------------- TESTNAME="AI_ML" RES_FILE="./${TESTNAME}.res" - +# ---------------------------------------------------------------------- +# Locate test case directory +# ---------------------------------------------------------------------- +test_path=$(find_test_case_by_name "${TESTNAME}") +log_info "test path is ${test_path}" +if ! cd "${test_path}"; then + log_error "Unable to cd to test directory: ${test_path}" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 0 +fi TIMEOUT=60 # seconds -ENCODED_VIDEO_PATH="/opt/video_out.mp4" +# ENCODED_VIDEO_PATH=''"${test_path}"'/video_out.mp4' +ENCODED_VIDEO_PATH='./video_out.mp4' GST_DEBUG_LEVEL=2 - # ---------------------------------------------------------------------- -# GStreamer pipelines (kept on a single line for readability) +# GStreamer pipelines # ---------------------------------------------------------------------- -DETECTION_PIPELINE='gst-launch-1.0 -e filesrc location=/opt/video.mp4 ! \ - qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 \ - ! queue ! tee name=split \ - split. ! queue ! qtivcomposer name=mixer ! queue ! waylandsink sync=true fullscreen=true \ - split. ! queue ! qtimlvconverter ! queue ! qtimltflite delegate=external \ - external-delegate-path=libQnnTFLiteDelegate.so \ - external-delegate-options="QNNExternalDelegate,backend_type=htp;" \ - model=/opt/yolox_quantized.tflite ! queue ! qtimlpostprocess \ - module=yolov8 labels=/opt/labels/yolox.json settings="{\"confidence\": 50.0}" \ - ! video/x-raw,format=BGRA,width=640,height=360 ! queue ! mixer.' - -CLASSIFICATION_PIPELINE='gst-launch-1.0 -e filesrc location=/opt/video.mp4 ! \ - qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 \ - ! video/x-raw,format=NV12_Q08C ! queue ! tee name=split \ - ! queue ! qtimetamux name=metamux ! queue ! qtivoverlay ! queue \ - ! v4l2h264enc capture-io-mode=4 output-io-mode=5 ! h264parse ! queue \ - ! mp4mux ! queue ! filesink location='"${ENCODED_VIDEO_PATH}"' \ - split. ! queue ! qtimlvconverter ! queue ! qtimltflite delegate=external \ - external-delegate-path=libQnnTFLiteDelegate.so \ - external-delegate-options="QNNExternalDelegate,backend_type=htp;" \ - model=/opt/inception_v3_quantized.tflite ! queue ! qtimlpostprocess \ - results=5 module=mobilenet-softmax labels=/opt/labels/imagenet_labels.txt \ - settings="{\"confidence\": 31.0}" ! text/x-raw ! queue ! metamux.' +DETECTION_PIPELINE='filesrc location='"${test_path}"'/assets/video.mp4 ! qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 ! queue ! tee name=split split. ! queue ! qtivcomposer name=mixer ! queue ! waylandsink sync=true fullscreen=true split. ! queue ! qtimlvconverter ! queue ! qtimltflite delegate=external external-delegate-path=libQnnTFLiteDelegate.so external-delegate-options="QNNExternalDelegate,backend_type=htp;" model='"${test_path}"'/assets/yolox_quantized.tflite ! queue ! qtimlpostprocess module=yolov8 labels='"${test_path}"'/assets/labels_ga16/labels/yolox.json settings="{\"confidence\": 50.0}" ! video/x-raw,format=BGRA,width=640,height=360 ! queue ! mixer.' +# gst-launch-1.0 -e +CLASSIFICATION_PIPELINE='filesrc location='"${test_path}"'/assets/video.mp4 ! qtdemux ! queue ! h264parse ! v4l2h264dec capture-io-mode=4 output-io-mode=4 ! video/x-raw,format=NV12_Q08C ! queue ! tee name=split ! queue ! qtimetamux name=metamux ! queue ! qtivoverlay ! queue ! v4l2h264enc capture-io-mode=4 output-io-mode=5 ! h264parse ! queue ! mp4mux ! queue ! filesink location='"${ENCODED_VIDEO_PATH}"' split. ! queue ! qtimlvconverter ! queue ! qtimltflite delegate=external external-delegate-path=libQnnTFLiteDelegate.so external-delegate-options="QNNExternalDelegate,backend_type=htp;" model='"${test_path}"'/assets/inception_v3_quantized.tflite ! queue ! qtimlpostprocess results=5 module=mobilenet-softmax labels='"${test_path}"'/assets/labels_ga15/labels/imagenet_labels.txt settings="{\"confidence\": 31.0}" ! text/x-raw ! queue ! metamux.' # ---------------------------------------------------------------------- # Helper functions @@ -91,7 +78,7 @@ Options: --gstdebug GST_DEBUG level (numeric). Default: 2 --output-video Path for the encoded video generated by the classification pipeline. - Default: /opt/video_out.mp4 + Default: /test_path/video_out.mp4 -h, --help Show this help message and exit. EOF exit "${exit_code}" @@ -113,7 +100,7 @@ download_model_artifacts() { fi log_info "Network is online, proceed to download!" - out="/opt" + out=''"${test_path}"'/assets' mkdir -p "${out}" # ------------------------------------------------------------------ @@ -137,17 +124,17 @@ download_model_artifacts() { # ------------------------------------------------------------------ # Download zip-files (labels) and extract them # ------------------------------------------------------------------ - log_info "Downloading labels (set 1)…" + log_info "Downloading labels (set 1)…" zip_path=$(download_resource \ "https://github.com/quic/sample-apps-for-qualcomm-linux/releases/download/GA1.5-rel/labels.zip" \ - "${out}") # dest is a directory → original name kept (labels.zip) - extract_zip_to_dir "${zip_path}" "${out}" + "${out}/labels_ga15.zip") + extract_zip_to_dir "${zip_path}" "${out}/labels_ga15" - log_info "Downloading labels (set 2)…" + log_info "Downloading labels (set 2)…" zip_path=$(download_resource \ "https://github.com/quic/sample-apps-for-qualcomm-linux/releases/download/GA1.6-labels/labels.zip" \ - "${out}") - extract_zip_to_dir "${zip_path}" "${out}" + "${out}/labels_ga16.zip") + extract_zip_to_dir "${zip_path}" "${out}/labels_ga16" } # ---------------------------------------------------------------------- @@ -187,32 +174,18 @@ while [ "$#" -gt 0 ]; do done # ---------------------------------------------------------------------- -# Wayland environment handling (if available) +# Wayland environment handling # ---------------------------------------------------------------------- -log_info "Setting up Wayland environment (if available)" -if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then - socket=$(discover_wayland_socket_anywhere | head -n1 || true) -fi - -if [ -n "${socket:-}" ] && command -v adopt_wayland_env_from_socket >/dev/null 2>&1; then - log_info "Found Wayland socket: ${socket}" - if ! adopt_wayland_env_from_socket "${socket}"; then - log_fail "Failed to adopt Wayland env from ${socket}" - printf 'FAIL %s\n' "${TESTNAME}" > "${RES_FILE}" - exit 1 +log_info "Setting up Wayland environment" +if command -v weston_pick_env_or_start >/dev/null 2>&1; then + log_info "Checking if Weston socket exists; if not, stop+start Weston and adopt helper socket." + if ! weston_pick_env_or_start; then + log_fail "Failed to adopt Wayland env" + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 0 fi fi -# ---------------------------------------------------------------------- -# Locate test case directory -# ---------------------------------------------------------------------- -test_path=$(find_test_case_by_name "${TESTNAME}") -if ! cd "${test_path}"; then - log_error "Unable to cd to test directory: ${test_path}" - printf 'FAIL %s\n' "${TESTNAME}" > "${RES_FILE}" - exit 1 -fi - # ---------------------------------------------------------------------- # Start of test execution # ---------------------------------------------------------------------- @@ -224,13 +197,13 @@ log_info "------------------- Starting ${TESTNAME} Testcase -------------------- # ---------------------------------------------------------------------- if ! check_pipeline_elements "${DETECTION_PIPELINE}"; then log_skip "${TESTNAME} SKIP - Object-Detection pipeline missing elements" - printf '%s SKIP\n' "${TESTNAME}" > "${RES_FILE}" + echo "$TESTNAME SKIP" > "$RES_FILE" exit 0 fi if ! check_pipeline_elements "${CLASSIFICATION_PIPELINE}"; then log_skip "${TESTNAME} SKIP - Image-Classification pipeline missing elements" - printf '%s SKIP\n' "${TESTNAME}" > "${RES_FILE}" + echo "$TESTNAME SKIP" > "$RES_FILE" exit 0 fi @@ -240,7 +213,7 @@ fi log_info "Downloading required models/assets" if ! download_model_artifacts; then log_skip "${TESTNAME} SKIP - Failed fetching assets online!" - printf '%s SKIP\n' "${TESTNAME}" > "${RES_FILE}" + echo "$TESTNAME SKIP" > "$RES_FILE" exit 0 fi @@ -250,6 +223,9 @@ fi export GST_DEBUG="${GST_DEBUG_LEVEL}" log_info "GStreamer debug level set to ${GST_DEBUG_LEVEL}" log_info "Setting timeout to ${TIMEOUT} seconds" +log_info "Creating logs directory" +GST_LOGS_DIR=''"${test_path}"'/logs' +mkdir -p "${GST_LOGS_DIR}" # ---------------------------------------------------------------------- # Run both pipelines (object detection + image classification) @@ -268,7 +244,7 @@ if run_pipeline_with_logs "image_classification" "${CLASSIFICATION_PIPELINE}" "$ # Verify that the encoded video file was produced log_info "Checking encoded video file: ${ENCODED_VIDEO_PATH}" - expected=10485760 + expected=1048576 if check_file_size "${ENCODED_VIDEO_PATH}" "$expected"; then log_pass "${ENCODED_VIDEO_PATH} is present and non-empty" else @@ -285,10 +261,10 @@ fi # ---------------------------------------------------------------------- if [ "$overall_success" -eq 1 ]; then log_pass "Both pipelines executed successfully." - printf 'PASS %s\n' "${TESTNAME}" > "${RES_FILE}" + echo "$TESTNAME PASS" > "$RES_FILE" exit 0 else log_fail "One or more pipelines failed." - printf 'FAIL %s\n' "${TESTNAME}" > "${RES_FILE}" - exit 1 + echo "$TESTNAME FAIL" > "$RES_FILE" + exit 0 fi \ No newline at end of file diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh index 48975931..729c1fc5 100755 --- a/Runner/utils/lib_gstreamer.sh +++ b/Runner/utils/lib_gstreamer.sh @@ -452,7 +452,8 @@ gstreamer_run_gstlaunch_timeout() { case "$secs" in ''|*[!0-9]*) secs=10 ;; esac command -v "$GSTBIN" >/dev/null 2>&1 || return 127 - gstreamer_print_cmd_multiline "$pipe" + # gstreamer_print_cmd_multiline "$pipe" + log_info "${pipe}" if [ "$secs" -gt 0 ] 2>/dev/null; then if command -v audio_timeout_run >/dev/null 2>&1; then @@ -514,23 +515,41 @@ download_resource() { dest="${dest%/}/${filename}" fi - mkdir -p "$(dirname "${dest}")" + # Check if file already exists and is non-empty + if [ -f "${dest}" ] && [ -s "${dest}" ]; then + if command -v realpath >/dev/null 2>&1; then + realpath "${dest}" + else + case "${dest}" in + ./*) echo "${dest#./}" ;; + *) echo "${dest}" ;; + esac + fi + return 0 + fi + mkdir -p "$(dirname "${dest}")" if command -v curl >/dev/null 2>&1; then - curl -fkL "${url}" -o "${dest}" + curl -fkL "${url}" -o "${dest}" || { echo "Error: curl failed to download ${url}" >&2; return 1; } elif command -v wget >/dev/null 2>&1; then - wget -q "${url}" -O "${dest}" + wget -q "${url}" -O "${dest}" || { echo "Error: wget failed to download ${url}" >&2; return 1; } else echo "Error: neither 'curl' nor 'wget' is installed." >&2 return 1 fi + # Verify successful download with non-empty file + if [ ! -s "${dest}" ]; then + echo "Error: downloaded file is empty: ${dest}" >&2 + return 1 + fi + if command -v realpath >/dev/null 2>&1; then realpath "${dest}" else case "${dest}" in - ./*) echo "${dest#./}" ;; - *) echo "${dest}" ;; + ./*) echo "${dest#./}" ;; + *) echo "${dest}" ;; esac fi } @@ -541,19 +560,11 @@ extract_zip_to_dir() { zip_path=$1 dest_dir=$2 - tmp_dir=$(mktemp -d -t "$(basename "${zip_path%.*}")_XXXX") - - if ! unzip -o "${zip_path}" -d "${tmp_dir}" >/dev/null; then + mkdir -p "${dest_dir}" + if ! unzip -o "${zip_path}" -d "${dest_dir}" >/dev/null; then echo "Unzip of ${zip_path} failed" >&2 - rm -rf "${tmp_dir}" return 1 fi - - mkdir -p "${dest_dir}" - cp -r "${tmp_dir}"/* "${dest_dir}/" - - # Clean up the temporary folder; - rm -rf "${tmp_dir}" "${zip_path}" } # ------------------------------------------------------------------------- # check_pipeline_elements @@ -590,7 +601,7 @@ check_pipeline_elements() { # --------------------------------------------------------- # Write the token list to a temporary file # --------------------------------------------------------- - tmpfile=$(mktemp) || exit 1 + tmpfile=$(mktemp) printf '%s' "$pipeline" | tr '!' '\n' >"$tmpfile" # --------------------------------------------------------- @@ -600,7 +611,8 @@ check_pipeline_elements() { while IFS= read -r element_spec; do # ---- NEW ---- # Strip surrounding whitespace; skip blank lines - element_spec=$(printf '%s' "$element_spec" | xargs) + # element_spec=$(printf '%s' "$element_spec" | xargs) + element_spec=$(printf '%s\n' "$element_spec" | awk '{$1=$1; print}') [ -z "$element_spec" ] && continue # -------------- @@ -645,14 +657,25 @@ run_pipeline_with_logs() { timeout=$3 TIMEOUT=${timeout:-60} # default 60 seconds - console_log="${name}_console.log" - gst_debug_log="${name}_gst_debug.log" + console_log="logs/${name}_console.log" + gst_debug_log="logs/${name}_gst_debug.log" export GST_DEBUG_FILE="${gst_debug_log}" log_info "Running ${name} (timeout=${TIMEOUT}s)" - timeout "${TIMEOUT}" sh -c "$cmd" >"$console_log" 2>&1 - rc=$? + # Check if timeout command is available + if command -v timeout >/dev/null 2>&1; then + final_command='timeout '"${TIMEOUT}"' gst-launch-1.0 -e '"${cmd}"'' + sh -c "$final_command" >"$console_log" 2>&1 + rc=$? + else + log_warn "timeout command not found, falling back to gstreamer_run_gstlaunch_timeout" + # Run with gstreamer_run_gstlaunch_timeout using just the pipeline + { + gstreamer_run_gstlaunch_timeout "$TIMEOUT" "$cmd" + rc=$? + } > "$console_log" 2>&1 + fi # Look for a successful PLAYING state and the absence of ERROR messages. playing=$(grep -c "Setting pipeline to PLAYING" "$console_log" || true) @@ -709,7 +732,7 @@ check_file_size() { fi # ---- Get the actual size ------------------------------------------------- - size_in_bytes=$(stat -c %s "$input_file_path" 2>/dev/null) || { + size_in_bytes=$(stat -c %s "$input_file_path" 2>/dev/null || wc -c <"$input_file_path" 2>/dev/null) || { log_fail "Unable to read size of file: $input_file_path" return 1 } @@ -722,4 +745,4 @@ check_file_size() { log_info "File too small (size ${size_in_bytes} bytes < ${expected_file_size} bytes): $input_file_path" return 1 fi -} +} \ No newline at end of file