Add unit tests for CI OBU
diff --git a/av2/encoder/bitstream_ci.c b/av2/encoder/bitstream_ci.c index 315f9e9..293ed65 100644 --- a/av2/encoder/bitstream_ci.c +++ b/av2/encoder/bitstream_ci.c
@@ -25,6 +25,7 @@ #include "av2/common/blockd.h" #include "av2/encoder/bitstream.h" #include "avm/avm_image.h" +#include "av2/encoder/ci_syntax.h" void av2_write_color_info(const struct ContentInterpretation *ci_params, struct avm_write_bit_buffer *wb) { const ColorInfo *col_info = &ci_params->color_info; @@ -47,34 +48,43 @@ } } +int av2_write_ci_info(const ContentInterpretation *ci_params, + struct avm_write_bit_buffer *wb) { + avm_wb_write_literal(wb, ci_params->ci_scan_type_idc, 2); + avm_wb_write_bit(wb, ci_params->ci_color_description_present_flag); + avm_wb_write_bit(wb, ci_params->ci_chroma_sample_position_present_flag); + avm_wb_write_bit(wb, ci_params->ci_aspect_ratio_info_present_flag); + avm_wb_write_bit(wb, ci_params->ci_timing_info_present_flag); + avm_wb_write_literal(wb, 0, 2); + + if (ci_params->ci_color_description_present_flag) { + av2_write_color_info(ci_params, wb); + } + + if (ci_params->ci_chroma_sample_position_present_flag) { + avm_wb_write_uvlc(wb, ci_params->ci_chroma_sample_position[0]); + if (ci_params->ci_scan_type_idc != 1) { + avm_wb_write_uvlc(wb, ci_params->ci_chroma_sample_position[1]); + } + } + if (ci_params->ci_aspect_ratio_info_present_flag) { + av2_write_sar_info(ci_params, wb); + } + + if (ci_params->ci_timing_info_present_flag) { + av2_write_timing_info_header(&ci_params->timing_info, wb); + } + return 0; +} + uint32_t av2_write_content_interpretation_obu( const ContentInterpretation *ci_params, uint8_t *const dst) { struct avm_write_bit_buffer wb = { dst, 0 }; - uint32_t size = 0; - avm_wb_write_literal(&wb, ci_params->ci_scan_type_idc, 2); - avm_wb_write_bit(&wb, ci_params->ci_color_description_present_flag); - avm_wb_write_bit(&wb, ci_params->ci_chroma_sample_position_present_flag); - avm_wb_write_bit(&wb, ci_params->ci_aspect_ratio_info_present_flag); - avm_wb_write_bit(&wb, ci_params->ci_timing_info_present_flag); - avm_wb_write_literal(&wb, 0, 2); - if (ci_params->ci_color_description_present_flag) - av2_write_color_info(ci_params, &wb); + av2_write_ci_info(ci_params, &wb); - if (ci_params->ci_chroma_sample_position_present_flag) { - avm_wb_write_uvlc(&wb, ci_params->ci_chroma_sample_position[0]); - if (ci_params->ci_scan_type_idc != 1) - avm_wb_write_uvlc(&wb, ci_params->ci_chroma_sample_position[1]); - } - if (ci_params->ci_aspect_ratio_info_present_flag) - av2_write_sar_info(ci_params, &wb); - - if (ci_params->ci_timing_info_present_flag) { - av2_write_timing_info_header(&ci_params->timing_info, &wb); - } avm_wb_write_bit(&wb, ci_params->ci_extension_present_flag); assert(!ci_params->ci_extension_present_flag); av2_add_trailing_bits(&wb); - size = avm_wb_bytes_written(&wb); - return size; + return avm_wb_bytes_written(&wb); }
diff --git a/av2/encoder/ci_syntax.h b/av2/encoder/ci_syntax.h new file mode 100644 index 0000000..84a140e --- /dev/null +++ b/av2/encoder/ci_syntax.h
@@ -0,0 +1,30 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved + * + * This source code is subject to the terms of the BSD 3-Clause Clear License + * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear + * License was not distributed with this source code in the LICENSE file, you + * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the + * Alliance for Open Media Patent License 1.0 was not distributed with this + * source code in the PATENTS file, you can obtain it at + * aomedia.org/license/patent-license/. + */ + +#ifndef AVM_AV2_ENCODER_CI_SYNTAX_H_ +#define AVM_AV2_ENCODER_CI_SYNTAX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct ContentInterpretation; +struct avm_write_bit_buffer; + +int av2_write_ci_info(const struct ContentInterpretation *ci_params, + struct avm_write_bit_buffer *wb); + +#ifdef __cplusplus +} +#endif + +#endif // AVM_AV2_ENCODER_CI_SYNTAX_H_
diff --git a/test/ci_test.cc b/test/ci_test.cc new file mode 100644 index 0000000..bf9e227 --- /dev/null +++ b/test/ci_test.cc
@@ -0,0 +1,648 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved + * + * This source code is subject to the terms of the BSD 3-Clause Clear License + * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear + * License was not distributed with this source code in the LICENSE file, you + * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. If the + * Alliance for Open Media Patent License 1.0 was not distributed with this + * source code in the PATENTS file, you can obtain it at + * aomedia.org/license/patent-license/. + */ + +#include <cstring> + +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +#include "av2/encoder/ci_syntax.h" +#include "av2/decoder/decoder.h" +#include "av2/decoder/decodeframe.h" +#include "avm_dsp/bitwriter_buffer.h" +#include "avm_dsp/bitreader_buffer.h" +#include "avm_mem/avm_mem.h" + +namespace { + +static void rb_error_handler(void *data, avm_codec_err_t error, + const char *detail) { + (void)data; + (void)error; + (void)detail; +} + +static uint32_t write_ci_obu(const ContentInterpretation *ci, uint8_t *dst) { + struct avm_write_bit_buffer wb = { dst, 0 }; + av2_write_ci_info(ci, &wb); + avm_wb_write_bit(&wb, 0); // ci_extension_present_flag + avm_wb_write_bit(&wb, 1); // trailing stop bit + int pad = (8 - wb.bit_offset % 8) % 8; + if (pad > 0) { + avm_wb_write_literal(&wb, 0, pad); + } + return avm_wb_bytes_written(&wb); +} + +class CiTest : public ::testing::Test { + protected: + void SetUp() override { + pbi_ = static_cast<AV2Decoder *>(avm_memalign(32, sizeof(AV2Decoder))); + ASSERT_NE(pbi_, nullptr); + memset(pbi_, 0, sizeof(*pbi_)); + memset(buf_, 0, sizeof(buf_)); + } + void TearDown() override { avm_free(pbi_); } + + AV2Decoder *pbi_; + uint8_t buf_[4096]; +}; + +TEST_F(CiTest, MinimalCiRoundtrip) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + // Set up decoder state for CI OBU reading. + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_scan_type_idc, AVM_SCAN_TYPE_UNSPECIFIED); + EXPECT_EQ(dst->ci_color_description_present_flag, 0); + EXPECT_EQ(dst->ci_chroma_sample_position_present_flag, 0); + EXPECT_EQ(dst->ci_aspect_ratio_info_present_flag, 0); + EXPECT_EQ(dst->ci_timing_info_present_flag, 0); +} + +TEST_F(CiTest, ColorInfoExplicitRoundtrip) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE; + src.ci_color_description_present_flag = 1; + src.color_info.color_description_idc = 0; // explicit + src.color_info.color_primaries = AVM_CICP_CP_BT_709; + src.color_info.transfer_characteristics = AVM_CICP_TC_SRGB; + src.color_info.matrix_coefficients = AVM_CICP_MC_BT_709; + src.color_info.full_range_flag = 1; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_color_description_present_flag, 1); + EXPECT_EQ(dst->color_info.color_description_idc, 0); + EXPECT_EQ(dst->color_info.color_primaries, AVM_CICP_CP_BT_709); + EXPECT_EQ(dst->color_info.transfer_characteristics, AVM_CICP_TC_SRGB); + EXPECT_EQ(dst->color_info.matrix_coefficients, AVM_CICP_MC_BT_709); + EXPECT_EQ(dst->color_info.full_range_flag, 1); +} + +TEST_F(CiTest, ColorInfoImplicitRoundtrip) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_color_description_present_flag = 1; + src.color_info.color_description_idc = 1; // implicit + src.color_info.full_range_flag = 0; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->color_info.color_description_idc, 1); + EXPECT_EQ(dst->color_info.full_range_flag, 0); +} + +// Progressive scan: only ci_chroma_sample_position[0] is written. +TEST_F(CiTest, ChromaSamplePositionProgressive) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE; + src.ci_chroma_sample_position_present_flag = 1; + src.ci_chroma_sample_position[0] = AVM_CSP_LEFT; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_LEFT); + // Progressive: [1] is copied from [0]. + EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_LEFT); +} + +// Non-progressive scan: both chroma sample positions are written. +TEST_F(CiTest, ChromaSamplePositionInterlace) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = AVM_SCAN_TYPE_INTERLACE; + src.ci_chroma_sample_position_present_flag = 1; + src.ci_chroma_sample_position[0] = AVM_CSP_LEFT; + src.ci_chroma_sample_position[1] = AVM_CSP_TOPLEFT; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_LEFT); + EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_TOPLEFT); +} + +TEST_F(CiTest, SarInfoStandardRoundtrip) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_aspect_ratio_info_present_flag = 1; + src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_1_TO_1; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->sar_info.sar_aspect_ratio_idc, AVM_SAR_IDC_1_TO_1); +} + +// SAR_IDC_255 = explicit SAR with width/height. +TEST_F(CiTest, SarInfoExplicitRoundtrip) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_aspect_ratio_info_present_flag = 1; + src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_255; + src.sar_info.sar_width = 4; + src.sar_info.sar_height = 3; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->sar_info.sar_aspect_ratio_idc, AVM_SAR_IDC_255); + EXPECT_EQ(dst->sar_info.sar_width, 4); + EXPECT_EQ(dst->sar_info.sar_height, 3); +} + +TEST_F(CiTest, TimingInfoRoundtrip) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_timing_info_present_flag = 1; + src.timing_info.num_units_in_display_tick = 1000; + src.timing_info.time_scale = 30000; + src.timing_info.equal_elemental_interval = 1; + src.timing_info.num_ticks_per_elemental_duration = 1; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->timing_info.num_units_in_display_tick, 1000u); + EXPECT_EQ(dst->timing_info.time_scale, 30000u); + EXPECT_EQ(dst->timing_info.equal_elemental_interval, 1); + EXPECT_EQ(dst->timing_info.num_ticks_per_elemental_duration, 1u); +} + +TEST_F(CiTest, TimingInfoNoEqualInterval) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_timing_info_present_flag = 1; + src.timing_info.num_units_in_display_tick = 1001; + src.timing_info.time_scale = 60000; + src.timing_info.equal_elemental_interval = 0; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->timing_info.num_units_in_display_tick, 1001u); + EXPECT_EQ(dst->timing_info.time_scale, 60000u); + EXPECT_EQ(dst->timing_info.equal_elemental_interval, 0); +} + +// All optional fields enabled. +TEST_F(CiTest, AllFieldsPresent) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE; + src.ci_color_description_present_flag = 1; + src.ci_chroma_sample_position_present_flag = 1; + src.ci_aspect_ratio_info_present_flag = 1; + src.ci_timing_info_present_flag = 1; + + src.color_info.color_description_idc = 0; + src.color_info.color_primaries = AVM_CICP_CP_BT_2020; + src.color_info.transfer_characteristics = AVM_CICP_TC_SMPTE_2084; + src.color_info.matrix_coefficients = AVM_CICP_MC_BT_2020_NCL; + src.color_info.full_range_flag = 0; + + src.ci_chroma_sample_position[0] = AVM_CSP_CENTER; + + src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_1_TO_1; + + src.timing_info.num_units_in_display_tick = 1; + src.timing_info.time_scale = 24; + src.timing_info.equal_elemental_interval = 1; + src.timing_info.num_ticks_per_elemental_duration = 1; + + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u); + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written); + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_scan_type_idc, AVM_SCAN_TYPE_PROGRESSIVE); + EXPECT_EQ(dst->color_info.color_primaries, AVM_CICP_CP_BT_2020); + EXPECT_EQ(dst->color_info.transfer_characteristics, AVM_CICP_TC_SMPTE_2084); + EXPECT_EQ(dst->color_info.matrix_coefficients, AVM_CICP_MC_BT_2020_NCL); + EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_CENTER); + EXPECT_EQ(dst->sar_info.sar_aspect_ratio_idc, AVM_SAR_IDC_1_TO_1); + EXPECT_EQ(dst->timing_info.time_scale, 24u); +} + +TEST_F(CiTest, ScanTypeSweep) { + const int scan_types[] = { AVM_SCAN_TYPE_UNSPECIFIED, + AVM_SCAN_TYPE_PROGRESSIVE, AVM_SCAN_TYPE_INTERLACE, + AVM_SCAN_TYPE_INTERLACE_COMPLEMENTARY }; + for (int si = 0; si < 4; si++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = (avm_pic_scan_type_t)scan_types[si]; + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "scan=" << scan_types[si]; + + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "scan=" << scan_types[si]; + EXPECT_EQ(pbi_->common.ci_params_per_layer[0].ci_scan_type_idc, + scan_types[si]); + } +} + +TEST_F(CiTest, ColorIdcHighValues) { + const int idc_values[] = { 0, 1, 4, 8, 20 }; + for (int ci = 0; ci < 5; ci++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_color_description_present_flag = 1; + src.color_info.color_description_idc = idc_values[ci]; + if (idc_values[ci] == 0) { + src.color_info.color_primaries = AVM_CICP_CP_BT_2020; + src.color_info.transfer_characteristics = AVM_CICP_TC_SMPTE_2084; + src.color_info.matrix_coefficients = AVM_CICP_MC_BT_2020_NCL; + } + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "idc=" << idc_values[ci]; + + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "idc=" << idc_values[ci]; + EXPECT_EQ( + pbi_->common.ci_params_per_layer[0].color_info.color_description_idc, + idc_values[ci]); + } +} + +TEST_F(CiTest, FlagCombinations) { + // Timing combinations are tested separately in TimingInfoRoundtrip, + // TimingInfoNoEqualInterval, and AllFieldsPresent. The combined sweep + // skips them because av2_read_timing_info_header uses avm_internal_error + // which requires setjmp context not available in loop resets. + for (int flags = 0; flags < 8; flags++) { + bool color = flags & 1; + bool chroma = (flags >> 1) & 1; + bool sar = (flags >> 2) & 1; + bool timing = (flags >> 3) & 1; + + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = AVM_SCAN_TYPE_PROGRESSIVE; + src.ci_color_description_present_flag = color; + src.ci_chroma_sample_position_present_flag = chroma; + src.ci_aspect_ratio_info_present_flag = sar; + src.ci_timing_info_present_flag = timing; + + if (color) { + src.color_info.color_description_idc = 1; + } + if (chroma) { + src.ci_chroma_sample_position[0] = AVM_CSP_LEFT; + } + if (sar) { + src.sar_info.sar_aspect_ratio_idc = AVM_SAR_IDC_1_TO_1; + } + if (timing) { + src.timing_info.num_units_in_display_tick = 1; + src.timing_info.time_scale = 30; + } + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "flags=" << flags; + + // Reset CI params and OBU tracking without wiping error state. + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "flags=" << flags; + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_color_description_present_flag, color) + << "flags=" << flags; + EXPECT_EQ(dst->ci_chroma_sample_position_present_flag, chroma) + << "flags=" << flags; + EXPECT_EQ(dst->ci_aspect_ratio_info_present_flag, sar) << "flags=" << flags; + EXPECT_EQ(dst->ci_timing_info_present_flag, timing) << "flags=" << flags; + } +} + +TEST_F(CiTest, ChromaSamplePositionValueSweep) { + const int csp_values[] = { AVM_CSP_LEFT, AVM_CSP_CENTER, + AVM_CSP_TOPLEFT, AVM_CSP_TOP, + AVM_CSP_BOTTOMLEFT, AVM_CSP_BOTTOM, + AVM_CSP_UNSPECIFIED }; + for (int ci = 0; ci < 7; ci++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = AVM_SCAN_TYPE_INTERLACE; + src.ci_chroma_sample_position_present_flag = 1; + src.ci_chroma_sample_position[0] = csp_values[ci]; + src.ci_chroma_sample_position[1] = csp_values[(ci + 1) % 7]; + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "csp=" << csp_values[ci]; + + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "csp=" << csp_values[ci]; + EXPECT_EQ(pbi_->common.ci_params_per_layer[0].ci_chroma_sample_position[0], + csp_values[ci]); + EXPECT_EQ(pbi_->common.ci_params_per_layer[0].ci_chroma_sample_position[1], + csp_values[(ci + 1) % 7]); + } +} + +TEST_F(CiTest, SarIdcStandardValueSweep) { + const int sar_values[] = { AVM_SAR_IDC_UNSPECIFIED, + AVM_SAR_IDC_1_TO_1, + AVM_SAR_IDC_12_TO_11, + 14, + 17, + 254 }; + for (int si = 0; si < 6; si++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_aspect_ratio_info_present_flag = 1; + src.sar_info.sar_aspect_ratio_idc = sar_values[si]; + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "sar=" << sar_values[si]; + + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "sar=" << sar_values[si]; + EXPECT_EQ(pbi_->common.ci_params_per_layer[0].sar_info.sar_aspect_ratio_idc, + sar_values[si]); + } +} + +TEST_F(CiTest, ColorFieldBoundaryValues) { + struct { + int primaries; + int transfer; + int matrix; + } params[] = { + { 0, 0, 0 }, + { AVM_CICP_CP_EBU_3213, AVM_CICP_TC_HLG, AVM_CICP_MC_ICTCP }, + }; + for (int pi = 0; pi < 2; pi++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_color_description_present_flag = 1; + src.color_info.color_description_idc = 0; // explicit + src.color_info.color_primaries = + (avm_color_primaries_t)params[pi].primaries; + src.color_info.transfer_characteristics = + (avm_transfer_characteristics_t)params[pi].transfer; + src.color_info.matrix_coefficients = + (avm_matrix_coefficients_t)params[pi].matrix; + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "pi=" << pi; + + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "pi=" << pi; + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->color_info.color_primaries, params[pi].primaries); + EXPECT_EQ(dst->color_info.transfer_characteristics, params[pi].transfer); + EXPECT_EQ(dst->color_info.matrix_coefficients, params[pi].matrix); + } +} + +TEST_F(CiTest, TimingInfoBoundaryValues) { + struct { + uint32_t units; + uint32_t scale; + int equal; + uint32_t ticks; + } params[] = { + { 1, 1, 0, 0 }, + { 1, 1, 1, 2 }, + { 0x7FFFFFFF, 0x7FFFFFFF, 1, 65535 }, + }; + for (int ti = 0; ti < 3; ti++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_timing_info_present_flag = 1; + src.timing_info.num_units_in_display_tick = params[ti].units; + src.timing_info.time_scale = params[ti].scale; + src.timing_info.equal_elemental_interval = params[ti].equal; + src.timing_info.num_ticks_per_elemental_duration = params[ti].ticks; + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "ti=" << ti; + + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "ti=" << ti; + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->timing_info.num_units_in_display_tick, params[ti].units); + EXPECT_EQ(dst->timing_info.time_scale, params[ti].scale); + EXPECT_EQ(dst->timing_info.equal_elemental_interval, params[ti].equal); + if (params[ti].equal) { + EXPECT_EQ(dst->timing_info.num_ticks_per_elemental_duration, + params[ti].ticks); + } + } +} + +// All 4 scan types with chroma present — verifies the ci_scan_type_idc != 1 +// condition. +TEST_F(CiTest, ScanTypeChromaInteraction) { + const int scan_types[] = { AVM_SCAN_TYPE_UNSPECIFIED, + AVM_SCAN_TYPE_PROGRESSIVE, AVM_SCAN_TYPE_INTERLACE, + AVM_SCAN_TYPE_INTERLACE_COMPLEMENTARY }; + for (int si = 0; si < 4; si++) { + ContentInterpretation src; + av2_initialize_ci_params(&src); + src.ci_scan_type_idc = (avm_pic_scan_type_t)scan_types[si]; + src.ci_chroma_sample_position_present_flag = 1; + src.ci_chroma_sample_position[0] = AVM_CSP_LEFT; + src.ci_chroma_sample_position[1] = AVM_CSP_TOP; + + memset(buf_, 0, sizeof(buf_)); + uint32_t written = write_ci_obu(&src, buf_); + ASSERT_GT(written, 0u) << "scan=" << scan_types[si]; + + for (int m = 0; m < MAX_NUM_MLAYERS; m++) { + av2_initialize_ci_params(&pbi_->common.ci_params_per_layer[m]); + } + memset(pbi_->obus_in_frame_unit_data, 0, + sizeof(pbi_->obus_in_frame_unit_data)); + pbi_->obus_in_frame_unit_data[0][0][OBU_CLOSED_LOOP_KEY] = 1; + pbi_->obus_in_frame_unit_data[0][0][OBU_CONTENT_INTERPRETATION] = 1; + pbi_->common.error.error_code = AVM_CODEC_OK; + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + uint32_t read = av2_read_content_interpretation_obu(pbi_, &rb); + ASSERT_EQ(read, written) << "scan=" << scan_types[si]; + + const ContentInterpretation *dst = &pbi_->common.ci_params_per_layer[0]; + EXPECT_EQ(dst->ci_chroma_sample_position[0], AVM_CSP_LEFT); + if (scan_types[si] == AVM_SCAN_TYPE_PROGRESSIVE) { + // Progressive: [1] copied from [0] + EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_LEFT); + } else { + // Non-progressive: [1] read from bitstream + EXPECT_EQ(dst->ci_chroma_sample_position[1], AVM_CSP_TOP); + } + } +} + +} // namespace
diff --git a/test/test.cmake b/test/test.cmake index ce7567f..5290677 100644 --- a/test/test.cmake +++ b/test/test.cmake
@@ -119,6 +119,7 @@ "${AVM_ROOT}/test/av2_encoder_parms_get_to_decoder.cc" "${AVM_ROOT}/test/binary_codes_test.cc" "${AVM_ROOT}/test/boolcoder_test.cc" + "${AVM_ROOT}/test/ci_test.cc" "${AVM_ROOT}/test/cnn_test.cc" "${AVM_ROOT}/test/decode_multithreaded_test.cc" "${AVM_ROOT}/test/divu_small_test.cc"