Files
daedalus-v4l2/daemon/src/av1_obu_synth.h
T
claude-noether 9797a0daa6 daemon: AV1 Frame Header OBU synthesiser + Temporal Delimiter
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).
2026-05-23 18:31:41 +02:00

120 lines
5.2 KiB
C

/* 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 Sequence Header (§5.5.1), Temporal Delimiter
* (§5.6), and Frame Header (§5.9) OBUs. 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);
/*
* Encode an AV1 Temporal Delimiter OBU into @out. Always exactly 2
* bytes: 0x12 (obu_type=TEMPORAL_DELIMITER, has_size_field=1) followed
* by 0x00 (leb128(0) — zero-payload). Returns 2 on success, 0 if
* @out_cap < 2.
*
* Per AV1 spec §5.6 every temporal unit MUST start with a Temporal
* Delimiter OBU when temporal_delimiter_obus_present is implied — the
* libavcodec AV1 parser uses TD OBUs as access-unit boundaries when
* fed full-bitstream input.
*/
size_t av1_synth_temporal_delimiter_obu(uint8_t *out, size_t out_cap);
/*
* Integration status (2026-05-23):
*
* The Sequence / Frame Header / Temporal Delimiter encoders below are
* standalone primitives. They are NOT yet called from decoder.c — the
* AV1 decode hot path still passes the OUTPUT buffer straight to
* libavcodec, which only works if the V4L2 consumer happens to be
* sending a fully-OBU'd access unit (i.e. is not strictly following
* the V4L2 stateless AV1 "tile-group bytes only" contract).
*
* Wiring these primitives in requires a separate kernel-side change:
*
* - extend daedalus_v4l2_proto.h with a `struct daedalus_av1_meta`
* mirroring v4l2_ctrl_av1_sequence + v4l2_ctrl_av1_frame
* - update kernel/daedalus_v4l2_main.c to capture
* V4L2_CID_STATELESS_AV1_{SEQUENCE,FRAME} at device_run time and
* ship the meta alongside the bitstream over the chardev
* - update daemon/src/chardev_client.c to receive the meta
* - update daemon/src/decoder.c to: synth TD + SH + FH OBUs, wrap
* the OUTPUT bytes as an OBU_TILE_GROUP, concat in that order,
* hand the assembled bitstream to libavcodec
*
* Tracked as a follow-on; see daedalus-v4l2 task notes.
*/
/*
* Encode an AV1 Frame Header OBU from the V4L2 stateless frame control
* (and the matching sequence control, which provides fields the
* frame-header encoder branches on per §5.9.1).
*
* Scope (this revision — libva-v4l2-request common-case path):
* - Frame types KEY / INTER / INTRA_ONLY. SWITCH frames return 0
* (caller should fall back to libavcodec native parsing).
* - segmentation_params() emits the "segmentation disabled" path
* when V4L2_AV1_SEGMENTATION_FLAG_ENABLED is 0. Enabled
* segmentation returns 0.
* - loop_restoration_params(): only RESTORE_NONE on all planes
* supported. Other restoration types return 0.
* - global_motion: only IDENTITY warp model emitted. Non-IDENTITY
* entries return 0.
* - film_grain_params(): treated as "not present" — only valid when
* the sequence header has film_grain_params_present = 0. If the
* sequence claims film grain is present this revision returns 0
* (the per-frame film-grain coding is a separate follow-on).
*
* Out-of-scope branches return 0 so the caller can surface a coverage
* warning and fall back to direct libavcodec parsing of the original
* bitstream where possible.
*
* @out_cap must be at least 128 bytes for any reasonable frame header;
* 256 bytes is a safe upper bound for the supported subset.
*/
size_t av1_synth_frame_header_obu(const struct v4l2_ctrl_av1_sequence *seq,
const struct v4l2_ctrl_av1_frame *fr,
uint8_t *out, size_t out_cap);
#endif /* DAEDALUS_AV1_OBU_SYNTH_H */