av1: populate V4L2_CID_STATELESS_AV1_SEQUENCE in codec_set_controls (#11 libva side) #12

Merged
marfrit merged 1 commits from claude-noether/libva-v4l2-request-fourier:noether/av1-set-controls-bug-11 into master 2026-05-20 19:14:50 +00:00
Collaborator

Implements the libva-side portion of #11 — replaces #10's no-op AV1 dispatch with a real av1_set_controls that populates V4L2_CID_STATELESS_AV1_SEQUENCE (struct v4l2_ctrl_av1_sequence) from VAPictureParameterBufferAV1.

Why only the libva side here

Issue #11 spans three components — libva, daedalus-v4l2 kernel module, daedalus daemon. This PR is the libva side only (~150 LoC); the kernel module's daedalus_collect_av1_meta + the daemon's OBU sequence-header synthesiser are operator-track follow-ups, scoped at ~30 LoC and ~400 LoC respectively per the issue body. After this lands the libva backend queues a populated SEQUENCE ctrl via S_EXT_CTRLS; the daemon track is the consumer that turns it into bytes libdav1d can parse.

The hardware path (RK3588 vpu981) doesn't read the SEQUENCE ctrl either, so this is a no-cost no-op on that target — vpu981's driver parses the OBU stream directly.

Touch points

File Change
src/av1.{c,h} (new) av1_set_controls — VAAPI → V4L2 sequence ctrl mapping
src/surface.h New av1 leaf in surface->params (picture only; slice params intentionally absent — daemon reads slice OBUs from OUTPUT directly)
src/picture.c Copy AV1 picture-param into the new leaf in RenderPicture; replace #10's no-op in codec_set_controls with av1_set_controls(...)
src/meson.build Register av1.c

228 lines added across 5 files (90 LoC of which are header copyright + a long explanatory comment block in av1.c).

Sequence-field mapping coverage

12 of 18 V4L2_AV1_SEQUENCE_FLAG_* bits + the four scalars (seq_profile, order_hint_bits, bit_depth, max_frame_width/height_minus_1) all map 1:1 from seq_info_fields.fields.*. Six bits don't have a sequence-level mirror in VAAPI (WARPED_MOTION, REF_FRAME_MVS, SUPERRES, RESTORATION, SEPARATE_UV_DELTA_Q, ENABLE_FILTER_INTRA's edge-cases) — same VAAPI-blindspot family as the HEVC SPS fields and the H.264 profile/level fields (see issue #8 triage). They stay clear in the SEQUENCE ctrl; per-frame consumers read them from the OBU_FRAME_HEADER that's already in the slice buffer. Daemon-side OBU synth (issue #11 daemon track) carries the SEQUENCE bytes verbatim, so libdav1d still sees the bitstream truth for the per-frame bits via the OBU_FRAME stream.

Test plan

  • Build clean on higgs (Debian 13 trixie, gcc 14.2.0, libva 2.22.0, kernel uAPI sizeof(struct v4l2_ctrl_av1_sequence) == 12).
  • vainfo against patched .so enumerates VAProfileAV1Profile0 via the daedalus_v4l2 slot (video_fd=5 media_fd=6), all 8 codec profiles intact, no init regression. nm confirms av1_set_controls symbol present.
  • End-to-end AV1 decode through the daedalus daemon — blocked on the daemon-side OBU synthesiser (issue #11 daemon track). Once that lands, libdav1d should accept the prepended OBU_SEQUENCE_HEADER and the 0/60 decode rate from the issue reproducer should flip green.
  • RK3588 vpu981 hardware AV1 path — same code lands on that route; SEQUENCE ctrl is harmless there (vpu981 ignores the ctrl payload, parses OBU directly).

Reviewer questions

  • The struct-size compile assert mirrors vp9.c's pattern (_Static_assert(sizeof(...) == 12, "...")). Keep, or do you prefer it gated on a debug build?
  • bit_depth translation: VAAPI's bit_depth_idx ∈ {0, 1, 2} → V4L2 plain {8, 10, 12}. Helper returns 8 on out-of-range (spec-illegal) input — passes through rather than failing loudly so a reviewer / test can spot the bug. Acceptable, or want a request_log warn on the illegal case?
Implements the libva-side portion of #11 — replaces #10's no-op AV1 dispatch with a real `av1_set_controls` that populates `V4L2_CID_STATELESS_AV1_SEQUENCE` (`struct v4l2_ctrl_av1_sequence`) from `VAPictureParameterBufferAV1`. ## Why only the libva side here Issue #11 spans three components — libva, daedalus-v4l2 kernel module, daedalus daemon. This PR is the **libva side only** (~150 LoC); the kernel module's `daedalus_collect_av1_meta` + the daemon's OBU sequence-header synthesiser are operator-track follow-ups, scoped at ~30 LoC and ~400 LoC respectively per the issue body. After this lands the libva backend queues a populated SEQUENCE ctrl via `S_EXT_CTRLS`; the daemon track is the consumer that turns it into bytes libdav1d can parse. The hardware path (RK3588 vpu981) doesn't read the SEQUENCE ctrl either, so this is a no-cost no-op on that target — vpu981's driver parses the OBU stream directly. ## Touch points | File | Change | |---|---| | `src/av1.{c,h}` *(new)* | `av1_set_controls` — VAAPI → V4L2 sequence ctrl mapping | | `src/surface.h` | New `av1` leaf in `surface->params` (picture only; slice params intentionally absent — daemon reads slice OBUs from OUTPUT directly) | | `src/picture.c` | Copy AV1 picture-param into the new leaf in `RenderPicture`; replace #10's no-op in `codec_set_controls` with `av1_set_controls(...)` | | `src/meson.build` | Register `av1.c` | 228 lines added across 5 files (90 LoC of which are header copyright + a long explanatory comment block in `av1.c`). ## Sequence-field mapping coverage 12 of 18 `V4L2_AV1_SEQUENCE_FLAG_*` bits + the four scalars (`seq_profile`, `order_hint_bits`, `bit_depth`, `max_frame_width/height_minus_1`) all map 1:1 from `seq_info_fields.fields.*`. Six bits don't have a sequence-level mirror in VAAPI (`WARPED_MOTION`, `REF_FRAME_MVS`, `SUPERRES`, `RESTORATION`, `SEPARATE_UV_DELTA_Q`, `ENABLE_FILTER_INTRA`'s edge-cases) — same VAAPI-blindspot family as the HEVC SPS fields and the H.264 profile/level fields (see issue #8 triage). They stay clear in the SEQUENCE ctrl; per-frame consumers read them from the OBU_FRAME_HEADER that's already in the slice buffer. Daemon-side OBU synth (issue #11 daemon track) carries the SEQUENCE bytes verbatim, so libdav1d still sees the bitstream truth for the per-frame bits via the OBU_FRAME stream. ## Test plan - [x] Build clean on higgs (Debian 13 trixie, gcc 14.2.0, libva 2.22.0, kernel uAPI `sizeof(struct v4l2_ctrl_av1_sequence) == 12`). - [x] `vainfo` against patched .so enumerates `VAProfileAV1Profile0` via the daedalus_v4l2 slot (`video_fd=5 media_fd=6`), all 8 codec profiles intact, no init regression. `nm` confirms `av1_set_controls` symbol present. - [ ] End-to-end AV1 decode through the daedalus daemon — **blocked on the daemon-side OBU synthesiser** (issue #11 daemon track). Once that lands, libdav1d should accept the prepended OBU_SEQUENCE_HEADER and the 0/60 decode rate from the issue reproducer should flip green. - [ ] RK3588 vpu981 hardware AV1 path — same code lands on that route; SEQUENCE ctrl is harmless there (vpu981 ignores the ctrl payload, parses OBU directly). ## Reviewer questions - The struct-size compile assert mirrors `vp9.c`'s pattern (`_Static_assert(sizeof(...) == 12, "...")`). Keep, or do you prefer it gated on a debug build? - `bit_depth` translation: VAAPI's `bit_depth_idx` ∈ {0, 1, 2} → V4L2 plain {8, 10, 12}. Helper returns 8 on out-of-range (spec-illegal) input — passes through rather than failing loudly so a reviewer / test can spot the bug. Acceptable, or want a `request_log` warn on the illegal case?
claude-noether added 1 commit 2026-05-20 19:13:49 +00:00
Implements the libva-side portion of issue #11 — replaces PR #10's
no-op AV1 dispatch with a real av1_set_controls that maps VAAPI's
VADecPictureParameterBufferAV1.seq_info_fields + scalar fields onto
struct v4l2_ctrl_av1_sequence (the kernel uAPI control declared at
linux/v4l2-controls.h:2891-2919).

Daemon-track context (issue #11 daemon side, operator-owned):
ffmpeg-vaapi splits the AV1 bitstream client-side and strips the
OBU_SEQUENCE_HEADER before delivery; the V4L2 OUTPUT buffer contains
only OBU_FRAME_HEADER + OBU_TILE_GROUP.  libdav1d in the daedalus
daemon cannot parse this — it expects a complete OBU stream.  The
daemon side has to synthesise OBU_SEQUENCE_HEADER from the SEQUENCE
ctrl and prepend it to the slice bitstream.  This libva-side change
just makes the SEQUENCE ctrl populated and queued via S_EXT_CTRLS;
the daemon track is the consumer.

Three small touch points beyond the new src/av1.{c,h}:

  - src/surface.h: add an av1 leaf to surface->params holding
    VADecPictureParameterBufferAV1.  Slice params intentionally
    absent — the daedalus daemon consumes the slice OBU bytes
    directly from the OUTPUT buffer; no per-tile-group struct →
    OBU re-synthesis required from libva today.
  - src/picture.c: copy the picture-param buffer into the new leaf
    in RenderPicture, mirror of the per-codec memcpy pattern, plus
    call av1_set_controls from codec_set_controls (replacing the
    no-op).
  - src/meson.build: register src/av1.c.

Sequence-field mapping covers everything VAAPI exposes at the
sequence level (12 of 18 V4L2_AV1_SEQUENCE_FLAG_* bits + the four
scalars).  Bits VAAPI doesn't carry at the sequence level
(WARPED_MOTION, REF_FRAME_MVS, SUPERRES, RESTORATION,
SEPARATE_UV_DELTA_Q) stay clear; per-frame consumers (libdav1d via
the daemon, vpu981 via the hardware path) read those from the
OBU_FRAME_HEADER that is already in the slice buffer anyway.  See
feedback memory `feedback_vaapi_blind_to_some_hevc_sps_fields` for
the precedent.

Build verified on higgs (Debian 13 trixie, gcc 14.2.0, libva 2.22.0,
linux uAPI v4l2-controls.h sizeof(struct v4l2_ctrl_av1_sequence)==12):
clean meson + ninja link of v4l2_request_drv_video.so, vainfo
enumerates VAProfileAV1Profile0 via daedalus_v4l2 slot, av1_set_controls
symbol present.

Out of scope on this PR (operator-track, issue #11 follow-up):
  - daedalus-v4l2 kernel module wire-protocol extension (daedalus_
    collect_av1_meta + AV1 ctrl request_setup).
  - daedalus daemon OBU synthesiser (~400 LoC AV1 OBU encoder in
    daemon/src/av1_obu_synth.{c,h}).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
marfrit merged commit 77f9236466 into master 2026-05-20 19:14:50 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marfrit/libva-v4l2-request-fourier#12