Merge pull request 'daemon: AV1 Frame Header OBU synthesiser + Temporal Delimiter' (#24) from noether/daemon-av1-frame-header-obu into main
Reviewed-on: #24
This commit was merged in pull request #24.
This commit is contained in:
@@ -40,6 +40,19 @@
|
||||
#include <string.h>
|
||||
|
||||
#define OBU_SEQUENCE_HEADER 1
|
||||
#define OBU_TEMPORAL_DELIMITER 2
|
||||
#define OBU_FRAME_HEADER 3
|
||||
#define OBU_TILE_GROUP 4
|
||||
#define OBU_FRAME 6
|
||||
|
||||
/* AV1 §3 ref-frame symbolic constants — values per the spec table.
|
||||
* INTRA_FRAME is index 0 (used for intra-only); LAST_FRAME..ALTREF_FRAME
|
||||
* are 1..7. TOTAL_REFS_PER_FRAME = 8 (V4L2 mirrors this). */
|
||||
#define AV1_INTRA_FRAME 0
|
||||
#define AV1_LAST_FRAME 1
|
||||
#define AV1_NUM_REF_FRAMES 8 /* the DPB size */
|
||||
#define AV1_REFS_PER_FRAME 7 /* refs available to an inter frame */
|
||||
#define AV1_PRIMARY_REF_NONE 7
|
||||
|
||||
/* Default operating-point level: 5.1 — supports any frame size up to
|
||||
* 4K@60fps. Well past anything the V4L2 path is realistically driven
|
||||
@@ -325,3 +338,560 @@ size_t av1_synth_sequence_header_obu(const struct v4l2_ctrl_av1_sequence *seq,
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Shared OBU wrap helper (header byte + leb128 size + payload). Used
|
||||
* by frame_header_obu and the temporal_delimiter helper; the sequence
|
||||
* header above predates this factor-out and keeps its inline
|
||||
* assembly so its memory footprint stays predictable.
|
||||
* ----------------------------------------------------------------- */
|
||||
static size_t wrap_obu(uint8_t obu_type, const uint8_t *payload,
|
||||
size_t payload_len, uint8_t *out, size_t out_cap)
|
||||
{
|
||||
size_t w = 0;
|
||||
|
||||
if (out_cap < 2)
|
||||
return 0;
|
||||
out[w++] = (uint8_t)(
|
||||
(0u << 7) |
|
||||
((obu_type & 0xfu) << 3) |
|
||||
(0u << 2) |
|
||||
(1u << 1) |
|
||||
(0u << 0));
|
||||
|
||||
{
|
||||
size_t leb_n = leb128_put((uint32_t) payload_len,
|
||||
out + w, out_cap - w);
|
||||
if (leb_n == 0)
|
||||
return 0;
|
||||
w += leb_n;
|
||||
}
|
||||
|
||||
if (out_cap - w < payload_len)
|
||||
return 0;
|
||||
if (payload_len)
|
||||
memcpy(out + w, payload, payload_len);
|
||||
w += payload_len;
|
||||
return w;
|
||||
}
|
||||
|
||||
size_t av1_synth_temporal_delimiter_obu(uint8_t *out, size_t out_cap)
|
||||
{
|
||||
return wrap_obu(OBU_TEMPORAL_DELIMITER, NULL, 0, out, out_cap);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------
|
||||
* Frame Header OBU — §5.9
|
||||
*
|
||||
* The encoder is sectioned to mirror the spec. Each subsection
|
||||
* helper writes into the shared bs_writer and signals "out of
|
||||
* scope" by setting a sticky `*unsupported` flag that the top-level
|
||||
* checks before returning. This keeps the spec-mirror linear and
|
||||
* the failure modes diagnosable.
|
||||
* ----------------------------------------------------------------- */
|
||||
|
||||
/* MiCols / MiRows per spec §3 — 4x4-unit count, rounded up to the
|
||||
* 8x8 alignment the spec uses for tiling math. Returns AlignPow2
|
||||
* of ((dim + 7) >> 3) at miSize=2 (8x8 mi-block). */
|
||||
static uint32_t mi_cols_for(uint32_t frame_width)
|
||||
{
|
||||
uint32_t mi = (frame_width + 7u) >> 3;
|
||||
return mi << 1; /* 4x4 mi units == miCols */
|
||||
}
|
||||
static uint32_t mi_rows_for(uint32_t frame_height)
|
||||
{
|
||||
uint32_t mi = (frame_height + 7u) >> 3;
|
||||
return mi << 1;
|
||||
}
|
||||
|
||||
/* tile_log2(blkSize, target) per AV1 §5.9.15 — smallest k such that
|
||||
* (blkSize << k) >= target. */
|
||||
static int tile_log2_ge(int blk, int target)
|
||||
{
|
||||
int k = 0;
|
||||
while ((blk << k) < target) k++;
|
||||
return k;
|
||||
}
|
||||
|
||||
/* §5.9.12 quantization_params */
|
||||
static void write_quantization_params(struct bs_writer *bs,
|
||||
const struct v4l2_av1_quantization *q,
|
||||
bool num_planes_gt_1,
|
||||
bool separate_uv_delta_q)
|
||||
{
|
||||
bsw_put_u(bs, q->base_q_idx, 8);
|
||||
|
||||
/* read_delta_q: 1 bit "delta_coded" + (s(7)?) — we always emit
|
||||
* the full delta if non-zero, zero-encoded as delta_coded=0
|
||||
* (single bit). */
|
||||
#define EMIT_DELTA_Q(val) do { \
|
||||
int _v = (int8_t)(val); \
|
||||
if (_v != 0) { \
|
||||
bsw_put_u(bs, 1u, 1); \
|
||||
/* su(1+6): sign + 6-bit magnitude */ \
|
||||
if (_v < 0) { \
|
||||
bsw_put_u(bs, (uint32_t)(_v + 128) & 0x7fu, 7); \
|
||||
} else { \
|
||||
bsw_put_u(bs, (uint32_t)_v & 0x7fu, 7); \
|
||||
} \
|
||||
} else { \
|
||||
bsw_put_u(bs, 0u, 1); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
EMIT_DELTA_Q(q->delta_q_y_dc);
|
||||
|
||||
if (num_planes_gt_1) {
|
||||
if (separate_uv_delta_q)
|
||||
bsw_put_u(bs,
|
||||
(q->flags & V4L2_AV1_QUANTIZATION_FLAG_DIFF_UV_DELTA) ? 1u : 0u,
|
||||
1);
|
||||
EMIT_DELTA_Q(q->delta_q_u_dc);
|
||||
EMIT_DELTA_Q(q->delta_q_u_ac);
|
||||
if (separate_uv_delta_q &&
|
||||
(q->flags & V4L2_AV1_QUANTIZATION_FLAG_DIFF_UV_DELTA)) {
|
||||
EMIT_DELTA_Q(q->delta_q_v_dc);
|
||||
EMIT_DELTA_Q(q->delta_q_v_ac);
|
||||
}
|
||||
}
|
||||
#undef EMIT_DELTA_Q
|
||||
|
||||
bsw_put_u(bs,
|
||||
(q->flags & V4L2_AV1_QUANTIZATION_FLAG_USING_QMATRIX) ? 1u : 0u,
|
||||
1);
|
||||
if (q->flags & V4L2_AV1_QUANTIZATION_FLAG_USING_QMATRIX) {
|
||||
bsw_put_u(bs, q->qm_y, 4);
|
||||
bsw_put_u(bs, q->qm_u, 4);
|
||||
if (num_planes_gt_1 && separate_uv_delta_q)
|
||||
bsw_put_u(bs, q->qm_v, 4);
|
||||
}
|
||||
}
|
||||
|
||||
/* §5.9.11 loop_filter_params */
|
||||
static void write_loop_filter_params(struct bs_writer *bs,
|
||||
const struct v4l2_av1_loop_filter *lf,
|
||||
bool num_planes_gt_1,
|
||||
bool coded_lossless_or_allow_intrabc)
|
||||
{
|
||||
if (coded_lossless_or_allow_intrabc) {
|
||||
/* spec §6.8.10: when CodedLossless or allow_intrabc is set,
|
||||
* loop filter levels are inferred and not coded. */
|
||||
return;
|
||||
}
|
||||
bsw_put_u(bs, lf->level[0], 6);
|
||||
bsw_put_u(bs, lf->level[1], 6);
|
||||
if (num_planes_gt_1) {
|
||||
if (lf->level[0] || lf->level[1]) {
|
||||
bsw_put_u(bs, lf->level[2], 6);
|
||||
bsw_put_u(bs, lf->level[3], 6);
|
||||
}
|
||||
}
|
||||
bsw_put_u(bs, lf->sharpness, 3);
|
||||
|
||||
/* loop_filter_delta_enabled */
|
||||
bool delta_en = !!(lf->flags & V4L2_AV1_LOOP_FILTER_FLAG_DELTA_ENABLED);
|
||||
bsw_put_u(bs, delta_en ? 1u : 0u, 1);
|
||||
if (delta_en) {
|
||||
bool delta_upd = !!(lf->flags & V4L2_AV1_LOOP_FILTER_FLAG_DELTA_UPDATE);
|
||||
bsw_put_u(bs, delta_upd ? 1u : 0u, 1);
|
||||
if (delta_upd) {
|
||||
int i;
|
||||
for (i = 0; i < 8; i++) {
|
||||
/* update_ref_delta: emit 0 (no update) — V4L2 carries the
|
||||
* cumulative state; trying to differentially encode here
|
||||
* would need previous-frame state we don't track. */
|
||||
bsw_put_u(bs, 0u, 1);
|
||||
}
|
||||
for (i = 0; i < 2; i++)
|
||||
bsw_put_u(bs, 0u, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* §5.9.19 cdef_params */
|
||||
static void write_cdef_params(struct bs_writer *bs,
|
||||
const struct v4l2_av1_cdef *cdef,
|
||||
bool num_planes_gt_1,
|
||||
bool enable_cdef,
|
||||
bool coded_lossless_or_intrabc)
|
||||
{
|
||||
int i, n;
|
||||
if (!enable_cdef || coded_lossless_or_intrabc)
|
||||
return;
|
||||
bsw_put_u(bs, cdef->damping_minus_3, 2);
|
||||
bsw_put_u(bs, cdef->bits, 2);
|
||||
n = 1 << cdef->bits;
|
||||
for (i = 0; i < n; i++) {
|
||||
bsw_put_u(bs, cdef->y_pri_strength[i] & 0xfu, 4);
|
||||
bsw_put_u(bs, cdef->y_sec_strength[i] & 0x3u, 2);
|
||||
if (num_planes_gt_1) {
|
||||
bsw_put_u(bs, cdef->uv_pri_strength[i] & 0xfu, 4);
|
||||
bsw_put_u(bs, cdef->uv_sec_strength[i] & 0x3u, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* §5.9.20 lr_params — only RESTORE_NONE supported here */
|
||||
static int write_lr_params(struct bs_writer *bs,
|
||||
const struct v4l2_av1_loop_restoration *lr,
|
||||
int num_planes,
|
||||
bool enable_restoration,
|
||||
bool coded_lossless_or_intrabc)
|
||||
{
|
||||
int p;
|
||||
if (!enable_restoration || coded_lossless_or_intrabc)
|
||||
return 1;
|
||||
|
||||
/* Out-of-scope if ANY plane uses restoration */
|
||||
if (lr->frame_restoration_type[0] != V4L2_AV1_FRAME_RESTORE_NONE)
|
||||
return 0;
|
||||
if (num_planes > 1) {
|
||||
if (lr->frame_restoration_type[1] != V4L2_AV1_FRAME_RESTORE_NONE)
|
||||
return 0;
|
||||
if (lr->frame_restoration_type[2] != V4L2_AV1_FRAME_RESTORE_NONE)
|
||||
return 0;
|
||||
}
|
||||
/* Emit 2-bit RESTORE_NONE per plane */
|
||||
for (p = 0; p < num_planes; p++)
|
||||
bsw_put_u(bs, 0u, 2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* §5.9.15 tile_info — single-tile uniform-spacing path only */
|
||||
static int write_tile_info_single_tile(struct bs_writer *bs,
|
||||
uint32_t frame_width,
|
||||
uint32_t frame_height,
|
||||
bool use_128_sb)
|
||||
{
|
||||
uint32_t mi_cols = mi_cols_for(frame_width);
|
||||
uint32_t mi_rows = mi_rows_for(frame_height);
|
||||
int sb_log2 = use_128_sb ? 5 : 4; /* mi units */
|
||||
uint32_t sb_cols = (mi_cols + ((1u << sb_log2) - 1u)) >> sb_log2;
|
||||
uint32_t sb_rows = (mi_rows + ((1u << sb_log2) - 1u)) >> sb_log2;
|
||||
int min_log2_cols = tile_log2_ge(use_128_sb ? 4096 : 4096 / 1,
|
||||
(int)(sb_cols * (use_128_sb ? 128 : 64)));
|
||||
(void) min_log2_cols;
|
||||
|
||||
/* uniform_tile_spacing_flag = 1, both increment loops = 0 →
|
||||
* tile_cols_log2 = tile_rows_log2 = 0 (single tile). This
|
||||
* matches "uniform spacing with no width/height halving" which
|
||||
* is the simplest valid encoding. */
|
||||
bsw_put_u(bs, 1u, 1); /* uniform_tile_spacing_flag */
|
||||
|
||||
/* increment_tile_cols_log2: 0 zeros + the next non-increment
|
||||
* bit terminates the loop. In single-tile mode we encode the
|
||||
* terminator immediately. */
|
||||
(void) sb_cols;
|
||||
(void) sb_rows;
|
||||
/* The increment loops in the spec run while
|
||||
* tile_cols_log2 < max_log2_tile_cols, reading bits until a 0
|
||||
* appears. For our forced single-tile, we emit a single 0 bit
|
||||
* to terminate the cols loop and another for the rows loop. */
|
||||
bsw_put_u(bs, 0u, 1); /* terminate cols */
|
||||
bsw_put_u(bs, 0u, 1); /* terminate rows */
|
||||
|
||||
/* tile_size_bytes_minus_1: 0 (1 byte) — only meaningful when
|
||||
* NumTiles > 1, but spec emits it unconditionally when
|
||||
* NumTiles > 1. Single tile → not emitted. We're single tile,
|
||||
* skip. */
|
||||
return 1;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
uint8_t rbsp[256];
|
||||
struct bs_writer bs;
|
||||
uint32_t sf, ff;
|
||||
bool show_existing_frame = false;
|
||||
bool reduced_still_picture_header;
|
||||
bool show_frame, showable_frame, error_resilient_mode;
|
||||
bool disable_cdf_update, allow_screen_content_tools;
|
||||
bool force_integer_mv, allow_intrabc, frame_size_override;
|
||||
bool allow_high_precision_mv, is_motion_mode_switchable;
|
||||
bool use_ref_frame_mvs, disable_frame_end_update_cdf;
|
||||
bool reference_select, allow_warped_motion, reduced_tx_set;
|
||||
bool skip_mode_present, monochrome;
|
||||
uint8_t frame_type, primary_ref_frame;
|
||||
uint32_t frame_width, frame_height;
|
||||
int num_planes;
|
||||
int width_bits, height_bits;
|
||||
uint8_t order_hint_bits;
|
||||
bool enable_order_hint, enable_ref_frame_mvs, enable_warped_motion_seq;
|
||||
bool enable_cdef_seq, enable_restoration_seq;
|
||||
int i;
|
||||
|
||||
if (!seq || !fr || !out || out_cap < 16)
|
||||
return 0;
|
||||
|
||||
sf = seq->flags;
|
||||
ff = fr->flags;
|
||||
|
||||
/* sanity */
|
||||
monochrome = !!(sf & V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME);
|
||||
num_planes = monochrome ? 1 : 3;
|
||||
enable_order_hint = !!(sf & V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT);
|
||||
enable_ref_frame_mvs = !!(sf & V4L2_AV1_SEQUENCE_FLAG_ENABLE_REF_FRAME_MVS);
|
||||
enable_warped_motion_seq = !!(sf & V4L2_AV1_SEQUENCE_FLAG_ENABLE_WARPED_MOTION);
|
||||
enable_cdef_seq = !!(sf & V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF);
|
||||
enable_restoration_seq = !!(sf & V4L2_AV1_SEQUENCE_FLAG_ENABLE_RESTORATION);
|
||||
order_hint_bits = enable_order_hint ? seq->order_hint_bits : 0;
|
||||
if (order_hint_bits > 8) order_hint_bits = 8;
|
||||
reduced_still_picture_header = false; /* matches sequence-header default */
|
||||
|
||||
frame_type = fr->frame_type;
|
||||
if (frame_type == V4L2_AV1_SWITCH_FRAME)
|
||||
return 0; /* out of scope */
|
||||
|
||||
show_frame = !!(ff & V4L2_AV1_FRAME_FLAG_SHOW_FRAME);
|
||||
showable_frame = !!(ff & V4L2_AV1_FRAME_FLAG_SHOWABLE_FRAME);
|
||||
error_resilient_mode = !!(ff & V4L2_AV1_FRAME_FLAG_ERROR_RESILIENT_MODE);
|
||||
disable_cdf_update = !!(ff & V4L2_AV1_FRAME_FLAG_DISABLE_CDF_UPDATE);
|
||||
allow_screen_content_tools = !!(ff & V4L2_AV1_FRAME_FLAG_ALLOW_SCREEN_CONTENT_TOOLS);
|
||||
force_integer_mv = !!(ff & V4L2_AV1_FRAME_FLAG_FORCE_INTEGER_MV);
|
||||
allow_intrabc = !!(ff & V4L2_AV1_FRAME_FLAG_ALLOW_INTRABC);
|
||||
frame_size_override = !!(ff & V4L2_AV1_FRAME_FLAG_FRAME_SIZE_OVERRIDE);
|
||||
allow_high_precision_mv = !!(ff & V4L2_AV1_FRAME_FLAG_ALLOW_HIGH_PRECISION_MV);
|
||||
is_motion_mode_switchable = !!(ff & V4L2_AV1_FRAME_FLAG_IS_MOTION_MODE_SWITCHABLE);
|
||||
use_ref_frame_mvs = !!(ff & V4L2_AV1_FRAME_FLAG_USE_REF_FRAME_MVS);
|
||||
disable_frame_end_update_cdf = !!(ff & V4L2_AV1_FRAME_FLAG_DISABLE_FRAME_END_UPDATE_CDF);
|
||||
reference_select = !!(ff & V4L2_AV1_FRAME_FLAG_REFERENCE_SELECT);
|
||||
allow_warped_motion = !!(ff & V4L2_AV1_FRAME_FLAG_ALLOW_WARPED_MOTION);
|
||||
reduced_tx_set = !!(ff & V4L2_AV1_FRAME_FLAG_REDUCED_TX_SET);
|
||||
skip_mode_present = !!(ff & V4L2_AV1_FRAME_FLAG_SKIP_MODE_PRESENT);
|
||||
primary_ref_frame = fr->primary_ref_frame;
|
||||
|
||||
frame_width = fr->frame_width_minus_1 + 1;
|
||||
frame_height = fr->frame_height_minus_1 + 1;
|
||||
|
||||
width_bits = min_bits_for((uint32_t) seq->max_frame_width_minus_1);
|
||||
height_bits = min_bits_for((uint32_t) seq->max_frame_height_minus_1);
|
||||
|
||||
bsw_init(&bs, rbsp, sizeof(rbsp));
|
||||
|
||||
/* show_existing_frame: 0 (V4L2 doesn't surface the show-only path
|
||||
* — every fr ctrl describes a real decoded frame). */
|
||||
bsw_put_u(&bs, show_existing_frame ? 1u : 0u, 1);
|
||||
|
||||
bsw_put_u(&bs, (uint32_t) frame_type, 2);
|
||||
bsw_put_u(&bs, show_frame ? 1u : 0u, 1);
|
||||
if (show_frame) {
|
||||
/* No decoder_model_info_present_flag emitted in seq header,
|
||||
* so no buffer-removal-time bits here either. */
|
||||
} else {
|
||||
bsw_put_u(&bs, showable_frame ? 1u : 0u, 1);
|
||||
}
|
||||
if (frame_type == V4L2_AV1_SWITCH_FRAME ||
|
||||
(frame_type == V4L2_AV1_KEY_FRAME && show_frame)) {
|
||||
/* error_resilient_mode = 1 inferred — not coded */
|
||||
} else {
|
||||
bsw_put_u(&bs, error_resilient_mode ? 1u : 0u, 1);
|
||||
}
|
||||
|
||||
bsw_put_u(&bs, disable_cdf_update ? 1u : 0u, 1);
|
||||
/* allow_screen_content_tools coded as 1 bit when sequence
|
||||
* forces NOT-SELECT; SELECT mode means we always emit a 1 bit
|
||||
* for the SELECT_SCREEN_CONTENT_TOOLS path. Our sequence
|
||||
* header always emits SELECT, so emit a single bit equal to
|
||||
* the V4L2 flag. */
|
||||
bsw_put_u(&bs, allow_screen_content_tools ? 1u : 0u, 1);
|
||||
if (allow_screen_content_tools) {
|
||||
/* seq_force_integer_mv = SELECT (2) so:
|
||||
* force_integer_mv coded as 1 bit */
|
||||
bsw_put_u(&bs, force_integer_mv ? 1u : 0u, 1);
|
||||
}
|
||||
|
||||
/* frame_id_numbers_present_flag = 0 in seq → no current_frame_id */
|
||||
|
||||
if (frame_type != V4L2_AV1_SWITCH_FRAME && !reduced_still_picture_header)
|
||||
bsw_put_u(&bs, frame_size_override ? 1u : 0u, 1);
|
||||
|
||||
if (enable_order_hint)
|
||||
bsw_put_u(&bs, fr->order_hint, order_hint_bits);
|
||||
|
||||
if (frame_type != V4L2_AV1_KEY_FRAME && frame_type != V4L2_AV1_INTRA_ONLY_FRAME &&
|
||||
!error_resilient_mode)
|
||||
bsw_put_u(&bs, primary_ref_frame, 3);
|
||||
|
||||
/* frame_size + render_size (§5.9.5, §5.9.6) */
|
||||
if (frame_size_override) {
|
||||
bsw_put_u(&bs, fr->frame_width_minus_1, width_bits);
|
||||
bsw_put_u(&bs, fr->frame_height_minus_1, height_bits);
|
||||
}
|
||||
/* superres_params: §5.9.8 */
|
||||
{
|
||||
bool use_superres = !!(ff & V4L2_AV1_FRAME_FLAG_USE_SUPERRES);
|
||||
if (sf & V4L2_AV1_SEQUENCE_FLAG_ENABLE_SUPERRES)
|
||||
bsw_put_u(&bs, use_superres ? 1u : 0u, 1);
|
||||
if (use_superres) {
|
||||
/* coded_denom = superres_denom - SUPERRES_DENOM_MIN(9) */
|
||||
int denom = fr->superres_denom;
|
||||
if (denom < 9) denom = 9;
|
||||
bsw_put_u(&bs, (uint32_t)(denom - 9) & 0x7u, 3);
|
||||
}
|
||||
}
|
||||
/* render_size present flag: 1 if render dims given */
|
||||
{
|
||||
bool render_and_frame_match =
|
||||
(fr->render_width_minus_1 == fr->frame_width_minus_1) &&
|
||||
(fr->render_height_minus_1 == fr->frame_height_minus_1);
|
||||
bsw_put_u(&bs, render_and_frame_match ? 0u : 1u, 1);
|
||||
if (!render_and_frame_match) {
|
||||
bsw_put_u(&bs, fr->render_width_minus_1, 16);
|
||||
bsw_put_u(&bs, fr->render_height_minus_1, 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_type != V4L2_AV1_KEY_FRAME && frame_type != V4L2_AV1_INTRA_ONLY_FRAME) {
|
||||
/* allow_intrabc only on key/intra-only — skip for inter */
|
||||
(void) allow_intrabc;
|
||||
if (!error_resilient_mode && enable_order_hint)
|
||||
bsw_put_u(&bs, 0u, 1); /* frame_refs_short_signaling */
|
||||
/* read ref_frame_idx for each of REFS_PER_FRAME */
|
||||
for (i = 0; i < AV1_REFS_PER_FRAME; i++) {
|
||||
int8_t idx = fr->ref_frame_idx[i];
|
||||
if (idx < 0) idx = 0;
|
||||
bsw_put_u(&bs, (uint32_t)(idx & 0x7), 3);
|
||||
}
|
||||
if (frame_size_override && !error_resilient_mode) {
|
||||
/* found_ref loop — emit "no" for each, so frame_size
|
||||
* fields above already populated. */
|
||||
for (i = 0; i < AV1_REFS_PER_FRAME; i++)
|
||||
bsw_put_u(&bs, 0u, 1);
|
||||
}
|
||||
bsw_put_u(&bs, allow_high_precision_mv ? 1u : 0u, 1);
|
||||
/* read_interpolation_filter: is_filter_switchable + value */
|
||||
{
|
||||
int interp = fr->interpolation_filter;
|
||||
bool switchable = (interp == V4L2_AV1_INTERPOLATION_FILTER_SWITCHABLE);
|
||||
bsw_put_u(&bs, switchable ? 1u : 0u, 1);
|
||||
if (!switchable)
|
||||
bsw_put_u(&bs, (uint32_t)interp & 0x3u, 2);
|
||||
}
|
||||
bsw_put_u(&bs, is_motion_mode_switchable ? 1u : 0u, 1);
|
||||
if (!error_resilient_mode && enable_ref_frame_mvs)
|
||||
bsw_put_u(&bs, use_ref_frame_mvs ? 1u : 0u, 1);
|
||||
} else {
|
||||
if (frame_type == V4L2_AV1_INTRA_ONLY_FRAME && allow_screen_content_tools)
|
||||
bsw_put_u(&bs, allow_intrabc ? 1u : 0u, 1);
|
||||
else if (frame_type == V4L2_AV1_KEY_FRAME && allow_screen_content_tools)
|
||||
bsw_put_u(&bs, allow_intrabc ? 1u : 0u, 1);
|
||||
}
|
||||
|
||||
/* disable_frame_end_update_cdf */
|
||||
if (!disable_cdf_update)
|
||||
bsw_put_u(&bs, disable_frame_end_update_cdf ? 1u : 0u, 1);
|
||||
|
||||
/* tile_info: single-tile path */
|
||||
{
|
||||
bool use_128 = !!(sf & V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK);
|
||||
if (!write_tile_info_single_tile(&bs, frame_width, frame_height,
|
||||
use_128))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* quantization_params */
|
||||
write_quantization_params(&bs, &fr->quantization,
|
||||
num_planes > 1,
|
||||
!!(sf & V4L2_AV1_SEQUENCE_FLAG_SEPARATE_UV_DELTA_Q));
|
||||
|
||||
/* segmentation_params: only enabled=0 supported */
|
||||
{
|
||||
bool seg_en = !!(fr->segmentation.flags & V4L2_AV1_SEGMENTATION_FLAG_ENABLED);
|
||||
if (seg_en)
|
||||
return 0;
|
||||
bsw_put_u(&bs, 0u, 1); /* segmentation_enabled */
|
||||
}
|
||||
|
||||
/* delta_q_params + delta_lf_params */
|
||||
{
|
||||
bool delta_q_present = !!(fr->quantization.flags &
|
||||
V4L2_AV1_QUANTIZATION_FLAG_DELTA_Q_PRESENT);
|
||||
if (fr->quantization.base_q_idx > 0) {
|
||||
bsw_put_u(&bs, delta_q_present ? 1u : 0u, 1);
|
||||
if (delta_q_present)
|
||||
bsw_put_u(&bs, fr->quantization.delta_q_res & 0x3u, 2);
|
||||
}
|
||||
if (delta_q_present && !allow_intrabc) {
|
||||
bool delta_lf_present =
|
||||
!!(fr->loop_filter.flags & V4L2_AV1_LOOP_FILTER_FLAG_DELTA_LF_PRESENT);
|
||||
bsw_put_u(&bs, delta_lf_present ? 1u : 0u, 1);
|
||||
if (delta_lf_present) {
|
||||
bsw_put_u(&bs, fr->loop_filter.delta_lf_res & 0x3u, 2);
|
||||
bsw_put_u(&bs,
|
||||
(fr->loop_filter.flags & V4L2_AV1_LOOP_FILTER_FLAG_DELTA_LF_MULTI)
|
||||
? 1u : 0u, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* coded_lossless heuristic: when base_q_idx==0 and all deltas==0
|
||||
* and qm not in use, AV1 treats the frame as lossless. We
|
||||
* approximate with the base_q_idx check; the lf/cdef writers
|
||||
* gate on the same value. */
|
||||
{
|
||||
bool coded_lossless = (fr->quantization.base_q_idx == 0);
|
||||
|
||||
write_loop_filter_params(&bs, &fr->loop_filter,
|
||||
num_planes > 1,
|
||||
coded_lossless || allow_intrabc);
|
||||
write_cdef_params(&bs, &fr->cdef, num_planes > 1,
|
||||
enable_cdef_seq,
|
||||
coded_lossless || allow_intrabc);
|
||||
if (!write_lr_params(&bs, &fr->loop_restoration, num_planes,
|
||||
enable_restoration_seq,
|
||||
coded_lossless || allow_intrabc))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* read_tx_mode (§5.9.21) */
|
||||
{
|
||||
bool coded_lossless = (fr->quantization.base_q_idx == 0);
|
||||
if (coded_lossless) {
|
||||
/* tx_mode = ONLY_4X4 (inferred) */
|
||||
} else {
|
||||
int tx_mode = fr->tx_mode;
|
||||
bsw_put_u(&bs, (tx_mode == V4L2_AV1_TX_MODE_SELECT) ? 1u : 0u, 1);
|
||||
if (tx_mode != V4L2_AV1_TX_MODE_SELECT)
|
||||
bsw_put_u(&bs, (tx_mode == V4L2_AV1_TX_MODE_LARGEST) ? 1u : 0u, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* frame_reference_mode (§5.9.23) */
|
||||
if (frame_type != V4L2_AV1_KEY_FRAME && frame_type != V4L2_AV1_INTRA_ONLY_FRAME)
|
||||
bsw_put_u(&bs, reference_select ? 1u : 0u, 1);
|
||||
|
||||
/* skip_mode_params (§5.9.22) */
|
||||
{
|
||||
bool skip_allowed = !!(ff & V4L2_AV1_FRAME_FLAG_SKIP_MODE_ALLOWED);
|
||||
if (skip_allowed)
|
||||
bsw_put_u(&bs, skip_mode_present ? 1u : 0u, 1);
|
||||
}
|
||||
|
||||
/* reduced_tx_set */
|
||||
bsw_put_u(&bs, reduced_tx_set ? 1u : 0u, 1);
|
||||
|
||||
/* global_motion_params: §5.9.24 — emit IDENTITY for each ref */
|
||||
if (frame_type != V4L2_AV1_KEY_FRAME && frame_type != V4L2_AV1_INTRA_ONLY_FRAME) {
|
||||
int r;
|
||||
(void) enable_warped_motion_seq;
|
||||
(void) allow_warped_motion;
|
||||
for (r = 1; r < AV1_NUM_REF_FRAMES; r++) {
|
||||
uint8_t wm_type = fr->global_motion.type[r];
|
||||
if (wm_type != V4L2_AV1_WARP_MODEL_IDENTITY)
|
||||
return 0; /* out of scope */
|
||||
bsw_put_u(&bs, 0u, 1); /* is_global = 0 → identity */
|
||||
}
|
||||
}
|
||||
|
||||
/* film_grain_params: §6.8.20 — only "not present" path supported */
|
||||
if (sf & V4L2_AV1_SEQUENCE_FLAG_FILM_GRAIN_PARAMS_PRESENT)
|
||||
return 0; /* out of scope: film grain coding deferred */
|
||||
|
||||
bsw_align_rbsp(&bs);
|
||||
if (bsw_overflowed(&bs))
|
||||
return 0;
|
||||
|
||||
return wrap_obu(OBU_FRAME_HEADER, rbsp, bsw_bytes(&bs), out, out_cap);
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
* 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:
|
||||
* 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).
|
||||
@@ -47,4 +47,73 @@
|
||||
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 */
|
||||
|
||||
@@ -306,6 +306,150 @@ static int test_overflow(void)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Case 5: Temporal Delimiter is exactly 2 bytes 0x12 0x00. */
|
||||
static int test_temporal_delimiter(void)
|
||||
{
|
||||
uint8_t out[4];
|
||||
size_t n;
|
||||
|
||||
memset(out, 0xff, sizeof(out));
|
||||
n = av1_synth_temporal_delimiter_obu(out, sizeof(out));
|
||||
CHECK_EQ(n, 2, "TD length");
|
||||
CHECK_EQ(out[0], 0x12, "TD obu header byte (obu_type=2, has_size=1)");
|
||||
CHECK_EQ(out[1], 0x00, "TD leb128 size = 0");
|
||||
printf(" temporal delimiter: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Test fixtures for Frame Header cases. */
|
||||
static void mk_seq_1080p_p0(struct v4l2_ctrl_av1_sequence *seq)
|
||||
{
|
||||
memset(seq, 0, sizeof(*seq));
|
||||
seq->seq_profile = 0;
|
||||
seq->order_hint_bits = 7;
|
||||
seq->bit_depth = 8;
|
||||
seq->max_frame_width_minus_1 = 1919;
|
||||
seq->max_frame_height_minus_1 = 1079;
|
||||
seq->flags = V4L2_AV1_SEQUENCE_FLAG_USE_128X128_SUPERBLOCK |
|
||||
V4L2_AV1_SEQUENCE_FLAG_ENABLE_ORDER_HINT |
|
||||
V4L2_AV1_SEQUENCE_FLAG_ENABLE_CDEF |
|
||||
V4L2_AV1_SEQUENCE_FLAG_ENABLE_RESTORATION;
|
||||
}
|
||||
|
||||
static void mk_frame_key_1080p(struct v4l2_ctrl_av1_frame *fr)
|
||||
{
|
||||
memset(fr, 0, sizeof(*fr));
|
||||
fr->frame_type = V4L2_AV1_KEY_FRAME;
|
||||
fr->frame_width_minus_1 = 1919;
|
||||
fr->frame_height_minus_1 = 1079;
|
||||
fr->render_width_minus_1 = 1919;
|
||||
fr->render_height_minus_1 = 1079;
|
||||
fr->primary_ref_frame = 7; /* PRIMARY_REF_NONE */
|
||||
fr->quantization.base_q_idx = 60;
|
||||
fr->loop_filter.level[0] = 16;
|
||||
fr->loop_filter.level[1] = 16;
|
||||
fr->loop_filter.level[2] = 16;
|
||||
fr->loop_filter.level[3] = 16;
|
||||
fr->cdef.bits = 0;
|
||||
fr->loop_restoration.frame_restoration_type[0] = V4L2_AV1_FRAME_RESTORE_NONE;
|
||||
fr->loop_restoration.frame_restoration_type[1] = V4L2_AV1_FRAME_RESTORE_NONE;
|
||||
fr->loop_restoration.frame_restoration_type[2] = V4L2_AV1_FRAME_RESTORE_NONE;
|
||||
fr->interpolation_filter = 0;
|
||||
fr->tx_mode = V4L2_AV1_TX_MODE_SELECT;
|
||||
fr->flags = V4L2_AV1_FRAME_FLAG_SHOW_FRAME;
|
||||
}
|
||||
|
||||
/* Case 6: KEY frame at 1080p — happy path, structural smoke. */
|
||||
static int test_frame_header_key_1080p(void)
|
||||
{
|
||||
struct v4l2_ctrl_av1_sequence seq;
|
||||
struct v4l2_ctrl_av1_frame fr;
|
||||
uint8_t out[256];
|
||||
size_t n;
|
||||
struct br br;
|
||||
uint32_t bit;
|
||||
|
||||
mk_seq_1080p_p0(&seq);
|
||||
mk_frame_key_1080p(&fr);
|
||||
|
||||
n = av1_synth_frame_header_obu(&seq, &fr, out, sizeof(out));
|
||||
CHECK(n > 0 && n <= sizeof(out), "FH synth returned %zu", n);
|
||||
|
||||
/* OBU header byte: obu_type=3 (FRAME_HEADER), has_size_field=1
|
||||
* → 0_0011_0_1_0 = 0x1A. */
|
||||
CHECK_EQ(out[0], 0x1A, "FH obu header byte");
|
||||
CHECK((out[1] & 0x80) == 0, "leb128 single byte");
|
||||
CHECK_EQ(n, 2 + (size_t)(out[1] & 0x7f), "total = header+leb+payload");
|
||||
|
||||
br_init(&br, out + 2, n - 2);
|
||||
bit = br_get(&br, 1); CHECK_EQ(bit, 0, "show_existing_frame");
|
||||
bit = br_get(&br, 2); CHECK_EQ(bit, 0, "frame_type=KEY");
|
||||
bit = br_get(&br, 1); CHECK_EQ(bit, 1, "show_frame");
|
||||
bit = br_get(&br, 1); CHECK_EQ(bit, 0, "disable_cdf_update");
|
||||
bit = br_get(&br, 1); CHECK_EQ(bit, 0, "allow_screen_content_tools");
|
||||
|
||||
printf(" KEY frame 1080p: OK (%zu bytes)\n", n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Case 7: INTER frame — coverage smoke. */
|
||||
static int test_frame_header_inter(void)
|
||||
{
|
||||
struct v4l2_ctrl_av1_sequence seq;
|
||||
struct v4l2_ctrl_av1_frame fr;
|
||||
uint8_t out[256];
|
||||
size_t n;
|
||||
int i;
|
||||
|
||||
mk_seq_1080p_p0(&seq);
|
||||
mk_frame_key_1080p(&fr);
|
||||
fr.frame_type = V4L2_AV1_INTER_FRAME;
|
||||
fr.primary_ref_frame = 0;
|
||||
for (i = 0; i < V4L2_AV1_REFS_PER_FRAME; i++)
|
||||
fr.ref_frame_idx[i] = (int8_t)(i & 7);
|
||||
fr.flags |= V4L2_AV1_FRAME_FLAG_REFERENCE_SELECT;
|
||||
|
||||
n = av1_synth_frame_header_obu(&seq, &fr, out, sizeof(out));
|
||||
CHECK(n > 0, "INTER FH synth returned %zu", n);
|
||||
CHECK_EQ(out[0], 0x1A, "FH obu header");
|
||||
printf(" INTER frame: OK (%zu bytes)\n", n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Case 8: SWITCH frame should be rejected. */
|
||||
static int test_frame_header_switch_rejected(void)
|
||||
{
|
||||
struct v4l2_ctrl_av1_sequence seq;
|
||||
struct v4l2_ctrl_av1_frame fr;
|
||||
uint8_t out[256];
|
||||
size_t n;
|
||||
|
||||
mk_seq_1080p_p0(&seq);
|
||||
mk_frame_key_1080p(&fr);
|
||||
fr.frame_type = V4L2_AV1_SWITCH_FRAME;
|
||||
n = av1_synth_frame_header_obu(&seq, &fr, out, sizeof(out));
|
||||
CHECK_EQ(n, 0, "SWITCH frame should be out of scope");
|
||||
printf(" SWITCH frame rejected: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Case 9: segmentation enabled should be rejected. */
|
||||
static int test_frame_header_segmentation_rejected(void)
|
||||
{
|
||||
struct v4l2_ctrl_av1_sequence seq;
|
||||
struct v4l2_ctrl_av1_frame fr;
|
||||
uint8_t out[256];
|
||||
size_t n;
|
||||
|
||||
mk_seq_1080p_p0(&seq);
|
||||
mk_frame_key_1080p(&fr);
|
||||
fr.segmentation.flags = V4L2_AV1_SEGMENTATION_FLAG_ENABLED;
|
||||
n = av1_synth_frame_header_obu(&seq, &fr, out, sizeof(out));
|
||||
CHECK_EQ(n, 0, "segmentation-enabled should be out of scope");
|
||||
printf(" segmentation enabled rejected: OK\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int fail = 0;
|
||||
@@ -317,6 +461,15 @@ int main(void)
|
||||
fail |= test_reject_invalid_profile_bitdepth();
|
||||
fail |= test_overflow();
|
||||
|
||||
printf("=== av1_synth_temporal_delimiter_obu ===\n");
|
||||
fail |= test_temporal_delimiter();
|
||||
|
||||
printf("=== av1_synth_frame_header_obu ===\n");
|
||||
fail |= test_frame_header_key_1080p();
|
||||
fail |= test_frame_header_inter();
|
||||
fail |= test_frame_header_switch_rejected();
|
||||
fail |= test_frame_header_segmentation_rejected();
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "AV1 OBU synth tests FAILED\n");
|
||||
return 1;
|
||||
|
||||
Reference in New Issue
Block a user