daemon: AV1 Sequence Header OBU synthesiser + unit test #22

Merged
marfrit merged 1 commits from noether/daemon-av1-obu-synth into main 2026-05-23 15:12:17 +00:00
Owner

Lands the Sequence Header OBU half of the AV1 daemon-side bitstream reconstruction. V4L2 stateless AV1 ships only tile-group bytes in the OUTPUT buffer and passes the sequence header as a structured control (V4L2_CID_STATELESS_AV1_SEQUENCE); libavcodec's AV1 decoder is full-bitstream, so the daemon needs to reconstruct the OBUs before handing the assembled stream to libavcodec.

Frame Header / Frame OBU synthesisers + the integration that wires the assembled OBUs into the decode hot path are separate follow-on modules. This PR is the first piece — module shape, public API, and bit-accurate unit test.

Module shape

Mirrors the H.264 NAL synthesiser (PR #1):

  • Single public function av1_synth_sequence_header_obu(seq, out, out_cap) returning byte count or 0 on overflow/invalid input.
  • Wire encoder uses the existing bitstream_writer (bsw_put_u is AV1's f(n); bsw_put_ue is bit-identical to AV1's uvlc; bsw_align_rbsp matches AV1's trailing_bits()).
  • AV1-specific helpers (leb128_put, min_bits_for, resolve_subsampling per §5.5.2) are file-local statics.
  • No emulation prevention — AV1 uses leb128-sized OBUs for bitstream boundaries, not byte-pattern escapes.

Synthesis decisions

Fields V4L2 doesn't carry get documented defaults (verbatim in the file header):

  • reduced_still_picture_header = 0
  • single operating point at seq_level_idx = 13 (level 5.1, supports up to 4K)
  • color_description_present_flag = 0
  • chroma_sample_position = 0 (CSP_UNKNOWN)
  • seq_choose_screen_detection_tools = 1, seq_choose_integer_mv = 1

Rejection cases

Returns 0 (loud failure) instead of emitting nonsense:

  • seq_profile > 2
  • bit_depth ∉ {8, 10, 12}
  • seq_profile = 1 + monochrome (spec forces 4:4:4 colour)
  • seq_profile = 1 + bit_depth = 12 (only profile 2 allows 12-bit)
  • max_frame_{width,height}_minus_1 requiring > 16 length bits
  • out_cap too small

Unit test

test_av1_obu_synth.c, opt-in via -DDAEDALUS_BUILD_TESTS=ON. Exercises four cases bit-by-bit against a hand-computed reference:

=== av1_synth_sequence_header_obu ===
  profile0 1080p 8-bit: OK (13 bytes)
  profile0 monochrome 10-bit: OK (12 bytes)
  reject profile1+12bit: OK
  out_cap overflow: OK
AV1 OBU synth tests PASSED

All green on hertz (aarch64 Arch, gcc -Wall -Wextra -Wpedantic clean). The MSB-first bit reader in the test mirrors bitstream_writer's emission semantics so any drift in either direction surfaces immediately.

Daemon behaviour

Unchanged. av1_obu_synth.c is compiled into the daemon binary so the symbols are reachable, but no call site is wired yet. Integration lands in the follow-on DAEMON-AV1 patches that also synthesise the Frame Header OBU and bracket the assembled OBUs with a Temporal Delimiter.

Refs reauktion/daedalus-v4l2#11 daemon-half; closes daedalus task #144.

Lands the Sequence Header OBU half of the AV1 daemon-side bitstream reconstruction. V4L2 stateless AV1 ships only tile-group bytes in the OUTPUT buffer and passes the sequence header as a structured control (`V4L2_CID_STATELESS_AV1_SEQUENCE`); libavcodec's AV1 decoder is full-bitstream, so the daemon needs to reconstruct the OBUs before handing the assembled stream to libavcodec. Frame Header / Frame OBU synthesisers + the integration that wires the assembled OBUs into the decode hot path are separate follow-on modules. This PR is the first piece — module shape, public API, and bit-accurate unit test. ## Module shape Mirrors the H.264 NAL synthesiser (PR #1): - Single public function `av1_synth_sequence_header_obu(seq, out, out_cap)` returning byte count or 0 on overflow/invalid input. - Wire encoder uses the existing `bitstream_writer` (`bsw_put_u` is AV1's `f(n)`; `bsw_put_ue` is bit-identical to AV1's `uvlc`; `bsw_align_rbsp` matches AV1's `trailing_bits()`). - AV1-specific helpers (`leb128_put`, `min_bits_for`, `resolve_subsampling` per §5.5.2) are file-local statics. - **No emulation prevention** — AV1 uses leb128-sized OBUs for bitstream boundaries, not byte-pattern escapes. ## Synthesis decisions Fields V4L2 doesn't carry get documented defaults (verbatim in the file header): - `reduced_still_picture_header = 0` - single operating point at `seq_level_idx = 13` (level 5.1, supports up to 4K) - `color_description_present_flag = 0` - `chroma_sample_position = 0` (CSP_UNKNOWN) - `seq_choose_screen_detection_tools = 1`, `seq_choose_integer_mv = 1` ## Rejection cases Returns 0 (loud failure) instead of emitting nonsense: - `seq_profile > 2` - `bit_depth ∉ {8, 10, 12}` - `seq_profile = 1` + monochrome (spec forces 4:4:4 colour) - `seq_profile = 1` + `bit_depth = 12` (only profile 2 allows 12-bit) - `max_frame_{width,height}_minus_1` requiring > 16 length bits - `out_cap` too small ## Unit test `test_av1_obu_synth.c`, opt-in via `-DDAEDALUS_BUILD_TESTS=ON`. Exercises four cases bit-by-bit against a hand-computed reference: ``` === av1_synth_sequence_header_obu === profile0 1080p 8-bit: OK (13 bytes) profile0 monochrome 10-bit: OK (12 bytes) reject profile1+12bit: OK out_cap overflow: OK AV1 OBU synth tests PASSED ``` All green on hertz (aarch64 Arch, gcc -Wall -Wextra -Wpedantic clean). The MSB-first bit reader in the test mirrors `bitstream_writer`'s emission semantics so any drift in either direction surfaces immediately. ## Daemon behaviour **Unchanged.** `av1_obu_synth.c` is compiled into the daemon binary so the symbols are reachable, but no call site is wired yet. Integration lands in the follow-on DAEMON-AV1 patches that also synthesise the Frame Header OBU and bracket the assembled OBUs with a Temporal Delimiter. Refs reauktion/daedalus-v4l2#11 daemon-half; closes daedalus task #144.
marfrit added 1 commit 2026-05-23 13:41:37 +00:00
V4L2 stateless AV1 passes the sequence header information as a
structured control (V4L2_CID_STATELESS_AV1_SEQUENCE) and ships
only tile-group bytes in the OUTPUT buffer.  libavcodec's AV1
decoder is full-bitstream, so the daemon needs to reconstruct
the OBU bytes the consumer parsed out before feeding the
assembled stream to libavcodec.

This commit lands the Sequence Header OBU half of that
reconstruction — av1_synth_sequence_header_obu().  Frame
Header / Frame OBU synthesisers + the integration that wires
the assembled OBUs into the decode hot path are separate
follow-on modules.

Module shape mirrors the H.264 NAL synthesiser (PR #1):

  - Public API: single function returning byte count or 0
    on overflow/invalid input.
  - Wire encoder uses the existing bitstream_writer (bsw_put_u
    is AV1's f(n); bsw_put_ue is bit-identical to AV1's uvlc;
    bsw_align_rbsp matches AV1's trailing_bits()).
  - AV1-specific helpers (leb128 size, min_bits_for, subsampling
    resolution per §5.5.2) are file-local statics.
  - No emulation prevention — AV1 uses leb128-sized OBUs for
    bitstream boundaries, not byte-pattern escapes.

Synthesis decisions for fields V4L2 doesn't carry are documented
verbatim in the file header (reduced_still_picture_header = 0;
single operating point at seq_level_idx = 13 / level 5.1;
color_description_present_flag = 0; chroma_sample_position = 0;
seq_choose_screen_detection_tools = 1; seq_choose_integer_mv = 1).

Rejection cases:
  - seq_profile > 2
  - bit_depth not in {8, 10, 12}
  - seq_profile = 1 + monochrome (4:4:4 forced colour)
  - seq_profile = 1 + bit_depth = 12 (only profile 2 allows it)
  - max_frame_{width,height}_minus_1 requiring > 16 length bits
  - out_cap too small to hold header + leb128 + payload

Each returns 0 to surface the mismatch loudly rather than emit
nonsense the libavcodec parser would reject downstream.

Unit test (test_av1_obu_synth.c, opt-in via DAEDALUS_BUILD_TESTS=ON)
exercises four cases bit-by-bit against a hand-computed reference:

  1. profile 0, 1080p, 8-bit, 4:2:0, order_hint on (7 bits),
     CDEF+restoration on — the common Pi 5 path.
  2. profile 0, 720p, 10-bit, monochrome — exercises high_bitdepth
     and the monochrome short-form color_config.
  3. profile 1 + bit_depth 12 → expects 0 (rejected).
  4. tiny out_cap → expects 0 (overflow).

All four green on hertz (aarch64 Arch, gcc Wall+Wextra+Wpedantic
clean).

This commit does not change daemon behaviour — av1_obu_synth.c is
built into the daemon binary so the symbols are reachable, but
no call site is wired yet.  Integration goes in the follow-on
DAEMON-AV1 patches that also synthesise the Frame Header OBU
and bracket the assembled OBUs with a Temporal Delimiter.

Refs reauktion/daedalus-v4l2#11 daemon-half; closes daedalus
backlog task #144.
marfrit merged commit 3a8f5405d4 into main 2026-05-23 15:12:17 +00:00
marfrit deleted branch noether/daemon-av1-obu-synth 2026-05-23 15:12:17 +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#22