diff --git a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md
index 2b9b5fe8f..cfa2cd903 100644
--- a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md
+++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/README.md
@@ -49,7 +49,7 @@ At a high level, the test:
- Uses `videotestsrc` to generate test video patterns (SMPTE color bars)
- Encodes to H.264 or H.265 using V4L2 hardware encoders
- Saves encoded files to `logs/Video_Encode_Decode/encoded/`
- - Tests 4K resolution (3840x2160) by default
+ - Tests 480p resolution (640x480) by default
6. **Decoding phase**:
- Reads the previously encoded files (H.264/H.265) or downloaded clips (VP9)
- Decodes using V4L2 hardware decoders
@@ -75,6 +75,195 @@ By default, the test runs the following test cases at 4K resolution for H.264/H.
---
+## Advanced Test Cases
+
+The test suite includes 9 advanced test cases that can be run using the `--test-type` parameter. These tests validate specific video features and scenarios.
+
+### Test Type: UVC Camera Preview
+
+**Test: UVC_Live_Preview_1080p (PR76474)**
+- **Description**: UVC camera live preview at 1080p@5fps with Wayland display output
+- **Requirements**: UVC camera device, Wayland compositor
+- **Pipeline**: `v4l2src → videoconvert → qtivtransform (optional) → waylandsink`
+- **Usage**: `./run.sh --test-type uvc --duration 30`
+- **Features**:
+ - Automatic UVC camera detection
+ - Wayland display setup (auto-starts Weston if needed)
+ - Optional rotation support via qtivtransform
+
+### Test Type: Dynamic Resolution Change (DRC)
+
+**Test: DRC_H264_Decode_1080p_720p (FR82787)**
+- **Description**: Validates dynamic resolution change during H.264 decode (1080p → 720p)
+- **Requirements**: Special DRC test clip, Wayland display, fpsdisplaysink
+- **Pipeline**: `filesrc → qtdemux → h264parse → v4l2h264dec → fpsdisplaysink → waylandsink`
+- **Usage**: `./run.sh --test-type drc --clip-path /path/to/clips`
+- **Test Clip**: `1080_720_h264.mp4` (automatically downloaded or copied from --clip-path)
+- **Features**:
+ - Monitors FPS and resolution changes during playback
+ - Validates seamless resolution transitions
+
+### Test Type: Concurrent Decode
+
+**Concurrency Tests (PR43865, PR43866, FR98277)**
+
+These tests validate concurrent decode sessions using qtivcomposer for multi-stream composition.
+
+**1. H264_Decode_Concurrency_8x480p (PR43865)**
+- **Description**: 8 concurrent H.264 480p@30fps decode sessions
+- **Requirements**: qtivcomposer, Wayland display
+- **Test Clip**: `H264_480p_30fps.mp4`
+- **Pipeline (per session)**: `filesrc → qtdemux → h264parse → v4l2h264dec → qtivcomposer → waylandsink`
+
+**2. H265_Decode_Concurrency_8x480p (PR43866)**
+- **Description**: 8 concurrent H.265 480p decode sessions
+- **Requirements**: qtivcomposer, Wayland display
+- **Test Clip**: `H265_480p_30fps.mp4`
+- **Pipeline (per session)**: `filesrc → qtdemux → h265parse → v4l2h265dec → qtivcomposer → waylandsink`
+
+**3. MJPEG_Decode_Concurrency_2x1080p (FR98277)**
+- **Description**: 2 concurrent MJPEG 1080p decode sessions
+- **Requirements**: jpegdec (software decoder), qtivcomposer, Wayland display
+- **Test Clip**: `mjpeg1.avi`
+- **Pipeline (per session)**: `filesrc → avidemux → jpegdec → qtivcomposer → waylandsink`
+
+**Usage**:
+```bash
+# Run all 3 concurrency tests
+./run.sh --test-type concurrency --clip-path /path/to/clips
+```
+
+**Features**:
+- Fixed session counts: 8 for H.264/H.265, 2 for MJPEG
+- Single pipeline with qtivcomposer for multi-stream composition
+- Comprehensive error checking and validation
+
+### Test Type: Advanced Encoding (Downstream Only)
+
+**Advanced Encoding Tests (FR74943, FR82773, FR82771, FR72846)**
+
+These tests require downstream video driver (Config2) and Qualcomm camera source (qtiqmmfsrc).
+
+**1. HEVC_Encode_Smart_Bitrate_FPS_720p (FR74943)**
+- **Description**: HEVC 720p encode with smart FPS and bitrate adaptation using dual camera streams
+- **Requirements**: Downstream driver, qtiqmmfsrc, qtismartvencbin
+- **Pipeline**: `qtiqmmfsrc (dual streams: video + control) → qtismartvencbin (smart-fps=true, smart-bitrate=true, smart-gop=false) → h265parse → mp4mux → filesink`
+- **Features**:
+ - Adaptive FPS and bitrate control
+ - Dual camera streams (720p video + 480p control)
+ - Noise reduction (noise-reduction=2)
+ - Extra buffers (extra-buffers=20)
+ - Control sink for smart encoding decisions
+
+**2. HEVC_Encode_1080p_Cyclic_IR (FR82773)**
+- **Description**: HEVC 1080p (1920x1080) encode with Cyclic Intra Refresh and CBR mode
+- **Requirements**: Downstream driver, qtiqmmfsrc, v4l2h265enc
+- **Pipeline**: `qtiqmmfsrc → v4l2h265enc (intra_refresh_period_type=1, intra_refresh_period=20, CBR mode, comprehensive QP controls) → h265parse → mp4mux → filesink`
+- **Features**:
+ - Cyclic intra refresh mode for error resilience (period=20)
+ - Constant Bitrate (CBR) mode with 5 Mbps target
+ - Comprehensive QP (Quantization Parameter) controls for I/P/B frames
+ - GOP size control (29 frames)
+
+**3. H264_Encode_Slice_MB_VGA (FR82771)**
+- **Description**: H.264 VGA (640x480) encode with Slice MB mode
+- **Requirements**: Downstream driver, qtiqmmfsrc, v4l2h264enc
+- **Pipeline**: `qtiqmmfsrc → v4l2h264enc (slice_partitioning_method=1, number_of_mbs_in_a_slice=368) → h264parse → mp4mux → filesink`
+- **Features**: Macroblock-based slice partitioning
+
+**4. HEVC_Encode_1080p_Rotate90 (FR72846)**
+- **Description**: HEVC 1080p (1920x1080) encode with 90° VPU rotation and VBR mode
+- **Requirements**: qtiqmmfsrc, v4l2h265enc (rotate control requires downstream driver)
+- **Pipeline**: `qtiqmmfsrc → v4l2h265enc (rotate=90, VBR mode, comprehensive QP controls) → h265parse → mp4mux → filesink`
+- **Features**:
+ - Hardware-accelerated rotation during encoding
+ - Variable Bitrate (VBR) mode with 2.2 Mbps target
+ - Comprehensive QP (Quantization Parameter) controls for I/P/B frames
+ - GOP size control (29 frames)
+
+**Usage**:
+```bash
+# Run all 4 advanced encoding tests (requires downstream driver + camera)
+./run.sh --test-type advanced-encode --stack downstream --duration 30
+```
+
+**Note**: These tests will automatically skip if:
+- Downstream video driver is not detected
+- Camera source (qtiqmmfsrc) is not available
+- Required encoder elements are missing
+
+---
+
+## Test Clip Requirements
+
+### Automatic Download
+
+Test clips are automatically downloaded from the configured URL (default: GitHub releases) or copied from a local path if provided via `--clip-path`.
+
+### Required Clips by Test Type
+
+| Test Type | Clips Required | Auto-Download |
+|-----------|----------------|---------------|
+| basic | VP9_640x480_10s.webm | ✅ Yes |
+| uvc | None (uses camera) | N/A |
+| drc | 1080_720_h264.mp4 | ✅ Yes |
+| concurrency | H264_480p_30fps.mp4
H265_480p_30fps.mp4
mjpeg1.avi | ✅ Yes |
+| advanced-encode | None (uses camera) | N/A |
+| all (default) | All clips above | ✅ Yes |
+
+### Clip Download Behavior
+
+1. **Check if clip exists** in output directory
+2. **Try local path** if `--clip-path` is provided
+3. **Download from URL** if not found locally
+4. **Skip test** if download fails (offline mode)
+
+### Manual Clip Provision
+
+If you have clips locally, provide the path:
+
+```bash
+./run.sh --test-type concurrency --clip-path /path/to/clips
+```
+
+The script will look for clips in the specified directory and copy them to the output directory.
+
+---
+
+## Advanced Test Examples
+
+### 1) Run UVC camera preview for 60 seconds
+
+```bash
+./run.sh --test-type uvc --duration 60
+```
+
+### 2) Run DRC test with local clips
+
+```bash
+./run.sh --test-type drc --clip-path /mnt/test_clips
+```
+
+### 3) Run concurrency tests
+
+```bash
+./run.sh --test-type concurrency --clip-path /opt
+```
+
+### 4) Run all advanced encoding tests (downstream only)
+
+```bash
+./run.sh --test-type advanced-encode --stack downstream
+```
+
+### 5) Run basic tests with custom settings
+
+```bash
+./run.sh --test-type basic --codecs h264,h265 --resolutions 480p,1080p --duration 10
+```
+
+---
+
## PASS / FAIL / SKIP criteria
### PASS
@@ -167,6 +356,15 @@ Help:
### Options
+- `--test-type `
+ - Default: `basic` (standard encode/decode tests only - backward compatible)
+ - `basic`: Standard encode/decode tests only (H.264, H.265, VP9)
+ - `all`: Run ALL tests (basic + uvc + drc + concurrency + advanced-encode)
+ - `uvc`: UVC camera live preview test
+ - `drc`: Dynamic Resolution Change H.264 decode test
+ - `concurrency`: Concurrent decode tests (H.264, H.265, MJPEG)
+ - `advanced-encode`: Advanced encoding tests (downstream only)
+
- `--mode `
- Default: `all` (run both encode and decode tests)
- `encode`: Run only encoding tests
@@ -178,12 +376,22 @@ Help:
- Examples: `h264`, `h265`, `h264,h265`, `vp9`, `h264,vp9`
- Note: VP9 only supports decode (no encode)
-- `--resolutions <480p,4k>`
+- `--resolutions <480p,720p,1080p,4k>`
- Comma-separated list of resolutions to test
- - Default: `480p,4k`
+ - Default: `480p`
- Supported: `480p` (640x480), `720p` (1280x720), `1080p` (1920x1080), `4k` (3840x2160)
- Examples: `480p`, `4k`, `480p,1080p,4k`
+- `--clip-path `
+ - Local path to test video files
+ - Overrides --clip-url if files exist
+ - Example: `--clip-path /opt` or `--clip-path /mnt/test_clips`
+
+- `--clip-url `
+ - URL to download test video files
+ - Default: GitHub release URL
+ - Example: `--clip-url https://example.com/clips.tar.gz`
+
- `--duration `
- Duration for encoding (in seconds)
- Default: `30`
@@ -222,84 +430,122 @@ Help:
## Examples
-### 1) Run all tests (default - encode + decode for H.264/H.265/VP9 at 4K for 30 seconds)
+### 1) Run basic tests (default - backward compatible)
```bash
./run.sh
```
-**Note:** Default behavior runs H.264, H.265, and VP9 tests at 4K resolution with 30 second duration.
+**Note:** Default behavior (`--test-type basic`) runs only basic encode/decode tests:
+- H.264 encode/decode at 480p for 30 seconds
+- H.265 encode/decode at 480p for 30 seconds
+- VP9 decode at 480p (if clip available)
-### 2) Run only encoding tests
+This is backward compatible with existing LAVA jobs and nightly runs.
+
+### 2) Run ALL tests (basic + advanced)
```bash
-./run.sh --mode encode
+./run.sh --test-type all
```
-### 3) Run only decoding tests (requires encoded files from previous run)
+**Note:** `--test-type all` runs ALL 9+ tests:
+- Basic encode/decode tests (H.264, H.265, VP9)
+- UVC camera preview (if camera available)
+- DRC H.264 decode (if clip available)
+- Concurrency tests: 8x H.264, 8x H.265, 2x MJPEG (if clips available)
+- Advanced encode tests (if downstream stack + camera available)
+
+Tests that require missing hardware or clips will be gracefully skipped.
+
+### 3) Run only basic encode/decode tests (explicit)
```bash
-./run.sh --mode decode
+./run.sh --test-type basic
+```
+
+### 4) Run only UVC camera test
+
+```bash
+./run.sh --test-type uvc --duration 60
+```
+
+### 5) Run only concurrency tests
+
+```bash
+./run.sh --test-type concurrency --clip-path /opt
+```
+
+### 6) Run only advanced encoding tests (downstream only)
+
+```bash
+./run.sh --test-type advanced-encode --stack downstream
+```
+
+### 7) Run basic tests with specific codecs and resolutions
+
+```bash
+./run.sh --test-type basic --codecs h264,h265 --resolutions 480p,1080p
```
-### 4) Test only H.264 codec
+### 8) Run only encoding tests
```bash
-./run.sh --codecs h264
+./run.sh --mode encode
```
-### 5) Test only H.265 codec at 4K resolution
+### 9) Run only decoding tests (requires encoded files from previous run)
```bash
-./run.sh --codecs h265 --resolutions 4k
+./run.sh --mode decode
```
-### 6) Test all codecs at 480p only
+### 10) Test only H.264 codec
```bash
-./run.sh --resolutions 480p
+./run.sh --test-type basic --codecs h264
```
-### 7) Test with longer duration (10 seconds)
+### 11) Test with shorter duration (10 seconds)
```bash
./run.sh --duration 10
```
-### 8) Test with higher framerate (60fps)
+### 12) Test with higher framerate (60fps)
```bash
-./run.sh --framerate 60
+./run.sh --test-type basic --framerate 60
```
-### 9) Test multiple resolutions
+### 13) Test multiple resolutions
```bash
-./run.sh --resolutions 480p,720p,1080p,4k
+./run.sh --test-type basic --resolutions 480p,720p,1080p,4k
```
-### 10) Increase GStreamer debug verbosity
+### 14) Increase GStreamer debug verbosity
```bash
./run.sh --gst-debug 5
```
-### 11) Quick test - H.264 only at 480p with 3 second duration
+### 15) Quick test - H.264 only at 480p with 3 second duration
```bash
-./run.sh --codecs h264 --resolutions 480p --duration 3
+./run.sh --test-type basic --codecs h264 --resolutions 480p --duration 3
```
-### 12) Test VP9 decode only (requires network connectivity)
+### 16) Test VP9 decode only (requires network connectivity)
```bash
-./run.sh --codecs vp9 --mode decode
+./run.sh --test-type basic --codecs vp9 --mode decode
```
-### 13) Test all codecs including VP9
+### 17) Provide local test clips
```bash
-./run.sh --codecs h264,h265,vp9
+./run.sh --clip-path /opt
```
---
@@ -309,8 +555,8 @@ Help:
### Encoding Pipeline
```
-videotestsrc num-buffers= pattern=smpte
- ! video/x-raw,width=,height=,format=NV12,framerate=/1
+videotestsrc num-buffers= pattern=smpte
+ ! video/x-raw,width=,height=,format=NV12,framerate=/1
! v4l2h264enc extra-controls="controls,video_bitrate=" (or v4l2h265enc)
! h264parse (or h265parse)
! filesink location=
@@ -330,10 +576,10 @@ Where:
### Decoding Pipeline (H.264/H.265)
```
-filesrc location=
+filesrc location=
! h264parse (or h265parse)
! v4l2h264dec (or v4l2h265dec)
- ! videoconvert
+ ! videoconvert
! fakesink
```
@@ -344,10 +590,10 @@ Where:
### Decoding Pipeline (VP9)
```
-filesrc location=VP9_640x480_10s.webm
- ! matroskademux
- ! v4l2vp9dec
- ! videoconvert
+filesrc location=VP9_640x480_10s.webm
+ ! matroskademux
+ ! v4l2vp9dec
+ ! videoconvert
! fakesink
```
@@ -528,6 +774,105 @@ This test uses reusable helper functions from `lib_gstreamer.sh` that other GStr
gstreamer_run_gstlaunch_timeout 40 "$pipeline"
```
+#### 5. `gstreamer_build_uvc_preview_pipeline`
+
+Build pipeline for UVC camera live preview (used by UVC test).
+
+- **Parameters**:
+ - `device`: UVC device path (e.g., `/dev/video2`)
+ - `width`: Video width (default: 1920)
+ - `height`: Video height (default: 1080)
+ - `framerate`: Framerate (default: 5)
+- Returns: Complete pipeline string for UVC preview
+- Automatically includes `qtivtransform` if available
+- Example:
+ ```sh
+ uvc_dev=$(gstreamer_detect_uvc_camera)
+ pipeline=$(gstreamer_build_uvc_preview_pipeline "$uvc_dev" "1920" "1080" "5")
+ gstreamer_run_gstlaunch_timeout 30 "$pipeline"
+ ```
+
+#### 6. `gstreamer_build_drc_decode_pipeline`
+
+Build pipeline for Dynamic Resolution Change H.264 decode test.
+
+- **Parameters**:
+ - `clip_path`: Path to DRC test clip
+ - `video_stack`: `upstream` or `downstream`
+- Returns: Complete pipeline string with fpsdisplaysink wrapper
+- Example:
+ ```sh
+ pipeline=$(gstreamer_build_drc_decode_pipeline "/tmp/drc_clip.mp4" "downstream")
+ gstreamer_run_gstlaunch_timeout 60 "$pipeline"
+ ```
+
+#### 7. `gstreamer_build_concurrency_decode_pipeline`
+
+Build pipeline for concurrent decode tests with qtivcomposer and dynamic grid layouts.
+
+- **Parameters**:
+ - `codec`: `h264`, `h265`, or `mjpeg`
+ - `clip_path`: Path to test clip
+ - `video_stack`: `upstream` or `downstream`
+ - `session_count`: Number of concurrent sessions (must be `2` or `8`)
+- **Supported Session Layouts**:
+ - **8 sessions** (required for H.264/H.265): 4x2 grid (480x540 per tile on 1920x1080 display)
+ - **2 sessions** (required for MJPEG): 2x1 grid (960x1080 per tile on 1920x1080 display)
+- **Codec-Specific Requirements**:
+ - H.264/H.265: Must use exactly 8 sessions (enforced by validation)
+ - MJPEG: Must use exactly 2 sessions (enforced by validation)
+- Returns: Complete pipeline string with qtivcomposer grid layout, or empty on error
+- Automatically handles:
+ - Codec-specific decode chains (v4l2h264dec, v4l2h265dec, jpegdec)
+ - Stack-dependent decoder arguments (IO modes for downstream)
+ - Dynamic grid positioning based on session count
+- Example (H.264 with 8 sessions):
+ ```sh
+ pipeline=$(gstreamer_build_concurrency_decode_pipeline \
+ "h264" "/tmp/test.mp4" "downstream" "8")
+ gstreamer_run_gstlaunch_timeout 40 "$pipeline"
+ ```
+- Example (MJPEG with 2 sessions):
+ ```sh
+ pipeline=$(gstreamer_build_concurrency_decode_pipeline \
+ "mjpeg" "/tmp/test.avi" "upstream" "2")
+ gstreamer_run_gstlaunch_timeout 40 "$pipeline"
+ ```
+
+#### 8. `gstreamer_build_smart_encode_pipeline`
+
+Build pipeline for HEVC Smart Encode with dual camera streams.
+
+- **Parameters**:
+ - `output_file`: Output file path
+- Returns: Complete pipeline string with qtismartvencbin
+- Requires: qtiqmmfsrc, qtismartvencbin (downstream only)
+- Example:
+ ```sh
+ pipeline=$(gstreamer_build_smart_encode_pipeline "/tmp/output.mp4")
+ gstreamer_run_gstlaunch_timeout 40 "$pipeline"
+ ```
+
+#### 9. `gstreamer_build_camera_encode_pipeline`
+
+Build pipeline for camera-based encoding with custom controls.
+
+- **Parameters**:
+ - `codec`: `h264` or `h265`
+ - `width`: Video width
+ - `height`: Video height
+ - `output_file`: Output file path
+ - `extra_controls`: Custom encoder controls string (optional)
+ - `video_stack`: `upstream` or `downstream`
+- Returns: Complete pipeline string with qtiqmmfsrc
+- Example:
+ ```sh
+ controls="controls,video_bitrate=8000000,intra_refresh_period_type=1"
+ pipeline=$(gstreamer_build_camera_encode_pipeline \
+ "h265" "1920" "1080" "/tmp/output.mp4" "$controls" "downstream")
+ gstreamer_run_gstlaunch_timeout 40 "$pipeline"
+ ```
+
### Usage in Other Tests
To use these functions in your GStreamer test:
@@ -582,16 +927,18 @@ This will output example pipelines for various codecs, resolutions, and video st
The test supports these environment variables (can be set in LAVA job definition):
+- `VIDEO_TEST_TYPE` - Test type (all/basic/uvc/drc/concurrency/advanced-encode) (default: all)
- `VIDEO_TEST_MODE` - Test mode (all/encode/decode) (default: all)
- `VIDEO_CODECS` - Comma-separated codec list (default: `h264,h265,vp9`)
-- `VIDEO_RESOLUTIONS` - Comma-separated resolution list (default: `4k`)
+- `VIDEO_RESOLUTIONS` - Comma-separated resolution list (default: `480p`)
- `VIDEO_DURATION` - Encoding duration in seconds (default: 30)
- `RUNTIMESEC` - Alternative to VIDEO_DURATION
- `VIDEO_FRAMERATE` - Video framerate (default: 30)
- `VIDEO_STACK` - Video stack selection (auto/upstream/downstream) (default: auto)
- `VIDEO_GST_DEBUG` - GStreamer debug level (default: 2)
- `GST_DEBUG_LEVEL` - Alternative to VIDEO_GST_DEBUG
-- `VIDEO_CLIP_URL` - URL for VP9 clip download (default: GitHub releases)
+- `VIDEO_CLIP_URL` - URL for test clip download (default: GitHub releases)
+- `VIDEO_CLIP_PATH` - Local path to test clips (overrides VIDEO_CLIP_URL)
- `LAVA_TESTCASE_ID` - Override test case name for LAVA reporting (default: Video_Encode_Decode)
**Priority order for duration**: `VIDEO_DURATION` > `RUNTIMESEC` > default (30)
diff --git a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml
index e84984c99..a091a3182 100644
--- a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml
+++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/Video_Encode_Decode.yaml
@@ -11,6 +11,7 @@ metadata:
params:
VIDEO_TEST_MODE: "all"
+ VIDEO_TEST_TYPE: "basic"
VIDEO_CODECS: "h264,h265,vp9"
VIDEO_RESOLUTIONS: "480p"
VIDEO_DURATION: "30"
@@ -26,7 +27,7 @@ run:
- REPO_PATH="$PWD"
- cd Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/
- VIDEO_SHARED_ENCODE_DIR="${REPO_PATH%%/tests/*}/shared/video-encode-decode"
- - export VIDEO_TEST_MODE VIDEO_CODECS VIDEO_RESOLUTIONS VIDEO_DURATION VIDEO_FRAMERATE
+ - export VIDEO_TEST_MODE VIDEO_TEST_TYPE VIDEO_CODECS VIDEO_RESOLUTIONS VIDEO_DURATION VIDEO_FRAMERATE
- export VIDEO_STACK VIDEO_GST_DEBUG VIDEO_CLIP_URL VIDEO_CLIP_PATH VIDEO_SHARED_ENCODE_DIR
- - ./run.sh --mode "${VIDEO_TEST_MODE}" --codecs "${VIDEO_CODECS}" --resolutions "${VIDEO_RESOLUTIONS}" --duration "${VIDEO_DURATION}" --framerate "${VIDEO_FRAMERATE}" --stack "${VIDEO_STACK}" --gst-debug "${VIDEO_GST_DEBUG}" --clip-url "${VIDEO_CLIP_URL}" --clip-path "${VIDEO_CLIP_PATH}" --lava-testcase-id "${LAVA_TESTCASE_ID}" || true
+ - ./run.sh --mode "${VIDEO_TEST_MODE}" --test-type "${VIDEO_TEST_TYPE}" --codecs "${VIDEO_CODECS}" --resolutions "${VIDEO_RESOLUTIONS}" --duration "${VIDEO_DURATION}" --framerate "${VIDEO_FRAMERATE}" --stack "${VIDEO_STACK}" --gst-debug "${VIDEO_GST_DEBUG}" --clip-url "${VIDEO_CLIP_URL}" --clip-path "${VIDEO_CLIP_PATH}" --lava-testcase-id "${LAVA_TESTCASE_ID}" || true
- $REPO_PATH/Runner/utils/send-to-lava.sh Video_Encode_Decode.res
diff --git a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh
index a0e556ee8..af334a6de 100755
--- a/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh
+++ b/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/run.sh
@@ -51,6 +51,9 @@ fi
# shellcheck disable=SC1091
[ -f "$TOOLS/lib_video.sh" ] && . "$TOOLS/lib_video.sh"
+# shellcheck disable=SC1091
+[ -f "$TOOLS/lib_display.sh" ] && . "$TOOLS/lib_display.sh"
+
# Use the shared encoded directory if supported; otherwise default to $OUTDIR/encoded.
if command -v gstreamer_shared_encoded_dir >/dev/null 2>&1; then
ENCODED_DIR="$(gstreamer_shared_encoded_dir "$SCRIPT_DIR" "$OUTDIR")"
@@ -79,6 +82,7 @@ total_tests=0
# -------------------- Defaults (LAVA env vars -> defaults; CLI overrides) --------------------
testMode="${VIDEO_TEST_MODE:-all}"
+testType="${VIDEO_TEST_TYPE:-basic}"
codecList="${VIDEO_CODECS:-h264,h265,vp9}"
resolutionList="${VIDEO_RESOLUTIONS:-480p}"
duration="${VIDEO_DURATION:-${RUNTIMESEC:-30}}"
@@ -141,6 +145,16 @@ while [ $# -gt 0 ]; do
shift 2
;;
+ --test-type)
+ if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then
+ log_warn "Missing/invalid value for --test-type"
+ echo "$RESULT_TESTNAME SKIP" >"$RES_FILE"
+ exit 0
+ fi
+ [ -n "$2" ] && testType="$2"
+ shift 2
+ ;;
+
--codecs)
if [ $# -lt 2 ] || [ "${2#--}" != "$2" ]; then
log_warn "Missing/invalid value for --codecs"
@@ -275,6 +289,22 @@ OPTIONS:
- encode: Run only encoding tests
- decode: Run only decoding tests
+ --test-type
+ Test type (default: basic)
+ - basic: Standard encode/decode tests only (H.264, H.265, VP9)
+ - all: Run ALL tests (basic + uvc + drc + concurrency + advanced-encode)
+ - uvc: UVC camera live preview at 1080p@5fps (PR76474)
+ - drc: Dynamic Resolution Change H.264 decode (FR82787)
+ - concurrency: Concurrent decode tests (PR43865, PR43866, FR98277)
+ * 8x H.264 480p decode sessions
+ * 8x H.265 480p decode sessions
+ * 2x MJPEG 1080p decode sessions
+ - advanced-encode: Advanced encoding tests (downstream only)
+ * HEVC Smart encode 720p (FR74943)
+ * HEVC 1080p Cyclic IR (FR82773)
+ * H.264 VGA Slice MB (FR82771)
+ * HEVC 1080p Rotate 90° (FR72846)
+
--codecs
Comma-separated list of codecs to test
(default: h264,h265,vp9)
@@ -303,6 +333,7 @@ OPTIONS:
--clip-path Local path to test video files
(overrides --clip-url if files exist)
+ Example: --clip-path /opt
--lava-testcase-id
Override the test case name reported to LAVA
@@ -311,7 +342,37 @@ OPTIONS:
-h, --help Display this help message
+FILE LOCATIONS:
+ Test clips and logs are stored in:
+ $SCRIPT_DIR/logs/Video_Encode_Decode/
+
+ On device, this typically resolves to:
+ /var/Runner/suites/Multimedia/GSTreamer/Video/Video_Encode_Decode/logs/Video_Encode_Decode/
+
+ Downloaded/copied clips:
+ - VP9_640x480_10s.webm
+ - H264_480p_30fps.mp4
+ - H265_480p_30fps.mp4
+ - mjpeg1.avi
+ - 1080_720_h264.mp4
+
+ Encoded files:
+ logs/Video_Encode_Decode/encoded/
+ - encode_h264_480p.mp4
+ - encode_h265_480p.mp4
+ - (etc.)
+
+ Log files:
+ - gst.log (GStreamer debug output)
+ - encode_*.log (individual test logs)
+ - decode_*.log
+ - UVC_*.log
+ - DRC_*.log
+ - H264_Decode_Concurrency_*.log
+ - (etc.)
+
ENVIRONMENT VARIABLES:
+ VIDEO_TEST_TYPE Same as --test-type
VIDEO_TEST_MODE Same as --mode
VIDEO_CODECS Same as --codecs
VIDEO_RESOLUTIONS Same as --resolutions
@@ -325,9 +386,12 @@ ENVIRONMENT VARIABLES:
RUNTIMESEC Alternative to VIDEO_DURATION
EXAMPLES:
- # Run all tests with default settings
+ # Run basic tests with default settings (backward compatible)
$0
+ # Run ALL tests (basic + advanced)
+ $0 --test-type all
+
# Run only encoding tests for H.264 at 720p
$0 --mode encode --codecs h264 --resolutions 720p
@@ -361,6 +425,13 @@ case "$testMode" in all|encode|decode) : ;; *)
;;
esac
+case "$testType" in basic|uvc|drc|concurrency|advanced-encode|all) : ;; *)
+ log_warn "Invalid --test-type '$testType' (allowed: basic, uvc, drc, concurrency, advanced-encode, all)"
+ echo "$RESULT_TESTNAME SKIP" >"$RES_FILE"
+ exit 0
+ ;;
+esac
+
case "$gstDebugLevel" in 1|2|3|4|5|6|7|8|9) : ;; *)
log_warn "Invalid --gst-debug '$gstDebugLevel' (allowed: 1-9)"
echo "$RESULT_TESTNAME SKIP" >"$RES_FILE"
@@ -597,111 +668,964 @@ run_decode_test() {
fi
}
-# -------------------- Main test execution --------------------
-log_info "Starting video encode/decode tests..."
-
-# Parse codec list
-codecs=$(printf '%s' "$codecList" | tr ',' ' ')
-
-# Parse resolution list
-resolutions=$(printf '%s' "$resolutionList" | tr ',' ' ')
-
-# -------------------- VP9 clip prep --------------------
-need_vp9_clip=0
-for codec in $codecs; do
- if [ "$codec" = "vp9" ]; then
- need_vp9_clip=1
- break
+# -------------------- Helper: Check display connection --------------------
+check_display_connected() {
+ # Check if a physical display is connected
+ # Returns 0 if display is confirmed connected, 1 otherwise
+ if ! command -v display_connected_summary >/dev/null 2>&1; then
+ log_warn "display_connected_summary not available, cannot verify display connection"
+ return 1
fi
-done
+
+ display_status=$(display_connected_summary)
+ if [ "$display_status" = "none" ] || [ -z "$display_status" ]; then
+ log_warn "No display connected (status: ${display_status:-empty})"
+ return 1
+ fi
+
+ log_info "Display connected: $display_status"
+ return 0
+}
-if [ "$need_vp9_clip" -eq 1 ] && [ "$testMode" != "encode" ]; then
+# -------------------- UVC Live Preview Test --------------------
+# Test: UVC_Live_Preview_1080p (PR76474)
+run_uvc_preview_test() {
+ testname="UVC_Live_Preview_1080p"
log_info "=========================================="
- log_info "VP9 CLIP PREP"
+ log_info "Running: $testname (PR76474)"
log_info "=========================================="
- vp9_clip_webm="$OUTDIR/VP9_640x480_10s.webm"
+ total_tests=$((total_tests + 1))
+
+ # Check for required elements first (fast checks)
+ if ! has_element v4l2src; then
+ log_skip "$testname: v4l2src not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element waylandsink; then
+ log_skip "$testname: waylandsink not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Detect UVC camera
+ uvc_dev=$(gstreamer_detect_uvc_camera)
+ if [ -z "$uvc_dev" ]; then
+ log_skip "$testname: No UVC camera detected"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ log_info "UVC camera detected: $uvc_dev"
+
+ # Check if display is connected (required for waylandsink)
+ if ! check_display_connected; then
+ log_skip "$testname: No display connected (required for waylandsink)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Set up Wayland display using shared function
+ if ! camera_setup_wayland_environment "uvc-preview"; then
+ log_skip "$testname: Failed to set up Wayland environment"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ : >"$test_log"
+
+ # Build pipeline using library function
+ pipeline=$(gstreamer_build_uvc_preview_pipeline "$uvc_dev" "1920" "1080" "5")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: $pipeline"
- # Check if WebM file already exists
- if [ -f "$vp9_clip_webm" ]; then
- log_info "VP9 WebM clip already exists: $vp9_clip_webm"
+ # Use run_pipeline_with_logs with extended validation
+ if run_pipeline_with_logs "$testname" "$pipeline" "$OUTDIR" "$duration" "uvc" "1"; then
+ pass_count=$((pass_count + 1))
+ return 0
else
- # Try to get WebM file from provided path or URL
- if [ -n "$clipPath" ]; then
- log_info "Attempting to get VP9 WebM clip from local path: $clipPath"
- if [ -f "$clipPath/VP9_640x480_10s.webm" ]; then
- cp "$clipPath/VP9_640x480_10s.webm" "$vp9_clip_webm"
- log_info "VP9 WebM clip copied from local path"
- else
- log_warn "VP9 WebM clip not found in local path: $clipPath"
- fi
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
+
+# -------------------- DRC H.264 Decode Test --------------------
+# Test: DRC_H264_Decode_1080p_720p (FR82787)
+run_drc_decode_test() {
+ testname="DRC_H264_Decode_1080p_720p"
+ log_info "=========================================="
+ log_info "Running: $testname (FR82787)"
+ log_info "=========================================="
+
+ total_tests=$((total_tests + 1))
+
+ # Check for required elements first (fast checks)
+ if ! has_element v4l2h264dec; then
+ log_skip "$testname: v4l2h264dec not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element fpsdisplaysink; then
+ log_skip "$testname: fpsdisplaysink not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element waylandsink; then
+ log_skip "$testname: waylandsink not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for DRC test clip
+ drc_clip="$OUTDIR/1080_720_h264.mp4"
+ if [ ! -f "$drc_clip" ]; then
+ if [ -n "$clipPath" ] && [ -f "$clipPath/1080_720_h264.mp4" ]; then
+ cp "$clipPath/1080_720_h264.mp4" "$drc_clip"
+ else
+ log_skip "$testname: DRC test clip not found (1080_720_h264.mp4)"
+ skip_count=$((skip_count + 1))
+ return 1
fi
+ fi
+
+ # Check if display is connected (required for waylandsink)
+ if ! check_display_connected; then
+ log_skip "$testname: No display connected (required for waylandsink)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Set up Wayland display using shared function
+ if ! camera_setup_wayland_environment "drc-test"; then
+ log_skip "$testname: Failed to set up Wayland environment"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ : >"$test_log"
+
+ # Build pipeline using library function
+ pipeline=$(gstreamer_build_drc_decode_pipeline "$drc_clip" "$detected_stack")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: $pipeline"
+
+ # Use run_pipeline_with_logs with extended validation
+ if run_pipeline_with_logs "$testname" "$pipeline" "$OUTDIR" "$((duration + 10))" "drc" "1"; then
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
- # If not found locally, try URL download
- if [ ! -f "$vp9_clip_webm" ]; then
- log_info "VP9 WebM clip not found locally; attempting download from URL..."
- if extract_tar_from_url "$clipUrl" "$OUTDIR"; then
- # Move the extracted file from current directory to OUTDIR
- if [ -f "VP9_640x480_10s.webm" ]; then
- mv "VP9_640x480_10s.webm" "$vp9_clip_webm"
- log_pass "VP9 WebM clip downloaded and moved successfully"
- else
- log_warn "VP9 WebM clip not found in downloaded content"
- fi
- else
- log_warn "VP9 WebM clip download failed (offline or URL issue)"
- fi
+# -------------------- H.264 Concurrency Decode Test --------------------
+# Test: H264_Decode_Concurrency_8x480p (PR43865)
+run_h264_concurrency_test() {
+ testname="H264_Decode_Concurrency_8x480p"
+ log_info "=========================================="
+ log_info "Running: $testname (PR43865)"
+ log_info "=========================================="
+
+ total_tests=$((total_tests + 1))
+
+ # Check for required elements first (fast checks)
+ if ! has_element v4l2h264dec; then
+ log_skip "$testname: v4l2h264dec not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element qtivcomposer; then
+ log_skip "$testname: qtivcomposer not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element waylandsink; then
+ log_skip "$testname: waylandsink not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for test clip
+ h264_clip="$OUTDIR/H264_480p_30fps.mp4"
+ if [ ! -f "$h264_clip" ]; then
+ if [ -n "$clipPath" ] && [ -f "$clipPath/H264_480p_30fps.mp4" ]; then
+ cp "$clipPath/H264_480p_30fps.mp4" "$h264_clip"
+ else
+ log_skip "$testname: Test clip not found (H264_480p_30fps.mp4)"
+ skip_count=$((skip_count + 1))
+ return 1
fi
fi
-fi
+
+ # Set up Wayland display using shared function
+ if ! camera_setup_wayland_environment "h264-concurrency"; then
+ log_skip "$testname: Failed to set up Wayland environment"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ : >"$test_log"
+
+ # Increase file descriptor limit
+ ulimit -n 4096 2>/dev/null || true
+
+ # Build pipeline using library function (8 sessions for H.264)
+ pipeline=$(gstreamer_build_concurrency_decode_pipeline "h264" "$h264_clip" "$detected_stack" "8")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: gst-launch-1.0 -e $pipeline"
+
+ # Use run_pipeline_with_logs with extended validation (8 sessions fixed)
+ if run_pipeline_with_logs "$testname" "$pipeline" "$OUTDIR" "$((duration + 10))" "concurrency-h264" "8"; then
+ log_pass "$testname: PASS (8 concurrent sessions)"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
-# Run encode tests (skip VP9 as it doesn't support encoding in this test)
-if [ "$testMode" = "all" ] || [ "$testMode" = "encode" ]; then
+# -------------------- H.265 Concurrency Decode Test --------------------
+# Test: H265_Decode_Concurrency_8x480p (PR43866)
+run_h265_concurrency_test() {
+ testname="H265_Decode_Concurrency_8x480p"
log_info "=========================================="
- log_info "ENCODE TESTS"
+ log_info "Running: $testname (PR43866)"
log_info "=========================================="
- for codec in $codecs; do
- # Skip VP9 for encode tests (no v4l2vp9enc support in this test)
- if [ "$codec" = "vp9" ]; then
- log_info "Skipping VP9 encode (not supported)"
- continue
+ total_tests=$((total_tests + 1))
+
+ # Check for required elements first (fast checks)
+ if ! has_element v4l2h265dec; then
+ log_skip "$testname: v4l2h265dec not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element qtivcomposer; then
+ log_skip "$testname: qtivcomposer not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element waylandsink; then
+ log_skip "$testname: waylandsink not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for test clip
+ h265_clip="$OUTDIR/H265_480p_30fps.mp4"
+ if [ ! -f "$h265_clip" ]; then
+ if [ -n "$clipPath" ] && [ -f "$clipPath/H265_480p_30fps.mp4" ]; then
+ cp "$clipPath/H265_480p_30fps.mp4" "$h265_clip"
+ else
+ log_skip "$testname: Test clip not found (H265_480p_30fps.mp4)"
+ skip_count=$((skip_count + 1))
+ return 1
fi
-
- for res in $resolutions; do
- params=$(gstreamer_resolution_to_wh "$res")
-
- # ---------------- FIX: robust split independent of IFS ----------------
- width=$(printf '%s\n' "$params" | awk '{print $1}')
- height=$(printf '%s\n' "$params" | awk '{print $2}')
- case "$width" in ''|*[!0-9]*) width="640" ;; esac
- case "$height" in ''|*[!0-9]*) height="480" ;; esac
- # ---------------------------------------------------------------------
-
- total_tests=$((total_tests + 1))
- run_encode_test "$codec" "$res" "$width" "$height" || true
- done
- done
-fi
+ fi
+
+ # Check if display is connected (required for waylandsink)
+ if ! check_display_connected; then
+ log_skip "$testname: No display connected (required for waylandsink)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Set up Wayland display using shared function
+ if ! camera_setup_wayland_environment "h265-concurrency"; then
+ log_skip "$testname: Failed to set up Wayland environment"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ : >"$test_log"
+
+ # Increase file descriptor limit
+ ulimit -n 4096 2>/dev/null || true
+
+ # Build pipeline using library function (8 sessions for H.265)
+ pipeline=$(gstreamer_build_concurrency_decode_pipeline "h265" "$h265_clip" "$detected_stack" "8")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: gst-launch-1.0 -e $pipeline"
+
+ # Use run_pipeline_with_logs with extended validation (8 sessions fixed)
+ if run_pipeline_with_logs "$testname" "$pipeline" "$OUTDIR" "$((duration + 10))" "concurrency-h265" "8"; then
+ log_pass "$testname: PASS (8 concurrent sessions)"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
-# Run decode tests
-if [ "$testMode" = "all" ] || [ "$testMode" = "decode" ]; then
+# -------------------- MJPEG Concurrency Decode Test --------------------
+# Test: MJPEG_Decode_Concurrency_2x1080p (FR98277)
+run_mjpeg_concurrency_test() {
+ testname="MJPEG_Decode_Concurrency_2x1080p"
log_info "=========================================="
- log_info "DECODE TESTS"
+ log_info "Running: $testname (FR98277)"
log_info "=========================================="
- for codec in $codecs; do
- if [ "$codec" = "vp9" ]; then
- total_tests=$((total_tests + 1))
- run_decode_test "$codec" "480p" || true
+ total_tests=$((total_tests + 1))
+
+ # Check for required elements first (fast checks)
+ if ! has_element jpegdec; then
+ log_skip "$testname: jpegdec not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element qtivcomposer; then
+ log_skip "$testname: qtivcomposer not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element waylandsink; then
+ log_skip "$testname: waylandsink not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for test clip
+ mjpeg_clip="$OUTDIR/mjpeg1.avi"
+ if [ ! -f "$mjpeg_clip" ]; then
+ if [ -n "$clipPath" ] && [ -f "$clipPath/mjpeg1.avi" ]; then
+ cp "$clipPath/mjpeg1.avi" "$mjpeg_clip"
else
- for res in $resolutions; do
- total_tests=$((total_tests + 1))
- run_decode_test "$codec" "$res" || true
- done
+ log_skip "$testname: Test clip not found (mjpeg1.avi)"
+ skip_count=$((skip_count + 1))
+ return 1
fi
- done
-fi
+ fi
+
+ # Check if display is connected (required for waylandsink)
+ if ! check_display_connected; then
+ log_skip "$testname: No display connected (required for waylandsink)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Set up Wayland display using shared function
+ if ! camera_setup_wayland_environment "mjpeg-concurrency"; then
+ log_skip "$testname: Failed to set up Wayland environment"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ : >"$test_log"
+
+ # Build pipeline using library function (2 sessions for MJPEG)
+ pipeline=$(gstreamer_build_concurrency_decode_pipeline "mjpeg" "$mjpeg_clip" "$detected_stack" "2")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: gst-launch-1.0 -e $pipeline"
+
+ # Use run_pipeline_with_logs with extended validation (2 sessions)
+ if run_pipeline_with_logs "$testname" "$pipeline" "$OUTDIR" "$((duration + 10))" "concurrency-mjpeg" "2"; then
+ log_pass "$testname: PASS (2 concurrent sessions)"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
+
+# -------------------- HEVC Smart Encode Test --------------------
+# Test: HEVC_Encode_Smart_Bitrate_FPS_720p (FR74943) - Downstream only
+run_smart_encode_test() {
+ testname="HEVC_Encode_Smart_Bitrate_FPS_720p"
+ log_info "=========================================="
+ log_info "Running: $testname (FR74943)"
+ log_info "=========================================="
+
+ total_tests=$((total_tests + 1))
+
+ # Check if downstream stack
+ if ! gstreamer_is_downstream_stack; then
+ log_skip "$testname: Requires downstream video driver (Config2)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for required elements
+ if ! has_element qtiqmmfsrc; then
+ log_skip "$testname: qtiqmmfsrc not available (camera source required)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element qtismartvencbin; then
+ log_skip "$testname: qtismartvencbin not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ output_file="$ENCODED_DIR/${testname}.mp4"
+
+ # Remove output file before encoding to ensure fresh file
+ rm -f "$output_file"
+ : >"$test_log"
+
+ # Build pipeline using shared helper function
+ pipeline=$(gstreamer_build_smart_encode_pipeline "$output_file")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: $pipeline"
+
+ # Run encoding
+ if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$pipeline" >>"$test_log" 2>&1; then
+ gstRc=0
+ else
+ gstRc=$?
+ fi
+
+ log_info "Exit code: $gstRc"
+
+ # Validate log
+ if ! gstreamer_validate_log "$test_log" "$testname"; then
+ log_fail "$testname: FAIL (GStreamer errors detected)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ # Comprehensive validation using shared helper
+ if gstreamer_validate_encode_output "$testname" "$output_file" "$gstRc" "h265" "1280" "720" "$duration"; then
+ log_pass "$testname: PASS"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
+
+# -------------------- HEVC Cyclic IR Encode Test --------------------
+# Test: HEVC_Encode_1080p_Cyclic_IR (FR82773) - Downstream only
+run_cyclic_ir_encode_test() {
+ testname="HEVC_Encode_1080p_Cyclic_IR"
+ log_info "=========================================="
+ log_info "Running: $testname (FR82773)"
+ log_info "=========================================="
+
+ total_tests=$((total_tests + 1))
+
+ # Check if downstream stack
+ if ! gstreamer_is_downstream_stack; then
+ log_skip "$testname: Requires downstream video driver (Config2)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for required elements
+ if ! has_element qtiqmmfsrc; then
+ log_skip "$testname: qtiqmmfsrc not available (camera source required)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element v4l2h265enc; then
+ log_skip "$testname: v4l2h265enc not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ output_file="$ENCODED_DIR/${testname}.mp4"
+
+ # Remove output file before encoding to ensure fresh file
+ rm -f "$output_file"
+ : >"$test_log"
+
+ # Build pipeline using shared helper function with cyclic intra refresh controls
+ # 1080p (1920x1080) with CBR mode, intra refresh period=20, and comprehensive QP controls
+ pipeline=$(gstreamer_build_camera_encode_pipeline \
+ "h265" "1920" "1080" "$output_file" \
+ "controls,intra_refresh_period_type=1,intra_refresh_period=20,video_bitrate_mode=1,video_bitrate=5000000,hevc_minimum_qp_value=10,hevc_maximum_qp_value=51,hevc_i_frame_qp_value=27,hevc_b_frame_qp_value=28,hevc_p_frame_qp_value=28,hevc_i_frame_minimum_qp_value=10,hevc_i_frame_maximum_qp_value=51,hevc_p_frame_minimum_qp_value=10,hevc_p_frame_maximum_qp_value=51,hevc_b_frame_minimum_qp_value=10,hevc_b_frame_maximum_qp_value=51,video_gop_size=29" \
+ "$detected_stack")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: $pipeline"
+
+ # Run encoding
+ if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$pipeline" >>"$test_log" 2>&1; then
+ gstRc=0
+ else
+ gstRc=$?
+ fi
+
+ log_info "Exit code: $gstRc"
+
+ # Validate log
+ if ! gstreamer_validate_log "$test_log" "$testname"; then
+ log_fail "$testname: FAIL (GStreamer errors detected)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ # Comprehensive validation using shared helper (validates output as 1920x1080)
+ if gstreamer_validate_encode_output "$testname" "$output_file" "$gstRc" "h265" "1920" "1080" "$duration"; then
+ log_pass "$testname: PASS"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
+
+# -------------------- H.264 Slice MB Encode Test --------------------
+# Test: H264_Encode_Slice_MB_VGA (FR82771) - Downstream only
+run_slice_mb_encode_test() {
+ testname="H264_Encode_Slice_MB_VGA"
+ log_info "=========================================="
+ log_info "Running: $testname (FR82771)"
+ log_info "=========================================="
+
+ total_tests=$((total_tests + 1))
+
+ # Check if downstream stack
+ if ! gstreamer_is_downstream_stack; then
+ log_skip "$testname: Requires downstream video driver (Config2)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Check for required elements
+ if ! has_element qtiqmmfsrc; then
+ log_skip "$testname: qtiqmmfsrc not available (camera source required)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element v4l2h264enc; then
+ log_skip "$testname: v4l2h264enc not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ output_file="$ENCODED_DIR/${testname}.mp4"
+
+ # Remove output file before encoding to ensure fresh file
+ rm -f "$output_file"
+ : >"$test_log"
+
+ # Build pipeline using shared helper function with slice MB controls
+ pipeline=$(gstreamer_build_camera_encode_pipeline \
+ "h264" "640" "480" "$output_file" \
+ "controls,video_bitrate=1000000,slice_partitioning_method=1,number_of_mbs_in_a_slice=368" \
+ "$detected_stack")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: $pipeline"
+
+ # Run encoding
+ if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$pipeline" >>"$test_log" 2>&1; then
+ gstRc=0
+ else
+ gstRc=$?
+ fi
+
+ log_info "Exit code: $gstRc"
+
+ # Validate log
+ if ! gstreamer_validate_log "$test_log" "$testname"; then
+ log_fail "$testname: FAIL (GStreamer errors detected)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ # Comprehensive validation using shared helper
+ if gstreamer_validate_encode_output "$testname" "$output_file" "$gstRc" "h264" "640" "480" "$duration"; then
+ log_pass "$testname: PASS"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
+
+# -------------------- HEVC 1080p Rotate90 Encode Test --------------------
+# Test: HEVC_Encode_1080p_Rotate90 (FR72846)
+run_rotate_encode_test() {
+ testname="HEVC_Encode_1080p_Rotate90"
+ log_info "=========================================="
+ log_info "Running: $testname (FR72846)"
+ log_info "=========================================="
+
+ total_tests=$((total_tests + 1))
+
+ # Check for required elements
+ if ! has_element qtiqmmfsrc; then
+ log_skip "$testname: qtiqmmfsrc not available (camera source required)"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ if ! has_element v4l2h265enc; then
+ log_skip "$testname: v4l2h265enc not available"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ # Rotation control requires downstream stack
+ if [ "$detected_stack" != "downstream" ]; then
+ log_skip "$testname: rotate=90 control is unavailable on this stack"
+ skip_count=$((skip_count + 1))
+ return 1
+ fi
+
+ test_log="$OUTDIR/${testname}.log"
+ output_file="$ENCODED_DIR/${testname}.mp4"
+
+ # Remove output file before encoding to ensure fresh file
+ rm -f "$output_file"
+ : >"$test_log"
+
+ # Build pipeline using shared helper function with rotation control and extended QP parameters
+ # Updated to 1080p (1920x1080) with VBR mode and comprehensive QP controls
+ pipeline=$(gstreamer_build_camera_encode_pipeline \
+ "h265" "1920" "1080" "$output_file" \
+ "controls,rotate=90,video_bitrate_mode=0,video_bitrate=2200000,hevc_minimum_qp_value=10,hevc_maximum_qp_value=51,hevc_i_frame_qp_value=27,hevc_b_frame_qp_value=28,hevc_p_frame_qp_value=28,hevc_i_frame_minimum_qp_value=10,hevc_i_frame_maximum_qp_value=51,hevc_p_frame_minimum_qp_value=10,hevc_p_frame_maximum_qp_value=51,hevc_b_frame_minimum_qp_value=10,hevc_b_frame_maximum_qp_value=51,video_gop_size=29" \
+ "$detected_stack")
+
+ if [ -z "$pipeline" ]; then
+ log_fail "$testname: FAIL (could not build pipeline)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ log_info "Pipeline: $pipeline"
+
+ # Run encoding
+ if gstreamer_run_gstlaunch_timeout "$((duration + 10))" "$pipeline" >>"$test_log" 2>&1; then
+ gstRc=0
+ else
+ gstRc=$?
+ fi
+
+ log_info "Exit code: $gstRc"
+
+ # Validate log
+ if ! gstreamer_validate_log "$test_log" "$testname"; then
+ log_fail "$testname: FAIL (GStreamer errors detected)"
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ # Verify rotation using shared helper (90° rotation should swap dimensions: 1920x1080 → 1080x1920)
+ # This validates BOTH rotation AND output quality (codec, container, duration, etc.)
+ if ! gstreamer_verify_rotation "$testname" "$output_file" "1920" "1080" "90"; then
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+
+ # Comprehensive validation using shared helper with ROTATED dimensions (1080x1920)
+ if gstreamer_validate_encode_output "$testname" "$output_file" "$gstRc" "h265" "1080" "1920" "$duration"; then
+ log_pass "$testname: PASS (with rotation verified)"
+ pass_count=$((pass_count + 1))
+ return 0
+ else
+ fail_count=$((fail_count + 1))
+ return 1
+ fi
+}
+
+# -------------------- Parse codec and resolution lists --------------------
+codecs=$(printf '%s' "$codecList" | tr ',' ' ')
+resolutions=$(printf '%s' "$resolutionList" | tr ',' ' ')
+
+# -------------------- Helper: Get test clip --------------------
+get_test_clip() {
+ clip_name="$1"
+ dest_path="$2"
+
+ # Check if clip already exists
+ if [ -f "$dest_path" ]; then
+ log_info "Clip already exists: $clip_name"
+ return 0
+ fi
+
+ # Try to get from provided local path first
+ if [ -n "$clipPath" ] && [ -f "$clipPath/$clip_name" ]; then
+ log_info "Copying $clip_name from local path: $clipPath"
+ if cp "$clipPath/$clip_name" "$dest_path"; then
+ log_info "Successfully copied $clip_name"
+ return 0
+ else
+ log_warn "Failed to copy $clip_name from local path"
+ fi
+ fi
+
+ # Try to download from URL
+ log_info "Attempting to download $clip_name from URL..."
+ if extract_tar_from_url "$clipUrl" "$OUTDIR"; then
+ # Check if file was extracted
+ if [ -f "$clip_name" ]; then
+ mv "$clip_name" "$dest_path"
+ log_info "Successfully downloaded and moved $clip_name"
+ return 0
+ elif [ -f "$OUTDIR/$clip_name" ]; then
+ log_info "Clip $clip_name already in correct location"
+ return 0
+ else
+ log_warn "Clip $clip_name not found in downloaded content"
+ return 1
+ fi
+ else
+ log_warn "Download failed for $clip_name (offline or URL issue)"
+ return 1
+ fi
+}
+
+# -------------------- Prepare basic clips --------------------
+prepare_basic_clips() {
+ log_info "=========================================="
+ log_info "PREPARING BASIC TEST CLIPS"
+ log_info "=========================================="
+
+ # VP9 clip for basic decode tests
+ need_vp9_clip=0
+ for codec in $codecs; do
+ if [ "$codec" = "vp9" ]; then
+ need_vp9_clip=1
+ break
+ fi
+ done
+
+ if [ "$need_vp9_clip" -eq 1 ] && [ "$testMode" != "encode" ]; then
+ get_test_clip "VP9_640x480_10s.webm" "$OUTDIR/VP9_640x480_10s.webm"
+ fi
+
+ log_info "Basic clip preparation complete"
+}
+
+# -------------------- Prepare advanced clips --------------------
+prepare_advanced_clips() {
+ log_info "=========================================="
+ log_info "PREPARING ADVANCED TEST CLIPS"
+ log_info "=========================================="
+
+ # DRC test clip
+ get_test_clip "1080_720_h264.mp4" "$OUTDIR/1080_720_h264.mp4"
+
+ # Concurrency test clips
+ get_test_clip "H264_480p_30fps.mp4" "$OUTDIR/H264_480p_30fps.mp4"
+ get_test_clip "H265_480p_30fps.mp4" "$OUTDIR/H265_480p_30fps.mp4"
+ get_test_clip "mjpeg1.avi" "$OUTDIR/mjpeg1.avi"
+
+ log_info "Advanced clip preparation complete"
+}
+
+# -------------------- Run basic tests --------------------
+run_basic_tests() {
+ log_info "=========================================="
+ log_info "RUNNING BASIC ENCODE/DECODE TESTS"
+ log_info "=========================================="
+
+ # Run encode tests (skip VP9 as it doesn't support encoding in this test)
+ if [ "$testMode" = "all" ] || [ "$testMode" = "encode" ]; then
+ log_info "=========================================="
+ log_info "ENCODE TESTS"
+ log_info "=========================================="
+
+ for codec in $codecs; do
+ # Skip VP9 for encode tests (no v4l2vp9enc support in this test)
+ if [ "$codec" = "vp9" ]; then
+ log_info "Skipping VP9 encode (not supported)"
+ continue
+ fi
+
+ for res in $resolutions; do
+ params=$(gstreamer_resolution_to_wh "$res")
+
+ # ---------------- FIX: robust split independent of IFS ----------------
+ width=$(printf '%s\n' "$params" | awk '{print $1}')
+ height=$(printf '%s\n' "$params" | awk '{print $2}')
+ case "$width" in ''|*[!0-9]*) width="640" ;; esac
+ case "$height" in ''|*[!0-9]*) height="480" ;; esac
+ # ---------------------------------------------------------------------
+
+ total_tests=$((total_tests + 1))
+ run_encode_test "$codec" "$res" "$width" "$height" || true
+ done
+ done
+ fi
+
+ # Run decode tests
+ if [ "$testMode" = "all" ] || [ "$testMode" = "decode" ]; then
+ log_info "=========================================="
+ log_info "DECODE TESTS"
+ log_info "=========================================="
+
+ for codec in $codecs; do
+ if [ "$codec" = "vp9" ]; then
+ total_tests=$((total_tests + 1))
+ run_decode_test "$codec" "480p" || true
+ else
+ for res in $resolutions; do
+ total_tests=$((total_tests + 1))
+ run_decode_test "$codec" "$res" || true
+ done
+ fi
+ done
+ fi
+}
+
+# -------------------- Run advanced tests --------------------
+run_advanced_tests() {
+ log_info "=========================================="
+ log_info "RUNNING ADVANCED TESTS"
+ log_info "=========================================="
+
+ # Run UVC test (no mode restriction - it's a preview test)
+ log_info "Will run: UVC camera test"
+ run_uvc_preview_test || true
+
+ # Run DRC test (decode only)
+ if [ "$testMode" = "all" ] || [ "$testMode" = "decode" ]; then
+ log_info "Will run: DRC decode test"
+ run_drc_decode_test || true
+
+ # Run concurrency tests (decode only)
+ log_info "Will run: Concurrency decode tests"
+ run_h264_concurrency_test || true
+ run_h265_concurrency_test || true
+ run_mjpeg_concurrency_test || true
+ fi
+
+ # Run advanced encode tests (encode only)
+ if [ "$testMode" = "all" ] || [ "$testMode" = "encode" ]; then
+ log_info "Will run: Advanced encode tests"
+ run_smart_encode_test || true
+ run_cyclic_ir_encode_test || true
+ run_slice_mb_encode_test || true
+ run_rotate_encode_test || true
+ fi
+}
+
+# -------------------- Main test execution --------------------
+log_info "=========================================="
+log_info "STARTING TEST EXECUTION"
+log_info "=========================================="
+log_info "Test type: $testType, Mode: $testMode"
+
+# Route to appropriate test based on test type
+case "$testType" in
+ basic|all)
+ # Prepare and run basic tests
+ prepare_basic_clips
+ run_basic_tests
+
+ # If test type is 'all', prepare and run advanced tests too
+ if [ "$testType" = "all" ]; then
+ prepare_advanced_clips
+ run_advanced_tests
+ fi
+ ;;
+
+ uvc)
+ # Run only UVC test (no clips needed)
+ log_info "Running UVC camera test only"
+ run_uvc_preview_test
+ ;;
+
+ drc)
+ # Prepare DRC clip and run test
+ log_info "Running DRC decode test only"
+ get_test_clip "1080_720_h264.mp4" "$OUTDIR/1080_720_h264.mp4"
+ run_drc_decode_test
+ ;;
+
+ concurrency)
+ # Prepare concurrency clips and run tests
+ log_info "Running concurrency decode tests only"
+ get_test_clip "H264_480p_30fps.mp4" "$OUTDIR/H264_480p_30fps.mp4"
+ get_test_clip "H265_480p_30fps.mp4" "$OUTDIR/H265_480p_30fps.mp4"
+ get_test_clip "mjpeg1.avi" "$OUTDIR/mjpeg1.avi"
+ run_h264_concurrency_test
+ run_h265_concurrency_test
+ run_mjpeg_concurrency_test
+ ;;
+
+ advanced-encode)
+ # Run only advanced encoding tests (no clips needed - uses camera)
+ log_info "Running advanced encode tests only"
+ run_smart_encode_test
+ run_cyclic_ir_encode_test
+ run_slice_mb_encode_test
+ run_rotate_encode_test
+ ;;
+
+ *)
+ log_warn "Unknown test type: $testType"
+ echo "$RESULT_TESTNAME SKIP" >"$RES_FILE"
+ exit 0
+ ;;
+esac
# -------------------- Dmesg error scan --------------------
log_info "=========================================="
diff --git a/Runner/utils/lib_gstreamer.sh b/Runner/utils/lib_gstreamer.sh
index d852bbf47..2e339b1e2 100755
--- a/Runner/utils/lib_gstreamer.sh
+++ b/Runner/utils/lib_gstreamer.sh
@@ -928,6 +928,21 @@ gstreamer_v4l2_decoder_for_codec() {
return 1
}
+# gstreamer_v4l2_decoder_args
+# Returns stack-dependent decoder arguments for V4L2 decoders
+# Prints: decoder arguments string (e.g., "capture-io-mode=4 output-io-mode=4") or empty
+gstreamer_v4l2_decoder_args() {
+ video_stack="$1"
+ case "$video_stack" in
+ downstream)
+ printf '%s\n' "capture-io-mode=4 output-io-mode=4"
+ ;;
+ *)
+ printf '%s\n' ""
+ ;;
+ esac
+}
+
# gstreamer_container_ext_for_codec
# Returns the default container file extension for the given video codec.
# This standardizes container format selection across encode/decode operations:
@@ -1094,25 +1109,22 @@ gstreamer_build_v4l2_decode_pipeline() {
;;
esac
- # Build decoder parameters
- decoder_params=""
- if [ "$video_stack" = "downstream" ]; then
- decoder_params="capture-io-mode=4 output-io-mode=4"
- fi
+ # Get stack-dependent decoder arguments
+ decoder_args=$(gstreamer_v4l2_decoder_args "$video_stack")
# Build pipeline based on parser availability
# All supported formats (h264, h265, vp9) have containers (MP4 or WebM)
if [ -n "$parser" ]; then
# Use parser if available
- if [ -n "$decoder_params" ]; then
- printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${parser} ! ${decoder} ${decoder_params} ! videoconvert ! fakesink"
+ if [ -n "$decoder_args" ]; then
+ printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${parser} ! ${decoder} ${decoder_args} ! videoconvert ! fakesink"
else
printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${parser} ! ${decoder} ! videoconvert ! fakesink"
fi
else
# Skip parser if not available (e.g. VP9 without vp9parse)
- if [ -n "$decoder_params" ]; then
- printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${decoder} ${decoder_params} ! videoconvert ! fakesink"
+ if [ -n "$decoder_args" ]; then
+ printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${decoder} ${decoder_args} ! videoconvert ! fakesink"
else
printf '%s\n' "filesrc location=${input_file} ! ${container} ! ${decoder} ! videoconvert ! fakesink"
fi
@@ -1198,6 +1210,7 @@ prepare_vp9_from_local_path() {
return 1
}
+
# --------------------------------------------------------------
# download_resource
# $1 url – URL to download
@@ -1357,9 +1370,19 @@ run_pipeline_with_logs() {
cmd=$2
logdir=${3:-logs}
TIMEOUT=${4:-60} # default 60 seconds
+ test_type=${5:-} # Optional: uvc, drc, concurrency-h264, etc. for extended validation
+ expected_count=${6:-1} # Optional: For concurrency tests
console_log="${logdir}/${name}_console.log"
gst_debug_log="${logdir}/${name}_gst_debug.log"
+ dmesg_dir="${logdir}/dmesg"
+ dmesg_log="${dmesg_dir}/${name}_dmesg.log"
+
+ # Create dmesg directory if it doesn't exist
+ mkdir -p "$dmesg_dir" 2>/dev/null || true
+
+ # Capture dmesg line count before test execution
+ dmesg_before_lines=$(dmesg | wc -l 2>/dev/null || echo 0)
export GST_DEBUG_FILE="${gst_debug_log}"
@@ -1367,33 +1390,197 @@ run_pipeline_with_logs() {
gstreamer_run_gstlaunch_timeout "$TIMEOUT" "$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)
+ # Capture only new dmesg messages (messages added during test)
+ dmesg | tail -n +$((dmesg_before_lines + 1)) > "$dmesg_log" 2>/dev/null || true
+ new_lines=$(wc -l < "$dmesg_log" 2>/dev/null || echo 0)
+ log_info "${name}: Captured dmesg log ($new_lines new lines)"
- if [ "$playing" -gt 0 ] && [ "$error_present" -eq 0 ]; then
- log_pass "${name} PASS"
- return 0
+ # Check 1: PLAYING state reached
+ playing=$(grep -c "Setting pipeline to PLAYING" "$console_log" || true)
+ if [ "$playing" -eq 0 ]; then
+ log_fail "${name} FAIL: Pipeline never reached PLAYING state"
+ return 1
fi
+
+ # Check 2: No ERROR messages (original check)
+ error_present=$(grep -c "ERROR:" "$console_log" || true)
+
+ # Extended validation for timeout-based tests (if test_type provided)
+ if [ -n "$test_type" ]; then
+ log_info "${name}: ✓ PLAYING state reached"
+
+ # Check 2b: Negotiated video caps observed (extended check)
+ if ! grep -q "caps.*video/x-raw" "$console_log"; then
+ log_fail "${name} FAIL: No video caps negotiation detected"
+ return 1
+ fi
+ log_info "${name}: ✓ Video caps negotiated"
+
+ # Check 3: No fatal/error patterns (extended check - more comprehensive)
+ if grep -qi "ERROR\|CRITICAL\|FATAL" "$console_log"; then
+ log_fail "${name} FAIL: Fatal errors detected in log"
+ log_info "=== ERROR DETAILS ==="
+ grep -i "ERROR\|CRITICAL\|FATAL" "$console_log" | tail -n 20 |
+ while IFS= read -r line; do log_info "$line"; done
+ log_info "====================="
+ return 1
+ fi
+ log_info "${name}: ✓ No fatal errors"
+
+ # Check 4: Test-specific activity evidence
+ case "$test_type" in
+ uvc)
+ # For UVC: Check for frame/buffer activity or FPS evidence
+ if grep -q "rendered.*frames\|fps.*[0-9]" "$console_log"; then
+ log_info "${name}: ✓ Frame activity detected"
+ else
+ log_warn "${name}: No explicit frame activity evidence (may be normal for short tests)"
+ fi
+ ;;
+
+ drc)
+ # For DRC: Verify dynamic resolution change occurred
+ # Expected: 1080p (1920x1080) ↔ 720p (1280x720) transition
+ # Look for caps events showing resolution changes
+
+ # Extract all video caps with resolution from console log
+ caps_lines=$(grep -E "caps.*video/x-raw.*width.*height" "$console_log" || true)
+
+ # Check dmesg log (new messages during test) for resolution changes
+ # Look for various patterns: resolution changes, dimension updates, format changes
+ dmesg_res_changes=$(grep -E "venus|v4l2|video.*resolution|width.*height|1920x1080|1280x720" "$dmesg_log" 2>/dev/null | grep -E "1920x1080|1280x720|resolution|width.*height" || true)
+
+ if [ -z "$caps_lines" ] && [ -z "$dmesg_res_changes" ]; then
+ log_fail "${name}: No video caps or dmesg resolution evidence - cannot verify DRC"
+ log_info "${name}: Console log caps: $(grep -c "caps" "$console_log" || echo 0) lines"
+ log_info "${name}: Dmesg resolution lines: $(printf '%s' "$dmesg_res_changes" | wc -l || echo 0)"
+ return 1
+ fi
+
+ # Count distinct resolutions from console log
+ has_1080p=$(printf '%s\n' "$caps_lines" | grep -c "width.*1920.*height.*1080\|width=1920.*height=1080\|width=(int)1920.*height=(int)1080" || true)
+ has_720p=$(printf '%s\n' "$caps_lines" | grep -c "width.*1280.*height.*720\|width=1280.*height=720\|width=(int)1280.*height=(int)720" || true)
+
+ # Also check dmesg for resolution evidence
+ dmesg_1080p=$(printf '%s\n' "$dmesg_res_changes" | grep -c "1920x1080\|1920.*1080" || true)
+ dmesg_720p=$(printf '%s\n' "$dmesg_res_changes" | grep -c "1280x720\|1280.*720" || true)
+
+ # Sanitize to ensure single integer (handle potential multi-line output)
+ has_1080p=$(printf '%s' "$has_1080p" | head -n 1 | tr -d '\n')
+ has_720p=$(printf '%s' "$has_720p" | head -n 1 | tr -d '\n')
+ dmesg_1080p=$(printf '%s' "$dmesg_1080p" | head -n 1 | tr -d '\n')
+ dmesg_720p=$(printf '%s' "$dmesg_720p" | head -n 1 | tr -d '\n')
+
+ # Validate numeric values
+ case "$has_1080p" in ''|*[!0-9]*) has_1080p=0 ;; esac
+ case "$has_720p" in ''|*[!0-9]*) has_720p=0 ;; esac
+ case "$dmesg_1080p" in ''|*[!0-9]*) dmesg_1080p=0 ;; esac
+ case "$dmesg_720p" in ''|*[!0-9]*) dmesg_720p=0 ;; esac
+
+ # Combine evidence from both sources
+ total_1080p=$((has_1080p + dmesg_1080p))
+ total_720p=$((has_720p + dmesg_720p))
+
+ log_info "${name}: Resolution evidence:"
+ log_info "${name}: Console: 1080p=$has_1080p, 720p=$has_720p"
+ log_info "${name}: Dmesg: 1080p=$dmesg_1080p, 720p=$dmesg_720p"
+ log_info "${name}: Total: 1080p=$total_1080p, 720p=$total_720p"
+
+ # Verify we saw both resolutions (indicating DRC occurred)
+ if [ "$total_1080p" -eq 0 ] || [ "$total_720p" -eq 0 ]; then
+ if [ "$total_1080p" -eq 0 ]; then
+ log_fail "${name}: FAIL: No 1920x1080 resolution detected in console or dmesg"
+ fi
+ if [ "$total_720p" -eq 0 ]; then
+ log_fail "${name}: FAIL: No 1280x720 resolution detected in console or dmesg"
+ fi
+ return 1
+ fi
+
+ log_info "${name}: ✓ Dynamic resolution change verified (1080p ↔ 720p)"
+
+ # Check for frame activity after resolution changes
+ if grep -q "rendered.*frames\|fps.*[0-9]\|frame.*[0-9]" "$console_log"; then
+ log_info "${name}: ✓ Frame activity detected after resolution changes"
+ else
+ log_warn "${name}: No explicit frame activity evidence (may be normal for short tests)"
+ fi
+ ;;
+
+ concurrency-*)
+ # For concurrency: Count distinct decoder instances by looking for unique element names
+ # Each decoder instance has a unique name like "v4l2h264dec0", "v4l2h264dec1", etc.
+ codec="${test_type#concurrency-}"
+
+ case "$codec" in
+ h264)
+ # Count distinct v4l2h264dec instances (e.g., v4l2h264dec0, v4l2h264dec1, ...)
+ decoder_count=$(grep -o "v4l2h264dec[0-9]\+" "$console_log" 2>/dev/null | sort -u | wc -l || echo 0)
+ ;;
+ h265)
+ # Count distinct v4l2h265dec instances (e.g., v4l2h265dec0, v4l2h265dec1, ...)
+ decoder_count=$(grep -o "v4l2h265dec[0-9]\+" "$console_log" 2>/dev/null | sort -u | wc -l || echo 0)
+ ;;
+ mjpeg)
+ # Count distinct jpegdec instances (e.g., jpegdec0, jpegdec1, ...)
+ decoder_count=$(grep -o "jpegdec[0-9]\+" "$console_log" 2>/dev/null | sort -u | wc -l || echo 0)
+ ;;
+ *)
+ decoder_count=0
+ ;;
+ esac
+
+ # Sanitize decoder_count to ensure it's numeric
+ case "$decoder_count" in
+ ''|*[!0-9]*) decoder_count=0 ;;
+ esac
+
+ log_info "${name}: Detected $decoder_count distinct decoder instances (expected: $expected_count)"
+
+ # Strict validation: decoder_count must equal expected_count
+ if [ "$decoder_count" -eq "$expected_count" ]; then
+ log_info "${name}: ✓ Correct number of decoder instances active ($decoder_count = $expected_count)"
+ else
+ log_fail "${name}: FAIL - Decoder instance count mismatch (found: $decoder_count, expected: $expected_count)"
+ return 1
+ fi
+ ;;
+ esac
+
+ # Accept timeout (124) or success (0) if all extended checks passed
+ if [ "$rc" -eq 124 ] || [ "$rc" -eq 0 ]; then
+ log_pass "${name} PASS"
+ return 0
+ else
+ log_fail "${name} FAIL (unexpected exit code: $rc)"
+ return 1
+ fi
+ else
+ # Original validation logic (backward compatible)
+ 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
+ # 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
+ # 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
fi
- log_info "====================="
- return 1
}
# ------------------------------------------------------------------
# Function: check_file_size
@@ -1616,6 +1803,7 @@ camera_build_libcamera_snapshot_pipeline() {
# camera_setup_wayland_environment
# Sets up Wayland/Weston environment for camera preview tests
# Sets wayland_ready=1 if successful, 0 otherwise
+# Tracks whether Weston was started and registers cleanup trap
# Parameters:
# test_name: Name of the test for logging purposes
# Returns: 0 if Wayland is ready, 1 otherwise
@@ -1625,6 +1813,21 @@ camera_setup_wayland_environment() {
wayland_ready=0
sock=""
+ # Track initial Weston state (for potential future use in cleanup logic)
+ # shellcheck disable=SC2034 # Reserved for future Weston state restoration
+ weston_was_running=0
+ if command -v weston_is_running >/dev/null 2>&1; then
+ if weston_is_running >/dev/null 2>&1; then
+ # shellcheck disable=SC2034 # Reserved for future Weston state restoration
+ weston_was_running=1
+ fi
+ elif command -v pgrep >/dev/null 2>&1; then
+ if pgrep -x weston >/dev/null 2>&1; then
+ # shellcheck disable=SC2034 # Reserved for future Weston state restoration
+ weston_was_running=1
+ fi
+ fi
+
# Try to find existing Wayland socket
if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then
sock=$(discover_wayland_socket_anywhere | head -n 1 || true)
@@ -1644,6 +1847,10 @@ camera_setup_wayland_environment() {
if command -v weston_pick_env_or_start >/dev/null 2>&1; then
log_info "No Wayland socket found, attempting to start Weston..."
if weston_pick_env_or_start "$test_name"; then
+ # Track that we started Weston (for cleanup by runner)
+ GSTREAMER_STARTED_WESTON=1
+ export GSTREAMER_STARTED_WESTON
+
# Re-discover socket after Weston start
if command -v discover_wayland_socket_anywhere >/dev/null 2>&1; then
sock=$(discover_wayland_socket_anywhere | head -n 1 || true)
@@ -1682,3 +1889,615 @@ camera_setup_wayland_environment() {
return $((1 - wayland_ready))
}
+
+# camera_cleanup_wayland_environment
+# Cleanup function to restore Weston state if it was started by camera_setup_wayland_environment
+# This function should be called from the runner's existing cleanup function (not via trap).
+# The runner should check GSTREAMER_STARTED_WESTON and call this if needed.
+camera_cleanup_wayland_environment() {
+ # Only cleanup if we started Weston
+ if [ "${GSTREAMER_STARTED_WESTON:-0}" -eq 1 ]; then
+ log_info "Cleaning up Weston (started by camera_setup_wayland_environment)"
+
+ # Use captured initial state to decide cleanup action
+ # If Weston was not running initially, just stop it
+ # If Weston was running initially, restore it using weston_restore_runtime
+ if [ "${weston_was_running:-0}" -eq 0 ]; then
+ # Weston was not running initially - just stop it
+ if command -v weston_stop >/dev/null 2>&1; then
+ weston_stop >/dev/null 2>&1 || true
+ log_info "Stopped Weston (was not running initially)"
+ fi
+ else
+ # Weston was running initially - restore it
+ if command -v weston_restore_runtime >/dev/null 2>&1; then
+ if ! weston_restore_runtime 15; then
+ log_warn "Weston restoration failed"
+ else
+ log_info "Restored Weston (was running initially)"
+ fi
+ fi
+ fi
+
+ # Clear the flag
+ GSTREAMER_STARTED_WESTON=0
+ export GSTREAMER_STARTED_WESTON
+ fi
+}
+
+# -------------------- Downstream-Only Test Detection --------------------
+# gstreamer_is_downstream_stack
+# Checks if current video stack is downstream
+# Returns: 0 if downstream, 1 otherwise
+gstreamer_is_downstream_stack() {
+ if ! command -v video_stack_status >/dev/null 2>&1; then
+ return 1
+ fi
+ stack=$(video_stack_status "" 2>/dev/null || echo "unknown")
+ [ "$stack" = "downstream" ]
+}
+
+# -------------------- UVC Camera Detection --------------------
+# gstreamer_detect_uvc_camera
+# Detects UVC camera device by checking kernel driver via sysfs
+# Prints: device path (e.g., /dev/video2) or empty if not found
+gstreamer_detect_uvc_camera() {
+ for dev in /dev/video*; do
+ [ -c "$dev" ] || continue
+
+ # Check if device uses uvcvideo driver via sysfs
+ drv=$(readlink -f "/sys/class/video4linux/${dev##*/}/device/driver" 2>/dev/null)
+
+ if printf '%s\n' "$drv" | grep -q "/uvcvideo$"; then
+ printf '%s\n' "$dev"
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+# -------------------- Advanced Test Pipeline Builders --------------------
+
+# gstreamer_build_uvc_preview_pipeline
+# Build pipeline for UVC camera live preview
+# Args: device width height framerate
+# Returns: pipeline string or empty on error
+gstreamer_build_uvc_preview_pipeline() {
+ device="$1"
+ width="$2"
+ height="$3"
+ framerate="$4"
+
+ [ -n "$device" ] || return 1
+ [ -n "$width" ] || width="1920"
+ [ -n "$height" ] || height="1080"
+ [ -n "$framerate" ] || framerate="5"
+
+ if has_element qtivtransform; then
+ printf 'v4l2src device=%s ! qtivtransform rotate=0 ! video/x-raw,width=%s,height=%s,framerate=%s/1 ! waylandsink fullscreen=true' \
+ "$device" "$width" "$height" "$framerate"
+ else
+ printf 'v4l2src device=%s ! video/x-raw,width=%s,height=%s,framerate=%s/1 ! waylandsink fullscreen=true' \
+ "$device" "$width" "$height" "$framerate"
+ fi
+}
+
+# gstreamer_build_drc_decode_pipeline
+# Build pipeline for Dynamic Resolution Change H.264 decode test
+# Args: clip_path video_stack
+# Returns: pipeline string or empty on error
+gstreamer_build_drc_decode_pipeline() {
+ clip_path="$1"
+ video_stack="${2:-upstream}"
+
+ [ -n "$clip_path" ] || return 1
+ [ -f "$clip_path" ] || return 1
+
+ # Get stack-dependent decoder arguments
+ decoder_args=$(gstreamer_v4l2_decoder_args "$video_stack")
+
+ if [ -n "$decoder_args" ]; then
+ printf 'filesrc location=%s ! qtdemux ! queue ! h264parse ! v4l2h264dec %s ! video/x-raw,format=NV12 ! fpsdisplaysink video-sink="waylandsink fullscreen=true" text-overlay=false' \
+ "$clip_path" "$decoder_args"
+ else
+ printf 'filesrc location=%s ! qtdemux ! queue ! h264parse ! v4l2h264dec ! video/x-raw,format=NV12 ! fpsdisplaysink video-sink="waylandsink fullscreen=true" text-overlay=false' \
+ "$clip_path"
+ fi
+}
+
+# gstreamer_build_concurrency_decode_pipeline
+# Build pipeline for concurrent decode tests (H.264, H.265, MJPEG)
+# Supports configurable session count with dynamic grid layout
+# Args: codec clip_path video_stack session_count
+# Returns: pipeline string or empty on error
+gstreamer_build_concurrency_decode_pipeline() {
+ codec="$1"
+ clip_path="$2"
+ video_stack="${3:-upstream}"
+ sessions="${4:-8}"
+
+ [ -n "$codec" ] || return 1
+ [ -n "$clip_path" ] || return 1
+ [ -f "$clip_path" ] || return 1
+
+ # Validate session count is numeric and supported (only 2 or 8)
+ case "$sessions" in
+ ''|*[!0-9]*)
+ # Invalid: not numeric
+ return 1
+ ;;
+ 2|8)
+ # Valid session counts - now validate against codec
+ ;;
+ *)
+ # Invalid: unsupported session count (must be 2 or 8)
+ return 1
+ ;;
+ esac
+
+ # Validate session count matches codec requirements
+ # H.264/H.265 require 8 sessions, MJPEG requires 2 sessions
+ case "$codec" in
+ h264|h265)
+ if [ "$sessions" -ne 8 ]; then
+ # H.264/H.265 must use 8 sessions
+ return 1
+ fi
+ ;;
+ mjpeg)
+ if [ "$sessions" -ne 2 ]; then
+ # MJPEG must use 2 sessions
+ return 1
+ fi
+ ;;
+ *)
+ # Unknown codec
+ return 1
+ ;;
+ esac
+
+ # Get stack-dependent decoder arguments (only for H.264/H.265, not MJPEG)
+ decoder_args=""
+ case "$codec" in
+ h264|h265)
+ decoder_args=$(gstreamer_v4l2_decoder_args "$video_stack")
+ ;;
+ esac
+
+ # Build qtivcomposer with grid layout based on session count
+ # Display: 1920x1080
+ pipeline="qtivcomposer name=mix"
+
+ case "$sessions" in
+ 2)
+ # 2x1 grid: 960x1080 each
+ pipeline="$pipeline sink_0::position=\"<0,0>\" sink_0::dimensions=\"<960,1080>\""
+ pipeline="$pipeline sink_1::position=\"<960,0>\" sink_1::dimensions=\"<960,1080>\""
+ ;;
+ 8)
+ # 4x2 grid: 480x540 each
+ pipeline="$pipeline sink_0::position=\"<0,0>\" sink_0::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_1::position=\"<480,0>\" sink_1::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_2::position=\"<960,0>\" sink_2::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_3::position=\"<1440,0>\" sink_3::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_4::position=\"<0,540>\" sink_4::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_5::position=\"<480,540>\" sink_5::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_6::position=\"<960,540>\" sink_6::dimensions=\"<480,540>\""
+ pipeline="$pipeline sink_7::position=\"<1440,540>\" sink_7::dimensions=\"<480,540>\""
+ ;;
+ esac
+
+ # Different output for MJPEG (needs mix. before waylandsink)
+ case "$codec" in
+ mjpeg)
+ pipeline="$pipeline mix. ! queue ! waylandsink fullscreen=true"
+ ;;
+ *)
+ pipeline="$pipeline ! queue ! waylandsink fullscreen=true"
+ ;;
+ esac
+
+ # Add decode chains based on codec and session count
+ i=0
+ while [ "$i" -lt "$sessions" ]; do
+ case "$codec" in
+ h264)
+ if [ -n "$decoder_args" ]; then
+ pipeline="$pipeline filesrc location=${clip_path} ! qtdemux ! queue ! h264parse ! v4l2h264dec ${decoder_args} ! video/x-raw,format=NV12 ! queue ! mix."
+ else
+ pipeline="$pipeline filesrc location=${clip_path} ! qtdemux ! queue ! h264parse ! v4l2h264dec ! video/x-raw,format=NV12 ! queue ! mix."
+ fi
+ ;;
+ h265)
+ if [ -n "$decoder_args" ]; then
+ pipeline="$pipeline filesrc location=${clip_path} ! qtdemux ! queue ! h265parse ! v4l2h265dec ${decoder_args} ! video/x-raw,format=NV12 ! queue ! mix."
+ else
+ pipeline="$pipeline filesrc location=${clip_path} ! qtdemux ! queue ! h265parse ! v4l2h265dec ! video/x-raw,format=NV12 ! queue ! mix."
+ fi
+ ;;
+ mjpeg)
+ # MJPEG uses jpegdec (software decoder), no stack-specific args needed
+ pipeline="$pipeline filesrc location=${clip_path} ! avidemux ! queue ! jpegdec ! videoconvert ! video/x-raw,format=RGB ! queue ! mix."
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+ i=$((i + 1))
+ done
+
+ printf '%s' "$pipeline"
+}
+
+# gstreamer_build_smart_encode_pipeline
+# Build pipeline for HEVC Smart Encode with dual camera streams
+# Args: output_file
+# Returns: pipeline string or empty on error
+gstreamer_build_smart_encode_pipeline() {
+ output_file="$1"
+
+ [ -n "$output_file" ] || return 1
+
+ printf 'qtiqmmfsrc camera=0 noise-reduction=2 video_0::extra-buffers=20 video_0::type=preview name=camsrc video_0::type=video ! video/x-raw,format=NV12,width=1280,height=720,framerate=30/1 ! queue ! scb.sink qtismartvencbin default-gop=30 max-gop=600 smart-framerate=true smart-bitrate=true smart-gop=false max-bitrate=4200000 name=scb encoder=v4l2h265enc ! queue ! h265parse ! queue ! mp4mux ! queue ! filesink location=%s camsrc. ! video/x-raw,format=NV12,width=640,height=480,framerate=15/1 ! queue ! scb.sink_ctrl' \
+ "$output_file"
+}
+
+# gstreamer_build_camera_encode_pipeline
+# Build pipeline for camera-based encoding with custom controls
+# Args: codec width height output_file extra_controls video_stack
+# Returns: pipeline string or empty on error
+gstreamer_build_camera_encode_pipeline() {
+ codec="$1"
+ width="$2"
+ height="$3"
+ output_file="$4"
+ extra_controls="$5"
+ video_stack="$6"
+
+ [ -n "$codec" ] || return 1
+ [ -n "$width" ] || return 1
+ [ -n "$height" ] || return 1
+ [ -n "$output_file" ] || return 1
+
+ # Determine encoder and parser
+ case "$codec" in
+ h264)
+ encoder="v4l2h264enc"
+ parser="h264parse"
+ ;;
+ h265|hevc)
+ encoder="v4l2h265enc"
+ parser="h265parse"
+ ;;
+ *)
+ return 1
+ ;;
+ esac
+
+ # Build pipeline
+ if [ -n "$extra_controls" ]; then
+ printf 'qtiqmmfsrc name=qmmf ! video/x-raw,format=NV12,width=%s,height=%s,framerate=30/1 ! %s extra-controls="%s" capture-io-mode=4 output-io-mode=4 ! %s ! mp4mux ! filesink location=%s' \
+ "$width" "$height" "$encoder" "$extra_controls" "$parser" "$output_file"
+ else
+ printf 'qtiqmmfsrc name=qmmf ! video/x-raw,format=NV12,width=%s,height=%s,framerate=30/1 ! %s capture-io-mode=4 output-io-mode=4 ! %s ! mp4mux ! filesink location=%s' \
+ "$width" "$height" "$encoder" "$parser" "$output_file"
+ fi
+}
+
+# -------------------- Advanced Encode Output Validation --------------------
+# gstreamer_validate_encode_output
+# Comprehensive validation for camera-based encode tests
+# Validates: exit code, file creation, file size, container, codec, resolution, duration
+# Returns: 0 if all validations pass, 1 otherwise
+#
+# Parameters:
+# testname: Test name for logging
+# output_file: Path to encoded output file
+# gstRc: GStreamer exit code
+# expected_codec: Expected codec (h264, H.264, h265, H.265, hevc, HEVC)
+# expected_width: Expected video width in pixels
+# expected_height: Expected video height in pixels
+# min_duration: Minimum expected duration in seconds (optional, default: 5)
+#
+# Example usage:
+# gstreamer_validate_encode_output "HEVC_Encode_720p" "$output_file" "$gstRc" "h265" "1280" "720" "10"
+gstreamer_validate_encode_output() {
+ testname="$1"
+ output_file="$2"
+ gstRc="$3"
+ expected_codec="$4"
+ expected_width="$5"
+ expected_height="$6"
+ # shellcheck disable=SC2034 # min_duration is used later in duration validation
+ min_duration="${7:-5}"
+
+ # 1. Verify exit code is expected (0 or timeout 124)
+ if [ "$gstRc" -ne 0 ] && [ "$gstRc" -ne 124 ]; then
+ log_fail "$testname: FAIL (unexpected exit code: $gstRc)"
+ return 1
+ fi
+ log_info "$testname: ✓ Exit code acceptable ($gstRc)"
+
+ # 2. Verify file was created
+ if [ ! -f "$output_file" ]; then
+ log_fail "$testname: FAIL (output file not created: $output_file)"
+ return 1
+ fi
+ log_info "$testname: ✓ Output file created"
+
+ # 3. Verify file size exceeds minimum (1MB for camera-based encodes)
+ file_size=$(gstreamer_file_size_bytes "$output_file")
+ min_size=1048576 # 1MB minimum for camera encodes
+ if [ "$file_size" -lt "$min_size" ]; then
+ log_fail "$testname: FAIL (file too small: $file_size bytes < $min_size bytes)"
+ return 1
+ fi
+ log_info "$testname: ✓ File size acceptable ($file_size bytes)"
+
+ # 4. Use gst-discoverer-1.0 to verify container, codec, resolution, duration
+ if ! command -v gst-discoverer-1.0 >/dev/null 2>&1; then
+ log_warn "$testname: gst-discoverer-1.0 not available, skipping detailed validation"
+ return 0
+ fi
+
+ discover_log="${output_file}.discover.log"
+ if ! gst-discoverer-1.0 "$output_file" >"$discover_log" 2>&1; then
+ log_fail "$testname: FAIL (gst-discoverer-1.0 failed to analyze file)"
+ return 1
+ fi
+
+ # Log full gst-discoverer output for debugging
+ log_info "$testname: === gst-discoverer output ==="
+ while IFS= read -r line; do
+ log_info "$testname: $line"
+ done < "$discover_log"
+ log_info "$testname: ==================================="
+
+ # Extract and log detected properties
+ # Pattern matches both "container: Quicktime" and "container #0: Quicktime"
+ detected_container=$(grep -i "container" "$discover_log" | head -n1 | sed -n 's/.*container[^:]*:[[:space:]]*\(.*\)/\1/p' || echo "Unknown")
+ detected_codec=$(grep -E "video.*:" "$discover_log" | head -n1 | sed -n 's/.*video[^:]*:[[:space:]]*\([^(]*\).*/\1/p' | sed 's/[[:space:]]*$//' || echo "Unknown")
+ detected_width=$(grep -E "^[[:space:]]*Width:" "$discover_log" | head -n1 | sed -n 's/.*Width:[[:space:]]*\([0-9][0-9]*\).*/\1/p' || echo "0")
+ detected_height=$(grep -E "^[[:space:]]*Height:" "$discover_log" | head -n1 | sed -n 's/.*Height:[[:space:]]*\([0-9][0-9]*\).*/\1/p' || echo "0")
+
+ log_info "$testname: Detected properties:"
+ log_info "$testname: Container: $detected_container"
+ log_info "$testname: Codec: $detected_codec"
+ log_info "$testname: Resolution: ${detected_width}x${detected_height}"
+
+ # Start validation section
+ log_info "$testname: === Validation Results ==="
+ validation_failed=0
+
+ # Verify container (MP4) - STRICT: return failure if mismatch
+ # Match both "container: Quicktime" and "container #0: Quicktime"
+ log_info "$testname: Validating Container..."
+ log_info "$testname: Expected: Quicktime (MP4 container)"
+ log_info "$testname: Detected: $detected_container"
+ if ! grep -q -i "quicktime" "$discover_log"; then
+ log_fail "$testname: Result: ✗ FAIL - Container mismatch (expected Quicktime, got $detected_container)"
+ validation_failed=1
+ else
+ log_pass "$testname: Result: ✓ PASS - Container matches (Quicktime)"
+ fi
+
+ # Verify codec - STRICT: return failure if mismatch
+ log_info "$testname: Validating Codec..."
+ case "$expected_codec" in
+ h264|H.264)
+ log_info "$testname: Expected: H.264"
+ log_info "$testname: Detected: $detected_codec"
+ if grep -q "H\.264" "$discover_log"; then
+ log_pass "$testname: Result: ✓ PASS - Codec matches"
+ else
+ log_fail "$testname: Result: ✗ FAIL - Codec mismatch"
+ validation_failed=1
+ fi
+ ;;
+ h265|H.265|hevc|HEVC)
+ log_info "$testname: Expected: H.265/HEVC"
+ log_info "$testname: Detected: $detected_codec"
+ if grep -q "H\.265\|HEVC" "$discover_log"; then
+ log_pass "$testname: Result: ✓ PASS - Codec matches"
+ else
+ log_fail "$testname: Result: ✗ FAIL - Codec mismatch"
+ validation_failed=1
+ fi
+ ;;
+ esac
+
+ # Verify resolution - STRICT: return failure if mismatch
+ log_info "$testname: Validating Resolution..."
+ log_info "$testname: Expected: ${expected_width}x${expected_height}"
+ log_info "$testname: Detected: ${detected_width}x${detected_height}"
+ if [ "$detected_width" = "$expected_width" ] && [ "$detected_height" = "$expected_height" ]; then
+ log_pass "$testname: Result: ✓ PASS - Resolution matches"
+ else
+ log_fail "$testname: Result: ✗ FAIL - Resolution mismatch"
+ validation_failed=1
+ fi
+
+ # Verify duration - STRICT: parse to seconds and enforce min_duration
+ log_info "$testname: Validating Duration..."
+ duration_line=$(grep "Duration:" "$discover_log" | head -n 1)
+ if [ -z "$duration_line" ]; then
+ log_fail "$testname: Result: ✗ FAIL - No duration information found"
+ log_info "$testname: ==================================="
+ rm -f "$discover_log" 2>/dev/null || true
+ return 1
+ fi
+
+ # Extract duration in format like "0:00:30.123456789"
+ duration_str=$(printf '%s' "$duration_line" | sed -n 's/.*Duration: \([0-9:\.]*\).*/\1/p')
+ if [ -z "$duration_str" ]; then
+ log_fail "$testname: Result: ✗ FAIL - Could not parse duration"
+ log_info "$testname: ==================================="
+ rm -f "$discover_log" 2>/dev/null || true
+ return 1
+ fi
+
+ # Parse duration to seconds (format: H:MM:SS.nanoseconds or M:SS.nanoseconds)
+ # Split by colons and extract hours, minutes, seconds
+ hours=0
+ minutes=0
+ seconds=0
+
+ # Count colons to determine format
+ colon_count=$(printf '%s' "$duration_str" | tr -cd ':' | wc -c)
+
+ case "$colon_count" in
+ 2)
+ # Format: H:MM:SS.nnn
+ hours=$(printf '%s' "$duration_str" | cut -d: -f1)
+ minutes=$(printf '%s' "$duration_str" | cut -d: -f2)
+ seconds=$(printf '%s' "$duration_str" | cut -d: -f3 | cut -d. -f1)
+ ;;
+ 1)
+ # Format: M:SS.nnn or MM:SS.nnn
+ minutes=$(printf '%s' "$duration_str" | cut -d: -f1)
+ seconds=$(printf '%s' "$duration_str" | cut -d: -f2 | cut -d. -f1)
+ ;;
+ 0)
+ # Format: SS.nnn (just seconds)
+ seconds=$(printf '%s' "$duration_str" | cut -d. -f1)
+ ;;
+ esac
+
+ # Sanitize to ensure numeric values
+ case "$hours" in ''|*[!0-9]*) hours=0 ;; esac
+ case "$minutes" in ''|*[!0-9]*) minutes=0 ;; esac
+ case "$seconds" in ''|*[!0-9]*) seconds=0 ;; esac
+
+ # Convert to total seconds
+ total_seconds=$((hours * 3600 + minutes * 60 + seconds))
+
+ log_info "$testname: Expected: >= ${min_duration}s (minimum)"
+ log_info "$testname: Detected: ${total_seconds}s (from $duration_str)"
+
+ # Enforce minimum duration
+ if [ "$total_seconds" -lt "$min_duration" ]; then
+ log_fail "$testname: Result: ✗ FAIL - Duration too short (${total_seconds}s < ${min_duration}s)"
+ validation_failed=1
+ else
+ log_pass "$testname: Result: ✓ PASS - Duration meets minimum requirement"
+ fi
+
+ log_info "$testname: ==================================="
+
+ # Final validation result
+ if [ "$validation_failed" -eq 1 ]; then
+ log_fail "$testname: Overall validation: FAILED"
+ rm -f "$discover_log" 2>/dev/null || true
+ return 1
+ else
+ log_pass "$testname: Overall validation: PASSED"
+ rm -f "$discover_log" 2>/dev/null || true
+ return 0
+ fi
+}
+
+# gstreamer_verify_rotation
+# Verify that video rotation was applied by checking output dimensions
+# For 90° or 270° rotation, dimensions should be swapped
+# Returns: 0 if rotation verified, 1 otherwise
+# REQUIRES gst-discoverer-1.0 and usable dimension evidence
+#
+# Parameters:
+# testname: Test name for logging
+# output_file: Path to encoded output file
+# input_width: Original input width before rotation
+# input_height: Original input height before rotation
+# rotation_degrees: Expected rotation (90, 180, 270)
+#
+# Example usage:
+# gstreamer_verify_rotation "HEVC_Encode_4K_Rotate90" "$output_file" "3840" "2160" "90"
+gstreamer_verify_rotation() {
+ testname="$1"
+ output_file="$2"
+ input_width="$3"
+ input_height="$4"
+ rotation_degrees="$5"
+
+ [ -n "$testname" ] || return 1
+ [ -f "$output_file" ] || return 1
+ [ -n "$input_width" ] || return 1
+ [ -n "$input_height" ] || return 1
+ [ -n "$rotation_degrees" ] || return 1
+
+ # REQUIRE gst-discoverer - fail if not available
+ if ! command -v gst-discoverer-1.0 >/dev/null 2>&1; then
+ log_fail "$testname: FAIL (gst-discoverer-1.0 required for rotation verification)"
+ return 1
+ fi
+
+ log_info "$testname: Verifying ${rotation_degrees}° rotation..."
+
+ # Get discoverer output and save to log file for debugging
+ discover_log="${output_file}.rotation_discover.log"
+ if ! gst-discoverer-1.0 "$output_file" >"$discover_log" 2>&1; then
+ log_fail "$testname: FAIL (gst-discoverer-1.0 failed to analyze file for rotation verification)"
+ rm -f "$discover_log" 2>/dev/null || true
+ return 1
+ fi
+
+ # Log discoverer output for debugging
+ log_info "$testname: === gst-discoverer output (rotation check) ==="
+ while IFS= read -r line; do
+ log_info "$testname: $line"
+ done < "$discover_log"
+ log_info "$testname: ==================================="
+
+ # Extract video dimensions using same robust parsing as validation function
+ # Handles indented format: " Width: 1280"
+ actual_width=$(grep -E "^[[:space:]]*Width:" "$discover_log" | head -n1 | sed -n 's/.*Width:[[:space:]]*\([0-9][0-9]*\).*/\1/p' || echo "")
+ actual_height=$(grep -E "^[[:space:]]*Height:" "$discover_log" | head -n1 | sed -n 's/.*Height:[[:space:]]*\([0-9][0-9]*\).*/\1/p' || echo "")
+
+ # REQUIRE usable dimension evidence - fail if cannot extract
+ if [ -z "$actual_width" ] || [ -z "$actual_height" ]; then
+ log_fail "$testname: FAIL (Could not extract dimensions from output file - no usable evidence)"
+ rm -f "$discover_log" 2>/dev/null || true
+ return 1
+ fi
+
+ log_info "$testname: Extracted dimensions: ${actual_width}x${actual_height}"
+
+ # Determine expected dimensions based on rotation
+ case "$rotation_degrees" in
+ 90|270)
+ # Dimensions should be swapped
+ expected_width="$input_height"
+ expected_height="$input_width"
+ log_info "$testname: Expected dimensions after ${rotation_degrees}° rotation: ${expected_width}x${expected_height}"
+ ;;
+ 180)
+ # Dimensions should remain the same
+ expected_width="$input_width"
+ expected_height="$input_height"
+ log_info "$testname: Expected dimensions after ${rotation_degrees}° rotation: ${expected_width}x${expected_height}"
+ ;;
+ *)
+ log_fail "$testname: FAIL (Unsupported rotation angle: ${rotation_degrees}°)"
+ return 1
+ ;;
+ esac
+
+ # Verify dimensions match expected
+ log_info "$testname: === Rotation Validation ==="
+ log_info "$testname: Expected dimensions: ${expected_width}x${expected_height}"
+ log_info "$testname: Actual dimensions: ${actual_width}x${actual_height}"
+
+ if [ "$actual_width" = "$expected_width" ] && [ "$actual_height" = "$expected_height" ]; then
+ log_pass "$testname: Result: ✓ PASS - Rotation verified (dimensions correctly transformed)"
+ log_info "$testname: ==================================="
+ rm -f "$discover_log" 2>/dev/null || true
+ return 0
+ else
+ log_fail "$testname: Result: ✗ FAIL - Rotation not applied correctly"
+ log_fail "$testname: Expected: ${expected_width}x${expected_height}, Got: ${actual_width}x${actual_height}"
+ log_info "$testname: ==================================="
+ rm -f "$discover_log" 2>/dev/null || true
+ return 1
+ fi
+}
+