daemon: AV1 Sequence Header OBU synthesiser + unit test

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.
This commit is contained in:
2026-05-23 15:41:07 +02:00
parent 872eec505e
commit 1e9619afe8
4 changed files with 726 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
/* SPDX-License-Identifier: BSD-2-Clause */
/*
* av1_obu_synth.h — synthesise AV1 OBU bytes from the V4L2 stateless
* AV1 controls.
*
* V4L2 stateless AV1 (per drivers/media/v4l2-core/v4l2-h264.c-style
* contract) passes the OUTPUT buffer as bare tile-group bitstream and
* the sequence / frame-header information as structured controls
* (V4L2_CID_STATELESS_AV1_SEQUENCE, V4L2_CID_STATELESS_AV1_FRAME, ...).
* libavcodec's AV1 decoder is full-bitstream, so the daemon has to
* reconstruct the OBUs that the consumer parsed out and prepend them
* to the tile-group bytes before handing the assembled stream to
* libavcodec.
*
* This header covers the Sequence Header OBU (AV1 spec §5.5.1).
* The Frame Header / Frame OBU synthesisers are separate modules
* (follow-on tasks); they all share the same wire conventions:
* - No emulation prevention (AV1 uses leb128 sized fields instead).
* - obu_has_size_field = 1 in the OBU header byte.
* - obu_extension_flag = 0 (no temporal_id / spatial_id encoding).
* - trailing_bits() finalises the payload to a byte boundary the same
* way H.264's rbsp_trailing_bits does — bsw_align_rbsp covers it.
*
* Synthesis decisions for fields V4L2 doesn't carry are documented in
* the .c file (search for "synthesis default").
*/
#ifndef DAEDALUS_AV1_OBU_SYNTH_H
#define DAEDALUS_AV1_OBU_SYNTH_H
#include <stddef.h>
#include <stdint.h>
#include <linux/v4l2-controls.h>
/*
* Encode an AV1 Sequence Header OBU (header byte + leb128 size + RBSP)
* into @out. Returns total bytes written, or 0 on overflow / malformed
* input (e.g. inconsistent bit_depth vs seq_profile). @out_cap must
* be at least 32 bytes for any reasonable sequence header; 64 bytes
* is a generous upper bound.
*
* The caller is expected to bracket the resulting bytes with a
* Temporal Delimiter OBU (1 byte: 0x12 0x00) before any Frame OBU so
* that libavcodec's AV1 parser sees a well-formed access unit; the
* temporal-delimiter byte is trivial and not produced here.
*/
size_t av1_synth_sequence_header_obu(const struct v4l2_ctrl_av1_sequence *seq,
uint8_t *out, size_t out_cap);
#endif /* DAEDALUS_AV1_OBU_SYNTH_H */