From e2ae2de3fe657f25523d1c7cbac79cba5616758f Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Mon, 5 Jan 2026 10:52:54 +0530 Subject: [PATCH 1/4] Multimedia/GStreamer: Audio_Playback: add LAVA env defaults and shared lib runners - Audio_Playback run.sh now supports LAVA-provided environment defaults and reduces inline duplication by using shared helpers from lib_gstreamer.sh. - Add env-to-default mapping for all CLI params (AUDIO_* and AUDIO_PLAYBACK_*) - Keep CLI as highest priority override - Use shared gst-launch timeout runner and shared evidence sampling wrapper - Use shared caps inference + caps forcing policy (user-requested or inferred only) - Improve logging: prints normalized duration, backend chain, sink selection and pipeline Signed-off-by: Srikanth Muppandam --- .../GSTreamer/Audio/Audio_Playback/run.sh | 657 ++++++++++++++++++ 1 file changed, 657 insertions(+) create mode 100755 Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/run.sh diff --git a/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/run.sh b/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/run.sh new file mode 100755 index 00000000..f1c97d9d --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/run.sh @@ -0,0 +1,657 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# Audio Playback validation using GStreamer (auto backend: PipeWire/Pulse/ALSA) +# 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" + +# NOTE: lib_gstreamer.sh is under Runner/utils only +# shellcheck disable=SC1091 +. "$TOOLS/lib_gstreamer.sh" + +# Optional wrapper/aliases for audio-specific helpers (if present) +# shellcheck disable=SC1091 +[ -f "$TOOLS/audio_common.sh" ] && . "$TOOLS/audio_common.sh" + +TESTNAME="Audio_Playback" +RES_FILE="./${TESTNAME}.res" +LOG_DIR="./logs" +OUTDIR="$LOG_DIR/$TESTNAME" +GST_LOG="$OUTDIR/gst.log" +MIXER_LOG="$OUTDIR/mixers.txt" +DMESG_DIR="$OUTDIR/dmesg" +META_LOG="$OUTDIR/clip_meta.txt" + +mkdir -p "$OUTDIR" "$DMESG_DIR" >/dev/null 2>&1 || true +: >"$RES_FILE" +: >"$GST_LOG" +: >"$META_LOG" + +AUDIO_LOGCTX="$GST_LOG" +export AUDIO_LOGCTX + +result="FAIL" +reason="unknown" +finalBackend="" +finalPipe="" +gstRc=1 + +# -------------------- Defaults (LAVA env vars -> defaults; CLI overrides) -------------------- +# LAVA job can set these via the job "environment:" section. +backend="${AUDIO_BACKEND:-${AUDIO_PLAYBACK_BACKEND:-auto}}" +stack="${AUDIO_STACK:-${AUDIO_PLAYBACK_STACK:-auto}}" +format="${AUDIO_FORMAT:-${AUDIO_PLAYBACK_FORMAT:-wav}}" +duration="${AUDIO_DURATION:-${AUDIO_PLAYBACK_DURATION:-${RUNTIMESEC:-10s}}}" +clipDur="${AUDIO_CLIPDUR:-${AUDIO_PLAYBACK_CLIPDUR:-short}}" +clipPath="${AUDIO_CLIP:-${AUDIO_PLAYBACK_CLIP:-}}" + +clipsDir="${AUDIO_CLIPS_DIR:-${AUDIO_PLAYBACK_CLIPS_DIR:-}}" +assetsPath="${AUDIO_ASSETS:-${AUDIO_PLAYBACK_ASSETS:-}}" +assetsUrl="${AUDIO_ASSETS_URL:-${AUDIO_PLAYBACK_ASSETS_URL:-${AUDIO_TAR_URL:-}}}" + +rate="${AUDIO_RATE:-${AUDIO_PLAYBACK_RATE:-}}" +channels="${AUDIO_CHANNELS:-${AUDIO_PLAYBACK_CHANNELS:-}}" +sinkSel="${AUDIO_SINK:-${AUDIO_PLAYBACK_SINK:-}}" +useNullSink="${AUDIO_NULL_SINK:-${AUDIO_PLAYBACK_NULL_SINK:-0}}" + +alsaDevice="${AUDIO_ALSA_DEVICE:-${AUDIO_PLAYBACK_ALSA_DEVICE:-}}" + +gstDebugLevel="${AUDIO_GST_DEBUG:-${AUDIO_PLAYBACK_GST_DEBUG:-2}}" + +rateUser="0" +channelsUser="0" +rateInferred="0" +channelsInferred="0" + +# If env provided rate/channels, treat as "user provided" +if [ -n "$rate" ]; then + rateUser="1" +fi +if [ -n "$channels" ]; then + channelsUser="1" +fi + +cleanup() { + pkill -x gst-launch-1.0 >/dev/null 2>&1 || true +} +trap cleanup INT TERM EXIT + +# -------------------- Arg parse (SC2015-clean) -------------------- +while [ $# -gt 0 ]; do + case "$1" in + --backend) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --backend" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + backend="$2" + shift 2 + ;; + + --stack) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --stack" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + stack="$2" + shift 2 + ;; + + --format) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --format" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + format="$2" + shift 2 + ;; + + --duration) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --duration" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + duration="$2" + shift 2 + ;; + + --clipdur) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --clipdur" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + clipDur="$2" + shift 2 + ;; + + --clip) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --clip" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + clipPath="$2" + shift 2 + ;; + + --clips-dir) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --clips-dir" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + clipsDir="$2" + shift 2 + ;; + + --assets) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --assets" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + assetsPath="$2" + shift 2 + ;; + + --assets-url) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --assets-url" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + assetsUrl="$2" + shift 2 + ;; + + --rate) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --rate" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + rate="$2" + rateUser="1" + shift 2 + ;; + + --channels) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --channels" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + channels="$2" + channelsUser="1" + shift 2 + ;; + + --sink) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --sink" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + sinkSel="$2" + shift 2 + ;; + + --null-sink) + useNullSink="1" + shift 1 + ;; + + --alsa-device) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --alsa-device" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + alsaDevice="$2" + shift 2 + ;; + + --gst-debug) + if [ $# -lt 2 ] || [ -z "$2" ] || [ "${2#--}" != "$2" ]; then + log_warn "Missing/invalid value for --gst-debug" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + gstDebugLevel="$2" + shift 2 + ;; + + -h|--help) + cat < + Default: auto (tries pipewire -> pulseaudio -> alsa) + + --stack + Default: auto + auto : detect overlay (audioreach modules) and apply setup only if detected + base : force base (do not run overlay setup even if audioreach modules present) + overlay : force overlay setup (fails if setup_overlay_audio_environment fails) + + --format + Default: ${format} + + --duration + Default: ${duration} + + --clipdur + Used with resolve_clip(). Default: ${clipDur} + + --clip + Override resolved clip path. + + --clips-dir + Untarred clips directory on device. Sets AUDIO_CLIPS_BASE_DIR. + + --assets + Device-provided assets (no network): + - directory: treated as --clips-dir + - file: .tar/.tar.gz/.tgz extracted into clips-dir (or default AudioClips under script dir) + + --assets-url + Optional remote tarball URL (only used if clip missing). + + --rate + Force caps rate after decode/resample. Example: 48000 + + --channels + Force caps channels after decode/resample. Example: 1 or 2 + + --sink + For PipeWire: numeric id or substring match in wpctl status. + For PulseAudio: sink name or numeric index. + + --null-sink + Prefer null/dummy sink if available (for non-audible CI runs). + + --alsa-device + Override ALSA device used when backend falls back to alsasink. + + --gst-debug + Sets GST_DEBUG= (single numeric level only). + 1 ERROR + 2 WARNING + 3 FIXME + 4 INFO + 5 DEBUG + 6 LOG + 7 TRACE + 9 MEMDUMP + Default: ${gstDebugLevel} + +LAVA env defaults: + AUDIO_BACKEND/AUDIO_STACK/AUDIO_FORMAT/AUDIO_DURATION/AUDIO_CLIPDUR/AUDIO_CLIP + AUDIO_CLIPS_DIR/AUDIO_ASSETS/AUDIO_ASSETS_URL/AUDIO_RATE/AUDIO_CHANNELS + AUDIO_SINK/AUDIO_NULL_SINK/AUDIO_ALSA_DEVICE/AUDIO_GST_DEBUG + (or AUDIO_PLAYBACK_* equivalents) + +EOF + echo "SKIP" >"$RES_FILE" + exit 0 + ;; + + *) + log_warn "Unknown argument: $1" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; + esac +done + +# -------------------- Validate parsed values -------------------- +case "$backend" in auto|pipewire|pulseaudio|alsa) : ;; *) + log_warn "Invalid --backend '$backend'" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +case "$stack" in auto|base|overlay) : ;; *) + log_warn "Invalid --stack '$stack'" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +case "$format" in wav|aac|mp3|flac) : ;; *) + log_warn "Invalid --format '$format'" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +case "$clipDur" in short|medium|long) : ;; *) + log_warn "Invalid --clipdur '$clipDur'" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +case "$gstDebugLevel" in 1|2|3|4|5|6|7|9) : ;; *) + log_warn "Invalid --gst-debug '$gstDebugLevel' (allowed: 1 2 3 4 5 6 7 9)" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; +esac + +if [ -n "$rate" ]; then + case "$rate" in *[!0-9]*) + log_warn "Invalid --rate '$rate' (expected integer Hz)" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; + esac +fi + +if [ -n "$channels" ]; then + case "$channels" in *[!0-9]*) + log_warn "Invalid --channels '$channels' (expected integer)" + echo "SKIP" >"$RES_FILE" + exit 0 + ;; + esac + if [ "$channels" -lt 1 ] 2>/dev/null || [ "$channels" -gt 8 ] 2>/dev/null; then + log_warn "Invalid --channels '$channels' (expected 1..8)" + echo "SKIP" >"$RES_FILE" + exit 0 + fi +fi + +# -------------------- Pre-checks -------------------- +check_dependencies "gst-launch-1.0" "gst-inspect-1.0" >/dev/null 2>&1 || { + log_warn "Missing gstreamer runtime (gst-launch-1.0/gst-inspect-1.0)" + echo "SKIP" >"$RES_FILE" + exit 0 +} + +log_info "Test: $TESTNAME" +log_info "Requested: backend=$backend stack=$stack format=$format duration=$duration clipDur=$clipDur clip=${clipPath:-}" +log_info "Options: rate=${rate:-} channels=${channels:-} nullSink=$useNullSink sinkSel=${sinkSel:-}" +log_info "GST debug: GST_DEBUG=$gstDebugLevel (to file: $GST_LOG)" +log_info "Assets: clipsDir=${clipsDir:-} assets=${assetsPath:-} assetsUrl=${assetsUrl:-}" +log_info "Logs: $OUTDIR" + +# -------------------- Stack handling (base/overlay/auto) -------------------- +if command -v setup_overlay_audio_environment >/dev/null 2>&1; then + if [ "$stack" = "overlay" ]; then + log_info "Stack=overlay: applying overlay audio environment setup" + if ! setup_overlay_audio_environment; then + log_warn "Overlay audio environment setup failed (forced overlay)" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + elif [ "$stack" = "auto" ]; then + if lsmod 2>/dev/null | awk '$1 ~ /^audioreach/ { found=1; exit } END { exit !found }'; then + log_info "Stack=auto: overlay detected (audioreach modules present); applying overlay setup" + if ! setup_overlay_audio_environment; then + log_warn "Overlay audio environment setup failed (auto)" + fi + else + log_info "Stack=auto: base detected; skipping overlay setup" + fi + else + log_info "Stack=base: skipping overlay setup" + fi +else + if [ "$stack" = "overlay" ]; then + log_warn "setup_overlay_audio_environment not available but stack=overlay requested" + echo "SKIP" >"$RES_FILE" + exit 0 + fi +fi + +# -------------------- Device-provided assets provisioning -------------------- +if [ -n "$assetsPath" ] || [ -n "$clipsDir" ]; then + finalClipsDir="$(gstreamer_assets_provision "$assetsPath" "$clipsDir" "$SCRIPT_DIR")" + if [ -n "$finalClipsDir" ]; then + clipsDir="$finalClipsDir" + export AUDIO_CLIPS_BASE_DIR="$clipsDir" + log_info "AUDIO_CLIPS_BASE_DIR=$AUDIO_CLIPS_BASE_DIR" + fi +fi + +# -------------------- Resolve clip -------------------- +if [ -z "$clipPath" ]; then + clipPath="$(resolve_clip "$format" "$clipDur")" +fi + +if [ -z "$clipPath" ]; then + log_warn "No clip mapping for format=$format clipDur=$clipDur" + echo "SKIP" >"$RES_FILE" + exit 0 +fi + +log_info "Selected clip: $clipPath" + +if [ ! -s "$clipPath" ]; then + if [ -n "$assetsPath" ] || [ -n "$clipsDir" ]; then + log_warn "Clip missing/empty after local provisioning: $clipPath" + echo "SKIP" >"$RES_FILE" + exit 0 + fi + + if [ -n "$assetsUrl" ] && command -v audio_ensure_clip_ready >/dev/null 2>&1; then + log_info "Clip missing; attempting fetch via assetsUrl" + audio_ensure_clip_ready "$clipPath" "$assetsUrl" + clipRc=$? + if [ "$clipRc" -ne 0 ] 2>/dev/null; then + log_warn "Clip not ready (fetch/extract failed). SKIP." + echo "SKIP" >"$RES_FILE" + exit 0 + fi + else + log_warn "Clip missing/empty: $clipPath" + echo "SKIP" >"$RES_FILE" + exit 0 + fi +fi + +# -------------------- Clip metadata + infer missing caps -------------------- +# Only infer if user didn't provide. +if [ "$rateUser" = "0" ] || [ "$channelsUser" = "0" ]; then + if gstreamer_log_clip_metadata "$clipPath" "$META_LOG"; then + inferred="$(gstreamer_infer_audio_params_from_meta "$META_LOG")" + set -- "$inferred" + infRate="${1:-}" + infCh="${2:-}" + + if [ "$rateUser" = "0" ] && [ -z "$rate" ] && [ -n "$infRate" ]; then + rate="$infRate" + rateInferred="1" + fi + if [ "$channelsUser" = "0" ] && [ -z "$channels" ] && [ -n "$infCh" ]; then + channels="$infCh" + channelsInferred="1" + fi + fi +fi + +# -------------------- Normalize duration seconds -------------------- +secs="" +if command -v audio_parse_secs >/dev/null 2>&1; then + secs="$(audio_parse_secs "$duration" 2>/dev/null || true)" +fi +if [ -z "$secs" ]; then + case "$duration" in + '' ) secs="10" ;; + *[!0-9]* ) secs="10" ;; + * ) secs="$duration" ;; + esac +fi +log_info "Normalized duration seconds: $secs" + +# -------------------- Caps filter (ONLY if user asked OR inference succeeded) -------------------- +useCaps="0" +if [ "$rateUser" = "1" ] || [ "$channelsUser" = "1" ] || [ "$rateInferred" = "1" ] || [ "$channelsInferred" = "1" ]; then + useCaps="1" +fi + +capsStr="" +if [ "$useCaps" = "1" ]; then + capsStr="$(gstreamer_build_capsfilter_string "$rate" "$channels")" + if [ -n "$capsStr" ]; then + log_info "Playback caps: $capsStr (forced)" + else + log_info "Playback caps: (no caps forced)" + useCaps="0" + fi +else + log_info "Playback caps: (no caps forced)" +fi +# -------------------- Backend chain -------------------- +chain="" +if [ "$backend" = "auto" ] && command -v build_backend_chain >/dev/null 2>&1; then + chain="$(build_backend_chain)" +else + chain="$backend" +fi +log_info "Backend chain: $chain" + +# ALSA device: allow explicit override, else pick dynamically (audio_common-aware) +if [ -n "$alsaDevice" ]; then + GST_ALSA_PLAYBACK_DEVICE="$alsaDevice" + export GST_ALSA_PLAYBACK_DEVICE +fi +alsaPick="$(gstreamer_alsa_pick_playback_hw)" +log_info "ALSA playback device (best-effort): $alsaPick" + +# -------------------- GStreamer debug capture (single level only) -------------------- +export GST_DEBUG_NO_COLOR=1 +export GST_DEBUG="$gstDebugLevel" +export GST_DEBUG_FILE="$GST_LOG" + +# -------------------- Attempt playback across backends -------------------- +for b in $chain; do + [ -n "$b" ] || continue + + case "$b" in + pipewire|pulseaudio|alsa) : ;; + auto) continue ;; + *) + log_warn "Unsupported backend: $b" + continue + ;; + esac + + if command -v check_audio_daemon >/dev/null 2>&1; then + if [ "$b" = "pipewire" ] || [ "$b" = "pulseaudio" ]; then + if ! check_audio_daemon "$b"; then + log_warn "Audio daemon not running for backend=$b; trying next backend" + continue + fi + fi + fi + + # Default routing selection (pipewire/pulseaudio), best-effort only + gstreamer_select_default_sink "$b" "$sinkSel" "$useNullSink" || true + + # Build pipeline via lib (explicit decode chains + optional caps + backend sink) + pipe="$(gstreamer_build_playback_pipeline "$b" "$format" "$clipPath" "$capsStr" "$alsaPick")" + if [ -z "$pipe" ]; then + log_warn "No usable sink element/pipeline for backend=$b; trying next backend" + continue + fi + + finalBackend="$b" + finalPipe="$pipe" + + log_info "Selected backend: $finalBackend" + + : >"$GST_LOG" + + # Run gst-launch with timeout (shared lib runner) + gstreamer_run_gstlaunch_timeout "$secs" "$finalPipe" + gstRc=$? + + log_info "gst-launch exit code: $gstRc" + + # Evidence after run (backend + optional asoc fallback centralized in lib) + ev="$(gstreamer_backend_evidence_sampled "$finalBackend" 3)" + log_info "Evidence streaming/path_on: $ev" + + okRc="0" + if [ "$gstRc" -eq 0 ] 2>/dev/null; then + okRc="1" + elif [ "$secs" -gt 0 ] 2>/dev/null; then + # tolerate timeout exit codes when we intentionally stop playback + if [ "$gstRc" -eq 124 ] 2>/dev/null || [ "$gstRc" -eq 143 ] 2>/dev/null; then + okRc="1" + fi + fi + + if [ "$okRc" = "1" ] && [ "$ev" = "1" ]; then + result="PASS" + reason="backend=$finalBackend rc=$gstRc evidence=$ev" + break + fi + + log_warn "Playback attempt did not meet PASS criteria on backend=$finalBackend (rc=$gstRc evidence=$ev). Trying next." +done + +# -------------------- Post validation -------------------- +if command -v dump_mixers >/dev/null 2>&1; then + dump_mixers "$MIXER_LOG" || true +fi + +if command -v scan_audio_dmesg >/dev/null 2>&1; then + scan_audio_dmesg "$DMESG_DIR" || true +fi + +# -------------------- Emit result -------------------- +case "$result" in + PASS) + log_pass "$TESTNAME PASS: $reason" + echo "PASS" >"$RES_FILE" + ;; + *) + if [ -z "$finalBackend" ]; then + log_warn "$TESTNAME SKIP: no usable backend found" + echo "SKIP" >"$RES_FILE" + else + log_fail "$TESTNAME FAIL: backend=$finalBackend rc=$gstRc" + log_fail "Pipeline: $finalPipe" + echo "FAIL" >"$RES_FILE" + fi + ;; +esac + +exit 0 From da85edc359728dc8911128ab0809d7ff14e4751b Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Mon, 5 Jan 2026 10:56:09 +0530 Subject: [PATCH 2/4] utils: lib_gstreamer: deduplicate helpers and centralize caps inference/timeout/evidence Refactor lib_gstreamer.sh to provide reusable building blocks for all Audio_* GST tests (Playback) and remove duplicate/unused implementations. Changes: - Centralize gst-discoverer capture + audio caps inference (rate/channels) - Provide a single gst-launch runner with timeout fallback (audio_timeout_run/timeout/direct) - Consolidate backend evidence sampling with optional ASoC path_on fallback - Keep backend-aware sink/source selection helpers for reuse by other audio runners - Remove duplicated function variants and align naming/behavior to one canonical set Signed-off-by: Srikanth Muppandam --- Runner/utils/lib_gstreamer.sh | 500 ++++++++++++++++++++++++++++++++++ 1 file changed, 500 insertions(+) create mode 100755 Runner/utils/lib_gstreamer.sh diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh new file mode 100755 index 00000000..95b90f19 --- /dev/null +++ b/Runner/utils/lib_gstreamer.sh @@ -0,0 +1,500 @@ +#!/bin/sh +# Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. +# SPDX-License-Identifier: BSD-3-Clause-Clear +# +# Runner/utils/lib_gstreamer.sh +# +# GStreamer helpers. +# +# Contract: +# - run.sh sources functestlib.sh, and other required lib_* (optional), then this file. +# - run.sh decides PASS/FAIL/SKIP and writes .res (and always exits 0). +# +# POSIX only. + +GSTBIN="${GSTBIN:-gst-launch-1.0}" +GSTINSPECT="${GSTINSPECT:-gst-inspect-1.0}" +GSTDISCOVER="${GSTDISCOVER:-gst-discoverer-1.0}" +GSTLAUNCHFLAGS="${GSTLAUNCHFLAGS:--e -v -m}" + +# Optional env overrides (set by run.sh) +# GST_ALSA_PLAYBACK_DEVICE=hw:0,0 +# GST_ALSA_CAPTURE_DEVICE=hw:0,1 + +# -------------------- Element check -------------------- +has_element() { + elem="$1" + [ -n "$elem" ] || return 1 + command -v "$GSTINSPECT" >/dev/null 2>&1 || return 1 + "$GSTINSPECT" "$elem" >/dev/null 2>&1 +} + +# -------------------- Pretty printing (multi-line) -------------------- +gstreamer_pretty_pipeline() { + pipe="$1" + printf '%s\n' "$pipe" | sed 's/[[:space:]]\+![[:space:]]\+/ ! \\\n /g' +} + +gstreamer_print_cmd_multiline() { + pipe="$1" + log_info "Final gst-launch command:" + printf '%s \\\n' "$GSTBIN" + printf ' %s \\\n' "$GSTLAUNCHFLAGS" + gstreamer_pretty_pipeline "$pipe" +} + +# -------------------- ALSA hw discovery (FIXED) -------------------- +gstreamer_alsa_pick_playback_hw() { + if [ -n "${GST_ALSA_PLAYBACK_DEVICE:-}" ]; then + printf '%s\n' "$GST_ALSA_PLAYBACK_DEVICE" + return 0 + fi + + # Prefer audio_common if present + if command -v alsa_pick_playback >/dev/null 2>&1; then + v="$(alsa_pick_playback 2>/dev/null || true)" + [ -n "$v" ] && { printf '%s\n' "$v"; return 0; } + fi + + command -v aplay >/dev/null 2>&1 || { printf '%s\n' "default"; return 0; } + + line="$(aplay -l 2>/dev/null \ + | sed -n 's/^card \([0-9][0-9]*\):.*device \([0-9][0-9]*\):.*/\1 \2/p' \ + | head -n1)" + + if [ -n "$line" ]; then + card="$(printf '%s\n' "$line" | awk '{print $1}')" + dev="$(printf '%s\n' "$line" | awk '{print $2}')" + case "$card:$dev" in + (*[!0-9]*:*|*:*[!0-9]*) : ;; + (*) printf 'hw:%s,%s\n' "$card" "$dev"; return 0 ;; + esac + fi + + printf '%s\n' "default" + return 0 +} + +gstreamer_alsa_pick_capture_hw() { + if [ -n "${GST_ALSA_CAPTURE_DEVICE:-}" ]; then + printf '%s\n' "$GST_ALSA_CAPTURE_DEVICE" + return 0 + fi + + # Prefer audio_common's alsa_pick_capture if present + if command -v alsa_pick_capture >/dev/null 2>&1; then + v="$(alsa_pick_capture 2>/dev/null || true)" + [ -n "$v" ] && { printf '%s\n' "$v"; return 0; } + fi + + command -v arecord >/dev/null 2>&1 || { printf '%s\n' "default"; return 0; } + + line="$(arecord -l 2>/dev/null \ + | sed -n 's/^card \([0-9][0-9]*\):.*device \([0-9][0-9]*\):.*/\1 \2/p' \ + | head -n1)" + + if [ -n "$line" ]; then + card="$(printf '%s\n' "$line" | awk '{print $1}')" + dev="$(printf '%s\n' "$line" | awk '{print $2}')" + case "$card:$dev" in + (*[!0-9]*:*|*:*[!0-9]*) : ;; + (*) printf 'hw:%s,%s\n' "$card" "$dev"; return 0 ;; + esac + fi + + printf '%s\n' "default" + return 0 +} + +# -------------------- PipeWire/Pulse default sink selection -------------------- +# gstreamer_select_default_sink +gstreamer_select_default_sink() { + backend="$1" + sinkSel="$2" + useNullSink="$3" + + case "$backend" in + pipewire) + if [ "$useNullSink" = "1" ] && command -v pw_default_null >/dev/null 2>&1; then + sid="$(pw_default_null 2>/dev/null || true)" + if [ -n "$sid" ] && command -v pw_set_default_sink >/dev/null 2>&1; then + pw_set_default_sink "$sid" >/dev/null 2>&1 || true + log_info "PipeWire: set default sink to null/dummy id=$sid" + return 0 + fi + fi + + if [ -n "$sinkSel" ] && command -v wpctl >/dev/null 2>&1; then + case "$sinkSel" in + *[!0-9]*) + blk="$(wpctl status 2>/dev/null | sed -n '/Sinks:/,/Sources:/p')" + sid="$(printf '%s\n' "$blk" | grep -i "$sinkSel" | sed -n 's/^[^0-9]*\([0-9][0-9]*\)\..*/\1/p' | head -n1)" + ;; + *) + sid="$sinkSel" + ;; + esac + if [ -n "${sid:-}" ] && command -v pw_set_default_sink >/dev/null 2>&1; then + pw_set_default_sink "$sid" >/dev/null 2>&1 || true + log_info "PipeWire: set default sink id=$sid (from --sink '$sinkSel')" + return 0 + fi + fi + return 0 + ;; + + pulseaudio) + if [ "$useNullSink" = "1" ] && command -v pa_default_null >/dev/null 2>&1; then + sname="$(pa_default_null 2>/dev/null || true)" + if [ -n "$sname" ] && command -v pa_set_default_sink >/dev/null 2>&1; then + pa_set_default_sink "$sname" >/dev/null 2>&1 || true + log_info "PulseAudio: set default sink to null/dummy '$sname'" + return 0 + fi + fi + + if [ -n "$sinkSel" ] && command -v pa_sink_name >/dev/null 2>&1 && command -v pa_set_default_sink >/dev/null 2>&1; then + sname="$(pa_sink_name "$sinkSel" 2>/dev/null || true)" + if [ -n "$sname" ]; then + pa_set_default_sink "$sname" >/dev/null 2>&1 || true + log_info "PulseAudio: set default sink '$sname' (from --sink '$sinkSel')" + return 0 + fi + fi + return 0 + ;; + + alsa) + return 0 + ;; + + *) + return 1 + ;; + esac +} + +# -------------------- Sink element picker (backend-aware) -------------------- +# Prints sink element string or empty (meaning: no usable sink). +gstreamer_pick_sink_element() { + backend="$1" + alsadev="$2" + [ -n "$alsadev" ] || alsadev="default" + + case "$backend" in + pipewire) + if has_element pipewiresink; then + printf '%s\n' "pipewiresink" + return 0 + fi + if has_element pulsesink; then + printf '%s\n' "pulsesink" + return 0 + fi + if has_element alsasink; then + printf '%s\n' "alsasink device=$alsadev" + return 0 + fi + ;; + pulseaudio) + if has_element pulsesink; then + printf '%s\n' "pulsesink" + return 0 + fi + ;; + alsa) + if has_element alsasink; then + printf '%s\n' "alsasink device=$alsadev" + return 0 + fi + ;; + esac + + printf '%s\n' "" + return 0 +} + +# -------------------- Decoder chain pickers -------------------- +gstreamer_pick_aac_decode_chain() { + if has_element aacparse && has_element avdec_aac; then + printf '%s\n' "aacparse ! avdec_aac" + return 0 + fi + if has_element aacparse && has_element faad; then + printf '%s\n' "aacparse ! faad" + return 0 + fi + printf '%s\n' "decodebin" + return 0 +} + +gstreamer_pick_mp3_decode_chain() { + if has_element mpegaudioparse && has_element mpg123audiodec; then + printf '%s\n' "mpegaudioparse ! mpg123audiodec" + return 0 + fi + if has_element mpegaudioparse && has_element mad; then + printf '%s\n' "mpegaudioparse ! mad" + return 0 + fi + printf '%s\n' "decodebin" + return 0 +} + +gstreamer_pick_flac_decode_chain() { + if has_element flacparse && has_element flacdec; then + printf '%s\n' "flacparse ! flacdec" + return 0 + fi + printf '%s\n' "decodebin" + return 0 +} + +gstreamer_pick_wav_decode_chain() { + if has_element wavparse; then + printf '%s\n' "wavparse" + return 0 + fi + printf '%s\n' "decodebin" + return 0 +} + +gstreamer_pick_decode_chain() { + format="$1" + case "$format" in + aac) gstreamer_pick_aac_decode_chain ;; + flac) gstreamer_pick_flac_decode_chain ;; + mp3) gstreamer_pick_mp3_decode_chain ;; + wav) gstreamer_pick_wav_decode_chain ;; + *) printf '%s\n' "decodebin" ;; + esac +} + +# -------------------- Device-provided assets provisioning (reusable) -------------------- +# gstreamer_assets_provision +# Prints final clipsDir (or empty if none) +gstreamer_assets_provision() { + assetsPath="$1" + clipsDir="$2" + scriptDir="$3" + + [ -n "$assetsPath" ] || { printf '%s\n' "${clipsDir:-}"; return 0; } + + if [ -d "$assetsPath" ]; then + printf '%s\n' "$assetsPath" + return 0 + fi + + if [ ! -f "$assetsPath" ]; then + log_warn "Invalid assets path: $assetsPath" + printf '%s\n' "${clipsDir:-}" + return 0 + fi + + if [ -z "$clipsDir" ]; then + clipsDir="${scriptDir:-.}/AudioClips" + fi + + mkdir -p "$clipsDir" >/dev/null 2>&1 || true + log_info "Extracting assets into clipsDir=$clipsDir" + + tar -xzf "$assetsPath" -C "$clipsDir" >/dev/null 2>&1 \ + || tar -xJf "$assetsPath" -C "$clipsDir" >/dev/null 2>&1 \ + || tar -xf "$assetsPath" -C "$clipsDir" >/dev/null 2>&1 \ + || log_warn "Failed to extract assets: $assetsPath" + + printf '%s\n' "$clipsDir" + return 0 +} + +# -------------------- Clip metadata + caps inference (reusable) -------------------- +# gstreamer_log_clip_metadata +gstreamer_log_clip_metadata() { + clip="$1" + metaLog="$2" + + [ -n "$clip" ] || return 1 + [ -n "$metaLog" ] || return 1 + command -v "$GSTDISCOVER" >/dev/null 2>&1 || return 1 + [ -f "$clip" ] || return 1 + + : >"$metaLog" 2>/dev/null || true + + "$GSTDISCOVER" "$clip" >"$metaLog" 2>&1 || true + + log_info "Clip metadata ($GSTDISCOVER):" + while IFS= read -r line; do + [ -n "$line" ] || continue + log_info "$line" + done <"$metaLog" + + return 0 +} + +# gstreamer_infer_audio_params_from_meta +# Prints: " " (either can be empty) +gstreamer_infer_audio_params_from_meta() { + metaLog="$1" + [ -f "$metaLog" ] || { printf '%s\n' " "; return 0; } + + rate="" + ch="" + + # Prefer explicit keys first (avoids matching "Bitrate") + rate="$(grep -i -m1 -E '^[[:space:]]*Sample[[:space:]]+rate[[:space:]]*[:=][[:space:]]*[0-9]+' "$metaLog" 2>/dev/null \ + | sed -n 's/.*[:=][[:space:]]*\([0-9][0-9]*\).*/\1/p')" + + ch="$(grep -i -m1 -E '^[[:space:]]*Channels[[:space:]]*[:=][[:space:]]*[0-9]+' "$metaLog" 2>/dev/null \ + | sed -n 's/.*[:=][[:space:]]*\([0-9][0-9]*\).*/\1/p')" + + # Fallback: audio/x-raw caps line + if [ -z "$rate" ] || [ -z "$ch" ]; then + capsLine="$(grep -m1 -E 'audio/x-raw' "$metaLog" 2>/dev/null || true)" + if [ -z "$rate" ] && [ -n "$capsLine" ]; then + rate="$(printf '%s' "$capsLine" | sed -n 's/.*rate[^0-9]*\([0-9][0-9]*\).*/\1/p')" + fi + if [ -z "$ch" ] && [ -n "$capsLine" ]; then + ch="$(printf '%s' "$capsLine" | sed -n 's/.*channels[^0-9]*\([0-9][0-9]*\).*/\1/p')" + fi + fi + + printf '%s %s\n' "${rate:-}" "${ch:-}" + return 0 +} + +# gstreamer_build_capsfilter_string +# Prints "audio/x-raw[,rate=...][,channels=...]" or "" if neither set. +gstreamer_build_capsfilter_string() { + rate="$1" + channels="$2" + + if [ -n "$rate" ]; then + case "$rate" in *[!0-9]* ) rate="";; esac + fi + if [ -n "$channels" ]; then + case "$channels" in *[!0-9]* ) channels="";; esac + fi + + if [ -z "$rate" ] && [ -z "$channels" ]; then + printf '%s\n' "" + return 0 + fi + + caps="audio/x-raw" + if [ -n "$rate" ]; then + caps="${caps},rate=${rate}" + fi + if [ -n "$channels" ]; then + caps="${caps},channels=${channels}" + fi + + printf '%s\n' "$caps" + return 0 +} + +# -------------------- Evidence (central wrapper) -------------------- +gstreamer_backend_evidence() { + backend="$1" + + case "$backend" in + pipewire) + command -v audio_evidence_pw_streaming >/dev/null 2>&1 && { + v="$(audio_evidence_pw_streaming 2>/dev/null || echo 0)" + [ "$v" -eq 1 ] 2>/dev/null && { echo 1; return; } + } + ;; + pulseaudio) + command -v audio_evidence_pa_streaming >/dev/null 2>&1 && { + v="$(audio_evidence_pa_streaming 2>/dev/null || echo 0)" + [ "$v" -eq 1 ] 2>/dev/null && { echo 1; return; } + } + ;; + alsa) + command -v audio_evidence_alsa_running_any >/dev/null 2>&1 && { + v="$(audio_evidence_alsa_running_any 2>/dev/null || echo 0)" + [ "$v" -eq 1 ] 2>/dev/null && { echo 1; return; } + } + ;; + esac + + command -v audio_evidence_asoc_path_on >/dev/null 2>&1 && { + audio_evidence_asoc_path_on + return + } + + echo 0 +} + +gstreamer_backend_evidence_sampled() { + backend="$1" + tries="${2:-3}" + + case "$tries" in ''|*[!0-9]*) tries=3 ;; esac + + i=0 + while [ "$i" -lt "$tries" ] 2>/dev/null; do + v="$(gstreamer_backend_evidence "$backend")" + [ "$v" -eq 1 ] 2>/dev/null && { echo 1; return; } + sleep 1 + i=$((i + 1)) + done + + echo 0 +} + +# -------------------- Single runner: gst-launch with timeout -------------------- +# gstreamer_run_gstlaunch_timeout +# Returns gst-launch rc. +gstreamer_run_gstlaunch_timeout() { + secs="$1" + pipe="$2" + + case "$secs" in ''|*[!0-9]*) secs=10 ;; esac + command -v "$GSTBIN" >/dev/null 2>&1 || return 127 + + gstreamer_print_cmd_multiline "$pipe" + + if [ "$secs" -gt 0 ] 2>/dev/null; then + if command -v audio_timeout_run >/dev/null 2>&1; then + # shellcheck disable=SC2086 + audio_timeout_run "${secs}s" "$GSTBIN" $GSTLAUNCHFLAGS $pipe + return $? + fi + if command -v timeout >/dev/null 2>&1; then + # shellcheck disable=SC2086 + timeout "$secs" "$GSTBIN" $GSTLAUNCHFLAGS $pipe + return $? + fi + fi + + # shellcheck disable=SC2086 + "$GSTBIN" $GSTLAUNCHFLAGS $pipe + return $? +} + +# -------------------- Playback pipeline builder (backend-aware) -------------------- +# gstreamer_build_playback_pipeline +gstreamer_build_playback_pipeline() { + backend="$1" + format="$2" + file="$3" + capsStr="$4" + alsadev="$5" + + [ -n "$alsadev" ] || alsadev="default" + + dec="$(gstreamer_pick_decode_chain "$format")" + sinkElem="$(gstreamer_pick_sink_element "$backend" "$alsadev")" + if [ -z "$sinkElem" ]; then + printf '%s\n' "" + return 0 + fi + + if [ -n "$capsStr" ]; then + printf '%s\n' "filesrc location=${file} ! ${dec} ! audioconvert ! audioresample ! ${capsStr} ! ${sinkElem}" + return 0 + fi + + printf '%s\n' "filesrc location=${file} ! ${dec} ! audioconvert ! audioresample ! ${sinkElem}" + return 0 +} From cc00ce41ad1498b65264ed9eb2f9fae2f4695822 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Mon, 5 Jan 2026 10:57:37 +0530 Subject: [PATCH 3/4] docs: add GStreamer audio playback runner usage and GST debug guidance - Backend/stack selection examples (pipewire/pulseaudio/alsa; base/overlay/auto) - Assets provisioning (local directory, local tarball, optional URL fetch) - Caps inference vs forced caps behavior (--rate/--channels) - GST debug controls (GST_DEBUG level) and where logs are written - Typical CI usage patterns (null sink, non-interactive runs) Signed-off-by: Srikanth Muppandam --- .../GSTreamer/Audio/Audio_Playback/README.md | 348 ++++++++++++++++++ 1 file changed, 348 insertions(+) create mode 100644 Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/README.md diff --git a/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/README.md b/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/README.md new file mode 100644 index 00000000..71ad18d3 --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/README.md @@ -0,0 +1,348 @@ +# Audio_Playback (GStreamer) — Runner Test + +This directory contains the **Audio_Playback** validation test for Qualcomm Linux Testkit runners. + +It validates audio **playback** using **GStreamer (`gst-launch-1.0`)** with an auto-selected backend: +- **PipeWire** +- **PulseAudio** +- **ALSA (direct)** + +The script is designed to be **CI/LAVA-friendly**: +- Writes **PASS/FAIL/SKIP** into `Audio_Playback.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 + +--- + +## Location in repo + +Expected path: + +``` +Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/run.sh +``` + +Required shared utils (sourced from `Runner/utils` via `init_env`): +- `functestlib.sh` +- `lib_gstreamer.sh` +- optional: `audio_common.sh` (wrapper/aliases if present) + +--- + +## 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/audio_common.sh` +3. Resolves or accepts a clip path +4. Ensures the clip is present locally (supports device-provided assets via `--assets` / `--clips-dir`, + and optionally remote fetch via `--assets-url` if the helper exists) +5. Builds a **backend chain** (auto: pipewire → pulseaudio → alsa) +6. Selects a sink (optional `--sink`, optional `--null-sink`) +7. Constructs a **multi-line** `gst-launch-1.0` playback pipeline +8. Runs the pipeline for the requested duration (watchdog timeout) +9. Applies **post-validation evidence** (streaming/path activity) and emits PASS/FAIL/SKIP +10. Collects best-effort diagnostics: + - mixer dump + - dmesg scan + +--- + +## PASS / FAIL / SKIP criteria + +### PASS +- `gst-launch-1.0` either: + - exits `0`, or + - is killed by watchdog timeout (`124` or `143`) **when a duration timeout is used** +- **AND** evidence indicates the audio path was active, such as: + - PipeWire stream RUNNING, or + - PulseAudio stream/sink-input exists, or + - ALSA PCM substream RUNNING, or + - ASoC DAPM path shows `On` (fallback heuristic) + +### FAIL +- A backend was attempted and the run did not meet PASS criteria (e.g., immediate pipeline failure, no evidence of activity) + +### SKIP +- Missing required tools (`gst-launch-1.0`, `gst-inspect-1.0`) +- No usable backend found +- Clip missing and assets not available +- Invalid arguments or required values missing + +**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: + +``` +./Audio_Playback.res +./logs/Audio_Playback/ + gst.log + mixers.txt + clip_meta.txt + dmesg/ + (dmesg scan outputs) +``` + +The final `gst-launch-1.0` command is always printed to the console. + +--- + +## Dependencies + +### Required +- `gst-launch-1.0` +- `gst-inspect-1.0` + +### Recommended (for best results) +- `gst-discoverer-1.0` (for clip metadata and cap inference) +- For PipeWire backend: + - `pipewire` running + - `wpctl` available + - `pipewiresink` GStreamer plugin (preferred) +- For PulseAudio backend: + - `pulseaudio` or `pipewire-pulse` running + - `pactl` available + - `pulsesink` GStreamer plugin +- For ALSA backend: + - `alsasink` GStreamer plugin + - proper hw device path (default `hw:0,0`) + +--- + +## Caps negotiation behavior (important) + +The pipeline only forces caps (`audio/x-raw,rate=...,channels=...`) when: + +- User explicitly sets `--rate` and/or `--channels`, **OR** +- Inference succeeded via `gst-discoverer-1.0` + +Otherwise the test **does NOT force caps** and lets GStreamer negotiate. +This reduces false failures on hardware with unusual constraints. + +--- + +## Usage + +Run: + +``` +./run.sh [options] +``` + +Help: + +``` +./run.sh --help +``` + +### Options + +- `--backend ` + - Default: `auto` (tries pipewire → pulseaudio → alsa) + +- `--stack ` + - Default: `auto` + - `auto` : detect overlay (audioreach modules) and apply overlay setup only if detected + - `base` : force base (do not run overlay setup even if audioreach modules are present) + - `overlay` : force overlay setup (SKIP if overlay setup fails) + +- `--format ` + - Default: `wav` + +- `--duration ` + - Default: `${RUNTIMESEC:-10s}` + +- `--clipdur ` + - Used with `resolve_clip()` when `--clip` is not specified + - Default: `short` + +- `--clip ` + - Override resolved clip path + +- `--clips-dir ` + - Points to **already untarred** clips directory on device. + - Sets `AUDIO_CLIPS_BASE_DIR` + +- `--assets ` + - Device-provided assets (no network): + - If directory: treated as `--clips-dir` + - If file: `.tar` / `.tar.gz` extracted into `clips-dir` (or `AudioClips` under script dir) + +- `--assets-url ` + - Optional remote tarball URL (used only if clip missing and helper exists) + +- `--rate ` + - Forces output caps **rate** after decode/resample + - Example: `48000` + +- `--channels ` + - Forces output caps **channels** after decode/resample + - Example: `1` or `2` + +- `--sink ` + - For PipeWire: numeric id or substring match in `wpctl status` + - For PulseAudio: sink name or numeric index + +- `--null-sink` + - Prefer null/dummy sink if available (useful for silent CI runs) + +- `--gst-debug ` + - Sets `GST_DEBUG=` (single numeric level) + - Values: + - `1` ERROR + - `2` WARNING + - `3` FIXME + - `4` INFO + - `5` DEBUG + - `6` LOG + - `7` TRACE + - `9` MEMDUMP + - Default: `2` + +--- + +## Examples + +### 1) Basic WAV playback (auto backend) + +``` +./run.sh --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +### 2) Base stack (force no overlay actions) + +``` +./run.sh --stack base --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +### 3) Overlay stack (force overlay setup) + +``` +./run.sh --stack overlay --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +### 4) AAC playback (matches your reference pipeline intent) + +Your reference pipeline: + +``` +gst-launch-1.0 filesrc location=/opt/aac_48k_mono.aac ! aacparse ! avdec_aac ! audioconvert ! audioresample ! audio/x-raw,rate=48000,channels=1 ! alsasink device=hw:0,0 +``` + +Equivalent test invocation: + +``` +./run.sh --format aac --clip /opt/aac_48k_mono.aac --rate 48000 --channels 1 --duration 10s +``` + +### 5) FLAC 48k stereo playback (reference intent) + +``` +./run.sh --format flac --clip /opt/flac_48k_stereo.flac --rate 48000 --channels 2 --duration 10s +``` + +### 6) MP3 44.1k stereo playback (reference intent) + +``` +./run.sh --format mp3 --clip /opt/mp3_44k1_stereo.mp3 --rate 44100 --channels 2 --duration 10s +``` + +### 7) Prefer a null/dummy sink for CI + +``` +./run.sh --null-sink --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +### 8) Choose a specific sink + +PipeWire example: + +``` +./run.sh --backend pipewire --sink speaker --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +PulseAudio example: + +``` +./run.sh --backend pulseaudio --sink 0 --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +### 9) Use device-provided local assets (tar.gz) + +``` +./run.sh --assets /opt/audio_clips.tar.gz --format wav --clipdur short --duration 10s +``` + +### 10) Use device-provided untar directory + +``` +./run.sh --clips-dir /opt/AudioClips --format wav --clipdur short --duration 10s +``` + +### 11) Increase GStreamer debug verbosity + +``` +./run.sh --gst-debug 5 --format wav --clip /var/yesterday_48KHz.wav --duration 10s +``` + +--- + +## Troubleshooting + +### A) “SKIP: Missing gstreamer runtime” +- Ensure `gst-launch-1.0` and `gst-inspect-1.0` are installed in the image. + +### B) PipeWire/PulseAudio backend not used +- Ensure the daemon is running: + - PipeWire: `pgrep -x pipewire` + - PulseAudio: `pgrep -x pulseaudio` or `pgrep -x pipewire-pulse` +- Ensure control tools exist: + - PipeWire: `wpctl` + - PulseAudio: `pactl` +- Ensure plugin exists: + - PipeWire: `pipewiresink` + - PulseAudio: `pulsesink` + +### C) ALSA backend fails +- Confirm `alsasink` plugin exists: + - `gst-inspect-1.0 alsasink` +- Confirm device is correct (default `hw:0,0` in the test): + - You can update the default in the test or extend args later if needed. + +### D) “FAIL: evidence=0” +- If audio is routed through a sound server, ensure the sound server interfaces are accessible. +- Check `logs/Audio_Playback/gst.log`, `mixers.txt`, and `dmesg/` outputs. +- Try forcing `--gst-debug 5` for more detail. + +--- + +## Notes for CI / LAVA + +- The test always exits `0`. +- Use the `.res` file for result: + - `PASS` + - `FAIL` + - `SKIP` + +--- + +## Maintainers + +- This test is intended to be robust and easy to extend alongside: + - `Audio_Record` + - `Audio_Duplex_Loopback` + - `Audio_Concurrency` + +Follow the same conventions: +- Keep **usage in run.sh** +- Use shared helpers in `Runner/utils/lib_gstreamer.sh` and `audio_common.sh` +- Use `log_*` helpers from `functestlib.sh` +- Always emit `.res` and exit `0` From 0142a76bab3f2dfb72ff4b1bfad61503e1d59532 Mon Sep 17 00:00:00 2001 From: Srikanth Muppandam Date: Mon, 5 Jan 2026 10:58:14 +0530 Subject: [PATCH 4/4] lava: add gstreamer-audio-playback test definition for Audio_Playback runner - Maps YAML params to run.sh CLI (only passes args when non-empty) - Supports backend/stack/format/duration/clipdur/clip overrides - Supports assets and clips-dir provisioning inputs - Supports optional caps enforcement, sink selection, null sink and GST debug level - Publishes Audio_Playback.res via send-to-lava.sh Signed-off-by: Srikanth Muppandam --- .../Audio/Audio_Playback/Audio_Playback.yaml | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/Audio_Playback.yaml diff --git a/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/Audio_Playback.yaml b/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/Audio_Playback.yaml new file mode 100755 index 00000000..d06425cb --- /dev/null +++ b/Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/Audio_Playback.yaml @@ -0,0 +1,72 @@ +metadata: + name: gstreamer-audio-playback + format: "Lava-Test Test Definition 1.0" + description: > + Audio playback validation using GStreamer (gst-launch-1.0) on Qualcomm Linux platforms. + Supports backend chaining (pipewire/pulseaudio/alsa), optional overlay stack setup, + clip provisioning from local assets or URL, caps inference via gst-discoverer, and + evidence-based PASS/FAIL (PipeWire/Pulse/ALSA + optional ASoC path_on fallback). + os: + - linux + scope: + - functional + +params: + # Mirrors run.sh CLI + env behavior + AUDIO_BACKEND: "" # auto|pipewire|pulseaudio|alsa (empty -> run.sh default "auto") + AUDIO_STACK: "" # auto|base|overlay (empty -> run.sh default "auto") + AUDIO_FORMAT: "wav" # wav|aac|mp3|flac + AUDIO_DURATION: "10s" # N|Ns|Nm|Nh|MM:SS|HH:MM:SS + AUDIO_CLIPDUR: "short" # short|medium|long + AUDIO_CLIP: "" # absolute path override (optional) + + AUDIO_CLIPS_DIR: "" # directory containing clips (sets AUDIO_CLIPS_BASE_DIR inside run.sh) + AUDIO_ASSETS: "" # dir or .tar/.tar.gz/.tgz file to provision clips + AUDIO_ASSETS_URL: "" # optional URL used only if clip missing and audio_ensure_clip_ready exists + + AUDIO_RATE: "" # force/inject caps rate (Hz) (optional) + AUDIO_CHANNELS: "" # force/inject caps channels (optional) + AUDIO_SINK: "" # sink selector (PipeWire id/substr, Pulse sink/index) + AUDIO_NULL_SINK: "0" # 1 to add --null-sink + + AUDIO_ALSA_DEVICE: "" # hw:C,D or default (optional override for alsasink fallback) + AUDIO_GST_DEBUG: "2" # 1|2|3|4|5|6|7|9 + +run: + steps: + - REPO_PATH="$PWD" + + # IMPORTANT: path matches your repo layout shown in the prompt + - cd Runner/suites/Multimedia/GSTreamer/Audio/Audio_Playback/ + + # Build CLI args only when params are non-empty + - | + CMD="./run.sh" + + [ -n "${AUDIO_BACKEND}" ] && CMD="${CMD} --backend ${AUDIO_BACKEND}" + [ -n "${AUDIO_STACK}" ] && CMD="${CMD} --stack ${AUDIO_STACK}" + [ -n "${AUDIO_FORMAT}" ] && CMD="${CMD} --format ${AUDIO_FORMAT}" + [ -n "${AUDIO_DURATION}" ] && CMD="${CMD} --duration ${AUDIO_DURATION}" + [ -n "${AUDIO_CLIPDUR}" ] && CMD="${CMD} --clipdur ${AUDIO_CLIPDUR}" + [ -n "${AUDIO_CLIP}" ] && CMD="${CMD} --clip ${AUDIO_CLIP}" + + [ -n "${AUDIO_CLIPS_DIR}" ] && CMD="${CMD} --clips-dir ${AUDIO_CLIPS_DIR}" + [ -n "${AUDIO_ASSETS}" ] && CMD="${CMD} --assets ${AUDIO_ASSETS}" + [ -n "${AUDIO_ASSETS_URL}" ] && CMD="${CMD} --assets-url ${AUDIO_ASSETS_URL}" + + [ -n "${AUDIO_RATE}" ] && CMD="${CMD} --rate ${AUDIO_RATE}" + [ -n "${AUDIO_CHANNELS}" ] && CMD="${CMD} --channels ${AUDIO_CHANNELS}" + [ -n "${AUDIO_SINK}" ] && CMD="${CMD} --sink ${AUDIO_SINK}" + + [ -n "${AUDIO_ALSA_DEVICE}" ] && CMD="${CMD} --alsa-device ${AUDIO_ALSA_DEVICE}" + [ -n "${AUDIO_GST_DEBUG}" ] && CMD="${CMD} --gst-debug ${AUDIO_GST_DEBUG}" + + if [ "${AUDIO_NULL_SINK}" = "1" ] || [ "${AUDIO_NULL_SINK}" = "true" ]; then + CMD="${CMD} --null-sink" + fi + + echo "[LAVA] Running: ${CMD}" + sh -c "${CMD}" || true + + # Your run.sh writes ./Audio_Playback.res in this directory + - "${REPO_PATH}/Runner/utils/send-to-lava.sh Audio_Playback.res || true"