# Iteration 4 — Phase 0 (substrate / motivation / inventory) → Phase 1 lock Opens 2026-05-08 immediately after iter3 close ([`phase8_iteration3_close.md`](phase8_iteration3_close.md), commit `d5d4beb`). Per `feedback_dev_process.md` Phase 0, this document captures iter4's locked research question + substrate + scope, ending with the Phase 1 measurable success criterion. ## Locked research question (iteration 4) > **"Make VP9 the fifth codec to pass boolean-correctness on fresnel via the libva-v4l2-request-fourier path — `mpv --hwdec=vaapi bbb_720p10s_vp9.webm` engages the backend cleanly on rkvdec, and HW pixel readback yields byte-identical output to a software-decoded reference for the same frames."** Pass/fail (boolean): 1. **Profile enumeration**. `vainfo` with the rkvdec env binding (`LIBVA_V4L2_REQUEST_VIDEO_PATH=/dev/video`, `LIBVA_V4L2_REQUEST_MEDIA_PATH=/dev/media` — re-verify per-boot device numbering) lists `VAProfileVP9Profile0`. Currently NOT enumerated — `config.c` has no VP9 enumeration block (same starting condition as iter3 VP8). 2. **Config creation succeeds.** `vaCreateConfig(VAProfileVP9Profile0, VAEntrypointVLD)` returns `VA_STATUS_SUCCESS`. 3. **End-to-end ffmpeg-direct decode**. `ffmpeg -hwaccel vaapi -i ~/fourier-test/bbb_720p10s_vp9.webm -frames:v 5 -f null -` (with rkvdec env binding) shows the libva chain in stderr, no `EINVAL` from `VIDIOC_S_EXT_CTRLS` for the VP9_FRAME or VP9_COMPRESSED_HDR controls, exits 0. 4. **HW=SW byte-identical, with HW engagement verified per memory `feedback_hw_decode_engagement_check.md`**: - **Step 0**: confirm mpv `--hwdec=vaapi` engages for VP9 via `mpv -v` log (must NOT say `Using software decoding` or `Selected decoder: vp9 - `). If mpv falls back like it did for VP8, fall through to transitive proof. - **Step 1 (preferred — direct)**: `mpv --hwdec=vaapi --vo=image --frames=2 --start=00:00:02 ~/fourier-test/bbb_720p10s_vp9.webm` produces JPEGs byte-identical to a `--hwdec=no` SW run for both frame 1 and frame 2; frames 1+2 hash-differ (real motion). - **Step 2 (fallback — transitive per `reference_dmabuf_resv_blocker.md`)**: if mpv falls back, OR if rkvdec hits the same dma_resv kernel issue as hantro on iter3, prove HW correctness via (A) libva backend `S_EXT_CTRLS` payload == kernel-direct ffmpeg-v4l2request payload AND (B) kernel-direct decode == SW reference. 5. **Four-codec regression**. iter1 MPEG-2 + iter2 HEVC + iter3 VP8 + T4 H.264 reference hashes all hold: - H.264 +30s: `f623d5f7...` and `7d7bc6f2...` - MPEG-2 +02s: `6e7873030dbf...` and `ccc7ce08810d...` - HEVC +02s: `47a5f3850df5...` and `a467b3bc9d7b...` - VP8 (criterion-4 reference, when re-verifiable): kernel-direct + SW match per iter3 Phase 7 transitive proof; OR JPEGs `e43757a4...` + `a86bf885...` once kernel patches land. A clean iter4 close has all five checks green. Phase 7 → Phase 4 loopback per `feedback_dev_process.md` if any fail. ## Mechanism the question targets Phase 0 cross-validator sweep ([`phase0_evidence/2026-05-07/cross_validator_traces.md`](phase0_evidence/2026-05-07/cross_validator_traces.md)) established that the kernel + rkvdec driver path works for VP9: rkvdec advertises VP9 capability per Phase 0 V4L2 inventory: ``` rkvdec /dev/video3 (boot-dependent path): OUTPUT_MPLANE codec_format VP9F (V4L2_PIX_FMT_VP9_FRAME, compressed) Stateless controls (per kernel UAPI :2696,2797): V4L2_CID_STATELESS_VP9_FRAME = 0xa40b2c (CODEC_STATELESS_BASE + 300) V4L2_CID_STATELESS_VP9_COMPRESSED_HDR = 0xa40b2d (CODEC_STATELESS_BASE + 301) ``` The broken link is the libva backend at sites analogous to iter3: - **`src/config.c::RequestQueryConfigProfiles`** — no VP9 enumeration block. iter3 added VP8 block; iter4 adds VP9 block probing `V4L2_PIX_FMT_VP9_FRAME` and adding `VAProfileVP9Profile0` (and possibly `VAProfileVP9Profile2` if scope extends). - **`src/config.c::RequestCreateConfig`** — no `case VAProfileVP9Profile0:`. Mirror iter3 VP8 case shape with `break;`. - **`src/config.c::RequestQueryConfigEntrypoints`** — switch case missing; add to the VLD case list. - **`src/vp9.c`** — file does NOT exist. NEW file. - **`src/vp9.h`** — file does NOT exist. NEW file. - **`src/meson.build`** — `'vp9.c'` + `'vp9.h'` aren't in the sources/headers lists. - **`src/picture.c::codec_set_controls`** — no `VAProfileVP9Profile0` dispatch case. - **`src/picture.c::codec_store_buffer`** — no VP9 cases for VAPictureParameterBufferType, VASliceParameterBufferType. - **`src/surface.h`** — `params` union has no VP9 member. - **`src/buffer.c`** — predicted Commit D fix-forward per memory `feedback_runtime_enumerates_allowlists.md`. Verify whether VP9's buffer types fall in the existing allow-list at `buffer.c:59-70`. VAAPI VP9 uses VAPictureParameterBufferType + VASliceParameterBufferType + VASliceDataBufferType — all already whitelisted (used by H.264). **Predicted no Commit D needed** for iter4 buffer.c, but plan for fix-forward if a runtime miss surfaces. VP9 contract surface from kernel UAPI: - **TWO controls per frame** (vs iter3 VP8's ONE): - `V4L2_CID_STATELESS_VP9_FRAME` — frame parameters, includes 8-entry DPB, segmentation, quantization, loop filter. - `V4L2_CID_STATELESS_VP9_COMPRESSED_HDR` — probability updates from compressed header (separate control because parsing it requires running the boolean decoder). - 8 reference frames in DPB (`active_ref_frames[8]` in `v4l2_ctrl_vp9_frame` — vs VP8's 3: last/golden/altref). - Tile-based decoding (VP9 has 1..N tiles per frame; rkvdec supports parallel tile decode). - VP9 Profile 0 = 8-bit 4:2:0 only (out-of-scope: Profile 1 4:4:4, Profile 2 10-bit, Profile 3 10-bit 4:4:4). Phase 4 plan must cite the contract before patching: read kernel `drivers/staging/media/rkvdec/rkvdec-vp9.c`, read FFmpeg `libavcodec/v4l2_request_vp9.c`, read kernel UAPI `` for both VP9 control structs, state the contract before any code lands. ## Predecessor carry-over (iter3 → iter4) ### State that carries forward (re-verified at iter4 open) - **Hardware**: fresnel RK3399, kernel `linux-eos-arm 6.19.9-99-eos-arm`. Custom OC kernel. - **rkvdec node**: device numbering shuffles per boot. iter1 had `/dev/video3+/dev/media1`; iter2 had `/dev/video3+/dev/media1`; iter3 last boot had `/dev/video3+/dev/media1` (consistent so far). iter4 binding cells re-verify via `v4l2-ctl --info` at session start. - **Decoder formats** (rkvdec, from Phase 0 v4l2_inventory): OUTPUT_MPLANE = `H264`, `HEVC`, `VP9F`. CAPTURE_MPLANE = `NV12`. - **Kernel UAPI**: `V4L2_CID_STATELESS_VP9_FRAME` + `V4L2_CID_STATELESS_VP9_COMPRESSED_HDR` available in `/usr/include/linux/v4l2-controls.h` (verified Phase 0 iter4 open). - **Backend build state**: libva-v4l2-request-fourier post-iter3-close. Fork tip `e1aca9c`. SHA256 `0ab5b2ba22df19569be26228629968ee254c030cd3664ce7afd1bc0396c254ef` of `/usr/lib/dri/v4l2_request_drv_video.so` on fresnel. - **Test fixture**: `~/fourier-test/bbb_720p10s_vp9.webm` on fresnel (3.4 MB, VP9 Profile 0, 1280×720@24fps yuv420p, 10s). Provenance in [`phase0_evidence/2026-05-07/test_fixtures.md`](phase0_evidence/2026-05-07/test_fixtures.md). - **Reference fixtures for regression**: bbb_1080p30_h264.mp4, bbb_720p10s_mpeg2.ts, bbb_720p10s_hevc.mp4, bbb_720p10s_vp8.webm. - **Reference hashes for criterion 5**: - H.264 (T4) at +30s: `f623d5f7a41697f67dd227275c6f1b21ffc257f65626d32fde8229357f8764c9` + `7d7bc6f2146dda8b2d223bba622c4b9fbe9674181ff1e02afe286b620342e0a8` - MPEG-2 (iter1) at +02s: `6e7873030dbf0403c67f35dd106ebef3c7909a0fd12433b82ad758e7fee9f092` + `ccc7ce08810d4a96e9ba7a19f4f95bbf6cc861bda9337604b5c668ad52bef7de` - HEVC (iter2) at +02s: `47a5f3850df5d8c732767a227830c2272ff78402a7b6adeea329e29838808be5` + `a467b3bc9d7b6374b6786ecfac46932d6c7bb932ab11d311edaa233d7863e656` - VP8 (iter3) — kernel-direct decoded YUV per-frame SHAs (Phase 3 baseline) + libva backend transitive-equivalent. SW reference JPEGs `e43757a40e5d71...` + `a86bf885e58825...` re-verifiable when `vb2_dma_resv` kernel patches land (memory `reference_dmabuf_resv_blocker.md`). - **Cross-validator anchor**: ffmpeg-v4l2request VP9 trace from [`phase0_evidence/2026-05-07/cross_validator/vp9/`](phase0_evidence/2026-05-07/cross_validator/vp9/). Phase 3 will re-capture verbatim payloads. - **Reference sources local**: - `/home/mfritsche/src/libva-multiplanar/references/ffmpeg-kwiboo/libavcodec/v4l2_request_vp9.c` - `/home/mfritsche/src/libva-multiplanar/references/ffmpeg-kwiboo/libavcodec/vaapi_vp9.c` - `/home/mfritsche/src/libva-multiplanar/references/linux-mainline/drivers/staging/media/rkvdec/rkvdec-vp9.c` (verify presence at Phase 2) ### Data that does NOT carry forward (re-acquire if needed) - VP9-specific Phase 3 baseline payloads — Phase 3 captures fresh. - Pre-iter4 VP9 trace data — none exists in this fork. - ohm/RK3568 rkvdec VP9 behaviour — ohm rkvdec also supports VP9 but the libva-multiplanar campaign never tested VP9 end-to-end. Reference history only. ### Open questions inherited from iter3 close - **iter3-flags-anomaly bit 0x40** — VP8-specific, doesn't apply to VP9. - **iter3-criterion-4-readback (dmabuf-resv kernel issue)** — was hantro-specific. Open question for iter4: does rkvdec exhibit the same all-zero pages from libva readback? rkvdec is a different driver subsystem (drivers/staging/media/rkvdec/, not drivers/media/platform/verisilicon/hantro). Likely DOES NOT hit the same bug — iter1+iter2's mpv-DMA-BUF-GL paths worked for rkvdec. **Phase 3 baseline answers: re-test ffmpeg-vaapi-hwdownload on rkvdec and check if output is non-zero.** - **iter3-mpv-vp8-fallback** — mpv 0.41.0 silently falls back to SW for VP8 hwdec=vaapi. Open question for iter4: does mpv engage HW for VP9? If yes (and rkvdec doesn't have dmabuf-resv issue), criterion 4 direct verification works. If no, transitive proof per `reference_dmabuf_resv_blocker.md`. - **Phase 4 cross-cutting backlog** — same as iter3 close (B1, B3, B4, B5, B6, L3 inherited). Not iter4 scope. ## Tooling and measurement-instrument inventory (live verification) Re-verified at iter4 open (carries forward from iter3; all proven working): - `strace -ff -tt -y -v -e trace=ioctl,openat,close` — V4L2 + media-request ioctl tracing. - `LIBVA_TRACE` environment variable — vaCreate/vaQuery/vaInitialize call traces. - `mpv -v` for HW engagement check (per memory `feedback_hw_decode_engagement_check.md`). - `mpv --hwdec=vaapi --vo=image` — cache-safe pixel verifier (when mpv engages). - `ffmpeg -hwaccel v4l2request` — independent V4L2 client cross-validator (kernel-direct, no libva backend). - `ffmpeg -hwaccel vaapi` — through-libva-backend test path. - Backend build: `ninja -C ~/src/libva-v4l2-request-fourier/build && sudo ninja -C build install`. - Phase 3 decoder (`/tmp/iter3_phase3/decode_vp8.py` reusable for VP9 with new field layout). - gcc test-compile for VAAPI field-availability checks per `feedback_review_empirical_over_theoretical.md` Direction 2. ## In-scope (LOCKED 2026-05-08 for iteration 4) - libva-v4l2-request-fourier backend VP9 path on rkvdec. - `src/config.c` — ADD VP9 enumeration block in `RequestQueryConfigProfiles`; ADD `case VAProfileVP9Profile0:` with `break;` in `RequestCreateConfig`; ADD case in `RequestQueryConfigEntrypoints`. Same shape as iter3 VP8 changes. - `src/picture.c::codec_set_controls` — ADD `VAProfileVP9Profile0` dispatch to `vp9_set_controls()`. - `src/picture.c::codec_store_buffer` — ADD VP9 cases for VAPictureParameterBufferType + VASliceParameterBufferType. (VP9 doesn't use VAProbabilityBufferType — that was VP8-specific. VP9 doesn't use VAIQMatrixBufferType — quantization is in the picture-parameter struct.) - `src/vp9.c` — NEW file. Implements `vp9_set_controls()` against `V4L2_CID_STATELESS_VP9_FRAME` + `V4L2_CID_STATELESS_VP9_COMPRESSED_HDR`. Predicted ~250-350 LOC (between iter3 VP8 and iter2 HEVC scope). - `src/vp9.h` — NEW file. Declares `vp9_set_controls()`. - `src/surface.h` — extend `params` union with `params.vp9` struct holding VAAPI VP9 buffer types. - `src/meson.build` — add `'vp9.c'` + `'vp9.h'` to sources/headers lists. - iter4 binding-cell test harness: re-run iter1's Phase 7 5-criterion shape with VP9 fixture substituted + 4-codec regression block. - Pixel verify uses mpv-DMA-BUF-GL if mpv engages HW for VP9; transitive proof if not (per memory `reference_dmabuf_resv_blocker.md`). - VP9 Profile 0 only (8-bit 4:2:0). ## Out-of-scope (LOCKED 2026-05-08 for iteration 4) - VP9 Profile 1 (4:4:4 8-bit), Profile 2 (4:2:0 10/12-bit), Profile 3 (4:4:4 10/12-bit). - VP9 super-frames (multi-frame packets — not present in BBB fixture). - Multi-tile parallel decode performance optimization (kernel may decode tiles serially in single-threaded mode; not iter4 scope). - VP9 SVC (scalable layers). - Performance metrics. - Long-duration VP9 stress (>10s). - Phase 4 cross-cutting backlog items (B1 device-discovery, B3 BeginPicture profile-aware reset, B4 context.c log suppression, B5 vbv_buffer_size, B6 SPS fidelity, L3 vaDeriveImage cache-stale). - Fixing the kernel-side dma_resv issue (sibling campaign `dmabuf-modifier-triage` iter1). - Fixing mpv-VP8-fallback (consumer-side, not iter4 scope). - chromium-fourier 149 install on fresnel. - Upstream Linux engagement. ## Phase 1 success criterion (LOCKED 2026-05-08) The five Pass/fail bullets at the top of this document are the iter4 success criterion. Phase 3 baseline measurements feed Phase 4 plan; Phase 7 verification re-runs all five against the patched backend. If Phase 3 baseline reveals the chosen criterion is the wrong target (Phase 3 → Phase 1 loopback per `feedback_dev_process.md`), the criterion will be rewritten and re-locked. Plausible reasons that would trigger the loopback: - VP9 fixture is malformed in a way that exposes a fixture-side bug rather than a backend-side bug. (Mitigation: ffmpeg-v4l2request decoded the fixture cleanly per Phase 0 cross-validator; unlikely.) - VAAPI's VP9 buffer types include something not exposed by mpv-vaapi consumer chain (e.g. compressed-header buffer not wired through). Phase 3 baseline LIBVA_TRACE will surface. - mpv `--hwdec=vaapi` falls back to SW for VP9 (analogous to iter3 VP8 mpv fallback). Mitigation: criterion 4 falls through to transitive proof per memory `reference_dmabuf_resv_blocker.md`. - rkvdec exhibits the same dma_resv kernel issue as hantro (unlikely but possible — rkvdec is a different driver but might share the videobuf2 vb2_dma_resv gap). Mitigation: same transitive proof. The other four Phase 1 criteria hold as locked. ## Phase 2 source-read targets For the upcoming Phase 2 situation analysis: - `src/config.c` — `RequestQueryConfigProfiles` (lines 121-165): pattern for VP9 enumeration block; `RequestCreateConfig` (lines 54-78): pattern for adding `case VAProfileVP9Profile0:`; `RequestQueryConfigEntrypoints` (lines 167-191): adding to entry-point case list. - `src/picture.c::codec_set_controls` (lines 188-225): pattern for VP9 dispatch. - `src/picture.c::codec_store_buffer` (lines 54-186): patterns for adding VP9 cases per VAAPI buffer type. VAAPI VP9 sends 2 distinct buffer types per frame (Picture + Slice). NOT 4 like iter3 VP8 (no Probability + IQMatrix). - `src/surface.h` (lines 92-119): `params` union pattern for adding `vp9` member. - VAAPI `` — VAAPI VP9 buffer struct definitions: `VADecPictureParameterBufferVP9`, `VASliceParameterBufferVP9`. - Kernel UAPI `:2696-2870` — `V4L2_CID_STATELESS_VP9_FRAME` + `V4L2_CID_STATELESS_VP9_COMPRESSED_HDR` + sub-structs (segmentation, loop filter, quantization, MV probabilities). - Linux mainline kernel `drivers/staging/media/rkvdec/rkvdec-vp9.c` — rkvdec VP9 driver source. - FFmpeg downstream `libavcodec/v4l2_request_vp9.c` — independent V4L2 client implementation. Submission shape, per-frame field mapping. - FFmpeg `libavcodec/vaapi_vp9.c` — VAAPI source-side reference (used in iter3 transitive proof Step A). ## What "iteration 4 close" looks like A clean iter4 close per `feedback_dev_process.md` Phase 8: - All 5 Phase 1 criteria green (criterion 4 either direct or transitive — criteria don't differentiate). - `phase8_iteration4_close.md` summarizing the bug, contract, fix, binding-cell numbers. - Fifth-codec passing on the campaign-level scoreboard: 4/5 → 5/5 (campaign complete). - Memory entries distilled for any new lessons. - Debug-instrumentation sweep at close. - Phase 5 sonnet-architect review pass signed off. - Commits all authored as `claude-noether` per memory `feedback_gitea_as_claude_noether.md`. - `src/vp9.c` + `src/vp9.h` added to repo, enabled in meson.build. Predicted iter4 difficulty vs iter1+iter2+iter3: - **vs iter3 VP8** (single control, ~370 LOC): VP9 has 2 controls (FRAME + COMPRESSED_HDR), more DPB state (8 refs vs 3), more segmentation+lf complexity. Larger. - **vs iter2 HEVC** (5 controls, ~470 LOC): VP9 has 2 controls + per-frame submission (no init-time menus expected). Smaller in control-count but field-fidelity comparable. - **Predicted scope**: ~400-500 LOC, 3-4 commits + 0-1 fix-forwards (Commit D may not be needed; VP9 buffer types are already in buffer.c allow-list). If Phase 7 misses a check, most likely culprits: 1. **Compressed header probability mapping**: `v4l2_ctrl_vp9_compressed_hdr` is a separate control with VP9 prob-update tables. VAAPI may not expose all bits the kernel needs; gap candidates similar to iter3's `first_part_header_bits`. 2. **8-frame DPB**: VP9 has 8 reference frame slots. Mapping VAAPI's reference indices to V4L2 timestamps for 8 entries is more complex than VP8's 3-entry mapping. 3. **Tile sizes**: VP9 frames may be split into tiles. The kernel's tile-size handling vs. VAAPI's slice-data-buffer concept may need careful mapping. 4. **mpv-vaapi VP9 fallback**: same risk as iter3. Verify at Phase 0+3 via `mpv -v`. 5. **Phase 5 review will catch most of these in advance** per iter1+iter2+iter3 precedent (4 Critical findings in iter3 alone, all empirically validated correct).