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 +} +