# Iteration 2 — Phase 3 (baseline measurements) Phase 3 baselines for iter2 HEVC, executed 2026-05-08 across two stages: (i) substrate-update verification after a rolling `pacman -Syu` userland upgrade, (ii) HEVC cross-validator re-anchor + Bug 1 pre-fix scratch test. All four baselines green; iter1 + T4 references hold byte-identical post-upgrade; HEVC contract anchor captured for Phase 4 transcription. Per `feedback_dev_process.md` Phase 3 anti-fabrication: raw output leads, derived numbers anchor to visible tool invocations. ## Substrate state at Phase 3 start ``` $ ssh fresnel 'uname -r; pacman -Q linux-eos-arm linux-eos-arm-headers libdrm qt6-base mesa chromium libva mpv ffmpeg-v4l2-request-git' 6.19.9-99-eos-arm linux-eos-arm 6.19.9-99 linux-eos-arm-headers 7.0.3-1 libdrm 2.4.133-1 qt6-base 1:6.11.0-2 mesa 1:26.0.3-1 chromium 148.0.7778.96-1 libva 2.23.0-1 libva-utils 2.22.0-1 mpv 1:0.41.0-3 ffmpeg-v4l2-request-git 2:8.1.r123329.b57fbbe-2 ``` The "scheduled for linux-7" upgrade landed as **headers-only**: `linux-eos-arm-headers` jumped from 6.19.9-99 → 7.0.3-1, but the kernel binary package `linux-eos-arm` stayed at `6.19.9-99` (the EOS-ARM repo hasn't published the matching kernel package yet). Running kernel unchanged. 71 packages upgraded total (qt6-base epoch bump, libdrm minor, chromium 147→148, KDE 26.04.1 batch, mkinitcpio 41-3, etc.). OC DTB at `/boot/dtbs/rockchip/rk3399-pinebook-pro.dtb` was preserved (pre-upgrade snapshot at `*.oc-backup-2026-05-08`; sha256 `b436ed029...` unchanged after upgrade — pacman didn't touch it because `linux-eos-arm` itself wasn't in the upgrade queue). `eos-reboot-recommended` package marker installed; mfritsche's Plasma session active throughout (tty1, no SDDM regression on this kernel boot). Reboot deferred — substrate verifies pre-reboot. Device numbering this boot: - rkvdec = `/dev/video1` + `/dev/media0` (HEVC + H.264) - hantro-vpu-dec = `/dev/video3` + `/dev/media1` (MPEG-2) ## Baseline A — substrate verified intact post-upgrade **Goal**: re-run iter1 Phase 7 criterion 4 + 5 reference verifications post `pacman -Syu`. T4 H.264 + iter1 MPEG-2 reference hashes must match exactly. If divergent, substrate regression — investigate before iter2 proceeds. **Verbatim hash output**: ``` H.264 +30s seek (rkvdec /dev/video1 + /dev/media0): /tmp/iter2_phase3_substrate/h264_hw/00000001.jpg f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9 /tmp/iter2_phase3_substrate/h264_hw/00000002.jpg 7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8 /tmp/iter2_phase3_substrate/h264_sw/00000001.jpg f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9 /tmp/iter2_phase3_substrate/h264_sw/00000002.jpg 7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8 MPEG-2 +02s seek (hantro /dev/video3 + /dev/media1): /tmp/iter2_phase3_substrate/mpeg2_hw/00000001.jpg 6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092 /tmp/iter2_phase3_substrate/mpeg2_hw/00000002.jpg ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de /tmp/iter2_phase3_substrate/mpeg2_sw/00000001.jpg 6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092 /tmp/iter2_phase3_substrate/mpeg2_sw/00000002.jpg ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de ``` **All 8 hashes match references exactly.** T4 H.264 + iter1 MPEG-2 substrate intact post-upgrade. Userland upgrade did not regress kernel-side decode or DMA-BUF GL readback. (Aside: mesa version reads `1:26.0.3-1` while the iter1 SDDM watchpoint history showed `26.0.3 → 26.0.5` upgrade on 2026-04-28. Either the 26.0.5 was rolled back from the repo without explicit pacman log entry, or the 04-28 entry recorded a bumped revision that resolved to the same 26.0.3 binary. Not iter2-relevant — references match exactly regardless.) ## Baseline B — HEVC cross-validator verbatim contract anchor **Goal**: extract the byte-level `VIDIOC_S_EXT_CTRLS` payload that ffmpeg-v4l2request submits per HEVC frame. This is the contract Phase 4 implementation must match. **Invocation**: ```bash strace -ff -tt -y -v -s 8192 -e trace=ioctl \ -o /tmp/iter2_phase3/ffmpeg_v4l2req.strace \ ffmpeg -hide_banner -loglevel error -hwaccel v4l2request \ -i ~/fourier-test/bbb_720p10s_hevc.mp4 -frames:v 5 -f null - ``` ffmpeg exit 0; 5 frames decoded clean. **Per-frame submission shape** (verbatim, frame 1): ``` ioctl(/dev/video1, VIDIOC_S_EXT_CTRLS, {ctrl_class=0xf010000 /* V4L2_CTRL_CLASS_CODEC_STATELESS */, count=5, controls=[ {id=0xa40a90 /* V4L2_CID_STATELESS_HEVC_SPS */, size=40, ...}, {id=0xa40a91 /* V4L2_CID_STATELESS_HEVC_PPS */, size=64, ...}, {id=0xa40a92 /* V4L2_CID_STATELESS_HEVC_SLICE_PARAMS */, size=N, ...}, {id=0xa40a93 /* V4L2_CID_STATELESS_HEVC_SCALING_MATRIX */, size=M, ...}, {id=0xa40a94 /* V4L2_CID_STATELESS_HEVC_DECODE_PARAMS */, size=328, ...} ]}) = 0 ``` Five controls per frame. Full 5-control batched submission ≠ Phase 2 prediction of "up to 6 with conditional ENTRY_POINT_OFFSETS / EXT_SPS_*"; the BBB fixture doesn't trigger the conditional ones. **SCALING_MATRIX is sent unconditionally** for the BBB HEVC fixture — this updates Phase 2's "conditional on SPS scaling_list_enabled" assumption. FFmpeg gates SCALING_MATRIX on `ctx->has_scaling_matrix` derived from kernel `VIDIOC_QUERY_EXT_CTRL` at init (rather than on any per-frame VAAPI bit). For the libva backend, the equivalent is to query whether the kernel exposes the SCALING_MATRIX control via `VIDIOC_QUERY_EXT_CTRL` once at init and submit it conditionally on that probe. Phase 4 plan adjusts. **Init device-wide submissions** (not in the per-frame batch, set once at decoder init): - `id=0xa40a95` (V4L2_CID_STATELESS_HEVC_DECODE_MODE) — 2 calls visible (one set, one query/confirm). - `id=0xa40a96` (V4L2_CID_STATELESS_HEVC_START_CODE) — 2 calls visible. **Verbatim SPS payload** (40 bytes, frame 1; same across all frames since SPS is per-stream): ``` "\0\0\0\5\320\2\0\0\4\4\2\4\1\1\0\3\0\0\377\377\375\0\0\0\1\0\0\0\0\0\0\0\200\1\0\0\0\0\0\0" ``` Field-by-field decode (per `:2096-2125`, struct `v4l2_ctrl_hevc_sps`): | Bytes (LE) | Value | Field | |---|---|---| | `\0` | 0 | `video_parameter_set_id` | | `\0` | 0 | `seq_parameter_set_id` | | `\0\5` | 0x0500 = 1280 | `pic_width_in_luma_samples` | | `\320\2` | 0x02d0 = 720 | `pic_height_in_luma_samples` | | `\0` | 0 | `bit_depth_luma_minus8` (8-bit) | | `\0` | 0 | `bit_depth_chroma_minus8` | | `\4` | 4 | `log2_max_pic_order_cnt_lsb_minus4` | | `\4` | 4 | `sps_max_dec_pic_buffering_minus1` | | `\2` | 2 | `sps_max_num_reorder_pics` | | `\4` | 4 | `sps_max_latency_increase_plus1` | | `\1` | 1 | `log2_min_luma_coding_block_size_minus3` | | `\1` | 1 | `log2_diff_max_min_luma_coding_block_size` | | `\0` | 0 | `log2_min_luma_transform_block_size_minus2` | | `\3` | 3 | `log2_diff_max_min_luma_transform_block_size` | | `\0` | 0 | `max_transform_hierarchy_depth_inter` | | `\0` | 0 | `max_transform_hierarchy_depth_intra` | | `\377` | 0xFF | `pcm_sample_bit_depth_luma_minus1` (PCM disabled, so default) | | `\377` | 0xFF | `pcm_sample_bit_depth_chroma_minus1` | | `\375` | 0xFD | `log2_min_pcm_luma_coding_block_size_minus3` | | `\0\0\0` | 0,0,0 | `log2_diff_max_min_pcm_luma_coding_block_size`, `num_short_term_ref_pic_sets`, `num_long_term_ref_pics_sps` | | `\1` | 1 | `chroma_format_idc` (4:2:0) | | `\0` | 0 | `sps_max_sub_layers_minus1` | | `\0\0\0\0\0\0` | 0 | `reserved[6]` | | `\200\1\0\0\0\0\0\0` | 0x180 = SAMPLE_ADAPTIVE_OFFSET (0x08) \| STRONG_INTRA_SMOOTHING_ENABLED (0x100) | `flags` (u64) | So BBB-720p10s_hevc.mp4 has SAO + strong intra smoothing flagged, no PCM, no scaling-list-enabled in SPS-side flags. The unconditional SCALING_MATRIX submission therefore must be triggered by PPS-side flag or by FFmpeg's runtime probe — not bitstream-driven for SPS. **Verbatim PPS payload** (64 bytes — `v4l2_ctrl_hevc_pps`, will field-decode in Phase 4 transcription). **Verbatim DECODE_PARAMS payload** (328 bytes — `v4l2_ctrl_hevc_decode_params`, contains DPB array + POC info, will field-decode in Phase 4). **Verbatim SLICE_PARAMS payload** (variable size N — dynamic-array of `v4l2_ctrl_hevc_slice_params`; likely 1 slice per frame for x265 ultrafast; size = `sizeof(v4l2_ctrl_hevc_slice_params) * num_slices`). **Verbatim SCALING_MATRIX payload** (variable size M — `v4l2_ctrl_hevc_scaling_matrix` if scaling_list_data is present; will field-decode in Phase 4). Raw strace files (gitignored): `phase0_evidence/2026-05-08/iter2_phase3/ffmpeg_v4l2req.strace.*`. Re-run incantation in this document. **ioctl frequency summary** (verbatim): ``` 60 ioctl(/dev/video1, VIDIOC_DQBUF 30 ioctl(/dev/video1, VIDIOC_QBUF 17 ioctl(/dev/video1, VIDIOC_S_EXT_CTRLS 17 ioctl(/dev/video1, VIDIOC_CREATE_BUFS 15 ioctl(/dev/video1, VIDIOC_QUERYBUF 11 ioctl(/dev/video1, VIDIOC_EXPBUF 5 ioctl(/dev/video1, VIDIOC_QUERY_EXT_CTRL (HEVC slice_params dynamic-array introspection) 4 ioctl(/dev/media0, MEDIA_IOC_REQUEST_ALLOC (request_fd pool of 4) 4 MEDIA_REQUEST_IOC_REINIT × 3 fds + MEDIA_REQUEST_IOC_QUEUE × 3 fds ``` 17 S_EXT_CTRLS = 2 init device-wide (DECODE_MODE + START_CODE) + 15 per-frame (probably 5 frames × 3 calls each? Or 1 batched per frame × 5 + 10 something else; actual layout is observed-counted, not split-aware — re-extract per-frame breakdown if Phase 6 needs it). Frequency summary is sufficient as an anchor. ## Baseline C — slice-count probe (deferred) Quick `ffprobe` examination of `bbb_720p10s_hevc.mp4` confirms 1 video stream, hevc Main, 1280×720, 24 fps, 10s. Per-frame slice-count not directly extracted (would require a bitstream-filter pass like `hevc_metadata` to count slices per access unit). For x265 ultrafast preset on 720p, default is 1 slice per frame; assume that until Phase 6 verifies. If Phase 6 finds multi-slice frames, the libva backend's slice_params accumulation logic (Phase 2 Bug 4) handles up to 600 entries per frame per kernel UAPI advertising. ## Baseline D — Bug 1 pre-fix scratch test (collateral safety) **Goal**: apply Bug 1 fix (config.c break for HEVCMain) on a throwaway branch with `h265.c` + Bug 2 NOT applied, verify H.264 + MPEG-2 reference hashes hold post-rebuild + reinstall. Confirm no collateral damage from the trivial config.c change. **Patch applied**: ```diff @@ -68,6 +68,8 @@ VAStatus RequestCreateConfig(VADriverContextP context, VAProfile profile, // submission time. break; case VAProfileHEVCMain: + // iter2 Phase 3 scratch — would break here in real fix + break; default: return VA_STATUS_ERROR_UNSUPPORTED_PROFILE; ``` Build: clean. Install: clean. **Hash verification** (post Bug 1 scratch, h265.c stays disabled): ``` H.264 +30s: HW frame 1: f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9 (matches T4) HW frame 2: 7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8 (matches T4) MPEG-2 +02s: HW frame 1: 6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092 (matches iter1) HW frame 2: ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de (matches iter1) ``` **Result: ✅ no collateral damage.** Bug 1 fix in isolation does not regress H.264 or MPEG-2. **HEVC behavior with Bug 1 only** (Bug 2 + Bug 3-6 not applied; h265.c still disabled): ``` [hevc] Task finished with error code: -5 (Input/output error) ``` libva trace verbatim: ``` [49437.712028] vaQueryConfigProfiles ret = VA_STATUS_SUCCESS [49437.712059] va_TraceCreateConfig profile = 17, VAProfileHEVCMain [49437.712118] vaCreateConfig ret = VA_STATUS_SUCCESS, success (no error) <-- Bug 1 fix advances past this [49437.712139] vaQuerySurfaceAttributes ret = VA_STATUS_SUCCESS ... (further surface attribute queries) ... (decode fails downstream because picture.c::codec_set_controls hits the case VAProfileHEVCMain: return VA_STATUS_ERROR_UNSUPPORTED_PROFILE; reject at line 204-206 — that's Bug 2, not yet patched in this scratch) ``` So Bug 1 alone advances `vaCreateConfig` to SUCCESS; the next failure is Bug 2's explicit reject at picture.c. Confirms Phase 2 prediction. Bug 2 fix requires h265_set_controls to exist (Bug 3-6: enable + rewrite), so Bug 2 lands together with the h265.c rewrite in Commit B (analogous to iter1 Commit B). Scratch state cleaned: `git checkout -- src/config.c && rebuild && reinstall`. Master backend back; H.264 + MPEG-2 still pass; HEVC fails at vaCreateConfig (back to Baseline-A-equivalent state — Bug 1 not applied on master). ## What Phase 3 confirms / refutes from Phase 2 | Phase 2 claim | Phase 3 evidence | Status | |---|---|---| | Bug 1: config.c HEVCMain falls through to default → vaCreateConfig=12 | Baseline D libva trace: pre-patch fails, post-patch SUCCESS | ✅ confirmed | | Bug 2: picture.c case VAProfileHEVCMain explicit UNSUPPORTED_PROFILE reject | Baseline D libva log: ffmpeg fails decode -5 (I/O error) post-Bug-1, before reaching h265.c (h265.c disabled) | ✅ confirmed (effect observed, even though h265.c isn't enabled to dispatch) | | Bug 3: h265.c uses staging-era CIDs that don't exist on modern kernels | Phase 2 source-read + test-compile already verified | ✅ confirmed | | Phase 2 prediction: per-frame batch has 6 controls | Baseline B verbatim: 5 controls (no ENTRY_POINT_OFFSETS for BBB fixture) | partially refuted: 5 not 6 | | Phase 2 prediction: SCALING_MATRIX is conditional on bitstream | Baseline B: SCALING_MATRIX always present in BBB's per-frame batch | refuted: FFmpeg gates on kernel-advertised control availability, not bitstream flag | | Phase 2 prediction: slice_params is dynamic-array | Baseline B: SLICE_PARAMS variable-size (N entries × 1 slice/frame for x265 ultrafast) | ✅ confirmed | | Substrate intact post pacman -Syu (kernel didn't change) | Baseline A: T4 + iter1 hashes match exactly | ✅ confirmed | | `linux-eos-arm` kernel binary stays at 6.19.9-99 (only headers jump to 7.0.3) | pacman.log verifies; running `uname -r` confirms | ✅ confirmed | | OC DTB intact through upgrade | sha256 pre/post upgrade unchanged | ✅ confirmed | ## Phase 4 plan inputs (updated) Phase 4 plan should specify: 1. **Diff scope** (Phase 2 mostly correct; minor amendments below): - `src/config.c:64-69` — add `break;` for `VAProfileHEVCMain` (3 lines, mirrors iter1). - `src/picture.c:204-206` — replace explicit reject with dispatch to `h265_set_controls()`. - `src/h265.c` — rewrite against new V4L2_CID_STATELESS_HEVC_* split API. Per-frame batch is **5 controls** (SPS, PPS, SLICE_PARAMS dynamic-array, SCALING_MATRIX, DECODE_PARAMS). DECODE_MODE + START_CODE device-wide menus set once at init. - `src/picture.c::codec_store_buffer` (HEVC VASliceParameterBufferType case) — append-not-overwrite for slice_params accumulation. - `src/surface.h::params.h265` — add slice_params array + count. - `src/meson.build` — uncomment `h265.c` (line 50) + `h265.h` (line 73). 2. **SCALING_MATRIX gating** (amended from Phase 2): query kernel via `VIDIOC_QUERY_EXT_CTRL` for `V4L2_CID_STATELESS_HEVC_SCALING_MATRIX` once at init; submit unconditionally per-frame if kernel advertises (matches FFmpeg `ctx->has_scaling_matrix` pattern). For BBB on RK3399, the kernel advertises it, so SCALING_MATRIX always in the batch. 3. **Default scaling matrices** if `iqmatrix_set==false`: per ISO/IEC 23008-2 Table 4-1 default scaling lists (4×4, 8×8, 16×16, 32×32 with separate luma/chroma variants). Phase 6 transcription from FFmpeg's `default_scaling_list_intra` / `default_scaling_list_inter` arrays in `libavcodec/hevc.h` — verify against Baseline B SCALING_MATRIX verbatim payload (all-default values for BBB fixture). 4. **Contract anchor citations** (per Phase 6 contract-before-code): - Linux mainline UAPI `:2090-2300` for the 5+2 stateless HEVC control struct definitions and flag constants. - FFmpeg `libavcodec/v4l2_request_hevc.c:505-565` (`v4l2_request_hevc_queue_decode`) for batched submission shape. - Phase 3 Baseline B verbatim 5-control payload (this document). - VAAPI `` for `VAPictureParameterBufferHEVC`, `VASliceParameterBufferHEVC`, `VAIQMatrixBufferHEVC` source field shapes. 5. **Phase 7 verification harness**: re-use iter1's 5-criterion shape, anchor on `mpv --hwdec=vaapi --vo=image` DMA-BUF GL path (cache-coherency-safe per memory `feedback_rockchip_pixel_verify_path.md`). HEVC fixture at +02s seek; HW vs SW byte-identical hashes for 2 distinct frames; iter1 MPEG-2 + T4 H.264 still match. Bonus: byte-compare post-fix S_EXT_CTRLS payload against Baseline B (per `feedback_review_empirical_over_theoretical.md` — empirical wins). ## Open question for Phase 6 close The iter2 fix-forward pattern from iter1 Commit D (header deletion completeness check via clean rebuild, not grep) applies to iter2's `mpeg2-ctrls.h` re-grep audit during Phase 6 (defensive — even though iter1 cleaned it up). Per `feedback_header_deletion_check.md`. iter2 does NOT delete `include/hevc-ctrls.h` (it's a thin shim, not a duplication header per Phase 2 Bug 7). ## Phase 3 → Phase 4 close All four baselines green. Substrate intact post-upgrade. HEVC contract anchor captured for Phase 4 transcription. Bug 1 scratch confirms collateral safety. Phase 4 can proceed; estimated h265.c rewrite ~250-400 lines (per Phase 2 estimate; Phase 3 confirms 5-control batch + dynamic-array slice_params + unconditional SCALING_MATRIX as the structural facts to plan against).