daemon: AV1 Frame Header OBU synthesiser + Temporal Delimiter #24

Merged
marfrit merged 1 commits from noether/daemon-av1-frame-header-obu into main 2026-05-23 17:16:28 +00:00
Owner

Phase 2 of the AV1 OBU encoder pack (PR #22 landed Phase 1 — the Sequence Header). Adds the two remaining pieces of the per-frame OBU assembly:

  • av1_synth_temporal_delimiter_obu() — trivial 2-byte OBU that AV1 temporal units must start with so libavcodec's parser detects access-unit boundaries.
  • av1_synth_frame_header_obu() — encodes a Frame Header OBU (AV1 §5.9) from V4L2_CID_STATELESS_AV1_SEQUENCE + V4L2_CID_STATELESS_AV1_FRAME controls.

Frame Header scope (libva-v4l2-request common-case path)

Section Coverage
frame_type KEY / INTER / INTRA_ONLY supported; SWITCH returns 0
tile_info single-tile uniform-spacing only
quantization_params full (base_q_idx, delta_q_*, qmatrix)
loop_filter_params full (levels, sharpness, ref/mode deltas)
cdef_params full
segmentation_params enabled=0 only; returns 0 if enabled
loop_restoration RESTORE_NONE only; returns 0 otherwise
global_motion IDENTITY warp only; returns 0 otherwise
film_grain_params not-present only; returns 0 if seq sets FILM_GRAIN_PARAMS_PRESENT

Out-of-scope branches return 0 so a future decoder.c integration can surface coverage warnings and fall back to direct libavcodec parsing where the consumer happens to ship a fully-OBU'd access unit.

Integration status (NOT in this PR)

The new primitives are not yet wired into decoder.c. The AV1 decode hot path still passes the OUTPUT buffer straight to libavcodec — which works only when the V4L2 consumer is sending a fully-OBU'd access unit, not strictly the V4L2 stateless contract.

A real wiring needs a separate kernel-side change:

  1. daedalus_v4l2_proto.h: add struct daedalus_av1_meta mirroring v4l2_ctrl_av1_sequence + v4l2_ctrl_av1_frame.
  2. kernel/daedalus_v4l2_main.c: capture V4L2_CID_STATELESS_AV1_{SEQUENCE,FRAME} at device_run, forward over the chardev.
  3. daemon/src/chardev_client.c: receive meta.
  4. daemon/src/decoder.c: assemble TD + SH + FH + OBU_TILE_GROUP-wrapped OUTPUT bytes, send to libavcodec.

Tracked as a follow-on. This PR ships the encoder primitives; the wiring lands later once the kernel-side meta forwarding is in place.

Tests

test_av1_obu_synth.c grows 5 new cases (9 total, all green on hertz):

=== av1_synth_temporal_delimiter_obu ===
  temporal delimiter: OK
=== av1_synth_frame_header_obu ===
  KEY frame 1080p: OK (13 bytes)
  INTER frame: OK (18 bytes)
  SWITCH frame rejected: OK
  segmentation enabled rejected: OK
AV1 OBU synth tests PASSED

Bit-walk of the KEY-frame happy path confirms the OBU envelope (obu_type=3 = FRAME_HEADER, has_size_field=1, leb128 size byte), then steps through show_existing_frame, frame_type, show_frame, disable_cdf_update, allow_screen_content_tools. Build clean on hertz (Pi 5, Debian trixie, 6.18.29+rpt-rpi-2712, gcc -Wall -Wextra -Wpedantic). No new warnings.

Closes task #159.

Phase 2 of the AV1 OBU encoder pack (PR #22 landed Phase 1 — the Sequence Header). Adds the two remaining pieces of the per-frame OBU assembly: - `av1_synth_temporal_delimiter_obu()` — trivial 2-byte OBU that AV1 temporal units must start with so libavcodec's parser detects access-unit boundaries. - `av1_synth_frame_header_obu()` — encodes a Frame Header OBU (AV1 §5.9) from `V4L2_CID_STATELESS_AV1_SEQUENCE` + `V4L2_CID_STATELESS_AV1_FRAME` controls. ## Frame Header scope (libva-v4l2-request common-case path) | Section | Coverage | |---|---| | frame_type | KEY / INTER / INTRA_ONLY supported; SWITCH returns 0 | | tile_info | single-tile uniform-spacing only | | quantization_params | full (base_q_idx, delta_q_*, qmatrix) | | loop_filter_params | full (levels, sharpness, ref/mode deltas) | | cdef_params | full | | segmentation_params | enabled=0 only; returns 0 if enabled | | loop_restoration | RESTORE_NONE only; returns 0 otherwise | | global_motion | IDENTITY warp only; returns 0 otherwise | | film_grain_params | not-present only; returns 0 if seq sets FILM_GRAIN_PARAMS_PRESENT | Out-of-scope branches return 0 so a future `decoder.c` integration can surface coverage warnings and fall back to direct libavcodec parsing where the consumer happens to ship a fully-OBU'd access unit. ## Integration status (NOT in this PR) The new primitives are **not** yet wired into `decoder.c`. The AV1 decode hot path still passes the OUTPUT buffer straight to libavcodec — which works only when the V4L2 consumer is sending a fully-OBU'd access unit, not strictly the V4L2 stateless contract. A real wiring needs a separate kernel-side change: 1. `daedalus_v4l2_proto.h`: add `struct daedalus_av1_meta` mirroring `v4l2_ctrl_av1_sequence + v4l2_ctrl_av1_frame`. 2. `kernel/daedalus_v4l2_main.c`: capture `V4L2_CID_STATELESS_AV1_{SEQUENCE,FRAME}` at `device_run`, forward over the chardev. 3. `daemon/src/chardev_client.c`: receive meta. 4. `daemon/src/decoder.c`: assemble `TD + SH + FH + OBU_TILE_GROUP`-wrapped OUTPUT bytes, send to libavcodec. Tracked as a follow-on. This PR ships the encoder primitives; the wiring lands later once the kernel-side meta forwarding is in place. ## Tests `test_av1_obu_synth.c` grows 5 new cases (9 total, all green on hertz): ``` === av1_synth_temporal_delimiter_obu === temporal delimiter: OK === av1_synth_frame_header_obu === KEY frame 1080p: OK (13 bytes) INTER frame: OK (18 bytes) SWITCH frame rejected: OK segmentation enabled rejected: OK AV1 OBU synth tests PASSED ``` Bit-walk of the KEY-frame happy path confirms the OBU envelope (`obu_type=3 = FRAME_HEADER`, `has_size_field=1`, leb128 size byte), then steps through `show_existing_frame`, `frame_type`, `show_frame`, `disable_cdf_update`, `allow_screen_content_tools`. Build clean on hertz (Pi 5, Debian trixie, 6.18.29+rpt-rpi-2712, `gcc -Wall -Wextra -Wpedantic`). No new warnings. Closes task #159.
marfrit added 1 commit 2026-05-23 16:32:07 +00:00
Extends the AV1 OBU encoder pack (PR #22 landed the Sequence Header
half) with the two remaining pieces of the per-frame OBU assembly:

  - av1_synth_temporal_delimiter_obu() — trivial 2-byte OBU (0x12,
    0x00) that AV1 temporal units must start with so libavcodec's
    parser can detect access-unit boundaries.

  - av1_synth_frame_header_obu() — encodes a Frame Header OBU (AV1
    §5.9) from V4L2_CID_STATELESS_AV1_SEQUENCE + V4L2_CID_STATELESS_
    AV1_FRAME controls.

## Frame Header scope

The encoder covers the libva-v4l2-request common-case path:

  - frame_type: KEY / INTER / INTRA_ONLY supported.  SWITCH returns 0.
  - tile_info: single-tile uniform-spacing only (forced
    tile_cols_log2 = tile_rows_log2 = 0).
  - quantization_params: full coverage (base_q_idx, delta_q_*, qmatrix).
  - loop_filter_params: full coverage (levels, sharpness, ref/mode deltas).
  - cdef_params: full coverage.
  - segmentation: only enabled=0 path supported (returns 0 if enabled).
  - loop_restoration: only RESTORE_NONE supported (returns 0 if
    any plane uses Wiener / SGRPROJ / SWITCHABLE).
  - global_motion: only IDENTITY warp model emitted (returns 0 if
    any ref uses ROTZOOM / AFFINE / TRANSLATION).
  - film_grain_params: only "not present" path — returns 0 if the
    sequence header has FILM_GRAIN_PARAMS_PRESENT set.

Out-of-scope branches return 0 so a future decoder.c integration can
surface a coverage warning and fall back to direct libavcodec
parsing of the original bitstream where the consumer happens to
ship a fully-OBU'd access unit.

## Integration status

The new primitives are NOT yet wired into decoder.c.  The AV1 decode
hot path still passes the OUTPUT buffer straight to libavcodec,
which works only when the V4L2 consumer is sending a fully-OBU'd
access unit (not strictly the V4L2 stateless contract).

A real wiring needs a separate kernel-side change:
  - daedalus_v4l2_proto.h: add struct daedalus_av1_meta mirroring
    v4l2_ctrl_av1_sequence + v4l2_ctrl_av1_frame
  - kernel/daedalus_v4l2_main.c: capture V4L2_CID_STATELESS_AV1_{SEQUENCE,
    FRAME} at device_run, ship over the chardev
  - daemon/src/chardev_client.c: receive meta
  - daemon/src/decoder.c: assemble TD + SH + FH + OBU_TILE_GROUP-wrapped
    OUTPUT bytes, send to libavcodec

Tracked as a follow-on.

## Tests

test_av1_obu_synth.c grows 5 new cases (9 total, all green on hertz):

  === av1_synth_temporal_delimiter_obu ===
    temporal delimiter: OK
  === av1_synth_frame_header_obu ===
    KEY frame 1080p: OK (13 bytes)
    INTER frame: OK (18 bytes)
    SWITCH frame rejected: OK
    segmentation enabled rejected: OK
  AV1 OBU synth tests PASSED

Bit-walk of the KEY-frame happy path confirms the OBU envelope
(obu_type=3 = FRAME_HEADER, has_size_field=1, leb128 size byte),
then steps through show_existing_frame, frame_type, show_frame,
disable_cdf_update, allow_screen_content_tools.  Fuller bit-walks
would tie the test to encoder details that are spec-driven and
already linear in the source; structural smoke + spec-driven
linearity is the right gate.

Build clean on hertz (Pi 5, Debian trixie, 6.18.29+rpt-rpi-2712,
gcc -Wall -Wextra -Wpedantic).  No new warnings.

Closes daedalus backlog task #159 (AV1 Frame Header OBU synthesiser;
decoder.c integration deferred per task notes above).
marfrit merged commit 5d1ff51178 into main 2026-05-23 17:16:28 +00:00
marfrit deleted branch noether/daemon-av1-frame-header-obu 2026-05-23 17:16:28 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: reauktion/daedalus-v4l2#24