/* SPDX-License-Identifier: BSD-2-Clause */ /* * test_av1_obu_synth — standalone unit test for av1_synth_sequence_header_obu. * * Builds as an opt-in executable target (test_av1_obu_synth) gated on * -DDAEDALUS_BUILD_TESTS=ON. Runs by default in the CI build matrix * to gate the OBU encoder against regressions. * * Each test case sets up a struct v4l2_ctrl_av1_sequence with known * field values, calls the synthesiser, then walks the output bit by * bit against a hand-computed expected encoding. The bit-walker uses * the same reader semantics as bitstream_writer: MSB-first within each * byte, with the OBU header byte / leb128 size at byte-aligned * positions and the RBSP payload starting at the byte right after. */ #include "av1_obu_synth.h" #include #include #include #include #include /* MSB-first bit reader over a byte stream. */ struct br { const uint8_t *buf; size_t bytes; size_t pos_bytes; int pos_bit; int overflow; }; static void br_init(struct br *b, const uint8_t *buf, size_t bytes) { b->buf = buf; b->bytes = bytes; b->pos_bytes = 0; b->pos_bit = 0; b->overflow = 0; } static uint32_t br_get(struct br *b, int n) { uint32_t v = 0; int i; for (i = 0; i < n; i++) { uint8_t bit; if (b->pos_bytes >= b->bytes) { b->overflow = 1; return 0; } bit = (b->buf[b->pos_bytes] >> (7 - b->pos_bit)) & 1u; v = (v << 1) | bit; b->pos_bit++; if (b->pos_bit == 8) { b->pos_bit = 0; b->pos_bytes++; } } return v; } /* Round up to next byte; returns bytes consumed for boundary. */ static void br_byte_align(struct br *b) { if (b->pos_bit != 0) { b->pos_bit = 0; b->pos_bytes++; } } #define CHECK(cond, ...) do { \ if (!(cond)) { \ fprintf(stderr, "FAIL %s:%d: ", \ __func__, __LINE__); \ fprintf(stderr, __VA_ARGS__); \ fputc('\n', stderr); \ return 1; \ } \ } while (0) #define CHECK_EQ(actual, expected, name) do { \ uint32_t _a = (uint32_t)(actual); \ uint32_t _e = (uint32_t)(expected); \ if (_a != _e) { \ fprintf(stderr, "FAIL %s:%d %s: " \ "got %u, expected %u\n", \ __func__, __LINE__, (name), \ _a, _e); \ return 1; \ } \ } while (0) /* * Case 1: 1080p, profile 0 (4:2:0), 8-bit, color_range studio, * order_hint enabled with 7 bits, CDEF + restoration on, no film grain. * Covers the most common decode path libva-v4l2-request drives on * the daedalus daemon. */ static int test_profile0_1080p(void) { struct v4l2_ctrl_av1_sequence seq; uint8_t out[64]; size_t n; struct br br; uint32_t bit; 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; /* 1920 */ seq.max_frame_height_minus_1 = 1079; /* 1080 */ 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; /* COLOR_RANGE flag unset = studio swing (limited range, =0 in spec) */ n = av1_synth_sequence_header_obu(&seq, out, sizeof(out)); CHECK(n > 0 && n <= sizeof(out), "synthesiser returned %zu bytes", n); /* OBU header byte: 0x0A (obu_type=1, has_size_field=1). */ CHECK_EQ(out[0], 0x0A, "OBU header byte"); /* leb128 size — payload fits in 1 byte for sub-128-byte payloads. */ CHECK(n >= 2, "OBU has size field byte"); CHECK((out[1] & 0x80) == 0, "leb128 single-byte form (no continuation)"); { size_t payload_len = out[1] & 0x7fu; CHECK_EQ(n, 2 + payload_len, "total length matches header+leb+payload"); } /* Walk payload bits. */ br_init(&br, out + 2, n - 2); bit = br_get(&br, 3); CHECK_EQ(bit, 0, "seq_profile"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "still_picture"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "reduced_still_picture_header"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "timing_info_present_flag"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "initial_display_delay_present_flag"); bit = br_get(&br, 5); CHECK_EQ(bit, 0, "operating_points_cnt_minus_1"); bit = br_get(&br, 12); CHECK_EQ(bit, 0, "operating_point_idc[0]"); bit = br_get(&br, 5); CHECK_EQ(bit, 13, "seq_level_idx[0]"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "seq_tier[0]"); /* min_bits_for(1919) = 11; encoded value = 11 - 1 = 10 */ bit = br_get(&br, 4); CHECK_EQ(bit, 10, "frame_width_bits_minus_1"); /* min_bits_for(1079) = 11; same value */ bit = br_get(&br, 4); CHECK_EQ(bit, 10, "frame_height_bits_minus_1"); bit = br_get(&br, 11); CHECK_EQ(bit, 1919, "max_frame_width_minus_1"); bit = br_get(&br, 11); CHECK_EQ(bit, 1079, "max_frame_height_minus_1"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "frame_id_numbers_present_flag"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "use_128x128_superblock"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_filter_intra"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_intra_edge_filter"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_interintra_compound"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_masked_compound"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_warped_motion"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_dual_filter"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "enable_order_hint"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_jnt_comp"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_ref_frame_mvs"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "seq_choose_screen_detection_tools"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "seq_choose_integer_mv"); /* order_hint_bits=7 → order_hint_bits_minus_1 = 6 */ bit = br_get(&br, 3); CHECK_EQ(bit, 6, "order_hint_bits_minus_1"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_superres"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "enable_cdef"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "enable_restoration"); /* color_config: high_bitdepth=0 (8-bit), monochrome=0, * color_description_present=0, color_range=0, subsampling forced (no bits), * chroma_sample_position=0 (2 bits when subsampling_x && subsampling_y), * separate_uv_delta_q=0. */ bit = br_get(&br, 1); CHECK_EQ(bit, 0, "high_bitdepth"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "monochrome"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "color_description_present_flag"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "color_range"); bit = br_get(&br, 2); CHECK_EQ(bit, 0, "chroma_sample_position"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "separate_uv_delta_q"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "film_grain_params_present"); /* trailing_bits — single '1' then zero-fill */ bit = br_get(&br, 1); CHECK_EQ(bit, 1, "trailing_bits stop_one"); br_byte_align(&br); CHECK(!br.overflow, "no bit-reader overflow"); CHECK_EQ(br.pos_bytes, n - 2, "consumed exactly the payload"); printf(" profile0 1080p 8-bit: OK (%zu bytes)\n", n); return 0; } /* Case 2: profile 0, 10-bit, 4:2:0, monochrome. * Exercises the high_bitdepth + monochrome short-form color_config path. */ static int test_profile0_monochrome_10bit(void) { struct v4l2_ctrl_av1_sequence seq; uint8_t out[64]; size_t n; struct br br; uint32_t bit; memset(&seq, 0, sizeof(seq)); seq.seq_profile = 0; seq.order_hint_bits = 0; seq.bit_depth = 10; seq.max_frame_width_minus_1 = 1279; seq.max_frame_height_minus_1 = 719; seq.flags = V4L2_AV1_SEQUENCE_FLAG_MONO_CHROME; n = av1_synth_sequence_header_obu(&seq, out, sizeof(out)); CHECK(n > 0, "synthesiser returned %zu bytes", n); CHECK_EQ(out[0], 0x0A, "OBU header byte"); br_init(&br, out + 2, n - 2); bit = br_get(&br, 3); CHECK_EQ(bit, 0, "seq_profile"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "still_picture"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "reduced_still_picture_header"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "timing_info_present_flag"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "initial_display_delay_present_flag"); bit = br_get(&br, 5); CHECK_EQ(bit, 0, "operating_points_cnt_minus_1"); bit = br_get(&br, 12); CHECK_EQ(bit, 0, "operating_point_idc[0]"); bit = br_get(&br, 5); CHECK_EQ(bit, 13, "seq_level_idx[0]"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "seq_tier[0]"); /* 1279 fits in 11 bits → width_bits_minus_1 = 10 */ bit = br_get(&br, 4); CHECK_EQ(bit, 10, "frame_width_bits_minus_1"); /* 719 fits in 10 bits → height_bits_minus_1 = 9 */ bit = br_get(&br, 4); CHECK_EQ(bit, 9, "frame_height_bits_minus_1"); bit = br_get(&br, 11); CHECK_EQ(bit, 1279, "max_frame_width_minus_1"); bit = br_get(&br, 10); CHECK_EQ(bit, 719, "max_frame_height_minus_1"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "frame_id_numbers_present_flag"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "use_128x128_superblock"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_filter_intra"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_intra_edge_filter"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_interintra_compound"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_masked_compound"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_warped_motion"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_dual_filter"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_order_hint"); /* enable_order_hint=0 → no jnt_comp / ref_frame_mvs / order_hint_bits */ bit = br_get(&br, 1); CHECK_EQ(bit, 1, "seq_choose_screen_detection_tools"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "seq_choose_integer_mv"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_superres"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_cdef"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "enable_restoration"); /* color_config: high_bitdepth=1 (10-bit), seq_profile==0 so no twelve_bit, * monochrome=1, color_description_present=0, color_range=0. * Monochrome short-form: no subsampling/chroma_sample_position/uv_delta_q bits. */ bit = br_get(&br, 1); CHECK_EQ(bit, 1, "high_bitdepth"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "monochrome"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "color_description_present_flag"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "color_range"); bit = br_get(&br, 1); CHECK_EQ(bit, 0, "film_grain_params_present"); bit = br_get(&br, 1); CHECK_EQ(bit, 1, "trailing_bits stop_one"); CHECK(!br.overflow, "no overflow"); printf(" profile0 monochrome 10-bit: OK (%zu bytes)\n", n); return 0; } /* Case 3: reject illegal seq_profile + bit_depth combination. */ static int test_reject_invalid_profile_bitdepth(void) { struct v4l2_ctrl_av1_sequence seq; uint8_t out[64]; size_t n; memset(&seq, 0, sizeof(seq)); seq.seq_profile = 1; /* 4:4:4 only */ seq.bit_depth = 12; /* but profile 1 doesn't allow 12-bit */ seq.max_frame_width_minus_1 = 1919; seq.max_frame_height_minus_1 = 1079; n = av1_synth_sequence_header_obu(&seq, out, sizeof(out)); CHECK_EQ(n, 0, "expected 0 (rejected) for profile1+12bit"); printf(" reject profile1+12bit: OK\n"); return 0; } /* Case 4: small out_cap → overflow path. */ static int test_overflow(void) { struct v4l2_ctrl_av1_sequence seq; uint8_t out[4]; /* deliberately too small */ size_t n; memset(&seq, 0, sizeof(seq)); seq.seq_profile = 0; seq.bit_depth = 8; seq.max_frame_width_minus_1 = 1919; seq.max_frame_height_minus_1 = 1079; n = av1_synth_sequence_header_obu(&seq, out, sizeof(out)); CHECK_EQ(n, 0, "expected 0 (overflow) for tiny out buffer"); printf(" out_cap overflow: OK\n"); return 0; } int main(void) { int fail = 0; printf("=== av1_synth_sequence_header_obu ===\n"); fail |= test_profile0_1080p(); fail |= test_profile0_monochrome_10bit(); fail |= test_reject_invalid_profile_bitdepth(); fail |= test_overflow(); if (fail) { fprintf(stderr, "AV1 OBU synth tests FAILED\n"); return 1; } printf("AV1 OBU synth tests PASSED\n"); return 0; }