From 811afede8810dafbae880f6721c84adf9c6193b0 Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Mon, 10 Aug 2026 14:20:36 -0700 Subject: [PATCH 1/8] Decoder model implementation and fixes to decoder model in encoder --- apps/avmdec.c | 33 +- av2/av2.cmake | 4 + av2/av2_cx_iface.c | 8 +- av2/av2_dx_iface.c | 95 +- av2/common/annexA.c | 63 +- av2/common/annexA.h | 27 +- av2/common/av2_common_int.h | 3 + av2/common/decoder_model.c | 3845 ++++++++++++++++++++++++ av2/common/decoder_model.h | 458 +++ av2/common/level.c | 2102 +++++++++++-- av2/common/level.h | 211 +- av2/common/tile_common.h | 2 + av2/common/timing.c | 50 +- av2/common/timing.h | 5 + av2/decoder/annexF.c | 75 + av2/decoder/annexF.h | 9 + av2/decoder/decodeframe.c | 40 + av2/decoder/decoder.c | 58 +- av2/decoder/decoder.h | 4 + av2/decoder/decoder_model.c | 3634 ++++++++++++++++++++++ av2/decoder/decoder_model.h | 166 + av2/decoder/obu.c | 58 +- av2/decoder/obu_buf.c | 4 + av2/decoder/obu_ops.c | 9 +- av2/encoder/bitstream.c | 27 +- av2/encoder/bitstream.h | 5 + av2/encoder/encode_strategy.c | 1 + av2/encoder/encoder.c | 59 +- av2/encoder/encoder.h | 14 +- av2/encoder/encoder_alloc.h | 1 + av2/encoder/encoder_utils.h | 26 +- avm/avmdx.h | 22 + avm_dsp/bitreader.c | 2 + avm_dsp/bitreader.h | 5 + test/brt_test.cc | 8 + test/decoder_model_integration_test.cc | 2405 +++++++++++++++ test/decoder_model_parser_test.cc | 764 +++++ test/decoder_model_test.cc | 3141 +++++++++++++++++++ test/encode_api_test.cc | 9 + test/encoder_decoder_model_test.cc | 1306 ++++++++ test/level_test.cc | 1703 +++++++++++ test/ops_test.cc | 39 + test/test.cmake | 10 + 43 files changed, 20091 insertions(+), 419 deletions(-) create mode 100644 av2/common/decoder_model.c create mode 100644 av2/common/decoder_model.h create mode 100644 av2/decoder/decoder_model.c create mode 100644 av2/decoder/decoder_model.h create mode 100644 test/decoder_model_integration_test.cc create mode 100644 test/decoder_model_parser_test.cc create mode 100644 test/decoder_model_test.cc create mode 100644 test/encoder_decoder_model_test.cc diff --git a/apps/avmdec.c b/apps/avmdec.c index 1d80f28558..8ed482ce32 100644 --- a/apps/avmdec.c +++ b/apps/avmdec.c @@ -113,6 +113,9 @@ static const arg_def_t verifyarg = ARG_DEF(NULL, "verify", 1, "Use Decoded Frame Hash Metadata to verify integrity of decoded " "frames (off, fatal, warn)"); +static const arg_def_t checkconformancearg = + ARG_DEF(NULL, "check-conformance", 1, + "Check decoder-model conformance (off, warn, fatal)"); static const arg_def_t framestatsarg = ARG_DEF(NULL, "framestats", 1, "Output per-frame stats (.csv format)"); static const arg_def_t outbitdeptharg = @@ -159,6 +162,7 @@ static const arg_def_t *all_args[] = { &help, &fb_arg, &md5arg, &verifyarg, + &checkconformancearg, &framestatsarg, &continuearg, &outbitdeptharg, @@ -654,6 +658,8 @@ static int main_loop(int argc, const char **argv_) { int frame_in = 0, frame_out = 0, flipuv = 0, noblit = 0; int do_md5 = 0, progress = 0; int do_verify = 0, error_on_verify = 0; + avm_decoder_model_check_mode_t decoder_model_check_mode = + AVM_DECODER_MODEL_CHECK_OFF; int stop_after = 0, summary = 0, quiet = 1; int arg_skip = 0; int num_streams = 1; @@ -812,6 +818,16 @@ static int main_loop(int argc, const char **argv_) { error_on_verify = 1; } else if (strcmp(arg.val, "off")) die("Error: Invalid argument for --verify (%s).\n", arg.val); + } else if (arg_match(&arg, &checkconformancearg, argi)) { + if (!strcmp(arg.val, "warn")) { + decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_WARN; + } else if (!strcmp(arg.val, "fatal")) { + decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_FATAL; + } else if (!strcmp(arg.val, "off")) { + decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_OFF; + } else { + die("Error: Invalid argument for --check-conformance (%s).\n", arg.val); + } } else if (arg_match(&arg, &framestatsarg, argi)) { framestats_file = fopen(arg.val, "w"); if (!framestats_file) { @@ -1004,6 +1020,13 @@ static int main_loop(int argc, const char **argv_) { if (!quiet) fprintf(stderr, "%s\n", decoder.name); + if (AVM_CODEC_CONTROL_TYPECHECKED(&decoder, AV2D_SET_DECODER_MODEL_CHECK_MODE, + decoder_model_check_mode)) { + fprintf(stderr, "Failed to set decoder-model conformance mode: %s\n", + avm_codec_error(&decoder)); + goto fail; + } + // Only set selected OPS when explicitly requested via --select-ops. // Setting it to 0,0 by default enables sub-bitstream extraction (SBE), // which can incorrectly filter out frame OBUs in multi-layer bitstreams @@ -1096,13 +1119,19 @@ static int main_loop(int argc, const char **argv_) { avm_usec_timer_start(&timer); - if (avm_codec_decode(&decoder, buf, bytes_in_buffer, NULL)) { + const avm_codec_err_t decode_status = + avm_codec_decode(&decoder, buf, bytes_in_buffer, NULL); + if (decode_status != AVM_CODEC_OK) { const char *detail = avm_codec_error_detail(&decoder); warn("Failed to decode frame %d: %s", frame_in, avm_codec_error(&decoder)); if (detail) warn("Additional information: %s", detail); - if (!keep_going) goto fail; + if (!keep_going || + (detail != NULL && + !strcmp(detail, "Decoder model conformance violation"))) { + goto fail; + } } if (framestats_file) { diff --git a/av2/av2.cmake b/av2/av2.cmake index 57f639be12..8098bf851b 100644 --- a/av2/av2.cmake +++ b/av2/av2.cmake @@ -47,6 +47,8 @@ list( "${AVM_ROOT}/av2/common/common_data.h" "${AVM_ROOT}/av2/common/convolve.c" "${AVM_ROOT}/av2/common/convolve.h" + "${AVM_ROOT}/av2/common/decoder_model.c" + "${AVM_ROOT}/av2/common/decoder_model.h" "${AVM_ROOT}/av2/common/entropy.c" "${AVM_ROOT}/av2/common/entropy.h" "${AVM_ROOT}/av2/common/entropymode.c" @@ -157,6 +159,8 @@ list( "${AVM_ROOT}/av2/decoder/decodemv.h" "${AVM_ROOT}/av2/decoder/decoder.c" "${AVM_ROOT}/av2/decoder/decoder.h" + "${AVM_ROOT}/av2/decoder/decoder_model.c" + "${AVM_ROOT}/av2/decoder/decoder_model.h" "${AVM_ROOT}/av2/decoder/decodetxb.c" "${AVM_ROOT}/av2/decoder/decodetxb.h" "${AVM_ROOT}/av2/decoder/detokenize.c" diff --git a/av2/av2_cx_iface.c b/av2/av2_cx_iface.c index 06a08cb5ba..c34a837f41 100644 --- a/av2/av2_cx_iface.c +++ b/av2/av2_cx_iface.c @@ -666,6 +666,10 @@ static avm_codec_err_t validate_config(avm_codec_alg_priv_t *ctx, RANGE_CHECK(cfg, g_timebase.num, 1, cfg->g_timebase.den); RANGE_CHECK_HI(cfg, g_profile, MAX_PROFILES - 1); + if (cfg->g_profile >= (unsigned int)RESERVED_PROFILES_START && + cfg->g_profile < (unsigned int)CONFIGURABLE) { + ERROR("g_profile selects a reserved profile"); + } #if !CONFIG_12BIT_PROFILE RANGE_CHECK(cfg, g_bit_depth, AVM_BITS_8, AVM_BITS_10); RANGE_CHECK(cfg, g_input_bit_depth, AVM_BITS_8, AVM_BITS_10); @@ -3388,7 +3392,7 @@ static avm_codec_err_t encoder_encode(avm_codec_alg_priv_t *ctx, cpi_lap->mt_info.num_workers = cpi->mt_info.num_workers; const int status = av2_get_compressed_data( cpi_lap, &lib_flags, &frame_size, NULL, &dst_time_stamp_la, - &dst_end_time_stamp_la, !img, timestamp_ratio); + &dst_end_time_stamp_la, !img, timestamp_ratio, false); if (status != -1) { if (status != AVM_CODEC_OK) { avm_internal_error(&cpi_lap->common.error, status, NULL); @@ -3420,7 +3424,7 @@ static avm_codec_err_t encoder_encode(avm_codec_alg_priv_t *ctx, const int status = av2_get_compressed_data( cpi, &lib_flags, &frame_size, cx_data, &dst_time_stamp, - &dst_end_time_stamp, !img, timestamp_ratio); + &dst_end_time_stamp, !img, timestamp_ratio, ready_for_next_tu != 0); avm_usec_timer_mark(&timer); cx_time += avm_usec_timer_elapsed(&timer); if (status == -1) break; diff --git a/av2/av2_dx_iface.c b/av2/av2_dx_iface.c index d65e86fff9..a393bdc289 100644 --- a/av2/av2_dx_iface.c +++ b/av2/av2_dx_iface.c @@ -34,6 +34,7 @@ #include "av2/decoder/decoder.h" #include "av2/decoder/decodeframe.h" +#include "av2/decoder/decoder_model.h" #include "av2/decoder/obu.h" #include "avm_dsp/bitwriter_buffer.h" @@ -62,6 +63,9 @@ struct avm_codec_alg_priv { int local_ops_selections[MAX_NUM_XLAYERS - 1][3]; int num_local_ops_selections; int output_all_layers; + avm_decoder_model_check_mode_t decoder_model_check_mode; + int compressed_input_started; + int decoder_model_fatal_latched; AVxWorker *frame_worker; @@ -116,6 +120,7 @@ static avm_codec_err_t decoder_init(avm_codec_ctx_t *ctx) { priv->random_access_point_index = 0; priv->enable_sub_bitstream_extraction = 0; priv->num_local_ops_selections = 0; + priv->decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_OFF; init_ibp_info(ctx->priv->ibp_directional_weights); } @@ -401,6 +406,10 @@ static int frame_worker_hook(void *arg1, void *arg2) { if (result != 0) { // Check decode result in serial decode. + if (frame_worker_data->pbi->decoder_model_verifier != NULL && + !av2_decoder_model_verifier_should_stop(frame_worker_data->pbi)) { + av2_decoder_model_verifier_on_recovery_reset(frame_worker_data->pbi); + } frame_worker_data->pbi->need_resync = 1; } return !result; @@ -473,6 +482,11 @@ static avm_codec_err_t init_decoder(avm_codec_alg_priv_t *ctx) { } } frame_worker_data->pbi->output_all_layers = ctx->output_all_layers; + frame_worker_data->pbi->decoder_model_check_mode = + ctx->decoder_model_check_mode; + if (ctx->decoder_model_check_mode != AVM_DECODER_MODEL_CHECK_OFF) { + av2_decoder_model_verifier_init(frame_worker_data->pbi); + } frame_worker_data->pbi->row_mt = ctx->row_mt; frame_worker_data->pbi->is_fwd_kf_present = 0; frame_worker_data->pbi->enable_subgop_stats = ctx->enable_subgop_stats; @@ -578,6 +592,10 @@ static avm_codec_err_t decoder_inspect(avm_codec_alg_priv_t *ctx, frame_worker_data->pbi->inspect_tip_cb = ctx->inspect_tip_cb; frame_worker_data->pbi->inspect_ctx = ctx->inspect_ctx; res = av2_receive_compressed_data(frame_worker_data->pbi, data_sz, &data); + if (res != AVM_CODEC_OK && pbi->decoder_model_verifier != NULL && + !av2_decoder_model_verifier_should_stop(frame_worker_data->pbi)) { + av2_decoder_model_verifier_on_recovery_reset(frame_worker_data->pbi); + } check_resync(ctx, frame_worker_data->pbi); if (ctx->frame_worker->had_error) @@ -910,7 +928,26 @@ static avm_codec_err_t decoder_decode(avm_codec_alg_priv_t *ctx, #if CONFIG_INSPECTION if (user_priv != 0) { - return decoder_inspect(ctx, data, data_sz, user_priv); + if (data != NULL && data_sz != 0) { + ctx->compressed_input_started = 1; + if (ctx->decoder_model_fatal_latched) { + set_error_detail(ctx, "Decoder model conformance violation"); + return AVM_CODEC_UNSUP_BITSTREAM; + } + } + res = decoder_inspect(ctx, data, data_sz, user_priv); + if (ctx->frame_worker != NULL) { + FrameWorkerData *const frame_worker_data = + (FrameWorkerData *)ctx->frame_worker->data1; + AV2Decoder *const pbi = frame_worker_data->pbi; + if (av2_decoder_model_verifier_should_stop(pbi)) { + av2_decoder_model_verifier_finish(pbi); + ctx->decoder_model_fatal_latched = 1; + set_error_detail(ctx, "Decoder model conformance violation"); + return AVM_CODEC_UNSUP_BITSTREAM; + } + } + return res; } #endif @@ -945,6 +982,15 @@ static avm_codec_err_t decoder_decode(avm_codec_alg_priv_t *ctx, if (data == NULL && data_sz == 0) { AV2_COMMON *const cm = &pbi->common; avm_codec_err_t err = flush_all_xlayer_frames(pbi, cm, false); + if (pbi->decoder_model_verifier != NULL || + pbi->decoder_model_verifier_allocation_failed) { + av2_decoder_model_verifier_finish(pbi); + } + if (av2_decoder_model_verifier_should_stop(pbi)) { + ctx->decoder_model_fatal_latched = 1; + set_error_detail(ctx, "Decoder model conformance violation"); + return AVM_CODEC_UNSUP_BITSTREAM; + } bool global_lcr_present = false; bool local_lcr_present = false; @@ -976,6 +1022,12 @@ static avm_codec_err_t decoder_decode(avm_codec_alg_priv_t *ctx, } if (data == NULL || data_sz == 0) return AVM_CODEC_INVALID_PARAM; + ctx->compressed_input_started = 1; + if (ctx->decoder_model_fatal_latched) { + set_error_detail(ctx, "Decoder model conformance violation"); + return AVM_CODEC_UNSUP_BITSTREAM; + } + // Reset flushed when receiving a valid frame. ctx->flushed = 0; @@ -1105,13 +1157,29 @@ static avm_codec_err_t decoder_decode(avm_codec_alg_priv_t *ctx, // Decode in serial mode. + // This boundary is established by the raw pre-scan, before Annex F can + // remove a frame unit. It therefore remains unique even when consecutive + // source frames are not decoded. + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_source_frame_unit_start( + pbi, xlayer_id, mlayer_id, tlayer_id); + } + res = decode_one(ctx, &data_start, frame_unit_size, user_priv); if (res != AVM_CODEC_OK) return res; set_last_frame_unit(frame_worker_data->pbi); free(frame_worker_data->pbi->obu_list); + frame_worker_data->pbi->obu_list = NULL; frame_worker_data->pbi->num_obus_with_frame_unit = 0; + + if (av2_decoder_model_verifier_should_stop(pbi)) { + av2_decoder_model_verifier_finish(pbi); + ctx->decoder_model_fatal_latched = 1; + set_error_detail(ctx, "Decoder model conformance violation"); + return AVM_CODEC_UNSUP_BITSTREAM; + } } if (data_start != data_end) { @@ -1974,6 +2042,30 @@ static avm_codec_err_t ctrl_set_output_all_layers(avm_codec_alg_priv_t *ctx, return AVM_CODEC_OK; } +static avm_codec_err_t ctrl_set_decoder_model_check_mode( + avm_codec_alg_priv_t *ctx, va_list args) { + const int raw_mode = va_arg(args, int); + if (raw_mode < AVM_DECODER_MODEL_CHECK_OFF || + raw_mode > AVM_DECODER_MODEL_CHECK_WARN || + ctx->compressed_input_started) { + return AVM_CODEC_INVALID_PARAM; + } + const avm_decoder_model_check_mode_t mode = + (avm_decoder_model_check_mode_t)raw_mode; + ctx->decoder_model_check_mode = mode; + if (ctx->frame_worker != NULL) { + FrameWorkerData *const frame_worker_data = + (FrameWorkerData *)ctx->frame_worker->data1; + AV2Decoder *const pbi = frame_worker_data->pbi; + av2_decoder_model_verifier_destroy(pbi); + pbi->decoder_model_check_mode = mode; + if (mode != AVM_DECODER_MODEL_CHECK_OFF) { + av2_decoder_model_verifier_init(pbi); + } + } + return AVM_CODEC_OK; +} + static avm_codec_err_t ctrl_set_sub_bitstream_extraction( avm_codec_alg_priv_t *ctx, va_list args) { ctx->enable_sub_bitstream_extraction = va_arg(args, int); @@ -2039,6 +2131,7 @@ static avm_codec_ctrl_fn_map_t decoder_ctrl_maps[] = { { AV2D_SET_SKIP_FILM_GRAIN, ctrl_set_skip_film_grain }, { AV2D_SET_RANDOM_ACCESS, ctrl_set_random_access }, { AV2D_SET_BRU_OPT_MODE, ctrl_set_bru_opt_mode }, + { AV2D_SET_DECODER_MODEL_CHECK_MODE, ctrl_set_decoder_model_check_mode }, { AV2D_ENABLE_SUBGOP_STATS, ctrl_enable_subgop_stats }, // Getters diff --git a/av2/common/annexA.c b/av2/common/annexA.c index d23b3447c4..bb3351cf6b 100644 --- a/av2/common/annexA.c +++ b/av2/common/annexA.c @@ -315,39 +315,42 @@ int av2_check_profile_interop_conformance( */ /* clang-format on */ -// Returns the row index used to look up a profile's factors in the -// PicSize/Bitrate factor tables (see bitrate_profile_factor_table[] and -// picture_size_profile_factor_table[] in level.c, and bitrate_profile_factor[] -// in timing.c). -// -// NOTE: This is a table row index, NOT the spec's ProfileScalingFactor from -// Table A.2. The two coincide for profiles 0..4 (0, 0, 0, 1, 2), but profile -// MAIN_444C_12_IP2 (idc 5) has spec ProfileScalingFactor 2 -- the same as -// profile MAIN_444_10_IP1 (idc 4) -- while requiring DIFFERENT PicSize and -// Bitrate factors (36 / 3.0 vs 30 / 2.5). Since one ProfileScalingFactor value -// cannot select two different factor rows, profile 5 is given its own row -// index (3) here. -int get_profile_factor_table_row_index(int seq_profile_idc) { - if (seq_profile_idc == MAIN_420_10_IP0 || - seq_profile_idc == MAIN_420_10_IP1 || - seq_profile_idc == MAIN_420_10_IP2) { - return 0; - } +static const AV2ProfileLevelFactors profile_level_factors[] = { + { 15, 1, 1 }, { 15, 1, 1 }, { 15, 1, 1 }, + { 20, 1667, 1000 }, { 30, 5, 2 }, { 36, 3, 1 }, +}; - if (seq_profile_idc == MAIN_422_10_IP1) { - return 1; - } +_Static_assert(sizeof(profile_level_factors) / + sizeof(profile_level_factors[0]) == + 6, + "Annex A profile factor table must cover profiles 0 through 5"); - if (seq_profile_idc == MAIN_444_10_IP1) { - return 2; +bool av2_get_profile_level_factors(int seq_profile_idc, + AV2ProfileLevelFactors *factors) { + if (factors == NULL || seq_profile_idc < 0 || + seq_profile_idc >= (int)(sizeof(profile_level_factors) / + sizeof(profile_level_factors[0]))) { + return false; } + *factors = profile_level_factors[seq_profile_idc]; + return true; +} -#if CONFIG_12BIT_PROFILE - if (seq_profile_idc == MAIN_444C_12_IP2) { - return 3; +bool av2_obu_counts_toward_compressed_size(OBU_TYPE obu_type) { + switch (obu_type) { + case OBU_CLOSED_LOOP_KEY: + case OBU_OPEN_LOOP_KEY: + case OBU_LEADING_TILE_GROUP: + case OBU_REGULAR_TILE_GROUP: + case OBU_METADATA_SHORT: + case OBU_METADATA_GROUP: + case OBU_SWITCH: + case OBU_LEADING_SEF: + case OBU_REGULAR_SEF: + case OBU_LEADING_TIP: + case OBU_REGULAR_TIP: + case OBU_BRIDGE_FRAME: + case OBU_RAS_FRAME: return true; + default: return false; } -#endif // CONFIG_12BIT_PROFILE - - // Default for invalid combinations and Configurable profile - return 0; } diff --git a/av2/common/annexA.h b/av2/common/annexA.h index 3b0b5fe4f0..584b7dacf2 100644 --- a/av2/common/annexA.h +++ b/av2/common/annexA.h @@ -13,6 +13,9 @@ #ifndef AVM_AV2_COMMON_ANNEXA_H_ #define AVM_AV2_COMMON_ANNEXA_H_ +#include +#include + /*!\file * \brief Provides the profile related functions * These include: @@ -43,15 +46,23 @@ int av2_check_profile_interop_conformance( struct SequenceHeader *seq_params, struct avm_internal_error_info *error_info, int is_decoder); -//========================================== -// Profile Scaling and Bitrate Functions -//=========================================== -// Gets the row index for a profile's PicSize/Bitrate factors (Table A.2, -// CWG-G004). See the definition in annexA.c for why this is not the spec's -// ProfileScalingFactor. -int get_profile_factor_table_row_index(int seq_profile_idc); +typedef struct AV2ProfileLevelFactors { + uint32_t picture_size_profile_factor; + uint32_t bitrate_factor_numerator; + uint32_t bitrate_factor_denominator; +} AV2ProfileLevelFactors; + +// Returns the exact PicSizeProfileFactor and BitrateProfileFactor values for +// profiles 0 through 5, independent of CONFIG_12BIT_PROFILE. Callers that +// select a bitstream profile enforce its build-time support separately. +// Returns false for values without a table row, including Configurable. +bool av2_get_profile_level_factors(int seq_profile_idc, + AV2ProfileLevelFactors *factors); + +// Returns whether an OBU contributes to CompressedSize in Annex A. +bool av2_obu_counts_toward_compressed_size(OBU_TYPE obu_type); #ifdef __cplusplus } // extern "C" #endif -#endif // AVM_AV2_COMMON_TIMING_H_ +#endif // AVM_AV2_COMMON_ANNEXA_H_ diff --git a/av2/common/av2_common_int.h b/av2/common/av2_common_int.h index e90a00f57b..4ab6ba03b5 100644 --- a/av2/common/av2_common_int.h +++ b/av2/common/av2_common_int.h @@ -973,6 +973,7 @@ typedef struct OperatingPoint { // Details per layer int ops_xlayer_map; + bool ops_initial_display_delay_present_flag; int ops_initial_display_delay; int ops_decoder_model_info_for_this_op_present_flag; int ops_mlayer_explicit_info_flag[MAX_NUM_XLAYERS]; @@ -2983,6 +2984,8 @@ typedef struct AV2Common { * Temporal point info */ avm_metadata_temporal_point_info_t temporal_point_info_metadata; + /*! Whether temporal point information is present. */ + bool temporal_point_info_present; /*! * Order hint of the last encountered OLK diff --git a/av2/common/decoder_model.c b/av2/common/decoder_model.c new file mode 100644 index 0000000000..49a1d284d2 --- /dev/null +++ b/av2/common/decoder_model.c @@ -0,0 +1,3845 @@ +/* + * 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 "av2/common/decoder_model.h" + +#include +#include + +#include "avm_mem/avm_mem.h" +#include "av2/common/annexA.h" +#include "av2/common/level.h" +#include "av2/common/tile_common.h" +#include "av2/common/timing.h" + +_Static_assert(sizeof(uint32_t) * CHAR_BIT == 32, "uint32_t must be 32 bits"); +_Static_assert(sizeof(uint64_t) * CHAR_BIT == 64, "uint64_t must be 64 bits"); +_Static_assert(sizeof(Av2DmUnsignedWide) * CHAR_BIT == 256, + "Av2DmUnsignedWide must be 256 bits"); + +#define AV2_DM_WIDE_LIMBS 4 +#define AV2_DM_PRODUCT_LIMBS (2 * AV2_DM_WIDE_LIMBS) + +typedef struct Av2DmUnsignedProduct { + uint64_t limbs[AV2_DM_PRODUCT_LIMBS]; +} Av2DmUnsignedProduct; + +static Av2DmUnsignedWide wide_from_u64(uint64_t value) { + Av2DmUnsignedWide result = { { value, 0, 0, 0 } }; + return result; +} + +static bool wide_is_zero(Av2DmUnsignedWide value) { + for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { + if (value.limbs[i] != 0) return false; + } + return true; +} + +static bool wide_equals_u64(Av2DmUnsignedWide value, uint64_t expected) { + return value.limbs[0] == expected && value.limbs[1] == 0 && + value.limbs[2] == 0 && value.limbs[3] == 0; +} + +static bool wide_fits_u64(Av2DmUnsignedWide value) { + return value.limbs[1] == 0 && value.limbs[2] == 0 && value.limbs[3] == 0; +} + +static int wide_compare(Av2DmUnsignedWide left, Av2DmUnsignedWide right) { + for (int i = AV2_DM_WIDE_LIMBS - 1; i >= 0; --i) { + if (left.limbs[i] != right.limbs[i]) { + return left.limbs[i] < right.limbs[i] ? -1 : 1; + } + } + return 0; +} + +#if defined(__clang__) && defined(__has_attribute) +#if __has_attribute(no_sanitize) +#define AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK \ + __attribute__(( \ + no_sanitize("unsigned-integer-overflow", "unsigned-shift-base"))) +#endif +#endif + +#ifndef AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK +#define AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK +#endif + +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool wide_add( + Av2DmUnsignedWide left, Av2DmUnsignedWide right, + Av2DmUnsignedWide *result) { + uint64_t carry = 0; + for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { + const uint64_t partial = left.limbs[i] + right.limbs[i]; + const uint64_t partial_carry = partial < left.limbs[i]; + const uint64_t sum = partial + carry; + const uint64_t carry_carry = sum < partial; + result->limbs[i] = sum; + carry = partial_carry | carry_carry; + } + return carry == 0; +} + +// Subtraction is modulo 2^256. Callers either establish left >= right or use +// the wraparound result as one step of long division with a 257th carry bit. +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static Av2DmUnsignedWide wide_subtract( + Av2DmUnsignedWide left, Av2DmUnsignedWide right) { + Av2DmUnsignedWide result; + uint64_t borrow = 0; + for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { + const uint64_t partial = left.limbs[i] - right.limbs[i]; + const uint64_t partial_borrow = left.limbs[i] < right.limbs[i]; + result.limbs[i] = partial - borrow; + const uint64_t borrow_borrow = partial < borrow; + borrow = partial_borrow | borrow_borrow; + } + return result; +} + +static uint64_t wide_get_bit(Av2DmUnsignedWide value, uint32_t bit_index) { + return (value.limbs[bit_index / 64] >> (bit_index % 64)) & 1; +} + +static void wide_set_bit(Av2DmUnsignedWide *value, uint32_t bit_index) { + value->limbs[bit_index / 64] |= UINT64_C(1) << (bit_index % 64); +} + +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool wide_shift_left_one( + Av2DmUnsignedWide *value) { + const bool overflow = (value->limbs[AV2_DM_WIDE_LIMBS - 1] >> 63) != 0; + for (int i = AV2_DM_WIDE_LIMBS - 1; i > 0; --i) { + value->limbs[i] = (value->limbs[i] << 1) | (value->limbs[i - 1] >> 63); + } + value->limbs[0] <<= 1; + return overflow; +} + +// Binary long division over the complete two-limb-independent representation. +static bool wide_divide(Av2DmUnsignedWide dividend, Av2DmUnsignedWide divisor, + Av2DmUnsignedWide *quotient, + Av2DmUnsignedWide *remainder) { + if (wide_is_zero(divisor)) return false; + if (wide_fits_u64(dividend) && wide_fits_u64(divisor)) { + const uint64_t divisor_low = divisor.limbs[0]; + if (divisor_low == 0) return false; + *quotient = wide_from_u64(dividend.limbs[0] / divisor_low); + *remainder = wide_from_u64(dividend.limbs[0] % divisor_low); + return true; + } + Av2DmUnsignedWide result = { { 0, 0, 0, 0 } }; + Av2DmUnsignedWide rem = { { 0, 0, 0, 0 } }; + for (int bit_index = 255; bit_index >= 0; --bit_index) { + const bool overflow = wide_shift_left_one(&rem); + rem.limbs[0] |= wide_get_bit(dividend, (uint32_t)bit_index); + if (overflow || wide_compare(rem, divisor) >= 0) { + rem = wide_subtract(rem, divisor); + wide_set_bit(&result, (uint32_t)bit_index); + } + } + *quotient = result; + *remainder = rem; + return true; +} + +static Av2DmUnsignedWide wide_gcd(Av2DmUnsignedWide left, + Av2DmUnsignedWide right) { + if (wide_fits_u64(left) && wide_fits_u64(right)) { + uint64_t a = left.limbs[0]; + uint64_t b = right.limbs[0]; + while (b != 0) { + const uint64_t remainder = a % b; + a = b; + b = remainder; + } + return wide_from_u64(a); + } + while (!wide_is_zero(right)) { + Av2DmUnsignedWide quotient; + Av2DmUnsignedWide remainder; + if (!wide_divide(left, right, "ient, &remainder)) { + return wide_from_u64(0); + } + left = right; + right = remainder; + } + return left; +} + +// Computes the complete 64-by-64-bit product using only fixed-width portable +// C arithmetic. Unsigned wraparound in the 32-bit partial-product assembly is +// intentional and defined by the C language. +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static void multiply_64( + uint64_t left, uint64_t right, uint64_t *product_low, + uint64_t *product_high) { + const uint64_t mask = UINT32_MAX; + const uint64_t left_low = left & mask; + const uint64_t left_high = left >> 32; + const uint64_t right_low = right & mask; + const uint64_t right_high = right >> 32; + const uint64_t low = left_low * right_low; + const uint64_t middle_1 = left_high * right_low + (low >> 32); + const uint64_t middle_2 = left_low * right_high + (middle_1 & mask); + *product_high = left_high * right_high + (middle_1 >> 32) + (middle_2 >> 32); + *product_low = (middle_2 << 32) | (low & mask); +} + +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool product_add_at( + Av2DmUnsignedProduct *product, uint32_t index, uint64_t low, + uint64_t high) { + if (index >= AV2_DM_PRODUCT_LIMBS) return low == 0 && high == 0; + const uint64_t old_low = product->limbs[index]; + product->limbs[index] += low; + uint64_t carry = product->limbs[index] < old_low; + ++index; + if (index >= AV2_DM_PRODUCT_LIMBS) return high == 0 && carry == 0; + + const uint64_t old_high = product->limbs[index]; + product->limbs[index] += high; + const uint64_t high_carry = product->limbs[index] < old_high; + const uint64_t partial = product->limbs[index]; + product->limbs[index] += carry; + const uint64_t carry_carry = product->limbs[index] < partial; + carry = high_carry | carry_carry; + ++index; + while (carry != 0 && index < AV2_DM_PRODUCT_LIMBS) { + ++product->limbs[index]; + carry = product->limbs[index] == 0; + ++index; + } + return carry == 0; +} + +#undef AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK + +static bool wide_multiply(Av2DmUnsignedWide left, Av2DmUnsignedWide right, + Av2DmUnsignedProduct *product) { + memset(product, 0, sizeof(*product)); + for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { + for (uint32_t j = 0; j < AV2_DM_WIDE_LIMBS; ++j) { + uint64_t low; + uint64_t high; + multiply_64(left.limbs[i], right.limbs[j], &low, &high); + if (!product_add_at(product, i + j, low, high)) return false; + } + } + return true; +} + +static int product_compare(Av2DmUnsignedProduct left, + Av2DmUnsignedProduct right) { + for (int i = AV2_DM_PRODUCT_LIMBS - 1; i >= 0; --i) { + if (left.limbs[i] != right.limbs[i]) { + return left.limbs[i] < right.limbs[i] ? -1 : 1; + } + } + return 0; +} + +static bool product_to_wide(Av2DmUnsignedProduct product, + Av2DmUnsignedWide *result) { + for (uint32_t i = AV2_DM_WIDE_LIMBS; i < AV2_DM_PRODUCT_LIMBS; ++i) { + if (product.limbs[i] != 0) return false; + } + for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { + result->limbs[i] = product.limbs[i]; + } + return true; +} + +static bool wide_multiply_checked(Av2DmUnsignedWide left, + Av2DmUnsignedWide right, + Av2DmUnsignedWide *result) { + if (wide_fits_u64(left) && wide_fits_u64(right)) { + memset(result, 0, sizeof(*result)); + multiply_64(left.limbs[0], right.limbs[0], &result->limbs[0], + &result->limbs[1]); + return true; + } + Av2DmUnsignedProduct product; + return wide_multiply(left, right, &product) && + product_to_wide(product, result); +} + +static bool rational_normalize(Av2DmRational *value) { + if (wide_is_zero(value->denominator)) return false; + if (wide_is_zero(value->magnitude)) { + value->denominator = wide_from_u64(1); + value->negative = false; + return true; + } + + const Av2DmUnsignedWide divisor = + wide_gcd(value->magnitude, value->denominator); + if (wide_is_zero(divisor)) return false; + if (!wide_equals_u64(divisor, 1)) { + Av2DmUnsignedWide remainder; + Av2DmUnsignedWide reduced; + if (!wide_divide(value->magnitude, divisor, &reduced, &remainder) || + !wide_is_zero(remainder)) { + return false; + } + value->magnitude = reduced; + if (!wide_divide(value->denominator, divisor, &reduced, &remainder) || + !wide_is_zero(remainder)) { + return false; + } + value->denominator = reduced; + } + return true; +} + +bool av2_dm_rational_make(uint64_t numerator, uint64_t denominator, + Av2DmRational *result) { + return av2_dm_rational_make_wide(wide_from_u64(numerator), denominator, false, + result); +} + +bool av2_dm_rational_make_wide(Av2DmUnsignedWide numerator, + uint64_t denominator, bool negative, + Av2DmRational *result) { + if (result == NULL || denominator == 0) return false; + result->magnitude = numerator; + result->denominator = wide_from_u64(denominator); + result->negative = negative; + return rational_normalize(result); +} + +static bool rational_compare_magnitudes(const Av2DmRational *left, + const Av2DmRational *right, + int *comparison) { + // Cancel factors common to both numerators and both denominators before + // forming the exact cross-products. The products are retained in 512 bits. + const Av2DmUnsignedWide numerator_gcd = + wide_gcd(left->magnitude, right->magnitude); + const Av2DmUnsignedWide denominator_gcd = + wide_gcd(left->denominator, right->denominator); + if (wide_is_zero(numerator_gcd) || wide_is_zero(denominator_gcd)) { + return false; + } + Av2DmUnsignedWide left_numerator; + Av2DmUnsignedWide right_numerator; + Av2DmUnsignedWide left_denominator; + Av2DmUnsignedWide right_denominator; + Av2DmUnsignedWide remainder; + if (!wide_divide(left->magnitude, numerator_gcd, &left_numerator, + &remainder) || + !wide_is_zero(remainder) || + !wide_divide(right->magnitude, numerator_gcd, &right_numerator, + &remainder) || + !wide_is_zero(remainder) || + !wide_divide(left->denominator, denominator_gcd, &left_denominator, + &remainder) || + !wide_is_zero(remainder) || + !wide_divide(right->denominator, denominator_gcd, &right_denominator, + &remainder) || + !wide_is_zero(remainder)) { + return false; + } + Av2DmUnsignedProduct left_product; + Av2DmUnsignedProduct right_product; + if (!wide_multiply(left_numerator, right_denominator, &left_product) || + !wide_multiply(right_numerator, left_denominator, &right_product)) { + return false; + } + *comparison = product_compare(left_product, right_product); + return true; +} + +bool av2_dm_rational_add(const Av2DmRational *left, const Av2DmRational *right, + Av2DmRational *result) { + if (left == NULL || right == NULL || result == NULL || + wide_is_zero(left->denominator) || wide_is_zero(right->denominator)) { + return false; + } + Av2DmRational normalized_left = *left; + Av2DmRational normalized_right = *right; + if (!rational_normalize(&normalized_left) || + !rational_normalize(&normalized_right)) { + return false; + } + + const Av2DmUnsignedWide denominator_gcd = + wide_gcd(normalized_left.denominator, normalized_right.denominator); + if (wide_is_zero(denominator_gcd)) return false; + Av2DmUnsignedWide left_multiplier; + Av2DmUnsignedWide right_multiplier; + Av2DmUnsignedWide remainder; + if (!wide_divide(normalized_right.denominator, denominator_gcd, + &left_multiplier, &remainder) || + !wide_is_zero(remainder) || + !wide_divide(normalized_left.denominator, denominator_gcd, + &right_multiplier, &remainder) || + !wide_is_zero(remainder) || + !wide_multiply_checked(normalized_left.denominator, left_multiplier, + &result->denominator)) { + return false; + } + Av2DmUnsignedWide scaled_left; + Av2DmUnsignedWide scaled_right; + if (!wide_multiply_checked(normalized_left.magnitude, left_multiplier, + &scaled_left) || + !wide_multiply_checked(normalized_right.magnitude, right_multiplier, + &scaled_right)) { + return false; + } + + if (normalized_left.negative == normalized_right.negative) { + if (!wide_add(scaled_left, scaled_right, &result->magnitude)) return false; + result->negative = normalized_left.negative; + } else { + const int comparison = wide_compare(scaled_left, scaled_right); + if (comparison >= 0) { + result->magnitude = wide_subtract(scaled_left, scaled_right); + result->negative = normalized_left.negative; + } else { + result->magnitude = wide_subtract(scaled_right, scaled_left); + result->negative = normalized_right.negative; + } + } + return rational_normalize(result); +} + +bool av2_dm_rational_subtract(const Av2DmRational *left, + const Av2DmRational *right, + Av2DmRational *result) { + if (right == NULL) return false; + Av2DmRational negated_right = *right; + if (!wide_is_zero(negated_right.magnitude)) { + negated_right.negative = !negated_right.negative; + } + return av2_dm_rational_add(left, &negated_right, result); +} + +bool av2_dm_rational_multiply_u64(const Av2DmRational *value, + uint64_t multiplier, Av2DmRational *result) { + if (value == NULL || result == NULL || wide_is_zero(value->denominator)) { + return false; + } + Av2DmRational normalized = *value; + if (!rational_normalize(&normalized)) return false; + const Av2DmUnsignedWide wide_multiplier = wide_from_u64(multiplier); + const Av2DmUnsignedWide divisor = + wide_gcd(wide_multiplier, normalized.denominator); + Av2DmUnsignedWide reduced_multiplier; + Av2DmUnsignedWide remainder; + if (!wide_divide(wide_multiplier, divisor, &reduced_multiplier, &remainder) || + !wide_is_zero(remainder) || + !wide_divide(normalized.denominator, divisor, &normalized.denominator, + &remainder) || + !wide_is_zero(remainder) || + !wide_multiply_checked(normalized.magnitude, reduced_multiplier, + &normalized.magnitude)) { + return false; + } + *result = normalized; + return rational_normalize(result); +} + +bool av2_dm_rational_divide_u64(const Av2DmRational *value, uint64_t divisor, + Av2DmRational *result) { + if (value == NULL || result == NULL || wide_is_zero(value->denominator) || + divisor == 0) { + return false; + } + Av2DmRational normalized = *value; + if (!rational_normalize(&normalized)) return false; + const Av2DmUnsignedWide wide_divisor = wide_from_u64(divisor); + const Av2DmUnsignedWide common_divisor = + wide_gcd(normalized.magnitude, wide_divisor); + Av2DmUnsignedWide reduced_divisor; + Av2DmUnsignedWide remainder; + if (!wide_divide(normalized.magnitude, common_divisor, &normalized.magnitude, + &remainder) || + !wide_is_zero(remainder) || + !wide_divide(wide_divisor, common_divisor, &reduced_divisor, + &remainder) || + !wide_is_zero(remainder) || + !wide_multiply_checked(normalized.denominator, reduced_divisor, + &normalized.denominator)) { + return false; + } + *result = normalized; + return rational_normalize(result); +} + +bool av2_dm_rational_compare(const Av2DmRational *left, + const Av2DmRational *right, int *comparison) { + if (left == NULL || right == NULL || comparison == NULL || + wide_is_zero(left->denominator) || wide_is_zero(right->denominator)) { + return false; + } + Av2DmRational normalized_left = *left; + Av2DmRational normalized_right = *right; + if (!rational_normalize(&normalized_left) || + !rational_normalize(&normalized_right)) { + return false; + } + if (wide_is_zero(normalized_left.magnitude) && + wide_is_zero(normalized_right.magnitude)) { + *comparison = 0; + return true; + } + if (wide_is_zero(normalized_left.magnitude)) { + *comparison = normalized_right.negative ? 1 : -1; + return true; + } + if (wide_is_zero(normalized_right.magnitude)) { + *comparison = normalized_left.negative ? -1 : 1; + return true; + } + if (normalized_left.negative != normalized_right.negative) { + *comparison = normalized_left.negative ? -1 : 1; + return true; + } + if (!rational_compare_magnitudes(&normalized_left, &normalized_right, + comparison)) { + return false; + } + if (normalized_left.negative) *comparison = -*comparison; + return true; +} + +bool av2_dm_rational_rebase(Av2DmRational *values, uint32_t value_count, + const Av2DmRational *origin) { + if ((values == NULL && value_count != 0) || origin == NULL) return false; + Av2DmRational fixed_origin = *origin; + if (!rational_normalize(&fixed_origin)) return false; + + // Preflight every subtraction so arithmetic failure cannot leave the array + // containing a mixture of old and new time origins. + for (uint32_t i = 0; i < value_count; ++i) { + Av2DmRational rebased; + if (!av2_dm_rational_subtract(&values[i], &fixed_origin, &rebased)) { + return false; + } + } + for (uint32_t i = 0; i < value_count; ++i) { + Av2DmRational rebased; + if (!av2_dm_rational_subtract(&values[i], &fixed_origin, &rebased)) { + return false; + } + values[i] = rebased; + } + return true; +} + +bool av2_dm_rational_is_zero(const Av2DmRational *value) { + return value != NULL && !wide_is_zero(value->denominator) && + wide_is_zero(value->magnitude); +} + +static void buffer_reset(Av2DmBuffer *buffer) { + memset(buffer, 0, sizeof(*buffer)); + buffer->display_index = -1; + av2_dm_rational_make(0, 1, &buffer->presentation_time); + av2_dm_rational_make(0, 1, &buffer->decode_completion_time); +} + +static bool valid_buffer_index(const Av2DmBufferPool *pool, + uint32_t buffer_index) { + return pool != NULL && buffer_index < pool->pool_size; +} + +bool av2_dm_buffer_pool_initialize(Av2DmBufferPool *pool, + uint32_t num_ref_frames) { + if (pool == NULL || num_ref_frames == 0 || + num_ref_frames > AV2_DM_MAX_REF_FRAMES) { + return false; + } + memset(pool, 0, sizeof(*pool)); + pool->num_ref_frames = num_ref_frames; + pool->pool_size = num_ref_frames + 2; + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) pool->vbi[i] = -1; + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + buffer_reset(&pool->buffers[i]); + } + return true; +} + +int32_t av2_dm_buffer_pool_get_free_buffer(const Av2DmBufferPool *pool) { + if (pool == NULL) return -1; + for (uint32_t i = 0; i < pool->pool_size; ++i) { + const Av2DmBuffer *const buffer = &pool->buffers[i]; + if (buffer->decoder_ref_count == 0 && buffer->player_ref_count == 0) { + return (int32_t)i; + } + } + return -1; +} + +bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index) { + if (!valid_buffer_index(pool, buffer_index)) return false; + Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; + if (buffer->decoder_ref_count != 0 || buffer->player_ref_count != 0) { + return false; + } + buffer_reset(buffer); + return true; +} + +bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + if (!valid_buffer_index(pool, buffer_index)) return false; + Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; + if (buffer->decoder_ref_count == UINT32_MAX) return false; + ++buffer->decoder_ref_count; + return true; +} + +bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + if (!valid_buffer_index(pool, buffer_index)) return false; + Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; + if (buffer->decoder_ref_count == 0) return false; + --buffer->decoder_ref_count; + if (buffer->decoder_ref_count == 0 && buffer->player_ref_count == 0) { + buffer_reset(buffer); + } + return true; +} + +bool av2_dm_buffer_pool_add_player_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + if (!valid_buffer_index(pool, buffer_index)) return false; + Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; + if (buffer->player_ref_count == UINT32_MAX) return false; + ++buffer->player_ref_count; + return true; +} + +bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + if (!valid_buffer_index(pool, buffer_index)) return false; + Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; + if (buffer->player_ref_count == 0) return false; + --buffer->player_ref_count; + if (buffer->decoder_ref_count == 0 && buffer->player_ref_count == 0) { + buffer_reset(buffer); + } + return true; +} + +bool av2_dm_buffer_pool_set_vbi(Av2DmBufferPool *pool, uint32_t ref_index, + int32_t buffer_index) { + if (pool == NULL || ref_index >= pool->num_ref_frames || buffer_index < -1 || + (buffer_index >= 0 && (uint32_t)buffer_index >= pool->pool_size)) { + return false; + } + const int32_t old_buffer_index = pool->vbi[ref_index]; + if (old_buffer_index == buffer_index) return true; + if (buffer_index >= 0 && + pool->buffers[buffer_index].decoder_ref_count == UINT32_MAX) { + return false; + } + if (old_buffer_index >= 0 && !av2_dm_buffer_pool_remove_decoder_ref( + pool, (uint32_t)old_buffer_index)) { + return false; + } + if (buffer_index >= 0 && + !av2_dm_buffer_pool_add_decoder_ref(pool, (uint32_t)buffer_index)) { + return false; + } + pool->vbi[ref_index] = buffer_index; + return true; +} + +uint32_t av2_dm_buffer_pool_frames_in_use(const Av2DmBufferPool *pool) { + if (pool == NULL) return 0; + uint32_t frames_in_use = 0; + for (uint32_t i = 0; i < pool->pool_size; ++i) { + if (pool->buffers[i].decoder_ref_count != 0 || + pool->buffers[i].player_ref_count != 0) { + ++frames_in_use; + } + } + return frames_in_use; +} + +typedef struct Av2DmDfgRecord { + uint64_t event_index; + uint64_t temporal_unit_index; + uint64_t generation; + uint64_t coded_bits; + uint64_t decode_order; + uint64_t rap_epoch; + uint64_t smoothing_epoch; + Av2DmLevelLimits limits; + uint32_t tier; + Av2DmMode mode; + Av2DmRational first_arrival; + Av2DmRational last_arrival; + Av2DmRational scheduled_removal; + Av2DmRational removal; + Av2DmRational decode_time; + Av2DmRational decode_completion; + bool random_access_point; + bool parameters_updated; + bool count_frame_header; + bool decode_count_two; + bool coded_as_closed_loop_key; + bool smoothing_overflow_reported; + uint64_t luma_samples; + uint32_t num_tiles; + uint64_t max_tile_area; + uint64_t compressed_size; + uint64_t frame_symbol_count; +} Av2DmDfgRecord; + +typedef struct Av2DmTuRecord { + uint64_t temporal_unit_index; + uint64_t event_index; + uint64_t output_luma_samples; + uint32_t output_frames; + uint32_t frame_headers; + bool header_complete; + bool header_window_checked; + bool header_rate_reported; + bool tile_header_rate_reported; + bool output_time_valid; + bool presentation_time_valid; + bool prior_presentation_interval_checked; + Av2DmRational output_time; + Av2DmRational presentation_time; + uint64_t header_window_headers; +} Av2DmTuRecord; + +typedef struct Av2DmLane { + Av2DmBufferPool pool; + Av2DmRational time; + Av2DmRational initial_presentation_delay; + bool initial_presentation_delay_known; + int32_t current_buffer_index; +} Av2DmLane; + +typedef struct Av2DmPendingOutputWitness { + bool valid; + uint64_t event_index; + Av2DmRational threshold; + Av2DmRational observed; + Av2DmRational presentation_offset; +} Av2DmPendingOutputWitness; + +typedef struct Av2DmRapPresentationAnchor { + bool valid; + uint64_t rap_epoch; + Av2DmRational presentation_offset; +} Av2DmRapPresentationAnchor; + +typedef struct Av2DmResolvedParameters { + Av2DmLevelLimits limits; + Av2DmRational decoder_buffer_delay; + Av2DmRational encoder_buffer_delay; + uint32_t decoder_buffer_delay_ticks; + bool low_delay_mode; + Av2DmRational dec_ct; + Av2DmRational disp_ct; +} Av2DmResolvedParameters; + +struct Av2DecoderModel { + Av2DmConfig config; + Av2DmLevelLimits limits; + Av2DmRational decoder_buffer_delay; + Av2DmRational encoder_buffer_delay; + uint32_t decoder_buffer_delay_ticks; + bool low_delay_mode; + Av2DmRational dec_ct; + Av2DmRational disp_ct; + Av2DmLane lane; + Av2DmLane resource_lane; + Av2DmResult result; + Av2DmReportFn report; + void *report_opaque; + // Live smoothing/fullness records only. The adjacent parsing DFG and + // generation output metadata have separate bounded homes below. + Av2DmDfgRecord *dfgs; + uint32_t dfg_count; + uint32_t dfg_capacity; + uint64_t dfg_number; + bool previous_dfg_valid; + Av2DmDfgRecord previous_dfg; + Av2DmTuRecord *tus; + uint32_t tu_count; + uint32_t tu_capacity; + uint64_t frame_number; + uint64_t shown_frame_number; + uint64_t rap_epoch; + uint64_t smoothing_epoch; + bool smoothing_epoch_prepared; + bool most_recent_rap_removal_valid; + Av2DmRational most_recent_rap_scheduled_removal; + bool previous_output_order_valid; + uint64_t previous_output_decode_order; + bool previous_output_presentation_valid; + Av2DmRational previous_output_presentation_offset; + uint64_t previous_output_rap_epoch; + bool last_presentation_offset_valid; + Av2DmRational last_presentation_offset; + bool last_presentation_valid; + Av2DmRational last_presentation; + Av2DmRapPresentationAnchor + rap_presentation_anchors[AV2_DM_MAX_BUFFER_POOL_SIZE + 2]; + uint64_t last_output_temporal_unit; + uint64_t latest_frame_event_index; + uint64_t latest_header_check_event_index; + bool last_frame_parsing_time_valid; + Av2DmRational last_frame_parsing_time; + bool last_display_duration_valid; + Av2DmRational last_display_duration; + bool last_output_tu_valid; + uint64_t last_output_tu; + bool latest_timed_tu_valid; + Av2DmRational latest_timed_tu_output_time; + bool coded_tu_valid; + uint64_t coded_tu; + bool retired_header_summary_valid; + bool retired_header_summary_reported; + uint64_t retired_max_frame_headers; + uint64_t retired_header_event_index; + bool retired_unresolved_tu; + Av2DmPendingOutputWitness pending_display_late; + Av2DmPendingOutputWitness pending_decode_deadline; + uint64_t maximum_tile_area; + bool any_decode_count_two_requires_reserved_buffer; + bool max_reference_frames_checked; + bool max_reference_frames_reserved; + bool max_reference_frames_violated; + bool processing_stopped; + uint64_t model_events; + bool violation_seen[AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE + 1]; + Av2DmStorageStats storage; +}; + +static bool invalidate_lane_reference_buffers(Av2DmLane *lane, + uint32_t ref_valid_mask); +static void check_smoothing_buffer_overflow(Av2DecoderModel *model, + uint64_t proving_event_index); +static void retire_closed_smoothing_records(Av2DecoderModel *model, + const Av2DmRational *frontier); +static void check_max_reference_frames(Av2DecoderModel *model, + uint64_t event_index); +static void check_header_rate_windows(Av2DecoderModel *model, + bool require_complete, + uint64_t proving_event_index); +static void update_storage_stats(Av2DecoderModel *model); +static void check_retired_tile_header_summary(Av2DecoderModel *model, + uint64_t proving_event_index); +static void retire_unresolvable_tus(Av2DecoderModel *model); +static void restart_tu_history(Av2DecoderModel *model, + uint64_t temporal_unit_index); +static bool update_latest_timed_tu(Av2DecoderModel *model, + const Av2DmRational *output_time); + +static bool rational_zero(Av2DmRational *value) { + return av2_dm_rational_make(0, 1, value); +} + +static bool rational_from_product(uint64_t left, uint64_t right, + Av2DmRational *value) { + Av2DmUnsignedWide product; + if (!wide_multiply_checked(wide_from_u64(left), wide_from_u64(right), + &product)) { + return false; + } + return av2_dm_rational_make_wide(product, 1, false, value); +} + +static bool rational_multiply(const Av2DmRational *left, + const Av2DmRational *right, + Av2DmRational *result) { + if (left == NULL || right == NULL || result == NULL || + wide_is_zero(left->denominator) || wide_is_zero(right->denominator)) { + return false; + } + Av2DmRational a = *left; + Av2DmRational b = *right; + if (!rational_normalize(&a) || !rational_normalize(&b)) return false; + const Av2DmUnsignedWide cross_a = wide_gcd(a.magnitude, b.denominator); + const Av2DmUnsignedWide cross_b = wide_gcd(b.magnitude, a.denominator); + Av2DmUnsignedWide remainder; + if (!wide_divide(a.magnitude, cross_a, &a.magnitude, &remainder) || + !wide_is_zero(remainder) || + !wide_divide(b.denominator, cross_a, &b.denominator, &remainder) || + !wide_is_zero(remainder) || + !wide_divide(b.magnitude, cross_b, &b.magnitude, &remainder) || + !wide_is_zero(remainder) || + !wide_divide(a.denominator, cross_b, &a.denominator, &remainder) || + !wide_is_zero(remainder) || + !wide_multiply_checked(a.magnitude, b.magnitude, &result->magnitude) || + !wide_multiply_checked(a.denominator, b.denominator, + &result->denominator)) { + return false; + } + result->negative = a.negative != b.negative; + return rational_normalize(result); +} + +static bool rational_max(const Av2DmRational *left, const Av2DmRational *right, + Av2DmRational *result) { + int comparison; + if (!av2_dm_rational_compare(left, right, &comparison)) return false; + *result = comparison >= 0 ? *left : *right; + return true; +} + +static bool rational_less(const Av2DmRational *left, const Av2DmRational *right, + bool *is_less) { + int comparison; + if (!av2_dm_rational_compare(left, right, &comparison)) return false; + *is_less = comparison < 0; + return true; +} + +static bool rational_greater(const Av2DmRational *left, + const Av2DmRational *right, bool *is_greater) { + int comparison; + if (!av2_dm_rational_compare(left, right, &comparison)) return false; + *is_greater = comparison > 0; + return true; +} + +static bool grow_array(void **array, uint32_t *capacity, uint32_t count, + size_t element_size) { + if (count < *capacity) return true; + const uint32_t new_capacity = *capacity == 0 ? 16 : *capacity * 2; + if (new_capacity < *capacity || element_size > SIZE_MAX / new_capacity) { + return false; + } + void *const replacement = avm_calloc(new_capacity, element_size); + if (replacement == NULL) return false; + if (*array != NULL) { + memcpy(replacement, *array, (size_t)count * element_size); + avm_free(*array); + } + *array = replacement; + *capacity = new_capacity; + return true; +} + +bool av2_dm_get_level_limits(uint32_t level_idx, uint32_t tier, + uint32_t profile, Av2DmLevelLimits *limits) { + if (limits == NULL || level_idx >= 22 || tier > 1 || profile > 5) { + return false; + } + const AV2LevelSpec *const row = &av2_level_defs[level_idx]; + uint32_t kbps; + uint32_t compression; + if (!av2_get_level_base_bitrate_kbps((int)level_idx, (int)tier, &kbps) || + !av2_get_level_compression_basis((int)level_idx, (int)tier, + &compression)) { + return false; + } + + AV2ProfileLevelFactors factors; + if (!av2_get_profile_level_factors((int)profile, &factors)) return false; + + memset(limits, 0, sizeof(*limits)); + limits->max_picture_size = (uint64_t)row->max_picture_size; + limits->max_horizontal_size = (uint32_t)row->max_h_size; + limits->max_vertical_size = (uint32_t)row->max_v_size; + limits->max_display_rate = (uint64_t)row->max_display_rate; + limits->max_decode_rate = (uint64_t)row->max_decode_rate; + limits->max_header_rate = (uint32_t)row->max_header_rate; + limits->max_tiles = (uint32_t)row->max_tiles; + limits->max_tile_columns = (uint32_t)row->max_tile_cols; + limits->max_tile_width = + (uint64_t)av2_tile_width_scaling_factor[tier][level_idx] * + MAX_TILE_WIDTH / 4; + limits->max_tile_area = + (uint64_t)av2_tile_area_scaling_factor[tier][level_idx] * MAX_TILE_AREA / + 4; + limits->max_tile_size_header_rate_product = + (uint64_t)av2_tile_area_scaling_factor[tier][level_idx] * + MAX_TILE_SIZE_HEADER_RATE_PRODUCT / 4; + limits->picture_size_profile_factor = factors.picture_size_profile_factor; + limits->min_compression_basis = compression; + + Av2DmRational base_rate; + Av2DmRational profile_factor; + if (!rational_from_product(kbps, 1000, &base_rate) || + !av2_dm_rational_make(factors.bitrate_factor_numerator, + factors.bitrate_factor_denominator, + &profile_factor) || + !rational_multiply(&base_rate, &profile_factor, &limits->bit_rate)) { + return false; + } + // Annex A defines MaxBufferSize as one second of MaxBitrate. + limits->buffer_size = limits->bit_rate; + return true; +} + +static bool scaled_integer(uint64_t value, uint32_t scale_numerator, + uint32_t scale_denominator, uint64_t *scaled) { + Av2DmRational rational; + if (!av2_dm_rational_make(value, 1, &rational) || + !av2_dm_rational_multiply_u64(&rational, scale_denominator, &rational) || + !av2_dm_rational_divide_u64(&rational, scale_numerator, &rational) || + rational.negative || !wide_equals_u64(rational.denominator, 1) || + rational.magnitude.limbs[1] != 0 || rational.magnitude.limbs[2] != 0 || + rational.magnitude.limbs[3] != 0) { + return false; + } + *scaled = rational.magnitude.limbs[0]; + return true; +} + +bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, + uint32_t profile, uint32_t scale_numerator, + uint32_t scale_denominator, + Av2DmLevelLimits *limits) { + // Annex A does not define substream limits below Level 4.0. + if (limits == NULL || scale_denominator == 0 || level_idx < 4 || + level_idx >= 22) { + return false; + } + AV2SubstreamLevelSpec row; + if (!av2_get_substream_level_spec((int)level_idx, scale_numerator, + scale_denominator, &row)) { + return false; + } + Av2DmLevelLimits multistream; + if (!av2_dm_get_level_limits(level_idx, tier, profile, &multistream)) { + return false; + } + uint64_t scaled_display; + uint64_t scaled_decode; + if (!scaled_integer(multistream.max_display_rate, scale_numerator, + scale_denominator, &scaled_display) || + !scaled_integer(multistream.max_decode_rate, scale_numerator, + scale_denominator, &scaled_decode) || + !av2_dm_rational_multiply_u64(&multistream.bit_rate, scale_denominator, + &multistream.bit_rate) || + !av2_dm_rational_divide_u64(&multistream.bit_rate, scale_numerator, + &multistream.bit_rate) || + !av2_dm_rational_multiply_u64(&multistream.buffer_size, scale_denominator, + &multistream.buffer_size) || + !av2_dm_rational_divide_u64(&multistream.buffer_size, scale_numerator, + &multistream.buffer_size)) { + return false; + } + multistream.max_picture_size = + (uint64_t)row.max_h_size_x * (uint32_t)row.max_v_size_x; + multistream.max_horizontal_size = (uint32_t)row.max_h_size_x; + multistream.max_vertical_size = (uint32_t)row.max_v_size_x; + multistream.max_display_rate = scaled_display; + multistream.max_decode_rate = scaled_decode; + multistream.max_header_rate = 132; + multistream.max_tiles = (uint32_t)((uint64_t)multistream.max_tiles * + scale_denominator / scale_numerator); + multistream.max_tile_columns = (uint32_t)row.max_tile_cols_x; + +#define MIN_LIMIT(member) \ + do { \ + if (multistream.member < limits->member) \ + limits->member = multistream.member; \ + } while (0) + MIN_LIMIT(max_picture_size); + MIN_LIMIT(max_horizontal_size); + MIN_LIMIT(max_vertical_size); + MIN_LIMIT(max_display_rate); + MIN_LIMIT(max_decode_rate); + MIN_LIMIT(max_header_rate); + MIN_LIMIT(max_tiles); + MIN_LIMIT(max_tile_columns); +#undef MIN_LIMIT + int comparison; + if (!av2_dm_rational_compare(&multistream.bit_rate, &limits->bit_rate, + &comparison)) { + return false; + } + if (comparison < 0) limits->bit_rate = multistream.bit_rate; + if (!av2_dm_rational_compare(&multistream.buffer_size, &limits->buffer_size, + &comparison)) { + return false; + } + if (comparison < 0) limits->buffer_size = multistream.buffer_size; + if (multistream.min_compression_basis > limits->min_compression_basis) { + limits->min_compression_basis = multistream.min_compression_basis; + } + return true; +} + +static void update_result_status(Av2DecoderModel *model) { + if (model->result.applicability == AV2_DM_NOT_APPLICABLE) { + model->result.status = AV2_DM_RESULT_NOT_APPLICABLE; + } else if (model->result.violations != 0) { + model->result.status = AV2_DM_RESULT_NON_CONFORMANT; + } else if (model->result.arithmetic_failed || + model->result.missing_required_input || + model->result.applicability == AV2_DM_MISSING_REQUIRED_INPUT) { + model->result.status = AV2_DM_RESULT_INDETERMINATE; + } else { + model->result.status = AV2_DM_RESULT_CONFORMANT; + } +} + +static void arithmetic_failure(Av2DecoderModel *model) { + model->result.arithmetic_failed = true; + model->processing_stopped = true; + update_result_status(model); +} + +void av2_decoder_model_fail_arithmetic_for_testing(Av2DecoderModel *model) { + if (model != NULL) arithmetic_failure(model); +} + +static bool increment_model_u64(Av2DecoderModel *model, uint64_t *value) { + if (*value == UINT64_MAX) { + arithmetic_failure(model); + return false; + } + ++*value; + return true; +} + +static bool increment_output_count(Av2DecoderModel *model) { + if (model->shown_frame_number == UINT64_MAX || + model->result.output_frames == UINT64_MAX) { + arithmetic_failure(model); + return false; + } + ++model->shown_frame_number; + ++model->result.output_frames; + return true; +} + +static void missing_input(Av2DecoderModel *model) { + model->result.missing_required_input = true; + model->processing_stopped = true; + if (model->result.applicability == AV2_DM_APPLICABLE) { + model->result.applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + update_result_status(model); +} + +static void incomplete_verification(Av2DecoderModel *model) { + model->result.missing_required_input = true; + if (model->result.applicability == AV2_DM_APPLICABLE) { + model->result.applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + update_result_status(model); +} + +static bool violation_seen(const Av2DecoderModel *model, + Av2DmViolationCode code) { + return code <= AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE && + model->violation_seen[code]; +} + +static void report_violation_for_affected( + Av2DecoderModel *model, Av2DmViolationCode code, uint64_t event_index, + Av2DmViolationAffectedKind affected_kind, uint64_t affected_index, + const Av2DmRational *observed, const Av2DmRational *limit, + const Av2DmViolationDetail *detail) { + if (model->processing_stopped) return; + if (code > AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) { + arithmetic_failure(model); + return; + } + model->violation_seen[code] = true; + if (model->result.violations != UINT64_MAX) { + ++model->result.violations; + } + model->result.status = AV2_DM_RESULT_NON_CONFORMANT; + if (code == AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) { + // Once this code is proven, no retained header-window witness can change + // the CVS or bitstream verdict. Direct later occurrences may still be + // reported without retaining the old windows. + model->retired_header_summary_valid = false; + } + if (model->report != NULL) { + Av2DmViolation violation; + memset(&violation, 0, sizeof(violation)); + violation.code = code; + violation.scope = model->config.scope; + violation.event_index = event_index; + violation.affected_kind = affected_kind; + violation.affected_index = affected_index; + violation.observed_present = observed != NULL; + violation.limit_present = limit != NULL; + if (observed != NULL) violation.observed = *observed; + if (limit != NULL) violation.limit = *limit; + if (detail != NULL) violation.detail = *detail; + model->report(model->report_opaque, &violation); + } + if (model->config.stop_after_first_violation) { + model->processing_stopped = true; + } +} + +static void report_violation(Av2DecoderModel *model, Av2DmViolationCode code, + uint64_t event_index, + const Av2DmRational *observed, + const Av2DmRational *limit) { + report_violation_for_affected(model, code, event_index, + AV2_DM_VIOLATION_AFFECTED_EVENT, event_index, + observed, limit, NULL); +} + +static bool lane_initialize(Av2DmLane *lane, uint32_t num_ref_frames) { + memset(lane, 0, sizeof(*lane)); + lane->current_buffer_index = -1; + return av2_dm_buffer_pool_initialize(&lane->pool, num_ref_frames) && + rational_zero(&lane->time) && + rational_zero(&lane->initial_presentation_delay); +} + +static bool seed_ras_buffers(Av2DecoderModel *model, Av2DmLane *lane) { + for (uint32_t i = 0; i < model->config.ras_seed_count; ++i) { + const Av2DmRasSeed *const seed = &model->config.ras_seeds[i]; + if (seed->ref_index >= lane->pool.num_ref_frames) return false; + int32_t buffer_index = -1; + for (uint32_t j = 0; j < lane->pool.pool_size; ++j) { + if (lane->pool.buffers[j].generation_valid && + lane->pool.buffers[j].generation == seed->generation) { + buffer_index = (int32_t)j; + break; + } + } + if (buffer_index == -1) { + buffer_index = av2_dm_buffer_pool_get_free_buffer(&lane->pool); + if (buffer_index == -1) return false; + lane->pool.buffers[buffer_index].generation_valid = true; + lane->pool.buffers[buffer_index].generation = seed->generation; + } + if (!av2_dm_buffer_pool_set_vbi(&lane->pool, seed->ref_index, + buffer_index)) { + return false; + } + } + return true; +} + +static bool resolve_parameters(const Av2DmConfig *config, + Av2DmResolvedParameters *parameters) { + memset(parameters, 0, sizeof(*parameters)); + if (config->level_limits_present) { + parameters->limits = config->level_limits; + } else if (!av2_dm_get_level_limits(config->level_idx, config->tier, + config->profile, ¶meters->limits)) { + return false; + } + if (!rational_normalize(¶meters->limits.bit_rate) || + !rational_normalize(¶meters->limits.buffer_size) || + wide_is_zero(parameters->limits.bit_rate.magnitude) || + parameters->limits.max_decode_rate == 0 || + parameters->limits.max_display_rate == 0 || + parameters->limits.max_header_rate == 0 || + parameters->limits.picture_size_profile_factor == 0 || + parameters->limits.min_compression_basis == 0 || + !config->timing_info_present || config->time_scale == 0 || + config->num_units_in_display_tick == 0 || + (config->mode == AV2_DM_DECODING_SCHEDULE_MODE && + config->num_units_in_decoding_tick == 0) || + (config->equal_picture_interval && config->ticks_per_picture == 0) || + config->initial_display_delay == 0 || + !av2_dm_rational_make(config->num_units_in_display_tick, + config->time_scale, ¶meters->disp_ct)) { + return false; + } + if (config->mode == AV2_DM_DECODING_SCHEDULE_MODE && + !av2_dm_rational_make(config->num_units_in_decoding_tick, + config->time_scale, ¶meters->dec_ct)) { + return false; + } + + uint32_t decoder_delay = 70000; + uint32_t encoder_delay = 20000; + if (config->mode == AV2_DM_DECODING_SCHEDULE_MODE) { + if (!config->scope.whole_xlayer && + config->operating_point_parameters_present) { + decoder_delay = config->operating_point_decoder_buffer_delay; + encoder_delay = config->operating_point_encoder_buffer_delay; + parameters->low_delay_mode = config->operating_point_low_delay_mode; + } else if (config->sequence_parameters_present) { + // DM-SPEC-1: operating-point selection falls back to the associated + // sequence-header parameters when OP parameters are absent. + decoder_delay = config->sequence_decoder_buffer_delay; + encoder_delay = config->sequence_encoder_buffer_delay; + parameters->low_delay_mode = config->sequence_low_delay_mode; + } else { + return false; + } + } else if (!config->equal_picture_interval) { + return false; + } + if (!av2_dm_rational_make(decoder_delay, 90000, + ¶meters->decoder_buffer_delay) || + !av2_dm_rational_make(encoder_delay, 90000, + ¶meters->encoder_buffer_delay)) { + return false; + } + parameters->decoder_buffer_delay_ticks = decoder_delay; + return true; +} + +static void apply_parameters(Av2DecoderModel *model, const Av2DmConfig *config, + const Av2DmResolvedParameters *parameters) { + model->config = *config; + model->limits = parameters->limits; + model->decoder_buffer_delay = parameters->decoder_buffer_delay; + model->encoder_buffer_delay = parameters->encoder_buffer_delay; + model->decoder_buffer_delay_ticks = parameters->decoder_buffer_delay_ticks; + model->low_delay_mode = parameters->low_delay_mode; + model->dec_ct = parameters->dec_ct; + model->disp_ct = parameters->disp_ct; +} + +Av2DecoderModel *av2_decoder_model_create(const Av2DmConfig *config, + Av2DmReportFn report, + void *report_opaque) { + if (config == NULL) return NULL; + Av2DecoderModel *const model = avm_calloc(1, sizeof(*model)); + if (model == NULL) return NULL; + model->config = *config; + model->report = report; + model->report_opaque = report_opaque; + model->result.status = AV2_DM_RESULT_CONFORMANT; + model->result.applicability = config->applicability; + model->result.mode = config->mode; + model->result.scope = config->scope; + + if (config->level_idx == 31 || + config->applicability == AV2_DM_NOT_APPLICABLE) { + model->result.applicability = AV2_DM_NOT_APPLICABLE; + update_result_status(model); + return model; + } + if (config->applicability == AV2_DM_MISSING_REQUIRED_INPUT) { + missing_input(model); + return model; + } + if (config->num_ref_frames == 0 || + config->num_ref_frames > AV2_DM_MAX_REF_FRAMES || + !lane_initialize(&model->lane, config->num_ref_frames) || + !lane_initialize(&model->resource_lane, config->num_ref_frames)) { + av2_decoder_model_destroy(model); + return NULL; + } + Av2DmResolvedParameters parameters; + if (!resolve_parameters(config, ¶meters)) { + missing_input(model); + } else { + apply_parameters(model, config, ¶meters); + } + + if (config->ras_start) { + if (!config->ras_seed_complete || !seed_ras_buffers(model, &model->lane) || + !seed_ras_buffers(model, &model->resource_lane)) { + // DM-SPEC-6: a RAS run is provable only when all established long-term + // slot/generation relationships can be reconstructed. + missing_input(model); + } + } + update_result_status(model); + update_storage_stats(model); + return model; +} + +void av2_decoder_model_destroy(Av2DecoderModel *model) { + if (model == NULL) return; + avm_free(model->dfgs); + avm_free(model->tus); + avm_free(model); +} + +static bool rational_multiply_wide(const Av2DmRational *value, + Av2DmUnsignedWide multiplier, + Av2DmRational *result) { + if (value == NULL || result == NULL || wide_is_zero(value->denominator)) { + return false; + } + Av2DmRational normalized = *value; + if (!rational_normalize(&normalized)) return false; + const Av2DmUnsignedWide divisor = + wide_gcd(multiplier, normalized.denominator); + Av2DmUnsignedWide remainder; + if (!wide_divide(multiplier, divisor, &multiplier, &remainder) || + !wide_is_zero(remainder) || + !wide_divide(normalized.denominator, divisor, &normalized.denominator, + &remainder) || + !wide_is_zero(remainder) || + !wide_multiply_checked(normalized.magnitude, multiplier, + &normalized.magnitude)) { + return false; + } + *result = normalized; + return rational_normalize(result); +} + +static bool rational_ceil_ratio_to_tick(const Av2DmRational *time, + const Av2DmRational *tick, + Av2DmRational *result) { + if (time->negative || tick->negative || wide_is_zero(tick->magnitude)) { + return false; + } + Av2DmRational reciprocal; + reciprocal.magnitude = tick->denominator; + reciprocal.denominator = tick->magnitude; + reciprocal.negative = false; + Av2DmRational ratio; + if (!rational_multiply(time, &reciprocal, &ratio)) return false; + Av2DmUnsignedWide quotient; + Av2DmUnsignedWide remainder; + if (!wide_divide(ratio.magnitude, ratio.denominator, "ient, &remainder)) { + return false; + } + if (!wide_is_zero(remainder)) { + const Av2DmUnsignedWide one = wide_from_u64(1); + if (!wide_add(quotient, one, "ient)) return false; + } + return rational_multiply_wide(tick, quotient, result); +} + +static bool rational_ceil_to_integer(const Av2DmRational *value, + Av2DmRational *result) { + if (value == NULL || result == NULL || wide_is_zero(value->denominator)) { + return false; + } + Av2DmUnsignedWide quotient; + Av2DmUnsignedWide remainder; + if (!wide_divide(value->magnitude, value->denominator, "ient, + &remainder)) { + return false; + } + if (!value->negative && !wide_is_zero(remainder)) { + if (!wide_add(quotient, wide_from_u64(1), "ient)) return false; + } + return av2_dm_rational_make_wide(quotient, 1, value->negative, result); +} + +static void compare_upper_limit(Av2DecoderModel *model, Av2DmViolationCode code, + uint64_t event_index, + const Av2DmRational *observed, + const Av2DmRational *limit) { + bool greater; + if (!rational_greater(observed, limit, &greater)) { + arithmetic_failure(model); + } else if (greater) { + report_violation(model, code, event_index, observed, limit); + } +} + +static void compare_upper_limit_for_affected( + Av2DecoderModel *model, Av2DmViolationCode code, uint64_t event_index, + Av2DmViolationAffectedKind affected_kind, uint64_t affected_index, + const Av2DmRational *observed, const Av2DmRational *limit) { + bool greater; + if (!rational_greater(observed, limit, &greater)) { + arithmetic_failure(model); + } else if (greater) { + report_violation_for_affected(model, code, event_index, affected_kind, + affected_index, observed, limit, NULL); + } +} + +static void compare_upper_limit_for_affected_with_detail( + Av2DecoderModel *model, Av2DmViolationCode code, uint64_t event_index, + Av2DmViolationAffectedKind affected_kind, uint64_t affected_index, + const Av2DmRational *observed, const Av2DmRational *limit, + const Av2DmViolationDetail *detail) { + bool greater; + if (!rational_greater(observed, limit, &greater)) { + arithmetic_failure(model); + } else if (greater) { + report_violation_for_affected(model, code, event_index, affected_kind, + affected_index, observed, limit, detail); + } +} + +static void compare_lower_limit(Av2DecoderModel *model, Av2DmViolationCode code, + uint64_t event_index, + const Av2DmRational *observed, + const Av2DmRational *limit) { + bool less; + if (!rational_less(observed, limit, &less)) { + arithmetic_failure(model); + } else if (less) { + report_violation(model, code, event_index, observed, limit); + } +} + +static void compare_lower_limit_for_affected( + Av2DecoderModel *model, Av2DmViolationCode code, uint64_t event_index, + Av2DmViolationAffectedKind affected_kind, uint64_t affected_index, + const Av2DmRational *observed, const Av2DmRational *limit) { + bool less; + if (!rational_less(observed, limit, &less)) { + arithmetic_failure(model); + } else if (less) { + report_violation_for_affected(model, code, event_index, affected_kind, + affected_index, observed, limit, NULL); + } +} + +static void compare_lower_limit_for_affected_with_detail( + Av2DecoderModel *model, Av2DmViolationCode code, uint64_t event_index, + Av2DmViolationAffectedKind affected_kind, uint64_t affected_index, + const Av2DmRational *observed, const Av2DmRational *limit, + const Av2DmViolationDetail *detail) { + bool less; + if (!rational_less(observed, limit, &less)) { + arithmetic_failure(model); + } else if (less) { + report_violation_for_affected(model, code, event_index, affected_kind, + affected_index, observed, limit, detail); + } +} + +static Av2DmViolationDetail buffer_pool_violation_detail( + const Av2DmBufferPool *pool, bool resource_lane) { + Av2DmViolationDetail detail; + memset(&detail, 0, sizeof(detail)); + detail.kind = AV2_DM_VIOLATION_DETAIL_BUFFER_POOL; + detail.value.buffer_pool.resource_lane = resource_lane; + detail.value.buffer_pool.pool_size = pool->pool_size; + detail.value.buffer_pool.frames_in_use = + av2_dm_buffer_pool_frames_in_use(pool); + detail.value.buffer_pool.free_buffers = + pool->pool_size - detail.value.buffer_pool.frames_in_use; + for (uint32_t i = 0; i < pool->pool_size; ++i) { + if (pool->buffers[i].decoder_ref_count != 0) { + ++detail.value.buffer_pool.decoder_held_buffers; + } + if (pool->buffers[i].player_ref_count != 0) { + ++detail.value.buffer_pool.player_held_buffers; + } + } + return detail; +} + +static Av2DmTuRecord *find_tu(Av2DecoderModel *model, + uint64_t temporal_unit_index) { + for (uint32_t i = model->tu_count; i > 0; --i) { + if (model->tus[i - 1].temporal_unit_index == temporal_unit_index) { + return &model->tus[i - 1]; + } + } + return NULL; +} + +static Av2DmTuRecord *get_tu(Av2DecoderModel *model, + uint64_t temporal_unit_index, + uint64_t event_index) { + Av2DmTuRecord *const existing = find_tu(model, temporal_unit_index); + if (existing != NULL) return existing; + if (model->tu_count == UINT32_MAX || + !grow_array((void **)&model->tus, &model->tu_capacity, model->tu_count, + sizeof(*model->tus))) { + arithmetic_failure(model); + return NULL; + } + Av2DmTuRecord *const tu = &model->tus[model->tu_count++]; + memset(tu, 0, sizeof(*tu)); + tu->temporal_unit_index = temporal_unit_index; + tu->event_index = event_index; + return tu; +} + +static bool update_latest_timed_tu(Av2DecoderModel *model, + const Av2DmRational *output_time) { + if (!model->latest_timed_tu_valid) { + model->latest_timed_tu_output_time = *output_time; + model->latest_timed_tu_valid = true; + return true; + } + int comparison; + if (!av2_dm_rational_compare(output_time, &model->latest_timed_tu_output_time, + &comparison)) { + return false; + } + if (comparison > 0) model->latest_timed_tu_output_time = *output_time; + return true; +} + +static void check_static_level_limits(Av2DecoderModel *model, + const Av2DmFrameEvent *event) { + Av2DmRational observed; + Av2DmRational limit; + if (!rational_from_product(event->frame_width, event->frame_height, + &observed) || + !av2_dm_rational_make(model->limits.max_picture_size, 1, &limit)) { + arithmetic_failure(model); + return; + } + compare_upper_limit(model, AV2_DM_VIOLATION_MAX_PICTURE_SIZE, + event->event_index, &observed, &limit); + +#define CHECK_INTEGER_LIMIT(field, member, violation_code) \ + do { \ + if (!av2_dm_rational_make((field), 1, &observed) || \ + !av2_dm_rational_make(model->limits.member, 1, &limit)) { \ + arithmetic_failure(model); \ + } else { \ + compare_upper_limit(model, (violation_code), event->event_index, \ + &observed, &limit); \ + } \ + } while (0) + CHECK_INTEGER_LIMIT(event->frame_width, max_horizontal_size, + AV2_DM_VIOLATION_MAX_HORIZONTAL_SIZE); + CHECK_INTEGER_LIMIT(event->frame_height, max_vertical_size, + AV2_DM_VIOLATION_MAX_VERTICAL_SIZE); + CHECK_INTEGER_LIMIT(event->num_tiles, max_tiles, AV2_DM_VIOLATION_MAX_TILES); + CHECK_INTEGER_LIMIT(event->tile_columns, max_tile_columns, + AV2_DM_VIOLATION_MAX_TILE_COLUMNS); + CHECK_INTEGER_LIMIT(event->max_tile_width, max_tile_width, + AV2_DM_VIOLATION_MAX_TILE_WIDTH); + CHECK_INTEGER_LIMIT(event->max_tile_area, max_tile_area, + AV2_DM_VIOLATION_MAX_TILE_AREA); +#undef CHECK_INTEGER_LIMIT + + if (event->frame_width < 16) { + av2_dm_rational_make(event->frame_width, 1, &observed); + av2_dm_rational_make(16, 1, &limit); + report_violation(model, AV2_DM_VIOLATION_MIN_HORIZONTAL_SIZE, + event->event_index, &observed, &limit); + } + if (event->frame_height < 16) { + av2_dm_rational_make(event->frame_height, 1, &observed); + av2_dm_rational_make(16, 1, &limit); + report_violation(model, AV2_DM_VIOLATION_MIN_VERTICAL_SIZE, + event->event_index, &observed, &limit); + } + if (!event->non_rightmost_tile_width_valid) { + report_violation(model, AV2_DM_VIOLATION_MIN_TILE_WIDTH, event->event_index, + NULL, NULL); + } +} + +static bool release_presented_buffers(Av2DmLane *lane, + const Av2DmRational *removal) { + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + Av2DmBuffer *const buffer = &lane->pool.buffers[i]; + if (buffer->player_ref_count == 0 || !buffer->presentation_time_valid) { + continue; + } + int comparison; + if (!av2_dm_rational_compare(&buffer->presentation_time, removal, + &comparison)) { + return false; + } + if (comparison <= 0) { + buffer->player_ref_count = 0; + if (buffer->decoder_ref_count == 0) buffer_reset(buffer); + } + } + return true; +} + +static bool next_resource_removal(Av2DecoderModel *model, Av2DmLane *lane, + uint64_t dfg_index, Av2DmRational *removal) { + if (dfg_index == 0) { + *removal = model->decoder_buffer_delay; + return true; + } + if (!release_presented_buffers(lane, &lane->time)) return false; + if (av2_dm_buffer_pool_get_free_buffer(&lane->pool) >= 0) { + *removal = lane->time; + return true; + } + bool found = false; + Av2DmRational earliest; + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + const Av2DmBuffer *const buffer = &lane->pool.buffers[i]; + if (buffer->decoder_ref_count != 0 || buffer->player_ref_count == 0) { + continue; + } + if (!buffer->presentation_time_valid) { + missing_input(model); + return false; + } + if (!found) { + earliest = buffer->presentation_time; + found = true; + } else { + bool less; + if (!rational_less(&buffer->presentation_time, &earliest, &less)) { + return false; + } + if (less) earliest = buffer->presentation_time; + } + } + if (!found) return false; + *removal = earliest; + return true; +} + +static bool lane_start_decode(Av2DmLane *lane, const Av2DmRational *removal, + const Av2DmRational *decode_time, + uint64_t generation, int32_t *buffer_index) { + if (!release_presented_buffers(lane, removal)) return false; + lane->time = *removal; + const int32_t free_buffer = av2_dm_buffer_pool_get_free_buffer(&lane->pool); + *buffer_index = free_buffer; + lane->current_buffer_index = free_buffer; + if (!av2_dm_rational_add(&lane->time, decode_time, &lane->time)) { + return false; + } + if (free_buffer < 0) return true; + Av2DmBuffer *const buffer = &lane->pool.buffers[free_buffer]; + buffer_reset(buffer); + buffer->generation_valid = true; + buffer->generation = generation; + buffer->decode_completion_time = lane->time; + buffer->decode_completion_time_valid = true; + return true; +} + +static bool calculate_decode_time(Av2DecoderModel *model, + const Av2DmFrameEvent *event, + uint64_t *luma_samples, + Av2DmRational *decode_time) { + uint64_t samples; + if (event->frame_is_intra) { + Av2DmRational product; + if (!rational_from_product(event->frame_width, event->frame_height, + &product) || + product.magnitude.limbs[1] != 0 || product.magnitude.limbs[2] != 0 || + product.magnitude.limbs[3] != 0) { + return false; + } + samples = product.magnitude.limbs[0]; + if (event->allow_global_intrabc && event->inloop_filtering_enabled) { + if (samples > UINT64_MAX / 2) return false; + samples *= 2; + } + } else { + Av2DmRational product; + if (!rational_from_product(model->config.max_frame_width, + model->config.max_frame_height, &product) || + product.magnitude.limbs[1] != 0 || product.magnitude.limbs[2] != 0 || + product.magnitude.limbs[3] != 0) { + return false; + } + samples = product.magnitude.limbs[0]; + } + *luma_samples = samples; + return av2_dm_rational_make(samples, model->limits.max_decode_rate, + decode_time); +} + +static void check_frame_parsing_constraints(Av2DecoderModel *model, + Av2DmDfgRecord *dfg, + const Av2DmRational *interval, + uint64_t proving_event_index) { + if (model->config.still_picture) return; + Av2DmViolationDetail detail; + memset(&detail, 0, sizeof(detail)); + detail.kind = AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL; + detail.value.frame_interval = *interval; + const Av2DmLevelLimits *const limits = &dfg->limits; + Av2DmRational limit; + Av2DmRational observed; + if (!av2_dm_rational_multiply_u64(interval, limits->max_decode_rate, + &limit) || + !av2_dm_rational_make(dfg->luma_samples, 1, &observed)) { + arithmetic_failure(model); + return; + } + compare_upper_limit_for_affected_with_detail( + model, AV2_DM_VIOLATION_FRAME_DECODE_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, + &detail); + + Av2DmRational dynamic_tiles; + Av2DmRational one; + Av2DmRational max_tiles; + if (!av2_dm_rational_multiply_u64(interval, (uint64_t)limits->max_tiles * 120, + &dynamic_tiles) || + !av2_dm_rational_make(1, 1, &one) || + !av2_dm_rational_make(limits->max_tiles, 1, &max_tiles) || + !rational_max(&dynamic_tiles, &one, &dynamic_tiles)) { + arithmetic_failure(model); + return; + } + bool greater; + if (!rational_greater(&dynamic_tiles, &max_tiles, &greater)) { + arithmetic_failure(model); + return; + } + if (greater) dynamic_tiles = max_tiles; + if (!av2_dm_rational_make(dfg->num_tiles, 1, &observed)) { + arithmetic_failure(model); + return; + } + compare_upper_limit_for_affected_with_detail( + model, AV2_DM_VIOLATION_FRAME_TILE_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, + &dynamic_tiles, &detail); + + Av2DmRational compressed_limit_1; + Av2DmRational compressed_limit_2; + if (dfg->luma_samples > UINT64_MAX / limits->picture_size_profile_factor) { + arithmetic_failure(model); + return; + } + const uint64_t picture_units = + dfg->luma_samples * limits->picture_size_profile_factor / 8; + if (!av2_dm_rational_make(picture_units, 1, &compressed_limit_1) || + !av2_dm_rational_multiply_u64(&compressed_limit_1, 5, + &compressed_limit_1) || + !av2_dm_rational_divide_u64(&compressed_limit_1, 4, + &compressed_limit_1) || + !av2_dm_rational_multiply_u64(interval, limits->max_decode_rate, + &compressed_limit_2) || + !av2_dm_rational_multiply_u64(&compressed_limit_2, + limits->picture_size_profile_factor, + &compressed_limit_2) || + !av2_dm_rational_divide_u64(&compressed_limit_2, + (uint64_t)8 * limits->min_compression_basis, + &compressed_limit_2)) { + arithmetic_failure(model); + return; + } + bool first_is_greater; + if (!rational_greater(&compressed_limit_1, &compressed_limit_2, + &first_is_greater)) { + arithmetic_failure(model); + return; + } + limit = first_is_greater ? compressed_limit_2 : compressed_limit_1; + if (!av2_dm_rational_make(dfg->compressed_size, 1, &observed)) { + arithmetic_failure(model); + return; + } + compare_upper_limit_for_affected_with_detail( + model, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, + &detail); + + Av2DmRational symbol_factor_a; + Av2DmRational symbol_factor_b; + Av2DmRational symbol_factor; + if (!av2_dm_rational_make(8, (uint64_t)9 * limits->min_compression_basis, + &symbol_factor_a) || + !av2_dm_rational_make(1, 48, &symbol_factor_b) || + !av2_dm_rational_add(&symbol_factor_a, &symbol_factor_b, + &symbol_factor) || + !av2_dm_rational_multiply_u64(interval, limits->max_decode_rate, + &limit) || + !av2_dm_rational_multiply_u64(&limit, limits->picture_size_profile_factor, + &limit) || + !rational_multiply(&limit, &symbol_factor, &limit) || + !av2_dm_rational_make(dfg->frame_symbol_count, 1, &observed)) { + arithmetic_failure(model); + return; + } + compare_upper_limit_for_affected_with_detail( + model, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, + &detail); +} + +static void check_previous_dfg_interval(Av2DecoderModel *model, + Av2DmDfgRecord *previous, + const Av2DmDfgRecord *current) { + Av2DmRational interval; + if (!av2_dm_rational_subtract(¤t->removal, &previous->removal, + &interval) || + !av2_dm_rational_divide_u64(&interval, previous->decode_count_two ? 2 : 1, + &interval)) { + arithmetic_failure(model); + return; + } + model->last_frame_parsing_time = interval; + model->last_frame_parsing_time_valid = true; + // The previous DFG retains the affected frame/generation identity; the + // current DFG supplies the removal interval that proves these constraints. + check_frame_parsing_constraints(model, previous, &interval, + current->event_index); + + if (previous->mode == AV2_DM_DECODING_SCHEDULE_MODE) { + Av2DmRational available; + Av2DmRational one_header_time; + Av2DmRational required; + const uint64_t max_headers = (uint64_t)previous->limits.max_header_rate * + (1 + ((uint64_t)previous->tier << 1)); + if (!av2_dm_rational_subtract(¤t->scheduled_removal, + &previous->removal, &available) || + !av2_dm_rational_make(1, max_headers, &one_header_time) || + !rational_max(&previous->decode_time, &one_header_time, &required)) { + arithmetic_failure(model); + return; + } + Av2DmViolationDetail detail; + memset(&detail, 0, sizeof(detail)); + detail.kind = AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME; + detail.value.minimum_decode_time.frame_decode_time = previous->decode_time; + detail.value.minimum_decode_time.one_header_time = one_header_time; + compare_lower_limit_for_affected_with_detail( + model, AV2_DM_VIOLATION_MINIMUM_DECODE_TIME, current->event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, previous->event_index, &available, + &required, &detail); + } +} + +static bool calculate_arrival_times(Av2DecoderModel *model, + Av2DmDfgRecord *dfg) { + if (model->dfg_number == 1 || dfg->parameters_updated) { + if (!rational_zero(&dfg->first_arrival)) return false; + } else { + if (!model->previous_dfg_valid) return false; + Av2DmRational total_delay; + Av2DmRational latest; + if (!av2_dm_rational_add(&model->encoder_buffer_delay, + &model->decoder_buffer_delay, &total_delay) || + !av2_dm_rational_subtract(&dfg->scheduled_removal, &total_delay, + &latest) || + !rational_max(&model->previous_dfg.last_arrival, &latest, + &dfg->first_arrival)) { + return false; + } + } + Av2DmRational coded_bits; + Av2DmRational reciprocal_rate; + Av2DmRational arrival_duration; + if (wide_is_zero(model->limits.bit_rate.magnitude) || + !av2_dm_rational_make(dfg->coded_bits, 1, &coded_bits)) { + return false; + } + reciprocal_rate.magnitude = model->limits.bit_rate.denominator; + reciprocal_rate.denominator = model->limits.bit_rate.magnitude; + reciprocal_rate.negative = false; + return rational_multiply(&coded_bits, &reciprocal_rate, &arrival_duration) && + av2_dm_rational_add(&dfg->first_arrival, &arrival_duration, + &dfg->last_arrival); +} + +static bool calculate_scheduled_removal(Av2DecoderModel *model, + const Av2DmFrameEvent *event, + Av2DmDfgRecord *dfg) { + if (model->config.mode == AV2_DM_RESOURCE_AVAILABILITY_MODE) { + return next_resource_removal(model, &model->lane, model->dfg_number - 1, + &dfg->scheduled_removal); + } + if (!event->buffer_removal_time_present) { + missing_input(model); + return false; + } + if (model->dfg_number == 1) { + dfg->scheduled_removal = model->decoder_buffer_delay; + return true; + } + if (!model->most_recent_rap_removal_valid) { + missing_input(model); + return false; + } + Av2DmRational offset; + return av2_dm_rational_multiply_u64(&model->dec_ct, + event->buffer_removal_time, &offset) && + av2_dm_rational_add(&model->most_recent_rap_scheduled_removal, &offset, + &dfg->scheduled_removal); +} + +static void check_schedule_delay_limits(Av2DecoderModel *model, + uint64_t event_index) { + if (model->dfg_number != 1 || + model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE) { + return; + } + Av2DmRational zero; + rational_zero(&zero); + if (av2_dm_rational_is_zero(&model->decoder_buffer_delay)) { + report_violation(model, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO, + event_index, &model->decoder_buffer_delay, &zero); + } + Av2DmRational reciprocal_rate; + Av2DmRational maximum_delay; + reciprocal_rate.magnitude = model->limits.bit_rate.denominator; + reciprocal_rate.denominator = model->limits.bit_rate.magnitude; + reciprocal_rate.negative = false; + if (!rational_multiply(&model->limits.buffer_size, &reciprocal_rate, + &maximum_delay)) { + arithmetic_failure(model); + return; + } + compare_upper_limit(model, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_TOO_LARGE, + event_index, &model->decoder_buffer_delay, + &maximum_delay); +} + +static void check_delay_consistency(Av2DecoderModel *model, + Av2DmDfgRecord *dfg) { + if (model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE || + !dfg->random_access_point || !model->previous_dfg_valid) { + return; + } + Av2DmRational time_delta; + Av2DmRational threshold; + if (!av2_dm_rational_subtract(&dfg->scheduled_removal, + &model->previous_dfg.last_arrival, + &time_delta) || + !av2_dm_rational_multiply_u64(&time_delta, 90000, &time_delta) || + model->decoder_buffer_delay_ticks == 0 || + !av2_dm_rational_make(model->decoder_buffer_delay_ticks - 1, 1, + &threshold)) { + arithmetic_failure(model); + return; + } + int comparison; + if (!av2_dm_rational_compare(&time_delta, &threshold, &comparison)) { + arithmetic_failure(model); + } else if (comparison <= 0) { + Av2DmViolationDetail detail; + memset(&detail, 0, sizeof(detail)); + detail.kind = AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY; + detail.value.delay_consistency.decoder_buffer_delay_ticks = + model->decoder_buffer_delay_ticks; + detail.value.delay_consistency.ceil_time_delta_present = + rational_ceil_to_integer( + &time_delta, &detail.value.delay_consistency.ceil_time_delta_ticks); + report_violation_for_affected( + model, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT, + dfg->event_index, AV2_DM_VIOLATION_AFFECTED_EVENT, dfg->event_index, + &time_delta, &threshold, &detail); + } +} + +typedef struct Av2DmPreparedRebase { + Av2DmRational **targets; + Av2DmRational *values; + uint32_t count; +} Av2DmPreparedRebase; + +static bool add_rebase_target(Av2DmPreparedRebase *prepared, + Av2DmRational *target, uint32_t capacity) { + if (prepared->count >= capacity) return false; + prepared->targets[prepared->count] = target; + prepared->values[prepared->count] = *target; + ++prepared->count; + return true; +} + +static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, + bool primary, const Av2DmRational *origin, + Av2DmPreparedRebase *prepared) { + uint64_t capacity = 2 + 2 * lane->pool.pool_size; + if (primary) { + capacity += (uint64_t)5 * model->dfg_count + 16; + } + if (capacity > UINT32_MAX || + capacity > SIZE_MAX / sizeof(*prepared->values)) { + return false; + } + prepared->targets = avm_calloc((size_t)capacity, sizeof(*prepared->targets)); + prepared->values = avm_calloc((size_t)capacity, sizeof(*prepared->values)); + if (prepared->targets == NULL || prepared->values == NULL) return false; + const uint32_t count_limit = (uint32_t)capacity; + if (!add_rebase_target(prepared, &lane->time, count_limit) || + !add_rebase_target(prepared, &lane->initial_presentation_delay, + count_limit)) { + return false; + } + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + Av2DmBuffer *const buffer = &lane->pool.buffers[i]; + if (buffer->presentation_time_valid && + !add_rebase_target(prepared, &buffer->presentation_time, count_limit)) { + return false; + } + if (buffer->decode_completion_time_valid && + !add_rebase_target(prepared, &buffer->decode_completion_time, + count_limit)) { + return false; + } + } + if (primary) { + for (uint32_t i = 0; i < model->dfg_count; ++i) { + Av2DmDfgRecord *const dfg = &model->dfgs[i]; + if (!add_rebase_target(prepared, &dfg->first_arrival, count_limit) || + !add_rebase_target(prepared, &dfg->last_arrival, count_limit) || + !add_rebase_target(prepared, &dfg->scheduled_removal, count_limit) || + !add_rebase_target(prepared, &dfg->removal, count_limit) || + !add_rebase_target(prepared, &dfg->decode_completion, count_limit)) { + return false; + } + } + if (model->previous_dfg_valid) { + Av2DmDfgRecord *const dfg = &model->previous_dfg; + if (!add_rebase_target(prepared, &dfg->first_arrival, count_limit) || + !add_rebase_target(prepared, &dfg->last_arrival, count_limit) || + !add_rebase_target(prepared, &dfg->scheduled_removal, count_limit) || + !add_rebase_target(prepared, &dfg->removal, count_limit) || + !add_rebase_target(prepared, &dfg->decode_completion, count_limit)) { + return false; + } + } + if (model->most_recent_rap_removal_valid && + !add_rebase_target(prepared, &model->most_recent_rap_scheduled_removal, + count_limit)) { + return false; + } + if (model->last_presentation_valid && + !add_rebase_target(prepared, &model->last_presentation, count_limit)) { + return false; + } + if (model->pending_display_late.valid && + (!add_rebase_target(prepared, &model->pending_display_late.threshold, + count_limit) || + !add_rebase_target(prepared, &model->pending_display_late.observed, + count_limit))) { + return false; + } + if (model->pending_decode_deadline.valid && + (!add_rebase_target(prepared, &model->pending_decode_deadline.threshold, + count_limit) || + !add_rebase_target(prepared, &model->pending_decode_deadline.observed, + count_limit))) { + return false; + } + } + return av2_dm_rational_rebase(prepared->values, prepared->count, origin); +} + +static void free_prepared_rebase(Av2DmPreparedRebase *prepared) { + avm_free(prepared->targets); + avm_free(prepared->values); + memset(prepared, 0, sizeof(*prepared)); +} + +static void commit_prepared_rebase(const Av2DmPreparedRebase *prepared) { + for (uint32_t i = 0; i < prepared->count; ++i) { + *prepared->targets[i] = prepared->values[i]; + } +} + +static void maybe_rebase_model(Av2DecoderModel *model) { + const uint32_t interval = model->config.rebase_interval_events == 0 + ? 4096 + : model->config.rebase_interval_events; + if (model->model_events == 0 || model->model_events % interval != 0 || + !model->lane.initial_presentation_delay_known || + !model->resource_lane.initial_presentation_delay_known) { + return; + } + Av2DmPreparedRebase primary = { 0 }; + Av2DmPreparedRebase resource = { 0 }; + // Both lanes participate in the Annex E schedule-vs-resource ordering + // comparison, so every absolute lane time must retain one shared origin. + const Av2DmRational origin = model->lane.time; + if (!prepare_lane_rebase(model, &model->lane, true, &origin, &primary) || + !prepare_lane_rebase(model, &model->resource_lane, false, &origin, + &resource)) { + free_prepared_rebase(&primary); + free_prepared_rebase(&resource); + arithmetic_failure(model); + return; + } + commit_prepared_rebase(&primary); + commit_prepared_rebase(&resource); + free_prepared_rebase(&primary); + free_prepared_rebase(&resource); +} + +static bool lane_buffer_is_live(const Av2DmLane *lane, uint32_t buffer_index) { + const Av2DmBuffer *const buffer = &lane->pool.buffers[buffer_index]; + return buffer->generation_valid && + (lane->current_buffer_index == (int32_t)buffer_index || + buffer->decoder_ref_count != 0 || buffer->player_ref_count != 0); +} + +static bool earlier_lane_has_generation(const Av2DmLane *const lanes[2], + uint32_t lane_index, + uint32_t buffer_index, + uint64_t generation) { + for (uint32_t i = 0; i <= lane_index; ++i) { + const uint32_t limit = + i == lane_index ? buffer_index : lanes[i]->pool.pool_size; + for (uint32_t j = 0; j < limit; ++j) { + const Av2DmBuffer *const buffer = &lanes[i]->pool.buffers[j]; + if (lane_buffer_is_live(lanes[i], j) && + buffer->generation == generation) { + return true; + } + } + } + return false; +} + +static uint32_t active_generation_count(const Av2DecoderModel *model) { + const Av2DmLane *const lanes[2] = { &model->lane, &model->resource_lane }; + uint32_t count = 0; + for (uint32_t i = 0; i < 2; ++i) { + for (uint32_t j = 0; j < lanes[i]->pool.pool_size; ++j) { + const Av2DmBuffer *const buffer = &lanes[i]->pool.buffers[j]; + if (lane_buffer_is_live(lanes[i], j) && + !earlier_lane_has_generation(lanes, i, j, buffer->generation)) { + ++count; + } + } + } + return count; +} + +static void update_storage_high_water(uint32_t active, uint32_t *current, + uint32_t *high_water) { + *current = active; + if (active > *high_water) *high_water = active; +} + +static void update_storage_stats(Av2DecoderModel *model) { + if (model->result.finished) { + model->storage.active_dfgs = 0; + model->storage.active_outputs = 0; + model->storage.active_tus = 0; + model->storage.active_generations = 0; + model->storage.active_cvs = 0; + model->storage.active_rap_runs = 0; + return; + } + const uint32_t active_outputs = + (uint32_t)model->pending_display_late.valid + + (uint32_t)model->pending_decode_deadline.valid; + const uint32_t active_run = + !model->result.finished && + model->result.applicability == AV2_DM_APPLICABLE + ? 1 + : 0; + uint32_t active_dfgs = model->dfg_count; + if (model->previous_dfg_valid && active_dfgs != UINT32_MAX) ++active_dfgs; + update_storage_high_water(active_dfgs, &model->storage.active_dfgs, + &model->storage.high_water_dfgs); + update_storage_high_water(active_outputs, &model->storage.active_outputs, + &model->storage.high_water_outputs); + update_storage_high_water(model->tu_count, &model->storage.active_tus, + &model->storage.high_water_tus); + update_storage_high_water(active_generation_count(model), + &model->storage.active_generations, + &model->storage.high_water_generations); + update_storage_high_water(active_run, &model->storage.active_cvs, + &model->storage.high_water_cvs); + update_storage_high_water(active_run, &model->storage.active_rap_runs, + &model->storage.high_water_rap_runs); +} + +static void model_event_complete(Av2DecoderModel *model) { + if (!increment_model_u64(model, &model->model_events)) return; + maybe_rebase_model(model); + update_storage_stats(model); +} + +void av2_decoder_model_start_frame(Av2DecoderModel *model, + const Av2DmFrameEvent *event) { + if (model == NULL || event == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + if (model->frame_number == UINT64_MAX) { + arithmetic_failure(model); + return; + } + model->latest_frame_event_index = event->event_index; + // Annex E synchronizes VBI with RefValid at every start_frame_decode(), + // before FrameNum advances or a current buffer is selected. + if (!invalidate_lane_reference_buffers(&model->lane, event->ref_valid_mask) || + !invalidate_lane_reference_buffers(&model->resource_lane, + event->ref_valid_mask)) { + arithmetic_failure(model); + return; + } + ++model->frame_number; + if (model->coded_tu_valid && model->coded_tu != event->temporal_unit_index) { + Av2DmTuRecord *const previous_coded = find_tu(model, model->coded_tu); + if (previous_coded == NULL) { + arithmetic_failure(model); + return; + } + previous_coded->header_complete = true; + } + model->coded_tu = event->temporal_unit_index; + model->coded_tu_valid = true; + Av2DmTuRecord *const tu = + get_tu(model, event->temporal_unit_index, event->event_index); + if (tu != NULL && event->temporal_unit_output_time_present) { + tu->output_time = event->temporal_unit_output_time; + tu->output_time_valid = true; + if (!update_latest_timed_tu(model, &tu->output_time)) { + arithmetic_failure(model); + return; + } + } + if (tu != NULL && event->count_frame_header) { + if (tu->frame_headers == UINT32_MAX) { + arithmetic_failure(model); + return; + } + ++tu->frame_headers; + } + if (event->show_existing_frame) { + if (!model->config.defer_nonterminal_checks_for_testing) { + check_header_rate_windows(model, false, event->event_index); + } + retire_unresolvable_tus(model); + model_event_complete(model); + return; + } + if (event->max_tile_area > model->maximum_tile_area) { + // Annex A MaxTileSizeInLumaSamples covers every tile in the coded video + // sequence, independently of CountFrameHeaderForLevelConstraint. + model->maximum_tile_area = event->max_tile_area; + check_retired_tile_header_summary(model, event->event_index); + } + if (!model->config.defer_nonterminal_checks_for_testing) { + check_header_rate_windows(model, false, event->event_index); + } + if (model->processing_stopped) return; + if (model->smoothing_epoch_prepared && + !event->decoder_model_parameters_updated) { + arithmetic_failure(model); + return; + } + if (event->decoder_model_parameters_updated && model->dfg_number != 0 && + !model->smoothing_epoch_prepared) { + // FirstBitArrival restarts at zero when decoder-model parameters change. + // Close the prior smoothing epoch before accepting the new epoch so its + // occupancy cannot be combined with the reset timeline. + if (!model->config.defer_nonterminal_checks_for_testing) { + check_smoothing_buffer_overflow(model, event->event_index); + model->dfg_count = 0; + } + if (model->smoothing_epoch == UINT64_MAX) { + arithmetic_failure(model); + return; + } + ++model->smoothing_epoch; + } + model->smoothing_epoch_prepared = false; + if (model->dfg_count == UINT32_MAX || model->dfg_number == UINT64_MAX || + (event->random_access_point && model->rap_epoch == UINT64_MAX) || + !grow_array((void **)&model->dfgs, &model->dfg_capacity, model->dfg_count, + sizeof(*model->dfgs))) { + arithmetic_failure(model); + return; + } + Av2DmDfgRecord *const dfg = &model->dfgs[model->dfg_count++]; + memset(dfg, 0, sizeof(*dfg)); + dfg->event_index = event->event_index; + dfg->temporal_unit_index = event->temporal_unit_index; + dfg->generation = event->generation; + dfg->coded_bits = event->coded_bits; + dfg->decode_order = model->result.decoded_frames; + dfg->smoothing_epoch = model->smoothing_epoch; + dfg->limits = model->limits; + dfg->tier = model->config.tier; + dfg->mode = model->config.mode; + dfg->random_access_point = event->random_access_point; + dfg->parameters_updated = event->decoder_model_parameters_updated; + dfg->count_frame_header = event->count_frame_header; + dfg->decode_count_two = + event->allow_global_intrabc && event->inloop_filtering_enabled; + dfg->coded_as_closed_loop_key = event->coded_as_closed_loop_key; + dfg->num_tiles = event->num_tiles; + dfg->max_tile_area = event->max_tile_area; + dfg->compressed_size = event->compressed_size_bytes > 128 + ? event->compressed_size_bytes - 128 + : 0; + dfg->frame_symbol_count = event->frame_symbol_count; + if (event->random_access_point) ++model->rap_epoch; + dfg->rap_epoch = model->rap_epoch; + ++model->dfg_number; + + check_static_level_limits(model, event); + if (model->processing_stopped) return; + uint64_t decode_luma_samples; + if (!calculate_decode_time(model, event, &decode_luma_samples, + &dfg->decode_time)) { + arithmetic_failure(model); + return; + } + dfg->luma_samples = + dfg->decode_count_two ? decode_luma_samples / 2 : decode_luma_samples; + if (dfg->decode_count_two && + (model->config.max_mlayer_id != 0 || !dfg->coded_as_closed_loop_key)) { + model->any_decode_count_two_requires_reserved_buffer = true; + } + if (!model->config.defer_nonterminal_checks_for_testing) { + check_max_reference_frames(model, event->event_index); + } + if (model->processing_stopped) return; + if (!calculate_scheduled_removal(model, event, dfg) || + !calculate_arrival_times(model, dfg)) { + if (!model->result.missing_required_input) arithmetic_failure(model); + return; + } + dfg->removal = dfg->scheduled_removal; + bool scheduled_before_arrival; + if (!rational_less(&dfg->scheduled_removal, &dfg->last_arrival, + &scheduled_before_arrival)) { + arithmetic_failure(model); + return; + } + if (scheduled_before_arrival && !model->low_delay_mode) { + // DM-SPEC-2: availability at the scheduled time is required only in + // strict-arrival mode. + report_violation(model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW, + event->event_index, &dfg->scheduled_removal, + &dfg->last_arrival); + } else if (scheduled_before_arrival && + model->config.mode == AV2_DM_DECODING_SCHEDULE_MODE && + !rational_ceil_ratio_to_tick(&dfg->last_arrival, &model->dec_ct, + &dfg->removal)) { + arithmetic_failure(model); + return; + } + if (!model->config.defer_nonterminal_checks_for_testing) { + check_smoothing_buffer_overflow(model, event->event_index); + } + if (model->processing_stopped) return; + + Av2DmRational resource_removal; + if (!next_resource_removal(model, &model->resource_lane, + model->dfg_number - 1, &resource_removal)) { + if (!model->result.missing_required_input) arithmetic_failure(model); + return; + } + int32_t resource_buffer_index; + if (!lane_start_decode(&model->resource_lane, &resource_removal, + &dfg->decode_time, event->generation, + &resource_buffer_index)) { + arithmetic_failure(model); + return; + } + if (resource_buffer_index < 0 && + model->config.mode == AV2_DM_RESOURCE_AVAILABILITY_MODE) { + const Av2DmViolationDetail detail = + buffer_pool_violation_detail(&model->resource_lane.pool, true); + report_violation_for_affected( + model, AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE, + event->event_index, AV2_DM_VIOLATION_AFFECTED_EVENT, event->event_index, + NULL, NULL, &detail); + } + + int32_t buffer_index; + if (!lane_start_decode(&model->lane, &dfg->removal, &dfg->decode_time, + event->generation, &buffer_index)) { + arithmetic_failure(model); + return; + } + if (buffer_index < 0) { + const Av2DmViolationDetail detail = + buffer_pool_violation_detail(&model->lane.pool, false); + report_violation_for_affected( + model, AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE, + event->event_index, AV2_DM_VIOLATION_AFFECTED_EVENT, event->event_index, + NULL, NULL, &detail); + } + dfg->decode_completion = model->lane.time; + if (buffer_index >= 0) { + Av2DmBuffer *const buffer = &model->lane.pool.buffers[buffer_index]; + buffer->decode_order = dfg->decode_order; + buffer->rap_epoch = dfg->rap_epoch; + buffer->random_access_point = dfg->random_access_point; + buffer->coded_temporal_unit_index = dfg->temporal_unit_index; + buffer->coded_temporal_unit_valid = true; + } + if (resource_buffer_index >= 0) { + Av2DmBuffer *const buffer = + &model->resource_lane.pool.buffers[resource_buffer_index]; + buffer->decode_order = dfg->decode_order; + buffer->rap_epoch = dfg->rap_epoch; + buffer->random_access_point = dfg->random_access_point; + buffer->coded_temporal_unit_index = dfg->temporal_unit_index; + buffer->coded_temporal_unit_valid = true; + } + + if (model->config.mode == AV2_DM_DECODING_SCHEDULE_MODE) { + compare_lower_limit( + model, AV2_DM_VIOLATION_SCHEDULE_BEFORE_RESOURCE_REMOVAL, + event->event_index, &dfg->scheduled_removal, &resource_removal); + } + if (model->previous_dfg_valid) { + check_previous_dfg_interval(model, &model->previous_dfg, dfg); + } + check_schedule_delay_limits(model, event->event_index); + check_delay_consistency(model, dfg); + + if (model->dfg_number == 1 || event->random_access_point) { + model->most_recent_rap_scheduled_removal = dfg->scheduled_removal; + model->most_recent_rap_removal_valid = true; + } + model->previous_dfg = *dfg; + model->previous_dfg_valid = true; + if (!model->config.defer_nonterminal_checks_for_testing) { + retire_closed_smoothing_records(model, &dfg->last_arrival); + } + retire_unresolvable_tus(model); + if (!increment_model_u64(model, &model->result.decoded_frames)) return; + model_event_complete(model); + update_result_status(model); +} + +static bool update_lane_reference_buffers( + Av2DmLane *lane, const Av2DmReferenceUpdateEvent *event) { + for (uint32_t i = 0; i < lane->pool.num_ref_frames; ++i) { + if (((event->refresh_frame_flags >> i) & 1) == 0) continue; + int32_t buffer_index = -1; + if (((event->ref_valid_mask >> i) & 1) != 0) { + buffer_index = lane->current_buffer_index; + } + if (!av2_dm_buffer_pool_set_vbi(&lane->pool, i, buffer_index)) { + return false; + } + } + return true; +} + +void av2_decoder_model_update_reference_buffers( + Av2DecoderModel *model, const Av2DmReferenceUpdateEvent *event) { + if (model == NULL || event == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + if (model->shown_frame_number == UINT64_MAX || + model->result.output_frames == UINT64_MAX) { + arithmetic_failure(model); + return; + } + if (!update_lane_reference_buffers(&model->lane, event) || + !update_lane_reference_buffers(&model->resource_lane, event)) { + arithmetic_failure(model); + } + retire_unresolvable_tus(model); + model_event_complete(model); +} + +static bool invalidate_lane_reference_buffers(Av2DmLane *lane, + uint32_t ref_valid_mask) { + for (uint32_t i = 0; i < lane->pool.num_ref_frames; ++i) { + if (((ref_valid_mask >> i) & 1) == 0 && lane->pool.vbi[i] != -1 && + !av2_dm_buffer_pool_set_vbi(&lane->pool, i, -1)) { + return false; + } + } + return true; +} + +void av2_decoder_model_invalidate_olk_reference_buffers( + Av2DecoderModel *model, uint32_t ref_valid_mask) { + if (model == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + // DM-SPEC-5 / Annex E invalidate_olk_ref_buffers(): RefValid has already + // been updated by frame_header_info(), so every invalid slot is mirrored, + // including slots absent from the current refresh_frame_flags. + if (!invalidate_lane_reference_buffers(&model->lane, ref_valid_mask) || + !invalidate_lane_reference_buffers(&model->resource_lane, + ref_valid_mask)) { + arithmetic_failure(model); + } + model_event_complete(model); +} + +static void complete_output_checks(Av2DecoderModel *model, + uint64_t affected_event_index, + uint64_t proving_event_index, + const Av2DmRational *output_time, + const Av2DmRational *decode_completion, + const Av2DmRational *presentation) { + bool late; + if (!rational_greater(output_time, presentation, &late)) { + arithmetic_failure(model); + return; + } + if (late) { + report_violation_for_affected( + model, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_OUTPUT, affected_event_index, output_time, + presentation, NULL); + } + if (decode_completion == NULL) return; + bool missed_deadline; + if (!rational_greater(decode_completion, presentation, &missed_deadline)) { + arithmetic_failure(model); + return; + } + if (missed_deadline) { + // DM-SPEC-3 associates the three times by decoded generation, not by + // their positions in the decode- and presentation-order arrays. + report_violation_for_affected( + model, AV2_DM_VIOLATION_DECODE_DEADLINE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_OUTPUT, affected_event_index, + decode_completion, presentation, NULL); + } +} + +static bool update_pending_output_witness( + Av2DmPendingOutputWitness *pending, uint64_t event_index, + const Av2DmRational *observed, const Av2DmRational *presentation_offset) { + Av2DmRational threshold; + if (!av2_dm_rational_subtract(observed, presentation_offset, &threshold)) { + return false; + } + if (pending->valid) { + int comparison; + if (!av2_dm_rational_compare(&threshold, &pending->threshold, + &comparison)) { + return false; + } + if (comparison <= 0) return true; + } + pending->valid = true; + pending->event_index = event_index; + pending->threshold = threshold; + pending->observed = *observed; + pending->presentation_offset = *presentation_offset; + return true; +} + +static bool complete_pending_output_check(Av2DecoderModel *model, + Av2DmPendingOutputWitness *pending, + Av2DmViolationCode code, + const Av2DmRational *initial_delay, + uint64_t proving_event_index) { + if (!pending->valid) return true; + bool violated; + if (!rational_greater(&pending->threshold, initial_delay, &violated)) { + return false; + } + if (violated) { + Av2DmRational presentation; + if (!av2_dm_rational_add(&pending->presentation_offset, initial_delay, + &presentation)) { + return false; + } + report_violation_for_affected( + model, code, proving_event_index, AV2_DM_VIOLATION_AFFECTED_OUTPUT, + pending->event_index, &pending->observed, &presentation, NULL); + } + pending->valid = false; + return true; +} + +static bool set_lane_initial_presentation_delay(Av2DecoderModel *model, + Av2DmLane *lane, + bool primary_lane, + uint64_t proving_event_index) { + if (lane->initial_presentation_delay_known || + av2_dm_buffer_pool_frames_in_use(&lane->pool) < + model->config.initial_display_delay) { + return true; + } + lane->initial_presentation_delay = lane->time; + lane->initial_presentation_delay_known = true; + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + Av2DmBuffer *const buffer = &lane->pool.buffers[i]; + if (buffer->player_ref_count != 0 && !buffer->presentation_time_valid) { + if (!av2_dm_rational_add(&buffer->presentation_time, + &lane->initial_presentation_delay, + &buffer->presentation_time)) { + return false; + } + buffer->presentation_time_valid = true; + } + } + if (primary_lane && + (!complete_pending_output_check(model, &model->pending_display_late, + AV2_DM_VIOLATION_DISPLAY_FRAME_LATE, + &lane->initial_presentation_delay, + proving_event_index) || + !complete_pending_output_check(model, &model->pending_decode_deadline, + AV2_DM_VIOLATION_DECODE_DEADLINE, + &lane->initial_presentation_delay, + proving_event_index))) { + return false; + } + if (primary_lane && model->last_presentation_offset_valid) { + if (!av2_dm_rational_add(&model->last_presentation_offset, + &lane->initial_presentation_delay, + &model->last_presentation)) { + return false; + } + model->last_presentation_valid = true; + } + return true; +} + +void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, + uint64_t event_index) { + if (model == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + if (!set_lane_initial_presentation_delay(model, &model->lane, true, + event_index) || + !set_lane_initial_presentation_delay(model, &model->resource_lane, false, + event_index)) { + arithmetic_failure(model); + } + model_event_complete(model); +} + +static void check_tu_display_rate(Av2DecoderModel *model, Av2DmTuRecord *tu, + const Av2DmRational *duration, + uint64_t proving_event_index) { + if (model->config.still_picture) return; + Av2DmRational observed; + Av2DmRational capacity; + if (!av2_dm_rational_multiply_u64(duration, model->limits.max_display_rate, + &capacity) || + !av2_dm_rational_make(tu->output_luma_samples, 1, &observed)) { + arithmetic_failure(model); + return; + } + compare_upper_limit_for_affected( + model, AV2_DM_VIOLATION_MAX_DISPLAY_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, tu->temporal_unit_index, + &observed, &capacity); +} + +static void check_tu_minimum_presentation_interval( + Av2DecoderModel *model, Av2DmTuRecord *tu, const Av2DmRational *interval, + uint64_t proving_event_index) { + if (model->config.still_picture) return; + Av2DmRational limit; + const uint64_t max_headers = (uint64_t)model->limits.max_header_rate * + (1 + ((uint64_t)model->config.tier << 1)); + Av2DmRational sample_interval; + Av2DmRational min_frame_time; + if (!rational_from_product(model->config.max_frame_width, + model->config.max_frame_height, + &sample_interval) || + !av2_dm_rational_multiply_u64(&sample_interval, tu->output_frames, + &sample_interval) || + !av2_dm_rational_divide_u64( + &sample_interval, model->limits.max_display_rate, &sample_interval) || + !av2_dm_rational_make(model->limits.max_decode_rate, + model->limits.max_display_rate, &min_frame_time) || + !av2_dm_rational_divide_u64(&min_frame_time, max_headers, + &min_frame_time) || + !rational_max(&sample_interval, &min_frame_time, &limit)) { + arithmetic_failure(model); + return; + } + compare_lower_limit_for_affected( + model, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL, + proving_event_index, AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, + tu->temporal_unit_index, interval, &limit); +} + +static void update_tu_for_output(Av2DecoderModel *model, + const Av2DmOutputEvent *event, + const Av2DmRational *presentation_offset) { + Av2DmTuRecord *const tu = + get_tu(model, event->temporal_unit_index, event->event_index); + if (tu == NULL) return; + bool output_time_regressed = false; + if (UINT64_MAX - tu->output_luma_samples < event->output_luma_samples || + tu->output_frames == UINT32_MAX) { + arithmetic_failure(model); + return; + } + tu->output_luma_samples += event->output_luma_samples; + ++tu->output_frames; + if (!tu->presentation_time_valid) { + tu->presentation_time = *presentation_offset; + tu->presentation_time_valid = true; + } + if (!tu->output_time_valid) { + // When no external TU output time was supplied, the first actual output + // event establishes the TU output time in display order. Coding-order TU + // indices are identifiers and are not timestamps. + tu->output_time = *presentation_offset; + tu->output_time_valid = true; + } + if (!update_latest_timed_tu(model, &tu->output_time)) { + arithmetic_failure(model); + return; + } + if (model->last_output_tu_valid && + model->last_output_tu != tu->temporal_unit_index) { + Av2DmTuRecord *const previous = find_tu(model, model->last_output_tu); + if (previous == NULL) { + arithmetic_failure(model); + return; + } + if (previous->presentation_time_valid) { + Av2DmRational presentation_interval; + if (!av2_dm_rational_subtract(presentation_offset, + &previous->presentation_time, + &presentation_interval)) { + arithmetic_failure(model); + return; + } + check_tu_minimum_presentation_interval( + model, previous, &presentation_interval, event->event_index); + previous->prior_presentation_interval_checked = true; + } + if (previous->output_time_valid && tu->output_time_valid) { + int ordering; + if (!av2_dm_rational_compare(&tu->output_time, &previous->output_time, + &ordering)) { + arithmetic_failure(model); + return; + } + output_time_regressed = ordering <= 0; + Av2DmRational display_duration; + if (!av2_dm_rational_subtract(&tu->output_time, &previous->output_time, + &display_duration)) { + arithmetic_failure(model); + return; + } + check_tu_display_rate(model, previous, &display_duration, + event->event_index); + model->last_display_duration = display_duration; + model->last_display_duration_valid = true; + } + } + model->last_output_tu = tu->temporal_unit_index; + model->last_output_tu_valid = true; + if (output_time_regressed && + (violation_seen(model, AV2_DM_VIOLATION_MAX_DISPLAY_RATE) || + violation_seen(model, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL) || + violation_seen(model, AV2_DM_VIOLATION_PRESENTATION_TIME_DECREASE))) { + // A non-increasing output timeline has already proven conformance failure. + // Start a new bounded rate-window segment while retaining any generations + // that can still be output and checked for other violation classes. + restart_tu_history(model, tu->temporal_unit_index); + } +} + +static const Av2DmRapPresentationAnchor *find_rap_presentation_anchor( + const Av2DecoderModel *model, uint64_t rap_epoch) { + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + if (model->rap_presentation_anchors[i].valid && + model->rap_presentation_anchors[i].rap_epoch == rap_epoch) { + return &model->rap_presentation_anchors[i]; + } + } + return NULL; +} + +static void store_rap_presentation_anchor(Av2DecoderModel *model, + uint64_t rap_epoch, + const Av2DmRational *offset) { + Av2DmRapPresentationAnchor *free_anchor = NULL; + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + if (model->rap_presentation_anchors[i].valid && + model->rap_presentation_anchors[i].rap_epoch == rap_epoch) { + model->rap_presentation_anchors[i].presentation_offset = *offset; + return; + } + if (!model->rap_presentation_anchors[i].valid && free_anchor == NULL) { + free_anchor = &model->rap_presentation_anchors[i]; + } + } + if (free_anchor == NULL) { + // At most one anchor is needed per DPB generation epoch, plus the current + // and immediately preceding RAP. Reclaim an epoch that no live generation + // can present again before treating exhaustion as an internal failure. + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + Av2DmRapPresentationAnchor *const candidate = + &model->rap_presentation_anchors[i]; + bool live = candidate->rap_epoch == model->rap_epoch || + (model->rap_epoch != 0 && + candidate->rap_epoch == model->rap_epoch - 1); + for (uint32_t j = 0; j < model->lane.pool.pool_size && !live; ++j) { + const Av2DmBuffer *const buffer = &model->lane.pool.buffers[j]; + live = buffer->generation_valid && + buffer->rap_epoch == candidate->rap_epoch; + } + if (!live) { + free_anchor = candidate; + break; + } + } + } + if (free_anchor == NULL) { + arithmetic_failure(model); + return; + } + free_anchor->valid = true; + free_anchor->rap_epoch = rap_epoch; + free_anchor->presentation_offset = *offset; +} + +static bool calculate_presentation_offset(Av2DecoderModel *model, + const Av2DmOutputEvent *event, + const Av2DmBuffer *buffer, + Av2DmRational *offset) { + if (model->config.equal_picture_interval) { + if (!model->last_presentation_offset_valid) return rational_zero(offset); + if (event->temporal_unit_index == model->last_output_temporal_unit) { + *offset = model->last_presentation_offset; + return true; + } + Av2DmRational increment; + return av2_dm_rational_multiply_u64( + &model->disp_ct, model->config.ticks_per_picture, &increment) && + av2_dm_rational_add(&model->last_presentation_offset, &increment, + offset); + } + if (!event->presentation_time_present) { + missing_input(model); + return false; + } + if (model->shown_frame_number == 0) return rational_zero(offset); + Av2DmRational base; + bool base_found = false; + uint64_t presentation_epoch = model->rap_epoch; + bool random_access_point = event->presentation_random_access_point; + if (!event->presentation_uses_current_frame) { + presentation_epoch = buffer->rap_epoch; + random_access_point = buffer->random_access_point; + } + if (buffer->generation_valid || event->presentation_uses_current_frame || + model->config.ras_start) { + const uint64_t base_epoch = + (random_access_point || event->leading_frame) + ? (presentation_epoch == 0 ? 0 : presentation_epoch - 1) + : presentation_epoch; + if (base_epoch == 0) { + base_found = rational_zero(&base); + } else { + const Av2DmRapPresentationAnchor *const anchor = + find_rap_presentation_anchor(model, base_epoch); + if (anchor != NULL) { + base = anchor->presentation_offset; + base_found = true; + } + } + } + if (!base_found && event->presentation_base_offset_present) { + // Externally seeded RAS frames have no decode record in this model run. + base = event->presentation_base_offset; + base_found = true; + } + if (!base_found) { + missing_input(model); + return false; + } + Av2DmRational increment; + return av2_dm_rational_multiply_u64( + &model->disp_ct, event->presentation_time_ticks, &increment) && + av2_dm_rational_add(&base, &increment, offset); +} + +static int32_t select_output_buffer(Av2DecoderModel *model, Av2DmLane *lane, + const Av2DmOutputEvent *event, + bool report_error) { + if (event->frame_to_show_map_idx == -1) { + return lane->current_buffer_index; + } + if (event->frame_to_show_map_idx < 0 || + (uint32_t)event->frame_to_show_map_idx >= lane->pool.num_ref_frames || + ((event->ref_valid_mask >> event->frame_to_show_map_idx) & 1) == 0 || + lane->pool.vbi[event->frame_to_show_map_idx] == -1) { + if (report_error) { + Av2DmViolationDetail detail; + memset(&detail, 0, sizeof(detail)); + detail.kind = AV2_DM_VIOLATION_DETAIL_REFERENCE_SLOT; + Av2DmReferenceSlotViolationDetail *const slot = + &detail.value.reference_slot; + slot->requested_slot = event->frame_to_show_map_idx; + slot->slot_in_range = + event->frame_to_show_map_idx >= 0 && + (uint32_t)event->frame_to_show_map_idx < lane->pool.num_ref_frames; + slot->buffer_index = -1; + if (slot->slot_in_range) { + slot->reference_valid = + ((event->ref_valid_mask >> event->frame_to_show_map_idx) & 1) != 0; + slot->buffer_index = lane->pool.vbi[event->frame_to_show_map_idx]; + } + slot->pool = + buffer_pool_violation_detail(&lane->pool, false).value.buffer_pool; + report_violation_for_affected( + model, AV2_DM_VIOLATION_DECODE_EXISTING_FRAME_BUFFER_EMPTY, + event->event_index, AV2_DM_VIOLATION_AFFECTED_OUTPUT, + event->event_index, NULL, NULL, &detail); + } + return -1; + } + return lane->pool.vbi[event->frame_to_show_map_idx]; +} + +void av2_decoder_model_output_frame(Av2DecoderModel *model, + const Av2DmOutputEvent *event) { + if (model == NULL || event == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + const int32_t buffer_index = + select_output_buffer(model, &model->lane, event, true); + const int32_t resource_buffer_index = + select_output_buffer(model, &model->resource_lane, event, false); + if (buffer_index < 0 || resource_buffer_index < 0) { + if (!increment_output_count(model)) return; + model_event_complete(model); + update_result_status(model); + return; + } + Av2DmBuffer *const buffer = &model->lane.pool.buffers[buffer_index]; + Av2DmBuffer *const resource_buffer = + &model->resource_lane.pool.buffers[resource_buffer_index]; + if (!buffer->generation_valid || !resource_buffer->generation_valid || + buffer->generation != event->generation || + resource_buffer->generation != event->generation) { + missing_input(model); + return; + } + Av2DmRational presentation_offset; + if (!calculate_presentation_offset(model, event, buffer, + &presentation_offset)) { + if (!model->result.missing_required_input) arithmetic_failure(model); + return; + } + const uint64_t presentation_epoch = event->presentation_uses_current_frame + ? model->rap_epoch + : buffer->rap_epoch; + const uint64_t output_rap_epoch = + event->leading_frame && presentation_epoch != 0 ? presentation_epoch - 1 + : presentation_epoch; + const bool random_access_point = event->presentation_uses_current_frame + ? event->presentation_random_access_point + : buffer->random_access_point; + Av2DmRational presentation = presentation_offset; + if (model->lane.initial_presentation_delay_known) { + if (!av2_dm_rational_add(&presentation, + &model->lane.initial_presentation_delay, + &presentation)) { + arithmetic_failure(model); + return; + } + } + buffer->presentation_time = presentation; + buffer->presentation_time_valid = + model->lane.initial_presentation_delay_known; + if (!av2_dm_buffer_pool_add_player_ref(&model->lane.pool, + (uint32_t)buffer_index)) { + arithmetic_failure(model); + return; + } + + resource_buffer->presentation_time = presentation_offset; + resource_buffer->presentation_time_valid = false; + if (model->resource_lane.initial_presentation_delay_known) { + if (!av2_dm_rational_add(&resource_buffer->presentation_time, + &model->resource_lane.initial_presentation_delay, + &resource_buffer->presentation_time)) { + arithmetic_failure(model); + return; + } + resource_buffer->presentation_time_valid = true; + } + if (!av2_dm_buffer_pool_add_player_ref(&model->resource_lane.pool, + (uint32_t)resource_buffer_index)) { + arithmetic_failure(model); + return; + } + + if (model->previous_output_presentation_valid && + model->previous_output_rap_epoch == output_rap_epoch) { + compare_lower_limit(model, AV2_DM_VIOLATION_PRESENTATION_TIME_DECREASE, + event->event_index, &presentation_offset, + &model->previous_output_presentation_offset); + } + if (buffer->decode_completion_time_valid) { + if (model->previous_output_order_valid && + buffer->decode_order < model->previous_output_decode_order) { + if (!increment_model_u64(model, &model->result.reordered_outputs)) { + return; + } + } + model->previous_output_decode_order = buffer->decode_order; + model->previous_output_order_valid = true; + } + model->previous_output_presentation_offset = presentation_offset; + model->previous_output_presentation_valid = true; + model->previous_output_rap_epoch = output_rap_epoch; + model->last_presentation_offset = presentation_offset; + model->last_presentation_offset_valid = true; + if (model->lane.initial_presentation_delay_known) { + model->last_presentation = presentation; + model->last_presentation_valid = true; + } + model->last_output_temporal_unit = event->temporal_unit_index; + if (random_access_point) { + store_rap_presentation_anchor(model, output_rap_epoch, + &presentation_offset); + } + update_tu_for_output(model, event, &presentation_offset); + if (model->processing_stopped) return; + if (!model->config.defer_nonterminal_checks_for_testing) { + check_header_rate_windows(model, false, event->event_index); + } + if (model->processing_stopped) return; + if (model->lane.initial_presentation_delay_known) { + complete_output_checks( + model, event->event_index, event->event_index, &model->lane.time, + buffer->decode_completion_time_valid ? &buffer->decode_completion_time + : NULL, + &presentation); + } else if (!update_pending_output_witness( + &model->pending_display_late, event->event_index, + &model->lane.time, &presentation_offset) || + (buffer->decode_completion_time_valid && + !update_pending_output_witness( + &model->pending_decode_deadline, event->event_index, + &buffer->decode_completion_time, &presentation_offset))) { + arithmetic_failure(model); + return; + } + + if (!increment_output_count(model)) return; + model_event_complete(model); + update_result_status(model); +} + +static void check_smoothing_fullness_at(Av2DecoderModel *model, + const Av2DmRational *time, + Av2DmDfgRecord *breakpoint, + uint64_t proving_event_index) { + Av2DmRational fullness; + if (!rational_zero(&fullness)) { + arithmetic_failure(model); + return; + } + for (uint32_t i = 0; i < model->dfg_count; ++i) { + const Av2DmDfgRecord *const dfg = &model->dfgs[i]; + if (dfg->smoothing_epoch != breakpoint->smoothing_epoch) continue; + int before_first; + int after_removal; + if (!av2_dm_rational_compare(time, &dfg->first_arrival, &before_first) || + !av2_dm_rational_compare(time, &dfg->removal, &after_removal)) { + arithmetic_failure(model); + return; + } + if (before_first < 0 || after_removal > 0) continue; + Av2DmRational duration; + Av2DmRational arrived; + Av2DmRational coded_bits; + if (!av2_dm_rational_subtract(time, &dfg->first_arrival, &duration) || + !rational_multiply(&duration, &breakpoint->limits.bit_rate, &arrived) || + !av2_dm_rational_make(dfg->coded_bits, 1, &coded_bits)) { + arithmetic_failure(model); + return; + } + bool too_many; + if (!rational_greater(&arrived, &coded_bits, &too_many)) { + arithmetic_failure(model); + return; + } + if (too_many) arrived = coded_bits; + if (!av2_dm_rational_add(&fullness, &arrived, &fullness)) { + arithmetic_failure(model); + return; + } + } + bool overflow; + if (!rational_greater(&fullness, &breakpoint->limits.buffer_size, + &overflow)) { + arithmetic_failure(model); + return; + } + if (overflow && !breakpoint->smoothing_overflow_reported) { + breakpoint->smoothing_overflow_reported = true; + report_violation_for_affected( + model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, breakpoint->event_index, &fullness, + &breakpoint->limits.buffer_size, NULL); + } +} + +static void retire_closed_smoothing_records(Av2DecoderModel *model, + const Av2DmRational *frontier) { + uint32_t write_index = 0; + for (uint32_t i = 0; i < model->dfg_count; ++i) { + Av2DmDfgRecord *const dfg = &model->dfgs[i]; + int comparison; + if (!av2_dm_rational_compare(&dfg->removal, frontier, &comparison)) { + arithmetic_failure(model); + return; + } + // Equality remains live because a later DFG may start arriving at exactly + // this frontier and introduce another breakpoint at the same instant. + if (comparison < 0) continue; + if (write_index != i) model->dfgs[write_index] = *dfg; + ++write_index; + } + model->dfg_count = write_index; +} + +static void check_smoothing_buffer_overflow(Av2DecoderModel *model, + uint64_t proving_event_index) { + if (violation_seen(model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)) { + model->dfg_count = 0; + return; + } + for (uint32_t i = 0; i < model->dfg_count; ++i) { + Av2DmDfgRecord *const breakpoint = &model->dfgs[i]; + check_smoothing_fullness_at(model, &breakpoint->last_arrival, breakpoint, + proving_event_index); + check_smoothing_fullness_at(model, &breakpoint->removal, breakpoint, + proving_event_index); + } + if (violation_seen(model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)) { + // Fullness history cannot prove a different code after overflow has made + // this CVS non-conformant. Adjacent-DFG and per-frame checks retain their + // independent scalar state and continue online. + model->dfg_count = 0; + } +} + +static bool same_scope(const Av2DmScope *a, const Av2DmScope *b) { + return a->xlayer_id == b->xlayer_id && a->ops_xlayer_id == b->ops_xlayer_id && + a->ops_id == b->ops_id && a->operating_point == b->operating_point && + a->whole_xlayer == b->whole_xlayer; +} + +static bool same_model_topology_and_clock(const Av2DmConfig *a, + const Av2DmConfig *b) { + // Section 7 keeps the active sequence header fixed until the next CLK, + // which starts a new CVS and therefore a new model. In-place RAP updates + // can replace OPS parameters, but not the active sequence-level fallback. + return same_scope(&a->scope, &b->scope) && + a->num_ref_frames == b->num_ref_frames && + a->max_frame_width == b->max_frame_width && + a->max_frame_height == b->max_frame_height && + a->max_mlayer_id == b->max_mlayer_id && + a->still_picture == b->still_picture && + a->explicit_num_ref_frames == b->explicit_num_ref_frames && + a->timing_info_present == b->timing_info_present && + a->num_units_in_display_tick == b->num_units_in_display_tick && + a->time_scale == b->time_scale && + a->num_units_in_decoding_tick == b->num_units_in_decoding_tick && + a->equal_picture_interval == b->equal_picture_interval && + a->ticks_per_picture == b->ticks_per_picture && + a->sequence_parameters_present == b->sequence_parameters_present && + a->sequence_decoder_buffer_delay == b->sequence_decoder_buffer_delay && + a->sequence_encoder_buffer_delay == b->sequence_encoder_buffer_delay && + a->sequence_low_delay_mode == b->sequence_low_delay_mode && + a->rebase_interval_events == b->rebase_interval_events && + a->defer_nonterminal_checks_for_testing == + b->defer_nonterminal_checks_for_testing && + a->stop_after_first_violation == b->stop_after_first_violation; +} + +bool av2_decoder_model_update_parameters(Av2DecoderModel *model, + const Av2DmConfig *config, + uint64_t event_index) { + if (model == NULL || config == NULL || model->result.finished || + model->processing_stopped) { + return false; + } + if (model->result.applicability != AV2_DM_APPLICABLE || + config->applicability != AV2_DM_APPLICABLE || + !same_model_topology_and_clock(&model->config, config)) { + missing_input(model); + return false; + } + + Av2DmResolvedParameters parameters; + if (!resolve_parameters(config, ¶meters)) { + missing_input(model); + return false; + } + if (!model->config.defer_nonterminal_checks_for_testing) { + // The old smoothing epoch is evaluated with the old BitRate and + // BufferSize before the replacement parameters take effect. + check_smoothing_buffer_overflow(model, event_index); + model->dfg_count = 0; + } + if (model->processing_stopped) return false; + if (model->smoothing_epoch == UINT64_MAX) { + arithmetic_failure(model); + return false; + } + ++model->smoothing_epoch; + model->smoothing_epoch_prepared = true; + + Av2DmConfig updated = *config; + updated.initial_display_delay = model->config.initial_display_delay; + updated.ras_start = model->config.ras_start; + updated.ras_seed_complete = model->config.ras_seed_complete; + updated.ras_seed_count = model->config.ras_seed_count; + memcpy(updated.ras_seeds, model->config.ras_seeds, sizeof(updated.ras_seeds)); + const Av2DmMode old_mode = model->config.mode; + apply_parameters(model, &updated, ¶meters); + if (!model->max_reference_frames_violated) { + // The maximum depends on the active level limits and must be reconsidered + // for the RAP frame after an OPS parameter update. + model->max_reference_frames_checked = false; + } + if (old_mode == AV2_DM_DECODING_SCHEDULE_MODE && + updated.mode == AV2_DM_RESOURCE_AVAILABILITY_MODE) { + // The resource lane is maintained for every event. It is therefore the + // continuous resource-availability state when that mode becomes active. + model->lane = model->resource_lane; + } + model->result.mode = updated.mode; + update_result_status(model); + update_storage_stats(model); + return true; +} + +void av2_decoder_model_mark_incomplete(Av2DecoderModel *model) { + if (model == NULL || model->result.finished || model->processing_stopped) { + return; + } + missing_input(model); +} + +static void check_max_reference_frames(Av2DecoderModel *model, + uint64_t event_index) { + if (model->config.still_picture) return; + if (model->max_reference_frames_violated) return; + if (model->max_reference_frames_checked && + model->max_reference_frames_reserved == + model->any_decode_count_two_requires_reserved_buffer) { + return; + } + model->max_reference_frames_checked = true; + model->max_reference_frames_reserved = + model->any_decode_count_two_requires_reserved_buffer; + const uint64_t frame_size = + (uint64_t)model->config.max_frame_width * model->config.max_frame_height; + if (frame_size == 0) { + missing_input(model); + return; + } + if (model->limits.max_picture_size > UINT64_MAX / 8) { + arithmetic_failure(model); + return; + } + uint64_t maximum = 8 * model->limits.max_picture_size / frame_size; + if (model->any_decode_count_two_requires_reserved_buffer && maximum != 0) { + --maximum; + } + const uint64_t syntax_maximum = + model->config.explicit_num_ref_frames ? 16 : 8; + if (maximum > syntax_maximum) maximum = syntax_maximum; + Av2DmRational observed; + Av2DmRational limit; + if (!av2_dm_rational_make(model->config.num_ref_frames, 1, &observed) || + !av2_dm_rational_make(maximum, 1, &limit)) { + arithmetic_failure(model); + return; + } + bool too_many_reference_frames; + if (!rational_greater(&observed, &limit, &too_many_reference_frames)) { + arithmetic_failure(model); + } else if (too_many_reference_frames) { + model->max_reference_frames_violated = true; + report_violation(model, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES, event_index, + &observed, &limit); + } +} + +static void check_header_rate_at(Av2DecoderModel *model, Av2DmTuRecord *end_tu, + uint64_t frame_headers, + uint64_t maximum_headers, + uint64_t proving_event_index) { + Av2DmRational observed; + Av2DmRational limit; + if (!av2_dm_rational_make(frame_headers, 1, &observed) || + !av2_dm_rational_make(maximum_headers, 1, &limit)) { + arithmetic_failure(model); + return; + } + bool violated; + if (!rational_greater(&observed, &limit, &violated)) { + arithmetic_failure(model); + return; + } + if (violated && !end_tu->header_rate_reported) { + end_tu->header_rate_reported = true; + report_violation_for_affected( + model, AV2_DM_VIOLATION_MAX_HEADER_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, end_tu->temporal_unit_index, + &observed, &limit, NULL); + } + if (!rational_from_product(model->maximum_tile_area, frame_headers, + &observed) || + !av2_dm_rational_make(model->limits.max_tile_size_header_rate_product, 1, + &limit)) { + arithmetic_failure(model); + return; + } + if (!rational_greater(&observed, &limit, &violated)) { + arithmetic_failure(model); + return; + } + if (violated && !end_tu->tile_header_rate_reported) { + end_tu->tile_header_rate_reported = true; + report_violation_for_affected( + model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, end_tu->temporal_unit_index, + &observed, &limit, NULL); + } +} + +static void check_retired_tile_header_summary(Av2DecoderModel *model, + uint64_t proving_event_index) { + if (violation_seen(model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) || + !model->retired_header_summary_valid || + model->retired_header_summary_reported) { + return; + } + Av2DmRational observed; + Av2DmRational limit; + if (!rational_from_product(model->maximum_tile_area, + model->retired_max_frame_headers, &observed) || + !av2_dm_rational_make(model->limits.max_tile_size_header_rate_product, 1, + &limit)) { + arithmetic_failure(model); + return; + } + bool violated; + if (!rational_greater(&observed, &limit, &violated)) { + arithmetic_failure(model); + return; + } + if (violated) { + model->retired_header_summary_reported = true; + report_violation_for_affected( + model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, + model->retired_header_event_index, &observed, &limit, NULL); + } +} + +static bool order_tus_by_output_time(const Av2DecoderModel *model, + uint32_t **ordered_tus, + uint32_t *ordered_tu_count) { + *ordered_tus = NULL; + *ordered_tu_count = 0; + if (model->tu_count == 0) return true; + const uint64_t capacity = model->tu_count; + if (capacity > SIZE_MAX / sizeof(**ordered_tus)) return false; + const size_t allocation_size = (size_t)capacity * sizeof(**ordered_tus); + uint32_t *source = avm_malloc(allocation_size); + uint32_t *destination = avm_malloc(allocation_size); + if (source == NULL || destination == NULL) { + avm_free(source); + avm_free(destination); + return false; + } + uint32_t count = 0; + for (uint32_t i = 0; i < model->tu_count; ++i) { + if (model->tus[i].output_time_valid) source[count++] = i; + } + for (size_t width = 1; width < count; width *= 2) { + const size_t block_width = 2 * width; + for (size_t left = 0; left < count; left += block_width) { + const size_t middle = left + width < count ? left + width : count; + const size_t right = + left + block_width < count ? left + block_width : count; + size_t first = left; + size_t second = middle; + size_t output = left; + while (first < middle && second < right) { + int comparison; + if (!av2_dm_rational_compare(&model->tus[source[first]].output_time, + &model->tus[source[second]].output_time, + &comparison)) { + avm_free(source); + avm_free(destination); + return false; + } + destination[output++] = + comparison <= 0 ? source[first++] : source[second++]; + } + while (first < middle) destination[output++] = source[first++]; + while (second < right) destination[output++] = source[second++]; + } + uint32_t *const swap = source; + source = destination; + destination = swap; + if (width > count / 2) break; + } + avm_free(destination); + *ordered_tus = source; + *ordered_tu_count = count; + return true; +} + +static void check_header_rate_windows_in_output_order( + Av2DecoderModel *model, const uint32_t *ordered_tus, + uint32_t ordered_tu_count, const Av2DmRational *one_second, + uint64_t maximum_headers, uint64_t proving_event_index) { + uint32_t first_tu = 0; + uint64_t frame_headers = 0; + for (uint32_t i = 0; i < ordered_tu_count; ++i) { + Av2DmTuRecord *const end_tu = &model->tus[ordered_tus[i]]; + if (UINT64_MAX - frame_headers < end_tu->frame_headers) { + arithmetic_failure(model); + return; + } + frame_headers += end_tu->frame_headers; + Av2DmRational window_start; + if (!av2_dm_rational_subtract(&end_tu->output_time, one_second, + &window_start)) { + arithmetic_failure(model); + return; + } + while (first_tu <= i) { + const Av2DmTuRecord *const candidate = &model->tus[ordered_tus[first_tu]]; + int comparison; + if (!av2_dm_rational_compare(&candidate->output_time, &window_start, + &comparison)) { + arithmetic_failure(model); + return; + } + if (comparison >= 0) break; + if (frame_headers < candidate->frame_headers) { + arithmetic_failure(model); + return; + } + frame_headers -= candidate->frame_headers; + ++first_tu; + } + if (end_tu->header_complete && !end_tu->header_window_checked) { + end_tu->header_window_checked = true; + end_tu->header_window_headers = frame_headers; + } + check_header_rate_at(model, end_tu, + end_tu->header_window_checked + ? end_tu->header_window_headers + : frame_headers, + maximum_headers, proving_event_index); + if (model->processing_stopped) return; + } +} + +static bool lane_has_live_coded_tu(const Av2DmLane *lane, + uint64_t temporal_unit_index) { + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + const Av2DmBuffer *const buffer = &lane->pool.buffers[i]; + if (lane_buffer_is_live(lane, i) && buffer->coded_temporal_unit_valid && + buffer->coded_temporal_unit_index == temporal_unit_index) { + return true; + } + } + return false; +} + +static bool tu_has_live_generation(const Av2DecoderModel *model, + uint64_t temporal_unit_index) { + return lane_has_live_coded_tu(&model->lane, temporal_unit_index) || + lane_has_live_coded_tu(&model->resource_lane, temporal_unit_index); +} + +static void remember_retired_tu(Av2DecoderModel *model, + const Av2DmTuRecord *tu) { + if (!tu->output_time_valid && + (tu->frame_headers != 0 || tu->output_frames != 0)) { + model->retired_unresolved_tu = true; + } + if (violation_seen(model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) || + !tu->header_window_checked || tu->tile_header_rate_reported) { + return; + } + if (!model->retired_header_summary_valid || + tu->header_window_headers > model->retired_max_frame_headers) { + model->retired_header_summary_valid = true; + model->retired_max_frame_headers = tu->header_window_headers; + model->retired_header_event_index = tu->temporal_unit_index; + } +} + +static void retire_unresolvable_tus(Av2DecoderModel *model) { + uint32_t write_index = 0; + for (uint32_t i = 0; i < model->tu_count; ++i) { + Av2DmTuRecord *const tu = &model->tus[i]; + const bool current_coded = + model->coded_tu_valid && tu->temporal_unit_index == model->coded_tu; + const bool retire = tu->header_complete && !tu->output_time_valid && + !current_coded && + !tu_has_live_generation(model, tu->temporal_unit_index); + if (retire) { + remember_retired_tu(model, tu); + continue; + } + if (write_index != i) model->tus[write_index] = *tu; + ++write_index; + } + model->tu_count = write_index; +} + +static void restart_tu_history(Av2DecoderModel *model, + uint64_t temporal_unit_index) { + uint32_t write_index = 0; + for (uint32_t i = 0; i < model->tu_count; ++i) { + Av2DmTuRecord *const tu = &model->tus[i]; + const bool keep_current = + tu->temporal_unit_index == temporal_unit_index || + (model->coded_tu_valid && tu->temporal_unit_index == model->coded_tu); + const bool keep_pending = + !tu->output_time_valid && + tu_has_live_generation(model, tu->temporal_unit_index); + if (!keep_current && !keep_pending) { + remember_retired_tu(model, tu); + continue; + } + if (write_index != i) model->tus[write_index] = *tu; + ++write_index; + } + model->tu_count = write_index; + const Av2DmTuRecord *const current = find_tu(model, temporal_unit_index); + if (current != NULL && current->output_time_valid) { + model->latest_timed_tu_output_time = current->output_time; + model->latest_timed_tu_valid = true; + } +} + +static void retire_closed_tus(Av2DecoderModel *model, + const Av2DmRational *one_second) { + const bool header_history_proven = + violation_seen(model, AV2_DM_VIOLATION_MAX_HEADER_RATE) && + violation_seen(model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE); + if (!header_history_proven && !model->latest_timed_tu_valid) return; + Av2DmRational frontier; + if (!header_history_proven && + !av2_dm_rational_subtract(&model->latest_timed_tu_output_time, one_second, + &frontier)) { + arithmetic_failure(model); + return; + } + uint32_t write_index = 0; + for (uint32_t i = 0; i < model->tu_count; ++i) { + Av2DmTuRecord *const tu = &model->tus[i]; + bool retire = false; + if (tu->header_complete && + (!model->coded_tu_valid || + tu->temporal_unit_index != model->coded_tu) && + tu->temporal_unit_index != model->last_output_tu && + (tu->output_time_valid || + !tu_has_live_generation(model, tu->temporal_unit_index))) { + if (header_history_proven) { + retire = true; + } else if (tu->header_window_checked && tu->output_time_valid) { + int comparison; + if (!av2_dm_rational_compare(&tu->output_time, &frontier, + &comparison)) { + arithmetic_failure(model); + return; + } + retire = comparison < 0; + } + } + if (retire) { + remember_retired_tu(model, tu); + continue; + } + if (write_index != i) model->tus[write_index] = *tu; + ++write_index; + } + model->tu_count = write_index; +} + +static void check_header_rate_windows(Av2DecoderModel *model, + bool require_complete, + uint64_t proving_event_index) { + if (model->config.still_picture) return; + if (!require_complete) { + model->latest_header_check_event_index = proving_event_index; + } + if (require_complete) { + for (uint32_t i = 0; i < model->tu_count; ++i) { + if (model->tus[i].frame_headers != 0 && + !model->tus[i].output_time_valid) { + incomplete_verification(model); + return; + } + } + } + Av2DmRational one_second; + if (!av2_dm_rational_make(1, 1, &one_second)) { + arithmetic_failure(model); + return; + } + const uint64_t maximum_headers = (uint64_t)model->limits.max_header_rate * + (1 + ((uint64_t)model->config.tier << 1)); + uint32_t *ordered_tus; + uint32_t ordered_tu_count; + if (!order_tus_by_output_time(model, &ordered_tus, &ordered_tu_count)) { + arithmetic_failure(model); + return; + } + check_header_rate_windows_in_output_order( + model, ordered_tus, ordered_tu_count, &one_second, maximum_headers, + proving_event_index); + avm_free(ordered_tus); + if (!model->processing_stopped && !require_complete) { + retire_closed_tus(model, &one_second); + } +} + +void av2_decoder_model_finish(Av2DecoderModel *model) { + if (model == NULL || model->result.finished) return; + if (model->result.applicability != AV2_DM_NOT_APPLICABLE && + !model->processing_stopped) { + if (model->coded_tu_valid) { + Av2DmTuRecord *const coded_tu = find_tu(model, model->coded_tu); + if (coded_tu == NULL) { + arithmetic_failure(model); + } else { + coded_tu->header_complete = true; + } + } + if (!model->lane.initial_presentation_delay_known && + model->shown_frame_number != 0) { + incomplete_verification(model); + } + if (!model->processing_stopped && !model->config.still_picture && + model->previous_dfg_valid) { + if (model->last_frame_parsing_time_valid) { + check_frame_parsing_constraints(model, &model->previous_dfg, + &model->last_frame_parsing_time, + model->previous_dfg.event_index); + } else { + incomplete_verification(model); + } + if (!model->processing_stopped) { + Av2DmTuRecord *const last_output_tu = + model->last_output_tu_valid ? find_tu(model, model->last_output_tu) + : NULL; + if (last_output_tu != NULL) { + if (model->last_display_duration_valid) { + // Annex A reuses the preceding output duration for the last TU. + // Annex E does not synthesize another presentation interval. + check_tu_display_rate(model, last_output_tu, + &model->last_display_duration, + last_output_tu->event_index); + } else { + incomplete_verification(model); + } + } + } + } + if (!model->processing_stopped && + model->config.defer_nonterminal_checks_for_testing) { + check_smoothing_buffer_overflow(model, model->latest_frame_event_index); + } + if (!model->processing_stopped && + model->config.defer_nonterminal_checks_for_testing) { + check_max_reference_frames(model, model->latest_frame_event_index); + } + if (!model->processing_stopped && model->retired_unresolved_tu) { + incomplete_verification(model); + } + if (!model->processing_stopped) { + check_header_rate_windows(model, true, + model->latest_header_check_event_index); + } + } + model->result.finished = true; + update_result_status(model); + update_storage_stats(model); +} + +bool av2_decoder_model_get_result(const Av2DecoderModel *model, + Av2DmResult *result) { + if (model == NULL || result == NULL) return false; + *result = model->result; + return true; +} + +bool av2_decoder_model_get_state(const Av2DecoderModel *model, + Av2DmState *state) { + if (model == NULL || state == NULL) return false; + memset(state, 0, sizeof(*state)); + state->time = model->lane.time; + state->initial_presentation_delay_known = + model->lane.initial_presentation_delay_known; + state->initial_presentation_delay = model->lane.initial_presentation_delay; + state->current_buffer_index = model->lane.current_buffer_index; + state->frame_number = model->frame_number; + state->dfg_number = model->dfg_number; + state->shown_frame_number = model->shown_frame_number; + state->buffer_pool = model->lane.pool; + if (model->previous_dfg_valid) { + const Av2DmDfgRecord *const dfg = &model->previous_dfg; + state->last_dfg_valid = true; + state->first_bit_arrival = dfg->first_arrival; + state->last_bit_arrival = dfg->last_arrival; + state->scheduled_removal = dfg->scheduled_removal; + state->removal = dfg->removal; + state->time_to_decode = dfg->decode_time; + state->decode_completion = dfg->decode_completion; + } + if (model->shown_frame_number != 0) { + state->last_presentation_valid = model->last_presentation_valid; + state->last_presentation = model->last_presentation; + state->last_presentation_offset_valid = + model->last_presentation_offset_valid; + state->last_presentation_offset = model->last_presentation_offset; + state->last_output_temporal_unit_valid = true; + state->last_output_temporal_unit = model->last_output_temporal_unit; + } + if (model->last_output_tu_valid) { + const Av2DmTuRecord *tu = NULL; + for (uint32_t i = model->tu_count; i > 0; --i) { + if (model->tus[i - 1].temporal_unit_index == model->last_output_tu) { + tu = &model->tus[i - 1]; + break; + } + } + if (tu == NULL) return false; + state->last_temporal_unit_output_time_valid = tu->output_time_valid; + state->last_temporal_unit_output_time = tu->output_time; + state->last_temporal_unit_output_luma_samples = tu->output_luma_samples; + state->last_temporal_unit_output_frames = tu->output_frames; + } + return true; +} + +bool av2_decoder_model_get_storage_stats(const Av2DecoderModel *model, + Av2DmStorageStats *stats) { + if (model == NULL || stats == NULL) return false; + *stats = model->storage; + return true; +} + +const char *av2_dm_violation_code_name(Av2DmViolationCode code) { + static const char *const names[] = { + "DECODE_FRAME_BUFFER_UNAVAILABLE", + "DECODE_EXISTING_FRAME_BUFFER_EMPTY", + "DISPLAY_FRAME_LATE", + "SMOOTHING_BUFFER_UNDERFLOW", + "SMOOTHING_BUFFER_OVERFLOW", + "PRESENTATION_TIME_DECREASE", + "SCHEDULE_BEFORE_RESOURCE_REMOVAL", + "DECODER_BUFFER_DELAY_INCONSISTENT", + "MINIMUM_DECODE_TIME", + "MINIMUM_PRESENTATION_INTERVAL", + "DECODE_DEADLINE", + "DECODER_BUFFER_DELAY_ZERO", + "DECODER_BUFFER_DELAY_TOO_LARGE", + "MAX_PICTURE_SIZE", + "MAX_HORIZONTAL_SIZE", + "MAX_VERTICAL_SIZE", + "MIN_HORIZONTAL_SIZE", + "MIN_VERTICAL_SIZE", + "MAX_TILES", + "MAX_TILE_COLUMNS", + "MAX_TILE_WIDTH", + "MIN_TILE_WIDTH", + "MAX_TILE_AREA", + "MAX_DISPLAY_RATE", + "MAX_HEADER_RATE", + "MAX_REFERENCE_FRAMES", + "FRAME_DECODE_RATE", + "FRAME_TILE_RATE", + "MAX_COMPRESSED_SIZE", + "MAX_FRAME_SYMBOLS", + "TILE_SIZE_HEADER_RATE", + }; + if (code > AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) { + return "UNKNOWN"; + } + return names[code]; +} diff --git a/av2/common/decoder_model.h b/av2/common/decoder_model.h new file mode 100644 index 0000000000..bcd26cb292 --- /dev/null +++ b/av2/common/decoder_model.h @@ -0,0 +1,458 @@ +/* + * 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_COMMON_DECODER_MODEL_H_ +#define AVM_AV2_COMMON_DECODER_MODEL_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define AV2_DM_MAX_REF_FRAMES 16 +#define AV2_DM_MAX_BUFFER_POOL_SIZE (AV2_DM_MAX_REF_FRAMES + 2) + +// A portable unsigned 256-bit value, stored least-significant limb first. The +// decoder model never exposes a +// compiler-specific wide-integer type through its internal C interface. +typedef struct Av2DmUnsignedWide { + uint64_t limbs[4]; +} Av2DmUnsignedWide; + +// Exact signed rational used for all normative decoder-model decisions. +// denominator is positive. Zero is canonicalized to 0/1 with negative false. +typedef struct Av2DmRational { + Av2DmUnsignedWide magnitude; + Av2DmUnsignedWide denominator; + bool negative; +} Av2DmRational; + +bool av2_dm_rational_make(uint64_t numerator, uint64_t denominator, + Av2DmRational *result); +bool av2_dm_rational_make_wide(Av2DmUnsignedWide numerator, + uint64_t denominator, bool negative, + Av2DmRational *result); +bool av2_dm_rational_add(const Av2DmRational *left, const Av2DmRational *right, + Av2DmRational *result); +bool av2_dm_rational_subtract(const Av2DmRational *left, + const Av2DmRational *right, + Av2DmRational *result); +bool av2_dm_rational_multiply_u64(const Av2DmRational *value, + uint64_t multiplier, Av2DmRational *result); +bool av2_dm_rational_divide_u64(const Av2DmRational *value, uint64_t divisor, + Av2DmRational *result); +bool av2_dm_rational_compare(const Av2DmRational *left, + const Av2DmRational *right, int *comparison); +bool av2_dm_rational_rebase(Av2DmRational *values, uint32_t value_count, + const Av2DmRational *origin); +bool av2_dm_rational_is_zero(const Av2DmRational *value); + +typedef struct Av2DmBuffer { + uint32_t decoder_ref_count; + uint32_t player_ref_count; + int32_t display_index; + bool presentation_time_valid; + Av2DmRational presentation_time; + bool generation_valid; + uint64_t generation; + bool decode_completion_time_valid; + Av2DmRational decode_completion_time; + uint64_t decode_order; + uint64_t rap_epoch; + bool random_access_point; + uint64_t coded_temporal_unit_index; + bool coded_temporal_unit_valid; +} Av2DmBuffer; + +typedef struct Av2DmBufferPool { + uint32_t num_ref_frames; + uint32_t pool_size; + int32_t vbi[AV2_DM_MAX_REF_FRAMES]; + Av2DmBuffer buffers[AV2_DM_MAX_BUFFER_POOL_SIZE]; +} Av2DmBufferPool; + +bool av2_dm_buffer_pool_initialize(Av2DmBufferPool *pool, + uint32_t num_ref_frames); +int32_t av2_dm_buffer_pool_get_free_buffer(const Av2DmBufferPool *pool); +bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index); +bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, + uint32_t buffer_index); +bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, + uint32_t buffer_index); +bool av2_dm_buffer_pool_add_player_ref(Av2DmBufferPool *pool, + uint32_t buffer_index); +bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, + uint32_t buffer_index); +bool av2_dm_buffer_pool_set_vbi(Av2DmBufferPool *pool, uint32_t ref_index, + int32_t buffer_index); +uint32_t av2_dm_buffer_pool_frames_in_use(const Av2DmBufferPool *pool); + +typedef struct Av2DecoderModel Av2DecoderModel; + +typedef enum Av2DmMode { + AV2_DM_RESOURCE_AVAILABILITY_MODE, + AV2_DM_DECODING_SCHEDULE_MODE +} Av2DmMode; + +typedef enum Av2DmApplicability { + AV2_DM_APPLICABLE, + AV2_DM_NOT_APPLICABLE, + AV2_DM_MISSING_REQUIRED_INPUT +} Av2DmApplicability; + +typedef enum Av2DmResultStatus { + AV2_DM_RESULT_CONFORMANT, + AV2_DM_RESULT_NON_CONFORMANT, + AV2_DM_RESULT_INDETERMINATE, + AV2_DM_RESULT_NOT_APPLICABLE +} Av2DmResultStatus; + +typedef enum Av2DmViolationCode { + AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE, + AV2_DM_VIOLATION_DECODE_EXISTING_FRAME_BUFFER_EMPTY, + AV2_DM_VIOLATION_DISPLAY_FRAME_LATE, + AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW, + AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW, + AV2_DM_VIOLATION_PRESENTATION_TIME_DECREASE, + AV2_DM_VIOLATION_SCHEDULE_BEFORE_RESOURCE_REMOVAL, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT, + AV2_DM_VIOLATION_MINIMUM_DECODE_TIME, + AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL, + AV2_DM_VIOLATION_DECODE_DEADLINE, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_TOO_LARGE, + AV2_DM_VIOLATION_MAX_PICTURE_SIZE, + AV2_DM_VIOLATION_MAX_HORIZONTAL_SIZE, + AV2_DM_VIOLATION_MAX_VERTICAL_SIZE, + AV2_DM_VIOLATION_MIN_HORIZONTAL_SIZE, + AV2_DM_VIOLATION_MIN_VERTICAL_SIZE, + AV2_DM_VIOLATION_MAX_TILES, + AV2_DM_VIOLATION_MAX_TILE_COLUMNS, + AV2_DM_VIOLATION_MAX_TILE_WIDTH, + AV2_DM_VIOLATION_MIN_TILE_WIDTH, + AV2_DM_VIOLATION_MAX_TILE_AREA, + AV2_DM_VIOLATION_MAX_DISPLAY_RATE, + AV2_DM_VIOLATION_MAX_HEADER_RATE, + AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES, + AV2_DM_VIOLATION_FRAME_DECODE_RATE, + AV2_DM_VIOLATION_FRAME_TILE_RATE, + AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE, + AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS, + AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE +} Av2DmViolationCode; + +typedef enum Av2DmViolationAffectedKind { + AV2_DM_VIOLATION_AFFECTED_EVENT, + AV2_DM_VIOLATION_AFFECTED_DFG, + AV2_DM_VIOLATION_AFFECTED_OUTPUT, + AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT +} Av2DmViolationAffectedKind; + +typedef enum Av2DmViolationDetailKind { + AV2_DM_VIOLATION_DETAIL_NONE, + AV2_DM_VIOLATION_DETAIL_BUFFER_POOL, + AV2_DM_VIOLATION_DETAIL_REFERENCE_SLOT, + AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY, + AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME, + AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL +} Av2DmViolationDetailKind; + +typedef struct Av2DmBufferPoolViolationDetail { + bool resource_lane; + uint32_t pool_size; + uint32_t frames_in_use; + uint32_t free_buffers; + uint32_t decoder_held_buffers; + uint32_t player_held_buffers; +} Av2DmBufferPoolViolationDetail; + +typedef struct Av2DmReferenceSlotViolationDetail { + int32_t requested_slot; + bool slot_in_range; + bool reference_valid; + int32_t buffer_index; + Av2DmBufferPoolViolationDetail pool; +} Av2DmReferenceSlotViolationDetail; + +typedef struct Av2DmDelayConsistencyViolationDetail { + uint32_t decoder_buffer_delay_ticks; + bool ceil_time_delta_present; + Av2DmRational ceil_time_delta_ticks; +} Av2DmDelayConsistencyViolationDetail; + +typedef struct Av2DmMinimumDecodeTimeViolationDetail { + Av2DmRational frame_decode_time; + Av2DmRational one_header_time; +} Av2DmMinimumDecodeTimeViolationDetail; + +typedef struct Av2DmViolationDetail { + Av2DmViolationDetailKind kind; + union { + Av2DmBufferPoolViolationDetail buffer_pool; + Av2DmReferenceSlotViolationDetail reference_slot; + Av2DmDelayConsistencyViolationDetail delay_consistency; + Av2DmMinimumDecodeTimeViolationDetail minimum_decode_time; + Av2DmRational frame_interval; + } value; +} Av2DmViolationDetail; + +typedef struct Av2DmScope { + int32_t xlayer_id; + // Xlayer carrying the OPS syntax. This is -1 for a whole-xlayer model and + // GLOBAL_XLAYER_ID for an operating point from a global OPS. + int32_t ops_xlayer_id; + int32_t ops_id; + int32_t operating_point; + bool whole_xlayer; +} Av2DmScope; + +typedef struct Av2DmLevelLimits { + uint64_t max_picture_size; + uint32_t max_horizontal_size; + uint32_t max_vertical_size; + uint64_t max_display_rate; + uint64_t max_decode_rate; + uint32_t max_header_rate; + uint32_t max_tiles; + uint32_t max_tile_columns; + uint64_t max_tile_width; + uint64_t max_tile_area; + uint64_t max_tile_size_header_rate_product; + uint32_t picture_size_profile_factor; + uint32_t min_compression_basis; + Av2DmRational bit_rate; + Av2DmRational buffer_size; +} Av2DmLevelLimits; + +typedef struct Av2DmRasSeed { + uint32_t ref_index; + uint64_t generation; +} Av2DmRasSeed; + +typedef struct Av2DmConfig { + Av2DmScope scope; + Av2DmMode mode; + Av2DmApplicability applicability; + uint32_t level_idx; + uint32_t tier; + uint32_t profile; + uint32_t num_ref_frames; + uint32_t max_frame_width; + uint32_t max_frame_height; + uint32_t max_mlayer_id; + bool still_picture; + bool explicit_num_ref_frames; + + bool timing_info_present; + uint32_t num_units_in_display_tick; + uint32_t time_scale; + uint32_t num_units_in_decoding_tick; + bool equal_picture_interval; + uint32_t ticks_per_picture; + uint32_t initial_display_delay; + + bool sequence_parameters_present; + uint32_t sequence_decoder_buffer_delay; + uint32_t sequence_encoder_buffer_delay; + bool sequence_low_delay_mode; + bool operating_point_parameters_present; + uint32_t operating_point_decoder_buffer_delay; + uint32_t operating_point_encoder_buffer_delay; + bool operating_point_low_delay_mode; + + bool level_limits_present; + Av2DmLevelLimits level_limits; + + bool ras_start; + bool ras_seed_complete; + uint32_t ras_seed_count; + Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; + + // Zero selects the implementation default. Tests may request a smaller + // interval to exercise periodic rebasing without a huge input. + uint32_t rebase_interval_events; + + // Temporary Commit-7 differential oracle. Decoder contexts leave this + // false; tests may defer the checks moved online until finish(). + bool defer_nonterminal_checks_for_testing; + + // Decoder fatal mode stops this model after its first proven violation. + bool stop_after_first_violation; +} Av2DmConfig; + +typedef struct Av2DmFrameEvent { + uint64_t event_index; + uint64_t temporal_unit_index; + uint32_t ref_valid_mask; + bool temporal_unit_output_time_present; + Av2DmRational temporal_unit_output_time; + uint64_t generation; + uint64_t coded_bits; + bool show_existing_frame; + bool random_access_point; + bool coded_as_closed_loop_key; + bool frame_is_intra; + bool allow_global_intrabc; + bool inloop_filtering_enabled; + uint32_t frame_width; + uint32_t frame_height; + uint32_t num_tiles; + uint32_t tile_columns; + uint64_t max_tile_width; + uint64_t max_tile_area; + bool non_rightmost_tile_width_valid; + bool buffer_removal_time_present; + uint32_t buffer_removal_time; + bool decoder_model_parameters_updated; + bool count_frame_header; + uint64_t compressed_size_bytes; + uint64_t frame_symbol_count; +} Av2DmFrameEvent; + +typedef struct Av2DmReferenceUpdateEvent { + uint32_t refresh_frame_flags; + uint32_t ref_valid_mask; +} Av2DmReferenceUpdateEvent; + +typedef struct Av2DmOutputEvent { + uint64_t event_index; + uint64_t temporal_unit_index; + uint64_t generation; + int32_t frame_to_show_map_idx; + uint32_t ref_valid_mask; + uint64_t output_luma_samples; + bool leading_frame; + bool presentation_uses_current_frame; + bool presentation_random_access_point; + bool presentation_time_present; + uint64_t presentation_time_ticks; + bool presentation_base_offset_present; + Av2DmRational presentation_base_offset; +} Av2DmOutputEvent; + +typedef struct Av2DmViolation { + Av2DmViolationCode code; + Av2DmScope scope; + uint64_t event_index; + Av2DmViolationAffectedKind affected_kind; + uint64_t affected_index; + bool observed_present; + Av2DmRational observed; + bool limit_present; + Av2DmRational limit; + Av2DmViolationDetail detail; +} Av2DmViolation; + +typedef struct Av2DmResult { + Av2DmResultStatus status; + Av2DmApplicability applicability; + Av2DmMode mode; + Av2DmScope scope; + uint64_t decoded_frames; + uint64_t output_frames; + uint64_t reordered_outputs; + uint64_t violations; + bool arithmetic_failed; + bool missing_required_input; + bool finished; +} Av2DmResult; + +typedef struct Av2DmState { + Av2DmRational time; + bool last_dfg_valid; + Av2DmRational first_bit_arrival; + Av2DmRational last_bit_arrival; + Av2DmRational scheduled_removal; + Av2DmRational removal; + Av2DmRational time_to_decode; + Av2DmRational decode_completion; + bool last_presentation_valid; + Av2DmRational last_presentation; + bool last_presentation_offset_valid; + Av2DmRational last_presentation_offset; + bool last_output_temporal_unit_valid; + uint64_t last_output_temporal_unit; + bool last_temporal_unit_output_time_valid; + Av2DmRational last_temporal_unit_output_time; + uint64_t last_temporal_unit_output_luma_samples; + uint32_t last_temporal_unit_output_frames; + bool initial_presentation_delay_known; + Av2DmRational initial_presentation_delay; + int32_t current_buffer_index; + uint64_t frame_number; + uint64_t dfg_number; + uint64_t shown_frame_number; + Av2DmBufferPool buffer_pool; +} Av2DmState; + +// Private verifier-storage instrumentation used by decoder-model tests. These +// counters describe live normative state, not allocated capacity or lifetime +// event totals, and do not affect conformance decisions. +typedef struct Av2DmStorageStats { + uint32_t active_dfgs; + uint32_t high_water_dfgs; + uint32_t active_outputs; + uint32_t high_water_outputs; + uint32_t active_tus; + uint32_t high_water_tus; + uint32_t active_generations; + uint32_t high_water_generations; + uint32_t active_cvs; + uint32_t high_water_cvs; + uint32_t active_rap_runs; + uint32_t high_water_rap_runs; +} Av2DmStorageStats; + +typedef void (*Av2DmReportFn)(void *opaque, const Av2DmViolation *violation); + +bool av2_dm_get_level_limits(uint32_t level_idx, uint32_t tier, + uint32_t profile, Av2DmLevelLimits *limits); +bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, + uint32_t profile, uint32_t scale_numerator, + uint32_t scale_denominator, + Av2DmLevelLimits *limits); + +Av2DecoderModel *av2_decoder_model_create(const Av2DmConfig *config, + Av2DmReportFn report, + void *report_opaque); +void av2_decoder_model_destroy(Av2DecoderModel *model); +bool av2_decoder_model_update_parameters(Av2DecoderModel *model, + const Av2DmConfig *config, + uint64_t event_index); +void av2_decoder_model_mark_incomplete(Av2DecoderModel *model); +void av2_decoder_model_fail_arithmetic_for_testing(Av2DecoderModel *model); +void av2_decoder_model_start_frame(Av2DecoderModel *model, + const Av2DmFrameEvent *event); +void av2_decoder_model_update_reference_buffers( + Av2DecoderModel *model, const Av2DmReferenceUpdateEvent *event); +void av2_decoder_model_invalidate_olk_reference_buffers( + Av2DecoderModel *model, uint32_t ref_valid_mask); +void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, + uint64_t event_index); +void av2_decoder_model_output_frame(Av2DecoderModel *model, + const Av2DmOutputEvent *event); +void av2_decoder_model_finish(Av2DecoderModel *model); +bool av2_decoder_model_get_result(const Av2DecoderModel *model, + Av2DmResult *result); +bool av2_decoder_model_get_state(const Av2DecoderModel *model, + Av2DmState *state); +bool av2_decoder_model_get_storage_stats(const Av2DecoderModel *model, + Av2DmStorageStats *stats); +const char *av2_dm_violation_code_name(Av2DmViolationCode code); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // AVM_AV2_COMMON_DECODER_MODEL_H_ diff --git a/av2/common/level.c b/av2/common/level.c index 2c33bc62d3..06b3b8c8f4 100644 --- a/av2/common/level.c +++ b/av2/common/level.c @@ -10,9 +10,15 @@ * aomedia.org/license/patent-license/. */ +#include +#include +#include + +#include "avm/avm_integer.h" #include "avm_ports/system_state.h" #include "av2/common/level.h" #include "av2/common/tile_common.h" +#include "av2/common/timing.h" #include "av2/encoder/encoder.h" #include "av2/common/annexA.h" @@ -430,6 +436,50 @@ static const AV2SubstreamLevelSpec av2_substream_level_defs[15] = { .max_header_rate_x = 132 }, }; +int av2_get_level_compression_basis(int level_index, int tier, + uint32_t *compression_basis) { + if (compression_basis == NULL || level_index < 0 || + level_index >= SEQ_LEVELS || tier < 0 || tier > 1) { + return 0; + } + const AV2LevelSpec *const level_spec = &av2_level_defs[level_index]; + const double basis = tier == 0 ? level_spec->main_cr : level_spec->high_cr; + if (basis <= 0 || basis > UINT32_MAX) return 0; + const uint32_t integer_basis = (uint32_t)basis; + if ((double)integer_basis != basis) return 0; + *compression_basis = integer_basis; + return 1; +} + +int av2_get_substream_level_spec(int level_index, uint32_t scale_numerator, + uint32_t scale_denominator, + AV2SubstreamLevelSpec *level_spec) { + if (level_spec == NULL || level_index < SEQ_LEVEL_4_0 || + level_index >= SEQ_LEVELS) { + return 0; + } + int scale_index; + if (scale_numerator == 3 && scale_denominator == 2) { + scale_index = 0; + } else if (scale_numerator == 4 && scale_denominator == 1) { + scale_index = 1; + } else if (scale_numerator == 9 && scale_denominator == 1) { + scale_index = 2; + } else { + return 0; + } + const int level_group = level_index < SEQ_LEVEL_5_0 + ? 0 + : ((level_index - SEQ_LEVEL_5_0) >> 2) + 1; + const int index = 3 * level_group + scale_index; + if (index < 0 || index >= (int)(sizeof(av2_substream_level_defs) / + sizeof(av2_substream_level_defs[0]))) { + return 0; + } + *level_spec = av2_substream_level_defs[index]; + return 1; +} + typedef enum { LUMA_PIC_SIZE_TOO_LARGE, LUMA_PIC_H_SIZE_TOO_LARGE, @@ -452,6 +502,7 @@ typedef enum { TILE_SIZE_HEADER_RATE_TOO_HIGH, BITRATE_TOO_HIGH, DECODER_MODEL_FAIL, + DECODER_MODEL_UNAVAILABLE, REF_FRAMES_FAIL, PRESENTATION_INTERVAL_TOO_SMALL, @@ -481,14 +532,11 @@ static const char *level_fail_messages[TARGET_LEVEL_FAIL_IDS] = { "The product of max tile size and header rate is too high.", "The bitrate is too high.", "The decoder model fails.", + "The decoder model is unable to verify the target level.", "The number of reference frames is invalid.", "The presentation interval is too small.", }; -static const double bitrate_profile_factor_table[] = { 1.0, 1.667, 2.5, 3.0 }; -static const double picture_size_profile_factor_table[] = { 15.0, 20.0, 30.0, - 36.0 }; - static const char level_string[SEQ_LEVEL_MAX + 1][9] = { "2.0", "2.1", "3.0", "3.1", "4.0", "4.1", "5.0", "5.1", "5.2", "5.3", "6.0", "6.1", @@ -498,63 +546,94 @@ static const char level_string[SEQ_LEVEL_MAX + 1][9] = { "reserved", "31" }; +static bool get_multistream_scale(double multistream_scaling_x, + uint32_t *scale_numerator, + uint32_t *scale_denominator) { + if (scale_numerator == NULL || scale_denominator == NULL) return false; + *scale_numerator = 1; + *scale_denominator = 1; + if (multistream_scaling_x == 0.0 || multistream_scaling_x == 1.0) { + // No scaling. + } else if (multistream_scaling_x == 1.5) { + *scale_numerator = 3; + *scale_denominator = 2; + } else if (multistream_scaling_x == 4.0) { + *scale_numerator = 4; + } else if (multistream_scaling_x == 9.0) { + *scale_numerator = 9; + } else { + return false; + } + return true; +} + +static bool get_max_bitrate_rational(const AV2LevelSpec *const level_spec, + int tier, BITSTREAM_PROFILE profile, + double multistream_scaling_x, + Av2DmRational *bit_rate) { + if (bit_rate == NULL) return false; + if (profile == CONFIGURABLE) return false; + if (level_spec->level < SEQ_LEVEL_4_0 && tier != 0) return false; + const int64_t base_rate = + av2_max_level_bitrate(profile, level_spec->level, tier); + if (base_rate <= 0) return false; + uint32_t scale_numerator; + uint32_t scale_denominator; + if (!get_multistream_scale(multistream_scaling_x, &scale_numerator, + &scale_denominator)) { + return false; + } + // Annex A does not define multistream substream limits below level 4.0. + if (scale_numerator != scale_denominator && + level_spec->level < SEQ_LEVEL_4_0) { + return false; + } + Av2DmRational base; + Av2DmRational scaled; + return av2_dm_rational_make((uint64_t)base_rate, 1, &base) && + av2_dm_rational_multiply_u64(&base, scale_denominator, &scaled) && + av2_dm_rational_divide_u64(&scaled, scale_numerator, bit_rate); +} + +static long double unsigned_wide_to_long_double( + const Av2DmUnsignedWide *value) { + long double result = 0.0L; + for (int i = 3; i >= 0; --i) { + result = ldexpl(result, 64) + value->limbs[i]; + } + return result; +} + +static bool rational_to_long_double(const Av2DmRational *value, + long double *result) { + if (value == NULL || result == NULL || value->negative) return false; + const long double denominator = + unsigned_wide_to_long_double(&value->denominator); + if (!(denominator > 0.0L)) return false; + *result = unsigned_wide_to_long_double(&value->magnitude) / denominator; + return isfinite(*result); +} + +static bool rational_to_double(const Av2DmRational *value, double *result) { + long double converted; + if (result == NULL || !rational_to_long_double(value, &converted) || + converted > DBL_MAX) { + return false; + } + *result = (double)converted; + return isfinite(*result); +} + static double get_max_bitrate(const AV2LevelSpec *const level_spec, int tier, BITSTREAM_PROFILE profile, - double multistream_scalling_x) { - if (level_spec->level < SEQ_LEVEL_4_0) tier = 0; - const double scale = - multistream_scalling_x == 0.0 ? 1 : multistream_scalling_x; - const double bitrate_basis = - (tier ? level_spec->high_mbps / scale : level_spec->main_mbps / scale) * - 1e6; - const int profile_factor_row = get_profile_factor_table_row_index(profile); - double bitrate_profile_factor = - bitrate_profile_factor_table[profile_factor_row]; - return bitrate_basis * bitrate_profile_factor; -} - -// Returns maximum allowed compressed size in bytes. -static double get_max_compressed_size(const AV2LevelSpec *const level_spec, - int tier, BITSTREAM_PROFILE profile, - int luma_sample_count, - double frame_parsing_time) { - if (level_spec->level < SEQ_LEVEL_4_0) tier = 0; - const double min_comp_basis = - (tier ? level_spec->high_cr : level_spec->main_cr); - - const int profile_factor_row = get_profile_factor_table_row_index(profile); - double picture_size_profile_factor = - picture_size_profile_factor_table[profile_factor_row]; - - double max_compressed_size = - ((long long)(frame_parsing_time * level_spec->max_decode_rate * - picture_size_profile_factor) >> - 3) / - min_comp_basis; - - double limit = - ((long long)(luma_sample_count * picture_size_profile_factor) >> 3) * - 1.25; - - return AVMMIN(limit, max_compressed_size); -} - -static double get_max_frame_symbol_count(const AV2LevelSpec *const level_spec, - int tier, BITSTREAM_PROFILE profile, - double frame_parsing_time, - double multi_stream_scaling_x) { - if (level_spec->level < SEQ_LEVEL_4_0) tier = 0; - const double min_comp_basis = - (tier ? level_spec->high_cr : level_spec->main_cr); - - const int profile_factor_row = get_profile_factor_table_row_index(profile); - double picture_size_profile_factor = - picture_size_profile_factor_table[profile_factor_row]; - double scale = multi_stream_scaling_x == 0 ? 1 : multi_stream_scaling_x; - double max_frame_symbol_count = - frame_parsing_time * (level_spec->max_decode_rate / scale) * - picture_size_profile_factor * (8 / (9 * min_comp_basis) + 1 / 48); - return max_frame_symbol_count; + double multistream_scaling_x) { + Av2DmRational bit_rate; + double result; + return get_max_bitrate_rational(level_spec, tier, profile, + multistream_scaling_x, &bit_rate) && + rational_to_double(&bit_rate, &result) + ? result + : 0.0; } double av2_get_max_bitrate_for_level(AV2_LEVEL level_index, int tier, @@ -577,6 +656,209 @@ void av2_get_max_tiles_for_level(AV2_LEVEL level_index, int *const max_tiles, // So INVALID_TIME can be defined as anything less than 0. #define INVALID_TIME (-1.0) +bool av2_encoder_decoder_model_reserve_dfg_intervals( + DECODER_MODEL *decoder_model, size_t interval_count) { + if (decoder_model == NULL) return false; + DFG_INTERVAL_QUEUE *const queue = &decoder_model->dfg_interval_queue; + if (queue->size > queue->capacity || + (queue->size > 0 && (queue->buf == NULL || queue->capacity == 0 || + queue->head >= queue->capacity))) { + return false; + } + if (interval_count <= queue->capacity) return true; + if (interval_count > SIZE_MAX / sizeof(*queue->buf)) return false; + + size_t new_capacity = queue->capacity == 0 ? 64 : queue->capacity; + while (new_capacity < interval_count) { + if (new_capacity > SIZE_MAX / 2) { + new_capacity = interval_count; + break; + } + new_capacity *= 2; + } + if (new_capacity > SIZE_MAX / sizeof(*queue->buf)) return false; + + DFG_INTERVAL *const replacement = + (DFG_INTERVAL *)avm_malloc(new_capacity * sizeof(*replacement)); + if (replacement == NULL) return false; + for (size_t i = 0; i < queue->size; ++i) { + replacement[i] = queue->buf[(queue->head + i) % queue->capacity]; + } + avm_free(queue->buf); + queue->buf = replacement; + queue->head = 0; + queue->capacity = new_capacity; + return true; +} + +bool av2_encoder_decoder_model_push_dfg_interval(DECODER_MODEL *decoder_model, + const DFG_INTERVAL *interval) { + if (decoder_model == NULL || interval == NULL) return false; + DFG_INTERVAL_QUEUE *const queue = &decoder_model->dfg_interval_queue; + const double duration = + interval->last_bit_arrival_time - interval->first_bit_arrival_time; + if (!isfinite(interval->first_bit_arrival_time) || + !isfinite(interval->last_bit_arrival_time) || + !isfinite(interval->removal_time) || !isfinite(duration) || + duration < 0.0 || !isfinite(queue->total_interval) || + duration > DBL_MAX - queue->total_interval || + interval->coded_bits > UINT64_MAX - queue->total_bits) { + return false; + } + if (queue->size == SIZE_MAX || + !av2_encoder_decoder_model_reserve_dfg_intervals(decoder_model, + queue->size + 1)) { + return false; + } + const size_t queue_index = (queue->head + queue->size) % queue->capacity; + queue->buf[queue_index] = *interval; + ++queue->size; + queue->total_interval += duration; + queue->total_bits += interval->coded_bits; + return true; +} + +bool av2_encoder_decoder_model_smoothing_buffer_fits( + const DECODER_MODEL *decoder_model, uint64_t coded_bits, bool *fits) { + if (decoder_model == NULL || fits == NULL) return false; + Av2DmRational fullness; + int comparison; + if (!av2_dm_rational_make(coded_bits, 1, &fullness) || + !av2_dm_rational_compare(&fullness, &decoder_model->buffer_size, + &comparison)) { + return false; + } + *fits = comparison <= 0; + return true; +} + +bool av2_encoder_decoder_model_arrival_fits(const DECODER_MODEL *decoder_model, + uint64_t coded_bits, + double available_duration, + bool *fits) { + if (decoder_model == NULL || fits == NULL || !isfinite(available_duration)) { + return false; + } + long double bit_rate; + if (!rational_to_long_double(&decoder_model->bit_rate, &bit_rate) || + !(bit_rate > 0.0L)) { + return false; + } + const long double available_bits = (long double)available_duration * bit_rate; + if (!isfinite(available_bits)) return false; + *fits = available_bits >= 0.0L && coded_bits <= available_bits; + return true; +} + +static bool smoothing_buffer_fits_with_partial_arrival( + const DECODER_MODEL *decoder_model, uint64_t queued_bits, + uint64_t current_dfg_bits, double partial_arrival_duration, bool *fits) { + if (decoder_model == NULL || fits == NULL || + !isfinite(partial_arrival_duration)) { + return false; + } + long double bit_rate; + long double buffer_size; + if (!rational_to_long_double(&decoder_model->bit_rate, &bit_rate) || + !rational_to_long_double(&decoder_model->buffer_size, &buffer_size) || + !(bit_rate > 0.0L)) { + return false; + } + const long double duration = + partial_arrival_duration > 0.0 ? partial_arrival_duration : 0.0L; + long double partial_bits = duration * bit_rate; + if (!isfinite(partial_bits)) return false; + if (partial_bits > current_dfg_bits) partial_bits = current_dfg_bits; + const long double fullness = queued_bits + partial_bits; + if (!isfinite(fullness)) return false; + *fits = fullness <= buffer_size; + return true; +} + +bool av2_encoder_decoder_model_count_obu_bytes( + const uint8_t *data, size_t data_size, uint64_t *dfg_bytes, + uint64_t *frame_compressed_bytes) { + if (dfg_bytes == NULL || frame_compressed_bytes == NULL || + (data == NULL && data_size != 0)) { + return false; + } + uint64_t compressed_bytes = 0; + size_t offset = 0; + while (offset < data_size) { + const size_t remaining = data_size - offset; + const size_t header_size = (data[offset] & 0x80) != 0 ? 2 : 1; + if (remaining <= header_size) return false; + const OBU_TYPE type = (OBU_TYPE)((data[offset] >> 2) & 0x1f); + uint64_t payload_size; + size_t length_field_size; + if (avm_uleb_decode(data + offset + header_size, remaining - header_size, + &payload_size, &length_field_size) != 0 || + payload_size > SIZE_MAX - header_size - length_field_size) { + return false; + } + const size_t obu_size = + header_size + length_field_size + (size_t)payload_size; + if (obu_size > remaining) return false; + if (av2_obu_counts_toward_compressed_size(type)) { + if ((uint64_t)obu_size > UINT64_MAX - compressed_bytes) return false; + compressed_bytes += (uint64_t)obu_size; + } + offset += obu_size; + } + *dfg_bytes = (uint64_t)data_size; + *frame_compressed_bytes = compressed_bytes; + return true; +} + +bool av2_encoder_decoder_model_accumulate_dfg_bits(DECODER_MODEL *decoder_model, + uint64_t frame_unit_bits, + bool closes_dfg, + uint64_t *closed_dfg_bits) { + if (decoder_model == NULL || closed_dfg_bits == NULL || + frame_unit_bits > UINT64_MAX - decoder_model->coded_bits) { + return false; + } + decoder_model->coded_bits += frame_unit_bits; + *closed_dfg_bits = 0; + if (closes_dfg) { + *closed_dfg_bits = decoder_model->coded_bits; + decoder_model->coded_bits = 0; + } + return true; +} + +bool av2_encoder_decoder_model_get_compressed_size( + uint64_t frame_compressed_bytes, int64_t *compressed_size) { + if (compressed_size == NULL || + frame_compressed_bytes > (uint64_t)INT64_MAX + 128) { + return false; + } + if (frame_compressed_bytes >= 128) { + *compressed_size = (int64_t)(frame_compressed_bytes - 128); + } else { + *compressed_size = -(int64_t)(128 - frame_compressed_bytes); + } + return true; +} + +void av2_encoder_decoder_model_destroy(DECODER_MODEL *decoder_model) { + if (decoder_model == NULL) return; + avm_free(decoder_model->dfg_interval_queue.buf); + decoder_model->dfg_interval_queue.buf = NULL; + decoder_model->dfg_interval_queue.head = 0; + decoder_model->dfg_interval_queue.size = 0; + decoder_model->dfg_interval_queue.capacity = 0; + decoder_model->dfg_interval_queue.total_interval = 0.0; + decoder_model->dfg_interval_queue.total_bits = 0; +} + +void av2_encoder_decoder_models_destroy(AV2LevelInfo *level_info) { + if (level_info == NULL) return; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + av2_encoder_decoder_model_destroy(&level_info->decoder_models[level]); + } +} + // This corresponds to "free_buffer" in the spec. static void release_buffer(DECODER_MODEL *const decoder_model, int idx) { assert(idx >= 0 && idx < BUFFER_POOL_MAX_SIZE); @@ -585,6 +867,7 @@ static void release_buffer(DECODER_MODEL *const decoder_model, int idx) { this_buffer->player_ref_count = 0; this_buffer->display_index = -1; this_buffer->presentation_time = INVALID_TIME; + memset(&this_buffer->presentation, 0, sizeof(this_buffer->presentation)); } static void initialize_buffer_pool(DECODER_MODEL *const decoder_model) { @@ -608,24 +891,202 @@ static int get_free_buffer(DECODER_MODEL *const decoder_model) { return -1; } -static void update_ref_buffers(const AV2_COMMON *const cm, - DECODER_MODEL *const decoder_model, - int refresh_frame_flags) { - if (cm->show_existing_frame) - return; // refresh_frame_flags are equal to 0 in show_existing_frame. +static bool release_decoder_reference(DECODER_MODEL *const decoder_model, + int buffer_index) { + if (buffer_index < 0 || buffer_index >= decoder_model->num_ref_frames + 2) { + return false; + } + FRAME_BUFFER *const buffer = &decoder_model->frame_buffer_pool[buffer_index]; + if (buffer->decoder_ref_count == 0) return false; + --buffer->decoder_ref_count; + if (buffer->decoder_ref_count == 0 && buffer->player_ref_count == 0) { + release_buffer(decoder_model, buffer_index); + } + return true; +} + +bool av2_encoder_decoder_model_sync_invalid_ref_buffers( + const AV2_COMMON *const cm, DECODER_MODEL *const decoder_model) { + if (cm == NULL || decoder_model == NULL || + decoder_model->status != DECODER_MODEL_OK) { + return false; + } + if (decoder_model->num_ref_frames < 1 || + decoder_model->num_ref_frames > REF_FRAMES || + cm->seq_params.ref_frames != decoder_model->num_ref_frames) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + for (int i = 0; i < decoder_model->num_ref_frames; ++i) { + if (cm->ref_frame_map[i] == NULL && decoder_model->vbi[i] != -1) { + if (!release_decoder_reference(decoder_model, decoder_model->vbi[i])) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + decoder_model->vbi[i] = -1; + } + } + return true; +} + +static bool capture_presentation_descriptor( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, + uint64_t output_luma_samples, int buffer_index, uint64_t generation, + bool implicit_output_eligible, + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation) { + if (cpi == NULL || decoder_model == NULL || presentation == NULL || + decoder_model->status != DECODER_MODEL_OK) { + return false; + } + const AV2_COMMON *const cm = &cpi->common; + if (cm->cur_frame == NULL || decoder_model->num_frame < 0) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + const uint64_t layer_count = (uint64_t)cm->seq_params.max_mlayer_id + 1; + if (layer_count == 0 || + cm->current_frame.display_order_hint > UINT64_MAX / layer_count) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + const uint64_t base_output_order = + layer_count * cm->current_frame.display_order_hint; + if ((uint64_t)cm->mlayer_id > UINT64_MAX - base_output_order) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + + memset(presentation, 0, sizeof(*presentation)); + presentation->valid = true; + presentation->implicit_output_eligible = implicit_output_eligible; + presentation->restricted = cm->bridge_frame_info.is_bridge_frame + ? cm->cur_frame->is_restricted + : false; + presentation->leading_frame = cm->is_leading_picture == 1; + presentation->random_access_point = + !cm->show_existing_frame && + (cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY || + cm->current_frame.cm_obu_type == OBU_OPEN_LOOP_KEY || + cm->current_frame.cm_obu_type == OBU_RAS_FRAME); + presentation->generation = generation; + presentation->temporal_unit_index = decoder_model->temporal_unit_index; + presentation->output_order = base_output_order + (uint64_t)cm->mlayer_id; + presentation->order_hint = cm->current_frame.display_order_hint; + presentation->output_luma_samples = output_luma_samples; + presentation->presentation_time_present = + cm->ci_params_encoder.ci_timing_info_present_flag && + !cm->ci_params_encoder.timing_info.equal_elemental_interval; + presentation->presentation_time_ticks = + cm->temporal_point_info_metadata.mtpi_frame_presentation_time; + presentation->rap_epoch = decoder_model->rap_epoch; + presentation->decode_completion_time = decoder_model->current_time; + presentation->source_frame_unit_index = decoder_model->num_frame; + presentation->buffer_index = buffer_index; + presentation->xlayer_id = cm->xlayer_id; + presentation->mlayer_id = cm->mlayer_id; + presentation->temporal_id = cm->tlayer_id; + return true; +} + +bool av2_encoder_decoder_model_capture_current_generation( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, + uint64_t output_luma_samples) { + if (cpi == NULL || decoder_model == NULL || + decoder_model->status != DECODER_MODEL_OK || decoder_model->cfbi < 0 || + decoder_model->cfbi >= decoder_model->num_ref_frames + 2 || + decoder_model->next_generation == UINT64_MAX) { + if (decoder_model != NULL && decoder_model->status == DECODER_MODEL_OK) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } + return false; + } + const AV2_COMMON *const cm = &cpi->common; + const bool random_access_point = + cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY || + cm->current_frame.cm_obu_type == OBU_OPEN_LOOP_KEY || + cm->current_frame.cm_obu_type == OBU_RAS_FRAME; + if (random_access_point) { + if (decoder_model->rap_epoch == UINT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + ++decoder_model->rap_epoch; + } + FRAME_BUFFER *const buffer = + &decoder_model->frame_buffer_pool[decoder_model->cfbi]; + if (!capture_presentation_descriptor( + cpi, decoder_model, output_luma_samples, decoder_model->cfbi, + ++decoder_model->next_generation, + cm->cur_frame->implicit_output_picture != 0, &buffer->presentation)) { + return false; + } + decoder_model->current_presentation = buffer->presentation; + return true; +} + +static bool update_ref_buffer(const AV2_COMMON *const cm, + DECODER_MODEL *const decoder_model, int ref_idx) { + if (decoder_model->cfbi < 0 || + decoder_model->cfbi >= decoder_model->num_ref_frames + 2 || ref_idx < 0 || + ref_idx >= decoder_model->num_ref_frames) { + return false; + } + const uint32_t ref_flag = 1u << ref_idx; + if (decoder_model->mirrored_refresh_frame_flags & ref_flag) return true; FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[decoder_model->cfbi]; - for (int i = 0; i < cm->seq_params.ref_frames; ++i) { - if (refresh_frame_flags & (1 << i)) { - const int pre_idx = decoder_model->vbi[i]; - if (pre_idx != -1) { - --decoder_model->frame_buffer_pool[pre_idx].decoder_ref_count; - } - if (cm->ref_frame_map[i] != NULL) { - decoder_model->vbi[i] = decoder_model->cfbi; - ++this_buffer->decoder_ref_count; - } else { - decoder_model->vbi[i] = -1; + const int pre_idx = decoder_model->vbi[ref_idx]; + if (pre_idx != -1 && !release_decoder_reference(decoder_model, pre_idx)) { + return false; + } + if (cm->ref_frame_map[ref_idx] != NULL) { + decoder_model->vbi[ref_idx] = decoder_model->cfbi; + if (this_buffer->decoder_ref_count == UINT32_MAX) return false; + ++this_buffer->decoder_ref_count; + } else { + decoder_model->vbi[ref_idx] = -1; + } + decoder_model->mirrored_refresh_frame_flags |= ref_flag; + return true; +} + +static bool update_ref_buffers(const AV2_COMMON *const cm, + DECODER_MODEL *const decoder_model, + int refresh_frame_flags) { + if (cm->show_existing_frame) return true; + for (int i = 0; i < decoder_model->num_ref_frames; ++i) { + if ((refresh_frame_flags & (1 << i)) && + !update_ref_buffer(cm, decoder_model, i)) { + return false; + } + } + return true; +} + +void av2_decoder_model_mirror_ref_buffer_for_operating_points( + const AV2_COMP *const cpi, int ref_idx) { + if (cpi == NULL || ref_idx < 0 || + ref_idx >= cpi->common.seq_params.ref_frames || + !cpi->level_params.keep_level_stats || is_stat_generation_stage(cpi) || + !((cpi->common.current_frame.refresh_frame_flags >> ref_idx) & 1)) { + return; + } + const AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + for (int op = 0; op < seq_params->operating_points_cnt_minus_1 + 1; ++op) { + if (!((cpi->level_params.keep_level_stats >> op) & 1) || + cpi->level_params.level_info[op] == NULL || + !is_in_operating_point(seq_params->operating_point_idc[op], + cm->tlayer_id, cm->mlayer_id)) { + continue; + } + DECODER_MODEL *const decoder_models = + cpi->level_params.level_info[op]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + DECODER_MODEL *const decoder_model = &decoder_models[level]; + if (decoder_model->status == DECODER_MODEL_OK && + !update_ref_buffer(cm, decoder_model, ref_idx)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; } } } @@ -649,7 +1110,7 @@ bool is_filter_enabled_frame(const AV2_COMMON *const cm) { // The time (in seconds) required to decode a frame. static double time_to_decode_frame(const AV2_COMMON *const cm, - int64_t max_decode_rate) { + double max_decode_rate) { if (cm->show_existing_frame) return 0.0; const FRAME_TYPE frame_type = cm->current_frame.frame_type; int luma_samples = 0; @@ -665,7 +1126,7 @@ static double time_to_decode_frame(const AV2_COMMON *const cm, luma_samples = max_frame_width * max_frame_height; } - return luma_samples / (double)max_decode_rate; + return luma_samples / max_decode_rate; } // Release frame buffers that are no longer needed for decode or display. @@ -675,7 +1136,10 @@ static void release_processed_frames(DECODER_MODEL *const decoder_model, for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->player_ref_count > 0) { - if (this_buffer->presentation_time >= 0.0 && + // Presentation offsets assigned before the initial delay is known are + // not yet absolute PresentationTimes and cannot release a buffer. + if (decoder_model->initial_presentation_delay >= 0.0 && + this_buffer->presentation_time >= 0.0 && this_buffer->presentation_time <= removal_time) { this_buffer->player_ref_count = 0; if (this_buffer->decoder_ref_count == 0) { @@ -699,27 +1163,10 @@ static int frames_in_buffer_pool(const DECODER_MODEL *const decoder_model) { return frames_in_pool; } -static double get_presentation_time(const DECODER_MODEL *const decoder_model, - int display_index) { - if (decoder_model->mode == SCHEDULE_MODE) { - assert(0 && "SCHEDULE_MODE NOT SUPPORTED"); - return INVALID_TIME; - } else { - const double initial_presentation_delay = - decoder_model->initial_presentation_delay; - // Can't decide presentation time until the initial presentation delay is - // known. - if (initial_presentation_delay < 0.0) - return display_index * decoder_model->num_ticks_per_picture * - decoder_model->display_clock_tick; - return initial_presentation_delay + - display_index * decoder_model->num_ticks_per_picture * - decoder_model->display_clock_tick; - } -} - #define MAX_TIME 1e16 -double time_next_buffer_is_free(const DECODER_MODEL *const decoder_model) { +static double time_next_buffer_is_free_with_source( + const DECODER_MODEL *const decoder_model, bool *from_current_time) { + if (from_current_time != NULL) *from_current_time = false; if (decoder_model->num_decoded_frame == 0) { return (double)decoder_model->decoder_buffer_delay / 90000.0; } @@ -730,6 +1177,7 @@ double time_next_buffer_is_free(const DECODER_MODEL *const decoder_model) { &decoder_model->frame_buffer_pool[i]; if (this_buffer->decoder_ref_count == 0) { if (this_buffer->player_ref_count == 0) { + if (from_current_time != NULL) *from_current_time = true; return decoder_model->current_time; } const double presentation_time = this_buffer->presentation_time; @@ -740,31 +1188,38 @@ double time_next_buffer_is_free(const DECODER_MODEL *const decoder_model) { } return buf_free_time < MAX_TIME ? buf_free_time : INVALID_TIME; } + +double time_next_buffer_is_free(const DECODER_MODEL *const decoder_model) { + return time_next_buffer_is_free_with_source(decoder_model, NULL); +} #undef MAX_TIME -static double get_removal_time(const DECODER_MODEL *const decoder_model) { +static double get_removal_time(const DECODER_MODEL *const decoder_model, + bool *from_current_time) { if (decoder_model->mode == SCHEDULE_MODE) { assert(0 && "SCHEDULE_MODE IS NOT SUPPORTED YET"); return INVALID_TIME; } else { - return time_next_buffer_is_free(decoder_model); + return time_next_buffer_is_free_with_source(decoder_model, + from_current_time); } } void av2_decoder_model_print_status(const DECODER_MODEL *const decoder_model) { - printf( - "\n status %d, num_frame %3d, num_decoded_frame %3d, " - "num_shown_frame %3d, current time %6.2f, frames in buffer %2d, " - "presentation delay %6.2f, total interval %6.2f\n", - decoder_model->status, decoder_model->num_frame, - decoder_model->num_decoded_frame, decoder_model->num_shown_frame, - decoder_model->current_time, frames_in_buffer_pool(decoder_model), - decoder_model->initial_presentation_delay, - decoder_model->dfg_interval_queue.total_interval); + printf("\n status %d, num_frame %3" PRId64 ", num_decoded_frame %3" PRId64 + ", num_shown_frame %3" PRId64 + ", current time %6.2f, frames in buffer %2d, " + "presentation delay %6.2f, total interval %6.2f\n", + decoder_model->status, decoder_model->num_frame, + decoder_model->num_decoded_frame, decoder_model->num_shown_frame, + decoder_model->current_time, frames_in_buffer_pool(decoder_model), + decoder_model->initial_presentation_delay, + decoder_model->dfg_interval_queue.total_interval); for (int i = 0; i < 10; ++i) { const FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; - printf("buffer %d, decode count %d, display count %d, present time %6.4f\n", + printf("buffer %d, decode count %" PRIu32 ", display count %" PRIu32 + ", present time %6.4f\n", i, this_buffer->decoder_ref_count, this_buffer->player_ref_count, this_buffer->presentation_time); } @@ -774,14 +1229,42 @@ void av2_decoder_model_print_status(const DECODER_MODEL *const decoder_model) { void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, int op_index, DECODER_MODEL *const decoder_model) { avm_clear_system_state(); + memset(decoder_model, 0, sizeof(*decoder_model)); decoder_model->status = DECODER_MODEL_OK; decoder_model->level = level; + decoder_model->operating_point = op_index; + decoder_model->tier = cpi->tier[op_index]; const AV2_COMMON *const cm = &cpi->common; const SequenceHeader *const seq_params = &cm->seq_params; - decoder_model->bit_rate = get_max_bitrate( - av2_level_defs + level, cpi->tier[op_index], seq_params->seq_profile_idc, - cpi->level_params.multi_stream_scaling_x); + decoder_model->is_still_picture = seq_params->still_picture; + uint32_t scale_numerator; + uint32_t scale_denominator; + Av2DmRational bit_rate; + if (seq_params->seq_profile_idc == CONFIGURABLE || + !get_multistream_scale(cpi->level_params.multi_stream_scaling_x, + &scale_numerator, &scale_denominator) || + !av2_dm_get_level_limits(level, decoder_model->tier, + seq_params->seq_profile_idc, + &decoder_model->level_limits) || + !get_max_bitrate_rational(av2_level_defs + level, decoder_model->tier, + seq_params->seq_profile_idc, + cpi->level_params.multi_stream_scaling_x, + &bit_rate)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->multistream_scale_numerator = scale_numerator; + decoder_model->multistream_scale_denominator = scale_denominator; + decoder_model->bit_rate = bit_rate; + if (!av2_dm_rational_multiply_u64(&decoder_model->level_limits.buffer_size, + scale_denominator, + &decoder_model->buffer_size) || + !av2_dm_rational_divide_u64(&decoder_model->buffer_size, scale_numerator, + &decoder_model->buffer_size)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } // TODO(huisu or anyone): implement SCHEDULE_MODE. decoder_model->mode = RESOURCE_MODE; @@ -798,11 +1281,17 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, decoder_model->decode_samples = 0; decoder_model->display_samples = 0; decoder_model->max_decode_rate = 0.0; + decoder_model->max_decode_rate_satisfy = true; decoder_model->max_tile_rate_satisfy = true; decoder_model->compressed_size_satisfy = true; decoder_model->frame_symbol_count_satisfy = true; decoder_model->max_display_rate = 0.0; decoder_model->num_ref_frames = cm->seq_params.ref_frames; + if (decoder_model->num_ref_frames < 1 || + decoder_model->num_ref_frames > REF_FRAMES) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } decoder_model->num_frames_current_tu = 0; decoder_model->min_presentation_interval_satisfy = true; @@ -819,25 +1308,40 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, DFG_INTERVAL_QUEUE *const dfg_interval_queue = &decoder_model->dfg_interval_queue; dfg_interval_queue->total_interval = 0.0; + dfg_interval_queue->total_bits = 0; dfg_interval_queue->head = 0; dfg_interval_queue->size = 0; + dfg_interval_queue->capacity = 0; + dfg_interval_queue->buf = NULL; if (cm->ci_params_encoder.ci_timing_info_present_flag) { + decoder_model->equal_picture_interval = + cm->ci_params_encoder.timing_info.equal_elemental_interval != 0; decoder_model->num_ticks_per_picture = cm->ci_params_encoder.timing_info.num_ticks_per_elemental_duration; decoder_model->display_clock_tick = - (double) - cm->ci_params_encoder.timing_info.num_ticks_per_elemental_duration / + (double)cm->ci_params_encoder.timing_info.num_units_in_display_tick / cm->ci_params_encoder.timing_info.time_scale; } else { + decoder_model->equal_picture_interval = true; decoder_model->num_ticks_per_picture = 1; decoder_model->display_clock_tick = 1.0 / cpi->framerate; } decoder_model->initial_display_delay = - seq_params->op_params[op_index].initial_display_delay; + seq_params->seq_max_display_model_info_present_flag + ? seq_params->seq_max_initial_display_delay_minus_1 + 1 + : seq_params->ref_frames + 2; decoder_model->initial_presentation_delay = INVALID_TIME; - decoder_model->decode_rate = av2_level_defs[level].max_decode_rate; + decoder_model->decode_rate = + (double)decoder_model->level_limits.max_decode_rate * scale_denominator / + scale_numerator; + if (!(decoder_model->decode_rate > 0.0) || + !isfinite(decoder_model->decode_rate)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->initialized = true; } int av2_get_max_level_ref_frames(const AV2_COMMON *const cm, OBU_TYPE obu_type, @@ -863,81 +1367,475 @@ int av2_get_max_level_ref_frames(const AV2_COMMON *const cm, OBU_TYPE obu_type, return max_level_ref_frames; } +static bool encoder_dm_rational_less_than_or_equal( + const Av2DmRational *observed, const Av2DmRational *limit, bool *result) { + int comparison; + if (!av2_dm_rational_compare(observed, limit, &comparison)) return false; + *result = comparison <= 0; + return true; +} + +// In resource mode, a free buffer often makes the next removal time equal to +// Time after decoding the preceding frame. In that case FrameParsingTime is +// exactly LumaSampleCount / MaxDecodeRate. Evaluate all four Annex A limits by +// rational cross-products rather than reconstructing that interval from two +// rounded double removal times. +static bool check_frame_constraints_at_decode_limit( + DECODER_MODEL *decoder_model, const ENCODER_DECODER_MODEL_FRAME *frame, + uint64_t frame_parsing_time_decode_luma_samples) { + const Av2DmLevelLimits *const limits = &decoder_model->level_limits; + const uint32_t scale_numerator = decoder_model->multistream_scale_numerator; + const uint32_t scale_denominator = + decoder_model->multistream_scale_denominator; + Av2DmRational observed; + Av2DmRational limit; + bool satisfies; + + if (frame_parsing_time_decode_luma_samples == 0) return false; + const long double max_decode_rate = (long double)limits->max_decode_rate * + scale_denominator / scale_numerator; + const long double observed_decode_rate = + max_decode_rate * frame->luma_sample_count / + frame_parsing_time_decode_luma_samples; + if (!isfinite(max_decode_rate) || !isfinite(observed_decode_rate)) { + return false; + } + decoder_model->max_decode_rate = + AVMMAX(decoder_model->max_decode_rate, observed_decode_rate); + decoder_model->max_decode_rate_satisfy &= + frame->luma_sample_count <= frame_parsing_time_decode_luma_samples; + + Av2DmRational dynamic_tile_limit; + Av2DmRational max_tile_limit; + Av2DmRational one; + if (!av2_dm_rational_make(frame_parsing_time_decode_luma_samples, 1, + &dynamic_tile_limit) || + !av2_dm_rational_multiply_u64(&dynamic_tile_limit, + (uint64_t)limits->max_tiles * 120, + &dynamic_tile_limit) || + !av2_dm_rational_divide_u64(&dynamic_tile_limit, limits->max_decode_rate, + &dynamic_tile_limit) || + !av2_dm_rational_make(limits->max_tiles, 1, &max_tile_limit) || + !av2_dm_rational_multiply_u64(&max_tile_limit, scale_denominator, + &max_tile_limit) || + !av2_dm_rational_divide_u64(&max_tile_limit, scale_numerator, + &max_tile_limit) || + !av2_dm_rational_make(1, 1, &one)) { + return false; + } + int comparison; + if (!av2_dm_rational_compare(&dynamic_tile_limit, &one, &comparison)) { + return false; + } + if (comparison < 0) dynamic_tile_limit = one; + if (!av2_dm_rational_compare(&dynamic_tile_limit, &max_tile_limit, + &comparison)) { + return false; + } + if (comparison > 0) dynamic_tile_limit = max_tile_limit; + if (!av2_dm_rational_make(frame->num_tiles, 1, &observed) || + !encoder_dm_rational_less_than_or_equal(&observed, &dynamic_tile_limit, + &satisfies)) { + return false; + } + decoder_model->max_tile_rate_satisfy &= satisfies; + + if (frame->luma_sample_count > + UINT64_MAX / limits->picture_size_profile_factor) { + return false; + } + const uint64_t picture_units = + frame->luma_sample_count * limits->picture_size_profile_factor >> 3; + Av2DmRational picture_compressed_limit; + Av2DmRational rate_compressed_limit; + if (!av2_dm_rational_make(picture_units, 1, &picture_compressed_limit) || + !av2_dm_rational_multiply_u64(&picture_compressed_limit, 5, + &picture_compressed_limit) || + !av2_dm_rational_divide_u64(&picture_compressed_limit, 4, + &picture_compressed_limit) || + !av2_dm_rational_make(frame_parsing_time_decode_luma_samples, 1, + &rate_compressed_limit) || + !av2_dm_rational_multiply_u64(&rate_compressed_limit, + limits->picture_size_profile_factor, + &rate_compressed_limit) || + !av2_dm_rational_divide_u64(&rate_compressed_limit, + (uint64_t)8 * limits->min_compression_basis, + &rate_compressed_limit) || + !av2_dm_rational_compare(&picture_compressed_limit, + &rate_compressed_limit, &comparison)) { + return false; + } + limit = comparison <= 0 ? picture_compressed_limit : rate_compressed_limit; + if (frame->compressed_size > 0) { + if (!av2_dm_rational_make((uint64_t)frame->compressed_size, 1, &observed) || + !encoder_dm_rational_less_than_or_equal(&observed, &limit, + &satisfies)) { + return false; + } + decoder_model->compressed_size_satisfy &= satisfies; + } + + Av2DmRational symbol_factor_a; + Av2DmRational symbol_factor_b; + if (!av2_dm_rational_make(8, (uint64_t)9 * limits->min_compression_basis, + &symbol_factor_a) || + !av2_dm_rational_make(1, 48, &symbol_factor_b) || + !av2_dm_rational_add(&symbol_factor_a, &symbol_factor_b, &limit) || + !av2_dm_rational_multiply_u64( + &limit, frame_parsing_time_decode_luma_samples, &limit) || + !av2_dm_rational_multiply_u64(&limit, limits->picture_size_profile_factor, + &limit) || + !av2_dm_rational_make(frame->frame_symbol_count, 1, &observed) || + !encoder_dm_rational_less_than_or_equal(&observed, &limit, &satisfies)) { + return false; + } + decoder_model->frame_symbol_count_satisfy &= satisfies; + return true; +} + +bool av2_encoder_decoder_model_check_frame_constraints( + DECODER_MODEL *decoder_model, const ENCODER_DECODER_MODEL_FRAME *frame, + double frame_parsing_time, bool frame_parsing_time_at_decode_limit, + uint64_t frame_parsing_time_decode_luma_samples) { + if (decoder_model == NULL || frame == NULL || !frame->valid || + decoder_model->status != DECODER_MODEL_OK || + !(frame_parsing_time > 0.0) || !isfinite(frame_parsing_time)) { + if (decoder_model != NULL && decoder_model->status == DECODER_MODEL_OK) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } + return false; + } + + const Av2DmLevelLimits *const limits = &decoder_model->level_limits; + if (limits->max_decode_rate == 0 || limits->max_tiles == 0 || + limits->picture_size_profile_factor == 0 || + limits->min_compression_basis == 0) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + if (frame_parsing_time_at_decode_limit) { + if (!check_frame_constraints_at_decode_limit( + decoder_model, frame, frame_parsing_time_decode_luma_samples)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + return true; + } + const long double parsing_time = frame_parsing_time; + const long double scale_numerator = + decoder_model->multistream_scale_numerator; + const long double scale_denominator = + decoder_model->multistream_scale_denominator; + if (!(scale_numerator > 0.0L) || !(scale_denominator > 0.0L)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + const long double max_decode_rate = (long double)limits->max_decode_rate * + scale_denominator / scale_numerator; + const long double max_tiles = + (long double)limits->max_tiles * scale_denominator / scale_numerator; + const long double observed_decode_rate = + (long double)frame->luma_sample_count / parsing_time; + if (!isfinite(max_decode_rate) || !isfinite(max_tiles) || + !isfinite(observed_decode_rate)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + decoder_model->max_decode_rate = + AVMMAX(decoder_model->max_decode_rate, observed_decode_rate); + decoder_model->max_decode_rate_satisfy &= + observed_decode_rate <= max_decode_rate; + + const long double dynamic_tile_limit = max_tiles * 120.0L * parsing_time; + const long double tile_limit = + AVMMIN(max_tiles, AVMMAX(1.0L, dynamic_tile_limit)); + if (!isfinite(dynamic_tile_limit) || !isfinite(tile_limit)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + decoder_model->max_tile_rate_satisfy &= + (long double)frame->num_tiles <= tile_limit; + + if (frame->luma_sample_count > + UINT64_MAX / limits->picture_size_profile_factor) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + const uint64_t picture_units = + frame->luma_sample_count * limits->picture_size_profile_factor >> 3; + const long double compressed_limit_from_picture = + (long double)picture_units * 5.0L / 4.0L; + const long double compressed_limit_from_rate = + parsing_time * max_decode_rate * limits->picture_size_profile_factor / + ((long double)8 * limits->min_compression_basis); + const long double compressed_limit = + AVMMIN(compressed_limit_from_picture, compressed_limit_from_rate); + if (!isfinite(compressed_limit_from_picture) || + !isfinite(compressed_limit_from_rate) || !isfinite(compressed_limit)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + decoder_model->compressed_size_satisfy &= + frame->compressed_size <= 0 || + (long double)frame->compressed_size <= compressed_limit; + + const long double symbol_factor = + 8.0L / ((long double)9 * limits->min_compression_basis) + 1.0L / 48.0L; + const long double symbol_limit = parsing_time * max_decode_rate * + limits->picture_size_profile_factor * + symbol_factor; + if (!isfinite(symbol_factor) || !isfinite(symbol_limit)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + decoder_model->frame_symbol_count_satisfy &= + (long double)frame->frame_symbol_count <= symbol_limit; + return true; +} + +bool av2_encoder_decoder_model_store_frame_constraints( + DECODER_MODEL *decoder_model, + const ENCODER_DECODER_MODEL_FRAME *current_frame, + bool previous_frame_parsing_time_at_decode_limit) { + if (decoder_model == NULL || current_frame == NULL || !current_frame->valid || + current_frame->decode_count == 0 || + decoder_model->status != DECODER_MODEL_OK || + !isfinite(current_frame->removal_time)) { + if (decoder_model != NULL && decoder_model->status == DECODER_MODEL_OK) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } + return false; + } + + if (decoder_model->pending_frame.valid) { + const double frame_parsing_time = + (current_frame->removal_time - + decoder_model->pending_frame.removal_time) / + decoder_model->pending_frame.decode_count; + if (!av2_encoder_decoder_model_check_frame_constraints( + decoder_model, &decoder_model->pending_frame, frame_parsing_time, + previous_frame_parsing_time_at_decode_limit, + decoder_model->pending_frame.luma_sample_count)) { + return false; + } + decoder_model->last_frame_parsing_time = frame_parsing_time; + decoder_model->last_frame_parsing_time_valid = true; + decoder_model->last_frame_parsing_time_at_decode_limit = + previous_frame_parsing_time_at_decode_limit; + decoder_model->last_frame_parsing_time_decode_luma_samples = + decoder_model->pending_frame.luma_sample_count; + } + + decoder_model->pending_frame = *current_frame; + decoder_model->frame_constraints_finalized = false; + return true; +} + +void av2_encoder_decoder_model_finalize_frame_constraints( + DECODER_MODEL *decoder_model, bool is_still_picture) { + if (decoder_model == NULL || !decoder_model->initialized || + decoder_model->status != DECODER_MODEL_OK || + decoder_model->frame_constraints_finalized) { + return; + } + decoder_model->frame_constraints_finalized = true; + if (is_still_picture || !decoder_model->pending_frame.valid) return; + if (!decoder_model->last_frame_parsing_time_valid) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return; + } + (void)av2_encoder_decoder_model_check_frame_constraints( + decoder_model, &decoder_model->pending_frame, + decoder_model->last_frame_parsing_time, + decoder_model->last_frame_parsing_time_at_decode_limit, + decoder_model->last_frame_parsing_time_decode_luma_samples); +} + +static bool update_max_display_rate(DECODER_MODEL *decoder_model, + double display_duration) { + if (decoder_model == NULL || !isfinite(display_duration)) return false; + if (display_duration <= 0.0) { + decoder_model->max_display_rate = LDBL_MAX; + return true; + } + const long double display_rate = + (long double)decoder_model->display_samples / display_duration; + if (!isfinite(display_rate)) return false; + decoder_model->max_display_rate = + AVMMAX(decoder_model->max_display_rate, display_rate); + return true; +} + +static bool get_minimum_presentation_interval( + const AV2_COMP *const cpi, const DECODER_MODEL *const decoder_model, + double *const min_interval) { + if (cpi == NULL || decoder_model == NULL || min_interval == NULL || + decoder_model->tier < 0 || decoder_model->tier > 1) { + return false; + } + + const SequenceHeader *const seq_params = &cpi->common.seq_params; + Av2DmLevelLimits limits; + if (!av2_dm_get_level_limits(decoder_model->level, decoder_model->tier, + seq_params->seq_profile_idc, &limits)) { + return false; + } + const uint32_t scale_numerator = decoder_model->multistream_scale_numerator; + const uint32_t scale_denominator = + decoder_model->multistream_scale_denominator; + if (scale_numerator == 0 || scale_denominator == 0 || + (scale_numerator != scale_denominator && + !av2_dm_apply_multistream_limits( + decoder_model->level, decoder_model->tier, + seq_params->seq_profile_idc, scale_numerator, scale_denominator, + &limits))) { + return false; + } + + if (seq_params->max_frame_width <= 0 || seq_params->max_frame_height <= 0 || + decoder_model->num_frames_current_tu == 0 || + limits.max_display_rate == 0 || limits.max_decode_rate == 0 || + limits.max_header_rate == 0) { + return false; + } + const uint64_t max_frame_width = (uint32_t)seq_params->max_frame_width; + const uint64_t max_frame_height = (uint32_t)seq_params->max_frame_height; + if (max_frame_width > UINT64_MAX / max_frame_height) return false; + const uint64_t max_frame_samples = max_frame_width * max_frame_height; + if (max_frame_samples > UINT64_MAX / decoder_model->num_frames_current_tu) { + return false; + } + const uint64_t output_samples = + max_frame_samples * decoder_model->num_frames_current_tu; + + const uint64_t tier_multiplier = 1 + ((uint64_t)decoder_model->tier << 1); + if (limits.max_header_rate > UINT64_MAX / tier_multiplier) return false; + const uint64_t max_frame_headers_per_second = + limits.max_header_rate * tier_multiplier; + + const double sample_interval = + (double)output_samples / (double)limits.max_display_rate; + const double min_frame_time = + (double)limits.max_decode_rate / + ((double)max_frame_headers_per_second * (double)limits.max_display_rate); + *min_interval = AVMMAX(sample_interval, min_frame_time); + return isfinite(sample_interval) && isfinite(min_frame_time) && + isfinite(*min_interval); +} + +void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, + bool is_still_picture) { + if (decoder_model == NULL || !decoder_model->initialized || + decoder_model->finalized) { + return; + } + decoder_model->finalized = true; + if (decoder_model->status != DECODER_MODEL_OK) return; + av2_encoder_decoder_model_finalize_frame_constraints(decoder_model, + is_still_picture); + if (decoder_model->status != DECODER_MODEL_OK || is_still_picture || + decoder_model->display_samples == 0) { + return; + } + if (!decoder_model->last_display_duration_valid) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return; + } + if (!update_max_display_rate(decoder_model, + decoder_model->last_display_duration)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } +} + static void av2_decoder_model_start_frame_decode( - const AV2_COMP *const cpi, size_t coded_bits, - DECODER_MODEL *const decoder_model, AV2LevelSpec *const level_spec) { + const AV2_COMP *const cpi, uint64_t dfg_bits, int64_t compressed_size, + DECODER_MODEL *const decoder_model) { if (!decoder_model || decoder_model->status != DECODER_MODEL_OK) return; avm_clear_system_state(); const AV2_COMMON *const cm = &cpi->common; const SequenceHeader *const seq_params = &cm->seq_params; - const AV2LevelParams *const level_params = &cpi->level_params; - const double multi_stream_scaling_x = - level_params->multi_stream_scaling_x == 0 - ? 1.0 - : level_params->multi_stream_scaling_x; + if (!av2_encoder_decoder_model_sync_invalid_ref_buffers(cm, decoder_model)) { + return; + } + if (cpi->dm_starts_temporal_unit) { + if (decoder_model->temporal_unit_started) { + if (decoder_model->temporal_unit_index == UINT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + ++decoder_model->temporal_unit_index; + } + decoder_model->temporal_unit_started = true; + } else if (!decoder_model->temporal_unit_started) { + decoder_model->temporal_unit_started = true; + } + if (cm->current_frame.cm_obu_type == OBU_OPEN_LOOP_KEY) { + decoder_model->olk_encountered = true; + } + if (decoder_model->olk_encountered && cpi->olk_encountered && + (cm->immediate_output_picture || cm->implicit_output_picture)) { + decoder_model->olk_tu_order_hint = cm->current_frame.display_order_hint; + decoder_model->olk_tu_order_hint_valid = true; + } - int luma_pic_size; + uint64_t luma_pic_size; const FRAME_TYPE frame_type = cm->current_frame.frame_type; if (frame_type == KEY_FRAME || frame_type == INTRA_ONLY_FRAME) { - luma_pic_size = cm->width * cm->height; + luma_pic_size = (uint64_t)cm->width * (uint32_t)cm->height; } else { - const int max_frame_width = seq_params->max_frame_width; - const int max_frame_height = seq_params->max_frame_height; - luma_pic_size = max_frame_width * max_frame_height; + luma_pic_size = (uint64_t)seq_params->max_frame_width * + (uint32_t)seq_params->max_frame_height; } const int show_existing_frame = cm->show_existing_frame; + if (decoder_model->num_frame == INT64_MAX || + (!show_existing_frame && decoder_model->num_decoded_frame == INT64_MAX)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } ++decoder_model->num_frame; if (!show_existing_frame) ++decoder_model->num_decoded_frame; // DfgNum - decoder_model->coded_bits += coded_bits; + uint64_t closed_dfg_bits; + if (!av2_encoder_decoder_model_accumulate_dfg_bits( + decoder_model, dfg_bits, !show_existing_frame, &closed_dfg_bits)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } if (!show_existing_frame) { - const double removal_time = get_removal_time(decoder_model); + decoder_model->mirrored_refresh_frame_flags = 0; + bool removal_from_current_time; + const double removal_time = + get_removal_time(decoder_model, &removal_from_current_time); if (removal_time < 0.0) { decoder_model->status = DECODE_FRAME_BUF_UNAVAILABLE; return; } - const int previous_decode_samples = decoder_model->decode_samples; - const double previous_removal_time = decoder_model->removal_time; decoder_model->removal_time = removal_time; decoder_model->decode_samples = luma_pic_size; - bool is_global_intrabc_inloop_filtered_frame = - cm->features.allow_global_intrabc && is_filter_enabled_frame(cm); - const double dt = is_global_intrabc_inloop_filtered_frame - ? 0.5 * (removal_time - previous_removal_time) - : (removal_time - previous_removal_time); - const int64_t this_decode_rate = (int64_t)(previous_decode_samples / dt); - - decoder_model->max_decode_rate = - AVMMAX(decoder_model->max_decode_rate, this_decode_rate); - const int scaled = - (int)((level_spec->max_tiles / multi_stream_scaling_x) * 120.0 * dt); - int max_tile_limit = AVMMIN(level_spec->max_tiles, AVMMAX(1, scaled)); - - decoder_model->max_tile_rate_satisfy = - decoder_model->max_tile_rate_satisfy && - (cm->tiles.rows * cm->tiles.cols <= max_tile_limit); - - AV2_LEVEL level = decoder_model->level; - - double compressed_size_limit = - get_max_compressed_size(av2_level_defs + level, cpi->tier[0], - seq_params->seq_profile_idc, luma_pic_size, dt); - decoder_model->compressed_size_satisfy = - decoder_model->compressed_size_satisfy && - ((coded_bits >> 3) <= compressed_size_limit); - - double frame_symbol_count_limit = get_max_frame_symbol_count( - av2_level_defs + level, cpi->tier[0], seq_params->seq_profile_idc, dt, - multi_stream_scaling_x); - decoder_model->frame_symbol_count_satisfy = - decoder_model->frame_symbol_count_satisfy && - (cm->features.frame_symbol_count <= frame_symbol_count_limit); + const uint64_t num_tiles = (uint64_t)cm->tiles.rows * cm->tiles.cols; + if (num_tiles > UINT32_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + const ENCODER_DECODER_MODEL_FRAME current_frame = { + true, + removal_time, + luma_pic_size, + cm->features.allow_global_intrabc && is_filter_enabled_frame(cm) ? 2 : 1, + (uint32_t)num_tiles, + compressed_size, + cm->features.frame_symbol_count, + }; + if (!av2_encoder_decoder_model_store_frame_constraints( + decoder_model, ¤t_frame, removal_from_current_time)) { + return; + } // A frame with show_existing_frame being false indicates the end of a DFG. // Update the bits arrival time of this DFG. const double buffer_delay = (decoder_model->encoder_buffer_delay + @@ -946,50 +1844,74 @@ static void av2_decoder_model_start_frame_decode( const double latest_arrival_time = removal_time - buffer_delay; decoder_model->first_bit_arrival_time = AVMMAX(decoder_model->last_bit_arrival_time, latest_arrival_time); + double bit_rate; + if (!rational_to_double(&decoder_model->bit_rate, &bit_rate) || + !(bit_rate > 0.0)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } decoder_model->last_bit_arrival_time = decoder_model->first_bit_arrival_time + - (double)decoder_model->coded_bits / decoder_model->bit_rate; + (double)closed_dfg_bits / bit_rate; // Smoothing buffer underflows if the last bit arrives after the removal // time. - if (decoder_model->last_bit_arrival_time > removal_time && - !decoder_model->is_low_delay_mode) { + bool dfg_available_at_removal; + if (!av2_encoder_decoder_model_arrival_fits( + decoder_model, closed_dfg_bits, + removal_time - decoder_model->first_bit_arrival_time, + &dfg_available_at_removal)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + if (!dfg_available_at_removal && !decoder_model->is_low_delay_mode) { decoder_model->status = SMOOTHING_BUFFER_UNDERFLOW; return; } - // Reset the coded bits for the next DFG. - decoder_model->coded_bits = 0; - // Check if the smoothing buffer overflows. DFG_INTERVAL_QUEUE *const queue = &decoder_model->dfg_interval_queue; - if (queue->size >= DFG_INTERVAL_QUEUE_SIZE) { - assert(0); - } const double first_bit_arrival_time = decoder_model->first_bit_arrival_time; const double last_bit_arrival_time = decoder_model->last_bit_arrival_time; // Remove the DFGs with removal time earlier than last_bit_arrival_time. - while (queue->buf[queue->head].removal_time <= last_bit_arrival_time && - queue->size > 0) { - if (queue->buf[queue->head].removal_time - first_bit_arrival_time + - queue->total_interval > - 1.0) { + while (queue->size > 0 && + queue->buf[queue->head].removal_time <= last_bit_arrival_time) { + bool smoothing_buffer_fits; + if (!smoothing_buffer_fits_with_partial_arrival( + decoder_model, queue->total_bits, closed_dfg_bits, + queue->buf[queue->head].removal_time - first_bit_arrival_time, + &smoothing_buffer_fits)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + if (!smoothing_buffer_fits) { decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; return; } + if (queue->buf[queue->head].coded_bits > queue->total_bits) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } queue->total_interval -= queue->buf[queue->head].last_bit_arrival_time - queue->buf[queue->head].first_bit_arrival_time; - queue->head = (queue->head + 1) % DFG_INTERVAL_QUEUE_SIZE; + queue->total_bits -= queue->buf[queue->head].coded_bits; + queue->head = (queue->head + 1) % queue->capacity; --queue->size; } // Push current DFG into the queue. - const int queue_index = - (queue->head + queue->size++) % DFG_INTERVAL_QUEUE_SIZE; - queue->buf[queue_index].first_bit_arrival_time = first_bit_arrival_time; - queue->buf[queue_index].last_bit_arrival_time = last_bit_arrival_time; - queue->buf[queue_index].removal_time = removal_time; - queue->total_interval += last_bit_arrival_time - first_bit_arrival_time; - // The smoothing buffer can hold at most "bit_rate" bits, which is - // equivalent to 1 second of total interval. - if (queue->total_interval > 1.0) { + const DFG_INTERVAL interval = { first_bit_arrival_time, + last_bit_arrival_time, removal_time, + closed_dfg_bits }; + if (!av2_encoder_decoder_model_push_dfg_interval(decoder_model, + &interval)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + bool smoothing_buffer_fits; + if (!av2_encoder_decoder_model_smoothing_buffer_fits( + decoder_model, queue->total_bits, &smoothing_buffer_fits)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + if (!smoothing_buffer_fits) { decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; return; } @@ -1001,14 +1923,24 @@ static void av2_decoder_model_start_frame_decode( decoder_model->status = DECODE_FRAME_BUF_UNAVAILABLE; return; } - decoder_model->current_time = removal_time + time_to_decode_frame(cm, decoder_model->decode_rate); + if (!av2_encoder_decoder_model_capture_current_generation( + cpi, decoder_model, luma_pic_size)) { + return; + } + } else if (!capture_presentation_descriptor( + cpi, decoder_model, luma_pic_size, -1, 0, false, + &decoder_model->current_presentation)) { + return; } } static void av2_decoder_model_update_buffer_and_finish_frame_decode( const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model) { + if (decoder_model == NULL || decoder_model->status != DECODER_MODEL_OK) { + return; + } const AV2_COMMON *const cm = &cpi->common; const int show_existing_frame = cm->show_existing_frame; @@ -1017,14 +1949,18 @@ static void av2_decoder_model_update_buffer_and_finish_frame_decode( const CurrentFrame *const current_frame = &cm->current_frame; decoder_model->frame_buffer_pool[decoder_model->cfbi].frame_type = cm->current_frame.frame_type; - update_ref_buffers(cm, decoder_model, current_frame->refresh_frame_flags); + if (!update_ref_buffers(cm, decoder_model, + current_frame->refresh_frame_flags)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } if (decoder_model->initial_presentation_delay < 0.0) { // Display can begin after required number of frames have been buffered. if (frames_in_buffer_pool(decoder_model) >= decoder_model->initial_display_delay) { decoder_model->initial_presentation_delay = decoder_model->current_time; - if (decoder_model->presentation_time > 0) + if (decoder_model->presentation_time >= 0.0) decoder_model->presentation_time += decoder_model->initial_presentation_delay; // Update presentation time for each shown frame in the frame buffer. @@ -1033,8 +1969,10 @@ static void av2_decoder_model_update_buffer_and_finish_frame_decode( &decoder_model->frame_buffer_pool[i]; if (this_buffer->player_ref_count == 0) continue; assert(this_buffer->display_index >= 0); - this_buffer->presentation_time = - get_presentation_time(decoder_model, this_buffer->display_index); + if (this_buffer->presentation_time >= 0.0) { + this_buffer->presentation_time += + decoder_model->initial_presentation_delay; + } } } } @@ -1064,47 +2002,188 @@ void av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_point } } -static void av2_decoder_model_check_output_frame( - const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, int ref_idx, - RefCntBuffer *output_frame_ptr) { - const AV2_COMMON *const cm = &cpi->common; - const SequenceHeader *const seq_params = &cm->seq_params; - const int luma_disp_pic_size = - seq_params->max_frame_width * seq_params->max_frame_height; +static bool decoder_model_get_eligible_output( + DECODER_MODEL *const decoder_model, int ref_idx, int *const buffer_index) { + if (ref_idx < 0 || ref_idx >= decoder_model->num_ref_frames) return false; + const int index = decoder_model->vbi[ref_idx]; + if (index == -1) return false; + if (index < 0 || index >= decoder_model->num_ref_frames + 2) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &decoder_model->frame_buffer_pool[index].presentation; + if (!presentation->valid || presentation->buffer_index != index) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return false; + } + if (!presentation->implicit_output_eligible || + presentation->normative_output_done || presentation->restricted) { + return false; + } + if (buffer_index != NULL) *buffer_index = index; + return true; +} - // Display. - FRAME_BUFFER *this_buffer; - if (ref_idx < 0) { - this_buffer = &decoder_model->frame_buffer_pool[decoder_model->cfbi]; - } else { - if (cm->ref_frame_map[ref_idx] == NULL || - decoder_model->vbi[ref_idx] == -1) { - decoder_model->status = DECODE_EXISTING_FRAME_BUF_EMPTY; - return; +static const ENCODER_DM_RAP_PRESENTATION_ANCHOR *find_rap_anchor( + const DECODER_MODEL *const decoder_model, uint64_t rap_epoch) { + for (int i = 0; i < BUFFER_POOL_MAX_SIZE + 2; ++i) { + const ENCODER_DM_RAP_PRESENTATION_ANCHOR *const anchor = + &decoder_model->rap_presentation_anchors[i]; + if (anchor->valid && anchor->rap_epoch == rap_epoch) return anchor; + } + return NULL; +} + +static bool rap_epoch_is_live(const DECODER_MODEL *const decoder_model, + uint64_t rap_epoch) { + for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &decoder_model->frame_buffer_pool[i].presentation; + if (presentation->valid && presentation->rap_epoch == rap_epoch) { + return true; } - this_buffer = - &decoder_model->frame_buffer_pool[decoder_model->vbi[ref_idx]]; } - ++this_buffer->player_ref_count; - decoder_model->num_shown_frame++; - if (decoder_model->last_display_index < 0) { - decoder_model->last_display_index = 0; - } else { - if (decoder_model->last_output_xlayer != output_frame_ptr->xlayer_id) { - decoder_model->status = DECODER_MODEL_MULTIPLE_XLAYERS; - return; + return decoder_model->current_presentation.valid && + decoder_model->current_presentation.rap_epoch == rap_epoch; +} + +static bool store_rap_anchor(DECODER_MODEL *const decoder_model, + uint64_t rap_epoch, double presentation_offset) { + ENCODER_DM_RAP_PRESENTATION_ANCHOR *free_anchor = NULL; + for (int i = 0; i < BUFFER_POOL_MAX_SIZE + 2; ++i) { + ENCODER_DM_RAP_PRESENTATION_ANCHOR *const anchor = + &decoder_model->rap_presentation_anchors[i]; + if (anchor->valid && anchor->rap_epoch == rap_epoch) { + anchor->presentation_offset = presentation_offset; + return true; } - if (output_frame_ptr->mlayer_id <= decoder_model->last_output_mlayer) { - decoder_model->last_display_index++; + if ((!anchor->valid || + !rap_epoch_is_live(decoder_model, anchor->rap_epoch)) && + free_anchor == NULL) { + free_anchor = anchor; } } - decoder_model->last_output_mlayer = output_frame_ptr->mlayer_id; - decoder_model->last_output_xlayer = output_frame_ptr->xlayer_id; + if (free_anchor == NULL) return false; + free_anchor->valid = true; + free_anchor->rap_epoch = rap_epoch; + free_anchor->presentation_offset = presentation_offset; + return true; +} + +static bool get_presentation_offset( + DECODER_MODEL *const decoder_model, + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation, + double *const offset) { + if (decoder_model->num_shown_frame < 0) { + *offset = 0.0; + return true; + } + if (decoder_model->equal_picture_interval) { + if (!decoder_model->last_presentation_offset_valid) return false; + if (decoder_model->last_output_temporal_unit_valid && + decoder_model->last_output_temporal_unit == + presentation->temporal_unit_index) { + *offset = decoder_model->last_presentation_offset; + return true; + } + *offset = decoder_model->last_presentation_offset + + decoder_model->num_ticks_per_picture * + decoder_model->display_clock_tick; + return isfinite(*offset); + } + if (!presentation->presentation_time_present) return false; + uint64_t base_epoch = presentation->rap_epoch; + if (presentation->random_access_point || presentation->leading_frame) { + if (base_epoch == 0) return false; + --base_epoch; + } + double base = 0.0; + if (base_epoch != 0) { + const ENCODER_DM_RAP_PRESENTATION_ANCHOR *const anchor = + find_rap_anchor(decoder_model, base_epoch); + if (anchor == NULL) return false; + base = anchor->presentation_offset; + } + *offset = base + presentation->presentation_time_ticks * + decoder_model->display_clock_tick; + return isfinite(*offset); +} + +static void av2_decoder_model_check_output_frame( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, + int buffer_index, + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation, + bool completes_implicit_output) { + if (decoder_model == NULL || decoder_model->status != DECODER_MODEL_OK) { + return; + } + if (buffer_index < 0 || buffer_index >= decoder_model->num_ref_frames + 2 || + presentation == NULL || !presentation->valid || + presentation->output_luma_samples == 0) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return; + } + FRAME_BUFFER *const this_buffer = + &decoder_model->frame_buffer_pool[buffer_index]; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const generation = + &this_buffer->presentation; + if (!generation->valid || generation->buffer_index != buffer_index) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return; + } + double presentation_offset; + if (!get_presentation_offset(decoder_model, presentation, + &presentation_offset)) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return; + } + if (completes_implicit_output) { + generation->normative_output_done = true; + } + if (this_buffer->player_ref_count == UINT32_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + ++this_buffer->player_ref_count; + if (decoder_model->num_shown_frame == INT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->num_shown_frame++; + if (decoder_model->last_output_xlayer != -1 && + decoder_model->last_output_xlayer != presentation->xlayer_id) { + decoder_model->status = DECODER_MODEL_MULTIPLE_XLAYERS; + return; + } + if (decoder_model->last_display_index == INT_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + ++decoder_model->last_display_index; + decoder_model->last_output_mlayer = presentation->mlayer_id; + decoder_model->last_output_xlayer = presentation->xlayer_id; + const bool starts_new_output_temporal_unit = + decoder_model->last_output_temporal_unit_valid && + presentation->temporal_unit_index != + decoder_model->last_output_temporal_unit; + const uint64_t output_rap_epoch = + presentation->leading_frame && presentation->rap_epoch != 0 + ? presentation->rap_epoch - 1 + : presentation->rap_epoch; + decoder_model->last_output_temporal_unit = presentation->temporal_unit_index; + decoder_model->last_output_temporal_unit_valid = true; this_buffer->display_index = decoder_model->last_display_index; - const double presentation_time = - get_presentation_time(decoder_model, this_buffer->display_index); + double presentation_time = presentation_offset; + if (decoder_model->initial_presentation_delay >= 0.0) { + presentation_time += decoder_model->initial_presentation_delay; + } + if (!isfinite(presentation_time)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } this_buffer->presentation_time = presentation_time; if (decoder_model->initial_presentation_delay >= 0.0) { if (presentation_time >= 0.0 && @@ -1112,43 +2191,39 @@ static void av2_decoder_model_check_output_frame( decoder_model->status = DISPLAY_FRAME_LATE; return; } + if (generation->decode_completion_time > presentation_time) { + decoder_model->status = DISPLAY_FRAME_LATE; + return; + } } const double previous_presentation_time = decoder_model->presentation_time; - if (presentation_time >= 0.0 && previous_presentation_time >= 0.0 && - presentation_time > previous_presentation_time) { + if (decoder_model->last_presentation_offset_valid && + decoder_model->previous_output_rap_epoch_valid && + decoder_model->previous_output_rap_epoch == output_rap_epoch && + presentation_offset < decoder_model->last_presentation_offset) { + decoder_model->min_presentation_interval_satisfy = false; + } + if (starts_new_output_temporal_unit && presentation_time >= 0.0 && + previous_presentation_time >= 0.0) { // A new temporal unit has started. Compute metrics over the inter-TU // interval using the previous TU's accumulated display samples. - assert(previous_presentation_time <= presentation_time); const double interval = presentation_time - previous_presentation_time; - // Peak display rate across TU boundaries (spec §E.3.2). - const int64_t this_display_rate = - (int64_t)(decoder_model->display_samples / interval); - decoder_model->max_display_rate = - AVMMAX(decoder_model->max_display_rate, this_display_rate); - const AV2LevelParams *const level_params = &cpi->level_params; - const AV2LevelSpec *const target_spec = - av2_level_defs + decoder_model->level; - const double multi_stream_scaling_x = - level_params->multi_stream_scaling_x == 0 - ? 1.0 - : level_params->multi_stream_scaling_x; - const double max_display_rate = - (double)target_spec->max_display_rate / multi_stream_scaling_x; - const double max_decode_rate = - (double)target_spec->max_decode_rate / multi_stream_scaling_x; - int max_header_rate = target_spec->max_header_rate; - if (multi_stream_scaling_x != 1.0) { - const int substream_idx = level_to_sub_stream_level_index( - decoder_model->level, multi_stream_scaling_x); - max_header_rate = - av2_substream_level_defs[substream_idx].max_header_rate_x; - } - const double min_frame_time = - max_decode_rate / ((double)max_header_rate * max_display_rate); - const double min_interval = AVMMAX( - decoder_model->display_samples / max_display_rate, min_frame_time); + // Peak display rate across TU boundaries (Annex A). + if (!update_max_display_rate(decoder_model, interval)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->last_display_duration = interval; + decoder_model->last_display_duration_valid = true; + if (interval <= 0.0) + decoder_model->min_presentation_interval_satisfy = false; + double min_interval; + if (!get_minimum_presentation_interval(cpi, decoder_model, &min_interval)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } decoder_model->min_presentation_interval_satisfy = decoder_model->min_presentation_interval_satisfy && (interval >= min_interval); @@ -1158,13 +2233,104 @@ static void av2_decoder_model_check_output_frame( decoder_model->num_frames_current_tu = 0; } // Accumulate this frame's samples into the current temporal unit. - decoder_model->display_samples += luma_disp_pic_size; + if (presentation->output_luma_samples > + UINT64_MAX - decoder_model->display_samples) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->display_samples += presentation->output_luma_samples; + if (decoder_model->num_frames_current_tu == UINT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } ++decoder_model->num_frames_current_tu; - decoder_model->presentation_time = presentation_time; + if (previous_presentation_time < 0.0 || starts_new_output_temporal_unit) { + decoder_model->presentation_time = presentation_time; + } + decoder_model->last_presentation_offset = presentation_offset; + decoder_model->last_presentation_offset_valid = true; + decoder_model->previous_output_rap_epoch = output_rap_epoch; + decoder_model->previous_output_rap_epoch_valid = true; + if (presentation->random_access_point && + !store_rap_anchor(decoder_model, presentation->rap_epoch, + presentation_offset)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } } -void av2_decoder_model_check_output_frame_for_operating_points( - const AV2_COMP *const cpi, int ref_idx, RefCntBuffer *output_frame_ptr) { +static void decoder_model_observe_output_frame_buffers( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, + int trigger_ref_idx, int trigger_buffer_index, + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const trigger_presentation, + bool trigger_completes_implicit_output) { + if (decoder_model->status != DECODER_MODEL_OK || trigger_buffer_index < 0 || + trigger_buffer_index >= decoder_model->num_ref_frames + 2 || + trigger_presentation == NULL || !trigger_presentation->valid) { + if (decoder_model->status == DECODER_MODEL_OK) { + decoder_model->status = DECODE_EXISTING_FRAME_BUF_EMPTY; + } + return; + } + + while (decoder_model->status == DECODER_MODEL_OK) { + int output_ref_idx = trigger_ref_idx; + int output_buffer_index = trigger_buffer_index; + uint64_t output_order = trigger_presentation->output_order; + for (int i = 0; i < decoder_model->num_ref_frames; ++i) { + int candidate_index; + if (decoder_model_get_eligible_output(decoder_model, i, + &candidate_index)) { + const uint64_t candidate_order = + decoder_model->frame_buffer_pool[candidate_index] + .presentation.output_order; + if (candidate_order < output_order) { + output_ref_idx = i; + output_buffer_index = candidate_index; + output_order = candidate_order; + } + } + } + if (output_ref_idx == trigger_ref_idx) break; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const output_presentation = + &decoder_model->frame_buffer_pool[output_buffer_index].presentation; + av2_decoder_model_check_output_frame( + cpi, decoder_model, output_buffer_index, output_presentation, true); + } + if (decoder_model->status != DECODER_MODEL_OK) return; + + av2_decoder_model_check_output_frame(cpi, decoder_model, trigger_buffer_index, + trigger_presentation, + trigger_completes_implicit_output); + if (decoder_model->status != DECODER_MODEL_OK) return; + + for (int k = 1; k <= decoder_model->num_ref_frames; ++k) { + if (trigger_presentation->output_order > UINT64_MAX - (uint64_t)k) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + const uint64_t target_order = + trigger_presentation->output_order + (uint64_t)k; + bool made_output = false; + for (int i = 0; i < decoder_model->num_ref_frames; ++i) { + int candidate_index; + if (decoder_model_get_eligible_output(decoder_model, i, + &candidate_index)) { + ENCODER_DM_PRESENTATION_DESCRIPTOR *const candidate = + &decoder_model->frame_buffer_pool[candidate_index].presentation; + if (candidate->output_order == target_order) { + av2_decoder_model_check_output_frame( + cpi, decoder_model, candidate_index, candidate, true); + made_output = true; + } + } + if (decoder_model->status != DECODER_MODEL_OK) return; + } + if (!made_output) break; + } +} + +void av2_decoder_model_observe_output_frame_buffers_for_operating_points( + const AV2_COMP *const cpi, int ref_idx) { const AV2_COMMON *const cm = &cpi->common; const SequenceHeader *const seq_params = &cm->seq_params; const AV2LevelParams *const level_params = &cpi->level_params; @@ -1178,10 +2344,251 @@ void av2_decoder_model_check_output_frame_for_operating_points( continue; } AV2LevelInfo *const level_info = level_params->level_info[i]; + if (level_info == NULL) continue; DECODER_MODEL *const decoder_models = level_info->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { - av2_decoder_model_check_output_frame(cpi, &decoder_models[level], ref_idx, - output_frame_ptr); + DECODER_MODEL *const decoder_model = &decoder_models[level]; + if (decoder_model->status != DECODER_MODEL_OK) continue; + if (ref_idx < 0 && !cm->show_existing_frame && decoder_model->cfbi >= 0 && + decoder_model->cfbi < decoder_model->num_ref_frames + 2) { + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const current_generation = + &decoder_model->frame_buffer_pool[decoder_model->cfbi].presentation; + if (current_generation->valid && + decoder_model->current_presentation.valid && + current_generation->buffer_index == decoder_model->cfbi && + decoder_model->current_presentation.buffer_index == + decoder_model->cfbi && + current_generation->generation == + decoder_model->current_presentation.generation && + current_generation->normative_output_done) { + continue; + } + } + int trigger_buffer_index = decoder_model->cfbi; + const ENCODER_DM_PRESENTATION_DESCRIPTOR *trigger_presentation = + &decoder_model->current_presentation; + bool trigger_completes_implicit_output = !cm->show_existing_frame; + if (ref_idx >= 0) { + if (ref_idx >= decoder_model->num_ref_frames || + decoder_model->vbi[ref_idx] == -1) { + decoder_model->status = DECODE_EXISTING_FRAME_BUF_EMPTY; + continue; + } + trigger_buffer_index = decoder_model->vbi[ref_idx]; + trigger_presentation = + &decoder_model->frame_buffer_pool[trigger_buffer_index] + .presentation; + trigger_completes_implicit_output = true; + } else if (cm->show_existing_frame) { + const int generation_ref_idx = cm->sef_ref_fb_idx; + if (generation_ref_idx < 0 || + generation_ref_idx >= decoder_model->num_ref_frames || + decoder_model->vbi[generation_ref_idx] == -1) { + decoder_model->status = DECODE_EXISTING_FRAME_BUF_EMPTY; + continue; + } + trigger_buffer_index = decoder_model->vbi[generation_ref_idx]; + } + decoder_model_observe_output_frame_buffers( + cpi, decoder_model, ref_idx, trigger_buffer_index, + trigger_presentation, trigger_completes_implicit_output); + } + } +} + +void av2_decoder_model_observe_displaced_output_for_operating_points( + const AV2_COMP *const cpi, int ref_idx) { + if (cpi == NULL || ref_idx < 0) return; + const AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + const AV2LevelParams *const level_params = &cpi->level_params; + if (ref_idx >= seq_params->ref_frames || !level_params->keep_level_stats || + is_stat_generation_stage(cpi)) { + return; + } + + for (int i = 0; i < seq_params->operating_points_cnt_minus_1 + 1; ++i) { + if (!((level_params->keep_level_stats >> i) & 1) || + level_params->level_info[i] == NULL || + !is_in_operating_point(seq_params->operating_point_idc[i], + cm->tlayer_id, cm->mlayer_id)) { + continue; + } + DECODER_MODEL *const decoder_models = + level_params->level_info[i]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + DECODER_MODEL *const decoder_model = &decoder_models[level]; + if (decoder_model->status != DECODER_MODEL_OK) continue; + const int buffer_index = decoder_model->vbi[ref_idx]; + // A newly initialized model intentionally has no ownership of stale + // encoder references from the preceding CVS. + if (buffer_index == -1) continue; + if (buffer_index < 0 || + buffer_index >= decoder_model->num_ref_frames + 2) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + continue; + } + FRAME_BUFFER *const buffer = + &decoder_model->frame_buffer_pool[buffer_index]; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &buffer->presentation; + if (!presentation->valid || presentation->buffer_index != buffer_index) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + continue; + } + if (!is_in_operating_point(seq_params->operating_point_idc[i], + presentation->temporal_id, + presentation->mlayer_id) || + !presentation->implicit_output_eligible || + presentation->normative_output_done) { + continue; + } + decoder_model_observe_output_frame_buffers( + cpi, decoder_model, ref_idx, buffer_index, presentation, true); + } + } +} + +void av2_decoder_model_observe_restricted_output_for_operating_points( + const AV2_COMP *const cpi) { + if (cpi == NULL || !cpi->level_params.keep_level_stats || + is_stat_generation_stage(cpi)) { + return; + } + const AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + for (int op = 0; op < seq_params->operating_points_cnt_minus_1 + 1; ++op) { + if (!((cpi->level_params.keep_level_stats >> op) & 1) || + cpi->level_params.level_info[op] == NULL || + !is_in_operating_point(seq_params->operating_point_idc[op], + cm->tlayer_id, cm->mlayer_id)) { + continue; + } + DECODER_MODEL *const decoder_models = + cpi->level_params.level_info[op]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + DECODER_MODEL *const decoder_model = &decoder_models[level]; + if (decoder_model->status != DECODER_MODEL_OK) continue; + for (int ref_idx = 0; ref_idx < decoder_model->num_ref_frames; + ++ref_idx) { + const int buffer_index = decoder_model->vbi[ref_idx]; + if (buffer_index == -1) continue; + if (buffer_index < 0 || + buffer_index >= decoder_model->num_ref_frames + 2) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + break; + } + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &decoder_model->frame_buffer_pool[buffer_index].presentation; + if (!presentation->valid || + presentation->buffer_index != buffer_index) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + break; + } + if (!is_mlayer_transitively_dependent( + seq_params, presentation->mlayer_id, cm->mlayer_id)) { + continue; + } + int eligible_buffer_index; + if (decoder_model_get_eligible_output(decoder_model, ref_idx, + &eligible_buffer_index)) { + decoder_model_observe_output_frame_buffers( + cpi, decoder_model, ref_idx, eligible_buffer_index, presentation, + true); + } + presentation->restricted = true; + if (decoder_model->status != DECODER_MODEL_OK) break; + } + } + } +} + +static void decoder_model_flush_implicit_output( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, + bool olk_limit) { + if (olk_limit && !decoder_model->olk_encountered) return; + if (olk_limit && !decoder_model->olk_tu_order_hint_valid) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return; + } + while (decoder_model->status == DECODER_MODEL_OK) { + int output_buffer_index = -1; + uint64_t output_order = 0; + for (int i = 0; i < decoder_model->num_ref_frames; ++i) { + int candidate_index; + if (!decoder_model_get_eligible_output(decoder_model, i, + &candidate_index)) { + continue; + } + ENCODER_DM_PRESENTATION_DESCRIPTOR *const candidate = + &decoder_model->frame_buffer_pool[candidate_index].presentation; + if (olk_limit && + candidate->order_hint >= decoder_model->olk_tu_order_hint) { + continue; + } + if (output_buffer_index == -1 || + candidate->output_order <= output_order) { + output_buffer_index = candidate_index; + output_order = candidate->output_order; + } + } + if (output_buffer_index == -1) return; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &decoder_model->frame_buffer_pool[output_buffer_index].presentation; + av2_decoder_model_check_output_frame( + cpi, decoder_model, output_buffer_index, presentation, true); + } +} + +void av2_decoder_model_flush_implicit_output_for_operating_points( + const AV2_COMP *const cpi, bool olk_limit) { + if (cpi == NULL || !cpi->level_params.keep_level_stats || + is_stat_generation_stage(cpi)) { + return; + } + const SequenceHeader *const seq_params = &cpi->common.seq_params; + const int operating_point_count = + olk_limit ? seq_params->operating_points_cnt_minus_1 + 1 + : MAX_NUM_OPERATING_POINTS; + for (int i = 0; i < operating_point_count; ++i) { + if (!((cpi->level_params.keep_level_stats >> i) & 1) || + cpi->level_params.level_info[i] == NULL || + (olk_limit && !is_in_operating_point(seq_params->operating_point_idc[i], + cpi->common.tlayer_id, + cpi->common.mlayer_id))) { + continue; + } + DECODER_MODEL *const decoder_models = + cpi->level_params.level_info[i]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + DECODER_MODEL *const decoder_model = &decoder_models[level]; + decoder_model_flush_implicit_output(cpi, decoder_model, olk_limit); + if (olk_limit) { + decoder_model->olk_encountered = false; + decoder_model->olk_tu_order_hint_valid = false; + } + } + } +} + +void av2_encoder_decoder_model_finish_for_operating_points( + const AV2_COMP *const cpi) { + if (cpi == NULL || !cpi->level_params.keep_level_stats || + is_stat_generation_stage(cpi)) { + return; + } + av2_decoder_model_flush_implicit_output_for_operating_points(cpi, false); + for (int op = 0; op < MAX_NUM_OPERATING_POINTS; ++op) { + if (!((cpi->level_params.keep_level_stats >> op) & 1) || + cpi->level_params.level_info[op] == NULL) { + continue; + } + DECODER_MODEL *const decoder_models = + cpi->level_params.level_info[op]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + DECODER_MODEL *const decoder_model = &decoder_models[level]; + av2_encoder_decoder_model_finalize(decoder_model, + decoder_model->is_still_picture); } } } @@ -1201,6 +2608,7 @@ void av2_init_level_info(AV2_COMP *cpi) { AV2LevelInfo *const this_level_info = cpi->level_params.level_info[op_index]; if (!this_level_info) continue; + av2_encoder_decoder_models_destroy(this_level_info); memset(this_level_info, 0, sizeof(*this_level_info)); AV2LevelSpec *const level_spec = &this_level_info->level_spec; level_spec->level = SEQ_LEVEL_MAX; @@ -1257,8 +2665,23 @@ static void get_temporal_parallel_params(int scalability_mode_idc, #define MIN_FRAME_WIDTH 16 #define MIN_FRAME_HEIGHT 16 -// (547430400 = 3840 * 2160 * 60 * 1.1) -#define MAX_TILE_SIZE_HEADER_RATE_PRODUCT 547430400 +ENCODER_DM_RESULT_CLASS av2_encoder_decoder_model_classify_status( + DECODER_MODEL_STATUS status) { + switch (status) { + case DECODER_MODEL_OK: + case DECODER_MODEL_DISABLED: return ENCODER_DM_RESULT_PASS; + case DECODE_FRAME_BUF_UNAVAILABLE: + case DECODE_EXISTING_FRAME_BUF_EMPTY: + case DISPLAY_FRAME_LATE: + case SMOOTHING_BUFFER_UNDERFLOW: + case SMOOTHING_BUFFER_OVERFLOW: return ENCODER_DM_RESULT_VIOLATION; + case DECODER_MODEL_MULTIPLE_XLAYERS: + case DECODER_MODEL_UNSUPPORTED: + case DECODER_MODEL_INCOMPLETE: + case DECODER_MODEL_INTERNAL_ERROR: return ENCODER_DM_RESULT_UNAVAILABLE; + } + return ENCODER_DM_RESULT_UNAVAILABLE; +} static TARGET_LEVEL_FAIL_ID check_level_constraints( const AV2_COMP *const cpi, const AV2LevelInfo *const level_info, @@ -1266,10 +2689,15 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( int check_bitrate) { const DECODER_MODEL *const decoder_model = &level_info->decoder_models[level]; const DECODER_MODEL_STATUS decoder_model_status = decoder_model->status; - if (decoder_model_status != DECODER_MODEL_OK && - decoder_model_status != DECODER_MODEL_DISABLED) { + const ENCODER_DM_RESULT_CLASS model_result = + av2_encoder_decoder_model_classify_status(decoder_model_status); + if (model_result == ENCODER_DM_RESULT_VIOLATION) { return DECODER_MODEL_FAIL; } + bool model_unavailable = model_result == ENCODER_DM_RESULT_UNAVAILABLE; + if (decoder_model_status == DECODER_MODEL_OK && !decoder_model->initialized) { + model_unavailable = true; + } bool is_multi_stream = cpi->level_params.multi_stream_scaling_x == 1.5 || cpi->level_params.multi_stream_scaling_x == 4.0 || cpi->level_params.multi_stream_scaling_x == 9.0; @@ -1365,14 +2793,12 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( fail_id = TILE_WIDTH_INVALID; break; } - if (!is_still_picture) { + if (!is_still_picture && decoder_model->initialized) { const int max_header_rate = is_multi_stream ? target_sub_stream_level_spec->max_header_rate_x : target_level_spec->max_header_rate; const double max_display_rate = (double)target_level_spec->max_display_rate / multi_stream_scaling_x; - const double max_decode_rate = - (double)target_level_spec->max_decode_rate / multi_stream_scaling_x; if (level_spec->max_header_rate > (max_header_rate * (1 + (tier * 2)))) { fail_id = FRAME_HEADER_RATE_TOO_HIGH; @@ -1382,7 +2808,7 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( fail_id = DISPLAY_RATE_TOO_HIGH; break; } - if (decoder_model->max_decode_rate > max_decode_rate) { + if (!decoder_model->max_decode_rate_satisfy) { fail_id = DECODE_RATE_TOO_HIGH; break; } @@ -1408,6 +2834,12 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( if (check_bitrate) { // Check average bitrate instead of max_bitrate. + if (!(level_stats->total_time_encoded > 0.0) || + !isfinite(level_stats->total_time_encoded) || + !isfinite(level_stats->total_compressed_size)) { + model_unavailable = true; + break; + } const double bitrate_limit = get_max_bitrate(target_level_spec, tier, profile, cpi->level_params.multi_stream_scaling_x); @@ -1446,9 +2878,64 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( } while (0); + if (fail_id == TARGET_LEVEL_OK && model_unavailable) { + return DECODER_MODEL_UNAVAILABLE; + } return fail_id; } +static bool level_info_has_model_state(const AV2LevelInfo *level_info) { + if (level_info == NULL) return false; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + const DECODER_MODEL *const model = &level_info->decoder_models[level]; + if (model->initialized || model->status != DECODER_MODEL_OK) return true; + } + return false; +} + +void av2_encoder_check_target_level(AV2_COMP *cpi, bool all_operating_points) { + if (cpi == NULL || !cpi->level_params.keep_level_stats || + is_stat_generation_stage(cpi)) { + return; + } + AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + AV2LevelParams *const level_params = &cpi->level_params; + const int operating_point_count = + all_operating_points ? MAX_NUM_OPERATING_POINTS + : seq_params->operating_points_cnt_minus_1 + 1; + for (int op = 0; op < operating_point_count; ++op) { + if (!((level_params->keep_level_stats >> op) & 1) || + level_params->level_info[op] == NULL || + (!all_operating_points && + !is_in_operating_point(seq_params->operating_point_idc[op], + cm->tlayer_id, cm->mlayer_id))) { + continue; + } + AV2LevelInfo *const level_info = level_params->level_info[op]; + if (!level_info_has_model_state(level_info)) continue; + const AV2_LEVEL target_level = level_params->target_seq_level_idx[op]; + if (target_level >= SEQ_LEVELS) continue; + assert(is_valid_seq_level_idx(target_level)); + const DECODER_MODEL *const target_model = + &level_info->decoder_models[target_level]; + const int tier = + target_model->initialized ? target_model->tier : cpi->tier[op]; + const int is_still_picture = target_model->initialized + ? target_model->is_still_picture + : seq_params->still_picture; + const TARGET_LEVEL_FAIL_ID fail_id = check_level_constraints( + cpi, level_info, target_level, tier, is_still_picture, + seq_params->seq_profile_idc, 0); + if (fail_id != TARGET_LEVEL_OK) { + avm_internal_error(&cm->error, AVM_CODEC_ERROR, + "Failed to encode to the target level %s. %s", + level_string[target_level], + level_fail_messages[fail_id]); + } + } +} + static void get_tile_stats(const AV2_COMMON *const cm, const TileDataEnc *const tile_data, int *max_tile_size, int *min_cropped_tile_width, @@ -1583,22 +3070,28 @@ double av2_get_compression_ratio(const AV2_COMMON *const cm, size_t encoded_frame_size) { const int upscaled_width = cm->width; const int height = cm->height; - const int luma_pic_size = upscaled_width * height; + const uint64_t luma_pic_size = (uint64_t)upscaled_width * (uint64_t)height; const SequenceHeader *const seq_params = &cm->seq_params; const BITSTREAM_PROFILE profile = seq_params->seq_profile_idc; - const int profile_factor_row = get_profile_factor_table_row_index(profile); - const int picture_size_profile_factor = - (int)picture_size_profile_factor_table[profile_factor_row]; + AV2ProfileLevelFactors factors; + const bool profile_supported = profile < RESERVED_PROFILES_START; + if ((!profile_supported || + !av2_get_profile_level_factors(profile, &factors)) && + !av2_get_profile_level_factors(MAIN_420_10_IP0, &factors)) { + return 0.0; + } encoded_frame_size = (encoded_frame_size > 129 ? encoded_frame_size - 128 : 1); - const size_t uncompressed_frame_size = - (luma_pic_size * picture_size_profile_factor) >> 3; + const uint64_t uncompressed_frame_size = + luma_pic_size * factors.picture_size_profile_factor >> 3; return uncompressed_frame_size / (double)encoded_frame_size; } -void av2_update_level_info(AV2_COMP *cpi, size_t size, int64_t ts_start, - int64_t ts_end, int decode_frame) { +void av2_update_level_info(AV2_COMP *cpi, const uint8_t *data, size_t size, + int64_t ts_start, int64_t ts_end, + bool has_serialized_frame_unit, + uint64_t dfg_prefix_bits) { AV2_COMMON *const cm = &cpi->common; AV2LevelParams *const level_params = &cpi->level_params; const int upscaled_width = cm->width; @@ -1628,8 +3121,23 @@ void av2_update_level_info(AV2_COMP *cpi, size_t size, int64_t ts_start, const int xlayer_id = cm->xlayer_id; (void)xlayer_id; const SequenceHeader *const seq_params = &cm->seq_params; - const BITSTREAM_PROFILE profile = seq_params->seq_profile_idc; - const int is_still_picture = seq_params->still_picture; + uint64_t dfg_bits = 0; + int64_t compressed_size = 0; + bool model_accounting_valid = true; + if (has_serialized_frame_unit) { + uint64_t dfg_bytes; + uint64_t frame_compressed_bytes; + model_accounting_valid = + !cpi->dm_frame_symbol_count_overflow && + av2_encoder_decoder_model_count_obu_bytes(data, size, &dfg_bytes, + &frame_compressed_bytes) && + dfg_bytes <= (UINT64_MAX - dfg_prefix_bits) / 8 && + av2_encoder_decoder_model_get_compressed_size(frame_compressed_bytes, + &compressed_size); + if (model_accounting_valid) { + dfg_bits = dfg_bytes * 8 + dfg_prefix_bits; + } + } // update level_stats // TODO(kyslov@) fix the implementation according to buffer model for (int i = 0; i < seq_params->operating_points_cnt_minus_1 + 1; ++i) { @@ -1642,8 +3150,6 @@ void av2_update_level_info(AV2_COMP *cpi, size_t size, int64_t ts_start, AV2LevelInfo *const level_info = level_params->level_info[i]; assert(level_info != NULL); AV2LevelStats *const level_stats = &level_info->level_stats; - // update the multitream scaling factor. - level_params->multi_stream_scaling_x = 0; level_stats->max_tile_size = AVMMAX(level_stats->max_tile_size, max_tile_size); level_stats->max_tile_width = @@ -1686,26 +3192,16 @@ void av2_update_level_info(AV2_COMP *cpi, size_t size, int64_t ts_start, (double)TICKS_PER_SEC; } - if (decode_frame) { + if (has_serialized_frame_unit) { DECODER_MODEL *const decoder_models = level_info->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { - av2_decoder_model_start_frame_decode( - cpi, size << 3, &decoder_models[level], level_spec); - } - } - - // Check whether target level is met. - const AV2_LEVEL target_level = level_params->target_seq_level_idx[i]; - if (target_level < SEQ_LEVELS) { - assert(is_valid_seq_level_idx(target_level)); - const int tier = cpi->tier[i]; - const TARGET_LEVEL_FAIL_ID fail_id = check_level_constraints( - cpi, level_info, target_level, tier, is_still_picture, profile, 0); - if (fail_id != TARGET_LEVEL_OK) { - avm_internal_error(&cm->error, AVM_CODEC_ERROR, - "Failed to encode to the target level %s. %s", - level_string[target_level], - level_fail_messages[fail_id]); + if (decoder_models[level].status != DECODER_MODEL_OK) continue; + if (!model_accounting_valid) { + decoder_models[level].status = DECODER_MODEL_INTERNAL_ERROR; + } else { + av2_decoder_model_start_frame_decode(cpi, dfg_bits, compressed_size, + &decoder_models[level]); + } } } } @@ -1726,8 +3222,14 @@ avm_codec_err_t av2_get_seq_level_idx(const AV2_COMP *cpi, assert(level_info != NULL); for (int level = 0; level < SEQ_LEVELS; ++level) { if (!is_valid_seq_level_idx(level)) continue; + if ((tier != 0 || (level_params->multi_stream_scaling_x != 0.0 && + level_params->multi_stream_scaling_x != 1.0)) && + level < SEQ_LEVEL_4_0) { + continue; + } const TARGET_LEVEL_FAIL_ID fail_id = check_level_constraints( cpi, level_info, level, tier, is_still_picture, profile, 1); + if (fail_id == DECODER_MODEL_UNAVAILABLE) break; if (fail_id == TARGET_LEVEL_OK) { seq_level_idx[op] = level; break; diff --git a/av2/common/level.h b/av2/common/level.h index caaa89f4a2..d3d0f7e34e 100644 --- a/av2/common/level.h +++ b/av2/common/level.h @@ -14,6 +14,7 @@ #define AVM_AV2_ENCODER_LEVEL_H_ #include "av2/common/av2_common_int.h" +#include "av2/common/decoder_model.h" struct AV2_COMP; @@ -83,13 +84,45 @@ typedef struct { double min_cr; } AV2LevelStats; -// The following data structures are for the decoder model. +// The following data structures are for the encoder-side decoder model. +// Presentation state is private to this model and never owns an encoder image +// or changes RefCntBuffer::frame_output_done. typedef struct { - int decoder_ref_count; - int player_ref_count; + bool valid; + bool implicit_output_eligible; + bool normative_output_done; + bool restricted; + bool leading_frame; + bool random_access_point; + bool presentation_time_present; + uint64_t generation; + uint64_t temporal_unit_index; + uint64_t output_order; + uint64_t order_hint; + uint64_t output_luma_samples; + uint64_t presentation_time_ticks; + uint64_t rap_epoch; + double decode_completion_time; + int64_t source_frame_unit_index; + int buffer_index; + int xlayer_id; + int mlayer_id; + int temporal_id; +} ENCODER_DM_PRESENTATION_DESCRIPTOR; + +typedef struct { + bool valid; + uint64_t rap_epoch; + double presentation_offset; +} ENCODER_DM_RAP_PRESENTATION_ANCHOR; + +typedef struct { + uint32_t decoder_ref_count; + uint32_t player_ref_count; int display_index; FRAME_TYPE frame_type; double presentation_time; + ENCODER_DM_PRESENTATION_DESCRIPTOR presentation; } FRAME_BUFFER; // Interval of bits transmission for a DFG(Decodable Frame Group). @@ -100,14 +133,26 @@ typedef struct { // the smoothing buffer. Removal time is essentially the time when the // decoding of the frame starts. double removal_time; + uint64_t coded_bits; } DFG_INTERVAL; -#define DFG_INTERVAL_QUEUE_SIZE 64 typedef struct { - int head; - int size; + bool valid; + double removal_time; + uint64_t luma_sample_count; + uint32_t decode_count; + uint32_t num_tiles; + int64_t compressed_size; + uint64_t frame_symbol_count; +} ENCODER_DECODER_MODEL_FRAME; + +typedef struct { + size_t head; + size_t size; + size_t capacity; double total_interval; - DFG_INTERVAL buf[DFG_INTERVAL_QUEUE_SIZE]; + uint64_t total_bits; + DFG_INTERVAL *buf; } DFG_INTERVAL_QUEUE; enum { @@ -124,26 +169,61 @@ enum { SMOOTHING_BUFFER_OVERFLOW, DECODER_MODEL_DISABLED, DECODER_MODEL_MULTIPLE_XLAYERS, + DECODER_MODEL_UNSUPPORTED, + DECODER_MODEL_INCOMPLETE, + DECODER_MODEL_INTERNAL_ERROR, } UENUM1BYTE(DECODER_MODEL_STATUS); +enum { + ENCODER_DM_RESULT_PASS = 0, + ENCODER_DM_RESULT_VIOLATION, + ENCODER_DM_RESULT_UNAVAILABLE, +} UENUM1BYTE(ENCODER_DM_RESULT_CLASS); + typedef struct { DECODER_MODEL_STATUS status; DECODER_MODEL_MODE mode; bool is_low_delay_mode; + bool initialized; + bool is_still_picture; AV2_LEVEL level; + int operating_point; + int tier; + Av2DmLevelLimits level_limits; int encoder_buffer_delay; // In units of 1/90000 seconds. int decoder_buffer_delay; // In units of 1/90000 seconds. int num_ticks_per_picture; int initial_display_delay; // In units of frames. - int64_t decode_rate; + uint32_t multistream_scale_numerator; + uint32_t multistream_scale_denominator; + double decode_rate; double display_clock_tick; // In units of seconds. double current_time; // In units of seconds. double initial_presentation_delay; // In units of seconds. - double bit_rate; // Bits per second. - - int num_frame; - int num_decoded_frame; - int num_shown_frame; + Av2DmRational bit_rate; // Bits per second. + Av2DmRational buffer_size; // Bits. + + int64_t num_frame; + int64_t num_decoded_frame; + int64_t num_shown_frame; + uint64_t next_generation; + uint64_t temporal_unit_index; + bool temporal_unit_started; + bool equal_picture_interval; + bool last_output_temporal_unit_valid; + uint64_t last_output_temporal_unit; + bool last_presentation_offset_valid; + double last_presentation_offset; + bool previous_output_rap_epoch_valid; + uint64_t previous_output_rap_epoch; + uint64_t rap_epoch; + bool olk_encountered; + bool olk_tu_order_hint_valid; + uint64_t olk_tu_order_hint; + uint32_t mirrored_refresh_frame_flags; + ENCODER_DM_PRESENTATION_DESCRIPTOR current_presentation; + ENCODER_DM_RAP_PRESENTATION_ANCHOR + rap_presentation_anchors[BUFFER_POOL_MAX_SIZE + 2]; int vbi[REF_FRAMES]; // Virtual buffer index. FRAME_BUFFER frame_buffer_pool[BUFFER_POOL_MAX_SIZE]; DFG_INTERVAL_QUEUE dfg_interval_queue; @@ -151,16 +231,17 @@ typedef struct { // Information for the DFG(Decodable Frame Group) being processed. double first_bit_arrival_time; double last_bit_arrival_time; - size_t coded_bits; + uint64_t coded_bits; // Information for the frame being processed. double removal_time; double presentation_time; - int decode_samples; - int display_samples; + uint64_t decode_samples; + uint64_t display_samples; - int64_t max_display_rate; - int64_t max_decode_rate; + long double max_display_rate; + long double max_decode_rate; + bool max_decode_rate_satisfy; bool max_tile_rate_satisfy; bool compressed_size_satisfy; bool frame_symbol_count_satisfy; @@ -173,7 +254,17 @@ typedef struct { // Number of shown frames that share the current presentation time (i.e. // belong to the same temporal unit). Reset to 0 when the presentation time // advances to a new temporal unit. - int num_frames_current_tu; + uint64_t num_frames_current_tu; + + ENCODER_DECODER_MODEL_FRAME pending_frame; + bool last_frame_parsing_time_valid; + double last_frame_parsing_time; + bool last_frame_parsing_time_at_decode_limit; + uint64_t last_frame_parsing_time_decode_luma_samples; + bool frame_constraints_finalized; + bool last_display_duration_valid; + double last_display_duration; + bool finalized; // Tracks whether every inter-TU presentation interval satisfies the minimum // required by the spec (§E.3.2). @@ -217,12 +308,25 @@ static INLINE int is_in_operating_point(int operating_point, int tlayer_id, } int level_to_sub_stream_level_index(AV2_LEVEL level, double scaling_factor_x); +// Validated read-only Annex A lookups used by the decoder-model verifier. +int av2_get_level_compression_basis(int level_index, int tier, + uint32_t *compression_basis); +int av2_get_substream_level_spec(int level_index, uint32_t scale_numerator, + uint32_t scale_denominator, + AV2SubstreamLevelSpec *level_spec); + void av2_init_level_info(struct AV2_COMP *cpi); +void av2_encoder_decoder_model_finish_for_operating_points( + const struct AV2_COMP *cpi); +void av2_encoder_check_target_level(struct AV2_COMP *cpi, + bool all_operating_points); bool is_filter_enabled_frame(const AV2_COMMON *const cm); -void av2_update_level_info(struct AV2_COMP *cpi, size_t size, int64_t ts_start, - int64_t ts_end, int decode_frame); +void av2_update_level_info(struct AV2_COMP *cpi, const uint8_t *data, + size_t size, int64_t ts_start, int64_t ts_end, + bool has_serialized_frame_unit, + uint64_t dfg_prefix_bits); // Compression ratio of current frame. double av2_get_compression_ratio(const AV2_COMMON *const cm, @@ -237,12 +341,71 @@ avm_codec_err_t av2_get_seq_level_idx(const struct AV2_COMP *cpi, void av2_decoder_model_init(const struct AV2_COMP *const cpi, AV2_LEVEL level, int op_index, DECODER_MODEL *const decoder_model); +void av2_encoder_decoder_model_destroy(DECODER_MODEL *decoder_model); +void av2_encoder_decoder_models_destroy(AV2LevelInfo *level_info); + +// Reserves storage for at least interval_count live DFG intervals. This is an +// internal encoder-model helper exposed for deterministic allocation testing. +bool av2_encoder_decoder_model_reserve_dfg_intervals( + DECODER_MODEL *decoder_model, size_t interval_count); +bool av2_encoder_decoder_model_push_dfg_interval(DECODER_MODEL *decoder_model, + const DFG_INTERVAL *interval); +bool av2_encoder_decoder_model_smoothing_buffer_fits( + const DECODER_MODEL *decoder_model, uint64_t coded_bits, bool *fits); +bool av2_encoder_decoder_model_arrival_fits(const DECODER_MODEL *decoder_model, + uint64_t coded_bits, + double available_duration, + bool *fits); +bool av2_encoder_decoder_model_count_obu_bytes( + const uint8_t *data, size_t data_size, uint64_t *dfg_bytes, + uint64_t *frame_compressed_bytes); +bool av2_encoder_decoder_model_accumulate_dfg_bits(DECODER_MODEL *decoder_model, + uint64_t frame_unit_bits, + bool closes_dfg, + uint64_t *closed_dfg_bits); +bool av2_encoder_decoder_model_get_compressed_size( + uint64_t frame_compressed_bytes, int64_t *compressed_size); +bool av2_encoder_decoder_model_check_frame_constraints( + DECODER_MODEL *decoder_model, const ENCODER_DECODER_MODEL_FRAME *frame, + double frame_parsing_time, bool frame_parsing_time_at_decode_limit, + uint64_t frame_parsing_time_decode_luma_samples); +bool av2_encoder_decoder_model_store_frame_constraints( + DECODER_MODEL *decoder_model, + const ENCODER_DECODER_MODEL_FRAME *current_frame, + bool previous_frame_parsing_time_at_decode_limit); +void av2_encoder_decoder_model_finalize_frame_constraints( + DECODER_MODEL *decoder_model, bool is_still_picture); +void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, + bool is_still_picture); +ENCODER_DM_RESULT_CLASS av2_encoder_decoder_model_classify_status( + DECODER_MODEL_STATUS status); + +// Encoder-internal, model-only helpers corresponding to the invalid-reference +// synchronization at the start of Annex E start_frame_decode() and the +// decoded-generation assignment performed for a newly decoded frame. +bool av2_encoder_decoder_model_sync_invalid_ref_buffers( + const AV2_COMMON *cm, DECODER_MODEL *decoder_model); +bool av2_encoder_decoder_model_capture_current_generation( + const struct AV2_COMP *cpi, DECODER_MODEL *decoder_model, + uint64_t output_luma_samples); + void av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( const struct AV2_COMP *const cpi); -void av2_decoder_model_check_output_frame_for_operating_points( - const struct AV2_COMP *const cpi, int ref_idx, - RefCntBuffer *output_frame_ptr); +void av2_decoder_model_mirror_ref_buffer_for_operating_points( + const struct AV2_COMP *cpi, int ref_idx); + +void av2_decoder_model_observe_displaced_output_for_operating_points( + const struct AV2_COMP *cpi, int ref_idx); + +void av2_decoder_model_observe_restricted_output_for_operating_points( + const struct AV2_COMP *cpi); + +void av2_decoder_model_observe_output_frame_buffers_for_operating_points( + const struct AV2_COMP *cpi, int ref_idx); + +void av2_decoder_model_flush_implicit_output_for_operating_points( + const struct AV2_COMP *cpi, bool olk_limit); // Return max bitrate(bps) for given level. double av2_get_max_bitrate_for_level(AV2_LEVEL level_index, int tier, diff --git a/av2/common/tile_common.h b/av2/common/tile_common.h index 110814f336..aad801580a 100644 --- a/av2/common/tile_common.h +++ b/av2/common/tile_common.h @@ -59,6 +59,8 @@ AV2PixelRect av2_get_tile_rect(const TileInfo *tile_info, // The minimum tile width or height is fixed at one superblock #define MAX_TILE_WIDTH (4096) // Max Tile width in pixels #define MAX_TILE_AREA (4096 * 2304) // Maximum tile area in pixels +// 3840 * 2160 * 60 * 1.1, used by the Annex A tile-rate constraint. +#define MAX_TILE_SIZE_HEADER_RATE_PRODUCT 547430400 // Tile width scaling factors for different levels and tiers // [tier][lev] - values are multiplied by MAX_TILE_WIDTH and divided by 4 diff --git a/av2/common/timing.c b/av2/common/timing.c index f852ba1977..35305cf721 100644 --- a/av2/common/timing.c +++ b/av2/common/timing.c @@ -50,30 +50,46 @@ static int32_t high_kbps[1 << LEVEL_BITS] = { UNDEFINED_RATE, UNDEFINED_RATE, UNDEFINED_RATE, UNDEFINED_RATE }; -/* BitrateProfileFactor, indexed by the profile's factor table row - * (see get_profile_factor_table_row_index() in annexA.c). - * A zero entry indicates an unsupported profile. */ -static int bitrate_profile_factor[1 << PROFILE_BITS] = { - 1, 2, 3, 3, 0, 0, 0, 0 -}; - // Callers treat a zero bitrate as "profile, level, and tier combination not // supported". int64_t av2_max_level_bitrate(BITSTREAM_PROFILE seq_profile, int seq_level_idx, int seq_tier) { - int64_t bitrate; + if (seq_level_idx < 0 || seq_level_idx >= (1 << LEVEL_BITS) || seq_tier < 0 || + seq_tier > 1) { + return 0; + } + AV2ProfileLevelFactors factors; + bool supported_profile = av2_get_profile_level_factors(seq_profile, &factors); + supported_profile &= seq_profile < RESERVED_PROFILES_START; + // Preserve the pre-existing sequence-header setup behavior for the legal + // Configurable profile. Its actual factors require configuration syntax, so + // decoder-model callers must reject it when those factors are unavailable. + if (seq_profile == CONFIGURABLE) { + supported_profile = + av2_get_profile_level_factors(MAIN_420_10_IP0, &factors); + } + if (!supported_profile) return 0; - int profile_factor_row = get_profile_factor_table_row_index(seq_profile); + const int64_t bitrate_kbps = + seq_tier ? high_kbps[seq_level_idx] : main_kbps[seq_level_idx]; + if (bitrate_kbps == INVALID_RATE) return 0; + const int64_t numerator = factors.bitrate_factor_numerator; + const int64_t denominator = factors.bitrate_factor_denominator; + if (bitrate_kbps > INT64_MAX / 1000 / numerator) return 0; + return bitrate_kbps * 1000 * numerator / denominator; +} - if (seq_tier) { - bitrate = - high_kbps[seq_level_idx] * bitrate_profile_factor[profile_factor_row]; - } else { - bitrate = - main_kbps[seq_level_idx] * bitrate_profile_factor[profile_factor_row]; +int av2_get_level_base_bitrate_kbps(int seq_level_idx, int seq_tier, + uint32_t *bitrate_kbps) { + if (bitrate_kbps == NULL || seq_level_idx < 0 || + seq_level_idx >= SEQ_LEVELS || seq_tier < 0 || seq_tier > 1) { + return 0; } - - return bitrate * 1000; + const int32_t bitrate = + seq_tier == 0 ? main_kbps[seq_level_idx] : high_kbps[seq_level_idx]; + if (bitrate <= 0 || bitrate == UNDEFINED_RATE) return 0; + *bitrate_kbps = (uint32_t)bitrate; + return 1; } void av2_set_avm_dec_model_info(avm_dec_model_info_t *decoder_model) { diff --git a/av2/common/timing.h b/av2/common/timing.h index 51610b1e3d..5141b0debe 100644 --- a/av2/common/timing.h +++ b/av2/common/timing.h @@ -50,4 +50,9 @@ void av2_set_resource_availability_parameters( int64_t av2_max_level_bitrate(BITSTREAM_PROFILE seq_profile_idc, int seq_level_idx, int seq_tier); +// Returns the Annex A base bitrate in kbps before applying a profile factor. +// Only defined level indices 0..SEQ_LEVELS-1 and tiers 0 or 1 are accepted. +int av2_get_level_base_bitrate_kbps(int seq_level_idx, int seq_tier, + uint32_t *bitrate_kbps); + #endif // AVM_AV2_COMMON_TIMING_H_ diff --git a/av2/decoder/annexF.c b/av2/decoder/annexF.c index 4c3e00fc8d..3ef9511147 100644 --- a/av2/decoder/annexF.c +++ b/av2/decoder/annexF.c @@ -495,6 +495,81 @@ int av2_sbe_should_retain_obu(const SubBitstreamExtractionState *sbe, return 1; // Retain: layer combination is in retention map } +static void retain_all_layers(SubBitstreamExtractionState *sbe, int xlayer_id) { + sbe->xlayer_is_selected[xlayer_id] = 1; + for (int m = 0; m < MAX_NUM_MLAYERS; ++m) { + for (int t = 0; t < MAX_NUM_TLAYERS; ++t) { + sbe->retention_map[xlayer_id][m][t] = 1; + } + } +} + +static void retain_operating_point_layers(SubBitstreamExtractionState *sbe, + const OperatingPointSet *ops, + const OperatingPoint *op, + int xlayer_id) { + sbe->xlayer_is_selected[xlayer_id] = 1; + if (ops->ops_mlayer_info_idc == 0) { + retain_all_layers(sbe, xlayer_id); + return; + } + + const int mlayer_map = op->mlayer_info.ops_mlayer_map[xlayer_id]; + for (int m = 0; m < MAX_NUM_MLAYERS; ++m) { + if ((mlayer_map & (1 << m)) == 0) continue; + const int tlayer_map = op->mlayer_info.ops_tlayer_map[xlayer_id][m]; + for (int t = 0; t < MAX_NUM_TLAYERS; ++t) { + if (tlayer_map & (1 << t)) { + sbe->retention_map[xlayer_id][m][t] = 1; + } + } + } +} + +int av2_sbe_configure_decoder_model_scope(SubBitstreamExtractionState *sbe, + int xlayer_id, + const OperatingPointSet *ops, + int op_index, int whole_xlayer) { + if (sbe == NULL || xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return 0; + + memset(sbe, 0, sizeof(*sbe)); + sbe->extraction_enabled = 1; + av2_sbe_init(sbe); + sbe->retention_map_ready = 1; + + // Annex F retains global structural OBUs in extracted multistreams. Setting + // the global base-layer entry is harmless for singlestreams, which contain + // no such OBU, and makes the membership decision independent of parse order. + sbe->xlayer_is_selected[GLOBAL_XLAYER_ID] = 1; + sbe->retention_map[GLOBAL_XLAYER_ID][0][0] = 1; + + if (whole_xlayer) { + if (xlayer_id == GLOBAL_XLAYER_ID) return 0; + retain_all_layers(sbe, xlayer_id); + return 1; + } + + if (ops == NULL || !ops->valid || op_index < 0 || op_index >= ops->ops_cnt) { + return 0; + } + + const OperatingPoint *const op = &ops->op[op_index]; + if (ops->obu_xlayer_id != GLOBAL_XLAYER_ID) { + if (ops->obu_xlayer_id != xlayer_id) return 0; + retain_operating_point_layers(sbe, ops, op, xlayer_id); + return 1; + } + + // Annex E checks each xlayer selected by a global operating point + // independently. The caller creates one scope per selected xlayer. + if (xlayer_id == GLOBAL_XLAYER_ID || + (op->ops_xlayer_map & (1 << xlayer_id)) == 0) { + return 0; + } + retain_operating_point_layers(sbe, ops, op, xlayer_id); + return 1; +} + // Step 5 fallback: extract profile/level/tier from sequence header. void av2_sbe_extract_seq_header_params(SubBitstreamExtractionState *sbe, int xlayer_id, int seq_profile_idc, diff --git a/av2/decoder/annexF.h b/av2/decoder/annexF.h index c197bb0621..9a8cf089f3 100644 --- a/av2/decoder/annexF.h +++ b/av2/decoder/annexF.h @@ -79,6 +79,7 @@ typedef struct SubBitstreamExtractionState { } SubBitstreamExtractionState; struct AV2Decoder; +struct OperatingPointSet; // clang-format off // Operating point selection and analysis process (Annex F, Section F.3.1): @@ -143,6 +144,14 @@ int av2_sbe_should_retain_obu(const SubBitstreamExtractionState *sbe, OBU_TYPE obu_type, int obu_xlayer_id, int obu_mlayer_id, int obu_tlayer_id); +// Build the exact Annex F retention map used to account CodedBits for one +// decoder-model scope. A whole-xlayer scope retains every embedded and temporal +// layer of xlayer_id. An operating-point scope uses op_index from ops. +int av2_sbe_configure_decoder_model_scope(SubBitstreamExtractionState *sbe, + int xlayer_id, + const struct OperatingPointSet *ops, + int op_index, int whole_xlayer); + // Step 5 fallback: extract profile/level/tier from sequence header // when no OPS or LCR provides this information. void av2_sbe_extract_seq_header_params(SubBitstreamExtractionState *sbe, diff --git a/av2/decoder/decodeframe.c b/av2/decoder/decodeframe.c index 4b785a8d36..69108e0250 100644 --- a/av2/decoder/decodeframe.c +++ b/av2/decoder/decodeframe.c @@ -73,6 +73,7 @@ #include "av2/decoder/decodeframe.h" #include "av2/decoder/decodemv.h" #include "av2/decoder/decoder.h" +#include "av2/decoder/decoder_model.h" #include "av2/decoder/decodetxb.h" #include "av2/decoder/detokenize.h" #include "av2/decoder/obu.h" @@ -4787,6 +4788,7 @@ static const uint8_t *decode_tiles(AV2Decoder *pbi, const uint8_t *data, td->dcb.xd.current_base_qindex = cm->quant_params.base_qindex; setup_bool_decoder(tile_bs_buf->data, data_end, tile_bs_buf->size, &cm->error, td->bit_reader, allow_update_cdf); + td->bit_reader->count_frame_symbols = pbi->decoder_model_verifier != NULL; #if CONFIG_ACCOUNTING if (pbi->acct_enabled) { td->bit_reader->accounting = &pbi->accounting; @@ -4862,6 +4864,7 @@ static AVM_INLINE void tile_worker_hook_init( setup_bool_decoder(tile_buffer->data, thread_data->data_end, tile_buffer->size, &thread_data->error_info, td->bit_reader, allow_update_cdf); + td->bit_reader->count_frame_symbols = pbi->decoder_model_verifier != NULL; #if CONFIG_ACCOUNTING if (pbi->acct_enabled) { td->bit_reader->accounting = &pbi->accounting; @@ -6919,6 +6922,10 @@ static void reset_buffer_other_than_OLK(AV2Decoder *pbi) { } } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_olk_reference_invalidation(pbi); + } + for (int layer = 0; layer <= seq_params->max_mlayer_id; layer++) { cm->olk_refresh_frame_flags[layer] = -1; cm->olk_co_vcl_refresh_frame_flags[layer] = -1; @@ -7648,6 +7655,10 @@ static void handle_sequence_header(AV2Decoder *pbi, OBU_TYPE obu_type, "Sequence Header changed at %s", avm_obu_type_to_string(obu_type)); } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_active_configuration(pbi, xlayer_id, + seq_header_id); + } return; } @@ -7748,6 +7759,10 @@ static void handle_sequence_header(AV2Decoder *pbi, OBU_TYPE obu_type, check_lcr_layer_map_conformance(pbi, xlayer_id); // check dependency map consistency for OPS check_ops_layer_map_conformance(pbi, xlayer_id); + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_active_configuration(pbi, xlayer_id, + seq_header_id); + } } static int is_reference_mapping_consistent( @@ -9548,6 +9563,12 @@ int32_t av2_read_tilegroup_header( *first_tile_group_in_frame = is_first_tile_group; if (is_first_tile_group) { + if (pbi->decoder_model_verifier != NULL) { + cm->features.frame_symbol_count = 0; + for (int tile = 0; tile < pbi->allocated_tiles; ++tile) { + pbi->tile_data[tile].bit_reader.frame_symbol_count = 0; + } + } #if CONFIG_MISMATCH_DEBUG mismatch_move_frame_idx_r(1); #endif // CONFIG_MISMATCH_DEBUG @@ -9839,6 +9860,25 @@ void av2_decode_tg_tiles_and_wrapup(AV2Decoder *pbi, const uint8_t *data, return; } + if (pbi->decoder_model_verifier != NULL) { + uint64_t frame_symbol_count = 0; + for (int tile = 0; tile < tiles->rows * tiles->cols; ++tile) { + const uint64_t tile_symbol_count = + pbi->tile_data[tile].bit_reader.frame_symbol_count; + if (UINT64_MAX - frame_symbol_count < tile_symbol_count) { + av2_decoder_model_verifier_on_accounting_failure(pbi); + frame_symbol_count = UINT64_MAX; + break; + } + frame_symbol_count += tile_symbol_count; + } + cm->features.frame_symbol_count = frame_symbol_count; + } + + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_frame_wrapup_start(pbi); + } + av2_alloc_cdef_buffers(cm, &pbi->cdef_worker, &pbi->cdef_sync, pbi->num_workers); av2_alloc_cdef_sync(cm, &pbi->cdef_sync, pbi->num_workers); diff --git a/av2/decoder/decoder.c b/av2/decoder/decoder.c index d60d319113..48dc902aac 100644 --- a/av2/decoder/decoder.c +++ b/av2/decoder/decoder.c @@ -40,6 +40,7 @@ #include "av2/decoder/decodeframe.h" #include "av2/decoder/decoder.h" +#include "av2/decoder/decoder_model.h" #include "av2/decoder/detokenize.h" #include "av2/decoder/obu.h" @@ -393,6 +394,8 @@ void av2_decoder_remove(AV2Decoder *pbi) { if (!pbi) return; + av2_decoder_model_verifier_destroy(pbi); + avm_get_worker_interface()->end(&pbi->lf_worker); avm_free(pbi->lf_worker.data1); @@ -582,14 +585,28 @@ static void release_current_frame(AV2Decoder *pbi) { cm->cur_frame = NULL; } +static void queue_output_frame(AV2Decoder *pbi, RefCntBuffer *frame, + int frame_to_show_map_idx, + Av2DmPresentationOwner presentation_owner) { + assign_output_frame_buffer_p(&pbi->output_frames[pbi->num_output_frames++], + frame); + frame->frame_output_done = 1; + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_output(pbi, frame_to_show_map_idx, frame, + presentation_owner); + } +} + // This function flushes out the DPB, all the slots in the dpb is free to use. avm_codec_err_t flush_remaining_frames(struct AV2Decoder *pbi, int order_hint_limit) { avm_codec_err_t res = AVM_CODEC_OK; AV2_COMMON *const cm = &pbi->common; RefCntBuffer *output_candidate = NULL; + int output_candidate_ref_idx = -1; do { output_candidate = NULL; + output_candidate_ref_idx = -1; for (int i = 0; i < REF_FRAMES; i++) { RefCntBuffer *const buf = cm->ref_frame_map[i]; if (buf == NULL) continue; @@ -600,15 +617,15 @@ avm_codec_err_t flush_remaining_frames(struct AV2Decoder *pbi, derive_output_order_idx(cm, buf) <= derive_output_order_idx(cm, output_candidate))) { output_candidate = buf; + output_candidate_ref_idx = i; } } if (output_candidate != NULL) { if (pbi->num_output_frames >= (REF_FRAMES + 1) * AVM_MAX_NUM_STREAMS) { return AVM_CODEC_MEM_ERROR; } - assign_output_frame_buffer_p( - &pbi->output_frames[pbi->num_output_frames++], output_candidate); - output_candidate->frame_output_done = 1; + queue_output_frame(pbi, output_candidate, output_candidate_ref_idx, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); } } while (output_candidate != NULL); return res; @@ -633,6 +650,7 @@ int av2_output_frame_buffers(AV2Decoder *pbi, int ref_idx) { AV2_COMMON *const cm = &pbi->common; RefCntBuffer *trigger_frame = NULL; RefCntBuffer *output_candidate = NULL; + int output_candidate_ref_idx = -1; int doh_error = 0; // Determine if the triggering frame is the current frame or a frame @@ -642,20 +660,21 @@ int av2_output_frame_buffers(AV2Decoder *pbi, int ref_idx) { // Add the previous frames into the output queue. do { output_candidate = trigger_frame; + output_candidate_ref_idx = ref_idx; for (int i = 0; i < cm->seq_params.ref_frames; i++) { if (is_frame_eligible_for_output(cm->ref_frame_map[i]) && derive_output_order_idx(cm, cm->ref_frame_map[i]) < derive_output_order_idx(cm, output_candidate)) { output_candidate = cm->ref_frame_map[i]; + output_candidate_ref_idx = i; } } if (output_candidate != trigger_frame) { if (cm->seq_params.monotonic_output_order_flag == 0) { doh_error |= check_and_update_output_doh(pbi, output_candidate); } - assign_output_frame_buffer_p( - &pbi->output_frames[pbi->num_output_frames++], output_candidate); - output_candidate->frame_output_done = 1; + queue_output_frame(pbi, output_candidate, output_candidate_ref_idx, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); #if CONFIG_BITSTREAM_DEBUG avm_bitstream_queue_set_frame_read( derive_output_order_idx(cm, output_candidate) * 2 + 1); @@ -670,9 +689,12 @@ int av2_output_frame_buffers(AV2Decoder *pbi, int ref_idx) { // Add the output triggering frame into the output queue. doh_error |= check_and_update_output_doh(pbi, trigger_frame); } - assign_output_frame_buffer_p(&pbi->output_frames[pbi->num_output_frames++], - trigger_frame); - trigger_frame->frame_output_done = 1; + const int trigger_model_ref_idx = + ref_idx >= 0 ? ref_idx + : (cm->show_existing_frame ? cm->sef_ref_fb_idx : -1); + queue_output_frame(pbi, trigger_frame, trigger_model_ref_idx, + ref_idx < 0 ? AV2_DM_PRESENTATION_OWNER_CURRENT + : AV2_DM_PRESENTATION_OWNER_IMPLICIT); #if CONFIG_BITSTREAM_DEBUG if (trigger_frame->order_hint != cm->cur_frame->order_hint) { @@ -701,10 +723,8 @@ int av2_output_frame_buffers(AV2Decoder *pbi, int ref_idx) { if (cm->seq_params.monotonic_output_order_flag == 0) { doh_error |= check_and_update_output_doh(pbi, cm->ref_frame_map[i]); } - assign_output_frame_buffer_p( - &pbi->output_frames[pbi->num_output_frames++], - cm->ref_frame_map[i]); - cm->ref_frame_map[i]->frame_output_done = 1; + queue_output_frame(pbi, cm->ref_frame_map[i], i, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); successive_output++; #if CONFIG_BITSTREAM_DEBUG avm_bitstream_queue_set_frame_read( @@ -772,6 +792,10 @@ static void update_frame_buffers(AV2Decoder *pbi, int frame_decoded) { } ++ref_index; } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_after_reference_update( + pbi, (uint32_t)cm->current_frame.refresh_frame_flags); + } update_subgop_stats(cm, &pbi->subgop_stats, cm->cur_frame->order_hint, pbi->enable_subgop_stats); if (((cm->immediate_output_picture && !cm->cur_frame->frame_output_done) || @@ -872,6 +896,14 @@ int av2_receive_compressed_data(AV2Decoder *pbi, size_t size, return 1; } + if (frame_decoded) { + // The suffix-OBU loop has completed, so CodedBits for this frame unit is + // now exact. Show-existing units leave the pending DFG open by definition. + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_frame_unit_complete(pbi); + } + } + #if TXCOEFF_TIMER cm->cum_txcoeff_timer += cm->txcoeff_timer; fprintf(stderr, diff --git a/av2/decoder/decoder.h b/av2/decoder/decoder.h index 5672354c17..7efc240f2d 100644 --- a/av2/decoder/decoder.h +++ b/av2/decoder/decoder.h @@ -411,6 +411,10 @@ typedef struct AV2Decoder { DecOperatingPointParams dec_op_params; // Sub-bitstream extraction state (Annex F) SubBitstreamExtractionState sbe_state; + struct Av2DecoderModelVerifier *decoder_model_verifier; + int decoder_model_check_mode; + bool decoder_model_verifier_allocation_failed; + bool decoder_model_verifier_allocation_reported; int seen_frame_header; // The expected start_tile (tg_start syntax element) of the next tile group. int next_start_tile; diff --git a/av2/decoder/decoder_model.c b/av2/decoder/decoder_model.c new file mode 100644 index 0000000000..271593a75d --- /dev/null +++ b/av2/decoder/decoder_model.c @@ -0,0 +1,3634 @@ +/* + * 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 "av2/decoder/decoder_model.h" + +#include +#include +#include +#include +#include +#include + +#include "avm/avm_codec.h" +#include "avm/avmdx.h" +#include "avm_mem/avm_mem.h" +#include "av2/common/annexA.h" +#include "av2/common/av2_common_int.h" +#include "av2/common/level.h" +#include "av2/decoder/annexF.h" +#include "av2/decoder/decoder.h" + +typedef enum Av2DmAdapterEventType { + AV2_DM_ADAPTER_RAW_OBU, + AV2_DM_ADAPTER_SEQUENCE_HEADER, + AV2_DM_ADAPTER_OPERATING_POINT_SET, + AV2_DM_ADAPTER_ACTIVE_CONFIGURATION, + AV2_DM_ADAPTER_BUFFER_REMOVAL_TIMING, + AV2_DM_ADAPTER_TEMPORAL_POINT, + AV2_DM_ADAPTER_FRAME_WRAPUP_START, + AV2_DM_ADAPTER_FRAME_UNIT_COMPLETE, + AV2_DM_ADAPTER_OLK_REFERENCE_INVALIDATION, + AV2_DM_ADAPTER_REFERENCE_UPDATE, + AV2_DM_ADAPTER_OUTPUT, + AV2_DM_ADAPTER_RECOVERY_RESET, + AV2_DM_ADAPTER_STREAM_CONFIGURATION_CHANGE, + AV2_DM_ADAPTER_FINISH +} Av2DmAdapterEventType; + +typedef enum Av2DmContextEventType { + AV2_DM_CONTEXT_FRAME, + AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION, + AV2_DM_CONTEXT_REFERENCE_UPDATE, + AV2_DM_CONTEXT_OUTPUT, + AV2_DM_CONTEXT_RECOVERY_RESET +} Av2DmContextEventType; + +typedef enum Av2DmIndeterminateReason { + AV2_DM_REASON_NONE, + AV2_DM_REASON_MISSING_REQUIRED_INPUT, + AV2_DM_REASON_MISSING_ACTIVE_CONFIGURATION, + AV2_DM_REASON_INCOMPLETE_EXTRACTION, + AV2_DM_REASON_MISSING_FRAME_GENERATION, + AV2_DM_REASON_MISSING_PRESENTATION_PROVENANCE, + AV2_DM_REASON_MISSING_PRESENTATION_TIMING, + AV2_DM_REASON_INCOMPLETE_RAS_SEED, + AV2_DM_REASON_RECOVERY_RESET, + AV2_DM_REASON_INTERNAL_FAILURE +} Av2DmIndeterminateReason; + +typedef struct Av2DmRasSeedSnapshot { + uint32_t ref_index; + uint64_t generation; + int xlayer_id; + int mlayer_id; + int temporal_id; + bool generation_valid; +} Av2DmRasSeedSnapshot; + +typedef struct Av2DmPendingObu { + int obu_type; + int xlayer_id; + int mlayer_id; + int temporal_id; + uint64_t bits; + uint64_t frame_unit_index; + uint64_t temporal_unit_index; + uint64_t event_index; + bool decoder_retained; +} Av2DmPendingObu; + +typedef struct Av2DmAdapterEvent { + Av2DmAdapterEventType type; + uint64_t index; + uint64_t record_index; + uint64_t value; +} Av2DmAdapterEvent; + +typedef struct Av2DmSequenceRecord { + int xlayer_id; + int sequence_header_id; + SequenceHeader sequence; +} Av2DmSequenceRecord; + +typedef struct Av2DmOpsRecord { + int xlayer_id; + int ops_id; + OperatingPointSet ops; +} Av2DmOpsRecord; + +typedef struct Av2DmBrtRecord { + int xlayer_id; + BufferRemovalTimingInfo brt; +} Av2DmBrtRecord; + +typedef struct Av2DmActiveConfigurationRecord { + int xlayer_id; + int sequence_header_id; + SequenceHeader sequence; + ContentInterpretation ci[MAX_NUM_MLAYERS]; +} Av2DmActiveConfigurationRecord; + +typedef struct Av2DmFrameSnapshot { + bool valid; + bool generation_valid; + uint64_t source_frame_unit_index; + uint64_t event_index; + uint64_t temporal_unit_index; + uint64_t parameter_generation; + uint64_t stream_generation; + uint64_t generation; + uint32_t ref_valid_mask; + int obu_type; + int xlayer_id; + int mlayer_id; + int temporal_id; + bool show_existing_frame; + bool implicit_output_frame; + bool leading_frame; + bool frame_is_intra; + bool allow_global_intrabc; + bool inloop_filtering_enabled; + uint32_t frame_width; + uint32_t frame_height; + uint64_t output_luma_samples; + uint32_t num_tiles; + uint32_t tile_columns; + uint64_t max_tile_width; + uint64_t max_tile_area; + bool non_rightmost_tile_width_valid; + uint64_t frame_symbol_count; + bool presentation_time_present; + uint64_t presentation_time_ticks; + bool ras_seed_complete; + uint32_t ras_seed_count; + Av2DmRasSeedSnapshot ras_seeds[AV2_DM_MAX_REF_FRAMES]; + bool multistream_decoder_mode; + MultistreamDecoderOperation msdo; + int multistream_even_allocation; + int multistream_large_picture_index; + int num_streams; + int stream_ids[AVM_MAX_NUM_STREAMS]; +} Av2DmFrameSnapshot; + +typedef struct Av2DmGenerationRecord { + const RefCntBuffer *buffer; + uint64_t generation; + uint64_t source_frame_unit_index; + uint64_t temporal_unit_index; + int xlayer_id; + int mlayer_id; + int temporal_id; + uint32_t width; + uint32_t height; + uint64_t output_luma_samples; + bool leading_frame; + bool random_access_point; + bool presentation_time_present; + uint64_t presentation_time_ticks; + bool presentation_timing_config_valid; + bool equal_picture_interval; + bool implicit_presentation_pending; +} Av2DmGenerationRecord; + +typedef struct Av2DmContextEvent { + Av2DmContextEventType type; + uint64_t event_index; + uint64_t source_frame_unit_index; + uint64_t presentation_frame_unit_index; + int presentation_xlayer_id; + int presentation_mlayer_id; + int presentation_tlayer_id; + uint64_t parameter_generation; + uint64_t stream_generation; + uint64_t generation; + int frame_obu_type; + bool leading_frame; + bool config_present; + Av2DmConfig config; + Av2DmFrameEvent frame; + Av2DmReferenceUpdateEvent reference_update; + bool set_initial_presentation_delay; + uint32_t ref_valid_mask; + Av2DmOutputEvent output; + uint32_t ras_seed_count; + Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; + bool ras_seed_complete; + Av2DmIndeterminateReason indeterminate_reason; +} Av2DmContextEvent; + +typedef struct Av2DmContextKey { + int xlayer_id; + int ops_xlayer_id; + int ops_id; + int operating_point; + bool whole_xlayer; +} Av2DmContextKey; + +typedef struct Av2DmLiveRun Av2DmLiveRun; + +typedef struct Av2DmContext { + Av2DmContextKey key; + bool active; + SubBitstreamExtractionState membership; + uint64_t pending_dfg_bits; + bool pending_after_event_valid; + uint64_t pending_after_event; + uint64_t last_closed_dfg_bits; + uint64_t closed_dfgs; + uint64_t configuration_generation; + uint64_t active_sequence_record; + uint64_t active_ops_record; + uint64_t active_configuration_record; + Av2DmApplicability applicability; + bool incomplete_extraction; + bool recovery_reset_pending; + Av2DmLiveRun **runs; + size_t run_count; + size_t run_capacity; + Av2DmContextEvent *prefix_events; + size_t prefix_event_count; + size_t prefix_event_capacity; + bool last_config_present; + Av2DmConfig last_config; + uint64_t last_stream_generation; + bool last_ras_seed_complete; + uint32_t last_ras_seed_count; +} Av2DmContext; + +typedef struct Av2DmCvsAggregate { + bool open; + uint64_t number; + uint64_t violations; + uint64_t run_status_count[4]; + bool verification_complete; + Av2DmIndeterminateReason reason; +} Av2DmCvsAggregate; + +typedef enum Av2DmVerifierErrorCode { + AV2_DM_VERIFIER_ERROR_NONE, + AV2_DM_VERIFIER_ERROR_ALLOCATION, + AV2_DM_VERIFIER_ERROR_ARITHMETIC, + AV2_DM_VERIFIER_ERROR_INTERNAL_STATE, +} Av2DmVerifierErrorCode; + +struct Av2DecoderModelVerifier { + bool failed; + bool finished; + bool fatal_violation; + bool aggregate_incomplete; + bool error_emitted; + int check_mode; + Av2DmVerifierErrorCode error_code; + bool temporal_point_present; + bool temporal_unit_has_obu; + uint64_t temporal_point; + uint64_t raw_obus; + uint64_t raw_bits; + uint64_t temporal_unit_index; + uint64_t frame_unit_index; + uint64_t source_frame_unit_index; + bool source_frame_unit_started; + uint64_t closed_dfgs; + uint64_t temporal_points; + uint64_t parameter_generation; + uint64_t stream_generation; + uint64_t next_generation; + uint64_t frame_starts; + uint64_t reference_updates; + uint64_t olk_invalidations; + uint64_t outputs; + uint64_t last_frame_start_event; + uint64_t last_reference_update_event; + uint64_t last_olk_invalidation_event; + uint64_t last_output_event; + uint64_t last_output_callback_frame_unit; + uint64_t last_output_presentation_frame_unit; + uint64_t last_output_presentation_temporal_unit; + uint64_t last_output_generation; + int last_output_presentation_xlayer_id; + int last_output_presentation_mlayer_id; + int last_output_presentation_tlayer_id; + bool last_output_uses_current_presentation; + bool replay_previous_presentation_offset_valid; + Av2DmRational replay_previous_presentation_offset; + bool replay_last_presentation_offset_valid; + Av2DmRational replay_last_presentation_offset; + uint64_t finish_event; + uint64_t result_count; + uint64_t result_status_count[4]; + Av2DmFrameSnapshot pending_frame; + Av2DmFrameSnapshot last_completed_frame; + + Av2DmAdapterEvent last_event; + bool last_event_valid; + size_t event_count; + Av2DmPendingObu *current_tu_obus; + size_t current_tu_obu_count; + size_t current_tu_obu_capacity; + Av2DmSequenceRecord *sequence_records; + size_t sequence_record_count; + size_t sequence_record_capacity; + Av2DmOpsRecord *ops_records; + size_t ops_record_count; + size_t ops_record_capacity; + Av2DmBrtRecord *brt_records; + size_t brt_record_count; + size_t brt_record_capacity; + Av2DmActiveConfigurationRecord *active_records; + size_t active_record_count; + size_t active_record_capacity; + size_t rap_start_count; + bool last_rap_start_valid; + int last_rap_xlayer_id; + int last_rap_mlayer_id; + int last_rap_temporal_id; + uint64_t last_rap_frame_unit_index; + Av2DmContext *contexts; + size_t context_count; + size_t context_capacity; + Av2DmGenerationRecord *generations; + size_t generation_count; + size_t generation_capacity; + bool active_configuration_present[MAX_NUM_XLAYERS]; + uint64_t active_configuration_record[MAX_NUM_XLAYERS]; + uint64_t active_sequence_record[MAX_NUM_XLAYERS]; + bool current_brt_present[MAX_NUM_XLAYERS]; + uint64_t current_brt_record[MAX_NUM_XLAYERS]; + int multistream_even_allocation; + int multistream_large_picture_index; + bool current_source_frame_dispatched; + bool clk_boundary_seen[MAX_NUM_XLAYERS]; + uint64_t clk_boundary_temporal_unit[MAX_NUM_XLAYERS]; + Av2DmCvsAggregate cvs[MAX_NUM_XLAYERS]; + uint64_t bitstream_cvs; + uint64_t bitstream_status_count[4]; + bool first_non_conformant_valid; + int first_non_conformant_xlayer; + uint64_t first_non_conformant_cvs; + bool bitstream_result_emitted; +}; + +static void mark_failed(Av2DecoderModelVerifier *verifier); +static void mark_arithmetic_failed(Av2DecoderModelVerifier *verifier); +static void mark_allocation_failed(Av2DecoderModelVerifier *verifier); +static bool increment_u64(Av2DecoderModelVerifier *verifier, uint64_t *value); +static bool increment_size(Av2DecoderModelVerifier *verifier, size_t *value); +static bool obu_belongs_to_context(const Av2DmContext *context, + const Av2DmPendingObu *obu); +static void dispatch_context_event(Av2DecoderModelVerifier *verifier, + Av2DmContext *context, + const Av2DmContextEvent *event); +static void ensure_cvs_open(Av2DecoderModelVerifier *verifier, int xlayer_id); +static void finish_xlayer_cvs(Av2DecoderModelVerifier *verifier, int xlayer_id); +static void finish_all_cvs(Av2DecoderModelVerifier *verifier); +static void emit_bitstream_result(Av2DecoderModelVerifier *verifier, + bool complete); +static void destroy_context_runs(Av2DmContext *context); + +static void retire_xlayer_generations(Av2DecoderModelVerifier *verifier, + const AV2Decoder *pbi, int xlayer_id) { + size_t write_index = 0; + for (size_t i = 0; i < verifier->generation_count; ++i) { + Av2DmGenerationRecord *const generation = &verifier->generations[i]; + bool referenced = generation->implicit_presentation_pending; + for (int ref = 0; !referenced && ref < AV2_DM_MAX_REF_FRAMES; ++ref) { + referenced = pbi->common.ref_frame_map[ref] == generation->buffer && + pbi->valid_for_referencing[ref]; + } + if (generation->xlayer_id == xlayer_id && !referenced) continue; + if (write_index != i) { + verifier->generations[write_index] = *generation; + } + ++write_index; + } + verifier->generation_count = write_index; +} + +static bool reserve_array(Av2DecoderModelVerifier *verifier, void **array, + size_t *capacity, size_t needed, + size_t element_size) { + if (needed <= *capacity) return true; + size_t new_capacity = *capacity == 0 ? 8 : *capacity; + while (new_capacity < needed) { + if (new_capacity > SIZE_MAX / 2) { + new_capacity = needed; + break; + } + new_capacity *= 2; + } + if (new_capacity > SIZE_MAX / element_size) { + mark_arithmetic_failed(verifier); + return false; + } + void *const resized = avm_malloc(new_capacity * element_size); + if (resized == NULL) { + mark_allocation_failed(verifier); + return false; + } + if (*array != NULL) { + memcpy(resized, *array, *capacity * element_size); + avm_free(*array); + } + *array = resized; + *capacity = new_capacity; + return true; +} + +static void emit_generic_internal_failure_result(void) { + fprintf(stderr, + "AV2_DECODER_MODEL_RESULT status=INDETERMINATE xlayer=-1 ops=-1 " + "op=-1 rap=-1 mode=resource decoded=0 outputs=0 " + "reordered_outputs=0 violations=0 reason=internal_failure\n"); +} + +static void emit_generic_internal_failure_bitstream_result(void) { + fprintf(stderr, + "AV2_DECODER_MODEL_BITSTREAM_RESULT status=INDETERMINATE complete=0 " + "cvs=0 conformant_cvs=0 non_conformant_cvs=0 " + "indeterminate_cvs=0 not_applicable_cvs=0 " + "first_non_conformant_xlayer=-1 first_non_conformant_cvs=0\n"); +} + +static const char *verifier_error_name(Av2DmVerifierErrorCode code) { + switch (code) { + case AV2_DM_VERIFIER_ERROR_ALLOCATION: return "ALLOCATION_FAILURE"; + case AV2_DM_VERIFIER_ERROR_ARITHMETIC: return "ARITHMETIC_FAILURE"; + case AV2_DM_VERIFIER_ERROR_INTERNAL_STATE: + case AV2_DM_VERIFIER_ERROR_NONE: return "INTERNAL_STATE_FAILURE"; + } + return "INTERNAL_STATE_FAILURE"; +} + +static void emit_verifier_error(Av2DecoderModelVerifier *verifier, + Av2DmVerifierErrorCode code, int xlayer_id, + uint64_t cvs) { + if (verifier == NULL || verifier->error_emitted) return; + fprintf(stderr, "AV2_DECODER_MODEL_ERROR code=%s xlayer=%d cvs=%" PRIu64 "\n", + verifier_error_name(code), xlayer_id, cvs); + verifier->error_emitted = true; +} + +static bool add_u64(uint64_t left, uint64_t right, uint64_t *result) { + if (UINT64_MAX - left < right) return false; + *result = left + right; + return true; +} + +static uint32_t real_ref_valid_mask(const AV2Decoder *pbi) { + const AV2_COMMON *const cm = &pbi->common; + uint32_t mask = 0; + const int num_refs = cm->seq_params.ref_frames < AV2_DM_MAX_REF_FRAMES + ? cm->seq_params.ref_frames + : AV2_DM_MAX_REF_FRAMES; + for (int i = 0; i < num_refs; ++i) { + if (cm->ref_frame_map[i] != NULL && pbi->valid_for_referencing[i]) { + mask |= (uint32_t)1 << i; + } + } + return mask; +} + +static Av2DmGenerationRecord *find_generation_by_buffer( + Av2DecoderModelVerifier *verifier, const RefCntBuffer *buffer) { + if (buffer == NULL) return NULL; + for (size_t i = verifier->generation_count; i > 0; --i) { + if (verifier->generations[i - 1].buffer == buffer) { + return &verifier->generations[i - 1]; + } + } + return NULL; +} + +static Av2DmGenerationRecord *find_generation_by_id( + Av2DecoderModelVerifier *verifier, uint64_t generation) { + if (generation == 0) return NULL; + for (size_t i = verifier->generation_count; i > 0; --i) { + if (verifier->generations[i - 1].generation == generation) { + return &verifier->generations[i - 1]; + } + } + return NULL; +} + +static Av2DmGenerationRecord *assign_generation( + Av2DecoderModelVerifier *verifier, const RefCntBuffer *buffer) { + if (buffer == NULL) return NULL; + if (verifier->next_generation == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } + Av2DmGenerationRecord *record = find_generation_by_buffer(verifier, buffer); + if (record == NULL) { + if (verifier->generation_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } + if (!reserve_array(verifier, (void **)&verifier->generations, + &verifier->generation_capacity, + verifier->generation_count + 1, + sizeof(*verifier->generations))) { + mark_failed(verifier); + return NULL; + } + record = &verifier->generations[verifier->generation_count]; + if (!increment_size(verifier, &verifier->generation_count)) return NULL; + } + memset(record, 0, sizeof(*record)); + record->buffer = buffer; + if (!increment_u64(verifier, &verifier->next_generation)) return NULL; + record->generation = verifier->next_generation; + return record; +} + +static void initialize_context_event(const Av2DecoderModelVerifier *verifier, + Av2DmContextEventType type, + Av2DmContextEvent *event) { + memset(event, 0, sizeof(*event)); + event->type = type; + event->event_index = verifier->event_count; + event->source_frame_unit_index = verifier->source_frame_unit_index; + event->parameter_generation = verifier->parameter_generation; + event->stream_generation = verifier->stream_generation; +} + +static bool frame_obu_matches(const Av2DmPendingObu *obu, + const Av2DmFrameSnapshot *frame) { + return obu->frame_unit_index == frame->source_frame_unit_index; +} + +static bool compressed_size_for_context(Av2DecoderModelVerifier *verifier, + const Av2DmContext *context, + const Av2DmFrameSnapshot *frame, + uint64_t *bytes) { + uint64_t bits = 0; + for (size_t i = 0; i < verifier->current_tu_obu_count; ++i) { + const Av2DmPendingObu *const obu = &verifier->current_tu_obus[i]; + if (!av2_obu_counts_toward_compressed_size((OBU_TYPE)obu->obu_type) || + !frame_obu_matches(obu, frame) || + !obu_belongs_to_context(context, obu)) { + continue; + } + if (!add_u64(bits, obu->bits, &bits)) { + mark_arithmetic_failed(verifier); + return false; + } + } + if ((bits & 7) != 0) return false; + *bytes = bits / 8; + return true; +} + +static void retire_completed_frame_obus(Av2DecoderModelVerifier *verifier, + uint64_t source_frame_unit_index) { + size_t write_index = 0; + for (size_t i = 0; i < verifier->current_tu_obu_count; ++i) { + Av2DmPendingObu *const obu = &verifier->current_tu_obus[i]; + if (av2_obu_counts_toward_compressed_size((OBU_TYPE)obu->obu_type) && + obu->frame_unit_index == source_frame_unit_index) { + continue; + } + if (write_index != i) verifier->current_tu_obus[write_index] = *obu; + ++write_index; + } + verifier->current_tu_obu_count = write_index; +} + +static void mark_failed(Av2DecoderModelVerifier *verifier) { + if (verifier == NULL) return; + verifier->failed = true; + if (verifier->error_code == AV2_DM_VERIFIER_ERROR_NONE) { + verifier->error_code = AV2_DM_VERIFIER_ERROR_INTERNAL_STATE; + } +} + +static void mark_failed_with_code(Av2DecoderModelVerifier *verifier, + Av2DmVerifierErrorCode code) { + if (verifier == NULL) return; + verifier->failed = true; + if (verifier->error_code == AV2_DM_VERIFIER_ERROR_NONE) { + verifier->error_code = code; + } +} + +static void mark_arithmetic_failed(Av2DecoderModelVerifier *verifier) { + mark_failed_with_code(verifier, AV2_DM_VERIFIER_ERROR_ARITHMETIC); +} + +static void mark_allocation_failed(Av2DecoderModelVerifier *verifier) { + mark_failed_with_code(verifier, AV2_DM_VERIFIER_ERROR_ALLOCATION); +} + +static bool verifier_accepts_events(const Av2DecoderModelVerifier *verifier) { + return verifier != NULL && !verifier->failed && !verifier->finished && + !verifier->fatal_violation; +} + +static bool increment_u64(Av2DecoderModelVerifier *verifier, uint64_t *value) { + if (*value == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return false; + } + ++*value; + return true; +} + +static void add_u64_saturated(uint64_t *value, uint64_t addend) { + if (UINT64_MAX - *value < addend) { + *value = UINT64_MAX; + return; + } + *value += addend; +} + +static bool increment_size(Av2DecoderModelVerifier *verifier, size_t *value) { + if (*value == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return false; + } + ++*value; + return true; +} + +static Av2DmAdapterEvent *append_event(Av2DecoderModelVerifier *verifier, + Av2DmAdapterEventType type) { + if (verifier == NULL || verifier->failed) { + mark_failed(verifier); + return NULL; + } + if (verifier->event_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } + Av2DmAdapterEvent *const event = &verifier->last_event; + memset(event, 0, sizeof(*event)); + event->type = type; + event->index = verifier->event_count; + if (!increment_size(verifier, &verifier->event_count)) return NULL; + verifier->last_event_valid = true; + return event; +} + +static bool keys_equal(const Av2DmContextKey *left, + const Av2DmContextKey *right) { + return left->xlayer_id == right->xlayer_id && left->ops_id == right->ops_id && + left->ops_xlayer_id == right->ops_xlayer_id && + left->operating_point == right->operating_point && + left->whole_xlayer == right->whole_xlayer; +} + +static Av2DmContext *find_context(Av2DecoderModelVerifier *verifier, + const Av2DmContextKey *key) { + for (size_t i = 0; i < verifier->context_count; ++i) { + if (keys_equal(&verifier->contexts[i].key, key)) { + return &verifier->contexts[i]; + } + } + return NULL; +} + +static bool obu_belongs_to_context(const Av2DmContext *context, + const Av2DmPendingObu *obu) { + return av2_sbe_should_retain_obu(&context->membership, + (OBU_TYPE)obu->obu_type, obu->xlayer_id, + obu->mlayer_id, obu->temporal_id) != 0; +} + +static bool frame_belongs_to_context(const Av2DmContext *context, int xlayer_id, + int mlayer_id, int temporal_id) { + const Av2DmPendingObu frame_obu = { + OBU_REGULAR_TILE_GROUP, xlayer_id, mlayer_id, temporal_id, 0, 0, 0, 0, false + }; + return obu_belongs_to_context(context, &frame_obu); +} + +static const OperatingPoint *context_operating_point( + const Av2DecoderModelVerifier *verifier, const Av2DmContext *context, + const OperatingPointSet **ops_out) { + *ops_out = NULL; + if (context->key.whole_xlayer || + context->active_ops_record >= verifier->ops_record_count) { + return NULL; + } + const OperatingPointSet *const ops = + &verifier->ops_records[context->active_ops_record].ops; + if (context->key.operating_point < 0 || + context->key.operating_point >= ops->ops_cnt) { + return NULL; + } + *ops_out = ops; + return &ops->op[context->key.operating_point]; +} + +static bool build_context_config(const Av2DecoderModelVerifier *verifier, + const Av2DmContext *context, int mlayer_id, + const Av2DmFrameSnapshot *snapshot, + Av2DmConfig *config) { + memset(config, 0, sizeof(*config)); + config->scope.xlayer_id = context->key.xlayer_id; + config->scope.ops_xlayer_id = context->key.ops_xlayer_id; + config->scope.ops_id = context->key.ops_id; + config->scope.operating_point = context->key.operating_point; + config->scope.whole_xlayer = context->key.whole_xlayer; + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + config->mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + config->stop_after_first_violation = + verifier->check_mode == AVM_DECODER_MODEL_CHECK_FATAL; + + if (context->active_configuration_record >= verifier->active_record_count) { + return true; + } + const Av2DmActiveConfigurationRecord *const active = + &verifier->active_records[context->active_configuration_record]; + const SequenceHeader *const sequence = &active->sequence; + if (mlayer_id < 0 || mlayer_id >= MAX_NUM_MLAYERS) return true; + const ContentInterpretation *const ci = &active->ci[mlayer_id]; + + config->applicability = sequence->seq_max_level_idx == 31 + ? AV2_DM_NOT_APPLICABLE + : AV2_DM_APPLICABLE; + config->level_idx = sequence->seq_max_level_idx; + config->tier = sequence->seq_tier; + config->profile = sequence->seq_profile_idc; + config->num_ref_frames = sequence->ref_frames; + config->explicit_num_ref_frames = true; + config->max_frame_width = sequence->max_frame_width; + config->max_frame_height = sequence->max_frame_height; + config->max_mlayer_id = sequence->max_mlayer_id; + config->still_picture = sequence->still_picture != 0; + config->timing_info_present = ci->ci_timing_info_present_flag != 0; + config->num_units_in_display_tick = ci->timing_info.num_units_in_display_tick; + config->time_scale = ci->timing_info.time_scale; + config->num_units_in_decoding_tick = + sequence->decoder_model_info.num_units_in_decoding_tick; + config->equal_picture_interval = + ci->timing_info.equal_elemental_interval != 0; + config->ticks_per_picture = ci->timing_info.num_ticks_per_elemental_duration; + config->initial_display_delay = + sequence->seq_max_display_model_info_present_flag + ? (uint32_t)sequence->seq_max_initial_display_delay_minus_1 + 1 + : (uint32_t)sequence->ref_frames + 2; + config->sequence_parameters_present = + sequence->seq_max_decoder_model_present_flag != 0; + config->sequence_decoder_buffer_delay = + sequence->seq_max_decoder_buffer_delay; + config->sequence_encoder_buffer_delay = + sequence->seq_max_encoder_buffer_delay; + config->sequence_low_delay_mode = sequence->seq_max_low_delay_mode_flag != 0; + + const OperatingPointSet *ops; + const OperatingPoint *const op = + context_operating_point(verifier, context, &ops); + if (!context->key.whole_xlayer) { + if (op == NULL || ops == NULL) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + return true; + } + if (ops->ops_ptl_present_flag) { + const int xlayer_id = context->key.xlayer_id; + config->profile = op->ops_seq_profile_idc[xlayer_id]; + config->level_idx = op->ops_level_idx[xlayer_id]; + config->tier = op->ops_tier_flag[xlayer_id]; + if (config->level_idx == 31) { + config->applicability = AV2_DM_NOT_APPLICABLE; + } + } + config->operating_point_parameters_present = + op->ops_decoder_model_info_for_this_op_present_flag != 0; + config->operating_point_decoder_buffer_delay = + op->decoder_model_info.ops_decoder_buffer_delay; + config->operating_point_encoder_buffer_delay = + op->decoder_model_info.ops_encoder_buffer_delay; + config->operating_point_low_delay_mode = + op->decoder_model_info.ops_low_delay_mode_flag != 0; + if (op->ops_initial_display_delay_present_flag) { + config->initial_display_delay = op->ops_initial_display_delay; + } + } + + if (config->sequence_parameters_present || + config->operating_point_parameters_present) { + config->mode = AV2_DM_DECODING_SCHEDULE_MODE; + } + if (config->mode == AV2_DM_DECODING_SCHEDULE_MODE && + !sequence->decoder_model_info_present_flag) { + // DecCT is not present, even if a separately parsed parameter structure + // appears to request schedule mode. + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + if (config->num_ref_frames == 0 || + config->num_ref_frames > AV2_DM_MAX_REF_FRAMES || + config->initial_display_delay == 0 || + config->initial_display_delay > AV2_DM_MAX_BUFFER_POOL_SIZE) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + if (snapshot->multistream_decoder_mode && + config->applicability == AV2_DM_APPLICABLE) { + uint32_t scale_numerator = 4; + uint32_t scale_denominator = 1; + if (!snapshot->multistream_even_allocation) { + const int large = snapshot->multistream_large_picture_index; + if (large >= snapshot->num_streams || large >= AVM_MAX_NUM_STREAMS) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + return true; + } + if (snapshot->stream_ids[large] == context->key.xlayer_id) { + scale_numerator = 3; + scale_denominator = 2; + } else { + scale_numerator = 9; + } + } + if (!av2_dm_get_level_limits(config->level_idx, config->tier, + config->profile, &config->level_limits) || + !av2_dm_apply_multistream_limits(snapshot->msdo.multistream_level_idx, + snapshot->msdo.multistream_tier_idx, + snapshot->msdo.multistream_profile_idc, + scale_numerator, scale_denominator, + &config->level_limits)) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } else { + config->level_limits_present = true; + } + } + return true; +} + +static bool find_buffer_removal_time(const Av2DecoderModelVerifier *verifier, + const Av2DmContext *context, + uint32_t *buffer_removal_time) { + const int xlayer_id = context->key.whole_xlayer ? context->key.xlayer_id + : context->key.ops_xlayer_id; + if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS || + !verifier->current_brt_present[xlayer_id] || + verifier->current_brt_record[xlayer_id] >= verifier->brt_record_count) { + return false; + } + const BufferRemovalTimingInfo *const brt = + &verifier->brt_records[verifier->current_brt_record[xlayer_id]].brt; + if (context->key.whole_xlayer) { + if (brt->br_ops_dependent_flag || brt->br_time < 0) return false; + *buffer_removal_time = (uint32_t)brt->br_time; + return true; + } + const int ops_id = context->key.ops_id; + const int op = context->key.operating_point; + if (!brt->br_ops_dependent_flag || brt->br_ops_id != ops_id || ops_id < 0 || + ops_id >= MAX_NUM_OPS_ID || op < 0 || op >= MAX_OPS_COUNT || + !brt->br_decoder_model_present_op_flag[ops_id][op] || + brt->br_time_op[ops_id][op] < 0) { + return false; + } + *buffer_removal_time = (uint32_t)brt->br_time_op[ops_id][op]; + return true; +} + +static bool recompute_pending_bits(Av2DecoderModelVerifier *verifier, + Av2DmContext *context) { + uint64_t bits = 0; + for (size_t i = 0; i < verifier->current_tu_obu_count; ++i) { + const Av2DmPendingObu *const obu = &verifier->current_tu_obus[i]; + if ((!context->pending_after_event_valid || + obu->event_index > context->pending_after_event) && + obu_belongs_to_context(context, obu) && + !add_u64(bits, obu->bits, &bits)) { + mark_arithmetic_failed(verifier); + return false; + } + } + context->pending_dfg_bits = bits; + return true; +} + +static void rebuild_incomplete_extraction( + const Av2DecoderModelVerifier *verifier, Av2DmContext *context) { + context->incomplete_extraction = false; + for (size_t i = 0; i < verifier->current_tu_obu_count; ++i) { + const Av2DmPendingObu *const obu = &verifier->current_tu_obus[i]; + if ((!context->pending_after_event_valid || + obu->event_index > context->pending_after_event) && + !obu->decoder_retained && obu_belongs_to_context(context, obu)) { + context->incomplete_extraction = true; + return; + } + } +} + +static bool reset_xlayer_cvs_obu_accounting(Av2DecoderModelVerifier *verifier, + int xlayer_id) { + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->key.xlayer_id != xlayer_id) continue; + context->pending_dfg_bits = 0; + context->pending_after_event_valid = false; + context->pending_after_event = 0; + context->last_closed_dfg_bits = 0; + if (!recompute_pending_bits(verifier, context)) return false; + rebuild_incomplete_extraction(verifier, context); + } + return true; +} + +static Av2DmContext *configure_context(Av2DecoderModelVerifier *verifier, + const Av2DmContextKey *key, + const OperatingPointSet *ops) { + Av2DmContext *context = find_context(verifier, key); + bool created = false; + if (context == NULL) { + if (verifier->context_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } + if (!reserve_array(verifier, (void **)&verifier->contexts, + &verifier->context_capacity, verifier->context_count + 1, + sizeof(*verifier->contexts))) { + mark_failed(verifier); + return NULL; + } + context = &verifier->contexts[verifier->context_count]; + if (!increment_size(verifier, &verifier->context_count)) return NULL; + memset(context, 0, sizeof(*context)); + created = true; + context->key = *key; + context->active_sequence_record = UINT64_MAX; + context->active_ops_record = UINT64_MAX; + context->active_configuration_record = UINT64_MAX; + context->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + + SubBitstreamExtractionState membership; + if (!av2_sbe_configure_decoder_model_scope(&membership, key->xlayer_id, ops, + key->operating_point, + key->whole_xlayer)) { + mark_failed(verifier); + return NULL; + } + const bool was_active = context->active; + context->membership = membership; + context->configuration_generation = verifier->parameter_generation; + context->active = true; + if (verifier->active_configuration_present[key->xlayer_id]) { + context->active_configuration_record = + verifier->active_configuration_record[key->xlayer_id]; + context->active_sequence_record = + verifier->active_sequence_record[key->xlayer_id]; + } else { + context->active_configuration_record = UINT64_MAX; + context->active_sequence_record = UINT64_MAX; + } + if ((created || !was_active) && !recompute_pending_bits(verifier, context)) { + mark_failed(verifier); + return NULL; + } + for (size_t i = 0; i < verifier->current_tu_obu_count; ++i) { + const Av2DmPendingObu *const obu = &verifier->current_tu_obus[i]; + if ((!context->pending_after_event_valid || + obu->event_index > context->pending_after_event) && + !obu->decoder_retained && obu_belongs_to_context(context, obu)) { + context->incomplete_extraction = true; + } + } + return context; +} + +static void append_rap_start(Av2DecoderModelVerifier *verifier, int obu_type, + int xlayer_id, int mlayer_id, int temporal_id, + uint64_t event_position) { + (void)event_position; + if (obu_type != OBU_CLOSED_LOOP_KEY && obu_type != OBU_OPEN_LOOP_KEY && + obu_type != OBU_RAS_FRAME) { + return; + } + if (verifier->last_rap_start_valid && + verifier->last_rap_frame_unit_index == + verifier->source_frame_unit_index && + verifier->last_rap_xlayer_id == xlayer_id && + verifier->last_rap_mlayer_id == mlayer_id && + verifier->last_rap_temporal_id == temporal_id) { + return; + } + if (verifier->rap_start_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + ++verifier->rap_start_count; + verifier->last_rap_start_valid = true; + verifier->last_rap_xlayer_id = xlayer_id; + verifier->last_rap_mlayer_id = mlayer_id; + verifier->last_rap_temporal_id = temporal_id; + verifier->last_rap_frame_unit_index = verifier->source_frame_unit_index; +} + +void av2_decoder_model_verifier_init(AV2Decoder *pbi) { + if (pbi == NULL || pbi->decoder_model_verifier != NULL || + pbi->decoder_model_verifier_allocation_failed) { + return; + } + pbi->decoder_model_verifier_allocation_reported = false; + pbi->decoder_model_verifier = + (Av2DecoderModelVerifier *)avm_calloc(1, sizeof(Av2DecoderModelVerifier)); + pbi->decoder_model_verifier_allocation_failed = + pbi->decoder_model_verifier == NULL; + if (pbi->decoder_model_verifier != NULL) { + pbi->decoder_model_verifier->check_mode = pbi->decoder_model_check_mode; + } +} + +void av2_decoder_model_verifier_destroy(AV2Decoder *pbi) { + if (pbi == NULL) return; + if (pbi->decoder_model_verifier == NULL) { + pbi->decoder_model_verifier_allocation_failed = false; + pbi->decoder_model_verifier_allocation_reported = false; + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + for (size_t i = 0; i < verifier->context_count; ++i) { + destroy_context_runs(&verifier->contexts[i]); + avm_free(verifier->contexts[i].runs); + avm_free(verifier->contexts[i].prefix_events); + } + avm_free(verifier->current_tu_obus); + avm_free(verifier->sequence_records); + avm_free(verifier->ops_records); + avm_free(verifier->brt_records); + avm_free(verifier->active_records); + avm_free(verifier->contexts); + avm_free(verifier->generations); + avm_free(verifier); + pbi->decoder_model_verifier = NULL; + pbi->decoder_model_verifier_allocation_failed = false; + pbi->decoder_model_verifier_allocation_reported = false; +} + +void av2_decoder_model_verifier_record_obu(AV2Decoder *pbi, int obu_type, + int xlayer_id, int mlayer_id, + int temporal_id, uint64_t obu_bits) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + + if (obu_type == OBU_TEMPORAL_DELIMITER && verifier->temporal_unit_has_obu) { + if (verifier->temporal_unit_index == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return; + } + ++verifier->temporal_unit_index; + } + if (obu_type == OBU_TEMPORAL_DELIMITER) { + memset(verifier->current_brt_present, 0, + sizeof(verifier->current_brt_present)); + verifier->current_tu_obu_count = 0; + } + verifier->temporal_unit_has_obu = true; + + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_RAW_OBU); + if (event == NULL) return; + + if (verifier->current_tu_obu_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + if (!reserve_array(verifier, (void **)&verifier->current_tu_obus, + &verifier->current_tu_obu_capacity, + verifier->current_tu_obu_count + 1, + sizeof(*verifier->current_tu_obus))) { + mark_failed(verifier); + return; + } + event->record_index = verifier->current_tu_obu_count; + Av2DmPendingObu *const obu = + &verifier->current_tu_obus[verifier->current_tu_obu_count]; + obu->obu_type = obu_type; + obu->xlayer_id = xlayer_id; + obu->mlayer_id = mlayer_id; + obu->temporal_id = temporal_id; + obu->bits = obu_bits; + obu->frame_unit_index = verifier->source_frame_unit_index; + obu->temporal_unit_index = verifier->temporal_unit_index; + obu->event_index = event->index; + obu->decoder_retained = true; + if (!increment_size(verifier, &verifier->current_tu_obu_count)) return; + + if (!add_u64(verifier->raw_bits, obu_bits, &verifier->raw_bits) || + verifier->raw_obus == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return; + } + ++verifier->raw_obus; + + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->active && obu_belongs_to_context(context, obu) && + !add_u64(context->pending_dfg_bits, obu_bits, + &context->pending_dfg_bits)) { + mark_arithmetic_failed(verifier); + return; + } + } + append_rap_start(verifier, obu_type, xlayer_id, mlayer_id, temporal_id, + event->index); +} + +void av2_decoder_model_verifier_on_source_frame_unit_start(AV2Decoder *pbi, + int xlayer_id, + int mlayer_id, + int temporal_id) { + (void)xlayer_id; + (void)mlayer_id; + (void)temporal_id; + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + if (verifier->source_frame_unit_started) { + if (verifier->source_frame_unit_index == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return; + } + ++verifier->source_frame_unit_index; + } else { + verifier->source_frame_unit_started = true; + } + verifier->current_source_frame_dispatched = false; + for (size_t i = 0; i < verifier->context_count; ++i) { + verifier->contexts[i].prefix_event_count = 0; + } +} + +void av2_decoder_model_verifier_on_obu_filtered(AV2Decoder *pbi) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + if (!verifier->last_event_valid) return; + Av2DmAdapterEvent *const event = &verifier->last_event; + if (event->type == AV2_DM_ADAPTER_RAW_OBU) { + if (event->record_index >= verifier->current_tu_obu_count) { + mark_failed(verifier); + return; + } + Av2DmPendingObu *const obu = + &verifier->current_tu_obus[(size_t)event->record_index]; + if (obu->event_index != event->index) { + mark_failed(verifier); + return; + } + obu->decoder_retained = false; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->active && obu_belongs_to_context(context, obu)) { + context->incomplete_extraction = true; + } + } + } +} + +void av2_decoder_model_verifier_on_accounting_failure(AV2Decoder *pbi) { + if (pbi != NULL && verifier_accepts_events(pbi->decoder_model_verifier)) { + mark_arithmetic_failed(pbi->decoder_model_verifier); + } +} + +void av2_decoder_model_verifier_on_internal_failure_for_testing( + AV2Decoder *pbi) { + if (pbi != NULL && verifier_accepts_events(pbi->decoder_model_verifier)) { + mark_failed(pbi->decoder_model_verifier); + } +} + +void av2_decoder_model_verifier_on_sequence_header(AV2Decoder *pbi, + int xlayer_id, + int sequence_header_id) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL || xlayer_id < 0 || + xlayer_id >= MAX_NUM_XLAYERS || sequence_header_id < 0 || + sequence_header_id >= MAX_SEQ_NUM) { + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + size_t record_index = verifier->sequence_record_count; + for (size_t i = 0; i < verifier->sequence_record_count; ++i) { + if (verifier->sequence_records[i].xlayer_id == xlayer_id && + verifier->sequence_records[i].sequence_header_id == + sequence_header_id) { + record_index = i; + break; + } + } + if (record_index == verifier->sequence_record_count && + verifier->sequence_record_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + if (verifier->failed || + (record_index == verifier->sequence_record_count && + !reserve_array(verifier, (void **)&verifier->sequence_records, + &verifier->sequence_record_capacity, + verifier->sequence_record_count + 1, + sizeof(*verifier->sequence_records)))) { + mark_failed(verifier); + return; + } + Av2DmSequenceRecord *const record = &verifier->sequence_records[record_index]; + record->xlayer_id = xlayer_id; + record->sequence_header_id = sequence_header_id; + record->sequence = pbi->seq_list[xlayer_id][sequence_header_id]; + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_SEQUENCE_HEADER); + if (event == NULL) return; + event->record_index = record_index; + if (record_index == verifier->sequence_record_count) { + if (!increment_size(verifier, &verifier->sequence_record_count)) return; + } + if (!increment_u64(verifier, &verifier->parameter_generation)) return; + + const Av2DmContextKey key = { xlayer_id, -1, -1, -1, true }; + (void)configure_context(verifier, &key, NULL); +} + +static void deactivate_ops_contexts(Av2DecoderModelVerifier *verifier, + int ops_xlayer_id, int ops_id, + bool all_ops_sources) { + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->key.whole_xlayer) continue; + if (!all_ops_sources && context->key.ops_xlayer_id != ops_xlayer_id) { + continue; + } + if (ops_id >= 0 && context->key.ops_id != ops_id) continue; + context->active = false; + } +} + +static void configure_ops_contexts(Av2DecoderModelVerifier *verifier, + const OperatingPointSet *ops, + uint64_t ops_record) { + const int ops_xlayer_id = ops->obu_xlayer_id; + for (int op = 0; op < ops->ops_cnt; ++op) { + const int first_xlayer = + ops_xlayer_id == GLOBAL_XLAYER_ID ? 0 : ops_xlayer_id; + const int last_xlayer = ops_xlayer_id == GLOBAL_XLAYER_ID + ? GLOBAL_XLAYER_ID - 1 + : ops_xlayer_id; + for (int model_xlayer = first_xlayer; model_xlayer <= last_xlayer; + ++model_xlayer) { + if (ops_xlayer_id == GLOBAL_XLAYER_ID && + (ops->op[op].ops_xlayer_map & (1 << model_xlayer)) == 0) { + continue; + } + const Av2DmContextKey key = { model_xlayer, ops_xlayer_id, ops->ops_id, + op, false }; + Av2DmContext *const context = configure_context(verifier, &key, ops); + if (context != NULL) context->active_ops_record = ops_record; + } + } +} + +void av2_decoder_model_verifier_on_operating_point_set(AV2Decoder *pbi, + int xlayer_id, + int ops_id) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL || xlayer_id < 0 || + xlayer_id >= MAX_NUM_XLAYERS || ops_id < 0 || ops_id >= MAX_NUM_OPS_ID) { + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + const OperatingPointSet *const ops = &pbi->ops_list[xlayer_id][ops_id]; + size_t record_index = verifier->ops_record_count; + for (size_t i = 0; i < verifier->ops_record_count; ++i) { + if (verifier->ops_records[i].xlayer_id == xlayer_id && + verifier->ops_records[i].ops_id == ops_id) { + record_index = i; + break; + } + } + if (record_index == verifier->ops_record_count && + verifier->ops_record_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + if (verifier->failed || !ops->valid || + (record_index == verifier->ops_record_count && + !reserve_array(verifier, (void **)&verifier->ops_records, + &verifier->ops_record_capacity, + verifier->ops_record_count + 1, + sizeof(*verifier->ops_records)))) { + mark_failed(verifier); + return; + } + Av2DmOpsRecord *const record = &verifier->ops_records[record_index]; + record->xlayer_id = xlayer_id; + record->ops_id = ops_id; + record->ops = *ops; + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_OPERATING_POINT_SET); + if (event == NULL) return; + event->record_index = record_index; + if (record_index == verifier->ops_record_count && + !increment_size(verifier, &verifier->ops_record_count)) { + return; + } + if (!increment_u64(verifier, &verifier->parameter_generation)) return; + + if (ops->ops_reset_flag) { + deactivate_ops_contexts(verifier, xlayer_id, -1, + xlayer_id == GLOBAL_XLAYER_ID); + } else if (ops->ops_cnt == 0) { + deactivate_ops_contexts(verifier, xlayer_id, ops_id, false); + } + + configure_ops_contexts(verifier, ops, event->record_index); + if (!ops->ops_reset_flag && ops->ops_cnt > 0) { + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->key.whole_xlayer || + context->key.ops_xlayer_id != xlayer_id || + context->key.ops_id != ops_id) { + continue; + } + const int op = context->key.operating_point; + if (op < 0 || op >= ops->ops_cnt || + (xlayer_id == GLOBAL_XLAYER_ID && + (ops->op[op].ops_xlayer_map & (1 << context->key.xlayer_id)) == 0)) { + context->active = false; + } + } + } +} + +void av2_decoder_model_verifier_on_active_configuration( + AV2Decoder *pbi, int xlayer_id, int sequence_header_id) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL || xlayer_id < 0 || + xlayer_id >= MAX_NUM_XLAYERS || sequence_header_id < 0 || + sequence_header_id >= MAX_SEQ_NUM) { + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + // The CLK OBU and current-TU prefix have already been recorded, while the + // decoder has already flushed implicit outputs owned by the preceding CVS. + // Close only that xlayer and leave the retained prefix for the new CVS. + if (pbi->obu_type == OBU_CLOSED_LOOP_KEY && + (!verifier->clk_boundary_seen[xlayer_id] || + verifier->clk_boundary_temporal_unit[xlayer_id] != + verifier->temporal_unit_index)) { + finish_xlayer_cvs(verifier, xlayer_id); + if (verifier->fatal_violation || verifier->failed) return; + if (!reset_xlayer_cvs_obu_accounting(verifier, xlayer_id)) return; + retire_xlayer_generations(verifier, pbi, xlayer_id); + ensure_cvs_open(verifier, xlayer_id); + verifier->clk_boundary_seen[xlayer_id] = true; + verifier->clk_boundary_temporal_unit[xlayer_id] = + verifier->temporal_unit_index; + } + if (verifier->active_configuration_present[xlayer_id]) { + const Av2DmActiveConfigurationRecord *const previous = + &verifier + ->active_records[verifier->active_configuration_record[xlayer_id]]; + if (previous->sequence_header_id == sequence_header_id && + memcmp(&previous->sequence, &pbi->common.seq_params, + sizeof(previous->sequence)) == 0 && + memcmp(previous->ci, pbi->common.ci_params_per_layer, + sizeof(previous->ci)) == 0) { + return; + } + } + size_t record_index = verifier->active_record_count; + for (size_t i = 0; i < verifier->active_record_count; ++i) { + if (verifier->active_records[i].xlayer_id == xlayer_id) { + record_index = i; + break; + } + } + if (record_index == verifier->active_record_count && + verifier->active_record_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + if (verifier->failed || + (record_index == verifier->active_record_count && + !reserve_array(verifier, (void **)&verifier->active_records, + &verifier->active_record_capacity, + verifier->active_record_count + 1, + sizeof(*verifier->active_records)))) { + mark_failed(verifier); + return; + } + Av2DmActiveConfigurationRecord *const record = + &verifier->active_records[record_index]; + record->xlayer_id = xlayer_id; + record->sequence_header_id = sequence_header_id; + record->sequence = pbi->common.seq_params; + memcpy(record->ci, pbi->common.ci_params_per_layer, sizeof(record->ci)); + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_ACTIVE_CONFIGURATION); + if (event == NULL) return; + event->record_index = record_index; + if (record_index == verifier->active_record_count) { + if (!increment_size(verifier, &verifier->active_record_count)) return; + } + verifier->active_configuration_present[xlayer_id] = true; + verifier->active_configuration_record[xlayer_id] = event->record_index; + + uint64_t sequence_record = UINT64_MAX; + for (size_t i = verifier->sequence_record_count; i > 0; --i) { + const Av2DmSequenceRecord *const sequence = + &verifier->sequence_records[i - 1]; + if (sequence->xlayer_id == xlayer_id && + sequence->sequence_header_id == sequence_header_id) { + sequence_record = i - 1; + break; + } + } + verifier->active_sequence_record[xlayer_id] = sequence_record; + + const Av2DmContextKey whole_key = { xlayer_id, -1, -1, -1, true }; + (void)configure_context(verifier, &whole_key, NULL); + const int ops_sources[2] = { xlayer_id, GLOBAL_XLAYER_ID }; + for (int source_index = 0; source_index < 2; ++source_index) { + const int source = ops_sources[source_index]; + if (source_index == 1 && source == xlayer_id) continue; + for (int ops_id = 0; ops_id < MAX_NUM_OPS_ID; ++ops_id) { + const OperatingPointSet *const ops = &pbi->ops_list[source][ops_id]; + if (!ops->valid || ops->ops_cnt == 0) continue; + uint64_t ops_record = UINT64_MAX; + for (size_t i = verifier->ops_record_count; i > 0; --i) { + const Av2DmOpsRecord *const candidate = &verifier->ops_records[i - 1]; + if (candidate->xlayer_id == source && candidate->ops_id == ops_id) { + ops_record = i - 1; + break; + } + } + configure_ops_contexts(verifier, ops, ops_record); + } + } + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->key.xlayer_id != xlayer_id) continue; + context->active_configuration_record = event->record_index; + context->active_sequence_record = sequence_record; + } +} + +void av2_decoder_model_verifier_on_buffer_removal_timing(AV2Decoder *pbi, + int xlayer_id) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + size_t record_index = verifier->brt_record_count; + for (size_t i = 0; i < verifier->brt_record_count; ++i) { + if (verifier->brt_records[i].xlayer_id == xlayer_id) { + record_index = i; + break; + } + } + if (record_index == verifier->brt_record_count && + verifier->brt_record_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + if (verifier->failed || + (record_index == verifier->brt_record_count && + !reserve_array(verifier, (void **)&verifier->brt_records, + &verifier->brt_record_capacity, + verifier->brt_record_count + 1, + sizeof(*verifier->brt_records)))) { + mark_failed(verifier); + return; + } + Av2DmBrtRecord *const record = &verifier->brt_records[record_index]; + record->xlayer_id = xlayer_id; + record->brt = pbi->common.brt_info; + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_BUFFER_REMOVAL_TIMING); + if (event != NULL) { + event->record_index = record_index; + if (record_index == verifier->brt_record_count) { + if (!increment_size(verifier, &verifier->brt_record_count)) return; + } + if (xlayer_id >= 0 && xlayer_id < MAX_NUM_XLAYERS) { + verifier->current_brt_present[xlayer_id] = true; + verifier->current_brt_record[xlayer_id] = event->record_index; + } + } +} + +void av2_decoder_model_verifier_on_temporal_point(AV2Decoder *pbi, + uint64_t presentation_time) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_TEMPORAL_POINT); + if (event == NULL) return; + event->value = presentation_time; + verifier->temporal_point_present = true; + verifier->temporal_point = presentation_time; + if (verifier->pending_frame.valid && + verifier->pending_frame.source_frame_unit_index == + verifier->source_frame_unit_index) { + verifier->pending_frame.presentation_time_present = true; + verifier->pending_frame.presentation_time_ticks = presentation_time; + } + if (verifier->temporal_points == UINT64_MAX) { + mark_arithmetic_failed(verifier); + } else { + ++verifier->temporal_points; + } +} + +void av2_decoder_model_verifier_on_multistream_configuration( + AV2Decoder *pbi, int even_allocation, int large_picture_index) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + verifier->multistream_even_allocation = even_allocation != 0; + verifier->multistream_large_picture_index = large_picture_index; +} + +static void capture_tile_statistics(const AV2_COMMON *cm, + Av2DmFrameSnapshot *snapshot) { + snapshot->num_tiles = (uint32_t)(cm->tiles.cols * cm->tiles.rows); + snapshot->tile_columns = (uint32_t)cm->tiles.cols; + snapshot->non_rightmost_tile_width_valid = true; + for (int row = 0; row < cm->tiles.rows; ++row) { + const int row_start = cm->tiles.row_start_sb[row] << cm->mib_size_log2; + int row_end = cm->tiles.row_start_sb[row + 1] << cm->mib_size_log2; + if (row_end > cm->mi_params.mi_rows) row_end = cm->mi_params.mi_rows; + const uint64_t tile_height = (uint64_t)(row_end - row_start) * MI_SIZE; + for (int col = 0; col < cm->tiles.cols; ++col) { + const int col_start = cm->tiles.col_start_sb[col] << cm->mib_size_log2; + int col_end = cm->tiles.col_start_sb[col + 1] << cm->mib_size_log2; + if (col_end > cm->mi_params.mi_cols) col_end = cm->mi_params.mi_cols; + const uint64_t tile_width = (uint64_t)(col_end - col_start) * MI_SIZE; + const uint64_t tile_area = tile_width * tile_height; + if (tile_width > snapshot->max_tile_width) { + snapshot->max_tile_width = tile_width; + } + if (tile_area > snapshot->max_tile_area) { + snapshot->max_tile_area = tile_area; + } + if (col + 1 != cm->tiles.cols && tile_width < 64) { + snapshot->non_rightmost_tile_width_valid = false; + } + } + } +} + +static void capture_ras_seed(Av2DecoderModelVerifier *verifier, + const AV2Decoder *pbi, + Av2DmFrameSnapshot *snapshot) { + snapshot->ras_seed_complete = true; + if (snapshot->obu_type != OBU_RAS_FRAME) return; + const AV2_COMMON *const cm = &pbi->common; + for (int i = 0; i < cm->seq_params.ref_frames; ++i) { + const RefCntBuffer *const frame = cm->ref_frame_map[i]; + if (frame == NULL || !pbi->valid_for_referencing[i] || + frame->long_term_id == -1) { + continue; + } + if (snapshot->ras_seed_count == AV2_DM_MAX_REF_FRAMES) { + snapshot->ras_seed_complete = false; + continue; + } + Av2DmRasSeedSnapshot *const seed = + &snapshot->ras_seeds[snapshot->ras_seed_count++]; + seed->ref_index = (uint32_t)i; + seed->xlayer_id = frame->xlayer_id; + seed->mlayer_id = frame->mlayer_id; + seed->temporal_id = frame->tlayer_id; + Av2DmGenerationRecord *const generation = + find_generation_by_buffer(verifier, frame); + if (generation != NULL) { + seed->generation_valid = true; + seed->generation = generation->generation; + } + } +} + +void av2_decoder_model_verifier_on_frame_wrapup_start(AV2Decoder *pbi) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + if (verifier->pending_frame.valid) { + if (verifier->pending_frame.source_frame_unit_index == + verifier->source_frame_unit_index) { + return; + } + mark_failed(verifier); + return; + } + Av2DmAdapterEvent *const adapter_event = + append_event(verifier, AV2_DM_ADAPTER_FRAME_WRAPUP_START); + if (adapter_event == NULL) return; + + AV2_COMMON *const cm = &pbi->common; + Av2DmFrameSnapshot *const snapshot = &verifier->pending_frame; + memset(snapshot, 0, sizeof(*snapshot)); + snapshot->valid = true; + snapshot->source_frame_unit_index = verifier->source_frame_unit_index; + snapshot->event_index = adapter_event->index; + verifier->last_frame_start_event = adapter_event->index; + snapshot->temporal_unit_index = verifier->temporal_unit_index; + snapshot->parameter_generation = verifier->parameter_generation; + snapshot->stream_generation = verifier->stream_generation; + snapshot->obu_type = pbi->obu_type; + snapshot->xlayer_id = cm->xlayer_id; + snapshot->mlayer_id = cm->mlayer_id; + snapshot->temporal_id = cm->tlayer_id; + snapshot->show_existing_frame = cm->show_existing_frame != 0; + snapshot->ref_valid_mask = real_ref_valid_mask(pbi); + snapshot->implicit_output_frame = cm->implicit_output_picture != 0; + snapshot->leading_frame = cm->is_leading_picture == 1; + snapshot->frame_is_intra = cm->current_frame.frame_type == KEY_FRAME || + cm->current_frame.frame_type == INTRA_ONLY_FRAME; + snapshot->allow_global_intrabc = cm->features.allow_global_intrabc != 0; + snapshot->inloop_filtering_enabled = + !snapshot->show_existing_frame && cm->cur_frame != NULL + ? is_filter_enabled_frame(cm) + : false; + snapshot->frame_width = cm->width; + snapshot->frame_height = cm->height; + const uint32_t output_width = snapshot->frame_is_intra + ? snapshot->frame_width + : (uint32_t)cm->seq_params.max_frame_width; + const uint32_t output_height = + snapshot->frame_is_intra ? snapshot->frame_height + : (uint32_t)cm->seq_params.max_frame_height; + snapshot->output_luma_samples = (uint64_t)output_width * output_height; + snapshot->frame_symbol_count = cm->features.frame_symbol_count; + snapshot->presentation_time_present = cm->temporal_point_info_present; + snapshot->presentation_time_ticks = + cm->temporal_point_info_metadata.mtpi_frame_presentation_time; + snapshot->multistream_decoder_mode = pbi->multistream_decoder_mode != 0; + snapshot->msdo = cm->msdo_params; + snapshot->multistream_even_allocation = verifier->multistream_even_allocation; + snapshot->multistream_large_picture_index = + verifier->multistream_large_picture_index; + snapshot->num_streams = cm->num_streams; + memcpy(snapshot->stream_ids, cm->stream_ids, sizeof(snapshot->stream_ids)); + if (!snapshot->show_existing_frame) capture_tile_statistics(cm, snapshot); + + capture_ras_seed(verifier, pbi, snapshot); + Av2DmGenerationRecord *generation = NULL; + if (snapshot->show_existing_frame) { + const int ref = cm->sef_ref_fb_idx; + if (ref >= 0 && ref < cm->seq_params.ref_frames) { + generation = find_generation_by_buffer(verifier, cm->ref_frame_map[ref]); + } + } else { + generation = assign_generation(verifier, cm->cur_frame); + } + if (generation != NULL) { + snapshot->generation_valid = true; + snapshot->generation = generation->generation; + if (!snapshot->show_existing_frame) { + generation->source_frame_unit_index = snapshot->source_frame_unit_index; + generation->temporal_unit_index = snapshot->temporal_unit_index; + generation->xlayer_id = snapshot->xlayer_id; + generation->mlayer_id = snapshot->mlayer_id; + generation->temporal_id = snapshot->temporal_id; + generation->width = snapshot->frame_width; + generation->height = snapshot->frame_height; + generation->output_luma_samples = snapshot->output_luma_samples; + generation->leading_frame = snapshot->leading_frame; + generation->random_access_point = + snapshot->obu_type == OBU_CLOSED_LOOP_KEY || + snapshot->obu_type == OBU_OPEN_LOOP_KEY || + snapshot->obu_type == OBU_RAS_FRAME; + generation->presentation_time_present = + snapshot->presentation_time_present; + generation->presentation_time_ticks = snapshot->presentation_time_ticks; + generation->implicit_presentation_pending = + snapshot->implicit_output_frame; + } + } + (void)increment_u64(verifier, &verifier->frame_starts); +} + +static void append_frame_to_context(Av2DecoderModelVerifier *verifier, + Av2DmContext *context, + const Av2DmFrameSnapshot *snapshot) { + Av2DmContextEvent storage; + initialize_context_event(verifier, AV2_DM_CONTEXT_FRAME, &storage); + Av2DmContextEvent *const model_event = &storage; + model_event->event_index = snapshot->event_index; + model_event->source_frame_unit_index = snapshot->source_frame_unit_index; + model_event->parameter_generation = snapshot->parameter_generation; + model_event->stream_generation = snapshot->stream_generation; + model_event->generation = snapshot->generation; + model_event->frame_obu_type = snapshot->obu_type; + model_event->leading_frame = snapshot->leading_frame; + model_event->config_present = build_context_config( + verifier, context, snapshot->mlayer_id, snapshot, &model_event->config); + Av2DmGenerationRecord *const generation = + find_generation_by_id(verifier, snapshot->generation); + if (generation != NULL && !snapshot->show_existing_frame && + model_event->config_present) { + generation->presentation_timing_config_valid = true; + generation->equal_picture_interval = + model_event->config.equal_picture_interval; + } + if (context->active_configuration_record >= verifier->active_record_count) { + model_event->indeterminate_reason = + AV2_DM_REASON_MISSING_ACTIVE_CONFIGURATION; + } else if (context->incomplete_extraction) { + model_event->indeterminate_reason = AV2_DM_REASON_INCOMPLETE_EXTRACTION; + } else if (!snapshot->generation_valid) { + model_event->indeterminate_reason = AV2_DM_REASON_MISSING_FRAME_GENERATION; + } else if (context->recovery_reset_pending) { + model_event->indeterminate_reason = AV2_DM_REASON_RECOVERY_RESET; + } + context->recovery_reset_pending = false; + if (model_event->indeterminate_reason != AV2_DM_REASON_NONE) { + model_event->config.applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + Av2DmFrameEvent *const frame = &model_event->frame; + frame->event_index = snapshot->event_index; + frame->temporal_unit_index = snapshot->temporal_unit_index; + frame->generation = snapshot->generation; + frame->ref_valid_mask = snapshot->ref_valid_mask; + frame->coded_bits = + snapshot->show_existing_frame ? 0 : context->last_closed_dfg_bits; + frame->show_existing_frame = snapshot->show_existing_frame; + frame->random_access_point = snapshot->obu_type == OBU_CLOSED_LOOP_KEY || + snapshot->obu_type == OBU_OPEN_LOOP_KEY || + snapshot->obu_type == OBU_RAS_FRAME; + frame->coded_as_closed_loop_key = snapshot->obu_type == OBU_CLOSED_LOOP_KEY; + frame->frame_is_intra = snapshot->frame_is_intra; + frame->allow_global_intrabc = snapshot->allow_global_intrabc; + frame->inloop_filtering_enabled = snapshot->inloop_filtering_enabled; + frame->frame_width = snapshot->frame_width; + frame->frame_height = snapshot->frame_height; + frame->num_tiles = snapshot->num_tiles; + frame->tile_columns = snapshot->tile_columns; + frame->max_tile_width = snapshot->max_tile_width; + frame->max_tile_area = snapshot->max_tile_area; + frame->non_rightmost_tile_width_valid = + snapshot->non_rightmost_tile_width_valid; + const bool parameters_changed = + !context->last_config_present || + context->last_stream_generation != model_event->stream_generation || + memcmp(&context->last_config, &model_event->config, + sizeof(model_event->config)) != 0; + frame->decoder_model_parameters_updated = + frame->random_access_point && parameters_changed; + frame->count_frame_header = true; + frame->frame_symbol_count = snapshot->frame_symbol_count; + if (!compressed_size_for_context(verifier, context, snapshot, + &frame->compressed_size_bytes)) { + mark_failed(verifier); + } + frame->buffer_removal_time_present = + find_buffer_removal_time(verifier, context, &frame->buffer_removal_time); + // AVM has no external TU output-time source here. The common model derives + // it from the first presentation-owner event in display order. + frame->temporal_unit_output_time_present = false; + model_event->ras_seed_complete = snapshot->ras_seed_complete; + for (uint32_t i = 0; i < snapshot->ras_seed_count; ++i) { + const Av2DmRasSeedSnapshot *const candidate = &snapshot->ras_seeds[i]; + if (!frame_belongs_to_context(context, candidate->xlayer_id, + candidate->mlayer_id, + candidate->temporal_id)) { + continue; + } + if (!candidate->generation_valid || + model_event->ras_seed_count == AV2_DM_MAX_REF_FRAMES) { + model_event->ras_seed_complete = false; + continue; + } + Av2DmRasSeed *const seed = + &model_event->ras_seeds[model_event->ras_seed_count++]; + seed->ref_index = candidate->ref_index; + seed->generation = candidate->generation; + } + if (snapshot->obu_type == OBU_RAS_FRAME && !model_event->ras_seed_complete && + model_event->indeterminate_reason == AV2_DM_REASON_NONE) { + model_event->indeterminate_reason = AV2_DM_REASON_INCOMPLETE_RAS_SEED; + model_event->config.applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + dispatch_context_event(verifier, context, model_event); +} + +void av2_decoder_model_verifier_on_frame_unit_complete(AV2Decoder *pbi) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + Av2DmAdapterEvent *const event = + append_event(verifier, AV2_DM_ADAPTER_FRAME_UNIT_COMPLETE); + if (event == NULL) return; + Av2DmFrameSnapshot snapshot = verifier->pending_frame; + const bool have_snapshot = + snapshot.valid && + snapshot.source_frame_unit_index == verifier->source_frame_unit_index; + if (!have_snapshot) { + memset(&snapshot, 0, sizeof(snapshot)); + snapshot.show_existing_frame = pbi->common.show_existing_frame != 0; + snapshot.xlayer_id = pbi->common.xlayer_id; + snapshot.mlayer_id = pbi->common.mlayer_id; + snapshot.temporal_id = pbi->common.tlayer_id; + } + event->value = snapshot.show_existing_frame; + if (!snapshot.show_existing_frame) { + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (!context->active || + !frame_belongs_to_context(context, snapshot.xlayer_id, + snapshot.mlayer_id, snapshot.temporal_id)) { + continue; + } + context->last_closed_dfg_bits = context->pending_dfg_bits; + context->pending_dfg_bits = 0; + context->pending_after_event_valid = true; + context->pending_after_event = event->index; + if (context->closed_dfgs == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return; + } + ++context->closed_dfgs; + } + if (verifier->closed_dfgs == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return; + } + ++verifier->closed_dfgs; + } + if (have_snapshot) { + Av2DmGenerationRecord *const generation = + find_generation_by_id(verifier, snapshot.generation); + if (generation != NULL && !snapshot.show_existing_frame) { + // Temporal-point metadata may be a suffix OBU parsed after the wrapup + // snapshot. Preserve its final frame-unit value with the pending owner. + generation->presentation_time_present = + snapshot.presentation_time_present; + generation->presentation_time_ticks = snapshot.presentation_time_ticks; + } + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->active && + frame_belongs_to_context(context, snapshot.xlayer_id, + snapshot.mlayer_id, snapshot.temporal_id)) { + append_frame_to_context(verifier, context, &snapshot); + } + } + verifier->last_completed_frame = snapshot; + if (!snapshot.show_existing_frame) { + retire_completed_frame_obus(verifier, snapshot.source_frame_unit_index); + } + } + verifier->current_source_frame_dispatched = true; + for (size_t i = 0; i < verifier->context_count; ++i) { + verifier->contexts[i].prefix_event_count = 0; + } + memset(&verifier->pending_frame, 0, sizeof(verifier->pending_frame)); + if (verifier->frame_unit_index == UINT64_MAX) { + mark_arithmetic_failed(verifier); + } else { + ++verifier->frame_unit_index; + } +} + +void av2_decoder_model_verifier_on_olk_reference_invalidation(AV2Decoder *pbi) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + const uint32_t ref_valid_mask = real_ref_valid_mask(pbi); + Av2DmAdapterEvent *const adapter_event = + append_event(verifier, AV2_DM_ADAPTER_OLK_REFERENCE_INVALIDATION); + if (adapter_event == NULL) return; + adapter_event->value = ref_valid_mask; + verifier->last_olk_invalidation_event = adapter_event->index; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (!context->active || !frame_belongs_to_context( + context, pbi->common.xlayer_id, + pbi->common.mlayer_id, pbi->common.tlayer_id)) { + continue; + } + Av2DmContextEvent storage; + initialize_context_event( + verifier, AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION, &storage); + Av2DmContextEvent *const event = &storage; + event->event_index = adapter_event->index; + event->source_frame_unit_index = verifier->source_frame_unit_index; + event->ref_valid_mask = ref_valid_mask; + event->leading_frame = pbi->common.is_leading_picture == 1; + dispatch_context_event(verifier, context, event); + } + (void)increment_u64(verifier, &verifier->olk_invalidations); +} + +void av2_decoder_model_verifier_after_reference_update( + AV2Decoder *pbi, uint32_t refresh_frame_flags) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + const uint32_t ref_valid_mask = real_ref_valid_mask(pbi); + Av2DmAdapterEvent *const adapter_event = + append_event(verifier, AV2_DM_ADAPTER_REFERENCE_UPDATE); + if (adapter_event == NULL) return; + adapter_event->value = refresh_frame_flags; + verifier->last_reference_update_event = adapter_event->index; + Av2DmGenerationRecord *const generation = + find_generation_by_buffer(verifier, pbi->common.cur_frame); + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (!context->active || !frame_belongs_to_context( + context, pbi->common.xlayer_id, + pbi->common.mlayer_id, pbi->common.tlayer_id)) { + continue; + } + Av2DmContextEvent storage; + initialize_context_event(verifier, AV2_DM_CONTEXT_REFERENCE_UPDATE, + &storage); + Av2DmContextEvent *const event = &storage; + event->event_index = adapter_event->index; + event->source_frame_unit_index = verifier->source_frame_unit_index; + event->reference_update.refresh_frame_flags = refresh_frame_flags; + event->reference_update.ref_valid_mask = ref_valid_mask; + event->set_initial_presentation_delay = + pbi->common.show_existing_frame == 0; + if (generation != NULL) { + event->generation = generation->generation; + event->leading_frame = generation->leading_frame; + } + dispatch_context_event(verifier, context, event); + } + (void)increment_u64(verifier, &verifier->reference_updates); +} + +void av2_decoder_model_verifier_on_output(AV2Decoder *pbi, + int frame_to_show_map_idx, + const RefCntBuffer *frame, + Av2DmPresentationOwner owner) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL || frame == NULL) { + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + Av2DmAdapterEvent *const adapter_event = + append_event(verifier, AV2_DM_ADAPTER_OUTPUT); + if (adapter_event == NULL) return; + adapter_event->value = + frame_to_show_map_idx < 0 ? UINT64_MAX : (uint64_t)frame_to_show_map_idx; + verifier->last_output_event = adapter_event->index; + const RefCntBuffer *generation_frame = frame; + if (frame_to_show_map_idx >= 0 && + frame_to_show_map_idx < pbi->common.seq_params.ref_frames && + pbi->common.ref_frame_map[frame_to_show_map_idx] != NULL) { + // A non-derived show-existing output can be queued through a copied + // current buffer. Annex E identifies it by the selected reference slot. + generation_frame = pbi->common.ref_frame_map[frame_to_show_map_idx]; + } + Av2DmGenerationRecord *const generation = + find_generation_by_buffer(verifier, generation_frame); + const bool current_presentation = owner == AV2_DM_PRESENTATION_OWNER_CURRENT; + const Av2DmFrameSnapshot *const current_owner = + current_presentation && verifier->last_completed_frame.valid && + verifier->last_completed_frame.source_frame_unit_index == + verifier->source_frame_unit_index + ? &verifier->last_completed_frame + : NULL; + const Av2DmGenerationRecord *const implicit_owner = + !current_presentation && generation != NULL && + generation->implicit_presentation_pending + ? generation + : NULL; + const bool owner_valid = current_owner != NULL || implicit_owner != NULL; + const uint64_t owner_frame_unit = + current_owner != NULL ? current_owner->source_frame_unit_index + : implicit_owner != NULL ? implicit_owner->source_frame_unit_index + : verifier->source_frame_unit_index; + const uint64_t owner_temporal_unit = + current_owner != NULL ? current_owner->temporal_unit_index + : implicit_owner != NULL ? implicit_owner->temporal_unit_index + : verifier->temporal_unit_index; + const int owner_xlayer = current_owner != NULL ? current_owner->xlayer_id + : implicit_owner != NULL ? implicit_owner->xlayer_id + : frame->xlayer_id; + const int owner_mlayer = current_owner != NULL ? current_owner->mlayer_id + : implicit_owner != NULL ? implicit_owner->mlayer_id + : frame->mlayer_id; + const int owner_tlayer = current_owner != NULL ? current_owner->temporal_id + : implicit_owner != NULL + ? implicit_owner->temporal_id + : (int)frame->tlayer_id; + const bool owner_leading = + current_owner != NULL + ? current_owner->leading_frame + : implicit_owner != NULL && implicit_owner->leading_frame; + const bool owner_rap = + current_owner != NULL + ? current_owner->obu_type == OBU_CLOSED_LOOP_KEY || + current_owner->obu_type == OBU_OPEN_LOOP_KEY || + current_owner->obu_type == OBU_RAS_FRAME + : implicit_owner != NULL && implicit_owner->random_access_point; + const bool owner_presentation_time_present = + current_owner != NULL + ? current_owner->presentation_time_present + : implicit_owner != NULL && implicit_owner->presentation_time_present; + const uint64_t owner_presentation_time_ticks = + current_owner != NULL ? current_owner->presentation_time_ticks + : implicit_owner != NULL ? implicit_owner->presentation_time_ticks + : 0; + const uint64_t owner_luma_samples = + current_owner != NULL ? current_owner->output_luma_samples + : implicit_owner != NULL ? implicit_owner->output_luma_samples + : 0; + verifier->last_output_callback_frame_unit = verifier->source_frame_unit_index; + verifier->last_output_presentation_frame_unit = owner_frame_unit; + verifier->last_output_presentation_temporal_unit = owner_temporal_unit; + verifier->last_output_generation = + generation != NULL ? generation->generation : 0; + verifier->last_output_presentation_xlayer_id = owner_xlayer; + verifier->last_output_presentation_mlayer_id = owner_mlayer; + verifier->last_output_presentation_tlayer_id = owner_tlayer; + verifier->last_output_uses_current_presentation = current_presentation; + const uint32_t ref_valid_mask = real_ref_valid_mask(pbi); + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (!context->active || + !frame_belongs_to_context(context, owner_xlayer, owner_mlayer, + owner_tlayer)) { + continue; + } + Av2DmContextEvent storage; + initialize_context_event(verifier, AV2_DM_CONTEXT_OUTPUT, &storage); + Av2DmContextEvent *const event = &storage; + event->event_index = adapter_event->index; + event->source_frame_unit_index = verifier->source_frame_unit_index; + event->presentation_frame_unit_index = owner_frame_unit; + event->presentation_xlayer_id = owner_xlayer; + event->presentation_mlayer_id = owner_mlayer; + event->presentation_tlayer_id = owner_tlayer; + event->generation = generation != NULL ? generation->generation : 0; + event->leading_frame = owner_leading; + if (generation == NULL) { + event->indeterminate_reason = AV2_DM_REASON_MISSING_FRAME_GENERATION; + } else if (!owner_valid) { + event->indeterminate_reason = + AV2_DM_REASON_MISSING_PRESENTATION_PROVENANCE; + } + const bool owner_timing_config_valid = + current_owner != NULL + ? context->last_config_present + : implicit_owner != NULL && + implicit_owner->presentation_timing_config_valid; + const bool owner_equal_picture_interval = + current_owner != NULL + ? context->last_config.equal_picture_interval + : implicit_owner != NULL && implicit_owner->equal_picture_interval; + if (owner_valid && event->indeterminate_reason == AV2_DM_REASON_NONE && + owner_timing_config_valid && !owner_equal_picture_interval && + !owner_presentation_time_present) { + event->indeterminate_reason = AV2_DM_REASON_MISSING_PRESENTATION_TIMING; + } + Av2DmOutputEvent *const output = &event->output; + output->event_index = adapter_event->index; + output->temporal_unit_index = owner_temporal_unit; + output->generation = event->generation; + output->frame_to_show_map_idx = frame_to_show_map_idx; + output->ref_valid_mask = ref_valid_mask; + output->output_luma_samples = owner_luma_samples; + output->leading_frame = event->leading_frame; + output->presentation_uses_current_frame = current_presentation; + output->presentation_random_access_point = owner_rap; + output->presentation_time_present = owner_presentation_time_present; + output->presentation_time_ticks = owner_presentation_time_ticks; + dispatch_context_event(verifier, context, event); + } + if (!current_presentation && generation != NULL) { + generation->implicit_presentation_pending = false; + } + (void)increment_u64(verifier, &verifier->outputs); +} + +void av2_decoder_model_verifier_on_recovery_reset(AV2Decoder *pbi) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + Av2DmAdapterEvent *const adapter_event = + append_event(verifier, AV2_DM_ADAPTER_RECOVERY_RESET); + if (adapter_event == NULL) return; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (!context->active) continue; + Av2DmContextEvent storage; + initialize_context_event(verifier, AV2_DM_CONTEXT_RECOVERY_RESET, &storage); + Av2DmContextEvent *const event = &storage; + event->event_index = adapter_event->index; + event->source_frame_unit_index = verifier->source_frame_unit_index; + event->indeterminate_reason = AV2_DM_REASON_RECOVERY_RESET; + context->recovery_reset_pending = true; + context->pending_dfg_bits = 0; + dispatch_context_event(verifier, context, event); + } + memset(&verifier->pending_frame, 0, sizeof(verifier->pending_frame)); +} + +void av2_decoder_model_verifier_on_stream_configuration_change( + AV2Decoder *pbi, bool preserve_current_tu_prefix) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + if (append_event(verifier, AV2_DM_ADAPTER_STREAM_CONFIGURATION_CHANGE) != + NULL) { + finish_all_cvs(verifier); + if (verifier->fatal_violation || verifier->failed) return; + verifier->generation_count = 0; + if (!preserve_current_tu_prefix) verifier->current_tu_obu_count = 0; + if (!increment_u64(verifier, &verifier->parameter_generation) || + !increment_u64(verifier, &verifier->stream_generation)) { + return; + } + for (size_t i = 0; i < verifier->context_count; ++i) { + verifier->contexts[i].active = false; + verifier->contexts[i].incomplete_extraction = false; + verifier->contexts[i].recovery_reset_pending = false; + verifier->contexts[i].pending_dfg_bits = 0; + verifier->contexts[i].pending_after_event_valid = false; + verifier->contexts[i].prefix_event_count = 0; + verifier->contexts[i].last_config_present = false; + } + memset(verifier->active_configuration_present, 0, + sizeof(verifier->active_configuration_present)); + } +} + +typedef struct Av2DmRunReport { + Av2DecoderModelVerifier *verifier; + Av2DmScope scope; + Av2DmMode mode; + int64_t rap; + uint64_t cvs; + uint32_t level_idx; + uint32_t tier; + uint64_t max_display_rate; + uint64_t max_decode_rate; + Av2DmContextEvent current_event; + bool current_event_valid; + uint64_t violations; +} Av2DmRunReport; + +struct Av2DmLiveRun { + Av2DecoderModel *model; + Av2DmRunReport report; + Av2DmConfig config; + Av2DmIndeterminateReason reason; + int64_t rap; + bool olk; + uint64_t start_source_frame_unit; +}; + +void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( + AV2Decoder *pbi) { + if (pbi == NULL || !verifier_accepts_events(pbi->decoder_model_verifier)) { + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->run_count != 0) { + av2_decoder_model_fail_arithmetic_for_testing(context->runs[0]->model); + return; + } + } +} + +static const char *indeterminate_reason_name(Av2DmIndeterminateReason reason) { + switch (reason) { + case AV2_DM_REASON_NONE: return "none"; + case AV2_DM_REASON_MISSING_REQUIRED_INPUT: return "missing_required_input"; + case AV2_DM_REASON_MISSING_ACTIVE_CONFIGURATION: + return "missing_active_configuration"; + case AV2_DM_REASON_INCOMPLETE_EXTRACTION: return "incomplete_extraction"; + case AV2_DM_REASON_MISSING_FRAME_GENERATION: + return "missing_frame_generation"; + case AV2_DM_REASON_MISSING_PRESENTATION_PROVENANCE: + return "missing_presentation_provenance"; + case AV2_DM_REASON_MISSING_PRESENTATION_TIMING: + return "missing_presentation_timing"; + case AV2_DM_REASON_INCOMPLETE_RAS_SEED: return "incomplete_ras_seed"; + case AV2_DM_REASON_RECOVERY_RESET: return "decoder_recovery_reset"; + case AV2_DM_REASON_INTERNAL_FAILURE: return "internal_failure"; + } + return "internal_failure"; +} + +static const char *mode_name(Av2DmMode mode) { + return mode == AV2_DM_DECODING_SCHEDULE_MODE ? "schedule" : "resource"; +} + +static const char *result_name(Av2DmResultStatus status) { + switch (status) { + case AV2_DM_RESULT_CONFORMANT: return "CONFORMANT"; + case AV2_DM_RESULT_NON_CONFORMANT: return "NON_CONFORMANT"; + case AV2_DM_RESULT_INDETERMINATE: return "INDETERMINATE"; + case AV2_DM_RESULT_NOT_APPLICABLE: return "NOT_APPLICABLE"; + } + return "INDETERMINATE"; +} + +static const char *scope_name(const Av2DmScope *scope) { + return scope->whole_xlayer ? "whole_xlayer" : "operating_point"; +} + +static const char *tier_name(uint32_t tier) { + return tier == 0 ? "main" : "high"; +} + +static const char *level_name(uint32_t level_idx) { + static const char *const names[] = { "2.0", "2.1", "3.0", "3.1", "4.0", "4.1", + "5.0", "5.1", "5.2", "5.3", "6.0", "6.1", + "6.2", "6.3", "7.0", "7.1", "7.2", "7.3", + "8.0", "8.1", "8.2", "8.3" }; + if (level_idx < sizeof(names) / sizeof(names[0])) return names[level_idx]; + if (level_idx == SEQ_LEVEL_MAX) return "maximum_parameters"; + return "reserved"; +} + +static bool wide_fits_u64(const Av2DmUnsignedWide *value) { + return value->limbs[1] == 0 && value->limbs[2] == 0 && value->limbs[3] == 0; +} + +static bool format_unsigned_wide(const Av2DmUnsignedWide *value, char *text, + size_t text_size) { + if (wide_fits_u64(value)) { + const int written = snprintf(text, text_size, "%" PRIu64, value->limbs[0]); + return written >= 0 && (size_t)written < text_size; + } + int highest_limb = 3; + while (highest_limb > 0 && value->limbs[highest_limb] == 0) --highest_limb; + int written = + snprintf(text, text_size, "0x%" PRIx64, value->limbs[highest_limb]); + if (written < 0 || (size_t)written >= text_size) return false; + size_t offset = (size_t)written; + for (int i = highest_limb - 1; i >= 0; --i) { + written = snprintf(text + offset, text_size - offset, "%016" PRIx64, + value->limbs[i]); + if (written < 0 || (size_t)written >= text_size - offset) return false; + offset += (size_t)written; + } + return true; +} + +static bool format_rational(const Av2DmRational *value, char *text, + size_t text_size) { + if (value == NULL) { + const int written = snprintf(text, text_size, "NA"); + return written >= 0 && (size_t)written < text_size; + } + char magnitude[67]; + char denominator[67]; + if (!format_unsigned_wide(&value->magnitude, magnitude, sizeof(magnitude)) || + !format_unsigned_wide(&value->denominator, denominator, + sizeof(denominator))) { + return false; + } + int written; + if (wide_fits_u64(&value->denominator) && value->denominator.limbs[0] == 1) { + written = snprintf(text, text_size, "%s%s", value->negative ? "-" : "", + magnitude); + } else { + written = snprintf(text, text_size, "%s%s/%s", value->negative ? "-" : "", + magnitude, denominator); + } + return written >= 0 && (size_t)written < text_size; +} + +typedef enum Av2DmMarginRule { + AV2_DM_MARGIN_NONE, + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT, + AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED +} Av2DmMarginRule; + +typedef struct Av2DmViolationDescriptor { + const char *spec; + const char *condition; + const char *relation; + const char *observed_name; + const char *limit_name; + const char *unit; + const char *requirement; + const char *margin_name; + Av2DmMarginRule margin_rule; +} Av2DmViolationDescriptor; + +#define DM_DESCRIPTOR(spec, condition, relation, observed, limit, unit, \ + requirement, margin, margin_rule) \ + { spec, condition, relation, observed, limit, \ + unit, requirement, margin, margin_rule } + +static const Av2DmViolationDescriptor violation_descriptors[] = { + DM_DESCRIPTOR("annex_e.decoder_model_error_codes", + "free_decode_frame_buffer_available", "available", + "free_buffers", "required_free_buffers", "buffers", "available", + "availability", AV2_DM_MARGIN_NONE), + DM_DESCRIPTOR("annex_e.decoder_model_error_codes", + "show_existing_reference_buffer_available", "available", + "reference_buffer_state", "required_buffer_state", "buffers", + "available", "availability", AV2_DM_MARGIN_NONE), + DM_DESCRIPTOR("annex_e.decoder_model_error_codes", + "output_time_lte_presentation_time", "lte", "output_time", + "presentation_time", "seconds", "maximum", "lateness", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_e.smoothing_buffer_underflow", + "scheduled_removal_gte_last_bit_arrival", "gte", + "scheduled_removal", "last_bit_arrival", "seconds", "minimum", + "lateness", AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_e.smoothing_buffer_overflow", + "buffer_fullness_lte_buffer_size", "lte", + "buffer_fullness_bits", "buffer_size_bits", "bits", "maximum", + "excess_bits", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_e.bitstream_conformance.general", + "presentation_time_gte_previous_presentation_time", "gte", + "presentation_time", "previous_presentation_time", "seconds", + "minimum", "shortfall", AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_e.bitstream_conformance.general", + "scheduled_removal_gte_resource_removal", "gte", + "scheduled_removal", "resource_removal", "seconds", "minimum", + "shortfall", AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_e.decoder_buffer_delay_consistency", + "decoder_buffer_delay_lte_ceil_time_delta", "lte", + "time_delta_ticks", "decoder_buffer_delay_minus_one_ticks", + "ticks", "maximum", "decoder_buffer_delay_excess", + AV2_DM_MARGIN_NONE), + DM_DESCRIPTOR("annex_e.minimum_decode_time", + "available_decode_interval_gte_required_decode_interval", "gte", + "available_decode_interval", "required_decode_interval", + "seconds", "minimum", "shortfall", + AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_e.minimum_presentation_interval", + "presentation_interval_gte_required_presentation_interval", + "gte", "presentation_interval", + "required_presentation_interval", "seconds", "minimum", + "shortfall", AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_e.decode_deadline", + "decode_completion_time_lte_presentation_time", "lte", + "decode_completion_time", "presentation_time", "seconds", + "maximum", "lateness", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_e.level_imposed_constraints", + "decoder_buffer_delay_nonzero", "nonzero", + "decoder_buffer_delay", "zero", "seconds", "nonzero", + "difference", AV2_DM_MARGIN_NONE), + DM_DESCRIPTOR( + "annex_e.level_imposed_constraints", "decoder_buffer_delay_lte_maximum", + "lte", "decoder_buffer_delay", "maximum_decoder_buffer_delay", "seconds", + "maximum", "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "frame_luma_samples_lte_max_picture_size", + "lte", "frame_luma_samples", "max_picture_size", "luma_samples", + "maximum", "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "frame_width_lte_max_horizontal_size", "lte", + "frame_width", "max_horizontal_size", "luma_samples", "maximum", + "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "frame_height_lte_max_vertical_size", "lte", + "frame_height", "max_vertical_size", "luma_samples", "maximum", + "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "frame_width_gte_16", "gte", "frame_width", + "min_horizontal_size", "luma_samples", "minimum", "shortfall", + AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_a.levels", "frame_height_gte_16", "gte", "frame_height", + "min_vertical_size", "luma_samples", "minimum", "shortfall", + AV2_DM_MARGIN_LIMIT_MINUS_OBSERVED), + DM_DESCRIPTOR("annex_a.levels", "num_tiles_lte_max_tiles", "lte", "num_tiles", + "max_tiles", "tiles", "maximum", "excess", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "tile_columns_lte_max_tile_columns", "lte", + "tile_columns", "max_tile_columns", "tile_columns", "maximum", + "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "tile_width_lte_max_tile_width", "lte", + "tile_width", "max_tile_width", "luma_samples", "maximum", + "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "non_rightmost_tile_width_gte_64", "gte", + "offending_tile_width", "min_tile_width", "luma_samples", + "minimum", "shortfall", AV2_DM_MARGIN_NONE), + DM_DESCRIPTOR("annex_a.levels", "tile_area_lte_max_tile_area", "lte", + "tile_area", "max_tile_area", "luma_samples", "maximum", + "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", + "display_luma_samples_lte_output_interval_capacity", "lte", + "display_luma_samples", "display_capacity", + "luma_samples_per_interval", "maximum", "excess", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "frame_headers_lte_max_header_rate", "lte", + "frame_headers_in_window", "max_frame_headers_in_window", + "frame_headers_per_second", "maximum", "excess", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "num_ref_frames_lte_max_level_ref_frames", + "lte", "num_ref_frames", "max_level_ref_frames", + "reference_frames", "maximum", "excess", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", + "luma_sample_count_lte_frame_parsing_capacity", "lte", + "luma_sample_count", "frame_parsing_capacity", + "luma_samples_per_interval", "maximum", "excess", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "num_tiles_lte_frame_parsing_tile_limit", + "lte", "num_tiles", "frame_parsing_tile_limit", + "tiles_per_interval", "maximum", "excess", + AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "compressed_size_lte_derived_maximum", "lte", + "compressed_size", "maximum_compressed_size", "bytes", + "maximum", "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR("annex_a.levels", "frame_symbol_count_lte_derived_maximum", + "lte", "frame_symbol_count", "maximum_frame_symbols", "symbols", + "maximum", "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), + DM_DESCRIPTOR( + "annex_a.levels", "max_tile_area_times_header_rate_lte_level_limit", + "lte", "tile_area_header_rate_product", + "max_tile_area_header_rate_product", "luma_samples_x_headers_per_second", + "maximum", "excess", AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT), +}; + +#undef DM_DESCRIPTOR + +_Static_assert(sizeof(violation_descriptors) / + sizeof(violation_descriptors[0]) == + AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE + 1, + "Each decoder-model violation needs a descriptor"); + +static const Av2DmViolationDescriptor unknown_violation_descriptor = { + "unknown", "unknown_violation", "unknown", "observed_value", "limit_value", + "value", "unknown", "margin", AV2_DM_MARGIN_NONE +}; + +static const Av2DmViolationDescriptor *get_violation_descriptor( + Av2DmViolationCode code) { + if ((int)code < 0 || code > AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) { + return &unknown_violation_descriptor; + } + return &violation_descriptors[code]; +} + +bool av2_decoder_model_violation_descriptor_is_complete( + Av2DmViolationCode code) { + if ((int)code < 0 || code > AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) { + return false; + } + const Av2DmViolationDescriptor *const descriptor = + get_violation_descriptor(code); + return descriptor->spec != NULL && descriptor->spec[0] != '\0' && + descriptor->condition != NULL && descriptor->condition[0] != '\0' && + descriptor->relation != NULL && descriptor->relation[0] != '\0' && + descriptor->observed_name != NULL && + descriptor->observed_name[0] != '\0' && + descriptor->limit_name != NULL && descriptor->limit_name[0] != '\0' && + descriptor->unit != NULL && descriptor->unit[0] != '\0' && + descriptor->requirement != NULL && + descriptor->requirement[0] != '\0' && + descriptor->margin_name != NULL && descriptor->margin_name[0] != '\0'; +} + +typedef struct Av2DmTextBuilder { + char *text; + size_t size; + size_t length; + bool valid; +} Av2DmTextBuilder; + +static void append_detail(Av2DmTextBuilder *builder, const char *format, ...) { + if (!builder->valid) return; + va_list arguments; + va_start(arguments, format); + const int written = + vsnprintf(builder->text + builder->length, + builder->size - builder->length, format, arguments); + va_end(arguments); + if (written < 0 || (size_t)written >= builder->size - builder->length) { + builder->valid = false; + return; + } + builder->length += (size_t)written; +} + +static bool rational_to_long_double(const Av2DmRational *value, + long double *result) { + if (value == NULL || result == NULL) return false; + long double numerator = 0.0L; + long double denominator = 0.0L; + const long double limb_base = 18446744073709551616.0L; + for (int i = 3; i >= 0; --i) { + numerator = numerator * limb_base + (long double)value->magnitude.limbs[i]; + denominator = + denominator * limb_base + (long double)value->denominator.limbs[i]; + } + if (denominator == 0.0L) return false; + *result = numerator / denominator; + if (value->negative) *result = -*result; + return true; +} + +static bool append_decimal(Av2DmTextBuilder *builder, const char *name, + long double value, long double scale, + const char *suffix) { + bool negative = value < 0.0L; + if (negative) value = -value; + const long double scaled = value * scale * 1000.0L; + const long double rounded_value = scaled + 0.5L; + const long double uint64_limit = 18446744073709551616.0L; + if (!(rounded_value >= 0.0L) || rounded_value >= uint64_limit) { + return false; + } + const uint64_t rounded = (uint64_t)rounded_value; + append_detail(builder, " %s=%s%" PRIu64 ".%03" PRIu64 "%s", name, + negative ? "-" : "", rounded / 1000, rounded % 1000, suffix); + return builder->valid; +} + +static bool append_rational(Av2DmTextBuilder *builder, const char *name, + const Av2DmRational *value) { + char formatted[150]; + if (!format_rational(value, formatted, sizeof(formatted))) return false; + append_detail(builder, " %s=%s", name, formatted); + return builder->valid; +} + +static bool append_milliseconds(Av2DmTextBuilder *builder, const char *name, + const Av2DmRational *value) { + long double decimal; + char field[96]; + const int written = snprintf(field, sizeof(field), "%s_ms", name); + return written >= 0 && (size_t)written < sizeof(field) && + rational_to_long_double(value, &decimal) && + append_decimal(builder, field, decimal, 1000.0L, ""); +} + +static const char *affected_kind_name(Av2DmViolationAffectedKind kind) { + switch (kind) { + case AV2_DM_VIOLATION_AFFECTED_EVENT: return "event"; + case AV2_DM_VIOLATION_AFFECTED_DFG: return "dfg"; + case AV2_DM_VIOLATION_AFFECTED_OUTPUT: return "output"; + case AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT: return "temporal_unit"; + } + return "unknown"; +} + +static bool append_payload_details(Av2DmTextBuilder *builder, + const Av2DmViolation *violation) { + const Av2DmViolationDetail *const detail = &violation->detail; + switch (detail->kind) { + case AV2_DM_VIOLATION_DETAIL_NONE: return true; + case AV2_DM_VIOLATION_DETAIL_BUFFER_POOL: { + const Av2DmBufferPoolViolationDetail *const pool = + &detail->value.buffer_pool; + append_detail(builder, + " lane=%s pool_size=%u frames_in_use=%u free_buffers=%u " + "decoder_held_buffers=%u player_held_buffers=%u", + pool->resource_lane ? "resource" : "model", pool->pool_size, + pool->frames_in_use, pool->free_buffers, + pool->decoder_held_buffers, pool->player_held_buffers); + return builder->valid; + } + case AV2_DM_VIOLATION_DETAIL_REFERENCE_SLOT: { + const Av2DmReferenceSlotViolationDetail *const slot = + &detail->value.reference_slot; + append_detail(builder, " requested_reference_slot=%d slot_in_range=%d", + slot->requested_slot, slot->slot_in_range); + if (slot->slot_in_range) { + append_detail(builder, " ref_valid=%d vbi=%d", slot->reference_valid, + slot->buffer_index); + } else { + append_detail(builder, " ref_valid=NA vbi=NA"); + } + append_detail(builder, + " pool_size=%u frames_in_use=%u free_buffers=%u " + "decoder_held_buffers=%u player_held_buffers=%u", + slot->pool.pool_size, slot->pool.frames_in_use, + slot->pool.free_buffers, slot->pool.decoder_held_buffers, + slot->pool.player_held_buffers); + return builder->valid; + } + case AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY: + append_detail(builder, " decoder_buffer_delay_ticks=%u", + detail->value.delay_consistency.decoder_buffer_delay_ticks); + if (detail->value.delay_consistency.ceil_time_delta_present) { + Av2DmRational decoder_delay; + Av2DmRational excess; + if (!av2_dm_rational_make( + detail->value.delay_consistency.decoder_buffer_delay_ticks, 1, + &decoder_delay) || + !av2_dm_rational_subtract( + &decoder_delay, + &detail->value.delay_consistency.ceil_time_delta_ticks, + &excess) || + !append_rational( + builder, "ceil_time_delta_ticks", + &detail->value.delay_consistency.ceil_time_delta_ticks) || + !append_rational(builder, "decoder_buffer_delay_excess", &excess)) { + return false; + } + } else { + append_detail(builder, " ceil_time_delta_ticks=NA"); + } + return builder->valid; + case AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME: + return append_rational( + builder, "frame_decode_time", + &detail->value.minimum_decode_time.frame_decode_time) && + append_rational( + builder, "one_header_time", + &detail->value.minimum_decode_time.one_header_time) && + append_milliseconds( + builder, "frame_decode_time", + &detail->value.minimum_decode_time.frame_decode_time) && + append_milliseconds( + builder, "one_header_time", + &detail->value.minimum_decode_time.one_header_time); + case AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL: + return append_rational(builder, "frame_parsing_interval", + &detail->value.frame_interval) && + append_milliseconds(builder, "frame_parsing_interval", + &detail->value.frame_interval); + } + return false; +} + +bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, + uint64_t max_display_rate, + uint64_t max_decode_rate, + char *text, size_t text_size) { + if (violation == NULL || text == NULL || text_size == 0) return false; + text[0] = '\0'; + Av2DmTextBuilder builder = { text, text_size, 0, true }; + const Av2DmViolationDescriptor *const descriptor = + get_violation_descriptor(violation->code); + append_detail(&builder, "unit=%s requirement=%s relation=%s condition=%s", + descriptor->unit, descriptor->requirement, descriptor->relation, + descriptor->condition); + if (violation->affected_kind != AV2_DM_VIOLATION_AFFECTED_EVENT || + violation->affected_index != violation->event_index) { + append_detail(&builder, " affected=%s", + affected_kind_name(violation->affected_kind)); + if (violation->affected_kind == AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT) { + append_detail(&builder, " affected_temporal_unit=%" PRIu64, + violation->affected_index); + } else { + append_detail(&builder, " affected_event=%" PRIu64, + violation->affected_index); + } + } + if (violation->observed_present && + !append_rational(&builder, descriptor->observed_name, + &violation->observed)) { + return false; + } + if (violation->limit_present && + !append_rational(&builder, descriptor->limit_name, &violation->limit)) { + return false; + } + + Av2DmRational margin; + bool margin_present = false; + if (violation->observed_present && violation->limit_present && + descriptor->margin_rule != AV2_DM_MARGIN_NONE) { + margin_present = + descriptor->margin_rule == AV2_DM_MARGIN_OBSERVED_MINUS_LIMIT + ? av2_dm_rational_subtract(&violation->observed, &violation->limit, + &margin) + : av2_dm_rational_subtract(&violation->limit, &violation->observed, + &margin); + if (!margin_present || + !append_rational(&builder, descriptor->margin_name, &margin)) { + return false; + } + } + + if (!append_payload_details(&builder, violation)) return false; + if (strcmp(descriptor->unit, "seconds") == 0) { + if ((violation->observed_present && + !append_milliseconds(&builder, descriptor->observed_name, + &violation->observed)) || + (violation->limit_present && + !append_milliseconds(&builder, descriptor->limit_name, + &violation->limit)) || + (margin_present && + !append_milliseconds(&builder, descriptor->margin_name, &margin))) { + return false; + } + } + + if (violation->code == AV2_DM_VIOLATION_MIN_TILE_WIDTH) { + append_detail(&builder, + " non_rightmost_tile_width_valid=0 " + "offending_tile_width=NA min_tile_width=64"); + } + if (violation->code == AV2_DM_VIOLATION_MAX_DISPLAY_RATE && + violation->limit_present && max_display_rate != 0) { + Av2DmRational interval = violation->limit; + if (!av2_dm_rational_divide_u64(&interval, max_display_rate, &interval) || + !append_rational(&builder, "output_interval", &interval) || + !append_milliseconds(&builder, "output_interval", &interval)) { + return false; + } + long double samples; + long double seconds; + if (!violation->observed_present || + !rational_to_long_double(&violation->observed, &samples) || + !rational_to_long_double(&interval, &seconds) || seconds <= 0.0L || + !append_decimal(&builder, "observed_rate", samples / seconds, 0.000001L, + "Msamples/s") || + !append_decimal(&builder, "limit_rate", (long double)max_display_rate, + 0.000001L, "Msamples/s")) { + return false; + } + } else if (violation->code == AV2_DM_VIOLATION_FRAME_DECODE_RATE && + violation->detail.kind == AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL && + max_decode_rate != 0) { + long double samples; + long double seconds; + if (!violation->observed_present || + !rational_to_long_double(&violation->observed, &samples) || + !rational_to_long_double(&violation->detail.value.frame_interval, + &seconds) || + seconds <= 0.0L || + !append_decimal(&builder, "observed_rate", samples / seconds, 0.000001L, + "Msamples/s") || + !append_decimal(&builder, "limit_rate", (long double)max_decode_rate, + 0.000001L, "Msamples/s")) { + return false; + } + } else if (violation->code == AV2_DM_VIOLATION_FRAME_TILE_RATE && + violation->detail.kind == AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL) { + long double tiles; + long double tile_limit; + long double seconds; + if (!violation->observed_present || !violation->limit_present || + !rational_to_long_double(&violation->observed, &tiles) || + !rational_to_long_double(&violation->limit, &tile_limit) || + !rational_to_long_double(&violation->detail.value.frame_interval, + &seconds) || + seconds <= 0.0L || + !append_decimal(&builder, "observed_tile_rate", tiles / seconds, 1.0L, + "tiles/s") || + !append_decimal(&builder, "limit_tile_rate", tile_limit / seconds, 1.0L, + "tiles/s")) { + return false; + } + } + append_detail(&builder, " spec=%s", descriptor->spec); + return builder.valid; +} + +static const Av2DmContextEvent *find_context_event(const Av2DmRunReport *report, + uint64_t event_index) { + if (report->current_event_valid && + report->current_event.event_index == event_index) { + return &report->current_event; + } + return NULL; +} + +static const char *context_event_type_name(Av2DmContextEventType type) { + switch (type) { + case AV2_DM_CONTEXT_FRAME: return "frame"; + case AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION: return "olk_invalidation"; + case AV2_DM_CONTEXT_REFERENCE_UPDATE: return "reference_update"; + case AV2_DM_CONTEXT_OUTPUT: return "output"; + case AV2_DM_CONTEXT_RECOVERY_RESET: return "recovery_reset"; + } + return "unknown"; +} + +static void print_event_location(const Av2DmRunReport *report, + uint64_t event_index) { + const Av2DmContextEvent *const event = + find_context_event(report, event_index); + if (event == NULL) return; + fprintf(stderr, " event_type=%s frame_unit=%" PRIu64, + context_event_type_name(event->type), event->source_frame_unit_index); + if (event->type == AV2_DM_CONTEXT_FRAME) { + fprintf(stderr, " temporal_unit=%" PRIu64, + event->frame.temporal_unit_index); + } else if (event->type == AV2_DM_CONTEXT_OUTPUT) { + fprintf(stderr, + " presentation_frame_unit=%" PRIu64 " temporal_unit=%" PRIu64, + event->presentation_frame_unit_index, + event->output.temporal_unit_index); + } +} + +static void print_violation_explanation(const Av2DmRunReport *report, + const Av2DmViolation *violation) { + char details[1024]; + if (av2_decoder_model_format_violation_details( + violation, report->max_display_rate, report->max_decode_rate, details, + sizeof(details))) { + fprintf(stderr, " %s", details); + } else { + fprintf(stderr, " details=unavailable"); + } +} + +static void report_decoder_model_violation(void *opaque, + const Av2DmViolation *violation) { + Av2DmRunReport *const report = (Av2DmRunReport *)opaque; + if (report->verifier->fatal_violation) return; + if (report->violations != UINT64_MAX) { + ++report->violations; + } + if (report->verifier->check_mode == AVM_DECODER_MODEL_CHECK_FATAL) { + report->verifier->fatal_violation = true; + } + char observed[150]; + char limit[150]; + if (!format_rational( + violation->observed_present ? &violation->observed : NULL, observed, + sizeof(observed))) { + snprintf(observed, sizeof(observed), "NA"); + } + if (!format_rational(violation->limit_present ? &violation->limit : NULL, + limit, sizeof(limit))) { + snprintf(limit, sizeof(limit), "NA"); + } + fprintf(stderr, + "AV2_DECODER_MODEL_WARNING status=NON_CONFORMANT code=%s " + "xlayer=%d ops=%d op=%d rap=%" PRId64 + " level=%u level_name=%s tier=%s scope=%s mode=%s event=%" PRIu64, + av2_dm_violation_code_name(violation->code), report->scope.xlayer_id, + report->scope.ops_id, report->scope.operating_point, report->rap, + report->level_idx, level_name(report->level_idx), + tier_name(report->tier), scope_name(&report->scope), + mode_name(report->mode), violation->event_index); + fprintf(stderr, " cvs=%" PRIu64, report->cvs); + print_event_location(report, violation->event_index); + fprintf(stderr, " observed=%s limit=%s", observed, limit); + print_violation_explanation(report, violation); + fprintf(stderr, "\n"); +} + +bool av2_decoder_model_report_violation_for_testing( + const Av2DmViolation *violation, bool fatal_mode, uint64_t *violation_count, + bool *fatal_violation) { + if (violation == NULL || violation_count == NULL || fatal_violation == NULL) { + return false; + } + Av2DecoderModelVerifier verifier; + memset(&verifier, 0, sizeof(verifier)); + verifier.check_mode = + fatal_mode ? AVM_DECODER_MODEL_CHECK_FATAL : AVM_DECODER_MODEL_CHECK_WARN; + Av2DmRunReport report; + memset(&report, 0, sizeof(report)); + report.verifier = &verifier; + report.scope.xlayer_id = 0; + report.scope.ops_xlayer_id = -1; + report.scope.ops_id = -1; + report.scope.operating_point = -1; + report.scope.whole_xlayer = true; + report.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + report.cvs = 1; + report_decoder_model_violation(&report, violation); + *violation_count = report.violations; + *fatal_violation = verifier.fatal_violation; + return true; +} + +static Av2DmResultStatus aggregate_status(const uint64_t status_count[4]) { + if (status_count[AV2_DM_RESULT_NON_CONFORMANT] != 0) { + return AV2_DM_RESULT_NON_CONFORMANT; + } + if (status_count[AV2_DM_RESULT_INDETERMINATE] != 0) { + return AV2_DM_RESULT_INDETERMINATE; + } + if (status_count[AV2_DM_RESULT_CONFORMANT] != 0) { + return AV2_DM_RESULT_CONFORMANT; + } + return AV2_DM_RESULT_NOT_APPLICABLE; +} + +static void ensure_cvs_open(Av2DecoderModelVerifier *verifier, int xlayer_id) { + if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return; + Av2DmCvsAggregate *const cvs = &verifier->cvs[xlayer_id]; + if (cvs->open) return; + (void)increment_u64(verifier, &cvs->number); + cvs->open = true; + cvs->verification_complete = true; + cvs->reason = AV2_DM_REASON_NONE; + cvs->violations = 0; + memset(cvs->run_status_count, 0, sizeof(cvs->run_status_count)); +} + +static void emit_result(Av2DecoderModelVerifier *verifier, + const Av2DmResult *model_result, int64_t rap, + Av2DmIndeterminateReason reason, + const Av2DmRunReport *report) { + Av2DmResult result = *model_result; + if (result.arithmetic_failed) { + reason = AV2_DM_REASON_INTERNAL_FAILURE; + verifier->aggregate_incomplete = true; + if (verifier->error_code == AV2_DM_VERIFIER_ERROR_NONE) { + verifier->error_code = AV2_DM_VERIFIER_ERROR_ARITHMETIC; + } + emit_verifier_error(verifier, verifier->error_code, + report != NULL ? report->scope.xlayer_id : -1, + report != NULL ? report->cvs : 0); + } else if (result.missing_required_input && reason == AV2_DM_REASON_NONE) { + reason = AV2_DM_REASON_MISSING_REQUIRED_INPUT; + } + Av2DmCvsAggregate *cvs = NULL; + if (report != NULL && report->scope.xlayer_id >= 0 && + report->scope.xlayer_id < MAX_NUM_XLAYERS) { + cvs = &verifier->cvs[report->scope.xlayer_id]; + } + + if ((unsigned int)result.status >= 4) { + mark_failed(verifier); + result.status = AV2_DM_RESULT_INDETERMINATE; + } + if (verifier->failed) reason = AV2_DM_REASON_INTERNAL_FAILURE; + if (result.status != AV2_DM_RESULT_NON_CONFORMANT && + reason != AV2_DM_REASON_NONE) { + result.status = AV2_DM_RESULT_INDETERMINATE; + result.missing_required_input = true; + } + + bool accounting_overflow = + verifier->result_count == UINT64_MAX || + verifier->result_status_count[result.status] == UINT64_MAX; + if (cvs != NULL) { + accounting_overflow = accounting_overflow || + cvs->run_status_count[result.status] == UINT64_MAX; + } + if (accounting_overflow) { + mark_arithmetic_failed(verifier); + reason = AV2_DM_REASON_INTERNAL_FAILURE; + if (result.status != AV2_DM_RESULT_NON_CONFORMANT) { + result.status = AV2_DM_RESULT_INDETERMINATE; + result.missing_required_input = true; + } + } + // An accounting failure can change the destination from CONFORMANT to + // INDETERMINATE. Recheck that final target before any diagnostic is printed. + if (verifier->result_status_count[result.status] == UINT64_MAX || + (cvs != NULL && cvs->run_status_count[result.status] == UINT64_MAX)) { + mark_arithmetic_failed(verifier); + reason = AV2_DM_REASON_INTERNAL_FAILURE; + } + + (void)increment_u64(verifier, &verifier->result_count); + (void)increment_u64(verifier, &verifier->result_status_count[result.status]); + if (cvs != NULL) { + (void)increment_u64(verifier, &cvs->run_status_count[result.status]); + add_u64_saturated(&cvs->violations, result.violations); + if (result.status == AV2_DM_RESULT_INDETERMINATE || + reason != AV2_DM_REASON_NONE) { + cvs->verification_complete = false; + if (cvs->reason == AV2_DM_REASON_NONE || + reason == AV2_DM_REASON_INTERNAL_FAILURE) { + cvs->reason = reason; + } + } + } + + fprintf(stderr, + "AV2_DECODER_MODEL_RESULT status=%s xlayer=%d ops=%d op=%d " + "rap=%" PRId64 " mode=%s decoded=%" PRIu64 " outputs=%" PRIu64 + " reordered_outputs=%" PRIu64 " violations=%" PRIu64 " reason=%s", + result_name(result.status), result.scope.xlayer_id, + result.scope.ops_id, result.scope.operating_point, rap, + mode_name(result.mode), result.decoded_frames, result.output_frames, + result.reordered_outputs, result.violations, + indeterminate_reason_name(reason)); + if (report != NULL) { + fprintf(stderr, " level=%u level_name=%s tier=%s scope=%s", + report->level_idx, level_name(report->level_idx), + tier_name(report->tier), scope_name(&report->scope)); + } + fprintf(stderr, "\n"); +} + +static bool run_seed_contains_generation(const Av2DmLiveRun *run, + uint64_t generation) { + for (uint32_t i = 0; i < run->config.ras_seed_count; ++i) { + if (run->config.ras_seeds[i].generation == generation) return true; + } + return false; +} + +static void apply_event_to_run(Av2DecoderModelVerifier *verifier, + Av2DmLiveRun *run, + const Av2DmContextEvent *event) { + if (verifier->fatal_violation) return; + if (run->olk && + event->source_frame_unit_index > run->start_source_frame_unit && + event->leading_frame) { + return; + } + run->report.current_event = *event; + run->report.current_event_valid = true; + if (event->indeterminate_reason != AV2_DM_REASON_NONE && + run->reason == AV2_DM_REASON_NONE) { + run->reason = event->indeterminate_reason; + } + switch (event->type) { + case AV2_DM_CONTEXT_FRAME: + av2_decoder_model_start_frame(run->model, &event->frame); + break; + case AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION: + av2_decoder_model_invalidate_olk_reference_buffers(run->model, + event->ref_valid_mask); + break; + case AV2_DM_CONTEXT_REFERENCE_UPDATE: + av2_decoder_model_update_reference_buffers(run->model, + &event->reference_update); + if (event->set_initial_presentation_delay) { + av2_decoder_model_set_initial_presentation_delay(run->model, + event->event_index); + } + break; + case AV2_DM_CONTEXT_OUTPUT: { + Av2DmOutputEvent output = event->output; + av2_decoder_model_output_frame(run->model, &output); + Av2DmState state; + if (av2_decoder_model_get_state(run->model, &state) && + state.last_presentation_offset_valid) { + if (verifier->replay_last_presentation_offset_valid) { + verifier->replay_previous_presentation_offset = + verifier->replay_last_presentation_offset; + verifier->replay_previous_presentation_offset_valid = true; + } + verifier->replay_last_presentation_offset = + state.last_presentation_offset; + verifier->replay_last_presentation_offset_valid = true; + } + break; + } + case AV2_DM_CONTEXT_RECOVERY_RESET: break; + } + run->report.current_event_valid = false; + memset(&run->report.current_event, 0, sizeof(run->report.current_event)); +} + +static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, + Av2DmContext *context, + const Av2DmContextEvent *start_frame, + int64_t rap) { + if (context->run_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } + if (!reserve_array(verifier, (void **)&context->runs, &context->run_capacity, + context->run_count + 1, sizeof(*context->runs))) { + mark_failed(verifier); + return NULL; + } + Av2DmLiveRun *const run = avm_calloc(1, sizeof(*run)); + if (run == NULL) { + mark_allocation_failed(verifier); + return NULL; + } + run->config = start_frame->config; + run->reason = start_frame->indeterminate_reason; + if (!start_frame->config_present || run->reason != AV2_DM_REASON_NONE) { + run->config.applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + if (start_frame->frame_obu_type == OBU_RAS_FRAME) { + run->config.ras_start = true; + run->config.ras_seed_complete = start_frame->ras_seed_complete; + run->config.ras_seed_count = start_frame->ras_seed_count; + memcpy(run->config.ras_seeds, start_frame->ras_seeds, + sizeof(run->config.ras_seeds)); + } + run->rap = rap; + run->olk = start_frame->frame_obu_type == OBU_OPEN_LOOP_KEY; + run->start_source_frame_unit = start_frame->source_frame_unit_index; + run->report.verifier = verifier; + run->report.scope = run->config.scope; + run->report.mode = run->config.mode; + run->report.rap = rap; + run->report.cvs = verifier->cvs[context->key.xlayer_id].number; + run->report.level_idx = run->config.level_idx; + run->report.tier = run->config.tier; + if (run->config.level_limits_present) { + run->report.max_display_rate = run->config.level_limits.max_display_rate; + run->report.max_decode_rate = run->config.level_limits.max_decode_rate; + } else { + Av2DmLevelLimits limits; + if (av2_dm_get_level_limits(run->config.level_idx, run->config.tier, + run->config.profile, &limits)) { + run->report.max_display_rate = limits.max_display_rate; + run->report.max_decode_rate = limits.max_decode_rate; + } + } + run->model = av2_decoder_model_create( + &run->config, report_decoder_model_violation, &run->report); + if (run->model == NULL) { + avm_free(run); + mark_allocation_failed(verifier); + return NULL; + } + context->runs[context->run_count] = run; + if (!increment_size(verifier, &context->run_count)) { + av2_decoder_model_destroy(run->model); + avm_free(run); + return NULL; + } + return run; +} + +static void finish_context_runs(Av2DecoderModelVerifier *verifier, + Av2DmContext *context) { + for (size_t i = 0; i < context->run_count; ++i) { + Av2DmLiveRun *const run = context->runs[i]; + if (!verifier->failed && !verifier->fatal_violation) { + av2_decoder_model_finish(run->model); + } + Av2DmResult result; + if (av2_decoder_model_get_result(run->model, &result)) { + Av2DmIndeterminateReason reason = run->reason; + if ((verifier->failed || verifier->fatal_violation) && + result.status != AV2_DM_RESULT_NON_CONFORMANT) { + av2_decoder_model_mark_incomplete(run->model); + (void)av2_decoder_model_get_result(run->model, &result); + if (reason == AV2_DM_REASON_NONE) { + reason = verifier->failed ? AV2_DM_REASON_INTERNAL_FAILURE + : AV2_DM_REASON_MISSING_REQUIRED_INPUT; + } + } + emit_result(verifier, &result, run->rap, reason, &run->report); + } else { + mark_failed(verifier); + } + av2_decoder_model_destroy(run->model); + avm_free(run); + } + context->run_count = 0; +} + +static void finish_partial_context_runs(Av2DecoderModelVerifier *verifier, + Av2DmContext *context) { + for (size_t i = 0; i < context->run_count; ++i) { + Av2DmLiveRun *const run = context->runs[i]; + Av2DmResult result; + if (av2_decoder_model_get_result(run->model, &result)) { + Av2DmIndeterminateReason reason = run->reason; + if (result.status != AV2_DM_RESULT_NON_CONFORMANT) { + av2_decoder_model_mark_incomplete(run->model); + (void)av2_decoder_model_get_result(run->model, &result); + if (reason == AV2_DM_REASON_NONE) { + reason = verifier->failed ? AV2_DM_REASON_INTERNAL_FAILURE + : AV2_DM_REASON_MISSING_REQUIRED_INPUT; + } + } + emit_result(verifier, &result, run->rap, reason, &run->report); + } else { + mark_failed(verifier); + } + av2_decoder_model_destroy(run->model); + avm_free(run); + } + context->run_count = 0; +} + +static void destroy_context_runs(Av2DmContext *context) { + for (size_t i = 0; i < context->run_count; ++i) { + av2_decoder_model_destroy(context->runs[i]->model); + avm_free(context->runs[i]); + } + context->run_count = 0; +} + +static bool prefix_event_applies(const Av2DmLiveRun *run, + const Av2DmContextEvent *event) { + if (run->olk) { + return event->type == AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION; + } + if (run->config.ras_start) { + return event->type == AV2_DM_CONTEXT_OUTPUT && + run_seed_contains_generation(run, event->generation); + } + return true; +} + +static void update_live_run_parameters(Av2DmLiveRun *run, + const Av2DmContextEvent *event) { + run->report.current_event = *event; + run->report.current_event_valid = true; + const bool updated = av2_decoder_model_update_parameters( + run->model, &event->config, event->event_index); + run->report.current_event_valid = false; + memset(&run->report.current_event, 0, sizeof(run->report.current_event)); + if (!updated) { + if (run->reason == AV2_DM_REASON_NONE) { + run->reason = AV2_DM_REASON_MISSING_REQUIRED_INPUT; + } + return; + } + const bool ras_start = run->config.ras_start; + const bool ras_seed_complete = run->config.ras_seed_complete; + const uint32_t ras_seed_count = run->config.ras_seed_count; + Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; + memcpy(ras_seeds, run->config.ras_seeds, sizeof(ras_seeds)); + const uint32_t initial_display_delay = run->config.initial_display_delay; + run->config = event->config; + run->config.initial_display_delay = initial_display_delay; + run->config.ras_start = ras_start; + run->config.ras_seed_complete = ras_seed_complete; + run->config.ras_seed_count = ras_seed_count; + memcpy(run->config.ras_seeds, ras_seeds, sizeof(run->config.ras_seeds)); + run->report.mode = run->config.mode; + run->report.level_idx = run->config.level_idx; + run->report.tier = run->config.tier; + run->report.max_display_rate = 0; + run->report.max_decode_rate = 0; + if (run->config.level_limits_present) { + run->report.max_display_rate = run->config.level_limits.max_display_rate; + run->report.max_decode_rate = run->config.level_limits.max_decode_rate; + } else { + Av2DmLevelLimits limits; + if (av2_dm_get_level_limits(run->config.level_idx, run->config.tier, + run->config.profile, &limits)) { + run->report.max_display_rate = limits.max_display_rate; + run->report.max_decode_rate = limits.max_decode_rate; + } + } +} + +static void mark_live_runs_incomplete(Av2DmContext *context) { + for (size_t i = 0; i < context->run_count; ++i) { + Av2DmLiveRun *const run = context->runs[i]; + av2_decoder_model_mark_incomplete(run->model); + if (run->reason == AV2_DM_REASON_NONE) { + run->reason = AV2_DM_REASON_MISSING_REQUIRED_INPUT; + } + } +} + +static void dispatch_context_event(Av2DecoderModelVerifier *verifier, + Av2DmContext *context, + const Av2DmContextEvent *event) { + if (verifier->fatal_violation) return; + if (event->type != AV2_DM_CONTEXT_FRAME) { + for (size_t i = 0; i < context->run_count && !verifier->fatal_violation; + ++i) { + apply_event_to_run(verifier, context->runs[i], event); + } + if (!verifier->current_source_frame_dispatched) { + if (context->prefix_event_count == SIZE_MAX) { + mark_arithmetic_failed(verifier); + return; + } + if (!reserve_array(verifier, (void **)&context->prefix_events, + &context->prefix_event_capacity, + context->prefix_event_count + 1, + sizeof(*context->prefix_events))) { + mark_failed(verifier); + return; + } + context->prefix_events[context->prefix_event_count] = *event; + if (!increment_size(verifier, &context->prefix_event_count)) return; + } + return; + } + + ensure_cvs_open(verifier, context->key.xlayer_id); + const bool config_changed = + context->last_config_present && + (context->last_stream_generation != event->stream_generation || + memcmp(&context->last_config, &event->config, sizeof(event->config)) != + 0); + if (config_changed) { + if (context->last_stream_generation == event->stream_generation && + event->frame.random_access_point && + event->frame.decoder_model_parameters_updated) { + for (size_t i = 0; i < context->run_count; ++i) { + update_live_run_parameters(context->runs[i], event); + } + } else { + // Annex E resets FirstBitArrival only when a new parameter set is + // received at a random-access point. A different transition cannot be + // verified by silently restarting the sequential model. + mark_live_runs_incomplete(context); + } + } + + bool created_segment = false; + if (context->run_count == 0) { + const int64_t rap = event->frame.random_access_point + ? (int64_t)event->source_frame_unit_index + : -1; + Av2DmLiveRun *const run = create_live_run(verifier, context, event, rap); + if (run == NULL) return; + for (size_t i = 0; i < context->prefix_event_count; ++i) { + if (prefix_event_applies(run, &context->prefix_events[i])) { + apply_event_to_run(verifier, run, &context->prefix_events[i]); + } + } + created_segment = true; + } + if (!created_segment && event->frame.random_access_point) { + Av2DmLiveRun *const run = create_live_run( + verifier, context, event, (int64_t)event->source_frame_unit_index); + if (run == NULL) return; + for (size_t i = 0; i < context->prefix_event_count; ++i) { + if (prefix_event_applies(run, &context->prefix_events[i])) { + apply_event_to_run(verifier, run, &context->prefix_events[i]); + } + } + } + for (size_t i = 0; i < context->run_count && !verifier->fatal_violation; + ++i) { + apply_event_to_run(verifier, context->runs[i], event); + } + context->last_config_present = true; + context->last_config = event->config; + context->last_stream_generation = event->stream_generation; + context->last_ras_seed_complete = event->ras_seed_complete; + context->last_ras_seed_count = event->ras_seed_count; +} + +static void finish_xlayer_cvs_internal(Av2DecoderModelVerifier *verifier, + int xlayer_id, bool partial) { + if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS || + !verifier->cvs[xlayer_id].open) { + return; + } + partial = partial || verifier->failed || verifier->fatal_violation; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (context->key.xlayer_id != xlayer_id) continue; + if (partial) { + finish_partial_context_runs(verifier, context); + } else { + finish_context_runs(verifier, context); + } + if (verifier->failed || verifier->fatal_violation) partial = true; + context->prefix_event_count = 0; + context->last_config_present = false; + rebuild_incomplete_extraction(verifier, context); + } + Av2DmCvsAggregate *const cvs = &verifier->cvs[xlayer_id]; + if (partial) cvs->verification_complete = false; + if (verifier->failed) { + cvs->verification_complete = false; + cvs->reason = AV2_DM_REASON_INTERNAL_FAILURE; + if (cvs->run_status_count[AV2_DM_RESULT_NON_CONFORMANT] == 0) { + cvs->run_status_count[AV2_DM_RESULT_INDETERMINATE] = 1; + } + } + Av2DmResultStatus status = aggregate_status(cvs->run_status_count); + if (verifier->bitstream_cvs == UINT64_MAX || + verifier->bitstream_status_count[status] == UINT64_MAX) { + mark_arithmetic_failed(verifier); + cvs->verification_complete = false; + cvs->reason = AV2_DM_REASON_INTERNAL_FAILURE; + if (status != AV2_DM_RESULT_NON_CONFORMANT) { + cvs->run_status_count[AV2_DM_RESULT_INDETERMINATE] = 1; + status = AV2_DM_RESULT_INDETERMINATE; + } + } + // The failure above can redirect a conformant CVS to the indeterminate + // counter. Recheck and saturate that final target before reporting it. + if (verifier->bitstream_status_count[status] == UINT64_MAX) { + mark_arithmetic_failed(verifier); + cvs->verification_complete = false; + cvs->reason = AV2_DM_REASON_INTERNAL_FAILURE; + } + (void)increment_u64(verifier, &verifier->bitstream_cvs); + (void)increment_u64(verifier, &verifier->bitstream_status_count[status]); + if (status == AV2_DM_RESULT_NON_CONFORMANT && + !verifier->first_non_conformant_valid) { + verifier->first_non_conformant_valid = true; + verifier->first_non_conformant_xlayer = xlayer_id; + verifier->first_non_conformant_cvs = cvs->number; + } + fprintf(stderr, + "AV2_DECODER_MODEL_CVS_RESULT status=%s xlayer=%d cvs=%" PRIu64 + " violations=%" PRIu64 " verification_complete=%d reason=%s\n", + result_name(status), xlayer_id, cvs->number, cvs->violations, + cvs->verification_complete ? 1 : 0, + indeterminate_reason_name(cvs->reason)); + cvs->open = false; +} + +static void finish_xlayer_cvs(Av2DecoderModelVerifier *verifier, + int xlayer_id) { + finish_xlayer_cvs_internal(verifier, xlayer_id, false); +} + +static void finish_all_cvs(Av2DecoderModelVerifier *verifier) { + for (int xlayer_id = 0; xlayer_id < MAX_NUM_XLAYERS; ++xlayer_id) { + finish_xlayer_cvs(verifier, xlayer_id); + } +} + +static void finish_all_cvs_partial(Av2DecoderModelVerifier *verifier) { + for (int xlayer_id = 0; xlayer_id < MAX_NUM_XLAYERS; ++xlayer_id) { + finish_xlayer_cvs_internal(verifier, xlayer_id, true); + } +} + +static void emit_bitstream_result(Av2DecoderModelVerifier *verifier, + bool complete) { + if (verifier->bitstream_result_emitted) return; + Av2DmResultStatus status = aggregate_status(verifier->bitstream_status_count); + if (verifier->failed && status != AV2_DM_RESULT_NON_CONFORMANT) { + status = AV2_DM_RESULT_INDETERMINATE; + complete = false; + } + fprintf( + stderr, + "AV2_DECODER_MODEL_BITSTREAM_RESULT status=%s complete=%d cvs=%" PRIu64 + " conformant_cvs=%" PRIu64 " non_conformant_cvs=%" PRIu64 + " indeterminate_cvs=%" PRIu64 " not_applicable_cvs=%" PRIu64 + " first_non_conformant_xlayer=%d first_non_conformant_cvs=%" PRIu64 "\n", + result_name(status), complete ? 1 : 0, verifier->bitstream_cvs, + verifier->bitstream_status_count[AV2_DM_RESULT_CONFORMANT], + verifier->bitstream_status_count[AV2_DM_RESULT_NON_CONFORMANT], + verifier->bitstream_status_count[AV2_DM_RESULT_INDETERMINATE], + verifier->bitstream_status_count[AV2_DM_RESULT_NOT_APPLICABLE], + verifier->first_non_conformant_valid + ? verifier->first_non_conformant_xlayer + : -1, + verifier->first_non_conformant_valid ? verifier->first_non_conformant_cvs + : 0); + verifier->bitstream_result_emitted = true; +} + +static void find_error_location(const Av2DecoderModelVerifier *verifier, + int *xlayer_id, uint64_t *cvs) { + *xlayer_id = -1; + *cvs = 0; + for (int i = 0; i < MAX_NUM_XLAYERS; ++i) { + if (verifier->cvs[i].open) { + *xlayer_id = i; + *cvs = verifier->cvs[i].number; + return; + } + } +} + +void av2_decoder_model_verifier_finish(AV2Decoder *pbi) { + if (pbi == NULL) return; + if (pbi->decoder_model_verifier == NULL) { + if (pbi->decoder_model_verifier_allocation_failed && + !pbi->decoder_model_verifier_allocation_reported) { + fprintf(stderr, + "AV2_DECODER_MODEL_ERROR code=ALLOCATION_FAILURE xlayer=-1 " + "cvs=0\n"); + emit_generic_internal_failure_result(); + emit_generic_internal_failure_bitstream_result(); + pbi->decoder_model_verifier_allocation_reported = true; + } + return; + } + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (verifier->finished) return; + if (verifier->failed) { + int xlayer_id; + uint64_t cvs; + find_error_location(verifier, &xlayer_id, &cvs); + emit_verifier_error(verifier, verifier->error_code, xlayer_id, cvs); + } + if (!verifier->failed) { + Av2DmAdapterEvent *const finish = + append_event(verifier, AV2_DM_ADAPTER_FINISH); + if (finish != NULL) verifier->finish_event = finish->index; + } + verifier->finished = true; + if (verifier->fatal_violation) { + finish_all_cvs_partial(verifier); + } else { + finish_all_cvs(verifier); + } + if (verifier->failed && verifier->result_count == 0) { + emit_generic_internal_failure_result(); + (void)increment_u64(verifier, &verifier->result_count); + (void)increment_u64( + verifier, &verifier->result_status_count[AV2_DM_RESULT_INDETERMINATE]); + } + emit_bitstream_result(verifier, !verifier->failed && + !verifier->fatal_violation && + !verifier->aggregate_incomplete); +} + +bool av2_decoder_model_verifier_should_stop(const AV2Decoder *pbi) { + return pbi != NULL && pbi->decoder_model_verifier != NULL && + pbi->decoder_model_verifier->fatal_violation; +} + +static uint32_t saturate_size_to_u32(size_t value) { + return value > UINT32_MAX ? UINT32_MAX : (uint32_t)value; +} + +static void add_size_to_saturated_u32(uint32_t *total, size_t value) { + const uint32_t addend = saturate_size_to_u32(value); + if (UINT32_MAX - *total < addend) { + *total = UINT32_MAX; + } else { + *total += addend; + } +} + +bool av2_decoder_model_verifier_get_stats(const AV2Decoder *pbi, + Av2DmVerifierStats *stats) { + if (stats == NULL) return false; + memset(stats, 0, sizeof(*stats)); + if (pbi == NULL) return false; + if (pbi->decoder_model_verifier == NULL) { + if (!pbi->decoder_model_verifier_allocation_failed) return false; + stats->failed = true; + stats->result_count = + pbi->decoder_model_verifier_allocation_reported ? 1 : 0; + stats->indeterminate_results = stats->result_count; + return true; + } + const Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + stats->available = true; + stats->failed = verifier->failed; + stats->raw_obus = verifier->raw_obus; + stats->raw_bits = verifier->raw_bits; + stats->event_count = verifier->event_count; + stats->temporal_unit_index = verifier->temporal_unit_index; + stats->frame_unit_index = verifier->frame_unit_index; + stats->closed_dfgs = verifier->closed_dfgs; + stats->rap_starts = verifier->rap_start_count; + stats->temporal_points = verifier->temporal_points; + stats->temporal_point_present = verifier->temporal_point_present; + stats->temporal_point = verifier->temporal_point; + stats->contexts = saturate_size_to_u32(verifier->context_count); + stats->frame_starts = verifier->frame_starts; + stats->reference_updates = verifier->reference_updates; + stats->olk_invalidations = verifier->olk_invalidations; + stats->outputs = verifier->outputs; + stats->last_frame_start_event = verifier->last_frame_start_event; + stats->last_reference_update_event = verifier->last_reference_update_event; + stats->last_olk_invalidation_event = verifier->last_olk_invalidation_event; + stats->last_output_event = verifier->last_output_event; + stats->last_output_callback_frame_unit = + verifier->last_output_callback_frame_unit; + stats->last_output_presentation_frame_unit = + verifier->last_output_presentation_frame_unit; + stats->last_output_presentation_temporal_unit = + verifier->last_output_presentation_temporal_unit; + stats->last_output_generation = verifier->last_output_generation; + stats->last_output_presentation_xlayer_id = + verifier->last_output_presentation_xlayer_id; + stats->last_output_presentation_mlayer_id = + verifier->last_output_presentation_mlayer_id; + stats->last_output_presentation_tlayer_id = + verifier->last_output_presentation_tlayer_id; + stats->last_output_uses_current_presentation = + verifier->last_output_uses_current_presentation; + stats->replay_previous_presentation_offset_valid = + verifier->replay_previous_presentation_offset_valid; + stats->replay_previous_presentation_offset = + verifier->replay_previous_presentation_offset; + stats->replay_last_presentation_offset_valid = + verifier->replay_last_presentation_offset_valid; + stats->replay_last_presentation_offset = + verifier->replay_last_presentation_offset; + stats->finish_event = verifier->finish_event; + stats->result_count = verifier->result_count; + stats->conformant_results = + verifier->result_status_count[AV2_DM_RESULT_CONFORMANT]; + stats->non_conformant_results = + verifier->result_status_count[AV2_DM_RESULT_NON_CONFORMANT]; + stats->indeterminate_results = + verifier->result_status_count[AV2_DM_RESULT_INDETERMINATE]; + stats->not_applicable_results = + verifier->result_status_count[AV2_DM_RESULT_NOT_APPLICABLE]; + for (size_t i = 0; i < verifier->context_count; ++i) { + add_size_to_saturated_u32(&stats->live_runs, + verifier->contexts[i].run_count); + } + stats->live_generations = saturate_size_to_u32(verifier->generation_count); + add_size_to_saturated_u32(&stats->parameter_records, + verifier->sequence_record_count); + add_size_to_saturated_u32(&stats->parameter_records, + verifier->ops_record_count); + add_size_to_saturated_u32(&stats->parameter_records, + verifier->brt_record_count); + add_size_to_saturated_u32(&stats->parameter_records, + verifier->active_record_count); + return true; +} + +bool av2_decoder_model_verifier_get_context_stats(const AV2Decoder *pbi, + uint32_t context_index, + Av2DmContextStats *stats) { + if (stats == NULL || pbi == NULL || pbi->decoder_model_verifier == NULL || + context_index >= pbi->decoder_model_verifier->context_count) { + return false; + } + const Av2DmContext *const context = + &pbi->decoder_model_verifier->contexts[context_index]; + memset(stats, 0, sizeof(*stats)); + stats->scope.xlayer_id = context->key.xlayer_id; + stats->scope.ops_xlayer_id = context->key.ops_xlayer_id; + stats->scope.ops_id = context->key.ops_id; + stats->scope.operating_point = context->key.operating_point; + stats->scope.whole_xlayer = context->key.whole_xlayer; + stats->active = context->active; + stats->active_configuration_present = + context->active_configuration_record != UINT64_MAX; + stats->active_sequence_header_id = -1; + if (context->active_sequence_record < + pbi->decoder_model_verifier->sequence_record_count) { + stats->active_sequence_header_id = + pbi->decoder_model_verifier + ->sequence_records[context->active_sequence_record] + .sequence_header_id; + } + stats->pending_dfg_bits = context->pending_dfg_bits; + stats->last_closed_dfg_bits = context->last_closed_dfg_bits; + stats->closed_dfgs = context->closed_dfgs; + stats->configuration_generation = context->configuration_generation; + stats->resolved_config_present = context->last_config_present; + if (context->last_config_present) { + stats->resolved_applicability = context->last_config.applicability; + stats->resolved_mode = context->last_config.mode; + stats->resolved_initial_display_delay = + context->last_config.initial_display_delay; + } + stats->last_ras_seed_complete = context->last_ras_seed_complete; + stats->last_ras_seed_count = context->last_ras_seed_count; + return true; +} diff --git a/av2/decoder/decoder_model.h b/av2/decoder/decoder_model.h new file mode 100644 index 0000000000..7177980b40 --- /dev/null +++ b/av2/decoder/decoder_model.h @@ -0,0 +1,166 @@ +/* + * 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_DECODER_DECODER_MODEL_H_ +#define AVM_AV2_DECODER_DECODER_MODEL_H_ + +#include +#include +#include + +#include "av2/common/decoder_model.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct AV2Decoder; +struct RefCntBuffer; +typedef struct Av2DecoderModelVerifier Av2DecoderModelVerifier; + +typedef enum Av2DmPresentationOwner { + AV2_DM_PRESENTATION_OWNER_CURRENT, + AV2_DM_PRESENTATION_OWNER_IMPLICIT +} Av2DmPresentationOwner; + +typedef struct Av2DmVerifierStats { + bool available; + bool failed; + uint64_t raw_obus; + uint64_t raw_bits; + uint64_t event_count; + uint64_t temporal_unit_index; + uint64_t frame_unit_index; + uint64_t closed_dfgs; + uint64_t rap_starts; + uint64_t temporal_points; + bool temporal_point_present; + uint64_t temporal_point; + uint32_t contexts; + uint64_t frame_starts; + uint64_t reference_updates; + uint64_t olk_invalidations; + uint64_t outputs; + uint64_t last_frame_start_event; + uint64_t last_reference_update_event; + uint64_t last_olk_invalidation_event; + uint64_t last_output_event; + uint64_t last_output_callback_frame_unit; + uint64_t last_output_presentation_frame_unit; + uint64_t last_output_presentation_temporal_unit; + uint64_t last_output_generation; + int last_output_presentation_xlayer_id; + int last_output_presentation_mlayer_id; + int last_output_presentation_tlayer_id; + bool last_output_uses_current_presentation; + bool replay_previous_presentation_offset_valid; + Av2DmRational replay_previous_presentation_offset; + bool replay_last_presentation_offset_valid; + Av2DmRational replay_last_presentation_offset; + uint64_t finish_event; + uint64_t result_count; + uint64_t conformant_results; + uint64_t non_conformant_results; + uint64_t indeterminate_results; + uint64_t not_applicable_results; + uint32_t live_runs; + uint32_t live_generations; + uint32_t parameter_records; +} Av2DmVerifierStats; + +typedef struct Av2DmContextStats { + Av2DmScope scope; + bool active; + bool active_configuration_present; + int active_sequence_header_id; + uint64_t pending_dfg_bits; + uint64_t last_closed_dfg_bits; + uint64_t closed_dfgs; + uint64_t configuration_generation; + bool resolved_config_present; + Av2DmApplicability resolved_applicability; + Av2DmMode resolved_mode; + uint32_t resolved_initial_display_delay; + bool last_ras_seed_complete; + uint32_t last_ras_seed_count; +} Av2DmContextStats; + +void av2_decoder_model_verifier_init(struct AV2Decoder *pbi); +void av2_decoder_model_verifier_destroy(struct AV2Decoder *pbi); + +void av2_decoder_model_verifier_on_sequence_header(struct AV2Decoder *pbi, + int xlayer_id, + int sequence_header_id); +void av2_decoder_model_verifier_on_operating_point_set(struct AV2Decoder *pbi, + int xlayer_id, + int ops_id); +void av2_decoder_model_verifier_on_active_configuration(struct AV2Decoder *pbi, + int xlayer_id, + int sequence_header_id); +void av2_decoder_model_verifier_on_buffer_removal_timing(struct AV2Decoder *pbi, + int xlayer_id); + +void av2_decoder_model_verifier_record_obu(struct AV2Decoder *pbi, int obu_type, + int xlayer_id, int mlayer_id, + int temporal_id, uint64_t obu_bits); +void av2_decoder_model_verifier_on_source_frame_unit_start( + struct AV2Decoder *pbi, int xlayer_id, int mlayer_id, int temporal_id); +void av2_decoder_model_verifier_on_obu_filtered(struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_accounting_failure(struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_internal_failure_for_testing( + struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( + struct AV2Decoder *pbi); + +void av2_decoder_model_verifier_on_temporal_point(struct AV2Decoder *pbi, + uint64_t presentation_time); +void av2_decoder_model_verifier_on_multistream_configuration( + struct AV2Decoder *pbi, int even_allocation, int large_picture_index); + +void av2_decoder_model_verifier_on_frame_wrapup_start(struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_frame_unit_complete(struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_olk_reference_invalidation( + struct AV2Decoder *pbi); +void av2_decoder_model_verifier_after_reference_update( + struct AV2Decoder *pbi, uint32_t refresh_frame_flags); +void av2_decoder_model_verifier_on_output(struct AV2Decoder *pbi, + int frame_to_show_map_idx, + const struct RefCntBuffer *frame, + Av2DmPresentationOwner owner); +void av2_decoder_model_verifier_on_recovery_reset(struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_stream_configuration_change( + struct AV2Decoder *pbi, bool preserve_current_tu_prefix); +void av2_decoder_model_verifier_finish(struct AV2Decoder *pbi); +bool av2_decoder_model_verifier_should_stop(const struct AV2Decoder *pbi); + +bool av2_decoder_model_verifier_get_stats(const struct AV2Decoder *pbi, + Av2DmVerifierStats *stats); +bool av2_decoder_model_verifier_get_context_stats(const struct AV2Decoder *pbi, + uint32_t context_index, + Av2DmContextStats *stats); + +// Internal test support for the machine-readable violation diagnostics. +bool av2_decoder_model_violation_descriptor_is_complete( + Av2DmViolationCode code); +bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, + uint64_t max_display_rate, + uint64_t max_decode_rate, + char *text, size_t text_size); +bool av2_decoder_model_report_violation_for_testing( + const Av2DmViolation *violation, bool fatal_mode, uint64_t *violation_count, + bool *fatal_violation); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // AVM_AV2_DECODER_DECODER_MODEL_H_ diff --git a/av2/decoder/obu.c b/av2/decoder/obu.c index 7f2f0c2808..118a42367f 100644 --- a/av2/decoder/obu.c +++ b/av2/decoder/obu.c @@ -31,9 +31,28 @@ #include "av2/common/enums.h" #include "av2/common/annexA.h" #include "av2/decoder/annexF.h" +#include "av2/decoder/decoder_model.h" static uint32_t read_temporal_delimiter_obu() { return 0; } +static void decoder_model_record_obu(AV2Decoder *pbi, + const ObuHeader *obu_header, + size_t header_bytes, + size_t payload_bytes) { + if (pbi->decoder_model_verifier == NULL) return; + const uint64_t max_obu_bytes = UINT64_MAX / 8; + if (header_bytes > max_obu_bytes || + payload_bytes > max_obu_bytes - header_bytes) { + av2_decoder_model_verifier_on_accounting_failure(pbi); + return; + } + const uint64_t obu_bits = + ((uint64_t)header_bytes + (uint64_t)payload_bytes) * 8; + av2_decoder_model_verifier_record_obu( + pbi, obu_header->type, obu_header->obu_xlayer_id, + obu_header->obu_mlayer_id, obu_header->obu_tlayer_id, obu_bits); +} + // Returns a boolean that indicates success. static int read_bitstream_level(AV2_LEVEL *seq_level_idx, struct avm_read_bit_buffer *rb) { @@ -458,11 +477,15 @@ static uint32_t read_multi_stream_decoder_operation_obu( const int multistream_even_allocation_flag = avm_rb_read_bit(rb); // read multistream_even_allocation_flag + int multistream_large_picture_idc = 0; if (!multistream_even_allocation_flag) { - const int multistream_large_picture_idc = + multistream_large_picture_idc = avm_rb_read_literal(rb, 3); // read multistream_large_picture_idc - (void)multistream_large_picture_idc; + } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_multistream_configuration( + pbi, multistream_even_allocation_flag, multistream_large_picture_idc); } for (int i = 0; i < num_streams; i++) { @@ -491,6 +514,11 @@ static uint32_t read_multi_stream_decoder_operation_obu( // Flush remaining frames from all active streams before switching config if (pbi->stream_info != NULL && config_changed) { flush_all_xlayer_frames(pbi, cm, true); + if (pbi->decoder_model_verifier != NULL) { + // The temporal delimiter and new MSDO have already been recorded and + // belong to the first DFG of the replacement configuration. + av2_decoder_model_verifier_on_stream_configuration_change(pbi, true); + } avm_free(pbi->stream_info); pbi->stream_info = NULL; pbi->glcr_stream_info_num_allocated = 0; @@ -718,6 +746,10 @@ static uint32_t read_sequence_header_obu(AV2Decoder *pbi, int xlayer_id, // cm->error.error_code is already set. return 0; } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_sequence_header(pbi, xlayer_id, + (int)seq_header_id); + } return ((rb->bit_offset - saved_bit_offset + 7) >> 3); } @@ -789,6 +821,9 @@ static uint32_t read_tilegroup_obu(AV2Decoder *pbi, // cm->error.error_code is already set. return 0; } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_frame_wrapup_start(pbi); + } header_size = (int32_t)avm_rb_bytes_read(rb); } else { if (av2_check_byte_alignment(cm, rb)) return 0; @@ -1083,6 +1118,11 @@ static void read_metadata_temporal_point_info(AV2Decoder *const pbi, AV2_COMMON *const cm = &pbi->common; cm->temporal_point_info_metadata.mtpi_frame_presentation_time = avm_rb_read_uleb(rb); + cm->temporal_point_info_present = true; + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_temporal_point( + pbi, cm->temporal_point_info_metadata.mtpi_frame_presentation_time); + } uint8_t payload[1]; payload[0] = (cm->temporal_point_info_metadata.mtpi_frame_presentation_time & 0XFF); @@ -2366,6 +2406,7 @@ int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data, int frame_decoding_finished = 0; ObuHeader obu_header; memset(&obu_header, 0, sizeof(obu_header)); + cm->temporal_point_info_present = false; // Enable is_multistream if multiple extended layers are present. // Enable multistream_decoder_mode only when an MSDO OBU is present. @@ -2396,6 +2437,12 @@ int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data, // Flush and reset like a config change if (pbi->stream_info != NULL) { flush_all_xlayer_frames(pbi, cm, true); + if (pbi->decoder_model_verifier != NULL) { + // This transition is detected before the current frame unit's + // OBUs are parsed, so any retained OBU working set is stale. + av2_decoder_model_verifier_on_stream_configuration_change(pbi, + false); + } avm_free(pbi->stream_info); pbi->stream_info = NULL; pbi->glcr_stream_info_num_allocated = 0; @@ -2518,6 +2565,8 @@ int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data, return -1; } + decoder_model_record_obu(pbi, &obu_header, bytes_read, payload_size); + // Annex F: Sub-bitstream extraction. // When extraction is enabled, trigger retention map construction when // transitioning from structural OBUs to non-structural OBUs, then @@ -2532,6 +2581,9 @@ int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data, if (!av2_sbe_should_retain_obu( &pbi->sbe_state, obu_header.type, obu_header.obu_xlayer_id, obu_header.obu_mlayer_id, obu_header.obu_tlayer_id)) { + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_obu_filtered(pbi); + } pbi->sbe_state.obus_removed++; data += payload_size; continue; @@ -3104,6 +3156,8 @@ int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data, return -1; } + decoder_model_record_obu(pbi, &obu_header, bytes_read, payload_size); + if (obu_header.type == OBU_PADDING) { decoded_payload_size = read_padding(cm, data, payload_size); obu_info *const curr_obu_info = &obu_list[count_obus_with_frame_unit]; diff --git a/av2/decoder/obu_buf.c b/av2/decoder/obu_buf.c index 2f3652fe1d..aa811cc3c1 100644 --- a/av2/decoder/obu_buf.c +++ b/av2/decoder/obu_buf.c @@ -24,6 +24,7 @@ #include "av2/common/timing.h" #include "av2/decoder/decoder.h" #include "av2/decoder/decodeframe.h" +#include "av2/decoder/decoder_model.h" #include "av2/decoder/obu.h" uint32_t av2_read_buffer_removal_timing_obu(struct AV2Decoder *pbi, @@ -86,5 +87,8 @@ uint32_t av2_read_buffer_removal_timing_obu(struct AV2Decoder *pbi, // cm->error.error_code is already set. return 0; } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_buffer_removal_timing(pbi, xlayer_id); + } return ((rb->bit_offset - saved_bit_offset + 7) >> 3); } diff --git a/av2/decoder/obu_ops.c b/av2/decoder/obu_ops.c index d98ba4a570..6bcde44c28 100644 --- a/av2/decoder/obu_ops.c +++ b/av2/decoder/obu_ops.c @@ -19,6 +19,7 @@ #include "av2/decoder/decoder.h" #include "av2/decoder/decodeframe.h" #include "av2/decoder/obu.h" +#include "av2/decoder/decoder_model.h" static void read_ops_mlayer_info(int xLId, struct OpsMLayerInfo *ops_mlayer_info, @@ -170,8 +171,8 @@ uint32_t av2_read_operating_point_set_obu(struct AV2Decoder *pbi, if (op->ops_decoder_model_info_for_this_op_present_flag) { read_ops_decoder_model_info(&op->decoder_model_info, rb); } - int ops_initial_display_delay_present_flag = avm_rb_read_bit(rb); - if (ops_initial_display_delay_present_flag) { + op->ops_initial_display_delay_present_flag = avm_rb_read_bit(rb); + if (op->ops_initial_display_delay_present_flag) { int ops_initial_display_delay_minus_1 = avm_rb_read_literal(rb, 4); op->ops_initial_display_delay = ops_initial_display_delay_minus_1 + 1; } else { @@ -291,5 +292,9 @@ uint32_t av2_read_operating_point_set_obu(struct AV2Decoder *pbi, return 0; } ops->valid = 1; + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_operating_point_set(pbi, obu_xlayer_id, + ops_id); + } return ((rb->bit_offset - saved_bit_offset + 7) >> 3); } diff --git a/av2/encoder/bitstream.c b/av2/encoder/bitstream.c index 8a85ead1ae..012341610b 100644 --- a/av2/encoder/bitstream.c +++ b/av2/encoder/bitstream.c @@ -131,6 +131,16 @@ static AVM_INLINE void write_inter_mode( INTER_SINGLE_MODES); } +bool av2_encoder_decoder_model_accumulate_frame_symbols( + uint64_t *frame_symbols, uint64_t tile_group_symbols) { + if (frame_symbols == NULL || + tile_group_symbols > UINT64_MAX - *frame_symbols) { + return false; + } + *frame_symbols += tile_group_symbols; + return true; +} + static void write_drl_idx(int max_drl_bits, const int16_t mode_ctx, FRAME_CONTEXT *ec_ctx, const MB_MODE_INFO *mbmi, const MB_MODE_INFO_EXT_FRAME *mbmi_ext_frame, @@ -6154,7 +6164,6 @@ static uint32_t write_tilegroup_payload(AV2_COMP *const cpi, uint8_t *const dst, *largest_tile_id = tile_cols * tile_row + tile_col; max_tile_size = tile_size; } - cm->features.frame_symbol_count = mode_bc.frame_symbol_count; if (tile_idx < end_tile_idx) { mem_put_le32(buf->data, tile_size - AV2_MIN_TILE_SIZE_BYTES); } @@ -6168,6 +6177,17 @@ static uint32_t write_tilegroup_payload(AV2_COMP *const cpi, uint8_t *const dst, break; } + if (cpi->level_params.keep_level_stats != 0 && + !is_stat_generation_stage(cpi)) { + if (!av2_encoder_decoder_model_accumulate_frame_symbols( + &cm->features.frame_symbol_count, mode_bc.frame_symbol_count)) { + cpi->dm_frame_symbol_count_overflow = true; + } + } else { + // Preserve the pre-model behavior when level statistics are inactive. + cm->features.frame_symbol_count = mode_bc.frame_symbol_count; + } + if (tile_cols * tile_rows > 1 && cm->features.tip_frame_mode != TIP_FRAME_AS_OUTPUT) { if (!cm->seq_params.enable_avg_cdf || !cm->seq_params.avg_cdf_type) { @@ -7441,6 +7461,11 @@ static int av2_pack_bitstream_internal(AV2_COMP *const cpi, uint8_t *dst, int av2_pack_bitstream(AV2_COMP *const cpi, uint8_t *dst, size_t *size, int *const largest_tile_id) { AV2_COMMON *const cm = &cpi->common; + if (cpi->level_params.keep_level_stats != 0 && + !is_stat_generation_stage(cpi)) { + cm->features.frame_symbol_count = 0; + cpi->dm_frame_symbol_count_overflow = false; + } // For some pairs of sequence-level and frame-level flags, if // single_picture_header_flag is true and the frame-level flag is 0, force diff --git a/av2/encoder/bitstream.h b/av2/encoder/bitstream.h index 58f2238b02..358e10138f 100644 --- a/av2/encoder/bitstream.h +++ b/av2/encoder/bitstream.h @@ -83,6 +83,11 @@ void av2_set_buffer_removal_timing_params(AV2_COMP *const cpi); int av2_pack_bitstream(AV2_COMP *const cpi, uint8_t *dst, size_t *size, int *const largest_tile_id); +// Adds one tile group's symbol count to the model-only frame total. Returns +// false without modifying the total if the addition would overflow. +bool av2_encoder_decoder_model_accumulate_frame_symbols( + uint64_t *frame_symbols, uint64_t tile_group_symbols); + void av2_write_sec_tx_type(const AV2_COMMON *const cm, const MACROBLOCKD *xd, TX_TYPE tx_type, TX_SIZE tx_size, uint16_t eob, avm_writer *w); diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index 6e6eb63d39..1e7da5b5f0 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -1332,6 +1332,7 @@ int av2_encode_strategy(AV2_COMP *const cpi, size_t *const size, assert(cpi->gf_group.update_type[cpi->gf_group.index] == OVERLAY_UPDATE || cpi->gf_group.update_type[cpi->gf_group.index] == KFFLT_OVERLAY_UPDATE); + av2_decoder_model_flush_implicit_output_for_operating_points(cpi, true); // This is an OLK KF overlay. We need to clear all references except for the // OLK. for (int ref_index = 0; ref_index < cm->seq_params.ref_frames; diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index e0d0376d4d..b9cb6da234 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -17,6 +17,7 @@ #include #include +#include "avm/avm_integer.h" #include "av2/common/av2_common_int.h" #include "av2/common/bru.h" #include "config/avm_config.h" @@ -4518,6 +4519,21 @@ static bool need_sef_obu_for_hidden_frame(AV2_COMP *cpi) { cpi->common.ref_frame_map[cpi->fb_idx_for_overlay] != NULL; } +static void update_level_info_for_frame_unit(AV2_COMP *cpi, const uint8_t *data, + size_t size, int64_t time_stamp, + int64_t time_end) { + uint64_t dfg_prefix_bits = 0; + if (size > 0 && cpi->dm_starts_temporal_unit && cpi->common.mlayer_id == 0) { + uint8_t obu_header[2]; + const uint32_t obu_header_size = av2_write_obu_header( + OBU_TEMPORAL_DELIMITER, 0, GLOBAL_XLAYER_ID, obu_header); + const size_t delimiter_size = obu_header_size + avm_uleb_size_in_bytes(0); + dfg_prefix_bits = (uint64_t)delimiter_size * 8; + } + av2_update_level_info(cpi, data, size, time_stamp, time_end, size > 0, + dfg_prefix_bits); +} + /*!\brief Run the final pass encoding for 1-pass/2-pass encoding mode, and pack * the bitstream * @@ -4646,9 +4662,10 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, if (cm->immediate_output_picture) cpi->last_show_frame_buf = cm->cur_frame; if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi)) { - av2_update_level_info(cpi, *size, *time_stamp, *time_end, 0); - av2_decoder_model_check_output_frame_for_operating_points( - cpi, ref_idx_for_dm, cm->ref_frame_map[cpi->fb_idx_for_overlay]); + update_level_info_for_frame_unit(cpi, dest, *size, *time_stamp, + *time_end); + av2_decoder_model_observe_displaced_output_for_operating_points( + cpi, ref_idx_for_dm); } // current_frame->frame_number is incremented already for @@ -4710,9 +4727,10 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, cpi->last_show_frame_buf = cm->cur_frame; if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi)) { - av2_update_level_info(cpi, *size, *time_stamp, *time_end, 0); - av2_decoder_model_check_output_frame_for_operating_points( - cpi, cpi->fb_idx_for_overlay, cm->cur_frame); + update_level_info_for_frame_unit(cpi, dest, *size, *time_stamp, + *time_end); + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi, cpi->fb_idx_for_overlay); } if (!av2_check_keyframe_overlay(cpi->gf_group.index, &cpi->gf_group, @@ -4754,9 +4772,10 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, refresh_reference_frames(cpi); if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi)) { - av2_update_level_info(cpi, *size, *time_stamp, *time_end, 0); - av2_decoder_model_check_output_frame_for_operating_points( - cpi, cm->sef_ref_fb_idx, cpi->last_show_frame_buf); + update_level_info_for_frame_unit(cpi, dest, *size, *time_stamp, + *time_end); + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi, + -1); } // Since we allocate a spot for the OVERLAY frame in the gf group, we need @@ -4946,19 +4965,21 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi)) { // Initialize level info. at the beginning of each sequence. if (av2_is_shown_keyframe(cpi, cm->current_frame.frame_type)) { + av2_encoder_decoder_model_finish_for_operating_points(cpi); + av2_encoder_check_target_level(cpi, true); av2_init_level_info(cpi); } - av2_update_level_info(cpi, *size, *time_stamp, *time_end, 1); + update_level_info_for_frame_unit(cpi, dest, *size, *time_stamp, *time_end); } + refresh_reference_frames(cpi); + if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi) && cm->immediate_output_picture) { - av2_decoder_model_check_output_frame_for_operating_points(cpi, -1, - cm->cur_frame); + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi, + -1); } - refresh_reference_frames(cpi); - #if CONFIG_ENTROPY_STATS av2_accumulate_frame_counts(&aggregate_fc, &cpi->counts); #endif // CONFIG_ENTROPY_STATS @@ -5110,6 +5131,7 @@ int av2_encode(AV2_COMP *const cpi, uint8_t *const dest, if (cm->restricted_prediction_switch) { if (current_frame->frame_type == S_FRAME) { + av2_decoder_model_observe_restricted_output_for_operating_points(cpi); for (int i = 0; i < cm->seq_params.ref_frames; i++) { if (cm->ref_frame_map[i] != NULL) { if (is_mlayer_transitively_dependent(&cm->seq_params, @@ -5444,12 +5466,14 @@ static void compute_internal_stats(AV2_COMP *cpi, int frame_bytes) { int av2_get_compressed_data(AV2_COMP *cpi, unsigned int *frame_flags, size_t *size, uint8_t *dest, int64_t *time_stamp, int64_t *time_end, int flush, - const avm_rational64_t *timestamp_ratio) { + const avm_rational64_t *timestamp_ratio, + bool dm_starts_temporal_unit) { const AV2EncoderConfig *const oxcf = &cpi->oxcf; AV2_COMMON *const cm = &cpi->common; cm->cur_mfh_id = oxcf->tool_cfg.enable_mfh_obu_signaling ? 1 : 0; cm->implicit_output_picture = 0; cm->allow_direct_use = 0; + cpi->dm_starts_temporal_unit = dm_starts_temporal_unit; *size = 0; #if CONFIG_INTERNAL_STATS struct avm_usec_timer cmptimer; @@ -5475,11 +5499,16 @@ int av2_get_compressed_data(AV2_COMP *cpi, unsigned int *frame_flags, timestamp_ratio, flush); if (result == -1) { // Returning -1 indicates no frame encoded; more input is required + if (flush) { + av2_encoder_decoder_model_finish_for_operating_points(cpi); + av2_encoder_check_target_level(cpi, true); + } return -1; } if (result != AVM_CODEC_OK) { return AVM_CODEC_ERROR; } + av2_encoder_check_target_level(cpi, false); #if CONFIG_INTERNAL_STATS avm_usec_timer_mark(&cmptimer); cpi->time_compress_data += avm_usec_timer_elapsed(&cmptimer); diff --git a/av2/encoder/encoder.h b/av2/encoder/encoder.h index 15df43f57f..c3126e1503 100644 --- a/av2/encoder/encoder.h +++ b/av2/encoder/encoder.h @@ -2803,6 +2803,15 @@ typedef struct AV2_COMP { * Parameters for AV2 bitstream levels. */ AV2LevelParams level_params; + /*! + * Model-only indication that the outer interface will prepend a temporal + * delimiter to the current serialized frame unit. + */ + bool dm_starts_temporal_unit; + /*! + * Model-only overflow state for the per-frame Annex A symbol total. + */ + bool dm_frame_symbol_count_overflow; /*! * Whether any no-zero delta_q was actually used. @@ -3165,6 +3174,8 @@ int av2_receive_raw_frame(AV2_COMP *cpi, avm_enc_frame_flags_t frame_flags, * \param[out] time_end Time end * \param[in] flush Decide to encode one frame or the rest of frames * \param[in] timebase Time base used + * \param[in] dm_starts_temporal_unit Whether a temporal delimiter precedes + * this modelled frame unit * * \return Returns a value to indicate if the encoding is done successfully. * \retval #AVM_CODEC_OK @@ -3175,7 +3186,8 @@ int av2_receive_raw_frame(AV2_COMP *cpi, avm_enc_frame_flags_t frame_flags, int av2_get_compressed_data(AV2_COMP *cpi, unsigned int *frame_flags, size_t *size, uint8_t *dest, int64_t *time_stamp, int64_t *time_end, int flush, - const avm_rational64_t *timebase); + const avm_rational64_t *timebase, + bool dm_starts_temporal_unit); /*!\brief Run 1-pass/2-pass encoding * diff --git a/av2/encoder/encoder_alloc.h b/av2/encoder/encoder_alloc.h index a55cd13f18..7d26904d0e 100644 --- a/av2/encoder/encoder_alloc.h +++ b/av2/encoder/encoder_alloc.h @@ -267,6 +267,7 @@ static AVM_INLINE void dealloc_compressor_data(AV2_COMP *cpi) { } for (int i = 0; i < MAX_NUM_OPERATING_POINTS; ++i) { + av2_encoder_decoder_models_destroy(cpi->level_params.level_info[i]); avm_free(cpi->level_params.level_info[i]); } diff --git a/av2/encoder/encoder_utils.h b/av2/encoder/encoder_utils.h index d049f379dd..330e301bec 100644 --- a/av2/encoder/encoder_utils.h +++ b/av2/encoder/encoder_utils.h @@ -874,6 +874,8 @@ static AVM_INLINE void release_scaled_references(AV2_COMP *cpi) { // Refresh reference frame buffers according to refresh_frame_flags. static AVM_INLINE void refresh_reference_frames(AV2_COMP *cpi) { AV2_COMMON *const cm = &cpi->common; + const bool observe_decoder_model = + cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi); // Don't clear is_restricted for bridge frames - they should maintain // the restricted status inherited from their reference frame @@ -891,6 +893,10 @@ static AVM_INLINE void refresh_reference_frames(AV2_COMP *cpi) { for (int ref_frame = 0; ref_frame < cm->seq_params.ref_frames; ref_frame++) { if (((cm->current_frame.refresh_frame_flags >> ref_frame) & 1) == 1) { + if (observe_decoder_model) { + av2_decoder_model_observe_displaced_output_for_operating_points( + cpi, ref_frame); + } if (av2_skip_reference_buffer_update(clear_multiple_insert_in_one, ref_frame, first_ref_index) && marked) { @@ -902,11 +908,29 @@ static AVM_INLINE void refresh_reference_frames(AV2_COMP *cpi) { assign_frame_buffer_p(&cm->ref_frame_map[ref_frame], cm->cur_frame); marked = 1; } + if (observe_decoder_model) { + av2_decoder_model_mirror_ref_buffer_for_operating_points(cpi, + ref_frame); + } + } + } + } else if (observe_decoder_model) { + // bru_swap_common() has already replaced the encoder buffer in place. + // The model's VBI and private presentation descriptor still identify the + // displaced decoded generation, so observe its output before mirroring the + // reference update into the model below. + for (int ref_frame = 0; ref_frame < cm->seq_params.ref_frames; + ++ref_frame) { + if ((cm->current_frame.refresh_frame_flags >> ref_frame) & 1) { + av2_decoder_model_observe_displaced_output_for_operating_points( + cpi, ref_frame); + av2_decoder_model_mirror_ref_buffer_for_operating_points(cpi, + ref_frame); } } } - if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi)) { + if (observe_decoder_model) { av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( cpi); } diff --git a/avm/avmdx.h b/avm/avmdx.h index e83a34305f..fa04a6e7a7 100644 --- a/avm/avmdx.h +++ b/avm/avmdx.h @@ -173,6 +173,16 @@ typedef struct av2_ext_ref_frame { int num; } av2_ext_ref_frame_t; +/*!\enum avm_decoder_model_check_mode + * \brief Decoder-model conformance verification mode. + */ +typedef enum avm_decoder_model_check_mode { + AVM_DECODER_MODEL_CHECK_OFF = 0, + AVM_DECODER_MODEL_CHECK_FATAL, + AVM_DECODER_MODEL_CHECK_WARN, +} avm_decoder_model_check_mode_t; /**< alias for enum + avm_decoder_model_check_mode */ + /*!\enum avm_dec_control_id * \brief AVM decoder control functions * @@ -399,6 +409,14 @@ enum avm_dec_control_id { /*!\brief Codec control function to advance output_frames_offset by given step */ AVMD_INCR_OUTPUT_FRAMES_OFFSET, + + /*!\brief Codec control function to set decoder-model conformance checking, + * avm_decoder_model_check_mode_t parameter. + * + * The default is AVM_DECODER_MODEL_CHECK_OFF. The mode must be set before + * compressed input is submitted. + */ + AV2D_SET_DECODER_MODEL_CHECK_MODE, }; /*!\cond */ @@ -510,6 +528,10 @@ AVM_CTRL_USE_TYPE(AV2D_SET_SELECTED_LOCAL_OPS, int *) AVM_CTRL_USE_TYPE(AV2D_SET_OUTPUT_ALL_LAYERS, int) #define AVM_CTRL_AV2D_SET_OUTPUT_ALL_LAYERS +AVM_CTRL_USE_TYPE(AV2D_SET_DECODER_MODEL_CHECK_MODE, + avm_decoder_model_check_mode_t) +#define AVM_CTRL_AV2D_SET_DECODER_MODEL_CHECK_MODE + AVM_CTRL_USE_TYPE(AV2_SET_INSPECTION_CALLBACK, avm_inspect_init *) #define AVM_CTRL_AV2_SET_INSPECTION_CALLBACK /*!\endcond */ diff --git a/avm_dsp/bitreader.c b/avm_dsp/bitreader.c index ef46182ec9..222141a931 100644 --- a/avm_dsp/bitreader.c +++ b/avm_dsp/bitreader.c @@ -18,6 +18,8 @@ int avm_reader_init(avm_reader *r, const uint8_t *buffer, size_t size) { } r->buffer_end = buffer + size; r->buffer = buffer; + r->count_frame_symbols = 1; + r->frame_symbol_count = 0; avm_od_ec_dec_init(&r->ec, buffer, (uint32_t)size); #if CONFIG_ACCOUNTING r->accounting = NULL; diff --git a/avm_dsp/bitreader.h b/avm_dsp/bitreader.h index 3be50a72db..08c5971f36 100644 --- a/avm_dsp/bitreader.h +++ b/avm_dsp/bitreader.h @@ -82,6 +82,8 @@ struct avm_reader { Accounting *accounting; #endif uint8_t allow_update_cdf; + uint8_t count_frame_symbols; + uint64_t frame_symbol_count; }; typedef struct avm_reader avm_reader; @@ -270,6 +272,7 @@ static INLINE int avm_read_literal_(avm_reader *r, int bits ACCT_INFO_PARAM) { literal += od_ec_decode_literal_bypass(&r->ec, n); n_bits -= n; } + if (r->count_frame_symbols) r->frame_symbol_count += (uint64_t)bits; #if CONFIG_BITSTREAM_DEBUG bitstream_queue_pop_literal(literal, bits); #endif // CONFIG_BITSTREAM_DEBUG @@ -368,6 +371,7 @@ static INLINE int avm_read_symbol_(avm_reader *r, avm_cdf_prob *cdf, int nsymbs ACCT_INFO_PARAM) { int ret; ret = avm_read_cdf(r, cdf, nsymbs, ACCT_INFO_NAME); + if (r->count_frame_symbols) ++r->frame_symbol_count; if (r->allow_update_cdf) update_cdf(cdf, ret, nsymbs); return ret; } @@ -389,6 +393,7 @@ static INLINE int avm_read_symbol_probdata(avm_reader *r, avm_cdf_prob *cdf, ProbModelInfo prob_info) { FILE *filedata = prob_info.fDataCollect; const int symLength = prob_info.num_symb; + if (r->count_frame_symbols) ++r->frame_symbol_count; // Estimated probability and counter information const int counter_engine = (int)cdf[symLength]; for (int i = 0; i < prob_info.num_dim; i++) { diff --git a/test/brt_test.cc b/test/brt_test.cc index d604b2a811..f016c5c814 100644 --- a/test/brt_test.cc +++ b/test/brt_test.cc @@ -16,6 +16,7 @@ #include "av2/encoder/brt_syntax.h" #include "av2/decoder/decoder.h" +#include "av2/decoder/decoder_model.h" #include "av2/decoder/decodeframe.h" extern "C" { #include "av2/decoder/obu.h" @@ -59,6 +60,8 @@ class BrtTest : public ::testing::Test { }; TEST_F(BrtTest, NonOpsDependent) { + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); BufferRemovalTimingInfo src; memset(&src, 0, sizeof(src)); src.br_ops_dependent_flag = 0; @@ -74,6 +77,11 @@ TEST_F(BrtTest, NonOpsDependent) { EXPECT_EQ(pbi_->common.brt_info.br_ops_dependent_flag, 0); EXPECT_EQ(pbi_->common.brt_info.br_time, 42); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.event_count, 1u); + av2_decoder_model_verifier_destroy(pbi_); } TEST_F(BrtTest, OpsDependentWithModel) { diff --git a/test/decoder_model_integration_test.cc b/test/decoder_model_integration_test.cc new file mode 100644 index 0000000000..9472475fda --- /dev/null +++ b/test/decoder_model_integration_test.cc @@ -0,0 +1,2405 @@ +/* + * 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 +#include +#include +#include +#include + +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +#include "avm/avm_encoder.h" +#include "avm/avmcx.h" +#include "avm_dsp/bitreader_buffer.h" +#include "avm_mem/avm_mem.h" +#include "av2/common/decoder_model.h" +#include "av2/common/obu_util.h" +#include "av2/decoder/decoder.h" +#include "av2/decoder/decoder_model.h" +#include "test/codec_factory.h" +#include "test/encode_test_driver.h" +#include "test/i420_video_source.h" +#include "test/y4m_video_source.h" + +namespace { + +class DecoderModelAdapterTestBase : public ::testing::Test { + protected: + void SetUp() override { + pbi_ = static_cast(avm_memalign(32, sizeof(*pbi_))); + ASSERT_NE(pbi_, nullptr); + memset(pbi_, 0, sizeof(*pbi_)); + memset(&frame_, 0, sizeof(frame_)); + memset(&second_frame_, 0, sizeof(second_frame_)); + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + Configure(64, 64); + } + + void TearDown() override { + av2_decoder_model_verifier_destroy(pbi_); + avm_free(pbi_); + } + + void Configure(int width, int height) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + memset(sequence, 0, sizeof(*sequence)); + sequence->seq_header_id = 0; + sequence->seq_max_level_idx = SEQ_LEVEL_2_0; + sequence->seq_tier = 0; + sequence->seq_profile_idc = MAIN_420_10_IP0; + sequence->ref_frames = 8; + sequence->max_frame_width = width; + sequence->max_frame_height = height; + sequence->seq_max_mlayer_cnt = 1; + sequence->seq_max_display_model_info_present_flag = 1; + sequence->seq_max_initial_display_delay_minus_1 = 0; + sequence->decoder_model_info.num_units_in_decoding_tick = 1; + sequence->still_picture = 1; + pbi_->common.seq_params = *sequence; + ContentInterpretation *const ci = &pbi_->common.ci_params_per_layer[0]; + ci->ci_timing_info_present_flag = 1; + ci->timing_info.num_units_in_display_tick = 1; + ci->timing_info.time_scale = 30; + ci->timing_info.equal_elemental_interval = 1; + ci->timing_info.num_ticks_per_elemental_duration = 1; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + } + + void StartFrame(int obu_type, int width = 64, int height = 64, + RefCntBuffer *frame = nullptr, int mlayer_id = 0, + FRAME_TYPE frame_type = KEY_FRAME, + bool implicit_output = false, bool complete = true, + int xlayer_id = 0, int temporal_id = 0, + bool activate_configuration = false, + bool filter_obu = false) { + if (frame == nullptr) frame = &frame_; + av2_decoder_model_verifier_on_source_frame_unit_start( + pbi_, xlayer_id, mlayer_id, temporal_id); + av2_decoder_model_verifier_record_obu(pbi_, obu_type, xlayer_id, mlayer_id, + temporal_id, 800); + if (filter_obu) av2_decoder_model_verifier_on_obu_filtered(pbi_); + pbi_->obu_type = static_cast(obu_type); + AV2_COMMON *const cm = &pbi_->common; + cm->xlayer_id = xlayer_id; + cm->mlayer_id = mlayer_id; + cm->tlayer_id = temporal_id; + cm->is_leading_picture = 0; + cm->show_existing_frame = 0; + cm->implicit_output_picture = implicit_output; + cm->cur_frame = frame; + cm->width = width; + cm->height = height; + cm->mi_params.mi_cols = (width + MI_SIZE - 1) / MI_SIZE; + cm->mi_params.mi_rows = (height + MI_SIZE - 1) / MI_SIZE; + cm->mib_size_log2 = 0; + cm->tiles.cols = 1; + cm->tiles.rows = 1; + cm->tiles.col_start_sb[0] = 0; + cm->tiles.col_start_sb[1] = cm->mi_params.mi_cols; + cm->tiles.row_start_sb[0] = 0; + cm->tiles.row_start_sb[1] = cm->mi_params.mi_rows; + cm->current_frame.frame_type = frame_type; + cm->current_frame.refresh_frame_flags = 1; + frame->xlayer_id = xlayer_id; + frame->mlayer_id = mlayer_id; + frame->tlayer_id = temporal_id; + frame->width = width; + frame->height = height; + frame->implicit_output_picture = implicit_output; + if (activate_configuration) { + av2_decoder_model_verifier_on_active_configuration(pbi_, xlayer_id, 0); + } + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + if (complete) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, xlayer_id, + mlayer_id, temporal_id, 80); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + } + } + + void UpdateAndOutput() { + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + } + + AV2Decoder *pbi_ = nullptr; + RefCntBuffer frame_; + RefCntBuffer second_frame_; +}; + +class DecoderModelHookOrderTest : public DecoderModelAdapterTestBase {}; +class DecoderModelResultTest : public DecoderModelAdapterTestBase {}; + +size_t CountOccurrences(const std::string &text, const std::string &pattern) { + size_t count = 0; + for (size_t position = 0; + (position = text.find(pattern, position)) != std::string::npos; + position += pattern.size()) { + ++count; + } + return count; +} + +void ExpectAdapterRational(const Av2DmRational &actual, uint64_t numerator, + uint64_t denominator) { + Av2DmRational expected; + ASSERT_TRUE(av2_dm_rational_make(numerator, denominator, &expected)); + int comparison = 1; + ASSERT_TRUE(av2_dm_rational_compare(&actual, &expected, &comparison)); + EXPECT_EQ(comparison, 0); +} + +TEST(DecoderModelDiagnosticTest, DescriptorsAreExhaustiveAndRangeSafe) { + struct ExpectedDescriptor { + const char *spec; + const char *condition; + const char *relation; + const char *observed; + const char *limit; + const char *unit; + const char *requirement; + const char *margin; + bool lower_bound; + }; +#define EXPECTED_DESCRIPTOR(spec, condition, relation, observed, limit, unit, \ + requirement, margin, lower_bound) \ + { \ + spec, condition, relation, observed, limit, unit, requirement, margin, \ + lower_bound \ + } + static const ExpectedDescriptor expected[] = { + EXPECTED_DESCRIPTOR("annex_e.decoder_model_error_codes", + "free_decode_frame_buffer_available", "available", + "free_buffers", "required_free_buffers", "buffers", + "available", nullptr, false), + EXPECTED_DESCRIPTOR("annex_e.decoder_model_error_codes", + "show_existing_reference_buffer_available", "available", + "reference_buffer_state", "required_buffer_state", + "buffers", "available", nullptr, false), + EXPECTED_DESCRIPTOR("annex_e.decoder_model_error_codes", + "output_time_lte_presentation_time", "lte", + "output_time", "presentation_time", "seconds", + "maximum", "lateness", false), + EXPECTED_DESCRIPTOR("annex_e.smoothing_buffer_underflow", + "scheduled_removal_gte_last_bit_arrival", "gte", + "scheduled_removal", "last_bit_arrival", "seconds", + "minimum", "lateness", true), + EXPECTED_DESCRIPTOR("annex_e.smoothing_buffer_overflow", + "buffer_fullness_lte_buffer_size", "lte", + "buffer_fullness_bits", "buffer_size_bits", "bits", + "maximum", "excess_bits", false), + EXPECTED_DESCRIPTOR("annex_e.bitstream_conformance.general", + "presentation_time_gte_previous_presentation_time", + "gte", "presentation_time", + "previous_presentation_time", "seconds", "minimum", + "shortfall", true), + EXPECTED_DESCRIPTOR("annex_e.bitstream_conformance.general", + "scheduled_removal_gte_resource_removal", "gte", + "scheduled_removal", "resource_removal", "seconds", + "minimum", "shortfall", true), + EXPECTED_DESCRIPTOR("annex_e.decoder_buffer_delay_consistency", + "decoder_buffer_delay_lte_ceil_time_delta", "lte", + "time_delta_ticks", + "decoder_buffer_delay_minus_one_ticks", "ticks", + "maximum", nullptr, false), + EXPECTED_DESCRIPTOR( + "annex_e.minimum_decode_time", + "available_decode_interval_gte_required_decode_interval", "gte", + "available_decode_interval", "required_decode_interval", "seconds", + "minimum", "shortfall", true), + EXPECTED_DESCRIPTOR( + "annex_e.minimum_presentation_interval", + "presentation_interval_gte_required_presentation_interval", "gte", + "presentation_interval", "required_presentation_interval", "seconds", + "minimum", "shortfall", true), + EXPECTED_DESCRIPTOR("annex_e.decode_deadline", + "decode_completion_time_lte_presentation_time", "lte", + "decode_completion_time", "presentation_time", + "seconds", "maximum", "lateness", false), + EXPECTED_DESCRIPTOR("annex_e.level_imposed_constraints", + "decoder_buffer_delay_nonzero", "nonzero", + "decoder_buffer_delay", "zero", "seconds", "nonzero", + nullptr, false), + EXPECTED_DESCRIPTOR("annex_e.level_imposed_constraints", + "decoder_buffer_delay_lte_maximum", "lte", + "decoder_buffer_delay", "maximum_decoder_buffer_delay", + "seconds", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", + "frame_luma_samples_lte_max_picture_size", "lte", + "frame_luma_samples", "max_picture_size", + "luma_samples", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "frame_width_lte_max_horizontal_size", + "lte", "frame_width", "max_horizontal_size", + "luma_samples", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "frame_height_lte_max_vertical_size", + "lte", "frame_height", "max_vertical_size", + "luma_samples", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "frame_width_gte_16", "gte", + "frame_width", "min_horizontal_size", "luma_samples", + "minimum", "shortfall", true), + EXPECTED_DESCRIPTOR("annex_a.levels", "frame_height_gte_16", "gte", + "frame_height", "min_vertical_size", "luma_samples", + "minimum", "shortfall", true), + EXPECTED_DESCRIPTOR("annex_a.levels", "num_tiles_lte_max_tiles", "lte", + "num_tiles", "max_tiles", "tiles", "maximum", "excess", + false), + EXPECTED_DESCRIPTOR("annex_a.levels", "tile_columns_lte_max_tile_columns", + "lte", "tile_columns", "max_tile_columns", + "tile_columns", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "tile_width_lte_max_tile_width", + "lte", "tile_width", "max_tile_width", "luma_samples", + "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "non_rightmost_tile_width_gte_64", + "gte", "offending_tile_width", "min_tile_width", + "luma_samples", "minimum", nullptr, true), + EXPECTED_DESCRIPTOR("annex_a.levels", "tile_area_lte_max_tile_area", "lte", + "tile_area", "max_tile_area", "luma_samples", "maximum", + "excess", false), + EXPECTED_DESCRIPTOR( + "annex_a.levels", "display_luma_samples_lte_output_interval_capacity", + "lte", "display_luma_samples", "display_capacity", + "luma_samples_per_interval", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "frame_headers_lte_max_header_rate", + "lte", "frame_headers_in_window", + "max_frame_headers_in_window", + "frame_headers_per_second", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", + "num_ref_frames_lte_max_level_ref_frames", "lte", + "num_ref_frames", "max_level_ref_frames", + "reference_frames", "maximum", "excess", false), + EXPECTED_DESCRIPTOR( + "annex_a.levels", "luma_sample_count_lte_frame_parsing_capacity", "lte", + "luma_sample_count", "frame_parsing_capacity", + "luma_samples_per_interval", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", + "num_tiles_lte_frame_parsing_tile_limit", "lte", + "num_tiles", "frame_parsing_tile_limit", + "tiles_per_interval", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", "compressed_size_lte_derived_maximum", + "lte", "compressed_size", "maximum_compressed_size", + "bytes", "maximum", "excess", false), + EXPECTED_DESCRIPTOR("annex_a.levels", + "frame_symbol_count_lte_derived_maximum", "lte", + "frame_symbol_count", "maximum_frame_symbols", + "symbols", "maximum", "excess", false), + EXPECTED_DESCRIPTOR( + "annex_a.levels", "max_tile_area_times_header_rate_lte_level_limit", + "lte", "tile_area_header_rate_product", + "max_tile_area_header_rate_product", + "luma_samples_x_headers_per_second", "maximum", "excess", false), + }; +#undef EXPECTED_DESCRIPTOR + static_assert(sizeof(expected) / sizeof(expected[0]) == + AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE + 1, + "Update this test when a violation code is added"); + + for (int code = AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE; + code <= AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE; ++code) { + const auto violation_code = static_cast(code); + EXPECT_TRUE( + av2_decoder_model_violation_descriptor_is_complete(violation_code)); + Av2DmViolation violation{}; + violation.code = violation_code; + violation.observed_present = true; + violation.limit_present = true; + const uint64_t observed = expected[code].lower_bound ? 9 : 11; + ASSERT_TRUE(av2_dm_rational_make(observed, 1, &violation.observed)); + ASSERT_TRUE(av2_dm_rational_make(10, 1, &violation.limit)); + char details[1024]; + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &violation, 1000000, 1000000, details, sizeof(details))); + const std::string formatted(details); + const ExpectedDescriptor &descriptor = expected[code]; + EXPECT_NE(formatted.find(std::string("unit=") + descriptor.unit + + " requirement=" + descriptor.requirement + + " relation=" + descriptor.relation + + " condition=" + descriptor.condition), + std::string::npos) + << code << ": " << formatted; + EXPECT_NE(formatted.find(std::string(" ") + descriptor.observed + "=" + + std::to_string(observed)), + std::string::npos) + << code << ": " << formatted; + EXPECT_NE(formatted.find(std::string(" ") + descriptor.limit + "=10"), + std::string::npos) + << code << ": " << formatted; + if (descriptor.margin != nullptr) { + EXPECT_NE(formatted.find(std::string(" ") + descriptor.margin + "=1"), + std::string::npos) + << code << ": " << formatted; + } + EXPECT_NE(formatted.find(std::string(" spec=") + descriptor.spec), + std::string::npos) + << code << ": " << formatted; + EXPECT_LT(formatted.size(), sizeof(details)) << code; + } + + const auto unknown = static_cast(999); + EXPECT_FALSE(av2_decoder_model_violation_descriptor_is_complete(unknown)); + Av2DmViolation violation{}; + violation.code = unknown; + char details[128]; + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &violation, 0, 0, details, sizeof(details))); + EXPECT_STREQ(details, + "unit=value requirement=unknown relation=unknown " + "condition=unknown_violation spec=unknown"); +} + +TEST(DecoderModelDiagnosticTest, UnderflowAndOverflowUseExactNamedOperands) { + Av2DmViolation underflow{}; + underflow.code = AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW; + underflow.event_index = 108; + underflow.affected_index = 108; + underflow.observed_present = true; + underflow.limit_present = true; + ASSERT_TRUE(av2_dm_rational_make(28906, 27225, &underflow.observed)); + ASSERT_TRUE(av2_dm_rational_make(460253, 375000, &underflow.limit)); + char details[1024]; + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &underflow, 0, 0, details, sizeof(details))); + EXPECT_STREQ(details, + "unit=seconds requirement=minimum relation=gte " + "condition=scheduled_removal_gte_last_bit_arrival " + "scheduled_removal=28906/27225 last_bit_arrival=460253/375000 " + "lateness=22541839/136125000 scheduled_removal_ms=1061.745 " + "last_bit_arrival_ms=1227.341 lateness_ms=165.597 " + "spec=annex_e.smoothing_buffer_underflow"); + + Av2DmViolation overflow{}; + overflow.code = AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW; + overflow.observed_present = true; + overflow.limit_present = true; + ASSERT_TRUE(av2_dm_rational_make(101, 1, &overflow.observed)); + ASSERT_TRUE(av2_dm_rational_make(100, 1, &overflow.limit)); + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &overflow, 0, 0, details, sizeof(details))); + EXPECT_STREQ(details, + "unit=bits requirement=maximum relation=lte " + "condition=buffer_fullness_lte_buffer_size " + "buffer_fullness_bits=101 buffer_size_bits=100 excess_bits=1 " + "spec=annex_e.smoothing_buffer_overflow"); + EXPECT_EQ(std::string(details).find("underflow"), std::string::npos); +} + +TEST(DecoderModelDiagnosticTest, TypedAvailabilityAndDelayDetailsAreExact) { + Av2DmViolation unavailable{}; + unavailable.code = AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE; + unavailable.detail.kind = AV2_DM_VIOLATION_DETAIL_BUFFER_POOL; + unavailable.detail.value.buffer_pool = { false, 10, 10, 0, 8, 2 }; + char details[1024]; + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &unavailable, 0, 0, details, sizeof(details))); + EXPECT_NE(std::string(details).find( + "lane=model pool_size=10 frames_in_use=10 free_buffers=0 " + "decoder_held_buffers=8 player_held_buffers=2"), + std::string::npos); + + Av2DmViolation empty{}; + empty.code = AV2_DM_VIOLATION_DECODE_EXISTING_FRAME_BUFFER_EMPTY; + empty.detail.kind = AV2_DM_VIOLATION_DETAIL_REFERENCE_SLOT; + empty.detail.value.reference_slot.requested_slot = 3; + empty.detail.value.reference_slot.slot_in_range = true; + empty.detail.value.reference_slot.reference_valid = true; + empty.detail.value.reference_slot.buffer_index = -1; + empty.detail.value.reference_slot.pool = { false, 10, 4, 6, 4, 1 }; + ASSERT_TRUE(av2_decoder_model_format_violation_details(&empty, 0, 0, details, + sizeof(details))); + EXPECT_NE(std::string(details).find( + "requested_reference_slot=3 slot_in_range=1 ref_valid=1 " + "vbi=-1 pool_size=10 frames_in_use=4 free_buffers=6"), + std::string::npos); + + empty.detail.value.reference_slot.requested_slot = 8; + empty.detail.value.reference_slot.slot_in_range = false; + empty.detail.value.reference_slot.reference_valid = false; + empty.detail.value.reference_slot.buffer_index = -1; + ASSERT_TRUE(av2_decoder_model_format_violation_details(&empty, 0, 0, details, + sizeof(details))); + EXPECT_NE(std::string(details).find( + "requested_reference_slot=8 slot_in_range=0 ref_valid=NA " + "vbi=NA pool_size=10 frames_in_use=4 free_buffers=6"), + std::string::npos); + + Av2DmViolation delay{}; + delay.code = AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT; + delay.observed_present = true; + delay.limit_present = true; + ASSERT_TRUE(av2_dm_rational_make(4, 1, &delay.observed)); + ASSERT_TRUE(av2_dm_rational_make(4, 1, &delay.limit)); + delay.detail.kind = AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY; + delay.detail.value.delay_consistency.decoder_buffer_delay_ticks = 5; + delay.detail.value.delay_consistency.ceil_time_delta_present = true; + ASSERT_TRUE(av2_dm_rational_make( + 4, 1, &delay.detail.value.delay_consistency.ceil_time_delta_ticks)); + ASSERT_TRUE(av2_decoder_model_format_violation_details(&delay, 0, 0, details, + sizeof(details))); + EXPECT_NE(std::string(details).find( + "unit=ticks requirement=maximum relation=lte " + "condition=decoder_buffer_delay_lte_ceil_time_delta " + "time_delta_ticks=4 decoder_buffer_delay_minus_one_ticks=4 " + "decoder_buffer_delay_ticks=5 " + "ceil_time_delta_ticks=4 decoder_buffer_delay_excess=1"), + std::string::npos); +} + +TEST(DecoderModelDiagnosticTest, DerivedIntervalsAndRatesRemainExplicit) { + Av2DmViolation minimum_decode{}; + minimum_decode.code = AV2_DM_VIOLATION_MINIMUM_DECODE_TIME; + minimum_decode.observed_present = true; + minimum_decode.limit_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, 200, &minimum_decode.observed)); + ASSERT_TRUE(av2_dm_rational_make(1, 100, &minimum_decode.limit)); + minimum_decode.detail.kind = AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME; + ASSERT_TRUE(av2_dm_rational_make( + 1, 100, + &minimum_decode.detail.value.minimum_decode_time.frame_decode_time)); + ASSERT_TRUE(av2_dm_rational_make( + 1, 120, + &minimum_decode.detail.value.minimum_decode_time.one_header_time)); + char details[1024]; + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &minimum_decode, 0, 0, details, sizeof(details))); + EXPECT_NE(std::string(details).find( + "required_decode_interval=1/100 shortfall=1/200 " + "frame_decode_time=1/100 one_header_time=1/120"), + std::string::npos); + + Av2DmViolation tile_rate{}; + tile_rate.code = AV2_DM_VIOLATION_FRAME_TILE_RATE; + tile_rate.observed_present = true; + tile_rate.limit_present = true; + ASSERT_TRUE(av2_dm_rational_make(3, 1, &tile_rate.observed)); + ASSERT_TRUE(av2_dm_rational_make(2, 1, &tile_rate.limit)); + tile_rate.detail.kind = AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL; + ASSERT_TRUE( + av2_dm_rational_make(1, 60, &tile_rate.detail.value.frame_interval)); + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &tile_rate, 0, 0, details, sizeof(details))); + EXPECT_NE(std::string(details).find("frame_parsing_interval=1/60 " + "frame_parsing_interval_ms=16.667 " + "observed_tile_rate=180.000tiles/s " + "limit_tile_rate=120.000tiles/s"), + std::string::npos); +} + +TEST(DecoderModelDiagnosticTest, FormattingIsBoundedForMaximumWidthValues) { + Av2DmViolation violation{}; + violation.code = AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS; + violation.observed_present = true; + violation.observed.magnitude = { { UINT64_MAX, UINT64_MAX, UINT64_MAX, + UINT64_MAX } }; + violation.observed.denominator = { { 1, 0, 0, 0 } }; + char details[1024]; + ASSERT_TRUE(av2_decoder_model_format_violation_details( + &violation, 0, 0, details, sizeof(details))); + EXPECT_NE(std::string(details).find("frame_symbol_count=0xffffffffffffffff"), + std::string::npos); + char too_small[8]; + EXPECT_FALSE(av2_decoder_model_format_violation_details( + &violation, 0, 0, too_small, sizeof(too_small))); + + // A huge optional decimal cannot be represented by the bounded formatter. + violation.code = AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO; + violation.observed.magnitude = { { 0, 1, 0, 0 } }; + EXPECT_FALSE(av2_decoder_model_format_violation_details( + &violation, 0, 0, details, sizeof(details))); + + uint64_t violation_count = 0; + bool fatal_violation = true; + testing::internal::CaptureStderr(); + ASSERT_TRUE(av2_decoder_model_report_violation_for_testing( + &violation, false, &violation_count, &fatal_violation)); + const std::string warning = testing::internal::GetCapturedStderr(); + EXPECT_NE(warning.find("AV2_DECODER_MODEL_WARNING status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE(warning.find(" details=unavailable\n"), std::string::npos); + EXPECT_EQ(violation_count, 1u); + EXPECT_FALSE(fatal_violation); + + violation_count = 0; + testing::internal::CaptureStderr(); + ASSERT_TRUE(av2_decoder_model_report_violation_for_testing( + &violation, true, &violation_count, &fatal_violation)); + const std::string fatal_warning = testing::internal::GetCapturedStderr(); + EXPECT_NE( + fatal_warning.find("AV2_DECODER_MODEL_WARNING status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE(fatal_warning.find(" details=unavailable\n"), std::string::npos); + EXPECT_EQ(violation_count, 1u); + EXPECT_TRUE(fatal_violation); +} + +TEST(DecoderModelDiagnosticTest, FormattingIsIndependentOfNumericLocale) { + Av2DmViolation timing{}; + timing.code = AV2_DM_VIOLATION_DISPLAY_FRAME_LATE; + timing.observed_present = true; + timing.limit_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, 3, &timing.observed)); + ASSERT_TRUE(av2_dm_rational_make(1, 4, &timing.limit)); + const char *const previous_locale = std::setlocale(LC_NUMERIC, nullptr); + const std::string saved_locale = + previous_locale == nullptr ? "C" : previous_locale; + const char *selected_locale = std::setlocale(LC_NUMERIC, "fr_FR.UTF-8"); + if (selected_locale == nullptr) { + selected_locale = std::setlocale(LC_NUMERIC, "de_DE.UTF-8"); + } + char details[1024]; + const bool formatted = selected_locale != nullptr && + av2_decoder_model_format_violation_details( + &timing, 0, 0, details, sizeof(details)); + const std::string result = formatted ? details : ""; + (void)std::setlocale(LC_NUMERIC, saved_locale.c_str()); + if (selected_locale == nullptr) GTEST_SKIP() << "No comma-decimal locale"; + ASSERT_TRUE(formatted); + EXPECT_NE(result.find("output_time_ms=333.333"), std::string::npos); + EXPECT_EQ(result.find(','), std::string::npos); +} + +TEST_F(DecoderModelHookOrderTest, + HookOrderMatchesWrapupReferenceOutputAndFinish) { + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.frame_starts, 1u); + EXPECT_EQ(stats.reference_updates, 1u); + EXPECT_EQ(stats.outputs, 1u); + EXPECT_LT(stats.last_frame_start_event, stats.last_reference_update_event); + EXPECT_LT(stats.last_reference_update_event, stats.last_output_event); + EXPECT_LT(stats.last_output_event, stats.finish_event); +} + +TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + pbi_->obu_type = OBU_OPEN_LOOP_KEY; + pbi_->common.ref_frame_map[0] = nullptr; + pbi_->valid_for_referencing[0] = 0; + av2_decoder_model_verifier_on_olk_reference_invalidation(pbi_); + Av2DmVerifierStats before_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_start)); + av2_decoder_model_verifier_record_obu(pbi_, OBU_OPEN_LOOP_KEY, 0, 0, 0, 800); + pbi_->common.cur_frame = &second_frame_; + second_frame_.xlayer_id = 0; + second_frame_.mlayer_id = 0; + second_frame_.tlayer_id = 0; + second_frame_.width = 64; + second_frame_.height = 64; + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + + Av2DmVerifierStats after_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_start)); + EXPECT_EQ(after_start.olk_invalidations, 1u); + EXPECT_EQ(after_start.frame_starts, 2u); + EXPECT_EQ(before_start.last_olk_invalidation_event, + after_start.last_olk_invalidation_event); + EXPECT_LT(after_start.last_olk_invalidation_event, + after_start.last_frame_start_event); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, 0, 0, 0, 80); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + pbi_->common.ref_frame_map[0] = &second_frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + Av2DmVerifierStats live; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); + EXPECT_EQ(live.live_runs, 2u); + EXPECT_EQ(live.result_count, 0u); + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); + EXPECT_EQ(live.result_count, 2u); + EXPECT_EQ(live.live_runs, 0u); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_CVS_RESULT "), 1u); + EXPECT_NE(diagnostics.find("rap=0 mode=resource decoded=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("rap=1 mode=resource decoded=1"), + std::string::npos); +} + +TEST_F(DecoderModelHookOrderTest, + DelayedImplicitAndCurrentOutputsKeepSeparateProvenance) { + pbi_->seq_list[1][0] = pbi_->seq_list[0][0]; + pbi_->seq_list[1][0].max_mlayer_id = 1; + pbi_->seq_list[1][0].seq_max_mlayer_cnt = 2; + pbi_->common.seq_params = pbi_->seq_list[1][0]; + pbi_->common.ci_params_per_layer[1] = pbi_->common.ci_params_per_layer[0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 1, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 1, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 1, KEY_FRAME, true, true, 1, + 1); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + Av2DmVerifierStats before_output; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_output)); + EXPECT_EQ(before_output.outputs, 0u); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + Av2DmVerifierStats implicit_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &implicit_stats)); + EXPECT_EQ(implicit_stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(implicit_stats.last_output_presentation_frame_unit, 0u); + EXPECT_EQ(implicit_stats.last_output_presentation_temporal_unit, 0u); + EXPECT_EQ(implicit_stats.last_output_generation, 1u); + EXPECT_EQ(implicit_stats.last_output_presentation_xlayer_id, 1); + EXPECT_EQ(implicit_stats.last_output_presentation_mlayer_id, 1); + EXPECT_EQ(implicit_stats.last_output_presentation_tlayer_id, 1); + EXPECT_FALSE(implicit_stats.last_output_uses_current_presentation); + + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + Av2DmVerifierStats current_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, ¤t_stats)); + EXPECT_EQ(current_stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(current_stats.last_output_presentation_frame_unit, 1u); + EXPECT_EQ(current_stats.last_output_presentation_temporal_unit, 1u); + EXPECT_EQ(current_stats.last_output_generation, 2u); + EXPECT_EQ(current_stats.last_output_presentation_xlayer_id, 0); + EXPECT_EQ(current_stats.last_output_presentation_mlayer_id, 0); + EXPECT_EQ(current_stats.last_output_presentation_tlayer_id, 0); + EXPECT_TRUE(current_stats.last_output_uses_current_presentation); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, ¤t_stats)); + EXPECT_EQ(current_stats.result_count, 2u); + EXPECT_EQ(current_stats.conformant_results, 2u) << diagnostics; +} + +TEST_F(DecoderModelHookOrderTest, + OutputFrameBuffersQueuesImplicitBeforeCurrentWithoutRetiming) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + frame_.display_order_hint = 4; + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME); + second_frame_.display_order_hint = 5; + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + pbi_->last_output_doh[0][0] = -1; + + ASSERT_EQ(av2_output_frame_buffers(pbi_, -1), 0); + ASSERT_EQ(pbi_->num_output_frames, 2u); + EXPECT_EQ(pbi_->output_frames[0], &frame_); + EXPECT_EQ(pbi_->output_frames[1], &second_frame_); + EXPECT_TRUE(frame_.frame_output_done); + EXPECT_TRUE(second_frame_.frame_output_done); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.outputs, 2u); + EXPECT_EQ(stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(stats.last_output_presentation_frame_unit, 1u); + EXPECT_EQ(stats.last_output_presentation_temporal_unit, 1u); + EXPECT_EQ(stats.last_output_generation, 2u); + EXPECT_TRUE(stats.last_output_uses_current_presentation); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + testing::internal::GetCapturedStderr(); + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + ASSERT_TRUE(stats.replay_previous_presentation_offset_valid); + ASSERT_TRUE(stats.replay_last_presentation_offset_valid); + ExpectAdapterRational(stats.replay_previous_presentation_offset, 0, 1); + ExpectAdapterRational(stats.replay_last_presentation_offset, 1, 30); +} + +TEST_F(DecoderModelHookOrderTest, + OutputFrameBuffersQueuesSuccessiveImplicitWithItsOwner) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + frame_.display_order_hint = 6; + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME); + second_frame_.display_order_hint = 5; + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + pbi_->last_output_doh[0][0] = -1; + + ASSERT_EQ(av2_output_frame_buffers(pbi_, -1), 0); + ASSERT_EQ(pbi_->num_output_frames, 2u); + EXPECT_EQ(pbi_->output_frames[0], &second_frame_); + EXPECT_EQ(pbi_->output_frames[1], &frame_); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.outputs, 2u); + EXPECT_EQ(stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(stats.last_output_presentation_frame_unit, 0u); + EXPECT_EQ(stats.last_output_presentation_temporal_unit, 0u); + EXPECT_EQ(stats.last_output_generation, 1u); + EXPECT_FALSE(stats.last_output_uses_current_presentation); +} + +TEST_F(DecoderModelHookOrderTest, + DisplacedReferenceOutputUsesPendingImplicitOwner) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + frame_.display_order_hint = 4; + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME); + second_frame_.display_order_hint = 5; + pbi_->last_output_doh[0][0] = -1; + + ASSERT_EQ(av2_output_frame_buffers(pbi_, 0), 0); + ASSERT_EQ(pbi_->num_output_frames, 1u); + EXPECT_EQ(pbi_->output_frames[0], &frame_); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(stats.last_output_presentation_frame_unit, 0u); + EXPECT_EQ(stats.last_output_presentation_temporal_unit, 0u); + EXPECT_EQ(stats.last_output_generation, 1u); + EXPECT_FALSE(stats.last_output_uses_current_presentation); +} + +TEST_F(DecoderModelHookOrderTest, FlushUsesPendingImplicitOwner) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + frame_.display_order_hint = 4; + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + ASSERT_EQ(flush_remaining_frames(pbi_, 100), AVM_CODEC_OK); + ASSERT_EQ(pbi_->num_output_frames, 1u); + EXPECT_EQ(pbi_->output_frames[0], &frame_); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.last_output_callback_frame_unit, 0u); + EXPECT_EQ(stats.last_output_presentation_frame_unit, 0u); + EXPECT_EQ(stats.last_output_presentation_temporal_unit, 0u); + EXPECT_EQ(stats.last_output_generation, 1u); + EXPECT_FALSE(stats.last_output_uses_current_presentation); +} + +TEST_F(DecoderModelHookOrderTest, + ClkBoundaryKeepsPrefixAfterOldImplicitOutput) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->obu_type = OBU_CLOSED_LOOP_KEY; + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_RESULT status=CONFORMANT " + "xlayer=0 ops=-1 op=-1 rap=0 mode=resource " + "decoded=1 outputs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + "xlayer=0 cvs=1"), + std::string::npos); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 800u); +} + +TEST_F(DecoderModelHookOrderTest, ClkBoundaryDropsPriorCvsPendingDfgBits) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_SEF, 0, 0, 0, 400); + AV2_COMMON *const cm = &pbi_->common; + pbi_->obu_type = OBU_REGULAR_SEF; + cm->show_existing_frame = 1; + cm->sef_ref_fb_idx = 0; + cm->cur_frame = &second_frame_; + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + av2_decoder_model_verifier_after_reference_update(pbi_, 0); + av2_decoder_model_verifier_on_output(pbi_, 0, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->obu_type = OBU_CLOSED_LOOP_KEY; + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + (void)testing::internal::GetCapturedStderr(); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 808u); +} + +TEST_F(DecoderModelHookOrderTest, ClkBoundaryOnlyClosesItsXlayer) { + pbi_->seq_list[1][0] = pbi_->seq_list[0][0]; + pbi_->common.seq_params = pbi_->seq_list[1][0]; + pbi_->obu_type = OBU_SEQUENCE_HEADER; + av2_decoder_model_verifier_on_sequence_header(pbi_, 1, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 1, 0); + + pbi_->common.seq_params = pbi_->seq_list[0][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, + 0); + UpdateAndOutput(); + pbi_->common.seq_params = pbi_->seq_list[1][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 1); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + pbi_->obu_type = OBU_CLOSED_LOOP_KEY; + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + "xlayer=0 cvs=1"), + std::string::npos); + EXPECT_EQ(diagnostics.find("xlayer=1 cvs=1"), std::string::npos); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.live_runs, 1u); +} + +TEST_F(DecoderModelHookOrderTest, + MultipleClkFrameUnitsInSameTemporalUnitShareCvs) { + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 0, 0, true); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_CVS_RESULT "), 1u); + EXPECT_NE(diagnostics.find("xlayer=0 cvs=1"), std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT "), + std::string::npos); + EXPECT_NE(diagnostics.find("cvs=1 "), std::string::npos); +} + +TEST_F(DecoderModelResultTest, ConformantResultAndFinishAreIdempotent) { + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + av2_decoder_model_verifier_finish(pbi_); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.conformant_results, 1u); + EXPECT_EQ(stats.non_conformant_results, 0u); + EXPECT_EQ(stats.indeterminate_results, 0u); +} + +#if CONFIG_12BIT_PROFILE +TEST_F(DecoderModelResultTest, Profile5HighTierConfigurationIsConformant) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_max_level_idx = SEQ_LEVEL_4_0; + sequence->seq_tier = 1; + sequence->seq_profile_idc = MAIN_444C_12_IP2; + sequence->bit_depth = AVM_BITS_12; + sequence->subsampling_x = 0; + sequence->subsampling_y = 0; + sequence->max_frame_width = 64; + sequence->max_frame_height = 64; + sequence->ref_frames = 1; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.conformant_results, 1u); + EXPECT_EQ(stats.non_conformant_results, 0u); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_WARNING "), 0u); + EXPECT_NE(diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(diagnostics.find("level=4 level_name=4.0 tier=high"), + std::string::npos); +} +#endif // CONFIG_12BIT_PROFILE + +TEST_F(DecoderModelResultTest, + VerifierAllocationFailureIsIndeterminateAndIdempotent) { + av2_decoder_model_verifier_destroy(pbi_); + ASSERT_EQ(pbi_->decoder_model_verifier, nullptr); + pbi_->decoder_model_verifier_allocation_failed = true; + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_ERROR "), 1u); + EXPECT_NE(diagnostics.find("code=ALLOCATION_FAILURE"), std::string::npos); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_RESULT "), 1u); + EXPECT_EQ( + CountOccurrences(diagnostics, "AV2_DECODER_MODEL_BITSTREAM_RESULT "), 1u); + EXPECT_NE( + diagnostics.find("status=INDETERMINATE xlayer=-1 ops=-1 op=-1 rap=-1 " + "mode=resource decoded=0 outputs=0 reordered_outputs=0 " + "violations=0 reason=internal_failure"), + std::string::npos); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_FALSE(stats.available); + EXPECT_TRUE(stats.failed); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.indeterminate_results, 1u); +} + +TEST_F(DecoderModelResultTest, + EarlyVerifierFailureIsIndeterminateAndIdempotent) { + av2_decoder_model_verifier_destroy(pbi_); + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + av2_decoder_model_verifier_on_accounting_failure(pbi_); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_ERROR "), 1u); + EXPECT_NE(diagnostics.find("code=ARITHMETIC_FAILURE"), std::string::npos); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_RESULT "), 1u); + EXPECT_NE( + diagnostics.find("status=INDETERMINATE xlayer=-1 ops=-1 op=-1 rap=-1 " + "mode=resource decoded=0 outputs=0 reordered_outputs=0 " + "violations=0 reason=internal_failure"), + std::string::npos); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_TRUE(stats.available); + EXPECT_TRUE(stats.failed); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.indeterminate_results, 1u); +} + +TEST_F(DecoderModelResultTest, + InternalFailureBeforeRunResultMakesOpenCvsIndeterminate) { + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->obu_type = OBU_CLOSED_LOOP_KEY; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + av2_decoder_model_verifier_on_internal_failure_for_testing(pbi_); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_ERROR "), 1u); + EXPECT_NE(diagnostics.find("code=INTERNAL_STATE_FAILURE"), std::string::npos); + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_WARNING "), std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE xlayer=0 cvs=1 " + "violations=0 verification_complete=0 " + "reason=internal_failure"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=INDETERMINATE complete=0 cvs=1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + InternalFailureAfterViolationPreservesNonConformantCvs) { + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 4096, 64); + av2_decoder_model_verifier_on_internal_failure_for_testing(pbi_); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_ERROR "), 1u); + EXPECT_NE(diagnostics.find("code=INTERNAL_STATE_FAILURE"), std::string::npos); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_WARNING "), 2u); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=NON_CONFORMANT xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("verification_complete=0 " + "reason=internal_failure"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=NON_CONFORMANT complete=0 cvs=1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, FatalModeDoesNotStopForInternalVerifierFailure) { + av2_decoder_model_verifier_destroy(pbi_); + pbi_->decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_FATAL; + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + Configure(64, 64); + + av2_decoder_model_verifier_on_internal_failure_for_testing(pbi_); + EXPECT_FALSE(av2_decoder_model_verifier_should_stop(pbi_)); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_ERROR "), 1u); + EXPECT_NE(diagnostics.find("code=INTERNAL_STATE_FAILURE"), std::string::npos); + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_WARNING "), std::string::npos); + EXPECT_NE(diagnostics.find("status=INDETERMINATE complete=0"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, ModelArithmeticFailureDoesNotSuppressLaterCvs) { + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing(pbi_); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_ERROR "), 1u); + EXPECT_NE(diagnostics.find("code=ARITHMETIC_FAILURE"), std::string::npos); + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 cvs=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("status=INDETERMINATE complete=0 cvs=2"), + std::string::npos); + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_WARNING "), std::string::npos); +} + +TEST_F(DecoderModelResultTest, StaticLevelViolationIsNonConformant) { + pbi_->common.seq_params.max_frame_width = 4096; + pbi_->seq_list[0][0].max_frame_width = 4096; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + StartFrame(OBU_CLOSED_LOOP_KEY, 4096, 64); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.non_conformant_results, 1u); +} + +TEST_F(DecoderModelResultTest, WarningIsReportedWhenViolationOccurs) { + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 4096, 64); + const std::string immediate = testing::internal::GetCapturedStderr(); + EXPECT_EQ(CountOccurrences(immediate, "AV2_DECODER_MODEL_WARNING "), 2u); + EXPECT_NE(immediate.find("code=MAX_PICTURE_SIZE "), std::string::npos); + EXPECT_NE(immediate.find("code=MAX_HORIZONTAL_SIZE "), std::string::npos); + + testing::internal::CaptureStderr(); + UpdateAndOutput(); + Av2DmVerifierStats live; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); + EXPECT_EQ(live.outputs, 1u); + av2_decoder_model_verifier_finish(pbi_); + const std::string final = testing::internal::GetCapturedStderr(); + EXPECT_EQ(final.find("AV2_DECODER_MODEL_WARNING "), std::string::npos); + EXPECT_NE(final.find("AV2_DECODER_MODEL_CVS_RESULT status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE( + final.find("AV2_DECODER_MODEL_BITSTREAM_RESULT status=NON_CONFORMANT"), + std::string::npos); + Av2DmVerifierStats finished; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &finished)); + EXPECT_EQ(finished.live_runs, 0u); +} + +TEST_F(DecoderModelResultTest, FatalModeStopsBeforeOpeningThirdCvs) { + av2_decoder_model_verifier_destroy(pbi_); + pbi_->decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_FATAL; + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + Configure(64, 64); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 4096, 64, &frame_, 0, KEY_FRAME, false, true, + 0, 0, true); + EXPECT_TRUE(av2_decoder_model_verifier_should_stop(pbi_)); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_WARNING "), 1u); + EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT xlayer=0 cvs=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("verification_complete=0"), std::string::npos); + EXPECT_EQ(diagnostics.find("xlayer=0 cvs=3"), std::string::npos); + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT complete=0 cvs=2"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + ThreeCvsResultsPreserveNonConformantBitstreamVerdict) { + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 4096, 64, &frame_, 0, KEY_FRAME, false, true, + 0, 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + "xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE( + diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=NON_CONFORMANT " + "xlayer=0 cvs=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + "xlayer=0 cvs=3"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=NON_CONFORMANT complete=1 cvs=3"), + std::string::npos); + EXPECT_NE(diagnostics.find("first_non_conformant_xlayer=0 " + "first_non_conformant_cvs=2"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + ClkBoundaryRebuildsIncompleteExtractionFromRetainedPrefix) { + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, true); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + "xlayer=0 cvs=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE xlayer=0 cvs=3"), + std::string::npos); + EXPECT_EQ(CountOccurrences(diagnostics, "reason=incomplete_extraction"), 4u); +} + +TEST_F(DecoderModelResultTest, CompletedCvsReleasesLiveStorage) { + testing::internal::CaptureStderr(); + for (int cvs = 0; cvs < 32; ++cvs) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, + 0, 0, true); + UpdateAndOutput(); + Av2DmVerifierStats live; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); + EXPECT_EQ(live.live_runs, 1u); + EXPECT_LE(live.live_generations, 1u); + EXPECT_LE(live.parameter_records, 2u); + } + av2_decoder_model_verifier_finish(pbi_); + (void)testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats finished; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &finished)); + EXPECT_EQ(finished.live_runs, 0u); + EXPECT_LE(finished.live_generations, 1u); + EXPECT_LE(finished.parameter_records, 2u); +} + +TEST_F(DecoderModelResultTest, ExplicitOperatingPointLevelOverridesSequence) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_max_level_idx = SEQ_LEVEL_3_0; + sequence->max_frame_width = 640; + sequence->max_frame_height = 480; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_ptl_present_flag = 1; + ops->op[0].ops_seq_profile_idc[0] = MAIN_420_10_IP0; + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_2_1; + ops->op[0].ops_mlayer_count[0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 640, 480); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 2u); + EXPECT_EQ(stats.conformant_results, 1u); + EXPECT_EQ(stats.non_conformant_results, 1u); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_WARNING "), 1u); + EXPECT_NE(diagnostics.find("code=MAX_PICTURE_SIZE xlayer=0 ops=1 op=0"), + std::string::npos); + EXPECT_NE(diagnostics.find("level=1 level_name=2.1 tier=main " + "scope=operating_point mode=resource"), + std::string::npos); + EXPECT_NE(diagnostics.find("event_type=frame frame_unit=0 temporal_unit=0"), + std::string::npos); + EXPECT_NE(diagnostics.find("observed=307200 limit=278784 " + "unit=luma_samples requirement=maximum"), + std::string::npos); + EXPECT_EQ(diagnostics.find("0x0000000000000000"), std::string::npos); + EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + OlkParameterUpdatePreservesContinuousRunAndStartsFreshRun) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_max_level_idx = SEQ_LEVEL_3_0; + sequence->max_frame_width = 640; + sequence->max_frame_height = 480; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_ptl_present_flag = 1; + ops->op[0].ops_seq_profile_idc[0] = MAIN_420_10_IP0; + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_3_0; + ops->op[0].ops_mlayer_count[0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_); + UpdateAndOutput(); + + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_2_0; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY, 640, 480, &second_frame_); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_CVS_RESULT "), 1u); + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT xlayer=0 ops=1 op=0 " + "rap=0 mode=resource decoded=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT xlayer=0 ops=1 op=0 " + "rap=1 mode=resource decoded=1"), + std::string::npos); + EXPECT_EQ(CountOccurrences(diagnostics, + "code=MAX_PICTURE_SIZE xlayer=0 ops=1 op=0"), + 2u); +} + +TEST_F(DecoderModelResultTest, + NonRapOperatingPointChangeIsIndeterminateWithoutRestart) { + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_ptl_present_flag = 1; + ops->op[0].ops_seq_profile_idc[0] = MAIN_420_10_IP0; + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_3_0; + ops->op[0].ops_mlayer_count[0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_); + UpdateAndOutput(); + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_2_0; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=1 op=0 " + "rap=0 mode=resource decoded=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("reason=missing_required_input"), + std::string::npos); + EXPECT_EQ(diagnostics.find("xlayer=0 ops=1 op=0 rap=1"), std::string::npos); +} + +TEST_F(DecoderModelResultTest, + MaximumLayerIdLowersReferenceLimitForWholeAndOperatingPointScopes) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_max_level_idx = SEQ_LEVEL_2_0; + sequence->max_frame_width = 512; + sequence->max_frame_height = 288; + sequence->max_mlayer_id = 1; + sequence->seq_max_mlayer_cnt = 1; + sequence->still_picture = 0; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_ptl_present_flag = 1; + ops->op[0].ops_seq_profile_idc[0] = MAIN_420_10_IP0; + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_2_0; + ops->op[0].ops_mlayer_count[0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + pbi_->common.features.allow_global_intrabc = 1; + pbi_->common.lf.apply_deblocking_filter[0] = 1; + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 512, 288); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 2u); + EXPECT_EQ(stats.non_conformant_results, 2u); + EXPECT_EQ(CountOccurrences(diagnostics, "code=MAX_REFERENCE_FRAMES "), 2u); + EXPECT_NE(diagnostics.find("code=MAX_REFERENCE_FRAMES xlayer=0 ops=-1"), + std::string::npos); + EXPECT_NE(diagnostics.find("code=MAX_REFERENCE_FRAMES xlayer=0 ops=1 op=0"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + InterOutputUsesSequenceMaximumForDisplaySampleRate) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_max_level_idx = SEQ_LEVEL_2_0; + sequence->max_frame_width = 640; + sequence->max_frame_height = 480; + sequence->ref_frames = 3; + sequence->still_picture = 0; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 320, 240, &frame_); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 320, 240, &second_frame_, 0, INTER_FRAME); + pbi_->common.ref_frame_map[0] = &second_frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.non_conformant_results, 1u); + EXPECT_EQ(CountOccurrences(diagnostics, "code=MAX_DISPLAY_RATE "), 1u); + EXPECT_EQ( + CountOccurrences(diagnostics, "code=MINIMUM_PRESENTATION_INTERVAL "), 1u); + EXPECT_NE(diagnostics.find("level=0 level_name=2.0 tier=main " + "scope=whole_xlayer mode=resource"), + std::string::npos); + EXPECT_NE(diagnostics.find("event_type=output"), std::string::npos); + EXPECT_NE(diagnostics.find("unit=luma_samples_per_interval " + "requirement=maximum relation=lte " + "condition=display_luma_samples_lte_" + "output_interval_capacity"), + std::string::npos); + EXPECT_NE(diagnostics.find("observed_rate="), std::string::npos); + EXPECT_NE(diagnostics.find("Msamples/s limit_rate="), std::string::npos); + EXPECT_NE(diagnostics.find("output_interval_ms="), std::string::npos); + EXPECT_NE(diagnostics.find("unit=seconds requirement=minimum relation=gte " + "condition=presentation_interval_gte_required_" + "presentation_interval"), + std::string::npos); + EXPECT_NE(diagnostics.find("presentation_interval_ms=33.333"), + std::string::npos); + EXPECT_EQ(diagnostics.find("0x0000000000000000"), std::string::npos); +} + +TEST_F(DecoderModelResultTest, UndefinedLowMultistreamLevelIsIndeterminate) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_max_level_idx = SEQ_LEVEL_4_0; + sequence->still_picture = 0; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + pbi_->multistream_decoder_mode = 1; + pbi_->common.num_streams = 2; + pbi_->common.stream_ids[0] = 0; + pbi_->common.stream_ids[1] = 1; + pbi_->common.msdo_params.multistream_profile_idc = MAIN_420_10_IP0; + pbi_->common.msdo_params.multistream_level_idx = SEQ_LEVEL_3_1; + pbi_->common.msdo_params.multistream_tier_idx = 0; + av2_decoder_model_verifier_on_multistream_configuration(pbi_, 1, 0); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.indeterminate_results, 1u); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_WARNING "), 0u); + EXPECT_NE(diagnostics.find("status=INDETERMINATE "), std::string::npos); + EXPECT_NE(diagnostics.find("reason=missing_required_input"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, MissingActiveConfigurationIsIndeterminate) { + av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.indeterminate_results, 1u); +} + +TEST_F(DecoderModelResultTest, + MissingVariablePresentationTimingHasExactReason) { + pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; + pbi_->common.seq_params.still_picture = 0; + pbi_->seq_list[0][0] = pbi_->common.seq_params; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.indeterminate_results, 1u); + EXPECT_EQ(stats.non_conformant_results, 0u); + EXPECT_NE(diagnostics.find("reason=missing_presentation_timing"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + MissingImplicitOwnerHasPresentationProvenanceReason) { + pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; + pbi_->common.seq_params.still_picture = 0; + pbi_->seq_list[0][0] = pbi_->common.seq_params; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.indeterminate_results, 1u); + EXPECT_EQ(stats.non_conformant_results, 0u); + EXPECT_NE(diagnostics.find("reason=missing_presentation_provenance"), + std::string::npos); + EXPECT_EQ(diagnostics.find("reason=missing_presentation_timing"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + SuffixTemporalPointRemainsWithDelayedImplicitOwner) { + pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; + pbi_->common.seq_params.still_picture = 0; + pbi_->seq_list[0][0] = pbi_->common.seq_params; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true, false); + av2_decoder_model_verifier_on_temporal_point(pbi_, 7); + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, 0, 0, 0, 80); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + + Av2DmVerifierStats output_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &output_stats)); + EXPECT_EQ(output_stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(output_stats.last_output_presentation_frame_unit, 0u); + EXPECT_EQ(output_stats.last_output_presentation_temporal_unit, 0u); + EXPECT_FALSE(output_stats.last_output_uses_current_presentation); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(diagnostics.find("reason=missing_presentation_timing"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, MissingOutputGenerationIsIndeterminate) { + StartFrame(OBU_CLOSED_LOOP_KEY); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + second_frame_.xlayer_id = 0; + second_frame_.mlayer_id = 0; + second_frame_.tlayer_id = 0; + second_frame_.width = 64; + second_frame_.height = 64; + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.indeterminate_results, 1u); + EXPECT_NE(diagnostics.find("reason=missing_frame_generation"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + CopiedShowExistingOutputUsesSourceReferenceGeneration) { + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_SEF, 0, 0, 0, 80); + AV2_COMMON *const cm = &pbi_->common; + pbi_->obu_type = OBU_REGULAR_SEF; + cm->show_existing_frame = 1; + cm->sef_ref_fb_idx = 0; + cm->cur_frame = &second_frame_; + second_frame_.xlayer_id = 0; + second_frame_.mlayer_id = 0; + second_frame_.tlayer_id = 0; + second_frame_.width = 64; + second_frame_.height = 64; + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + av2_decoder_model_verifier_after_reference_update(pbi_, 0); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_on_output(pbi_, 0, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + Av2DmVerifierStats output_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &output_stats)); + EXPECT_EQ(output_stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(output_stats.last_output_presentation_frame_unit, 1u); + EXPECT_EQ(output_stats.last_output_presentation_temporal_unit, 1u); + EXPECT_EQ(output_stats.last_output_generation, 1u); + EXPECT_TRUE(output_stats.last_output_uses_current_presentation); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(diagnostics.find("decoded=1 outputs=2"), std::string::npos); + EXPECT_EQ(diagnostics.find("reason=missing_frame_generation"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + AliasedShowExistingUsesCurrentOwnerWithoutConsumingImplicitOwner) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_SEF, 0, 0, 0, 80); + AV2_COMMON *const cm = &pbi_->common; + pbi_->obu_type = OBU_REGULAR_SEF; + cm->show_existing_frame = 1; + cm->sef_ref_fb_idx = 0; + cm->cur_frame = &frame_; + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + av2_decoder_model_verifier_after_reference_update(pbi_, 0); + + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + Av2DmVerifierStats current_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, ¤t_stats)); + EXPECT_EQ(current_stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(current_stats.last_output_presentation_frame_unit, 1u); + EXPECT_EQ(current_stats.last_output_presentation_temporal_unit, 1u); + EXPECT_EQ(current_stats.last_output_generation, 1u); + EXPECT_TRUE(current_stats.last_output_uses_current_presentation); + + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + Av2DmVerifierStats implicit_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &implicit_stats)); + EXPECT_EQ(implicit_stats.last_output_callback_frame_unit, 1u); + EXPECT_EQ(implicit_stats.last_output_presentation_frame_unit, 0u); + EXPECT_EQ(implicit_stats.last_output_presentation_temporal_unit, 0u); + EXPECT_EQ(implicit_stats.last_output_generation, 1u); + EXPECT_FALSE(implicit_stats.last_output_uses_current_presentation); +} + +TEST_F(DecoderModelResultTest, IncompleteRasSeedHasExactReason) { + RefCntBuffer external_long_term; + memset(&external_long_term, 0, sizeof(external_long_term)); + external_long_term.long_term_id = 7; + pbi_->common.ref_frame_map[0] = &external_long_term; + pbi_->valid_for_referencing[0] = 1; + + testing::internal::CaptureStderr(); + StartFrame(OBU_RAS_FRAME, 64, 64, &second_frame_); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.indeterminate_results, 1u); + EXPECT_NE(diagnostics.find("status=INDETERMINATE"), std::string::npos); + EXPECT_NE(diagnostics.find("reason=incomplete_ras_seed"), std::string::npos); +} + +TEST_F(DecoderModelResultTest, + DecoderRecoveryResetMakesOverlappingRunsIndeterminate) { + StartFrame(OBU_CLOSED_LOOP_KEY); + UpdateAndOutput(); + + av2_decoder_model_verifier_on_recovery_reset(pbi_); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_); + pbi_->common.ref_frame_map[0] = &second_frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 2u); + EXPECT_EQ(stats.indeterminate_results, 2u); + EXPECT_EQ(stats.conformant_results, 0u); + EXPECT_NE(diagnostics.find("reason=decoder_recovery_reset"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + UnknownFailedObuAffectsEveryDisjointOperatingPoint) { + pbi_->seq_list[0][0].seq_max_mlayer_cnt = 2; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + OperatingPointSet *const ops = &pbi_->ops_list[0][7]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 7; + ops->ops_cnt = 2; + ops->ops_mlayer_info_idc = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + ops->op[1].mlayer_info.ops_mlayer_map[0] = 2; + ops->op[1].mlayer_info.ops_tlayer_map[0][1] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 7); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 1); + UpdateAndOutput(); + + // The next source frame unit fails before its OBU header/payload has been + // recorded, so its Annex F membership cannot be inferred from the prior + // layer-1 OBU. + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_on_recovery_reset(pbi_); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0); + pbi_->common.ref_frame_map[0] = &second_frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=7 op=0"), + std::string::npos); + EXPECT_EQ(diagnostics.find("status=CONFORMANT xlayer=0 ops=7 op=0"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, CompleteRasSeedIsReplayedFromFreshModel) { + frame_.long_term_id = 5; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, true); + pbi_->common.ref_frame_map[0] = &frame_; + pbi_->valid_for_referencing[0] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + + StartFrame(OBU_RAS_FRAME, 64, 64, &second_frame_); + pbi_->common.current_frame.refresh_frame_flags = 2; + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 2u); + // In the continuous CLK run the TU-0 frame is not output until the RAS + // callback and is therefore late. The independently initialized RAS run is + // conformant using the seeded long-term generation. + EXPECT_EQ(stats.conformant_results, 1u); + EXPECT_EQ(stats.non_conformant_results, 1u); + EXPECT_EQ(stats.indeterminate_results, 0u); + EXPECT_EQ(CountOccurrences(diagnostics, "code=DISPLAY_FRAME_LATE "), 1u); +} + +TEST_F(DecoderModelResultTest, + AbsentSequenceDelayUsesReferenceCountBasedInference) { + pbi_->seq_list[0][0].seq_max_display_model_info_present_flag = 0; + pbi_->seq_list[0][0].seq_max_initial_display_delay_minus_1 = + BUFFER_POOL_MAX_SIZE - 1; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY); + Av2DmContextStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &stats)); + EXPECT_TRUE(stats.resolved_config_present); + EXPECT_EQ(stats.resolved_initial_display_delay, 10u); +} + +TEST_F(DecoderModelResultTest, ScheduleModeRequiresSignalledDecodingClock) { + pbi_->seq_list[0][0].seq_max_decoder_model_present_flag = 1; + pbi_->seq_list[0][0].decoder_model_info_present_flag = 0; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY); + Av2DmContextStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &stats)); + EXPECT_EQ(stats.resolved_mode, AV2_DM_DECODING_SCHEDULE_MODE); + EXPECT_EQ(stats.resolved_applicability, AV2_DM_MISSING_REQUIRED_INPUT); +} + +static_assert(AVM_DECODER_CTRL_ID_MAX == 279, + "Existing decoder control IDs must not change"); +static_assert(AVMD_INCR_OUTPUT_FRAMES_OFFSET == 291, + "Existing decoder control IDs must not change"); +static_assert(AV2D_SET_DECODER_MODEL_CHECK_MODE == 292, + "The decoder-model control must be appended"); + +TEST(DecoderModelControlTest, RejectsInvalidAndLateModeChanges) { + avm_codec_dec_cfg_t config = {}; + libavm_test::AV2Decoder decoder(config); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, + AVM_DECODER_MODEL_CHECK_OFF); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, + AVM_DECODER_MODEL_CHECK_WARN); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, + AVM_DECODER_MODEL_CHECK_FATAL); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, 3, + AVM_CODEC_INVALID_PARAM); + + const uint8_t invalid_input = 0; + EXPECT_NE(decoder.DecodeFrame(&invalid_input, 1), AVM_CODEC_OK); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, + AVM_DECODER_MODEL_CHECK_WARN, AVM_CODEC_INVALID_PARAM); +} + +#if CONFIG_AV2_ENCODER && CONFIG_AV2_DECODER + +struct DecoderModelEncodedPacket { + std::vector bytes; + avm_codec_pts_t pts; +}; + +struct DecoderModelDecodedFrame { + avm_img_fmt_t format; + unsigned int width; + unsigned int height; + unsigned int render_width; + unsigned int render_height; + unsigned int bit_depth; + unsigned int x_chroma_shift; + unsigned int y_chroma_shift; + int monochrome; + int color_primaries; + int transfer_characteristics; + int matrix_coefficients; + int color_range; + int tlayer_id; + int mlayer_id; + int xlayer_id; + uintptr_t timestamp; + std::vector pixels; + + bool operator==(const DecoderModelDecodedFrame &other) const { + return format == other.format && width == other.width && + height == other.height && render_width == other.render_width && + render_height == other.render_height && + bit_depth == other.bit_depth && + x_chroma_shift == other.x_chroma_shift && + y_chroma_shift == other.y_chroma_shift && + monochrome == other.monochrome && + color_primaries == other.color_primaries && + transfer_characteristics == other.transfer_characteristics && + matrix_coefficients == other.matrix_coefficients && + color_range == other.color_range && tlayer_id == other.tlayer_id && + mlayer_id == other.mlayer_id && xlayer_id == other.xlayer_id && + timestamp == other.timestamp && pixels == other.pixels; + } +}; + +struct DecoderModelDecodeOutput { + std::vector frames; + std::vector statuses; + std::string diagnostics; +}; + +static bool ParseSequenceLevel(uint8_t *payload, size_t payload_size, + uint32_t *level_bit_offset, uint32_t *level) { + if (payload == nullptr || payload_size == 0 || level_bit_offset == nullptr || + level == nullptr) { + return false; + } + avm_read_bit_buffer reader = { payload, payload + payload_size, 0, nullptr, + nullptr }; + const uint32_t sequence_header_id = avm_rb_read_uvlc(&reader); + const uint32_t profile = avm_rb_read_literal(&reader, PROFILE_BITS); + (void)avm_rb_read_bit(&reader); + if (sequence_header_id >= MAX_SEQ_NUM || profile >= MAX_PROFILES || + reader.bit_offset > payload_size * 8 || + payload_size * 8 - reader.bit_offset < LEVEL_BITS) { + return false; + } + *level_bit_offset = reader.bit_offset; + *level = avm_rb_read_literal(&reader, LEVEL_BITS); + return is_valid_seq_level_idx(static_cast(*level)); +} + +static void WriteBits(uint8_t *data, uint32_t bit_offset, uint32_t bit_count, + uint32_t value) { + for (uint32_t bit = 0; bit < bit_count; ++bit) { + const uint32_t position = bit_offset + bit; + const uint8_t mask = static_cast(1u << (7 - position % 8)); + if ((value >> (bit_count - bit - 1)) & 1) { + data[position / 8] |= mask; + } else { + data[position / 8] &= static_cast(~mask); + } + } +} + +static bool RewriteSequenceLevels(std::vector *data, + AV2_LEVEL expected_level, + AV2_LEVEL replacement_level, + size_t *rewritten_headers) { + if (data == nullptr || rewritten_headers == nullptr || + expected_level >= SEQ_LEVEL_4_0 || replacement_level >= SEQ_LEVEL_4_0) { + return false; + } + *rewritten_headers = 0; + for (DecoderModelEncodedPacket &packet : *data) { + size_t offset = 0; + while (offset < packet.bytes.size()) { + ObuHeader header; + size_t payload_size = 0; + size_t bytes_read = 0; + const size_t remaining = packet.bytes.size() - offset; + if (avm_read_obu_header_and_size(packet.bytes.data() + offset, remaining, + &header, &payload_size, + &bytes_read) != AVM_CODEC_OK || + bytes_read > remaining || payload_size > remaining - bytes_read) { + return false; + } + if (header.type == OBU_SEQUENCE_HEADER) { + uint8_t *const payload = packet.bytes.data() + offset + bytes_read; + uint32_t level_offset = 0; + uint32_t level = 0; + if (!ParseSequenceLevel(payload, payload_size, &level_offset, &level) || + level != static_cast(expected_level)) { + return false; + } + WriteBits(payload, level_offset, LEVEL_BITS, + static_cast(replacement_level)); + uint32_t reparsed_offset = 0; + if (!ParseSequenceLevel(payload, payload_size, &reparsed_offset, + &level) || + reparsed_offset != level_offset || + level != static_cast(replacement_level)) { + return false; + } + ++*rewritten_headers; + } + offset += bytes_read + payload_size; + } + } + return *rewritten_headers != 0; +} + +static DecoderModelDecodedFrame CopyDecodedFrame(const avm_image_t &image) { + DecoderModelDecodedFrame frame; + frame.format = image.fmt; + frame.width = image.d_w; + frame.height = image.d_h; + frame.render_width = image.r_w; + frame.render_height = image.r_h; + frame.bit_depth = image.bit_depth; + frame.x_chroma_shift = image.x_chroma_shift; + frame.y_chroma_shift = image.y_chroma_shift; + frame.monochrome = image.monochrome; + frame.color_primaries = image.cp; + frame.transfer_characteristics = image.tc; + frame.matrix_coefficients = image.mc; + frame.color_range = image.range; + frame.tlayer_id = image.tlayer_id; + frame.mlayer_id = image.mlayer_id; + frame.xlayer_id = image.xlayer_id; + frame.timestamp = reinterpret_cast(image.user_priv); + const int plane_count = image.monochrome ? 1 : 3; + const int bytes_per_sample = + (image.fmt & AVM_IMG_FMT_HIGHBITDEPTH) != 0 ? 2 : 1; + for (int plane = 0; plane < plane_count; ++plane) { + const int plane_width = avm_img_plane_width(&image, plane); + const int plane_height = avm_img_plane_height(&image, plane); + for (int row = 0; row < plane_height; ++row) { + const uint8_t *const row_start = + image.planes[plane] + row * image.stride[plane]; + frame.pixels.insert(frame.pixels.end(), row_start, + row_start + plane_width * bytes_per_sample); + } + } + return frame; +} + +static void AppendDecodedFrames(libavm_test::AV2Decoder *decoder, + DecoderModelDecodeOutput *output) { + libavm_test::DxDataIterator iterator = decoder->GetDxData(); + const avm_image_t *image = nullptr; + while ((image = iterator.Next()) != nullptr) { + output->frames.push_back(CopyDecodedFrame(*image)); + } +} + +static DecoderModelDecodeOutput DecodePackets( + const std::vector &packets, + avm_decoder_model_check_mode_t mode = AVM_DECODER_MODEL_CHECK_WARN, + bool set_mode = true) { + DecoderModelDecodeOutput output; + avm_codec_dec_cfg_t config = {}; + config.threads = 1; + libavm_test::AV2Decoder decoder(config); + if (set_mode) decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, mode); + testing::internal::CaptureStderr(); + for (const DecoderModelEncodedPacket &packet : packets) { + void *const timestamp = reinterpret_cast( + static_cast(packet.pts) + static_cast(1)); + const avm_codec_err_t status = decoder.DecodeFrame( + packet.bytes.data(), packet.bytes.size(), timestamp); + output.statuses.push_back(status); + if (status != AVM_CODEC_OK) break; + AppendDecodedFrames(&decoder, &output); + } + const avm_codec_err_t flush_status = decoder.DecodeFrame(nullptr, 0); + output.statuses.push_back(flush_status); + if (flush_status == AVM_CODEC_OK) AppendDecodedFrames(&decoder, &output); + output.diagnostics = testing::internal::GetCapturedStderr(); + return output; +} + +class DecoderModelEncodedStreamTest : public ::testing::Test, + public libavm_test::EncoderTest { + protected: + DecoderModelEncodedStreamTest() + : EncoderTest(&libavm_test::kAV2), controls_set_(false), + target_level_(SEQ_LEVEL_3_0), cpu_used_(5) {} + + void SetUp() override { + InitializeConfig(); + SetMode(libavm_test::kOnePassGood); + cfg_.g_threads = 1; + cfg_.g_lag_in_frames = 0; + cfg_.kf_min_dist = 9999; + cfg_.kf_max_dist = 9999; + cfg_.rc_end_usage = AVM_Q; + } + + void PreEncodeFrameHook(libavm_test::VideoSource *video, + libavm_test::Encoder *encoder) override { + if (controls_set_ || video->frame() != 0) return; + encoder->Control(AVME_SET_CPUUSED, cpu_used_); + encoder->Control(AVME_SET_QP, 235); + encoder->Control(AV2E_SET_TARGET_SEQ_LEVEL_IDX, target_level_); + encoder->Control(AV2E_SET_TIMING_INFO_TYPE, AVM_TIMING_EQUAL); + encoder->Control(AV2E_SET_ENABLE_INTRABC, 0); + encoder->Control(AV2E_SET_MAX_REFERENCE_FRAMES, 3); + encoder->SetOption("enable-intrabc-ext", "0"); + encoder->SetOption("dpb-size", "4"); + controls_set_ = true; + } + + bool DoDecode() const override { return false; } + + void FramePktHook(const avm_codec_cx_pkt_t *packet, + libavm_test::DxDataIterator *) override { + if (packet->kind != AVM_CODEC_CX_FRAME_PKT) return; + DecoderModelEncodedPacket encoded; + const uint8_t *const begin = + static_cast(packet->data.frame.buf); + encoded.bytes.assign(begin, begin + packet->data.frame.sz); + encoded.pts = packet->data.frame.pts; + packets_.push_back(encoded); + } + + bool controls_set_; + AV2_LEVEL target_level_; + int cpu_used_; + std::vector packets_; +}; + +TEST_F(DecoderModelEncodedStreamTest, Level20Control) { + Av2DmLevelLimits limits; + ASSERT_TRUE( + av2_dm_get_level_limits(SEQ_LEVEL_2_0, 0, MAIN_420_10_IP0, &limits)); + constexpr uint64_t kPictureSize = 352 * 288; + ASSERT_LE(kPictureSize, limits.max_picture_size); + ASSERT_LE(352u, limits.max_horizontal_size); + ASSERT_LE(288u, limits.max_vertical_size); + ASSERT_LE(kPictureSize * 10, limits.max_display_rate); + ASSERT_LE(kPictureSize * 10, limits.max_decode_rate); + + target_level_ = SEQ_LEVEL_2_0; + libavm_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, + 10, 1, 0, 12); + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + ASSERT_FALSE(packets_.empty()); + const DecoderModelDecodeOutput output = DecodePackets(packets_); + const DecoderModelDecodeOutput explicit_off = + DecodePackets(packets_, AVM_DECODER_MODEL_CHECK_OFF); + const DecoderModelDecodeOutput default_off = + DecodePackets(packets_, AVM_DECODER_MODEL_CHECK_OFF, false); + for (const avm_codec_err_t status : output.statuses) { + ASSERT_EQ(status, AVM_CODEC_OK); + } + EXPECT_EQ(output.frames.size(), 12u); + EXPECT_EQ(CountOccurrences(output.diagnostics, "AV2_DECODER_MODEL_RESULT "), + 1u); + EXPECT_EQ(CountOccurrences(output.diagnostics, "AV2_DECODER_MODEL_WARNING "), + 0u); + EXPECT_NE(output.diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(output.diagnostics.find("violations=0 reason=none"), + std::string::npos); + EXPECT_EQ(explicit_off.statuses, default_off.statuses); + EXPECT_EQ(explicit_off.frames, default_off.frames); + EXPECT_EQ(explicit_off.diagnostics.find("AV2_DECODER_MODEL_"), + std::string::npos); + EXPECT_EQ(default_off.diagnostics.find("AV2_DECODER_MODEL_"), + std::string::npos); +} + +TEST_F(DecoderModelEncodedStreamTest, Level30AndIncorrectLevel21) { + Av2DmLevelLimits level_2_1; + Av2DmLevelLimits level_3_0; + ASSERT_TRUE( + av2_dm_get_level_limits(SEQ_LEVEL_2_1, 0, MAIN_420_10_IP0, &level_2_1)); + ASSERT_TRUE( + av2_dm_get_level_limits(SEQ_LEVEL_3_0, 0, MAIN_420_10_IP0, &level_3_0)); + constexpr uint64_t kPictureSize = 640 * 480; + ASSERT_GT(kPictureSize, level_2_1.max_picture_size); + ASSERT_LE(kPictureSize, level_3_0.max_picture_size); + ASSERT_LE(640u, level_2_1.max_horizontal_size); + ASSERT_LE(480u, level_2_1.max_vertical_size); + ASSERT_LE(kPictureSize * 10, level_2_1.max_display_rate); + ASSERT_LE(kPictureSize * 10, level_2_1.max_decode_rate); + + libavm_test::I420VideoSource video("niklas_640_480_30.yuv", 640, 480, 10, 1, + 0, 12); + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + ASSERT_FALSE(packets_.empty()); + std::vector incorrect_level = packets_; + size_t rewritten_headers = 0; + ASSERT_TRUE(RewriteSequenceLevels(&incorrect_level, SEQ_LEVEL_3_0, + SEQ_LEVEL_2_1, &rewritten_headers)); + ASSERT_GT(rewritten_headers, 0u); + + const DecoderModelDecodeOutput positive = DecodePackets(packets_); + const DecoderModelDecodeOutput negative = DecodePackets(incorrect_level); + const DecoderModelDecodeOutput fatal = + DecodePackets(incorrect_level, AVM_DECODER_MODEL_CHECK_FATAL); + ASSERT_EQ(positive.statuses, negative.statuses); + for (const avm_codec_err_t status : positive.statuses) { + ASSERT_EQ(status, AVM_CODEC_OK); + } + ASSERT_EQ(positive.frames, negative.frames); + ASSERT_EQ(positive.frames.size(), 12u); + + EXPECT_EQ(CountOccurrences(positive.diagnostics, "AV2_DECODER_MODEL_RESULT "), + 1u); + EXPECT_EQ( + CountOccurrences(positive.diagnostics, "AV2_DECODER_MODEL_WARNING "), 0u); + EXPECT_NE(positive.diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(positive.diagnostics.find("violations=0 reason=none"), + std::string::npos); + + EXPECT_EQ(CountOccurrences(negative.diagnostics, "AV2_DECODER_MODEL_RESULT "), + 1u); + EXPECT_EQ( + CountOccurrences(negative.diagnostics, "AV2_DECODER_MODEL_WARNING "), + positive.frames.size()); + EXPECT_EQ(CountOccurrences(negative.diagnostics, "code=MAX_PICTURE_SIZE "), + positive.frames.size()); + EXPECT_NE(negative.diagnostics.find("status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE(negative.diagnostics.find("violations=12 reason=none"), + std::string::npos); + + ASSERT_FALSE(fatal.statuses.empty()); + EXPECT_EQ(fatal.statuses.front(), AVM_CODEC_UNSUP_BITSTREAM); + EXPECT_EQ(CountOccurrences(fatal.diagnostics, "AV2_DECODER_MODEL_WARNING "), + 1u); + EXPECT_EQ( + CountOccurrences(fatal.diagnostics, "AV2_DECODER_MODEL_CVS_RESULT "), 1u); + EXPECT_EQ(CountOccurrences(fatal.diagnostics, + "AV2_DECODER_MODEL_BITSTREAM_RESULT "), + 1u); + EXPECT_NE(fatal.diagnostics.find("status=NON_CONFORMANT"), std::string::npos); + EXPECT_NE(fatal.diagnostics.find("complete=0"), std::string::npos); +} + +#if CONFIG_12BIT_PROFILE +TEST_F(DecoderModelEncodedStreamTest, Profile5TwelveBit444IsConformant) { + cfg_.g_profile = MAIN_444C_12_IP2; + cfg_.g_input_bit_depth = 12; + cfg_.g_bit_depth = AVM_BITS_12; + target_level_ = SEQ_LEVEL_2_0; + cpu_used_ = 8; + + libavm_test::Y4mVideoSource video("park_joy_90p_12_444.y4m", 0, 10); + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + ASSERT_FALSE(packets_.empty()); + + const DecoderModelDecodeOutput output = DecodePackets(packets_); + for (const avm_codec_err_t status : output.statuses) { + ASSERT_EQ(status, AVM_CODEC_OK); + } + ASSERT_EQ(output.frames.size(), 10u); + for (const DecoderModelDecodedFrame &frame : output.frames) { + EXPECT_EQ(frame.bit_depth, 12u); + EXPECT_NE(frame.format & AVM_IMG_FMT_HIGHBITDEPTH, 0); + } + EXPECT_EQ(CountOccurrences(output.diagnostics, "AV2_DECODER_MODEL_RESULT "), + 1u); + EXPECT_EQ(CountOccurrences(output.diagnostics, "AV2_DECODER_MODEL_WARNING "), + 0u); + EXPECT_NE(output.diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(output.diagnostics.find("level=0 level_name=2.0 tier=main"), + std::string::npos); +} + +TEST_F(DecoderModelEncodedStreamTest, Profile5IncorrectLevel20PreservesPixels) { + cfg_.g_profile = MAIN_444C_12_IP2; + cfg_.g_input_bit_depth = 8; + cfg_.g_bit_depth = AVM_BITS_12; + target_level_ = SEQ_LEVEL_2_1; + cpu_used_ = 8; + + libavm_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, + 50, 1, 0, 10); + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + ASSERT_FALSE(packets_.empty()); + std::vector incorrect_level = packets_; + size_t rewritten_headers = 0; + ASSERT_TRUE(RewriteSequenceLevels(&incorrect_level, SEQ_LEVEL_2_1, + SEQ_LEVEL_2_0, &rewritten_headers)); + ASSERT_GT(rewritten_headers, 0u); + + const DecoderModelDecodeOutput positive = DecodePackets(packets_); + const DecoderModelDecodeOutput negative = DecodePackets(incorrect_level); + ASSERT_EQ(positive.statuses, negative.statuses); + for (const avm_codec_err_t status : positive.statuses) { + ASSERT_EQ(status, AVM_CODEC_OK); + } + ASSERT_EQ(positive.frames, negative.frames); + ASSERT_EQ(positive.frames.size(), 10u); + EXPECT_EQ( + CountOccurrences(positive.diagnostics, "AV2_DECODER_MODEL_WARNING "), 0u); + EXPECT_NE(positive.diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(negative.diagnostics.find("status=NON_CONFORMANT"), + std::string::npos); + EXPECT_GT(CountOccurrences(negative.diagnostics, "code=MAX_DISPLAY_RATE "), + 0u); +} +#endif // CONFIG_12BIT_PROFILE + +#endif // CONFIG_AV2_ENCODER && CONFIG_AV2_DECODER + +} // namespace diff --git a/test/decoder_model_parser_test.cc b/test/decoder_model_parser_test.cc new file mode 100644 index 0000000000..9eefcd27fc --- /dev/null +++ b/test/decoder_model_parser_test.cc @@ -0,0 +1,764 @@ +/* + * 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 +#include +#include + +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +#include "avm_dsp/bitreader.h" +#include "avm_dsp/bitwriter.h" +#include "avm_mem/avm_mem.h" +#include "av2/decoder/annexF.h" +#include "av2/decoder/decoder.h" +#include "av2/decoder/decoder_model.h" + +namespace { + +class DecoderModelParserTest : public ::testing::Test { + protected: + void SetUp() override { + pbi_ = static_cast(avm_memalign(32, sizeof(*pbi_))); + ASSERT_NE(pbi_, nullptr); + memset(pbi_, 0, sizeof(*pbi_)); + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + } + + void TearDown() override { + av2_decoder_model_verifier_destroy(pbi_); + avm_free(pbi_); + } + + void AddWholeXlayerContext(int xlayer_id, int sequence_header_id) { + pbi_->seq_list[xlayer_id][sequence_header_id].seq_header_id = + sequence_header_id; + av2_decoder_model_verifier_on_sequence_header(pbi_, xlayer_id, + sequence_header_id); + } + + AV2Decoder *pbi_ = nullptr; +}; + +TEST_F(DecoderModelParserTest, LifecycleStartsWithAvailableEmptyState) { + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_TRUE(stats.available); + EXPECT_FALSE(stats.failed); + EXPECT_EQ(stats.raw_obus, 0u); + EXPECT_EQ(stats.contexts, 0u); +} + +TEST_F(DecoderModelParserTest, ReplaysPrefixAndClosesCompleteDfg) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 16); + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 0, 0, 0, 80); + AddWholeXlayerContext(0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 0, + 120); + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, 0, 0, 0, 40); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 256u); + + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 0u); + EXPECT_EQ(context.last_closed_dfg_bits, 256u); + EXPECT_EQ(context.closed_dfgs, 1u); +} + +TEST_F(DecoderModelParserTest, ShowExistingDoesNotConsumePendingDfgBits) { + AddWholeXlayerContext(0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 0, 0, 0, 24); + pbi_->common.show_existing_frame = 1; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 24u); + EXPECT_EQ(context.closed_dfgs, 0u); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, 0, 0, 0, 8); + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 32u); + EXPECT_EQ(context.closed_dfgs, 1u); +} + +TEST_F(DecoderModelParserTest, OperatingPointUsesAnnexFMembership) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_OPERATING_POINT_SET, 0, 0, 0, + 32); + OperatingPointSet *const ops = &pbi_->ops_list[0][3]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 3; + ops->ops_cnt = 1; + ops->ops_mlayer_info_idc = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1 << 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][1] = 1 << 2; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 3); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 1, 2, + 100); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 1, + 200); + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 0, 0, 0, 16); + av2_decoder_model_verifier_record_obu( + pbi_, OBU_MULTI_STREAM_DECODER_OPERATION, GLOBAL_XLAYER_ID, 0, 0, 24); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_FALSE(context.scope.whole_xlayer); + EXPECT_EQ(context.scope.ops_xlayer_id, 0); + EXPECT_EQ(context.scope.ops_id, 3); + // OPS prefix + selected frame + preserved sequence/global structural OBUs. + EXPECT_EQ(context.pending_dfg_bits, 172u); +} + +TEST_F(DecoderModelParserTest, RasSeedsAreFilteredPerOperatingPoint) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->seq_header_id = 0; + sequence->seq_max_level_idx = SEQ_LEVEL_2_0; + sequence->seq_profile_idc = MAIN_420_10_IP0; + sequence->ref_frames = 8; + sequence->max_frame_width = 64; + sequence->max_frame_height = 64; + sequence->seq_max_mlayer_cnt = 2; + sequence->still_picture = 1; + pbi_->common.seq_params = *sequence; + ContentInterpretation *const ci = &pbi_->common.ci_params_per_layer[0]; + ci->ci_timing_info_present_flag = 1; + ci->timing_info.num_units_in_display_tick = 1; + ci->timing_info.time_scale = 30; + ci->timing_info.equal_elemental_interval = 1; + ci->timing_info.num_ticks_per_elemental_duration = 1; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + + OperatingPointSet *const ops = &pbi_->ops_list[0][3]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 3; + ops->ops_cnt = 2; + ops->ops_mlayer_info_idc = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + ops->op[1].mlayer_info.ops_mlayer_map[0] = 3; + ops->op[1].mlayer_info.ops_tlayer_map[0][0] = 1; + ops->op[1].mlayer_info.ops_tlayer_map[0][1] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 3); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + RefCntBuffer long_term; + RefCntBuffer untracked_long_term; + RefCntBuffer ras_frame; + memset(&long_term, 0, sizeof(long_term)); + memset(&untracked_long_term, 0, sizeof(untracked_long_term)); + memset(&ras_frame, 0, sizeof(ras_frame)); + long_term.xlayer_id = 0; + long_term.mlayer_id = 1; + long_term.tlayer_id = 0; + long_term.long_term_id = 7; + long_term.width = 64; + long_term.height = 64; + untracked_long_term.xlayer_id = 0; + untracked_long_term.mlayer_id = 1; + untracked_long_term.tlayer_id = 0; + untracked_long_term.long_term_id = 8; + + const auto snapshot_frame = [this](int obu_type, int mlayer_id, + RefCntBuffer *frame) { + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, mlayer_id, + 0); + av2_decoder_model_verifier_record_obu(pbi_, obu_type, 0, mlayer_id, 0, 800); + AV2_COMMON *const cm = &pbi_->common; + pbi_->obu_type = static_cast(obu_type); + cm->xlayer_id = 0; + cm->mlayer_id = mlayer_id; + cm->tlayer_id = 0; + cm->show_existing_frame = 0; + cm->cur_frame = frame; + cm->width = 64; + cm->height = 64; + cm->mi_params.mi_cols = 16; + cm->mi_params.mi_rows = 16; + cm->mib_size_log2 = 0; + cm->tiles.cols = 1; + cm->tiles.rows = 1; + cm->tiles.col_start_sb[0] = 0; + cm->tiles.col_start_sb[1] = 16; + cm->tiles.row_start_sb[0] = 0; + cm->tiles.row_start_sb[1] = 16; + cm->current_frame.frame_type = KEY_FRAME; + frame->xlayer_id = 0; + frame->mlayer_id = mlayer_id; + frame->tlayer_id = 0; + frame->width = 64; + frame->height = 64; + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + }; + + snapshot_frame(OBU_REGULAR_TILE_GROUP, 1, &long_term); + pbi_->common.ref_frame_map[0] = &long_term; + pbi_->common.ref_frame_map[1] = &untracked_long_term; + pbi_->valid_for_referencing[0] = 1; + pbi_->valid_for_referencing[1] = 1; + snapshot_frame(OBU_RAS_FRAME, 0, &ras_frame); + + bool found_excluding_op = false; + bool found_including_op = false; + Av2DmVerifierStats verifier_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); + for (uint32_t i = 0; i < verifier_stats.contexts; ++i) { + Av2DmContextStats context; + ASSERT_TRUE( + av2_decoder_model_verifier_get_context_stats(pbi_, i, &context)); + if (context.scope.ops_id != 3) continue; + if (context.scope.operating_point == 0) { + found_excluding_op = true; + EXPECT_TRUE(context.last_ras_seed_complete); + EXPECT_EQ(context.last_ras_seed_count, 0u); + } else if (context.scope.operating_point == 1) { + found_including_op = true; + EXPECT_FALSE(context.last_ras_seed_complete); + EXPECT_EQ(context.last_ras_seed_count, 1u); + } + } + EXPECT_TRUE(found_excluding_op); + EXPECT_TRUE(found_including_op); +} + +TEST_F(DecoderModelParserTest, GlobalOperatingPointCreatesPerXlayerContexts) { + OperatingPointSet *const ops = &pbi_->ops_list[GLOBAL_XLAYER_ID][2]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = GLOBAL_XLAYER_ID; + ops->ops_id = 2; + ops->ops_cnt = 1; + ops->ops_mlayer_info_idc = 1; + ops->op[0].ops_xlayer_map = (1 << 1) | (1 << 3); + ops->op[0].mlayer_info.ops_mlayer_map[1] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[1][0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[3] = 1 << 2; + ops->op[0].mlayer_info.ops_tlayer_map[3][2] = 1 << 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, GLOBAL_XLAYER_ID, 2); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + ASSERT_EQ(stats.contexts, 2u); + Av2DmContextStats first; + Av2DmContextStats second; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &first)); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 1, &second)); + EXPECT_EQ(first.scope.xlayer_id, 1); + EXPECT_EQ(first.scope.ops_xlayer_id, GLOBAL_XLAYER_ID); + EXPECT_EQ(second.scope.xlayer_id, 3); + EXPECT_EQ(second.scope.ops_xlayer_id, GLOBAL_XLAYER_ID); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 1, 0, 0, + 40); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 3, 2, 1, + 80); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &first)); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 1, &second)); + EXPECT_EQ(first.pending_dfg_bits, 40u); + EXPECT_EQ(second.pending_dfg_bits, 80u); +} + +TEST_F(DecoderModelParserTest, UnselectedFrameDoesNotCloseOperatingPointDfg) { + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_mlayer_info_idc = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1 << 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][1] = 1 << 2; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 0, 0, 0, 24); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 0, + 40); + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 24u); + EXPECT_EQ(context.closed_dfgs, 0u); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 1, 2, + 80); + pbi_->common.mlayer_id = 1; + pbi_->common.tlayer_id = 2; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 104u); + EXPECT_EQ(context.closed_dfgs, 1u); +} + +TEST_F(DecoderModelParserTest, OtherXlayerDoesNotCloseWholeXlayerDfg) { + AddWholeXlayerContext(2, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 2, 0, 0, 16); + pbi_->common.xlayer_id = 4; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 16u); + EXPECT_EQ(context.closed_dfgs, 0u); +} + +TEST_F(DecoderModelParserTest, RedefinedOpsDoesNotRewriteOldDfgMembership) { + OperatingPointSet *const ops = &pbi_->ops_list[0][4]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 4; + ops->ops_cnt = 1; + ops->ops_mlayer_info_idc = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 4); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 1, 0, + 40); + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 1; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_OPERATING_POINT_SET, 0, 0, 0, + 20); + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1 << 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][1] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 4); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 20u); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 1, 0, + 80); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 100u); + EXPECT_EQ(context.closed_dfgs, 1u); +} + +TEST_F(DecoderModelParserTest, OpsResetDeactivatesPriorScope) { + OperatingPointSet *const ops = &pbi_->ops_list[0][5]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 5; + ops->ops_cnt = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 5); + + ops->ops_cnt = 0; + ops->ops_reset_flag = 0; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 5); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_FALSE(context.active); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 0, + 64); + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 0u); + EXPECT_EQ(context.closed_dfgs, 0u); +} + +TEST_F(DecoderModelParserTest, ActiveConfigurationUsesActivatedSequence) { + pbi_->seq_list[0][1].seq_header_id = 1; + pbi_->seq_list[0][2].seq_header_id = 2; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 1); + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 2); + pbi_->common.seq_params = pbi_->seq_list[0][1]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 1); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_TRUE(context.active_configuration_present); + EXPECT_EQ(context.active_sequence_header_id, 1); + + OperatingPointSet *const ops = &pbi_->ops_list[0][6]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 6; + ops->ops_cnt = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 6); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 1, &context)); + EXPECT_TRUE(context.active_configuration_present); + EXPECT_EQ(context.active_sequence_header_id, 1); +} + +TEST_F(DecoderModelParserTest, LaterXlayerKeepsCurrentTuGlobalPrefix) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_record_obu( + pbi_, OBU_MULTI_STREAM_DECODER_OPERATION, GLOBAL_XLAYER_ID, 0, 0, 24); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 0, + 40); + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 1, 0, 0, 16); + AddWholeXlayerContext(1, 0); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.pending_dfg_bits, 48u); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 1, 0, 0, + 80); + pbi_->common.xlayer_id = 1; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 128u); +} + +TEST_F(DecoderModelParserTest, RedundantSequencePreservesOpenSefDfg) { + AddWholeXlayerContext(0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_SEF, 0, 0, 0, 16); + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 1; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_record_obu(pbi_, OBU_SEQUENCE_HEADER, 0, 0, 0, 24); + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 0, + 40); + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 96u); +} + +TEST_F(DecoderModelParserTest, RedundantOpsPreservesOpenSefDfg) { + OperatingPointSet *const ops = &pbi_->ops_list[0][4]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 4; + ops->ops_cnt = 1; + ops->ops_mlayer_info_idc = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 4); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_SEF, 0, 0, 0, 16); + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 1; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_record_obu(pbi_, OBU_OPERATING_POINT_SET, 0, 0, 0, + 20); + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 4); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_TILE_GROUP, 0, 0, 0, + 40); + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 92u); +} + +TEST_F(DecoderModelParserTest, StreamBoundaryRelinksIdenticalConfiguration) { + AddWholeXlayerContext(0, 0); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + Av2DmVerifierStats before; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before)); + av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + Av2DmVerifierStats after; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after)); + EXPECT_EQ(after.event_count, before.event_count + 2); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_TRUE(context.active); + EXPECT_TRUE(context.active_configuration_present); +} + +TEST_F(DecoderModelParserTest, StreamBoundaryKeepsNewTemporalUnitPrefix) { + AddWholeXlayerContext(0, 0); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, + GLOBAL_XLAYER_ID, 0, 0, 8); + av2_decoder_model_verifier_record_obu( + pbi_, OBU_MULTI_STREAM_DECODER_OPERATION, GLOBAL_XLAYER_ID, 0, 0, 80); + + av2_decoder_model_verifier_on_stream_configuration_change(pbi_, true); + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 888u); +} + +TEST_F(DecoderModelParserTest, StreamBoundaryDropsStaleTemporalUnitPrefix) { + AddWholeXlayerContext(0, 0); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_decoder_model_verifier_record_obu(pbi_, OBU_REGULAR_SEF, 0, 0, 0, 80); + + av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->common.show_existing_frame = 0; + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.last_closed_dfg_bits, 800u); +} + +TEST_F(DecoderModelParserTest, ReactivationDoesNotReusePriorConfiguration) { + AddWholeXlayerContext(0, 0); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); + + pbi_->seq_list[0][1].seq_header_id = 1; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 1); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_TRUE(context.active); + EXPECT_FALSE(context.active_configuration_present); + EXPECT_EQ(context.active_sequence_header_id, -1); +} + +TEST_F(DecoderModelParserTest, GlobalResetDeactivatesLocalAndGlobalOps) { + OperatingPointSet *const local = &pbi_->ops_list[1][0]; + memset(local, 0, sizeof(*local)); + local->valid = 1; + local->obu_xlayer_id = 1; + local->ops_id = 0; + local->ops_cnt = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 1, 0); + OperatingPointSet *const global = &pbi_->ops_list[GLOBAL_XLAYER_ID][0]; + memset(global, 0, sizeof(*global)); + global->valid = 1; + global->obu_xlayer_id = GLOBAL_XLAYER_ID; + global->ops_id = 0; + global->ops_cnt = 1; + global->op[0].ops_xlayer_map = 1 << 2; + av2_decoder_model_verifier_on_operating_point_set(pbi_, GLOBAL_XLAYER_ID, 0); + + global->ops_reset_flag = 1; + global->ops_cnt = 0; + av2_decoder_model_verifier_on_operating_point_set(pbi_, GLOBAL_XLAYER_ID, 0); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_FALSE(context.active); + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 1, &context)); + EXPECT_FALSE(context.active); +} + +TEST_F(DecoderModelParserTest, FilteredRapDoesNotSuppressOtherXlayerRap) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); + av2_decoder_model_verifier_on_obu_filtered(pbi_); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 2, 0, 0, 40); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.rap_starts, 2u); +} + +TEST_F(DecoderModelParserTest, FilteredRapDoesNotSuppressNextSourceRap) { + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 1, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); + av2_decoder_model_verifier_on_obu_filtered(pbi_); + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 1, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.rap_starts, 2u); +} + +TEST_F(DecoderModelParserTest, TemporalPointRetainsFullUlebValueAndPresence) { + constexpr uint64_t kPresentationTime = 0xfedcba98u; + av2_decoder_model_verifier_on_temporal_point(pbi_, kPresentationTime); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_TRUE(stats.temporal_point_present); + EXPECT_EQ(stats.temporal_point, kPresentationTime); + EXPECT_EQ(stats.temporal_points, 1u); +} + +TEST_F(DecoderModelParserTest, ConfigurationBoundaryIsImmutableEvent) { + av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.event_count, 1u); +} + +TEST(DecoderModelAnnexFTest, WholeXlayerAndStructuralMembership) { + SubBitstreamExtractionState scope; + ASSERT_TRUE(av2_sbe_configure_decoder_model_scope(&scope, 2, nullptr, -1, 1)); + EXPECT_TRUE( + av2_sbe_should_retain_obu(&scope, OBU_REGULAR_TILE_GROUP, 2, 7, 3)); + EXPECT_FALSE( + av2_sbe_should_retain_obu(&scope, OBU_REGULAR_TILE_GROUP, 3, 0, 0)); + EXPECT_TRUE( + av2_sbe_should_retain_obu(&scope, OBU_TEMPORAL_DELIMITER, 3, 0, 0)); + EXPECT_TRUE(av2_sbe_should_retain_obu( + &scope, OBU_MULTI_STREAM_DECODER_OPERATION, GLOBAL_XLAYER_ID, 0, 0)); +} + +TEST(DecoderModelAnnexFTest, OperatingPointPreservesBaseConfiguration) { + OperatingPointSet ops; + memset(&ops, 0, sizeof(ops)); + ops.valid = 1; + ops.obu_xlayer_id = 4; + ops.ops_cnt = 1; + ops.ops_mlayer_info_idc = 1; + ops.op[0].mlayer_info.ops_mlayer_map[4] = 1 << 1; + ops.op[0].mlayer_info.ops_tlayer_map[4][1] = 1 << 2; + + SubBitstreamExtractionState scope; + ASSERT_TRUE(av2_sbe_configure_decoder_model_scope(&scope, 4, &ops, 0, 0)); + EXPECT_TRUE( + av2_sbe_should_retain_obu(&scope, OBU_REGULAR_TILE_GROUP, 4, 1, 2)); + EXPECT_FALSE( + av2_sbe_should_retain_obu(&scope, OBU_REGULAR_TILE_GROUP, 4, 0, 1)); + EXPECT_TRUE(av2_sbe_should_retain_obu(&scope, OBU_SEQUENCE_HEADER, 4, 0, 0)); +} + +TEST(DecoderModelAnnexFTest, GlobalOperatingPointIsScopedPerXlayer) { + OperatingPointSet ops; + memset(&ops, 0, sizeof(ops)); + ops.valid = 1; + ops.obu_xlayer_id = GLOBAL_XLAYER_ID; + ops.ops_cnt = 1; + ops.ops_mlayer_info_idc = 1; + ops.op[0].ops_xlayer_map = (1 << 1) | (1 << 3); + ops.op[0].mlayer_info.ops_mlayer_map[1] = 1; + ops.op[0].mlayer_info.ops_tlayer_map[1][0] = 1; + ops.op[0].mlayer_info.ops_mlayer_map[3] = 1 << 2; + ops.op[0].mlayer_info.ops_tlayer_map[3][2] = 1 << 1; + + SubBitstreamExtractionState scope; + ASSERT_TRUE(av2_sbe_configure_decoder_model_scope(&scope, 3, &ops, 0, 0)); + EXPECT_TRUE( + av2_sbe_should_retain_obu(&scope, OBU_REGULAR_TILE_GROUP, 3, 2, 1)); + EXPECT_FALSE( + av2_sbe_should_retain_obu(&scope, OBU_REGULAR_TILE_GROUP, 1, 0, 0)); +} + +#if CONFIG_AV2_ENCODER +static bool DecodeCountedSymbols() { + uint8_t buffer[64] = { 0 }; + avm_cdf_prob write_cdf[CDF_SIZE(2)] = { AVM_CDF2(16384) }; + avm_writer writer; + memset(&writer, 0, sizeof(writer)); + avm_start_encode(&writer, buffer); + avm_write_literal(&writer, 21, 5); + avm_write_symbol(&writer, 1, write_cdf, 2); + avm_stop_encode(&writer); + + avm_cdf_prob read_cdf[CDF_SIZE(2)] = { AVM_CDF2(16384) }; + avm_reader reader; + if (avm_reader_init(&reader, buffer, writer.pos) != 0) return false; + reader.allow_update_cdf = 0; + if (avm_read_literal(&reader, 5, {}) != 21) return false; + if (avm_read_symbol(&reader, read_cdf, 2, {}) != 1) return false; + return reader.frame_symbol_count == 6; +} + +TEST(DecoderModelSymbolCountTest, MirrorsEncoderLiteralAndSymbolCount) { + EXPECT_TRUE(DecodeCountedSymbols()); +} + +TEST(DecoderModelSymbolCountTest, DirectCdfAndBitAreNotFrameSymbols) { + uint8_t buffer[64] = { 0 }; + avm_cdf_prob cdf[CDF_SIZE(2)] = { AVM_CDF2(16384) }; + avm_writer writer; + memset(&writer, 0, sizeof(writer)); + avm_start_encode(&writer, buffer); + avm_write_bit(&writer, 1); + avm_write_cdf(&writer, 0, cdf, 2); + avm_stop_encode(&writer); + + avm_reader reader; + ASSERT_EQ(avm_reader_init(&reader, buffer, writer.pos), 0); + EXPECT_EQ(avm_read_bit(&reader, {}), 1); + EXPECT_EQ(avm_read_cdf(&reader, cdf, 2, {}), 0); + EXPECT_EQ(reader.frame_symbol_count, 0u); +} + +TEST(DecoderModelSymbolCountTest, IndependentReadersAreThreadLocal) { + constexpr int kThreads = 8; + constexpr int kIterations = 100; + std::vector results(kThreads, 0); + std::vector threads; + for (int i = 0; i < kThreads; ++i) { + threads.emplace_back([i, &results]() { + bool result = true; + for (int j = 0; j < kIterations; ++j) result &= DecodeCountedSymbols(); + results[i] = result ? 1 : 0; + }); + } + for (std::thread &thread : threads) thread.join(); + for (int result : results) EXPECT_EQ(result, 1); +} +#endif // CONFIG_AV2_ENCODER + +} // namespace diff --git a/test/decoder_model_test.cc b/test/decoder_model_test.cc new file mode 100644 index 0000000000..b5ccf405a4 --- /dev/null +++ b/test/decoder_model_test.cc @@ -0,0 +1,3141 @@ +/* + * 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 +#include +#include +#include + +#include "av2/common/decoder_model.h" +extern "C" { +#include "av2/common/annexA.h" +#include "av2/common/level.h" +#include "av2/common/tile_common.h" +#include "av2/common/timing.h" +} +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +namespace { + +Av2DmUnsignedWide MakeWide(uint64_t limb3, uint64_t limb2, uint64_t limb1, + uint64_t limb0) { + return { { limb0, limb1, limb2, limb3 } }; +} + +void ExpectRational(const Av2DmRational &value, uint64_t limb3, uint64_t limb2, + uint64_t limb1, uint64_t limb0, uint64_t denominator, + bool negative = false) { + EXPECT_EQ(value.magnitude.limbs[3], limb3); + EXPECT_EQ(value.magnitude.limbs[2], limb2); + EXPECT_EQ(value.magnitude.limbs[1], limb1); + EXPECT_EQ(value.magnitude.limbs[0], limb0); + EXPECT_EQ(value.denominator.limbs[3], 0u); + EXPECT_EQ(value.denominator.limbs[2], 0u); + EXPECT_EQ(value.denominator.limbs[1], 0u); + EXPECT_EQ(value.denominator.limbs[0], denominator); + EXPECT_EQ(value.negative, negative); +} + +TEST(DecoderModelRationalTest, RejectsZeroDenominatorAndCanonicalizesZero) { + Av2DmRational value; + EXPECT_FALSE(av2_dm_rational_make(1, 0, &value)); + ASSERT_TRUE( + av2_dm_rational_make_wide(MakeWide(0, 0, 0, 0), 123, true, &value)); + ExpectRational(value, 0, 0, 0, 0, 1); +} + +TEST(DecoderModelRationalTest, ReducesGoldenVectors) { + Av2DmRational value; + ASSERT_TRUE(av2_dm_rational_make(1667000, 1000000, &value)); + ExpectRational(value, 0, 0, 0, 1667, 1000); + + ASSERT_TRUE( + av2_dm_rational_make_wide(MakeWide(0, 0, 1, 0), 16, false, &value)); + ExpectRational(value, 0, 0, 0, UINT64_C(1) << 60, 1); +} + +TEST(DecoderModelRationalTest, AddsAndSubtractsExactly) { + Av2DmRational one_third; + Av2DmRational one_sixth; + Av2DmRational result; + ASSERT_TRUE(av2_dm_rational_make(1, 3, &one_third)); + ASSERT_TRUE(av2_dm_rational_make(1, 6, &one_sixth)); + ASSERT_TRUE(av2_dm_rational_add(&one_third, &one_sixth, &result)); + ExpectRational(result, 0, 0, 0, 1, 2); + ASSERT_TRUE(av2_dm_rational_subtract(&one_sixth, &one_third, &result)); + ExpectRational(result, 0, 0, 0, 1, 6, true); + ASSERT_TRUE(av2_dm_rational_add(&one_third, &result, &result)); + ExpectRational(result, 0, 0, 0, 1, 6); +} + +TEST(DecoderModelRationalTest, MultipliesAndDividesWithCrossCancellation) { + Av2DmRational value; + Av2DmRational result; + ASSERT_TRUE(av2_dm_rational_make(UINT64_MAX, UINT64_MAX - 1, &value)); + ASSERT_TRUE(av2_dm_rational_multiply_u64(&value, UINT64_MAX - 1, &result)); + ExpectRational(result, 0, 0, 0, UINT64_MAX, 1); + + ASSERT_TRUE(av2_dm_rational_make(UINT64_MAX, 3, &value)); + ASSERT_TRUE(av2_dm_rational_divide_u64(&value, UINT64_MAX, &result)); + ExpectRational(result, 0, 0, 0, 1, 3); + + ASSERT_TRUE(av2_dm_rational_make(UINT64_MAX, 1, &value)); + ASSERT_TRUE(av2_dm_rational_multiply_u64(&value, UINT64_MAX, &result)); + ExpectRational(result, 0, 0, UINT64_MAX - 1, 1, 1); +} + +TEST(DecoderModelRationalTest, RetainsDenominatorsWiderThan64Bits) { + Av2DmRational left; + Av2DmRational right; + Av2DmRational result; + ASSERT_TRUE(av2_dm_rational_make(1, UINT64_MAX, &left)); + ASSERT_TRUE(av2_dm_rational_make(1, UINT64_MAX - 1, &right)); + // Golden result generated with Python fractions.Fraction: + // Fraction(1, 2**64 - 1) + Fraction(1, 2**64 - 2). + ASSERT_TRUE(av2_dm_rational_add(&left, &right, &result)); + + EXPECT_EQ(result.magnitude.limbs[0], UINT64_MAX - 2); + EXPECT_EQ(result.magnitude.limbs[1], 1u); + EXPECT_EQ(result.magnitude.limbs[2], 0u); + EXPECT_EQ(result.magnitude.limbs[3], 0u); + EXPECT_EQ(result.denominator.limbs[0], 2u); + EXPECT_EQ(result.denominator.limbs[1], UINT64_MAX - 2); + EXPECT_EQ(result.denominator.limbs[2], 0u); + EXPECT_EQ(result.denominator.limbs[3], 0u); + + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&result, &left, &comparison)); + EXPECT_EQ(comparison, 1); +} + +TEST(DecoderModelRationalTest, ComparesMaximumWideValuesAtExactBoundary) { + const Av2DmUnsignedWide maximum = + MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX); + const Av2DmUnsignedWide one_less = + MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX - 1); + Av2DmRational left; + Av2DmRational equal; + Av2DmRational lower; + ASSERT_TRUE(av2_dm_rational_make_wide(maximum, UINT64_MAX, false, &left)); + ASSERT_TRUE(av2_dm_rational_make_wide(maximum, UINT64_MAX, false, &equal)); + ASSERT_TRUE(av2_dm_rational_make_wide(one_less, UINT64_MAX, false, &lower)); + int comparison = 7; + ASSERT_TRUE(av2_dm_rational_compare(&left, &equal, &comparison)); + EXPECT_EQ(comparison, 0); + ASSERT_TRUE(av2_dm_rational_compare(&lower, &left, &comparison)); + EXPECT_EQ(comparison, -1); + ASSERT_TRUE(av2_dm_rational_compare(&left, &lower, &comparison)); + EXPECT_EQ(comparison, 1); +} + +TEST(DecoderModelRationalTest, DetectsMagnitudeAndDenominatorOverflow) { + Av2DmRational maximum; + Av2DmRational one; + Av2DmRational result; + ASSERT_TRUE(av2_dm_rational_make_wide( + MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX), 1, false, + &maximum)); + ASSERT_TRUE(av2_dm_rational_make(1, 1, &one)); + EXPECT_FALSE(av2_dm_rational_add(&maximum, &one, &result)); + EXPECT_FALSE(av2_dm_rational_multiply_u64(&maximum, 2, &result)); + + ASSERT_TRUE(av2_dm_rational_make(1, 1, &maximum)); + maximum.denominator = + MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX); + EXPECT_FALSE(av2_dm_rational_divide_u64(&maximum, 2, &result)); +} + +TEST(DecoderModelRationalTest, RebasesLongRunningTimelineExactly) { + Av2DmRational values[3]; + Av2DmRational origin; + const Av2DmUnsignedWide long_time = MakeWide(0, 0, UINT64_C(1) << 20, 12345); + ASSERT_TRUE(av2_dm_rational_make_wide(long_time, 90000, false, &origin)); + values[0] = origin; + Av2DmRational increment; + ASSERT_TRUE(av2_dm_rational_make(1, 60000, &increment)); + ASSERT_TRUE(av2_dm_rational_add(&origin, &increment, &values[1])); + ASSERT_TRUE(av2_dm_rational_add(&values[1], &increment, &values[2])); + ASSERT_TRUE(av2_dm_rational_rebase(values, 3, &origin)); + ExpectRational(values[0], 0, 0, 0, 0, 1); + ExpectRational(values[1], 0, 0, 0, 1, 60000); + ExpectRational(values[2], 0, 0, 0, 1, 30000); +} + +TEST(DecoderModelRationalTest, RebaseCopiesAliasedOrigin) { + Av2DmRational values[2]; + ASSERT_TRUE(av2_dm_rational_make(10, 1, &values[0])); + ASSERT_TRUE(av2_dm_rational_make(11, 1, &values[1])); + ASSERT_TRUE(av2_dm_rational_rebase(values, 2, &values[0])); + ExpectRational(values[0], 0, 0, 0, 0, 1); + ExpectRational(values[1], 0, 0, 0, 1, 1); +} + +TEST(DecoderModelRationalTest, RebaseFailureIsAtomic) { + Av2DmRational values[2]; + ASSERT_TRUE(av2_dm_rational_make(1, 1, &values[0])); + ASSERT_TRUE(av2_dm_rational_make_wide( + MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX), 1, false, + &values[1])); + Av2DmRational origin; + ASSERT_TRUE( + av2_dm_rational_make_wide(MakeWide(0, 0, 0, 1), 1, true, &origin)); + const Av2DmRational original_values[2] = { values[0], values[1] }; + + EXPECT_FALSE(av2_dm_rational_rebase(values, 2, &origin)); + EXPECT_EQ(memcmp(values, original_values, sizeof(values)), 0); +} + +TEST(DecoderModelBufferPoolTest, InitializesEightAndSixteenReferencePools) { + Av2DmBufferPool pool; + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); + EXPECT_EQ(pool.pool_size, 10u); + EXPECT_EQ(av2_dm_buffer_pool_get_free_buffer(&pool), 0); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); + for (uint32_t i = 0; i < 8; ++i) EXPECT_EQ(pool.vbi[i], -1); + + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 16)); + EXPECT_EQ(pool.pool_size, 18u); + for (uint32_t i = 0; i < 16; ++i) EXPECT_EQ(pool.vbi[i], -1); + EXPECT_FALSE(av2_dm_buffer_pool_initialize(&pool, 0)); + EXPECT_FALSE(av2_dm_buffer_pool_initialize(&pool, 17)); +} + +TEST(DecoderModelBufferPoolTest, ReferenceSlotsMaintainExactCounts) { + Av2DmBufferPool pool; + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); + ASSERT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 0, 3)); + ASSERT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 1, 3)); + EXPECT_EQ(pool.buffers[3].decoder_ref_count, 2u); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 1u); + ASSERT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 0, 4)); + EXPECT_EQ(pool.buffers[3].decoder_ref_count, 1u); + EXPECT_EQ(pool.buffers[4].decoder_ref_count, 1u); + ASSERT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 1, -1)); + EXPECT_EQ(pool.buffers[3].decoder_ref_count, 0u); + EXPECT_FALSE(pool.buffers[3].generation_valid); +} + +TEST(DecoderModelBufferPoolTest, DetectsFullPoolAndCountUnderflow) { + Av2DmBufferPool pool; + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 16)); + for (uint32_t i = 0; i < pool.pool_size; ++i) { + ASSERT_TRUE(av2_dm_buffer_pool_add_player_ref(&pool, i)); + } + EXPECT_EQ(av2_dm_buffer_pool_get_free_buffer(&pool), -1); + EXPECT_FALSE(av2_dm_buffer_pool_remove_decoder_ref(&pool, 0)); + EXPECT_FALSE(av2_dm_buffer_pool_release(&pool, 0)); + ASSERT_TRUE(av2_dm_buffer_pool_remove_player_ref(&pool, 17)); + EXPECT_EQ(av2_dm_buffer_pool_get_free_buffer(&pool), 17); +} + +TEST(DecoderModelBufferPoolTest, RejectsInvalidIndicesWithoutMutation) { + Av2DmBufferPool pool; + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); + EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 8, 0)); + EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 0, 10)); + EXPECT_FALSE(av2_dm_buffer_pool_add_decoder_ref(&pool, 10)); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); +} + +struct ViolationCollector { + std::vector violations; +}; + +void CollectViolation(void *opaque, const Av2DmViolation *violation) { + static_cast(opaque)->violations.push_back(*violation); +} + +bool HasViolation(const ViolationCollector &collector, + Av2DmViolationCode code) { + for (const Av2DmViolation &violation : collector.violations) { + if (violation.code == code) return true; + } + return false; +} + +size_t CountViolations(const ViolationCollector &collector, + Av2DmViolationCode code) { + size_t count = 0; + for (const Av2DmViolation &violation : collector.violations) { + if (violation.code == code) ++count; + } + return count; +} + +const Av2DmViolation *FindViolation(const ViolationCollector &collector, + Av2DmViolationCode code) { + for (const Av2DmViolation &violation : collector.violations) { + if (violation.code == code) return &violation; + } + return nullptr; +} + +bool EqualRational(const Av2DmRational &left, const Av2DmRational &right) { + int comparison; + return av2_dm_rational_compare(&left, &right, &comparison) && comparison == 0; +} + +void ExpectSameViolationMultiset(const ViolationCollector &online, + const ViolationCollector &deferred) { + ASSERT_EQ(online.violations.size(), deferred.violations.size()); + std::vector matched(deferred.violations.size(), false); + for (const Av2DmViolation &expected : online.violations) { + bool found = false; + for (size_t i = 0; i < deferred.violations.size(); ++i) { + const Av2DmViolation &candidate = deferred.violations[i]; + if (matched[i] || candidate.code != expected.code || + candidate.observed_present != expected.observed_present || + candidate.limit_present != expected.limit_present || + (expected.observed_present && + !EqualRational(candidate.observed, expected.observed)) || + (expected.limit_present && + !EqualRational(candidate.limit, expected.limit))) { + continue; + } + matched[i] = true; + found = true; + break; + } + EXPECT_TRUE(found) << "Missing deferred violation for code " + << expected.code; + } +} + +void ExpectSameResult(const Av2DecoderModel *online, + const Av2DecoderModel *deferred) { + Av2DmResult online_result; + Av2DmResult deferred_result; + ASSERT_TRUE(av2_decoder_model_get_result(online, &online_result)); + ASSERT_TRUE(av2_decoder_model_get_result(deferred, &deferred_result)); + EXPECT_EQ(online_result.applicability, deferred_result.applicability); + EXPECT_EQ(online_result.status, deferred_result.status); + EXPECT_EQ(online_result.violations, deferred_result.violations); +} + +Av2DmConfig MakeModelConfig(Av2DmMode mode) { + Av2DmConfig config = {}; + config.mode = mode; + config.applicability = AV2_DM_APPLICABLE; + config.level_idx = 2; + config.profile = 0; + config.num_ref_frames = 8; + config.max_frame_width = 64; + config.max_frame_height = 64; + config.explicit_num_ref_frames = false; + config.timing_info_present = true; + config.num_units_in_display_tick = 1; + config.time_scale = 90000; + config.num_units_in_decoding_tick = 1; + config.equal_picture_interval = true; + config.ticks_per_picture = 3000; + config.initial_display_delay = 2; + config.sequence_parameters_present = true; + config.sequence_decoder_buffer_delay = 9000; + config.sequence_encoder_buffer_delay = 9000; + config.level_limits_present = true; + config.level_limits.max_picture_size = 1000000; + config.level_limits.max_horizontal_size = 2000; + config.level_limits.max_vertical_size = 2000; + config.level_limits.max_display_rate = 1000000000; + config.level_limits.max_decode_rate = 1000000; + config.level_limits.max_header_rate = 1000; + config.level_limits.max_tiles = 512; + config.level_limits.max_tile_columns = 64; + config.level_limits.max_tile_width = 16384; + config.level_limits.max_tile_area = 100000000; + config.level_limits.max_tile_size_header_rate_product = UINT64_MAX; + config.level_limits.picture_size_profile_factor = 15; + config.level_limits.min_compression_basis = 2; + EXPECT_TRUE(av2_dm_rational_make(1000000, 1, &config.level_limits.bit_rate)); + EXPECT_TRUE( + av2_dm_rational_make(1000000, 1, &config.level_limits.buffer_size)); + return config; +} + +Av2DmFrameEvent MakeFrame(uint64_t index, uint64_t generation, + uint32_t removal_ticks = 0) { + Av2DmFrameEvent event = {}; + event.event_index = index; + event.temporal_unit_index = index; + event.generation = generation; + event.ref_valid_mask = UINT32_MAX; + event.coded_bits = 1000; + event.random_access_point = index == 0; + event.coded_as_closed_loop_key = index == 0; + event.frame_is_intra = index == 0; + event.frame_width = 64; + event.frame_height = 64; + event.num_tiles = 1; + event.tile_columns = 1; + event.max_tile_width = 64; + event.max_tile_area = 4096; + event.non_rightmost_tile_width_valid = true; + event.buffer_removal_time_present = true; + event.buffer_removal_time = removal_ticks; + event.count_frame_header = true; + event.compressed_size_bytes = 128; + return event; +} + +Av2DmReferenceUpdateEvent Refresh(uint32_t flags, uint32_t valid) { + Av2DmReferenceUpdateEvent event = {}; + event.refresh_frame_flags = flags; + event.ref_valid_mask = valid; + return event; +} + +Av2DmOutputEvent Output(uint64_t index, uint64_t generation, int map_index) { + Av2DmOutputEvent event = {}; + event.event_index = index; + event.temporal_unit_index = index; + event.generation = generation; + event.frame_to_show_map_idx = map_index; + event.ref_valid_mask = UINT32_MAX; + event.output_luma_samples = 4096; + return event; +} + +void ExpectEqualRational(const Av2DmRational &actual, uint64_t numerator, + uint64_t denominator) { + Av2DmRational expected; + ASSERT_TRUE(av2_dm_rational_make(numerator, denominator, &expected)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&actual, &expected, &comparison)); + EXPECT_EQ(comparison, 0); +} + +TEST(DecoderModelProcessTest, ResourceModeUsesDefaultInitialRemoval) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + const Av2DmFrameEvent frame = MakeFrame(0, 10); + av2_decoder_model_start_frame(model, &frame); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 7, 9); + ExpectEqualRational(state.time_to_decode, 64 * 64, 1000000); + Av2DmRational expected_completion; + ASSERT_TRUE(av2_dm_rational_add(&state.scheduled_removal, + &state.time_to_decode, &expected_completion)); + int comparison; + ASSERT_TRUE( + av2_dm_rational_compare(&state.time, &expected_completion, &comparison)); + EXPECT_EQ(comparison, 0); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, ResourceModeDoesNotRequireDecodingClock) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.num_units_in_decoding_tick = 0; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, ScheduleModeFallsBackToSequenceParameters) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.scope.whole_xlayer = false; + config.operating_point_parameters_present = false; + config.sequence_decoder_buffer_delay = 9000; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + const Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 1, 10); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, ExplicitOperatingPointParametersTakePrecedence) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.scope.whole_xlayer = false; + config.operating_point_parameters_present = true; + config.operating_point_decoder_buffer_delay = 18000; + config.operating_point_encoder_buffer_delay = 9000; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + const Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 1, 5); + av2_decoder_model_destroy(model); + + config.scope.whole_xlayer = true; + model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + av2_decoder_model_start_frame(model, &frame); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 1, 10); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, LowDelayDefersWithoutUnderflowViolation) { + for (const bool low_delay : { false, true }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.time_scale = 10; + config.num_units_in_decoding_tick = 1; + config.sequence_low_delay_mode = low_delay; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE( + av2_dm_rational_make(1000, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.coded_bits = 150; + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW), + !low_delay); + if (low_delay) { + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.removal, 1, 5); + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelProcessTest, OlkInvalidationMirrorsAllInvalidSlots) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 10); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh01 = Refresh(3, 3); + av2_decoder_model_update_reference_buffers(model, &refresh01); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[1]); + const int old_buffer = state.buffer_pool.vbi[0]; + ASSERT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 2u); + av2_decoder_model_invalidate_olk_reference_buffers(model, 1); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.vbi[0], old_buffer); + EXPECT_EQ(state.buffer_pool.vbi[1], -1); + EXPECT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + FrameStartSynchronizesNonOlkInvalidationBeforeBufferAllocation) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + uint32_t valid_mask = 0; + for (uint32_t i = 0; i < 8; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + av2_decoder_model_start_frame(model, &frame); + valid_mask |= 1u << i; + const Av2DmReferenceUpdateEvent refresh = Refresh(1u << i, valid_mask); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + + Av2DmFrameEvent ninth = MakeFrame(8, 9); + av2_decoder_model_start_frame(model, &ninth); + Av2DmOutputEvent first_old_output = Output(100, 1, 0); + av2_decoder_model_output_frame(model, &first_old_output); + Av2DmReferenceUpdateEvent ninth_refresh = Refresh(1, valid_mask); + av2_decoder_model_update_reference_buffers(model, &ninth_refresh); + + Av2DmFrameEvent tenth = MakeFrame(9, 10); + av2_decoder_model_start_frame(model, &tenth); + Av2DmOutputEvent second_old_output = Output(101, 2, 1); + av2_decoder_model_output_frame(model, &second_old_output); + Av2DmReferenceUpdateEvent tenth_refresh = Refresh(2, valid_mask); + av2_decoder_model_update_reference_buffers(model, &tenth_refresh); + + Av2DmState before; + ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); + ASSERT_EQ(av2_dm_buffer_pool_frames_in_use(&before.buffer_pool), 10u); + const int32_t invalidated_buffer = before.buffer_pool.vbi[7]; + ASSERT_GE(invalidated_buffer, 0); + + Av2DmFrameEvent eleventh = MakeFrame(10, 11); + eleventh.ref_valid_mask = valid_mask & ~(1u << 7); + av2_decoder_model_start_frame(model, &eleventh); + + Av2DmState after; + ASSERT_TRUE(av2_decoder_model_get_state(model, &after)); + EXPECT_EQ(after.buffer_pool.vbi[7], -1); + EXPECT_EQ(after.current_buffer_index, invalidated_buffer); + EXPECT_FALSE(HasViolation(collector, + AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + RasSeedsSharedLongTermGenerationOrIsIndeterminate) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.ras_start = true; + config.ras_seed_complete = true; + config.ras_seed_count = 2; + config.ras_seeds[0] = { 0, 77 }; + config.ras_seeds[1] = { 3, 77 }; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[3]); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 1u); + const int buffer = state.buffer_pool.vbi[0]; + ASSERT_GE(buffer, 0); + EXPECT_EQ(state.buffer_pool.buffers[buffer].decoder_ref_count, 2u); + av2_decoder_model_destroy(model); + + config.ras_seed_complete = false; + model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_INDETERMINATE); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 10); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh0 = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh0); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(0, 10, -1); + av2_decoder_model_output_frame(model, &first_output); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_FALSE(state.last_presentation_valid); + + Av2DmFrameEvent second = MakeFrame(1, 11); + av2_decoder_model_start_frame(model, &second); + const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &refresh1); + av2_decoder_model_set_initial_presentation_delay(model, 0); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.initial_presentation_delay_known); + ASSERT_TRUE(state.last_presentation_valid); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&state.last_presentation, + &state.initial_presentation_delay, + &comparison)); + EXPECT_EQ(comparison, 0); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_DECODE_DEADLINE)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + InitialDelayReportsConsolidatedWorstOutputAtReferenceUpdateEvent) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 2; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(1, 1); + // Make the first generation take longer to decode than the generation which + // later establishes the initial presentation delay. + first.random_access_point = true; + first.coded_as_closed_loop_key = true; + first.frame_is_intra = true; + first.frame_width = 640; + first.frame_height = 640; + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &first_refresh); + av2_decoder_model_set_initial_presentation_delay(model, 2); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_FALSE(state.initial_presentation_delay_known); + + Av2DmOutputEvent first_output = Output(10, 1, 0); + first_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &first_output); + Av2DmOutputEvent second_output = Output(11, 1, 0); + second_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &second_output); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_DECODE_DEADLINE)); + + // Continue after an intentionally non-conformant same-removal schedule. The + // resulting backward lane time makes the reference-update event the first + // point where both historical output occurrences can be proven late. + Av2DmFrameEvent second = MakeFrame(20, 2, 0); + av2_decoder_model_start_frame(model, &second); + const Av2DmReferenceUpdateEvent second_refresh = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &second_refresh); + av2_decoder_model_set_initial_presentation_delay(model, 99); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.initial_presentation_delay_known); + + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE), + 1u); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DECODE_DEADLINE), 1u); + for (const Av2DmViolation &violation : collector.violations) { + if (violation.code == AV2_DM_VIOLATION_DISPLAY_FRAME_LATE || + violation.code == AV2_DM_VIOLATION_DECODE_DEADLINE) { + EXPECT_EQ(violation.event_index, 99u); + } + } + av2_decoder_model_set_initial_presentation_delay(model, 100); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE), + 1u); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DECODE_DEADLINE), 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, DeadlineUsesDecodedGenerationIdentity) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 100); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh0 = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh0); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmFrameEvent second = MakeFrame(1, 200, 9000); + av2_decoder_model_start_frame(model, &second); + const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &refresh1); + Av2DmOutputEvent output = Output(2, 200, -1); + av2_decoder_model_output_frame(model, &output); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_DECODE_DEADLINE)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, EmptyShowExistingBufferIsReported) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmOutputEvent output = Output(0, 1, 0); + output.ref_valid_mask = 1; + av2_decoder_model_output_frame(model, &output); + EXPECT_TRUE(HasViolation( + collector, AV2_DM_VIOLATION_DECODE_EXISTING_FRAME_BUFFER_EMPTY)); + const Av2DmViolation *const violation = FindViolation( + collector, AV2_DM_VIOLATION_DECODE_EXISTING_FRAME_BUFFER_EMPTY); + ASSERT_NE(violation, nullptr); + ASSERT_EQ(violation->detail.kind, AV2_DM_VIOLATION_DETAIL_REFERENCE_SLOT); + EXPECT_EQ(violation->affected_kind, AV2_DM_VIOLATION_AFFECTED_OUTPUT); + EXPECT_EQ(violation->detail.value.reference_slot.requested_slot, 0); + EXPECT_TRUE(violation->detail.value.reference_slot.slot_in_range); + EXPECT_TRUE(violation->detail.value.reference_slot.reference_valid); + EXPECT_EQ(violation->detail.value.reference_slot.buffer_index, -1); + EXPECT_EQ(violation->detail.value.reference_slot.pool.free_buffers, + violation->detail.value.reference_slot.pool.pool_size); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, PeriodicRebasePreservesExactTimeline) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + config.rebase_interval_events = 3; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.time, 0, 1); + ExpectEqualRational(state.decode_completion, 0, 1); + Av2DmOutputEvent output = Output(3, 1, 0); + av2_decoder_model_output_frame(model, &output); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.last_presentation_valid); + ExpectEqualRational(state.last_presentation, 0, 1); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, RebaseKeepsScheduleAndResourceLaneOriginShared) { + for (const uint32_t removal_ticks : { 1799u, 1800u, 1801u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + config.rebase_interval_events = 4; + config.level_limits.max_decode_rate = 409600; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + + Av2DmFrameEvent delayed = MakeFrame(1, 2, 9000); + av2_decoder_model_start_frame(model, &delayed); + Av2DmFrameEvent boundary = MakeFrame(2, 3, removal_ticks); + av2_decoder_model_start_frame(model, &boundary); + + EXPECT_EQ(HasViolation(collector, + AV2_DM_VIOLATION_SCHEDULE_BEFORE_RESOURCE_REMOVAL), + removal_ticks < 1800); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelStorageTest, DirectWarningsDiscardPayloadAndStayBounded) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint64_t i = 0; i < 112; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, (uint32_t)(i * 3000)); + frame.temporal_unit_index = 0; + frame.frame_width = config.level_limits.max_horizontal_size + 1; + av2_decoder_model_start_frame(model, &frame); + } + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_HORIZONTAL_SIZE), + 112u); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_GE(result.violations, 112u); + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LE(storage.high_water_dfgs, 16u); + EXPECT_EQ(storage.high_water_outputs, 0u); + EXPECT_EQ(storage.high_water_tus, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelStorageTest, ProvenSmoothingOverflowReleasesFullnessHistory) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(10, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 200; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, (uint64_t)i + 1, 90000 - (i & 1)); + frame.coded_bits = 1; + if (i == 199) { + frame.frame_width = config.level_limits.max_horizontal_size + 1; + } + av2_decoder_model_start_frame(model, &frame); + } + ASSERT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_HORIZONTAL_SIZE)); + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LE(storage.active_dfgs, 1u); + EXPECT_LE(storage.high_water_dfgs, 16u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelStorageTest, NonIncreasingOutputTimesRestartRateHistory) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.level_limits.max_header_rate = 1; + config.level_limits.max_tile_size_header_rate_product = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 200; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, (uint64_t)i + 1); + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, i); + Av2DmOutputEvent output = Output(1000 + i, (uint64_t)i + 1, -1); + output.temporal_unit_index = i; + av2_decoder_model_output_frame(model, &output); + } + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE)); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE)); + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LE(storage.active_tus, 4u); + EXPECT_LE(storage.high_water_tus, 6u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelStorageTest, UnoutputDeadGenerationsRetireUnresolvedTus) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 200; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, (uint64_t)i + 1, 70000 + i * 9000); + av2_decoder_model_start_frame(model, &frame); + } + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LE(storage.active_tus, 2u); + EXPECT_LE(storage.high_water_tus, 2u); + + av2_decoder_model_finish(model); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_TRUE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelStorageTest, TimedTusRetireWithoutOutputCallbacks) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 200; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, (uint64_t)i + 1); + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(i, 30, &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + } + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LE(storage.active_tus, 33u); + EXPECT_LE(storage.high_water_tus, 33u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelStorageTest, EmptyTusDoNotRequireOutputTiming) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 4; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, (uint64_t)i + 1); + frame.count_frame_header = false; + av2_decoder_model_start_frame(model, &frame); + } + av2_decoder_model_finish(model); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelStorageTest, OneHourFixedRateTracesRemainBounded) { + struct Trace { + uint32_t frames; + uint32_t frames_per_second; + }; + for (const Trace trace : { Trace{ 108000, 30 }, Trace{ 216000, 60 } }) { + SCOPED_TRACE(trace.frames_per_second); + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + config.ticks_per_picture = 90000 / trace.frames_per_second; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < trace.frames; ++i) { + Av2DmFrameEvent frame = + MakeFrame(i, (uint64_t)i + 1, i * config.ticks_per_picture); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, i); + Av2DmOutputEvent output = + Output((uint64_t)trace.frames + i, (uint64_t)i + 1, -1); + output.temporal_unit_index = i; + av2_decoder_model_output_frame(model, &output); + ASSERT_TRUE(collector.violations.empty()) << "frame " << i; + } + + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LE(storage.high_water_dfgs, 16u); + EXPECT_EQ(storage.high_water_outputs, 0u); + EXPECT_LE(storage.high_water_tus, trace.frames_per_second + 3); + EXPECT_LE(storage.high_water_generations, + (uint32_t)AV2_DM_MAX_BUFFER_POOL_SIZE); + EXPECT_EQ(storage.high_water_cvs, 1u); + EXPECT_EQ(storage.high_water_rap_runs, 1u); + + av2_decoder_model_finish(model); + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_EQ(storage.active_dfgs, 0u); + EXPECT_EQ(storage.active_outputs, 0u); + EXPECT_EQ(storage.active_tus, 0u); + EXPECT_EQ(storage.active_generations, 0u); + EXPECT_EQ(storage.active_cvs, 0u); + EXPECT_EQ(storage.active_rap_runs, 0u); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelProcessTest, ScheduleModeReportsUnavailableDecodeBuffer) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.equal_picture_interval = false; + config.initial_display_delay = 8; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + for (uint32_t i = 0; i < 8; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + } + Av2DmOutputEvent rap_output = Output(99, 1, 0); + rap_output.presentation_time_present = true; + av2_decoder_model_output_frame(model, &rap_output); + for (uint32_t i = 8; i < 10; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + av2_decoder_model_start_frame(model, &frame); + Av2DmOutputEvent output = Output(100 + i, i + 1, -1); + output.temporal_unit_index = i; + output.presentation_time_present = true; + output.presentation_time_ticks = 900000 + i; + av2_decoder_model_output_frame(model, &output); + } + Av2DmFrameEvent blocked = MakeFrame(10, 11, 90000); + av2_decoder_model_start_frame(model, &blocked); + EXPECT_TRUE(HasViolation(collector, + AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE)); + const Av2DmViolation *const violation = FindViolation( + collector, AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE); + ASSERT_NE(violation, nullptr); + ASSERT_EQ(violation->detail.kind, AV2_DM_VIOLATION_DETAIL_BUFFER_POOL); + EXPECT_EQ(violation->detail.value.buffer_pool.free_buffers, 0u); + EXPECT_EQ(violation->detail.value.buffer_pool.frames_in_use, + violation->detail.value.buffer_pool.pool_size); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, ReorderedOutputsAreCountedByGeneration) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 10); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh0 = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh0); + Av2DmFrameEvent second = MakeFrame(1, 20, 9000); + av2_decoder_model_start_frame(model, &second); + const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &refresh1); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent second_output = Output(2, 20, 1); + av2_decoder_model_output_frame(model, &second_output); + Av2DmOutputEvent first_output = Output(3, 10, 0); + av2_decoder_model_output_frame(model, &first_output); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.reordered_outputs, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, RetiredDfgGenerationMetadataSurvivesUntilOutput) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + for (uint32_t i = 1; i <= 20; ++i) { + Av2DmFrameEvent filler = MakeFrame(i, (uint64_t)i + 1, i * 9000); + av2_decoder_model_start_frame(model, &filler); + } + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_LT(storage.active_dfgs, 20u); + + Av2DmOutputEvent latest = Output(100, 21, -1); + latest.temporal_unit_index = 20; + av2_decoder_model_output_frame(model, &latest); + Av2DmOutputEvent oldest = Output(101, 1, 0); + oldest.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &oldest); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.reordered_outputs, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + FixedRatePresentationFollowsReorderedOwnerTusExactly) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 4; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + const uint64_t decode_tus[] = { 0, 5, 4, 6 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + frame.temporal_unit_index = decode_tus[i]; + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + av2_decoder_model_set_initial_presentation_delay(model, 0); + + const uint64_t owner_tus[] = { 0, 4, 5, 6 }; + const uint64_t generations[] = { 1, 3, 2, 4 }; + const int map_indices[] = { 0, 2, 1, 3 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmOutputEvent output = Output(10 + i, generations[i], map_indices[i]); + output.temporal_unit_index = owner_tus[i]; + av2_decoder_model_output_frame(model, &output); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.last_presentation_offset_valid); + ExpectEqualRational(state.last_presentation_offset, i, 30); + ASSERT_TRUE(state.last_output_temporal_unit_valid); + EXPECT_EQ(state.last_output_temporal_unit, owner_tus[i]); + } + + av2_decoder_model_finish(model); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_FALSE(result.missing_required_input); + EXPECT_EQ(result.output_frames, 4u); + EXPECT_EQ(result.reordered_outputs, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + FixedRateSameOwnerTuSharesTimeAndAccumulatesSamples) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 3; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + const uint64_t decode_tus[] = { 0, 0, 1 }; + for (uint32_t i = 0; i < 3; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + frame.temporal_unit_index = decode_tus[i]; + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + av2_decoder_model_set_initial_presentation_delay(model, 0); + + const uint64_t owner_tus[] = { 0, 0, 1 }; + for (uint32_t i = 0; i < 3; ++i) { + Av2DmOutputEvent output = Output(10 + i, i + 1, i); + output.temporal_unit_index = owner_tus[i]; + av2_decoder_model_output_frame(model, &output); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.last_presentation_offset, i == 2 ? 1 : 0, + i == 2 ? 30 : 1); + if (i == 1) { + ASSERT_TRUE(state.last_temporal_unit_output_time_valid); + ExpectEqualRational(state.last_temporal_unit_output_time, 0, 1); + EXPECT_EQ(state.last_temporal_unit_output_frames, 2u); + EXPECT_EQ(state.last_temporal_unit_output_luma_samples, 8192u); + } + } + + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_FALSE(result.missing_required_input); + EXPECT_EQ(result.output_frames, 3u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, VariableRatePresentationUsesRapBasesExactly) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.equal_picture_interval = false; + config.initial_display_delay = 4; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 4; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + if (i == 2) { + frame.random_access_point = true; + frame.coded_as_closed_loop_key = true; + frame.frame_is_intra = true; + } + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + av2_decoder_model_set_initial_presentation_delay(model, 0); + + const uint64_t presentation_ticks[] = { 0, 2, 30, 3 }; + const uint64_t expected_numerators[] = { 0, 1, 1, 11 }; + const uint64_t expected_denominators[] = { 1, 45000, 3000, 30000 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmOutputEvent output = Output(10 + i, i + 1, i); + output.presentation_time_present = true; + output.presentation_time_ticks = presentation_ticks[i]; + av2_decoder_model_output_frame(model, &output); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.last_presentation_offset_valid); + ExpectEqualRational(state.last_presentation_offset, expected_numerators[i], + expected_denominators[i]); + } + + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_FALSE(result.missing_required_input); + EXPECT_EQ(result.output_frames, 4u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + MissingVariableRatePresentationTimingIsIndeterminate) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.equal_picture_interval = false; + config.initial_display_delay = 1; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent output = Output(1, 1, 0); + output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &output); + + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_TRUE(result.missing_required_input); + EXPECT_EQ(result.violations, 0u); + EXPECT_EQ(result.output_frames, 0u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, ExplicitTemporalUnitOutputTimeIsPreserved) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(7, 3, &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent output = Output(1, 1, 0); + output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &output); + + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.last_temporal_unit_output_time_valid); + ExpectEqualRational(state.last_temporal_unit_output_time, 7, 3); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, LongStreamRebasingPreservesDecisions) { + Av2DmResult results[2]; + for (uint32_t run = 0; run < 2; ++run) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + config.rebase_interval_events = run == 0 ? UINT32_MAX : 32; + Av2DecoderModel *model = + av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + for (uint32_t i = 0; i < 256; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + av2_decoder_model_start_frame(model, &frame); + if (i == 0) { + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + } + } + ASSERT_TRUE(av2_decoder_model_get_result(model, &results[run])); + EXPECT_FALSE(results[run].arithmetic_failed); + EXPECT_EQ(results[run].decoded_frames, 256u); + av2_decoder_model_destroy(model); + } + EXPECT_EQ(results[0].status, results[1].status); + EXPECT_EQ(results[0].violations, results[1].violations); +} + +TEST(DecoderModelConformanceTest, AnnexALevelFactorsAreExact) { + Av2DmLevelLimits limits; + ASSERT_TRUE(av2_dm_get_level_limits(4, 0, 3, &limits)); + ExpectEqualRational(limits.bit_rate, 20004000, 1); + EXPECT_EQ(limits.picture_size_profile_factor, 20u); + EXPECT_EQ(limits.max_tile_width, 4096u); + EXPECT_EQ(limits.max_tile_area, 4096u * 2304u); + ASSERT_TRUE(av2_dm_get_level_limits(4, 1, 4, &limits)); + ExpectEqualRational(limits.bit_rate, 75000000, 1); + EXPECT_EQ(limits.picture_size_profile_factor, 30u); + EXPECT_FALSE(av2_dm_get_level_limits(0, 1, 0, &limits)); + ASSERT_TRUE(av2_dm_get_level_limits(21, 1, 0, &limits)); + EXPECT_EQ(limits.max_decode_rate, UINT64_C(75296145408)); + EXPECT_EQ(limits.max_tile_width, 16384u); +} + +TEST(DecoderModelConformanceTest, AnnexAProfileLevelFactorsAreExact) { + const uint32_t picture_factor[] = { 15, 15, 15, 20, 30, 36 }; + const uint32_t bitrate_numerator[] = { 1, 1, 1, 1667, 5, 3 }; + const uint32_t bitrate_denominator[] = { 1, 1, 1, 1000, 2, 1 }; + for (uint32_t profile = 0; profile < 6; ++profile) { + AV2ProfileLevelFactors factors; + ASSERT_TRUE(av2_get_profile_level_factors(profile, &factors)); + EXPECT_EQ(factors.picture_size_profile_factor, picture_factor[profile]); + EXPECT_EQ(factors.bitrate_factor_numerator, bitrate_numerator[profile]); + EXPECT_EQ(factors.bitrate_factor_denominator, bitrate_denominator[profile]); + } + + AV2ProfileLevelFactors factors; + EXPECT_FALSE(av2_get_profile_level_factors(-1, &factors)); + EXPECT_FALSE(av2_get_profile_level_factors(6, &factors)); + EXPECT_FALSE(av2_get_profile_level_factors(CONFIGURABLE, &factors)); + EXPECT_FALSE(av2_get_profile_level_factors(0, nullptr)); +} + +TEST(DecoderModelConformanceTest, AnnexACompressedSizeObuMembershipIsExact) { + static_assert(NUM_OBU_TYPES == 32, "OBU types must cover all 5-bit values"); + bool expected[NUM_OBU_TYPES] = {}; + const OBU_TYPE counted_types[] = { + OBU_CLOSED_LOOP_KEY, + OBU_OPEN_LOOP_KEY, + OBU_LEADING_TILE_GROUP, + OBU_REGULAR_TILE_GROUP, + OBU_METADATA_SHORT, + OBU_METADATA_GROUP, + OBU_SWITCH, + OBU_LEADING_SEF, + OBU_REGULAR_SEF, + OBU_LEADING_TIP, + OBU_REGULAR_TIP, + OBU_BRIDGE_FRAME, + OBU_RAS_FRAME, + }; + for (const OBU_TYPE type : counted_types) expected[type] = true; + for (int type = 0; type < NUM_OBU_TYPES; ++type) { + EXPECT_EQ( + av2_obu_counts_toward_compressed_size(static_cast(type)), + expected[type]) + << "obu_type=" << type; + } +} + +TEST(DecoderModelConformanceTest, AnnexAProfile5FactorsAreExact) { + Av2DmLevelLimits limits; + ASSERT_TRUE(av2_dm_get_level_limits(4, 0, 5, &limits)); + ExpectEqualRational(limits.bit_rate, 36000000, 1); + ExpectEqualRational(limits.buffer_size, 36000000, 1); + EXPECT_EQ(limits.picture_size_profile_factor, 36u); + + ASSERT_TRUE(av2_dm_get_level_limits(4, 1, 5, &limits)); + ExpectEqualRational(limits.bit_rate, 90000000, 1); + ExpectEqualRational(limits.buffer_size, 90000000, 1); + EXPECT_EQ(limits.picture_size_profile_factor, 36u); + + EXPECT_FALSE(av2_dm_get_level_limits(4, 0, 6, &limits)); + + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits_present = false; + config.level_idx = 4; + config.profile = 5; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, AnnexATablesMatchSpecification) { + struct LevelRow { + uint64_t max_picture_size; + uint32_t max_dimension; + uint64_t max_display_rate; + uint64_t max_decode_rate; + uint32_t max_header_rate; + uint32_t main_kbps; + uint32_t high_kbps; + uint32_t main_cr; + uint32_t high_cr; + uint32_t max_tiles; + uint32_t max_tile_columns; + }; + const LevelRow rows[] = { + { 147456, 640, 4423680, 5529600, 150, 1500, 0, 2, 0, 8, 4 }, + { 278784, 880, 8363520, 10454400, 150, 3000, 0, 2, 0, 8, 4 }, + { 665856, 1360, 19975680, 24969600, 150, 6000, 0, 2, 0, 16, 6 }, + { 1065024, 1720, 31950720, 39938400, 150, 10000, 0, 2, 0, 16, 6 }, + { 2359296, 2560, 70778880, 77856768, 300, 12000, 30000, 4, 4, 32, 8 }, + { 2359296, 2560, 141557760, 155713536, 300, 20000, 50000, 4, 4, 32, 8 }, + { 8912896, 4975, 267386880, 273715200, 300, 30000, 100000, 6, 4, 64, 8 }, + { 8912896, 4975, 534773760, 547430400, 300, 40000, 160000, 8, 4, 64, 8 }, + { 8912896, 4975, 1069547520, 1094860800, 300, 60000, 240000, 8, 4, 64, 8 }, + { 8912896, 4975, 1069547520, 1176502272, 300, 60000, 240000, 8, 4, 64, 8 }, + { 35651584, 9951, 1069547520, 1176502272, 300, 60000, 240000, 8, 4, 128, + 16 }, + { 35651584, 9951, 2139095040, 2189721600, 300, 100000, 480000, 8, 4, 128, + 16 }, + { 35651584, 9951, 4278190080, 4379443200, 300, 160000, 800000, 8, 4, 128, + 16 }, + { 35651584, 9951, 4278190080, 4706009088, 300, 160000, 800000, 8, 4, 128, + 16 }, + { 142606336, 19902, 4278190080, 4706009088, 960, 160000, 800000, 8, 4, 256, + 32 }, + { 142606336, 19902, 8556380160, 8758886400, 960, 200000, 960000, 8, 4, 256, + 32 }, + { 142606336, 19902, 17112760320, 17517772800, 960, 320000, 1600000, 8, 4, + 256, 32 }, + { 142606336, 19902, 17112760320, 18824036352, 960, 320000, 1600000, 8, 4, + 256, 32 }, + { 530841600, 38400, 17112760320, 18824036352, 960, 320000, 1600000, 8, 4, + 512, 64 }, + { 530841600, 38400, 34225520640, 34910031052, 960, 400000, 1920000, 8, 4, + 512, 64 }, + { 530841600, 38400, 68451041280, 69820062105, 960, 640000, 3200000, 8, 4, + 512, 64 }, + { 530841600, 38400, 68451041280, 75296145408, 960, 640000, 3200000, 8, 4, + 512, 64 }, + }; + const uint32_t tile_width_scale[2][22] = { + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8 }, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 8, 8, 8, 16, 16, 16, 16 }, + }; + const uint32_t tile_area_scale[2] + [22] = { + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 8, 8, 8, 8, 16, 16, 16, 16 }, + { 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 16, 16, 16, 16, 32, 32, 32, 32 }, + }; + const uint32_t profile_factor_numerator[] = { 1, 1, 1, 1667, 5, 3 }; + const uint32_t profile_factor_denominator[] = { 1, 1, 1, 1000, 2, 1 }; + const uint32_t picture_factor[] = { 15, 15, 15, 20, 30, 36 }; + + static_assert(sizeof(rows) / sizeof(rows[0]) == 22, "Annex A level rows"); + for (uint32_t level = 0; level < 22; ++level) { + EXPECT_EQ(av2_level_defs[level].max_picture_size, + static_cast(rows[level].max_picture_size)); + EXPECT_EQ(av2_level_defs[level].max_h_size, + static_cast(rows[level].max_dimension)); + EXPECT_EQ(av2_level_defs[level].max_v_size, + static_cast(rows[level].max_dimension)); + EXPECT_EQ(av2_level_defs[level].max_display_rate, + static_cast(rows[level].max_display_rate)); + EXPECT_EQ(av2_level_defs[level].max_decode_rate, + static_cast(rows[level].max_decode_rate)); + EXPECT_EQ(av2_level_defs[level].max_header_rate, + static_cast(rows[level].max_header_rate)); + EXPECT_EQ(av2_level_defs[level].max_tiles, + static_cast(rows[level].max_tiles)); + EXPECT_EQ(av2_level_defs[level].max_tile_cols, + static_cast(rows[level].max_tile_columns)); + for (uint32_t tier = 0; tier < 2; ++tier) { + const uint32_t kbps = + tier == 0 ? rows[level].main_kbps : rows[level].high_kbps; + const uint32_t cr = tier == 0 ? rows[level].main_cr : rows[level].high_cr; + uint32_t common_kbps = 0; + uint32_t common_cr = 0; + EXPECT_EQ( + av2_get_level_base_bitrate_kbps(static_cast(level), + static_cast(tier), &common_kbps), + kbps != 0); + EXPECT_EQ( + av2_get_level_compression_basis(static_cast(level), + static_cast(tier), &common_cr), + cr != 0); + if (kbps != 0) EXPECT_EQ(common_kbps, kbps); + if (cr != 0) EXPECT_EQ(common_cr, cr); + EXPECT_EQ(av2_tile_width_scaling_factor[tier][level], + static_cast(tile_width_scale[tier][level])); + EXPECT_EQ(av2_tile_area_scaling_factor[tier][level], + static_cast(tile_area_scale[tier][level])); + for (uint32_t profile = 0; profile <= 5; ++profile) { + Av2DmLevelLimits limits; + if (kbps == 0 || cr == 0) { + EXPECT_FALSE(av2_dm_get_level_limits(level, tier, profile, &limits)); + continue; + } + ASSERT_TRUE(av2_dm_get_level_limits(level, tier, profile, &limits)); + EXPECT_EQ(limits.max_picture_size, rows[level].max_picture_size); + EXPECT_EQ(limits.max_horizontal_size, rows[level].max_dimension); + EXPECT_EQ(limits.max_vertical_size, rows[level].max_dimension); + EXPECT_EQ(limits.max_display_rate, rows[level].max_display_rate); + EXPECT_EQ(limits.max_decode_rate, rows[level].max_decode_rate); + EXPECT_EQ(limits.max_header_rate, rows[level].max_header_rate); + EXPECT_EQ(limits.max_tiles, rows[level].max_tiles); + EXPECT_EQ(limits.max_tile_columns, rows[level].max_tile_columns); + EXPECT_EQ(limits.max_tile_width, + tile_width_scale[tier][level] * 4096u / 4u); + EXPECT_EQ(limits.max_tile_area, + static_cast(tile_area_scale[tier][level]) * 4096u * + 2304u / 4u); + EXPECT_EQ(limits.max_tile_size_header_rate_product, + static_cast(tile_area_scale[tier][level]) * + 547430400u / 4u); + EXPECT_EQ(limits.picture_size_profile_factor, picture_factor[profile]); + EXPECT_EQ(limits.min_compression_basis, cr); + ExpectEqualRational(limits.bit_rate, + static_cast(kbps) * 1000u * + profile_factor_numerator[profile], + profile_factor_denominator[profile]); + EXPECT_TRUE(EqualRational(limits.buffer_size, limits.bit_rate)); + } + } + } + + Av2DmLevelLimits limits; + for (uint32_t level = 22; level <= 31; ++level) { + EXPECT_FALSE(av2_dm_get_level_limits(level, 0, 0, &limits)); + } + EXPECT_FALSE(av2_dm_get_level_limits(0, 2, 0, &limits)); + EXPECT_FALSE(av2_dm_get_level_limits(0, 0, 6, &limits)); + EXPECT_FALSE(av2_dm_get_level_limits(0, 0, 0, nullptr)); + uint32_t value; + EXPECT_FALSE(av2_get_level_base_bitrate_kbps(-1, 0, &value)); + EXPECT_FALSE(av2_get_level_base_bitrate_kbps(22, 0, &value)); + EXPECT_FALSE(av2_get_level_base_bitrate_kbps(0, 2, &value)); + EXPECT_FALSE(av2_get_level_base_bitrate_kbps(0, 0, nullptr)); + EXPECT_FALSE(av2_get_level_compression_basis(-1, 0, &value)); + EXPECT_FALSE(av2_get_level_compression_basis(22, 0, &value)); + EXPECT_FALSE(av2_get_level_compression_basis(0, 2, &value)); + EXPECT_FALSE(av2_get_level_compression_basis(0, 0, nullptr)); +} + +TEST(DecoderModelConformanceTest, AnnexASubstreamTablesMatchSpecification) { + struct SubstreamRow { + uint32_t max_picture_size; + uint32_t max_picture_size_x; + uint32_t max_horizontal_size; + uint32_t max_vertical_size; + uint32_t max_tile_columns; + }; + const SubstreamRow rows[5][3] = { + { { 2359296, 1433600, 896, 1600, 7 }, + { 2359296, 552960, 576, 960, 4 }, + { 2359296, 245760, 384, 640, 3 } }, + { { 8912896, 3768320, 1472, 2560, 7 }, + { 8912896, 2088960, 1088, 1920, 4 }, + { 8912896, 983040, 768, 1280, 3 } }, + { { 35651584, 11673600, 2280, 5120, 13 }, + { 35651584, 8355840, 2176, 3840, 8 }, + { 35651584, 3768320, 1472, 2560, 5 } }, + { { 142606336, 58982400, 5760, 10240, 26 }, + { 142606336, 33177600, 4320, 7680, 16 }, + { 142606336, 14745600, 2880, 5120, 11 } }, + { { 530841600, 235929600, 11520, 20480, 52 }, + { 530841600, 132710400, 8640, 15360, 32 }, + { 530841600, 58982400, 5760, 10240, 21 } }, + }; + const uint32_t group_for_level[] = { 0, 0, 1, 1, 1, 1, 2, 2, 2, + 2, 3, 3, 3, 3, 4, 4, 4, 4 }; + const uint32_t scale_numerator[] = { 3, 4, 9 }; + const uint32_t scale_denominator[] = { 2, 1, 1 }; + + for (uint32_t level = 4; level < 22; ++level) { + const uint32_t group = group_for_level[level - 4]; + for (uint32_t scale = 0; scale < 3; ++scale) { + SCOPED_TRACE(::testing::Message() << "level=" << level << " group=" + << group << " scale=" << scale); + AV2SubstreamLevelSpec spec; + ASSERT_TRUE(av2_get_substream_level_spec( + level, scale_numerator[scale], scale_denominator[scale], &spec)); + EXPECT_EQ(spec.max_picture_size, + static_cast(rows[group][scale].max_picture_size)); + EXPECT_EQ(spec.max_picture_size_x, + static_cast(rows[group][scale].max_picture_size_x)); + EXPECT_EQ(spec.max_h_size_x, + static_cast(rows[group][scale].max_horizontal_size)); + EXPECT_EQ(spec.max_v_size_x, + static_cast(rows[group][scale].max_vertical_size)); + EXPECT_EQ(spec.max_tile_cols_x, + static_cast(rows[group][scale].max_tile_columns)); + EXPECT_EQ(spec.max_header_rate_x, 132); + + Av2DmLevelLimits limits; + ASSERT_TRUE(av2_dm_get_level_limits(level, 0, 0, &limits)); + const bool integral_rates = + (limits.max_display_rate * scale_denominator[scale]) % + scale_numerator[scale] == + 0 && + (limits.max_decode_rate * scale_denominator[scale]) % + scale_numerator[scale] == + 0; + ASSERT_EQ( + av2_dm_apply_multistream_limits(level, 0, 0, scale_numerator[scale], + scale_denominator[scale], &limits), + integral_rates); + if (!integral_rates) continue; + EXPECT_EQ(limits.max_picture_size, rows[group][scale].max_picture_size_x); + EXPECT_EQ(limits.max_horizontal_size, + rows[group][scale].max_horizontal_size); + EXPECT_EQ(limits.max_vertical_size, rows[group][scale].max_vertical_size); + EXPECT_EQ(limits.max_header_rate, 132u); + EXPECT_EQ(limits.max_tile_columns, rows[group][scale].max_tile_columns); + } + } + + AV2SubstreamLevelSpec spec; + EXPECT_FALSE(av2_get_substream_level_spec(3, 3, 2, &spec)); + EXPECT_FALSE(av2_get_substream_level_spec(22, 3, 2, &spec)); + EXPECT_FALSE(av2_get_substream_level_spec(4, 1, 1, &spec)); + EXPECT_FALSE(av2_get_substream_level_spec(4, 3, 2, nullptr)); +} + +TEST(DecoderModelConformanceTest, MultistreamFactorsAreAppliedExactly) { + Av2DmLevelLimits limits; + ASSERT_TRUE(av2_dm_get_level_limits(21, 0, 0, &limits)); + EXPECT_FALSE(av2_dm_apply_multistream_limits(3, 0, 0, 3, 2, &limits)); + ASSERT_TRUE(av2_dm_apply_multistream_limits(4, 0, 0, 3, 2, &limits)); + EXPECT_EQ(limits.max_picture_size, 896u * 1600u); + EXPECT_EQ(limits.max_horizontal_size, 896u); + EXPECT_EQ(limits.max_vertical_size, 1600u); + EXPECT_EQ(limits.max_display_rate, 47185920u); + EXPECT_EQ(limits.max_decode_rate, 51904512u); + EXPECT_EQ(limits.max_header_rate, 132u); + EXPECT_EQ(limits.max_tiles, 21u); + EXPECT_EQ(limits.max_tile_columns, 7u); + ExpectEqualRational(limits.bit_rate, 8000000, 1); + EXPECT_FALSE(av2_dm_apply_multistream_limits(4, 0, 0, 2, 1, &limits)); +} + +TEST(DecoderModelConformanceTest, StaticLevelBoundariesAreInclusive) { + const auto run = [](const Av2DmConfig &config, const Av2DmFrameEvent &frame, + Av2DmViolationCode code, bool expected) { + Av2DmConfig isolated_config = config; + // Keep the independent sequence-level reference constraint away from the + // per-frame static boundary under test. + isolated_config.num_ref_frames = 1; + ViolationCollector collector; + Av2DecoderModel *model = av2_decoder_model_create( + &isolated_config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ(CountViolations(collector, code), expected ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), expected ? 1u : 0u); + av2_decoder_model_destroy(model); + }; + + for (const int delta : { -1, 0, 1 }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DmFrameEvent frame = MakeFrame(0, 1); + config.level_limits.max_picture_size = 4096 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_PICTURE_SIZE, delta < 0); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_horizontal_size = 64 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_HORIZONTAL_SIZE, delta < 0); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_vertical_size = 64 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_VERTICAL_SIZE, delta < 0); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + frame.num_tiles = 2; + config.level_limits.max_tiles = 2 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_TILES, delta < 0); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + frame = MakeFrame(0, 1); + frame.num_tiles = 2; + frame.tile_columns = 2; + config.level_limits.max_tile_columns = 2 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_TILE_COLUMNS, delta < 0); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + frame = MakeFrame(0, 1); + config.level_limits.max_tile_width = 64 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_TILE_WIDTH, delta < 0); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_tile_area = 4096 + delta; + run(config, frame, AV2_DM_VIOLATION_MAX_TILE_AREA, delta < 0); + } + + for (const uint32_t dimension : { 15u, 16u, 17u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.frame_width = dimension; + run(config, frame, AV2_DM_VIOLATION_MIN_HORIZONTAL_SIZE, dimension < 16); + + frame = MakeFrame(0, 1); + frame.frame_height = dimension; + run(config, frame, AV2_DM_VIOLATION_MIN_VERTICAL_SIZE, dimension < 16); + } + + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DmFrameEvent frame = MakeFrame(0, 1); + run(config, frame, AV2_DM_VIOLATION_MIN_TILE_WIDTH, false); + frame.non_rightmost_tile_width_valid = false; + run(config, frame, AV2_DM_VIOLATION_MIN_TILE_WIDTH, true); +} + +TEST(DecoderModelConformanceTest, FatalModeStopsAfterFirstViolation) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.stop_after_first_violation = true; + config.level_limits.max_picture_size = 1; + config.level_limits.max_horizontal_size = 1; + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + const Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + av2_decoder_model_finish(model); + + ASSERT_EQ(collector.violations.size(), 1u); + EXPECT_EQ(collector.violations[0].code, AV2_DM_VIOLATION_MAX_PICTURE_SIZE); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); + EXPECT_EQ(result.violations, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, FatalModeStopsOnTerminalOnlyViolation) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + config.defer_nonterminal_checks_for_testing = true; + config.stop_after_first_violation = true; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(100, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.coded_bits = 150; + av2_decoder_model_start_frame(model, &frame); + EXPECT_TRUE(collector.violations.empty()); + + av2_decoder_model_finish(model); + ASSERT_EQ(collector.violations.size(), 1u); + EXPECT_EQ(collector.violations[0].code, + AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); + EXPECT_EQ(result.violations, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, FrameParsingBoundariesAreExact) { + struct Boundary { + Av2DmViolationCode code; + uint64_t below; + uint64_t equal; + uint64_t above; + }; + const Boundary boundaries[] = { + { AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE, 3839, 3840, 3841 }, + { AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS, 28585, 28586, 28587 }, + }; + for (const Boundary &boundary : boundaries) { + for (const uint64_t observed : + { boundary.below, boundary.equal, boundary.above }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.time_scale = 1000000; + config.num_units_in_decoding_tick = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + if (boundary.code == AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE) { + first.compressed_size_bytes = 128 + observed; + } else { + first.frame_symbol_count = observed; + } + av2_decoder_model_start_frame(model, &first); + Av2DmFrameEvent second = MakeFrame(1, 2, 4096); + av2_decoder_model_start_frame(model, &second); + EXPECT_EQ(CountViolations(collector, boundary.code), + observed == boundary.above ? 1u : 0u); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE), + boundary.code == AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE && + observed == boundary.above); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS), + boundary.code == AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS && + observed == boundary.above); + if (observed == boundary.above) { + const Av2DmViolation *const violation = + FindViolation(collector, boundary.code); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->event_index, 1u); + } + av2_decoder_model_destroy(model); + } + } +} + +TEST(DecoderModelConformanceTest, FrameTileRateBoundaryIsExact) { + for (const uint32_t num_tiles : { 1u, 2u, 3u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.time_scale = 180; + config.num_units_in_decoding_tick = 1; + config.level_limits.max_tiles = 3; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.num_tiles = num_tiles; + av2_decoder_model_start_frame(model, &first); + Av2DmFrameEvent second = MakeFrame(1, 2, 1); + av2_decoder_model_start_frame(model, &second); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE), + num_tiles > 2 ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), num_tiles > 2 ? 1u : 0u); + if (num_tiles > 2) { + EXPECT_EQ(collector.violations[0].event_index, 1u); + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, FrameDecodeRateBoundaryIsExact) { + for (const uint32_t removal_ticks : { 4095u, 4096u, 4097u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.time_scale = 1000000; + config.num_units_in_decoding_tick = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + Av2DmFrameEvent second = MakeFrame(1, 2, removal_ticks); + av2_decoder_model_start_frame(model, &second); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE), + removal_ticks < 4096); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_DECODE_TIME), + removal_ticks < 4096); + EXPECT_EQ(HasViolation(collector, + AV2_DM_VIOLATION_SCHEDULE_BEFORE_RESOURCE_REMOVAL), + removal_ticks < 4096); + // These three inequalities share the same exact decode-completion + // boundary for this vector, so crossing it necessarily changes all three. + EXPECT_EQ(collector.violations.size(), removal_ticks < 4096 ? 3u : 0u); + if (removal_ticks < 4096) { + for (const Av2DmViolation &violation : collector.violations) { + EXPECT_EQ(violation.event_index, 1u); + } + const Av2DmViolation *const rate = + FindViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE); + ASSERT_NE(rate, nullptr); + ASSERT_EQ(rate->detail.kind, AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL); + ExpectEqualRational(rate->detail.value.frame_interval, removal_ticks, + 1000000); + EXPECT_EQ(rate->affected_kind, AV2_DM_VIOLATION_AFFECTED_DFG); + EXPECT_EQ(rate->affected_index, 0u); + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, MinimumPresentationIntervalIsExact) { + for (const int rate_delta : { 1, 0, -1 }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.time_scale = 1000; + config.num_units_in_display_tick = 1; + config.ticks_per_picture = 1; + config.initial_display_delay = 1; + config.level_limits.max_display_rate = 4096000 + rate_delta; + config.level_limits.max_decode_rate = 4096000; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(0, 1, 0); + av2_decoder_model_output_frame(model, &first_output); + Av2DmFrameEvent second = MakeFrame(1, 2); + av2_decoder_model_start_frame(model, &second); + Av2DmOutputEvent second_output = Output(1, 2, -1); + av2_decoder_model_output_frame(model, &second_output); + EXPECT_EQ( + HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL), + rate_delta < 0); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE), + rate_delta < 0); + EXPECT_EQ(collector.violations.size(), rate_delta < 0 ? 2u : 0u); + if (rate_delta < 0) { + for (const Av2DmViolation &violation : collector.violations) { + EXPECT_EQ(violation.event_index, 1u); + } + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, DisplayAndDecodeDeadlineBoundaryIsExact) { + for (const uint32_t ticks_per_picture : { 4095u, 4096u, 4097u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.time_scale = 1000000; + config.ticks_per_picture = ticks_per_picture; + config.initial_display_delay = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(10, 1, -1); + first_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &first_output); + + Av2DmFrameEvent second = MakeFrame(1, 2); + av2_decoder_model_start_frame(model, &second); + Av2DmOutputEvent second_output = Output(11, 2, -1); + second_output.temporal_unit_index = 1; + av2_decoder_model_output_frame(model, &second_output); + + const bool late = ticks_per_picture < 4096; + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE), + late ? 1u : 0u); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DECODE_DEADLINE), + late ? 1u : 0u); + // Output and decode completion coincide for this vector, so the two + // normative comparisons cross their common exact boundary together. + EXPECT_EQ(collector.violations.size(), late ? 2u : 0u); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, + OutputDurationAndPresentationIntervalUseSeparateClocks) { + struct TimingCase { + uint32_t time_scale; + uint32_t output_duration_denominator; + bool display_rate_violation; + bool presentation_interval_violation; + }; + const TimingCase cases[] = { + { 10, 20, true, false }, + { 20, 10, false, true }, + }; + for (const TimingCase &timing : cases) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.time_scale = timing.time_scale; + config.ticks_per_picture = 1; + config.initial_display_delay = 1; + config.level_limits.max_display_rate = 40960; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(10, 1, -1); + first_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &first_output); + + Av2DmFrameEvent second = MakeFrame(1, 2); + second.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, timing.output_duration_denominator, + &second.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &second); + Av2DmOutputEvent second_output = Output(11, 2, -1); + second_output.temporal_unit_index = 1; + av2_decoder_model_output_frame(model, &second_output); + + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE), + timing.display_rate_violation); + EXPECT_EQ( + HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL), + timing.presentation_interval_violation); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, + FinishReusesDurationOnlyForLastTuDisplayRate) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.time_scale = 10; + config.ticks_per_picture = 1; + config.initial_display_delay = 1; + config.level_limits.max_display_rate = 40960; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &first_refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(10, 1, 0); + first_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &first_output); + + Av2DmFrameEvent second = MakeFrame(1, 2); + second.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, 5, &second.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &second); + const Av2DmReferenceUpdateEvent second_refresh = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &second_refresh); + Av2DmOutputEvent second_output = Output(11, 2, -1); + second_output.temporal_unit_index = 1; + av2_decoder_model_output_frame(model, &second_output); + Av2DmOutputEvent repeated_output = Output(12, 2, 1); + repeated_output.temporal_unit_index = 1; + repeated_output.ref_valid_mask = 3; + av2_decoder_model_output_frame(model, &repeated_output); + + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL)); + av2_decoder_model_finish(model); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ReorderedFinalTuReusesLastDisplayDurationExactly) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 3; + config.level_limits.max_display_rate = 122880; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + const uint64_t decode_tus[] = { 0, 5, 4 }; + for (uint32_t i = 0; i < 3; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + frame.temporal_unit_index = decode_tus[i]; + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + av2_decoder_model_set_initial_presentation_delay(model, 0); + + const uint64_t owner_tus[] = { 0, 4, 5 }; + const uint64_t generations[] = { 1, 3, 2 }; + const int map_indices[] = { 0, 2, 1 }; + for (uint32_t i = 0; i < 3; ++i) { + Av2DmOutputEvent output = Output(10 + i, generations[i], map_indices[i]); + output.temporal_unit_index = owner_tus[i]; + output.output_luma_samples = i == 2 ? 4097 : 4096; + av2_decoder_model_output_frame(model, &output); + } + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); + + av2_decoder_model_finish(model); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE), 1u); + EXPECT_EQ(collector.violations.size(), 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, DisplayRateBoundaryIsExact) { + for (const uint64_t max_display_rate : + { UINT64_C(40959), UINT64_C(40960), UINT64_C(40961) }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.time_scale = 1; + config.ticks_per_picture = 1; + config.initial_display_delay = 1; + config.level_limits.max_display_rate = max_display_rate; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(10, 1, -1); + first_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &first_output); + Av2DmFrameEvent second = MakeFrame(1, 2); + second.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, 10, &second.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &second); + Av2DmOutputEvent second_output = Output(11, 2, -1); + second_output.temporal_unit_index = 1; + av2_decoder_model_output_frame(model, &second_output); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE), + max_display_rate < 40960 ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), max_display_rate < 40960 ? 1u : 0u); + if (max_display_rate < 40960) { + EXPECT_EQ(collector.violations[0].event_index, 11u); + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, HeaderRateUsesInclusiveOneSecondWindow) { + for (const uint32_t header_count : { 1u, 2u, 3u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_header_rate = 2; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + for (uint32_t i = 0; i < header_count; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + frame.show_existing_frame = true; + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(i, header_count == 1 ? 1 : 2, + &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE), + i == 2); + } + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE), + header_count > 2); + if (header_count > 2) { + ASSERT_EQ(collector.violations.size(), 1u); + EXPECT_EQ(collector.violations[0].event_index, 2u); + } + const size_t online_violations = collector.violations.size(); + av2_decoder_model_finish(model); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE), + header_count > 2); + EXPECT_EQ(collector.violations.size(), online_violations); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, + SameTuHeaderRateUsesLatestCountedHeaderEvent) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_header_rate = 2; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint64_t event_index = 10; event_index <= 12; ++event_index) { + Av2DmFrameEvent frame = MakeFrame(event_index, event_index + 1); + frame.temporal_unit_index = 7; + frame.show_existing_frame = true; + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE), + event_index == 12 ? 1u : 0u); + } + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->event_index, 12u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + GlobalMaximumTileRetainsEachAffectedHeaderWindow) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_header_rate = 100; + config.level_limits.max_tile_size_header_rate_product = 150; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent initial_tile = MakeFrame(1, 1); + initial_tile.count_frame_header = false; + initial_tile.max_tile_area = 50; + av2_decoder_model_start_frame(model, &initial_tile); + for (uint64_t i = 0; i < 3; ++i) { + Av2DmFrameEvent header = MakeFrame(10 + i, 10 + i); + header.temporal_unit_index = 10 + i; + header.show_existing_frame = true; + header.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(i, 2, &header.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &header); + } + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE)); + + Av2DmFrameEvent larger_tile = MakeFrame(20, 20); + larger_tile.count_frame_header = false; + larger_tile.max_tile_area = 100; + av2_decoder_model_start_frame(model, &larger_tile); + ASSERT_EQ(CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + 2u); + for (const Av2DmViolation &violation : collector.violations) { + EXPECT_EQ(violation.event_index, 20u); + } + + Av2DmFrameEvent recheck = MakeFrame(21, 21); + recheck.temporal_unit_index = 20; + recheck.show_existing_frame = true; + recheck.count_frame_header = false; + av2_decoder_model_start_frame(model, &recheck); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + 2u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + RetiredHeaderWindowsProduceOneConsolidatedTileWarning) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + config.ticks_per_picture = 9000; + config.level_limits.max_header_rate = 100; + config.level_limits.max_tile_size_header_rate_product = 200; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + const uint64_t output_time_numerators[] = { 0, 1, 2, 6 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + frame.max_tile_area = 50; + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(output_time_numerators[i], 2, + &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, i); + Av2DmOutputEvent output = Output(10 + i, i + 1, -1); + output.temporal_unit_index = i; + av2_decoder_model_output_frame(model, &output); + } + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE)); + + Av2DmFrameEvent larger_tile = MakeFrame(20, 20, 36000); + larger_tile.count_frame_header = false; + larger_tile.max_tile_area = 100; + av2_decoder_model_start_frame(model, &larger_tile); + ASSERT_EQ(CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + 1u); + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->event_index, 20u); + + larger_tile.event_index = 21; + larger_tile.generation = 21; + larger_tile.max_tile_area = 101; + av2_decoder_model_start_frame(model, &larger_tile); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + RetiredHeaderSummaryDoesNotRepeatProvenTileViolation) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.initial_display_delay = 1; + config.level_limits.max_header_rate = 100; + config.level_limits.max_tile_size_header_rate_product = 150; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + const uint64_t output_time_numerators[] = { 0, 1, 2, 6 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, (uint64_t)i + 1, i * 9000); + frame.count_frame_header = i != 3; + frame.max_tile_area = 100; + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(output_time_numerators[i], 2, + &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, i); + Av2DmOutputEvent output = Output(100 + i, (uint64_t)i + 1, -1); + output.temporal_unit_index = i; + av2_decoder_model_output_frame(model, &output); + } + const size_t directly_reported = + CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE); + ASSERT_GT(directly_reported, 0u); + + Av2DmFrameEvent larger_tile = MakeFrame(20, 20, 36000); + larger_tile.count_frame_header = false; + larger_tile.max_tile_area = 200; + av2_decoder_model_start_frame(model, &larger_tile); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + directly_reported); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ReorderedTuHeaderWindowsUseOutputTimeOrderExactly) { + for (const uint32_t maximum_headers : { 1u, 2u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.time_scale = 5; + config.ticks_per_picture = 3; + config.initial_display_delay = 4; + config.level_limits.max_header_rate = maximum_headers; + config.level_limits.max_tile_size_header_rate_product = + 4096 * maximum_headers; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + const uint64_t decode_tus[] = { 0, 5, 4, 6 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + frame.temporal_unit_index = decode_tus[i]; + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + av2_decoder_model_set_initial_presentation_delay(model, 0); + + const uint64_t owner_tus[] = { 0, 4, 5, 6 }; + const uint64_t generations[] = { 1, 3, 2, 4 }; + const int map_indices[] = { 0, 2, 1, 3 }; + for (uint32_t i = 0; i < 4; ++i) { + Av2DmOutputEvent output = Output(10 + i, generations[i], map_indices[i]); + output.temporal_unit_index = owner_tus[i]; + av2_decoder_model_output_frame(model, &output); + } + const size_t expected = maximum_headers == 1 ? 3u : 0u; + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE), + expected); + EXPECT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + expected); + EXPECT_EQ(collector.violations.size(), 2 * expected); + av2_decoder_model_finish(model); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE), + expected); + EXPECT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + expected); + EXPECT_EQ(collector.violations.size(), 2 * expected); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, TileHeaderRateUsesGlobalMaximumTile) { + for (const uint32_t header_count : { 1u, 2u, 3u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_header_rate = 100; + config.level_limits.max_tile_size_header_rate_product = 8192; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent largest_tile = MakeFrame(100, 100); + largest_tile.temporal_unit_index = 0; + largest_tile.count_frame_header = false; + largest_tile.max_tile_area = 4096; + largest_tile.temporal_unit_output_time_present = true; + ASSERT_TRUE( + av2_dm_rational_make(0, 1, &largest_tile.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &largest_tile); + for (uint32_t i = 0; i < header_count; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + frame.show_existing_frame = true; + frame.max_tile_area = 1; + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(i, header_count == 1 ? 1 : 2, + &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &frame); + } + av2_decoder_model_finish(model); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + header_count > 2); + EXPECT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE), + header_count > 2 ? 1u : 0u); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, ReferenceFrameBoundaryIsExact) { + for (const uint64_t max_picture_size : + { UINT64_C(4095), UINT64_C(4096), UINT64_C(4097) }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_picture_size = max_picture_size; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.frame_width = 16; + frame.frame_height = 16; + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + max_picture_size < 4096 ? 1u : 0u); + if (max_picture_size < 4096) { + ASSERT_EQ(collector.violations.size(), 1u); + EXPECT_EQ(collector.violations[0].event_index, 0u); + } + const size_t online_violations = collector.violations.size(); + av2_decoder_model_finish(model); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + max_picture_size < 4096 ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), online_violations); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MAX_PICTURE_SIZE), + false); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, DecodeCountCanReserveReferenceBuffer) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_picture_size = 4096; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.allow_global_intrabc = true; + frame.inloop_filtering_enabled = true; + frame.coded_as_closed_loop_key = false; + av2_decoder_model_start_frame(model, &frame); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES)); + ASSERT_EQ(collector.violations.size(), 1u); + EXPECT_EQ(collector.violations[0].event_index, 0u); + const size_t online_violations = collector.violations.size(); + av2_decoder_model_finish(model); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES)); + EXPECT_EQ(collector.violations.size(), online_violations); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ReferenceLimitIsNotRepeatedWhenDecodeCountReservesBuffer) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_picture_size = 3584; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + first.frame_width = 16; + first.frame_height = 16; + av2_decoder_model_start_frame(model, &first); + ASSERT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 1u); + EXPECT_EQ(collector.violations[0].event_index, 0u); + + Av2DmFrameEvent second = MakeFrame(1, 2); + second.frame_width = 16; + second.frame_height = 16; + second.allow_global_intrabc = true; + second.inloop_filtering_enabled = true; + second.coded_as_closed_loop_key = false; + av2_decoder_model_start_frame(model, &second); + + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 1u); + av2_decoder_model_finish(model); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, ScheduleDelayZeroAndTooLargeAreReported) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_decoder_buffer_delay = 0; + ASSERT_TRUE(av2_dm_rational_make(1, 100, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + const Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + EXPECT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO)); + av2_decoder_model_destroy(model); + + collector.violations.clear(); + config.sequence_decoder_buffer_delay = 9000; + model = av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + av2_decoder_model_start_frame(model, &frame); + EXPECT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_TOO_LARGE)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, DecoderBufferDelayMaximumIsExact) { + for (const uint32_t decoder_delay : { 8999u, 9000u, 9001u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_decoder_buffer_delay = decoder_delay; + config.sequence_encoder_buffer_delay = 9000; + ASSERT_TRUE(av2_dm_rational_make(90000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE( + av2_dm_rational_make(9000, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.coded_bits = 1000; + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ(CountViolations(collector, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_TOO_LARGE), + decoder_delay > 9000 ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), decoder_delay > 9000 ? 1u : 0u); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, SmoothingOverflowIsCheckedExactly) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + config.time_scale = 10; + config.num_units_in_decoding_tick = 1; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(100, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.coded_bits = 150; + av2_decoder_model_start_frame(model, &frame); + EXPECT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + ASSERT_EQ(collector.violations.size(), 1u); + EXPECT_EQ(collector.violations[0].event_index, 0u); + const size_t online_violations = collector.violations.size(); + av2_decoder_model_finish(model); + EXPECT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + EXPECT_EQ(collector.violations.size(), online_violations); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + SmoothingOverflowRetainsEachAffectedDfgAtProvingEvent) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + ASSERT_TRUE(av2_dm_rational_make(1000000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(1500, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(10, 1); + first.coded_bits = 1000; + av2_decoder_model_start_frame(model, &first); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + + Av2DmFrameEvent second = MakeFrame(11, 2, 9000); + second.coded_bits = 1000; + av2_decoder_model_start_frame(model, &second); + ASSERT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW), + 2u); + for (const Av2DmViolation &violation : collector.violations) { + if (violation.code == AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW) { + EXPECT_EQ(violation.event_index, 11u); + } + } + av2_decoder_model_finish(model); + EXPECT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW), + 2u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ParameterUpdatePartitionsSmoothingButRetainsAdjacentDfgTiming) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(150, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 100; + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + + Av2DmConfig replacement = config; + ASSERT_TRUE( + av2_dm_rational_make(2000, 1, &replacement.level_limits.bit_rate)); + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + Av2DmFrameEvent updated = MakeFrame(1, 2); + updated.coded_bits = 100; + updated.random_access_point = true; + updated.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &updated); + + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + EXPECT_EQ(CountViolations(collector, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT), + 1u); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.first_bit_arrival, 0, 1); + ExpectEqualRational(state.last_bit_arrival, 1, 20); + ASSERT_NE(state.buffer_pool.vbi[0], -1); + EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ParameterUpdateToResourceModeUsesContinuousResourceLane) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.scope.whole_xlayer = false; + config.sequence_parameters_present = false; + config.operating_point_parameters_present = true; + config.operating_point_decoder_buffer_delay = 9000; + config.operating_point_encoder_buffer_delay = 9000; + config.sequence_low_delay_mode = true; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + + Av2DmConfig replacement = config; + replacement.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + replacement.operating_point_parameters_present = false; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + Av2DmFrameEvent updated = MakeFrame(1, 2); + updated.random_access_point = true; + updated.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &updated); + + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.frame_number, 2u); + ASSERT_NE(state.buffer_pool.vbi[0], -1); + EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.mode, AV2_DM_RESOURCE_AVAILABILITY_MODE); + EXPECT_FALSE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ParameterUpdateKeepsAffectedDfgLimitsWithPreviousFrame) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + Av2DmConfig replacement = config; + replacement.level_limits.max_decode_rate = 100000; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + + Av2DmFrameEvent updated = MakeFrame(1, 2, 900); + updated.random_access_point = true; + updated.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &updated); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE), 0u); + + Av2DmFrameEvent next = MakeFrame(2, 3, 900); + av2_decoder_model_start_frame(model, &next); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE), 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ParameterUpdateFromResourceToSchedulePreservesDpb) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + + Av2DmConfig replacement = config; + replacement.mode = AV2_DM_DECODING_SCHEDULE_MODE; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + Av2DmFrameEvent updated = MakeFrame(1, 2, 9000); + updated.random_access_point = true; + updated.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &updated); + + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.frame_number, 2u); + ASSERT_NE(state.buffer_pool.vbi[0], -1); + EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.mode, AV2_DM_DECODING_SCHEDULE_MODE); + EXPECT_FALSE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, ParameterUpdateRejectsImmutableClockChange) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.time_scale += 1; + EXPECT_FALSE(av2_decoder_model_update_parameters(model, &replacement, 1)); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_TRUE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, SmoothingBoundariesAreInclusive) { + for (const uint64_t coded_bits : + { UINT64_C(99), UINT64_C(100), UINT64_C(101) }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + config.level_limits.buffer_size = config.level_limits.bit_rate; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.coded_bits = coded_bits; + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW), + coded_bits > 100); + EXPECT_EQ(collector.violations.size(), coded_bits > 100 ? 1u : 0u); + av2_decoder_model_destroy(model); + } + for (const uint64_t buffer_bits : + { UINT64_C(151), UINT64_C(150), UINT64_C(149) }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + config.time_scale = 10; + config.num_units_in_decoding_tick = 1; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE( + av2_dm_rational_make(buffer_bits, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.coded_bits = 150; + av2_decoder_model_start_frame(model, &frame); + EXPECT_EQ( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW), + buffer_bits < 150); + const size_t online_violations = collector.violations.size(); + av2_decoder_model_finish(model); + EXPECT_EQ( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW), + buffer_bits < 150); + EXPECT_EQ(collector.violations.size(), buffer_bits < 150 ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), online_violations); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, OnlineAndDeferredSmoothingResultsMatch) { + Av2DmConfig online_config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + online_config.sequence_low_delay_mode = true; + online_config.time_scale = 10; + online_config.num_units_in_decoding_tick = 1; + ASSERT_TRUE( + av2_dm_rational_make(1000, 1, &online_config.level_limits.bit_rate)); + ASSERT_TRUE( + av2_dm_rational_make(100, 1, &online_config.level_limits.buffer_size)); + Av2DmConfig deferred_config = online_config; + deferred_config.defer_nonterminal_checks_for_testing = true; + ViolationCollector online_collector; + ViolationCollector deferred_collector; + Av2DecoderModel *online = av2_decoder_model_create( + &online_config, CollectViolation, &online_collector); + Av2DecoderModel *deferred = av2_decoder_model_create( + &deferred_config, CollectViolation, &deferred_collector); + ASSERT_NE(online, nullptr); + ASSERT_NE(deferred, nullptr); + + Av2DmFrameEvent frame = MakeFrame(7, 1); + frame.coded_bits = 150; + av2_decoder_model_start_frame(online, &frame); + av2_decoder_model_start_frame(deferred, &frame); + EXPECT_TRUE(HasViolation(online_collector, + AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + EXPECT_TRUE(deferred_collector.violations.empty()); + av2_decoder_model_finish(online); + av2_decoder_model_finish(deferred); + ExpectSameViolationMultiset(online_collector, deferred_collector); + ExpectSameResult(online, deferred); + av2_decoder_model_destroy(online); + av2_decoder_model_destroy(deferred); +} + +TEST(DecoderModelConformanceTest, OnlineAndDeferredReferenceResultsMatch) { + Av2DmConfig online_config = + MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + online_config.level_limits.max_picture_size = 4095; + Av2DmConfig deferred_config = online_config; + deferred_config.defer_nonterminal_checks_for_testing = true; + ViolationCollector online_collector; + ViolationCollector deferred_collector; + Av2DecoderModel *online = av2_decoder_model_create( + &online_config, CollectViolation, &online_collector); + Av2DecoderModel *deferred = av2_decoder_model_create( + &deferred_config, CollectViolation, &deferred_collector); + ASSERT_NE(online, nullptr); + ASSERT_NE(deferred, nullptr); + + Av2DmFrameEvent frame = MakeFrame(7, 1); + frame.frame_width = 16; + frame.frame_height = 16; + av2_decoder_model_start_frame(online, &frame); + av2_decoder_model_start_frame(deferred, &frame); + EXPECT_TRUE( + HasViolation(online_collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES)); + EXPECT_TRUE(deferred_collector.violations.empty()); + av2_decoder_model_finish(online); + av2_decoder_model_finish(deferred); + ExpectSameViolationMultiset(online_collector, deferred_collector); + ExpectSameResult(online, deferred); + av2_decoder_model_destroy(online); + av2_decoder_model_destroy(deferred); +} + +TEST(DecoderModelConformanceTest, OnlineAndDeferredHeaderResultsMatch) { + Av2DmConfig online_config = + MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + online_config.level_limits.max_header_rate = 2; + online_config.level_limits.max_tile_size_header_rate_product = 8192; + Av2DmConfig deferred_config = online_config; + deferred_config.defer_nonterminal_checks_for_testing = true; + ViolationCollector online_collector; + ViolationCollector deferred_collector; + Av2DecoderModel *online = av2_decoder_model_create( + &online_config, CollectViolation, &online_collector); + Av2DecoderModel *deferred = av2_decoder_model_create( + &deferred_config, CollectViolation, &deferred_collector); + ASSERT_NE(online, nullptr); + ASSERT_NE(deferred, nullptr); + + Av2DmFrameEvent initial_tile = MakeFrame(1, 1); + initial_tile.count_frame_header = false; + initial_tile.temporal_unit_output_time_present = true; + ASSERT_TRUE( + av2_dm_rational_make(0, 1, &initial_tile.temporal_unit_output_time)); + av2_decoder_model_start_frame(online, &initial_tile); + av2_decoder_model_start_frame(deferred, &initial_tile); + + for (uint64_t event_index = 10; event_index <= 12; ++event_index) { + Av2DmFrameEvent frame = MakeFrame(event_index, event_index + 1); + frame.temporal_unit_index = 7; + frame.show_existing_frame = true; + frame.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &frame.temporal_unit_output_time)); + av2_decoder_model_start_frame(online, &frame); + av2_decoder_model_start_frame(deferred, &frame); + } + EXPECT_EQ(online_collector.violations.size(), 2u); + EXPECT_TRUE(deferred_collector.violations.empty()); + av2_decoder_model_finish(online); + av2_decoder_model_finish(deferred); + ExpectSameViolationMultiset(online_collector, deferred_collector); + ExpectSameResult(online, deferred); + av2_decoder_model_destroy(online); + av2_decoder_model_destroy(deferred); +} + +TEST(DecoderModelConformanceTest, RapDelayConsistencyUsesCeiling) { + for (const uint64_t coded_bits : + { UINT64_C(8999), UINT64_C(9000), UINT64_C(9001) }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + ASSERT_TRUE(av2_dm_rational_make(90000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE( + av2_dm_rational_make(90000, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = coded_bits; + av2_decoder_model_start_frame(model, &first); + Av2DmFrameEvent second = MakeFrame(1, 2, 9000); + second.random_access_point = true; + second.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &second); + EXPECT_EQ(HasViolation(collector, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT), + coded_bits > 9000); + EXPECT_EQ(collector.violations.size(), coded_bits > 9000 ? 1u : 0u); + if (coded_bits > 9000) { + const Av2DmViolation *const violation = FindViolation( + collector, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT); + ASSERT_NE(violation, nullptr); + ASSERT_EQ(violation->detail.kind, + AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY); + EXPECT_EQ( + violation->detail.value.delay_consistency.decoder_buffer_delay_ticks, + 9000u); + EXPECT_TRUE( + violation->detail.value.delay_consistency.ceil_time_delta_present); + ExpectEqualRational( + violation->detail.value.delay_consistency.ceil_time_delta_ticks, 8999, + 1); + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, MinimumDecodeTimeUsesExactBoundary) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + Av2DmFrameEvent second = MakeFrame(1, 2, 1); + av2_decoder_model_start_frame(model, &second); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_DECODE_TIME)); + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_MINIMUM_DECODE_TIME); + ASSERT_NE(violation, nullptr); + ASSERT_EQ(violation->detail.kind, + AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME); + EXPECT_EQ(violation->event_index, 1u); + EXPECT_EQ(violation->affected_kind, AV2_DM_VIOLATION_AFFECTED_DFG); + EXPECT_EQ(violation->affected_index, 0u); + ExpectEqualRational( + violation->detail.value.minimum_decode_time.frame_decode_time, 64, 15625); + ExpectEqualRational( + violation->detail.value.minimum_decode_time.one_header_time, 1, 1000); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, VariablePresentationMustNotDecrease) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.equal_picture_interval = false; + config.initial_display_delay = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first = Output(1, 1, 0); + first.presentation_time_present = true; + av2_decoder_model_output_frame(model, &first); + + Av2DmFrameEvent second_frame = MakeFrame(2, 2, 9000); + av2_decoder_model_start_frame(model, &second_frame); + Av2DmOutputEvent second = Output(3, 2, -1); + second.presentation_time_present = true; + second.presentation_time_ticks = 10; + av2_decoder_model_output_frame(model, &second); + + Av2DmFrameEvent next_rap = MakeFrame(4, 3, 18000); + next_rap.random_access_point = true; + next_rap.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &next_rap); + Av2DmFrameEvent leading = MakeFrame(5, 4, 1000); + av2_decoder_model_start_frame(model, &leading); + Av2DmOutputEvent decreased = Output(6, 4, -1); + decreased.leading_frame = true; + decreased.presentation_time_present = true; + decreased.presentation_time_ticks = 9; + av2_decoder_model_output_frame(model, &decreased); + EXPECT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_PRESENTATION_TIME_DECREASE)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ShowExistingPresentationUsesCurrentRapAndTemporalPoint) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.equal_picture_interval = false; + config.initial_display_delay = 1; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first_rap = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first_rap); + const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &first_refresh); + av2_decoder_model_set_initial_presentation_delay(model, 0); + Av2DmOutputEvent first_output = Output(1, 1, -1); + first_output.presentation_uses_current_frame = true; + first_output.presentation_random_access_point = true; + first_output.presentation_time_present = true; + av2_decoder_model_output_frame(model, &first_output); + + Av2DmFrameEvent second_rap = MakeFrame(2, 2, 90000); + second_rap.random_access_point = true; + second_rap.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &second_rap); + const Av2DmReferenceUpdateEvent second_refresh = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &second_refresh); + Av2DmOutputEvent second_output = Output(3, 2, -1); + second_output.presentation_uses_current_frame = true; + second_output.presentation_random_access_point = true; + second_output.presentation_time_present = true; + second_output.presentation_time_ticks = 90000; + av2_decoder_model_output_frame(model, &second_output); + + Av2DmFrameEvent show_existing = MakeFrame(4, 1, 180000); + show_existing.show_existing_frame = true; + show_existing.random_access_point = false; + av2_decoder_model_start_frame(model, &show_existing); + Av2DmOutputEvent repeated_output = Output(5, 1, 0); + repeated_output.presentation_uses_current_frame = true; + repeated_output.presentation_time_present = true; + repeated_output.presentation_time_ticks = 90000; + av2_decoder_model_output_frame(model, &repeated_output); + + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_PRESENTATION_TIME_DECREASE)); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, PresentationNonDecreaseBoundaryIsExact) { + for (const uint32_t presentation_ticks : { 89999u, 90000u, 90001u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.equal_picture_interval = false; + config.initial_display_delay = 3; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint32_t i = 0; i < 3; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, i * 9000); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + } + av2_decoder_model_set_initial_presentation_delay(model, 0); + + Av2DmOutputEvent first = Output(10, 1, 0); + first.temporal_unit_index = 0; + first.presentation_time_present = true; + av2_decoder_model_output_frame(model, &first); + Av2DmOutputEvent second = Output(11, 2, 1); + second.temporal_unit_index = 1; + second.presentation_time_present = true; + second.presentation_time_ticks = 90000; + av2_decoder_model_output_frame(model, &second); + Av2DmOutputEvent boundary = Output(12, 3, 2); + boundary.temporal_unit_index = 1; + boundary.presentation_time_present = true; + boundary.presentation_time_ticks = presentation_ticks; + av2_decoder_model_output_frame(model, &boundary); + + EXPECT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_PRESENTATION_TIME_DECREASE), + presentation_ticks < 90000 ? 1u : 0u); + EXPECT_EQ(collector.violations.size(), + presentation_ticks < 90000 ? 1u : 0u); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, MissingInputsAndMaximumLevelAreDistinct) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_parameters_present = false; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_INDETERMINATE); + av2_decoder_model_destroy(model); + + config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_idx = 31; + model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_NOT_APPLICABLE); + av2_decoder_model_finish(model); + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_TRUE(result.finished); + EXPECT_EQ(result.status, AV2_DM_RESULT_NOT_APPLICABLE); + av2_decoder_model_destroy(model); +} + +} // namespace diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc index 2bfea6a269..6c91d6b16c 100644 --- a/test/encode_api_test.cc +++ b/test/encode_api_test.cc @@ -72,6 +72,15 @@ TEST(EncodeAPI, InvalidControlId) { EXPECT_EQ(AVM_CODEC_OK, avm_codec_destroy(&enc)); } +TEST(EncodeAPI, ReservedProfileIsRejected) { + avm_codec_iface_t *iface = avm_codec_av2_cx(); + avm_codec_ctx_t enc; + avm_codec_enc_cfg_t cfg; + ASSERT_EQ(AVM_CODEC_OK, avm_codec_enc_config_default(iface, &cfg, 0)); + cfg.g_profile = RESERVED_PROFILES_START; + EXPECT_EQ(AVM_CODEC_INVALID_PARAM, avm_codec_enc_init(&enc, iface, &cfg, 0)); +} + TEST(EncodeAPI, EncodeOddWidthHeight420) { constexpr unsigned int kWidth = 9; constexpr unsigned kHeight = 9; diff --git a/test/encoder_decoder_model_test.cc b/test/encoder_decoder_model_test.cc new file mode 100644 index 0000000000..4e2cbb8026 --- /dev/null +++ b/test/encoder_decoder_model_test.cc @@ -0,0 +1,1306 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "av2/common/decoder_model.h" +extern "C" { +#include "av2/common/level.h" +#include "av2/encoder/encoder.h" +} +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +namespace { + +constexpr int kNumRefs = 4; +constexpr uint64_t kOutputSamples = 64 * 64; + +struct OracleGeneration { + uint64_t id; + uint64_t output_order; + uint64_t order_hint; + uint64_t temporal_unit; + bool implicit_output_eligible; + bool restricted; + bool output_done; + uint32_t player_ref_count; +}; + +// Independent executable model of the output processes in Section 7. This +// owns no codec object and deliberately does not use an encoder-model helper. +class Section7Oracle { + public: + Section7Oracle() { ref_slots_.fill(0); } + + void AddGeneration(uint64_t id, uint64_t output_order, uint64_t order_hint, + uint64_t temporal_unit, + bool implicit_output_eligible = true) { + generations_.push_back({ id, output_order, order_hint, temporal_unit, + implicit_output_eligible, false, false, 0 }); + } + + void AssignSlot(int ref_index, uint64_t generation) { + ASSERT_GE(ref_index, 0); + ASSERT_LT(ref_index, kNumRefs); + ASSERT_NE(Find(generation), nullptr); + ref_slots_[ref_index] = generation; + } + + void SetCurrent(uint64_t generation) { + ASSERT_NE(Find(generation), nullptr); + current_generation_ = generation; + } + + void OutputCurrent() { + OracleGeneration *const current = Find(current_generation_); + ASSERT_NE(current, nullptr); + if (current->output_done) return; + OutputAround(current_generation_, true); + } + + void ShowExisting(int ref_index) { + ASSERT_GE(ref_index, 0); + ASSERT_LT(ref_index, kNumRefs); + ASSERT_NE(ref_slots_[ref_index], 0u); + OutputAround(ref_slots_[ref_index], false); + } + + void Refresh(const std::vector &ref_indices) { + uint32_t refresh_mask = 0; + for (const int ref_index : ref_indices) { + ASSERT_GE(ref_index, 0); + ASSERT_LT(ref_index, kNumRefs); + refresh_mask |= 1u << ref_index; + } + for (int ref_index = 0; ref_index < kNumRefs; ++ref_index) { + if (((refresh_mask >> ref_index) & 1) == 0) continue; + const uint64_t displaced = ref_slots_[ref_index]; + if (displaced != 0 && IsEligible(displaced)) { + OutputAround(displaced, true); + } + ref_slots_[ref_index] = current_generation_; + } + OutputCurrent(); + } + + void Restrict(const std::vector &ref_indices) { + for (const int ref_index : ref_indices) { + ASSERT_GE(ref_index, 0); + ASSERT_LT(ref_index, kNumRefs); + const uint64_t generation = ref_slots_[ref_index]; + if (generation == 0) continue; + if (IsEligible(generation)) OutputAround(generation, true); + Find(generation)->restricted = true; + } + } + + void Flush(bool olk_limited, uint64_t olk_order_hint = 0) { + while (true) { + uint64_t selected = 0; + for (int ref_index = 0; ref_index < kNumRefs; ++ref_index) { + const uint64_t generation = ref_slots_[ref_index]; + if (generation == 0) continue; + const OracleGeneration *const candidate = Find(generation); + if (!IsEligible(generation) || + (olk_limited && candidate->order_hint >= olk_order_hint)) { + continue; + } + if (selected == 0 || + candidate->output_order <= Find(selected)->output_order) { + selected = generation; + } + } + if (selected == 0) return; + Emit(selected, true); + } + } + + const std::vector &outputs() const { return outputs_; } + + const OracleGeneration *generation(uint64_t id) const { return Find(id); } + + private: + OracleGeneration *Find(uint64_t id) { + for (OracleGeneration &generation : generations_) { + if (generation.id == id) return &generation; + } + return nullptr; + } + + const OracleGeneration *Find(uint64_t id) const { + for (const OracleGeneration &generation : generations_) { + if (generation.id == id) return &generation; + } + return nullptr; + } + + bool IsEligible(uint64_t id) const { + const OracleGeneration *const generation = Find(id); + return generation != nullptr && generation->implicit_output_eligible && + !generation->restricted && !generation->output_done; + } + + std::vector UniqueSlotGenerations() const { + std::vector result; + for (const uint64_t generation : ref_slots_) { + if (generation != 0 && + std::find(result.begin(), result.end(), generation) == result.end()) { + result.push_back(generation); + } + } + return result; + } + + void Emit(uint64_t id, bool completes_implicit_output) { + OracleGeneration *const generation = Find(id); + ASSERT_NE(generation, nullptr); + outputs_.push_back(id); + ++generation->player_ref_count; + if (completes_implicit_output) generation->output_done = true; + } + + void OutputAround(uint64_t trigger_id, bool completes_implicit_output) { + const OracleGeneration *const trigger = Find(trigger_id); + ASSERT_NE(trigger, nullptr); + const uint64_t trigger_order = trigger->output_order; + + while (true) { + uint64_t preceding = 0; + for (const uint64_t generation : UniqueSlotGenerations()) { + const OracleGeneration *const candidate = Find(generation); + if (!IsEligible(generation) || + candidate->output_order >= trigger_order) { + continue; + } + if (preceding == 0 || + candidate->output_order < Find(preceding)->output_order) { + preceding = generation; + } + } + if (preceding == 0) break; + Emit(preceding, true); + } + + Emit(trigger_id, completes_implicit_output); + + for (uint64_t distance = 1; distance <= kNumRefs; ++distance) { + const uint64_t target_order = trigger_order + distance; + bool found = false; + for (const uint64_t generation : UniqueSlotGenerations()) { + const OracleGeneration *const candidate = Find(generation); + if (IsEligible(generation) && candidate->output_order == target_order) { + Emit(generation, true); + found = true; + } + } + if (!found) break; + } + } + + std::vector generations_; + std::array ref_slots_; + uint64_t current_generation_ = 0; + std::vector outputs_; +}; + +class EncoderModelHarness { + public: + EncoderModelHarness() : cpi_(new AV2_COMP()) { + AV2_COMMON *const cm = &cpi_->common; + cm->seq_params.operating_points_cnt_minus_1 = 0; + cm->seq_params.operating_point_idc[0] = 0; + cm->seq_params.ref_frames = kNumRefs; + cm->seq_params.seq_profile_idc = MAIN_420_10_IP0; + cm->seq_params.max_frame_width = 64; + cm->seq_params.max_frame_height = 64; + cm->ci_params_encoder.ci_timing_info_present_flag = 1; + cm->ci_params_encoder.timing_info.num_units_in_display_tick = 1; + cm->ci_params_encoder.timing_info.time_scale = 90000; + cm->ci_params_encoder.timing_info.equal_elemental_interval = 1; + cm->ci_params_encoder.timing_info.num_ticks_per_elemental_duration = 3000; + cm->cur_frame = ¤t_; + cpi_->framerate = 30.0; + cpi_->level_params.keep_level_stats = 1; + cpi_->level_params.level_info[0] = &level_info_; + cpi_->level_params.multi_stream_scaling_x = 1.0; + cpi_->tier[0] = 0; + + for (int level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + level_info_.decoder_models[level].status = DECODER_MODEL_DISABLED; + } + model_ = &level_info_.decoder_models[SEQ_LEVEL_4_0]; + av2_decoder_model_init(cpi_.get(), SEQ_LEVEL_4_0, 0, model_); + model_->initial_presentation_delay = 0.0; + model_->equal_picture_interval = true; + model_->display_clock_tick = 1.0 / 90000.0; + model_->num_ticks_per_picture = 3000; + model_->current_time = 0.0; + seen_display_index_.fill(-1); + } + + ~EncoderModelHarness() { av2_encoder_decoder_model_destroy(model_); } + + void AddGeneration(int buffer_index, uint64_t generation, + uint64_t output_order, uint64_t order_hint, + uint64_t temporal_unit, + bool implicit_output_eligible = true) { + ASSERT_GE(buffer_index, 0); + ASSERT_LT(buffer_index, model_->num_ref_frames + 2); + FRAME_BUFFER *const buffer = &model_->frame_buffer_pool[buffer_index]; + buffer->display_index = -1; + buffer->presentation_time = -1.0; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &buffer->presentation; + *presentation = {}; + presentation->valid = true; + presentation->implicit_output_eligible = implicit_output_eligible; + presentation->generation = generation; + presentation->output_order = output_order; + presentation->order_hint = order_hint; + presentation->temporal_unit_index = temporal_unit; + presentation->output_luma_samples = kOutputSamples; + presentation->buffer_index = buffer_index; + seen_display_index_[buffer_index] = -1; + } + + void AssignSlot(int ref_index, int buffer_index) { + ASSERT_GE(ref_index, 0); + ASSERT_LT(ref_index, kNumRefs); + ASSERT_GE(buffer_index, 0); + const int old_buffer = model_->vbi[ref_index]; + if (old_buffer >= 0) { + ASSERT_GT(model_->frame_buffer_pool[old_buffer].decoder_ref_count, 0u); + --model_->frame_buffer_pool[old_buffer].decoder_ref_count; + } + model_->vbi[ref_index] = buffer_index; + ++model_->frame_buffer_pool[buffer_index].decoder_ref_count; + cpi_->common.ref_frame_map[ref_index] = &references_[ref_index]; + } + + void SetCurrent(int buffer_index) { + model_->cfbi = buffer_index; + model_->current_presentation = + model_->frame_buffer_pool[buffer_index].presentation; + cpi_->common.cur_frame = ¤t_; + cpi_->common.show_existing_frame = 0; + } + + void OutputCurrent() { + cpi_->common.show_existing_frame = 0; + Capture([this]() { + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi_.get(), -1); + }); + } + + void OutputStoredRef(int ref_index) { + cpi_->common.show_existing_frame = 0; + Capture([this, ref_index]() { + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi_.get(), ref_index); + }); + } + + void ShowExisting(int ref_index) { + cpi_->common.show_existing_frame = 1; + cpi_->common.sef_ref_fb_idx = ref_index; + model_->current_presentation = + model_->frame_buffer_pool[model_->vbi[ref_index]].presentation; + Capture([this]() { + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi_.get(), -1); + }); + cpi_->common.show_existing_frame = 0; + } + + void Refresh(const std::vector &ref_indices) { + int refresh_flags = 0; + for (const int ref_index : ref_indices) refresh_flags |= 1 << ref_index; + cpi_->common.current_frame.refresh_frame_flags = refresh_flags; + for (int ref_index = 0; ref_index < kNumRefs; ++ref_index) { + if (((refresh_flags >> ref_index) & 1) == 0) continue; + Capture([this, ref_index]() { + av2_decoder_model_observe_displaced_output_for_operating_points( + cpi_.get(), ref_index); + }); + cpi_->common.ref_frame_map[ref_index] = ¤t_; + av2_decoder_model_mirror_ref_buffer_for_operating_points(cpi_.get(), + ref_index); + } + OutputCurrent(); + } + + void Restrict(const std::vector &ref_indices) { + cpi_->common.seq_params.max_mlayer_id = 1; + cpi_->common.mlayer_id = 1; + for (int i = 0; i < kNumRefs; ++i) { + if (model_->vbi[i] < 0) continue; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &model_->frame_buffer_pool[model_->vbi[i]].presentation; + presentation->mlayer_id = + std::find(ref_indices.begin(), ref_indices.end(), i) != + ref_indices.end() + ? 1 + : 0; + } + Capture([this]() { + av2_decoder_model_observe_restricted_output_for_operating_points( + cpi_.get()); + }); + } + + void Flush(bool olk_limited, uint64_t olk_order_hint = 0) { + if (olk_limited) { + model_->olk_encountered = true; + model_->olk_tu_order_hint_valid = true; + model_->olk_tu_order_hint = olk_order_hint; + } + Capture([this, olk_limited]() { + av2_decoder_model_flush_implicit_output_for_operating_points(cpi_.get(), + olk_limited); + }); + } + + void CaptureDecodedGeneration(int buffer_index, uint64_t expected_generation, + uint64_t output_order, uint64_t temporal_unit, + int ref_index, double current_time) { + ASSERT_LE(output_order, std::numeric_limits::max()); + model_->cfbi = buffer_index; + model_->num_frame = static_cast(expected_generation - 1); + model_->num_decoded_frame = static_cast(expected_generation - 1); + model_->temporal_unit_index = temporal_unit; + model_->current_time = current_time; + cpi_->common.current_frame.display_order_hint = + static_cast(output_order); + current_.display_order_hint = static_cast(output_order); + current_.implicit_output_picture = 1; + cpi_->common.cur_frame = ¤t_; + ASSERT_TRUE(av2_encoder_decoder_model_capture_current_generation( + cpi_.get(), model_, kOutputSamples)); + ASSERT_EQ(model_->current_presentation.generation, expected_generation); + cpi_->common.current_frame.refresh_frame_flags = 1 << ref_index; + cpi_->common.ref_frame_map[ref_index] = &references_[ref_index]; + av2_decoder_model_mirror_ref_buffer_for_operating_points(cpi_.get(), + ref_index); + seen_display_index_[buffer_index] = -1; + } + + const std::vector &outputs() const { return outputs_; } + DECODER_MODEL *model() { return model_; } + const DECODER_MODEL *model() const { return model_; } + + private: + template + void Capture(Action action) { + action(); + std::vector > additions; + for (int i = 0; i < model_->num_ref_frames + 2; ++i) { + const FRAME_BUFFER &buffer = model_->frame_buffer_pool[i]; + if (buffer.display_index >= 0 && + buffer.display_index != seen_display_index_[i]) { + additions.push_back(std::make_pair(buffer.display_index, + buffer.presentation.generation)); + seen_display_index_[i] = buffer.display_index; + } + } + std::sort(additions.begin(), additions.end()); + for (const std::pair &addition : additions) { + outputs_.push_back(addition.second); + } + } + + std::unique_ptr cpi_; + AV2LevelInfo level_info_ = {}; + DECODER_MODEL *model_ = nullptr; + std::array references_ = {}; + RefCntBuffer current_ = {}; + std::array seen_display_index_; + std::vector outputs_; +}; + +void ExpectOracleMatchesEncoder(const Section7Oracle &oracle, + const EncoderModelHarness &encoder) { + EXPECT_EQ(encoder.model()->status, DECODER_MODEL_OK); + EXPECT_EQ(encoder.outputs(), oracle.outputs()); + for (uint64_t generation = 1; generation <= 8; ++generation) { + const OracleGeneration *const expected = oracle.generation(generation); + if (expected == nullptr) continue; + const FRAME_BUFFER *actual = nullptr; + for (int i = 0; i < encoder.model()->num_ref_frames + 2; ++i) { + const FRAME_BUFFER &candidate = encoder.model()->frame_buffer_pool[i]; + if (candidate.presentation.valid && + candidate.presentation.generation == generation) { + actual = &candidate; + break; + } + } + ASSERT_NE(actual, nullptr) << "generation " << generation; + EXPECT_EQ(actual->presentation.normative_output_done, + expected->output_done); + EXPECT_EQ(actual->player_ref_count, expected->player_ref_count); + } +} + +TEST(EncoderDecoderModelSection7OracleTest, + PrecedingTriggerAndSuccessiveOutputsAgree) { + Section7Oracle oracle; + oracle.AddGeneration(1, 1, 1, 10); + oracle.AddGeneration(2, 2, 2, 11); + oracle.AddGeneration(3, 3, 3, 12, false); + oracle.AddGeneration(4, 4, 4, 13); + oracle.AssignSlot(0, 1); + oracle.AssignSlot(1, 2); + oracle.AssignSlot(2, 4); + oracle.SetCurrent(3); + oracle.OutputCurrent(); + + EncoderModelHarness encoder; + encoder.AddGeneration(1, 1, 1, 1, 10); + encoder.AddGeneration(2, 2, 2, 2, 11); + encoder.AddGeneration(4, 3, 3, 3, 12, false); + encoder.AddGeneration(3, 4, 4, 4, 13); + encoder.AssignSlot(0, 1); + encoder.AssignSlot(1, 2); + encoder.AssignSlot(2, 3); + encoder.SetCurrent(4); + encoder.OutputCurrent(); + + EXPECT_EQ(oracle.outputs(), (std::vector{ 1, 2, 3, 4 })); + ExpectOracleMatchesEncoder(oracle, encoder); +} + +TEST(EncoderDecoderModelSection7OracleTest, + UnorderedMultiRefreshUsesAscendingSlotsAndOutputsCurrentOnce) { + Section7Oracle oracle; + oracle.AddGeneration(1, 0, 0, 0); + oracle.AddGeneration(2, 1, 1, 1); + oracle.AddGeneration(3, 2, 2, 2); + oracle.AssignSlot(0, 1); + oracle.AssignSlot(1, 3); + oracle.SetCurrent(2); + oracle.Refresh({ 1, 0, 1 }); + + EncoderModelHarness encoder; + encoder.AddGeneration(1, 1, 0, 0, 0); + encoder.AddGeneration(4, 2, 1, 1, 1); + encoder.AddGeneration(2, 3, 2, 2, 2); + encoder.AssignSlot(0, 1); + encoder.AssignSlot(1, 2); + encoder.SetCurrent(4); + encoder.Refresh({ 1, 0, 1 }); + + EXPECT_EQ(oracle.outputs(), (std::vector{ 1, 2, 3 })); + EXPECT_EQ(encoder.model()->vbi[0], encoder.model()->vbi[1]); + EXPECT_EQ(encoder.model() + ->frame_buffer_pool[encoder.model()->vbi[0]] + .decoder_ref_count, + 2u); + ExpectOracleMatchesEncoder(oracle, encoder); +} + +TEST(EncoderDecoderModelSection7OracleTest, + RepeatedShowExistingDoesNotCompleteImplicitOutput) { + Section7Oracle oracle; + oracle.AddGeneration(1, 0, 0, 0); + oracle.AssignSlot(0, 1); + oracle.ShowExisting(0); + oracle.ShowExisting(0); + + EncoderModelHarness encoder; + encoder.AddGeneration(1, 1, 0, 0, 0); + encoder.AssignSlot(0, 1); + encoder.ShowExisting(0); + encoder.ShowExisting(0); + + EXPECT_EQ(oracle.outputs(), (std::vector{ 1, 1 })); + ExpectOracleMatchesEncoder(oracle, encoder); +} + +TEST(EncoderDecoderModelSection7OracleTest, + RestrictedOutputIsNotRepeatedByFinalFlush) { + Section7Oracle oracle; + oracle.AddGeneration(1, 4, 4, 1); + oracle.AddGeneration(2, 9, 9, 2); + oracle.AssignSlot(0, 1); + oracle.AssignSlot(1, 2); + oracle.Restrict({ 0 }); + oracle.Flush(false); + + EncoderModelHarness encoder; + encoder.AddGeneration(1, 1, 4, 4, 1); + encoder.AddGeneration(2, 2, 9, 9, 2); + encoder.AssignSlot(0, 1); + encoder.AssignSlot(1, 2); + encoder.Restrict({ 0 }); + encoder.Flush(false); + + EXPECT_EQ(oracle.outputs(), (std::vector{ 1, 2 })); + ExpectOracleMatchesEncoder(oracle, encoder); +} + +TEST(EncoderDecoderModelSection7OracleTest, OlkLimitedAndFinalFlushAgree) { + Section7Oracle oracle; + oracle.AddGeneration(1, 5, 5, 5); + oracle.AddGeneration(2, 2, 2, 2); + oracle.AddGeneration(3, 4, 4, 4); + oracle.AssignSlot(0, 1); + oracle.AssignSlot(1, 2); + oracle.AssignSlot(2, 3); + oracle.Flush(true, 4); + oracle.Flush(false); + + EncoderModelHarness encoder; + encoder.AddGeneration(1, 1, 5, 5, 5); + encoder.AddGeneration(2, 2, 2, 2, 2); + encoder.AddGeneration(3, 3, 4, 4, 4); + encoder.AssignSlot(0, 1); + encoder.AssignSlot(1, 2); + encoder.AssignSlot(2, 3); + encoder.Flush(true, 4); + encoder.Flush(false); + + EXPECT_EQ(oracle.outputs(), (std::vector{ 2, 3, 1 })); + ExpectOracleMatchesEncoder(oracle, encoder); +} + +TEST(EncoderDecoderModelSection7OracleTest, + FlushTiesSelectHigherReferenceIndexFirst) { + Section7Oracle oracle; + oracle.AddGeneration(1, 7, 7, 1); + oracle.AddGeneration(2, 7, 7, 2); + oracle.AssignSlot(0, 1); + oracle.AssignSlot(3, 2); + oracle.Flush(false); + + EncoderModelHarness encoder; + encoder.AddGeneration(1, 1, 7, 7, 1); + encoder.AddGeneration(2, 2, 7, 7, 2); + encoder.AssignSlot(0, 1); + encoder.AssignSlot(3, 2); + encoder.Flush(false); + + EXPECT_EQ(oracle.outputs(), (std::vector{ 2, 1 })); + ExpectOracleMatchesEncoder(oracle, encoder); +} + +struct ViolationCollector { + std::vector violations; +}; + +void CollectViolation(void *opaque, const Av2DmViolation *violation) { + static_cast(opaque)->violations.push_back(*violation); +} + +Av2DmConfig MakeCommonModelConfig() { + Av2DmConfig config = {}; + config.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + config.applicability = AV2_DM_APPLICABLE; + config.level_idx = SEQ_LEVEL_4_0; + config.profile = MAIN_420_10_IP0; + config.num_ref_frames = kNumRefs; + config.max_frame_width = 64; + config.max_frame_height = 64; + config.timing_info_present = true; + config.num_units_in_display_tick = 1; + config.time_scale = 90000; + config.equal_picture_interval = true; + config.ticks_per_picture = 3000; + config.initial_display_delay = 3; + config.sequence_parameters_present = true; + config.sequence_decoder_buffer_delay = 9000; + config.sequence_encoder_buffer_delay = 9000; + config.level_limits_present = true; + config.level_limits.max_picture_size = 1000000; + config.level_limits.max_horizontal_size = 2000; + config.level_limits.max_vertical_size = 2000; + config.level_limits.max_display_rate = 1000000000; + config.level_limits.max_decode_rate = 1000000; + config.level_limits.max_header_rate = 1000; + config.level_limits.max_tiles = 512; + config.level_limits.max_tile_columns = 64; + config.level_limits.max_tile_width = 16384; + config.level_limits.max_tile_area = 100000000; + config.level_limits.max_tile_size_header_rate_product = UINT64_MAX; + config.level_limits.picture_size_profile_factor = 15; + config.level_limits.min_compression_basis = 2; + EXPECT_TRUE(av2_dm_rational_make(1000000, 1, &config.level_limits.bit_rate)); + EXPECT_TRUE( + av2_dm_rational_make(1000000, 1, &config.level_limits.buffer_size)); + return config; +} + +Av2DmFrameEvent MakeCommonFrame(uint64_t index, uint32_t ref_valid_mask) { + Av2DmFrameEvent event = {}; + event.event_index = index; + event.temporal_unit_index = index; + event.ref_valid_mask = ref_valid_mask; + event.generation = index + 1; + event.coded_bits = 1000; + event.random_access_point = index == 0; + event.coded_as_closed_loop_key = index == 0; + event.frame_is_intra = index == 0; + event.frame_width = 64; + event.frame_height = 64; + event.num_tiles = 1; + event.tile_columns = 1; + event.max_tile_width = 64; + event.max_tile_area = kOutputSamples; + event.non_rightmost_tile_width_valid = true; + event.count_frame_header = true; + event.compressed_size_bytes = 128; + return event; +} + +double RationalToDouble(const Av2DmRational &value) { + long double magnitude = 0; + long double denominator = 0; + for (int i = 3; i >= 0; --i) { + magnitude = std::ldexp(magnitude, 64) + value.magnitude.limbs[i]; + denominator = std::ldexp(denominator, 64) + value.denominator.limbs[i]; + } + const long double result = magnitude / denominator; + return static_cast(value.negative ? -result : result); +} + +const Av2DmBuffer *FindCommonGeneration(const Av2DmState &state, + uint64_t generation) { + for (uint32_t i = 0; i < state.buffer_pool.pool_size; ++i) { + const Av2DmBuffer &buffer = state.buffer_pool.buffers[i]; + if (buffer.generation_valid && buffer.generation == generation) { + return &buffer; + } + } + return nullptr; +} + +const FRAME_BUFFER *FindEncoderGeneration(const DECODER_MODEL &model, + uint64_t generation) { + for (int i = 0; i < model.num_ref_frames + 2; ++i) { + const FRAME_BUFFER &buffer = model.frame_buffer_pool[i]; + if (buffer.presentation.valid && + buffer.presentation.generation == generation) { + return &buffer; + } + } + return nullptr; +} + +TEST(EncoderDecoderModelDifferentialTest, + SharedFrameReferenceAndOutputStateAgree) { + Section7Oracle oracle; + oracle.AddGeneration(1, 2, 2, 0); + oracle.AddGeneration(2, 0, 0, 1); + oracle.AddGeneration(3, 1, 1, 2); + oracle.AssignSlot(0, 1); + oracle.AssignSlot(1, 2); + oracle.AssignSlot(2, 3); + oracle.SetCurrent(3); + oracle.OutputCurrent(); + ASSERT_EQ(oracle.outputs(), (std::vector{ 2, 3, 1 })); + + EncoderModelHarness encoder; + encoder.model()->initial_presentation_delay = -1.0; + ViolationCollector collector; + const Av2DmConfig config = MakeCommonModelConfig(); + Av2DecoderModel *const common = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(common, nullptr); + + const uint64_t output_orders[] = { 2, 0, 1 }; + uint32_t valid_mask = 0; + for (uint64_t index = 0; index < 3; ++index) { + Av2DmFrameEvent frame = MakeCommonFrame(index, valid_mask); + av2_decoder_model_start_frame(common, &frame); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(common, &state)); + ASSERT_GE(state.current_buffer_index, 0); + encoder.CaptureDecodedGeneration( + state.current_buffer_index, index + 1, output_orders[index], index, + static_cast(index), RationalToDouble(state.time)); + valid_mask |= 1u << index; + const Av2DmReferenceUpdateEvent refresh = { 1u << index, valid_mask }; + av2_decoder_model_update_reference_buffers(common, &refresh); + av2_decoder_model_set_initial_presentation_delay(common, 10 + index); + } + + Av2DmState common_state; + ASSERT_TRUE(av2_decoder_model_get_state(common, &common_state)); + ASSERT_TRUE(common_state.initial_presentation_delay_known); + encoder.model()->initial_presentation_delay = + RationalToDouble(common_state.initial_presentation_delay); + encoder.OutputStoredRef(2); + ASSERT_EQ(encoder.outputs(), oracle.outputs()); + + const int ref_for_generation[] = { -1, 0, 1, 2 }; + uint64_t event_index = 100; + for (const uint64_t generation : oracle.outputs()) { + Av2DmOutputEvent output = {}; + output.event_index = event_index++; + output.temporal_unit_index = generation - 1; + output.generation = generation; + output.frame_to_show_map_idx = ref_for_generation[generation]; + output.ref_valid_mask = valid_mask; + output.output_luma_samples = kOutputSamples; + av2_decoder_model_output_frame(common, &output); + } + + ASSERT_TRUE(av2_decoder_model_get_state(common, &common_state)); + Av2DmResult common_result; + ASSERT_TRUE(av2_decoder_model_get_result(common, &common_result)); + EXPECT_TRUE(collector.violations.empty()); + EXPECT_EQ(common_result.status, AV2_DM_RESULT_CONFORMANT); + EXPECT_EQ(common_result.decoded_frames, 3u); + EXPECT_EQ(common_result.output_frames, 3u); + EXPECT_EQ(common_state.frame_number, + static_cast(encoder.model()->num_frame + 1)); + EXPECT_EQ(common_state.dfg_number, + static_cast(encoder.model()->num_decoded_frame + 1)); + EXPECT_EQ(common_state.shown_frame_number, + static_cast(encoder.model()->num_shown_frame + 1)); + + for (int ref_index = 0; ref_index < kNumRefs; ++ref_index) { + const int common_buffer = common_state.buffer_pool.vbi[ref_index]; + const int encoder_buffer = encoder.model()->vbi[ref_index]; + if (common_buffer < 0 || encoder_buffer < 0) { + EXPECT_EQ(common_buffer, encoder_buffer); + continue; + } + EXPECT_EQ(common_state.buffer_pool.buffers[common_buffer].generation, + encoder.model() + ->frame_buffer_pool[encoder_buffer] + .presentation.generation); + } + + for (uint64_t generation = 1; generation <= 3; ++generation) { + const Av2DmBuffer *const common_buffer = + FindCommonGeneration(common_state, generation); + const FRAME_BUFFER *const encoder_buffer = + FindEncoderGeneration(*encoder.model(), generation); + ASSERT_NE(common_buffer, nullptr); + ASSERT_NE(encoder_buffer, nullptr); + EXPECT_EQ(common_buffer->decoder_ref_count, + encoder_buffer->decoder_ref_count); + EXPECT_EQ(common_buffer->player_ref_count, + encoder_buffer->player_ref_count); + ASSERT_TRUE(common_buffer->presentation_time_valid); + EXPECT_DOUBLE_EQ(RationalToDouble(common_buffer->presentation_time), + encoder_buffer->presentation_time); + } + + EXPECT_TRUE(common_state.last_output_temporal_unit_valid); + EXPECT_EQ(common_state.last_output_temporal_unit, + encoder.model()->last_output_temporal_unit); + EXPECT_EQ(common_state.last_temporal_unit_output_luma_samples, + encoder.model()->display_samples); + EXPECT_EQ(common_state.last_temporal_unit_output_frames, + encoder.model()->num_frames_current_tu); + EXPECT_DOUBLE_EQ(RationalToDouble(common_state.initial_presentation_delay), + encoder.model()->initial_presentation_delay); + av2_decoder_model_destroy(common); +} + +TEST(EncoderDecoderModelDifferentialTest, + EmptyReferenceIsSameFirstProvableViolation) { + EncoderModelHarness encoder; + encoder.OutputStoredRef(0); + EXPECT_EQ(encoder.model()->status, DECODE_EXISTING_FRAME_BUF_EMPTY); + EXPECT_EQ(av2_encoder_decoder_model_classify_status(encoder.model()->status), + ENCODER_DM_RESULT_VIOLATION); + + ViolationCollector collector; + const Av2DmConfig config = MakeCommonModelConfig(); + Av2DecoderModel *const common = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(common, nullptr); + Av2DmOutputEvent output = {}; + output.event_index = 1; + output.generation = 1; + output.frame_to_show_map_idx = 0; + output.ref_valid_mask = 1; + output.output_luma_samples = kOutputSamples; + av2_decoder_model_output_frame(common, &output); + ASSERT_FALSE(collector.violations.empty()); + EXPECT_EQ(collector.violations.front().code, + AV2_DM_VIOLATION_DECODE_EXISTING_FRAME_BUFFER_EMPTY); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(common, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); + av2_decoder_model_destroy(common); +} + +enum class ResourceAdapterMode { kLegacyOnly, kCommonOnly, kBoth }; + +uint64_t OrderedDoubleBits(double value) { + uint64_t bits; + static_assert(sizeof(bits) == sizeof(value), "double representation"); + std::memcpy(&bits, &value, sizeof(bits)); + return (bits >> 63) != 0 ? ~bits : bits | (UINT64_C(1) << 63); +} + +uint64_t UlpDistance(double left, double right) { + const uint64_t ordered_left = OrderedDoubleBits(left); + const uint64_t ordered_right = OrderedDoubleBits(right); + return ordered_left >= ordered_right ? ordered_left - ordered_right + : ordered_right - ordered_left; +} + +bool WideIsPowerOfTwo(const Av2DmUnsignedWide &value) { + bool found = false; + for (const uint64_t limb : value.limbs) { + if (limb == 0) continue; + if (found || (limb & (limb - 1)) != 0) return false; + found = true; + } + return found; +} + +// The common model retains the normative value exactly. The legacy model +// rounds after each floating operation. Binary-exact values must remain exact; +// all other observations get a fixed eight-ULP envelope, not a relative +// tolerance that could hide a boundary decision. +void ExpectRationalMatchesLegacyDouble(const Av2DmRational &exact, + double legacy) { + const double rounded_exact = RationalToDouble(exact); + ASSERT_TRUE(std::isfinite(legacy)); + ASSERT_TRUE(std::isfinite(rounded_exact)); + const bool small_numerator = exact.magnitude.limbs[1] == 0 && + exact.magnitude.limbs[2] == 0 && + exact.magnitude.limbs[3] == 0 && + exact.magnitude.limbs[0] <= (UINT64_C(1) << 53); + if (small_numerator && WideIsPowerOfTwo(exact.denominator)) { + EXPECT_EQ(legacy, rounded_exact); + } else { + EXPECT_LE(UlpDistance(legacy, rounded_exact), 8u) + << std::setprecision(17) << "legacy=" << legacy + << " exact-rounded=" << rounded_exact; + } +} + +class ResourceAvailabilityDifferentialAdapter { + public: + ResourceAvailabilityDifferentialAdapter(ResourceAdapterMode mode, + bool timing_info_present = true, + bool still_picture = false, + AV2_LEVEL level = SEQ_LEVEL_4_0) + : mode_(mode), cpi_(new AV2_COMP()), level_(level) { + AV2_COMMON *const cm = &cpi_->common; + cm->width = 64; + cm->height = 64; + cm->mi_params.mi_cols = 16; + cm->mi_params.mi_rows = 16; + cm->tiles.cols = 1; + cm->tiles.rows = 1; + cm->seq_params.ref_frames = kNumRefs; + cm->seq_params.seq_profile_idc = MAIN_420_10_IP0; + cm->seq_params.max_frame_width = 64; + cm->seq_params.max_frame_height = 64; + cm->seq_params.max_mlayer_id = 0; + cm->seq_params.operating_points_cnt_minus_1 = 0; + cm->seq_params.operating_point_idc[0] = 0; + cm->seq_params.still_picture = still_picture; + cm->seq_params.seq_max_display_model_info_present_flag = 1; + cm->seq_params.seq_max_initial_display_delay_minus_1 = 0; + cm->ci_params_encoder.ci_timing_info_present_flag = timing_info_present; + cm->ci_params_encoder.timing_info.num_units_in_display_tick = 1; + cm->ci_params_encoder.timing_info.time_scale = 90000; + cm->ci_params_encoder.timing_info.equal_elemental_interval = 1; + cm->ci_params_encoder.timing_info.num_ticks_per_elemental_duration = 3000; + cm->cur_frame = ¤t_; + cpi_->tile_data = &tile_data_; + tile_data_.tile_info.mi_col_end = 16; + tile_data_.tile_info.mi_row_end = 16; + cpi_->framerate = 30.0; + cpi_->level_params.keep_level_stats = 1; + cpi_->level_params.level_info[0] = &level_info_; + cpi_->level_params.multi_stream_scaling_x = 1.0; + cpi_->level_params.frame_header_count = 1; + cpi_->tier[0] = 0; + + for (int candidate = SEQ_LEVEL_2_0; candidate < SEQ_LEVELS; ++candidate) { + level_info_.decoder_models[candidate].status = DECODER_MODEL_DISABLED; + } + if (RunsLegacy()) { + legacy_ = &level_info_.decoder_models[level_]; + av2_decoder_model_init(cpi_.get(), level_, 0, legacy_); + } + + if (RunsCommon()) { + Av2DmConfig config = MakeCommonModelConfig(); + config.scope.whole_xlayer = true; + config.level_idx = level_; + config.explicit_num_ref_frames = true; + config.still_picture = still_picture; + config.timing_info_present = timing_info_present; + config.initial_display_delay = 1; + config.level_limits_present = false; + config.stop_after_first_violation = true; + common_ = + av2_decoder_model_create(&config, CollectViolation, &collector_); + } + } + + ~ResourceAvailabilityDifferentialAdapter() { + if (legacy_ != nullptr) av2_encoder_decoder_model_destroy(legacy_); + av2_decoder_model_destroy(common_); + } + + bool valid() const { + return (!RunsLegacy() || + (legacy_ != nullptr && legacy_->status == DECODER_MODEL_OK)) && + (!RunsCommon() || common_ != nullptr); + } + + void DecodeRefreshAndMaybeOutput(uint64_t coded_bits, uint32_t refresh_flags, + uint64_t output_order, bool output) { + ASSERT_GE(coded_bits, 24u); + AV2_COMMON *const cm = &cpi_->common; + cm->show_existing_frame = 0; + cm->current_frame.frame_type = frame_count_ == 0 ? KEY_FRAME : INTER_FRAME; + cm->current_frame.cm_obu_type = + frame_count_ == 0 ? OBU_CLOSED_LOOP_KEY : OBU_REGULAR_TILE_GROUP; + cm->current_frame.refresh_frame_flags = refresh_flags; + cm->current_frame.display_order_hint = static_cast(output_order); + cm->immediate_output_picture = 0; + current_.display_order_hint = static_cast(output_order); + current_.implicit_output_picture = 0; + cpi_->dm_starts_temporal_unit = true; + + const std::array obu = { + static_cast(cm->current_frame.cm_obu_type << 2), 1, 0 + }; + if (RunsLegacy()) { + av2_update_level_info(cpi_.get(), obu.data(), obu.size(), + static_cast(frame_count_) * 3000, + static_cast(frame_count_ + 1) * 3000, true, + coded_bits - 24); + } + + const uint64_t generation = frame_count_ + 1; + if (CommonHasParameters()) { + Av2DmFrameEvent frame = MakeCommonFrame(frame_count_, ref_valid_mask_); + frame.event_index = NextEventIndex(); + frame.coded_bits = coded_bits; + frame.compressed_size_bytes = obu.size(); + av2_decoder_model_start_frame(common_, &frame); + } + + for (int ref_index = 0; ref_index < kNumRefs; ++ref_index) { + if (((refresh_flags >> ref_index) & 1) != 0) { + cm->ref_frame_map[ref_index] = &references_[ref_index]; + } + } + ref_valid_mask_ |= refresh_flags; + if (RunsLegacy()) { + av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( + cpi_.get()); + } + if (CommonHasParameters()) { + const Av2DmReferenceUpdateEvent refresh = { refresh_flags, + ref_valid_mask_ }; + av2_decoder_model_update_reference_buffers(common_, &refresh); + av2_decoder_model_set_initial_presentation_delay(common_, + NextEventIndex()); + } + + if (output) { + if (RunsLegacy()) { + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi_.get(), -1); + } + if (CommonHasParameters()) { + Av2DmOutputEvent output_event = {}; + output_event.event_index = NextEventIndex(); + output_event.temporal_unit_index = frame_count_; + output_event.generation = generation; + output_event.frame_to_show_map_idx = -1; + output_event.ref_valid_mask = ref_valid_mask_; + output_event.output_luma_samples = kOutputSamples; + output_event.presentation_uses_current_frame = true; + output_event.presentation_random_access_point = frame_count_ == 0; + av2_decoder_model_output_frame(common_, &output_event); + } + } + ++frame_count_; + } + + void Finish() { + if (RunsLegacy()) { + av2_encoder_decoder_model_finalize(legacy_, + cpi_->common.seq_params.still_picture); + } + if (CommonHasParameters()) av2_decoder_model_finish(common_); + } + + DECODER_MODEL *legacy() { return legacy_; } + const DECODER_MODEL *legacy() const { return legacy_; } + Av2DecoderModel *common() { return common_; } + const Av2DecoderModel *common() const { return common_; } + const ViolationCollector &collector() const { return collector_; } + + size_t legacy_storage_bytes() const { + return legacy_ == nullptr + ? 0 + : sizeof(*legacy_) + legacy_->dfg_interval_queue.capacity * + sizeof(DFG_INTERVAL); + } + + private: + bool RunsLegacy() const { return mode_ != ResourceAdapterMode::kCommonOnly; } + bool RunsCommon() const { return mode_ != ResourceAdapterMode::kLegacyOnly; } + + bool CommonHasParameters() const { + if (common_ == nullptr) return false; + Av2DmResult result; + return av2_decoder_model_get_result(common_, &result) && + !result.missing_required_input && !result.arithmetic_failed; + } + + uint64_t NextEventIndex() { return ++event_index_; } + + ResourceAdapterMode mode_; + std::unique_ptr cpi_; + AV2LevelInfo level_info_ = {}; + DECODER_MODEL *legacy_ = nullptr; + Av2DecoderModel *common_ = nullptr; + ViolationCollector collector_; + TileDataEnc tile_data_ = {}; + RefCntBuffer current_ = {}; + std::array references_ = {}; + AV2_LEVEL level_; + uint32_t ref_valid_mask_ = 0; + uint64_t frame_count_ = 0; + uint64_t event_index_ = 0; +}; + +void ExpectSharedResourceState( + const ResourceAvailabilityDifferentialAdapter &adapter) { + ASSERT_NE(adapter.legacy(), nullptr); + ASSERT_NE(adapter.common(), nullptr); + const DECODER_MODEL &legacy = *adapter.legacy(); + Av2DmState exact; + ASSERT_TRUE(av2_decoder_model_get_state(adapter.common(), &exact)); + ASSERT_EQ(legacy.status, DECODER_MODEL_OK); + + EXPECT_EQ(exact.frame_number, static_cast(legacy.num_frame + 1)); + EXPECT_EQ(exact.dfg_number, + static_cast(legacy.num_decoded_frame + 1)); + EXPECT_EQ(exact.shown_frame_number, + static_cast(legacy.num_shown_frame + 1)); + if (exact.last_dfg_valid) { + ExpectRationalMatchesLegacyDouble(exact.first_bit_arrival, + legacy.first_bit_arrival_time); + ExpectRationalMatchesLegacyDouble(exact.last_bit_arrival, + legacy.last_bit_arrival_time); + ExpectRationalMatchesLegacyDouble(exact.scheduled_removal, + legacy.removal_time); + ExpectRationalMatchesLegacyDouble(exact.removal, legacy.removal_time); + ExpectRationalMatchesLegacyDouble(exact.decode_completion, + legacy.current_time); + ExpectRationalMatchesLegacyDouble(exact.time, legacy.current_time); + } + EXPECT_EQ(exact.initial_presentation_delay_known, + legacy.initial_presentation_delay >= 0.0); + if (exact.initial_presentation_delay_known) { + ExpectRationalMatchesLegacyDouble(exact.initial_presentation_delay, + legacy.initial_presentation_delay); + } + if (exact.last_presentation_valid) { + ExpectRationalMatchesLegacyDouble(exact.last_presentation, + legacy.presentation_time); + } + if (exact.last_presentation_offset_valid) { + ExpectRationalMatchesLegacyDouble(exact.last_presentation_offset, + legacy.last_presentation_offset); + } + EXPECT_EQ(exact.last_output_temporal_unit_valid, + legacy.last_output_temporal_unit_valid); + if (exact.last_output_temporal_unit_valid) { + EXPECT_EQ(exact.last_output_temporal_unit, + legacy.last_output_temporal_unit); + EXPECT_EQ(exact.last_temporal_unit_output_luma_samples, + legacy.display_samples); + EXPECT_EQ(exact.last_temporal_unit_output_frames, + legacy.num_frames_current_tu); + } + + for (int ref_index = 0; ref_index < kNumRefs; ++ref_index) { + const int legacy_index = legacy.vbi[ref_index]; + const int exact_index = exact.buffer_pool.vbi[ref_index]; + if (legacy_index < 0 || exact_index < 0) { + EXPECT_EQ(legacy_index, exact_index) << "ref_index=" << ref_index; + continue; + } + const FRAME_BUFFER &legacy_buffer = legacy.frame_buffer_pool[legacy_index]; + const Av2DmBuffer &exact_buffer = exact.buffer_pool.buffers[exact_index]; + EXPECT_EQ(exact_buffer.generation, legacy_buffer.presentation.generation); + EXPECT_EQ(exact_buffer.decoder_ref_count, legacy_buffer.decoder_ref_count); + EXPECT_EQ(exact_buffer.player_ref_count, legacy_buffer.player_ref_count); + } +} + +void ExpectFinalResourceClassification( + const ResourceAvailabilityDifferentialAdapter &adapter, + ENCODER_DM_RESULT_CLASS legacy_expected, + Av2DmResultStatus common_expected) { + ASSERT_NE(adapter.legacy(), nullptr); + Av2DmResult common_result; + ASSERT_TRUE(av2_decoder_model_get_result(adapter.common(), &common_result)); + EXPECT_EQ(av2_encoder_decoder_model_classify_status(adapter.legacy()->status), + legacy_expected); + EXPECT_EQ(common_result.status, common_expected); +} + +void RunNormalResourceTrace(ResourceAvailabilityDifferentialAdapter *adapter) { + ASSERT_NE(adapter, nullptr); + for (uint64_t frame = 0; frame < 3; ++frame) { + adapter->DecodeRefreshAndMaybeOutput(1024, 1u << frame, frame, true); + } + adapter->Finish(); +} + +TEST(EncoderDecoderModelRationalReuseTest, + NormalResourceTraceHasRetainedStateAndClassificationParity) { + ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth); + ASSERT_TRUE(adapter.valid()); + for (uint64_t frame = 0; frame < 3; ++frame) { + adapter.DecodeRefreshAndMaybeOutput(1024, 1u << frame, frame, true); + ASSERT_TRUE(adapter.collector().violations.empty()); + ExpectSharedResourceState(adapter); + } + adapter.Finish(); + ExpectFinalResourceClassification(adapter, ENCODER_DM_RESULT_PASS, + AV2_DM_RESULT_CONFORMANT); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ReportsRecoveredDecodeDurationRoundingWithoutThreshold) { + ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth, + true, true); + ASSERT_TRUE(adapter.valid()); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, false); + + Av2DmState exact; + ASSERT_TRUE(av2_decoder_model_get_state(adapter.common(), &exact)); + const double exact_decode_duration = RationalToDouble(exact.time_to_decode); + const double legacy_recovered_duration = + adapter.legacy()->current_time - adapter.legacy()->removal_time; + const uint64_t distance = + UlpDistance(legacy_recovered_duration, exact_decode_duration); + EXPECT_TRUE(std::isfinite(exact_decode_duration)); + EXPECT_TRUE(std::isfinite(legacy_recovered_duration)); + RecordProperty("decode_duration_ulp_distance", distance); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ArrivalBoundaryAndFirstViolationAgreeExactly) { + // Level 4.0 main-tier Profile 0 has 12,000,000 bit/s. The first resource + // removal is 70,000 / 90,000 s, so 9,333,328 is the greatest byte-aligned + // CodedBits value no greater than the exact 28,000,000 / 3-bit limit. + ResourceAvailabilityDifferentialAdapter at_limit(ResourceAdapterMode::kBoth, + true, true); + ASSERT_TRUE(at_limit.valid()); + at_limit.DecodeRefreshAndMaybeOutput(9333328, 1, 0, false); + EXPECT_EQ(at_limit.legacy()->status, DECODER_MODEL_OK); + EXPECT_TRUE(at_limit.collector().violations.empty()); + ExpectSharedResourceState(at_limit); + at_limit.Finish(); + ExpectFinalResourceClassification(at_limit, ENCODER_DM_RESULT_PASS, + AV2_DM_RESULT_CONFORMANT); + + ResourceAvailabilityDifferentialAdapter over_limit(ResourceAdapterMode::kBoth, + true, true); + ASSERT_TRUE(over_limit.valid()); + over_limit.DecodeRefreshAndMaybeOutput(9333336, 1, 0, false); + EXPECT_EQ(over_limit.legacy()->status, SMOOTHING_BUFFER_UNDERFLOW); + ASSERT_FALSE(over_limit.collector().violations.empty()); + EXPECT_EQ(over_limit.collector().violations.front().code, + AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW); + EXPECT_EQ(over_limit.collector().violations.front().event_index, 1u); + over_limit.Finish(); + ExpectFinalResourceClassification(over_limit, ENCODER_DM_RESULT_VIOLATION, + AV2_DM_RESULT_NON_CONFORMANT); +} + +TEST(EncoderDecoderModelRationalReuseTest, + MissingTimingProvesApplicabilityMismatchAndNoGo) { + ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth, + false, true); + ASSERT_TRUE(adapter.valid()); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.Finish(); + + Av2DmResult common_result; + ASSERT_TRUE(av2_decoder_model_get_result(adapter.common(), &common_result)); + EXPECT_EQ(av2_encoder_decoder_model_classify_status(adapter.legacy()->status), + ENCODER_DM_RESULT_PASS); + EXPECT_EQ(common_result.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_TRUE(common_result.missing_required_input); + RecordProperty("rational_core_migration_decision", "NO-GO"); + RecordProperty("no_go_reason", + "legacy_synthesizes_unsignalled_framerate_timing"); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ReportsRepresentativeRuntimeAndStorageWithoutThresholds) { + constexpr int kIterations = 20; + const auto RunTimed = [](ResourceAdapterMode mode) { + const auto start = std::chrono::steady_clock::now(); + for (int i = 0; i < kIterations; ++i) { + ResourceAvailabilityDifferentialAdapter adapter(mode); + EXPECT_TRUE(adapter.valid()); + RunNormalResourceTrace(&adapter); + EXPECT_TRUE(adapter.valid()); + if (mode != ResourceAdapterMode::kLegacyOnly) { + Av2DmResult result; + EXPECT_TRUE(av2_decoder_model_get_result(adapter.common(), &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + } + } + return std::chrono::duration_cast( + std::chrono::steady_clock::now() - start) + .count(); + }; + + const int64_t legacy_microseconds = + RunTimed(ResourceAdapterMode::kLegacyOnly); + const int64_t common_microseconds = + RunTimed(ResourceAdapterMode::kCommonOnly); + + ResourceAvailabilityDifferentialAdapter measured(ResourceAdapterMode::kBoth); + ASSERT_TRUE(measured.valid()); + RunNormalResourceTrace(&measured); + Av2DmStorageStats common_storage; + ASSERT_TRUE( + av2_decoder_model_get_storage_stats(measured.common(), &common_storage)); + RecordProperty("iterations", kIterations); + RecordProperty("legacy_elapsed_us", legacy_microseconds); + RecordProperty("common_elapsed_us", common_microseconds); + RecordProperty("legacy_state_and_dfg_bytes", measured.legacy_storage_bytes()); + RecordProperty("common_high_water_dfgs", common_storage.high_water_dfgs); + RecordProperty("common_high_water_outputs", + common_storage.high_water_outputs); + RecordProperty("common_high_water_tus", common_storage.high_water_tus); + RecordProperty("common_high_water_generations", + common_storage.high_water_generations); +} + +} // namespace diff --git a/test/level_test.cc b/test/level_test.cc index 4a5d6c25be..36a6ea51f2 100644 --- a/test/level_test.cc +++ b/test/level_test.cc @@ -9,11 +9,23 @@ * source code in the PATENTS file, you can obtain it at * aomedia.org/license/patent-license/. */ +#include +#include +#include +#include #include +#include #include "third_party/googletest/src/googletest/include/gtest/gtest.h" #include "av2/common/enums.h" +extern "C" { +#include "av2/common/annexA.h" +#include "av2/common/level.h" +#include "av2/common/timing.h" +#include "av2/encoder/bitstream.h" +#include "av2/encoder/encoder.h" +} #include "test/codec_factory.h" #include "test/encode_test_driver.h" #include "test/i420_video_source.h" @@ -22,6 +34,1697 @@ #include "test/yuv_video_source.h" namespace { +#if !CONFIG_SHARED +void AppendSection5Obu(std::vector *data, OBU_TYPE type, + uint8_t payload_size) { + data->push_back(static_cast(type) << 2); + data->push_back(payload_size); + data->insert(data->end(), payload_size, 0); +} + +DECODER_MODEL MakeFrameConstraintModel() { + DECODER_MODEL model = {}; + model.status = DECODER_MODEL_OK; + model.initialized = true; + model.max_tile_rate_satisfy = true; + model.compressed_size_satisfy = true; + model.frame_symbol_count_satisfy = true; + model.multistream_scale_numerator = 1; + model.multistream_scale_denominator = 1; + model.level_limits.max_decode_rate = 1000; + model.level_limits.max_tiles = 4; + model.level_limits.picture_size_profile_factor = 8; + model.level_limits.min_compression_basis = 2; + model.max_decode_rate_satisfy = true; + return model; +} + +DECODER_MODEL *EnableSingleDecoderModel(AV2LevelInfo *level_info, + AV2_LEVEL active_level) { + for (int level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + level_info->decoder_models[level].status = DECODER_MODEL_DISABLED; + } + DECODER_MODEL *const model = &level_info->decoder_models[active_level]; + *model = {}; + model->status = DECODER_MODEL_OK; + model->level = active_level; + model->multistream_scale_numerator = 1; + model->multistream_scale_denominator = 1; + return model; +} + +void SetPresentation(DECODER_MODEL *model, int buffer_index, + uint64_t output_order, uint64_t temporal_unit_index, + bool implicit_output_eligible = true) { + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &model->frame_buffer_pool[buffer_index].presentation; + *presentation = {}; + presentation->valid = true; + presentation->implicit_output_eligible = implicit_output_eligible; + presentation->generation = static_cast(buffer_index + 1); + presentation->temporal_unit_index = temporal_unit_index; + presentation->output_order = output_order; + presentation->order_hint = output_order; + presentation->output_luma_samples = 160 * 90; + presentation->buffer_index = buffer_index; +} + +struct MinimumPresentationIntervalResult { + DECODER_MODEL_STATUS status; + bool satisfies; +}; + +MinimumPresentationIntervalResult CheckMinimumPresentationInterval( + int tier, double multistream_scale, int max_frame_width, + int max_frame_height, uint32_t output_frames, double interval, + uint64_t forced_output_frames = 0) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->level_params.multi_stream_scaling_x = multistream_scale; + cpi->tier[0] = tier; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.max_frame_width = max_frame_width; + cpi->common.seq_params.max_frame_height = max_frame_height; + cpi->framerate = 30.0; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, model); + + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = interval; + model->num_ticks_per_picture = 1; + for (uint32_t output = 0; output < output_frames; ++output) { + const int buffer_index = (int)output + 1; + model->cfbi = buffer_index; + SetPresentation(model, buffer_index, output, 0, false); + model->current_presentation = + model->frame_buffer_pool[buffer_index].presentation; + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi.get(), -1); + } + if (forced_output_frames != 0) { + model->num_frames_current_tu = forced_output_frames; + } + const int next_buffer_index = (int)output_frames + 1; + model->cfbi = next_buffer_index; + SetPresentation(model, next_buffer_index, output_frames, 1, false); + model->current_presentation = + model->frame_buffer_pool[next_buffer_index].presentation; + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + + const MinimumPresentationIntervalResult result = { + model->status, + model->min_presentation_interval_satisfy, + }; + av2_encoder_decoder_model_destroy(model); + return result; +} + +TEST(LevelDecoderModelTest, DisplayClockTickUsesDisplayTimebaseUnits) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.subsampling_x = 1; + cpi->common.seq_params.subsampling_y = 1; + cpi->common.ci_params_encoder.ci_timing_info_present_flag = 1; + cpi->common.ci_params_encoder.timing_info.num_units_in_display_tick = 1001; + cpi->common.ci_params_encoder.timing_info.time_scale = 30000; + cpi->common.ci_params_encoder.timing_info.num_ticks_per_elemental_duration = + 7; + + DECODER_MODEL decoder_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); + + EXPECT_DOUBLE_EQ(1001.0 / 30000, decoder_model.display_clock_tick); + EXPECT_EQ(7, decoder_model.num_ticks_per_picture); +} + +TEST(LevelDecoderModelTest, InitialDisplayDelayUsesSequenceSemantics) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.op_params[0].initial_display_delay = 8; + cpi->framerate = 30.0; + + DECODER_MODEL decoder_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); + EXPECT_EQ(6, decoder_model.initial_display_delay); + + cpi->common.seq_params.seq_max_display_model_info_present_flag = 1; + cpi->common.seq_params.seq_max_initial_display_delay_minus_1 = 2; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); + EXPECT_EQ(3, decoder_model.initial_display_delay); +} + +TEST(LevelDecoderModelTest, AnnexABitrateProfileFactorsAreExact) { + EXPECT_EQ(12000000, av2_max_level_bitrate(MAIN_420_10_IP0, SEQ_LEVEL_4_0, 0)); + EXPECT_EQ(20004000, av2_max_level_bitrate(MAIN_422_10_IP1, SEQ_LEVEL_4_0, 0)); + EXPECT_EQ(30000000, av2_max_level_bitrate(MAIN_444_10_IP1, SEQ_LEVEL_4_0, 0)); +#if CONFIG_12BIT_PROFILE + EXPECT_EQ(36000000, + av2_max_level_bitrate(MAIN_444C_12_IP2, SEQ_LEVEL_4_0, 0)); + EXPECT_EQ(90000000, + av2_max_level_bitrate(MAIN_444C_12_IP2, SEQ_LEVEL_4_0, 1)); + + Av2DmLevelLimits main_limits; + Av2DmLevelLimits high_limits; + ASSERT_TRUE(av2_dm_get_level_limits(SEQ_LEVEL_4_0, 0, MAIN_444C_12_IP2, + &main_limits)); + ASSERT_TRUE(av2_dm_get_level_limits(SEQ_LEVEL_4_0, 1, MAIN_444C_12_IP2, + &high_limits)); + EXPECT_EQ(main_limits.picture_size_profile_factor, 36u); + EXPECT_EQ(high_limits.picture_size_profile_factor, 36u); + + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = MAIN_444C_12_IP2; + cpi->tier[0] = 0; + cpi->tier[1] = 1; + DECODER_MODEL main_model; + DECODER_MODEL high_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &main_model); + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 1, &high_model); + EXPECT_EQ(main_model.status, DECODER_MODEL_OK); + EXPECT_EQ(high_model.status, DECODER_MODEL_OK); + EXPECT_EQ(main_model.level_limits.picture_size_profile_factor, 36u); + EXPECT_EQ(high_model.level_limits.picture_size_profile_factor, 36u); + int comparison = 1; + ASSERT_TRUE(av2_dm_rational_compare(&main_model.bit_rate, + &main_limits.bit_rate, &comparison)); + EXPECT_EQ(comparison, 0); + ASSERT_TRUE(av2_dm_rational_compare(&high_model.bit_rate, + &high_limits.bit_rate, &comparison)); + EXPECT_EQ(comparison, 0); + av2_encoder_decoder_model_destroy(&main_model); + av2_encoder_decoder_model_destroy(&high_model); +#endif // CONFIG_12BIT_PROFILE + EXPECT_EQ(12000000, av2_max_level_bitrate(CONFIGURABLE, SEQ_LEVEL_4_0, 0)); + EXPECT_EQ(0, av2_max_level_bitrate(MAIN_420_10_IP0, SEQ_LEVEL_2_1, 1)); + EXPECT_EQ(0, av2_max_level_bitrate(MAIN_420_10_IP0, -1, 0)); + EXPECT_EQ(0, av2_max_level_bitrate(MAIN_420_10_IP0, 32, 0)); +} + +TEST(LevelDecoderModelTest, Profile5SupportFollowsBuildConfiguration) { + SequenceHeader sequence = {}; + sequence.seq_profile_idc = static_cast(5); + sequence.bit_depth = AVM_BITS_12; + sequence.subsampling_x = 0; + sequence.subsampling_y = 0; + sequence.seq_max_mlayer_cnt = 1; + avm_internal_error_info error = {}; + +#if CONFIG_12BIT_PROFILE + EXPECT_TRUE(av2_check_profile_interop_conformance(&sequence, &error, 0)); + EXPECT_EQ(36000000, + av2_max_level_bitrate(MAIN_444C_12_IP2, SEQ_LEVEL_4_0, 0)); + EXPECT_EQ(90000000, + av2_max_level_bitrate(MAIN_444C_12_IP2, SEQ_LEVEL_4_0, 1)); +#else + EXPECT_FALSE(av2_check_profile_interop_conformance(&sequence, &error, 0)); + EXPECT_EQ(0, av2_max_level_bitrate(static_cast(5), + SEQ_LEVEL_4_0, 0)); + EXPECT_EQ(0, av2_max_level_bitrate(static_cast(5), + SEQ_LEVEL_4_0, 1)); +#endif // CONFIG_12BIT_PROFILE +} + +TEST(LevelDecoderModelTest, CompressionRatioUsesWideIntermediate) { + AV2_COMMON cm = {}; + cm.width = 38400; + cm.height = 38400; + cm.seq_params.seq_profile_idc = MAIN_444_10_IP1; + + const uint64_t expected_uncompressed_size = + static_cast(cm.width) * cm.height * 30 / 8; + EXPECT_DOUBLE_EQ(av2_get_compression_ratio(&cm, 130), + expected_uncompressed_size / 2.0); +} + +TEST(LevelDecoderModelTest, UsesSelectedOperatingPointTierAndBufferSize) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->tier[0] = 0; + cpi->tier[1] = 1; + + DECODER_MODEL main_model; + DECODER_MODEL high_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &main_model); + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 1, &high_model); + + EXPECT_EQ(0, main_model.operating_point); + EXPECT_EQ(0, main_model.tier); + Av2DmRational main_expected; + Av2DmRational high_expected; + ASSERT_TRUE(av2_dm_rational_make(12000000, 1, &main_expected)); + ASSERT_TRUE(av2_dm_rational_make(30000000, 1, &high_expected)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&main_model.bit_rate, &main_expected, + &comparison)); + EXPECT_EQ(0, comparison); + ASSERT_TRUE(av2_dm_rational_compare(&main_model.bit_rate, + &main_model.buffer_size, &comparison)); + EXPECT_EQ(0, comparison); + EXPECT_EQ(1, high_model.operating_point); + EXPECT_EQ(1, high_model.tier); + ASSERT_TRUE(av2_dm_rational_compare(&high_model.bit_rate, &high_expected, + &comparison)); + EXPECT_EQ(0, comparison); + ASSERT_TRUE(av2_dm_rational_compare(&high_model.bit_rate, + &high_model.buffer_size, &comparison)); + EXPECT_EQ(0, comparison); + + av2_encoder_decoder_model_destroy(&main_model); + av2_encoder_decoder_model_destroy(&high_model); +} + +TEST(LevelDecoderModelTest, PreservesFractionalMultistreamBitrate) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->tier[0] = 0; + cpi->level_params.multi_stream_scaling_x = 9.0; + + DECODER_MODEL decoder_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); + + Av2DmRational expected; + ASSERT_TRUE(av2_dm_rational_make(4000000, 3, &expected)); + int comparison; + ASSERT_TRUE( + av2_dm_rational_compare(&decoder_model.bit_rate, &expected, &comparison)); + EXPECT_EQ(0, comparison); + EXPECT_EQ(DECODER_MODEL_OK, decoder_model.status); + av2_encoder_decoder_model_destroy(&decoder_model); +} + +TEST(LevelDecoderModelTest, RejectsHighTierBelowLevelFour) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->tier[0] = 1; + + DECODER_MODEL decoder_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_2_1, 0, &decoder_model); + + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, decoder_model.status); + av2_encoder_decoder_model_destroy(&decoder_model); +} + +TEST(LevelDecoderModelTest, ConfigurableProfileNeedsExplicitFactors) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = CONFIGURABLE; + cpi->tier[0] = 0; + + DECODER_MODEL decoder_model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); + + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, decoder_model.status); + av2_encoder_decoder_model_destroy(&decoder_model); +} + +TEST(LevelDecoderModelTest, ParametersMatchAnnexATableForSupportedScope) { + struct ScalingCase { + double value; + uint64_t numerator; + uint64_t denominator; + }; + const ScalingCase scalings[] = { + { 0.0, 1, 1 }, { 1.0, 1, 1 }, { 1.5, 3, 2 }, { 4.0, 4, 1 }, { 9.0, 9, 1 }, + }; + const int profile_count = +#if CONFIG_12BIT_PROFILE + 6; +#else + 5; +#endif // CONFIG_12BIT_PROFILE + + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = REF_FRAMES; + for (int level = SEQ_LEVEL_2_0; level <= SEQ_LEVEL_8_3; ++level) { + for (int tier = 0; tier <= 1; ++tier) { + cpi->tier[0] = tier; + for (int profile = 0; profile < profile_count; ++profile) { + cpi->common.seq_params.seq_profile_idc = + static_cast(profile); + Av2DmLevelLimits base_limits; + const bool defined = + av2_dm_get_level_limits(level, tier, profile, &base_limits); + for (const ScalingCase &scaling : scalings) { + cpi->level_params.multi_stream_scaling_x = scaling.value; + DECODER_MODEL decoder_model; + av2_decoder_model_init(cpi.get(), static_cast(level), 0, + &decoder_model); + const bool supported = + defined && (scaling.numerator == scaling.denominator || + level >= SEQ_LEVEL_4_0); + if (!supported) { + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, decoder_model.status) + << "level=" << level << " tier=" << tier + << " profile=" << profile; + } else { + Av2DmRational scaled; + Av2DmRational expected_bit_rate; + ASSERT_TRUE(av2_dm_rational_multiply_u64( + &base_limits.bit_rate, scaling.denominator, &scaled)); + ASSERT_TRUE(av2_dm_rational_divide_u64(&scaled, scaling.numerator, + &expected_bit_rate)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare( + &decoder_model.bit_rate, &expected_bit_rate, &comparison)); + EXPECT_EQ(0, comparison) + << "level=" << level << " tier=" << tier + << " profile=" << profile << " scale=" << scaling.value; + + Av2DmRational expected_buffer_size; + ASSERT_TRUE(av2_dm_rational_multiply_u64( + &base_limits.buffer_size, scaling.denominator, &scaled)); + ASSERT_TRUE(av2_dm_rational_divide_u64(&scaled, scaling.numerator, + &expected_buffer_size)); + ASSERT_TRUE(av2_dm_rational_compare(&decoder_model.buffer_size, + &expected_buffer_size, + &comparison)); + EXPECT_EQ(0, comparison); + EXPECT_EQ(DECODER_MODEL_OK, decoder_model.status); + } + av2_encoder_decoder_model_destroy(&decoder_model); + } + } + } + } +} + +TEST(LevelDecoderModelTest, UnavailableCandidatesIgnoreLaterHooks) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + for (int level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + level_info.decoder_models[level].status = DECODER_MODEL_DISABLED; + } + DECODER_MODEL *const unavailable = &level_info.decoder_models[SEQ_LEVEL_2_1]; + unavailable->status = DECODER_MODEL_INTERNAL_ERROR; + unavailable->cfbi = 7; + unavailable->vbi[0] = 3; + unavailable->frame_buffer_pool[3].decoder_ref_count = 2; + const DECODER_MODEL before = *unavailable; + + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.current_frame.refresh_frame_flags = 1; + av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( + cpi.get()); + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + + EXPECT_EQ(0, std::memcmp(&before, unavailable, sizeof(before))); +} + +TEST(LevelDecoderModelTest, InvalidReferenceSynchronizationClearsOnlyInvalid) { + AV2_COMMON cm = {}; + RefCntBuffer retained_reference = {}; + cm.seq_params.ref_frames = 4; + cm.ref_frame_map[0] = nullptr; + cm.ref_frame_map[1] = &retained_reference; + + DECODER_MODEL model = {}; + model.status = DECODER_MODEL_OK; + model.num_ref_frames = 4; + model.vbi[0] = 2; + model.vbi[1] = 2; + model.vbi[2] = -1; + model.vbi[3] = -1; + model.frame_buffer_pool[2].decoder_ref_count = 2; + model.frame_buffer_pool[2].presentation.valid = true; + model.frame_buffer_pool[2].presentation.generation = 9; + + ASSERT_TRUE(av2_encoder_decoder_model_sync_invalid_ref_buffers(&cm, &model)); + EXPECT_EQ(-1, model.vbi[0]); + EXPECT_EQ(2, model.vbi[1]); + EXPECT_EQ(1u, model.frame_buffer_pool[2].decoder_ref_count); + EXPECT_TRUE(model.frame_buffer_pool[2].presentation.valid); + + cm.ref_frame_map[1] = nullptr; + ASSERT_TRUE(av2_encoder_decoder_model_sync_invalid_ref_buffers(&cm, &model)); + EXPECT_EQ(-1, model.vbi[1]); + EXPECT_EQ(0u, model.frame_buffer_pool[2].decoder_ref_count); + EXPECT_FALSE(model.frame_buffer_pool[2].presentation.valid); +} + +TEST(LevelDecoderModelTest, ReferenceUpdateUsesPostUpdateValidity) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + RefCntBuffer current = {}; + + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.ref_frame_map[0] = ¤t; + cpi->common.ref_frame_map[1] = nullptr; + cpi->common.cur_frame = ¤t; + cpi->common.current_frame.refresh_frame_flags = 3; + + model->num_ref_frames = 4; + model->cfbi = 4; + model->initial_presentation_delay = 0.0; + for (int i = 0; i < model->num_ref_frames; ++i) model->vbi[i] = -1; + model->vbi[0] = 2; + model->vbi[1] = 3; + model->frame_buffer_pool[2].decoder_ref_count = 1; + model->frame_buffer_pool[3].decoder_ref_count = 1; + model->frame_buffer_pool[4].presentation.valid = true; + model->frame_buffer_pool[4].presentation.buffer_index = 4; + + av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( + cpi.get()); + + EXPECT_EQ(4, model->vbi[0]); + EXPECT_EQ(-1, model->vbi[1]); + EXPECT_EQ(1u, model->frame_buffer_pool[4].decoder_ref_count); + EXPECT_EQ(0u, model->frame_buffer_pool[2].decoder_ref_count); + EXPECT_EQ(0u, model->frame_buffer_pool[3].decoder_ref_count); + EXPECT_EQ(¤t, cpi->common.ref_frame_map[0]); + EXPECT_EQ(nullptr, cpi->common.ref_frame_map[1]); +} + +TEST(LevelDecoderModelTest, InitialDelayRebasesPreviouslyAssignedTimes) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + RefCntBuffer current = {}; + + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.ref_frame_map[0] = ¤t; + cpi->common.cur_frame = ¤t; + cpi->common.current_frame.refresh_frame_flags = 1; + + model->num_ref_frames = 4; + model->cfbi = 4; + for (int i = 0; i < model->num_ref_frames; ++i) model->vbi[i] = -1; + model->current_time = 5.0; + model->initial_display_delay = 2; + model->initial_presentation_delay = -1.0; + model->presentation_time = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->frame_buffer_pool[1].player_ref_count = 1; + model->frame_buffer_pool[1].display_index = 1; + model->frame_buffer_pool[1].presentation_time = 0.02; + model->frame_buffer_pool[1].presentation.valid = true; + model->frame_buffer_pool[1].presentation.buffer_index = 1; + model->frame_buffer_pool[4].presentation.valid = true; + model->frame_buffer_pool[4].presentation.buffer_index = 4; + + av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( + cpi.get()); + + EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); + EXPECT_DOUBLE_EQ(5.0, model->presentation_time); + EXPECT_DOUBLE_EQ(5.02, model->frame_buffer_pool[1].presentation_time); +} + +TEST(LevelDecoderModelTest, CapturedGenerationIsPrivateAndRecyclable) { + std::unique_ptr cpi(new AV2_COMP()); + RefCntBuffer current = {}; + current.display_order_hint = 5; + current.mlayer_id = 1; + current.xlayer_id = 2; + current.tlayer_id = 3; + current.implicit_output_picture = 1; + current.frame_output_done = false; + cpi->common.cur_frame = ¤t; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_mlayer_id = 1; + cpi->common.current_frame.display_order_hint = 5; + cpi->common.mlayer_id = 1; + cpi->common.xlayer_id = 2; + cpi->common.tlayer_id = 3; + cpi->common.ci_params_encoder.ci_timing_info_present_flag = 1; + cpi->common.ci_params_encoder.timing_info.equal_elemental_interval = 0; + cpi->common.temporal_point_info_metadata.mtpi_frame_presentation_time = 17; + + DECODER_MODEL model = {}; + model.status = DECODER_MODEL_OK; + model.num_ref_frames = 4; + model.cfbi = 4; + model.num_frame = 7; + model.temporal_unit_index = 9; + + ASSERT_TRUE(av2_encoder_decoder_model_capture_current_generation( + cpi.get(), &model, 1234)); + const ENCODER_DM_PRESENTATION_DESCRIPTOR first = + model.frame_buffer_pool[4].presentation; + EXPECT_EQ(1u, first.generation); + EXPECT_EQ(11u, first.output_order); + EXPECT_EQ(9u, first.temporal_unit_index); + EXPECT_EQ(7, first.source_frame_unit_index); + EXPECT_EQ(1234u, first.output_luma_samples); + EXPECT_TRUE(first.presentation_time_present); + EXPECT_EQ(17u, first.presentation_time_ticks); + + current.display_order_hint = 6; + cpi->common.current_frame.display_order_hint = 6; + ASSERT_TRUE(av2_encoder_decoder_model_capture_current_generation( + cpi.get(), &model, 1234)); + EXPECT_EQ(2u, model.frame_buffer_pool[4].presentation.generation); + EXPECT_EQ(13u, model.frame_buffer_pool[4].presentation.output_order); + EXPECT_FALSE(current.frame_output_done); +} + +TEST(LevelDecoderModelTest, DisplacedOutputUsesOldGenerationOnlyOnce) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + RefCntBuffer displaced = {}; + displaced.mlayer_id = 0; + displaced.xlayer_id = 0; + displaced.tlayer_id = 0; + displaced.frame_output_done = false; + + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + cpi->common.ref_frame_map[0] = &displaced; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->vbi[0] = 2; + model->initial_presentation_delay = 0.0; + model->equal_picture_interval = true; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->frame_buffer_pool[2].decoder_ref_count = 1; + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &model->frame_buffer_pool[2].presentation; + presentation->valid = true; + presentation->implicit_output_eligible = true; + presentation->generation = 7; + presentation->buffer_index = 2; + presentation->output_luma_samples = 160 * 90; + // Simulate BRU's in-place replacement or a global reference generation that + // differs from this operating point's old VBI generation. + displaced.mlayer_id = 1; + displaced.xlayer_id = 3; + + av2_decoder_model_observe_displaced_output_for_operating_points(cpi.get(), 0); + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(presentation->normative_output_done); + EXPECT_EQ(1u, model->frame_buffer_pool[2].player_ref_count); + EXPECT_EQ(0, model->num_shown_frame); + EXPECT_EQ(0, model->last_output_mlayer); + EXPECT_EQ(0, model->last_output_xlayer); + EXPECT_FALSE(displaced.frame_output_done); + + av2_decoder_model_observe_displaced_output_for_operating_points(cpi.get(), 0); + EXPECT_EQ(1u, model->frame_buffer_pool[2].player_ref_count); + EXPECT_EQ(0, model->num_shown_frame); + EXPECT_FALSE(displaced.frame_output_done); +} + +TEST(LevelDecoderModelTest, ShowExistingEmptySlotIsAConformanceFailure) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.show_existing_frame = 1; + cpi->common.sef_ref_fb_idx = 0; + model->num_ref_frames = 4; + model->vbi[0] = -1; + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + 0); + EXPECT_EQ(DECODE_EXISTING_FRAME_BUF_EMPTY, model->status); +} + +TEST(LevelDecoderModelTest, ObservesPrecedingTriggerAndSuccessiveOutputs) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->cfbi = 4; + model->vbi[0] = 1; + model->vbi[1] = 2; + model->vbi[2] = 3; + SetPresentation(model, 1, 1, 10); + SetPresentation(model, 2, 2, 11); + SetPresentation(model, 4, 3, 12, false); + SetPresentation(model, 3, 4, 13); + model->current_presentation = model->frame_buffer_pool[4].presentation; + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(3, model->num_shown_frame); + EXPECT_EQ(0, model->frame_buffer_pool[1].display_index); + EXPECT_EQ(1, model->frame_buffer_pool[2].display_index); + EXPECT_EQ(2, model->frame_buffer_pool[4].display_index); + EXPECT_EQ(3, model->frame_buffer_pool[3].display_index); + EXPECT_DOUBLE_EQ(0.0, model->frame_buffer_pool[1].presentation_time); + EXPECT_DOUBLE_EQ(0.02, model->frame_buffer_pool[2].presentation_time); + EXPECT_DOUBLE_EQ(0.04, model->frame_buffer_pool[4].presentation_time); + EXPECT_DOUBLE_EQ(0.06, model->frame_buffer_pool[3].presentation_time); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_TRUE(model->frame_buffer_pool[2].presentation.normative_output_done); + EXPECT_TRUE(model->frame_buffer_pool[3].presentation.normative_output_done); +} + +TEST(LevelDecoderModelTest, ShowExistingDoesNotCompleteImplicitPresentation) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + cpi->common.show_existing_frame = 1; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->vbi[0] = 1; + model->vbi[1] = 2; + SetPresentation(model, 1, 4, 2); + SetPresentation(model, 2, 2, 1); + model->current_presentation = model->frame_buffer_pool[1].presentation; + model->current_presentation.temporal_unit_index = 3; + model->current_presentation.output_order = 0; + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_FALSE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_FALSE(model->frame_buffer_pool[2].presentation.normative_output_done); + EXPECT_EQ(0u, model->frame_buffer_pool[2].player_ref_count); + + av2_decoder_model_observe_displaced_output_for_operating_points(cpi.get(), 0); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_EQ(2, model->num_shown_frame); +} + +TEST(LevelDecoderModelTest, DerivedShowExistingUsesStoredPresentation) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.show_existing_frame = 1; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->vbi[0] = 1; + SetPresentation(model, 1, 4, 2); + model->current_presentation = model->frame_buffer_pool[1].presentation; + model->current_presentation.temporal_unit_index = 9; + model->current_presentation.output_order = 20; + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + 0); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_EQ(2u, model->last_output_temporal_unit); +} + +TEST(LevelDecoderModelTest, RestrictedSwitchOutputsBeforeRestriction) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_mlayer_id = 1; + cpi->common.mlayer_id = 1; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->vbi[0] = 1; + model->vbi[1] = 2; + SetPresentation(model, 1, 4, 1); + SetPresentation(model, 2, 9, 2); + model->frame_buffer_pool[1].presentation.mlayer_id = 1; + model->frame_buffer_pool[2].presentation.mlayer_id = 0; + + av2_decoder_model_observe_restricted_output_for_operating_points(cpi.get()); + + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.restricted); + EXPECT_FALSE(model->frame_buffer_pool[2].presentation.normative_output_done); + EXPECT_FALSE(model->frame_buffer_pool[2].presentation.restricted); +} + +TEST(LevelDecoderModelTest, MultiRefreshUsesPerSlotReferenceState) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + RefCntBuffer old_ref0 = {}; + RefCntBuffer old_ref1 = {}; + RefCntBuffer current = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + cpi->common.current_frame.refresh_frame_flags = 3; + cpi->common.ref_frame_map[0] = &old_ref0; + cpi->common.ref_frame_map[1] = &old_ref1; + cpi->common.cur_frame = ¤t; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->cfbi = 4; + model->vbi[0] = 1; + model->vbi[1] = 2; + model->frame_buffer_pool[1].decoder_ref_count = 1; + model->frame_buffer_pool[2].decoder_ref_count = 1; + SetPresentation(model, 1, 0, 0); + SetPresentation(model, 4, 1, 1); + SetPresentation(model, 2, 2, 2); + model->current_presentation = model->frame_buffer_pool[4].presentation; + + av2_decoder_model_observe_displaced_output_for_operating_points(cpi.get(), 0); + cpi->common.ref_frame_map[0] = ¤t; + av2_decoder_model_mirror_ref_buffer_for_operating_points(cpi.get(), 0); + av2_decoder_model_observe_displaced_output_for_operating_points(cpi.get(), 1); + cpi->common.ref_frame_map[1] = ¤t; + av2_decoder_model_mirror_ref_buffer_for_operating_points(cpi.get(), 1); + + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(0, model->frame_buffer_pool[1].display_index); + EXPECT_EQ(1, model->frame_buffer_pool[4].display_index); + EXPECT_EQ(2, model->frame_buffer_pool[2].display_index); + EXPECT_EQ(4, model->vbi[0]); + EXPECT_EQ(4, model->vbi[1]); + EXPECT_EQ(2u, model->frame_buffer_pool[4].decoder_ref_count); + EXPECT_TRUE(model->frame_buffer_pool[4].presentation.normative_output_done); + EXPECT_EQ(1u, model->frame_buffer_pool[4].player_ref_count); + EXPECT_EQ(2, model->num_shown_frame); + EXPECT_EQ(2, model->last_display_index); + EXPECT_EQ(1u, model->num_frames_current_tu); + EXPECT_EQ(160u * 90u, model->display_samples); + EXPECT_DOUBLE_EQ(0.04, model->presentation_time); + EXPECT_FALSE(current.frame_output_done); + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(1u, model->frame_buffer_pool[4].player_ref_count); + EXPECT_EQ(2u, model->frame_buffer_pool[4].decoder_ref_count); + EXPECT_EQ(2, model->num_shown_frame); + EXPECT_EQ(2, model->last_display_index); + EXPECT_EQ(1u, model->num_frames_current_tu); + EXPECT_EQ(160u * 90u, model->display_samples); + EXPECT_DOUBLE_EQ(0.04, model->presentation_time); + EXPECT_FALSE(current.frame_output_done); +} + +TEST(LevelDecoderModelTest, RepeatedShowExistingOutputIsNotSuppressed) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.show_existing_frame = 1; + cpi->common.sef_ref_fb_idx = 0; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->vbi[0] = 1; + SetPresentation(model, 1, 0, 0); + model->frame_buffer_pool[1].presentation.normative_output_done = true; + model->current_presentation = model->frame_buffer_pool[1].presentation; + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(2u, model->frame_buffer_pool[1].player_ref_count); + EXPECT_EQ(1, model->num_shown_frame); + EXPECT_EQ(1, model->last_display_index); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); +} + +TEST(LevelDecoderModelTest, ConstantTimingSharesTimeWithinOwnerTemporalUnit) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->cfbi = 4; + model->vbi[0] = 1; + SetPresentation(model, 1, 1, 7); + SetPresentation(model, 4, 2, 7, false); + model->current_presentation = model->frame_buffer_pool[4].presentation; + + av2_decoder_model_observe_output_frame_buffers_for_operating_points(cpi.get(), + -1); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(0, model->frame_buffer_pool[1].display_index); + EXPECT_EQ(1, model->frame_buffer_pool[4].display_index); + EXPECT_DOUBLE_EQ(0.0, model->frame_buffer_pool[1].presentation_time); + EXPECT_DOUBLE_EQ(0.0, model->frame_buffer_pool[4].presentation_time); + EXPECT_EQ(2u, model->num_frames_current_tu); + EXPECT_EQ(2u * 160u * 90u, model->display_samples); +} + +TEST(LevelDecoderModelTest, VariableTimingUsesApplicableRapBase) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = false; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.01; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + + const uint64_t ticks[] = { 9, 3, 5, 2 }; + const uint64_t epochs[] = { 1, 1, 2, 2 }; + const bool rap[] = { true, false, true, false }; + const double expected[] = { 0.0, 0.03, 0.05, 0.07 }; + for (int i = 0; i < 4; ++i) { + const int buffer_index = i + 1; + model->cfbi = buffer_index; + SetPresentation(model, buffer_index, i, i, false); + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &model->frame_buffer_pool[buffer_index].presentation; + presentation->presentation_time_present = true; + presentation->presentation_time_ticks = ticks[i]; + presentation->rap_epoch = epochs[i]; + presentation->random_access_point = rap[i]; + model->current_presentation = *presentation; + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi.get(), -1); + ASSERT_EQ(DECODER_MODEL_OK, model->status) << i; + EXPECT_DOUBLE_EQ(expected[i], + model->frame_buffer_pool[buffer_index].presentation_time); + } +} + +TEST(LevelDecoderModelTest, NonIncreasingTuTimingIsAConstraintFailure) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = false; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.01; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + + const uint64_t ticks[] = { 0, 5, 3 }; + for (int i = 0; i < 3; ++i) { + const int buffer_index = i + 1; + model->cfbi = buffer_index; + SetPresentation(model, buffer_index, i, i, false); + ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = + &model->frame_buffer_pool[buffer_index].presentation; + presentation->presentation_time_present = true; + presentation->presentation_time_ticks = ticks[i]; + presentation->rap_epoch = 0; + model->current_presentation = *presentation; + av2_decoder_model_observe_output_frame_buffers_for_operating_points( + cpi.get(), -1); + ASSERT_EQ(DECODER_MODEL_OK, model->status) << i; + } + EXPECT_FALSE(model->min_presentation_interval_satisfy); + EXPECT_EQ(LDBL_MAX, model->max_display_rate); +} + +TEST(LevelDecoderModelTest, MinimumPresentationIntervalUsesTierHeaderRate) { + // At level 4.0, MaxDecodeRate / MaxDisplayRate reduces to 11 / 10. + // Main and high tier therefore require 11 / 3000 and 11 / 9000 seconds, + // respectively, when the sequence-size term is smaller. + const struct { + int tier; + double interval; + } vectors[] = { + { 0, 11.0 / 3000.0 }, + { 1, 11.0 / 9000.0 }, + }; + for (const auto &vector : vectors) { + const MinimumPresentationIntervalResult at_limit = + CheckMinimumPresentationInterval(vector.tier, 1.0, 16, 16, 1, + vector.interval); + EXPECT_EQ(DECODER_MODEL_OK, at_limit.status) << vector.tier; + EXPECT_TRUE(at_limit.satisfies) << vector.tier; + + const MinimumPresentationIntervalResult below_limit = + CheckMinimumPresentationInterval(vector.tier, 1.0, 16, 16, 1, + std::nextafter(vector.interval, 0.0)); + EXPECT_EQ(DECODER_MODEL_OK, below_limit.status) << vector.tier; + EXPECT_FALSE(below_limit.satisfies) << vector.tier; + } +} + +TEST(LevelDecoderModelTest, + MinimumPresentationIntervalUsesSequenceMaximumAndTuOutputCount) { + // Level 4.0 MaxDisplayRate is 70,778,880 samples/s. A 480x576 + // sequence maximum and two outputs require exactly 1/128 second, even + // though each synthetic output picture contains only 160x90 samples. + const double interval = 1.0 / 128.0; + const MinimumPresentationIntervalResult at_limit = + CheckMinimumPresentationInterval(0, 1.0, 480, 576, 2, interval); + EXPECT_EQ(DECODER_MODEL_OK, at_limit.status); + EXPECT_TRUE(at_limit.satisfies); + + const MinimumPresentationIntervalResult below_limit = + CheckMinimumPresentationInterval(0, 1.0, 480, 576, 2, + std::nextafter(interval, 0.0)); + EXPECT_EQ(DECODER_MODEL_OK, below_limit.status); + EXPECT_FALSE(below_limit.satisfies); +} + +TEST(LevelDecoderModelTest, + MinimumPresentationIntervalUsesMultistreamHeaderLimit) { + // Four-way multistream scaling retains the level 4.0 decode/display ratio + // of 11/10 and reduces MaxHeaderRate to 132, giving 1/120 second. + const double interval = 1.0 / 120.0; + const MinimumPresentationIntervalResult at_limit = + CheckMinimumPresentationInterval(0, 4.0, 16, 16, 1, interval); + EXPECT_EQ(DECODER_MODEL_OK, at_limit.status); + EXPECT_TRUE(at_limit.satisfies); + + const MinimumPresentationIntervalResult below_limit = + CheckMinimumPresentationInterval(0, 4.0, 16, 16, 1, + std::nextafter(interval, 0.0)); + EXPECT_EQ(DECODER_MODEL_OK, below_limit.status); + EXPECT_FALSE(below_limit.satisfies); +} + +TEST(LevelDecoderModelTest, + MinimumPresentationIntervalRejectsSampleProductOverflow) { + constexpr uint64_t kMaxFrameSamples = 480u * 576u; + const uint64_t overflowing_output_count = UINT64_MAX / kMaxFrameSamples + 1; + const MinimumPresentationIntervalResult result = + CheckMinimumPresentationInterval(0, 1.0, 480, 576, 1, 1.0, + overflowing_output_count); + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, result.status); +} + +TEST(LevelDecoderModelTest, FlushUsesNormativeOrderAndOlkLimit) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + cpi->common.last_olk_disp_order_hint = 99; + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->equal_picture_interval = true; + model->initial_presentation_delay = 0.0; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->olk_encountered = true; + model->olk_tu_order_hint_valid = true; + model->olk_tu_order_hint = 4; + model->vbi[0] = 1; + model->vbi[1] = 2; + model->vbi[2] = 3; + SetPresentation(model, 1, 5, 5); + SetPresentation(model, 2, 2, 2); + SetPresentation(model, 3, 4, 4); + + av2_decoder_model_flush_implicit_output_for_operating_points(cpi.get(), true); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(model->frame_buffer_pool[2].presentation.normative_output_done); + EXPECT_FALSE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_FALSE(model->frame_buffer_pool[3].presentation.normative_output_done); + EXPECT_FALSE(model->olk_tu_order_hint_valid); + + av2_decoder_model_flush_implicit_output_for_operating_points(cpi.get(), + false); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(0, model->frame_buffer_pool[2].display_index); + EXPECT_EQ(1, model->frame_buffer_pool[3].display_index); + EXPECT_EQ(2, model->frame_buffer_pool[1].display_index); +} + +TEST(LevelDecoderModelTest, DfgIntervalStorageGrowsPastSixtyFour) { + DECODER_MODEL decoder_model = {}; + DFG_INTERVAL_QUEUE *const queue = &decoder_model.dfg_interval_queue; + for (size_t i = 0; i < 65; ++i) { + const DFG_INTERVAL interval = { (double)i, (double)i + 0.25, + (double)i + 1.0, (uint64_t)i + 1 }; + ASSERT_TRUE( + av2_encoder_decoder_model_push_dfg_interval(&decoder_model, &interval)); + } + EXPECT_GE(queue->capacity, 65u); + EXPECT_EQ(0u, queue->head); + EXPECT_EQ(65u, queue->size); + for (size_t i = 0; i < queue->size; ++i) { + EXPECT_DOUBLE_EQ((double)i + 1.0, queue->buf[i].removal_time); + } + EXPECT_DOUBLE_EQ(65.0 * 0.25, queue->total_interval); + EXPECT_EQ(65u * 66 / 2, queue->total_bits); + + const DFG_INTERVAL non_finite = { 0.0, + std::numeric_limits::infinity(), + 1.0, 1 }; + EXPECT_FALSE( + av2_encoder_decoder_model_push_dfg_interval(&decoder_model, &non_finite)); + EXPECT_EQ(65u, queue->size); + + const DFG_INTERVAL reversed = { 2.0, 1.0, 3.0, 1 }; + const double valid_total_interval = queue->total_interval; + EXPECT_FALSE( + av2_encoder_decoder_model_push_dfg_interval(&decoder_model, &reversed)); + EXPECT_EQ(65u, queue->size); + EXPECT_DOUBLE_EQ(valid_total_interval, queue->total_interval); + + queue->total_interval = std::numeric_limits::max(); + const DFG_INTERVAL overflow = { 0.0, 1.0, 2.0, 1 }; + EXPECT_FALSE( + av2_encoder_decoder_model_push_dfg_interval(&decoder_model, &overflow)); + EXPECT_EQ(65u, queue->size); + EXPECT_DOUBLE_EQ(std::numeric_limits::max(), queue->total_interval); + queue->total_interval = valid_total_interval; + + DFG_INTERVAL *const allocation = queue->buf; + const size_t capacity = queue->capacity; + EXPECT_FALSE(av2_encoder_decoder_model_reserve_dfg_intervals(&decoder_model, + SIZE_MAX)); + EXPECT_EQ(allocation, queue->buf); + EXPECT_EQ(capacity, queue->capacity); + av2_encoder_decoder_model_destroy(&decoder_model); +} + +TEST(LevelDecoderModelTest, SmoothingBufferUsesBufferSizeInBits) { + DECODER_MODEL model = {}; + ASSERT_TRUE(av2_dm_rational_make(10, 1, &model.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(20, 1, &model.buffer_size)); + bool fits = false; + ASSERT_TRUE( + av2_encoder_decoder_model_smoothing_buffer_fits(&model, 20, &fits)); + EXPECT_TRUE(fits); + ASSERT_TRUE( + av2_encoder_decoder_model_smoothing_buffer_fits(&model, 21, &fits)); + EXPECT_FALSE(fits); + + ASSERT_TRUE(av2_dm_rational_make(3, 2, &model.buffer_size)); + ASSERT_TRUE( + av2_encoder_decoder_model_smoothing_buffer_fits(&model, 1, &fits)); + EXPECT_TRUE(fits); + ASSERT_TRUE( + av2_encoder_decoder_model_smoothing_buffer_fits(&model, 2, &fits)); + EXPECT_FALSE(fits); + + ASSERT_TRUE(av2_encoder_decoder_model_arrival_fits(&model, 10, 1.0, &fits)); + EXPECT_TRUE(fits); + ASSERT_TRUE(av2_encoder_decoder_model_arrival_fits(&model, 11, 1.0, &fits)); + EXPECT_FALSE(fits); +} + +TEST(LevelDecoderModelTest, DecoderModelStatusClassificationIsExplicit) { + EXPECT_EQ(ENCODER_DM_RESULT_PASS, + av2_encoder_decoder_model_classify_status(DECODER_MODEL_OK)); + EXPECT_EQ(ENCODER_DM_RESULT_PASS, + av2_encoder_decoder_model_classify_status(DECODER_MODEL_DISABLED)); + EXPECT_EQ( + ENCODER_DM_RESULT_VIOLATION, + av2_encoder_decoder_model_classify_status(SMOOTHING_BUFFER_UNDERFLOW)); + EXPECT_EQ( + ENCODER_DM_RESULT_VIOLATION, + av2_encoder_decoder_model_classify_status(SMOOTHING_BUFFER_OVERFLOW)); + EXPECT_EQ( + ENCODER_DM_RESULT_UNAVAILABLE, + av2_encoder_decoder_model_classify_status(DECODER_MODEL_UNSUPPORTED)); + EXPECT_EQ( + ENCODER_DM_RESULT_UNAVAILABLE, + av2_encoder_decoder_model_classify_status(DECODER_MODEL_INCOMPLETE)); + EXPECT_EQ( + ENCODER_DM_RESULT_UNAVAILABLE, + av2_encoder_decoder_model_classify_status(DECODER_MODEL_INTERNAL_ERROR)); +} + +TEST(LevelDecoderModelTest, AutomaticLevelIsThirtyOneWhenModelUnavailable) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + level_info.level_stats.min_frame_width = 16; + level_info.level_stats.min_frame_height = 16; + level_info.level_stats.tile_width_is_valid = 1; + level_info.level_stats.total_compressed_size = 1; + level_info.level_stats.total_time_encoded = 1; + level_info.decoder_models[SEQ_LEVEL_2_0].status = + DECODER_MODEL_INTERNAL_ERROR; + + int seq_level_idx[MAX_NUM_OPERATING_POINTS]; + ASSERT_EQ(AVM_CODEC_OK, + av2_get_seq_level_idx(cpi.get(), &cpi->common.seq_params, + &cpi->level_params, seq_level_idx)); + EXPECT_EQ(SEQ_LEVEL_MAX, seq_level_idx[0]); +} + +TEST(LevelDecoderModelTest, AutomaticHighTierStartsAtLevelFour) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->tier[0] = 1; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + level_info.level_stats.min_frame_width = 16; + level_info.level_stats.min_frame_height = 16; + level_info.level_stats.tile_width_is_valid = 1; + level_info.level_stats.total_compressed_size = 1; + level_info.level_stats.total_time_encoded = 1; + for (int level = SEQ_LEVEL_2_0; level < SEQ_LEVEL_4_0; ++level) { + level_info.decoder_models[level].status = DECODER_MODEL_INTERNAL_ERROR; + } + DECODER_MODEL *const model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + model->status = DECODER_MODEL_OK; + model->initialized = true; + model->max_decode_rate_satisfy = true; + model->max_tile_rate_satisfy = true; + model->compressed_size_satisfy = true; + model->frame_symbol_count_satisfy = true; + model->min_presentation_interval_satisfy = true; + + int seq_level_idx[MAX_NUM_OPERATING_POINTS]; + ASSERT_EQ(AVM_CODEC_OK, + av2_get_seq_level_idx(cpi.get(), &cpi->common.seq_params, + &cpi->level_params, seq_level_idx)); + EXPECT_EQ(SEQ_LEVEL_4_0, seq_level_idx[0]); +} + +TEST(LevelDecoderModelTest, CountsDfgAndAnnexACompressedObuBytesSeparately) { + std::vector data; + const OBU_TYPE counted_types[] = { + OBU_CLOSED_LOOP_KEY, + OBU_OPEN_LOOP_KEY, + OBU_LEADING_TILE_GROUP, + OBU_REGULAR_TILE_GROUP, + OBU_METADATA_SHORT, + OBU_METADATA_GROUP, + OBU_SWITCH, + OBU_LEADING_SEF, + OBU_REGULAR_SEF, + OBU_LEADING_TIP, + OBU_REGULAR_TIP, + OBU_BRIDGE_FRAME, + OBU_RAS_FRAME, + }; + for (const OBU_TYPE type : counted_types) AppendSection5Obu(&data, type, 1); + AppendSection5Obu(&data, OBU_SEQUENCE_HEADER, 1); + AppendSection5Obu(&data, OBU_TEMPORAL_DELIMITER, 0); + AppendSection5Obu(&data, OBU_FILM_GRAIN_MODEL, 1); + + uint64_t dfg_bytes = 17; + uint64_t compressed_bytes = 19; + ASSERT_TRUE(av2_encoder_decoder_model_count_obu_bytes( + data.data(), data.size(), &dfg_bytes, &compressed_bytes)); + EXPECT_EQ(data.size(), dfg_bytes); + EXPECT_EQ(sizeof(counted_types) / sizeof(counted_types[0]) * 3, + compressed_bytes); + + const uint8_t truncated[] = { + static_cast(OBU_REGULAR_TILE_GROUP) << 2, + 2, + 0, + }; + dfg_bytes = 17; + compressed_bytes = 19; + EXPECT_FALSE(av2_encoder_decoder_model_count_obu_bytes( + truncated, sizeof(truncated), &dfg_bytes, &compressed_bytes)); + EXPECT_EQ(17u, dfg_bytes); + EXPECT_EQ(19u, compressed_bytes); +} + +TEST(LevelDecoderModelTest, AccumulatesShowExistingFrameUnitsUntilDfgClosure) { + DECODER_MODEL model = {}; + uint64_t closed_bits; + ASSERT_TRUE(av2_encoder_decoder_model_accumulate_dfg_bits(&model, 16, false, + &closed_bits)); + EXPECT_EQ(0u, closed_bits); + EXPECT_EQ(16u, model.coded_bits); + ASSERT_TRUE(av2_encoder_decoder_model_accumulate_dfg_bits(&model, 24, false, + &closed_bits)); + EXPECT_EQ(40u, model.coded_bits); + ASSERT_TRUE(av2_encoder_decoder_model_accumulate_dfg_bits(&model, 32, true, + &closed_bits)); + EXPECT_EQ(72u, closed_bits); + EXPECT_EQ(0u, model.coded_bits); + + model.coded_bits = UINT64_MAX; + closed_bits = 91; + EXPECT_FALSE(av2_encoder_decoder_model_accumulate_dfg_bits(&model, 1, true, + &closed_bits)); + EXPECT_EQ(UINT64_MAX, model.coded_bits); + EXPECT_EQ(91u, closed_bits); +} + +TEST(LevelDecoderModelTest, AccumulatesEveryTileGroupSymbolCount) { + uint64_t frame_symbols = 17; + ASSERT_TRUE( + av2_encoder_decoder_model_accumulate_frame_symbols(&frame_symbols, 23)); + EXPECT_EQ(40u, frame_symbols); + ASSERT_TRUE( + av2_encoder_decoder_model_accumulate_frame_symbols(&frame_symbols, 29)); + EXPECT_EQ(69u, frame_symbols); + + frame_symbols = UINT64_MAX - 1; + EXPECT_FALSE( + av2_encoder_decoder_model_accumulate_frame_symbols(&frame_symbols, 2)); + EXPECT_EQ(UINT64_MAX - 1, frame_symbols); + EXPECT_FALSE(av2_encoder_decoder_model_accumulate_frame_symbols(nullptr, 1)); +} + +TEST(LevelDecoderModelTest, + CompressedSizeUsesSignedMinusOneHundredTwentyEight) { + const struct { + uint64_t bytes; + int64_t expected; + } cases[] = { + { 0, -128 }, + { 127, -1 }, + { 128, 0 }, + { 129, 1 }, + }; + for (const auto &test_case : cases) { + int64_t compressed_size = 7; + ASSERT_TRUE(av2_encoder_decoder_model_get_compressed_size( + test_case.bytes, &compressed_size)); + EXPECT_EQ(test_case.expected, compressed_size); + } + int64_t compressed_size = 7; + EXPECT_FALSE(av2_encoder_decoder_model_get_compressed_size( + static_cast(INT64_MAX) + 129, &compressed_size)); + EXPECT_EQ(7, compressed_size); +} + +TEST(LevelDecoderModelTest, ChecksAnnexAFrameConstraintBoundaries) { + const ENCODER_DECODER_MODEL_FRAME boundary = { + true, 0.0, 1000, 1, 4, 500, 3722, + }; + DECODER_MODEL model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints( + &model, &boundary, 1, false, 0)); + EXPECT_EQ(1000, model.max_decode_rate); + EXPECT_TRUE(model.max_tile_rate_satisfy); + EXPECT_TRUE(model.compressed_size_satisfy); + EXPECT_TRUE(model.frame_symbol_count_satisfy); + + ENCODER_DECODER_MODEL_FRAME over = boundary; + over.luma_sample_count = 1001; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints(&model, &over, + 1, false, 0)); + EXPECT_EQ(1001, model.max_decode_rate); + EXPECT_FALSE(model.max_decode_rate_satisfy); + + over = boundary; + over.luma_sample_count = 2001; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints(&model, &over, + 2, false, 0)); + EXPECT_EQ(1000.5L, model.max_decode_rate); + EXPECT_FALSE(model.max_decode_rate_satisfy); + + over = boundary; + over.num_tiles = 5; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints(&model, &over, + 1, false, 0)); + EXPECT_FALSE(model.max_tile_rate_satisfy); + + over = boundary; + over.compressed_size = 501; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints(&model, &over, + 1, false, 0)); + EXPECT_FALSE(model.compressed_size_satisfy); + + over = boundary; + over.frame_symbol_count = 3723; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints(&model, &over, + 1, false, 0)); + EXPECT_FALSE(model.frame_symbol_count_satisfy); +} + +TEST(LevelDecoderModelTest, DecodeLimitedIntervalUsesExactAnnexABoundaries) { + const ENCODER_DECODER_MODEL_FRAME boundary = { + true, 0.0, 1000, 1, 4, 500, 3722, + }; + DECODER_MODEL model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints( + &model, &boundary, 1.0, true, 1000)); + EXPECT_TRUE(model.max_decode_rate_satisfy); + EXPECT_TRUE(model.max_tile_rate_satisfy); + EXPECT_TRUE(model.compressed_size_satisfy); + EXPECT_TRUE(model.frame_symbol_count_satisfy); + + ENCODER_DECODER_MODEL_FRAME over = boundary; + over.num_tiles = 5; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints( + &model, &over, 1.0, true, 1000)); + EXPECT_FALSE(model.max_tile_rate_satisfy); + + over = boundary; + over.compressed_size = 501; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints( + &model, &over, 1.0, true, 1000)); + EXPECT_FALSE(model.compressed_size_satisfy); + + over = boundary; + ++over.frame_symbol_count; + model = MakeFrameConstraintModel(); + ASSERT_TRUE(av2_encoder_decoder_model_check_frame_constraints( + &model, &over, 1.0, true, 1000)); + EXPECT_FALSE(model.frame_symbol_count_satisfy); +} + +TEST(LevelDecoderModelTest, RejectsInvalidAnnexALimits) { + const ENCODER_DECODER_MODEL_FRAME frame = { + true, 0.0, 1000, 1, 1, 1, 1, + }; + DECODER_MODEL model = MakeFrameConstraintModel(); + model.level_limits.min_compression_basis = 0; + EXPECT_FALSE(av2_encoder_decoder_model_check_frame_constraints(&model, &frame, + 1, false, 0)); + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, model.status); + + model = MakeFrameConstraintModel(); + EXPECT_FALSE(av2_encoder_decoder_model_check_frame_constraints( + &model, &frame, std::numeric_limits::infinity(), false, 0)); + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, model.status); +} + +TEST(LevelDecoderModelTest, DefersFrameChecksAndUsesStoredDecodeCount) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME first = { + true, 1.0, 1000, 2, 1, 1, 1, + }; + const ENCODER_DECODER_MODEL_FRAME second = { + true, 2.0, 1100, 1, 1, 1, 1, + }; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &first, false)); + EXPECT_FALSE(model.last_frame_parsing_time_valid); + ASSERT_TRUE(av2_encoder_decoder_model_store_frame_constraints(&model, &second, + false)); + EXPECT_DOUBLE_EQ(0.5, model.last_frame_parsing_time); + EXPECT_EQ(2000, model.max_decode_rate); + + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + EXPECT_EQ(2200, model.max_decode_rate); + EXPECT_TRUE(model.frame_constraints_finalized); +} + +TEST(LevelDecoderModelTest, DecodeCountCancelsDecodeLimitedRemovalInterval) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME global_intrabc = { + true, 1.0, 1000, 2, 1, 1, 1, + }; + const ENCODER_DECODER_MODEL_FRAME next = { + true, 3.0, 1000, 1, 1, 1, 1, + }; + ASSERT_TRUE(av2_encoder_decoder_model_store_frame_constraints( + &model, &global_intrabc, false)); + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &next, true)); + EXPECT_TRUE(model.max_decode_rate_satisfy); + EXPECT_EQ(1000, model.max_decode_rate); + EXPECT_TRUE(model.last_frame_parsing_time_at_decode_limit); + EXPECT_EQ(1000u, model.last_frame_parsing_time_decode_luma_samples); +} + +TEST(LevelDecoderModelTest, FinalFrameReusesPreviousExactParsingInterval) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME previous = { + true, 1.0, 1000, 1, 1, 1, 1, + }; + const ENCODER_DECODER_MODEL_FRAME final = { + true, 2.0, 1100, 1, 1, 1, 1, + }; + ASSERT_TRUE(av2_encoder_decoder_model_store_frame_constraints( + &model, &previous, false)); + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &final, true)); + EXPECT_TRUE(model.max_decode_rate_satisfy); + + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + EXPECT_FALSE(model.max_decode_rate_satisfy); + EXPECT_EQ(1100, model.max_decode_rate); +} + +TEST(LevelDecoderModelTest, SingleFrameHasNoSubstituteParsingTime) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME frame = { + true, 1.0, 1000, 1, 1, 1, 1, + }; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + EXPECT_EQ(DECODER_MODEL_INCOMPLETE, model.status); +} + +TEST(LevelDecoderModelTest, FinalTemporalUnitReusesPreviousDuration) { + DECODER_MODEL model = MakeFrameConstraintModel(); + model.display_samples = 1001; + model.last_display_duration = 1.0; + model.last_display_duration_valid = true; + + av2_encoder_decoder_model_finalize(&model, false); + EXPECT_EQ(DECODER_MODEL_OK, model.status); + EXPECT_EQ(1001.0L, model.max_display_rate); + EXPECT_TRUE(model.finalized); + + model.display_samples = 2002; + av2_encoder_decoder_model_finalize(&model, false); + EXPECT_EQ(1001.0L, model.max_display_rate); +} + +TEST(LevelDecoderModelTest, NonStillSingleTemporalUnitIsIncomplete) { + DECODER_MODEL model = MakeFrameConstraintModel(); + model.display_samples = 1000; + + av2_encoder_decoder_model_finalize(&model, false); + EXPECT_EQ(DECODER_MODEL_INCOMPLETE, model.status); + EXPECT_TRUE(model.finalized); +} + +TEST(LevelDecoderModelTest, StillPictureNeedsNoPreviousDurations) { + DECODER_MODEL model = MakeFrameConstraintModel(); + model.display_samples = 1000; + const ENCODER_DECODER_MODEL_FRAME frame = { + true, 1.0, 1000, 1, 1, 1, 1, + }; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + + av2_encoder_decoder_model_finalize(&model, true); + EXPECT_EQ(DECODER_MODEL_OK, model.status); + EXPECT_TRUE(model.finalized); +} + +TEST(LevelDecoderModelTest, OperatingPointFinishIsIdempotent) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + DECODER_MODEL *const model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + *model = MakeFrameConstraintModel(); + model->display_samples = 1000; + model->last_display_duration = 0.5; + model->last_display_duration_valid = true; + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + EXPECT_TRUE(model->finalized); + EXPECT_EQ(2000.0L, model->max_display_rate); + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + EXPECT_EQ(2000.0L, model->max_display_rate); +} + +TEST(LevelDecoderModelTest, FinishThenResetStartsNewCvsModel) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = REF_FRAMES; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.width = 160; + cpi->common.height = 90; + cpi->framerate = 30.0; + DECODER_MODEL *model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + *model = MakeFrameConstraintModel(); + model->display_samples = 1000; + model->last_display_duration = 0.5; + model->last_display_duration_valid = true; + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + ASSERT_TRUE(model->finalized); + av2_init_level_info(cpi.get()); + model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + EXPECT_TRUE(model->initialized); + EXPECT_FALSE(model->finalized); + av2_encoder_decoder_models_destroy(&level_info); +} +#endif // !CONFIG_SHARED + // Speed settings tested static const int kCpuUsedVectors[] = { 2, diff --git a/test/ops_test.cc b/test/ops_test.cc index 975fe307cd..5dc3daa30c 100644 --- a/test/ops_test.cc +++ b/test/ops_test.cc @@ -16,6 +16,7 @@ #include "av2/encoder/ops_syntax.h" #include "av2/decoder/decoder.h" +#include "av2/decoder/decoder_model.h" #include "av2/decoder/decodeframe.h" #include "avm_dsp/bitwriter_buffer.h" #include "avm_dsp/bitreader_buffer.h" @@ -63,6 +64,8 @@ class OpsTest : public ::testing::Test { }; TEST_F(OpsTest, LocalOpsRoundtrip) { + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); const int xlayer_id = 0; OperatingPointSet src; av2_set_ops_params(&src, xlayer_id, 0, 1); @@ -98,6 +101,40 @@ TEST_F(OpsTest, LocalOpsRoundtrip) { EXPECT_EQ(dop->ops_mlayer_count[xlayer_id], 2); EXPECT_EQ(dop->mlayer_info.ops_mlayer_map[xlayer_id], 0x3); EXPECT_EQ(dop->mlayer_info.OPMLayerCount[xlayer_id], 2); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.contexts, 1u); + av2_decoder_model_verifier_destroy(pbi_); +} + +TEST_F(OpsTest, DecoderModelLevelPairRoundtripsThroughOpsSyntax) { + const int xlayer_id = 0; + const int levels[] = { SEQ_LEVEL_3_0, SEQ_LEVEL_2_1 }; + + for (int ops_id = 0; ops_id < 2; ++ops_id) { + OperatingPointSet src; + av2_set_ops_params(&src, xlayer_id, ops_id, 1); + src.ops_ptl_present_flag = 1; + OperatingPoint *const op = &src.op[0]; + op->ops_seq_profile_idc[xlayer_id] = MAIN_420_10_IP0; + op->ops_level_idx[xlayer_id] = levels[ops_id]; + op->ops_mlayer_count[xlayer_id] = 1; + op->mlayer_info.ops_mlayer_map[xlayer_id] = 1; + op->mlayer_info.ops_tlayer_map[xlayer_id][0] = 1; + + memset(buf_, 0, sizeof(buf_)); + const uint32_t written = write_ops_obu(&src, xlayer_id, buf_); + ASSERT_GT(written, 0u); + struct avm_read_bit_buffer rb = { buf_, buf_ + written, 0, nullptr, + rb_error_handler }; + ASSERT_EQ(av2_read_operating_point_set_obu(pbi_, xlayer_id, &rb), written); + + const OperatingPointSet *const parsed = &pbi_->ops_list[xlayer_id][ops_id]; + ASSERT_TRUE(parsed->valid); + ASSERT_EQ(parsed->ops_cnt, 1); + EXPECT_EQ(parsed->op[0].ops_level_idx[xlayer_id], levels[ops_id]); + } } TEST_F(OpsTest, LocalOpsDefaultParams) { @@ -119,6 +156,7 @@ TEST_F(OpsTest, LocalOpsDefaultParams) { EXPECT_EQ(dst->ops_cnt, 1); EXPECT_EQ(dst->ops_ptl_present_flag, 0); EXPECT_EQ(dst->ops_color_info_present_flag, 0); + EXPECT_FALSE(dst->op[0].ops_initial_display_delay_present_flag); EXPECT_EQ(dst->op[0].ops_initial_display_delay, BUFFER_POOL_MAX_SIZE); } @@ -165,6 +203,7 @@ TEST_F(OpsTest, LocalOpsDisplayDelay) { ASSERT_EQ(read, written); const OperatingPointSet *dst = &pbi_->ops_list[xlayer_id][0]; + EXPECT_TRUE(dst->op[0].ops_initial_display_delay_present_flag); EXPECT_EQ(dst->op[0].ops_initial_display_delay, 4); } diff --git a/test/test.cmake b/test/test.cmake index 3f68ac459f..ebdf034e28 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -83,6 +83,16 @@ list(APPEND AVM_TEST_INTRA_PRED_SPEED_SOURCES "${AVM_GEN_SRC_DIR}/usage_exit.c" "${AVM_ROOT}/test/test_intra_pred_speed.cc") if(NOT BUILD_SHARED_LIBS) + list(APPEND AVM_UNIT_TEST_COMMON_SOURCES + "${AVM_ROOT}/test/decoder_model_test.cc") + + list(APPEND AVM_UNIT_TEST_ENCODER_SOURCES + "${AVM_ROOT}/test/encoder_decoder_model_test.cc") + + list(APPEND AVM_UNIT_TEST_DECODER_SOURCES + "${AVM_ROOT}/test/decoder_model_integration_test.cc" + "${AVM_ROOT}/test/decoder_model_parser_test.cc") + list( APPEND AVM_UNIT_TEST_COMMON_SOURCES From dd6420b041705c41b72b2fcbcd2768aa2887a9bf Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Mon, 10 Aug 2026 16:33:54 -0700 Subject: [PATCH 2/8] Fix dangling-else warnings in decoder model test --- test/decoder_model_test.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/decoder_model_test.cc b/test/decoder_model_test.cc index b5ccf405a4..3969b466dd 100644 --- a/test/decoder_model_test.cc +++ b/test/decoder_model_test.cc @@ -1486,8 +1486,12 @@ TEST(DecoderModelConformanceTest, AnnexATablesMatchSpecification) { av2_get_level_compression_basis(static_cast(level), static_cast(tier), &common_cr), cr != 0); - if (kbps != 0) EXPECT_EQ(common_kbps, kbps); - if (cr != 0) EXPECT_EQ(common_cr, cr); + if (kbps != 0) { + EXPECT_EQ(common_kbps, kbps); + } + if (cr != 0) { + EXPECT_EQ(common_cr, cr); + } EXPECT_EQ(av2_tile_width_scaling_factor[tier][level], static_cast(tile_width_scale[tier][level])); EXPECT_EQ(av2_tile_area_scaling_factor[tier][level], From 8d803a5c4217e7bf0fa60a7fb5cc00de60912f6e Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Wed, 12 Aug 2026 19:15:33 -0700 Subject: [PATCH 3/8] Proposed changes to the decoder model --- av2/av2_dx_iface.c | 7 + av2/common/decoder_model.c | 200 ++++-- av2/common/decoder_model.h | 27 +- av2/decoder/decodeframe.c | 5 +- av2/decoder/decoder_model.c | 543 ++++++++++++++--- av2/decoder/decoder_model.h | 54 +- av2/decoder/obu.c | 13 + test/decoder_model_integration_test.cc | 807 ++++++++++++++++++++++++- test/decoder_model_test.cc | 560 ++++++++++++++--- test/encoder_decoder_model_test.cc | 4 +- 10 files changed, 1972 insertions(+), 248 deletions(-) diff --git a/av2/av2_dx_iface.c b/av2/av2_dx_iface.c index a393bdc289..89b48dfff8 100644 --- a/av2/av2_dx_iface.c +++ b/av2/av2_dx_iface.c @@ -981,6 +981,13 @@ static avm_codec_err_t decoder_decode(avm_codec_alg_priv_t *ctx, // temporal scalability. if (data == NULL && data_sz == 0) { AV2_COMMON *const cm = &pbi->common; + av2_decoder_model_verifier_before_final_output(pbi, UINT64_MAX, true); + if (av2_decoder_model_verifier_should_stop(pbi)) { + av2_decoder_model_verifier_finish(pbi); + ctx->decoder_model_fatal_latched = 1; + set_error_detail(ctx, "Decoder model conformance violation"); + return AVM_CODEC_UNSUP_BITSTREAM; + } avm_codec_err_t err = flush_all_xlayer_frames(pbi, cm, false); if (pbi->decoder_model_verifier != NULL || pbi->decoder_model_verifier_allocation_failed) { diff --git a/av2/common/decoder_model.c b/av2/common/decoder_model.c index 49a1d284d2..127b34d014 100644 --- a/av2/common/decoder_model.c +++ b/av2/common/decoder_model.c @@ -17,6 +17,7 @@ #include "avm_mem/avm_mem.h" #include "av2/common/annexA.h" +#include "av2/common/enums.h" #include "av2/common/level.h" #include "av2/common/tile_common.h" #include "av2/common/timing.h" @@ -25,6 +26,10 @@ _Static_assert(sizeof(uint32_t) * CHAR_BIT == 32, "uint32_t must be 32 bits"); _Static_assert(sizeof(uint64_t) * CHAR_BIT == 64, "uint64_t must be 64 bits"); _Static_assert(sizeof(Av2DmUnsignedWide) * CHAR_BIT == 256, "Av2DmUnsignedWide must be 256 bits"); +_Static_assert(AV2_DM_MAX_REF_FRAMES == REF_FRAMES, + "decoder-model VBI capacity must match REF_FRAMES"); +_Static_assert(AV2_DM_MAX_BUFFER_POOL_SIZE >= REF_FRAMES + 2, + "decoder-model BufferPool must hold REF_FRAMES + 2 buffers"); #define AV2_DM_WIDE_LIMBS 4 #define AV2_DM_PRODUCT_LIMBS (2 * AV2_DM_WIDE_LIMBS) @@ -545,8 +550,13 @@ static void buffer_reset(Av2DmBuffer *buffer) { av2_dm_rational_make(0, 1, &buffer->decode_completion_time); } -static bool valid_buffer_index(const Av2DmBufferPool *pool, - uint32_t buffer_index) { +static bool valid_physical_buffer_index(const Av2DmBufferPool *pool, + uint32_t buffer_index) { + return pool != NULL && buffer_index < AV2_DM_MAX_BUFFER_POOL_SIZE; +} + +static bool valid_active_buffer_index(const Av2DmBufferPool *pool, + uint32_t buffer_index) { return pool != NULL && buffer_index < pool->pool_size; } @@ -578,7 +588,7 @@ int32_t av2_dm_buffer_pool_get_free_buffer(const Av2DmBufferPool *pool) { } bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_buffer_index(pool, buffer_index)) return false; + if (!valid_physical_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->decoder_ref_count != 0 || buffer->player_ref_count != 0) { return false; @@ -589,7 +599,7 @@ bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index) { bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_buffer_index(pool, buffer_index)) return false; + if (!valid_physical_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->decoder_ref_count == UINT32_MAX) return false; ++buffer->decoder_ref_count; @@ -598,7 +608,7 @@ bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_buffer_index(pool, buffer_index)) return false; + if (!valid_physical_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->decoder_ref_count == 0) return false; --buffer->decoder_ref_count; @@ -610,7 +620,7 @@ bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_add_player_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_buffer_index(pool, buffer_index)) return false; + if (!valid_physical_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->player_ref_count == UINT32_MAX) return false; ++buffer->player_ref_count; @@ -619,7 +629,7 @@ bool av2_dm_buffer_pool_add_player_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_buffer_index(pool, buffer_index)) return false; + if (!valid_physical_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->player_ref_count == 0) return false; --buffer->player_ref_count; @@ -631,8 +641,10 @@ bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_set_vbi(Av2DmBufferPool *pool, uint32_t ref_index, int32_t buffer_index) { - if (pool == NULL || ref_index >= pool->num_ref_frames || buffer_index < -1 || - (buffer_index >= 0 && (uint32_t)buffer_index >= pool->pool_size)) { + if (pool == NULL || ref_index >= AV2_DM_MAX_REF_FRAMES || buffer_index < -1 || + (buffer_index >= 0 && + (ref_index >= pool->num_ref_frames || + !valid_active_buffer_index(pool, (uint32_t)buffer_index)))) { return false; } const int32_t old_buffer_index = pool->vbi[ref_index]; @@ -656,7 +668,7 @@ bool av2_dm_buffer_pool_set_vbi(Av2DmBufferPool *pool, uint32_t ref_index, uint32_t av2_dm_buffer_pool_frames_in_use(const Av2DmBufferPool *pool) { if (pool == NULL) return 0; uint32_t frames_in_use = 0; - for (uint32_t i = 0; i < pool->pool_size; ++i) { + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { if (pool->buffers[i].decoder_ref_count != 0 || pool->buffers[i].player_ref_count != 0) { ++frames_in_use; @@ -820,7 +832,8 @@ struct Av2DecoderModel { }; static bool invalidate_lane_reference_buffers(Av2DmLane *lane, - uint32_t ref_valid_mask); + uint32_t ref_valid_mask, + bool closed_loop_key); static void check_smoothing_buffer_overflow(Av2DecoderModel *model, uint64_t proving_event_index); static void retire_closed_smoothing_records(Av2DecoderModel *model, @@ -1092,6 +1105,13 @@ void av2_decoder_model_fail_arithmetic_for_testing(Av2DecoderModel *model) { if (model != NULL) arithmetic_failure(model); } +void av2_decoder_model_set_defer_nonterminal_checks_for_testing( + Av2DecoderModel *model, bool defer) { + if (model != NULL && !model->result.finished && !model->processing_stopped) { + model->config.defer_nonterminal_checks_for_testing = defer; + } +} + static bool increment_model_u64(Av2DecoderModel *model, uint64_t *value) { if (*value == UINT64_MAX) { arithmetic_failure(model); @@ -1502,8 +1522,13 @@ static Av2DmViolationDetail buffer_pool_violation_detail( detail.kind = AV2_DM_VIOLATION_DETAIL_BUFFER_POOL; detail.value.buffer_pool.resource_lane = resource_lane; detail.value.buffer_pool.pool_size = pool->pool_size; - detail.value.buffer_pool.frames_in_use = - av2_dm_buffer_pool_frames_in_use(pool); + detail.value.buffer_pool.frames_in_use = 0; + for (uint32_t i = 0; i < pool->pool_size; ++i) { + if (pool->buffers[i].decoder_ref_count != 0 || + pool->buffers[i].player_ref_count != 0) { + ++detail.value.buffer_pool.frames_in_use; + } + } detail.value.buffer_pool.free_buffers = pool->pool_size - detail.value.buffer_pool.frames_in_use; for (uint32_t i = 0; i < pool->pool_size; ++i) { @@ -1617,7 +1642,7 @@ static void check_static_level_limits(Av2DecoderModel *model, static bool release_presented_buffers(Av2DmLane *lane, const Av2DmRational *removal) { - for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->player_ref_count == 0 || !buffer->presentation_time_valid) { continue; @@ -2022,7 +2047,7 @@ static bool add_rebase_target(Av2DmPreparedRebase *prepared, static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, bool primary, const Av2DmRational *origin, Av2DmPreparedRebase *prepared) { - uint64_t capacity = 2 + 2 * lane->pool.pool_size; + uint64_t capacity = 2 + 2 * AV2_DM_MAX_BUFFER_POOL_SIZE; if (primary) { capacity += (uint64_t)5 * model->dfg_count + 16; } @@ -2039,7 +2064,7 @@ static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, count_limit)) { return false; } - for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->presentation_time_valid && !add_rebase_target(prepared, &buffer->presentation_time, count_limit)) { @@ -2152,7 +2177,7 @@ static bool earlier_lane_has_generation(const Av2DmLane *const lanes[2], uint64_t generation) { for (uint32_t i = 0; i <= lane_index; ++i) { const uint32_t limit = - i == lane_index ? buffer_index : lanes[i]->pool.pool_size; + i == lane_index ? buffer_index : AV2_DM_MAX_BUFFER_POOL_SIZE; for (uint32_t j = 0; j < limit; ++j) { const Av2DmBuffer *const buffer = &lanes[i]->pool.buffers[j]; if (lane_buffer_is_live(lanes[i], j) && @@ -2168,7 +2193,7 @@ static uint32_t active_generation_count(const Av2DecoderModel *model) { const Av2DmLane *const lanes[2] = { &model->lane, &model->resource_lane }; uint32_t count = 0; for (uint32_t i = 0; i < 2; ++i) { - for (uint32_t j = 0; j < lanes[i]->pool.pool_size; ++j) { + for (uint32_t j = 0; j < AV2_DM_MAX_BUFFER_POOL_SIZE; ++j) { const Av2DmBuffer *const buffer = &lanes[i]->pool.buffers[j]; if (lane_buffer_is_live(lanes[i], j) && !earlier_lane_has_generation(lanes, i, j, buffer->generation)) { @@ -2238,14 +2263,6 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, return; } model->latest_frame_event_index = event->event_index; - // Annex E synchronizes VBI with RefValid at every start_frame_decode(), - // before FrameNum advances or a current buffer is selected. - if (!invalidate_lane_reference_buffers(&model->lane, event->ref_valid_mask) || - !invalidate_lane_reference_buffers(&model->resource_lane, - event->ref_valid_mask)) { - arithmetic_failure(model); - return; - } ++model->frame_number; if (model->coded_tu_valid && model->coded_tu != event->temporal_unit_index) { Av2DmTuRecord *const previous_coded = find_tu(model, model->coded_tu); @@ -2513,9 +2530,13 @@ void av2_decoder_model_update_reference_buffers( } static bool invalidate_lane_reference_buffers(Av2DmLane *lane, - uint32_t ref_valid_mask) { - for (uint32_t i = 0; i < lane->pool.num_ref_frames; ++i) { - if (((ref_valid_mask >> i) & 1) == 0 && lane->pool.vbi[i] != -1 && + uint32_t ref_valid_mask, + bool closed_loop_key) { + const uint32_t limit = + closed_loop_key ? AV2_DM_MAX_REF_FRAMES : lane->pool.num_ref_frames; + for (uint32_t i = 0; i < limit; ++i) { + if ((closed_loop_key || ((ref_valid_mask >> i) & 1) == 0) && + lane->pool.vbi[i] != -1 && !av2_dm_buffer_pool_set_vbi(&lane->pool, i, -1)) { return false; } @@ -2523,19 +2544,20 @@ static bool invalidate_lane_reference_buffers(Av2DmLane *lane, return true; } -void av2_decoder_model_invalidate_olk_reference_buffers( - Av2DecoderModel *model, uint32_t ref_valid_mask) { +void av2_decoder_model_invalidate_reference_buffers(Av2DecoderModel *model, + uint32_t ref_valid_mask, + bool closed_loop_key) { if (model == NULL || model->result.finished || model->result.applicability == AV2_DM_NOT_APPLICABLE || model->processing_stopped) { return; } - // DM-SPEC-5 / Annex E invalidate_olk_ref_buffers(): RefValid has already - // been updated by frame_header_info(), so every invalid slot is mirrored, - // including slots absent from the current refresh_frame_flags. - if (!invalidate_lane_reference_buffers(&model->lane, ref_valid_mask) || - !invalidate_lane_reference_buffers(&model->resource_lane, - ref_valid_mask)) { + // DM-SPEC-5 / Annex E invalidate_ref_buffers(): CLK clears every physical + // VBI slot. OLK consults RefValid only in the current active range. + if (!invalidate_lane_reference_buffers(&model->lane, ref_valid_mask, + closed_loop_key) || + !invalidate_lane_reference_buffers(&model->resource_lane, ref_valid_mask, + closed_loop_key)) { arithmetic_failure(model); } model_event_complete(model); @@ -2624,15 +2646,16 @@ static bool complete_pending_output_check(Av2DecoderModel *model, static bool set_lane_initial_presentation_delay(Av2DecoderModel *model, Av2DmLane *lane, bool primary_lane, + bool end_of_bitstream, uint64_t proving_event_index) { if (lane->initial_presentation_delay_known || - av2_dm_buffer_pool_frames_in_use(&lane->pool) < - model->config.initial_display_delay) { + (!end_of_bitstream && av2_dm_buffer_pool_frames_in_use(&lane->pool) < + model->config.initial_display_delay)) { return true; } lane->initial_presentation_delay = lane->time; lane->initial_presentation_delay_known = true; - for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->player_ref_count != 0 && !buffer->presentation_time_valid) { if (!av2_dm_rational_add(&buffer->presentation_time, @@ -2666,6 +2689,7 @@ static bool set_lane_initial_presentation_delay(Av2DecoderModel *model, } void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, + bool end_of_bitstream, uint64_t event_index) { if (model == NULL || model->result.finished || model->result.applicability == AV2_DM_NOT_APPLICABLE || @@ -2673,9 +2697,9 @@ void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, return; } if (!set_lane_initial_presentation_delay(model, &model->lane, true, - event_index) || + end_of_bitstream, event_index) || !set_lane_initial_presentation_delay(model, &model->resource_lane, false, - event_index)) { + end_of_bitstream, event_index)) { arithmetic_failure(model); } model_event_complete(model); @@ -2845,7 +2869,7 @@ static void store_rap_presentation_anchor(Av2DecoderModel *model, bool live = candidate->rap_epoch == model->rap_epoch || (model->rap_epoch != 0 && candidate->rap_epoch == model->rap_epoch - 1); - for (uint32_t j = 0; j < model->lane.pool.pool_size && !live; ++j) { + for (uint32_t j = 0; j < AV2_DM_MAX_BUFFER_POOL_SIZE && !live; ++j) { const Av2DmBuffer *const buffer = &model->lane.pool.buffers[j]; live = buffer->generation_valid && buffer->rap_epoch == candidate->rap_epoch; @@ -3200,12 +3224,14 @@ static bool same_scope(const Av2DmScope *a, const Av2DmScope *b) { } static bool same_model_topology_and_clock(const Av2DmConfig *a, - const Av2DmConfig *b) { - // Section 7 keeps the active sequence header fixed until the next CLK, - // which starts a new CVS and therefore a new model. In-place RAP updates - // can replace OPS parameters, but not the active sequence-level fallback. + const Av2DmConfig *b, + bool allow_num_ref_frames_change) { + // A CLK may change only NumRefFrames after its explicit VBI clear-all event. + // Other in-place RAP updates may replace OPS parameters, but not topology or + // the active sequence-level timing fallback. return same_scope(&a->scope, &b->scope) && - a->num_ref_frames == b->num_ref_frames && + (allow_num_ref_frames_change || + a->num_ref_frames == b->num_ref_frames) && a->max_frame_width == b->max_frame_width && a->max_frame_height == b->max_frame_height && a->max_mlayer_id == b->max_mlayer_id && @@ -3227,16 +3253,49 @@ static bool same_model_topology_and_clock(const Av2DmConfig *a, a->stop_after_first_violation == b->stop_after_first_violation; } +Av2DmParameterUpdateDisposition av2_decoder_model_classify_parameter_update( + const Av2DecoderModel *model, const Av2DmConfig *config, + bool closed_loop_key_transition) { + if (model == NULL || config == NULL || model->result.finished || + model->processing_stopped || + model->result.applicability != AV2_DM_APPLICABLE || + config->applicability != AV2_DM_APPLICABLE || + config->num_ref_frames == 0 || + config->num_ref_frames > AV2_DM_MAX_REF_FRAMES) { + return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; + } + Av2DmResolvedParameters parameters; + if (!resolve_parameters(config, ¶meters)) { + return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; + } + if (!same_model_topology_and_clock(&model->config, config, + closed_loop_key_transition)) { + return AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION; + } + if (closed_loop_key_transition) { + const Av2DmLane *const lanes[2] = { &model->lane, &model->resource_lane }; + for (uint32_t lane_index = 0; lane_index < 2; ++lane_index) { + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + if (lanes[lane_index]->pool.vbi[i] != -1) { + return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; + } + } + } + } + return AV2_DM_PARAMETER_UPDATE_ALLOWED; +} + bool av2_decoder_model_update_parameters(Av2DecoderModel *model, const Av2DmConfig *config, - uint64_t event_index) { + uint64_t event_index, + bool closed_loop_key_transition) { if (model == NULL || config == NULL || model->result.finished || model->processing_stopped) { return false; } - if (model->result.applicability != AV2_DM_APPLICABLE || - config->applicability != AV2_DM_APPLICABLE || - !same_model_topology_and_clock(&model->config, config)) { + if (av2_decoder_model_classify_parameter_update(model, config, + closed_loop_key_transition) != + AV2_DM_PARAMETER_UPDATE_ALLOWED) { missing_input(model); return false; } @@ -3268,6 +3327,12 @@ bool av2_decoder_model_update_parameters(Av2DecoderModel *model, memcpy(updated.ras_seeds, model->config.ras_seeds, sizeof(updated.ras_seeds)); const Av2DmMode old_mode = model->config.mode; apply_parameters(model, &updated, ¶meters); + if (closed_loop_key_transition) { + model->lane.pool.num_ref_frames = config->num_ref_frames; + model->lane.pool.pool_size = config->num_ref_frames + 2; + model->resource_lane.pool.num_ref_frames = config->num_ref_frames; + model->resource_lane.pool.pool_size = config->num_ref_frames + 2; + } if (!model->max_reference_frames_violated) { // The maximum depends on the active level limits and must be reconsidered // for the RAP frame after an OPS parameter update. @@ -3516,7 +3581,7 @@ static void check_header_rate_windows_in_output_order( static bool lane_has_live_coded_tu(const Av2DmLane *lane, uint64_t temporal_unit_index) { - for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { const Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (lane_buffer_is_live(lane, i) && buffer->coded_temporal_unit_valid && buffer->coded_temporal_unit_index == temporal_unit_index) { @@ -3741,6 +3806,31 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { update_storage_stats(model); } +bool av2_decoder_model_seed_terminal_history( + Av2DecoderModel *model, const Av2DmRational *previous_frame_parsing_time, + const Av2DmRational *previous_tu_output_duration) { + if (model == NULL || model->result.finished || + (previous_frame_parsing_time != NULL && + wide_is_zero(previous_frame_parsing_time->denominator)) || + (previous_tu_output_duration != NULL && + wide_is_zero(previous_tu_output_duration->denominator))) { + return false; + } + // A suffix with local adjacent DFG/TU history is authoritative. The complete + // source contributes only an otherwise unavailable terminal predecessor. + if (!model->last_frame_parsing_time_valid && model->frame_number != 0 && + previous_frame_parsing_time != NULL) { + model->last_frame_parsing_time = *previous_frame_parsing_time; + model->last_frame_parsing_time_valid = true; + } + if (!model->last_display_duration_valid && model->shown_frame_number != 0 && + previous_tu_output_duration != NULL) { + model->last_display_duration = *previous_tu_output_duration; + model->last_display_duration_valid = true; + } + return true; +} + bool av2_decoder_model_get_result(const Av2DecoderModel *model, Av2DmResult *result) { if (model == NULL || result == NULL) return false; @@ -3761,6 +3851,10 @@ bool av2_decoder_model_get_state(const Av2DecoderModel *model, state->dfg_number = model->dfg_number; state->shown_frame_number = model->shown_frame_number; state->buffer_pool = model->lane.pool; + state->last_frame_parsing_time_valid = model->last_frame_parsing_time_valid; + state->last_frame_parsing_time = model->last_frame_parsing_time; + state->last_display_duration_valid = model->last_display_duration_valid; + state->last_display_duration = model->last_display_duration; if (model->previous_dfg_valid) { const Av2DmDfgRecord *const dfg = &model->previous_dfg; state->last_dfg_valid = true; diff --git a/av2/common/decoder_model.h b/av2/common/decoder_model.h index bcd26cb292..4ddb80c16a 100644 --- a/av2/common/decoder_model.h +++ b/av2/common/decoder_model.h @@ -387,6 +387,10 @@ typedef struct Av2DmState { Av2DmRational last_temporal_unit_output_time; uint64_t last_temporal_unit_output_luma_samples; uint32_t last_temporal_unit_output_frames; + bool last_frame_parsing_time_valid; + Av2DmRational last_frame_parsing_time; + bool last_display_duration_valid; + Av2DmRational last_display_duration; bool initial_presentation_delay_known; Av2DmRational initial_presentation_delay; int32_t current_buffer_index; @@ -416,6 +420,12 @@ typedef struct Av2DmStorageStats { typedef void (*Av2DmReportFn)(void *opaque, const Av2DmViolation *violation); +typedef enum Av2DmParameterUpdateDisposition { + AV2_DM_PARAMETER_UPDATE_ALLOWED, + AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT, + AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION +} Av2DmParameterUpdateDisposition; + bool av2_dm_get_level_limits(uint32_t level_idx, uint32_t tier, uint32_t profile, Av2DmLevelLimits *limits); bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, @@ -427,21 +437,32 @@ Av2DecoderModel *av2_decoder_model_create(const Av2DmConfig *config, Av2DmReportFn report, void *report_opaque); void av2_decoder_model_destroy(Av2DecoderModel *model); +Av2DmParameterUpdateDisposition av2_decoder_model_classify_parameter_update( + const Av2DecoderModel *model, const Av2DmConfig *config, + bool closed_loop_key_transition); bool av2_decoder_model_update_parameters(Av2DecoderModel *model, const Av2DmConfig *config, - uint64_t event_index); + uint64_t event_index, + bool closed_loop_key_transition); void av2_decoder_model_mark_incomplete(Av2DecoderModel *model); void av2_decoder_model_fail_arithmetic_for_testing(Av2DecoderModel *model); +void av2_decoder_model_set_defer_nonterminal_checks_for_testing( + Av2DecoderModel *model, bool defer); void av2_decoder_model_start_frame(Av2DecoderModel *model, const Av2DmFrameEvent *event); void av2_decoder_model_update_reference_buffers( Av2DecoderModel *model, const Av2DmReferenceUpdateEvent *event); -void av2_decoder_model_invalidate_olk_reference_buffers( - Av2DecoderModel *model, uint32_t ref_valid_mask); +void av2_decoder_model_invalidate_reference_buffers(Av2DecoderModel *model, + uint32_t ref_valid_mask, + bool closed_loop_key); void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, + bool end_of_bitstream, uint64_t event_index); void av2_decoder_model_output_frame(Av2DecoderModel *model, const Av2DmOutputEvent *event); +bool av2_decoder_model_seed_terminal_history( + Av2DecoderModel *model, const Av2DmRational *previous_frame_parsing_time, + const Av2DmRational *previous_tu_output_duration); void av2_decoder_model_finish(Av2DecoderModel *model); bool av2_decoder_model_get_result(const Av2DecoderModel *model, Av2DmResult *result); diff --git a/av2/decoder/decodeframe.c b/av2/decoder/decodeframe.c index 69108e0250..ac0089853e 100644 --- a/av2/decoder/decodeframe.c +++ b/av2/decoder/decodeframe.c @@ -6923,7 +6923,7 @@ static void reset_buffer_other_than_OLK(AV2Decoder *pbi) { } if (pbi->decoder_model_verifier != NULL) { - av2_decoder_model_verifier_on_olk_reference_invalidation(pbi); + av2_decoder_model_verifier_on_reference_invalidation(pbi, false); } for (int layer = 0; layer <= seq_params->max_mlayer_id; layer++) { @@ -8313,6 +8313,9 @@ static int read_uncompressed_header(AV2Decoder *pbi, OBU_TYPE obu_type, cm->ref_frame_map[ref_pos] = NULL; } } + if (pbi->decoder_model_verifier != NULL) { + av2_decoder_model_verifier_on_reference_invalidation(pbi, true); + } } if (obu_type == OBU_OPEN_LOOP_KEY) { cm->olk_refresh_frame_flags[cm->mlayer_id] = diff --git a/av2/decoder/decoder_model.c b/av2/decoder/decoder_model.c index 271593a75d..9aaa9a9b51 100644 --- a/av2/decoder/decoder_model.c +++ b/av2/decoder/decoder_model.c @@ -37,35 +37,24 @@ typedef enum Av2DmAdapterEventType { AV2_DM_ADAPTER_TEMPORAL_POINT, AV2_DM_ADAPTER_FRAME_WRAPUP_START, AV2_DM_ADAPTER_FRAME_UNIT_COMPLETE, - AV2_DM_ADAPTER_OLK_REFERENCE_INVALIDATION, + AV2_DM_ADAPTER_REFERENCE_INVALIDATION, AV2_DM_ADAPTER_REFERENCE_UPDATE, AV2_DM_ADAPTER_OUTPUT, AV2_DM_ADAPTER_RECOVERY_RESET, AV2_DM_ADAPTER_STREAM_CONFIGURATION_CHANGE, + AV2_DM_ADAPTER_END_OF_INPUT, AV2_DM_ADAPTER_FINISH } Av2DmAdapterEventType; typedef enum Av2DmContextEventType { AV2_DM_CONTEXT_FRAME, - AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION, + AV2_DM_CONTEXT_REFERENCE_INVALIDATION, AV2_DM_CONTEXT_REFERENCE_UPDATE, AV2_DM_CONTEXT_OUTPUT, - AV2_DM_CONTEXT_RECOVERY_RESET + AV2_DM_CONTEXT_RECOVERY_RESET, + AV2_DM_CONTEXT_END_OF_INPUT } Av2DmContextEventType; -typedef enum Av2DmIndeterminateReason { - AV2_DM_REASON_NONE, - AV2_DM_REASON_MISSING_REQUIRED_INPUT, - AV2_DM_REASON_MISSING_ACTIVE_CONFIGURATION, - AV2_DM_REASON_INCOMPLETE_EXTRACTION, - AV2_DM_REASON_MISSING_FRAME_GENERATION, - AV2_DM_REASON_MISSING_PRESENTATION_PROVENANCE, - AV2_DM_REASON_MISSING_PRESENTATION_TIMING, - AV2_DM_REASON_INCOMPLETE_RAS_SEED, - AV2_DM_REASON_RECOVERY_RESET, - AV2_DM_REASON_INTERNAL_FAILURE -} Av2DmIndeterminateReason; - typedef struct Av2DmRasSeedSnapshot { uint32_t ref_index; uint64_t generation; @@ -199,10 +188,13 @@ typedef struct Av2DmContextEvent { Av2DmReferenceUpdateEvent reference_update; bool set_initial_presentation_delay; uint32_t ref_valid_mask; + bool closed_loop_key_invalidation; Av2DmOutputEvent output; uint32_t ras_seed_count; Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; bool ras_seed_complete; + bool source_display_duration_valid; + Av2DmRational source_display_duration; Av2DmIndeterminateReason indeterminate_reason; } Av2DmContextEvent; @@ -246,8 +238,11 @@ typedef struct Av2DmContext { } Av2DmContext; typedef struct Av2DmCvsAggregate { - bool open; + int xlayer_id; + bool input_open; + bool result_emitted; uint64_t number; + uint64_t live_runs; uint64_t violations; uint64_t run_status_count[4]; bool verification_complete; @@ -268,6 +263,7 @@ struct Av2DecoderModelVerifier { bool aggregate_incomplete; bool error_emitted; int check_mode; + bool defer_nonterminal_checks_for_testing; Av2DmVerifierErrorCode error_code; bool temporal_point_present; bool temporal_unit_has_obu; @@ -285,11 +281,15 @@ struct Av2DecoderModelVerifier { uint64_t next_generation; uint64_t frame_starts; uint64_t reference_updates; + uint64_t reference_invalidations; uint64_t olk_invalidations; + uint64_t clk_invalidations; uint64_t outputs; uint64_t last_frame_start_event; uint64_t last_reference_update_event; + uint64_t last_reference_invalidation_event; uint64_t last_olk_invalidation_event; + uint64_t last_clk_invalidation_event; uint64_t last_output_event; uint64_t last_output_callback_frame_unit; uint64_t last_output_presentation_frame_unit; @@ -349,13 +349,20 @@ struct Av2DecoderModelVerifier { bool current_source_frame_dispatched; bool clk_boundary_seen[MAX_NUM_XLAYERS]; uint64_t clk_boundary_temporal_unit[MAX_NUM_XLAYERS]; - Av2DmCvsAggregate cvs[MAX_NUM_XLAYERS]; + Av2DmCvsAggregate *cvs; + size_t cvs_count; + size_t cvs_capacity; + size_t current_cvs[MAX_NUM_XLAYERS]; + uint64_t next_cvs_number[MAX_NUM_XLAYERS]; uint64_t bitstream_cvs; uint64_t bitstream_status_count[4]; bool first_non_conformant_valid; int first_non_conformant_xlayer; uint64_t first_non_conformant_cvs; bool bitstream_result_emitted; + bool end_of_input_generation_valid; + uint64_t end_of_input_generation; + bool all_generations_end_of_input; }; static void mark_failed(Av2DecoderModelVerifier *verifier); @@ -368,8 +375,10 @@ static bool obu_belongs_to_context(const Av2DmContext *context, static void dispatch_context_event(Av2DecoderModelVerifier *verifier, Av2DmContext *context, const Av2DmContextEvent *event); -static void ensure_cvs_open(Av2DecoderModelVerifier *verifier, int xlayer_id); -static void finish_xlayer_cvs(Av2DecoderModelVerifier *verifier, int xlayer_id); +static Av2DmCvsAggregate *ensure_cvs_open(Av2DecoderModelVerifier *verifier, + int xlayer_id, size_t *cvs_index); +static void close_xlayer_cvs_input(Av2DecoderModelVerifier *verifier, + int xlayer_id); static void finish_all_cvs(Av2DecoderModelVerifier *verifier); static void emit_bitstream_result(Av2DecoderModelVerifier *verifier, bool complete); @@ -723,6 +732,8 @@ static bool build_context_config(const Av2DecoderModelVerifier *verifier, config->mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; config->stop_after_first_violation = verifier->check_mode == AVM_DECODER_MODEL_CHECK_FATAL; + config->defer_nonterminal_checks_for_testing = + verifier->defer_nonterminal_checks_for_testing; if (context->active_configuration_record >= verifier->active_record_count) { return true; @@ -1020,6 +1031,9 @@ void av2_decoder_model_verifier_init(AV2Decoder *pbi) { pbi->decoder_model_verifier == NULL; if (pbi->decoder_model_verifier != NULL) { pbi->decoder_model_verifier->check_mode = pbi->decoder_model_check_mode; + for (int i = 0; i < MAX_NUM_XLAYERS; ++i) { + pbi->decoder_model_verifier->current_cvs[i] = SIZE_MAX; + } } } @@ -1043,6 +1057,7 @@ void av2_decoder_model_verifier_destroy(AV2Decoder *pbi) { avm_free(verifier->active_records); avm_free(verifier->contexts); avm_free(verifier->generations); + avm_free(verifier->cvs); avm_free(verifier); pbi->decoder_model_verifier = NULL; pbi->decoder_model_verifier_allocation_failed = false; @@ -1360,11 +1375,11 @@ void av2_decoder_model_verifier_on_active_configuration( (!verifier->clk_boundary_seen[xlayer_id] || verifier->clk_boundary_temporal_unit[xlayer_id] != verifier->temporal_unit_index)) { - finish_xlayer_cvs(verifier, xlayer_id); + close_xlayer_cvs_input(verifier, xlayer_id); if (verifier->fatal_violation || verifier->failed) return; if (!reset_xlayer_cvs_obu_accounting(verifier, xlayer_id)) return; retire_xlayer_generations(verifier, pbi, xlayer_id); - ensure_cvs_open(verifier, xlayer_id); + if (ensure_cvs_open(verifier, xlayer_id, NULL) == NULL) return; verifier->clk_boundary_seen[xlayer_id] = true; verifier->clk_boundary_temporal_unit[xlayer_id] = verifier->temporal_unit_index; @@ -1876,16 +1891,22 @@ void av2_decoder_model_verifier_on_frame_unit_complete(AV2Decoder *pbi) { } } -void av2_decoder_model_verifier_on_olk_reference_invalidation(AV2Decoder *pbi) { +void av2_decoder_model_verifier_on_reference_invalidation( + AV2Decoder *pbi, bool closed_loop_key) { if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; if (!verifier_accepts_events(verifier)) return; const uint32_t ref_valid_mask = real_ref_valid_mask(pbi); Av2DmAdapterEvent *const adapter_event = - append_event(verifier, AV2_DM_ADAPTER_OLK_REFERENCE_INVALIDATION); + append_event(verifier, AV2_DM_ADAPTER_REFERENCE_INVALIDATION); if (adapter_event == NULL) return; adapter_event->value = ref_valid_mask; - verifier->last_olk_invalidation_event = adapter_event->index; + verifier->last_reference_invalidation_event = adapter_event->index; + if (closed_loop_key) { + verifier->last_clk_invalidation_event = adapter_event->index; + } else { + verifier->last_olk_invalidation_event = adapter_event->index; + } for (size_t i = 0; i < verifier->context_count; ++i) { Av2DmContext *const context = &verifier->contexts[i]; if (!context->active || !frame_belongs_to_context( @@ -1894,16 +1915,22 @@ void av2_decoder_model_verifier_on_olk_reference_invalidation(AV2Decoder *pbi) { continue; } Av2DmContextEvent storage; - initialize_context_event( - verifier, AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION, &storage); + initialize_context_event(verifier, AV2_DM_CONTEXT_REFERENCE_INVALIDATION, + &storage); Av2DmContextEvent *const event = &storage; event->event_index = adapter_event->index; event->source_frame_unit_index = verifier->source_frame_unit_index; event->ref_valid_mask = ref_valid_mask; + event->closed_loop_key_invalidation = closed_loop_key; event->leading_frame = pbi->common.is_leading_picture == 1; dispatch_context_event(verifier, context, event); } - (void)increment_u64(verifier, &verifier->olk_invalidations); + (void)increment_u64(verifier, &verifier->reference_invalidations); + if (closed_loop_key) { + (void)increment_u64(verifier, &verifier->clk_invalidations); + } else { + (void)increment_u64(verifier, &verifier->olk_invalidations); + } } void av2_decoder_model_verifier_after_reference_update( @@ -2145,6 +2172,7 @@ void av2_decoder_model_verifier_on_stream_configuration_change( typedef struct Av2DmRunReport { Av2DecoderModelVerifier *verifier; + size_t originating_cvs; Av2DmScope scope; Av2DmMode mode; int64_t rap; @@ -2162,10 +2190,16 @@ struct Av2DmLiveRun { Av2DecoderModel *model; Av2DmRunReport report; Av2DmConfig config; + uint64_t stream_generation; Av2DmIndeterminateReason reason; + size_t originating_cvs; int64_t rap; bool olk; uint64_t start_source_frame_unit; + bool terminal_frame_parsing_time_valid; + Av2DmRational terminal_frame_parsing_time; + bool terminal_display_duration_valid; + Av2DmRational terminal_display_duration; }; void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( @@ -2198,6 +2232,8 @@ static const char *indeterminate_reason_name(Av2DmIndeterminateReason reason) { return "missing_presentation_timing"; case AV2_DM_REASON_INCOMPLETE_RAS_SEED: return "incomplete_ras_seed"; case AV2_DM_REASON_RECOVERY_RESET: return "decoder_recovery_reset"; + case AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION: + return "incompatible_configuration_transition"; case AV2_DM_REASON_INTERNAL_FAILURE: return "internal_failure"; } return "internal_failure"; @@ -2766,13 +2802,16 @@ static const Av2DmContextEvent *find_context_event(const Av2DmRunReport *report, return NULL; } -static const char *context_event_type_name(Av2DmContextEventType type) { - switch (type) { +static const char *context_event_type_name(const Av2DmContextEvent *event) { + switch (event->type) { case AV2_DM_CONTEXT_FRAME: return "frame"; - case AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION: return "olk_invalidation"; + case AV2_DM_CONTEXT_REFERENCE_INVALIDATION: + return event->closed_loop_key_invalidation ? "clk_invalidation" + : "olk_invalidation"; case AV2_DM_CONTEXT_REFERENCE_UPDATE: return "reference_update"; case AV2_DM_CONTEXT_OUTPUT: return "output"; case AV2_DM_CONTEXT_RECOVERY_RESET: return "recovery_reset"; + case AV2_DM_CONTEXT_END_OF_INPUT: return "end_of_input"; } return "unknown"; } @@ -2783,7 +2822,7 @@ static void print_event_location(const Av2DmRunReport *report, find_context_event(report, event_index); if (event == NULL) return; fprintf(stderr, " event_type=%s frame_unit=%" PRIu64, - context_event_type_name(event->type), event->source_frame_unit_index); + context_event_type_name(event), event->source_frame_unit_index); if (event->type == AV2_DM_CONTEXT_FRAME) { fprintf(stderr, " temporal_unit=%" PRIu64, event->frame.temporal_unit_index); @@ -2883,16 +2922,41 @@ static Av2DmResultStatus aggregate_status(const uint64_t status_count[4]) { return AV2_DM_RESULT_NOT_APPLICABLE; } -static void ensure_cvs_open(Av2DecoderModelVerifier *verifier, int xlayer_id) { - if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return; - Av2DmCvsAggregate *const cvs = &verifier->cvs[xlayer_id]; - if (cvs->open) return; - (void)increment_u64(verifier, &cvs->number); - cvs->open = true; +static Av2DmCvsAggregate *ensure_cvs_open(Av2DecoderModelVerifier *verifier, + int xlayer_id, size_t *cvs_index) { + if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return NULL; + const size_t current = verifier->current_cvs[xlayer_id]; + if (current != SIZE_MAX) { + if (current >= verifier->cvs_count || + verifier->cvs[current].xlayer_id != xlayer_id || + !verifier->cvs[current].input_open) { + mark_failed(verifier); + return NULL; + } + if (cvs_index != NULL) *cvs_index = current; + return &verifier->cvs[current]; + } + if (verifier->cvs_count == SIZE_MAX || + verifier->next_cvs_number[xlayer_id] == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } + if (!reserve_array(verifier, (void **)&verifier->cvs, &verifier->cvs_capacity, + verifier->cvs_count + 1, sizeof(*verifier->cvs))) { + mark_failed(verifier); + return NULL; + } + const size_t index = verifier->cvs_count; + Av2DmCvsAggregate *const cvs = &verifier->cvs[index]; + memset(cvs, 0, sizeof(*cvs)); + cvs->xlayer_id = xlayer_id; + cvs->input_open = true; + cvs->number = ++verifier->next_cvs_number[xlayer_id]; cvs->verification_complete = true; - cvs->reason = AV2_DM_REASON_NONE; - cvs->violations = 0; - memset(cvs->run_status_count, 0, sizeof(cvs->run_status_count)); + verifier->current_cvs[xlayer_id] = index; + if (!increment_size(verifier, &verifier->cvs_count)) return NULL; + if (cvs_index != NULL) *cvs_index = index; + return cvs; } static void emit_result(Av2DecoderModelVerifier *verifier, @@ -2913,9 +2977,17 @@ static void emit_result(Av2DecoderModelVerifier *verifier, reason = AV2_DM_REASON_MISSING_REQUIRED_INPUT; } Av2DmCvsAggregate *cvs = NULL; - if (report != NULL && report->scope.xlayer_id >= 0 && - report->scope.xlayer_id < MAX_NUM_XLAYERS) { - cvs = &verifier->cvs[report->scope.xlayer_id]; + if (report != NULL && report->originating_cvs < verifier->cvs_count) { + Av2DmCvsAggregate *const candidate = + &verifier->cvs[report->originating_cvs]; + if (candidate->xlayer_id == report->scope.xlayer_id && + candidate->number == report->cvs) { + cvs = candidate; + } else { + mark_failed(verifier); + } + } else if (report != NULL) { + mark_failed(verifier); } if ((unsigned int)result.status >= 4) { @@ -2992,15 +3064,17 @@ static bool run_seed_contains_generation(const Av2DmLiveRun *run, return false; } +static bool run_accepts_event(const Av2DmLiveRun *run, + const Av2DmContextEvent *event) { + return !run->olk || + event->source_frame_unit_index <= run->start_source_frame_unit || + !event->leading_frame; +} + static void apply_event_to_run(Av2DecoderModelVerifier *verifier, Av2DmLiveRun *run, const Av2DmContextEvent *event) { - if (verifier->fatal_violation) return; - if (run->olk && - event->source_frame_unit_index > run->start_source_frame_unit && - event->leading_frame) { - return; - } + if (verifier->fatal_violation || !run_accepts_event(run, event)) return; run->report.current_event = *event; run->report.current_event_valid = true; if (event->indeterminate_reason != AV2_DM_REASON_NONE && @@ -3011,15 +3085,16 @@ static void apply_event_to_run(Av2DecoderModelVerifier *verifier, case AV2_DM_CONTEXT_FRAME: av2_decoder_model_start_frame(run->model, &event->frame); break; - case AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION: - av2_decoder_model_invalidate_olk_reference_buffers(run->model, - event->ref_valid_mask); + case AV2_DM_CONTEXT_REFERENCE_INVALIDATION: + av2_decoder_model_invalidate_reference_buffers( + run->model, event->ref_valid_mask, + event->closed_loop_key_invalidation); break; case AV2_DM_CONTEXT_REFERENCE_UPDATE: av2_decoder_model_update_reference_buffers(run->model, &event->reference_update); if (event->set_initial_presentation_delay) { - av2_decoder_model_set_initial_presentation_delay(run->model, + av2_decoder_model_set_initial_presentation_delay(run->model, false, event->event_index); } break; @@ -3041,19 +3116,94 @@ static void apply_event_to_run(Av2DecoderModelVerifier *verifier, break; } case AV2_DM_CONTEXT_RECOVERY_RESET: break; + case AV2_DM_CONTEXT_END_OF_INPUT: + av2_decoder_model_set_initial_presentation_delay(run->model, true, + event->event_index); + break; } run->report.current_event_valid = false; memset(&run->report.current_event, 0, sizeof(run->report.current_event)); } +static void capture_terminal_history(Av2DmContext *context, + const Av2DmContextEvent *event) { + if (context->run_count < 2 || + (event->type != AV2_DM_CONTEXT_FRAME && + event->type != AV2_DM_CONTEXT_OUTPUT) || + !run_accepts_event(context->runs[0], event) || + context->runs[0]->reason == + AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION) { + return; + } + Av2DmState source_state; + if (!av2_decoder_model_get_state(context->runs[0]->model, &source_state)) { + return; + } + for (size_t i = 1; i < context->run_count; ++i) { + Av2DmLiveRun *const run = context->runs[i]; + if (run->rap < 0 || run->stream_generation != event->stream_generation || + run->stream_generation != context->runs[0]->stream_generation || + !run_accepts_event(run, event)) { + continue; + } + if (event->type == AV2_DM_CONTEXT_FRAME && + source_state.last_frame_parsing_time_valid) { + run->terminal_frame_parsing_time = source_state.last_frame_parsing_time; + run->terminal_frame_parsing_time_valid = true; + } else if (event->type == AV2_DM_CONTEXT_OUTPUT && + source_state.last_display_duration_valid) { + run->terminal_display_duration = source_state.last_display_duration; + run->terminal_display_duration_valid = true; + } + } +} + +static void capture_replayed_terminal_history(const Av2DmContext *context, + Av2DmLiveRun *run, + const Av2DmContextEvent *event) { + if (event->type == AV2_DM_CONTEXT_OUTPUT && + event->source_display_duration_valid && context->run_count != 0 && + context->runs[0]->reason != + AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION && + run->stream_generation == event->stream_generation && + run_accepts_event(run, event)) { + run->terminal_display_duration = event->source_display_duration; + run->terminal_display_duration_valid = true; + } +} + +static void attach_source_terminal_history(const Av2DmContext *context, + Av2DmContextEvent *event) { + if (context->run_count == 0 || event->type != AV2_DM_CONTEXT_OUTPUT || + !run_accepts_event(context->runs[0], event) || + context->runs[0]->reason == + AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION) { + return; + } + Av2DmState source_state; + if (av2_decoder_model_get_state(context->runs[0]->model, &source_state) && + source_state.last_display_duration_valid) { + event->source_display_duration = source_state.last_display_duration; + event->source_display_duration_valid = true; + } +} + static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, Av2DmContext *context, const Av2DmContextEvent *start_frame, int64_t rap) { + size_t originating_cvs; + Av2DmCvsAggregate *const cvs = + ensure_cvs_open(verifier, context->key.xlayer_id, &originating_cvs); + if (cvs == NULL) return NULL; if (context->run_count == SIZE_MAX) { mark_arithmetic_failed(verifier); return NULL; } + if (cvs->live_runs == UINT64_MAX) { + mark_arithmetic_failed(verifier); + return NULL; + } if (!reserve_array(verifier, (void **)&context->runs, &context->run_capacity, context->run_count + 1, sizeof(*context->runs))) { mark_failed(verifier); @@ -3065,6 +3215,7 @@ static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, return NULL; } run->config = start_frame->config; + run->stream_generation = start_frame->stream_generation; run->reason = start_frame->indeterminate_reason; if (!start_frame->config_present || run->reason != AV2_DM_REASON_NONE) { run->config.applicability = AV2_DM_MISSING_REQUIRED_INPUT; @@ -3076,14 +3227,16 @@ static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, memcpy(run->config.ras_seeds, start_frame->ras_seeds, sizeof(run->config.ras_seeds)); } + run->originating_cvs = originating_cvs; run->rap = rap; run->olk = start_frame->frame_obu_type == OBU_OPEN_LOOP_KEY; run->start_source_frame_unit = start_frame->source_frame_unit_index; run->report.verifier = verifier; + run->report.originating_cvs = originating_cvs; run->report.scope = run->config.scope; run->report.mode = run->config.mode; run->report.rap = rap; - run->report.cvs = verifier->cvs[context->key.xlayer_id].number; + run->report.cvs = cvs->number; run->report.level_idx = run->config.level_idx; run->report.tier = run->config.tier; if (run->config.level_limits_present) { @@ -3110,14 +3263,39 @@ static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, avm_free(run); return NULL; } + ++cvs->live_runs; return run; } +static void release_run_cvs_ownership(Av2DecoderModelVerifier *verifier, + const Av2DmLiveRun *run) { + if (run->originating_cvs >= verifier->cvs_count || + verifier->cvs[run->originating_cvs].live_runs == 0) { + mark_failed(verifier); + return; + } + --verifier->cvs[run->originating_cvs].live_runs; +} + static void finish_context_runs(Av2DecoderModelVerifier *verifier, Av2DmContext *context) { for (size_t i = 0; i < context->run_count; ++i) { Av2DmLiveRun *const run = context->runs[i]; if (!verifier->failed && !verifier->fatal_violation) { + if (i != 0 && run->rap >= 0) { + const Av2DmRational *const frame_parsing_time = + run->terminal_frame_parsing_time_valid + ? &run->terminal_frame_parsing_time + : NULL; + const Av2DmRational *const display_duration = + run->terminal_display_duration_valid + ? &run->terminal_display_duration + : NULL; + if (!av2_decoder_model_seed_terminal_history( + run->model, frame_parsing_time, display_duration)) { + mark_failed(verifier); + } + } av2_decoder_model_finish(run->model); } Av2DmResult result; @@ -3136,6 +3314,7 @@ static void finish_context_runs(Av2DecoderModelVerifier *verifier, } else { mark_failed(verifier); } + release_run_cvs_ownership(verifier, run); av2_decoder_model_destroy(run->model); avm_free(run); } @@ -3161,6 +3340,7 @@ static void finish_partial_context_runs(Av2DecoderModelVerifier *verifier, } else { mark_failed(verifier); } + release_run_cvs_ownership(verifier, run); av2_decoder_model_destroy(run->model); avm_free(run); } @@ -3178,7 +3358,8 @@ static void destroy_context_runs(Av2DmContext *context) { static bool prefix_event_applies(const Av2DmLiveRun *run, const Av2DmContextEvent *event) { if (run->olk) { - return event->type == AV2_DM_CONTEXT_OLK_REFERENCE_INVALIDATION; + return event->type == AV2_DM_CONTEXT_REFERENCE_INVALIDATION && + !event->closed_loop_key_invalidation; } if (run->config.ras_start) { return event->type == AV2_DM_CONTEXT_OUTPUT && @@ -3191,13 +3372,24 @@ static void update_live_run_parameters(Av2DmLiveRun *run, const Av2DmContextEvent *event) { run->report.current_event = *event; run->report.current_event_valid = true; + const Av2DmParameterUpdateDisposition disposition = + av2_decoder_model_classify_parameter_update( + run->model, &event->config, event->frame.coded_as_closed_loop_key); const bool updated = av2_decoder_model_update_parameters( - run->model, &event->config, event->event_index); + run->model, &event->config, event->event_index, + event->frame.coded_as_closed_loop_key); run->report.current_event_valid = false; memset(&run->report.current_event, 0, sizeof(run->report.current_event)); if (!updated) { - if (run->reason == AV2_DM_REASON_NONE) { - run->reason = AV2_DM_REASON_MISSING_REQUIRED_INPUT; + Av2DmResult result; + const bool non_conformant = + av2_decoder_model_get_result(run->model, &result) && + result.status == AV2_DM_RESULT_NON_CONFORMANT; + if (!non_conformant && run->reason == AV2_DM_REASON_NONE) { + run->reason = + disposition == AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION + ? AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION + : AV2_DM_REASON_MISSING_REQUIRED_INPUT; } return; } @@ -3231,12 +3423,27 @@ static void update_live_run_parameters(Av2DmLiveRun *run, } } -static void mark_live_runs_incomplete(Av2DmContext *context) { +void av2_decoder_model_verifier_set_defer_nonterminal_checks_for_testing( + AV2Decoder *pbi, bool defer) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + verifier->defer_nonterminal_checks_for_testing = defer; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + for (size_t j = 0; j < context->run_count; ++j) { + av2_decoder_model_set_defer_nonterminal_checks_for_testing( + context->runs[j]->model, defer); + } + } +} + +static void mark_live_runs_for_incompatible_configuration( + Av2DmContext *context) { for (size_t i = 0; i < context->run_count; ++i) { Av2DmLiveRun *const run = context->runs[i]; av2_decoder_model_mark_incomplete(run->model); if (run->reason == AV2_DM_REASON_NONE) { - run->reason = AV2_DM_REASON_MISSING_REQUIRED_INPUT; + run->reason = AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION; } } } @@ -3250,6 +3457,7 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, ++i) { apply_event_to_run(verifier, context->runs[i], event); } + if (!verifier->fatal_violation) capture_terminal_history(context, event); if (!verifier->current_source_frame_dispatched) { if (context->prefix_event_count == SIZE_MAX) { mark_arithmetic_failed(verifier); @@ -3263,12 +3471,14 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, return; } context->prefix_events[context->prefix_event_count] = *event; + attach_source_terminal_history( + context, &context->prefix_events[context->prefix_event_count]); if (!increment_size(verifier, &context->prefix_event_count)) return; } return; } - ensure_cvs_open(verifier, context->key.xlayer_id); + if (ensure_cvs_open(verifier, context->key.xlayer_id, NULL) == NULL) return; const bool config_changed = context->last_config_present && (context->last_stream_generation != event->stream_generation || @@ -3285,7 +3495,7 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, // Annex E resets FirstBitArrival only when a new parameter set is // received at a random-access point. A different transition cannot be // verified by silently restarting the sequential model. - mark_live_runs_incomplete(context); + mark_live_runs_for_incompatible_configuration(context); } } @@ -3299,6 +3509,8 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, for (size_t i = 0; i < context->prefix_event_count; ++i) { if (prefix_event_applies(run, &context->prefix_events[i])) { apply_event_to_run(verifier, run, &context->prefix_events[i]); + capture_replayed_terminal_history(context, run, + &context->prefix_events[i]); } } created_segment = true; @@ -3310,6 +3522,8 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, for (size_t i = 0; i < context->prefix_event_count; ++i) { if (prefix_event_applies(run, &context->prefix_events[i])) { apply_event_to_run(verifier, run, &context->prefix_events[i]); + capture_replayed_terminal_history(context, run, + &context->prefix_events[i]); } } } @@ -3317,6 +3531,7 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, ++i) { apply_event_to_run(verifier, context->runs[i], event); } + if (!verifier->fatal_violation) capture_terminal_history(context, event); context->last_config_present = true; context->last_config = event->config; context->last_stream_generation = event->stream_generation; @@ -3324,27 +3539,93 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, context->last_ras_seed_count = event->ras_seed_count; } -static void finish_xlayer_cvs_internal(Av2DecoderModelVerifier *verifier, - int xlayer_id, bool partial) { - if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS || - !verifier->cvs[xlayer_id].open) { - return; +void av2_decoder_model_verifier_before_final_output(AV2Decoder *pbi, + uint64_t stream_generation, + bool all_generations) { + if (pbi == NULL || pbi->decoder_model_verifier == NULL) return; + Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + if (!verifier_accepts_events(verifier)) return; + if (all_generations) { + if (verifier->all_generations_end_of_input) return; + } else { + if (stream_generation == UINT64_MAX) { + stream_generation = verifier->stream_generation; + } + if (verifier->end_of_input_generation_valid && + verifier->end_of_input_generation == stream_generation) { + return; + } } - partial = partial || verifier->failed || verifier->fatal_violation; - for (size_t i = 0; i < verifier->context_count; ++i) { + + bool target_found = false; + for (size_t i = 0; i < verifier->context_count && !target_found; ++i) { + const Av2DmContext *const context = &verifier->contexts[i]; + for (size_t j = 0; j < context->run_count; ++j) { + if (all_generations || + context->runs[j]->stream_generation == stream_generation) { + target_found = true; + break; + } + } + } + if (!target_found) return; + + Av2DmAdapterEvent *const adapter_event = + append_event(verifier, AV2_DM_ADAPTER_END_OF_INPUT); + if (adapter_event == NULL) return; + if (all_generations) { + verifier->all_generations_end_of_input = true; + } else { + verifier->end_of_input_generation_valid = true; + verifier->end_of_input_generation = stream_generation; + } + + for (size_t i = 0; i < verifier->context_count && + !verifier->fatal_violation && !verifier->failed; + ++i) { Av2DmContext *const context = &verifier->contexts[i]; - if (context->key.xlayer_id != xlayer_id) continue; - if (partial) { - finish_partial_context_runs(verifier, context); - } else { - finish_context_runs(verifier, context); + for (size_t j = 0; j < context->run_count && !verifier->fatal_violation && + !verifier->failed; + ++j) { + Av2DmLiveRun *const run = context->runs[j]; + if (!all_generations && run->stream_generation != stream_generation) { + continue; + } + Av2DmContextEvent storage; + initialize_context_event(verifier, AV2_DM_CONTEXT_END_OF_INPUT, &storage); + storage.event_index = adapter_event->index; + storage.source_frame_unit_index = verifier->source_frame_unit_index; + storage.stream_generation = run->stream_generation; + apply_event_to_run(verifier, run, &storage); } - if (verifier->failed || verifier->fatal_violation) partial = true; - context->prefix_event_count = 0; - context->last_config_present = false; - rebuild_incomplete_extraction(verifier, context); } - Av2DmCvsAggregate *const cvs = &verifier->cvs[xlayer_id]; +} + +static void close_xlayer_cvs_input(Av2DecoderModelVerifier *verifier, + int xlayer_id) { + if (xlayer_id < 0 || xlayer_id >= MAX_NUM_XLAYERS) return; + const size_t current = verifier->current_cvs[xlayer_id]; + if (current == SIZE_MAX) return; + if (current >= verifier->cvs_count) { + mark_failed(verifier); + return; + } + Av2DmCvsAggregate *const cvs = &verifier->cvs[current]; + if (cvs->xlayer_id != xlayer_id || !cvs->input_open) { + mark_failed(verifier); + return; + } + cvs->input_open = false; + verifier->current_cvs[xlayer_id] = SIZE_MAX; +} + +static void emit_cvs_result(Av2DecoderModelVerifier *verifier, + Av2DmCvsAggregate *cvs, bool partial) { + if (cvs->result_emitted) return; + if (cvs->input_open || cvs->live_runs != 0) { + mark_failed(verifier); + return; + } if (partial) cvs->verification_complete = false; if (verifier->failed) { cvs->verification_complete = false; @@ -3376,33 +3657,52 @@ static void finish_xlayer_cvs_internal(Av2DecoderModelVerifier *verifier, if (status == AV2_DM_RESULT_NON_CONFORMANT && !verifier->first_non_conformant_valid) { verifier->first_non_conformant_valid = true; - verifier->first_non_conformant_xlayer = xlayer_id; + verifier->first_non_conformant_xlayer = cvs->xlayer_id; verifier->first_non_conformant_cvs = cvs->number; } fprintf(stderr, "AV2_DECODER_MODEL_CVS_RESULT status=%s xlayer=%d cvs=%" PRIu64 " violations=%" PRIu64 " verification_complete=%d reason=%s\n", - result_name(status), xlayer_id, cvs->number, cvs->violations, + result_name(status), cvs->xlayer_id, cvs->number, cvs->violations, cvs->verification_complete ? 1 : 0, indeterminate_reason_name(cvs->reason)); - cvs->open = false; + cvs->result_emitted = true; } -static void finish_xlayer_cvs(Av2DecoderModelVerifier *verifier, - int xlayer_id) { - finish_xlayer_cvs_internal(verifier, xlayer_id, false); +static void finish_all_cvs_internal(Av2DecoderModelVerifier *verifier, + bool partial) { + partial = partial || verifier->failed || verifier->fatal_violation; + for (size_t i = 0; i < verifier->context_count; ++i) { + Av2DmContext *const context = &verifier->contexts[i]; + if (partial) { + finish_partial_context_runs(verifier, context); + } else { + finish_context_runs(verifier, context); + } + if (verifier->failed || verifier->fatal_violation) partial = true; + context->prefix_event_count = 0; + context->last_config_present = false; + rebuild_incomplete_extraction(verifier, context); + } + for (int xlayer_id = 0; xlayer_id < MAX_NUM_XLAYERS; ++xlayer_id) { + close_xlayer_cvs_input(verifier, xlayer_id); + } + for (int xlayer_id = 0; xlayer_id < MAX_NUM_XLAYERS; ++xlayer_id) { + for (size_t i = 0; i < verifier->cvs_count; ++i) { + Av2DmCvsAggregate *const cvs = &verifier->cvs[i]; + if (cvs->xlayer_id == xlayer_id) { + emit_cvs_result(verifier, cvs, partial); + } + } + } } static void finish_all_cvs(Av2DecoderModelVerifier *verifier) { - for (int xlayer_id = 0; xlayer_id < MAX_NUM_XLAYERS; ++xlayer_id) { - finish_xlayer_cvs(verifier, xlayer_id); - } + finish_all_cvs_internal(verifier, false); } static void finish_all_cvs_partial(Av2DecoderModelVerifier *verifier) { - for (int xlayer_id = 0; xlayer_id < MAX_NUM_XLAYERS; ++xlayer_id) { - finish_xlayer_cvs_internal(verifier, xlayer_id, true); - } + finish_all_cvs_internal(verifier, true); } static void emit_bitstream_result(Av2DecoderModelVerifier *verifier, @@ -3437,9 +3737,10 @@ static void find_error_location(const Av2DecoderModelVerifier *verifier, *xlayer_id = -1; *cvs = 0; for (int i = 0; i < MAX_NUM_XLAYERS; ++i) { - if (verifier->cvs[i].open) { + const size_t current = verifier->current_cvs[i]; + if (current < verifier->cvs_count && verifier->cvs[current].input_open) { *xlayer_id = i; - *cvs = verifier->cvs[i].number; + *cvs = verifier->cvs[current].number; return; } } @@ -3536,11 +3837,16 @@ bool av2_decoder_model_verifier_get_stats(const AV2Decoder *pbi, stats->contexts = saturate_size_to_u32(verifier->context_count); stats->frame_starts = verifier->frame_starts; stats->reference_updates = verifier->reference_updates; + stats->reference_invalidations = verifier->reference_invalidations; stats->olk_invalidations = verifier->olk_invalidations; + stats->clk_invalidations = verifier->clk_invalidations; stats->outputs = verifier->outputs; stats->last_frame_start_event = verifier->last_frame_start_event; stats->last_reference_update_event = verifier->last_reference_update_event; + stats->last_reference_invalidation_event = + verifier->last_reference_invalidation_event; stats->last_olk_invalidation_event = verifier->last_olk_invalidation_event; + stats->last_clk_invalidation_event = verifier->last_clk_invalidation_event; stats->last_output_event = verifier->last_output_event; stats->last_output_callback_frame_unit = verifier->last_output_callback_frame_unit; @@ -3580,6 +3886,12 @@ bool av2_decoder_model_verifier_get_stats(const AV2Decoder *pbi, verifier->contexts[i].run_count); } stats->live_generations = saturate_size_to_u32(verifier->generation_count); + stats->cvs_aggregates = saturate_size_to_u32(verifier->cvs_count); + for (size_t i = 0; i < verifier->cvs_count; ++i) { + if (verifier->cvs[i].input_open && stats->open_cvs != UINT32_MAX) { + ++stats->open_cvs; + } + } add_size_to_saturated_u32(&stats->parameter_records, verifier->sequence_record_count); add_size_to_saturated_u32(&stats->parameter_records, @@ -3632,3 +3944,46 @@ bool av2_decoder_model_verifier_get_context_stats(const AV2Decoder *pbi, stats->last_ras_seed_count = context->last_ras_seed_count; return true; } + +bool av2_decoder_model_verifier_get_run_stats(const AV2Decoder *pbi, + uint32_t context_index, + uint32_t run_index, + Av2DmRunStats *stats) { + if (stats == NULL || pbi == NULL || pbi->decoder_model_verifier == NULL || + context_index >= pbi->decoder_model_verifier->context_count) { + return false; + } + const Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; + const Av2DmContext *const context = &verifier->contexts[context_index]; + if (run_index >= context->run_count) return false; + const Av2DmLiveRun *const run = context->runs[run_index]; + if (run == NULL || run->originating_cvs >= verifier->cvs_count) return false; + const Av2DmCvsAggregate *const cvs = &verifier->cvs[run->originating_cvs]; + if (cvs->xlayer_id != context->key.xlayer_id) return false; + Av2DmResult result; + if (!av2_decoder_model_get_result(run->model, &result)) return false; + Av2DmState state; + if (!av2_decoder_model_get_state(run->model, &state)) return false; + memset(stats, 0, sizeof(*stats)); + stats->originating_cvs = cvs->number; + stats->stream_generation = run->stream_generation; + stats->rap = run->rap; + stats->decoded_frames = result.decoded_frames; + stats->output_frames = result.output_frames; + stats->active_num_ref_frames = state.buffer_pool.num_ref_frames; + stats->initial_presentation_delay_known = + state.initial_presentation_delay_known; + stats->initial_presentation_delay = state.initial_presentation_delay; + stats->last_frame_parsing_time_valid = state.last_frame_parsing_time_valid; + stats->last_frame_parsing_time = state.last_frame_parsing_time; + stats->last_display_duration_valid = state.last_display_duration_valid; + stats->last_display_duration = state.last_display_duration; + stats->terminal_frame_parsing_time_valid = + run->terminal_frame_parsing_time_valid; + stats->terminal_frame_parsing_time = run->terminal_frame_parsing_time; + stats->terminal_display_duration_valid = run->terminal_display_duration_valid; + stats->terminal_display_duration = run->terminal_display_duration; + stats->status = result.status; + stats->reason = run->reason; + return true; +} diff --git a/av2/decoder/decoder_model.h b/av2/decoder/decoder_model.h index 7177980b40..cee2deccee 100644 --- a/av2/decoder/decoder_model.h +++ b/av2/decoder/decoder_model.h @@ -32,6 +32,20 @@ typedef enum Av2DmPresentationOwner { AV2_DM_PRESENTATION_OWNER_IMPLICIT } Av2DmPresentationOwner; +typedef enum Av2DmIndeterminateReason { + AV2_DM_REASON_NONE, + AV2_DM_REASON_MISSING_REQUIRED_INPUT, + AV2_DM_REASON_MISSING_ACTIVE_CONFIGURATION, + AV2_DM_REASON_INCOMPLETE_EXTRACTION, + AV2_DM_REASON_MISSING_FRAME_GENERATION, + AV2_DM_REASON_MISSING_PRESENTATION_PROVENANCE, + AV2_DM_REASON_MISSING_PRESENTATION_TIMING, + AV2_DM_REASON_INCOMPLETE_RAS_SEED, + AV2_DM_REASON_RECOVERY_RESET, + AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION, + AV2_DM_REASON_INTERNAL_FAILURE +} Av2DmIndeterminateReason; + typedef struct Av2DmVerifierStats { bool available; bool failed; @@ -48,11 +62,15 @@ typedef struct Av2DmVerifierStats { uint32_t contexts; uint64_t frame_starts; uint64_t reference_updates; + uint64_t reference_invalidations; uint64_t olk_invalidations; + uint64_t clk_invalidations; uint64_t outputs; uint64_t last_frame_start_event; uint64_t last_reference_update_event; + uint64_t last_reference_invalidation_event; uint64_t last_olk_invalidation_event; + uint64_t last_clk_invalidation_event; uint64_t last_output_event; uint64_t last_output_callback_frame_unit; uint64_t last_output_presentation_frame_unit; @@ -74,6 +92,8 @@ typedef struct Av2DmVerifierStats { uint64_t not_applicable_results; uint32_t live_runs; uint32_t live_generations; + uint32_t cvs_aggregates; + uint32_t open_cvs; uint32_t parameter_records; } Av2DmVerifierStats; @@ -94,6 +114,27 @@ typedef struct Av2DmContextStats { uint32_t last_ras_seed_count; } Av2DmContextStats; +typedef struct Av2DmRunStats { + uint64_t originating_cvs; + uint64_t stream_generation; + int64_t rap; + uint64_t decoded_frames; + uint64_t output_frames; + uint32_t active_num_ref_frames; + bool initial_presentation_delay_known; + Av2DmRational initial_presentation_delay; + bool last_frame_parsing_time_valid; + Av2DmRational last_frame_parsing_time; + bool last_display_duration_valid; + Av2DmRational last_display_duration; + bool terminal_frame_parsing_time_valid; + Av2DmRational terminal_frame_parsing_time; + bool terminal_display_duration_valid; + Av2DmRational terminal_display_duration; + Av2DmResultStatus status; + Av2DmIndeterminateReason reason; +} Av2DmRunStats; + void av2_decoder_model_verifier_init(struct AV2Decoder *pbi); void av2_decoder_model_verifier_destroy(struct AV2Decoder *pbi); @@ -120,6 +161,8 @@ void av2_decoder_model_verifier_on_internal_failure_for_testing( struct AV2Decoder *pbi); void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( struct AV2Decoder *pbi); +void av2_decoder_model_verifier_set_defer_nonterminal_checks_for_testing( + struct AV2Decoder *pbi, bool defer); void av2_decoder_model_verifier_on_temporal_point(struct AV2Decoder *pbi, uint64_t presentation_time); @@ -128,8 +171,8 @@ void av2_decoder_model_verifier_on_multistream_configuration( void av2_decoder_model_verifier_on_frame_wrapup_start(struct AV2Decoder *pbi); void av2_decoder_model_verifier_on_frame_unit_complete(struct AV2Decoder *pbi); -void av2_decoder_model_verifier_on_olk_reference_invalidation( - struct AV2Decoder *pbi); +void av2_decoder_model_verifier_on_reference_invalidation( + struct AV2Decoder *pbi, bool closed_loop_key); void av2_decoder_model_verifier_after_reference_update( struct AV2Decoder *pbi, uint32_t refresh_frame_flags); void av2_decoder_model_verifier_on_output(struct AV2Decoder *pbi, @@ -139,6 +182,9 @@ void av2_decoder_model_verifier_on_output(struct AV2Decoder *pbi, void av2_decoder_model_verifier_on_recovery_reset(struct AV2Decoder *pbi); void av2_decoder_model_verifier_on_stream_configuration_change( struct AV2Decoder *pbi, bool preserve_current_tu_prefix); +void av2_decoder_model_verifier_before_final_output(struct AV2Decoder *pbi, + uint64_t stream_generation, + bool all_generations); void av2_decoder_model_verifier_finish(struct AV2Decoder *pbi); bool av2_decoder_model_verifier_should_stop(const struct AV2Decoder *pbi); @@ -147,6 +193,10 @@ bool av2_decoder_model_verifier_get_stats(const struct AV2Decoder *pbi, bool av2_decoder_model_verifier_get_context_stats(const struct AV2Decoder *pbi, uint32_t context_index, Av2DmContextStats *stats); +bool av2_decoder_model_verifier_get_run_stats(const struct AV2Decoder *pbi, + uint32_t context_index, + uint32_t run_index, + Av2DmRunStats *stats); // Internal test support for the machine-readable violation diagnostics. bool av2_decoder_model_violation_descriptor_is_complete( diff --git a/av2/decoder/obu.c b/av2/decoder/obu.c index 118a42367f..0a03c75703 100644 --- a/av2/decoder/obu.c +++ b/av2/decoder/obu.c @@ -513,6 +513,12 @@ static uint32_t read_multi_stream_decoder_operation_obu( // Flush remaining frames from all active streams before switching config if (pbi->stream_info != NULL && config_changed) { + av2_decoder_model_verifier_before_final_output(pbi, UINT64_MAX, false); + if (av2_decoder_model_verifier_should_stop(pbi)) { + av2_decoder_model_verifier_finish(pbi); + avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM, + "Decoder model conformance violation"); + } flush_all_xlayer_frames(pbi, cm, true); if (pbi->decoder_model_verifier != NULL) { // The temporal delimiter and new MSDO have already been recorded and @@ -2436,6 +2442,13 @@ int avm_decode_frame_from_obus(struct AV2Decoder *pbi, const uint8_t *data, if (!pbi->prescan_glcr_will_activate) { // Flush and reset like a config change if (pbi->stream_info != NULL) { + av2_decoder_model_verifier_before_final_output(pbi, UINT64_MAX, + false); + if (av2_decoder_model_verifier_should_stop(pbi)) { + av2_decoder_model_verifier_finish(pbi); + avm_internal_error(&cm->error, AVM_CODEC_UNSUP_BITSTREAM, + "Decoder model conformance violation"); + } flush_all_xlayer_frames(pbi, cm, true); if (pbi->decoder_model_verifier != NULL) { // This transition is detected before the current frame unit's diff --git a/test/decoder_model_integration_test.cc b/test/decoder_model_integration_test.cc index 9472475fda..0339cea618 100644 --- a/test/decoder_model_integration_test.cc +++ b/test/decoder_model_integration_test.cc @@ -82,8 +82,11 @@ class DecoderModelAdapterTestBase : public ::testing::Test { FRAME_TYPE frame_type = KEY_FRAME, bool implicit_output = false, bool complete = true, int xlayer_id = 0, int temporal_id = 0, - bool activate_configuration = false, - bool filter_obu = false) { + bool activate_configuration = false, bool filter_obu = false, + bool invalidate_references = false, + bool leading_frame = false, int64_t presentation_time = -1, + const RefCntBuffer *prefix_output = nullptr, + int prefix_output_ref_idx = -1) { if (frame == nullptr) frame = &frame_; av2_decoder_model_verifier_on_source_frame_unit_start( pbi_, xlayer_id, mlayer_id, temporal_id); @@ -95,7 +98,7 @@ class DecoderModelAdapterTestBase : public ::testing::Test { cm->xlayer_id = xlayer_id; cm->mlayer_id = mlayer_id; cm->tlayer_id = temporal_id; - cm->is_leading_picture = 0; + cm->is_leading_picture = leading_frame; cm->show_existing_frame = 0; cm->implicit_output_picture = implicit_output; cm->cur_frame = frame; @@ -118,10 +121,23 @@ class DecoderModelAdapterTestBase : public ::testing::Test { frame->width = width; frame->height = height; frame->implicit_output_picture = implicit_output; + if (prefix_output != nullptr) { + av2_decoder_model_verifier_on_output(pbi_, prefix_output_ref_idx, + prefix_output, + AV2_DM_PRESENTATION_OWNER_IMPLICIT); + } if (activate_configuration) { av2_decoder_model_verifier_on_active_configuration(pbi_, xlayer_id, 0); } + if (invalidate_references) { + av2_decoder_model_verifier_on_reference_invalidation( + pbi_, obu_type == OBU_CLOSED_LOOP_KEY); + } av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + if (presentation_time >= 0) { + av2_decoder_model_verifier_on_temporal_point( + pbi_, static_cast(presentation_time)); + } if (complete) { av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, xlayer_id, mlayer_id, temporal_id, 80); @@ -595,7 +611,7 @@ TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { pbi_->obu_type = OBU_OPEN_LOOP_KEY; pbi_->common.ref_frame_map[0] = nullptr; pbi_->valid_for_referencing[0] = 0; - av2_decoder_model_verifier_on_olk_reference_invalidation(pbi_); + av2_decoder_model_verifier_on_reference_invalidation(pbi_, false); Av2DmVerifierStats before_start; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_start)); av2_decoder_model_verifier_record_obu(pbi_, OBU_OPEN_LOOP_KEY, 0, 0, 0, 800); @@ -609,10 +625,14 @@ TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { Av2DmVerifierStats after_start; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_start)); + EXPECT_EQ(after_start.reference_invalidations, 1u); EXPECT_EQ(after_start.olk_invalidations, 1u); + EXPECT_EQ(after_start.clk_invalidations, 0u); EXPECT_EQ(after_start.frame_starts, 2u); EXPECT_EQ(before_start.last_olk_invalidation_event, after_start.last_olk_invalidation_event); + EXPECT_EQ(after_start.last_reference_invalidation_event, + after_start.last_olk_invalidation_event); EXPECT_LT(after_start.last_olk_invalidation_event, after_start.last_frame_start_event); @@ -641,6 +661,53 @@ TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { std::string::npos); } +TEST_F(DecoderModelHookOrderTest, ClkInvalidationPrecedesClkFrameStartOnce) { + av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 0, 0, 0); + av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 0, 0, 0, + 800); + pbi_->obu_type = OBU_CLOSED_LOOP_KEY; + pbi_->common.xlayer_id = 0; + pbi_->common.mlayer_id = 0; + pbi_->common.tlayer_id = 0; + pbi_->common.show_existing_frame = 0; + pbi_->common.cur_frame = &frame_; + pbi_->common.width = 64; + pbi_->common.height = 64; + pbi_->common.mi_params.mi_cols = 16; + pbi_->common.mi_params.mi_rows = 16; + pbi_->common.mib_size_log2 = 0; + pbi_->common.tiles.cols = 1; + pbi_->common.tiles.rows = 1; + pbi_->common.tiles.col_start_sb[0] = 0; + pbi_->common.tiles.col_start_sb[1] = 16; + pbi_->common.tiles.row_start_sb[0] = 0; + pbi_->common.tiles.row_start_sb[1] = 16; + pbi_->common.current_frame.frame_type = KEY_FRAME; + frame_.xlayer_id = 0; + frame_.mlayer_id = 0; + frame_.tlayer_id = 0; + frame_.width = 64; + frame_.height = 64; + + av2_decoder_model_verifier_on_reference_invalidation(pbi_, true); + Av2DmVerifierStats before_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_start)); + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + + Av2DmVerifierStats after_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_start)); + EXPECT_EQ(after_start.reference_invalidations, 1u); + EXPECT_EQ(after_start.olk_invalidations, 0u); + EXPECT_EQ(after_start.clk_invalidations, 1u); + EXPECT_EQ(before_start.last_clk_invalidation_event, + after_start.last_clk_invalidation_event); + EXPECT_EQ(after_start.last_reference_invalidation_event, + after_start.last_clk_invalidation_event); + EXPECT_LT(after_start.last_clk_invalidation_event, + after_start.last_frame_start_event); +} + TEST_F(DecoderModelHookOrderTest, DelayedImplicitAndCurrentOutputsKeepSeparateProvenance) { pbi_->seq_list[1][0] = pbi_->seq_list[0][0]; @@ -840,16 +907,37 @@ TEST_F(DecoderModelHookOrderTest, av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_RESULT status=CONFORMANT " - "xlayer=0 ops=-1 op=-1 rap=0 mode=resource " - "decoded=1 outputs=1"), - std::string::npos); - EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " - "xlayer=0 cvs=1"), + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_RESULT "), std::string::npos); + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT "), std::string::npos); Av2DmContextStats context; ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); EXPECT_EQ(context.pending_dfg_bits, 800u); + Av2DmVerifierStats boundary; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &boundary)); + EXPECT_EQ(boundary.live_runs, 1u); + EXPECT_EQ(boundary.cvs_aggregates, 2u); + EXPECT_EQ(boundary.open_cvs, 1u); + + av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, 0, 0, 0, 80); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + av2_decoder_model_verifier_after_reference_update(pbi_, 1); + av2_decoder_model_verifier_on_output(pbi_, -1, &frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + Av2DmRunStats continuing; + Av2DmRunStats clk_start; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &continuing)); + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); + EXPECT_EQ(continuing.originating_cvs, 1u); + EXPECT_EQ(continuing.decoded_frames, 2u); + EXPECT_EQ(clk_start.originating_cvs, 2u); + EXPECT_EQ(clk_start.decoded_frames, 1u); + Av2DmVerifierStats delivered; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &delivered)); + EXPECT_LT(boundary.last_output_event, delivered.last_frame_start_event); } TEST_F(DecoderModelHookOrderTest, ClkBoundaryDropsPriorCvsPendingDfgBits) { @@ -916,14 +1004,15 @@ TEST_F(DecoderModelHookOrderTest, ClkBoundaryOnlyClosesItsXlayer) { testing::internal::CaptureStderr(); av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " - "xlayer=0 cvs=1"), + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT "), std::string::npos); EXPECT_EQ(diagnostics.find("xlayer=1 cvs=1"), std::string::npos); Av2DmVerifierStats stats; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); - EXPECT_EQ(stats.live_runs, 1u); + EXPECT_EQ(stats.live_runs, 2u); + EXPECT_EQ(stats.cvs_aggregates, 3u); + EXPECT_EQ(stats.open_cvs, 2u); } TEST_F(DecoderModelHookOrderTest, @@ -1214,14 +1303,49 @@ TEST_F(DecoderModelResultTest, FatalModeStopsBeforeOpeningThirdCvs) { const std::string diagnostics = testing::internal::GetCapturedStderr(); EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_WARNING "), 1u); - EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 cvs=1"), + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT xlayer=0 cvs=1"), std::string::npos); - EXPECT_NE(diagnostics.find("status=NON_CONFORMANT xlayer=0 cvs=2"), + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 cvs=2"), std::string::npos); EXPECT_NE(diagnostics.find("verification_complete=0"), std::string::npos); EXPECT_EQ(diagnostics.find("xlayer=0 cvs=3"), std::string::npos); - EXPECT_NE(diagnostics.find("status=NON_CONFORMANT complete=0 cvs=2"), + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT complete=0 cvs=2 "), + std::string::npos); + EXPECT_NE(diagnostics.find("first_non_conformant_xlayer=0 " + "first_non_conformant_cvs=1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + EndOfInputResolvesPendingChecksWithoutFalseViolation) { + pbi_->seq_list[0][0].seq_max_initial_display_delay_minus_1 = 9; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + Av2DmRunStats before; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &before)); + EXPECT_EQ(before.originating_cvs, 1u); + EXPECT_FALSE(before.initial_presentation_delay_known); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); + EXPECT_FALSE(av2_decoder_model_verifier_should_stop(pbi_)); + av2_decoder_model_verifier_on_output(pbi_, -1, &frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_WARNING "), std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=CONFORMANT xlayer=0 cvs=1"), std::string::npos); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.outputs, 2u); } TEST_F(DecoderModelResultTest, @@ -1245,9 +1369,10 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " - "xlayer=0 cvs=1"), - std::string::npos); + EXPECT_NE( + diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=NON_CONFORMANT " + "xlayer=0 cvs=1"), + std::string::npos); EXPECT_NE( diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=NON_CONFORMANT " "xlayer=0 cvs=2"), @@ -1259,7 +1384,7 @@ TEST_F(DecoderModelResultTest, "status=NON_CONFORMANT complete=1 cvs=3"), std::string::npos); EXPECT_NE(diagnostics.find("first_non_conformant_xlayer=0 " - "first_non_conformant_cvs=2"), + "first_non_conformant_cvs=1"), std::string::npos); } @@ -1287,16 +1412,18 @@ TEST_F(DecoderModelResultTest, EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " "status=INDETERMINATE xlayer=0 cvs=1"), std::string::npos); - EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE " "xlayer=0 cvs=2"), std::string::npos); EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " "status=INDETERMINATE xlayer=0 cvs=3"), std::string::npos); EXPECT_EQ(CountOccurrences(diagnostics, "reason=incomplete_extraction"), 4u); + EXPECT_EQ(CountOccurrences(diagnostics, "reason=missing_required_input"), 2u); } -TEST_F(DecoderModelResultTest, CompletedCvsReleasesLiveStorage) { +TEST_F(DecoderModelResultTest, ConsecutiveCvsRetainRequiredLiveRuns) { testing::internal::CaptureStderr(); for (int cvs = 0; cvs < 32; ++cvs) { av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, @@ -1306,9 +1433,11 @@ TEST_F(DecoderModelResultTest, CompletedCvsReleasesLiveStorage) { UpdateAndOutput(); Av2DmVerifierStats live; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); - EXPECT_EQ(live.live_runs, 1u); + EXPECT_EQ(live.live_runs, static_cast(cvs + 1)); EXPECT_LE(live.live_generations, 1u); EXPECT_LE(live.parameter_records, 2u); + EXPECT_EQ(live.cvs_aggregates, static_cast(cvs + 1)); + EXPECT_EQ(live.open_cvs, 1u); } av2_decoder_model_verifier_finish(pbi_); (void)testing::internal::GetCapturedStderr(); @@ -1320,6 +1449,636 @@ TEST_F(DecoderModelResultTest, CompletedCvsReleasesLiveStorage) { EXPECT_LE(finished.parameter_records, 2u); } +TEST_F(DecoderModelResultTest, + FifteenOneFrameCvsKeepStableOriginsAndContinuousHistory) { + pbi_->seq_list[0][0].still_picture = 0; + pbi_->seq_list[0][0].seq_max_initial_display_delay_minus_1 = 9; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + for (uint32_t cvs = 1; cvs <= 15; ++cvs) { + if (cvs > 1) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, + 0, 8); + } + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, + 0, 0, true); + UpdateAndOutput(); + + Av2DmVerifierStats verifier_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); + EXPECT_EQ(verifier_stats.cvs_aggregates, cvs); + EXPECT_EQ(verifier_stats.open_cvs, 1u); + EXPECT_EQ(verifier_stats.live_runs, cvs); + for (uint32_t run = 0; run < cvs; ++run) { + Av2DmRunStats run_stats; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, 0, run, &run_stats)); + EXPECT_EQ(run_stats.originating_cvs, run + 1); + EXPECT_EQ(run_stats.decoded_frames, cvs - run); + EXPECT_EQ(run_stats.output_frames, cvs - run); + EXPECT_EQ(run_stats.initial_presentation_delay_known, cvs - run >= 10); + } + } + + Av2DmVerifierStats before_end; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_end)); + av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); + Av2DmVerifierStats after_end; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_end)); + EXPECT_EQ(after_end.event_count, before_end.event_count + 1); + for (uint32_t run = 0; run < 15; ++run) { + Av2DmRunStats run_stats; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, 0, run, &run_stats)); + EXPECT_TRUE(run_stats.initial_presentation_delay_known); + } + av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); + Av2DmVerifierStats repeated; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &repeated)); + EXPECT_EQ(repeated.event_count, after_end.event_count); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(CountOccurrences(diagnostics, + "AV2_DECODER_MODEL_CVS_RESULT " + "status=CONFORMANT"), + 15u); + EXPECT_EQ(CountOccurrences(diagnostics, "status=INDETERMINATE"), 0u); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=CONFORMANT complete=1 cvs=15"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + OlkLeadingOutputDoesNotReplaceAcceptedTerminalDuration) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->still_picture = 0; + sequence->decoder_model_info_present_flag = 1; + sequence->seq_max_decoder_model_present_flag = 1; + sequence->seq_max_decoder_buffer_delay = 70000; + sequence->seq_max_encoder_buffer_delay = 20000; + sequence->seq_max_low_delay_mode_flag = 1; + pbi_->common.seq_params = *sequence; + pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + pbi_->common.brt_info.br_ops_dependent_flag = 0; + pbi_->common.brt_info.br_time = 0; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true, false, 0); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.brt_info.br_time = 10; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + StartFrame(OBU_OPEN_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 0, 0, false, false, true, false, 10); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + Av2DmRunStats olk_before_leading; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, + &olk_before_leading)); + ASSERT_TRUE(olk_before_leading.terminal_frame_parsing_time_valid); + ASSERT_TRUE(olk_before_leading.terminal_display_duration_valid); + EXPECT_FALSE(olk_before_leading.last_frame_parsing_time_valid); + EXPECT_FALSE(olk_before_leading.last_display_duration_valid); + ExpectAdapterRational(olk_before_leading.terminal_display_duration, 1, 3); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.brt_info.br_time = 30; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + RefCntBuffer leading_frame; + memset(&leading_frame, 0, sizeof(leading_frame)); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &leading_frame, 0, INTER_FRAME, + false, true, 0, 0, false, false, false, true, 30); + pbi_->common.ref_frame_map[2] = &leading_frame; + pbi_->valid_for_referencing[2] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 4); + av2_decoder_model_verifier_on_output(pbi_, -1, &leading_frame, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + Av2DmRunStats source; + Av2DmRunStats olk_after_leading; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &source)); + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &olk_after_leading)); + ASSERT_TRUE(source.last_frame_parsing_time_valid); + ASSERT_TRUE(source.last_display_duration_valid); + ExpectAdapterRational(source.last_display_duration, 2, 3); + EXPECT_EQ(olk_after_leading.decoded_frames, 1u); + EXPECT_EQ(olk_after_leading.output_frames, 1u); + EXPECT_FALSE(olk_after_leading.last_frame_parsing_time_valid); + EXPECT_FALSE(olk_after_leading.last_display_duration_valid); + ASSERT_TRUE(olk_after_leading.terminal_frame_parsing_time_valid); + ASSERT_TRUE(olk_after_leading.terminal_display_duration_valid); + ExpectAdapterRational(olk_after_leading.terminal_display_duration, 1, 3); + int parsing_comparison; + ASSERT_TRUE(av2_dm_rational_compare( + &olk_before_leading.terminal_frame_parsing_time, + &olk_after_leading.terminal_frame_parsing_time, &parsing_comparison)); + EXPECT_EQ(parsing_comparison, 0); + ASSERT_TRUE(av2_dm_rational_compare( + &source.last_frame_parsing_time, + &olk_after_leading.terminal_frame_parsing_time, &parsing_comparison)); + EXPECT_NE(parsing_comparison, 0); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 rap=1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, PrefixOutputReplaysItsExactDurationIntoRasRun) { + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->still_picture = 0; + sequence->decoder_model_info_present_flag = 1; + sequence->seq_max_decoder_model_present_flag = 1; + sequence->seq_max_decoder_buffer_delay = 70000; + sequence->seq_max_encoder_buffer_delay = 20000; + sequence->seq_max_low_delay_mode_flag = 1; + pbi_->common.seq_params = *sequence; + pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + pbi_->common.brt_info.br_ops_dependent_flag = 0; + pbi_->common.brt_info.br_time = 0; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + + frame_.long_term_id = -1; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true, false, 0); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.brt_info.br_time = 10; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + second_frame_.long_term_id = 5; + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME, + true, true, 0, 0, false, false, false, false, 10); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + + pbi_->common.brt_info.br_time = 20; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + RefCntBuffer ras_frame; + memset(&ras_frame, 0, sizeof(ras_frame)); + ras_frame.long_term_id = -1; + StartFrame(OBU_RAS_FRAME, 64, 64, &ras_frame, 0, KEY_FRAME, false, true, 0, 0, + false, false, false, false, 20, &second_frame_, 1); + pbi_->common.ref_frame_map[2] = &ras_frame; + pbi_->valid_for_referencing[2] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 4); + + Av2DmRunStats ras; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &ras)); + EXPECT_EQ(ras.decoded_frames, 1u); + EXPECT_EQ(ras.output_frames, 1u); + EXPECT_FALSE(ras.last_display_duration_valid); + ASSERT_TRUE(ras.terminal_frame_parsing_time_valid); + ASSERT_TRUE(ras.terminal_display_duration_valid); + ExpectAdapterRational(ras.terminal_display_duration, 1, 3); + + av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("xlayer=0 ops=-1 op=-1 rap=2 mode=schedule " + "decoded=1 outputs=1"), + std::string::npos); + EXPECT_EQ(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " + "rap=2"), + std::string::npos) + << diagnostics; +} + +TEST_F(DecoderModelResultTest, + IncompatibleConfigurationDoesNotDonateTerminalHistory) { + pbi_->seq_list[0][0].still_picture = 0; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.ci_params_per_layer[0].timing_info.time_scale = 60; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 0, 0, true, false, true); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + Av2DmRunStats clk_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); + EXPECT_FALSE(clk_start.terminal_frame_parsing_time_valid); + EXPECT_FALSE(clk_start.terminal_display_duration_valid); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " + "rap=1 mode=resource decoded=1 outputs=1"), + std::string::npos); + EXPECT_EQ(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 rap=1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + ConsecutiveCvsRetainWholeXlayerAndOperatingPointRuns) { + pbi_->seq_list[0][0].still_picture = 0; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_ptl_present_flag = 1; + ops->op[0].ops_seq_profile_idc[0] = MAIN_420_10_IP0; + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_2_0; + ops->op[0].ops_mlayer_count[0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + for (uint32_t cvs = 1; cvs <= 3; ++cvs) { + if (cvs > 1) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, + 0, 8); + } + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, + 0, 0, true); + UpdateAndOutput(); + } + + Av2DmVerifierStats verifier_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); + ASSERT_EQ(verifier_stats.contexts, 2u); + EXPECT_EQ(verifier_stats.cvs_aggregates, 3u); + EXPECT_EQ(verifier_stats.live_runs, 6u); + for (uint32_t context = 0; context < verifier_stats.contexts; ++context) { + Av2DmContextStats context_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, context, + &context_stats)); + EXPECT_EQ(context_stats.scope.xlayer_id, 0); + for (uint32_t run = 0; run < 3; ++run) { + Av2DmRunStats run_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, context, run, + &run_stats)); + EXPECT_EQ(run_stats.originating_cvs, run + 1); + EXPECT_EQ(run_stats.decoded_frames, 3u - run); + } + Av2DmRunStats source; + Av2DmRunStats final_suffix; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, context, 0, &source)); + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, context, 2, + &final_suffix)); + ASSERT_TRUE(source.last_frame_parsing_time_valid); + ASSERT_TRUE(source.last_display_duration_valid); + ASSERT_TRUE(final_suffix.terminal_frame_parsing_time_valid); + ASSERT_TRUE(final_suffix.terminal_display_duration_valid); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare( + &source.last_frame_parsing_time, + &final_suffix.terminal_frame_parsing_time, &comparison)); + EXPECT_EQ(comparison, 0); + ASSERT_TRUE(av2_dm_rational_compare(&source.last_display_duration, + &final_suffix.terminal_display_duration, + &comparison)); + EXPECT_EQ(comparison, 0); + } + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(CountOccurrences(diagnostics, + "AV2_DECODER_MODEL_RESULT status=CONFORMANT"), + 6u); + EXPECT_EQ(CountOccurrences(diagnostics, "status=INDETERMINATE"), 0u); +} + +TEST_F(DecoderModelResultTest, EndOfInputTargetsOnlyTheEndingStreamGeneration) { + pbi_->seq_list[0][0].seq_max_initial_display_delay_minus_1 = 9; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + Av2DmRunStats run; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); + EXPECT_EQ(run.stream_generation, 0u); + EXPECT_FALSE(run.initial_presentation_delay_known); + Av2DmVerifierStats before; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before)); + + av2_decoder_model_verifier_before_final_output(pbi_, 1, false); + Av2DmVerifierStats wrong_generation; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &wrong_generation)); + EXPECT_EQ(wrong_generation.event_count, before.event_count); + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); + EXPECT_FALSE(run.initial_presentation_delay_known); + + av2_decoder_model_verifier_before_final_output(pbi_, 0, false); + Av2DmVerifierStats ending_generation; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &ending_generation)); + EXPECT_EQ(ending_generation.event_count, before.event_count + 1); + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); + EXPECT_TRUE(run.initial_presentation_delay_known); + + av2_decoder_model_verifier_before_final_output(pbi_, 0, false); + Av2DmVerifierStats repeated; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &repeated)); + EXPECT_EQ(repeated.event_count, ending_generation.event_count); +} + +TEST_F(DecoderModelResultTest, UnalignedXlayerClkBoundariesRemainIndependent) { + pbi_->seq_list[1][0] = pbi_->seq_list[0][0]; + pbi_->common.seq_params = pbi_->seq_list[1][0]; + pbi_->obu_type = OBU_SEQUENCE_HEADER; + av2_decoder_model_verifier_on_sequence_header(pbi_, 1, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 1, 0); + + pbi_->common.seq_params = pbi_->seq_list[0][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.seq_params = pbi_->seq_list[1][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 1, 0, true); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + Av2DmVerifierStats verifier_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); + ASSERT_EQ(verifier_stats.contexts, 2u); + EXPECT_EQ(verifier_stats.cvs_aggregates, 3u); + EXPECT_EQ(verifier_stats.open_cvs, 2u); + EXPECT_EQ(verifier_stats.live_runs, 3u); + for (uint32_t context = 0; context < verifier_stats.contexts; ++context) { + Av2DmContextStats context_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, context, + &context_stats)); + Av2DmRunStats first_run; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, context, 0, &first_run)); + EXPECT_EQ(first_run.originating_cvs, 1u); + if (context_stats.scope.xlayer_id == 0) { + EXPECT_EQ(first_run.decoded_frames, 2u); + Av2DmRunStats second_run; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, context, 1, + &second_run)); + EXPECT_EQ(second_run.originating_cvs, 2u); + EXPECT_EQ(second_run.decoded_frames, 1u); + } else { + EXPECT_EQ(context_stats.scope.xlayer_id, 1); + EXPECT_EQ(first_run.decoded_frames, 1u); + Av2DmRunStats unused; + EXPECT_FALSE( + av2_decoder_model_verifier_get_run_stats(pbi_, context, 1, &unused)); + } + } +} + +TEST_F(DecoderModelResultTest, + IncompatibleClkTransitionOnlyMakesOlderRunIndeterminate) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.ci_params_per_layer[0].timing_info.time_scale = 60; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + Av2DmRunStats older; + Av2DmRunStats clk_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); + EXPECT_EQ(older.originating_cvs, 1u); + EXPECT_EQ(older.reason, AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION); + EXPECT_EQ(older.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_EQ(clk_start.originating_cvs, 2u); + EXPECT_EQ(clk_start.reason, AV2_DM_REASON_NONE); + EXPECT_EQ(clk_start.decoded_frames, 1u); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " + "rap=0 mode=resource decoded=1 outputs=1 " + "reordered_outputs=0 violations=0 " + "reason=incompatible_configuration_transition"), + std::string::npos); + EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 rap=1 " + "mode=resource decoded=1 outputs=1"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + MissingClkReplacementInputIsNotAConfigurationTransition) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.ci_params_per_layer[0].timing_info.time_scale = 0; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + Av2DmRunStats older; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); + EXPECT_EQ(older.originating_cvs, 1u); + EXPECT_EQ(older.reason, AV2_DM_REASON_MISSING_REQUIRED_INPUT); + EXPECT_EQ(older.status, AV2_DM_RESULT_INDETERMINATE); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " + "rap=0 mode=resource decoded=1 outputs=1 " + "reordered_outputs=0 violations=0 " + "reason=missing_required_input"), + std::string::npos); + EXPECT_EQ(diagnostics.find("reason=incompatible_configuration_transition"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + FatalRapParameterUpdatePreservesProvenNonConformance) { + av2_decoder_model_verifier_destroy(pbi_); + pbi_->decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_FATAL; + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + av2_decoder_model_verifier_set_defer_nonterminal_checks_for_testing(pbi_, + true); + Configure(64, 64); + SequenceHeader *const sequence = &pbi_->seq_list[0][0]; + sequence->decoder_model_info_present_flag = 1; + sequence->seq_max_decoder_model_present_flag = 1; + sequence->seq_max_decoder_buffer_delay = 70000; + sequence->seq_max_encoder_buffer_delay = 20000; + sequence->seq_max_low_delay_mode_flag = 1; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + pbi_->common.brt_info.br_ops_dependent_flag = 0; + pbi_->common.brt_info.br_time = 0; + av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, false, + 0, 0, true); + av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, 0, 0, 0, + 100000000); + av2_decoder_model_verifier_on_frame_unit_complete(pbi_); + UpdateAndOutput(); + Av2DmRunStats before_update; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &before_update)); + EXPECT_EQ(before_update.status, AV2_DM_RESULT_CONFORMANT); + EXPECT_EQ(before_update.reason, AV2_DM_REASON_NONE); + av2_decoder_model_verifier_set_defer_nonterminal_checks_for_testing(pbi_, + false); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + sequence->seq_max_level_idx = SEQ_LEVEL_2_1; + pbi_->common.seq_params = *sequence; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + memset(pbi_->valid_for_referencing, 0, sizeof(pbi_->valid_for_referencing)); + + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true); + EXPECT_TRUE(av2_decoder_model_verifier_should_stop(pbi_)); + + Av2DmRunStats older; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); + EXPECT_EQ(older.originating_cvs, 1u); + EXPECT_EQ(older.status, AV2_DM_RESULT_NON_CONFORMANT); + EXPECT_EQ(older.reason, AV2_DM_REASON_NONE); + + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_NE(diagnostics.find("code=SMOOTHING_BUFFER_OVERFLOW"), + std::string::npos); + EXPECT_NE(diagnostics.find("status=NON_CONFORMANT xlayer=0 ops=-1 op=-1 " + "rap=0"), + std::string::npos); + EXPECT_NE(diagnostics.find("reason=none"), std::string::npos); +} + +TEST_F(DecoderModelResultTest, + CompatibleClkReferenceCountUpdateKeepsOlderRunVerifiable) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->seq_list[0][0].ref_frames = 16; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + memset(pbi_->valid_for_referencing, 0, sizeof(pbi_->valid_for_referencing)); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true); + UpdateAndOutput(); + + Av2DmVerifierStats verifier_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); + EXPECT_EQ(verifier_stats.clk_invalidations, 1u); + EXPECT_EQ(verifier_stats.live_runs, 2u); + Av2DmRunStats older; + Av2DmRunStats clk_start; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); + EXPECT_EQ(older.originating_cvs, 1u); + EXPECT_EQ(older.reason, AV2_DM_REASON_NONE); + EXPECT_EQ(older.decoded_frames, 2u); + EXPECT_EQ(older.active_num_ref_frames, 16u); + EXPECT_EQ(clk_start.originating_cvs, 2u); + EXPECT_EQ(clk_start.reason, AV2_DM_REASON_NONE); + EXPECT_EQ(clk_start.decoded_frames, 1u); + EXPECT_EQ(clk_start.active_num_ref_frames, 16u); +} + +TEST_F(DecoderModelResultTest, ClkOlkRasClkRunsCoexistAcrossCvsOwnership) { + frame_.long_term_id = -1; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + memset(pbi_->valid_for_referencing, 0, sizeof(pbi_->valid_for_referencing)); + StartFrame(OBU_OPEN_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, false, false, true); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_RAS_FRAME, 64, 64, &frame_); + UpdateAndOutput(); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + memset(pbi_->valid_for_referencing, 0, sizeof(pbi_->valid_for_referencing)); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true, false, true); + UpdateAndOutput(); + + Av2DmVerifierStats verifier_stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); + EXPECT_EQ(verifier_stats.cvs_aggregates, 2u); + EXPECT_EQ(verifier_stats.open_cvs, 1u); + EXPECT_EQ(verifier_stats.live_runs, 4u); + const uint64_t expected_decoded_frames[4] = { 4, 3, 2, 1 }; + for (uint32_t run = 0; run < 4; ++run) { + Av2DmRunStats run_stats; + ASSERT_TRUE( + av2_decoder_model_verifier_get_run_stats(pbi_, 0, run, &run_stats)); + EXPECT_EQ(run_stats.originating_cvs, run < 3 ? 1u : 2u); + EXPECT_EQ(run_stats.decoded_frames, expected_decoded_frames[run]); + } +} + TEST_F(DecoderModelResultTest, ExplicitOperatingPointLevelOverridesSequence) { SequenceHeader *const sequence = &pbi_->seq_list[0][0]; sequence->seq_max_level_idx = SEQ_LEVEL_3_0; @@ -1451,7 +2210,7 @@ TEST_F(DecoderModelResultTest, EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=1 op=0 " "rap=0 mode=resource decoded=1"), std::string::npos); - EXPECT_NE(diagnostics.find("reason=missing_required_input"), + EXPECT_NE(diagnostics.find("reason=incompatible_configuration_transition"), std::string::npos); EXPECT_EQ(diagnostics.find("xlayer=0 ops=1 op=0 rap=1"), std::string::npos); } diff --git a/test/decoder_model_test.cc b/test/decoder_model_test.cc index 3969b466dd..b3d22e0560 100644 --- a/test/decoder_model_test.cc +++ b/test/decoder_model_test.cc @@ -200,7 +200,16 @@ TEST(DecoderModelBufferPoolTest, InitializesEightAndSixteenReferencePools) { EXPECT_EQ(pool.pool_size, 10u); EXPECT_EQ(av2_dm_buffer_pool_get_free_buffer(&pool), 0); EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); - for (uint32_t i = 0; i < 8; ++i) EXPECT_EQ(pool.vbi[i], -1); + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + EXPECT_EQ(pool.vbi[i], -1); + } + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + EXPECT_EQ(pool.buffers[i].decoder_ref_count, 0u); + EXPECT_EQ(pool.buffers[i].player_ref_count, 0u); + EXPECT_EQ(pool.buffers[i].display_index, -1); + EXPECT_FALSE(pool.buffers[i].generation_valid); + EXPECT_FALSE(pool.buffers[i].presentation_time_valid); + } ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 16)); EXPECT_EQ(pool.pool_size, 18u); @@ -242,10 +251,29 @@ TEST(DecoderModelBufferPoolTest, RejectsInvalidIndicesWithoutMutation) { ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 8, 0)); EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 0, 10)); - EXPECT_FALSE(av2_dm_buffer_pool_add_decoder_ref(&pool, 10)); + EXPECT_FALSE( + av2_dm_buffer_pool_add_decoder_ref(&pool, AV2_DM_MAX_BUFFER_POOL_SIZE)); EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); } +TEST(DecoderModelBufferPoolTest, + InactiveHistoricalBuffersRemainCountedAndReleasable) { + Av2DmBufferPool pool; + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 16)); + ASSERT_TRUE(av2_dm_buffer_pool_add_player_ref(&pool, 17)); + pool.buffers[17].generation_valid = true; + pool.buffers[17].generation = 17; + + pool.num_ref_frames = 8; + pool.pool_size = 10; + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 1u); + EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 15, 0)); + EXPECT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 15, -1)); + EXPECT_TRUE(av2_dm_buffer_pool_remove_player_ref(&pool, 17)); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); + EXPECT_FALSE(pool.buffers[17].generation_valid); +} + struct ViolationCollector { std::vector violations; }; @@ -527,59 +555,72 @@ TEST(DecoderModelProcessTest, OlkInvalidationMirrorsAllInvalidSlots) { ASSERT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[1]); const int old_buffer = state.buffer_pool.vbi[0]; ASSERT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 2u); - av2_decoder_model_invalidate_olk_reference_buffers(model, 1); + av2_decoder_model_invalidate_reference_buffers(model, 1, false); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.buffer_pool.vbi[0], old_buffer); EXPECT_EQ(state.buffer_pool.vbi[1], -1); EXPECT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 1u); + av2_decoder_model_invalidate_reference_buffers(model, 0, false); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.vbi[0], -1); + EXPECT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 0u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, ClkInvalidationClearsEveryPhysicalVbiSlot) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.num_ref_frames = 16; + config.explicit_num_ref_frames = true; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent aliases = + Refresh((1u << 0) | (1u << 15), (1u << 0) | (1u << 15)); + av2_decoder_model_update_reference_buffers(model, &aliases); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[15]); + const int32_t old_buffer = state.buffer_pool.vbi[0]; + ASSERT_GE(old_buffer, 0); + ASSERT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 2u); + + av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + EXPECT_EQ(state.buffer_pool.vbi[i], -1); + } + EXPECT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 0u); av2_decoder_model_destroy(model); } TEST(DecoderModelProcessTest, - FrameStartSynchronizesNonOlkInvalidationBeforeBufferAllocation) { + FrameStartDoesNotPerformUnspecifiedReferenceInvalidation) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); ViolationCollector collector; Av2DecoderModel *model = av2_decoder_model_create(&config, CollectViolation, &collector); ASSERT_NE(model, nullptr); - uint32_t valid_mask = 0; - for (uint32_t i = 0; i < 8; ++i) { - Av2DmFrameEvent frame = MakeFrame(i, i + 1); - av2_decoder_model_start_frame(model, &frame); - valid_mask |= 1u << i; - const Av2DmReferenceUpdateEvent refresh = Refresh(1u << i, valid_mask); - av2_decoder_model_update_reference_buffers(model, &refresh); - } - - Av2DmFrameEvent ninth = MakeFrame(8, 9); - av2_decoder_model_start_frame(model, &ninth); - Av2DmOutputEvent first_old_output = Output(100, 1, 0); - av2_decoder_model_output_frame(model, &first_old_output); - Av2DmReferenceUpdateEvent ninth_refresh = Refresh(1, valid_mask); - av2_decoder_model_update_reference_buffers(model, &ninth_refresh); - - Av2DmFrameEvent tenth = MakeFrame(9, 10); - av2_decoder_model_start_frame(model, &tenth); - Av2DmOutputEvent second_old_output = Output(101, 2, 1); - av2_decoder_model_output_frame(model, &second_old_output); - Av2DmReferenceUpdateEvent tenth_refresh = Refresh(2, valid_mask); - av2_decoder_model_update_reference_buffers(model, &tenth_refresh); - + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh7 = Refresh(1u << 7, 1u << 7); + av2_decoder_model_update_reference_buffers(model, &refresh7); Av2DmState before; ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); - ASSERT_EQ(av2_dm_buffer_pool_frames_in_use(&before.buffer_pool), 10u); const int32_t invalidated_buffer = before.buffer_pool.vbi[7]; ASSERT_GE(invalidated_buffer, 0); - Av2DmFrameEvent eleventh = MakeFrame(10, 11); - eleventh.ref_valid_mask = valid_mask & ~(1u << 7); - av2_decoder_model_start_frame(model, &eleventh); + Av2DmFrameEvent second = MakeFrame(1, 2); + second.ref_valid_mask = 0; + av2_decoder_model_start_frame(model, &second); Av2DmState after; ASSERT_TRUE(av2_decoder_model_get_state(model, &after)); - EXPECT_EQ(after.buffer_pool.vbi[7], -1); - EXPECT_EQ(after.current_buffer_index, invalidated_buffer); + EXPECT_EQ(after.buffer_pool.vbi[7], invalidated_buffer); + EXPECT_EQ(after.buffer_pool.buffers[invalidated_buffer].decoder_ref_count, + 1u); + EXPECT_NE(after.current_buffer_index, invalidated_buffer); EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE)); av2_decoder_model_destroy(model); @@ -623,7 +664,7 @@ TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh0 = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh0); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(0, 10, -1); av2_decoder_model_output_frame(model, &first_output); Av2DmState state; @@ -634,7 +675,7 @@ TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { av2_decoder_model_start_frame(model, &second); const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); av2_decoder_model_update_reference_buffers(model, &refresh1); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_TRUE(state.initial_presentation_delay_known); ASSERT_TRUE(state.last_presentation_valid); @@ -648,6 +689,54 @@ TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { av2_decoder_model_destroy(model); } +TEST(DecoderModelProcessTest, + EndOfBitstreamDelayRebasesBuffersOutsideReducedActivePool) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.num_ref_frames = 16; + config.explicit_num_ref_frames = true; + config.initial_display_delay = 18; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + uint32_t valid_mask = 0; + for (uint32_t i = 0; i < 12; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + av2_decoder_model_start_frame(model, &frame); + valid_mask |= 1u << i; + const Av2DmReferenceUpdateEvent refresh = { 1u << i, valid_mask }; + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, i); + const Av2DmOutputEvent output = Output(i, i + 1, -1); + av2_decoder_model_output_frame(model, &output); + } + + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_FALSE(state.initial_presentation_delay_known); + ASSERT_NE(state.buffer_pool.buffers[10].player_ref_count, 0u); + ASSERT_FALSE(state.buffer_pool.buffers[10].presentation_time_valid); + + av2_decoder_model_invalidate_reference_buffers(model, 0, true); + Av2DmConfig reduced = config; + reduced.num_ref_frames = 8; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 20, true)); + av2_decoder_model_set_initial_presentation_delay(model, true, 21); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.initial_presentation_delay_known); + EXPECT_EQ(state.buffer_pool.pool_size, 10u); + EXPECT_NE(state.buffer_pool.buffers[10].player_ref_count, 0u); + EXPECT_TRUE(state.buffer_pool.buffers[10].presentation_time_valid); + + const Av2DmRational delay = state.initial_presentation_delay; + av2_decoder_model_set_initial_presentation_delay(model, true, 22); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + int comparison = 1; + ASSERT_TRUE(av2_dm_rational_compare(&state.initial_presentation_delay, &delay, + &comparison)); + EXPECT_EQ(comparison, 0); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelProcessTest, InitialDelayReportsConsolidatedWorstOutputAtReferenceUpdateEvent) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); @@ -668,7 +757,7 @@ TEST(DecoderModelProcessTest, av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &first_refresh); - av2_decoder_model_set_initial_presentation_delay(model, 2); + av2_decoder_model_set_initial_presentation_delay(model, false, 2); Av2DmState state; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_FALSE(state.initial_presentation_delay_known); @@ -689,7 +778,7 @@ TEST(DecoderModelProcessTest, av2_decoder_model_start_frame(model, &second); const Av2DmReferenceUpdateEvent second_refresh = Refresh(2, 3); av2_decoder_model_update_reference_buffers(model, &second_refresh); - av2_decoder_model_set_initial_presentation_delay(model, 99); + av2_decoder_model_set_initial_presentation_delay(model, false, 99); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_TRUE(state.initial_presentation_delay_known); @@ -702,7 +791,7 @@ TEST(DecoderModelProcessTest, EXPECT_EQ(violation.event_index, 99u); } } - av2_decoder_model_set_initial_presentation_delay(model, 100); + av2_decoder_model_set_initial_presentation_delay(model, false, 100); EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DISPLAY_FRAME_LATE), 1u); EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DECODE_DEADLINE), 1u); @@ -720,7 +809,7 @@ TEST(DecoderModelProcessTest, DeadlineUsesDecodedGenerationIdentity) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh0 = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh0); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmFrameEvent second = MakeFrame(1, 200, 9000); av2_decoder_model_start_frame(model, &second); const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); @@ -766,7 +855,7 @@ TEST(DecoderModelProcessTest, PeriodicRebasePreservesExactTimeline) { av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmState state; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.time, 0, 1); @@ -794,7 +883,7 @@ TEST(DecoderModelProcessTest, RebaseKeepsScheduleAndResourceLaneOriginShared) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmFrameEvent delayed = MakeFrame(1, 2, 9000); av2_decoder_model_start_frame(model, &delayed); @@ -879,7 +968,7 @@ TEST(DecoderModelStorageTest, NonIncreasingOutputTimesRestartRateHistory) { av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, i); + av2_decoder_model_set_initial_presentation_delay(model, false, i); Av2DmOutputEvent output = Output(1000 + i, (uint64_t)i + 1, -1); output.temporal_unit_index = i; av2_decoder_model_output_frame(model, &output); @@ -972,7 +1061,7 @@ TEST(DecoderModelStorageTest, OneHourFixedRateTracesRemainBounded) { av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, i); + av2_decoder_model_set_initial_presentation_delay(model, false, i); Av2DmOutputEvent output = Output((uint64_t)trace.frames + i, (uint64_t)i + 1, -1); output.temporal_unit_index = i; @@ -1016,7 +1105,7 @@ TEST(DecoderModelProcessTest, ScheduleModeReportsUnavailableDecodeBuffer) { const Av2DmReferenceUpdateEvent refresh = Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); } Av2DmOutputEvent rap_output = Output(99, 1, 0); rap_output.presentation_time_present = true; @@ -1056,7 +1145,7 @@ TEST(DecoderModelProcessTest, ReorderedOutputsAreCountedByGeneration) { av2_decoder_model_start_frame(model, &second); const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); av2_decoder_model_update_reference_buffers(model, &refresh1); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent second_output = Output(2, 20, 1); av2_decoder_model_output_frame(model, &second_output); Av2DmOutputEvent first_output = Output(3, 10, 0); @@ -1077,7 +1166,7 @@ TEST(DecoderModelProcessTest, RetiredDfgGenerationMetadataSurvivesUntilOutput) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); for (uint32_t i = 1; i <= 20; ++i) { Av2DmFrameEvent filler = MakeFrame(i, (uint64_t)i + 1, i * 9000); av2_decoder_model_start_frame(model, &filler); @@ -1114,7 +1203,7 @@ TEST(DecoderModelProcessTest, Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); } - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); const uint64_t owner_tus[] = { 0, 4, 5, 6 }; const uint64_t generations[] = { 1, 3, 2, 4 }; @@ -1157,7 +1246,7 @@ TEST(DecoderModelProcessTest, Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); } - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); const uint64_t owner_tus[] = { 0, 0, 1 }; for (uint32_t i = 0; i < 3; ++i) { @@ -1203,7 +1292,7 @@ TEST(DecoderModelProcessTest, VariableRatePresentationUsesRapBasesExactly) { Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); } - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); const uint64_t presentation_ticks[] = { 0, 2, 30, 3 }; const uint64_t expected_numerators[] = { 0, 1, 1, 11 }; @@ -1239,7 +1328,7 @@ TEST(DecoderModelProcessTest, av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent output = Output(1, 1, 0); output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &output); @@ -1264,7 +1353,7 @@ TEST(DecoderModelProcessTest, ExplicitTemporalUnitOutputTimeIsPreserved) { av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent output = Output(1, 1, 0); output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &output); @@ -1291,7 +1380,7 @@ TEST(DecoderModelProcessTest, LongStreamRebasingPreservesDecisions) { if (i == 0) { const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); } } ASSERT_TRUE(av2_decoder_model_get_result(model, &results[run])); @@ -1898,7 +1987,7 @@ TEST(DecoderModelConformanceTest, MinimumPresentationIntervalIsExact) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(0, 1, 0); av2_decoder_model_output_frame(model, &first_output); Av2DmFrameEvent second = MakeFrame(1, 2); @@ -1934,7 +2023,7 @@ TEST(DecoderModelConformanceTest, DisplayAndDecodeDeadlineBoundaryIsExact) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(10, 1, -1); first_output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &first_output); @@ -1986,7 +2075,7 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(10, 1, -1); first_output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &first_output); @@ -2027,7 +2116,7 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &first_refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(10, 1, 0); first_output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &first_output); @@ -2055,6 +2144,167 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, + TerminalHistorySeedsOnlyMissingOneFrameSuffixIntervals) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + Av2DecoderModel *const source = + av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(source, nullptr); + + for (uint64_t i = 0; i < 2; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + frame.temporal_unit_index = i; + av2_decoder_model_start_frame(source, &frame); + const Av2DmReferenceUpdateEvent refresh = + Refresh(1u << i, (1u << (i + 1)) - 1); + av2_decoder_model_update_reference_buffers(source, &refresh); + if (i == 0) { + av2_decoder_model_set_initial_presentation_delay(source, false, i); + } + Av2DmOutputEvent output = Output(10 + i, i + 1, -1); + output.temporal_unit_index = i; + av2_decoder_model_output_frame(source, &output); + } + Av2DmState source_state; + ASSERT_TRUE(av2_decoder_model_get_state(source, &source_state)); + ASSERT_TRUE(source_state.last_frame_parsing_time_valid); + ASSERT_TRUE(source_state.last_display_duration_valid); + + Av2DecoderModel *const suffix = + av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(suffix, nullptr); + Av2DmFrameEvent frame = MakeFrame(1, 2); + frame.random_access_point = true; + frame.coded_as_closed_loop_key = true; + frame.temporal_unit_index = 1; + av2_decoder_model_start_frame(suffix, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(suffix, &refresh); + av2_decoder_model_set_initial_presentation_delay(suffix, false, 1); + Av2DmOutputEvent output = Output(11, 2, -1); + output.temporal_unit_index = 1; + av2_decoder_model_output_frame(suffix, &output); + + Av2DmState before; + ASSERT_TRUE(av2_decoder_model_get_state(suffix, &before)); + ASSERT_FALSE(before.last_frame_parsing_time_valid); + ASSERT_FALSE(before.last_display_duration_valid); + ASSERT_TRUE(av2_decoder_model_seed_terminal_history( + suffix, &source_state.last_frame_parsing_time, + &source_state.last_display_duration)); + Av2DmState after; + ASSERT_TRUE(av2_decoder_model_get_state(suffix, &after)); + EXPECT_TRUE(after.last_frame_parsing_time_valid); + EXPECT_TRUE(after.last_display_duration_valid); + EXPECT_TRUE(EqualRational(after.last_frame_parsing_time, + source_state.last_frame_parsing_time)); + EXPECT_TRUE(EqualRational(after.last_display_duration, + source_state.last_display_duration)); + EXPECT_TRUE(EqualRational(after.time, before.time)); + EXPECT_EQ(after.current_buffer_index, before.current_buffer_index); + EXPECT_EQ(after.frame_number, before.frame_number); + EXPECT_EQ(after.dfg_number, before.dfg_number); + EXPECT_EQ(after.shown_frame_number, before.shown_frame_number); + EXPECT_EQ(memcmp(&after.buffer_pool, &before.buffer_pool, + sizeof(after.buffer_pool)), + 0); + + av2_decoder_model_finish(suffix); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(suffix, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + av2_decoder_model_destroy(suffix); + av2_decoder_model_destroy(source); +} + +TEST(DecoderModelConformanceTest, + TerminalHistoryStillEvaluatesOneFrameSuffixViolations) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); + Av2DmOutputEvent output = Output(1, 1, -1); + output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &output); + + Av2DmRational insufficient_interval; + ASSERT_TRUE( + av2_dm_rational_make(1, UINT64_C(1000000000), &insufficient_interval)); + ASSERT_TRUE(av2_decoder_model_seed_terminal_history( + model, &insufficient_interval, &insufficient_interval)); + av2_decoder_model_finish(model); + + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); + EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + LocallyDerivedTerminalHistorySupersedesSeededFallback) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &first_refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); + Av2DmOutputEvent first_output = Output(10, 1, -1); + first_output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &first_output); + + Av2DmRational insufficient_interval; + ASSERT_TRUE( + av2_dm_rational_make(1, UINT64_C(1000000000), &insufficient_interval)); + ASSERT_TRUE(av2_decoder_model_seed_terminal_history( + model, &insufficient_interval, &insufficient_interval)); + + Av2DmFrameEvent second = MakeFrame(1, 2); + second.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, 30, &second.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &second); + const Av2DmReferenceUpdateEvent second_refresh = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &second_refresh); + Av2DmOutputEvent second_output = Output(11, 2, -1); + second_output.temporal_unit_index = 1; + av2_decoder_model_output_frame(model, &second_output); + + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_TRUE(state.last_frame_parsing_time_valid); + ASSERT_TRUE(state.last_display_duration_valid); + int parsing_comparison; + ASSERT_TRUE(av2_dm_rational_compare(&state.last_frame_parsing_time, + &insufficient_interval, + &parsing_comparison)); + EXPECT_GT(parsing_comparison, 0); + ExpectEqualRational(state.last_display_duration, 1, 30); + av2_decoder_model_finish(model); + EXPECT_TRUE(collector.violations.empty()); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, ReorderedFinalTuReusesLastDisplayDurationExactly) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); @@ -2074,7 +2324,7 @@ TEST(DecoderModelConformanceTest, Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); } - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); const uint64_t owner_tus[] = { 0, 4, 5 }; const uint64_t generations[] = { 1, 3, 2 }; @@ -2111,7 +2361,7 @@ TEST(DecoderModelConformanceTest, DisplayRateBoundaryIsExact) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(10, 1, -1); first_output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &first_output); @@ -2258,7 +2508,7 @@ TEST(DecoderModelConformanceTest, const Av2DmReferenceUpdateEvent refresh = Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, i); + av2_decoder_model_set_initial_presentation_delay(model, false, i); Av2DmOutputEvent output = Output(10 + i, i + 1, -1); output.temporal_unit_index = i; av2_decoder_model_output_frame(model, &output); @@ -2307,7 +2557,7 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, i); + av2_decoder_model_set_initial_presentation_delay(model, false, i); Av2DmOutputEvent output = Output(100 + i, (uint64_t)i + 1, -1); output.temporal_unit_index = i; av2_decoder_model_output_frame(model, &output); @@ -2349,7 +2599,7 @@ TEST(DecoderModelConformanceTest, Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); } - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); const uint64_t owner_tus[] = { 0, 4, 5, 6 }; const uint64_t generations[] = { 1, 3, 2, 4 }; @@ -2625,7 +2875,8 @@ TEST(DecoderModelConformanceTest, Av2DmConfig replacement = config; ASSERT_TRUE( av2_dm_rational_make(2000, 1, &replacement.level_limits.bit_rate)); - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, false)); Av2DmFrameEvent updated = MakeFrame(1, 2); updated.coded_bits = 100; updated.random_access_point = true; @@ -2666,7 +2917,8 @@ TEST(DecoderModelConformanceTest, Av2DmConfig replacement = config; replacement.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; replacement.operating_point_parameters_present = false; - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, false)); Av2DmFrameEvent updated = MakeFrame(1, 2); updated.random_access_point = true; updated.decoder_model_parameters_updated = true; @@ -2697,7 +2949,8 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &first); Av2DmConfig replacement = config; replacement.level_limits.max_decode_rate = 100000; - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, false)); Av2DmFrameEvent updated = MakeFrame(1, 2, 900); updated.random_access_point = true; @@ -2724,7 +2977,8 @@ TEST(DecoderModelConformanceTest, Av2DmConfig replacement = config; replacement.mode = AV2_DM_DECODING_SCHEDULE_MODE; - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, 1)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, false)); Av2DmFrameEvent updated = MakeFrame(1, 2, 9000); updated.random_access_point = true; updated.decoder_model_parameters_updated = true; @@ -2751,7 +3005,8 @@ TEST(DecoderModelConformanceTest, ParameterUpdateRejectsImmutableClockChange) { Av2DmConfig replacement = config; replacement.time_scale += 1; - EXPECT_FALSE(av2_decoder_model_update_parameters(model, &replacement, 1)); + EXPECT_FALSE( + av2_decoder_model_update_parameters(model, &replacement, 1, false)); Av2DmResult result; ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); EXPECT_EQ(result.status, AV2_DM_RESULT_INDETERMINATE); @@ -2759,6 +3014,173 @@ TEST(DecoderModelConformanceTest, ParameterUpdateRejectsImmutableClockChange) { av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, + NumRefFramesUpdateRequiresCompletedClkInvalidation) { + const Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DmConfig replacement = config; + replacement.num_ref_frames = 16; + + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + EXPECT_FALSE( + av2_decoder_model_update_parameters(model, &replacement, 1, false)); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.num_ref_frames, 8u); + EXPECT_EQ(state.buffer_pool.pool_size, 10u); + av2_decoder_model_destroy(model); + + model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + EXPECT_FALSE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.num_ref_frames, 8u); + EXPECT_EQ(state.buffer_pool.pool_size, 10u); + EXPECT_NE(state.buffer_pool.vbi[0], -1); + av2_decoder_model_destroy(model); + + model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.num_ref_frames, 16u); + EXPECT_EQ(state.buffer_pool.pool_size, 18u); + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + EXPECT_EQ(state.buffer_pool.vbi[i], -1); + } + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + FixedBufferPoolSurvivesSixteenEightSixteenTransition) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.num_ref_frames = 16; + config.explicit_num_ref_frames = true; + config.initial_display_delay = AV2_DM_MAX_BUFFER_POOL_SIZE; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + for (uint64_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1); + av2_decoder_model_start_frame(model, &frame); + Av2DmOutputEvent output = Output(100 + i, i + 1, -1); + output.presentation_uses_current_frame = true; + output.presentation_random_access_point = i == 0; + av2_decoder_model_output_frame(model, &output); + } + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), + static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); + ASSERT_EQ(state.buffer_pool.buffers[17].player_ref_count, 1u); + ASSERT_FALSE(state.buffer_pool.buffers[17].presentation_time_valid); + const Av2DmRational high_presentation_offset = + state.buffer_pool.buffers[17].presentation_time; + + av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); + Av2DmConfig reduced = config; + reduced.num_ref_frames = 8; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 200, true)); + av2_decoder_model_set_initial_presentation_delay(model, false, 201); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.pool_size, 10u); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), + static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); + EXPECT_TRUE(state.initial_presentation_delay_known); + EXPECT_EQ(state.buffer_pool.buffers[17].player_ref_count, 1u); + EXPECT_TRUE(state.buffer_pool.buffers[17].presentation_time_valid); + Av2DmRational expected_high_presentation; + ASSERT_TRUE(av2_dm_rational_add(&high_presentation_offset, + &state.initial_presentation_delay, + &expected_high_presentation)); + int high_presentation_comparison; + ASSERT_TRUE(av2_dm_rational_compare( + &state.buffer_pool.buffers[17].presentation_time, + &expected_high_presentation, &high_presentation_comparison)); + EXPECT_EQ(high_presentation_comparison, 0); + Av2DmStorageStats storage; + ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); + EXPECT_EQ(storage.active_generations, + static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); + EXPECT_GE(storage.active_tus, + static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); + + for (uint64_t i = 0; i < 160; ++i) { + Av2DmFrameEvent frame = + MakeFrame(AV2_DM_MAX_BUFFER_POOL_SIZE + i, 1000 + i); + frame.random_access_point = i == 0; + frame.coded_as_closed_loop_key = i == 0; + frame.decoder_model_parameters_updated = i == 0; + av2_decoder_model_start_frame(model, &frame); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ASSERT_GE(state.current_buffer_index, 0); + EXPECT_LT(state.current_buffer_index, 10); + } + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.buffers[17].player_ref_count, 0u); + EXPECT_FALSE(state.buffer_pool.buffers[17].generation_valid); + + av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &config, 400, true)); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.num_ref_frames, 16u); + EXPECT_EQ(state.buffer_pool.pool_size, 18u); + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + EXPECT_EQ(state.buffer_pool.vbi[i], -1); + } + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, ReducedPoolDiagnosticsUseOnlyTheActiveRange) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.num_ref_frames = 16; + config.explicit_num_ref_frames = true; + config.initial_display_delay = AV2_DM_MAX_BUFFER_POOL_SIZE; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + for (uint64_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + Av2DmFrameEvent frame = MakeFrame(i, i + 1, (uint32_t)(i * 9000)); + av2_decoder_model_start_frame(model, &frame); + Av2DmOutputEvent output = Output(100 + i, i + 1, -1); + output.presentation_uses_current_frame = true; + output.presentation_random_access_point = i == 0; + av2_decoder_model_output_frame(model, &output); + } + av2_decoder_model_set_initial_presentation_delay(model, false, 200); + av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); + Av2DmConfig reduced = config; + reduced.num_ref_frames = 8; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 201, true)); + + Av2DmFrameEvent blocked = MakeFrame(202, 1000, 0); + blocked.random_access_point = true; + blocked.coded_as_closed_loop_key = true; + blocked.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &blocked); + const Av2DmViolation *const violation = FindViolation( + collector, AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE); + ASSERT_NE(violation, nullptr); + ASSERT_EQ(violation->detail.kind, AV2_DM_VIOLATION_DETAIL_BUFFER_POOL); + EXPECT_EQ(violation->detail.value.buffer_pool.pool_size, 10u); + EXPECT_EQ(violation->detail.value.buffer_pool.frames_in_use, 10u); + EXPECT_EQ(violation->detail.value.buffer_pool.free_buffers, 0u); + Av2DmState state; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), + static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, SmoothingBoundariesAreInclusive) { for (const uint64_t coded_bits : { UINT64_C(99), UINT64_C(100), UINT64_C(101) }) { @@ -2997,7 +3419,7 @@ TEST(DecoderModelConformanceTest, VariablePresentationMustNotDecrease) { av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first = Output(1, 1, 0); first.presentation_time_present = true; av2_decoder_model_output_frame(model, &first); @@ -3039,7 +3461,7 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &first_rap); const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &first_refresh); - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(1, 1, -1); first_output.presentation_uses_current_frame = true; first_output.presentation_random_access_point = true; @@ -3093,7 +3515,7 @@ TEST(DecoderModelConformanceTest, PresentationNonDecreaseBoundaryIsExact) { Refresh(1u << i, (1u << (i + 1)) - 1); av2_decoder_model_update_reference_buffers(model, &refresh); } - av2_decoder_model_set_initial_presentation_delay(model, 0); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first = Output(10, 1, 0); first.temporal_unit_index = 0; diff --git a/test/encoder_decoder_model_test.cc b/test/encoder_decoder_model_test.cc index 4e2cbb8026..670252a5ae 100644 --- a/test/encoder_decoder_model_test.cc +++ b/test/encoder_decoder_model_test.cc @@ -739,7 +739,7 @@ TEST(EncoderDecoderModelDifferentialTest, valid_mask |= 1u << index; const Av2DmReferenceUpdateEvent refresh = { 1u << index, valid_mask }; av2_decoder_model_update_reference_buffers(common, &refresh); - av2_decoder_model_set_initial_presentation_delay(common, 10 + index); + av2_decoder_model_set_initial_presentation_delay(common, false, 10 + index); } Av2DmState common_state; @@ -1017,7 +1017,7 @@ class ResourceAvailabilityDifferentialAdapter { const Av2DmReferenceUpdateEvent refresh = { refresh_flags, ref_valid_mask_ }; av2_decoder_model_update_reference_buffers(common_, &refresh); - av2_decoder_model_set_initial_presentation_delay(common_, + av2_decoder_model_set_initial_presentation_delay(common_, false, NextEventIndex()); } From 5674f49784c79a8d34071a5c6e258679a266dfdf Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Fri, 14 Aug 2026 16:53:40 -0700 Subject: [PATCH 4/8] Decoder model in encoder update --- apps/avmdec.c | 20 + av2/av2_dx_iface.c | 27 ++ av2/common/decoder_model.c | 75 ++- av2/common/decoder_model.h | 7 - av2/common/level.c | 337 ++++++++++--- av2/common/level.h | 27 +- av2/decoder/decoder.h | 1 + av2/decoder/decoder_model.c | 255 +++++----- av2/decoder/decoder_model.h | 21 +- av2/encoder/encode_strategy.c | 1 + av2/encoder/encoder.c | 12 +- avm/avmdx.h | 11 + test/avmdec.sh | 38 +- test/decoder_model_integration_test.cc | 643 ++++++++++++++++--------- test/decoder_model_test.cc | 126 +---- test/encoder_decoder_model_test.cc | 258 +++++++++- test/level_test.cc | 415 +++++++++++++++- 17 files changed, 1679 insertions(+), 595 deletions(-) diff --git a/apps/avmdec.c b/apps/avmdec.c index 8ed482ce32..63faba13fb 100644 --- a/apps/avmdec.c +++ b/apps/avmdec.c @@ -116,6 +116,9 @@ static const arg_def_t verifyarg = static const arg_def_t checkconformancearg = ARG_DEF(NULL, "check-conformance", 1, "Check decoder-model conformance (off, warn, fatal)"); +static const arg_def_t checkconformanceeveryraparg = + ARG_DEF(NULL, "check-conformance-every-rap", 1, + "Check decoder-model conformance from every RAP (0, 1)"); static const arg_def_t framestatsarg = ARG_DEF(NULL, "framestats", 1, "Output per-frame stats (.csv format)"); static const arg_def_t outbitdeptharg = @@ -163,6 +166,7 @@ static const arg_def_t *all_args[] = { &help, &md5arg, &verifyarg, &checkconformancearg, + &checkconformanceeveryraparg, &framestatsarg, &continuearg, &outbitdeptharg, @@ -660,6 +664,7 @@ static int main_loop(int argc, const char **argv_) { int do_verify = 0, error_on_verify = 0; avm_decoder_model_check_mode_t decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_OFF; + int decoder_model_check_every_rap = 1; int stop_after = 0, summary = 0, quiet = 1; int arg_skip = 0; int num_streams = 1; @@ -828,6 +833,13 @@ static int main_loop(int argc, const char **argv_) { } else { die("Error: Invalid argument for --check-conformance (%s).\n", arg.val); } + } else if (arg_match(&arg, &checkconformanceeveryraparg, argi)) { + decoder_model_check_every_rap = arg_parse_uint(&arg); + if (decoder_model_check_every_rap > 1) { + die("Error: Invalid argument for --check-conformance-every-rap " + "(%s).\n", + arg.val); + } } else if (arg_match(&arg, &framestatsarg, argi)) { framestats_file = fopen(arg.val, "w"); if (!framestats_file) { @@ -1020,6 +1032,14 @@ static int main_loop(int argc, const char **argv_) { if (!quiet) fprintf(stderr, "%s\n", decoder.name); + if (AVM_CODEC_CONTROL_TYPECHECKED(&decoder, + AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, + decoder_model_check_every_rap)) { + fprintf(stderr, "Failed to set decoder-model RAP coverage: %s\n", + avm_codec_error(&decoder)); + goto fail; + } + if (AVM_CODEC_CONTROL_TYPECHECKED(&decoder, AV2D_SET_DECODER_MODEL_CHECK_MODE, decoder_model_check_mode)) { fprintf(stderr, "Failed to set decoder-model conformance mode: %s\n", diff --git a/av2/av2_dx_iface.c b/av2/av2_dx_iface.c index 89b48dfff8..d267ef76e2 100644 --- a/av2/av2_dx_iface.c +++ b/av2/av2_dx_iface.c @@ -64,6 +64,7 @@ struct avm_codec_alg_priv { int num_local_ops_selections; int output_all_layers; avm_decoder_model_check_mode_t decoder_model_check_mode; + int decoder_model_check_every_rap; int compressed_input_started; int decoder_model_fatal_latched; @@ -121,6 +122,7 @@ static avm_codec_err_t decoder_init(avm_codec_ctx_t *ctx) { priv->enable_sub_bitstream_extraction = 0; priv->num_local_ops_selections = 0; priv->decoder_model_check_mode = AVM_DECODER_MODEL_CHECK_OFF; + priv->decoder_model_check_every_rap = 1; init_ibp_info(ctx->priv->ibp_directional_weights); } @@ -484,6 +486,8 @@ static avm_codec_err_t init_decoder(avm_codec_alg_priv_t *ctx) { frame_worker_data->pbi->output_all_layers = ctx->output_all_layers; frame_worker_data->pbi->decoder_model_check_mode = ctx->decoder_model_check_mode; + frame_worker_data->pbi->decoder_model_check_every_rap = + ctx->decoder_model_check_every_rap; if (ctx->decoder_model_check_mode != AVM_DECODER_MODEL_CHECK_OFF) { av2_decoder_model_verifier_init(frame_worker_data->pbi); } @@ -2073,6 +2077,27 @@ static avm_codec_err_t ctrl_set_decoder_model_check_mode( return AVM_CODEC_OK; } +static avm_codec_err_t ctrl_set_decoder_model_check_every_rap( + avm_codec_alg_priv_t *ctx, va_list args) { + const int check_every_rap = va_arg(args, int); + if ((check_every_rap != 0 && check_every_rap != 1) || + ctx->compressed_input_started) { + return AVM_CODEC_INVALID_PARAM; + } + ctx->decoder_model_check_every_rap = check_every_rap; + if (ctx->frame_worker != NULL) { + FrameWorkerData *const frame_worker_data = + (FrameWorkerData *)ctx->frame_worker->data1; + AV2Decoder *const pbi = frame_worker_data->pbi; + av2_decoder_model_verifier_destroy(pbi); + pbi->decoder_model_check_every_rap = check_every_rap; + if (pbi->decoder_model_check_mode != AVM_DECODER_MODEL_CHECK_OFF) { + av2_decoder_model_verifier_init(pbi); + } + } + return AVM_CODEC_OK; +} + static avm_codec_err_t ctrl_set_sub_bitstream_extraction( avm_codec_alg_priv_t *ctx, va_list args) { ctx->enable_sub_bitstream_extraction = va_arg(args, int); @@ -2138,6 +2163,8 @@ static avm_codec_ctrl_fn_map_t decoder_ctrl_maps[] = { { AV2D_SET_SKIP_FILM_GRAIN, ctrl_set_skip_film_grain }, { AV2D_SET_RANDOM_ACCESS, ctrl_set_random_access }, { AV2D_SET_BRU_OPT_MODE, ctrl_set_bru_opt_mode }, + { AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, + ctrl_set_decoder_model_check_every_rap }, { AV2D_SET_DECODER_MODEL_CHECK_MODE, ctrl_set_decoder_model_check_mode }, { AV2D_ENABLE_SUBGOP_STATS, ctrl_enable_subgop_stats }, diff --git a/av2/common/decoder_model.c b/av2/common/decoder_model.c index 127b34d014..1455d70239 100644 --- a/av2/common/decoder_model.c +++ b/av2/common/decoder_model.c @@ -782,6 +782,7 @@ struct Av2DecoderModel { Av2DmTuRecord *tus; uint32_t tu_count; uint32_t tu_capacity; + uint64_t output_tu_count; uint64_t frame_number; uint64_t shown_frame_number; uint64_t rap_epoch; @@ -2765,6 +2766,10 @@ static void update_tu_for_output(Av2DecoderModel *model, arithmetic_failure(model); return; } + if (tu->output_frames == 0 && + !increment_model_u64(model, &model->output_tu_count)) { + return; + } tu->output_luma_samples += event->output_luma_samples; ++tu->output_frames; if (!tu->presentation_time_valid) { @@ -3760,29 +3765,34 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { incomplete_verification(model); } if (!model->processing_stopped && !model->config.still_picture && - model->previous_dfg_valid) { - if (model->last_frame_parsing_time_valid) { + model->dfg_number > 1) { + if (!model->previous_dfg_valid || !model->last_frame_parsing_time_valid) { + incomplete_verification(model); + } else { + // Annex A reuses the preceding non-show-existing frame's parsing time + // for the last DFG when such a predecessor is present. check_frame_parsing_constraints(model, &model->previous_dfg, &model->last_frame_parsing_time, model->previous_dfg.event_index); - } else { - incomplete_verification(model); } - if (!model->processing_stopped) { - Av2DmTuRecord *const last_output_tu = - model->last_output_tu_valid ? find_tu(model, model->last_output_tu) - : NULL; - if (last_output_tu != NULL) { - if (model->last_display_duration_valid) { - // Annex A reuses the preceding output duration for the last TU. - // Annex E does not synthesize another presentation interval. - check_tu_display_rate(model, last_output_tu, - &model->last_display_duration, - last_output_tu->event_index); - } else { - incomplete_verification(model); - } + } + if (!model->processing_stopped && !model->config.still_picture && + model->output_tu_count > 1) { + Av2DmTuRecord *const last_output_tu = + model->last_output_tu_valid ? find_tu(model, model->last_output_tu) + : NULL; + if (last_output_tu != NULL) { + if (model->last_display_duration_valid) { + // Annex A reuses the preceding output duration for the last TU. + // Annex E does not synthesize another presentation interval. + check_tu_display_rate(model, last_output_tu, + &model->last_display_duration, + last_output_tu->event_index); + } else { + incomplete_verification(model); } + } else { + incomplete_verification(model); } } if (!model->processing_stopped && @@ -3806,31 +3816,6 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { update_storage_stats(model); } -bool av2_decoder_model_seed_terminal_history( - Av2DecoderModel *model, const Av2DmRational *previous_frame_parsing_time, - const Av2DmRational *previous_tu_output_duration) { - if (model == NULL || model->result.finished || - (previous_frame_parsing_time != NULL && - wide_is_zero(previous_frame_parsing_time->denominator)) || - (previous_tu_output_duration != NULL && - wide_is_zero(previous_tu_output_duration->denominator))) { - return false; - } - // A suffix with local adjacent DFG/TU history is authoritative. The complete - // source contributes only an otherwise unavailable terminal predecessor. - if (!model->last_frame_parsing_time_valid && model->frame_number != 0 && - previous_frame_parsing_time != NULL) { - model->last_frame_parsing_time = *previous_frame_parsing_time; - model->last_frame_parsing_time_valid = true; - } - if (!model->last_display_duration_valid && model->shown_frame_number != 0 && - previous_tu_output_duration != NULL) { - model->last_display_duration = *previous_tu_output_duration; - model->last_display_duration_valid = true; - } - return true; -} - bool av2_decoder_model_get_result(const Av2DecoderModel *model, Av2DmResult *result) { if (model == NULL || result == NULL) return false; @@ -3851,10 +3836,6 @@ bool av2_decoder_model_get_state(const Av2DecoderModel *model, state->dfg_number = model->dfg_number; state->shown_frame_number = model->shown_frame_number; state->buffer_pool = model->lane.pool; - state->last_frame_parsing_time_valid = model->last_frame_parsing_time_valid; - state->last_frame_parsing_time = model->last_frame_parsing_time; - state->last_display_duration_valid = model->last_display_duration_valid; - state->last_display_duration = model->last_display_duration; if (model->previous_dfg_valid) { const Av2DmDfgRecord *const dfg = &model->previous_dfg; state->last_dfg_valid = true; diff --git a/av2/common/decoder_model.h b/av2/common/decoder_model.h index 4ddb80c16a..0e2b7ed5c4 100644 --- a/av2/common/decoder_model.h +++ b/av2/common/decoder_model.h @@ -387,10 +387,6 @@ typedef struct Av2DmState { Av2DmRational last_temporal_unit_output_time; uint64_t last_temporal_unit_output_luma_samples; uint32_t last_temporal_unit_output_frames; - bool last_frame_parsing_time_valid; - Av2DmRational last_frame_parsing_time; - bool last_display_duration_valid; - Av2DmRational last_display_duration; bool initial_presentation_delay_known; Av2DmRational initial_presentation_delay; int32_t current_buffer_index; @@ -460,9 +456,6 @@ void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, uint64_t event_index); void av2_decoder_model_output_frame(Av2DecoderModel *model, const Av2DmOutputEvent *event); -bool av2_decoder_model_seed_terminal_history( - Av2DecoderModel *model, const Av2DmRational *previous_frame_parsing_time, - const Av2DmRational *previous_tu_output_duration); void av2_decoder_model_finish(Av2DecoderModel *model); bool av2_decoder_model_get_result(const Av2DecoderModel *model, Av2DmResult *result); diff --git a/av2/common/level.c b/av2/common/level.c index 06b3b8c8f4..683d6ddd94 100644 --- a/av2/common/level.c +++ b/av2/common/level.c @@ -871,11 +871,10 @@ static void release_buffer(DECODER_MODEL *const decoder_model, int idx) { } static void initialize_buffer_pool(DECODER_MODEL *const decoder_model) { - const int num_ref_frames = decoder_model->num_ref_frames; - for (int i = 0; i < num_ref_frames + 2; ++i) { + for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { release_buffer(decoder_model, i); } - for (int i = 0; i < num_ref_frames; ++i) { + for (int i = 0; i < REF_FRAMES; ++i) { decoder_model->vbi[i] = -1; } } @@ -893,7 +892,7 @@ static int get_free_buffer(DECODER_MODEL *const decoder_model) { static bool release_decoder_reference(DECODER_MODEL *const decoder_model, int buffer_index) { - if (buffer_index < 0 || buffer_index >= decoder_model->num_ref_frames + 2) { + if (buffer_index < 0 || buffer_index >= BUFFER_POOL_MAX_SIZE) { return false; } FRAME_BUFFER *const buffer = &decoder_model->frame_buffer_pool[buffer_index]; @@ -905,20 +904,26 @@ static bool release_decoder_reference(DECODER_MODEL *const decoder_model, return true; } -bool av2_encoder_decoder_model_sync_invalid_ref_buffers( - const AV2_COMMON *const cm, DECODER_MODEL *const decoder_model) { +bool av2_encoder_decoder_model_invalidate_ref_buffers( + const AV2_COMMON *const cm, DECODER_MODEL *const decoder_model, + bool closed_loop_key) { if (cm == NULL || decoder_model == NULL || decoder_model->status != DECODER_MODEL_OK) { return false; } if (decoder_model->num_ref_frames < 1 || decoder_model->num_ref_frames > REF_FRAMES || - cm->seq_params.ref_frames != decoder_model->num_ref_frames) { + cm->seq_params.ref_frames < 1 || cm->seq_params.ref_frames > REF_FRAMES || + (!closed_loop_key && + cm->seq_params.ref_frames != decoder_model->num_ref_frames)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } - for (int i = 0; i < decoder_model->num_ref_frames; ++i) { - if (cm->ref_frame_map[i] == NULL && decoder_model->vbi[i] != -1) { + const int limit = + closed_loop_key ? REF_FRAMES : decoder_model->num_ref_frames; + for (int i = 0; i < limit; ++i) { + if ((closed_loop_key || cm->ref_frame_map[i] == NULL) && + decoder_model->vbi[i] != -1) { if (!release_decoder_reference(decoder_model, decoder_model->vbi[i])) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; @@ -929,6 +934,30 @@ bool av2_encoder_decoder_model_sync_invalid_ref_buffers( return true; } +void av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points( + const AV2_COMP *const cpi) { + if (cpi == NULL || !cpi->level_params.keep_level_stats || + is_stat_generation_stage(cpi)) { + return; + } + const AV2LevelParams *const level_params = &cpi->level_params; + const SequenceHeader *const seq_params = &cpi->common.seq_params; + for (int op = 0; op < seq_params->operating_points_cnt_minus_1 + 1; ++op) { + if (!((level_params->keep_level_stats >> op) & 1) || + level_params->level_info[op] == NULL) { + continue; + } + DECODER_MODEL *const decoder_models = + level_params->level_info[op]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + DECODER_MODEL *const decoder_model = &decoder_models[level]; + if (decoder_model->status != DECODER_MODEL_OK) continue; + av2_encoder_decoder_model_invalidate_ref_buffers(&cpi->common, + decoder_model, false); + } + } +} + static bool capture_presentation_descriptor( const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, uint64_t output_luma_samples, int buffer_index, uint64_t generation, @@ -1133,7 +1162,7 @@ static double time_to_decode_frame(const AV2_COMMON *const cm, // It corresponds to "start_decode_at_removal_time" in the spec. static void release_processed_frames(DECODER_MODEL *const decoder_model, double removal_time) { - for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { + for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->player_ref_count > 0) { // Presentation offsets assigned before the initial delay is known are @@ -1152,7 +1181,7 @@ static void release_processed_frames(DECODER_MODEL *const decoder_model, static int frames_in_buffer_pool(const DECODER_MODEL *const decoder_model) { int frames_in_pool = 0; - for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { + for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { const FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->decoder_ref_count > 0 || @@ -1163,6 +1192,36 @@ static int frames_in_buffer_pool(const DECODER_MODEL *const decoder_model) { return frames_in_pool; } +// This corresponds to "set_initial_presentation_delay" in the spec. +static void decoder_model_set_initial_presentation_delay( + DECODER_MODEL *const decoder_model, bool end_of_bitstream, + bool show_existing_frame) { + if (decoder_model == NULL || decoder_model->status != DECODER_MODEL_OK || + decoder_model->initial_presentation_delay >= 0.0 || + (!end_of_bitstream && + (show_existing_frame || frames_in_buffer_pool(decoder_model) < + decoder_model->initial_display_delay))) { + return; + } + + decoder_model->initial_presentation_delay = decoder_model->current_time; + if (decoder_model->presentation_time >= 0.0) { + decoder_model->presentation_time += + decoder_model->initial_presentation_delay; + } + // Presentation times in every physical buffer are provisional until the + // initial delay is known, including buffers outside the active range. + for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { + FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; + if (this_buffer->player_ref_count == 0) continue; + assert(this_buffer->display_index >= 0); + if (this_buffer->presentation_time >= 0.0) { + this_buffer->presentation_time += + decoder_model->initial_presentation_delay; + } + } +} + #define MAX_TIME 1e16 static double time_next_buffer_is_free_with_source( const DECODER_MODEL *const decoder_model, bool *from_current_time) { @@ -1238,6 +1297,24 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, const AV2_COMMON *const cm = &cpi->common; const SequenceHeader *const seq_params = &cm->seq_params; decoder_model->is_still_picture = seq_params->still_picture; + decoder_model->configured_operating_point_count = + seq_params->operating_points_cnt_minus_1 + 1; + decoder_model->configured_operating_point_idc = + seq_params->operating_point_idc[op_index]; + decoder_model->configuration_snapshot_valid = true; + decoder_model->configured_profile = seq_params->seq_profile_idc; + decoder_model->configured_max_frame_width = seq_params->max_frame_width; + decoder_model->configured_max_frame_height = seq_params->max_frame_height; + decoder_model->configured_max_mlayer_id = seq_params->max_mlayer_id; + decoder_model->configured_number_xlayers = cm->number_xlayers; + decoder_model->configured_timing_info_present = + cm->ci_params_encoder.ci_timing_info_present_flag != 0; + if (decoder_model->configured_timing_info_present) { + decoder_model->configured_num_units_in_display_tick = + cm->ci_params_encoder.timing_info.num_units_in_display_tick; + decoder_model->configured_time_scale = + cm->ci_params_encoder.timing_info.time_scale; + } uint32_t scale_numerator; uint32_t scale_denominator; Av2DmRational bit_rate; @@ -1606,6 +1683,10 @@ bool av2_encoder_decoder_model_store_frame_constraints( } return false; } + if (decoder_model->applicable_dfg_count == UINT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } if (decoder_model->pending_frame.valid) { const double frame_parsing_time = @@ -1627,6 +1708,7 @@ bool av2_encoder_decoder_model_store_frame_constraints( } decoder_model->pending_frame = *current_frame; + ++decoder_model->applicable_dfg_count; decoder_model->frame_constraints_finalized = false; return true; } @@ -1640,6 +1722,7 @@ void av2_encoder_decoder_model_finalize_frame_constraints( } decoder_model->frame_constraints_finalized = true; if (is_still_picture || !decoder_model->pending_frame.valid) return; + if (decoder_model->applicable_dfg_count == 1) return; if (!decoder_model->last_frame_parsing_time_valid) { decoder_model->status = DECODER_MODEL_INCOMPLETE; return; @@ -1675,9 +1758,12 @@ static bool get_minimum_presentation_interval( } const SequenceHeader *const seq_params = &cpi->common.seq_params; + const BITSTREAM_PROFILE profile = decoder_model->configuration_snapshot_valid + ? decoder_model->configured_profile + : seq_params->seq_profile_idc; Av2DmLevelLimits limits; if (!av2_dm_get_level_limits(decoder_model->level, decoder_model->tier, - seq_params->seq_profile_idc, &limits)) { + profile, &limits)) { return false; } const uint32_t scale_numerator = decoder_model->multistream_scale_numerator; @@ -1686,20 +1772,27 @@ static bool get_minimum_presentation_interval( if (scale_numerator == 0 || scale_denominator == 0 || (scale_numerator != scale_denominator && !av2_dm_apply_multistream_limits( - decoder_model->level, decoder_model->tier, - seq_params->seq_profile_idc, scale_numerator, scale_denominator, - &limits))) { + decoder_model->level, decoder_model->tier, profile, scale_numerator, + scale_denominator, &limits))) { return false; } - if (seq_params->max_frame_width <= 0 || seq_params->max_frame_height <= 0 || + const int configured_max_frame_width = + decoder_model->configuration_snapshot_valid + ? decoder_model->configured_max_frame_width + : seq_params->max_frame_width; + const int configured_max_frame_height = + decoder_model->configuration_snapshot_valid + ? decoder_model->configured_max_frame_height + : seq_params->max_frame_height; + if (configured_max_frame_width <= 0 || configured_max_frame_height <= 0 || decoder_model->num_frames_current_tu == 0 || limits.max_display_rate == 0 || limits.max_decode_rate == 0 || limits.max_header_rate == 0) { return false; } - const uint64_t max_frame_width = (uint32_t)seq_params->max_frame_width; - const uint64_t max_frame_height = (uint32_t)seq_params->max_frame_height; + const uint64_t max_frame_width = (uint32_t)configured_max_frame_width; + const uint64_t max_frame_height = (uint32_t)configured_max_frame_height; if (max_frame_width > UINT64_MAX / max_frame_height) return false; const uint64_t max_frame_samples = max_frame_width * max_frame_height; if (max_frame_samples > UINT64_MAX / decoder_model->num_frames_current_tu) { @@ -1737,6 +1830,7 @@ void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, decoder_model->display_samples == 0) { return; } + if (decoder_model->output_tu_count == 1) return; if (!decoder_model->last_display_duration_valid) { decoder_model->status = DECODER_MODEL_INCOMPLETE; return; @@ -1747,6 +1841,81 @@ void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, } } +static bool decoder_model_configuration_is_compatible( + const DECODER_MODEL *const current, + const DECODER_MODEL *const replacement) { + return current->level == replacement->level && + current->operating_point == replacement->operating_point && + current->is_still_picture == replacement->is_still_picture && + current->configured_operating_point_count == + replacement->configured_operating_point_count && + current->configured_operating_point_idc == + replacement->configured_operating_point_idc && + current->configured_max_frame_width == + replacement->configured_max_frame_width && + current->configured_max_frame_height == + replacement->configured_max_frame_height && + current->configured_max_mlayer_id == + replacement->configured_max_mlayer_id && + current->configured_number_xlayers == + replacement->configured_number_xlayers && + current->configured_timing_info_present == + replacement->configured_timing_info_present && + current->configured_num_units_in_display_tick == + replacement->configured_num_units_in_display_tick && + current->configured_time_scale == replacement->configured_time_scale && + current->equal_picture_interval == + replacement->equal_picture_interval && + current->num_ticks_per_picture == replacement->num_ticks_per_picture && + current->display_clock_tick == replacement->display_clock_tick; +} + +static bool update_decoder_model_parameters_at_clk( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model) { + for (int i = 0; i < REF_FRAMES; ++i) { + if (decoder_model->vbi[i] != -1) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return false; + } + } + + DECODER_MODEL replacement; + av2_decoder_model_init(cpi, decoder_model->level, + decoder_model->operating_point, &replacement); + if (replacement.status != DECODER_MODEL_OK || !replacement.initialized) { + decoder_model->status = DECODER_MODEL_INCOMPLETE; + return false; + } + if (!decoder_model_configuration_is_compatible(decoder_model, &replacement)) { + decoder_model->status = DECODER_MODEL_UNSUPPORTED; + return false; + } + + decoder_model->tier = replacement.tier; + decoder_model->configured_profile = replacement.configured_profile; + decoder_model->level_limits = replacement.level_limits; + decoder_model->encoder_buffer_delay = replacement.encoder_buffer_delay; + decoder_model->decoder_buffer_delay = replacement.decoder_buffer_delay; + decoder_model->multistream_scale_numerator = + replacement.multistream_scale_numerator; + decoder_model->multistream_scale_denominator = + replacement.multistream_scale_denominator; + decoder_model->decode_rate = replacement.decode_rate; + decoder_model->bit_rate = replacement.bit_rate; + decoder_model->buffer_size = replacement.buffer_size; + decoder_model->mode = replacement.mode; + decoder_model->is_low_delay_mode = replacement.is_low_delay_mode; + decoder_model->num_ref_frames = replacement.num_ref_frames; + + decoder_model->first_bit_arrival_time = 0.0; + decoder_model->last_bit_arrival_time = 0.0; + decoder_model->dfg_interval_queue.head = 0; + decoder_model->dfg_interval_queue.size = 0; + decoder_model->dfg_interval_queue.total_interval = 0.0; + decoder_model->dfg_interval_queue.total_bits = 0; + return true; +} + static void av2_decoder_model_start_frame_decode( const AV2_COMP *const cpi, uint64_t dfg_bits, int64_t compressed_size, DECODER_MODEL *const decoder_model) { @@ -1756,7 +1925,15 @@ static void av2_decoder_model_start_frame_decode( const AV2_COMMON *const cm = &cpi->common; const SequenceHeader *const seq_params = &cm->seq_params; - if (!av2_encoder_decoder_model_sync_invalid_ref_buffers(cm, decoder_model)) { + const bool closed_loop_key = + cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY && + cpi->dm_starts_temporal_unit; + if (closed_loop_key && !av2_encoder_decoder_model_invalidate_ref_buffers( + cm, decoder_model, true)) { + return; + } + if (closed_loop_key && + !update_decoder_model_parameters_at_clk(cpi, decoder_model)) { return; } if (cpi->dm_starts_temporal_unit) { @@ -1955,27 +2132,10 @@ static void av2_decoder_model_update_buffer_and_finish_frame_decode( return; } - if (decoder_model->initial_presentation_delay < 0.0) { - // Display can begin after required number of frames have been buffered. - if (frames_in_buffer_pool(decoder_model) >= - decoder_model->initial_display_delay) { - decoder_model->initial_presentation_delay = decoder_model->current_time; - if (decoder_model->presentation_time >= 0.0) - decoder_model->presentation_time += - decoder_model->initial_presentation_delay; - // Update presentation time for each shown frame in the frame buffer. - for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { - FRAME_BUFFER *const this_buffer = - &decoder_model->frame_buffer_pool[i]; - if (this_buffer->player_ref_count == 0) continue; - assert(this_buffer->display_index >= 0); - if (this_buffer->presentation_time >= 0.0) { - this_buffer->presentation_time += - decoder_model->initial_presentation_delay; - } - } - } - } + // Display can begin after the required number of frames have been + // buffered. + decoder_model_set_initial_presentation_delay(decoder_model, false, + show_existing_frame); } } void av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( @@ -2167,6 +2327,14 @@ static void av2_decoder_model_check_output_frame( decoder_model->last_output_temporal_unit_valid && presentation->temporal_unit_index != decoder_model->last_output_temporal_unit; + if (!decoder_model->last_output_temporal_unit_valid || + starts_new_output_temporal_unit) { + if (decoder_model->output_tu_count == UINT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + ++decoder_model->output_tu_count; + } const uint64_t output_rap_epoch = presentation->leading_frame && presentation->rap_epoch != 0 ? presentation->rap_epoch - 1 @@ -2577,6 +2745,20 @@ void av2_encoder_decoder_model_finish_for_operating_points( is_stat_generation_stage(cpi)) { return; } + for (int op = 0; op < MAX_NUM_OPERATING_POINTS; ++op) { + if (!((cpi->level_params.keep_level_stats >> op) & 1) || + cpi->level_params.level_info[op] == NULL) { + continue; + } + DECODER_MODEL *const decoder_models = + cpi->level_params.level_info[op]->decoder_models; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + if (decoder_models[level].initialized) { + decoder_model_set_initial_presentation_delay(&decoder_models[level], + true, false); + } + } + } av2_decoder_model_flush_implicit_output_for_operating_points(cpi, false); for (int op = 0; op < MAX_NUM_OPERATING_POINTS; ++op) { if (!((cpi->level_params.keep_level_stats >> op) & 1) || @@ -2586,9 +2768,8 @@ void av2_encoder_decoder_model_finish_for_operating_points( DECODER_MODEL *const decoder_models = cpi->level_params.level_info[op]->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { - DECODER_MODEL *const decoder_model = &decoder_models[level]; - av2_encoder_decoder_model_finalize(decoder_model, - decoder_model->is_still_picture); + av2_encoder_decoder_model_finalize( + &decoder_models[level], decoder_models[level].is_still_picture); } } } @@ -2603,27 +2784,46 @@ int level_to_sub_stream_level_index(AV2_LEVEL level, double scaling_factor_x) { return 3 * level_base + offset; } +static void reset_non_model_level_info(AV2LevelInfo *const level_info) { + memset(&level_info->level_stats, 0, sizeof(level_info->level_stats)); + memset(&level_info->level_spec, 0, sizeof(level_info->level_spec)); + memset(&level_info->frame_window_buffer, 0, + sizeof(level_info->frame_window_buffer)); + + level_info->level_spec.level = SEQ_LEVEL_MAX; + level_info->level_stats.min_cropped_tile_width = INT_MAX; + level_info->level_stats.min_cropped_tile_height = INT_MAX; + level_info->level_stats.min_frame_width = INT_MAX; + level_info->level_stats.min_frame_height = INT_MAX; + level_info->level_stats.tile_width_is_valid = 1; + level_info->level_stats.min_cr = 1e8; +} + +void av2_reset_level_info_for_new_cvs(AV2_COMP *cpi) { + for (int op_index = 0; op_index < MAX_NUM_OPERATING_POINTS; ++op_index) { + AV2LevelInfo *const level_info = cpi->level_params.level_info[op_index]; + if (level_info != NULL) reset_non_model_level_info(level_info); + } +} + +static bool level_info_has_model_state(const AV2LevelInfo *level_info) { + if (level_info == NULL) return false; + for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + const DECODER_MODEL *const model = &level_info->decoder_models[level]; + if (model->initialized || model->status != DECODER_MODEL_OK) return true; + } + return false; +} + void av2_init_level_info(AV2_COMP *cpi) { for (int op_index = 0; op_index < MAX_NUM_OPERATING_POINTS; ++op_index) { AV2LevelInfo *const this_level_info = cpi->level_params.level_info[op_index]; if (!this_level_info) continue; av2_encoder_decoder_models_destroy(this_level_info); - memset(this_level_info, 0, sizeof(*this_level_info)); - AV2LevelSpec *const level_spec = &this_level_info->level_spec; - level_spec->level = SEQ_LEVEL_MAX; - AV2LevelStats *const level_stats = &this_level_info->level_stats; - level_stats->min_cropped_tile_width = INT_MAX; - level_stats->min_cropped_tile_height = INT_MAX; - level_stats->min_frame_width = INT_MAX; - level_stats->min_frame_height = INT_MAX; - level_stats->tile_width_is_valid = 1; - level_stats->min_cr = 1e8; - - FrameWindowBuffer *const frame_window_buffer = - &this_level_info->frame_window_buffer; - frame_window_buffer->num = 0; - frame_window_buffer->start = 0; + memset(this_level_info->decoder_models, 0, + sizeof(this_level_info->decoder_models)); + reset_non_model_level_info(this_level_info); const AV2_COMMON *const cm = &cpi->common; const int upscaled_width = cm->width; @@ -2644,6 +2844,18 @@ void av2_init_level_info(AV2_COMP *cpi) { } } +void av2_prepare_level_info_for_new_cvs(AV2_COMP *cpi) { + for (int op_index = 0; op_index < MAX_NUM_OPERATING_POINTS; ++op_index) { + const AV2LevelInfo *const level_info = + cpi->level_params.level_info[op_index]; + if (level_info != NULL && !level_info_has_model_state(level_info)) { + av2_init_level_info(cpi); + return; + } + } + av2_reset_level_info_for_new_cvs(cpi); +} + static void get_temporal_parallel_params(int scalability_mode_idc, int *temporal_parallel_num, int *temporal_parallel_denom) { @@ -2884,15 +3096,6 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( return fail_id; } -static bool level_info_has_model_state(const AV2LevelInfo *level_info) { - if (level_info == NULL) return false; - for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { - const DECODER_MODEL *const model = &level_info->decoder_models[level]; - if (model->initialized || model->status != DECODER_MODEL_OK) return true; - } - return false; -} - void av2_encoder_check_target_level(AV2_COMP *cpi, bool all_operating_points) { if (cpi == NULL || !cpi->level_params.keep_level_stats || is_stat_generation_stage(cpi)) { diff --git a/av2/common/level.h b/av2/common/level.h index d3d0f7e34e..b466264217 100644 --- a/av2/common/level.h +++ b/av2/common/level.h @@ -194,6 +194,17 @@ typedef struct { int decoder_buffer_delay; // In units of 1/90000 seconds. int num_ticks_per_picture; int initial_display_delay; // In units of frames. + int configured_operating_point_count; + int configured_operating_point_idc; + bool configuration_snapshot_valid; + BITSTREAM_PROFILE configured_profile; + int configured_max_frame_width; + int configured_max_frame_height; + int configured_max_mlayer_id; + unsigned int configured_number_xlayers; + bool configured_timing_info_present; + uint32_t configured_num_units_in_display_tick; + uint32_t configured_time_scale; uint32_t multistream_scale_numerator; uint32_t multistream_scale_denominator; double decode_rate; @@ -257,6 +268,7 @@ typedef struct { uint64_t num_frames_current_tu; ENCODER_DECODER_MODEL_FRAME pending_frame; + uint64_t applicable_dfg_count; bool last_frame_parsing_time_valid; double last_frame_parsing_time; bool last_frame_parsing_time_at_decode_limit; @@ -264,6 +276,7 @@ typedef struct { bool frame_constraints_finalized; bool last_display_duration_valid; double last_display_duration; + uint64_t output_tu_count; bool finalized; // Tracks whether every inter-TU presentation interval satisfies the minimum @@ -316,6 +329,8 @@ int av2_get_substream_level_spec(int level_index, uint32_t scale_numerator, AV2SubstreamLevelSpec *level_spec); void av2_init_level_info(struct AV2_COMP *cpi); +void av2_reset_level_info_for_new_cvs(struct AV2_COMP *cpi); +void av2_prepare_level_info_for_new_cvs(struct AV2_COMP *cpi); void av2_encoder_decoder_model_finish_for_operating_points( const struct AV2_COMP *cpi); void av2_encoder_check_target_level(struct AV2_COMP *cpi, @@ -380,11 +395,13 @@ void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, ENCODER_DM_RESULT_CLASS av2_encoder_decoder_model_classify_status( DECODER_MODEL_STATUS status); -// Encoder-internal, model-only helpers corresponding to the invalid-reference -// synchronization at the start of Annex E start_frame_decode() and the -// decoded-generation assignment performed for a newly decoded frame. -bool av2_encoder_decoder_model_sync_invalid_ref_buffers( - const AV2_COMMON *cm, DECODER_MODEL *decoder_model); +// Encoder-internal, model-only helpers corresponding to Annex E reference +// invalidation and the decoded-generation assignment for a newly decoded +// frame. +bool av2_encoder_decoder_model_invalidate_ref_buffers( + const AV2_COMMON *cm, DECODER_MODEL *decoder_model, bool closed_loop_key); +void av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points( + const struct AV2_COMP *cpi); bool av2_encoder_decoder_model_capture_current_generation( const struct AV2_COMP *cpi, DECODER_MODEL *decoder_model, uint64_t output_luma_samples); diff --git a/av2/decoder/decoder.h b/av2/decoder/decoder.h index 7efc240f2d..92dfd5b43a 100644 --- a/av2/decoder/decoder.h +++ b/av2/decoder/decoder.h @@ -413,6 +413,7 @@ typedef struct AV2Decoder { SubBitstreamExtractionState sbe_state; struct Av2DecoderModelVerifier *decoder_model_verifier; int decoder_model_check_mode; + int decoder_model_check_every_rap; bool decoder_model_verifier_allocation_failed; bool decoder_model_verifier_allocation_reported; int seen_frame_header; diff --git a/av2/decoder/decoder_model.c b/av2/decoder/decoder_model.c index 9aaa9a9b51..e1103c10c3 100644 --- a/av2/decoder/decoder_model.c +++ b/av2/decoder/decoder_model.c @@ -193,8 +193,6 @@ typedef struct Av2DmContextEvent { uint32_t ras_seed_count; Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; bool ras_seed_complete; - bool source_display_duration_valid; - Av2DmRational source_display_duration; Av2DmIndeterminateReason indeterminate_reason; } Av2DmContextEvent; @@ -235,6 +233,9 @@ typedef struct Av2DmContext { uint64_t last_stream_generation; bool last_ras_seed_complete; uint32_t last_ras_seed_count; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; } Av2DmContext; typedef struct Av2DmCvsAggregate { @@ -247,6 +248,9 @@ typedef struct Av2DmCvsAggregate { uint64_t run_status_count[4]; bool verification_complete; Av2DmIndeterminateReason reason; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; } Av2DmCvsAggregate; typedef enum Av2DmVerifierErrorCode { @@ -263,6 +267,8 @@ struct Av2DecoderModelVerifier { bool aggregate_incomplete; bool error_emitted; int check_mode; + bool check_every_rap; + bool rap_coverage_warning_emitted; bool defer_nonterminal_checks_for_testing; Av2DmVerifierErrorCode error_code; bool temporal_point_present; @@ -328,6 +334,9 @@ struct Av2DecoderModelVerifier { size_t active_record_count; size_t active_record_capacity; size_t rap_start_count; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; bool last_rap_start_valid; int last_rap_xlayer_id; int last_rap_mlayer_id; @@ -1031,6 +1040,8 @@ void av2_decoder_model_verifier_init(AV2Decoder *pbi) { pbi->decoder_model_verifier == NULL; if (pbi->decoder_model_verifier != NULL) { pbi->decoder_model_verifier->check_mode = pbi->decoder_model_check_mode; + pbi->decoder_model_verifier->check_every_rap = + pbi->decoder_model_check_every_rap != 0; for (int i = 0; i < MAX_NUM_XLAYERS; ++i) { pbi->decoder_model_verifier->current_cvs[i] = SIZE_MAX; } @@ -2196,10 +2207,6 @@ struct Av2DmLiveRun { int64_t rap; bool olk; uint64_t start_source_frame_unit; - bool terminal_frame_parsing_time_valid; - Av2DmRational terminal_frame_parsing_time; - bool terminal_display_duration_valid; - Av2DmRational terminal_display_duration; }; void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( @@ -2217,6 +2224,14 @@ void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( } } +void av2_decoder_model_verifier_force_rap_coverage_overflow_for_testing( + AV2Decoder *pbi) { + if (pbi == NULL || !verifier_accepts_events(pbi->decoder_model_verifier)) { + return; + } + pbi->decoder_model_verifier->applicable_rap_starts = UINT64_MAX; +} + static const char *indeterminate_reason_name(Av2DmIndeterminateReason reason) { switch (reason) { case AV2_DM_REASON_NONE: return "none"; @@ -2235,6 +2250,8 @@ static const char *indeterminate_reason_name(Av2DmIndeterminateReason reason) { case AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION: return "incompatible_configuration_transition"; case AV2_DM_REASON_INTERNAL_FAILURE: return "internal_failure"; + case AV2_DM_REASON_RAP_START_CHECKS_DISABLED: + return "rap_start_checks_disabled"; } return "internal_failure"; } @@ -3064,17 +3081,15 @@ static bool run_seed_contains_generation(const Av2DmLiveRun *run, return false; } -static bool run_accepts_event(const Av2DmLiveRun *run, - const Av2DmContextEvent *event) { - return !run->olk || - event->source_frame_unit_index <= run->start_source_frame_unit || - !event->leading_frame; -} - static void apply_event_to_run(Av2DecoderModelVerifier *verifier, Av2DmLiveRun *run, const Av2DmContextEvent *event) { - if (verifier->fatal_violation || !run_accepts_event(run, event)) return; + if (verifier->fatal_violation) return; + if (run->olk && + event->source_frame_unit_index > run->start_source_frame_unit && + event->leading_frame) { + return; + } run->report.current_event = *event; run->report.current_event_valid = true; if (event->indeterminate_reason != AV2_DM_REASON_NONE && @@ -3125,69 +3140,6 @@ static void apply_event_to_run(Av2DecoderModelVerifier *verifier, memset(&run->report.current_event, 0, sizeof(run->report.current_event)); } -static void capture_terminal_history(Av2DmContext *context, - const Av2DmContextEvent *event) { - if (context->run_count < 2 || - (event->type != AV2_DM_CONTEXT_FRAME && - event->type != AV2_DM_CONTEXT_OUTPUT) || - !run_accepts_event(context->runs[0], event) || - context->runs[0]->reason == - AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION) { - return; - } - Av2DmState source_state; - if (!av2_decoder_model_get_state(context->runs[0]->model, &source_state)) { - return; - } - for (size_t i = 1; i < context->run_count; ++i) { - Av2DmLiveRun *const run = context->runs[i]; - if (run->rap < 0 || run->stream_generation != event->stream_generation || - run->stream_generation != context->runs[0]->stream_generation || - !run_accepts_event(run, event)) { - continue; - } - if (event->type == AV2_DM_CONTEXT_FRAME && - source_state.last_frame_parsing_time_valid) { - run->terminal_frame_parsing_time = source_state.last_frame_parsing_time; - run->terminal_frame_parsing_time_valid = true; - } else if (event->type == AV2_DM_CONTEXT_OUTPUT && - source_state.last_display_duration_valid) { - run->terminal_display_duration = source_state.last_display_duration; - run->terminal_display_duration_valid = true; - } - } -} - -static void capture_replayed_terminal_history(const Av2DmContext *context, - Av2DmLiveRun *run, - const Av2DmContextEvent *event) { - if (event->type == AV2_DM_CONTEXT_OUTPUT && - event->source_display_duration_valid && context->run_count != 0 && - context->runs[0]->reason != - AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION && - run->stream_generation == event->stream_generation && - run_accepts_event(run, event)) { - run->terminal_display_duration = event->source_display_duration; - run->terminal_display_duration_valid = true; - } -} - -static void attach_source_terminal_history(const Av2DmContext *context, - Av2DmContextEvent *event) { - if (context->run_count == 0 || event->type != AV2_DM_CONTEXT_OUTPUT || - !run_accepts_event(context->runs[0], event) || - context->runs[0]->reason == - AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION) { - return; - } - Av2DmState source_state; - if (av2_decoder_model_get_state(context->runs[0]->model, &source_state) && - source_state.last_display_duration_valid) { - event->source_display_duration = source_state.last_display_duration; - event->source_display_duration_valid = true; - } -} - static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, Av2DmContext *context, const Av2DmContextEvent *start_frame, @@ -3282,20 +3234,6 @@ static void finish_context_runs(Av2DecoderModelVerifier *verifier, for (size_t i = 0; i < context->run_count; ++i) { Av2DmLiveRun *const run = context->runs[i]; if (!verifier->failed && !verifier->fatal_violation) { - if (i != 0 && run->rap >= 0) { - const Av2DmRational *const frame_parsing_time = - run->terminal_frame_parsing_time_valid - ? &run->terminal_frame_parsing_time - : NULL; - const Av2DmRational *const display_duration = - run->terminal_display_duration_valid - ? &run->terminal_display_duration - : NULL; - if (!av2_decoder_model_seed_terminal_history( - run->model, frame_parsing_time, display_duration)) { - mark_failed(verifier); - } - } av2_decoder_model_finish(run->model); } Av2DmResult result; @@ -3448,6 +3386,36 @@ static void mark_live_runs_for_incompatible_configuration( } } +static bool increment_rap_coverage_counts(Av2DecoderModelVerifier *verifier, + uint64_t *context_count, + uint64_t *cvs_count, + uint64_t *aggregate_count) { + return increment_u64(verifier, context_count) && + increment_u64(verifier, cvs_count) && + increment_u64(verifier, aggregate_count); +} + +static void emit_rap_coverage_warning(Av2DecoderModelVerifier *verifier, + const Av2DmContext *context, + const Av2DmCvsAggregate *cvs, + const Av2DmContextEvent *event) { + if (verifier->rap_coverage_warning_emitted) return; + fprintf( + stderr, + "AV2_DECODER_MODEL_COVERAGE_WARNING status=INDETERMINATE " + "code=RAP_START_CHECKS_DISABLED xlayer=%d ops=%d op=%d scope=%s " + "event=%" PRIu64 " cvs=%" PRIu64 + " source_rap_starts=%zu " + "scope_applicable_rap_starts=%" PRIu64 " scope_rap_runs_started=%" PRIu64 + " scope_rap_runs_skipped=%" PRIu64 " reason=rap_start_checks_disabled\n", + context->key.xlayer_id, context->key.ops_id, context->key.operating_point, + context->key.whole_xlayer ? "whole_xlayer" : "operating_point", + event->event_index, cvs->number, verifier->rap_start_count, + context->applicable_rap_starts, context->rap_runs_started, + context->rap_runs_skipped); + verifier->rap_coverage_warning_emitted = true; +} + static void dispatch_context_event(Av2DecoderModelVerifier *verifier, Av2DmContext *context, const Av2DmContextEvent *event) { @@ -3457,7 +3425,6 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, ++i) { apply_event_to_run(verifier, context->runs[i], event); } - if (!verifier->fatal_violation) capture_terminal_history(context, event); if (!verifier->current_source_frame_dispatched) { if (context->prefix_event_count == SIZE_MAX) { mark_arithmetic_failed(verifier); @@ -3471,14 +3438,20 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, return; } context->prefix_events[context->prefix_event_count] = *event; - attach_source_terminal_history( - context, &context->prefix_events[context->prefix_event_count]); if (!increment_size(verifier, &context->prefix_event_count)) return; } return; } - if (ensure_cvs_open(verifier, context->key.xlayer_id, NULL) == NULL) return; + Av2DmCvsAggregate *const cvs = + ensure_cvs_open(verifier, context->key.xlayer_id, NULL); + if (cvs == NULL) return; + if (event->frame.random_access_point && + !increment_rap_coverage_counts(verifier, &context->applicable_rap_starts, + &cvs->applicable_rap_starts, + &verifier->applicable_rap_starts)) { + return; + } const bool config_changed = context->last_config_present && (context->last_stream_generation != event->stream_generation || @@ -3506,32 +3479,48 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, : -1; Av2DmLiveRun *const run = create_live_run(verifier, context, event, rap); if (run == NULL) return; + if (event->frame.random_access_point && + !increment_rap_coverage_counts(verifier, &context->rap_runs_started, + &cvs->rap_runs_started, + &verifier->rap_runs_started)) { + return; + } for (size_t i = 0; i < context->prefix_event_count; ++i) { if (prefix_event_applies(run, &context->prefix_events[i])) { apply_event_to_run(verifier, run, &context->prefix_events[i]); - capture_replayed_terminal_history(context, run, - &context->prefix_events[i]); } } created_segment = true; } if (!created_segment && event->frame.random_access_point) { - Av2DmLiveRun *const run = create_live_run( - verifier, context, event, (int64_t)event->source_frame_unit_index); - if (run == NULL) return; - for (size_t i = 0; i < context->prefix_event_count; ++i) { - if (prefix_event_applies(run, &context->prefix_events[i])) { - apply_event_to_run(verifier, run, &context->prefix_events[i]); - capture_replayed_terminal_history(context, run, - &context->prefix_events[i]); + if (verifier->check_every_rap) { + Av2DmLiveRun *const run = create_live_run( + verifier, context, event, (int64_t)event->source_frame_unit_index); + if (run == NULL) return; + if (!increment_rap_coverage_counts(verifier, &context->rap_runs_started, + &cvs->rap_runs_started, + &verifier->rap_runs_started)) { + return; + } + for (size_t i = 0; i < context->prefix_event_count; ++i) { + if (prefix_event_applies(run, &context->prefix_events[i])) { + apply_event_to_run(verifier, run, &context->prefix_events[i]); + } + } + } else { + if (!increment_rap_coverage_counts(verifier, &context->rap_runs_skipped, + &cvs->rap_runs_skipped, + &verifier->rap_runs_skipped)) { + return; } + cvs->verification_complete = false; + emit_rap_coverage_warning(verifier, context, cvs, event); } } for (size_t i = 0; i < context->run_count && !verifier->fatal_violation; ++i) { apply_event_to_run(verifier, context->runs[i], event); } - if (!verifier->fatal_violation) capture_terminal_history(context, event); context->last_config_present = true; context->last_config = event->config; context->last_stream_generation = event->stream_generation; @@ -3635,6 +3624,16 @@ static void emit_cvs_result(Av2DecoderModelVerifier *verifier, } } Av2DmResultStatus status = aggregate_status(cvs->run_status_count); + const bool coverage_suppressed = cvs->rap_runs_skipped != 0; + if (coverage_suppressed) { + cvs->verification_complete = false; + if (status != AV2_DM_RESULT_NON_CONFORMANT) { + status = AV2_DM_RESULT_INDETERMINATE; + if (cvs->reason != AV2_DM_REASON_INTERNAL_FAILURE) { + cvs->reason = AV2_DM_REASON_RAP_START_CHECKS_DISABLED; + } + } + } if (verifier->bitstream_cvs == UINT64_MAX || verifier->bitstream_status_count[status] == UINT64_MAX) { mark_arithmetic_failed(verifier); @@ -3662,10 +3661,18 @@ static void emit_cvs_result(Av2DecoderModelVerifier *verifier, } fprintf(stderr, "AV2_DECODER_MODEL_CVS_RESULT status=%s xlayer=%d cvs=%" PRIu64 - " violations=%" PRIu64 " verification_complete=%d reason=%s\n", + " violations=%" PRIu64 " verification_complete=%d reason=%s", result_name(status), cvs->xlayer_id, cvs->number, cvs->violations, cvs->verification_complete ? 1 : 0, indeterminate_reason_name(cvs->reason)); + if (coverage_suppressed) { + fprintf(stderr, + " coverage_complete=0 applicable_rap_starts=%" PRIu64 + " rap_runs_started=%" PRIu64 " rap_runs_skipped=%" PRIu64, + cvs->applicable_rap_starts, cvs->rap_runs_started, + cvs->rap_runs_skipped); + } + fprintf(stderr, "\n"); cvs->result_emitted = true; } @@ -3718,7 +3725,7 @@ static void emit_bitstream_result(Av2DecoderModelVerifier *verifier, "AV2_DECODER_MODEL_BITSTREAM_RESULT status=%s complete=%d cvs=%" PRIu64 " conformant_cvs=%" PRIu64 " non_conformant_cvs=%" PRIu64 " indeterminate_cvs=%" PRIu64 " not_applicable_cvs=%" PRIu64 - " first_non_conformant_xlayer=%d first_non_conformant_cvs=%" PRIu64 "\n", + " first_non_conformant_xlayer=%d first_non_conformant_cvs=%" PRIu64, result_name(status), complete ? 1 : 0, verifier->bitstream_cvs, verifier->bitstream_status_count[AV2_DM_RESULT_CONFORMANT], verifier->bitstream_status_count[AV2_DM_RESULT_NON_CONFORMANT], @@ -3729,6 +3736,21 @@ static void emit_bitstream_result(Av2DecoderModelVerifier *verifier, : -1, verifier->first_non_conformant_valid ? verifier->first_non_conformant_cvs : 0); + if (verifier->rap_runs_skipped != 0) { + Av2DmIndeterminateReason reason = AV2_DM_REASON_NONE; + if (status != AV2_DM_RESULT_NON_CONFORMANT) { + reason = verifier->failed ? AV2_DM_REASON_INTERNAL_FAILURE + : AV2_DM_REASON_RAP_START_CHECKS_DISABLED; + } + fprintf(stderr, + " coverage_complete=0 source_rap_starts=%zu " + "applicable_rap_starts=%" PRIu64 " rap_runs_started=%" PRIu64 + " rap_runs_skipped=%" PRIu64 " reason=%s", + verifier->rap_start_count, verifier->applicable_rap_starts, + verifier->rap_runs_started, verifier->rap_runs_skipped, + indeterminate_reason_name(reason)); + } + fprintf(stderr, "\n"); verifier->bitstream_result_emitted = true; } @@ -3824,6 +3846,7 @@ bool av2_decoder_model_verifier_get_stats(const AV2Decoder *pbi, const Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; stats->available = true; stats->failed = verifier->failed; + stats->check_every_rap = verifier->check_every_rap; stats->raw_obus = verifier->raw_obus; stats->raw_bits = verifier->raw_bits; stats->event_count = verifier->event_count; @@ -3831,6 +3854,10 @@ bool av2_decoder_model_verifier_get_stats(const AV2Decoder *pbi, stats->frame_unit_index = verifier->frame_unit_index; stats->closed_dfgs = verifier->closed_dfgs; stats->rap_starts = verifier->rap_start_count; + stats->applicable_rap_starts = verifier->applicable_rap_starts; + stats->rap_runs_started = verifier->rap_runs_started; + stats->rap_runs_skipped = verifier->rap_runs_skipped; + stats->rap_coverage_complete = verifier->rap_runs_skipped == 0; stats->temporal_points = verifier->temporal_points; stats->temporal_point_present = verifier->temporal_point_present; stats->temporal_point = verifier->temporal_point; @@ -3942,6 +3969,9 @@ bool av2_decoder_model_verifier_get_context_stats(const AV2Decoder *pbi, } stats->last_ras_seed_complete = context->last_ras_seed_complete; stats->last_ras_seed_count = context->last_ras_seed_count; + stats->applicable_rap_starts = context->applicable_rap_starts; + stats->rap_runs_started = context->rap_runs_started; + stats->rap_runs_skipped = context->rap_runs_skipped; return true; } @@ -3974,15 +4004,6 @@ bool av2_decoder_model_verifier_get_run_stats(const AV2Decoder *pbi, stats->initial_presentation_delay_known = state.initial_presentation_delay_known; stats->initial_presentation_delay = state.initial_presentation_delay; - stats->last_frame_parsing_time_valid = state.last_frame_parsing_time_valid; - stats->last_frame_parsing_time = state.last_frame_parsing_time; - stats->last_display_duration_valid = state.last_display_duration_valid; - stats->last_display_duration = state.last_display_duration; - stats->terminal_frame_parsing_time_valid = - run->terminal_frame_parsing_time_valid; - stats->terminal_frame_parsing_time = run->terminal_frame_parsing_time; - stats->terminal_display_duration_valid = run->terminal_display_duration_valid; - stats->terminal_display_duration = run->terminal_display_duration; stats->status = result.status; stats->reason = run->reason; return true; diff --git a/av2/decoder/decoder_model.h b/av2/decoder/decoder_model.h index cee2deccee..88b2d1e7c0 100644 --- a/av2/decoder/decoder_model.h +++ b/av2/decoder/decoder_model.h @@ -43,12 +43,14 @@ typedef enum Av2DmIndeterminateReason { AV2_DM_REASON_INCOMPLETE_RAS_SEED, AV2_DM_REASON_RECOVERY_RESET, AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION, - AV2_DM_REASON_INTERNAL_FAILURE + AV2_DM_REASON_INTERNAL_FAILURE, + AV2_DM_REASON_RAP_START_CHECKS_DISABLED } Av2DmIndeterminateReason; typedef struct Av2DmVerifierStats { bool available; bool failed; + bool check_every_rap; uint64_t raw_obus; uint64_t raw_bits; uint64_t event_count; @@ -56,6 +58,10 @@ typedef struct Av2DmVerifierStats { uint64_t frame_unit_index; uint64_t closed_dfgs; uint64_t rap_starts; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; + bool rap_coverage_complete; uint64_t temporal_points; bool temporal_point_present; uint64_t temporal_point; @@ -112,6 +118,9 @@ typedef struct Av2DmContextStats { uint32_t resolved_initial_display_delay; bool last_ras_seed_complete; uint32_t last_ras_seed_count; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; } Av2DmContextStats; typedef struct Av2DmRunStats { @@ -123,14 +132,6 @@ typedef struct Av2DmRunStats { uint32_t active_num_ref_frames; bool initial_presentation_delay_known; Av2DmRational initial_presentation_delay; - bool last_frame_parsing_time_valid; - Av2DmRational last_frame_parsing_time; - bool last_display_duration_valid; - Av2DmRational last_display_duration; - bool terminal_frame_parsing_time_valid; - Av2DmRational terminal_frame_parsing_time; - bool terminal_display_duration_valid; - Av2DmRational terminal_display_duration; Av2DmResultStatus status; Av2DmIndeterminateReason reason; } Av2DmRunStats; @@ -161,6 +162,8 @@ void av2_decoder_model_verifier_on_internal_failure_for_testing( struct AV2Decoder *pbi); void av2_decoder_model_verifier_on_model_arithmetic_failure_for_testing( struct AV2Decoder *pbi); +void av2_decoder_model_verifier_force_rap_coverage_overflow_for_testing( + struct AV2Decoder *pbi); void av2_decoder_model_verifier_set_defer_nonterminal_checks_for_testing( struct AV2Decoder *pbi, bool defer); diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index 1e7da5b5f0..4707cd11d7 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -1346,6 +1346,7 @@ int av2_encode_strategy(AV2_COMP *const cpi, size_t *const size, } } } + av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points(cpi); // Set gf_state flag so the next gf group knows that the OLK has been // encoded cpi->gf_state.olk_overlay_last = 1; diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index b9cb6da234..52f8fe85c3 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -4652,6 +4652,7 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, } } } + av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points(cpi); cpi->is_olk_overlay = 1; cpi->gf_state.olk_overlay_last = 1; cpi->olk_encountered = 0; @@ -4696,6 +4697,7 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, } } } + av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points(cpi); cpi->is_olk_overlay = 1; cpi->gf_state.olk_overlay_last = 1; cpi->olk_encountered = 0; @@ -4963,11 +4965,15 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, } if (cpi->level_params.keep_level_stats && !is_stat_generation_stage(cpi)) { - // Initialize level info. at the beginning of each sequence. + // Reset per-CVS level statistics at the beginning of each sequence. if (av2_is_shown_keyframe(cpi, cm->current_frame.frame_type)) { - av2_encoder_decoder_model_finish_for_operating_points(cpi); + if (cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY && + cpi->dm_starts_temporal_unit) { + av2_decoder_model_flush_implicit_output_for_operating_points(cpi, + false); + } av2_encoder_check_target_level(cpi, true); - av2_init_level_info(cpi); + av2_prepare_level_info_for_new_cvs(cpi); } update_level_info_for_frame_unit(cpi, dest, *size, *time_stamp, *time_end); } diff --git a/avm/avmdx.h b/avm/avmdx.h index fa04a6e7a7..a8c0c2076d 100644 --- a/avm/avmdx.h +++ b/avm/avmdx.h @@ -417,6 +417,14 @@ enum avm_dec_control_id { * compressed input is submitted. */ AV2D_SET_DECODER_MODEL_CHECK_MODE, + + /*!\brief Codec control function to select whether decoder-model conformance + * checking covers every random access point, int parameter. + * + * Valid values are 0 and 1. The default is 1. The value must be set before + * compressed input is submitted. + */ + AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, }; /*!\cond */ @@ -532,6 +540,9 @@ AVM_CTRL_USE_TYPE(AV2D_SET_DECODER_MODEL_CHECK_MODE, avm_decoder_model_check_mode_t) #define AVM_CTRL_AV2D_SET_DECODER_MODEL_CHECK_MODE +AVM_CTRL_USE_TYPE(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, int) +#define AVM_CTRL_AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP + AVM_CTRL_USE_TYPE(AV2_SET_INSPECTION_CALLBACK, avm_inspect_init *) #define AVM_CTRL_AV2_SET_INSPECTION_CALLBACK /*!\endcond */ diff --git a/test/avmdec.sh b/test/avmdec.sh index 27bc53e6ef..6182fd329e 100755 --- a/test/avmdec.sh +++ b/test/avmdec.sh @@ -174,6 +174,41 @@ avmdec_random_access_kf2() { avmdec_random_access_common 2 } +avmdec_decoder_model_every_rap_option() { + if [ "$(avmdec_can_decode_av2)" = "yes" ]; then + local decoder="$(avm_tool_path avmdec)" + local file="${AV2_OBU_FILE}" + local omitted_log="${AVM_TEST_OUTPUT_DIR}/decoder_model_off_omitted.log" + local zero_log="${AVM_TEST_OUTPUT_DIR}/decoder_model_off_zero.log" + local one_log="${AVM_TEST_OUTPUT_DIR}/decoder_model_off_one.log" + if [ ! -e "${file}" ]; then + encode_yuv_raw_input_av2 "${file}" --obu || return 1 + fi + "${decoder}" --help 2>&1 | grep -q -- \ + "--check-conformance-every-rap" || return 1 + eval "${AVM_TEST_PREFIX}" "${decoder}" "${file}" --noblit \ + --check-conformance=off >"${omitted_log}" 2>&1 || return 1 + eval "${AVM_TEST_PREFIX}" "${decoder}" "${file}" --noblit \ + --check-conformance=off --check-conformance-every-rap=0 \ + >"${zero_log}" 2>&1 || return 1 + eval "${AVM_TEST_PREFIX}" "${decoder}" "${file}" --noblit \ + --check-conformance=off --check-conformance-every-rap=1 \ + >"${one_log}" 2>&1 || return 1 + if grep -q "AV2_DECODER_MODEL_" "${omitted_log}" "${zero_log}" \ + "${one_log}"; then + return 1 + fi + if avmdec "${file}" --noblit --check-conformance=off \ + --check-conformance-every-rap=2; then + return 1 + fi + if avmdec "${file}" --noblit --check-conformance=off \ + --check-conformance-every-rap=-1; then + return 1 + fi + fi +} + avmdec_tests="avmdec_av2_ivf @@ -183,7 +218,8 @@ avmdec_tests="avmdec_av2_ivf avmdec_av2_webm avmdec_random_access_kf0 avmdec_random_access_kf1 - avmdec_random_access_kf2" + avmdec_random_access_kf2 + avmdec_decoder_model_every_rap_option" if [ "$(avm_multithread_available)" = "yes" ]; then avmdec_tests="${avmdec_tests} diff --git a/test/decoder_model_integration_test.cc b/test/decoder_model_integration_test.cc index 0339cea618..1996a83a72 100644 --- a/test/decoder_model_integration_test.cc +++ b/test/decoder_model_integration_test.cc @@ -41,6 +41,7 @@ class DecoderModelAdapterTestBase : public ::testing::Test { memset(pbi_, 0, sizeof(*pbi_)); memset(&frame_, 0, sizeof(frame_)); memset(&second_frame_, 0, sizeof(second_frame_)); + pbi_->decoder_model_check_every_rap = 1; av2_decoder_model_verifier_init(pbi_); ASSERT_NE(pbi_->decoder_model_verifier, nullptr); Configure(64, 64); @@ -77,16 +78,24 @@ class DecoderModelAdapterTestBase : public ::testing::Test { av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); } + void ReinitializeVerifier(bool check_every_rap, + avm_decoder_model_check_mode_t check_mode, + int width = 64, int height = 64) { + av2_decoder_model_verifier_destroy(pbi_); + pbi_->decoder_model_check_every_rap = check_every_rap ? 1 : 0; + pbi_->decoder_model_check_mode = check_mode; + av2_decoder_model_verifier_init(pbi_); + ASSERT_NE(pbi_->decoder_model_verifier, nullptr); + Configure(width, height); + } + void StartFrame(int obu_type, int width = 64, int height = 64, RefCntBuffer *frame = nullptr, int mlayer_id = 0, FRAME_TYPE frame_type = KEY_FRAME, bool implicit_output = false, bool complete = true, int xlayer_id = 0, int temporal_id = 0, bool activate_configuration = false, bool filter_obu = false, - bool invalidate_references = false, - bool leading_frame = false, int64_t presentation_time = -1, - const RefCntBuffer *prefix_output = nullptr, - int prefix_output_ref_idx = -1) { + bool invalidate_references = false) { if (frame == nullptr) frame = &frame_; av2_decoder_model_verifier_on_source_frame_unit_start( pbi_, xlayer_id, mlayer_id, temporal_id); @@ -98,7 +107,7 @@ class DecoderModelAdapterTestBase : public ::testing::Test { cm->xlayer_id = xlayer_id; cm->mlayer_id = mlayer_id; cm->tlayer_id = temporal_id; - cm->is_leading_picture = leading_frame; + cm->is_leading_picture = 0; cm->show_existing_frame = 0; cm->implicit_output_picture = implicit_output; cm->cur_frame = frame; @@ -121,11 +130,6 @@ class DecoderModelAdapterTestBase : public ::testing::Test { frame->width = width; frame->height = height; frame->implicit_output_picture = implicit_output; - if (prefix_output != nullptr) { - av2_decoder_model_verifier_on_output(pbi_, prefix_output_ref_idx, - prefix_output, - AV2_DM_PRESENTATION_OWNER_IMPLICIT); - } if (activate_configuration) { av2_decoder_model_verifier_on_active_configuration(pbi_, xlayer_id, 0); } @@ -134,10 +138,6 @@ class DecoderModelAdapterTestBase : public ::testing::Test { pbi_, obu_type == OBU_CLOSED_LOOP_KEY); } av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); - if (presentation_time >= 0) { - av2_decoder_model_verifier_on_temporal_point( - pbi_, static_cast(presentation_time)); - } if (complete) { av2_decoder_model_verifier_record_obu(pbi_, OBU_METADATA_SHORT, xlayer_id, mlayer_id, temporal_id, 80); @@ -1449,6 +1449,366 @@ TEST_F(DecoderModelResultTest, ConsecutiveCvsRetainRequiredLiveRuns) { EXPECT_LE(finished.parameter_records, 2u); } +TEST_F(DecoderModelResultTest, + DisabledEveryRapRetainsOneRunAndReportsCoverageOnce) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_WARN); + frame_.long_term_id = -1; + testing::internal::CaptureStderr(); + + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY); + UpdateAndOutput(); + + Av2DmVerifierStats live; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); + EXPECT_EQ(live.rap_starts, 3u); + EXPECT_EQ(live.applicable_rap_starts, 3u); + EXPECT_EQ(live.rap_runs_started, 1u); + EXPECT_EQ(live.rap_runs_skipped, 2u); + EXPECT_FALSE(live.rap_coverage_complete); + EXPECT_EQ(live.live_runs, 1u); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.applicable_rap_starts, 3u); + EXPECT_EQ(context.rap_runs_started, 1u); + EXPECT_EQ(context.rap_runs_skipped, 2u); + + av2_decoder_model_verifier_finish(pbi_); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ( + CountOccurrences(diagnostics, "AV2_DECODER_MODEL_COVERAGE_WARNING "), 1u); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_RESULT "), 1u); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("reason=rap_start_checks_disabled " + "coverage_complete=0 applicable_rap_starts=3 " + "rap_runs_started=1 rap_runs_skipped=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=INDETERMINATE complete=1 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("coverage_complete=0 source_rap_starts=3 " + "applicable_rap_starts=3 rap_runs_started=1 " + "rap_runs_skipped=2 " + "reason=rap_start_checks_disabled"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + DisabledEveryRapAttributesSuppressedClkToTheNewCvs) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_WARN); + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_RESULT status=CONFORMANT " + "xlayer=0 ops=-1 op=-1 rap=0"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT status=CONFORMANT " + "xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE xlayer=0 cvs=2"), + std::string::npos); + EXPECT_NE(diagnostics.find("reason=rap_start_checks_disabled " + "coverage_complete=0 applicable_rap_starts=1 " + "rap_runs_started=0 rap_runs_skipped=1"), + std::string::npos); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_RESULT "), 1u); +} + +TEST_F(DecoderModelResultTest, + DisabledEveryRapRetainsNonRapInputRunAndSkipsFirstLaterRap) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_WARN); + testing::internal::CaptureStderr(); + StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &frame_, 0, INTER_FRAME); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY); + UpdateAndOutput(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.rap_starts, 1u); + EXPECT_EQ(stats.applicable_rap_starts, 1u); + EXPECT_EQ(stats.rap_runs_started, 0u); + EXPECT_EQ(stats.rap_runs_skipped, 1u); + EXPECT_EQ(stats.live_runs, 1u); + Av2DmRunStats run; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); + EXPECT_EQ(run.rap, -1); + EXPECT_EQ(run.decoded_frames, 2u); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ( + CountOccurrences(diagnostics, "AV2_DECODER_MODEL_COVERAGE_WARNING "), 1u); +} + +TEST_F(DecoderModelResultTest, + RapCoverageCounterOverflowCannotProduceConformantResult) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_WARN); + av2_decoder_model_verifier_force_rap_coverage_overflow_for_testing(pbi_); + StartFrame(OBU_CLOSED_LOOP_KEY); + + Av2DmVerifierStats failed; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &failed)); + EXPECT_TRUE(failed.failed); + EXPECT_EQ(failed.applicable_rap_starts, UINT64_MAX); + EXPECT_EQ(failed.live_runs, 0u); + Av2DmContextStats context; + ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.applicable_rap_starts, 1u); + + testing::internal::CaptureStderr(); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(diagnostics.find("status=CONFORMANT"), std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=INDETERMINATE"), + std::string::npos); + EXPECT_NE(diagnostics.find("reason=internal_failure"), std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=INDETERMINATE complete=0"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + DisabledEveryRapCountsMultipleScopesAndXlayersExactly) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_WARN); + pbi_->seq_list[1][0] = pbi_->seq_list[0][0]; + pbi_->common.seq_params = pbi_->seq_list[1][0]; + pbi_->obu_type = OBU_SEQUENCE_HEADER; + av2_decoder_model_verifier_on_sequence_header(pbi_, 1, 0); + av2_decoder_model_verifier_on_active_configuration(pbi_, 1, 0); + + OperatingPointSet *const ops = &pbi_->ops_list[0][1]; + memset(ops, 0, sizeof(*ops)); + ops->valid = 1; + ops->obu_xlayer_id = 0; + ops->ops_id = 1; + ops->ops_cnt = 1; + ops->ops_ptl_present_flag = 1; + ops->op[0].ops_seq_profile_idc[0] = MAIN_420_10_IP0; + ops->op[0].ops_level_idx[0] = SEQ_LEVEL_2_0; + ops->op[0].ops_mlayer_count[0] = 1; + ops->op[0].mlayer_info.ops_mlayer_map[0] = 1; + ops->op[0].mlayer_info.ops_tlayer_map[0][0] = 1; + av2_decoder_model_verifier_on_operating_point_set(pbi_, 0, 1); + + testing::internal::CaptureStderr(); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + pbi_->common.seq_params = pbi_->seq_list[1][0]; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 1, 0, true); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + pbi_->common.seq_params = pbi_->seq_list[0][0]; + StartFrame(OBU_OPEN_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0); + UpdateAndOutput(); + pbi_->common.seq_params = pbi_->seq_list[1][0]; + StartFrame(OBU_OPEN_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, + true, 1); + pbi_->common.ref_frame_map[1] = &second_frame_; + pbi_->valid_for_referencing[1] = 1; + av2_decoder_model_verifier_after_reference_update(pbi_, 2); + av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, + AV2_DM_PRESENTATION_OWNER_CURRENT); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.contexts, 3u); + EXPECT_EQ(stats.rap_starts, 4u); + EXPECT_EQ(stats.applicable_rap_starts, 6u); + EXPECT_EQ(stats.rap_runs_started, 3u); + EXPECT_EQ(stats.rap_runs_skipped, 3u); + EXPECT_EQ(stats.live_runs, 3u); + for (uint32_t i = 0; i < stats.contexts; ++i) { + Av2DmContextStats context; + ASSERT_TRUE( + av2_decoder_model_verifier_get_context_stats(pbi_, i, &context)); + EXPECT_EQ(context.applicable_rap_starts, 2u); + EXPECT_EQ(context.rap_runs_started, 1u); + EXPECT_EQ(context.rap_runs_skipped, 1u); + } + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ( + CountOccurrences(diagnostics, "AV2_DECODER_MODEL_COVERAGE_WARNING "), 1u); +} + +TEST_F(DecoderModelResultTest, + DisabledEveryRapPreservesNonConformantPrecedence) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_WARN, 640, 480); + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY, 640, 480); + UpdateAndOutput(); + EXPECT_FALSE(av2_decoder_model_verifier_should_stop(pbi_)); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.result_count, 1u); + EXPECT_EQ(stats.non_conformant_results, 1u); + EXPECT_EQ(stats.rap_runs_skipped, 1u); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " + "status=NON_CONFORMANT xlayer=0 cvs=1"), + std::string::npos); + EXPECT_NE(diagnostics.find("verification_complete=0 reason=none " + "coverage_complete=0"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE(diagnostics.find("coverage_complete=0"), std::string::npos); + EXPECT_NE(diagnostics.find("rap_runs_skipped=1 reason=none"), + std::string::npos); +} + +TEST_F(DecoderModelResultTest, + DisabledEveryRapFatalStopsOnlyAfterRetainedViolation) { + ReinitializeVerifier(false, AVM_DECODER_MODEL_CHECK_FATAL, 640, 480); + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY); + UpdateAndOutput(); + EXPECT_FALSE(av2_decoder_model_verifier_should_stop(pbi_)); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_REGULAR_TILE_GROUP, 640, 480, &frame_, 0, INTER_FRAME); + EXPECT_TRUE(av2_decoder_model_verifier_should_stop(pbi_)); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ( + CountOccurrences(diagnostics, "AV2_DECODER_MODEL_COVERAGE_WARNING "), 1u); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_WARNING " + "status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT " + "status=NON_CONFORMANT"), + std::string::npos); + EXPECT_NE(diagnostics.find("coverage_complete=0"), std::string::npos); +} + +TEST_F(DecoderModelResultTest, + EnabledEveryRapRetainsExistingRunCreationAndRecordFormat) { + ReinitializeVerifier(true, AVM_DECODER_MODEL_CHECK_WARN); + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY); + UpdateAndOutput(); + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + StartFrame(OBU_OPEN_LOOP_KEY); + UpdateAndOutput(); + + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_EQ(stats.applicable_rap_starts, 3u); + EXPECT_EQ(stats.rap_runs_started, 3u); + EXPECT_EQ(stats.rap_runs_skipped, 0u); + EXPECT_TRUE(stats.rap_coverage_complete); + EXPECT_EQ(stats.live_runs, 3u); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ(diagnostics.find("AV2_DECODER_MODEL_COVERAGE_WARNING"), + std::string::npos); + EXPECT_EQ(diagnostics.find("coverage_complete="), std::string::npos); +} + +TEST_F(DecoderModelResultTest, EveryRapSelectionScalesWithExactCoverage) { + constexpr int kRapCounts[] = { 0, 1, 2, 15, 128 }; + for (const bool check_every_rap : { false, true }) { + for (const int rap_count : kRapCounts) { + ReinitializeVerifier(check_every_rap, AVM_DECODER_MODEL_CHECK_WARN); + memset(&frame_, 0, sizeof(frame_)); + frame_.long_term_id = -1; + testing::internal::CaptureStderr(); + for (int rap = 0; rap < rap_count; ++rap) { + if (rap > 0) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, + 0, 0, 8); + } + StartFrame(rap == 0 ? OBU_CLOSED_LOOP_KEY : OBU_OPEN_LOOP_KEY, 64, 64, + &frame_, 0, KEY_FRAME, false, true, 0, 0, rap == 0); + UpdateAndOutput(); + } + + Av2DmVerifierStats live; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); + const uint64_t expected_started = + check_every_rap ? rap_count : (rap_count > 0 ? 1 : 0); + const uint64_t expected_skipped = rap_count - expected_started; + EXPECT_EQ(live.rap_starts, static_cast(rap_count)); + EXPECT_EQ(live.applicable_rap_starts, static_cast(rap_count)); + EXPECT_EQ(live.rap_runs_started, expected_started); + EXPECT_EQ(live.rap_runs_skipped, expected_skipped); + EXPECT_EQ(live.live_runs, expected_started); + EXPECT_EQ(live.rap_coverage_complete, expected_skipped == 0); + + Av2DmContextStats context; + ASSERT_EQ(live.contexts, 1u); + ASSERT_TRUE( + av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); + EXPECT_EQ(context.applicable_rap_starts, + static_cast(rap_count)); + EXPECT_EQ(context.rap_runs_started, expected_started); + EXPECT_EQ(context.rap_runs_skipped, expected_skipped); + + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + EXPECT_EQ( + CountOccurrences(diagnostics, "AV2_DECODER_MODEL_COVERAGE_WARNING "), + expected_skipped > 0 ? 1u : 0u); + EXPECT_EQ(CountOccurrences(diagnostics, "AV2_DECODER_MODEL_RESULT "), + static_cast(expected_started)); + if (expected_skipped > 0) { + EXPECT_NE(diagnostics.find("status=INDETERMINATE"), std::string::npos); + EXPECT_NE(diagnostics.find("reason=rap_start_checks_disabled"), + std::string::npos); + } + } + } +} + TEST_F(DecoderModelResultTest, FifteenOneFrameCvsKeepStableOriginsAndContinuousHistory) { pbi_->seq_list[0][0].still_picture = 0; @@ -1512,194 +1872,6 @@ TEST_F(DecoderModelResultTest, std::string::npos); } -TEST_F(DecoderModelResultTest, - OlkLeadingOutputDoesNotReplaceAcceptedTerminalDuration) { - SequenceHeader *const sequence = &pbi_->seq_list[0][0]; - sequence->still_picture = 0; - sequence->decoder_model_info_present_flag = 1; - sequence->seq_max_decoder_model_present_flag = 1; - sequence->seq_max_decoder_buffer_delay = 70000; - sequence->seq_max_encoder_buffer_delay = 20000; - sequence->seq_max_low_delay_mode_flag = 1; - pbi_->common.seq_params = *sequence; - pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; - av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); - av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); - pbi_->common.brt_info.br_ops_dependent_flag = 0; - pbi_->common.brt_info.br_time = 0; - av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); - - StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, - 0, true, false, true, false, 0); - UpdateAndOutput(); - av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, - 8); - pbi_->common.brt_info.br_time = 10; - av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); - StartFrame(OBU_OPEN_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, - true, 0, 0, false, false, true, false, 10); - pbi_->common.ref_frame_map[1] = &second_frame_; - pbi_->valid_for_referencing[1] = 1; - av2_decoder_model_verifier_after_reference_update(pbi_, 2); - av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, - AV2_DM_PRESENTATION_OWNER_CURRENT); - - Av2DmRunStats olk_before_leading; - ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, - &olk_before_leading)); - ASSERT_TRUE(olk_before_leading.terminal_frame_parsing_time_valid); - ASSERT_TRUE(olk_before_leading.terminal_display_duration_valid); - EXPECT_FALSE(olk_before_leading.last_frame_parsing_time_valid); - EXPECT_FALSE(olk_before_leading.last_display_duration_valid); - ExpectAdapterRational(olk_before_leading.terminal_display_duration, 1, 3); - - av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, - 8); - pbi_->common.brt_info.br_time = 30; - av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); - RefCntBuffer leading_frame; - memset(&leading_frame, 0, sizeof(leading_frame)); - StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &leading_frame, 0, INTER_FRAME, - false, true, 0, 0, false, false, false, true, 30); - pbi_->common.ref_frame_map[2] = &leading_frame; - pbi_->valid_for_referencing[2] = 1; - av2_decoder_model_verifier_after_reference_update(pbi_, 4); - av2_decoder_model_verifier_on_output(pbi_, -1, &leading_frame, - AV2_DM_PRESENTATION_OWNER_CURRENT); - - Av2DmRunStats source; - Av2DmRunStats olk_after_leading; - ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &source)); - ASSERT_TRUE( - av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &olk_after_leading)); - ASSERT_TRUE(source.last_frame_parsing_time_valid); - ASSERT_TRUE(source.last_display_duration_valid); - ExpectAdapterRational(source.last_display_duration, 2, 3); - EXPECT_EQ(olk_after_leading.decoded_frames, 1u); - EXPECT_EQ(olk_after_leading.output_frames, 1u); - EXPECT_FALSE(olk_after_leading.last_frame_parsing_time_valid); - EXPECT_FALSE(olk_after_leading.last_display_duration_valid); - ASSERT_TRUE(olk_after_leading.terminal_frame_parsing_time_valid); - ASSERT_TRUE(olk_after_leading.terminal_display_duration_valid); - ExpectAdapterRational(olk_after_leading.terminal_display_duration, 1, 3); - int parsing_comparison; - ASSERT_TRUE(av2_dm_rational_compare( - &olk_before_leading.terminal_frame_parsing_time, - &olk_after_leading.terminal_frame_parsing_time, &parsing_comparison)); - EXPECT_EQ(parsing_comparison, 0); - ASSERT_TRUE(av2_dm_rational_compare( - &source.last_frame_parsing_time, - &olk_after_leading.terminal_frame_parsing_time, &parsing_comparison)); - EXPECT_NE(parsing_comparison, 0); - - testing::internal::CaptureStderr(); - av2_decoder_model_verifier_finish(pbi_); - const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 rap=1"), - std::string::npos); -} - -TEST_F(DecoderModelResultTest, PrefixOutputReplaysItsExactDurationIntoRasRun) { - SequenceHeader *const sequence = &pbi_->seq_list[0][0]; - sequence->still_picture = 0; - sequence->decoder_model_info_present_flag = 1; - sequence->seq_max_decoder_model_present_flag = 1; - sequence->seq_max_decoder_buffer_delay = 70000; - sequence->seq_max_encoder_buffer_delay = 20000; - sequence->seq_max_low_delay_mode_flag = 1; - pbi_->common.seq_params = *sequence; - pbi_->common.ci_params_per_layer[0].timing_info.equal_elemental_interval = 0; - av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); - av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); - pbi_->common.brt_info.br_ops_dependent_flag = 0; - pbi_->common.brt_info.br_time = 0; - av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); - - frame_.long_term_id = -1; - StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, - 0, true, false, true, false, 0); - UpdateAndOutput(); - - av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, - 8); - pbi_->common.brt_info.br_time = 10; - av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); - second_frame_.long_term_id = 5; - StartFrame(OBU_REGULAR_TILE_GROUP, 64, 64, &second_frame_, 0, INTER_FRAME, - true, true, 0, 0, false, false, false, false, 10); - pbi_->common.ref_frame_map[1] = &second_frame_; - pbi_->valid_for_referencing[1] = 1; - av2_decoder_model_verifier_after_reference_update(pbi_, 2); - - pbi_->common.brt_info.br_time = 20; - av2_decoder_model_verifier_on_buffer_removal_timing(pbi_, 0); - RefCntBuffer ras_frame; - memset(&ras_frame, 0, sizeof(ras_frame)); - ras_frame.long_term_id = -1; - StartFrame(OBU_RAS_FRAME, 64, 64, &ras_frame, 0, KEY_FRAME, false, true, 0, 0, - false, false, false, false, 20, &second_frame_, 1); - pbi_->common.ref_frame_map[2] = &ras_frame; - pbi_->valid_for_referencing[2] = 1; - av2_decoder_model_verifier_after_reference_update(pbi_, 4); - - Av2DmRunStats ras; - ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &ras)); - EXPECT_EQ(ras.decoded_frames, 1u); - EXPECT_EQ(ras.output_frames, 1u); - EXPECT_FALSE(ras.last_display_duration_valid); - ASSERT_TRUE(ras.terminal_frame_parsing_time_valid); - ASSERT_TRUE(ras.terminal_display_duration_valid); - ExpectAdapterRational(ras.terminal_display_duration, 1, 3); - - av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); - testing::internal::CaptureStderr(); - av2_decoder_model_verifier_finish(pbi_); - const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("xlayer=0 ops=-1 op=-1 rap=2 mode=schedule " - "decoded=1 outputs=1"), - std::string::npos); - EXPECT_EQ(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " - "rap=2"), - std::string::npos) - << diagnostics; -} - -TEST_F(DecoderModelResultTest, - IncompatibleConfigurationDoesNotDonateTerminalHistory) { - pbi_->seq_list[0][0].still_picture = 0; - pbi_->common.seq_params = pbi_->seq_list[0][0]; - av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); - av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); - StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, - 0, true, false, true); - UpdateAndOutput(); - - av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, - 8); - pbi_->common.ci_params_per_layer[0].timing_info.time_scale = 60; - StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &second_frame_, 0, KEY_FRAME, false, - true, 0, 0, true, false, true); - pbi_->common.ref_frame_map[1] = &second_frame_; - pbi_->valid_for_referencing[1] = 1; - av2_decoder_model_verifier_after_reference_update(pbi_, 2); - av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, - AV2_DM_PRESENTATION_OWNER_CURRENT); - - Av2DmRunStats clk_start; - ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); - EXPECT_FALSE(clk_start.terminal_frame_parsing_time_valid); - EXPECT_FALSE(clk_start.terminal_display_duration_valid); - - testing::internal::CaptureStderr(); - av2_decoder_model_verifier_finish(pbi_); - const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " - "rap=1 mode=resource decoded=1 outputs=1"), - std::string::npos); - EXPECT_EQ(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 rap=1"), - std::string::npos); -} - TEST_F(DecoderModelResultTest, ConsecutiveCvsRetainWholeXlayerAndOperatingPointRuns) { pbi_->seq_list[0][0].still_picture = 0; @@ -1747,25 +1919,6 @@ TEST_F(DecoderModelResultTest, EXPECT_EQ(run_stats.originating_cvs, run + 1); EXPECT_EQ(run_stats.decoded_frames, 3u - run); } - Av2DmRunStats source; - Av2DmRunStats final_suffix; - ASSERT_TRUE( - av2_decoder_model_verifier_get_run_stats(pbi_, context, 0, &source)); - ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, context, 2, - &final_suffix)); - ASSERT_TRUE(source.last_frame_parsing_time_valid); - ASSERT_TRUE(source.last_display_duration_valid); - ASSERT_TRUE(final_suffix.terminal_frame_parsing_time_valid); - ASSERT_TRUE(final_suffix.terminal_display_duration_valid); - int comparison; - ASSERT_TRUE(av2_dm_rational_compare( - &source.last_frame_parsing_time, - &final_suffix.terminal_frame_parsing_time, &comparison)); - EXPECT_EQ(comparison, 0); - ASSERT_TRUE(av2_dm_rational_compare(&source.last_display_duration, - &final_suffix.terminal_display_duration, - &comparison)); - EXPECT_EQ(comparison, 0); } testing::internal::CaptureStderr(); @@ -2714,7 +2867,9 @@ static_assert(AVM_DECODER_CTRL_ID_MAX == 279, static_assert(AVMD_INCR_OUTPUT_FRAMES_OFFSET == 291, "Existing decoder control IDs must not change"); static_assert(AV2D_SET_DECODER_MODEL_CHECK_MODE == 292, - "The decoder-model control must be appended"); + "Existing decoder control IDs must not change"); +static_assert(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP == 293, + "The decoder-model RAP control must be appended"); TEST(DecoderModelControlTest, RejectsInvalidAndLateModeChanges) { avm_codec_dec_cfg_t config = {}; @@ -2734,6 +2889,50 @@ TEST(DecoderModelControlTest, RejectsInvalidAndLateModeChanges) { AVM_DECODER_MODEL_CHECK_WARN, AVM_CODEC_INVALID_PARAM); } +TEST(DecoderModelControlTest, AcceptsBooleanAndRejectsLateEveryRapChanges) { + avm_codec_dec_cfg_t config = {}; + libavm_test::AV2Decoder decoder(config); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, 0); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, 1); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, -1, + AVM_CODEC_INVALID_PARAM); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, 2, + AVM_CODEC_INVALID_PARAM); + + const uint8_t invalid_input = 0; + EXPECT_NE(decoder.DecodeFrame(&invalid_input, 1), AVM_CODEC_OK); + decoder.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, 0, + AVM_CODEC_INVALID_PARAM); +} + +TEST(DecoderModelControlTest, AcceptsEitherPreInputControlOrder) { + avm_codec_dec_cfg_t config = {}; + libavm_test::AV2Decoder every_rap_first(config); + every_rap_first.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, 0); + every_rap_first.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, + AVM_DECODER_MODEL_CHECK_WARN); + + libavm_test::AV2Decoder mode_first(config); + mode_first.Control(AV2D_SET_DECODER_MODEL_CHECK_MODE, + AVM_DECODER_MODEL_CHECK_WARN); + mode_first.Control(AV2D_SET_DECODER_MODEL_CHECK_EVERY_RAP, 0); +} + +TEST_F(DecoderModelResultTest, CopiesEveryRapSelectionIntoVerifierState) { + av2_decoder_model_verifier_destroy(pbi_); + pbi_->decoder_model_check_every_rap = 1; + av2_decoder_model_verifier_init(pbi_); + Av2DmVerifierStats stats; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_TRUE(stats.check_every_rap); + + av2_decoder_model_verifier_destroy(pbi_); + pbi_->decoder_model_check_every_rap = 0; + av2_decoder_model_verifier_init(pbi_); + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_FALSE(stats.check_every_rap); +} + #if CONFIG_AV2_ENCODER && CONFIG_AV2_DECODER struct DecoderModelEncodedPacket { diff --git a/test/decoder_model_test.cc b/test/decoder_model_test.cc index b3d22e0560..c84730c7c3 100644 --- a/test/decoder_model_test.cc +++ b/test/decoder_model_test.cc @@ -1332,6 +1332,7 @@ TEST(DecoderModelProcessTest, Av2DmOutputEvent output = Output(1, 1, 0); output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &output); + av2_decoder_model_finish(model); Av2DmResult result; ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); @@ -2145,81 +2146,7 @@ TEST(DecoderModelConformanceTest, } TEST(DecoderModelConformanceTest, - TerminalHistorySeedsOnlyMissingOneFrameSuffixIntervals) { - Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); - config.initial_display_delay = 1; - Av2DecoderModel *const source = - av2_decoder_model_create(&config, nullptr, nullptr); - ASSERT_NE(source, nullptr); - - for (uint64_t i = 0; i < 2; ++i) { - Av2DmFrameEvent frame = MakeFrame(i, i + 1); - frame.temporal_unit_index = i; - av2_decoder_model_start_frame(source, &frame); - const Av2DmReferenceUpdateEvent refresh = - Refresh(1u << i, (1u << (i + 1)) - 1); - av2_decoder_model_update_reference_buffers(source, &refresh); - if (i == 0) { - av2_decoder_model_set_initial_presentation_delay(source, false, i); - } - Av2DmOutputEvent output = Output(10 + i, i + 1, -1); - output.temporal_unit_index = i; - av2_decoder_model_output_frame(source, &output); - } - Av2DmState source_state; - ASSERT_TRUE(av2_decoder_model_get_state(source, &source_state)); - ASSERT_TRUE(source_state.last_frame_parsing_time_valid); - ASSERT_TRUE(source_state.last_display_duration_valid); - - Av2DecoderModel *const suffix = - av2_decoder_model_create(&config, nullptr, nullptr); - ASSERT_NE(suffix, nullptr); - Av2DmFrameEvent frame = MakeFrame(1, 2); - frame.random_access_point = true; - frame.coded_as_closed_loop_key = true; - frame.temporal_unit_index = 1; - av2_decoder_model_start_frame(suffix, &frame); - const Av2DmReferenceUpdateEvent refresh = Refresh(2, 3); - av2_decoder_model_update_reference_buffers(suffix, &refresh); - av2_decoder_model_set_initial_presentation_delay(suffix, false, 1); - Av2DmOutputEvent output = Output(11, 2, -1); - output.temporal_unit_index = 1; - av2_decoder_model_output_frame(suffix, &output); - - Av2DmState before; - ASSERT_TRUE(av2_decoder_model_get_state(suffix, &before)); - ASSERT_FALSE(before.last_frame_parsing_time_valid); - ASSERT_FALSE(before.last_display_duration_valid); - ASSERT_TRUE(av2_decoder_model_seed_terminal_history( - suffix, &source_state.last_frame_parsing_time, - &source_state.last_display_duration)); - Av2DmState after; - ASSERT_TRUE(av2_decoder_model_get_state(suffix, &after)); - EXPECT_TRUE(after.last_frame_parsing_time_valid); - EXPECT_TRUE(after.last_display_duration_valid); - EXPECT_TRUE(EqualRational(after.last_frame_parsing_time, - source_state.last_frame_parsing_time)); - EXPECT_TRUE(EqualRational(after.last_display_duration, - source_state.last_display_duration)); - EXPECT_TRUE(EqualRational(after.time, before.time)); - EXPECT_EQ(after.current_buffer_index, before.current_buffer_index); - EXPECT_EQ(after.frame_number, before.frame_number); - EXPECT_EQ(after.dfg_number, before.dfg_number); - EXPECT_EQ(after.shown_frame_number, before.shown_frame_number); - EXPECT_EQ(memcmp(&after.buffer_pool, &before.buffer_pool, - sizeof(after.buffer_pool)), - 0); - - av2_decoder_model_finish(suffix); - Av2DmResult result; - ASSERT_TRUE(av2_decoder_model_get_result(suffix, &result)); - EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); - av2_decoder_model_destroy(suffix); - av2_decoder_model_destroy(source); -} - -TEST(DecoderModelConformanceTest, - TerminalHistoryStillEvaluatesOneFrameSuffixViolations) { + SingleDfgAndOutputTuDoNotRequireTerminalPredecessors) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); config.initial_display_delay = 1; ViolationCollector collector; @@ -2227,7 +2154,7 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_create(&config, CollectViolation, &collector); ASSERT_NE(model, nullptr); - Av2DmFrameEvent frame = MakeFrame(0, 1); + const Av2DmFrameEvent frame = MakeFrame(0, 1); av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); @@ -2235,26 +2162,25 @@ TEST(DecoderModelConformanceTest, Av2DmOutputEvent output = Output(1, 1, -1); output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &output); - - Av2DmRational insufficient_interval; - ASSERT_TRUE( - av2_dm_rational_make(1, UINT64_C(1000000000), &insufficient_interval)); - ASSERT_TRUE(av2_decoder_model_seed_terminal_history( - model, &insufficient_interval, &insufficient_interval)); av2_decoder_model_finish(model); - EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); - EXPECT_TRUE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); Av2DmResult result; ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); - EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); + EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + EXPECT_FALSE(result.missing_required_input); av2_decoder_model_destroy(model); } TEST(DecoderModelConformanceTest, - LocallyDerivedTerminalHistorySupersedesSeededFallback) { + TwoDfgsAndOutputTusReuseLocalTerminalPredecessors) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); config.initial_display_delay = 1; + config.level_limits.max_display_rate = 100000; ViolationCollector collector; Av2DecoderModel *const model = av2_decoder_model_create(&config, CollectViolation, &collector); @@ -2269,15 +2195,13 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(10, 1, -1); first_output.temporal_unit_index = 0; + first_output.output_luma_samples = 1; av2_decoder_model_output_frame(model, &first_output); - Av2DmRational insufficient_interval; - ASSERT_TRUE( - av2_dm_rational_make(1, UINT64_C(1000000000), &insufficient_interval)); - ASSERT_TRUE(av2_decoder_model_seed_terminal_history( - model, &insufficient_interval, &insufficient_interval)); - Av2DmFrameEvent second = MakeFrame(1, 2); + second.frame_is_intra = true; + second.frame_width = 512; + second.frame_height = 512; second.temporal_unit_output_time_present = true; ASSERT_TRUE(av2_dm_rational_make(1, 30, &second.temporal_unit_output_time)); av2_decoder_model_start_frame(model, &second); @@ -2287,21 +2211,15 @@ TEST(DecoderModelConformanceTest, second_output.temporal_unit_index = 1; av2_decoder_model_output_frame(model, &second_output); - Av2DmState state; - ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - ASSERT_TRUE(state.last_frame_parsing_time_valid); - ASSERT_TRUE(state.last_display_duration_valid); - int parsing_comparison; - ASSERT_TRUE(av2_dm_rational_compare(&state.last_frame_parsing_time, - &insufficient_interval, - &parsing_comparison)); - EXPECT_GT(parsing_comparison, 0); - ExpectEqualRational(state.last_display_duration, 1, 30); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); av2_decoder_model_finish(model); - EXPECT_TRUE(collector.violations.empty()); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE), 1u); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE), 1u); Av2DmResult result; ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); - EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); + EXPECT_FALSE(result.missing_required_input); av2_decoder_model_destroy(model); } diff --git a/test/encoder_decoder_model_test.cc b/test/encoder_decoder_model_test.cc index 670252a5ae..da8291ffe1 100644 --- a/test/encoder_decoder_model_test.cc +++ b/test/encoder_decoder_model_test.cc @@ -970,18 +970,21 @@ class ResourceAvailabilityDifferentialAdapter { } void DecodeRefreshAndMaybeOutput(uint64_t coded_bits, uint32_t refresh_flags, - uint64_t output_order, bool output) { + uint64_t output_order, bool output, + bool closed_loop_key = false, + bool implicit_output_eligible = false) { ASSERT_GE(coded_bits, 24u); AV2_COMMON *const cm = &cpi_->common; cm->show_existing_frame = 0; - cm->current_frame.frame_type = frame_count_ == 0 ? KEY_FRAME : INTER_FRAME; + const bool is_closed_loop_key = frame_count_ == 0 || closed_loop_key; + cm->current_frame.frame_type = is_closed_loop_key ? KEY_FRAME : INTER_FRAME; cm->current_frame.cm_obu_type = - frame_count_ == 0 ? OBU_CLOSED_LOOP_KEY : OBU_REGULAR_TILE_GROUP; + is_closed_loop_key ? OBU_CLOSED_LOOP_KEY : OBU_REGULAR_TILE_GROUP; cm->current_frame.refresh_frame_flags = refresh_flags; cm->current_frame.display_order_hint = static_cast(output_order); cm->immediate_output_picture = 0; current_.display_order_hint = static_cast(output_order); - current_.implicit_output_picture = 0; + current_.implicit_output_picture = implicit_output_eligible; cpi_->dm_starts_temporal_unit = true; const std::array obu = { @@ -1042,16 +1045,40 @@ class ResourceAvailabilityDifferentialAdapter { ++frame_count_; } + void BeginNewCvs(int num_ref_frames) { + ASSERT_GE(num_ref_frames, 1); + ASSERT_LE(num_ref_frames, REF_FRAMES); + cpi_->common.seq_params.ref_frames = num_ref_frames; + av2_decoder_model_flush_implicit_output_for_operating_points(cpi_.get(), + false); + av2_reset_level_info_for_new_cvs(cpi_.get()); + } + + void SetInitialDisplayDelay(int initial_display_delay) { + ASSERT_GT(initial_display_delay, 0); + ASSERT_NE(legacy_, nullptr); + legacy_->initial_display_delay = initial_display_delay; + } + + void SetDisplayTimeScale(uint32_t time_scale) { + ASSERT_NE(time_scale, 0u); + cpi_->common.ci_params_encoder.timing_info.time_scale = time_scale; + } + void Finish() { if (RunsLegacy()) { - av2_encoder_decoder_model_finalize(legacy_, - cpi_->common.seq_params.still_picture); + av2_encoder_decoder_model_finish_for_operating_points(cpi_.get()); } if (CommonHasParameters()) av2_decoder_model_finish(common_); } DECODER_MODEL *legacy() { return legacy_; } const DECODER_MODEL *legacy() const { return legacy_; } + void ClearEncoderReferenceOnly(int ref_index) { + ASSERT_GE(ref_index, 0); + ASSERT_LT(ref_index, kNumRefs); + cpi_->common.ref_frame_map[ref_index] = nullptr; + } Av2DecoderModel *common() { return common_; } const Av2DecoderModel *common() const { return common_; } const ViolationCollector &collector() const { return collector_; } @@ -1177,6 +1204,192 @@ void RunNormalResourceTrace(ResourceAvailabilityDifferentialAdapter *adapter) { adapter->Finish(); } +TEST(EncoderDecoderModelTest, ReleasesPlayerOwnedInactiveBuffer) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + ASSERT_LT(model->num_ref_frames + 2, BUFFER_POOL_MAX_SIZE); + FRAME_BUFFER *const inactive = + &model->frame_buffer_pool[BUFFER_POOL_MAX_SIZE - 1]; + inactive->player_ref_count = 1; + inactive->display_index = 7; + inactive->presentation_time = 0.25; + inactive->presentation.valid = true; + inactive->presentation.buffer_index = BUFFER_POOL_MAX_SIZE - 1; + model->initial_presentation_delay = 0.0; + + adapter.DecodeRefreshAndMaybeOutput(1000, 1, 0, false); + + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(0u, inactive->player_ref_count); + EXPECT_EQ(-1, inactive->display_index); + EXPECT_DOUBLE_EQ(-1.0, inactive->presentation_time); + EXPECT_FALSE(inactive->presentation.valid); +} + +TEST(EncoderDecoderModelTest, OrdinaryFrameDoesNotInvalidateModelReference) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, false); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + const int buffer_index = model->vbi[0]; + ASSERT_GE(buffer_index, 0); + ASSERT_EQ(1u, model->frame_buffer_pool[buffer_index].decoder_ref_count); + + adapter.ClearEncoderReferenceOnly(0); + adapter.DecodeRefreshAndMaybeOutput(1024, 2, 1, false); + + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(buffer_index, model->vbi[0]); + EXPECT_EQ(1u, model->frame_buffer_pool[buffer_index].decoder_ref_count); +} + +TEST(EncoderDecoderModelTest, CompatibleClkPreservesContinuousModelState) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.SetInitialDisplayDelay(10); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + const double first_time = model->current_time; + const int64_t first_frame = model->num_frame; + const double first_initial_delay = model->initial_presentation_delay; + + adapter.BeginNewCvs(4); + adapter.DecodeRefreshAndMaybeOutput(1024, 2, 1, true, true); + + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(first_frame + 1, model->num_frame); + EXPECT_GE(model->current_time, first_time); + EXPECT_DOUBLE_EQ(first_initial_delay, model->initial_presentation_delay); + EXPECT_EQ(10, model->initial_display_delay); + EXPECT_FALSE(model->finalized); +} + +TEST(EncoderDecoderModelTest, ClkSupportsActiveReferenceRangeTransitions) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.SetInitialDisplayDelay(10); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + + adapter.BeginNewCvs(8); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 1, true, true); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(8, model->num_ref_frames); + EXPECT_EQ(1, model->num_frame); + + adapter.BeginNewCvs(4); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 2, true, true); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(4, model->num_ref_frames); + EXPECT_EQ(2, model->num_frame); +} + +TEST(EncoderDecoderModelTest, IncompatibleClkClockIsUnavailable) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + + adapter.SetDisplayTimeScale(60000); + adapter.BeginNewCvs(4); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 1, true, true); + + EXPECT_EQ(DECODER_MODEL_UNSUPPORTED, model->status); + EXPECT_EQ(ENCODER_DM_RESULT_UNAVAILABLE, + av2_encoder_decoder_model_classify_status(model->status)); +} + +TEST(EncoderDecoderModelTest, ClkFlushesImplicitOutputBeforeInvalidation) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.SetInitialDisplayDelay(10); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, false, false, true); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + const int old_buffer = model->vbi[0]; + ASSERT_GE(old_buffer, 0); + ASSERT_EQ(-1, model->num_shown_frame); + + adapter.BeginNewCvs(4); + ASSERT_EQ(0, model->num_shown_frame); + ASSERT_EQ(1u, model->frame_buffer_pool[old_buffer].player_ref_count); + ASSERT_TRUE( + model->frame_buffer_pool[old_buffer].presentation.normative_output_done); + + adapter.DecodeRefreshAndMaybeOutput(1024, 2, 1, false, true); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(0u, model->frame_buffer_pool[old_buffer].decoder_ref_count); + EXPECT_EQ(1u, model->frame_buffer_pool[old_buffer].player_ref_count); +} + +TEST(EncoderDecoderModelTest, OneFrameClkCvssEstablishInitialDelay) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.SetInitialDisplayDelay(10); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + + for (uint64_t cvs = 0; cvs < 15; ++cvs) { + adapter.BeginNewCvs(8); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, cvs, true, true); + ASSERT_EQ(DECODER_MODEL_OK, model->status) << "CVS " << cvs; + EXPECT_EQ(static_cast(cvs), model->num_frame); + if (cvs < 9) { + EXPECT_LT(model->initial_presentation_delay, 0.0); + } else { + EXPECT_GE(model->initial_presentation_delay, 0.0); + } + } + + EXPECT_EQ(14, model->num_frame); + EXPECT_EQ(14, model->num_decoded_frame); + EXPECT_EQ(14, model->num_shown_frame); + EXPECT_GE(model->initial_presentation_delay, 0.0); + const double initial_presentation_delay = model->initial_presentation_delay; + adapter.Finish(); + EXPECT_TRUE(model->finalized); + EXPECT_DOUBLE_EQ(initial_presentation_delay, + model->initial_presentation_delay); + adapter.Finish(); + EXPECT_DOUBLE_EQ(initial_presentation_delay, + model->initial_presentation_delay); +} + +TEST(EncoderDecoderModelTest, ShortInputEstablishesInitialDelayAtFinish) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + adapter.SetInitialDisplayDelay(10); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + + for (uint64_t frame = 0; frame < 3; ++frame) { + adapter.DecodeRefreshAndMaybeOutput(1024, 1u << frame, frame, true); + } + ASSERT_LT(model->initial_presentation_delay, 0.0); + const double final_time = model->current_time; + + adapter.Finish(); + + EXPECT_DOUBLE_EQ(final_time, model->initial_presentation_delay); + EXPECT_TRUE(model->finalized); +} + TEST(EncoderDecoderModelRationalReuseTest, NormalResourceTraceHasRetainedStateAndClassificationParity) { ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth); @@ -1191,6 +1404,39 @@ TEST(EncoderDecoderModelRationalReuseTest, AV2_DM_RESULT_CONFORMANT); } +TEST(EncoderDecoderModelRationalReuseTest, + SingleDfgAndOutputTuTerminalApplicabilityAgree) { + ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth); + ASSERT_TRUE(adapter.valid()); + + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.Finish(); + + ASSERT_NE(adapter.legacy(), nullptr); + EXPECT_EQ(1u, adapter.legacy()->applicable_dfg_count); + EXPECT_EQ(1u, adapter.legacy()->output_tu_count); + ExpectFinalResourceClassification(adapter, ENCODER_DM_RESULT_PASS, + AV2_DM_RESULT_CONFORMANT); +} + +TEST(EncoderDecoderModelRationalReuseTest, + TwoDfgAndOutputTuTerminalSubstitutionsAgree) { + ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth); + ASSERT_TRUE(adapter.valid()); + + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.DecodeRefreshAndMaybeOutput(1024, 2, 1, true); + adapter.Finish(); + + ASSERT_NE(adapter.legacy(), nullptr); + EXPECT_EQ(2u, adapter.legacy()->applicable_dfg_count); + EXPECT_EQ(2u, adapter.legacy()->output_tu_count); + EXPECT_TRUE(adapter.legacy()->last_frame_parsing_time_valid); + EXPECT_TRUE(adapter.legacy()->last_display_duration_valid); + ExpectFinalResourceClassification(adapter, ENCODER_DM_RESULT_PASS, + AV2_DM_RESULT_CONFORMANT); +} + TEST(EncoderDecoderModelRationalReuseTest, ReportsRecoveredDecodeDurationRoundingWithoutThreshold) { ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth, diff --git a/test/level_test.cc b/test/level_test.cc index 36a6ea51f2..d22d5a9306 100644 --- a/test/level_test.cc +++ b/test/level_test.cc @@ -92,6 +92,10 @@ void SetPresentation(DECODER_MODEL *model, int buffer_index, struct MinimumPresentationIntervalResult { DECODER_MODEL_STATUS status; bool satisfies; + uint64_t output_tu_count_before_transition; + uint64_t output_tu_count; + bool last_display_duration_valid; + double last_display_duration; }; MinimumPresentationIntervalResult CheckMinimumPresentationInterval( @@ -130,6 +134,7 @@ MinimumPresentationIntervalResult CheckMinimumPresentationInterval( if (forced_output_frames != 0) { model->num_frames_current_tu = forced_output_frames; } + const uint64_t output_tu_count_before_transition = model->output_tu_count; const int next_buffer_index = (int)output_frames + 1; model->cfbi = next_buffer_index; SetPresentation(model, next_buffer_index, output_frames, 1, false); @@ -141,6 +146,10 @@ MinimumPresentationIntervalResult CheckMinimumPresentationInterval( const MinimumPresentationIntervalResult result = { model->status, model->min_presentation_interval_satisfy, + output_tu_count_before_transition, + model->output_tu_count, + model->last_display_duration_valid, + model->last_display_duration, }; av2_encoder_decoder_model_destroy(model); return result; @@ -182,6 +191,96 @@ TEST(LevelDecoderModelTest, InitialDisplayDelayUsesSequenceSemantics) { EXPECT_EQ(3, decoder_model.initial_display_delay); } +TEST(LevelDecoderModelTest, NewCvsResetPreservesDecoderModelState) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.level_info[0] = &level_info; + cpi->level_params.multi_stream_scaling_x = 1.0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + cpi->framerate = 30.0; + DECODER_MODEL *const model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, model); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + model->current_time = 7.0; + model->initial_presentation_delay = 6.0; + const DFG_INTERVAL interval = { 1.0, 2.0, 3.0, 100 }; + ASSERT_TRUE(av2_encoder_decoder_model_push_dfg_interval(model, &interval)); + const DECODER_MODEL before = *model; + + level_info.level_stats.min_cr = 2.0; + level_info.level_stats.max_tile_size = 42; + level_info.level_spec.level = SEQ_LEVEL_4_0; + level_info.level_spec.max_picture_size = 1000; + level_info.frame_window_buffer.num = 3; + level_info.frame_window_buffer.start = 2; + + av2_reset_level_info_for_new_cvs(cpi.get()); + + EXPECT_EQ(0, std::memcmp(&before, model, sizeof(before))); + EXPECT_EQ(SEQ_LEVEL_MAX, level_info.level_spec.level); + EXPECT_EQ(0, level_info.level_spec.max_picture_size); + EXPECT_EQ(INT_MAX, level_info.level_stats.min_cropped_tile_width); + EXPECT_EQ(INT_MAX, level_info.level_stats.min_cropped_tile_height); + EXPECT_EQ(INT_MAX, level_info.level_stats.min_frame_width); + EXPECT_EQ(INT_MAX, level_info.level_stats.min_frame_height); + EXPECT_EQ(1, level_info.level_stats.tile_width_is_valid); + EXPECT_DOUBLE_EQ(1e8, level_info.level_stats.min_cr); + EXPECT_EQ(0, level_info.level_stats.max_tile_size); + EXPECT_EQ(0, level_info.frame_window_buffer.num); + EXPECT_EQ(0, level_info.frame_window_buffer.start); + av2_encoder_decoder_model_destroy(model); +} + +TEST(LevelDecoderModelTest, FirstCvsPreparesModelsThenPreservesThem) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.level_info[0] = &level_info; + cpi->level_params.multi_stream_scaling_x = 1.0; + cpi->common.width = 160; + cpi->common.height = 90; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + cpi->framerate = 30.0; + + av2_prepare_level_info_for_new_cvs(cpi.get()); + DECODER_MODEL *const model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + ASSERT_TRUE(model->initialized); + ASSERT_EQ(DECODER_MODEL_OK, model->status); + model->current_time = 7.0; + const DECODER_MODEL before = *model; + level_info.level_stats.max_tile_size = 42; + + av2_prepare_level_info_for_new_cvs(cpi.get()); + + EXPECT_EQ(0, std::memcmp(&before, model, sizeof(before))); + EXPECT_EQ(0, level_info.level_stats.max_tile_size); + av2_encoder_decoder_models_destroy(&level_info); +} + +TEST(LevelDecoderModelTest, BufferPoolInitializationUsesFixedCapacity) { + std::unique_ptr cpi(new AV2_COMP()); + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; + cpi->framerate = 30.0; + + DECODER_MODEL model; + av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &model); + + for (int i = 0; i < REF_FRAMES; ++i) EXPECT_EQ(-1, model.vbi[i]); + for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { + EXPECT_EQ(0u, model.frame_buffer_pool[i].decoder_ref_count); + EXPECT_EQ(0u, model.frame_buffer_pool[i].player_ref_count); + EXPECT_EQ(-1, model.frame_buffer_pool[i].display_index); + EXPECT_DOUBLE_EQ(-1.0, model.frame_buffer_pool[i].presentation_time); + } + av2_encoder_decoder_model_destroy(&model); +} + TEST(LevelDecoderModelTest, AnnexABitrateProfileFactorsAreExact) { EXPECT_EQ(12000000, av2_max_level_bitrate(MAIN_420_10_IP0, SEQ_LEVEL_4_0, 0)); EXPECT_EQ(20004000, av2_max_level_bitrate(MAIN_422_10_IP1, SEQ_LEVEL_4_0, 0)); @@ -447,7 +546,7 @@ TEST(LevelDecoderModelTest, UnavailableCandidatesIgnoreLaterHooks) { EXPECT_EQ(0, std::memcmp(&before, unavailable, sizeof(before))); } -TEST(LevelDecoderModelTest, InvalidReferenceSynchronizationClearsOnlyInvalid) { +TEST(LevelDecoderModelTest, OlkInvalidationClearsOnlyInvalidActiveSlots) { AV2_COMMON cm = {}; RefCntBuffer retained_reference = {}; cm.seq_params.ref_frames = 4; @@ -457,25 +556,105 @@ TEST(LevelDecoderModelTest, InvalidReferenceSynchronizationClearsOnlyInvalid) { DECODER_MODEL model = {}; model.status = DECODER_MODEL_OK; model.num_ref_frames = 4; + std::fill_n(model.vbi, REF_FRAMES, -1); model.vbi[0] = 2; model.vbi[1] = 2; - model.vbi[2] = -1; - model.vbi[3] = -1; model.frame_buffer_pool[2].decoder_ref_count = 2; model.frame_buffer_pool[2].presentation.valid = true; model.frame_buffer_pool[2].presentation.generation = 9; - ASSERT_TRUE(av2_encoder_decoder_model_sync_invalid_ref_buffers(&cm, &model)); + model.vbi[7] = 7; + model.frame_buffer_pool[7].decoder_ref_count = 1; + + ASSERT_TRUE( + av2_encoder_decoder_model_invalidate_ref_buffers(&cm, &model, false)); EXPECT_EQ(-1, model.vbi[0]); EXPECT_EQ(2, model.vbi[1]); + EXPECT_EQ(7, model.vbi[7]); EXPECT_EQ(1u, model.frame_buffer_pool[2].decoder_ref_count); + EXPECT_EQ(1u, model.frame_buffer_pool[7].decoder_ref_count); EXPECT_TRUE(model.frame_buffer_pool[2].presentation.valid); cm.ref_frame_map[1] = nullptr; - ASSERT_TRUE(av2_encoder_decoder_model_sync_invalid_ref_buffers(&cm, &model)); + ASSERT_TRUE( + av2_encoder_decoder_model_invalidate_ref_buffers(&cm, &model, false)); EXPECT_EQ(-1, model.vbi[1]); EXPECT_EQ(0u, model.frame_buffer_pool[2].decoder_ref_count); EXPECT_FALSE(model.frame_buffer_pool[2].presentation.valid); + + cm.seq_params.ref_frames = 8; + model.num_ref_frames = 8; + ASSERT_TRUE( + av2_encoder_decoder_model_invalidate_ref_buffers(&cm, &model, false)); + EXPECT_EQ(-1, model.vbi[7]); + EXPECT_EQ(0u, model.frame_buffer_pool[7].decoder_ref_count); +} + +TEST(LevelDecoderModelTest, OlkOperatingPointHookPreservesEncoderReferences) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + RefCntBuffer retained_reference = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.ref_frame_map[0] = nullptr; + cpi->common.ref_frame_map[1] = &retained_reference; + RefCntBuffer *ref_frame_map_before[REF_FRAMES]; + std::memcpy(ref_frame_map_before, cpi->common.ref_frame_map, + sizeof(ref_frame_map_before)); + + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->vbi[0] = 2; + model->vbi[1] = 2; + model->frame_buffer_pool[2].decoder_ref_count = 2; + + av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points(cpi.get()); + + EXPECT_EQ(-1, model->vbi[0]); + EXPECT_EQ(2, model->vbi[1]); + EXPECT_EQ(1u, model->frame_buffer_pool[2].decoder_ref_count); + EXPECT_EQ(0, std::memcmp(ref_frame_map_before, cpi->common.ref_frame_map, + sizeof(ref_frame_map_before))); +} + +void ExpectClkInvalidationClearsEveryVbiSlot(int old_num_ref_frames, + int new_num_ref_frames) { + AV2_COMMON cm = {}; + RefCntBuffer active_reference = {}; + cm.seq_params.ref_frames = new_num_ref_frames; + cm.ref_frame_map[0] = &active_reference; + RefCntBuffer *ref_frame_map_before[REF_FRAMES]; + std::memcpy(ref_frame_map_before, cm.ref_frame_map, + sizeof(ref_frame_map_before)); + + DECODER_MODEL model = {}; + model.status = DECODER_MODEL_OK; + model.num_ref_frames = old_num_ref_frames; + for (int i = 0; i < REF_FRAMES; ++i) { + model.vbi[i] = i; + model.frame_buffer_pool[i].decoder_ref_count = 1; + } + + ASSERT_TRUE( + av2_encoder_decoder_model_invalidate_ref_buffers(&cm, &model, true)); + for (int i = 0; i < REF_FRAMES; ++i) { + EXPECT_EQ(-1, model.vbi[i]); + EXPECT_EQ(0u, model.frame_buffer_pool[i].decoder_ref_count); + } + EXPECT_EQ(0, std::memcmp(ref_frame_map_before, cm.ref_frame_map, + sizeof(ref_frame_map_before))); +} + +TEST(LevelDecoderModelTest, ClkInvalidationAllowsDecreasedActiveRange) { + ExpectClkInvalidationClearsEveryVbiSlot(8, 4); +} + +TEST(LevelDecoderModelTest, ClkInvalidationAllowsIncreasedActiveRange) { + ExpectClkInvalidationClearsEveryVbiSlot(4, 8); } TEST(LevelDecoderModelTest, ReferenceUpdateUsesPostUpdateValidity) { @@ -559,6 +738,46 @@ TEST(LevelDecoderModelTest, InitialDelayRebasesPreviouslyAssignedTimes) { EXPECT_DOUBLE_EQ(5.02, model->frame_buffer_pool[1].presentation_time); } +TEST(LevelDecoderModelTest, InitialDelayCountsAndRebasesInactiveBuffer) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + RefCntBuffer current = {}; + + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.operating_points_cnt_minus_1 = 0; + cpi->common.seq_params.operating_point_idc[0] = 0; + cpi->common.seq_params.ref_frames = 4; + cpi->common.ref_frame_map[0] = ¤t; + cpi->common.cur_frame = ¤t; + cpi->common.current_frame.refresh_frame_flags = 1; + + model->num_ref_frames = 4; + model->cfbi = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->current_time = 5.0; + model->initial_display_delay = 2; + model->initial_presentation_delay = -1.0; + model->presentation_time = 0.0; + FRAME_BUFFER *const inactive = + &model->frame_buffer_pool[BUFFER_POOL_MAX_SIZE - 1]; + inactive->player_ref_count = 1; + inactive->display_index = 1; + inactive->presentation_time = 0.02; + inactive->presentation.valid = true; + inactive->presentation.buffer_index = BUFFER_POOL_MAX_SIZE - 1; + model->frame_buffer_pool[4].presentation.valid = true; + model->frame_buffer_pool[4].presentation.buffer_index = 4; + + av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( + cpi.get()); + + EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); + EXPECT_DOUBLE_EQ(5.02, inactive->presentation_time); +} + TEST(LevelDecoderModelTest, CapturedGenerationIsPrivateAndRecyclable) { std::unique_ptr cpi(new AV2_COMP()); RefCntBuffer current = {}; @@ -1133,6 +1352,10 @@ TEST(LevelDecoderModelTest, CheckMinimumPresentationInterval(0, 1.0, 480, 576, 2, interval); EXPECT_EQ(DECODER_MODEL_OK, at_limit.status); EXPECT_TRUE(at_limit.satisfies); + EXPECT_EQ(1u, at_limit.output_tu_count_before_transition); + EXPECT_EQ(2u, at_limit.output_tu_count); + EXPECT_TRUE(at_limit.last_display_duration_valid); + EXPECT_DOUBLE_EQ(interval, at_limit.last_display_duration); const MinimumPresentationIntervalResult below_limit = CheckMinimumPresentationInterval(0, 1.0, 480, 576, 2, @@ -1630,14 +1853,30 @@ TEST(LevelDecoderModelTest, FinalFrameReusesPreviousExactParsingInterval) { EXPECT_EQ(1100, model.max_decode_rate); } -TEST(LevelDecoderModelTest, SingleFrameHasNoSubstituteParsingTime) { +TEST(LevelDecoderModelTest, SingleFrameNeedsNoSubstituteParsingTime) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME frame = { + true, 1.0, 1000, 1, 1, 1, 1, + }; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + EXPECT_EQ(DECODER_MODEL_OK, model.status); + EXPECT_EQ(1u, model.applicable_dfg_count); + EXPECT_EQ(0.0L, model.max_decode_rate); +} + +TEST(LevelDecoderModelTest, MultipleFramesRequireLocalParsingTime) { DECODER_MODEL model = MakeFrameConstraintModel(); const ENCODER_DECODER_MODEL_FRAME frame = { true, 1.0, 1000, 1, 1, 1, 1, }; ASSERT_TRUE( av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + model.applicable_dfg_count = 2; + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + EXPECT_EQ(DECODER_MODEL_INCOMPLETE, model.status); } @@ -1646,6 +1885,7 @@ TEST(LevelDecoderModelTest, FinalTemporalUnitReusesPreviousDuration) { model.display_samples = 1001; model.last_display_duration = 1.0; model.last_display_duration_valid = true; + model.output_tu_count = 2; av2_encoder_decoder_model_finalize(&model, false); EXPECT_EQ(DECODER_MODEL_OK, model.status); @@ -1657,11 +1897,24 @@ TEST(LevelDecoderModelTest, FinalTemporalUnitReusesPreviousDuration) { EXPECT_EQ(1001.0L, model.max_display_rate); } -TEST(LevelDecoderModelTest, NonStillSingleTemporalUnitIsIncomplete) { +TEST(LevelDecoderModelTest, NonStillSingleTemporalUnitNeedsNoDuration) { DECODER_MODEL model = MakeFrameConstraintModel(); model.display_samples = 1000; + model.output_tu_count = 1; av2_encoder_decoder_model_finalize(&model, false); + EXPECT_EQ(DECODER_MODEL_OK, model.status); + EXPECT_TRUE(model.finalized); + EXPECT_EQ(0.0L, model.max_display_rate); +} + +TEST(LevelDecoderModelTest, MultipleTemporalUnitsRequireLocalDuration) { + DECODER_MODEL model = MakeFrameConstraintModel(); + model.display_samples = 1000; + model.output_tu_count = 2; + + av2_encoder_decoder_model_finalize(&model, false); + EXPECT_EQ(DECODER_MODEL_INCOMPLETE, model.status); EXPECT_TRUE(model.finalized); } @@ -1690,6 +1943,7 @@ TEST(LevelDecoderModelTest, OperatingPointFinishIsIdempotent) { model->display_samples = 1000; model->last_display_duration = 0.5; model->last_display_duration_valid = true; + model->output_tu_count = 2; av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); EXPECT_TRUE(model->finalized); @@ -1698,6 +1952,152 @@ TEST(LevelDecoderModelTest, OperatingPointFinishIsIdempotent) { EXPECT_EQ(2000.0L, model->max_display_rate); } +TEST(LevelDecoderModelTest, FinishRebasesEveryPlayerOwnedBuffer) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + DECODER_MODEL *const model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + *model = MakeFrameConstraintModel(); + model->is_still_picture = true; + model->current_time = 5.0; + model->initial_display_delay = 10; + model->initial_presentation_delay = -1.0; + model->presentation_time = 0.04; + FRAME_BUFFER *const active = &model->frame_buffer_pool[1]; + active->player_ref_count = 1; + active->display_index = 0; + active->presentation_time = 0.02; + FRAME_BUFFER *const inactive = + &model->frame_buffer_pool[BUFFER_POOL_MAX_SIZE - 1]; + inactive->player_ref_count = 1; + inactive->display_index = 1; + inactive->presentation_time = 0.04; + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(model->finalized); + EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); + EXPECT_DOUBLE_EQ(5.04, model->presentation_time); + EXPECT_DOUBLE_EQ(5.02, active->presentation_time); + EXPECT_DOUBLE_EQ(5.04, inactive->presentation_time); +} + +TEST(LevelDecoderModelTest, FinishPreservesKnownInitialDelay) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + DECODER_MODEL *const model = &level_info.decoder_models[SEQ_LEVEL_4_0]; + *model = MakeFrameConstraintModel(); + model->is_still_picture = true; + model->current_time = 5.0; + model->initial_presentation_delay = 3.0; + model->presentation_time = 3.04; + FRAME_BUFFER *const buffer = &model->frame_buffer_pool[1]; + buffer->player_ref_count = 1; + buffer->display_index = 0; + buffer->presentation_time = 3.02; + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_TRUE(model->finalized); + EXPECT_DOUBLE_EQ(3.0, model->initial_presentation_delay); + EXPECT_DOUBLE_EQ(3.04, model->presentation_time); + EXPECT_DOUBLE_EQ(3.02, buffer->presentation_time); +} + +TEST(LevelDecoderModelTest, FinishSetsDelayBeforeImplicitFlushAndFinalChecks) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + model->initialized = true; + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->vbi[0] = 1; + model->current_time = 5.0; + model->initial_display_delay = 10; + model->initial_presentation_delay = -1.0; + model->equal_picture_interval = true; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->frame_buffer_pool[1].decoder_ref_count = 1; + SetPresentation(model, 1, 0, 0); + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + + EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_EQ(1u, model->frame_buffer_pool[1].player_ref_count); + EXPECT_DOUBLE_EQ(5.0, model->frame_buffer_pool[1].presentation_time); + EXPECT_EQ(160u * 90u, model->display_samples); + EXPECT_EQ(1u, model->output_tu_count); + EXPECT_TRUE(model->finalized); + EXPECT_EQ(DECODER_MODEL_OK, model->status); + const int64_t shown_frames = model->num_shown_frame; + const uint32_t player_ref_count = + model->frame_buffer_pool[1].player_ref_count; + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_EQ(shown_frames, model->num_shown_frame); + EXPECT_EQ(player_ref_count, model->frame_buffer_pool[1].player_ref_count); + EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); +} + +TEST(LevelDecoderModelTest, FinishSetsDelayBeforeImplicitDeadlineCheck) { + std::unique_ptr cpi(new AV2_COMP()); + AV2LevelInfo level_info = {}; + cpi->level_params.keep_level_stats = 1; + cpi->level_params.level_info[0] = &level_info; + cpi->common.seq_params.ref_frames = 4; + cpi->common.seq_params.max_frame_width = 160; + cpi->common.seq_params.max_frame_height = 90; + DECODER_MODEL *const model = + EnableSingleDecoderModel(&level_info, SEQ_LEVEL_4_0); + model->initialized = true; + model->num_ref_frames = 4; + std::fill_n(model->vbi, REF_FRAMES, -1); + model->vbi[0] = 1; + model->current_time = 5.0; + model->initial_display_delay = 10; + model->initial_presentation_delay = -1.0; + model->equal_picture_interval = true; + model->display_clock_tick = 0.02; + model->num_ticks_per_picture = 1; + model->num_shown_frame = -1; + model->last_display_index = -1; + model->last_output_mlayer = -1; + model->last_output_xlayer = -1; + model->frame_buffer_pool[1].decoder_ref_count = 1; + SetPresentation(model, 1, 0, 0); + model->frame_buffer_pool[1].presentation.decode_completion_time = 6.0; + + av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); + + // The pending output can prove this violation only if the end-of-input + // delay is established before the model-only implicit flush. + EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); + EXPECT_TRUE(model->frame_buffer_pool[1].presentation.normative_output_done); + EXPECT_DOUBLE_EQ(5.0, model->frame_buffer_pool[1].presentation_time); + EXPECT_EQ(DISPLAY_FRAME_LATE, model->status); + EXPECT_TRUE(model->finalized); +} + TEST(LevelDecoderModelTest, FinishThenResetStartsNewCvsModel) { std::unique_ptr cpi(new AV2_COMP()); AV2LevelInfo level_info = {}; @@ -1714,6 +2114,7 @@ TEST(LevelDecoderModelTest, FinishThenResetStartsNewCvsModel) { model->display_samples = 1000; model->last_display_duration = 0.5; model->last_display_duration_valid = true; + model->output_tu_count = 2; av2_encoder_decoder_model_finish_for_operating_points(cpi.get()); ASSERT_TRUE(model->finalized); From 2b943f078c925b41ca88c14e4fed53477a56cf15 Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Fri, 14 Aug 2026 17:59:14 -0700 Subject: [PATCH 5/8] Fix MinGW decoder model build --- av2/common/level.c | 70 ++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/av2/common/level.c b/av2/common/level.c index 683d6ddd94..9d51dc2a18 100644 --- a/av2/common/level.c +++ b/av2/common/level.c @@ -604,6 +604,10 @@ static long double unsigned_wide_to_long_double( return result; } +static bool is_finite_number(long double value) { + return value >= -LDBL_MAX && value <= LDBL_MAX; +} + static bool rational_to_long_double(const Av2DmRational *value, long double *result) { if (value == NULL || result == NULL || value->negative) return false; @@ -611,7 +615,7 @@ static bool rational_to_long_double(const Av2DmRational *value, unsigned_wide_to_long_double(&value->denominator); if (!(denominator > 0.0L)) return false; *result = unsigned_wide_to_long_double(&value->magnitude) / denominator; - return isfinite(*result); + return is_finite_number(*result); } static bool rational_to_double(const Av2DmRational *value, double *result) { @@ -621,7 +625,7 @@ static bool rational_to_double(const Av2DmRational *value, double *result) { return false; } *result = (double)converted; - return isfinite(*result); + return is_finite_number(*result); } static double get_max_bitrate(const AV2LevelSpec *const level_spec, int tier, @@ -697,10 +701,11 @@ bool av2_encoder_decoder_model_push_dfg_interval(DECODER_MODEL *decoder_model, DFG_INTERVAL_QUEUE *const queue = &decoder_model->dfg_interval_queue; const double duration = interval->last_bit_arrival_time - interval->first_bit_arrival_time; - if (!isfinite(interval->first_bit_arrival_time) || - !isfinite(interval->last_bit_arrival_time) || - !isfinite(interval->removal_time) || !isfinite(duration) || - duration < 0.0 || !isfinite(queue->total_interval) || + if (!is_finite_number(interval->first_bit_arrival_time) || + !is_finite_number(interval->last_bit_arrival_time) || + !is_finite_number(interval->removal_time) || + !is_finite_number(duration) || duration < 0.0 || + !is_finite_number(queue->total_interval) || duration > DBL_MAX - queue->total_interval || interval->coded_bits > UINT64_MAX - queue->total_bits) { return false; @@ -736,7 +741,8 @@ bool av2_encoder_decoder_model_arrival_fits(const DECODER_MODEL *decoder_model, uint64_t coded_bits, double available_duration, bool *fits) { - if (decoder_model == NULL || fits == NULL || !isfinite(available_duration)) { + if (decoder_model == NULL || fits == NULL || + !is_finite_number(available_duration)) { return false; } long double bit_rate; @@ -745,7 +751,7 @@ bool av2_encoder_decoder_model_arrival_fits(const DECODER_MODEL *decoder_model, return false; } const long double available_bits = (long double)available_duration * bit_rate; - if (!isfinite(available_bits)) return false; + if (!is_finite_number(available_bits)) return false; *fits = available_bits >= 0.0L && coded_bits <= available_bits; return true; } @@ -754,7 +760,7 @@ static bool smoothing_buffer_fits_with_partial_arrival( const DECODER_MODEL *decoder_model, uint64_t queued_bits, uint64_t current_dfg_bits, double partial_arrival_duration, bool *fits) { if (decoder_model == NULL || fits == NULL || - !isfinite(partial_arrival_duration)) { + !is_finite_number(partial_arrival_duration)) { return false; } long double bit_rate; @@ -767,10 +773,10 @@ static bool smoothing_buffer_fits_with_partial_arrival( const long double duration = partial_arrival_duration > 0.0 ? partial_arrival_duration : 0.0L; long double partial_bits = duration * bit_rate; - if (!isfinite(partial_bits)) return false; + if (!is_finite_number(partial_bits)) return false; if (partial_bits > current_dfg_bits) partial_bits = current_dfg_bits; const long double fullness = queued_bits + partial_bits; - if (!isfinite(fullness)) return false; + if (!is_finite_number(fullness)) return false; *fits = fullness <= buffer_size; return true; } @@ -1414,7 +1420,7 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, (double)decoder_model->level_limits.max_decode_rate * scale_denominator / scale_numerator; if (!(decoder_model->decode_rate > 0.0) || - !isfinite(decoder_model->decode_rate)) { + !is_finite_number(decoder_model->decode_rate)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return; } @@ -1474,7 +1480,8 @@ static bool check_frame_constraints_at_decode_limit( const long double observed_decode_rate = max_decode_rate * frame->luma_sample_count / frame_parsing_time_decode_luma_samples; - if (!isfinite(max_decode_rate) || !isfinite(observed_decode_rate)) { + if (!is_finite_number(max_decode_rate) || + !is_finite_number(observed_decode_rate)) { return false; } decoder_model->max_decode_rate = @@ -1576,7 +1583,7 @@ bool av2_encoder_decoder_model_check_frame_constraints( uint64_t frame_parsing_time_decode_luma_samples) { if (decoder_model == NULL || frame == NULL || !frame->valid || decoder_model->status != DECODER_MODEL_OK || - !(frame_parsing_time > 0.0) || !isfinite(frame_parsing_time)) { + !(frame_parsing_time > 0.0) || !is_finite_number(frame_parsing_time)) { if (decoder_model != NULL && decoder_model->status == DECODER_MODEL_OK) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; } @@ -1613,8 +1620,8 @@ bool av2_encoder_decoder_model_check_frame_constraints( (long double)limits->max_tiles * scale_denominator / scale_numerator; const long double observed_decode_rate = (long double)frame->luma_sample_count / parsing_time; - if (!isfinite(max_decode_rate) || !isfinite(max_tiles) || - !isfinite(observed_decode_rate)) { + if (!is_finite_number(max_decode_rate) || !is_finite_number(max_tiles) || + !is_finite_number(observed_decode_rate)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } @@ -1626,7 +1633,7 @@ bool av2_encoder_decoder_model_check_frame_constraints( const long double dynamic_tile_limit = max_tiles * 120.0L * parsing_time; const long double tile_limit = AVMMIN(max_tiles, AVMMAX(1.0L, dynamic_tile_limit)); - if (!isfinite(dynamic_tile_limit) || !isfinite(tile_limit)) { + if (!is_finite_number(dynamic_tile_limit) || !is_finite_number(tile_limit)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } @@ -1647,8 +1654,9 @@ bool av2_encoder_decoder_model_check_frame_constraints( ((long double)8 * limits->min_compression_basis); const long double compressed_limit = AVMMIN(compressed_limit_from_picture, compressed_limit_from_rate); - if (!isfinite(compressed_limit_from_picture) || - !isfinite(compressed_limit_from_rate) || !isfinite(compressed_limit)) { + if (!is_finite_number(compressed_limit_from_picture) || + !is_finite_number(compressed_limit_from_rate) || + !is_finite_number(compressed_limit)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } @@ -1661,7 +1669,7 @@ bool av2_encoder_decoder_model_check_frame_constraints( const long double symbol_limit = parsing_time * max_decode_rate * limits->picture_size_profile_factor * symbol_factor; - if (!isfinite(symbol_factor) || !isfinite(symbol_limit)) { + if (!is_finite_number(symbol_factor) || !is_finite_number(symbol_limit)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } @@ -1677,7 +1685,7 @@ bool av2_encoder_decoder_model_store_frame_constraints( if (decoder_model == NULL || current_frame == NULL || !current_frame->valid || current_frame->decode_count == 0 || decoder_model->status != DECODER_MODEL_OK || - !isfinite(current_frame->removal_time)) { + !is_finite_number(current_frame->removal_time)) { if (decoder_model != NULL && decoder_model->status == DECODER_MODEL_OK) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; } @@ -1736,14 +1744,16 @@ void av2_encoder_decoder_model_finalize_frame_constraints( static bool update_max_display_rate(DECODER_MODEL *decoder_model, double display_duration) { - if (decoder_model == NULL || !isfinite(display_duration)) return false; + if (decoder_model == NULL || !is_finite_number(display_duration)) { + return false; + } if (display_duration <= 0.0) { decoder_model->max_display_rate = LDBL_MAX; return true; } const long double display_rate = (long double)decoder_model->display_samples / display_duration; - if (!isfinite(display_rate)) return false; + if (!is_finite_number(display_rate)) return false; decoder_model->max_display_rate = AVMMAX(decoder_model->max_display_rate, display_rate); return true; @@ -1812,8 +1822,8 @@ static bool get_minimum_presentation_interval( (double)limits.max_decode_rate / ((double)max_frame_headers_per_second * (double)limits.max_display_rate); *min_interval = AVMMAX(sample_interval, min_frame_time); - return isfinite(sample_interval) && isfinite(min_frame_time) && - isfinite(*min_interval); + return is_finite_number(sample_interval) && + is_finite_number(min_frame_time) && is_finite_number(*min_interval); } void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, @@ -2250,7 +2260,7 @@ static bool get_presentation_offset( *offset = decoder_model->last_presentation_offset + decoder_model->num_ticks_per_picture * decoder_model->display_clock_tick; - return isfinite(*offset); + return is_finite_number(*offset); } if (!presentation->presentation_time_present) return false; uint64_t base_epoch = presentation->rap_epoch; @@ -2267,7 +2277,7 @@ static bool get_presentation_offset( } *offset = base + presentation->presentation_time_ticks * decoder_model->display_clock_tick; - return isfinite(*offset); + return is_finite_number(*offset); } static void av2_decoder_model_check_output_frame( @@ -2348,7 +2358,7 @@ static void av2_decoder_model_check_output_frame( if (decoder_model->initial_presentation_delay >= 0.0) { presentation_time += decoder_model->initial_presentation_delay; } - if (!isfinite(presentation_time)) { + if (!is_finite_number(presentation_time)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return; } @@ -3047,8 +3057,8 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( if (check_bitrate) { // Check average bitrate instead of max_bitrate. if (!(level_stats->total_time_encoded > 0.0) || - !isfinite(level_stats->total_time_encoded) || - !isfinite(level_stats->total_compressed_size)) { + !is_finite_number(level_stats->total_time_encoded) || + !is_finite_number(level_stats->total_compressed_size)) { model_unavailable = true; break; } From 618eaf8961745e922f92fd04342779072b8d667c Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Wed, 19 Aug 2026 14:03:17 -0700 Subject: [PATCH 6/8] Decoder model across CVS --- av2/common/decoder_model.c | 3974 ++++++++++++++++++------ av2/common/decoder_model.h | 62 +- av2/common/level.c | 754 ++++- av2/common/level.h | 11 +- av2/decoder/decoder_model.c | 694 +++-- av2/decoder/decoder_model.h | 9 + test/brt_test.cc | 5 +- test/decoder_model_integration_test.cc | 304 +- test/decoder_model_lifecycle.h | 63 + test/decoder_model_parser_test.cc | 21 +- test/decoder_model_test.cc | 1566 ++++++++-- test/encoder_decoder_model_test.cc | 350 ++- test/level_test.cc | 90 +- test/ops_test.cc | 5 +- 14 files changed, 6236 insertions(+), 1672 deletions(-) create mode 100644 test/decoder_model_lifecycle.h diff --git a/av2/common/decoder_model.c b/av2/common/decoder_model.c index 1455d70239..d4f17c762c 100644 --- a/av2/common/decoder_model.c +++ b/av2/common/decoder_model.c @@ -13,6 +13,7 @@ #include "av2/common/decoder_model.h" #include +#include #include #include "avm_mem/avm_mem.h" @@ -32,42 +33,13 @@ _Static_assert(AV2_DM_MAX_BUFFER_POOL_SIZE >= REF_FRAMES + 2, "decoder-model BufferPool must hold REF_FRAMES + 2 buffers"); #define AV2_DM_WIDE_LIMBS 4 -#define AV2_DM_PRODUCT_LIMBS (2 * AV2_DM_WIDE_LIMBS) - -typedef struct Av2DmUnsignedProduct { - uint64_t limbs[AV2_DM_PRODUCT_LIMBS]; -} Av2DmUnsignedProduct; +#define AV2_DM_BIG_UINT_INLINE_LIMBS (AV2_DM_WIDE_LIMBS * 2) static Av2DmUnsignedWide wide_from_u64(uint64_t value) { Av2DmUnsignedWide result = { { value, 0, 0, 0 } }; return result; } -static bool wide_is_zero(Av2DmUnsignedWide value) { - for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { - if (value.limbs[i] != 0) return false; - } - return true; -} - -static bool wide_equals_u64(Av2DmUnsignedWide value, uint64_t expected) { - return value.limbs[0] == expected && value.limbs[1] == 0 && - value.limbs[2] == 0 && value.limbs[3] == 0; -} - -static bool wide_fits_u64(Av2DmUnsignedWide value) { - return value.limbs[1] == 0 && value.limbs[2] == 0 && value.limbs[3] == 0; -} - -static int wide_compare(Av2DmUnsignedWide left, Av2DmUnsignedWide right) { - for (int i = AV2_DM_WIDE_LIMBS - 1; i >= 0; --i) { - if (left.limbs[i] != right.limbs[i]) { - return left.limbs[i] < right.limbs[i] ? -1 : 1; - } - } - return 0; -} - #if defined(__clang__) && defined(__has_attribute) #if __has_attribute(no_sanitize) #define AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK \ @@ -80,229 +52,553 @@ static int wide_compare(Av2DmUnsignedWide left, Av2DmUnsignedWide right) { #define AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK #endif -AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool wide_add( - Av2DmUnsignedWide left, Av2DmUnsignedWide right, - Av2DmUnsignedWide *result) { - uint64_t carry = 0; - for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { - const uint64_t partial = left.limbs[i] + right.limbs[i]; - const uint64_t partial_carry = partial < left.limbs[i]; - const uint64_t sum = partial + carry; - const uint64_t carry_carry = sum < partial; - result->limbs[i] = sum; - carry = partial_carry | carry_carry; +// Computes the complete 64-by-64-bit product using only fixed-width portable +// C arithmetic. Unsigned wraparound in the 32-bit partial-product assembly is +// intentional and defined by the C language. +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static void multiply_64( + uint64_t left, uint64_t right, uint64_t *product_low, + uint64_t *product_high) { + const uint64_t mask = UINT32_MAX; + const uint64_t left_low = left & mask; + const uint64_t left_high = left >> 32; + const uint64_t right_low = right & mask; + const uint64_t right_high = right >> 32; + const uint64_t low = left_low * right_low; + const uint64_t middle_1 = left_high * right_low + (low >> 32); + const uint64_t middle_2 = left_low * right_high + (middle_1 & mask); + *product_high = left_high * right_high + (middle_1 >> 32) + (middle_2 >> 32); + *product_low = (middle_2 << 32) | (low & mask); +} + +#undef AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK + +typedef struct Av2DmBigUInt { + uint64_t *limbs; + uint32_t count; + uint32_t capacity; + uint64_t inline_limbs[AV2_DM_BIG_UINT_INLINE_LIMBS]; +} Av2DmBigUInt; + +static _Thread_local int64_t rational_allocations_before_failure = -1; +static _Thread_local bool rational_allocation_failed; +static _Thread_local int64_t internal_allocations_before_failure = -1; +static _Thread_local bool internal_allocation_failed; +static atomic_uint_fast64_t rational_active_allocations = ATOMIC_VAR_INIT(0); + +static void *internal_allocate(size_t count, size_t size, bool clear) { + internal_allocation_failed = false; + if (size != 0 && count > SIZE_MAX / size) return NULL; + if (internal_allocations_before_failure >= 0) { + if (internal_allocations_before_failure == 0) { + internal_allocation_failed = true; + return NULL; + } + --internal_allocations_before_failure; } - return carry == 0; + void *const allocation = + clear ? avm_calloc(count, size) : avm_malloc(count * size); + if (allocation == NULL) internal_allocation_failed = true; + return allocation; } -// Subtraction is modulo 2^256. Callers either establish left >= right or use -// the wraparound result as one step of long division with a 257th carry bit. -AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static Av2DmUnsignedWide wide_subtract( - Av2DmUnsignedWide left, Av2DmUnsignedWide right) { - Av2DmUnsignedWide result; - uint64_t borrow = 0; - for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { - const uint64_t partial = left.limbs[i] - right.limbs[i]; - const uint64_t partial_borrow = left.limbs[i] < right.limbs[i]; - result.limbs[i] = partial - borrow; - const uint64_t borrow_borrow = partial < borrow; - borrow = partial_borrow | borrow_borrow; +static void *internal_calloc(size_t count, size_t size) { + return internal_allocate(count, size, true); +} + +static void *internal_malloc(size_t size) { + return internal_allocate(1, size, false); +} + +static void *rational_allocate(size_t size) { + if (rational_allocations_before_failure >= 0) { + if (rational_allocations_before_failure == 0) { + rational_allocation_failed = true; + return NULL; + } + --rational_allocations_before_failure; } - return result; + void *const allocation = avm_malloc(size); + if (allocation == NULL) rational_allocation_failed = true; + if (allocation != NULL) atomic_fetch_add(&rational_active_allocations, 1); + return allocation; +} + +static void rational_deallocate(void *allocation) { + if (allocation == NULL) return; + avm_free(allocation); + atomic_fetch_sub(&rational_active_allocations, 1); +} + +void av2_dm_rational_set_allocation_failure_after_for_testing( + int64_t successful_allocations) { + rational_allocations_before_failure = successful_allocations; + rational_allocation_failed = false; +} + +void av2_dm_set_internal_allocation_failure_after_for_testing( + int64_t successful_allocations) { + internal_allocations_before_failure = successful_allocations; + internal_allocation_failed = false; +} + +uint64_t av2_dm_rational_allocation_count_for_testing(void) { + return atomic_load(&rational_active_allocations); +} + +bool av2_dm_rational_last_failure_was_allocation(void) { + return rational_allocation_failed; } -static uint64_t wide_get_bit(Av2DmUnsignedWide value, uint32_t bit_index) { - return (value.limbs[bit_index / 64] >> (bit_index % 64)) & 1; +bool av2_dm_last_failure_was_allocation(void) { + return rational_allocation_failed || internal_allocation_failed; } -static void wide_set_bit(Av2DmUnsignedWide *value, uint32_t bit_index) { - value->limbs[bit_index / 64] |= UINT64_C(1) << (bit_index % 64); +static void rational_begin_operation(void) { + rational_allocation_failed = false; + internal_allocation_failed = false; } -AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool wide_shift_left_one( - Av2DmUnsignedWide *value) { - const bool overflow = (value->limbs[AV2_DM_WIDE_LIMBS - 1] >> 63) != 0; - for (int i = AV2_DM_WIDE_LIMBS - 1; i > 0; --i) { - value->limbs[i] = (value->limbs[i] << 1) | (value->limbs[i - 1] >> 63); +static void big_uint_destroy(Av2DmBigUInt *value) { + if (value->limbs != value->inline_limbs) { + rational_deallocate(value->limbs); } - value->limbs[0] <<= 1; - return overflow; + memset(value, 0, sizeof(*value)); } -// Binary long division over the complete two-limb-independent representation. -static bool wide_divide(Av2DmUnsignedWide dividend, Av2DmUnsignedWide divisor, - Av2DmUnsignedWide *quotient, - Av2DmUnsignedWide *remainder) { - if (wide_is_zero(divisor)) return false; - if (wide_fits_u64(dividend) && wide_fits_u64(divisor)) { - const uint64_t divisor_low = divisor.limbs[0]; - if (divisor_low == 0) return false; - *quotient = wide_from_u64(dividend.limbs[0] / divisor_low); - *remainder = wide_from_u64(dividend.limbs[0] % divisor_low); - return true; +static void big_uint_trim(Av2DmBigUInt *value) { + while (value->count != 0 && value->limbs[value->count - 1] == 0) { + --value->count; } - Av2DmUnsignedWide result = { { 0, 0, 0, 0 } }; - Av2DmUnsignedWide rem = { { 0, 0, 0, 0 } }; - for (int bit_index = 255; bit_index >= 0; --bit_index) { - const bool overflow = wide_shift_left_one(&rem); - rem.limbs[0] |= wide_get_bit(dividend, (uint32_t)bit_index); - if (overflow || wide_compare(rem, divisor) >= 0) { - rem = wide_subtract(rem, divisor); - wide_set_bit(&result, (uint32_t)bit_index); - } +} + +static bool big_uint_allocate(Av2DmBigUInt *value, uint32_t capacity) { + if (capacity == 0) return true; + if ((uint64_t)capacity > SIZE_MAX / sizeof(*value->limbs)) return false; + if (capacity <= AV2_DM_BIG_UINT_INLINE_LIMBS) { + value->limbs = value->inline_limbs; + } else { + value->limbs = rational_allocate((size_t)capacity * sizeof(*value->limbs)); + if (value->limbs == NULL) return false; } - *quotient = result; - *remainder = rem; + memset(value->limbs, 0, (size_t)capacity * sizeof(*value->limbs)); + value->capacity = capacity; return true; } -static Av2DmUnsignedWide wide_gcd(Av2DmUnsignedWide left, - Av2DmUnsignedWide right) { - if (wide_fits_u64(left) && wide_fits_u64(right)) { - uint64_t a = left.limbs[0]; - uint64_t b = right.limbs[0]; - while (b != 0) { - const uint64_t remainder = a % b; - a = b; - b = remainder; - } - return wide_from_u64(a); +static bool big_uint_from_limbs(Av2DmBigUInt *value, const uint64_t *limbs, + uint32_t count) { + while (count != 0 && limbs[count - 1] == 0) --count; + if (!big_uint_allocate(value, count)) return false; + if (count != 0) { + memcpy(value->limbs, limbs, (size_t)count * sizeof(*limbs)); } - while (!wide_is_zero(right)) { - Av2DmUnsignedWide quotient; - Av2DmUnsignedWide remainder; - if (!wide_divide(left, right, "ient, &remainder)) { - return wide_from_u64(0); - } - left = right; - right = remainder; + value->count = count; + return true; +} + +static bool big_uint_from_u64(Av2DmBigUInt *value, uint64_t scalar) { + return scalar == 0 || (big_uint_allocate(value, 1) && + (value->limbs[0] = scalar, value->count = 1, true)); +} + +static bool big_uint_copy(Av2DmBigUInt *destination, + const Av2DmBigUInt *source) { + return big_uint_from_limbs(destination, source->limbs, source->count); +} + +static void big_uint_move(Av2DmBigUInt *destination, Av2DmBigUInt *source) { + big_uint_destroy(destination); + if (source->limbs == source->inline_limbs) { + destination->limbs = destination->inline_limbs; + destination->count = source->count; + destination->capacity = source->capacity; + memcpy(destination->inline_limbs, source->inline_limbs, + (size_t)source->capacity * sizeof(*source->inline_limbs)); + } else { + *destination = *source; } - return left; + memset(source, 0, sizeof(*source)); } -// Computes the complete 64-by-64-bit product using only fixed-width portable -// C arithmetic. Unsigned wraparound in the 32-bit partial-product assembly is -// intentional and defined by the C language. -AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static void multiply_64( - uint64_t left, uint64_t right, uint64_t *product_low, - uint64_t *product_high) { - const uint64_t mask = UINT32_MAX; - const uint64_t left_low = left & mask; - const uint64_t left_high = left >> 32; - const uint64_t right_low = right & mask; - const uint64_t right_high = right >> 32; - const uint64_t low = left_low * right_low; - const uint64_t middle_1 = left_high * right_low + (low >> 32); - const uint64_t middle_2 = left_low * right_high + (middle_1 & mask); - *product_high = left_high * right_high + (middle_1 >> 32) + (middle_2 >> 32); - *product_low = (middle_2 << 32) | (low & mask); +static bool big_uint_is_zero(const Av2DmBigUInt *value) { + return value->count == 0; } -AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool product_add_at( - Av2DmUnsignedProduct *product, uint32_t index, uint64_t low, - uint64_t high) { - if (index >= AV2_DM_PRODUCT_LIMBS) return low == 0 && high == 0; - const uint64_t old_low = product->limbs[index]; - product->limbs[index] += low; - uint64_t carry = product->limbs[index] < old_low; - ++index; - if (index >= AV2_DM_PRODUCT_LIMBS) return high == 0 && carry == 0; - - const uint64_t old_high = product->limbs[index]; - product->limbs[index] += high; - const uint64_t high_carry = product->limbs[index] < old_high; - const uint64_t partial = product->limbs[index]; - product->limbs[index] += carry; - const uint64_t carry_carry = product->limbs[index] < partial; - carry = high_carry | carry_carry; - ++index; - while (carry != 0 && index < AV2_DM_PRODUCT_LIMBS) { - ++product->limbs[index]; - carry = product->limbs[index] == 0; - ++index; - } - return carry == 0; +static int big_uint_compare(const Av2DmBigUInt *left, + const Av2DmBigUInt *right) { + if (left->count != right->count) return left->count < right->count ? -1 : 1; + for (uint32_t i = left->count; i > 0; --i) { + if (left->limbs[i - 1] != right->limbs[i - 1]) { + return left->limbs[i - 1] < right->limbs[i - 1] ? -1 : 1; + } + } + return 0; } -#undef AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK +static bool big_uint_add(const Av2DmBigUInt *left, const Av2DmBigUInt *right, + Av2DmBigUInt *result) { + const uint32_t maximum = + left->count > right->count ? left->count : right->count; + if (maximum == UINT32_MAX || !big_uint_allocate(result, maximum + 1)) { + return false; + } + uint64_t carry = 0; + for (uint32_t i = 0; i < maximum; ++i) { + const uint64_t left_limb = i < left->count ? left->limbs[i] : 0; + const uint64_t right_limb = i < right->count ? right->limbs[i] : 0; + const uint64_t partial = left_limb + right_limb; + const uint64_t partial_carry = partial < left_limb; + const uint64_t sum = partial + carry; + const uint64_t carry_carry = sum < partial; + result->limbs[i] = sum; + carry = partial_carry | carry_carry; + } + result->limbs[maximum] = carry; + result->count = maximum + (carry != 0); + return true; +} + +static bool big_uint_subtract(const Av2DmBigUInt *left, + const Av2DmBigUInt *right, Av2DmBigUInt *result) { + if (big_uint_compare(left, right) < 0 || + !big_uint_allocate(result, left->count)) { + return false; + } + uint64_t borrow = 0; + for (uint32_t i = 0; i < left->count; ++i) { + const uint64_t right_limb = i < right->count ? right->limbs[i] : 0; + const uint64_t partial = left->limbs[i] - right_limb; + const uint64_t partial_borrow = left->limbs[i] < right_limb; + result->limbs[i] = partial - borrow; + const uint64_t borrow_borrow = partial < borrow; + borrow = partial_borrow | borrow_borrow; + } + if (borrow != 0) return false; + result->count = left->count; + big_uint_trim(result); + return true; +} -static bool wide_multiply(Av2DmUnsignedWide left, Av2DmUnsignedWide right, - Av2DmUnsignedProduct *product) { - memset(product, 0, sizeof(*product)); - for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { - for (uint32_t j = 0; j < AV2_DM_WIDE_LIMBS; ++j) { +static bool big_uint_multiply(const Av2DmBigUInt *left, + const Av2DmBigUInt *right, Av2DmBigUInt *result) { + if (big_uint_is_zero(left) || big_uint_is_zero(right)) return true; + if (UINT32_MAX - left->count < right->count || + !big_uint_allocate(result, left->count + right->count)) { + return false; + } + for (uint32_t i = 0; i < left->count; ++i) { + for (uint32_t j = 0; j < right->count; ++j) { uint64_t low; uint64_t high; - multiply_64(left.limbs[i], right.limbs[j], &low, &high); - if (!product_add_at(product, i + j, low, high)) return false; + multiply_64(left->limbs[i], right->limbs[j], &low, &high); + uint32_t index = i + j; + uint64_t old = result->limbs[index]; + result->limbs[index] += low; + uint64_t carry = result->limbs[index] < old; + ++index; + old = result->limbs[index]; + result->limbs[index] += high; + uint64_t next_carry = result->limbs[index] < old; + old = result->limbs[index]; + result->limbs[index] += carry; + carry = next_carry | (result->limbs[index] < old); + ++index; + while (carry != 0 && index < result->capacity) { + ++result->limbs[index]; + carry = result->limbs[index] == 0; + ++index; + } + if (carry != 0) return false; } } + result->count = result->capacity; + big_uint_trim(result); return true; } -static int product_compare(Av2DmUnsignedProduct left, - Av2DmUnsignedProduct right) { - for (int i = AV2_DM_PRODUCT_LIMBS - 1; i >= 0; --i) { - if (left.limbs[i] != right.limbs[i]) { - return left.limbs[i] < right.limbs[i] ? -1 : 1; - } +static uint32_t big_uint_bit_count(const Av2DmBigUInt *value) { + if (value->count == 0) return 0; + uint64_t high = value->limbs[value->count - 1]; + uint32_t high_bits = 0; + while (high != 0) { + ++high_bits; + high >>= 1; } - return 0; + if (value->count - 1 > (UINT32_MAX - high_bits) / 64) return UINT32_MAX; + return (value->count - 1) * 64 + high_bits; } -static bool product_to_wide(Av2DmUnsignedProduct product, - Av2DmUnsignedWide *result) { - for (uint32_t i = AV2_DM_WIDE_LIMBS; i < AV2_DM_PRODUCT_LIMBS; ++i) { - if (product.limbs[i] != 0) return false; +static bool big_uint_shift_add_bit(Av2DmBigUInt *value, uint64_t bit) { + uint64_t carry = bit; + for (uint32_t i = 0; i < value->count; ++i) { + const uint64_t next_carry = value->limbs[i] >> 63; + value->limbs[i] = (value->limbs[i] << 1) | carry; + carry = next_carry; } - for (uint32_t i = 0; i < AV2_DM_WIDE_LIMBS; ++i) { - result->limbs[i] = product.limbs[i]; + if (carry != 0) { + if (value->count == value->capacity) return false; + value->limbs[value->count++] = carry; + } else if (value->count == 0 && bit != 0) { + if (value->capacity == 0) return false; + value->limbs[0] = bit; + value->count = 1; } return true; } -static bool wide_multiply_checked(Av2DmUnsignedWide left, - Av2DmUnsignedWide right, - Av2DmUnsignedWide *result) { - if (wide_fits_u64(left) && wide_fits_u64(right)) { - memset(result, 0, sizeof(*result)); - multiply_64(left.limbs[0], right.limbs[0], &result->limbs[0], - &result->limbs[1]); - return true; +static bool big_uint_subtract_in_place(Av2DmBigUInt *left, + const Av2DmBigUInt *right) { + uint64_t borrow = 0; + for (uint32_t i = 0; i < left->count; ++i) { + const uint64_t right_limb = i < right->count ? right->limbs[i] : 0; + const uint64_t partial = left->limbs[i] - right_limb; + const uint64_t partial_borrow = left->limbs[i] < right_limb; + left->limbs[i] = partial - borrow; + const uint64_t borrow_borrow = partial < borrow; + borrow = partial_borrow | borrow_borrow; } - Av2DmUnsignedProduct product; - return wide_multiply(left, right, &product) && - product_to_wide(product, result); + if (borrow != 0) return false; + big_uint_trim(left); + return true; } -static bool rational_normalize(Av2DmRational *value) { - if (wide_is_zero(value->denominator)) return false; - if (wide_is_zero(value->magnitude)) { - value->denominator = wide_from_u64(1); - value->negative = false; - return true; +static bool big_uint_divide(const Av2DmBigUInt *dividend, + const Av2DmBigUInt *divisor, Av2DmBigUInt *quotient, + Av2DmBigUInt *remainder) { + if (big_uint_is_zero(divisor)) return false; + if (big_uint_compare(dividend, divisor) < 0) { + return big_uint_copy(remainder, dividend); + } + if (!big_uint_allocate(quotient, dividend->count) || + divisor->count == UINT32_MAX || + !big_uint_allocate(remainder, divisor->count + 1)) { + return false; + } + const uint32_t bits = big_uint_bit_count(dividend); + if (bits == UINT32_MAX) return false; + for (uint32_t bit_offset = bits; bit_offset > 0; --bit_offset) { + const uint32_t bit = bit_offset - 1; + if (!big_uint_shift_add_bit( + remainder, + (dividend->limbs[bit / 64] >> (bit % 64)) & UINT64_C(1))) { + return false; + } + if (big_uint_compare(remainder, divisor) >= 0) { + if (!big_uint_subtract_in_place(remainder, divisor)) return false; + quotient->limbs[bit / 64] |= UINT64_C(1) << (bit % 64); + } + } + quotient->count = quotient->capacity; + big_uint_trim(quotient); + return true; +} + +static bool big_uint_divide_exact(const Av2DmBigUInt *dividend, + const Av2DmBigUInt *divisor, + Av2DmBigUInt *quotient) { + Av2DmBigUInt remainder = { 0 }; + const bool divided = big_uint_divide(dividend, divisor, quotient, &remainder); + const bool exact = divided && big_uint_is_zero(&remainder); + big_uint_destroy(&remainder); + return exact; +} + +static bool big_uint_gcd(const Av2DmBigUInt *left, const Av2DmBigUInt *right, + Av2DmBigUInt *result) { + Av2DmBigUInt a = { 0 }; + Av2DmBigUInt b = { 0 }; + bool ok = big_uint_copy(&a, left) && big_uint_copy(&b, right); + while (ok && !big_uint_is_zero(&b)) { + Av2DmBigUInt quotient = { 0 }; + Av2DmBigUInt remainder = { 0 }; + ok = big_uint_divide(&a, &b, "ient, &remainder); + big_uint_destroy("ient); + if (ok) { + big_uint_move(&a, &b); + big_uint_move(&b, &remainder); + } + big_uint_destroy(&remainder); } + if (ok) big_uint_move(result, &a); + big_uint_destroy(&a); + big_uint_destroy(&b); + return ok; +} - const Av2DmUnsignedWide divisor = - wide_gcd(value->magnitude, value->denominator); - if (wide_is_zero(divisor)) return false; - if (!wide_equals_u64(divisor, 1)) { - Av2DmUnsignedWide remainder; - Av2DmUnsignedWide reduced; - if (!wide_divide(value->magnitude, divisor, &reduced, &remainder) || - !wide_is_zero(remainder)) { +static bool rational_component_view(const Av2DmRational *value, + bool denominator, const uint64_t **limbs, + uint32_t *count) { + if (value->dynamic_limbs != NULL) { + if (value->magnitude_limb_count == 0 || + value->denominator_limb_count == 0) { return false; } - value->magnitude = reduced; - if (!wide_divide(value->denominator, divisor, &reduced, &remainder) || - !wide_is_zero(remainder)) { + *limbs = value->dynamic_limbs; + *count = value->magnitude_limb_count; + if (denominator) { + *limbs += value->magnitude_limb_count; + *count = value->denominator_limb_count; + } + return true; + } + *limbs = denominator ? value->denominator.limbs : value->magnitude.limbs; + *count = AV2_DM_WIDE_LIMBS; + while (*count != 0 && (*limbs)[*count - 1] == 0) --*count; + return true; +} + +bool av2_dm_rational_get_component(const Av2DmRational *value, bool denominator, + const uint64_t **limbs, + uint32_t *limb_count) { + return value != NULL && limbs != NULL && limb_count != NULL && + rational_component_view(value, denominator, limbs, limb_count); +} + +static bool big_uint_from_rational(const Av2DmRational *value, bool denominator, + Av2DmBigUInt *result) { + const uint64_t *limbs; + uint32_t count; + return rational_component_view(value, denominator, &limbs, &count) && + big_uint_from_limbs(result, limbs, count); +} + +void av2_dm_rational_init(Av2DmRational *value) { + if (value == NULL) return; + memset(value, 0, sizeof(*value)); + value->denominator.limbs[0] = 1; + value->magnitude_limb_count = 1; + value->denominator_limb_count = 1; +} + +void av2_dm_rational_destroy(Av2DmRational *value) { + if (value == NULL) return; + rational_deallocate(value->dynamic_limbs); + av2_dm_rational_init(value); +} + +static bool rational_store(Av2DmRational *result, const Av2DmBigUInt *magnitude, + const Av2DmBigUInt *denominator, bool negative) { + if (big_uint_is_zero(denominator)) return false; + Av2DmRational temporary; + av2_dm_rational_init(&temporary); + const uint32_t magnitude_count = magnitude->count == 0 ? 1 : magnitude->count; + const uint32_t denominator_count = denominator->count; + temporary.magnitude_limb_count = magnitude_count; + temporary.denominator_limb_count = denominator_count; + temporary.negative = !big_uint_is_zero(magnitude) && negative; + const uint32_t magnitude_copy = magnitude->count < AV2_DM_WIDE_LIMBS + ? magnitude->count + : AV2_DM_WIDE_LIMBS; + const uint32_t denominator_copy = denominator->count < AV2_DM_WIDE_LIMBS + ? denominator->count + : AV2_DM_WIDE_LIMBS; + if (magnitude_copy != 0) { + memcpy(temporary.magnitude.limbs, magnitude->limbs, + (size_t)magnitude_copy * sizeof(*magnitude->limbs)); + } + memset(temporary.denominator.limbs, 0, sizeof(temporary.denominator.limbs)); + memcpy(temporary.denominator.limbs, denominator->limbs, + (size_t)denominator_copy * sizeof(*denominator->limbs)); + if (magnitude->count > AV2_DM_WIDE_LIMBS || + denominator->count > AV2_DM_WIDE_LIMBS) { + if (UINT32_MAX - magnitude_count < denominator_count || + (uint64_t)magnitude_count + denominator_count > + SIZE_MAX / sizeof(*temporary.dynamic_limbs)) { return false; } - value->denominator = reduced; + const uint32_t total_count = magnitude_count + denominator_count; + temporary.dynamic_limbs = rational_allocate( + (size_t)total_count * sizeof(*temporary.dynamic_limbs)); + if (temporary.dynamic_limbs == NULL) return false; + memset(temporary.dynamic_limbs, 0, + (size_t)total_count * sizeof(*temporary.dynamic_limbs)); + if (magnitude->count != 0) { + memcpy(temporary.dynamic_limbs, magnitude->limbs, + (size_t)magnitude->count * sizeof(*magnitude->limbs)); + } + memcpy(temporary.dynamic_limbs + magnitude_count, denominator->limbs, + (size_t)denominator_count * sizeof(*denominator->limbs)); } + av2_dm_rational_move(result, &temporary); return true; } +void av2_dm_rational_move(Av2DmRational *destination, Av2DmRational *source) { + if (destination == NULL || source == NULL || destination == source) return; + av2_dm_rational_destroy(destination); + *destination = *source; + av2_dm_rational_init(source); +} + +bool av2_dm_rational_copy(Av2DmRational *destination, + const Av2DmRational *source) { + rational_begin_operation(); + if (destination == NULL || source == NULL) return false; + if (destination == source) return true; + if (source->dynamic_limbs == NULL) { + uint32_t magnitude_count = AV2_DM_WIDE_LIMBS; + uint32_t denominator_count = AV2_DM_WIDE_LIMBS; + while (magnitude_count != 0 && + source->magnitude.limbs[magnitude_count - 1] == 0) { + --magnitude_count; + } + while (denominator_count != 0 && + source->denominator.limbs[denominator_count - 1] == 0) { + --denominator_count; + } + if (denominator_count == 0) return false; + Av2DmRational temporary = *source; + temporary.magnitude_limb_count = magnitude_count == 0 ? 1 : magnitude_count; + temporary.denominator_limb_count = denominator_count; + temporary.negative = magnitude_count != 0 && source->negative; + av2_dm_rational_move(destination, &temporary); + return true; + } + Av2DmBigUInt magnitude = { 0 }; + Av2DmBigUInt denominator = { 0 }; + Av2DmRational temporary; + av2_dm_rational_init(&temporary); + const bool copied = + big_uint_from_rational(source, false, &magnitude) && + big_uint_from_rational(source, true, &denominator) && + rational_store(&temporary, &magnitude, &denominator, source->negative); + if (copied) av2_dm_rational_move(destination, &temporary); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + return copied; +} + +static bool rational_normalize(Av2DmRational *value) { + Av2DmBigUInt magnitude = { 0 }; + Av2DmBigUInt denominator = { 0 }; + Av2DmBigUInt divisor = { 0 }; + Av2DmBigUInt reduced_magnitude = { 0 }; + Av2DmBigUInt reduced_denominator = { 0 }; + bool ok = big_uint_from_rational(value, false, &magnitude) && + big_uint_from_rational(value, true, &denominator) && + !big_uint_is_zero(&denominator); + if (ok && big_uint_is_zero(&magnitude)) { + big_uint_destroy(&denominator); + ok = big_uint_from_u64(&denominator, 1); + } else if (ok) { + ok = big_uint_gcd(&magnitude, &denominator, &divisor) && + big_uint_divide_exact(&magnitude, &divisor, &reduced_magnitude) && + big_uint_divide_exact(&denominator, &divisor, &reduced_denominator); + if (ok) { + big_uint_move(&magnitude, &reduced_magnitude); + big_uint_move(&denominator, &reduced_denominator); + } + } + if (ok) ok = rational_store(value, &magnitude, &denominator, value->negative); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + big_uint_destroy(&divisor); + big_uint_destroy(&reduced_magnitude); + big_uint_destroy(&reduced_denominator); + return ok; +} + bool av2_dm_rational_make(uint64_t numerator, uint64_t denominator, Av2DmRational *result) { return av2_dm_rational_make_wide(wide_from_u64(numerator), denominator, false, @@ -312,247 +608,279 @@ bool av2_dm_rational_make(uint64_t numerator, uint64_t denominator, bool av2_dm_rational_make_wide(Av2DmUnsignedWide numerator, uint64_t denominator, bool negative, Av2DmRational *result) { + rational_begin_operation(); if (result == NULL || denominator == 0) return false; - result->magnitude = numerator; - result->denominator = wide_from_u64(denominator); - result->negative = negative; - return rational_normalize(result); -} - -static bool rational_compare_magnitudes(const Av2DmRational *left, - const Av2DmRational *right, - int *comparison) { - // Cancel factors common to both numerators and both denominators before - // forming the exact cross-products. The products are retained in 512 bits. - const Av2DmUnsignedWide numerator_gcd = - wide_gcd(left->magnitude, right->magnitude); - const Av2DmUnsignedWide denominator_gcd = - wide_gcd(left->denominator, right->denominator); - if (wide_is_zero(numerator_gcd) || wide_is_zero(denominator_gcd)) { - return false; - } - Av2DmUnsignedWide left_numerator; - Av2DmUnsignedWide right_numerator; - Av2DmUnsignedWide left_denominator; - Av2DmUnsignedWide right_denominator; - Av2DmUnsignedWide remainder; - if (!wide_divide(left->magnitude, numerator_gcd, &left_numerator, - &remainder) || - !wide_is_zero(remainder) || - !wide_divide(right->magnitude, numerator_gcd, &right_numerator, - &remainder) || - !wide_is_zero(remainder) || - !wide_divide(left->denominator, denominator_gcd, &left_denominator, - &remainder) || - !wide_is_zero(remainder) || - !wide_divide(right->denominator, denominator_gcd, &right_denominator, - &remainder) || - !wide_is_zero(remainder)) { - return false; - } - Av2DmUnsignedProduct left_product; - Av2DmUnsignedProduct right_product; - if (!wide_multiply(left_numerator, right_denominator, &left_product) || - !wide_multiply(right_numerator, left_denominator, &right_product)) { - return false; - } - *comparison = product_compare(left_product, right_product); - return true; + Av2DmBigUInt magnitude = { 0 }; + Av2DmBigUInt rational_denominator = { 0 }; + Av2DmRational temporary; + av2_dm_rational_init(&temporary); + const bool made = + big_uint_from_limbs(&magnitude, numerator.limbs, AV2_DM_WIDE_LIMBS) && + big_uint_from_u64(&rational_denominator, denominator) && + rational_store(&temporary, &magnitude, &rational_denominator, negative) && + rational_normalize(&temporary); + if (made) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&magnitude); + big_uint_destroy(&rational_denominator); + return made; +} + +static bool rational_normalized_copy(const Av2DmRational *source, + Av2DmRational *destination) { + return av2_dm_rational_copy(destination, source) && + rational_normalize(destination); } bool av2_dm_rational_add(const Av2DmRational *left, const Av2DmRational *right, Av2DmRational *result) { - if (left == NULL || right == NULL || result == NULL || - wide_is_zero(left->denominator) || wide_is_zero(right->denominator)) { - return false; - } - Av2DmRational normalized_left = *left; - Av2DmRational normalized_right = *right; - if (!rational_normalize(&normalized_left) || - !rational_normalize(&normalized_right)) { - return false; - } - - const Av2DmUnsignedWide denominator_gcd = - wide_gcd(normalized_left.denominator, normalized_right.denominator); - if (wide_is_zero(denominator_gcd)) return false; - Av2DmUnsignedWide left_multiplier; - Av2DmUnsignedWide right_multiplier; - Av2DmUnsignedWide remainder; - if (!wide_divide(normalized_right.denominator, denominator_gcd, - &left_multiplier, &remainder) || - !wide_is_zero(remainder) || - !wide_divide(normalized_left.denominator, denominator_gcd, - &right_multiplier, &remainder) || - !wide_is_zero(remainder) || - !wide_multiply_checked(normalized_left.denominator, left_multiplier, - &result->denominator)) { - return false; - } - Av2DmUnsignedWide scaled_left; - Av2DmUnsignedWide scaled_right; - if (!wide_multiply_checked(normalized_left.magnitude, left_multiplier, - &scaled_left) || - !wide_multiply_checked(normalized_right.magnitude, right_multiplier, - &scaled_right)) { - return false; - } - - if (normalized_left.negative == normalized_right.negative) { - if (!wide_add(scaled_left, scaled_right, &result->magnitude)) return false; - result->negative = normalized_left.negative; - } else { - const int comparison = wide_compare(scaled_left, scaled_right); - if (comparison >= 0) { - result->magnitude = wide_subtract(scaled_left, scaled_right); - result->negative = normalized_left.negative; - } else { - result->magnitude = wide_subtract(scaled_right, scaled_left); - result->negative = normalized_right.negative; - } - } - return rational_normalize(result); + rational_begin_operation(); + if (left == NULL || right == NULL || result == NULL) return false; + Av2DmRational normalized_left; + Av2DmRational normalized_right; + Av2DmRational temporary; + av2_dm_rational_init(&normalized_left); + av2_dm_rational_init(&normalized_right); + av2_dm_rational_init(&temporary); + Av2DmBigUInt left_magnitude = { 0 }, right_magnitude = { 0 }; + Av2DmBigUInt left_denominator = { 0 }, right_denominator = { 0 }; + Av2DmBigUInt denominator_gcd = { 0 }, left_multiplier = { 0 }; + Av2DmBigUInt right_multiplier = { 0 }, denominator = { 0 }; + Av2DmBigUInt scaled_left = { 0 }, scaled_right = { 0 }, magnitude = { 0 }; + bool negative = false; + bool ok = + rational_normalized_copy(left, &normalized_left) && + rational_normalized_copy(right, &normalized_right) && + big_uint_from_rational(&normalized_left, false, &left_magnitude) && + big_uint_from_rational(&normalized_right, false, &right_magnitude) && + big_uint_from_rational(&normalized_left, true, &left_denominator) && + big_uint_from_rational(&normalized_right, true, &right_denominator) && + big_uint_gcd(&left_denominator, &right_denominator, &denominator_gcd) && + big_uint_divide_exact(&right_denominator, &denominator_gcd, + &left_multiplier) && + big_uint_divide_exact(&left_denominator, &denominator_gcd, + &right_multiplier) && + big_uint_multiply(&left_denominator, &left_multiplier, &denominator) && + big_uint_multiply(&left_magnitude, &left_multiplier, &scaled_left) && + big_uint_multiply(&right_magnitude, &right_multiplier, &scaled_right); + if (ok && normalized_left.negative == normalized_right.negative) { + ok = big_uint_add(&scaled_left, &scaled_right, &magnitude); + negative = normalized_left.negative; + } else if (ok && big_uint_compare(&scaled_left, &scaled_right) >= 0) { + ok = big_uint_subtract(&scaled_left, &scaled_right, &magnitude); + negative = normalized_left.negative; + } else if (ok) { + ok = big_uint_subtract(&scaled_right, &scaled_left, &magnitude); + negative = normalized_right.negative; + } + if (ok) { + ok = rational_store(&temporary, &magnitude, &denominator, negative) && + rational_normalize(&temporary); + } + if (ok) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&normalized_left); + av2_dm_rational_destroy(&normalized_right); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&left_magnitude); + big_uint_destroy(&right_magnitude); + big_uint_destroy(&left_denominator); + big_uint_destroy(&right_denominator); + big_uint_destroy(&denominator_gcd); + big_uint_destroy(&left_multiplier); + big_uint_destroy(&right_multiplier); + big_uint_destroy(&denominator); + big_uint_destroy(&scaled_left); + big_uint_destroy(&scaled_right); + big_uint_destroy(&magnitude); + return ok; } bool av2_dm_rational_subtract(const Av2DmRational *left, const Av2DmRational *right, Av2DmRational *result) { + rational_begin_operation(); if (right == NULL) return false; - Av2DmRational negated_right = *right; - if (!wide_is_zero(negated_right.magnitude)) { + Av2DmRational negated_right; + av2_dm_rational_init(&negated_right); + const bool copied = av2_dm_rational_copy(&negated_right, right); + if (copied && !av2_dm_rational_is_zero(&negated_right)) { negated_right.negative = !negated_right.negative; } - return av2_dm_rational_add(left, &negated_right, result); + const bool subtracted = + copied && av2_dm_rational_add(left, &negated_right, result); + av2_dm_rational_destroy(&negated_right); + return subtracted; } bool av2_dm_rational_multiply_u64(const Av2DmRational *value, uint64_t multiplier, Av2DmRational *result) { - if (value == NULL || result == NULL || wide_is_zero(value->denominator)) { - return false; - } - Av2DmRational normalized = *value; - if (!rational_normalize(&normalized)) return false; - const Av2DmUnsignedWide wide_multiplier = wide_from_u64(multiplier); - const Av2DmUnsignedWide divisor = - wide_gcd(wide_multiplier, normalized.denominator); - Av2DmUnsignedWide reduced_multiplier; - Av2DmUnsignedWide remainder; - if (!wide_divide(wide_multiplier, divisor, &reduced_multiplier, &remainder) || - !wide_is_zero(remainder) || - !wide_divide(normalized.denominator, divisor, &normalized.denominator, - &remainder) || - !wide_is_zero(remainder) || - !wide_multiply_checked(normalized.magnitude, reduced_multiplier, - &normalized.magnitude)) { - return false; - } - *result = normalized; - return rational_normalize(result); + rational_begin_operation(); + if (value == NULL || result == NULL) return false; + Av2DmRational normalized; + Av2DmRational temporary; + av2_dm_rational_init(&normalized); + av2_dm_rational_init(&temporary); + Av2DmBigUInt magnitude = { 0 }, denominator = { 0 }, factor = { 0 }; + Av2DmBigUInt divisor = { 0 }, reduced_denominator = { 0 }; + Av2DmBigUInt reduced_factor = { 0 }, product = { 0 }; + bool ok = + rational_normalized_copy(value, &normalized) && + big_uint_from_rational(&normalized, false, &magnitude) && + big_uint_from_rational(&normalized, true, &denominator) && + big_uint_from_u64(&factor, multiplier) && + big_uint_gcd(&factor, &denominator, &divisor) && + big_uint_divide_exact(&denominator, &divisor, &reduced_denominator) && + big_uint_divide_exact(&factor, &divisor, &reduced_factor) && + big_uint_multiply(&magnitude, &reduced_factor, &product) && + rational_store(&temporary, &product, &reduced_denominator, + normalized.negative); + if (ok) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&normalized); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + big_uint_destroy(&factor); + big_uint_destroy(&divisor); + big_uint_destroy(&reduced_denominator); + big_uint_destroy(&reduced_factor); + big_uint_destroy(&product); + return ok; } bool av2_dm_rational_divide_u64(const Av2DmRational *value, uint64_t divisor, Av2DmRational *result) { - if (value == NULL || result == NULL || wide_is_zero(value->denominator) || - divisor == 0) { - return false; - } - Av2DmRational normalized = *value; - if (!rational_normalize(&normalized)) return false; - const Av2DmUnsignedWide wide_divisor = wide_from_u64(divisor); - const Av2DmUnsignedWide common_divisor = - wide_gcd(normalized.magnitude, wide_divisor); - Av2DmUnsignedWide reduced_divisor; - Av2DmUnsignedWide remainder; - if (!wide_divide(normalized.magnitude, common_divisor, &normalized.magnitude, - &remainder) || - !wide_is_zero(remainder) || - !wide_divide(wide_divisor, common_divisor, &reduced_divisor, - &remainder) || - !wide_is_zero(remainder) || - !wide_multiply_checked(normalized.denominator, reduced_divisor, - &normalized.denominator)) { - return false; - } - *result = normalized; - return rational_normalize(result); + rational_begin_operation(); + if (value == NULL || result == NULL || divisor == 0) return false; + Av2DmRational normalized; + Av2DmRational temporary; + av2_dm_rational_init(&normalized); + av2_dm_rational_init(&temporary); + Av2DmBigUInt magnitude = { 0 }, denominator = { 0 }, factor = { 0 }; + Av2DmBigUInt common = { 0 }, reduced_magnitude = { 0 }; + Av2DmBigUInt reduced_factor = { 0 }, product = { 0 }; + bool ok = rational_normalized_copy(value, &normalized) && + big_uint_from_rational(&normalized, false, &magnitude) && + big_uint_from_rational(&normalized, true, &denominator) && + big_uint_from_u64(&factor, divisor) && + big_uint_gcd(&magnitude, &factor, &common) && + big_uint_divide_exact(&magnitude, &common, &reduced_magnitude) && + big_uint_divide_exact(&factor, &common, &reduced_factor) && + big_uint_multiply(&denominator, &reduced_factor, &product) && + rational_store(&temporary, &reduced_magnitude, &product, + normalized.negative); + if (ok) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&normalized); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + big_uint_destroy(&factor); + big_uint_destroy(&common); + big_uint_destroy(&reduced_magnitude); + big_uint_destroy(&reduced_factor); + big_uint_destroy(&product); + return ok; } bool av2_dm_rational_compare(const Av2DmRational *left, const Av2DmRational *right, int *comparison) { - if (left == NULL || right == NULL || comparison == NULL || - wide_is_zero(left->denominator) || wide_is_zero(right->denominator)) { - return false; - } - Av2DmRational normalized_left = *left; - Av2DmRational normalized_right = *right; - if (!rational_normalize(&normalized_left) || - !rational_normalize(&normalized_right)) { - return false; - } - if (wide_is_zero(normalized_left.magnitude) && - wide_is_zero(normalized_right.magnitude)) { + rational_begin_operation(); + if (left == NULL || right == NULL || comparison == NULL) return false; + Av2DmBigUInt left_magnitude = { 0 }, right_magnitude = { 0 }; + Av2DmBigUInt left_denominator = { 0 }, right_denominator = { 0 }; + Av2DmBigUInt left_product = { 0 }, right_product = { 0 }; + bool ok = big_uint_from_rational(left, false, &left_magnitude) && + big_uint_from_rational(right, false, &right_magnitude) && + big_uint_from_rational(left, true, &left_denominator) && + big_uint_from_rational(right, true, &right_denominator) && + !big_uint_is_zero(&left_denominator) && + !big_uint_is_zero(&right_denominator); + if (ok && big_uint_is_zero(&left_magnitude) && + big_uint_is_zero(&right_magnitude)) { *comparison = 0; - return true; - } - if (wide_is_zero(normalized_left.magnitude)) { - *comparison = normalized_right.negative ? 1 : -1; - return true; - } - if (wide_is_zero(normalized_right.magnitude)) { - *comparison = normalized_left.negative ? -1 : 1; - return true; - } - if (normalized_left.negative != normalized_right.negative) { - *comparison = normalized_left.negative ? -1 : 1; - return true; - } - if (!rational_compare_magnitudes(&normalized_left, &normalized_right, - comparison)) { - return false; + } else if (ok && big_uint_is_zero(&left_magnitude)) { + *comparison = right->negative ? 1 : -1; + } else if (ok && big_uint_is_zero(&right_magnitude)) { + *comparison = left->negative ? -1 : 1; + } else if (ok && left->negative != right->negative) { + *comparison = left->negative ? -1 : 1; + } else if (ok) { + ok = + big_uint_multiply(&left_magnitude, &right_denominator, &left_product) && + big_uint_multiply(&right_magnitude, &left_denominator, &right_product); + if (ok) { + *comparison = big_uint_compare(&left_product, &right_product); + if (left->negative) *comparison = -*comparison; + } } - if (normalized_left.negative) *comparison = -*comparison; - return true; + big_uint_destroy(&left_magnitude); + big_uint_destroy(&right_magnitude); + big_uint_destroy(&left_denominator); + big_uint_destroy(&right_denominator); + big_uint_destroy(&left_product); + big_uint_destroy(&right_product); + return ok; } bool av2_dm_rational_rebase(Av2DmRational *values, uint32_t value_count, const Av2DmRational *origin) { - if ((values == NULL && value_count != 0) || origin == NULL) return false; - Av2DmRational fixed_origin = *origin; - if (!rational_normalize(&fixed_origin)) return false; - - // Preflight every subtraction so arithmetic failure cannot leave the array - // containing a mixture of old and new time origins. - for (uint32_t i = 0; i < value_count; ++i) { - Av2DmRational rebased; - if (!av2_dm_rational_subtract(&values[i], &fixed_origin, &rebased)) { - return false; + rational_begin_operation(); + if ((values == NULL && value_count != 0) || origin == NULL || + (uint64_t)value_count > SIZE_MAX / sizeof(*values)) { + return false; + } + Av2DmRational fixed_origin; + av2_dm_rational_init(&fixed_origin); + Av2DmRational *rebased = NULL; + bool ok = av2_dm_rational_copy(&fixed_origin, origin); + if (ok && value_count != 0) { + rebased = rational_allocate((size_t)value_count * sizeof(*rebased)); + ok = rebased != NULL; + if (ok) { + memset(rebased, 0, (size_t)value_count * sizeof(*rebased)); + for (uint32_t i = 0; i < value_count; ++i) { + av2_dm_rational_init(&rebased[i]); + } } } - for (uint32_t i = 0; i < value_count; ++i) { - Av2DmRational rebased; - if (!av2_dm_rational_subtract(&values[i], &fixed_origin, &rebased)) { - return false; + for (uint32_t i = 0; ok && i < value_count; ++i) { + ok = av2_dm_rational_subtract(&values[i], &fixed_origin, &rebased[i]); + } + if (ok) { + for (uint32_t i = 0; i < value_count; ++i) { + av2_dm_rational_move(&values[i], &rebased[i]); } - values[i] = rebased; } - return true; + if (rebased != NULL) { + for (uint32_t i = 0; i < value_count; ++i) { + av2_dm_rational_destroy(&rebased[i]); + } + } + rational_deallocate(rebased); + av2_dm_rational_destroy(&fixed_origin); + return ok; } bool av2_dm_rational_is_zero(const Av2DmRational *value) { - return value != NULL && !wide_is_zero(value->denominator) && - wide_is_zero(value->magnitude); + if (value == NULL) return false; + const uint64_t *magnitude; + const uint64_t *denominator; + uint32_t magnitude_count; + uint32_t denominator_count; + return rational_component_view(value, false, &magnitude, &magnitude_count) && + rational_component_view(value, true, &denominator, + &denominator_count) && + denominator_count != 0 && magnitude_count == 0; } static void buffer_reset(Av2DmBuffer *buffer) { + av2_dm_rational_destroy(&buffer->presentation_time); + av2_dm_rational_destroy(&buffer->decode_completion_time); + av2_dm_rational_destroy(&buffer->disp_ct); memset(buffer, 0, sizeof(*buffer)); buffer->display_index = -1; - av2_dm_rational_make(0, 1, &buffer->presentation_time); - av2_dm_rational_make(0, 1, &buffer->decode_completion_time); -} - -static bool valid_physical_buffer_index(const Av2DmBufferPool *pool, - uint32_t buffer_index) { - return pool != NULL && buffer_index < AV2_DM_MAX_BUFFER_POOL_SIZE; + av2_dm_rational_init(&buffer->presentation_time); + av2_dm_rational_init(&buffer->decode_completion_time); + av2_dm_rational_init(&buffer->disp_ct); } static bool valid_active_buffer_index(const Av2DmBufferPool *pool, @@ -560,12 +888,17 @@ static bool valid_active_buffer_index(const Av2DmBufferPool *pool, return pool != NULL && buffer_index < pool->pool_size; } +void av2_dm_buffer_pool_init(Av2DmBufferPool *pool) { + if (pool != NULL) memset(pool, 0, sizeof(*pool)); +} + bool av2_dm_buffer_pool_initialize(Av2DmBufferPool *pool, uint32_t num_ref_frames) { if (pool == NULL || num_ref_frames == 0 || num_ref_frames > AV2_DM_MAX_REF_FRAMES) { return false; } + if (pool->initialized) av2_dm_buffer_pool_destroy(pool); memset(pool, 0, sizeof(*pool)); pool->num_ref_frames = num_ref_frames; pool->pool_size = num_ref_frames + 2; @@ -573,6 +906,56 @@ bool av2_dm_buffer_pool_initialize(Av2DmBufferPool *pool, for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { buffer_reset(&pool->buffers[i]); } + pool->initialized = true; + return true; +} + +void av2_dm_buffer_pool_destroy(Av2DmBufferPool *pool) { + if (pool == NULL || !pool->initialized) return; + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + av2_dm_rational_destroy(&pool->buffers[i].presentation_time); + av2_dm_rational_destroy(&pool->buffers[i].decode_completion_time); + av2_dm_rational_destroy(&pool->buffers[i].disp_ct); + } + memset(pool, 0, sizeof(*pool)); +} + +static bool buffer_copy(Av2DmBuffer *destination, const Av2DmBuffer *source) { + Av2DmBuffer temporary = *source; + av2_dm_rational_init(&temporary.presentation_time); + av2_dm_rational_init(&temporary.decode_completion_time); + av2_dm_rational_init(&temporary.disp_ct); + if (!av2_dm_rational_copy(&temporary.presentation_time, + &source->presentation_time) || + !av2_dm_rational_copy(&temporary.decode_completion_time, + &source->decode_completion_time) || + !av2_dm_rational_copy(&temporary.disp_ct, &source->disp_ct)) { + av2_dm_rational_destroy(&temporary.presentation_time); + av2_dm_rational_destroy(&temporary.decode_completion_time); + av2_dm_rational_destroy(&temporary.disp_ct); + return false; + } + buffer_reset(destination); + *destination = temporary; + return true; +} + +static bool buffer_pool_copy(Av2DmBufferPool *destination, + const Av2DmBufferPool *source) { + Av2DmBufferPool temporary = { 0 }; + if (!source->initialized || + !av2_dm_buffer_pool_initialize(&temporary, source->num_ref_frames)) { + return false; + } + memcpy(temporary.vbi, source->vbi, sizeof(temporary.vbi)); + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + if (!buffer_copy(&temporary.buffers[i], &source->buffers[i])) { + av2_dm_buffer_pool_destroy(&temporary); + return false; + } + } + av2_dm_buffer_pool_destroy(destination); + *destination = temporary; return true; } @@ -588,7 +971,7 @@ int32_t av2_dm_buffer_pool_get_free_buffer(const Av2DmBufferPool *pool) { } bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_physical_buffer_index(pool, buffer_index)) return false; + if (!valid_active_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->decoder_ref_count != 0 || buffer->player_ref_count != 0) { return false; @@ -599,7 +982,7 @@ bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index) { bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_physical_buffer_index(pool, buffer_index)) return false; + if (!valid_active_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->decoder_ref_count == UINT32_MAX) return false; ++buffer->decoder_ref_count; @@ -608,7 +991,7 @@ bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_physical_buffer_index(pool, buffer_index)) return false; + if (!valid_active_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->decoder_ref_count == 0) return false; --buffer->decoder_ref_count; @@ -620,7 +1003,7 @@ bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_add_player_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_physical_buffer_index(pool, buffer_index)) return false; + if (!valid_active_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->player_ref_count == UINT32_MAX) return false; ++buffer->player_ref_count; @@ -629,7 +1012,7 @@ bool av2_dm_buffer_pool_add_player_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, uint32_t buffer_index) { - if (!valid_physical_buffer_index(pool, buffer_index)) return false; + if (!valid_active_buffer_index(pool, buffer_index)) return false; Av2DmBuffer *const buffer = &pool->buffers[buffer_index]; if (buffer->player_ref_count == 0) return false; --buffer->player_ref_count; @@ -641,10 +1024,9 @@ bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, bool av2_dm_buffer_pool_set_vbi(Av2DmBufferPool *pool, uint32_t ref_index, int32_t buffer_index) { - if (pool == NULL || ref_index >= AV2_DM_MAX_REF_FRAMES || buffer_index < -1 || + if (pool == NULL || ref_index >= pool->num_ref_frames || buffer_index < -1 || (buffer_index >= 0 && - (ref_index >= pool->num_ref_frames || - !valid_active_buffer_index(pool, (uint32_t)buffer_index)))) { + !valid_active_buffer_index(pool, (uint32_t)buffer_index))) { return false; } const int32_t old_buffer_index = pool->vbi[ref_index]; @@ -668,7 +1050,7 @@ bool av2_dm_buffer_pool_set_vbi(Av2DmBufferPool *pool, uint32_t ref_index, uint32_t av2_dm_buffer_pool_frames_in_use(const Av2DmBufferPool *pool) { if (pool == NULL) return 0; uint32_t frames_in_use = 0; - for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + for (uint32_t i = 0; i < pool->pool_size; ++i) { if (pool->buffers[i].decoder_ref_count != 0 || pool->buffers[i].player_ref_count != 0) { ++frames_in_use; @@ -684,8 +1066,10 @@ typedef struct Av2DmDfgRecord { uint64_t coded_bits; uint64_t decode_order; uint64_t rap_epoch; - uint64_t smoothing_epoch; Av2DmLevelLimits limits; + Av2DmRational buffer_size_at_last_arrival; + Av2DmRational buffer_size_before_removal; + Av2DmRational buffer_size_after_removal; uint32_t tier; Av2DmMode mode; Av2DmRational first_arrival; @@ -699,6 +1083,9 @@ typedef struct Av2DmDfgRecord { bool count_frame_header; bool decode_count_two; bool coded_as_closed_loop_key; + bool first_dfg_of_cvs; + bool still_picture; + bool buffer_size_decreases_after_removal; bool smoothing_overflow_reported; uint64_t luma_samples; uint32_t num_tiles; @@ -710,6 +1097,7 @@ typedef struct Av2DmDfgRecord { typedef struct Av2DmTuRecord { uint64_t temporal_unit_index; uint64_t event_index; + uint64_t cvs_number; uint64_t output_luma_samples; uint32_t output_frames; uint32_t frame_headers; @@ -717,12 +1105,19 @@ typedef struct Av2DmTuRecord { bool header_window_checked; bool header_rate_reported; bool tile_header_rate_reported; + bool maximum_tile_area_finalized; bool output_time_valid; bool presentation_time_valid; bool prior_presentation_interval_checked; + bool still_picture; Av2DmRational output_time; Av2DmRational presentation_time; uint64_t header_window_headers; + uint64_t maximum_tile_area; + Av2DmLevelLimits limits; + uint32_t tier; + uint32_t max_frame_width; + uint32_t max_frame_height; } Av2DmTuRecord; typedef struct Av2DmLane { @@ -757,6 +1152,28 @@ typedef struct Av2DmResolvedParameters { Av2DmRational disp_ct; } Av2DmResolvedParameters; +typedef struct Av2DmBufferSizeTransition { + Av2DmRational time; + Av2DmRational size; + uint64_t dfg_number; + bool after_removal; +} Av2DmBufferSizeTransition; + +static void buffer_size_transition_destroy( + Av2DmBufferSizeTransition *transition) { + av2_dm_rational_destroy(&transition->time); + av2_dm_rational_destroy(&transition->size); + memset(transition, 0, sizeof(*transition)); +} + +static void buffer_size_transition_move(Av2DmBufferSizeTransition *destination, + Av2DmBufferSizeTransition *source) { + if (destination == source) return; + buffer_size_transition_destroy(destination); + *destination = *source; + memset(source, 0, sizeof(*source)); +} + struct Av2DecoderModel { Av2DmConfig config; Av2DmLevelLimits limits; @@ -766,6 +1183,12 @@ struct Av2DecoderModel { bool low_delay_mode; Av2DmRational dec_ct; Av2DmRational disp_ct; + Av2DmRational buffer_size_base; + Av2DmRational pending_buffer_size; + int pending_buffer_size_change; + Av2DmBufferSizeTransition *buffer_size_transitions; + uint32_t buffer_size_transition_count; + uint32_t buffer_size_transition_capacity; Av2DmLane lane; Av2DmLane resource_lane; Av2DmResult result; @@ -777,6 +1200,7 @@ struct Av2DecoderModel { uint32_t dfg_count; uint32_t dfg_capacity; uint64_t dfg_number; + uint64_t cvs_number; bool previous_dfg_valid; Av2DmDfgRecord previous_dfg; Av2DmTuRecord *tus; @@ -786,8 +1210,6 @@ struct Av2DecoderModel { uint64_t frame_number; uint64_t shown_frame_number; uint64_t rap_epoch; - uint64_t smoothing_epoch; - bool smoothing_epoch_prepared; bool most_recent_rap_removal_valid; Av2DmRational most_recent_rap_scheduled_removal; bool previous_output_order_valid; @@ -818,10 +1240,12 @@ struct Av2DecoderModel { bool retired_header_summary_reported; uint64_t retired_max_frame_headers; uint64_t retired_header_event_index; + uint64_t retired_header_limit; bool retired_unresolved_tu; Av2DmPendingOutputWitness pending_display_late; Av2DmPendingOutputWitness pending_decode_deadline; uint64_t maximum_tile_area; + bool tile_cvs_finalized; bool any_decode_count_two_requires_reserved_buffer; bool max_reference_frames_checked; bool max_reference_frames_reserved; @@ -832,10 +1256,208 @@ struct Av2DecoderModel { Av2DmStorageStats storage; }; +void av2_dm_level_limits_init(Av2DmLevelLimits *limits) { + if (limits == NULL) return; + memset(limits, 0, sizeof(*limits)); + av2_dm_rational_init(&limits->bit_rate); + av2_dm_rational_init(&limits->buffer_size); +} + +void av2_dm_level_limits_destroy(Av2DmLevelLimits *limits) { + if (limits == NULL) return; + av2_dm_rational_destroy(&limits->bit_rate); + av2_dm_rational_destroy(&limits->buffer_size); + memset(limits, 0, sizeof(*limits)); +} + +bool av2_dm_level_limits_copy(Av2DmLevelLimits *destination, + const Av2DmLevelLimits *source) { + Av2DmLevelLimits temporary = *source; + av2_dm_rational_init(&temporary.bit_rate); + av2_dm_rational_init(&temporary.buffer_size); + if (!av2_dm_rational_copy(&temporary.bit_rate, &source->bit_rate) || + !av2_dm_rational_copy(&temporary.buffer_size, &source->buffer_size)) { + av2_dm_level_limits_destroy(&temporary); + return false; + } + av2_dm_level_limits_destroy(destination); + *destination = temporary; + return true; +} + +void av2_dm_config_destroy(Av2DmConfig *config) { + if (config == NULL) return; + av2_dm_level_limits_destroy(&config->level_limits); + memset(config, 0, sizeof(*config)); +} + +void av2_dm_config_init(Av2DmConfig *config) { + if (config == NULL) return; + memset(config, 0, sizeof(*config)); + av2_dm_level_limits_init(&config->level_limits); +} + +bool av2_dm_config_copy(Av2DmConfig *destination, const Av2DmConfig *source) { + if (destination == NULL || source == NULL) return false; + Av2DmConfig temporary = *source; + memset(&temporary.level_limits.bit_rate, 0, + sizeof(temporary.level_limits.bit_rate)); + memset(&temporary.level_limits.buffer_size, 0, + sizeof(temporary.level_limits.buffer_size)); + av2_dm_rational_init(&temporary.level_limits.bit_rate); + av2_dm_rational_init(&temporary.level_limits.buffer_size); + if (source->level_limits_present && + !av2_dm_level_limits_copy(&temporary.level_limits, + &source->level_limits)) { + av2_dm_config_destroy(&temporary); + return false; + } + av2_dm_config_destroy(destination); + *destination = temporary; + return true; +} + +static void dfg_record_destroy(Av2DmDfgRecord *dfg) { + av2_dm_level_limits_destroy(&dfg->limits); + av2_dm_rational_destroy(&dfg->buffer_size_at_last_arrival); + av2_dm_rational_destroy(&dfg->buffer_size_before_removal); + av2_dm_rational_destroy(&dfg->buffer_size_after_removal); + av2_dm_rational_destroy(&dfg->first_arrival); + av2_dm_rational_destroy(&dfg->last_arrival); + av2_dm_rational_destroy(&dfg->scheduled_removal); + av2_dm_rational_destroy(&dfg->removal); + av2_dm_rational_destroy(&dfg->decode_time); + av2_dm_rational_destroy(&dfg->decode_completion); + memset(dfg, 0, sizeof(*dfg)); +} + +static void dfg_record_init(Av2DmDfgRecord *dfg) { + memset(dfg, 0, sizeof(*dfg)); + av2_dm_rational_init(&dfg->limits.bit_rate); + av2_dm_rational_init(&dfg->limits.buffer_size); + av2_dm_rational_init(&dfg->buffer_size_at_last_arrival); + av2_dm_rational_init(&dfg->buffer_size_before_removal); + av2_dm_rational_init(&dfg->buffer_size_after_removal); + av2_dm_rational_init(&dfg->first_arrival); + av2_dm_rational_init(&dfg->last_arrival); + av2_dm_rational_init(&dfg->scheduled_removal); + av2_dm_rational_init(&dfg->removal); + av2_dm_rational_init(&dfg->decode_time); + av2_dm_rational_init(&dfg->decode_completion); +} + +static bool dfg_record_copy(Av2DmDfgRecord *destination, + const Av2DmDfgRecord *source) { + Av2DmDfgRecord temporary = *source; + memset(&temporary.limits.bit_rate, 0, sizeof(temporary.limits.bit_rate)); + memset(&temporary.limits.buffer_size, 0, + sizeof(temporary.limits.buffer_size)); + memset(&temporary.buffer_size_at_last_arrival, 0, + sizeof(temporary.buffer_size_at_last_arrival)); + memset(&temporary.buffer_size_before_removal, 0, + sizeof(temporary.buffer_size_before_removal)); + memset(&temporary.buffer_size_after_removal, 0, + sizeof(temporary.buffer_size_after_removal)); + memset(&temporary.first_arrival, 0, sizeof(temporary.first_arrival)); + memset(&temporary.last_arrival, 0, sizeof(temporary.last_arrival)); + memset(&temporary.scheduled_removal, 0, sizeof(temporary.scheduled_removal)); + memset(&temporary.removal, 0, sizeof(temporary.removal)); + memset(&temporary.decode_time, 0, sizeof(temporary.decode_time)); + memset(&temporary.decode_completion, 0, sizeof(temporary.decode_completion)); + const bool copied = + av2_dm_level_limits_copy(&temporary.limits, &source->limits) && + av2_dm_rational_copy(&temporary.buffer_size_at_last_arrival, + &source->buffer_size_at_last_arrival) && + av2_dm_rational_copy(&temporary.buffer_size_before_removal, + &source->buffer_size_before_removal) && + av2_dm_rational_copy(&temporary.buffer_size_after_removal, + &source->buffer_size_after_removal) && + av2_dm_rational_copy(&temporary.first_arrival, &source->first_arrival) && + av2_dm_rational_copy(&temporary.last_arrival, &source->last_arrival) && + av2_dm_rational_copy(&temporary.scheduled_removal, + &source->scheduled_removal) && + av2_dm_rational_copy(&temporary.removal, &source->removal) && + av2_dm_rational_copy(&temporary.decode_time, &source->decode_time) && + av2_dm_rational_copy(&temporary.decode_completion, + &source->decode_completion); + if (!copied) { + dfg_record_destroy(&temporary); + return false; + } + dfg_record_destroy(destination); + *destination = temporary; + return true; +} + +static void tu_record_destroy(Av2DmTuRecord *tu) { + av2_dm_rational_destroy(&tu->output_time); + av2_dm_rational_destroy(&tu->presentation_time); + av2_dm_level_limits_destroy(&tu->limits); + memset(tu, 0, sizeof(*tu)); +} + +static void tu_record_init(Av2DmTuRecord *tu) { + memset(tu, 0, sizeof(*tu)); + av2_dm_rational_init(&tu->output_time); + av2_dm_rational_init(&tu->presentation_time); + av2_dm_rational_init(&tu->limits.bit_rate); + av2_dm_rational_init(&tu->limits.buffer_size); +} + +static bool tu_record_copy(Av2DmTuRecord *destination, + const Av2DmTuRecord *source) { + Av2DmTuRecord temporary = *source; + av2_dm_rational_init(&temporary.output_time); + av2_dm_rational_init(&temporary.presentation_time); + av2_dm_level_limits_init(&temporary.limits); + if (!av2_dm_rational_copy(&temporary.output_time, &source->output_time) || + !av2_dm_rational_copy(&temporary.presentation_time, + &source->presentation_time) || + !av2_dm_level_limits_copy(&temporary.limits, &source->limits)) { + tu_record_destroy(&temporary); + return false; + } + tu_record_destroy(destination); + *destination = temporary; + return true; +} + +static void tu_record_move(Av2DmTuRecord *destination, Av2DmTuRecord *source) { + if (destination == source) return; + tu_record_destroy(destination); + *destination = *source; + memset(source, 0, sizeof(*source)); +} + +static void lane_destroy(Av2DmLane *lane) { + av2_dm_buffer_pool_destroy(&lane->pool); + av2_dm_rational_destroy(&lane->time); + av2_dm_rational_destroy(&lane->initial_presentation_delay); + memset(lane, 0, sizeof(*lane)); +} + +static bool lane_copy(Av2DmLane *destination, const Av2DmLane *source) { + Av2DmLane temporary = { 0 }; + temporary.current_buffer_index = source->current_buffer_index; + temporary.initial_presentation_delay_known = + source->initial_presentation_delay_known; + if (!buffer_pool_copy(&temporary.pool, &source->pool) || + !av2_dm_rational_copy(&temporary.time, &source->time) || + !av2_dm_rational_copy(&temporary.initial_presentation_delay, + &source->initial_presentation_delay)) { + lane_destroy(&temporary); + return false; + } + lane_destroy(destination); + *destination = temporary; + return true; +} + static bool invalidate_lane_reference_buffers(Av2DmLane *lane, uint32_t ref_valid_mask, bool closed_loop_key); static void check_smoothing_buffer_overflow(Av2DecoderModel *model, + const Av2DmRational *frontier, uint64_t proving_event_index); static void retire_closed_smoothing_records(Av2DecoderModel *model, const Av2DmRational *frontier); @@ -847,11 +1469,18 @@ static void check_header_rate_windows(Av2DecoderModel *model, static void update_storage_stats(Av2DecoderModel *model); static void check_retired_tile_header_summary(Av2DecoderModel *model, uint64_t proving_event_index); +static void finalize_tile_cvs(Av2DecoderModel *model, + uint64_t proving_event_index); static void retire_unresolvable_tus(Av2DecoderModel *model); static void restart_tu_history(Av2DecoderModel *model, uint64_t temporal_unit_index); static bool update_latest_timed_tu(Av2DecoderModel *model, const Av2DmRational *output_time); +static bool set_lane_initial_presentation_delay(Av2DecoderModel *model, + Av2DmLane *lane, + bool primary_lane, + bool end_of_bitstream, + uint64_t proving_event_index); static bool rational_zero(Av2DmRational *value) { return av2_dm_rational_make(0, 1, value); @@ -859,50 +1488,83 @@ static bool rational_zero(Av2DmRational *value) { static bool rational_from_product(uint64_t left, uint64_t right, Av2DmRational *value) { - Av2DmUnsignedWide product; - if (!wide_multiply_checked(wide_from_u64(left), wide_from_u64(right), - &product)) { - return false; - } - return av2_dm_rational_make_wide(product, 1, false, value); + Av2DmRational factor = { 0 }; + const bool made = av2_dm_rational_make(left, 1, &factor) && + av2_dm_rational_multiply_u64(&factor, right, value); + av2_dm_rational_destroy(&factor); + return made; } static bool rational_multiply(const Av2DmRational *left, const Av2DmRational *right, Av2DmRational *result) { - if (left == NULL || right == NULL || result == NULL || - wide_is_zero(left->denominator) || wide_is_zero(right->denominator)) { - return false; - } - Av2DmRational a = *left; - Av2DmRational b = *right; - if (!rational_normalize(&a) || !rational_normalize(&b)) return false; - const Av2DmUnsignedWide cross_a = wide_gcd(a.magnitude, b.denominator); - const Av2DmUnsignedWide cross_b = wide_gcd(b.magnitude, a.denominator); - Av2DmUnsignedWide remainder; - if (!wide_divide(a.magnitude, cross_a, &a.magnitude, &remainder) || - !wide_is_zero(remainder) || - !wide_divide(b.denominator, cross_a, &b.denominator, &remainder) || - !wide_is_zero(remainder) || - !wide_divide(b.magnitude, cross_b, &b.magnitude, &remainder) || - !wide_is_zero(remainder) || - !wide_divide(a.denominator, cross_b, &a.denominator, &remainder) || - !wide_is_zero(remainder) || - !wide_multiply_checked(a.magnitude, b.magnitude, &result->magnitude) || - !wide_multiply_checked(a.denominator, b.denominator, - &result->denominator)) { - return false; - } - result->negative = a.negative != b.negative; - return rational_normalize(result); + rational_begin_operation(); + if (left == NULL || right == NULL || result == NULL) return false; + Av2DmBigUInt left_magnitude = { 0 }, right_magnitude = { 0 }; + Av2DmBigUInt left_denominator = { 0 }, right_denominator = { 0 }; + Av2DmBigUInt magnitude = { 0 }, denominator = { 0 }; + Av2DmRational temporary = { 0 }; + bool ok = + big_uint_from_rational(left, false, &left_magnitude) && + big_uint_from_rational(right, false, &right_magnitude) && + big_uint_from_rational(left, true, &left_denominator) && + big_uint_from_rational(right, true, &right_denominator) && + !big_uint_is_zero(&left_denominator) && + !big_uint_is_zero(&right_denominator) && + big_uint_multiply(&left_magnitude, &right_magnitude, &magnitude) && + big_uint_multiply(&left_denominator, &right_denominator, &denominator) && + rational_store(&temporary, &magnitude, &denominator, + left->negative != right->negative) && + rational_normalize(&temporary); + if (ok) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&left_magnitude); + big_uint_destroy(&right_magnitude); + big_uint_destroy(&left_denominator); + big_uint_destroy(&right_denominator); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + return ok; } static bool rational_max(const Av2DmRational *left, const Av2DmRational *right, Av2DmRational *result) { int comparison; if (!av2_dm_rational_compare(left, right, &comparison)) return false; - *result = comparison >= 0 ? *left : *right; - return true; + return av2_dm_rational_copy(result, comparison >= 0 ? left : right); +} + +static bool rational_reciprocal(const Av2DmRational *value, + Av2DmRational *result) { + rational_begin_operation(); + Av2DmBigUInt magnitude = { 0 }; + Av2DmBigUInt denominator = { 0 }; + Av2DmRational temporary = { 0 }; + const bool made = + big_uint_from_rational(value, false, &magnitude) && + !big_uint_is_zero(&magnitude) && + big_uint_from_rational(value, true, &denominator) && + rational_store(&temporary, &denominator, &magnitude, value->negative); + if (made) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + return made; +} + +static bool rational_to_u64(const Av2DmRational *value, uint64_t *result) { + rational_begin_operation(); + Av2DmBigUInt magnitude = { 0 }; + Av2DmBigUInt denominator = { 0 }; + const bool converted = !value->negative && + big_uint_from_rational(value, false, &magnitude) && + big_uint_from_rational(value, true, &denominator) && + denominator.count == 1 && denominator.limbs[0] == 1 && + magnitude.count <= 1; + if (converted) *result = magnitude.count == 0 ? 0 : magnitude.limbs[0]; + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + return converted; } static bool rational_less(const Av2DmRational *left, const Av2DmRational *right, @@ -923,12 +1585,13 @@ static bool rational_greater(const Av2DmRational *left, static bool grow_array(void **array, uint32_t *capacity, uint32_t count, size_t element_size) { + internal_allocation_failed = false; if (count < *capacity) return true; const uint32_t new_capacity = *capacity == 0 ? 16 : *capacity * 2; if (new_capacity < *capacity || element_size > SIZE_MAX / new_capacity) { return false; } - void *const replacement = avm_calloc(new_capacity, element_size); + void *const replacement = internal_calloc(new_capacity, element_size); if (replacement == NULL) return false; if (*array != NULL) { memcpy(replacement, *array, (size_t)count * element_size); @@ -956,53 +1619,60 @@ bool av2_dm_get_level_limits(uint32_t level_idx, uint32_t tier, AV2ProfileLevelFactors factors; if (!av2_get_profile_level_factors((int)profile, &factors)) return false; - memset(limits, 0, sizeof(*limits)); - limits->max_picture_size = (uint64_t)row->max_picture_size; - limits->max_horizontal_size = (uint32_t)row->max_h_size; - limits->max_vertical_size = (uint32_t)row->max_v_size; - limits->max_display_rate = (uint64_t)row->max_display_rate; - limits->max_decode_rate = (uint64_t)row->max_decode_rate; - limits->max_header_rate = (uint32_t)row->max_header_rate; - limits->max_tiles = (uint32_t)row->max_tiles; - limits->max_tile_columns = (uint32_t)row->max_tile_cols; - limits->max_tile_width = + Av2DmLevelLimits computed; + av2_dm_level_limits_init(&computed); + computed.max_picture_size = (uint64_t)row->max_picture_size; + computed.max_horizontal_size = (uint32_t)row->max_h_size; + computed.max_vertical_size = (uint32_t)row->max_v_size; + computed.max_display_rate = (uint64_t)row->max_display_rate; + computed.max_decode_rate = (uint64_t)row->max_decode_rate; + computed.max_header_rate = (uint32_t)row->max_header_rate; + computed.max_tiles = (uint32_t)row->max_tiles; + computed.max_tile_columns = (uint32_t)row->max_tile_cols; + computed.max_tile_width = (uint64_t)av2_tile_width_scaling_factor[tier][level_idx] * MAX_TILE_WIDTH / 4; - limits->max_tile_area = + computed.max_tile_area = (uint64_t)av2_tile_area_scaling_factor[tier][level_idx] * MAX_TILE_AREA / 4; - limits->max_tile_size_header_rate_product = + computed.max_tile_size_header_rate_product = (uint64_t)av2_tile_area_scaling_factor[tier][level_idx] * MAX_TILE_SIZE_HEADER_RATE_PRODUCT / 4; - limits->picture_size_profile_factor = factors.picture_size_profile_factor; - limits->min_compression_basis = compression; + computed.picture_size_profile_factor = factors.picture_size_profile_factor; + computed.min_compression_basis = compression; - Av2DmRational base_rate; - Av2DmRational profile_factor; + Av2DmRational base_rate = { 0 }; + Av2DmRational profile_factor = { 0 }; if (!rational_from_product(kbps, 1000, &base_rate) || !av2_dm_rational_make(factors.bitrate_factor_numerator, factors.bitrate_factor_denominator, &profile_factor) || - !rational_multiply(&base_rate, &profile_factor, &limits->bit_rate)) { + !rational_multiply(&base_rate, &profile_factor, &computed.bit_rate) || + !av2_dm_rational_copy(&computed.buffer_size, &computed.bit_rate)) { + av2_dm_rational_destroy(&base_rate); + av2_dm_rational_destroy(&profile_factor); + av2_dm_level_limits_destroy(&computed); return false; } // Annex A defines MaxBufferSize as one second of MaxBitrate. - limits->buffer_size = limits->bit_rate; + av2_dm_rational_destroy(&base_rate); + av2_dm_rational_destroy(&profile_factor); + av2_dm_level_limits_destroy(limits); + *limits = computed; return true; } static bool scaled_integer(uint64_t value, uint32_t scale_numerator, uint32_t scale_denominator, uint64_t *scaled) { - Av2DmRational rational; + Av2DmRational rational = { 0 }; if (!av2_dm_rational_make(value, 1, &rational) || !av2_dm_rational_multiply_u64(&rational, scale_denominator, &rational) || !av2_dm_rational_divide_u64(&rational, scale_numerator, &rational) || - rational.negative || !wide_equals_u64(rational.denominator, 1) || - rational.magnitude.limbs[1] != 0 || rational.magnitude.limbs[2] != 0 || - rational.magnitude.limbs[3] != 0) { + !rational_to_u64(&rational, scaled)) { + av2_dm_rational_destroy(&rational); return false; } - *scaled = rational.magnitude.limbs[0]; + av2_dm_rational_destroy(&rational); return true; } @@ -1020,10 +1690,15 @@ bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, scale_denominator, &row)) { return false; } - Av2DmLevelLimits multistream; + Av2DmLevelLimits multistream = { 0 }; if (!av2_dm_get_level_limits(level_idx, tier, profile, &multistream)) { return false; } + Av2DmLevelLimits updated = { 0 }; + if (!av2_dm_level_limits_copy(&updated, limits)) { + av2_dm_level_limits_destroy(&multistream); + return false; + } uint64_t scaled_display; uint64_t scaled_decode; if (!scaled_integer(multistream.max_display_rate, scale_numerator, @@ -1038,6 +1713,8 @@ bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, &multistream.buffer_size) || !av2_dm_rational_divide_u64(&multistream.buffer_size, scale_numerator, &multistream.buffer_size)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); return false; } multistream.max_picture_size = @@ -1053,8 +1730,8 @@ bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, #define MIN_LIMIT(member) \ do { \ - if (multistream.member < limits->member) \ - limits->member = multistream.member; \ + if (multistream.member < updated.member) \ + updated.member = multistream.member; \ } while (0) MIN_LIMIT(max_picture_size); MIN_LIMIT(max_horizontal_size); @@ -1066,19 +1743,36 @@ bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, MIN_LIMIT(max_tile_columns); #undef MIN_LIMIT int comparison; - if (!av2_dm_rational_compare(&multistream.bit_rate, &limits->bit_rate, + if (!av2_dm_rational_compare(&multistream.bit_rate, &updated.bit_rate, &comparison)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); return false; } - if (comparison < 0) limits->bit_rate = multistream.bit_rate; - if (!av2_dm_rational_compare(&multistream.buffer_size, &limits->buffer_size, + if (comparison < 0 && + !av2_dm_rational_copy(&updated.bit_rate, &multistream.bit_rate)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); + return false; + } + if (!av2_dm_rational_compare(&multistream.buffer_size, &updated.buffer_size, &comparison)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); + return false; + } + if (comparison < 0 && + !av2_dm_rational_copy(&updated.buffer_size, &multistream.buffer_size)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); return false; } - if (comparison < 0) limits->buffer_size = multistream.buffer_size; - if (multistream.min_compression_basis > limits->min_compression_basis) { - limits->min_compression_basis = multistream.min_compression_basis; + if (multistream.min_compression_basis > updated.min_compression_basis) { + updated.min_compression_basis = multistream.min_compression_basis; } + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(limits); + *limits = updated; return true; } @@ -1087,17 +1781,28 @@ static void update_result_status(Av2DecoderModel *model) { model->result.status = AV2_DM_RESULT_NOT_APPLICABLE; } else if (model->result.violations != 0) { model->result.status = AV2_DM_RESULT_NON_CONFORMANT; - } else if (model->result.arithmetic_failed || + } else if (model->result.allocation_failed || + model->result.arithmetic_failed || model->result.missing_required_input || model->result.applicability == AV2_DM_MISSING_REQUIRED_INPUT) { model->result.status = AV2_DM_RESULT_INDETERMINATE; } else { - model->result.status = AV2_DM_RESULT_CONFORMANT; + model->result.status = AV2_DM_RESULT_CONFORMANT; + } +} + +static void arithmetic_failure(Av2DecoderModel *model) { + if (av2_dm_last_failure_was_allocation()) { + model->result.allocation_failed = true; + } else { + model->result.arithmetic_failed = true; } + model->processing_stopped = true; + update_result_status(model); } -static void arithmetic_failure(Av2DecoderModel *model) { - model->result.arithmetic_failed = true; +static void allocation_failure(Av2DecoderModel *model) { + model->result.allocation_failed = true; model->processing_stopped = true; update_result_status(model); } @@ -1156,6 +1861,104 @@ static bool violation_seen(const Av2DecoderModel *model, model->violation_seen[code]; } +static void violation_detail_destroy(Av2DmViolationDetail *detail) { + switch (detail->kind) { + case AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY: + av2_dm_rational_destroy( + &detail->value.delay_consistency.ceil_time_delta_ticks); + break; + case AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME: + av2_dm_rational_destroy( + &detail->value.minimum_decode_time.frame_decode_time); + av2_dm_rational_destroy( + &detail->value.minimum_decode_time.one_header_time); + break; + case AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL: + av2_dm_rational_destroy(&detail->value.frame_interval); + break; + default: break; + } + memset(detail, 0, sizeof(*detail)); +} + +static bool violation_detail_copy(Av2DmViolationDetail *destination, + const Av2DmViolationDetail *source) { + Av2DmViolationDetail temporary = *source; + bool copied = true; + switch (source->kind) { + case AV2_DM_VIOLATION_DETAIL_DELAY_CONSISTENCY: + memset(&temporary.value.delay_consistency.ceil_time_delta_ticks, 0, + sizeof(temporary.value.delay_consistency.ceil_time_delta_ticks)); + av2_dm_rational_init( + &temporary.value.delay_consistency.ceil_time_delta_ticks); + copied = !source->value.delay_consistency.ceil_time_delta_present || + av2_dm_rational_copy( + &temporary.value.delay_consistency.ceil_time_delta_ticks, + &source->value.delay_consistency.ceil_time_delta_ticks); + break; + case AV2_DM_VIOLATION_DETAIL_MINIMUM_DECODE_TIME: + memset(&temporary.value.minimum_decode_time.frame_decode_time, 0, + sizeof(temporary.value.minimum_decode_time.frame_decode_time)); + memset(&temporary.value.minimum_decode_time.one_header_time, 0, + sizeof(temporary.value.minimum_decode_time.one_header_time)); + copied = av2_dm_rational_copy( + &temporary.value.minimum_decode_time.frame_decode_time, + &source->value.minimum_decode_time.frame_decode_time) && + av2_dm_rational_copy( + &temporary.value.minimum_decode_time.one_header_time, + &source->value.minimum_decode_time.one_header_time); + break; + case AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL: + memset(&temporary.value.frame_interval, 0, + sizeof(temporary.value.frame_interval)); + copied = av2_dm_rational_copy(&temporary.value.frame_interval, + &source->value.frame_interval); + break; + default: break; + } + if (!copied) { + violation_detail_destroy(&temporary); + return false; + } + violation_detail_destroy(destination); + *destination = temporary; + return true; +} + +void av2_dm_violation_init(Av2DmViolation *violation) { + if (violation == NULL) return; + memset(violation, 0, sizeof(*violation)); + av2_dm_rational_init(&violation->observed); + av2_dm_rational_init(&violation->limit); +} + +void av2_dm_violation_destroy(Av2DmViolation *violation) { + if (violation == NULL) return; + av2_dm_rational_destroy(&violation->observed); + av2_dm_rational_destroy(&violation->limit); + violation_detail_destroy(&violation->detail); + memset(violation, 0, sizeof(*violation)); +} + +bool av2_dm_violation_copy(Av2DmViolation *destination, + const Av2DmViolation *source) { + if (destination == NULL || source == NULL) return false; + if (destination == source) return true; + Av2DmViolation temporary = *source; + memset(&temporary.observed, 0, sizeof(temporary.observed)); + memset(&temporary.limit, 0, sizeof(temporary.limit)); + memset(&temporary.detail, 0, sizeof(temporary.detail)); + if (!av2_dm_rational_copy(&temporary.observed, &source->observed) || + !av2_dm_rational_copy(&temporary.limit, &source->limit) || + !violation_detail_copy(&temporary.detail, &source->detail)) { + av2_dm_violation_destroy(&temporary); + return false; + } + av2_dm_violation_destroy(destination); + *destination = temporary; + return true; +} + static void report_violation_for_affected( Av2DecoderModel *model, Av2DmViolationCode code, uint64_t event_index, Av2DmViolationAffectedKind affected_kind, uint64_t affected_index, @@ -1179,7 +1982,7 @@ static void report_violation_for_affected( } if (model->report != NULL) { Av2DmViolation violation; - memset(&violation, 0, sizeof(violation)); + av2_dm_violation_init(&violation); violation.code = code; violation.scope = model->config.scope; violation.event_index = event_index; @@ -1187,10 +1990,15 @@ static void report_violation_for_affected( violation.affected_index = affected_index; violation.observed_present = observed != NULL; violation.limit_present = limit != NULL; - if (observed != NULL) violation.observed = *observed; - if (limit != NULL) violation.limit = *limit; - if (detail != NULL) violation.detail = *detail; - model->report(model->report_opaque, &violation); + if ((observed == NULL || + av2_dm_rational_copy(&violation.observed, observed)) && + (limit == NULL || av2_dm_rational_copy(&violation.limit, limit)) && + (detail == NULL || violation_detail_copy(&violation.detail, detail))) { + model->report(model->report_opaque, &violation); + } else { + arithmetic_failure(model); + } + av2_dm_violation_destroy(&violation); } if (model->config.stop_after_first_violation) { model->processing_stopped = true; @@ -1207,7 +2015,7 @@ static void report_violation(Av2DecoderModel *model, Av2DmViolationCode code, } static bool lane_initialize(Av2DmLane *lane, uint32_t num_ref_frames) { - memset(lane, 0, sizeof(*lane)); + lane_destroy(lane); lane->current_buffer_index = -1; return av2_dm_buffer_pool_initialize(&lane->pool, num_ref_frames) && rational_zero(&lane->time) && @@ -1232,6 +2040,14 @@ static bool seed_ras_buffers(Av2DecoderModel *model, Av2DmLane *lane) { lane->pool.buffers[buffer_index].generation_valid = true; lane->pool.buffers[buffer_index].generation = seed->generation; } + lane->pool.buffers[buffer_index].equal_picture_interval = + model->config.equal_picture_interval; + lane->pool.buffers[buffer_index].ticks_per_picture = + model->config.ticks_per_picture; + if (!av2_dm_rational_copy(&lane->pool.buffers[buffer_index].disp_ct, + &model->disp_ct)) { + return false; + } if (!av2_dm_buffer_pool_set_vbi(&lane->pool, seed->ref_index, buffer_index)) { return false; @@ -1240,29 +2056,81 @@ static bool seed_ras_buffers(Av2DecoderModel *model, Av2DmLane *lane) { return true; } +static bool rational_representation_valid(const Av2DmRational *value) { + const uint64_t *magnitude; + const uint64_t *denominator; + uint32_t magnitude_count; + uint32_t denominator_count; + return av2_dm_rational_get_component(value, false, &magnitude, + &magnitude_count) && + av2_dm_rational_get_component(value, true, &denominator, + &denominator_count) && + denominator_count != 0 && denominator[denominator_count - 1] != 0; +} + +static bool parameter_inputs_valid(const Av2DmConfig *config) { + if (config->num_ref_frames == 0 || + config->num_ref_frames > AV2_DM_MAX_REF_FRAMES || + (config->mode != AV2_DM_RESOURCE_AVAILABILITY_MODE && + config->mode != AV2_DM_DECODING_SCHEDULE_MODE) || + !config->timing_info_present || config->time_scale == 0 || + config->num_units_in_display_tick == 0 || + (config->mode == AV2_DM_DECODING_SCHEDULE_MODE && + config->num_units_in_decoding_tick == 0) || + (config->equal_picture_interval && config->ticks_per_picture == 0) || + config->initial_display_delay == 0) { + return false; + } + if (config->mode == AV2_DM_DECODING_SCHEDULE_MODE) { + if ((!config->scope.whole_xlayer && + config->operating_point_parameters_present) || + config->sequence_parameters_present) { + // The selected delay source is present. + } else { + return false; + } + } else if (!config->equal_picture_interval) { + return false; + } + if (config->level_limits_present) { + return rational_representation_valid(&config->level_limits.bit_rate) && + rational_representation_valid(&config->level_limits.buffer_size) && + !av2_dm_rational_is_zero(&config->level_limits.bit_rate) && + config->level_limits.max_decode_rate != 0 && + config->level_limits.max_display_rate != 0 && + config->level_limits.max_header_rate != 0 && + config->level_limits.picture_size_profile_factor != 0 && + config->level_limits.min_compression_basis != 0; + } + uint32_t kbps; + uint32_t compression; + AV2ProfileLevelFactors factors; + return av2_get_level_base_bitrate_kbps((int)config->level_idx, + (int)config->tier, &kbps) && + av2_get_level_compression_basis((int)config->level_idx, + (int)config->tier, &compression) && + av2_get_profile_level_factors((int)config->profile, &factors); +} + static bool resolve_parameters(const Av2DmConfig *config, Av2DmResolvedParameters *parameters) { memset(parameters, 0, sizeof(*parameters)); + av2_dm_rational_init(¶meters->limits.bit_rate); + av2_dm_rational_init(¶meters->limits.buffer_size); + av2_dm_rational_init(¶meters->decoder_buffer_delay); + av2_dm_rational_init(¶meters->encoder_buffer_delay); + av2_dm_rational_init(¶meters->dec_ct); + av2_dm_rational_init(¶meters->disp_ct); if (config->level_limits_present) { - parameters->limits = config->level_limits; + if (!av2_dm_level_limits_copy(¶meters->limits, &config->level_limits)) { + return false; + } } else if (!av2_dm_get_level_limits(config->level_idx, config->tier, config->profile, ¶meters->limits)) { return false; } if (!rational_normalize(¶meters->limits.bit_rate) || !rational_normalize(¶meters->limits.buffer_size) || - wide_is_zero(parameters->limits.bit_rate.magnitude) || - parameters->limits.max_decode_rate == 0 || - parameters->limits.max_display_rate == 0 || - parameters->limits.max_header_rate == 0 || - parameters->limits.picture_size_profile_factor == 0 || - parameters->limits.min_compression_basis == 0 || - !config->timing_info_present || config->time_scale == 0 || - config->num_units_in_display_tick == 0 || - (config->mode == AV2_DM_DECODING_SCHEDULE_MODE && - config->num_units_in_decoding_tick == 0) || - (config->equal_picture_interval && config->ticks_per_picture == 0) || - config->initial_display_delay == 0 || !av2_dm_rational_make(config->num_units_in_display_tick, config->time_scale, ¶meters->disp_ct)) { return false; @@ -1287,11 +2155,7 @@ static bool resolve_parameters(const Av2DmConfig *config, decoder_delay = config->sequence_decoder_buffer_delay; encoder_delay = config->sequence_encoder_buffer_delay; parameters->low_delay_mode = config->sequence_low_delay_mode; - } else { - return false; } - } else if (!config->equal_picture_interval) { - return false; } if (!av2_dm_rational_make(decoder_delay, 90000, ¶meters->decoder_buffer_delay) || @@ -1303,25 +2167,90 @@ static bool resolve_parameters(const Av2DmConfig *config, return true; } -static void apply_parameters(Av2DecoderModel *model, const Av2DmConfig *config, +static void resolved_parameters_destroy(Av2DmResolvedParameters *parameters) { + av2_dm_level_limits_destroy(¶meters->limits); + av2_dm_rational_destroy(¶meters->decoder_buffer_delay); + av2_dm_rational_destroy(¶meters->encoder_buffer_delay); + av2_dm_rational_destroy(¶meters->dec_ct); + av2_dm_rational_destroy(¶meters->disp_ct); + memset(parameters, 0, sizeof(*parameters)); +} + +static bool apply_parameters(Av2DecoderModel *model, const Av2DmConfig *config, const Av2DmResolvedParameters *parameters) { - model->config = *config; - model->limits = parameters->limits; - model->decoder_buffer_delay = parameters->decoder_buffer_delay; - model->encoder_buffer_delay = parameters->encoder_buffer_delay; + Av2DmConfig updated_config = { 0 }; + Av2DmLevelLimits updated_limits = { 0 }; + Av2DmRational decoder_buffer_delay = { 0 }; + Av2DmRational encoder_buffer_delay = { 0 }; + Av2DmRational dec_ct = { 0 }; + Av2DmRational disp_ct = { 0 }; + const bool copied = + av2_dm_config_copy(&updated_config, config) && + av2_dm_level_limits_copy(&updated_limits, ¶meters->limits) && + av2_dm_rational_copy(&decoder_buffer_delay, + ¶meters->decoder_buffer_delay) && + av2_dm_rational_copy(&encoder_buffer_delay, + ¶meters->encoder_buffer_delay) && + av2_dm_rational_copy(&dec_ct, ¶meters->dec_ct) && + av2_dm_rational_copy(&disp_ct, ¶meters->disp_ct); + if (!copied) { + av2_dm_config_destroy(&updated_config); + av2_dm_level_limits_destroy(&updated_limits); + av2_dm_rational_destroy(&decoder_buffer_delay); + av2_dm_rational_destroy(&encoder_buffer_delay); + av2_dm_rational_destroy(&dec_ct); + av2_dm_rational_destroy(&disp_ct); + return false; + } + av2_dm_config_destroy(&model->config); + model->config = updated_config; + av2_dm_level_limits_destroy(&model->limits); + model->limits = updated_limits; + av2_dm_rational_move(&model->decoder_buffer_delay, &decoder_buffer_delay); + av2_dm_rational_move(&model->encoder_buffer_delay, &encoder_buffer_delay); model->decoder_buffer_delay_ticks = parameters->decoder_buffer_delay_ticks; model->low_delay_mode = parameters->low_delay_mode; - model->dec_ct = parameters->dec_ct; - model->disp_ct = parameters->disp_ct; + av2_dm_rational_move(&model->dec_ct, &dec_ct); + av2_dm_rational_move(&model->disp_ct, &disp_ct); + return true; } Av2DecoderModel *av2_decoder_model_create(const Av2DmConfig *config, Av2DmReportFn report, void *report_opaque) { if (config == NULL) return NULL; - Av2DecoderModel *const model = avm_calloc(1, sizeof(*model)); + Av2DecoderModel *const model = internal_calloc(1, sizeof(*model)); if (model == NULL) return NULL; - model->config = *config; + av2_dm_level_limits_init(&model->config.level_limits); + av2_dm_level_limits_init(&model->limits); + av2_dm_rational_init(&model->decoder_buffer_delay); + av2_dm_rational_init(&model->encoder_buffer_delay); + av2_dm_rational_init(&model->dec_ct); + av2_dm_rational_init(&model->disp_ct); + av2_dm_rational_init(&model->buffer_size_base); + av2_dm_rational_init(&model->pending_buffer_size); + dfg_record_init(&model->previous_dfg); + av2_dm_rational_init(&model->most_recent_rap_scheduled_removal); + av2_dm_rational_init(&model->previous_output_presentation_offset); + av2_dm_rational_init(&model->last_presentation_offset); + av2_dm_rational_init(&model->last_presentation); + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + av2_dm_rational_init( + &model->rap_presentation_anchors[i].presentation_offset); + } + av2_dm_rational_init(&model->last_frame_parsing_time); + av2_dm_rational_init(&model->last_display_duration); + av2_dm_rational_init(&model->latest_timed_tu_output_time); + av2_dm_rational_init(&model->pending_display_late.threshold); + av2_dm_rational_init(&model->pending_display_late.observed); + av2_dm_rational_init(&model->pending_display_late.presentation_offset); + av2_dm_rational_init(&model->pending_decode_deadline.threshold); + av2_dm_rational_init(&model->pending_decode_deadline.observed); + av2_dm_rational_init(&model->pending_decode_deadline.presentation_offset); + if (!av2_dm_config_copy(&model->config, config)) { + av2_decoder_model_destroy(model); + return NULL; + } model->report = report; model->report_opaque = report_opaque; model->result.status = AV2_DM_RESULT_CONFORMANT; @@ -1346,19 +2275,36 @@ Av2DecoderModel *av2_decoder_model_create(const Av2DmConfig *config, av2_decoder_model_destroy(model); return NULL; } - Av2DmResolvedParameters parameters; - if (!resolve_parameters(config, ¶meters)) { + Av2DmResolvedParameters parameters = { 0 }; + if (!parameter_inputs_valid(config)) { missing_input(model); - } else { - apply_parameters(model, config, ¶meters); + } else if (!resolve_parameters(config, ¶meters)) { + arithmetic_failure(model); + } else if (!apply_parameters(model, config, ¶meters) || + !av2_dm_rational_copy(&model->buffer_size_base, + ¶meters.limits.buffer_size)) { + arithmetic_failure(model); } - - if (config->ras_start) { - if (!config->ras_seed_complete || !seed_ras_buffers(model, &model->lane) || - !seed_ras_buffers(model, &model->resource_lane)) { + resolved_parameters_destroy(¶meters); + + if (config->ras_start && !model->processing_stopped) { + const bool seeded = config->ras_seed_complete && + seed_ras_buffers(model, &model->lane) && + seed_ras_buffers(model, &model->resource_lane); + if (!seeded) { + (void)av2_dm_buffer_pool_initialize(&model->lane.pool, + config->num_ref_frames); + (void)av2_dm_buffer_pool_initialize(&model->resource_lane.pool, + config->num_ref_frames); + model->lane.current_buffer_index = -1; + model->resource_lane.current_buffer_index = -1; // DM-SPEC-6: a RAS run is provable only when all established long-term // slot/generation relationships can be reconstructed. - missing_input(model); + if (av2_dm_last_failure_was_allocation()) { + allocation_failure(model); + } else { + missing_input(model); + } } } update_result_status(model); @@ -1368,74 +2314,400 @@ Av2DecoderModel *av2_decoder_model_create(const Av2DmConfig *config, void av2_decoder_model_destroy(Av2DecoderModel *model) { if (model == NULL) return; + av2_dm_config_destroy(&model->config); + av2_dm_level_limits_destroy(&model->limits); + av2_dm_rational_destroy(&model->decoder_buffer_delay); + av2_dm_rational_destroy(&model->encoder_buffer_delay); + av2_dm_rational_destroy(&model->dec_ct); + av2_dm_rational_destroy(&model->disp_ct); + av2_dm_rational_destroy(&model->buffer_size_base); + av2_dm_rational_destroy(&model->pending_buffer_size); + for (uint32_t i = 0; i < model->buffer_size_transition_count; ++i) { + av2_dm_rational_destroy(&model->buffer_size_transitions[i].time); + av2_dm_rational_destroy(&model->buffer_size_transitions[i].size); + } + avm_free(model->buffer_size_transitions); + lane_destroy(&model->lane); + lane_destroy(&model->resource_lane); + for (uint32_t i = 0; i < model->dfg_count; ++i) { + dfg_record_destroy(&model->dfgs[i]); + } avm_free(model->dfgs); + dfg_record_destroy(&model->previous_dfg); + for (uint32_t i = 0; i < model->tu_count; ++i) { + tu_record_destroy(&model->tus[i]); + } avm_free(model->tus); + av2_dm_rational_destroy(&model->most_recent_rap_scheduled_removal); + av2_dm_rational_destroy(&model->previous_output_presentation_offset); + av2_dm_rational_destroy(&model->last_presentation_offset); + av2_dm_rational_destroy(&model->last_presentation); + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + av2_dm_rational_destroy( + &model->rap_presentation_anchors[i].presentation_offset); + } + av2_dm_rational_destroy(&model->last_frame_parsing_time); + av2_dm_rational_destroy(&model->last_display_duration); + av2_dm_rational_destroy(&model->latest_timed_tu_output_time); + av2_dm_rational_destroy(&model->pending_display_late.threshold); + av2_dm_rational_destroy(&model->pending_display_late.observed); + av2_dm_rational_destroy(&model->pending_display_late.presentation_offset); + av2_dm_rational_destroy(&model->pending_decode_deadline.threshold); + av2_dm_rational_destroy(&model->pending_decode_deadline.observed); + av2_dm_rational_destroy(&model->pending_decode_deadline.presentation_offset); avm_free(model); } -static bool rational_multiply_wide(const Av2DmRational *value, - Av2DmUnsignedWide multiplier, - Av2DmRational *result) { - if (value == NULL || result == NULL || wide_is_zero(value->denominator)) { - return false; +static Av2DecoderModel *decoder_model_clone(const Av2DecoderModel *source, + bool *allocation_failed) { + *allocation_failed = false; + Av2DecoderModel *const copy = internal_calloc(1, sizeof(*copy)); + if (copy == NULL) { + *allocation_failed = true; + return NULL; + } + *copy = *source; + av2_dm_level_limits_init(©->config.level_limits); + av2_dm_level_limits_init(©->limits); + av2_dm_rational_init(©->decoder_buffer_delay); + av2_dm_rational_init(©->encoder_buffer_delay); + av2_dm_rational_init(©->dec_ct); + av2_dm_rational_init(©->disp_ct); + av2_dm_rational_init(©->buffer_size_base); + av2_dm_rational_init(©->pending_buffer_size); + copy->buffer_size_transitions = NULL; + copy->buffer_size_transition_count = 0; + copy->buffer_size_transition_capacity = 0; + memset(©->lane, 0, sizeof(copy->lane)); + memset(©->resource_lane, 0, sizeof(copy->resource_lane)); + copy->dfgs = NULL; + copy->dfg_count = 0; + copy->dfg_capacity = 0; + dfg_record_init(©->previous_dfg); + copy->tus = NULL; + copy->tu_count = 0; + copy->tu_capacity = 0; + av2_dm_rational_init(©->most_recent_rap_scheduled_removal); + av2_dm_rational_init(©->previous_output_presentation_offset); + av2_dm_rational_init(©->last_presentation_offset); + av2_dm_rational_init(©->last_presentation); + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + av2_dm_rational_init( + ©->rap_presentation_anchors[i].presentation_offset); + } + av2_dm_rational_init(©->last_frame_parsing_time); + av2_dm_rational_init(©->last_display_duration); + av2_dm_rational_init(©->latest_timed_tu_output_time); + av2_dm_rational_init(©->pending_display_late.threshold); + av2_dm_rational_init(©->pending_display_late.observed); + av2_dm_rational_init(©->pending_display_late.presentation_offset); + av2_dm_rational_init(©->pending_decode_deadline.threshold); + av2_dm_rational_init(©->pending_decode_deadline.observed); + av2_dm_rational_init(©->pending_decode_deadline.presentation_offset); + + if (!av2_dm_config_copy(©->config, &source->config) || + !av2_dm_level_limits_copy(©->limits, &source->limits) || + !av2_dm_rational_copy(©->decoder_buffer_delay, + &source->decoder_buffer_delay) || + !av2_dm_rational_copy(©->encoder_buffer_delay, + &source->encoder_buffer_delay) || + !av2_dm_rational_copy(©->dec_ct, &source->dec_ct) || + !av2_dm_rational_copy(©->disp_ct, &source->disp_ct) || + !av2_dm_rational_copy(©->buffer_size_base, + &source->buffer_size_base) || + !av2_dm_rational_copy(©->pending_buffer_size, + &source->pending_buffer_size) || + !lane_copy(©->lane, &source->lane) || + !lane_copy(©->resource_lane, &source->resource_lane)) { + goto failure; + } + + if (source->buffer_size_transition_capacity != 0) { + copy->buffer_size_transitions = + internal_calloc(source->buffer_size_transition_capacity, + sizeof(*copy->buffer_size_transitions)); + if (copy->buffer_size_transitions == NULL) { + *allocation_failed = true; + goto failure; + } + copy->buffer_size_transition_capacity = + source->buffer_size_transition_capacity; + } + for (uint32_t i = 0; i < source->buffer_size_transition_count; ++i) { + Av2DmBufferSizeTransition *const destination = + ©->buffer_size_transitions[i]; + const Av2DmBufferSizeTransition *const existing = + &source->buffer_size_transitions[i]; + destination->dfg_number = existing->dfg_number; + destination->after_removal = existing->after_removal; + ++copy->buffer_size_transition_count; + if (!av2_dm_rational_copy(&destination->time, &existing->time) || + !av2_dm_rational_copy(&destination->size, &existing->size)) { + goto failure; + } + } + + if (source->dfg_capacity != 0) { + copy->dfgs = internal_calloc(source->dfg_capacity, sizeof(*copy->dfgs)); + if (copy->dfgs == NULL) { + *allocation_failed = true; + goto failure; + } + copy->dfg_capacity = source->dfg_capacity; + } + for (uint32_t i = 0; i < source->dfg_count; ++i) { + dfg_record_init(©->dfgs[i]); + if (!dfg_record_copy(©->dfgs[i], &source->dfgs[i])) goto failure; + ++copy->dfg_count; + } + if (source->previous_dfg_valid && + !dfg_record_copy(©->previous_dfg, &source->previous_dfg)) { + goto failure; + } + + if (source->tu_capacity != 0) { + copy->tus = internal_calloc(source->tu_capacity, sizeof(*copy->tus)); + if (copy->tus == NULL) { + *allocation_failed = true; + goto failure; + } + copy->tu_capacity = source->tu_capacity; + } + for (uint32_t i = 0; i < source->tu_count; ++i) { + tu_record_init(©->tus[i]); + if (!tu_record_copy(©->tus[i], &source->tus[i])) goto failure; + ++copy->tu_count; + } + + if (!av2_dm_rational_copy(©->most_recent_rap_scheduled_removal, + &source->most_recent_rap_scheduled_removal) || + !av2_dm_rational_copy(©->previous_output_presentation_offset, + &source->previous_output_presentation_offset) || + !av2_dm_rational_copy(©->last_presentation_offset, + &source->last_presentation_offset) || + !av2_dm_rational_copy(©->last_presentation, + &source->last_presentation) || + !av2_dm_rational_copy(©->last_frame_parsing_time, + &source->last_frame_parsing_time) || + !av2_dm_rational_copy(©->last_display_duration, + &source->last_display_duration) || + !av2_dm_rational_copy(©->latest_timed_tu_output_time, + &source->latest_timed_tu_output_time)) { + goto failure; + } + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { + if (!av2_dm_rational_copy( + ©->rap_presentation_anchors[i].presentation_offset, + &source->rap_presentation_anchors[i].presentation_offset)) { + goto failure; + } } - Av2DmRational normalized = *value; - if (!rational_normalize(&normalized)) return false; - const Av2DmUnsignedWide divisor = - wide_gcd(multiplier, normalized.denominator); - Av2DmUnsignedWide remainder; - if (!wide_divide(multiplier, divisor, &multiplier, &remainder) || - !wide_is_zero(remainder) || - !wide_divide(normalized.denominator, divisor, &normalized.denominator, - &remainder) || - !wide_is_zero(remainder) || - !wide_multiply_checked(normalized.magnitude, multiplier, - &normalized.magnitude)) { + if (!av2_dm_rational_copy(©->pending_display_late.threshold, + &source->pending_display_late.threshold) || + !av2_dm_rational_copy(©->pending_display_late.observed, + &source->pending_display_late.observed) || + !av2_dm_rational_copy( + ©->pending_display_late.presentation_offset, + &source->pending_display_late.presentation_offset) || + !av2_dm_rational_copy(©->pending_decode_deadline.threshold, + &source->pending_decode_deadline.threshold) || + !av2_dm_rational_copy(©->pending_decode_deadline.observed, + &source->pending_decode_deadline.observed) || + !av2_dm_rational_copy( + ©->pending_decode_deadline.presentation_offset, + &source->pending_decode_deadline.presentation_offset)) { + goto failure; + } + return copy; + +failure: + if (av2_dm_last_failure_was_allocation()) { + *allocation_failed = true; + } + av2_decoder_model_destroy(copy); + return NULL; +} + +typedef struct Av2DmModelTransaction { + Av2DecoderModel *snapshot; + Av2DmReportFn report; + void *report_opaque; + Av2DmViolation *violations; + size_t violation_count; + size_t violation_capacity; + bool allocation_failed; + bool arithmetic_failed; +} Av2DmModelTransaction; + +static void transaction_report(void *opaque, const Av2DmViolation *violation) { + Av2DmModelTransaction *const transaction = opaque; + if (transaction->allocation_failed || transaction->arithmetic_failed) return; + if (transaction->violation_count == transaction->violation_capacity) { + const size_t new_capacity = transaction->violation_capacity == 0 + ? 4 + : transaction->violation_capacity * 2; + if (new_capacity < transaction->violation_capacity || + new_capacity > SIZE_MAX / sizeof(*transaction->violations)) { + transaction->arithmetic_failed = true; + return; + } + Av2DmViolation *const replacement = + internal_calloc(new_capacity, sizeof(*replacement)); + if (replacement == NULL) { + transaction->allocation_failed = true; + return; + } + if (transaction->violations != NULL) { + memcpy(replacement, transaction->violations, + transaction->violation_count * sizeof(*replacement)); + avm_free(transaction->violations); + } + transaction->violations = replacement; + transaction->violation_capacity = new_capacity; + } + Av2DmViolation *const stored = + &transaction->violations[transaction->violation_count++]; + av2_dm_violation_init(stored); + if (!av2_dm_violation_copy(stored, violation)) { + if (av2_dm_last_failure_was_allocation()) { + transaction->allocation_failed = true; + } else { + transaction->arithmetic_failed = true; + } + } +} + +static void destroy_model_transaction(Av2DmModelTransaction *transaction) { + for (size_t i = 0; i < transaction->violation_count; ++i) { + av2_dm_violation_destroy(&transaction->violations[i]); + } + avm_free(transaction->violations); + memset(transaction, 0, sizeof(*transaction)); +} + +static bool begin_model_transaction(Av2DecoderModel *model, + Av2DmModelTransaction *transaction) { + memset(transaction, 0, sizeof(*transaction)); + transaction->snapshot = + decoder_model_clone(model, &transaction->allocation_failed); + if (transaction->snapshot == NULL) { + if (transaction->allocation_failed) { + allocation_failure(model); + } else { + arithmetic_failure(model); + } return false; } - *result = normalized; - return rational_normalize(result); + transaction->report = model->report; + transaction->report_opaque = model->report_opaque; + model->report = transaction_report; + model->report_opaque = transaction; + return true; +} + +static void end_model_transaction(Av2DecoderModel *model, + Av2DmModelTransaction *transaction) { + Av2DecoderModel *const snapshot = transaction->snapshot; + const bool allocation_failed = + transaction->allocation_failed || + (model->result.allocation_failed && !snapshot->result.allocation_failed); + const bool arithmetic_failed = + transaction->arithmetic_failed || + (model->result.arithmetic_failed && !snapshot->result.arithmetic_failed); + if (allocation_failed || arithmetic_failed) { + Av2DecoderModel failed = *model; + *model = *snapshot; + *snapshot = failed; + av2_decoder_model_destroy(snapshot); + transaction->snapshot = NULL; + if (allocation_failed) { + allocation_failure(model); + } else { + model->result.arithmetic_failed = true; + model->processing_stopped = true; + update_result_status(model); + } + destroy_model_transaction(transaction); + return; + } + model->report = transaction->report; + model->report_opaque = transaction->report_opaque; + av2_decoder_model_destroy(snapshot); + transaction->snapshot = NULL; + if (transaction->report != NULL) { + for (size_t i = 0; i < transaction->violation_count; ++i) { + transaction->report(transaction->report_opaque, + &transaction->violations[i]); + } + } + destroy_model_transaction(transaction); } +static bool rational_ceil_to_integer(const Av2DmRational *value, + Av2DmRational *result); + static bool rational_ceil_ratio_to_tick(const Av2DmRational *time, const Av2DmRational *tick, Av2DmRational *result) { - if (time->negative || tick->negative || wide_is_zero(tick->magnitude)) { + if (time->negative || tick->negative || av2_dm_rational_is_zero(tick)) { return false; } - Av2DmRational reciprocal; - reciprocal.magnitude = tick->denominator; - reciprocal.denominator = tick->magnitude; - reciprocal.negative = false; - Av2DmRational ratio; - if (!rational_multiply(time, &reciprocal, &ratio)) return false; - Av2DmUnsignedWide quotient; - Av2DmUnsignedWide remainder; - if (!wide_divide(ratio.magnitude, ratio.denominator, "ient, &remainder)) { - return false; - } - if (!wide_is_zero(remainder)) { - const Av2DmUnsignedWide one = wide_from_u64(1); - if (!wide_add(quotient, one, "ient)) return false; - } - return rational_multiply_wide(tick, quotient, result); + Av2DmRational reciprocal = { 0 }; + Av2DmRational ratio = { 0 }; + Av2DmRational quotient = { 0 }; + const bool rounded = rational_reciprocal(tick, &reciprocal) && + rational_multiply(time, &reciprocal, &ratio) && + rational_ceil_to_integer(&ratio, "ient) && + rational_multiply(tick, "ient, result); + av2_dm_rational_destroy(&reciprocal); + av2_dm_rational_destroy(&ratio); + av2_dm_rational_destroy("ient); + return rounded; +} + +static bool rational_ceil_from_anchor(const Av2DmRational *time, + const Av2DmRational *anchor, + const Av2DmRational *tick, + Av2DmRational *result) { + Av2DmRational delta = { 0 }; + Av2DmRational rounded = { 0 }; + const bool calculated = av2_dm_rational_subtract(time, anchor, &delta) && + rational_ceil_ratio_to_tick(&delta, tick, &rounded) && + av2_dm_rational_add(anchor, &rounded, result); + av2_dm_rational_destroy(&delta); + av2_dm_rational_destroy(&rounded); + return calculated; } static bool rational_ceil_to_integer(const Av2DmRational *value, Av2DmRational *result) { - if (value == NULL || result == NULL || wide_is_zero(value->denominator)) { - return false; - } - Av2DmUnsignedWide quotient; - Av2DmUnsignedWide remainder; - if (!wide_divide(value->magnitude, value->denominator, "ient, - &remainder)) { - return false; - } - if (!value->negative && !wide_is_zero(remainder)) { - if (!wide_add(quotient, wide_from_u64(1), "ient)) return false; - } - return av2_dm_rational_make_wide(quotient, 1, value->negative, result); + rational_begin_operation(); + if (value == NULL || result == NULL) return false; + Av2DmBigUInt magnitude = { 0 }, denominator = { 0 }; + Av2DmBigUInt quotient = { 0 }, remainder = { 0 }, one = { 0 }; + Av2DmBigUInt rounded = { 0 }; + Av2DmRational temporary = { 0 }; + bool ok = big_uint_from_rational(value, false, &magnitude) && + big_uint_from_rational(value, true, &denominator) && + !big_uint_is_zero(&denominator) && + big_uint_divide(&magnitude, &denominator, "ient, &remainder); + if (ok && !value->negative && !big_uint_is_zero(&remainder)) { + ok = big_uint_from_u64(&one, 1) && big_uint_add("ient, &one, &rounded); + if (ok) big_uint_move("ient, &rounded); + } + if (ok) { + if (big_uint_is_zero(&one)) ok = big_uint_from_u64(&one, 1); + if (ok) ok = rational_store(&temporary, "ient, &one, value->negative); + } + if (ok) av2_dm_rational_move(result, &temporary); + av2_dm_rational_destroy(&temporary); + big_uint_destroy(&magnitude); + big_uint_destroy(&denominator); + big_uint_destroy("ient); + big_uint_destroy(&remainder); + big_uint_destroy(&one); + big_uint_destroy(&rounded); + return ok; } static void compare_upper_limit(Av2DecoderModel *model, Av2DmViolationCode code, @@ -1565,16 +2837,30 @@ static Av2DmTuRecord *get_tu(Av2DecoderModel *model, return NULL; } Av2DmTuRecord *const tu = &model->tus[model->tu_count++]; - memset(tu, 0, sizeof(*tu)); + tu_record_init(tu); tu->temporal_unit_index = temporal_unit_index; tu->event_index = event_index; + tu->cvs_number = model->cvs_number; + if (!av2_dm_level_limits_copy(&tu->limits, &model->limits)) { + tu_record_destroy(tu); + --model->tu_count; + arithmetic_failure(model); + return NULL; + } + tu->tier = model->config.tier; + tu->still_picture = model->config.still_picture; + tu->max_frame_width = model->config.max_frame_width; + tu->max_frame_height = model->config.max_frame_height; return tu; } static bool update_latest_timed_tu(Av2DecoderModel *model, const Av2DmRational *output_time) { if (!model->latest_timed_tu_valid) { - model->latest_timed_tu_output_time = *output_time; + if (!av2_dm_rational_copy(&model->latest_timed_tu_output_time, + output_time)) { + return false; + } model->latest_timed_tu_valid = true; return true; } @@ -1583,18 +2869,23 @@ static bool update_latest_timed_tu(Av2DecoderModel *model, &comparison)) { return false; } - if (comparison > 0) model->latest_timed_tu_output_time = *output_time; + if (comparison > 0 && + !av2_dm_rational_copy(&model->latest_timed_tu_output_time, output_time)) { + return false; + } return true; } static void check_static_level_limits(Av2DecoderModel *model, const Av2DmFrameEvent *event) { - Av2DmRational observed; - Av2DmRational limit; + Av2DmRational observed = { 0 }; + Av2DmRational limit = { 0 }; if (!rational_from_product(event->frame_width, event->frame_height, &observed) || !av2_dm_rational_make(model->limits.max_picture_size, 1, &limit)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } compare_upper_limit(model, AV2_DM_VIOLATION_MAX_PICTURE_SIZE, @@ -1624,14 +2915,18 @@ static void check_static_level_limits(Av2DecoderModel *model, #undef CHECK_INTEGER_LIMIT if (event->frame_width < 16) { - av2_dm_rational_make(event->frame_width, 1, &observed); - av2_dm_rational_make(16, 1, &limit); + if (!av2_dm_rational_make(event->frame_width, 1, &observed) || + !av2_dm_rational_make(16, 1, &limit)) { + arithmetic_failure(model); + } report_violation(model, AV2_DM_VIOLATION_MIN_HORIZONTAL_SIZE, event->event_index, &observed, &limit); } if (event->frame_height < 16) { - av2_dm_rational_make(event->frame_height, 1, &observed); - av2_dm_rational_make(16, 1, &limit); + if (!av2_dm_rational_make(event->frame_height, 1, &observed) || + !av2_dm_rational_make(16, 1, &limit)) { + arithmetic_failure(model); + } report_violation(model, AV2_DM_VIOLATION_MIN_VERTICAL_SIZE, event->event_index, &observed, &limit); } @@ -1639,11 +2934,13 @@ static void check_static_level_limits(Av2DecoderModel *model, report_violation(model, AV2_DM_VIOLATION_MIN_TILE_WIDTH, event->event_index, NULL, NULL); } + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); } static bool release_presented_buffers(Av2DmLane *lane, const Av2DmRational *removal) { - for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->player_ref_count == 0 || !buffer->presentation_time_valid) { continue; @@ -1664,16 +2961,15 @@ static bool release_presented_buffers(Av2DmLane *lane, static bool next_resource_removal(Av2DecoderModel *model, Av2DmLane *lane, uint64_t dfg_index, Av2DmRational *removal) { if (dfg_index == 0) { - *removal = model->decoder_buffer_delay; - return true; + return av2_dm_rational_copy(removal, &model->decoder_buffer_delay); } if (!release_presented_buffers(lane, &lane->time)) return false; if (av2_dm_buffer_pool_get_free_buffer(&lane->pool) >= 0) { - *removal = lane->time; - return true; + return av2_dm_rational_copy(removal, &lane->time); } bool found = false; - Av2DmRational earliest; + Av2DmRational earliest = { 0 }; + bool copied = false; for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { const Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->decoder_ref_count != 0 || buffer->player_ref_count == 0) { @@ -1681,29 +2977,36 @@ static bool next_resource_removal(Av2DecoderModel *model, Av2DmLane *lane, } if (!buffer->presentation_time_valid) { missing_input(model); - return false; + goto cleanup; } if (!found) { - earliest = buffer->presentation_time; + if (!av2_dm_rational_copy(&earliest, &buffer->presentation_time)) { + goto cleanup; + } found = true; } else { bool less; if (!rational_less(&buffer->presentation_time, &earliest, &less)) { - return false; + goto cleanup; + } + if (less && + !av2_dm_rational_copy(&earliest, &buffer->presentation_time)) { + goto cleanup; } - if (less) earliest = buffer->presentation_time; } } - if (!found) return false; - *removal = earliest; - return true; + copied = found && av2_dm_rational_copy(removal, &earliest); + +cleanup: + av2_dm_rational_destroy(&earliest); + return copied; } static bool lane_start_decode(Av2DmLane *lane, const Av2DmRational *removal, const Av2DmRational *decode_time, uint64_t generation, int32_t *buffer_index) { if (!release_presented_buffers(lane, removal)) return false; - lane->time = *removal; + if (!av2_dm_rational_copy(&lane->time, removal)) return false; const int32_t free_buffer = av2_dm_buffer_pool_get_free_buffer(&lane->pool); *buffer_index = free_buffer; lane->current_buffer_index = free_buffer; @@ -1715,7 +3018,10 @@ static bool lane_start_decode(Av2DmLane *lane, const Av2DmRational *removal, buffer_reset(buffer); buffer->generation_valid = true; buffer->generation = generation; - buffer->decode_completion_time = lane->time; + if (!av2_dm_rational_copy(&buffer->decode_completion_time, &lane->time)) { + buffer_reset(buffer); + return false; + } buffer->decode_completion_time_valid = true; return true; } @@ -1726,27 +3032,27 @@ static bool calculate_decode_time(Av2DecoderModel *model, Av2DmRational *decode_time) { uint64_t samples; if (event->frame_is_intra) { - Av2DmRational product; + Av2DmRational product = { 0 }; if (!rational_from_product(event->frame_width, event->frame_height, &product) || - product.magnitude.limbs[1] != 0 || product.magnitude.limbs[2] != 0 || - product.magnitude.limbs[3] != 0) { + !rational_to_u64(&product, &samples)) { + av2_dm_rational_destroy(&product); return false; } - samples = product.magnitude.limbs[0]; + av2_dm_rational_destroy(&product); if (event->allow_global_intrabc && event->inloop_filtering_enabled) { if (samples > UINT64_MAX / 2) return false; samples *= 2; } } else { - Av2DmRational product; + Av2DmRational product = { 0 }; if (!rational_from_product(model->config.max_frame_width, model->config.max_frame_height, &product) || - product.magnitude.limbs[1] != 0 || product.magnitude.limbs[2] != 0 || - product.magnitude.limbs[3] != 0) { + !rational_to_u64(&product, &samples)) { + av2_dm_rational_destroy(&product); return false; } - samples = product.magnitude.limbs[0]; + av2_dm_rational_destroy(&product); } *luma_samples = samples; return av2_dm_rational_make(samples, model->limits.max_decode_rate, @@ -1757,18 +3063,40 @@ static void check_frame_parsing_constraints(Av2DecoderModel *model, Av2DmDfgRecord *dfg, const Av2DmRational *interval, uint64_t proving_event_index) { - if (model->config.still_picture) return; + if (dfg->still_picture) return; Av2DmViolationDetail detail; memset(&detail, 0, sizeof(detail)); detail.kind = AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL; detail.value.frame_interval = *interval; const Av2DmLevelLimits *const limits = &dfg->limits; - Av2DmRational limit; - Av2DmRational observed; + Av2DmRational limit = { 0 }; + Av2DmRational observed = { 0 }; + Av2DmRational dynamic_tiles = { 0 }; + Av2DmRational one = { 0 }; + Av2DmRational max_tiles = { 0 }; + Av2DmRational compressed_limit_1 = { 0 }; + Av2DmRational compressed_limit_2 = { 0 }; + Av2DmRational symbol_factor_a = { 0 }; + Av2DmRational symbol_factor_b = { 0 }; + Av2DmRational symbol_factor = { 0 }; +#define CLEANUP_FRAME_PARSING_RATIONALS() \ + do { \ + av2_dm_rational_destroy(&limit); \ + av2_dm_rational_destroy(&observed); \ + av2_dm_rational_destroy(&dynamic_tiles); \ + av2_dm_rational_destroy(&one); \ + av2_dm_rational_destroy(&max_tiles); \ + av2_dm_rational_destroy(&compressed_limit_1); \ + av2_dm_rational_destroy(&compressed_limit_2); \ + av2_dm_rational_destroy(&symbol_factor_a); \ + av2_dm_rational_destroy(&symbol_factor_b); \ + av2_dm_rational_destroy(&symbol_factor); \ + } while (0) if (!av2_dm_rational_multiply_u64(interval, limits->max_decode_rate, &limit) || !av2_dm_rational_make(dfg->luma_samples, 1, &observed)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } compare_upper_limit_for_affected_with_detail( @@ -1776,25 +3104,29 @@ static void check_frame_parsing_constraints(Av2DecoderModel *model, AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, &detail); - Av2DmRational dynamic_tiles; - Av2DmRational one; - Av2DmRational max_tiles; if (!av2_dm_rational_multiply_u64(interval, (uint64_t)limits->max_tiles * 120, &dynamic_tiles) || !av2_dm_rational_make(1, 1, &one) || !av2_dm_rational_make(limits->max_tiles, 1, &max_tiles) || !rational_max(&dynamic_tiles, &one, &dynamic_tiles)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } bool greater; if (!rational_greater(&dynamic_tiles, &max_tiles, &greater)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); + return; + } + if (greater && !av2_dm_rational_copy(&dynamic_tiles, &max_tiles)) { + arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } - if (greater) dynamic_tiles = max_tiles; if (!av2_dm_rational_make(dfg->num_tiles, 1, &observed)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } compare_upper_limit_for_affected_with_detail( @@ -1802,10 +3134,9 @@ static void check_frame_parsing_constraints(Av2DecoderModel *model, AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &dynamic_tiles, &detail); - Av2DmRational compressed_limit_1; - Av2DmRational compressed_limit_2; if (dfg->luma_samples > UINT64_MAX / limits->picture_size_profile_factor) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } const uint64_t picture_units = @@ -1824,17 +3155,25 @@ static void check_frame_parsing_constraints(Av2DecoderModel *model, (uint64_t)8 * limits->min_compression_basis, &compressed_limit_2)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } bool first_is_greater; if (!rational_greater(&compressed_limit_1, &compressed_limit_2, &first_is_greater)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); + return; + } + if (!av2_dm_rational_copy(&limit, first_is_greater ? &compressed_limit_2 + : &compressed_limit_1)) { + arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } - limit = first_is_greater ? compressed_limit_2 : compressed_limit_1; if (!av2_dm_rational_make(dfg->compressed_size, 1, &observed)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } compare_upper_limit_for_affected_with_detail( @@ -1842,9 +3181,6 @@ static void check_frame_parsing_constraints(Av2DecoderModel *model, AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, &detail); - Av2DmRational symbol_factor_a; - Av2DmRational symbol_factor_b; - Av2DmRational symbol_factor; if (!av2_dm_rational_make(8, (uint64_t)9 * limits->min_compression_basis, &symbol_factor_a) || !av2_dm_rational_make(1, 48, &symbol_factor_b) || @@ -1857,36 +3193,44 @@ static void check_frame_parsing_constraints(Av2DecoderModel *model, !rational_multiply(&limit, &symbol_factor, &limit) || !av2_dm_rational_make(dfg->frame_symbol_count, 1, &observed)) { arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); return; } compare_upper_limit_for_affected_with_detail( model, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS, proving_event_index, AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, &detail); + CLEANUP_FRAME_PARSING_RATIONALS(); +#undef CLEANUP_FRAME_PARSING_RATIONALS } static void check_previous_dfg_interval(Av2DecoderModel *model, Av2DmDfgRecord *previous, const Av2DmDfgRecord *current) { - Av2DmRational interval; + Av2DmRational interval = { 0 }; if (!av2_dm_rational_subtract(¤t->removal, &previous->removal, &interval) || !av2_dm_rational_divide_u64(&interval, previous->decode_count_two ? 2 : 1, &interval)) { arithmetic_failure(model); + av2_dm_rational_destroy(&interval); + return; + } + if (!av2_dm_rational_copy(&model->last_frame_parsing_time, &interval)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&interval); return; } - model->last_frame_parsing_time = interval; model->last_frame_parsing_time_valid = true; // The previous DFG retains the affected frame/generation identity; the // current DFG supplies the removal interval that proves these constraints. check_frame_parsing_constraints(model, previous, &interval, current->event_index); - if (previous->mode == AV2_DM_DECODING_SCHEDULE_MODE) { - Av2DmRational available; - Av2DmRational one_header_time; - Av2DmRational required; + if (current->mode == AV2_DM_DECODING_SCHEDULE_MODE) { + Av2DmRational available = { 0 }; + Av2DmRational one_header_time = { 0 }; + Av2DmRational required = { 0 }; const uint64_t max_headers = (uint64_t)previous->limits.max_header_rate * (1 + ((uint64_t)previous->tier << 1)); if (!av2_dm_rational_subtract(¤t->scheduled_removal, @@ -1894,6 +3238,10 @@ static void check_previous_dfg_interval(Av2DecoderModel *model, !av2_dm_rational_make(1, max_headers, &one_header_time) || !rational_max(&previous->decode_time, &one_header_time, &required)) { arithmetic_failure(model); + av2_dm_rational_destroy(&available); + av2_dm_rational_destroy(&one_header_time); + av2_dm_rational_destroy(&required); + av2_dm_rational_destroy(&interval); return; } Av2DmViolationDetail detail; @@ -1905,39 +3253,206 @@ static void check_previous_dfg_interval(Av2DecoderModel *model, model, AV2_DM_VIOLATION_MINIMUM_DECODE_TIME, current->event_index, AV2_DM_VIOLATION_AFFECTED_DFG, previous->event_index, &available, &required, &detail); + av2_dm_rational_destroy(&available); + av2_dm_rational_destroy(&one_header_time); + av2_dm_rational_destroy(&required); + } + av2_dm_rational_destroy(&interval); +} + +static bool buffer_size_transition_applies( + const Av2DmBufferSizeTransition *transition, const Av2DmRational *time, + uint64_t dfg_number, bool after_own_removal, bool at_last_arrival, + bool *applies) { + int comparison; + if (!av2_dm_rational_compare(&transition->time, time, &comparison)) { + return false; + } + *applies = + comparison < 0 || + (comparison == 0 && + (!transition->after_removal + ? !at_last_arrival || transition->dfg_number <= dfg_number + : transition->dfg_number < dfg_number || + (transition->dfg_number == dfg_number && after_own_removal))); + return true; +} + +static bool buffer_size_at(Av2DecoderModel *model, const Av2DmRational *time, + uint64_t dfg_number, bool after_own_removal, + bool at_last_arrival, Av2DmRational *size) { + if (!av2_dm_rational_copy(size, &model->buffer_size_base)) return false; + for (uint32_t i = 0; i < model->buffer_size_transition_count; ++i) { + const Av2DmBufferSizeTransition *const transition = + &model->buffer_size_transitions[i]; + bool applies; + if (!buffer_size_transition_applies(transition, time, dfg_number, + after_own_removal, at_last_arrival, + &applies)) { + return false; + } + if (applies) { + if (!av2_dm_rational_copy(size, &transition->size)) return false; + } + } + return true; +} + +static bool apply_buffer_size_transition_to_breakpoint( + const Av2DmBufferSizeTransition *transition, const Av2DmRational *time, + uint64_t dfg_number, bool after_own_removal, bool at_last_arrival, + Av2DmRational *size) { + bool applies; + return buffer_size_transition_applies(transition, time, dfg_number, + after_own_removal, at_last_arrival, + &applies) && + (!applies || av2_dm_rational_copy(size, &transition->size)); +} + +static bool add_pending_buffer_size_transition(Av2DecoderModel *model, + const Av2DmDfgRecord *dfg) { + if (model->pending_buffer_size_change == 0) return true; + if (model->buffer_size_transition_count == UINT32_MAX || + !grow_array((void **)&model->buffer_size_transitions, + &model->buffer_size_transition_capacity, + model->buffer_size_transition_count, + sizeof(*model->buffer_size_transitions))) { + return false; + } + Av2DmBufferSizeTransition *transition = + &model->buffer_size_transitions[model->buffer_size_transition_count]; + memset(transition, 0, sizeof(*transition)); + transition->after_removal = model->pending_buffer_size_change < 0; + if (!av2_dm_rational_copy(&transition->time, transition->after_removal + ? &dfg->removal + : &dfg->first_arrival) || + !av2_dm_rational_copy(&transition->size, &model->pending_buffer_size)) { + buffer_size_transition_destroy(transition); + return false; + } + transition->dfg_number = model->dfg_number; + ++model->buffer_size_transition_count; + uint32_t write_index = 0; + for (uint32_t i = 0; i + 1 < model->buffer_size_transition_count; ++i) { + Av2DmBufferSizeTransition *const older = &model->buffer_size_transitions[i]; + int comparison; + if (!av2_dm_rational_compare(&older->time, &transition->time, + &comparison)) { + return false; + } + // A later CVS value that becomes effective first supersedes an older + // pending value. Equal-time changes remain in signaling order so their + // before-arrival and after-removal phases remain distinct. + if (comparison > 0) { + buffer_size_transition_destroy(older); + continue; + } + if (write_index != i) { + buffer_size_transition_move(&model->buffer_size_transitions[write_index], + older); + } + ++write_index; + } + if (write_index + 1 != model->buffer_size_transition_count) { + buffer_size_transition_move(&model->buffer_size_transitions[write_index], + transition); + model->buffer_size_transition_count = write_index + 1; + transition = &model->buffer_size_transitions[write_index]; + } + for (uint32_t i = 0; i < model->dfg_count; ++i) { + Av2DmDfgRecord *const retained = &model->dfgs[i]; + const uint64_t retained_dfg_number = retained->decode_order + 1; + if (!apply_buffer_size_transition_to_breakpoint( + transition, &retained->last_arrival, retained_dfg_number, false, + true, &retained->buffer_size_at_last_arrival) || + !apply_buffer_size_transition_to_breakpoint( + transition, &retained->removal, retained_dfg_number, false, false, + &retained->buffer_size_before_removal) || + !apply_buffer_size_transition_to_breakpoint( + transition, &retained->removal, retained_dfg_number, true, false, + &retained->buffer_size_after_removal)) { + return false; + } + } + model->pending_buffer_size_change = 0; + return true; +} + +static bool retire_buffer_size_transitions(Av2DecoderModel *model, + const Av2DmDfgRecord *dfg) { + Av2DmRational base = { 0 }; + if (!buffer_size_at(model, &dfg->last_arrival, model->dfg_number, true, false, + &base)) { + return false; + } + uint32_t write_index = 0; + for (uint32_t i = 0; i < model->buffer_size_transition_count; ++i) { + Av2DmBufferSizeTransition *const transition = + &model->buffer_size_transitions[i]; + int comparison; + if (!av2_dm_rational_compare(&transition->time, &dfg->last_arrival, + &comparison)) { + av2_dm_rational_destroy(&base); + return false; + } + if (comparison <= 0) { + buffer_size_transition_destroy(transition); + continue; + } + if (write_index != i) { + buffer_size_transition_move(&model->buffer_size_transitions[write_index], + transition); + } + ++write_index; } + av2_dm_rational_move(&model->buffer_size_base, &base); + model->buffer_size_transition_count = write_index; + av2_dm_rational_destroy(&base); + return true; } static bool calculate_arrival_times(Av2DecoderModel *model, Av2DmDfgRecord *dfg) { - if (model->dfg_number == 1 || dfg->parameters_updated) { - if (!rational_zero(&dfg->first_arrival)) return false; + Av2DmRational total_delay = { 0 }; + Av2DmRational latest = { 0 }; + Av2DmRational coded_bits = { 0 }; + Av2DmRational reciprocal_rate = { 0 }; + Av2DmRational arrival_duration = { 0 }; + bool calculated = false; + if (model->dfg_number == 1) { + if (!rational_zero(&dfg->first_arrival)) goto cleanup; } else { - if (!model->previous_dfg_valid) return false; - Av2DmRational total_delay; - Av2DmRational latest; - if (!av2_dm_rational_add(&model->encoder_buffer_delay, - &model->decoder_buffer_delay, &total_delay) || - !av2_dm_rational_subtract(&dfg->scheduled_removal, &total_delay, - &latest) || + if (!model->previous_dfg_valid) goto cleanup; + const Av2DmRational *delay = &model->decoder_buffer_delay; + if (!dfg->first_dfg_of_cvs) { + if (!av2_dm_rational_add(&model->encoder_buffer_delay, + &model->decoder_buffer_delay, &total_delay)) { + goto cleanup; + } + delay = &total_delay; + } + if (!av2_dm_rational_subtract(&dfg->scheduled_removal, delay, &latest) || !rational_max(&model->previous_dfg.last_arrival, &latest, &dfg->first_arrival)) { - return false; + goto cleanup; } } - Av2DmRational coded_bits; - Av2DmRational reciprocal_rate; - Av2DmRational arrival_duration; - if (wide_is_zero(model->limits.bit_rate.magnitude) || + if (av2_dm_rational_is_zero(&dfg->limits.bit_rate) || !av2_dm_rational_make(dfg->coded_bits, 1, &coded_bits)) { - return false; - } - reciprocal_rate.magnitude = model->limits.bit_rate.denominator; - reciprocal_rate.denominator = model->limits.bit_rate.magnitude; - reciprocal_rate.negative = false; - return rational_multiply(&coded_bits, &reciprocal_rate, &arrival_duration) && - av2_dm_rational_add(&dfg->first_arrival, &arrival_duration, - &dfg->last_arrival); + goto cleanup; + } + calculated = + rational_reciprocal(&dfg->limits.bit_rate, &reciprocal_rate) && + rational_multiply(&coded_bits, &reciprocal_rate, &arrival_duration) && + av2_dm_rational_add(&dfg->first_arrival, &arrival_duration, + &dfg->last_arrival); +cleanup: + av2_dm_rational_destroy(&total_delay); + av2_dm_rational_destroy(&latest); + av2_dm_rational_destroy(&coded_bits); + av2_dm_rational_destroy(&reciprocal_rate); + av2_dm_rational_destroy(&arrival_duration); + return calculated; } static bool calculate_scheduled_removal(Av2DecoderModel *model, @@ -1952,63 +3467,78 @@ static bool calculate_scheduled_removal(Av2DecoderModel *model, return false; } if (model->dfg_number == 1) { - dfg->scheduled_removal = model->decoder_buffer_delay; - return true; + return av2_dm_rational_copy(&dfg->scheduled_removal, + &model->decoder_buffer_delay); } if (!model->most_recent_rap_removal_valid) { missing_input(model); return false; } - Av2DmRational offset; - return av2_dm_rational_multiply_u64(&model->dec_ct, - event->buffer_removal_time, &offset) && - av2_dm_rational_add(&model->most_recent_rap_scheduled_removal, &offset, - &dfg->scheduled_removal); + Av2DmRational offset = { 0 }; + const bool calculated = + av2_dm_rational_multiply_u64(&model->dec_ct, event->buffer_removal_time, + &offset) && + av2_dm_rational_add(&model->most_recent_rap_scheduled_removal, &offset, + &dfg->scheduled_removal); + av2_dm_rational_destroy(&offset); + return calculated; } static void check_schedule_delay_limits(Av2DecoderModel *model, - uint64_t event_index) { - if (model->dfg_number != 1 || - model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE) { + const Av2DmDfgRecord *dfg) { + if (model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE || + (model->dfg_number != 1 && !dfg->first_dfg_of_cvs)) { return; } - Av2DmRational zero; + Av2DmRational zero = { 0 }; rational_zero(&zero); if (av2_dm_rational_is_zero(&model->decoder_buffer_delay)) { report_violation(model, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO, - event_index, &model->decoder_buffer_delay, &zero); - } - Av2DmRational reciprocal_rate; - Av2DmRational maximum_delay; - reciprocal_rate.magnitude = model->limits.bit_rate.denominator; - reciprocal_rate.denominator = model->limits.bit_rate.magnitude; - reciprocal_rate.negative = false; - if (!rational_multiply(&model->limits.buffer_size, &reciprocal_rate, + dfg->event_index, &model->decoder_buffer_delay, &zero); + } + Av2DmRational reciprocal_rate = { 0 }; + Av2DmRational maximum_delay = { 0 }; + if (!rational_reciprocal(&model->limits.bit_rate, &reciprocal_rate) || + !rational_multiply(&model->limits.buffer_size, &reciprocal_rate, &maximum_delay)) { arithmetic_failure(model); + av2_dm_rational_destroy(&zero); + av2_dm_rational_destroy(&reciprocal_rate); + av2_dm_rational_destroy(&maximum_delay); return; } compare_upper_limit(model, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_TOO_LARGE, - event_index, &model->decoder_buffer_delay, + dfg->event_index, &model->decoder_buffer_delay, &maximum_delay); + av2_dm_rational_destroy(&zero); + av2_dm_rational_destroy(&reciprocal_rate); + av2_dm_rational_destroy(&maximum_delay); } static void check_delay_consistency(Av2DecoderModel *model, Av2DmDfgRecord *dfg) { - if (model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE || - !dfg->random_access_point || !model->previous_dfg_valid) { + if (!model->previous_dfg_valid || + (!dfg->first_dfg_of_cvs && + (model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE || + !dfg->random_access_point))) { return; } - Av2DmRational time_delta; - Av2DmRational threshold; + Av2DmRational time_delta = { 0 }; + Av2DmRational delay = { 0 }; + Av2DmRational one = { 0 }; + Av2DmRational threshold = { 0 }; if (!av2_dm_rational_subtract(&dfg->scheduled_removal, &model->previous_dfg.last_arrival, &time_delta) || !av2_dm_rational_multiply_u64(&time_delta, 90000, &time_delta) || - model->decoder_buffer_delay_ticks == 0 || - !av2_dm_rational_make(model->decoder_buffer_delay_ticks - 1, 1, - &threshold)) { + !av2_dm_rational_make(model->decoder_buffer_delay_ticks, 1, &delay) || + !av2_dm_rational_make(1, 1, &one) || + !av2_dm_rational_subtract(&delay, &one, &threshold)) { arithmetic_failure(model); + av2_dm_rational_destroy(&time_delta); + av2_dm_rational_destroy(&delay); + av2_dm_rational_destroy(&one); + av2_dm_rational_destroy(&threshold); return; } int comparison; @@ -2027,7 +3557,12 @@ static void check_delay_consistency(Av2DecoderModel *model, model, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT, dfg->event_index, AV2_DM_VIOLATION_AFFECTED_EVENT, dfg->event_index, &time_delta, &threshold, &detail); + violation_detail_destroy(&detail); } + av2_dm_rational_destroy(&time_delta); + av2_dm_rational_destroy(&delay); + av2_dm_rational_destroy(&one); + av2_dm_rational_destroy(&threshold); } typedef struct Av2DmPreparedRebase { @@ -2040,7 +3575,9 @@ static bool add_rebase_target(Av2DmPreparedRebase *prepared, Av2DmRational *target, uint32_t capacity) { if (prepared->count >= capacity) return false; prepared->targets[prepared->count] = target; - prepared->values[prepared->count] = *target; + if (!av2_dm_rational_copy(&prepared->values[prepared->count], target)) { + return false; + } ++prepared->count; return true; } @@ -2048,24 +3585,29 @@ static bool add_rebase_target(Av2DmPreparedRebase *prepared, static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, bool primary, const Av2DmRational *origin, Av2DmPreparedRebase *prepared) { - uint64_t capacity = 2 + 2 * AV2_DM_MAX_BUFFER_POOL_SIZE; + internal_allocation_failed = false; + uint64_t capacity = 2 + 2 * lane->pool.pool_size; if (primary) { - capacity += (uint64_t)5 * model->dfg_count + 16; + capacity += (uint64_t)5 * model->dfg_count + + model->buffer_size_transition_count + 16; } if (capacity > UINT32_MAX || capacity > SIZE_MAX / sizeof(*prepared->values)) { return false; } - prepared->targets = avm_calloc((size_t)capacity, sizeof(*prepared->targets)); - prepared->values = avm_calloc((size_t)capacity, sizeof(*prepared->values)); - if (prepared->targets == NULL || prepared->values == NULL) return false; + prepared->targets = + internal_calloc((size_t)capacity, sizeof(*prepared->targets)); + if (prepared->targets == NULL) return false; + prepared->values = + internal_calloc((size_t)capacity, sizeof(*prepared->values)); + if (prepared->values == NULL) return false; const uint32_t count_limit = (uint32_t)capacity; if (!add_rebase_target(prepared, &lane->time, count_limit) || !add_rebase_target(prepared, &lane->initial_presentation_delay, count_limit)) { return false; } - for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->presentation_time_valid && !add_rebase_target(prepared, &buffer->presentation_time, count_limit)) { @@ -2098,6 +3640,12 @@ static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, return false; } } + for (uint32_t i = 0; i < model->buffer_size_transition_count; ++i) { + if (!add_rebase_target(prepared, &model->buffer_size_transitions[i].time, + count_limit)) { + return false; + } + } if (model->most_recent_rap_removal_valid && !add_rebase_target(prepared, &model->most_recent_rap_scheduled_removal, count_limit)) { @@ -2126,14 +3674,17 @@ static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, } static void free_prepared_rebase(Av2DmPreparedRebase *prepared) { + for (uint32_t i = 0; i < prepared->count; ++i) { + av2_dm_rational_destroy(&prepared->values[i]); + } avm_free(prepared->targets); avm_free(prepared->values); memset(prepared, 0, sizeof(*prepared)); } -static void commit_prepared_rebase(const Av2DmPreparedRebase *prepared) { +static void commit_prepared_rebase(Av2DmPreparedRebase *prepared) { for (uint32_t i = 0; i < prepared->count; ++i) { - *prepared->targets[i] = prepared->values[i]; + av2_dm_rational_move(prepared->targets[i], &prepared->values[i]); } } @@ -2150,9 +3701,9 @@ static void maybe_rebase_model(Av2DecoderModel *model) { Av2DmPreparedRebase resource = { 0 }; // Both lanes participate in the Annex E schedule-vs-resource ordering // comparison, so every absolute lane time must retain one shared origin. - const Av2DmRational origin = model->lane.time; - if (!prepare_lane_rebase(model, &model->lane, true, &origin, &primary) || - !prepare_lane_rebase(model, &model->resource_lane, false, &origin, + const Av2DmRational *const origin = &model->lane.time; + if (!prepare_lane_rebase(model, &model->lane, true, origin, &primary) || + !prepare_lane_rebase(model, &model->resource_lane, false, origin, &resource)) { free_prepared_rebase(&primary); free_prepared_rebase(&resource); @@ -2178,7 +3729,7 @@ static bool earlier_lane_has_generation(const Av2DmLane *const lanes[2], uint64_t generation) { for (uint32_t i = 0; i <= lane_index; ++i) { const uint32_t limit = - i == lane_index ? buffer_index : AV2_DM_MAX_BUFFER_POOL_SIZE; + i == lane_index ? buffer_index : lanes[i]->pool.pool_size; for (uint32_t j = 0; j < limit; ++j) { const Av2DmBuffer *const buffer = &lanes[i]->pool.buffers[j]; if (lane_buffer_is_live(lanes[i], j) && @@ -2194,7 +3745,7 @@ static uint32_t active_generation_count(const Av2DecoderModel *model) { const Av2DmLane *const lanes[2] = { &model->lane, &model->resource_lane }; uint32_t count = 0; for (uint32_t i = 0; i < 2; ++i) { - for (uint32_t j = 0; j < AV2_DM_MAX_BUFFER_POOL_SIZE; ++j) { + for (uint32_t j = 0; j < lanes[i]->pool.pool_size; ++j) { const Av2DmBuffer *const buffer = &lanes[i]->pool.buffers[j]; if (lane_buffer_is_live(lanes[i], j) && !earlier_lane_has_generation(lanes, i, j, buffer->generation)) { @@ -2252,8 +3803,8 @@ static void model_event_complete(Av2DecoderModel *model) { update_storage_stats(model); } -void av2_decoder_model_start_frame(Av2DecoderModel *model, - const Av2DmFrameEvent *event) { +static void decoder_model_start_frame_internal(Av2DecoderModel *model, + const Av2DmFrameEvent *event) { if (model == NULL || event == NULL || model->result.finished || model->result.applicability == AV2_DM_NOT_APPLICABLE || model->processing_stopped) { @@ -2263,6 +3814,10 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, arithmetic_failure(model); return; } + const bool first_dfg_of_cvs = + model->dfg_number != 0 && event->coded_as_closed_loop_key && + (!model->previous_dfg_valid || + model->previous_dfg.temporal_unit_index != event->temporal_unit_index); model->latest_frame_event_index = event->event_index; ++model->frame_number; if (model->coded_tu_valid && model->coded_tu != event->temporal_unit_index) { @@ -2273,12 +3828,42 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, } previous_coded->header_complete = true; } + if (first_dfg_of_cvs) { + const bool old_cvs_already_finalized = model->tile_cvs_finalized; + finalize_tile_cvs(model, event->event_index); + if (model->processing_stopped) return; + if (model->config.defer_nonterminal_checks_for_testing && + !old_cvs_already_finalized) { + check_max_reference_frames(model, event->event_index); + if (model->processing_stopped) return; + } + model->any_decode_count_two_requires_reserved_buffer = false; + model->max_reference_frames_checked = false; + model->max_reference_frames_reserved = false; + model->max_reference_frames_violated = false; + model->maximum_tile_area = 0; + model->retired_header_summary_valid = false; + model->retired_header_summary_reported = false; + model->retired_max_frame_headers = 0; + model->retired_header_event_index = 0; + model->retired_header_limit = 0; + model->tile_cvs_finalized = false; + if (model->cvs_number == UINT64_MAX) { + arithmetic_failure(model); + return; + } + ++model->cvs_number; + } model->coded_tu = event->temporal_unit_index; model->coded_tu_valid = true; Av2DmTuRecord *const tu = get_tu(model, event->temporal_unit_index, event->event_index); if (tu != NULL && event->temporal_unit_output_time_present) { - tu->output_time = event->temporal_unit_output_time; + if (!av2_dm_rational_copy(&tu->output_time, + &event->temporal_unit_output_time)) { + arithmetic_failure(model); + return; + } tu->output_time_valid = true; if (!update_latest_timed_tu(model, &tu->output_time)) { arithmetic_failure(model); @@ -2310,27 +3895,6 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, check_header_rate_windows(model, false, event->event_index); } if (model->processing_stopped) return; - if (model->smoothing_epoch_prepared && - !event->decoder_model_parameters_updated) { - arithmetic_failure(model); - return; - } - if (event->decoder_model_parameters_updated && model->dfg_number != 0 && - !model->smoothing_epoch_prepared) { - // FirstBitArrival restarts at zero when decoder-model parameters change. - // Close the prior smoothing epoch before accepting the new epoch so its - // occupancy cannot be combined with the reset timeline. - if (!model->config.defer_nonterminal_checks_for_testing) { - check_smoothing_buffer_overflow(model, event->event_index); - model->dfg_count = 0; - } - if (model->smoothing_epoch == UINT64_MAX) { - arithmetic_failure(model); - return; - } - ++model->smoothing_epoch; - } - model->smoothing_epoch_prepared = false; if (model->dfg_count == UINT32_MAX || model->dfg_number == UINT64_MAX || (event->random_access_point && model->rap_epoch == UINT64_MAX) || !grow_array((void **)&model->dfgs, &model->dfg_capacity, model->dfg_count, @@ -2339,14 +3903,18 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, return; } Av2DmDfgRecord *const dfg = &model->dfgs[model->dfg_count++]; - memset(dfg, 0, sizeof(*dfg)); + dfg_record_init(dfg); dfg->event_index = event->event_index; dfg->temporal_unit_index = event->temporal_unit_index; dfg->generation = event->generation; dfg->coded_bits = event->coded_bits; dfg->decode_order = model->result.decoded_frames; - dfg->smoothing_epoch = model->smoothing_epoch; - dfg->limits = model->limits; + if (!av2_dm_level_limits_copy(&dfg->limits, &model->limits)) { + dfg_record_destroy(dfg); + --model->dfg_count; + arithmetic_failure(model); + return; + } dfg->tier = model->config.tier; dfg->mode = model->config.mode; dfg->random_access_point = event->random_access_point; @@ -2355,6 +3923,8 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, dfg->decode_count_two = event->allow_global_intrabc && event->inloop_filtering_enabled; dfg->coded_as_closed_loop_key = event->coded_as_closed_loop_key; + dfg->first_dfg_of_cvs = first_dfg_of_cvs; + dfg->still_picture = model->config.still_picture; dfg->num_tiles = event->num_tiles; dfg->max_tile_area = event->max_tile_area; dfg->compressed_size = event->compressed_size_bytes > 128 @@ -2388,7 +3958,10 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, if (!model->result.missing_required_input) arithmetic_failure(model); return; } - dfg->removal = dfg->scheduled_removal; + if (!av2_dm_rational_copy(&dfg->removal, &dfg->scheduled_removal)) { + arithmetic_failure(model); + return; + } bool scheduled_before_arrival; if (!rational_less(&dfg->scheduled_removal, &dfg->last_arrival, &scheduled_before_arrival)) { @@ -2403,20 +3976,39 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, &dfg->last_arrival); } else if (scheduled_before_arrival && model->config.mode == AV2_DM_DECODING_SCHEDULE_MODE && - !rational_ceil_ratio_to_tick(&dfg->last_arrival, &model->dec_ct, - &dfg->removal)) { + !rational_ceil_from_anchor(&dfg->last_arrival, + &dfg->scheduled_removal, &model->dec_ct, + &dfg->removal)) { + arithmetic_failure(model); + return; + } + dfg->buffer_size_decreases_after_removal = + model->pending_buffer_size_change < 0; + if (!add_pending_buffer_size_transition(model, dfg) || + !buffer_size_at(model, &dfg->last_arrival, model->dfg_number, false, true, + &dfg->buffer_size_at_last_arrival) || + !buffer_size_at(model, &dfg->removal, model->dfg_number, false, false, + &dfg->buffer_size_before_removal) || + !buffer_size_at(model, &dfg->removal, model->dfg_number, true, false, + &dfg->buffer_size_after_removal)) { arithmetic_failure(model); return; } if (!model->config.defer_nonterminal_checks_for_testing) { - check_smoothing_buffer_overflow(model, event->event_index); + check_smoothing_buffer_overflow(model, &dfg->last_arrival, + event->event_index); } if (model->processing_stopped) return; + if (!retire_buffer_size_transitions(model, dfg)) { + arithmetic_failure(model); + return; + } - Av2DmRational resource_removal; + Av2DmRational resource_removal = { 0 }; if (!next_resource_removal(model, &model->resource_lane, model->dfg_number - 1, &resource_removal)) { if (!model->result.missing_required_input) arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); return; } int32_t resource_buffer_index; @@ -2424,6 +4016,7 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, &dfg->decode_time, event->generation, &resource_buffer_index)) { arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); return; } if (resource_buffer_index < 0 && @@ -2440,6 +4033,7 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, if (!lane_start_decode(&model->lane, &dfg->removal, &dfg->decode_time, event->generation, &buffer_index)) { arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); return; } if (buffer_index < 0) { @@ -2450,7 +4044,11 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, event->event_index, AV2_DM_VIOLATION_AFFECTED_EVENT, event->event_index, NULL, NULL, &detail); } - dfg->decode_completion = model->lane.time; + if (!av2_dm_rational_copy(&dfg->decode_completion, &model->lane.time)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); + return; + } if (buffer_index >= 0) { Av2DmBuffer *const buffer = &model->lane.pool.buffers[buffer_index]; buffer->decode_order = dfg->decode_order; @@ -2458,6 +4056,13 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, buffer->random_access_point = dfg->random_access_point; buffer->coded_temporal_unit_index = dfg->temporal_unit_index; buffer->coded_temporal_unit_valid = true; + buffer->equal_picture_interval = model->config.equal_picture_interval; + buffer->ticks_per_picture = model->config.ticks_per_picture; + if (!av2_dm_rational_copy(&buffer->disp_ct, &model->disp_ct)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); + return; + } } if (resource_buffer_index >= 0) { Av2DmBuffer *const buffer = @@ -2467,6 +4072,13 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, buffer->random_access_point = dfg->random_access_point; buffer->coded_temporal_unit_index = dfg->temporal_unit_index; buffer->coded_temporal_unit_valid = true; + buffer->equal_picture_interval = model->config.equal_picture_interval; + buffer->ticks_per_picture = model->config.ticks_per_picture; + if (!av2_dm_rational_copy(&buffer->disp_ct, &model->disp_ct)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); + return; + } } if (model->config.mode == AV2_DM_DECODING_SCHEDULE_MODE) { @@ -2477,24 +4089,57 @@ void av2_decoder_model_start_frame(Av2DecoderModel *model, if (model->previous_dfg_valid) { check_previous_dfg_interval(model, &model->previous_dfg, dfg); } - check_schedule_delay_limits(model, event->event_index); + check_schedule_delay_limits(model, dfg); check_delay_consistency(model, dfg); if (model->dfg_number == 1 || event->random_access_point) { - model->most_recent_rap_scheduled_removal = dfg->scheduled_removal; + if (!av2_dm_rational_copy(&model->most_recent_rap_scheduled_removal, + &dfg->scheduled_removal)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); + return; + } model->most_recent_rap_removal_valid = true; } - model->previous_dfg = *dfg; + if (!dfg_record_copy(&model->previous_dfg, dfg)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); + return; + } model->previous_dfg_valid = true; if (!model->config.defer_nonterminal_checks_for_testing) { - retire_closed_smoothing_records(model, &dfg->last_arrival); + if (violation_seen(model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)) { + // The current DFG must remain live until all start-frame processing that + // reads it has completed. Once overflow is proven, no retained fullness + // record can change the CVS verdict. + for (uint32_t i = 0; i < model->dfg_count; ++i) { + dfg_record_destroy(&model->dfgs[i]); + } + model->dfg_count = 0; + } else { + retire_closed_smoothing_records(model, &dfg->last_arrival); + } } retire_unresolvable_tus(model); + av2_dm_rational_destroy(&resource_removal); if (!increment_model_u64(model, &model->result.decoded_frames)) return; model_event_complete(model); update_result_status(model); } +void av2_decoder_model_start_frame(Av2DecoderModel *model, + const Av2DmFrameEvent *event) { + if (model == NULL || event == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return; + decoder_model_start_frame_internal(model, event); + end_model_transaction(model, &transaction); +} + static bool update_lane_reference_buffers( Av2DmLane *lane, const Av2DmReferenceUpdateEvent *event) { for (uint32_t i = 0; i < lane->pool.num_ref_frames; ++i) { @@ -2522,19 +4167,21 @@ void av2_decoder_model_update_reference_buffers( arithmetic_failure(model); return; } + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return; if (!update_lane_reference_buffers(&model->lane, event) || !update_lane_reference_buffers(&model->resource_lane, event)) { arithmetic_failure(model); } retire_unresolvable_tus(model); model_event_complete(model); + end_model_transaction(model, &transaction); } static bool invalidate_lane_reference_buffers(Av2DmLane *lane, uint32_t ref_valid_mask, bool closed_loop_key) { - const uint32_t limit = - closed_loop_key ? AV2_DM_MAX_REF_FRAMES : lane->pool.num_ref_frames; + const uint32_t limit = lane->pool.num_ref_frames; for (uint32_t i = 0; i < limit; ++i) { if ((closed_loop_key || ((ref_valid_mask >> i) & 1) == 0) && lane->pool.vbi[i] != -1 && @@ -2553,8 +4200,9 @@ void av2_decoder_model_invalidate_reference_buffers(Av2DecoderModel *model, model->processing_stopped) { return; } - // DM-SPEC-5 / Annex E invalidate_ref_buffers(): CLK clears every physical - // VBI slot. OLK consults RefValid only in the current active range. + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return; + // Annex E invalidate_ref_buffers() operates on the active VBI range. if (!invalidate_lane_reference_buffers(&model->lane, ref_valid_mask, closed_loop_key) || !invalidate_lane_reference_buffers(&model->resource_lane, ref_valid_mask, @@ -2562,6 +4210,7 @@ void av2_decoder_model_invalidate_reference_buffers(Av2DecoderModel *model, arithmetic_failure(model); } model_event_complete(model); + end_model_transaction(model, &transaction); } static void complete_output_checks(Av2DecoderModel *model, @@ -2600,23 +4249,40 @@ static void complete_output_checks(Av2DecoderModel *model, static bool update_pending_output_witness( Av2DmPendingOutputWitness *pending, uint64_t event_index, const Av2DmRational *observed, const Av2DmRational *presentation_offset) { - Av2DmRational threshold; + Av2DmRational threshold = { 0 }; if (!av2_dm_rational_subtract(observed, presentation_offset, &threshold)) { + av2_dm_rational_destroy(&threshold); return false; } if (pending->valid) { int comparison; if (!av2_dm_rational_compare(&threshold, &pending->threshold, &comparison)) { + av2_dm_rational_destroy(&threshold); return false; } - if (comparison <= 0) return true; + if (comparison <= 0) { + av2_dm_rational_destroy(&threshold); + return true; + } + } + Av2DmRational copied_observed = { 0 }; + Av2DmRational copied_offset = { 0 }; + if (!av2_dm_rational_copy(&copied_observed, observed) || + !av2_dm_rational_copy(&copied_offset, presentation_offset)) { + av2_dm_rational_destroy(&threshold); + av2_dm_rational_destroy(&copied_observed); + av2_dm_rational_destroy(&copied_offset); + return false; } pending->valid = true; pending->event_index = event_index; - pending->threshold = threshold; - pending->observed = *observed; - pending->presentation_offset = *presentation_offset; + av2_dm_rational_move(&pending->threshold, &threshold); + av2_dm_rational_move(&pending->observed, &copied_observed); + av2_dm_rational_move(&pending->presentation_offset, &copied_offset); + av2_dm_rational_destroy(&threshold); + av2_dm_rational_destroy(&copied_observed); + av2_dm_rational_destroy(&copied_offset); return true; } @@ -2631,16 +4297,21 @@ static bool complete_pending_output_check(Av2DecoderModel *model, return false; } if (violated) { - Av2DmRational presentation; + Av2DmRational presentation = { 0 }; if (!av2_dm_rational_add(&pending->presentation_offset, initial_delay, &presentation)) { + av2_dm_rational_destroy(&presentation); return false; } report_violation_for_affected( model, code, proving_event_index, AV2_DM_VIOLATION_AFFECTED_OUTPUT, pending->event_index, &pending->observed, &presentation, NULL); + av2_dm_rational_destroy(&presentation); } pending->valid = false; + av2_dm_rational_destroy(&pending->threshold); + av2_dm_rational_destroy(&pending->observed); + av2_dm_rational_destroy(&pending->presentation_offset); return true; } @@ -2654,9 +4325,11 @@ static bool set_lane_initial_presentation_delay(Av2DecoderModel *model, model->config.initial_display_delay)) { return true; } - lane->initial_presentation_delay = lane->time; + if (!av2_dm_rational_copy(&lane->initial_presentation_delay, &lane->time)) { + return false; + } lane->initial_presentation_delay_known = true; - for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (buffer->player_ref_count != 0 && !buffer->presentation_time_valid) { if (!av2_dm_rational_add(&buffer->presentation_time, @@ -2689,9 +4362,8 @@ static bool set_lane_initial_presentation_delay(Av2DecoderModel *model, return true; } -void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, - bool end_of_bitstream, - uint64_t event_index) { +static void decoder_model_set_initial_presentation_delay_internal( + Av2DecoderModel *model, bool end_of_bitstream, uint64_t event_index) { if (model == NULL || model->result.finished || model->result.applicability == AV2_DM_NOT_APPLICABLE || model->processing_stopped) { @@ -2706,52 +4378,76 @@ void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, model_event_complete(model); } +void av2_decoder_model_set_initial_presentation_delay(Av2DecoderModel *model, + bool end_of_bitstream, + uint64_t event_index) { + if (model == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return; + decoder_model_set_initial_presentation_delay_internal(model, end_of_bitstream, + event_index); + end_model_transaction(model, &transaction); +} + static void check_tu_display_rate(Av2DecoderModel *model, Av2DmTuRecord *tu, const Av2DmRational *duration, uint64_t proving_event_index) { - if (model->config.still_picture) return; - Av2DmRational observed; - Av2DmRational capacity; - if (!av2_dm_rational_multiply_u64(duration, model->limits.max_display_rate, + if (tu->still_picture) return; + Av2DmRational observed = { 0 }; + Av2DmRational capacity = { 0 }; + if (!av2_dm_rational_multiply_u64(duration, tu->limits.max_display_rate, &capacity) || !av2_dm_rational_make(tu->output_luma_samples, 1, &observed)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&capacity); return; } compare_upper_limit_for_affected( model, AV2_DM_VIOLATION_MAX_DISPLAY_RATE, proving_event_index, AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, tu->temporal_unit_index, &observed, &capacity); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&capacity); } static void check_tu_minimum_presentation_interval( Av2DecoderModel *model, Av2DmTuRecord *tu, const Av2DmRational *interval, uint64_t proving_event_index) { - if (model->config.still_picture) return; - Av2DmRational limit; - const uint64_t max_headers = (uint64_t)model->limits.max_header_rate * - (1 + ((uint64_t)model->config.tier << 1)); - Av2DmRational sample_interval; - Av2DmRational min_frame_time; - if (!rational_from_product(model->config.max_frame_width, - model->config.max_frame_height, + if (tu->still_picture) return; + Av2DmRational limit = { 0 }; + const uint64_t max_headers = + (uint64_t)tu->limits.max_header_rate * (1 + ((uint64_t)tu->tier << 1)); + Av2DmRational sample_interval = { 0 }; + Av2DmRational min_frame_time = { 0 }; + if (!rational_from_product(tu->max_frame_width, tu->max_frame_height, &sample_interval) || !av2_dm_rational_multiply_u64(&sample_interval, tu->output_frames, &sample_interval) || - !av2_dm_rational_divide_u64( - &sample_interval, model->limits.max_display_rate, &sample_interval) || - !av2_dm_rational_make(model->limits.max_decode_rate, - model->limits.max_display_rate, &min_frame_time) || + !av2_dm_rational_divide_u64(&sample_interval, tu->limits.max_display_rate, + &sample_interval) || + !av2_dm_rational_make(tu->limits.max_decode_rate, + tu->limits.max_display_rate, &min_frame_time) || !av2_dm_rational_divide_u64(&min_frame_time, max_headers, &min_frame_time) || !rational_max(&sample_interval, &min_frame_time, &limit)) { arithmetic_failure(model); + av2_dm_rational_destroy(&limit); + av2_dm_rational_destroy(&sample_interval); + av2_dm_rational_destroy(&min_frame_time); return; } compare_lower_limit_for_affected( model, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL, proving_event_index, AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, tu->temporal_unit_index, interval, &limit); + av2_dm_rational_destroy(&limit); + av2_dm_rational_destroy(&sample_interval); + av2_dm_rational_destroy(&min_frame_time); } static void update_tu_for_output(Av2DecoderModel *model, @@ -2773,14 +4469,20 @@ static void update_tu_for_output(Av2DecoderModel *model, tu->output_luma_samples += event->output_luma_samples; ++tu->output_frames; if (!tu->presentation_time_valid) { - tu->presentation_time = *presentation_offset; + if (!av2_dm_rational_copy(&tu->presentation_time, presentation_offset)) { + arithmetic_failure(model); + return; + } tu->presentation_time_valid = true; } if (!tu->output_time_valid) { // When no external TU output time was supplied, the first actual output // event establishes the TU output time in display order. Coding-order TU // indices are identifiers and are not timestamps. - tu->output_time = *presentation_offset; + if (!av2_dm_rational_copy(&tu->output_time, presentation_offset)) { + arithmetic_failure(model); + return; + } tu->output_time_valid = true; } if (!update_latest_timed_tu(model, &tu->output_time)) { @@ -2795,16 +4497,18 @@ static void update_tu_for_output(Av2DecoderModel *model, return; } if (previous->presentation_time_valid) { - Av2DmRational presentation_interval; + Av2DmRational presentation_interval = { 0 }; if (!av2_dm_rational_subtract(presentation_offset, &previous->presentation_time, &presentation_interval)) { arithmetic_failure(model); + av2_dm_rational_destroy(&presentation_interval); return; } check_tu_minimum_presentation_interval( model, previous, &presentation_interval, event->event_index); previous->prior_presentation_interval_checked = true; + av2_dm_rational_destroy(&presentation_interval); } if (previous->output_time_valid && tu->output_time_valid) { int ordering; @@ -2814,16 +4518,23 @@ static void update_tu_for_output(Av2DecoderModel *model, return; } output_time_regressed = ordering <= 0; - Av2DmRational display_duration; + Av2DmRational display_duration = { 0 }; if (!av2_dm_rational_subtract(&tu->output_time, &previous->output_time, &display_duration)) { arithmetic_failure(model); + av2_dm_rational_destroy(&display_duration); return; } check_tu_display_rate(model, previous, &display_duration, event->event_index); - model->last_display_duration = display_duration; + if (!av2_dm_rational_copy(&model->last_display_duration, + &display_duration)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&display_duration); + return; + } model->last_display_duration_valid = true; + av2_dm_rational_destroy(&display_duration); } } model->last_output_tu = tu->temporal_unit_index; @@ -2857,7 +4568,11 @@ static void store_rap_presentation_anchor(Av2DecoderModel *model, for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE + 2; ++i) { if (model->rap_presentation_anchors[i].valid && model->rap_presentation_anchors[i].rap_epoch == rap_epoch) { - model->rap_presentation_anchors[i].presentation_offset = *offset; + if (!av2_dm_rational_copy( + &model->rap_presentation_anchors[i].presentation_offset, + offset)) { + arithmetic_failure(model); + } return; } if (!model->rap_presentation_anchors[i].valid && free_anchor == NULL) { @@ -2874,7 +4589,7 @@ static void store_rap_presentation_anchor(Av2DecoderModel *model, bool live = candidate->rap_epoch == model->rap_epoch || (model->rap_epoch != 0 && candidate->rap_epoch == model->rap_epoch - 1); - for (uint32_t j = 0; j < AV2_DM_MAX_BUFFER_POOL_SIZE && !live; ++j) { + for (uint32_t j = 0; j < model->lane.pool.pool_size && !live; ++j) { const Av2DmBuffer *const buffer = &model->lane.pool.buffers[j]; live = buffer->generation_valid && buffer->rap_epoch == candidate->rap_epoch; @@ -2889,33 +4604,38 @@ static void store_rap_presentation_anchor(Av2DecoderModel *model, arithmetic_failure(model); return; } + if (!av2_dm_rational_copy(&free_anchor->presentation_offset, offset)) { + arithmetic_failure(model); + return; + } free_anchor->valid = true; free_anchor->rap_epoch = rap_epoch; - free_anchor->presentation_offset = *offset; } static bool calculate_presentation_offset(Av2DecoderModel *model, const Av2DmOutputEvent *event, const Av2DmBuffer *buffer, Av2DmRational *offset) { - if (model->config.equal_picture_interval) { + if (buffer->equal_picture_interval) { if (!model->last_presentation_offset_valid) return rational_zero(offset); if (event->temporal_unit_index == model->last_output_temporal_unit) { - *offset = model->last_presentation_offset; - return true; + return av2_dm_rational_copy(offset, &model->last_presentation_offset); } - Av2DmRational increment; - return av2_dm_rational_multiply_u64( - &model->disp_ct, model->config.ticks_per_picture, &increment) && - av2_dm_rational_add(&model->last_presentation_offset, &increment, - offset); + Av2DmRational increment = { 0 }; + const bool calculated = + av2_dm_rational_multiply_u64(&buffer->disp_ct, + buffer->ticks_per_picture, &increment) && + av2_dm_rational_add(&model->last_presentation_offset, &increment, + offset); + av2_dm_rational_destroy(&increment); + return calculated; } if (!event->presentation_time_present) { missing_input(model); return false; } if (model->shown_frame_number == 0) return rational_zero(offset); - Av2DmRational base; + Av2DmRational base = { 0 }; bool base_found = false; uint64_t presentation_epoch = model->rap_epoch; bool random_access_point = event->presentation_random_access_point; @@ -2935,24 +4655,35 @@ static bool calculate_presentation_offset(Av2DecoderModel *model, const Av2DmRapPresentationAnchor *const anchor = find_rap_presentation_anchor(model, base_epoch); if (anchor != NULL) { - base = anchor->presentation_offset; + if (!av2_dm_rational_copy(&base, &anchor->presentation_offset)) { + av2_dm_rational_destroy(&base); + return false; + } base_found = true; } } } if (!base_found && event->presentation_base_offset_present) { // Externally seeded RAS frames have no decode record in this model run. - base = event->presentation_base_offset; + if (!av2_dm_rational_copy(&base, &event->presentation_base_offset)) { + av2_dm_rational_destroy(&base); + return false; + } base_found = true; } if (!base_found) { missing_input(model); + av2_dm_rational_destroy(&base); return false; } - Av2DmRational increment; - return av2_dm_rational_multiply_u64( - &model->disp_ct, event->presentation_time_ticks, &increment) && - av2_dm_rational_add(&base, &increment, offset); + Av2DmRational increment = { 0 }; + const bool calculated = + av2_dm_rational_multiply_u64( + &buffer->disp_ct, event->presentation_time_ticks, &increment) && + av2_dm_rational_add(&base, &increment, offset); + av2_dm_rational_destroy(&base); + av2_dm_rational_destroy(&increment); + return calculated; } static int32_t select_output_buffer(Av2DecoderModel *model, Av2DmLane *lane, @@ -2993,8 +4724,8 @@ static int32_t select_output_buffer(Av2DecoderModel *model, Av2DmLane *lane, return lane->pool.vbi[event->frame_to_show_map_idx]; } -void av2_decoder_model_output_frame(Av2DecoderModel *model, - const Av2DmOutputEvent *event) { +static void decoder_model_output_frame_internal(Av2DecoderModel *model, + const Av2DmOutputEvent *event) { if (model == NULL || event == NULL || model->result.finished || model->result.applicability == AV2_DM_NOT_APPLICABLE || model->processing_stopped) { @@ -3019,10 +4750,11 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, missing_input(model); return; } - Av2DmRational presentation_offset; + Av2DmRational presentation_offset = { 0 }; if (!calculate_presentation_offset(model, event, buffer, &presentation_offset)) { if (!model->result.missing_required_input) arithmetic_failure(model); + av2_dm_rational_destroy(&presentation_offset); return; } const uint64_t presentation_epoch = event->presentation_uses_current_frame @@ -3034,31 +4766,54 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, const bool random_access_point = event->presentation_uses_current_frame ? event->presentation_random_access_point : buffer->random_access_point; - Av2DmRational presentation = presentation_offset; + Av2DmRational presentation = { 0 }; + if (!av2_dm_rational_copy(&presentation, &presentation_offset)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&presentation_offset); + av2_dm_rational_destroy(&presentation); + return; + } +#define CLEANUP_OUTPUT_RATIONALS() \ + do { \ + av2_dm_rational_destroy(&presentation_offset); \ + av2_dm_rational_destroy(&presentation); \ + } while (0) if (model->lane.initial_presentation_delay_known) { if (!av2_dm_rational_add(&presentation, &model->lane.initial_presentation_delay, &presentation)) { arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); return; } } - buffer->presentation_time = presentation; + if (!av2_dm_rational_copy(&buffer->presentation_time, &presentation)) { + arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); + return; + } buffer->presentation_time_valid = model->lane.initial_presentation_delay_known; if (!av2_dm_buffer_pool_add_player_ref(&model->lane.pool, (uint32_t)buffer_index)) { arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); return; } - resource_buffer->presentation_time = presentation_offset; + if (!av2_dm_rational_copy(&resource_buffer->presentation_time, + &presentation_offset)) { + arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); + return; + } resource_buffer->presentation_time_valid = false; if (model->resource_lane.initial_presentation_delay_known) { if (!av2_dm_rational_add(&resource_buffer->presentation_time, &model->resource_lane.initial_presentation_delay, &resource_buffer->presentation_time)) { arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); return; } resource_buffer->presentation_time_valid = true; @@ -3066,6 +4821,7 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, if (!av2_dm_buffer_pool_add_player_ref(&model->resource_lane.pool, (uint32_t)resource_buffer_index)) { arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); return; } @@ -3079,19 +4835,27 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, if (model->previous_output_order_valid && buffer->decode_order < model->previous_output_decode_order) { if (!increment_model_u64(model, &model->result.reordered_outputs)) { + CLEANUP_OUTPUT_RATIONALS(); return; } } model->previous_output_decode_order = buffer->decode_order; model->previous_output_order_valid = true; } - model->previous_output_presentation_offset = presentation_offset; + if (!av2_dm_rational_copy(&model->previous_output_presentation_offset, + &presentation_offset) || + !av2_dm_rational_copy(&model->last_presentation_offset, + &presentation_offset) || + (model->lane.initial_presentation_delay_known && + !av2_dm_rational_copy(&model->last_presentation, &presentation))) { + arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); + return; + } model->previous_output_presentation_valid = true; model->previous_output_rap_epoch = output_rap_epoch; - model->last_presentation_offset = presentation_offset; model->last_presentation_offset_valid = true; if (model->lane.initial_presentation_delay_known) { - model->last_presentation = presentation; model->last_presentation_valid = true; } model->last_output_temporal_unit = event->temporal_unit_index; @@ -3100,11 +4864,17 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, &presentation_offset); } update_tu_for_output(model, event, &presentation_offset); - if (model->processing_stopped) return; + if (model->processing_stopped) { + CLEANUP_OUTPUT_RATIONALS(); + return; + } if (!model->config.defer_nonterminal_checks_for_testing) { check_header_rate_windows(model, false, event->event_index); } - if (model->processing_stopped) return; + if (model->processing_stopped) { + CLEANUP_OUTPUT_RATIONALS(); + return; + } if (model->lane.initial_presentation_delay_known) { complete_output_checks( model, event->event_index, event->event_index, &model->lane.time, @@ -3119,58 +4889,102 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, &model->pending_decode_deadline, event->event_index, &buffer->decode_completion_time, &presentation_offset))) { arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); return; } + CLEANUP_OUTPUT_RATIONALS(); +#undef CLEANUP_OUTPUT_RATIONALS if (!increment_output_count(model)) return; model_event_complete(model); update_result_status(model); } +void av2_decoder_model_output_frame(Av2DecoderModel *model, + const Av2DmOutputEvent *event) { + if (model == NULL || event == NULL || model->result.finished || + model->result.applicability == AV2_DM_NOT_APPLICABLE || + model->processing_stopped) { + return; + } + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return; + decoder_model_output_frame_internal(model, event); + end_model_transaction(model, &transaction); +} + static void check_smoothing_fullness_at(Av2DecoderModel *model, const Av2DmRational *time, Av2DmDfgRecord *breakpoint, + const Av2DmRational *buffer_size, + bool after_removal, uint64_t proving_event_index) { - Av2DmRational fullness; + Av2DmRational fullness = { 0 }; if (!rational_zero(&fullness)) { arithmetic_failure(model); + av2_dm_rational_destroy(&fullness); return; } for (uint32_t i = 0; i < model->dfg_count; ++i) { const Av2DmDfgRecord *const dfg = &model->dfgs[i]; - if (dfg->smoothing_epoch != breakpoint->smoothing_epoch) continue; int before_first; - int after_removal; + int removal_order; if (!av2_dm_rational_compare(time, &dfg->first_arrival, &before_first) || - !av2_dm_rational_compare(time, &dfg->removal, &after_removal)) { + !av2_dm_rational_compare(time, &dfg->removal, &removal_order)) { arithmetic_failure(model); + av2_dm_rational_destroy(&fullness); return; } - if (before_first < 0 || after_removal > 0) continue; - Av2DmRational duration; - Av2DmRational arrived; - Av2DmRational coded_bits; + if (before_first < 0 || removal_order > 0 || + (after_removal && removal_order == 0)) { + continue; + } + Av2DmRational duration = { 0 }; + Av2DmRational arrived = { 0 }; + Av2DmRational coded_bits = { 0 }; if (!av2_dm_rational_subtract(time, &dfg->first_arrival, &duration) || - !rational_multiply(&duration, &breakpoint->limits.bit_rate, &arrived) || + !rational_multiply(&duration, &dfg->limits.bit_rate, &arrived) || !av2_dm_rational_make(dfg->coded_bits, 1, &coded_bits)) { arithmetic_failure(model); + av2_dm_rational_destroy(&duration); + av2_dm_rational_destroy(&arrived); + av2_dm_rational_destroy(&coded_bits); + av2_dm_rational_destroy(&fullness); return; } bool too_many; if (!rational_greater(&arrived, &coded_bits, &too_many)) { arithmetic_failure(model); + av2_dm_rational_destroy(&duration); + av2_dm_rational_destroy(&arrived); + av2_dm_rational_destroy(&coded_bits); + av2_dm_rational_destroy(&fullness); + return; + } + if (too_many && !av2_dm_rational_copy(&arrived, &coded_bits)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&duration); + av2_dm_rational_destroy(&arrived); + av2_dm_rational_destroy(&coded_bits); + av2_dm_rational_destroy(&fullness); return; } - if (too_many) arrived = coded_bits; if (!av2_dm_rational_add(&fullness, &arrived, &fullness)) { arithmetic_failure(model); + av2_dm_rational_destroy(&duration); + av2_dm_rational_destroy(&arrived); + av2_dm_rational_destroy(&coded_bits); + av2_dm_rational_destroy(&fullness); return; } + av2_dm_rational_destroy(&duration); + av2_dm_rational_destroy(&arrived); + av2_dm_rational_destroy(&coded_bits); } bool overflow; - if (!rational_greater(&fullness, &breakpoint->limits.buffer_size, - &overflow)) { + if (!rational_greater(&fullness, buffer_size, &overflow)) { arithmetic_failure(model); + av2_dm_rational_destroy(&fullness); return; } if (overflow && !breakpoint->smoothing_overflow_reported) { @@ -3178,8 +4992,9 @@ static void check_smoothing_fullness_at(Av2DecoderModel *model, report_violation_for_affected( model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW, proving_event_index, AV2_DM_VIOLATION_AFFECTED_DFG, breakpoint->event_index, &fullness, - &breakpoint->limits.buffer_size, NULL); + buffer_size, NULL); } + av2_dm_rational_destroy(&fullness); } static void retire_closed_smoothing_records(Av2DecoderModel *model, @@ -3194,31 +5009,62 @@ static void retire_closed_smoothing_records(Av2DecoderModel *model, } // Equality remains live because a later DFG may start arriving at exactly // this frontier and introduce another breakpoint at the same instant. - if (comparison < 0) continue; - if (write_index != i) model->dfgs[write_index] = *dfg; + if (comparison < 0) { + dfg_record_destroy(dfg); + continue; + } + if (write_index != i) { + dfg_record_destroy(&model->dfgs[write_index]); + model->dfgs[write_index] = *dfg; + memset(dfg, 0, sizeof(*dfg)); + } ++write_index; } model->dfg_count = write_index; } static void check_smoothing_buffer_overflow(Av2DecoderModel *model, + const Av2DmRational *frontier, uint64_t proving_event_index) { if (violation_seen(model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)) { - model->dfg_count = 0; return; } for (uint32_t i = 0; i < model->dfg_count; ++i) { Av2DmDfgRecord *const breakpoint = &model->dfgs[i]; - check_smoothing_fullness_at(model, &breakpoint->last_arrival, breakpoint, - proving_event_index); - check_smoothing_fullness_at(model, &breakpoint->removal, breakpoint, - proving_event_index); - } - if (violation_seen(model, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)) { - // Fullness history cannot prove a different code after overflow has made - // this CVS non-conformant. Adjacent-DFG and per-frame checks retain their - // independent scalar state and continue online. - model->dfg_count = 0; + bool last_arrival_reached = true; + bool removal_reached = true; + if (frontier != NULL) { + int comparison; + if (!av2_dm_rational_compare(&breakpoint->last_arrival, frontier, + &comparison)) { + arithmetic_failure(model); + return; + } + last_arrival_reached = comparison <= 0; + if (!av2_dm_rational_compare(&breakpoint->removal, frontier, + &comparison)) { + arithmetic_failure(model); + return; + } + // Equality remains open until the arrival frontier advances: another + // DFG may begin arriving at that instant with a new effective capacity. + removal_reached = comparison < 0; + } + if (last_arrival_reached) { + check_smoothing_fullness_at(model, &breakpoint->last_arrival, breakpoint, + &breakpoint->buffer_size_at_last_arrival, + false, proving_event_index); + } + if (removal_reached) { + check_smoothing_fullness_at(model, &breakpoint->removal, breakpoint, + &breakpoint->buffer_size_before_removal, + false, proving_event_index); + } + if (removal_reached && breakpoint->buffer_size_decreases_after_removal) { + check_smoothing_fullness_at(model, &breakpoint->removal, breakpoint, + &breakpoint->buffer_size_after_removal, true, + proving_event_index); + } } } @@ -3228,34 +5074,12 @@ static bool same_scope(const Av2DmScope *a, const Av2DmScope *b) { a->whole_xlayer == b->whole_xlayer; } -static bool same_model_topology_and_clock(const Av2DmConfig *a, - const Av2DmConfig *b, - bool allow_num_ref_frames_change) { - // A CLK may change only NumRefFrames after its explicit VBI clear-all event. - // Other in-place RAP updates may replace OPS parameters, but not topology or - // the active sequence-level timing fallback. - return same_scope(&a->scope, &b->scope) && - (allow_num_ref_frames_change || - a->num_ref_frames == b->num_ref_frames) && +static bool same_dpb_configuration(const Av2DmConfig *a, const Av2DmConfig *b) { + return a->num_ref_frames == b->num_ref_frames && a->max_frame_width == b->max_frame_width && a->max_frame_height == b->max_frame_height && - a->max_mlayer_id == b->max_mlayer_id && - a->still_picture == b->still_picture && - a->explicit_num_ref_frames == b->explicit_num_ref_frames && - a->timing_info_present == b->timing_info_present && - a->num_units_in_display_tick == b->num_units_in_display_tick && - a->time_scale == b->time_scale && - a->num_units_in_decoding_tick == b->num_units_in_decoding_tick && - a->equal_picture_interval == b->equal_picture_interval && - a->ticks_per_picture == b->ticks_per_picture && - a->sequence_parameters_present == b->sequence_parameters_present && - a->sequence_decoder_buffer_delay == b->sequence_decoder_buffer_delay && - a->sequence_encoder_buffer_delay == b->sequence_encoder_buffer_delay && - a->sequence_low_delay_mode == b->sequence_low_delay_mode && - a->rebase_interval_events == b->rebase_interval_events && - a->defer_nonterminal_checks_for_testing == - b->defer_nonterminal_checks_for_testing && - a->stop_after_first_violation == b->stop_after_first_violation; + a->chroma_format_idc == b->chroma_format_idc && + a->bit_depth == b->bit_depth; } Av2DmParameterUpdateDisposition av2_decoder_model_classify_parameter_update( @@ -3265,96 +5089,197 @@ Av2DmParameterUpdateDisposition av2_decoder_model_classify_parameter_update( model->processing_stopped || model->result.applicability != AV2_DM_APPLICABLE || config->applicability != AV2_DM_APPLICABLE || - config->num_ref_frames == 0 || - config->num_ref_frames > AV2_DM_MAX_REF_FRAMES) { - return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; - } - Av2DmResolvedParameters parameters; - if (!resolve_parameters(config, ¶meters)) { + !parameter_inputs_valid(config)) { return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; } - if (!same_model_topology_and_clock(&model->config, config, - closed_loop_key_transition)) { + const bool incompatible = + !same_scope(&model->config.scope, &config->scope) || + (!closed_loop_key_transition && model->config.mode != config->mode) || + (!closed_loop_key_transition && + !same_dpb_configuration(&model->config, config)); + if (incompatible) { return AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION; } - if (closed_loop_key_transition) { - const Av2DmLane *const lanes[2] = { &model->lane, &model->resource_lane }; - for (uint32_t lane_index = 0; lane_index < 2; ++lane_index) { - for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { - if (lanes[lane_index]->pool.vbi[i] != -1) { - return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; - } - } - } + Av2DmResolvedParameters parameters = { 0 }; + if (!resolve_parameters(config, ¶meters)) { + resolved_parameters_destroy(¶meters); + return AV2_DM_PARAMETER_UPDATE_INTERNAL_FAILURE; } + resolved_parameters_destroy(¶meters); return AV2_DM_PARAMETER_UPDATE_ALLOWED; } -bool av2_decoder_model_update_parameters(Av2DecoderModel *model, - const Av2DmConfig *config, - uint64_t event_index, - bool closed_loop_key_transition) { +static bool decoder_model_update_parameters_internal( + Av2DecoderModel *model, const Av2DmConfig *config, uint64_t event_index, + bool closed_loop_key_transition) { if (model == NULL || config == NULL || model->result.finished || model->processing_stopped) { return false; } - if (av2_decoder_model_classify_parameter_update(model, config, - closed_loop_key_transition) != - AV2_DM_PARAMETER_UPDATE_ALLOWED) { + if (model->result.applicability != AV2_DM_APPLICABLE || + config->applicability != AV2_DM_APPLICABLE || + !parameter_inputs_valid(config)) { + missing_input(model); + return false; + } + if (!same_scope(&model->config.scope, &config->scope) || + (!closed_loop_key_transition && model->config.mode != config->mode) || + (!closed_loop_key_transition && + !same_dpb_configuration(&model->config, config))) { missing_input(model); return false; } - Av2DmResolvedParameters parameters; + Av2DmResolvedParameters parameters = { 0 }; if (!resolve_parameters(config, ¶meters)) { - missing_input(model); + resolved_parameters_destroy(¶meters); + arithmetic_failure(model); + return false; + } + if (closed_loop_key_transition) { + finalize_tile_cvs(model, event_index); + } + if (model->processing_stopped) { + resolved_parameters_destroy(¶meters); return false; } + if (closed_loop_key_transition && + model->config.defer_nonterminal_checks_for_testing) { + check_max_reference_frames(model, event_index); + if (model->processing_stopped) { + resolved_parameters_destroy(¶meters); + return false; + } + } if (!model->config.defer_nonterminal_checks_for_testing) { - // The old smoothing epoch is evaluated with the old BitRate and - // BufferSize before the replacement parameters take effect. - check_smoothing_buffer_overflow(model, event_index); - model->dfg_count = 0; + // Close every evaluable old-parameter breakpoint before prospective + // values take effect. The still-live smoothing history remains continuous. + if (model->previous_dfg_valid) { + check_smoothing_buffer_overflow(model, &model->previous_dfg.last_arrival, + event_index); + } + } + if (model->processing_stopped) { + resolved_parameters_destroy(¶meters); + return false; } - if (model->processing_stopped) return false; - if (model->smoothing_epoch == UINT64_MAX) { + const bool dpb_compatible = same_dpb_configuration(&model->config, config); + if (!dpb_compatible && model->shown_frame_number != 0 && + (!set_lane_initial_presentation_delay(model, &model->lane, true, true, + event_index) || + !set_lane_initial_presentation_delay(model, &model->resource_lane, false, + true, event_index))) { arithmetic_failure(model); + resolved_parameters_destroy(¶meters); + return false; + } + if (model->processing_stopped) { + resolved_parameters_destroy(¶meters); return false; } - ++model->smoothing_epoch; - model->smoothing_epoch_prepared = true; - Av2DmConfig updated = *config; + int buffer_size_comparison; + if (!av2_dm_rational_compare(¶meters.limits.buffer_size, + &model->limits.buffer_size, + &buffer_size_comparison)) { + arithmetic_failure(model); + resolved_parameters_destroy(¶meters); + return false; + } + Av2DmRational pending_buffer_size = { 0 }; + Av2DmConfig updated = { 0 }; + Av2DmLane copied_lane = { 0 }; + const Av2DmMode old_mode = model->config.mode; + if (!av2_dm_rational_copy(&pending_buffer_size, + ¶meters.limits.buffer_size) || + !av2_dm_config_copy(&updated, config) || + (old_mode == AV2_DM_DECODING_SCHEDULE_MODE && + updated.mode == AV2_DM_RESOURCE_AVAILABILITY_MODE && + !lane_copy(&copied_lane, &model->lane))) { + av2_dm_rational_destroy(&pending_buffer_size); + av2_dm_config_destroy(&updated); + lane_destroy(&copied_lane); + resolved_parameters_destroy(¶meters); + arithmetic_failure(model); + return false; + } updated.initial_display_delay = model->config.initial_display_delay; updated.ras_start = model->config.ras_start; updated.ras_seed_complete = model->config.ras_seed_complete; updated.ras_seed_count = model->config.ras_seed_count; memcpy(updated.ras_seeds, model->config.ras_seeds, sizeof(updated.ras_seeds)); - const Av2DmMode old_mode = model->config.mode; - apply_parameters(model, &updated, ¶meters); - if (closed_loop_key_transition) { - model->lane.pool.num_ref_frames = config->num_ref_frames; - model->lane.pool.pool_size = config->num_ref_frames + 2; - model->resource_lane.pool.num_ref_frames = config->num_ref_frames; - model->resource_lane.pool.pool_size = config->num_ref_frames + 2; + if (!apply_parameters(model, &updated, ¶meters)) { + av2_dm_rational_destroy(&pending_buffer_size); + av2_dm_config_destroy(&updated); + lane_destroy(&copied_lane); + resolved_parameters_destroy(¶meters); + arithmetic_failure(model); + return false; + } + model->pending_buffer_size_change = buffer_size_comparison; + av2_dm_rational_move(&model->pending_buffer_size, &pending_buffer_size); + if (!dpb_compatible) { + if (!av2_dm_buffer_pool_initialize(&model->lane.pool, + config->num_ref_frames) || + !av2_dm_buffer_pool_initialize(&model->resource_lane.pool, + config->num_ref_frames)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&pending_buffer_size); + av2_dm_config_destroy(&updated); + lane_destroy(&copied_lane); + resolved_parameters_destroy(¶meters); + return false; + } + model->lane.current_buffer_index = -1; + model->resource_lane.current_buffer_index = -1; } - if (!model->max_reference_frames_violated) { + if (!closed_loop_key_transition && !model->max_reference_frames_violated) { // The maximum depends on the active level limits and must be reconsidered // for the RAP frame after an OPS parameter update. model->max_reference_frames_checked = false; } if (old_mode == AV2_DM_DECODING_SCHEDULE_MODE && updated.mode == AV2_DM_RESOURCE_AVAILABILITY_MODE) { - // The resource lane is maintained for every event. It is therefore the - // continuous resource-availability state when that mode becomes active. - model->lane = model->resource_lane; + // Resource removal starts from the actual continuing decoder state. + if (dpb_compatible) { + lane_destroy(&model->resource_lane); + model->resource_lane = copied_lane; + memset(&copied_lane, 0, sizeof(copied_lane)); + } else { + model->resource_lane.initial_presentation_delay_known = + copied_lane.initial_presentation_delay_known; + av2_dm_rational_move(&model->resource_lane.time, &copied_lane.time); + av2_dm_rational_move(&model->resource_lane.initial_presentation_delay, + &copied_lane.initial_presentation_delay); + } } model->result.mode = updated.mode; update_result_status(model); update_storage_stats(model); + av2_dm_rational_destroy(&pending_buffer_size); + av2_dm_config_destroy(&updated); + lane_destroy(&copied_lane); + resolved_parameters_destroy(¶meters); return true; } +bool av2_decoder_model_update_parameters(Av2DecoderModel *model, + const Av2DmConfig *config, + uint64_t event_index, + bool closed_loop_key_transition) { + if (model == NULL || config == NULL || model->result.finished || + model->processing_stopped) { + return false; + } + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return false; + const bool updated = decoder_model_update_parameters_internal( + model, config, event_index, closed_loop_key_transition); + end_model_transaction(model, &transaction); + return updated && !model->result.allocation_failed && + !model->result.arithmetic_failed; +} + void av2_decoder_model_mark_incomplete(Av2DecoderModel *model) { if (model == NULL || model->result.finished || model->processing_stopped) { return; @@ -3391,11 +5316,13 @@ static void check_max_reference_frames(Av2DecoderModel *model, const uint64_t syntax_maximum = model->config.explicit_num_ref_frames ? 16 : 8; if (maximum > syntax_maximum) maximum = syntax_maximum; - Av2DmRational observed; - Av2DmRational limit; + Av2DmRational observed = { 0 }; + Av2DmRational limit = { 0 }; if (!av2_dm_rational_make(model->config.num_ref_frames, 1, &observed) || !av2_dm_rational_make(maximum, 1, &limit)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } bool too_many_reference_frames; @@ -3406,22 +5333,30 @@ static void check_max_reference_frames(Av2DecoderModel *model, report_violation(model, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES, event_index, &observed, &limit); } + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); } static void check_header_rate_at(Av2DecoderModel *model, Av2DmTuRecord *end_tu, uint64_t frame_headers, - uint64_t maximum_headers, uint64_t proving_event_index) { - Av2DmRational observed; - Av2DmRational limit; + if (end_tu->still_picture) return; + const uint64_t maximum_headers = (uint64_t)end_tu->limits.max_header_rate * + (1 + ((uint64_t)end_tu->tier << 1)); + Av2DmRational observed = { 0 }; + Av2DmRational limit = { 0 }; if (!av2_dm_rational_make(frame_headers, 1, &observed) || !av2_dm_rational_make(maximum_headers, 1, &limit)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } bool violated; if (!rational_greater(&observed, &limit, &violated)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } if (violated && !end_tu->header_rate_reported) { @@ -3431,15 +5366,21 @@ static void check_header_rate_at(Av2DecoderModel *model, Av2DmTuRecord *end_tu, AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, end_tu->temporal_unit_index, &observed, &limit, NULL); } - if (!rational_from_product(model->maximum_tile_area, frame_headers, - &observed) || - !av2_dm_rational_make(model->limits.max_tile_size_header_rate_product, 1, + const uint64_t maximum_tile_area = end_tu->maximum_tile_area_finalized + ? end_tu->maximum_tile_area + : model->maximum_tile_area; + if (!rational_from_product(maximum_tile_area, frame_headers, &observed) || + !av2_dm_rational_make(end_tu->limits.max_tile_size_header_rate_product, 1, &limit)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } if (!rational_greater(&observed, &limit, &violated)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } if (violated && !end_tu->tile_header_rate_reported) { @@ -3449,6 +5390,8 @@ static void check_header_rate_at(Av2DecoderModel *model, Av2DmTuRecord *end_tu, AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, end_tu->temporal_unit_index, &observed, &limit, NULL); } + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); } static void check_retired_tile_header_summary(Av2DecoderModel *model, @@ -3458,18 +5401,21 @@ static void check_retired_tile_header_summary(Av2DecoderModel *model, model->retired_header_summary_reported) { return; } - Av2DmRational observed; - Av2DmRational limit; + Av2DmRational observed = { 0 }; + Av2DmRational limit = { 0 }; if (!rational_from_product(model->maximum_tile_area, model->retired_max_frame_headers, &observed) || - !av2_dm_rational_make(model->limits.max_tile_size_header_rate_product, 1, - &limit)) { + !av2_dm_rational_make(model->retired_header_limit, 1, &limit)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } bool violated; if (!rational_greater(&observed, &limit, &violated)) { arithmetic_failure(model); + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); return; } if (violated) { @@ -3479,22 +5425,40 @@ static void check_retired_tile_header_summary(Av2DecoderModel *model, AV2_DM_VIOLATION_AFFECTED_TEMPORAL_UNIT, model->retired_header_event_index, &observed, &limit, NULL); } + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); +} + +static void finalize_tile_cvs(Av2DecoderModel *model, + uint64_t proving_event_index) { + if (model->tile_cvs_finalized) return; + check_retired_tile_header_summary(model, proving_event_index); + if (model->processing_stopped) return; + for (uint32_t i = 0; i < model->tu_count; ++i) { + Av2DmTuRecord *const tu = &model->tus[i]; + if (!tu->maximum_tile_area_finalized) { + tu->maximum_tile_area = model->maximum_tile_area; + tu->maximum_tile_area_finalized = true; + } + } + model->tile_cvs_finalized = true; } static bool order_tus_by_output_time(const Av2DecoderModel *model, uint32_t **ordered_tus, uint32_t *ordered_tu_count) { + internal_allocation_failed = false; *ordered_tus = NULL; *ordered_tu_count = 0; if (model->tu_count == 0) return true; const uint64_t capacity = model->tu_count; if (capacity > SIZE_MAX / sizeof(**ordered_tus)) return false; const size_t allocation_size = (size_t)capacity * sizeof(**ordered_tus); - uint32_t *source = avm_malloc(allocation_size); - uint32_t *destination = avm_malloc(allocation_size); - if (source == NULL || destination == NULL) { + uint32_t *source = internal_malloc(allocation_size); + if (source == NULL) return false; + uint32_t *destination = internal_malloc(allocation_size); + if (destination == NULL) { avm_free(source); - avm_free(destination); return false; } uint32_t count = 0; @@ -3511,10 +5475,16 @@ static bool order_tus_by_output_time(const Av2DecoderModel *model, size_t second = middle; size_t output = left; while (first < middle && second < right) { + const Av2DmTuRecord *const first_tu = &model->tus[source[first]]; + const Av2DmTuRecord *const second_tu = &model->tus[source[second]]; int comparison; - if (!av2_dm_rational_compare(&model->tus[source[first]].output_time, - &model->tus[source[second]].output_time, - &comparison)) { + if (first_tu->cvs_number < second_tu->cvs_number) { + comparison = -1; + } else if (first_tu->cvs_number > second_tu->cvs_number) { + comparison = 1; + } else if (!av2_dm_rational_compare(&first_tu->output_time, + &second_tu->output_time, + &comparison)) { avm_free(source); avm_free(destination); return false; @@ -3539,20 +5509,27 @@ static bool order_tus_by_output_time(const Av2DecoderModel *model, static void check_header_rate_windows_in_output_order( Av2DecoderModel *model, const uint32_t *ordered_tus, uint32_t ordered_tu_count, const Av2DmRational *one_second, - uint64_t maximum_headers, uint64_t proving_event_index) { + uint64_t proving_event_index) { uint32_t first_tu = 0; uint64_t frame_headers = 0; + uint64_t cvs_number = 0; for (uint32_t i = 0; i < ordered_tu_count; ++i) { Av2DmTuRecord *const end_tu = &model->tus[ordered_tus[i]]; + if (i == 0 || end_tu->cvs_number != cvs_number) { + first_tu = i; + frame_headers = 0; + cvs_number = end_tu->cvs_number; + } if (UINT64_MAX - frame_headers < end_tu->frame_headers) { arithmetic_failure(model); return; } frame_headers += end_tu->frame_headers; - Av2DmRational window_start; + Av2DmRational window_start = { 0 }; if (!av2_dm_rational_subtract(&end_tu->output_time, one_second, &window_start)) { arithmetic_failure(model); + av2_dm_rational_destroy(&window_start); return; } while (first_tu <= i) { @@ -3561,11 +5538,13 @@ static void check_header_rate_windows_in_output_order( if (!av2_dm_rational_compare(&candidate->output_time, &window_start, &comparison)) { arithmetic_failure(model); + av2_dm_rational_destroy(&window_start); return; } if (comparison >= 0) break; if (frame_headers < candidate->frame_headers) { arithmetic_failure(model); + av2_dm_rational_destroy(&window_start); return; } frame_headers -= candidate->frame_headers; @@ -3579,14 +5558,15 @@ static void check_header_rate_windows_in_output_order( end_tu->header_window_checked ? end_tu->header_window_headers : frame_headers, - maximum_headers, proving_event_index); + proving_event_index); + av2_dm_rational_destroy(&window_start); if (model->processing_stopped) return; } } static bool lane_has_live_coded_tu(const Av2DmLane *lane, uint64_t temporal_unit_index) { - for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + for (uint32_t i = 0; i < lane->pool.pool_size; ++i) { const Av2DmBuffer *const buffer = &lane->pool.buffers[i]; if (lane_buffer_is_live(lane, i) && buffer->coded_temporal_unit_valid && buffer->coded_temporal_unit_index == temporal_unit_index) { @@ -3609,7 +5589,8 @@ static void remember_retired_tu(Av2DecoderModel *model, model->retired_unresolved_tu = true; } if (violation_seen(model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE) || - !tu->header_window_checked || tu->tile_header_rate_reported) { + !tu->header_window_checked || tu->tile_header_rate_reported || + tu->maximum_tile_area_finalized) { return; } if (!model->retired_header_summary_valid || @@ -3617,6 +5598,7 @@ static void remember_retired_tu(Av2DecoderModel *model, model->retired_header_summary_valid = true; model->retired_max_frame_headers = tu->header_window_headers; model->retired_header_event_index = tu->temporal_unit_index; + model->retired_header_limit = tu->limits.max_tile_size_header_rate_product; } } @@ -3631,9 +5613,10 @@ static void retire_unresolvable_tus(Av2DecoderModel *model) { !tu_has_live_generation(model, tu->temporal_unit_index); if (retire) { remember_retired_tu(model, tu); + tu_record_destroy(tu); continue; } - if (write_index != i) model->tus[write_index] = *tu; + if (write_index != i) tu_record_move(&model->tus[write_index], tu); ++write_index; } model->tu_count = write_index; @@ -3652,16 +5635,21 @@ static void restart_tu_history(Av2DecoderModel *model, tu_has_live_generation(model, tu->temporal_unit_index); if (!keep_current && !keep_pending) { remember_retired_tu(model, tu); + tu_record_destroy(tu); continue; } - if (write_index != i) model->tus[write_index] = *tu; + if (write_index != i) tu_record_move(&model->tus[write_index], tu); ++write_index; } model->tu_count = write_index; const Av2DmTuRecord *const current = find_tu(model, temporal_unit_index); if (current != NULL && current->output_time_valid) { - model->latest_timed_tu_output_time = current->output_time; - model->latest_timed_tu_valid = true; + if (!av2_dm_rational_copy(&model->latest_timed_tu_output_time, + ¤t->output_time)) { + arithmetic_failure(model); + } else { + model->latest_timed_tu_valid = true; + } } } @@ -3671,11 +5659,12 @@ static void retire_closed_tus(Av2DecoderModel *model, violation_seen(model, AV2_DM_VIOLATION_MAX_HEADER_RATE) && violation_seen(model, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE); if (!header_history_proven && !model->latest_timed_tu_valid) return; - Av2DmRational frontier; + Av2DmRational frontier = { 0 }; if (!header_history_proven && !av2_dm_rational_subtract(&model->latest_timed_tu_output_time, one_second, &frontier)) { arithmetic_failure(model); + av2_dm_rational_destroy(&frontier); return; } uint32_t write_index = 0; @@ -3695,6 +5684,7 @@ static void retire_closed_tus(Av2DecoderModel *model, if (!av2_dm_rational_compare(&tu->output_time, &frontier, &comparison)) { arithmetic_failure(model); + av2_dm_rational_destroy(&frontier); return; } retire = comparison < 0; @@ -3702,53 +5692,53 @@ static void retire_closed_tus(Av2DecoderModel *model, } if (retire) { remember_retired_tu(model, tu); + tu_record_destroy(tu); continue; } - if (write_index != i) model->tus[write_index] = *tu; + if (write_index != i) tu_record_move(&model->tus[write_index], tu); ++write_index; } model->tu_count = write_index; + av2_dm_rational_destroy(&frontier); } static void check_header_rate_windows(Av2DecoderModel *model, bool require_complete, uint64_t proving_event_index) { - if (model->config.still_picture) return; if (!require_complete) { model->latest_header_check_event_index = proving_event_index; } if (require_complete) { for (uint32_t i = 0; i < model->tu_count; ++i) { - if (model->tus[i].frame_headers != 0 && + if (!model->tus[i].still_picture && model->tus[i].frame_headers != 0 && !model->tus[i].output_time_valid) { incomplete_verification(model); return; } } } - Av2DmRational one_second; + Av2DmRational one_second = { 0 }; if (!av2_dm_rational_make(1, 1, &one_second)) { arithmetic_failure(model); return; } - const uint64_t maximum_headers = (uint64_t)model->limits.max_header_rate * - (1 + ((uint64_t)model->config.tier << 1)); uint32_t *ordered_tus; uint32_t ordered_tu_count; if (!order_tus_by_output_time(model, &ordered_tus, &ordered_tu_count)) { arithmetic_failure(model); + av2_dm_rational_destroy(&one_second); return; } check_header_rate_windows_in_output_order( - model, ordered_tus, ordered_tu_count, &one_second, maximum_headers, - proving_event_index); + model, ordered_tus, ordered_tu_count, &one_second, proving_event_index); avm_free(ordered_tus); if (!model->processing_stopped && !require_complete) { retire_closed_tus(model, &one_second); } + av2_dm_rational_destroy(&one_second); } -void av2_decoder_model_finish(Av2DecoderModel *model) { +static void decoder_model_finish_internal(Av2DecoderModel *model) { if (model == NULL || model->result.finished) return; if (model->result.applicability != AV2_DM_NOT_APPLICABLE && !model->processing_stopped) { @@ -3764,7 +5754,8 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { model->shown_frame_number != 0) { incomplete_verification(model); } - if (!model->processing_stopped && !model->config.still_picture && + if (!model->processing_stopped && + (!model->previous_dfg_valid || !model->previous_dfg.still_picture) && model->dfg_number > 1) { if (!model->previous_dfg_valid || !model->last_frame_parsing_time_valid) { incomplete_verification(model); @@ -3776,12 +5767,13 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { model->previous_dfg.event_index); } } - if (!model->processing_stopped && !model->config.still_picture && - model->output_tu_count > 1) { + if (!model->processing_stopped && model->output_tu_count > 1) { Av2DmTuRecord *const last_output_tu = model->last_output_tu_valid ? find_tu(model, model->last_output_tu) : NULL; - if (last_output_tu != NULL) { + if (last_output_tu == NULL) { + incomplete_verification(model); + } else if (!last_output_tu->still_picture) { if (model->last_display_duration_valid) { // Annex A reuses the preceding output duration for the last TU. // Annex E does not synthesize another presentation interval. @@ -3791,13 +5783,11 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { } else { incomplete_verification(model); } - } else { - incomplete_verification(model); } } - if (!model->processing_stopped && - model->config.defer_nonterminal_checks_for_testing) { - check_smoothing_buffer_overflow(model, model->latest_frame_event_index); + if (!model->processing_stopped) { + check_smoothing_buffer_overflow(model, NULL, + model->latest_frame_event_index); } if (!model->processing_stopped && model->config.defer_nonterminal_checks_for_testing) { @@ -3816,6 +5806,19 @@ void av2_decoder_model_finish(Av2DecoderModel *model) { update_storage_stats(model); } +void av2_decoder_model_finish(Av2DecoderModel *model) { + if (model == NULL || model->result.finished) return; + if (model->processing_stopped || + model->result.applicability == AV2_DM_NOT_APPLICABLE) { + decoder_model_finish_internal(model); + return; + } + Av2DmModelTransaction transaction; + if (!begin_model_transaction(model, &transaction)) return; + decoder_model_finish_internal(model); + end_model_transaction(model, &transaction); +} + bool av2_decoder_model_get_result(const Av2DecoderModel *model, Av2DmResult *result) { if (model == NULL || result == NULL) return false; @@ -3823,37 +5826,84 @@ bool av2_decoder_model_get_result(const Av2DecoderModel *model, return true; } +void av2_dm_state_init(Av2DmState *state) { + if (state == NULL) return; + memset(state, 0, sizeof(*state)); + av2_dm_rational_init(&state->time); + av2_dm_rational_init(&state->first_bit_arrival); + av2_dm_rational_init(&state->last_bit_arrival); + av2_dm_rational_init(&state->scheduled_removal); + av2_dm_rational_init(&state->removal); + av2_dm_rational_init(&state->time_to_decode); + av2_dm_rational_init(&state->decode_completion); + av2_dm_rational_init(&state->last_presentation); + av2_dm_rational_init(&state->last_presentation_offset); + av2_dm_rational_init(&state->last_temporal_unit_output_time); + av2_dm_rational_init(&state->initial_presentation_delay); + state->current_buffer_index = -1; +} + +void av2_dm_state_destroy(Av2DmState *state) { + if (state == NULL) return; + av2_dm_rational_destroy(&state->time); + av2_dm_rational_destroy(&state->first_bit_arrival); + av2_dm_rational_destroy(&state->last_bit_arrival); + av2_dm_rational_destroy(&state->scheduled_removal); + av2_dm_rational_destroy(&state->removal); + av2_dm_rational_destroy(&state->time_to_decode); + av2_dm_rational_destroy(&state->decode_completion); + av2_dm_rational_destroy(&state->last_presentation); + av2_dm_rational_destroy(&state->last_presentation_offset); + av2_dm_rational_destroy(&state->last_temporal_unit_output_time); + av2_dm_rational_destroy(&state->initial_presentation_delay); + av2_dm_buffer_pool_destroy(&state->buffer_pool); + memset(state, 0, sizeof(*state)); +} + bool av2_decoder_model_get_state(const Av2DecoderModel *model, Av2DmState *state) { if (model == NULL || state == NULL) return false; - memset(state, 0, sizeof(*state)); - state->time = model->lane.time; - state->initial_presentation_delay_known = + Av2DmState temporary; + av2_dm_state_init(&temporary); + temporary.initial_presentation_delay_known = model->lane.initial_presentation_delay_known; - state->initial_presentation_delay = model->lane.initial_presentation_delay; - state->current_buffer_index = model->lane.current_buffer_index; - state->frame_number = model->frame_number; - state->dfg_number = model->dfg_number; - state->shown_frame_number = model->shown_frame_number; - state->buffer_pool = model->lane.pool; + temporary.current_buffer_index = model->lane.current_buffer_index; + temporary.frame_number = model->frame_number; + temporary.dfg_number = model->dfg_number; + temporary.shown_frame_number = model->shown_frame_number; + bool copied = av2_dm_rational_copy(&temporary.time, &model->lane.time) && + av2_dm_rational_copy(&temporary.initial_presentation_delay, + &model->lane.initial_presentation_delay) && + (!model->lane.pool.initialized || + buffer_pool_copy(&temporary.buffer_pool, &model->lane.pool)); if (model->previous_dfg_valid) { const Av2DmDfgRecord *const dfg = &model->previous_dfg; - state->last_dfg_valid = true; - state->first_bit_arrival = dfg->first_arrival; - state->last_bit_arrival = dfg->last_arrival; - state->scheduled_removal = dfg->scheduled_removal; - state->removal = dfg->removal; - state->time_to_decode = dfg->decode_time; - state->decode_completion = dfg->decode_completion; + temporary.last_dfg_valid = true; + copied = + copied && + av2_dm_rational_copy(&temporary.first_bit_arrival, + &dfg->first_arrival) && + av2_dm_rational_copy(&temporary.last_bit_arrival, &dfg->last_arrival) && + av2_dm_rational_copy(&temporary.scheduled_removal, + &dfg->scheduled_removal) && + av2_dm_rational_copy(&temporary.removal, &dfg->removal) && + av2_dm_rational_copy(&temporary.time_to_decode, &dfg->decode_time) && + av2_dm_rational_copy(&temporary.decode_completion, + &dfg->decode_completion); } if (model->shown_frame_number != 0) { - state->last_presentation_valid = model->last_presentation_valid; - state->last_presentation = model->last_presentation; - state->last_presentation_offset_valid = + temporary.last_presentation_valid = model->last_presentation_valid; + temporary.last_presentation_offset_valid = model->last_presentation_offset_valid; - state->last_presentation_offset = model->last_presentation_offset; - state->last_output_temporal_unit_valid = true; - state->last_output_temporal_unit = model->last_output_temporal_unit; + temporary.last_output_temporal_unit_valid = true; + temporary.last_output_temporal_unit = model->last_output_temporal_unit; + copied = copied && + (!model->last_presentation_valid || + av2_dm_rational_copy(&temporary.last_presentation, + &model->last_presentation)) && + (!model->last_presentation_offset_valid || + av2_dm_rational_copy(&temporary.last_presentation_offset, + &model->last_presentation_offset)); } if (model->last_output_tu_valid) { const Av2DmTuRecord *tu = NULL; @@ -3863,12 +5913,24 @@ bool av2_decoder_model_get_state(const Av2DecoderModel *model, break; } } - if (tu == NULL) return false; - state->last_temporal_unit_output_time_valid = tu->output_time_valid; - state->last_temporal_unit_output_time = tu->output_time; - state->last_temporal_unit_output_luma_samples = tu->output_luma_samples; - state->last_temporal_unit_output_frames = tu->output_frames; + if (tu == NULL) copied = false; + if (tu != NULL) { + temporary.last_temporal_unit_output_time_valid = tu->output_time_valid; + temporary.last_temporal_unit_output_luma_samples = + tu->output_luma_samples; + temporary.last_temporal_unit_output_frames = tu->output_frames; + copied = copied && + (!tu->output_time_valid || + av2_dm_rational_copy(&temporary.last_temporal_unit_output_time, + &tu->output_time)); + } + } + if (!copied) { + av2_dm_state_destroy(&temporary); + return false; } + av2_dm_state_destroy(state); + *state = temporary; return true; } diff --git a/av2/common/decoder_model.h b/av2/common/decoder_model.h index 0e2b7ed5c4..33510df6f0 100644 --- a/av2/common/decoder_model.h +++ b/av2/common/decoder_model.h @@ -32,12 +32,24 @@ typedef struct Av2DmUnsignedWide { // Exact signed rational used for all normative decoder-model decisions. // denominator is positive. Zero is canonicalized to 0/1 with negative false. +// The type owns dynamic_limbs. A destination passed to any operation below +// must first be initialized or zero-initialized and must eventually be +// destroyed. Operations that produce a value replace the destination only on +// success and permit it to alias an input unless stated otherwise. typedef struct Av2DmRational { Av2DmUnsignedWide magnitude; Av2DmUnsignedWide denominator; + uint64_t *dynamic_limbs; + uint32_t magnitude_limb_count; + uint32_t denominator_limb_count; bool negative; } Av2DmRational; +void av2_dm_rational_init(Av2DmRational *value); +bool av2_dm_rational_copy(Av2DmRational *destination, + const Av2DmRational *source); +void av2_dm_rational_move(Av2DmRational *destination, Av2DmRational *source); +void av2_dm_rational_destroy(Av2DmRational *value); bool av2_dm_rational_make(uint64_t numerator, uint64_t denominator, Av2DmRational *result); bool av2_dm_rational_make_wide(Av2DmUnsignedWide numerator, @@ -54,9 +66,19 @@ bool av2_dm_rational_divide_u64(const Av2DmRational *value, uint64_t divisor, Av2DmRational *result); bool av2_dm_rational_compare(const Av2DmRational *left, const Av2DmRational *right, int *comparison); +bool av2_dm_rational_get_component(const Av2DmRational *value, bool denominator, + const uint64_t **limbs, + uint32_t *limb_count); bool av2_dm_rational_rebase(Av2DmRational *values, uint32_t value_count, const Av2DmRational *origin); bool av2_dm_rational_is_zero(const Av2DmRational *value); +void av2_dm_rational_set_allocation_failure_after_for_testing( + int64_t successful_allocations); +void av2_dm_set_internal_allocation_failure_after_for_testing( + int64_t successful_allocations); +uint64_t av2_dm_rational_allocation_count_for_testing(void); +bool av2_dm_rational_last_failure_was_allocation(void); +bool av2_dm_last_failure_was_allocation(void); typedef struct Av2DmBuffer { uint32_t decoder_ref_count; @@ -73,6 +95,9 @@ typedef struct Av2DmBuffer { bool random_access_point; uint64_t coded_temporal_unit_index; bool coded_temporal_unit_valid; + bool equal_picture_interval; + uint32_t ticks_per_picture; + Av2DmRational disp_ct; } Av2DmBuffer; typedef struct Av2DmBufferPool { @@ -80,10 +105,15 @@ typedef struct Av2DmBufferPool { uint32_t pool_size; int32_t vbi[AV2_DM_MAX_REF_FRAMES]; Av2DmBuffer buffers[AV2_DM_MAX_BUFFER_POOL_SIZE]; + bool initialized; } Av2DmBufferPool; +void av2_dm_buffer_pool_init(Av2DmBufferPool *pool); +// pool must first be initialized with av2_dm_buffer_pool_init or be +// zero-initialized. Reinitialization is permitted. Call destroy when done. bool av2_dm_buffer_pool_initialize(Av2DmBufferPool *pool, uint32_t num_ref_frames); +void av2_dm_buffer_pool_destroy(Av2DmBufferPool *pool); int32_t av2_dm_buffer_pool_get_free_buffer(const Av2DmBufferPool *pool); bool av2_dm_buffer_pool_release(Av2DmBufferPool *pool, uint32_t buffer_index); bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, @@ -235,6 +265,8 @@ typedef struct Av2DmLevelLimits { Av2DmRational buffer_size; } Av2DmLevelLimits; +void av2_dm_level_limits_init(Av2DmLevelLimits *limits); + typedef struct Av2DmRasSeed { uint32_t ref_index; uint64_t generation; @@ -250,6 +282,8 @@ typedef struct Av2DmConfig { uint32_t num_ref_frames; uint32_t max_frame_width; uint32_t max_frame_height; + uint32_t chroma_format_idc; + uint32_t bit_depth; uint32_t max_mlayer_id; bool still_picture; bool explicit_num_ref_frames; @@ -291,6 +325,16 @@ typedef struct Av2DmConfig { bool stop_after_first_violation; } Av2DmConfig; +// These types own their embedded rational values. A destination passed to a +// copy or replacement operation must first be initialized or zero-initialized. +// Call the matching destroy function when done. +void av2_dm_level_limits_destroy(Av2DmLevelLimits *limits); +bool av2_dm_level_limits_copy(Av2DmLevelLimits *destination, + const Av2DmLevelLimits *source); +void av2_dm_config_init(Av2DmConfig *config); +void av2_dm_config_destroy(Av2DmConfig *config); +bool av2_dm_config_copy(Av2DmConfig *destination, const Av2DmConfig *source); + typedef struct Av2DmFrameEvent { uint64_t event_index; uint64_t temporal_unit_index; @@ -354,6 +398,13 @@ typedef struct Av2DmViolation { Av2DmViolationDetail detail; } Av2DmViolation; +void av2_dm_violation_init(Av2DmViolation *violation); +// The destination must first be initialized or zero-initialized. A successful +// copy is independent of the source and must be destroyed. +bool av2_dm_violation_copy(Av2DmViolation *destination, + const Av2DmViolation *source); +void av2_dm_violation_destroy(Av2DmViolation *violation); + typedef struct Av2DmResult { Av2DmResultStatus status; Av2DmApplicability applicability; @@ -363,6 +414,7 @@ typedef struct Av2DmResult { uint64_t output_frames; uint64_t reordered_outputs; uint64_t violations; + bool allocation_failed; bool arithmetic_failed; bool missing_required_input; bool finished; @@ -396,6 +448,10 @@ typedef struct Av2DmState { Av2DmBufferPool buffer_pool; } Av2DmState; +void av2_dm_state_init(Av2DmState *state); +// State passed to get_state must first be initialized. Call destroy when done. +void av2_dm_state_destroy(Av2DmState *state); + // Private verifier-storage instrumentation used by decoder-model tests. These // counters describe live normative state, not allocated capacity or lifetime // event totals, and do not affect conformance decisions. @@ -419,9 +475,12 @@ typedef void (*Av2DmReportFn)(void *opaque, const Av2DmViolation *violation); typedef enum Av2DmParameterUpdateDisposition { AV2_DM_PARAMETER_UPDATE_ALLOWED, AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT, - AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION + AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION, + AV2_DM_PARAMETER_UPDATE_INTERNAL_FAILURE } Av2DmParameterUpdateDisposition; +// limits must first be initialized or zero-initialized. Both functions replace +// it only on success; call av2_dm_level_limits_destroy when done. bool av2_dm_get_level_limits(uint32_t level_idx, uint32_t tier, uint32_t profile, Av2DmLevelLimits *limits); bool av2_dm_apply_multistream_limits(uint32_t level_idx, uint32_t tier, @@ -459,6 +518,7 @@ void av2_decoder_model_output_frame(Av2DecoderModel *model, void av2_decoder_model_finish(Av2DecoderModel *model); bool av2_decoder_model_get_result(const Av2DecoderModel *model, Av2DmResult *result); +// state must first be initialized with av2_dm_state_init. bool av2_decoder_model_get_state(const Av2DecoderModel *model, Av2DmState *state); bool av2_decoder_model_get_storage_stats(const Av2DecoderModel *model, diff --git a/av2/common/level.c b/av2/common/level.c index 9d51dc2a18..542242f240 100644 --- a/av2/common/level.c +++ b/av2/common/level.c @@ -22,6 +22,8 @@ #include "av2/encoder/encoder.h" #include "av2/common/annexA.h" +static uint32_t encoder_ref_valid_mask(const AV2_COMMON *cm); + /* clang-format off */ #define UNDEFINED_LEVEL \ { .level = SEQ_LEVEL_MAX, \ @@ -588,18 +590,22 @@ static bool get_max_bitrate_rational(const AV2LevelSpec *const level_spec, level_spec->level < SEQ_LEVEL_4_0) { return false; } - Av2DmRational base; - Av2DmRational scaled; - return av2_dm_rational_make((uint64_t)base_rate, 1, &base) && - av2_dm_rational_multiply_u64(&base, scale_denominator, &scaled) && - av2_dm_rational_divide_u64(&scaled, scale_numerator, bit_rate); + Av2DmRational base = { 0 }; + Av2DmRational scaled = { 0 }; + const bool converted = + av2_dm_rational_make((uint64_t)base_rate, 1, &base) && + av2_dm_rational_multiply_u64(&base, scale_denominator, &scaled) && + av2_dm_rational_divide_u64(&scaled, scale_numerator, bit_rate); + av2_dm_rational_destroy(&base); + av2_dm_rational_destroy(&scaled); + return converted; } -static long double unsigned_wide_to_long_double( - const Av2DmUnsignedWide *value) { +static long double unsigned_limbs_to_long_double(const uint64_t *limbs, + uint32_t limb_count) { long double result = 0.0L; - for (int i = 3; i >= 0; --i) { - result = ldexpl(result, 64) + value->limbs[i]; + for (uint32_t i = limb_count; i > 0; --i) { + result = ldexpl(result, 64) + limbs[i - 1]; } return result; } @@ -611,10 +617,21 @@ static bool is_finite_number(long double value) { static bool rational_to_long_double(const Av2DmRational *value, long double *result) { if (value == NULL || result == NULL || value->negative) return false; + const uint64_t *magnitude_limbs; + const uint64_t *denominator_limbs; + uint32_t magnitude_count; + uint32_t denominator_count; + if (!av2_dm_rational_get_component(value, false, &magnitude_limbs, + &magnitude_count) || + !av2_dm_rational_get_component(value, true, &denominator_limbs, + &denominator_count)) { + return false; + } const long double denominator = - unsigned_wide_to_long_double(&value->denominator); + unsigned_limbs_to_long_double(denominator_limbs, denominator_count); if (!(denominator > 0.0L)) return false; - *result = unsigned_wide_to_long_double(&value->magnitude) / denominator; + *result = unsigned_limbs_to_long_double(magnitude_limbs, magnitude_count) / + denominator; return is_finite_number(*result); } @@ -631,13 +648,14 @@ static bool rational_to_double(const Av2DmRational *value, double *result) { static double get_max_bitrate(const AV2LevelSpec *const level_spec, int tier, BITSTREAM_PROFILE profile, double multistream_scaling_x) { - Av2DmRational bit_rate; + Av2DmRational bit_rate = { 0 }; double result; - return get_max_bitrate_rational(level_spec, tier, profile, - multistream_scaling_x, &bit_rate) && - rational_to_double(&bit_rate, &result) - ? result - : 0.0; + const bool converted = + get_max_bitrate_rational(level_spec, tier, profile, multistream_scaling_x, + &bit_rate) && + rational_to_double(&bit_rate, &result); + av2_dm_rational_destroy(&bit_rate); + return converted ? result : 0.0; } double av2_get_max_bitrate_for_level(AV2_LEVEL level_index, int tier, @@ -726,15 +744,14 @@ bool av2_encoder_decoder_model_push_dfg_interval(DECODER_MODEL *decoder_model, bool av2_encoder_decoder_model_smoothing_buffer_fits( const DECODER_MODEL *decoder_model, uint64_t coded_bits, bool *fits) { if (decoder_model == NULL || fits == NULL) return false; - Av2DmRational fullness; + Av2DmRational fullness = { 0 }; int comparison; - if (!av2_dm_rational_make(coded_bits, 1, &fullness) || - !av2_dm_rational_compare(&fullness, &decoder_model->buffer_size, - &comparison)) { - return false; - } - *fits = comparison <= 0; - return true; + const bool ok = av2_dm_rational_make(coded_bits, 1, &fullness) && + av2_dm_rational_compare( + &fullness, &decoder_model->buffer_size, &comparison); + if (ok) *fits = comparison <= 0; + av2_dm_rational_destroy(&fullness); + return ok; } bool av2_encoder_decoder_model_arrival_fits(const DECODER_MODEL *decoder_model, @@ -849,13 +866,12 @@ bool av2_encoder_decoder_model_get_compressed_size( void av2_encoder_decoder_model_destroy(DECODER_MODEL *decoder_model) { if (decoder_model == NULL) return; + av2_decoder_model_destroy(decoder_model->exact_model); + av2_dm_level_limits_destroy(&decoder_model->level_limits); + av2_dm_rational_destroy(&decoder_model->bit_rate); + av2_dm_rational_destroy(&decoder_model->buffer_size); avm_free(decoder_model->dfg_interval_queue.buf); - decoder_model->dfg_interval_queue.buf = NULL; - decoder_model->dfg_interval_queue.head = 0; - decoder_model->dfg_interval_queue.size = 0; - decoder_model->dfg_interval_queue.capacity = 0; - decoder_model->dfg_interval_queue.total_interval = 0.0; - decoder_model->dfg_interval_queue.total_bits = 0; + memset(decoder_model, 0, sizeof(*decoder_model)); } void av2_encoder_decoder_models_destroy(AV2LevelInfo *level_info) { @@ -898,7 +914,7 @@ static int get_free_buffer(DECODER_MODEL *const decoder_model) { static bool release_decoder_reference(DECODER_MODEL *const decoder_model, int buffer_index) { - if (buffer_index < 0 || buffer_index >= BUFFER_POOL_MAX_SIZE) { + if (buffer_index < 0 || buffer_index >= decoder_model->num_ref_frames + 2) { return false; } FRAME_BUFFER *const buffer = &decoder_model->frame_buffer_pool[buffer_index]; @@ -920,13 +936,11 @@ bool av2_encoder_decoder_model_invalidate_ref_buffers( if (decoder_model->num_ref_frames < 1 || decoder_model->num_ref_frames > REF_FRAMES || cm->seq_params.ref_frames < 1 || cm->seq_params.ref_frames > REF_FRAMES || - (!closed_loop_key && - cm->seq_params.ref_frames != decoder_model->num_ref_frames)) { + cm->seq_params.ref_frames != decoder_model->num_ref_frames) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } - const int limit = - closed_loop_key ? REF_FRAMES : decoder_model->num_ref_frames; + const int limit = decoder_model->num_ref_frames; for (int i = 0; i < limit; ++i) { if ((closed_loop_key || cm->ref_frame_map[i] == NULL) && decoder_model->vbi[i] != -1) { @@ -957,6 +971,11 @@ void av2_decoder_model_invalidate_olk_ref_buffers_for_operating_points( level_params->level_info[op]->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { DECODER_MODEL *const decoder_model = &decoder_models[level]; + if (decoder_model->exact_model != NULL) { + av2_decoder_model_invalidate_reference_buffers( + decoder_model->exact_model, encoder_ref_valid_mask(&cpi->common), + false); + } if (decoder_model->status != DECODER_MODEL_OK) continue; av2_encoder_decoder_model_invalidate_ref_buffers(&cpi->common, decoder_model, false); @@ -1168,7 +1187,7 @@ static double time_to_decode_frame(const AV2_COMMON *const cm, // It corresponds to "start_decode_at_removal_time" in the spec. static void release_processed_frames(DECODER_MODEL *const decoder_model, double removal_time) { - for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { + for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->player_ref_count > 0) { // Presentation offsets assigned before the initial delay is known are @@ -1187,7 +1206,7 @@ static void release_processed_frames(DECODER_MODEL *const decoder_model, static int frames_in_buffer_pool(const DECODER_MODEL *const decoder_model) { int frames_in_pool = 0; - for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { + for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { const FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->decoder_ref_count > 0 || @@ -1215,9 +1234,7 @@ static void decoder_model_set_initial_presentation_delay( decoder_model->presentation_time += decoder_model->initial_presentation_delay; } - // Presentation times in every physical buffer are provisional until the - // initial delay is known, including buffers outside the active range. - for (int i = 0; i < BUFFER_POOL_MAX_SIZE; ++i) { + for (int i = 0; i < decoder_model->num_ref_frames + 2; ++i) { FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->player_ref_count == 0) continue; assert(this_buffer->display_index >= 0); @@ -1241,11 +1258,13 @@ static double time_next_buffer_is_free_with_source( const FRAME_BUFFER *const this_buffer = &decoder_model->frame_buffer_pool[i]; if (this_buffer->decoder_ref_count == 0) { - if (this_buffer->player_ref_count == 0) { + const double presentation_time = this_buffer->presentation_time; + if (this_buffer->player_ref_count == 0 || + (presentation_time >= 0.0 && + presentation_time <= decoder_model->current_time)) { if (from_current_time != NULL) *from_current_time = true; return decoder_model->current_time; } - const double presentation_time = this_buffer->presentation_time; if (presentation_time >= 0.0 && presentation_time < buf_free_time) { buf_free_time = presentation_time; } @@ -1270,6 +1289,152 @@ static double get_removal_time(const DECODER_MODEL *const decoder_model, } } +static bool exact_model_next_event(DECODER_MODEL *const decoder_model, + uint64_t *const event_index) { + if (decoder_model->exact_event_index == UINT64_MAX) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return false; + } + *event_index = ++decoder_model->exact_event_index; + return true; +} + +static bool exact_model_is_conformant( + const DECODER_MODEL *const decoder_model) { + Av2DmResult result; + return decoder_model->exact_model != NULL && + av2_decoder_model_get_result(decoder_model->exact_model, &result) && + result.status == AV2_DM_RESULT_CONFORMANT; +} + +static bool build_exact_model_config(const AV2_COMP *const cpi, + const DECODER_MODEL *const decoder_model, + Av2DmConfig *const config) { + const AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + const int op = decoder_model->operating_point; + av2_dm_config_init(config); + config->scope.xlayer_id = cm->xlayer_id; + config->scope.ops_xlayer_id = -1; + config->scope.ops_id = -1; + config->scope.operating_point = op; + config->scope.whole_xlayer = false; + config->applicability = AV2_DM_APPLICABLE; + config->level_idx = decoder_model->level; + config->tier = cpi->tier[op]; + config->profile = seq_params->seq_profile_idc; + config->stop_after_first_violation = true; + config->num_ref_frames = seq_params->ref_frames; + config->explicit_num_ref_frames = true; + config->max_frame_width = seq_params->max_frame_width; + config->max_frame_height = seq_params->max_frame_height; + if (av2_get_chroma_format_idc( + seq_params->subsampling_x, seq_params->subsampling_y, + seq_params->monochrome, &config->chroma_format_idc) != AVM_CODEC_OK) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + config->bit_depth = seq_params->bit_depth; + config->max_mlayer_id = seq_params->max_mlayer_id; + config->still_picture = seq_params->still_picture != 0; + config->timing_info_present = + cm->ci_params_encoder.ci_timing_info_present_flag != 0; + config->num_units_in_display_tick = + cm->ci_params_encoder.timing_info.num_units_in_display_tick; + config->time_scale = cm->ci_params_encoder.timing_info.time_scale; + config->num_units_in_decoding_tick = + seq_params->decoder_model_info.num_units_in_decoding_tick; + config->equal_picture_interval = + cm->ci_params_encoder.timing_info.equal_elemental_interval != 0; + config->ticks_per_picture = + cm->ci_params_encoder.timing_info.num_ticks_per_elemental_duration; + config->initial_display_delay = + seq_params->seq_max_display_model_info_present_flag + ? (uint32_t)seq_params->seq_max_initial_display_delay_minus_1 + 1 + : (uint32_t)seq_params->ref_frames + 2; + config->sequence_parameters_present = + seq_params->seq_max_decoder_model_present_flag != 0; + config->sequence_decoder_buffer_delay = + seq_params->seq_max_decoder_buffer_delay; + config->sequence_encoder_buffer_delay = + seq_params->seq_max_encoder_buffer_delay; + config->sequence_low_delay_mode = + seq_params->seq_max_low_delay_mode_flag != 0; + if (op >= 0 && op <= MAX_NUM_OPERATING_POINTS) { + const avm_dec_model_op_parameters_t *const op_params = + &seq_params->op_params[op]; + config->operating_point_parameters_present = + op_params->decoder_model_param_present_flag != 0; + config->operating_point_decoder_buffer_delay = + op_params->decoder_buffer_delay; + config->operating_point_encoder_buffer_delay = + op_params->encoder_buffer_delay; + config->operating_point_low_delay_mode = + op_params->low_delay_mode_flag != 0; + } + if (config->sequence_parameters_present || + config->operating_point_parameters_present) { + config->mode = AV2_DM_DECODING_SCHEDULE_MODE; + } else { + config->mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + } + if (config->mode == AV2_DM_DECODING_SCHEDULE_MODE && + !seq_params->decoder_model_info_present_flag) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + config->level_limits_present = true; + if (!av2_dm_level_limits_copy(&config->level_limits, + &decoder_model->level_limits)) { + return false; + } + if (decoder_model->multistream_scale_numerator != + decoder_model->multistream_scale_denominator && + !av2_dm_apply_multistream_limits( + decoder_model->level, decoder_model->tier, config->profile, + decoder_model->multistream_scale_numerator, + decoder_model->multistream_scale_denominator, + &config->level_limits)) { + if (av2_dm_last_failure_was_allocation()) return false; + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + if (!av2_dm_rational_copy(&config->level_limits.bit_rate, + &decoder_model->bit_rate) || + !av2_dm_rational_copy(&config->level_limits.buffer_size, + &decoder_model->buffer_size)) { + return false; + } + return true; +} + +static Av2DecoderModel *create_exact_model( + const AV2_COMP *const cpi, const DECODER_MODEL *const decoder_model) { + Av2DmConfig config = { 0 }; + if (!build_exact_model_config(cpi, decoder_model, &config)) { + av2_dm_config_destroy(&config); + return NULL; + } + Av2DecoderModel *const model = av2_decoder_model_create(&config, NULL, NULL); + av2_dm_config_destroy(&config); + return model; +} + +static bool update_exact_model_parameters(const AV2_COMP *const cpi, + DECODER_MODEL *const decoder_model) { + if (decoder_model->exact_model == NULL) return false; + Av2DmConfig config = { 0 }; + uint64_t event_index; + if (!build_exact_model_config(cpi, decoder_model, &config) || + !exact_model_next_event(decoder_model, &event_index)) { + av2_dm_config_destroy(&config); + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return false; + } + const bool updated = av2_decoder_model_update_parameters( + decoder_model->exact_model, &config, event_index, true); + av2_dm_config_destroy(&config); + decoder_model->exact_parameters_updated = updated; + return updated; +} + void av2_decoder_model_print_status(const DECODER_MODEL *const decoder_model) { printf("\n status %d, num_frame %3" PRId64 ", num_decoded_frame %3" PRId64 ", num_shown_frame %3" PRId64 @@ -1294,7 +1459,10 @@ void av2_decoder_model_print_status(const DECODER_MODEL *const decoder_model) { void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, int op_index, DECODER_MODEL *const decoder_model) { avm_clear_system_state(); - memset(decoder_model, 0, sizeof(*decoder_model)); + av2_encoder_decoder_model_destroy(decoder_model); + av2_dm_level_limits_init(&decoder_model->level_limits); + av2_dm_rational_init(&decoder_model->bit_rate); + av2_dm_rational_init(&decoder_model->buffer_size); decoder_model->status = DECODER_MODEL_OK; decoder_model->level = level; decoder_model->operating_point = op_index; @@ -1311,6 +1479,14 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, decoder_model->configured_profile = seq_params->seq_profile_idc; decoder_model->configured_max_frame_width = seq_params->max_frame_width; decoder_model->configured_max_frame_height = seq_params->max_frame_height; + if (av2_get_chroma_format_idc( + seq_params->subsampling_x, seq_params->subsampling_y, + seq_params->monochrome, + &decoder_model->configured_chroma_format_idc) != AVM_CODEC_OK) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->configured_bit_depth = seq_params->bit_depth; decoder_model->configured_max_mlayer_id = seq_params->max_mlayer_id; decoder_model->configured_number_xlayers = cm->number_xlayers; decoder_model->configured_timing_info_present = @@ -1323,7 +1499,7 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, } uint32_t scale_numerator; uint32_t scale_denominator; - Av2DmRational bit_rate; + Av2DmRational bit_rate = { 0 }; if (seq_params->seq_profile_idc == CONFIGURABLE || !get_multistream_scale(cpi->level_params.multi_stream_scaling_x, &scale_numerator, &scale_denominator) || @@ -1339,7 +1515,7 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, } decoder_model->multistream_scale_numerator = scale_numerator; decoder_model->multistream_scale_denominator = scale_denominator; - decoder_model->bit_rate = bit_rate; + av2_dm_rational_move(&decoder_model->bit_rate, &bit_rate); if (!av2_dm_rational_multiply_u64(&decoder_model->level_limits.buffer_size, scale_denominator, &decoder_model->buffer_size) || @@ -1424,6 +1600,7 @@ void av2_decoder_model_init(const AV2_COMP *const cpi, AV2_LEVEL level, decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return; } + decoder_model->exact_model = create_exact_model(cpi, decoder_model); decoder_model->initialized = true; } @@ -1470,11 +1647,20 @@ static bool check_frame_constraints_at_decode_limit( const uint32_t scale_numerator = decoder_model->multistream_scale_numerator; const uint32_t scale_denominator = decoder_model->multistream_scale_denominator; - Av2DmRational observed; - Av2DmRational limit; + Av2DmRational observed = { 0 }; + Av2DmRational limit = { 0 }; + Av2DmRational dynamic_tile_limit = { 0 }; + Av2DmRational max_tile_limit = { 0 }; + Av2DmRational one = { 0 }; + Av2DmRational picture_compressed_limit = { 0 }; + Av2DmRational rate_compressed_limit = { 0 }; + Av2DmRational symbol_factor_a = { 0 }; + Av2DmRational symbol_factor_b = { 0 }; bool satisfies; + bool success = false; + uint64_t picture_units; - if (frame_parsing_time_decode_luma_samples == 0) return false; + if (frame_parsing_time_decode_luma_samples == 0) goto cleanup; const long double max_decode_rate = (long double)limits->max_decode_rate * scale_denominator / scale_numerator; const long double observed_decode_rate = @@ -1482,16 +1668,13 @@ static bool check_frame_constraints_at_decode_limit( frame_parsing_time_decode_luma_samples; if (!is_finite_number(max_decode_rate) || !is_finite_number(observed_decode_rate)) { - return false; + goto cleanup; } decoder_model->max_decode_rate = AVMMAX(decoder_model->max_decode_rate, observed_decode_rate); decoder_model->max_decode_rate_satisfy &= frame->luma_sample_count <= frame_parsing_time_decode_luma_samples; - Av2DmRational dynamic_tile_limit; - Av2DmRational max_tile_limit; - Av2DmRational one; if (!av2_dm_rational_make(frame_parsing_time_decode_luma_samples, 1, &dynamic_tile_limit) || !av2_dm_rational_multiply_u64(&dynamic_tile_limit, @@ -1505,33 +1688,36 @@ static bool check_frame_constraints_at_decode_limit( !av2_dm_rational_divide_u64(&max_tile_limit, scale_numerator, &max_tile_limit) || !av2_dm_rational_make(1, 1, &one)) { - return false; + goto cleanup; } int comparison; if (!av2_dm_rational_compare(&dynamic_tile_limit, &one, &comparison)) { - return false; + goto cleanup; + } + if (comparison < 0 && !av2_dm_rational_copy(&dynamic_tile_limit, &one)) { + goto cleanup; } - if (comparison < 0) dynamic_tile_limit = one; if (!av2_dm_rational_compare(&dynamic_tile_limit, &max_tile_limit, &comparison)) { - return false; + goto cleanup; + } + if (comparison > 0 && + !av2_dm_rational_copy(&dynamic_tile_limit, &max_tile_limit)) { + goto cleanup; } - if (comparison > 0) dynamic_tile_limit = max_tile_limit; if (!av2_dm_rational_make(frame->num_tiles, 1, &observed) || !encoder_dm_rational_less_than_or_equal(&observed, &dynamic_tile_limit, &satisfies)) { - return false; + goto cleanup; } decoder_model->max_tile_rate_satisfy &= satisfies; if (frame->luma_sample_count > UINT64_MAX / limits->picture_size_profile_factor) { - return false; + goto cleanup; } - const uint64_t picture_units = + picture_units = frame->luma_sample_count * limits->picture_size_profile_factor >> 3; - Av2DmRational picture_compressed_limit; - Av2DmRational rate_compressed_limit; if (!av2_dm_rational_make(picture_units, 1, &picture_compressed_limit) || !av2_dm_rational_multiply_u64(&picture_compressed_limit, 5, &picture_compressed_limit) || @@ -1547,20 +1733,21 @@ static bool check_frame_constraints_at_decode_limit( &rate_compressed_limit) || !av2_dm_rational_compare(&picture_compressed_limit, &rate_compressed_limit, &comparison)) { - return false; + goto cleanup; + } + if (!av2_dm_rational_copy(&limit, comparison <= 0 ? &picture_compressed_limit + : &rate_compressed_limit)) { + goto cleanup; } - limit = comparison <= 0 ? picture_compressed_limit : rate_compressed_limit; if (frame->compressed_size > 0) { if (!av2_dm_rational_make((uint64_t)frame->compressed_size, 1, &observed) || !encoder_dm_rational_less_than_or_equal(&observed, &limit, &satisfies)) { - return false; + goto cleanup; } decoder_model->compressed_size_satisfy &= satisfies; } - Av2DmRational symbol_factor_a; - Av2DmRational symbol_factor_b; if (!av2_dm_rational_make(8, (uint64_t)9 * limits->min_compression_basis, &symbol_factor_a) || !av2_dm_rational_make(1, 48, &symbol_factor_b) || @@ -1571,10 +1758,22 @@ static bool check_frame_constraints_at_decode_limit( &limit) || !av2_dm_rational_make(frame->frame_symbol_count, 1, &observed) || !encoder_dm_rational_less_than_or_equal(&observed, &limit, &satisfies)) { - return false; + goto cleanup; } decoder_model->frame_symbol_count_satisfy &= satisfies; - return true; + success = true; + +cleanup: + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); + av2_dm_rational_destroy(&dynamic_tile_limit); + av2_dm_rational_destroy(&max_tile_limit); + av2_dm_rational_destroy(&one); + av2_dm_rational_destroy(&picture_compressed_limit); + av2_dm_rational_destroy(&rate_compressed_limit); + av2_dm_rational_destroy(&symbol_factor_a); + av2_dm_rational_destroy(&symbol_factor_b); + return success; } bool av2_encoder_decoder_model_check_frame_constraints( @@ -1771,7 +1970,7 @@ static bool get_minimum_presentation_interval( const BITSTREAM_PROFILE profile = decoder_model->configuration_snapshot_valid ? decoder_model->configured_profile : seq_params->seq_profile_idc; - Av2DmLevelLimits limits; + Av2DmLevelLimits limits = { 0 }; if (!av2_dm_get_level_limits(decoder_model->level, decoder_model->tier, profile, &limits)) { return false; @@ -1784,6 +1983,7 @@ static bool get_minimum_presentation_interval( !av2_dm_apply_multistream_limits( decoder_model->level, decoder_model->tier, profile, scale_numerator, scale_denominator, &limits))) { + av2_dm_level_limits_destroy(&limits); return false; } @@ -1799,20 +1999,28 @@ static bool get_minimum_presentation_interval( decoder_model->num_frames_current_tu == 0 || limits.max_display_rate == 0 || limits.max_decode_rate == 0 || limits.max_header_rate == 0) { + av2_dm_level_limits_destroy(&limits); return false; } const uint64_t max_frame_width = (uint32_t)configured_max_frame_width; const uint64_t max_frame_height = (uint32_t)configured_max_frame_height; - if (max_frame_width > UINT64_MAX / max_frame_height) return false; + if (max_frame_width > UINT64_MAX / max_frame_height) { + av2_dm_level_limits_destroy(&limits); + return false; + } const uint64_t max_frame_samples = max_frame_width * max_frame_height; if (max_frame_samples > UINT64_MAX / decoder_model->num_frames_current_tu) { + av2_dm_level_limits_destroy(&limits); return false; } const uint64_t output_samples = max_frame_samples * decoder_model->num_frames_current_tu; const uint64_t tier_multiplier = 1 + ((uint64_t)decoder_model->tier << 1); - if (limits.max_header_rate > UINT64_MAX / tier_multiplier) return false; + if (limits.max_header_rate > UINT64_MAX / tier_multiplier) { + av2_dm_level_limits_destroy(&limits); + return false; + } const uint64_t max_frame_headers_per_second = limits.max_header_rate * tier_multiplier; @@ -1822,8 +2030,11 @@ static bool get_minimum_presentation_interval( (double)limits.max_decode_rate / ((double)max_frame_headers_per_second * (double)limits.max_display_rate); *min_interval = AVMMAX(sample_interval, min_frame_time); - return is_finite_number(sample_interval) && - is_finite_number(min_frame_time) && is_finite_number(*min_interval); + const bool finite = is_finite_number(sample_interval) && + is_finite_number(min_frame_time) && + is_finite_number(*min_interval); + av2_dm_level_limits_destroy(&limits); + return finite; } void av2_encoder_decoder_model_finalize(DECODER_MODEL *decoder_model, @@ -1861,49 +2072,66 @@ static bool decoder_model_configuration_is_compatible( replacement->configured_operating_point_count && current->configured_operating_point_idc == replacement->configured_operating_point_idc && - current->configured_max_frame_width == - replacement->configured_max_frame_width && - current->configured_max_frame_height == - replacement->configured_max_frame_height && current->configured_max_mlayer_id == replacement->configured_max_mlayer_id && current->configured_number_xlayers == - replacement->configured_number_xlayers && - current->configured_timing_info_present == - replacement->configured_timing_info_present && - current->configured_num_units_in_display_tick == - replacement->configured_num_units_in_display_tick && - current->configured_time_scale == replacement->configured_time_scale && - current->equal_picture_interval == - replacement->equal_picture_interval && - current->num_ticks_per_picture == replacement->num_ticks_per_picture && - current->display_clock_tick == replacement->display_clock_tick; + replacement->configured_number_xlayers; } static bool update_decoder_model_parameters_at_clk( const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model) { - for (int i = 0; i < REF_FRAMES; ++i) { - if (decoder_model->vbi[i] != -1) { - decoder_model->status = DECODER_MODEL_INCOMPLETE; - return false; - } - } - - DECODER_MODEL replacement; + DECODER_MODEL replacement = { 0 }; av2_decoder_model_init(cpi, decoder_model->level, decoder_model->operating_point, &replacement); if (replacement.status != DECODER_MODEL_OK || !replacement.initialized) { + av2_encoder_decoder_model_destroy(&replacement); decoder_model->status = DECODER_MODEL_INCOMPLETE; return false; } if (!decoder_model_configuration_is_compatible(decoder_model, &replacement)) { + av2_encoder_decoder_model_destroy(&replacement); decoder_model->status = DECODER_MODEL_UNSUPPORTED; return false; } + int buffer_size_comparison; + if (!av2_dm_rational_compare(&decoder_model->buffer_size, + &replacement.buffer_size, + &buffer_size_comparison)) { + av2_encoder_decoder_model_destroy(&replacement); + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + const bool dpb_compatible = + decoder_model->num_ref_frames == replacement.num_ref_frames && + decoder_model->configured_max_frame_width == + replacement.configured_max_frame_width && + decoder_model->configured_max_frame_height == + replacement.configured_max_frame_height && + decoder_model->configured_chroma_format_idc == + replacement.configured_chroma_format_idc && + decoder_model->configured_bit_depth == replacement.configured_bit_depth; + if (!dpb_compatible && decoder_model->initial_presentation_delay < 0.0 && + decoder_model->num_shown_frame >= 0) { + decoder_model_set_initial_presentation_delay(decoder_model, true, false); + } decoder_model->tier = replacement.tier; decoder_model->configured_profile = replacement.configured_profile; + decoder_model->configured_max_frame_width = + replacement.configured_max_frame_width; + decoder_model->configured_max_frame_height = + replacement.configured_max_frame_height; + decoder_model->configured_chroma_format_idc = + replacement.configured_chroma_format_idc; + decoder_model->configured_bit_depth = replacement.configured_bit_depth; + decoder_model->configured_timing_info_present = + replacement.configured_timing_info_present; + decoder_model->configured_num_units_in_display_tick = + replacement.configured_num_units_in_display_tick; + decoder_model->configured_time_scale = replacement.configured_time_scale; + av2_dm_level_limits_destroy(&decoder_model->level_limits); decoder_model->level_limits = replacement.level_limits; + av2_dm_level_limits_init(&replacement.level_limits); decoder_model->encoder_buffer_delay = replacement.encoder_buffer_delay; decoder_model->decoder_buffer_delay = replacement.decoder_buffer_delay; decoder_model->multistream_scale_numerator = @@ -1911,25 +2139,99 @@ static bool update_decoder_model_parameters_at_clk( decoder_model->multistream_scale_denominator = replacement.multistream_scale_denominator; decoder_model->decode_rate = replacement.decode_rate; - decoder_model->bit_rate = replacement.bit_rate; - decoder_model->buffer_size = replacement.buffer_size; + decoder_model->equal_picture_interval = replacement.equal_picture_interval; + decoder_model->num_ticks_per_picture = replacement.num_ticks_per_picture; + decoder_model->display_clock_tick = replacement.display_clock_tick; + av2_dm_rational_move(&decoder_model->bit_rate, &replacement.bit_rate); + av2_dm_rational_move(&decoder_model->buffer_size, &replacement.buffer_size); decoder_model->mode = replacement.mode; decoder_model->is_low_delay_mode = replacement.is_low_delay_mode; decoder_model->num_ref_frames = replacement.num_ref_frames; - - decoder_model->first_bit_arrival_time = 0.0; - decoder_model->last_bit_arrival_time = 0.0; - decoder_model->dfg_interval_queue.head = 0; - decoder_model->dfg_interval_queue.size = 0; - decoder_model->dfg_interval_queue.total_interval = 0.0; - decoder_model->dfg_interval_queue.total_bits = 0; + decoder_model->exact_buffer_size_history_required |= + buffer_size_comparison != 0; + if (!dpb_compatible) { + initialize_buffer_pool(decoder_model); + decoder_model->cfbi = -1; + } + if (decoder_model->num_decoded_frame >= 0) { + update_exact_model_parameters(cpi, decoder_model); + } + av2_encoder_decoder_model_destroy(&replacement); return true; } +static uint32_t exact_model_ref_valid_mask( + const DECODER_MODEL *const decoder_model) { + uint32_t mask = 0; + for (int i = 0; i < decoder_model->num_ref_frames; ++i) { + if (decoder_model->vbi[i] != -1) mask |= 1u << i; + } + return mask; +} + +static void observe_exact_model_frame(const AV2_COMP *const cpi, + uint64_t coded_bits, + uint64_t compressed_size_bytes, + uint64_t max_tile_area, + uint64_t max_tile_width, + bool non_rightmost_tile_width_valid, + DECODER_MODEL *const decoder_model) { + if (decoder_model->exact_model == NULL) return; + const AV2_COMMON *const cm = &cpi->common; + uint64_t event_index; + if (!exact_model_next_event(decoder_model, &event_index) || + (!cm->show_existing_frame && + decoder_model->next_generation == UINT64_MAX)) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return; + } + Av2DmFrameEvent event = { 0 }; + event.event_index = event_index; + event.temporal_unit_index = decoder_model->temporal_unit_index; + event.ref_valid_mask = exact_model_ref_valid_mask(decoder_model); + event.generation = + cm->show_existing_frame ? 0 : decoder_model->next_generation + 1; + event.coded_bits = coded_bits; + event.show_existing_frame = cm->show_existing_frame != 0; + event.random_access_point = + !cm->show_existing_frame && + (cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY || + cm->current_frame.cm_obu_type == OBU_OPEN_LOOP_KEY || + cm->current_frame.cm_obu_type == OBU_RAS_FRAME); + event.coded_as_closed_loop_key = + cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY; + event.frame_is_intra = cm->current_frame.frame_type == KEY_FRAME || + cm->current_frame.frame_type == INTRA_ONLY_FRAME; + event.allow_global_intrabc = cm->features.allow_global_intrabc != 0; + event.inloop_filtering_enabled = is_filter_enabled_frame(cm); + event.frame_width = cm->width; + event.frame_height = cm->height; + event.num_tiles = (uint32_t)cm->tiles.rows * (uint32_t)cm->tiles.cols; + event.tile_columns = cm->tiles.cols; + event.max_tile_width = max_tile_width; + event.max_tile_area = max_tile_area; + event.non_rightmost_tile_width_valid = non_rightmost_tile_width_valid; + // The encoder does not yet generate normative BufferRemovalTime values. + event.buffer_removal_time_present = false; + event.decoder_model_parameters_updated = + decoder_model->exact_parameters_updated; + event.count_frame_header = cpi->level_params.frame_header_count != 0; + event.compressed_size_bytes = compressed_size_bytes; + event.frame_symbol_count = cm->features.frame_symbol_count; + av2_decoder_model_start_frame(decoder_model->exact_model, &event); + decoder_model->exact_parameters_updated = false; +} + static void av2_decoder_model_start_frame_decode( const AV2_COMP *const cpi, uint64_t dfg_bits, int64_t compressed_size, + uint64_t compressed_size_bytes, uint64_t max_tile_area, + uint64_t max_tile_width, bool non_rightmost_tile_width_valid, DECODER_MODEL *const decoder_model) { - if (!decoder_model || decoder_model->status != DECODER_MODEL_OK) return; + if (decoder_model == NULL) return; + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return; + } avm_clear_system_state(); @@ -1938,14 +2240,21 @@ static void av2_decoder_model_start_frame_decode( const bool closed_loop_key = cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY && cpi->dm_starts_temporal_unit; - if (closed_loop_key && !av2_encoder_decoder_model_invalidate_ref_buffers( - cm, decoder_model, true)) { - return; - } if (closed_loop_key && !update_decoder_model_parameters_at_clk(cpi, decoder_model)) { return; } + if (closed_loop_key && decoder_model->num_decoded_frame >= 0 && + decoder_model->exact_model != NULL) { + av2_decoder_model_invalidate_reference_buffers( + decoder_model->exact_model, exact_model_ref_valid_mask(decoder_model), + true); + } + if (closed_loop_key && !av2_encoder_decoder_model_invalidate_ref_buffers( + cm, decoder_model, true)) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return; + } if (cpi->dm_starts_temporal_unit) { if (decoder_model->temporal_unit_started) { if (decoder_model->temporal_unit_index == UINT64_MAX) { @@ -1992,6 +2301,9 @@ static void av2_decoder_model_start_frame_decode( decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return; } + observe_exact_model_frame(cpi, closed_dfg_bits, compressed_size_bytes, + max_tile_area, max_tile_width, + non_rightmost_tile_width_valid, decoder_model); if (!show_existing_frame) { decoder_model->mirrored_refresh_frame_flags = 0; @@ -2025,9 +2337,12 @@ static void av2_decoder_model_start_frame_decode( } // A frame with show_existing_frame being false indicates the end of a DFG. // Update the bits arrival time of this DFG. - const double buffer_delay = (decoder_model->encoder_buffer_delay + - decoder_model->decoder_buffer_delay) / - 90000.0; + const double buffer_delay = + (closed_loop_key && decoder_model->num_decoded_frame > 0 + ? decoder_model->decoder_buffer_delay + : decoder_model->encoder_buffer_delay + + decoder_model->decoder_buffer_delay) / + 90000.0; const double latest_arrival_time = removal_time - buffer_delay; decoder_model->first_bit_arrival_time = AVMMAX(decoder_model->last_bit_arrival_time, latest_arrival_time); @@ -2050,7 +2365,8 @@ static void av2_decoder_model_start_frame_decode( decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return; } - if (!dfg_available_at_removal && !decoder_model->is_low_delay_mode) { + if (!dfg_available_at_removal && !decoder_model->is_low_delay_mode && + !exact_model_is_conformant(decoder_model)) { decoder_model->status = SMOOTHING_BUFFER_UNDERFLOW; return; } @@ -2058,20 +2374,26 @@ static void av2_decoder_model_start_frame_decode( DFG_INTERVAL_QUEUE *const queue = &decoder_model->dfg_interval_queue; const double first_bit_arrival_time = decoder_model->first_bit_arrival_time; const double last_bit_arrival_time = decoder_model->last_bit_arrival_time; + const bool buffer_size_checked_by_exact = + exact_model_is_conformant(decoder_model) || + (decoder_model->exact_model != NULL && + decoder_model->exact_buffer_size_history_required); // Remove the DFGs with removal time earlier than last_bit_arrival_time. while (queue->size > 0 && queue->buf[queue->head].removal_time <= last_bit_arrival_time) { - bool smoothing_buffer_fits; - if (!smoothing_buffer_fits_with_partial_arrival( - decoder_model, queue->total_bits, closed_dfg_bits, - queue->buf[queue->head].removal_time - first_bit_arrival_time, - &smoothing_buffer_fits)) { - decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; - return; - } - if (!smoothing_buffer_fits) { - decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; - return; + if (!buffer_size_checked_by_exact) { + bool smoothing_buffer_fits; + if (!smoothing_buffer_fits_with_partial_arrival( + decoder_model, queue->total_bits, closed_dfg_bits, + queue->buf[queue->head].removal_time - first_bit_arrival_time, + &smoothing_buffer_fits)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + if (!smoothing_buffer_fits) { + decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; + return; + } } if (queue->buf[queue->head].coded_bits > queue->total_bits) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; @@ -2092,15 +2414,17 @@ static void av2_decoder_model_start_frame_decode( decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return; } - bool smoothing_buffer_fits; - if (!av2_encoder_decoder_model_smoothing_buffer_fits( - decoder_model, queue->total_bits, &smoothing_buffer_fits)) { - decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; - return; - } - if (!smoothing_buffer_fits) { - decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; - return; + if (!buffer_size_checked_by_exact) { + bool smoothing_buffer_fits; + if (!av2_encoder_decoder_model_smoothing_buffer_fits( + decoder_model, queue->total_bits, &smoothing_buffer_fits)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + if (!smoothing_buffer_fits) { + decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; + return; + } } release_processed_frames(decoder_model, removal_time); @@ -2125,7 +2449,9 @@ static void av2_decoder_model_start_frame_decode( static void av2_decoder_model_update_buffer_and_finish_frame_decode( const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model) { - if (decoder_model == NULL || decoder_model->status != DECODER_MODEL_OK) { + if (decoder_model == NULL) return; + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); return; } const AV2_COMMON *const cm = &cpi->common; @@ -2148,6 +2474,33 @@ static void av2_decoder_model_update_buffer_and_finish_frame_decode( show_existing_frame); } } + +static uint32_t encoder_ref_valid_mask(const AV2_COMMON *const cm) { + uint32_t mask = 0; + for (int i = 0; i < cm->seq_params.ref_frames; ++i) { + if (cm->ref_frame_map[i] != NULL) mask |= 1u << i; + } + return mask; +} + +static void observe_exact_model_reference_update( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model) { + if (decoder_model->exact_model == NULL) return; + const AV2_COMMON *const cm = &cpi->common; + if (!cm->show_existing_frame) { + const Av2DmReferenceUpdateEvent event = { + cm->current_frame.refresh_frame_flags, encoder_ref_valid_mask(cm) + }; + av2_decoder_model_update_reference_buffers(decoder_model->exact_model, + &event); + } + uint64_t event_index; + if (exact_model_next_event(decoder_model, &event_index)) { + av2_decoder_model_set_initial_presentation_delay(decoder_model->exact_model, + false, event_index); + } +} + void av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( const AV2_COMP *const cpi) { const AV2_COMMON *const cm = &cpi->common; @@ -2166,6 +2519,7 @@ void av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_point AV2LevelInfo *const level_info = level_params->level_info[i]; DECODER_MODEL *const decoder_models = level_info->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + observe_exact_model_reference_update(cpi, &decoder_models[level]); av2_decoder_model_update_buffer_and_finish_frame_decode( cpi, &decoder_models[level]); } @@ -2280,8 +2634,40 @@ static bool get_presentation_offset( return is_finite_number(*offset); } +static void observe_exact_model_output( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, int ref_idx, + int buffer_index, + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation) { + if (decoder_model->exact_model == NULL || buffer_index < 0 || + buffer_index >= decoder_model->num_ref_frames + 2) { + return; + } + const ENCODER_DM_PRESENTATION_DESCRIPTOR *const generation = + &decoder_model->frame_buffer_pool[buffer_index].presentation; + uint64_t event_index; + if (!generation->valid || + !exact_model_next_event(decoder_model, &event_index)) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return; + } + Av2DmOutputEvent event = { 0 }; + event.event_index = event_index; + event.temporal_unit_index = presentation->temporal_unit_index; + event.generation = generation->generation; + event.frame_to_show_map_idx = ref_idx; + event.ref_valid_mask = encoder_ref_valid_mask(&cpi->common); + event.output_luma_samples = presentation->output_luma_samples; + event.leading_frame = presentation->leading_frame; + event.presentation_uses_current_frame = + ref_idx < 0 && !cpi->common.show_existing_frame; + event.presentation_random_access_point = presentation->random_access_point; + event.presentation_time_present = presentation->presentation_time_present; + event.presentation_time_ticks = presentation->presentation_time_ticks; + av2_decoder_model_output_frame(decoder_model->exact_model, &event); +} + static void av2_decoder_model_check_output_frame( - const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, int ref_idx, int buffer_index, const ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation, bool completes_implicit_output) { @@ -2302,6 +2688,8 @@ static void av2_decoder_model_check_output_frame( decoder_model->status = DECODER_MODEL_INCOMPLETE; return; } + observe_exact_model_output(cpi, decoder_model, ref_idx, buffer_index, + presentation); double presentation_offset; if (!get_presentation_offset(decoder_model, presentation, &presentation_offset)) { @@ -2363,7 +2751,8 @@ static void av2_decoder_model_check_output_frame( return; } this_buffer->presentation_time = presentation_time; - if (decoder_model->initial_presentation_delay >= 0.0) { + if (decoder_model->initial_presentation_delay >= 0.0 && + !exact_model_is_conformant(decoder_model)) { if (presentation_time >= 0.0 && decoder_model->current_time > presentation_time) { decoder_model->status = DISPLAY_FRAME_LATE; @@ -2471,14 +2860,15 @@ static void decoder_model_observe_output_frame_buffers( if (output_ref_idx == trigger_ref_idx) break; ENCODER_DM_PRESENTATION_DESCRIPTOR *const output_presentation = &decoder_model->frame_buffer_pool[output_buffer_index].presentation; - av2_decoder_model_check_output_frame( - cpi, decoder_model, output_buffer_index, output_presentation, true); + av2_decoder_model_check_output_frame(cpi, decoder_model, output_ref_idx, + output_buffer_index, + output_presentation, true); } if (decoder_model->status != DECODER_MODEL_OK) return; - av2_decoder_model_check_output_frame(cpi, decoder_model, trigger_buffer_index, - trigger_presentation, - trigger_completes_implicit_output); + av2_decoder_model_check_output_frame( + cpi, decoder_model, trigger_ref_idx, trigger_buffer_index, + trigger_presentation, trigger_completes_implicit_output); if (decoder_model->status != DECODER_MODEL_OK) return; for (int k = 1; k <= decoder_model->num_ref_frames; ++k) { @@ -2497,7 +2887,7 @@ static void decoder_model_observe_output_frame_buffers( &decoder_model->frame_buffer_pool[candidate_index].presentation; if (candidate->output_order == target_order) { av2_decoder_model_check_output_frame( - cpi, decoder_model, candidate_index, candidate, true); + cpi, decoder_model, i, candidate_index, candidate, true); made_output = true; } } @@ -2526,7 +2916,10 @@ void av2_decoder_model_observe_output_frame_buffers_for_operating_points( DECODER_MODEL *const decoder_models = level_info->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { DECODER_MODEL *const decoder_model = &decoder_models[level]; - if (decoder_model->status != DECODER_MODEL_OK) continue; + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + continue; + } if (ref_idx < 0 && !cm->show_existing_frame && decoder_model->cfbi >= 0 && decoder_model->cfbi < decoder_model->num_ref_frames + 2) { const ENCODER_DM_PRESENTATION_DESCRIPTOR *const current_generation = @@ -2543,6 +2936,7 @@ void av2_decoder_model_observe_output_frame_buffers_for_operating_points( } } int trigger_buffer_index = decoder_model->cfbi; + int trigger_ref_idx = ref_idx; const ENCODER_DM_PRESENTATION_DESCRIPTOR *trigger_presentation = &decoder_model->current_presentation; bool trigger_completes_implicit_output = !cm->show_existing_frame; @@ -2565,10 +2959,11 @@ void av2_decoder_model_observe_output_frame_buffers_for_operating_points( decoder_model->status = DECODE_EXISTING_FRAME_BUF_EMPTY; continue; } + trigger_ref_idx = generation_ref_idx; trigger_buffer_index = decoder_model->vbi[generation_ref_idx]; } decoder_model_observe_output_frame_buffers( - cpi, decoder_model, ref_idx, trigger_buffer_index, + cpi, decoder_model, trigger_ref_idx, trigger_buffer_index, trigger_presentation, trigger_completes_implicit_output); } } @@ -2596,7 +2991,10 @@ void av2_decoder_model_observe_displaced_output_for_operating_points( level_params->level_info[i]->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { DECODER_MODEL *const decoder_model = &decoder_models[level]; - if (decoder_model->status != DECODER_MODEL_OK) continue; + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + continue; + } const int buffer_index = decoder_model->vbi[ref_idx]; // A newly initialized model intentionally has no ownership of stale // encoder references from the preceding CVS. @@ -2646,7 +3044,10 @@ void av2_decoder_model_observe_restricted_output_for_operating_points( cpi->level_params.level_info[op]->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { DECODER_MODEL *const decoder_model = &decoder_models[level]; - if (decoder_model->status != DECODER_MODEL_OK) continue; + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + continue; + } for (int ref_idx = 0; ref_idx < decoder_model->num_ref_frames; ++ref_idx) { const int buffer_index = decoder_model->vbi[ref_idx]; @@ -2684,12 +3085,17 @@ void av2_decoder_model_observe_restricted_output_for_operating_points( static void decoder_model_flush_implicit_output( const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model, bool olk_limit) { + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return; + } if (olk_limit && !decoder_model->olk_encountered) return; if (olk_limit && !decoder_model->olk_tu_order_hint_valid) { decoder_model->status = DECODER_MODEL_INCOMPLETE; return; } while (decoder_model->status == DECODER_MODEL_OK) { + int output_ref_idx = -1; int output_buffer_index = -1; uint64_t output_order = 0; for (int i = 0; i < decoder_model->num_ref_frames; ++i) { @@ -2706,6 +3112,7 @@ static void decoder_model_flush_implicit_output( } if (output_buffer_index == -1 || candidate->output_order <= output_order) { + output_ref_idx = i; output_buffer_index = candidate_index; output_order = candidate->output_order; } @@ -2713,8 +3120,9 @@ static void decoder_model_flush_implicit_output( if (output_buffer_index == -1) return; ENCODER_DM_PRESENTATION_DESCRIPTOR *const presentation = &decoder_model->frame_buffer_pool[output_buffer_index].presentation; - av2_decoder_model_check_output_frame( - cpi, decoder_model, output_buffer_index, presentation, true); + av2_decoder_model_check_output_frame(cpi, decoder_model, output_ref_idx, + output_buffer_index, presentation, + true); } } @@ -2778,6 +3186,12 @@ void av2_encoder_decoder_model_finish_for_operating_points( DECODER_MODEL *const decoder_models = cpi->level_params.level_info[op]->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { + if (decoder_models[level].exact_model != NULL) { + if (decoder_models[level].status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_models[level].exact_model); + } + av2_decoder_model_finish(decoder_models[level].exact_model); + } av2_encoder_decoder_model_finalize( &decoder_models[level], decoder_models[level].is_still_picture); } @@ -2911,8 +3325,25 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( int check_bitrate) { const DECODER_MODEL *const decoder_model = &level_info->decoder_models[level]; const DECODER_MODEL_STATUS decoder_model_status = decoder_model->status; - const ENCODER_DM_RESULT_CLASS model_result = + ENCODER_DM_RESULT_CLASS model_result = av2_encoder_decoder_model_classify_status(decoder_model_status); + const bool exact_model_staged = decoder_model->configuration_snapshot_valid; + if (exact_model_staged) { + Av2DmResult exact_result; + if (decoder_model->exact_model == NULL || + !av2_decoder_model_get_result(decoder_model->exact_model, + &exact_result)) { + model_result = ENCODER_DM_RESULT_UNAVAILABLE; + } else if (exact_result.status == AV2_DM_RESULT_NON_CONFORMANT) { + model_result = ENCODER_DM_RESULT_VIOLATION; + } else if (exact_result.status != AV2_DM_RESULT_CONFORMANT) { + model_result = ENCODER_DM_RESULT_UNAVAILABLE; + } else if (decoder_model_status != DECODER_MODEL_OK) { + model_result = ENCODER_DM_RESULT_UNAVAILABLE; + } else { + model_result = ENCODER_DM_RESULT_PASS; + } + } if (model_result == ENCODER_DM_RESULT_VIOLATION) { return DECODER_MODEL_FAIL; } @@ -2972,7 +3403,8 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( break; } - if (level_spec->max_tile_rate > target_level_spec->max_tiles * 120) { + if (!exact_model_staged && + level_spec->max_tile_rate > target_level_spec->max_tiles * 120) { fail_id = TILE_RATE_TOO_HIGH; break; } @@ -3015,7 +3447,8 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( fail_id = TILE_WIDTH_INVALID; break; } - if (!is_still_picture && decoder_model->initialized) { + if (!exact_model_staged && !is_still_picture && + decoder_model->initialized) { const int max_header_rate = is_multi_stream ? target_sub_stream_level_spec->max_header_rate_x : target_level_spec->max_header_rate; @@ -3073,7 +3506,7 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( } } - if (target_level_spec->level > SEQ_LEVEL_5_1) { + if (!exact_model_staged && target_level_spec->level > SEQ_LEVEL_5_1) { int temporal_parallel_num; int temporal_parallel_denom; const int scalability_mode_idc = -1; @@ -3335,11 +3768,11 @@ void av2_update_level_info(AV2_COMP *cpi, const uint8_t *data, size_t size, (void)xlayer_id; const SequenceHeader *const seq_params = &cm->seq_params; uint64_t dfg_bits = 0; + uint64_t frame_compressed_bytes = 0; int64_t compressed_size = 0; bool model_accounting_valid = true; if (has_serialized_frame_unit) { uint64_t dfg_bytes; - uint64_t frame_compressed_bytes; model_accounting_valid = !cpi->dm_frame_symbol_count_overflow && av2_encoder_decoder_model_count_obu_bytes(data, size, &dfg_bytes, @@ -3408,12 +3841,17 @@ void av2_update_level_info(AV2_COMP *cpi, const uint8_t *data, size_t size, if (has_serialized_frame_unit) { DECODER_MODEL *const decoder_models = level_info->decoder_models; for (AV2_LEVEL level = SEQ_LEVEL_2_0; level < SEQ_LEVELS; ++level) { - if (decoder_models[level].status != DECODER_MODEL_OK) continue; + if (decoder_models[level].status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_models[level].exact_model); + continue; + } if (!model_accounting_valid) { decoder_models[level].status = DECODER_MODEL_INTERNAL_ERROR; } else { - av2_decoder_model_start_frame_decode(cpi, dfg_bits, compressed_size, - &decoder_models[level]); + av2_decoder_model_start_frame_decode( + cpi, dfg_bits, compressed_size, frame_compressed_bytes, + max_tile_size, max_tile_width, tile_width_is_valid, + &decoder_models[level]); } } } diff --git a/av2/common/level.h b/av2/common/level.h index b466264217..2e035aec01 100644 --- a/av2/common/level.h +++ b/av2/common/level.h @@ -200,6 +200,8 @@ typedef struct { BITSTREAM_PROFILE configured_profile; int configured_max_frame_width; int configured_max_frame_height; + uint32_t configured_chroma_format_idc; + avm_bit_depth_t configured_bit_depth; int configured_max_mlayer_id; unsigned int configured_number_xlayers; bool configured_timing_info_present; @@ -214,6 +216,12 @@ typedef struct { Av2DmRational bit_rate; // Bits per second. Av2DmRational buffer_size; // Bits. + // Exact common-core observer used by the staged encoder-model migration. + Av2DecoderModel *exact_model; + uint64_t exact_event_index; + bool exact_parameters_updated; + bool exact_buffer_size_history_required; + int64_t num_frame; int64_t num_decoded_frame; int64_t num_shown_frame; @@ -353,9 +361,10 @@ avm_codec_err_t av2_get_seq_level_idx(const struct AV2_COMP *cpi, const AV2LevelParams *level_params, int *seq_level_idx); +// decoder_model must be zero-initialized or have been initialized previously. +// Call destroy when it is no longer needed. void av2_decoder_model_init(const struct AV2_COMP *const cpi, AV2_LEVEL level, int op_index, DECODER_MODEL *const decoder_model); - void av2_encoder_decoder_model_destroy(DECODER_MODEL *decoder_model); void av2_encoder_decoder_models_destroy(AV2LevelInfo *level_info); diff --git a/av2/decoder/decoder_model.c b/av2/decoder/decoder_model.c index e1103c10c3..906ede3aaf 100644 --- a/av2/decoder/decoder_model.c +++ b/av2/decoder/decoder_model.c @@ -206,6 +206,10 @@ typedef struct Av2DmContextKey { typedef struct Av2DmLiveRun Av2DmLiveRun; +static void update_live_run_parameters(Av2DecoderModelVerifier *verifier, + Av2DmLiveRun *run, + const Av2DmContextEvent *event); + typedef struct Av2DmContext { Av2DmContextKey key; bool active; @@ -395,11 +399,14 @@ static void destroy_context_runs(Av2DmContext *context); static void retire_xlayer_generations(Av2DecoderModelVerifier *verifier, const AV2Decoder *pbi, int xlayer_id) { + const int num_refs = pbi->common.seq_params.ref_frames < AV2_DM_MAX_REF_FRAMES + ? pbi->common.seq_params.ref_frames + : AV2_DM_MAX_REF_FRAMES; size_t write_index = 0; for (size_t i = 0; i < verifier->generation_count; ++i) { Av2DmGenerationRecord *const generation = &verifier->generations[i]; bool referenced = generation->implicit_presentation_pending; - for (int ref = 0; !referenced && ref < AV2_DM_MAX_REF_FRAMES; ++ref) { + for (int ref = 0; !referenced && ref < num_refs; ++ref) { referenced = pbi->common.ref_frame_map[ref] == generation->buffer && pbi->valid_for_referencing[ref]; } @@ -552,6 +559,9 @@ static void initialize_context_event(const Av2DecoderModelVerifier *verifier, Av2DmContextEventType type, Av2DmContextEvent *event) { memset(event, 0, sizeof(*event)); + av2_dm_level_limits_init(&event->config.level_limits); + av2_dm_rational_init(&event->frame.temporal_unit_output_time); + av2_dm_rational_init(&event->output.presentation_base_offset); event->type = type; event->event_index = verifier->event_count; event->source_frame_unit_index = verifier->source_frame_unit_index; @@ -559,6 +569,75 @@ static void initialize_context_event(const Av2DecoderModelVerifier *verifier, event->stream_generation = verifier->stream_generation; } +static void destroy_context_event(Av2DmContextEvent *event) { + if (event == NULL) return; + av2_dm_config_destroy(&event->config); + av2_dm_rational_destroy(&event->frame.temporal_unit_output_time); + av2_dm_rational_destroy(&event->output.presentation_base_offset); + memset(event, 0, sizeof(*event)); +} + +static bool copy_context_event(Av2DmContextEvent *destination, + const Av2DmContextEvent *source) { + Av2DmContextEvent temporary = *source; + memset(&temporary.config.level_limits.bit_rate, 0, + sizeof(temporary.config.level_limits.bit_rate)); + memset(&temporary.config.level_limits.buffer_size, 0, + sizeof(temporary.config.level_limits.buffer_size)); + memset(&temporary.frame.temporal_unit_output_time, 0, + sizeof(temporary.frame.temporal_unit_output_time)); + memset(&temporary.output.presentation_base_offset, 0, + sizeof(temporary.output.presentation_base_offset)); + av2_dm_level_limits_init(&temporary.config.level_limits); + av2_dm_rational_init(&temporary.frame.temporal_unit_output_time); + av2_dm_rational_init(&temporary.output.presentation_base_offset); + if (!av2_dm_config_copy(&temporary.config, &source->config) || + !av2_dm_rational_copy(&temporary.frame.temporal_unit_output_time, + &source->frame.temporal_unit_output_time) || + !av2_dm_rational_copy(&temporary.output.presentation_base_offset, + &source->output.presentation_base_offset)) { + destroy_context_event(&temporary); + return false; + } + destroy_context_event(destination); + *destination = temporary; + return true; +} + +static void clear_context_prefix_events(Av2DmContext *context) { + for (size_t i = 0; i < context->prefix_event_count; ++i) { + destroy_context_event(&context->prefix_events[i]); + } + context->prefix_event_count = 0; +} + +static bool config_equal(const Av2DmConfig *left, const Av2DmConfig *right) { + if (left->level_limits_present != right->level_limits_present) return false; + if (left->level_limits_present) { + int comparison; + if (!av2_dm_rational_compare(&left->level_limits.bit_rate, + &right->level_limits.bit_rate, &comparison) || + comparison != 0 || + !av2_dm_rational_compare(&left->level_limits.buffer_size, + &right->level_limits.buffer_size, + &comparison) || + comparison != 0) { + return false; + } + } + Av2DmConfig left_scalars = *left; + Av2DmConfig right_scalars = *right; + memset(&left_scalars.level_limits.bit_rate, 0, + sizeof(left_scalars.level_limits.bit_rate)); + memset(&left_scalars.level_limits.buffer_size, 0, + sizeof(left_scalars.level_limits.buffer_size)); + memset(&right_scalars.level_limits.bit_rate, 0, + sizeof(right_scalars.level_limits.bit_rate)); + memset(&right_scalars.level_limits.buffer_size, 0, + sizeof(right_scalars.level_limits.buffer_size)); + return memcmp(&left_scalars, &right_scalars, sizeof(left_scalars)) == 0; +} + static bool frame_obu_matches(const Av2DmPendingObu *obu, const Av2DmFrameSnapshot *frame) { return obu->frame_unit_index == frame->source_frame_unit_index; @@ -622,6 +701,14 @@ static void mark_arithmetic_failed(Av2DecoderModelVerifier *verifier) { mark_failed_with_code(verifier, AV2_DM_VERIFIER_ERROR_ARITHMETIC); } +static void mark_exact_operation_failed(Av2DecoderModelVerifier *verifier) { + if (av2_dm_last_failure_was_allocation()) { + mark_allocation_failed(verifier); + } else { + mark_arithmetic_failed(verifier); + } +} + static void mark_allocation_failed(Av2DecoderModelVerifier *verifier) { mark_failed_with_code(verifier, AV2_DM_VERIFIER_ERROR_ALLOCATION); } @@ -763,6 +850,12 @@ static bool build_context_config(const Av2DecoderModelVerifier *verifier, config->explicit_num_ref_frames = true; config->max_frame_width = sequence->max_frame_width; config->max_frame_height = sequence->max_frame_height; + if (av2_get_chroma_format_idc(sequence->subsampling_x, + sequence->subsampling_y, sequence->monochrome, + &config->chroma_format_idc) != AVM_CODEC_OK) { + config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; + } + config->bit_depth = sequence->bit_depth; config->max_mlayer_id = sequence->max_mlayer_id; config->still_picture = sequence->still_picture != 0; config->timing_info_present = ci->ci_timing_info_present_flag != 0; @@ -855,6 +948,7 @@ static bool build_context_config(const Av2DecoderModelVerifier *verifier, snapshot->msdo.multistream_profile_idc, scale_numerator, scale_denominator, &config->level_limits)) { + if (av2_dm_last_failure_was_allocation()) return false; config->applicability = AV2_DM_MISSING_REQUIRED_INPUT; } else { config->level_limits_present = true; @@ -1039,6 +1133,10 @@ void av2_decoder_model_verifier_init(AV2Decoder *pbi) { pbi->decoder_model_verifier_allocation_failed = pbi->decoder_model_verifier == NULL; if (pbi->decoder_model_verifier != NULL) { + av2_dm_rational_init( + &pbi->decoder_model_verifier->replay_previous_presentation_offset); + av2_dm_rational_init( + &pbi->decoder_model_verifier->replay_last_presentation_offset); pbi->decoder_model_verifier->check_mode = pbi->decoder_model_check_mode; pbi->decoder_model_verifier->check_every_rap = pbi->decoder_model_check_every_rap != 0; @@ -1058,9 +1156,13 @@ void av2_decoder_model_verifier_destroy(AV2Decoder *pbi) { Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; for (size_t i = 0; i < verifier->context_count; ++i) { destroy_context_runs(&verifier->contexts[i]); + clear_context_prefix_events(&verifier->contexts[i]); + av2_dm_config_destroy(&verifier->contexts[i].last_config); avm_free(verifier->contexts[i].runs); avm_free(verifier->contexts[i].prefix_events); } + av2_dm_rational_destroy(&verifier->replay_previous_presentation_offset); + av2_dm_rational_destroy(&verifier->replay_last_presentation_offset); avm_free(verifier->current_tu_obus); avm_free(verifier->sequence_records); avm_free(verifier->ops_records); @@ -1166,7 +1268,7 @@ void av2_decoder_model_verifier_on_source_frame_unit_start(AV2Decoder *pbi, } verifier->current_source_frame_dispatched = false; for (size_t i = 0; i < verifier->context_count; ++i) { - verifier->contexts[i].prefix_event_count = 0; + clear_context_prefix_events(&verifier->contexts[i]); } } @@ -1481,6 +1583,43 @@ void av2_decoder_model_verifier_on_active_configuration( if (context->key.xlayer_id != xlayer_id) continue; context->active_configuration_record = event->record_index; context->active_sequence_record = sequence_record; + if (pbi->obu_type == OBU_CLOSED_LOOP_KEY && context->last_config_present && + context->run_count != 0) { + Av2DmFrameSnapshot snapshot; + memset(&snapshot, 0, sizeof(snapshot)); + snapshot.multistream_decoder_mode = pbi->multistream_decoder_mode != 0; + snapshot.msdo = pbi->common.msdo_params; + snapshot.multistream_even_allocation = + verifier->multistream_even_allocation; + snapshot.multistream_large_picture_index = + verifier->multistream_large_picture_index; + snapshot.num_streams = pbi->common.num_streams; + memcpy(snapshot.stream_ids, pbi->common.stream_ids, + sizeof(snapshot.stream_ids)); + Av2DmContextEvent config_event; + initialize_context_event(verifier, AV2_DM_CONTEXT_FRAME, &config_event); + config_event.event_index = event->index; + config_event.source_frame_unit_index = verifier->source_frame_unit_index; + config_event.stream_generation = verifier->stream_generation; + config_event.config_present = + build_context_config(verifier, context, pbi->common.mlayer_id, + &snapshot, &config_event.config); + config_event.frame.coded_as_closed_loop_key = true; + if (config_event.config_present && + !config_equal(&context->last_config, &config_event.config)) { + for (size_t run_index = 0; run_index < context->run_count; + ++run_index) { + update_live_run_parameters(verifier, context->runs[run_index], + &config_event); + } + if (av2_dm_config_copy(&context->last_config, &config_event.config)) { + context->last_stream_generation = verifier->stream_generation; + } else { + mark_exact_operation_failed(verifier); + } + } + destroy_context_event(&config_event); + } } } @@ -1735,6 +1874,11 @@ static void append_frame_to_context(Av2DecoderModelVerifier *verifier, model_event->leading_frame = snapshot->leading_frame; model_event->config_present = build_context_config( verifier, context, snapshot->mlayer_id, snapshot, &model_event->config); + if (!model_event->config_present) { + mark_exact_operation_failed(verifier); + destroy_context_event(model_event); + return; + } Av2DmGenerationRecord *const generation = find_generation_by_id(verifier, snapshot->generation); if (generation != NULL && !snapshot->show_existing_frame && @@ -1783,8 +1927,7 @@ static void append_frame_to_context(Av2DecoderModelVerifier *verifier, const bool parameters_changed = !context->last_config_present || context->last_stream_generation != model_event->stream_generation || - memcmp(&context->last_config, &model_event->config, - sizeof(model_event->config)) != 0; + !config_equal(&context->last_config, &model_event->config); frame->decoder_model_parameters_updated = frame->random_access_point && parameters_changed; frame->count_frame_header = true; @@ -1822,6 +1965,7 @@ static void append_frame_to_context(Av2DecoderModelVerifier *verifier, model_event->config.applicability = AV2_DM_MISSING_REQUIRED_INPUT; } dispatch_context_event(verifier, context, model_event); + destroy_context_event(model_event); } void av2_decoder_model_verifier_on_frame_unit_complete(AV2Decoder *pbi) { @@ -1892,7 +2036,7 @@ void av2_decoder_model_verifier_on_frame_unit_complete(AV2Decoder *pbi) { } verifier->current_source_frame_dispatched = true; for (size_t i = 0; i < verifier->context_count; ++i) { - verifier->contexts[i].prefix_event_count = 0; + clear_context_prefix_events(&verifier->contexts[i]); } memset(&verifier->pending_frame, 0, sizeof(verifier->pending_frame)); if (verifier->frame_unit_index == UINT64_MAX) { @@ -1935,6 +2079,7 @@ void av2_decoder_model_verifier_on_reference_invalidation( event->closed_loop_key_invalidation = closed_loop_key; event->leading_frame = pbi->common.is_leading_picture == 1; dispatch_context_event(verifier, context, event); + destroy_context_event(event); } (void)increment_u64(verifier, &verifier->reference_invalidations); if (closed_loop_key) { @@ -1979,6 +2124,7 @@ void av2_decoder_model_verifier_after_reference_update( event->leading_frame = generation->leading_frame; } dispatch_context_event(verifier, context, event); + destroy_context_event(event); } (void)increment_u64(verifier, &verifier->reference_updates); } @@ -2122,6 +2268,7 @@ void av2_decoder_model_verifier_on_output(AV2Decoder *pbi, output->presentation_time_present = owner_presentation_time_present; output->presentation_time_ticks = owner_presentation_time_ticks; dispatch_context_event(verifier, context, event); + destroy_context_event(event); } if (!current_presentation && generation != NULL) { generation->implicit_presentation_pending = false; @@ -2148,6 +2295,7 @@ void av2_decoder_model_verifier_on_recovery_reset(AV2Decoder *pbi) { context->recovery_reset_pending = true; context->pending_dfg_bits = 0; dispatch_context_event(verifier, context, event); + destroy_context_event(event); } memset(&verifier->pending_frame, 0, sizeof(verifier->pending_frame)); } @@ -2173,7 +2321,8 @@ void av2_decoder_model_verifier_on_stream_configuration_change( verifier->contexts[i].recovery_reset_pending = false; verifier->contexts[i].pending_dfg_bits = 0; verifier->contexts[i].pending_after_event_valid = false; - verifier->contexts[i].prefix_event_count = 0; + clear_context_prefix_events(&verifier->contexts[i]); + av2_dm_config_destroy(&verifier->contexts[i].last_config); verifier->contexts[i].last_config_present = false; } memset(verifier->active_configuration_present, 0, @@ -2288,25 +2437,25 @@ static const char *level_name(uint32_t level_idx) { return "reserved"; } -static bool wide_fits_u64(const Av2DmUnsignedWide *value) { - return value->limbs[1] == 0 && value->limbs[2] == 0 && value->limbs[3] == 0; -} - -static bool format_unsigned_wide(const Av2DmUnsignedWide *value, char *text, - size_t text_size) { - if (wide_fits_u64(value)) { - const int written = snprintf(text, text_size, "%" PRIu64, value->limbs[0]); +static bool format_rational_component(const Av2DmRational *value, + bool denominator, char *text, + size_t text_size) { + const uint64_t *limbs; + uint32_t limb_count; + if (!av2_dm_rational_get_component(value, denominator, &limbs, &limb_count)) { + return false; + } + if (limb_count <= 1) { + const uint64_t scalar = limb_count == 0 ? 0 : limbs[0]; + const int written = snprintf(text, text_size, "%" PRIu64, scalar); return written >= 0 && (size_t)written < text_size; } - int highest_limb = 3; - while (highest_limb > 0 && value->limbs[highest_limb] == 0) --highest_limb; - int written = - snprintf(text, text_size, "0x%" PRIx64, value->limbs[highest_limb]); + int written = snprintf(text, text_size, "0x%" PRIx64, limbs[limb_count - 1]); if (written < 0 || (size_t)written >= text_size) return false; size_t offset = (size_t)written; - for (int i = highest_limb - 1; i >= 0; --i) { + for (uint32_t i = limb_count - 1; i > 0; --i) { written = snprintf(text + offset, text_size - offset, "%016" PRIx64, - value->limbs[i]); + limbs[i - 1]); if (written < 0 || (size_t)written >= text_size - offset) return false; offset += (size_t)written; } @@ -2319,22 +2468,67 @@ static bool format_rational(const Av2DmRational *value, char *text, const int written = snprintf(text, text_size, "NA"); return written >= 0 && (size_t)written < text_size; } - char magnitude[67]; - char denominator[67]; - if (!format_unsigned_wide(&value->magnitude, magnitude, sizeof(magnitude)) || - !format_unsigned_wide(&value->denominator, denominator, - sizeof(denominator))) { + const uint64_t *denominator_limbs; + uint32_t denominator_count; + if (!av2_dm_rational_get_component(value, true, &denominator_limbs, + &denominator_count)) { return false; } - int written; - if (wide_fits_u64(&value->denominator) && value->denominator.limbs[0] == 1) { - written = snprintf(text, text_size, "%s%s", value->negative ? "-" : "", - magnitude); - } else { - written = snprintf(text, text_size, "%s%s/%s", value->negative ? "-" : "", - magnitude, denominator); + size_t offset = 0; + if (value->negative) { + if (text_size < 2) return false; + text[offset++] = '-'; + text[offset] = '\0'; + } + if (!format_rational_component(value, false, text + offset, + text_size - offset)) { + return false; + } + offset += strlen(text + offset); + if (denominator_count == 1 && denominator_limbs[0] == 1) return true; + if (offset + 1 >= text_size) return false; + text[offset++] = '/'; + text[offset] = '\0'; + return format_rational_component(value, true, text + offset, + text_size - offset); +} + +static void print_rational_component(FILE *stream, const Av2DmRational *value, + bool denominator) { + const uint64_t *limbs; + uint32_t limb_count; + if (!av2_dm_rational_get_component(value, denominator, &limbs, &limb_count)) { + fprintf(stream, "NA"); + return; + } + if (limb_count <= 1) { + fprintf(stream, "%" PRIu64, limb_count == 0 ? 0 : limbs[0]); + return; + } + fprintf(stream, "0x%" PRIx64, limbs[limb_count - 1]); + for (uint32_t i = limb_count - 1; i > 0; --i) { + fprintf(stream, "%016" PRIx64, limbs[i - 1]); + } +} + +static void print_rational(FILE *stream, const Av2DmRational *value) { + if (value == NULL) { + fprintf(stream, "NA"); + return; + } + const uint64_t *denominator; + uint32_t denominator_count; + if (!av2_dm_rational_get_component(value, true, &denominator, + &denominator_count)) { + fprintf(stream, "NA"); + return; + } + if (value->negative) fputc('-', stream); + print_rational_component(stream, value, false); + if (denominator_count != 1 || denominator[0] != 1) { + fputc('/', stream); + print_rational_component(stream, value, true); } - return written >= 0 && (size_t)written < text_size; } typedef enum Av2DmMarginRule { @@ -2545,13 +2739,25 @@ static void append_detail(Av2DmTextBuilder *builder, const char *format, ...) { static bool rational_to_long_double(const Av2DmRational *value, long double *result) { if (value == NULL || result == NULL) return false; + const uint64_t *magnitude; + const uint64_t *denominator_limbs; + uint32_t magnitude_count; + uint32_t denominator_count; + if (!av2_dm_rational_get_component(value, false, &magnitude, + &magnitude_count) || + !av2_dm_rational_get_component(value, true, &denominator_limbs, + &denominator_count)) { + return false; + } long double numerator = 0.0L; long double denominator = 0.0L; const long double limb_base = 18446744073709551616.0L; - for (int i = 3; i >= 0; --i) { - numerator = numerator * limb_base + (long double)value->magnitude.limbs[i]; + for (uint32_t i = magnitude_count; i > 0; --i) { + numerator = numerator * limb_base + (long double)magnitude[i - 1]; + } + for (uint32_t i = denominator_count; i > 0; --i) { denominator = - denominator * limb_base + (long double)value->denominator.limbs[i]; + denominator * limb_base + (long double)denominator_limbs[i - 1]; } if (denominator == 0.0L) return false; *result = numerator / denominator; @@ -2578,9 +2784,14 @@ static bool append_decimal(Av2DmTextBuilder *builder, const char *name, static bool append_rational(Av2DmTextBuilder *builder, const char *name, const Av2DmRational *value) { - char formatted[150]; - if (!format_rational(value, formatted, sizeof(formatted))) return false; - append_detail(builder, " %s=%s", name, formatted); + append_detail(builder, " %s=", name); + if (!builder->valid || + !format_rational(value, builder->text + builder->length, + builder->size - builder->length)) { + builder->valid = false; + return false; + } + builder->length += strlen(builder->text + builder->length); return builder->valid; } @@ -2643,21 +2854,23 @@ static bool append_payload_details(Av2DmTextBuilder *builder, append_detail(builder, " decoder_buffer_delay_ticks=%u", detail->value.delay_consistency.decoder_buffer_delay_ticks); if (detail->value.delay_consistency.ceil_time_delta_present) { - Av2DmRational decoder_delay; - Av2DmRational excess; - if (!av2_dm_rational_make( + Av2DmRational decoder_delay = { 0 }; + Av2DmRational excess = { 0 }; + const bool appended = + av2_dm_rational_make( detail->value.delay_consistency.decoder_buffer_delay_ticks, 1, - &decoder_delay) || - !av2_dm_rational_subtract( + &decoder_delay) && + av2_dm_rational_subtract( &decoder_delay, &detail->value.delay_consistency.ceil_time_delta_ticks, - &excess) || - !append_rational( + &excess) && + append_rational( builder, "ceil_time_delta_ticks", - &detail->value.delay_consistency.ceil_time_delta_ticks) || - !append_rational(builder, "decoder_buffer_delay_excess", &excess)) { - return false; - } + &detail->value.delay_consistency.ceil_time_delta_ticks) && + append_rational(builder, "decoder_buffer_delay_excess", &excess); + av2_dm_rational_destroy(&decoder_delay); + av2_dm_rational_destroy(&excess); + if (!appended) return false; } else { append_detail(builder, " ceil_time_delta_ticks=NA"); } @@ -2718,7 +2931,12 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, return false; } - Av2DmRational margin; + Av2DmRational margin = { 0 }; +#define RETURN_FORMAT_RESULT(value) \ + do { \ + av2_dm_rational_destroy(&margin); \ + return (value); \ + } while (0) bool margin_present = false; if (violation->observed_present && violation->limit_present && descriptor->margin_rule != AV2_DM_MARGIN_NONE) { @@ -2730,11 +2948,13 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, &margin); if (!margin_present || !append_rational(&builder, descriptor->margin_name, &margin)) { - return false; + RETURN_FORMAT_RESULT(false); } } - if (!append_payload_details(&builder, violation)) return false; + if (!append_payload_details(&builder, violation)) { + RETURN_FORMAT_RESULT(false); + } if (strcmp(descriptor->unit, "seconds") == 0) { if ((violation->observed_present && !append_milliseconds(&builder, descriptor->observed_name, @@ -2744,7 +2964,7 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, &violation->limit)) || (margin_present && !append_milliseconds(&builder, descriptor->margin_name, &margin))) { - return false; + RETURN_FORMAT_RESULT(false); } } @@ -2755,11 +2975,13 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, } if (violation->code == AV2_DM_VIOLATION_MAX_DISPLAY_RATE && violation->limit_present && max_display_rate != 0) { - Av2DmRational interval = violation->limit; - if (!av2_dm_rational_divide_u64(&interval, max_display_rate, &interval) || + Av2DmRational interval = { 0 }; + if (!av2_dm_rational_copy(&interval, &violation->limit) || + !av2_dm_rational_divide_u64(&interval, max_display_rate, &interval) || !append_rational(&builder, "output_interval", &interval) || !append_milliseconds(&builder, "output_interval", &interval)) { - return false; + av2_dm_rational_destroy(&interval); + RETURN_FORMAT_RESULT(false); } long double samples; long double seconds; @@ -2770,8 +2992,10 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, "Msamples/s") || !append_decimal(&builder, "limit_rate", (long double)max_display_rate, 0.000001L, "Msamples/s")) { - return false; + av2_dm_rational_destroy(&interval); + RETURN_FORMAT_RESULT(false); } + av2_dm_rational_destroy(&interval); } else if (violation->code == AV2_DM_VIOLATION_FRAME_DECODE_RATE && violation->detail.kind == AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL && max_decode_rate != 0) { @@ -2786,7 +3010,7 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, "Msamples/s") || !append_decimal(&builder, "limit_rate", (long double)max_decode_rate, 0.000001L, "Msamples/s")) { - return false; + RETURN_FORMAT_RESULT(false); } } else if (violation->code == AV2_DM_VIOLATION_FRAME_TILE_RATE && violation->detail.kind == AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL) { @@ -2803,11 +3027,14 @@ bool av2_decoder_model_format_violation_details(const Av2DmViolation *violation, "tiles/s") || !append_decimal(&builder, "limit_tile_rate", tile_limit / seconds, 1.0L, "tiles/s")) { - return false; + RETURN_FORMAT_RESULT(false); } } append_detail(&builder, " spec=%s", descriptor->spec); - return builder.valid; + const bool valid = builder.valid; + av2_dm_rational_destroy(&margin); +#undef RETURN_FORMAT_RESULT + return valid; } static const Av2DmContextEvent *find_context_event(const Av2DmRunReport *report, @@ -2873,17 +3100,6 @@ static void report_decoder_model_violation(void *opaque, if (report->verifier->check_mode == AVM_DECODER_MODEL_CHECK_FATAL) { report->verifier->fatal_violation = true; } - char observed[150]; - char limit[150]; - if (!format_rational( - violation->observed_present ? &violation->observed : NULL, observed, - sizeof(observed))) { - snprintf(observed, sizeof(observed), "NA"); - } - if (!format_rational(violation->limit_present ? &violation->limit : NULL, - limit, sizeof(limit))) { - snprintf(limit, sizeof(limit), "NA"); - } fprintf(stderr, "AV2_DECODER_MODEL_WARNING status=NON_CONFORMANT code=%s " "xlayer=%d ops=%d op=%d rap=%" PRId64 @@ -2895,7 +3111,11 @@ static void report_decoder_model_violation(void *opaque, mode_name(report->mode), violation->event_index); fprintf(stderr, " cvs=%" PRIu64, report->cvs); print_event_location(report, violation->event_index); - fprintf(stderr, " observed=%s limit=%s", observed, limit); + fprintf(stderr, " observed="); + print_rational(stderr, + violation->observed_present ? &violation->observed : NULL); + fprintf(stderr, " limit="); + print_rational(stderr, violation->limit_present ? &violation->limit : NULL); print_violation_explanation(report, violation); fprintf(stderr, "\n"); } @@ -2981,11 +3201,13 @@ static void emit_result(Av2DecoderModelVerifier *verifier, Av2DmIndeterminateReason reason, const Av2DmRunReport *report) { Av2DmResult result = *model_result; - if (result.arithmetic_failed) { + if (result.allocation_failed || result.arithmetic_failed) { reason = AV2_DM_REASON_INTERNAL_FAILURE; verifier->aggregate_incomplete = true; if (verifier->error_code == AV2_DM_VERIFIER_ERROR_NONE) { - verifier->error_code = AV2_DM_VERIFIER_ERROR_ARITHMETIC; + verifier->error_code = result.allocation_failed + ? AV2_DM_VERIFIER_ERROR_ALLOCATION + : AV2_DM_VERIFIER_ERROR_ARITHMETIC; } emit_verifier_error(verifier, verifier->error_code, report != NULL ? report->scope.xlayer_id : -1, @@ -3116,18 +3338,25 @@ static void apply_event_to_run(Av2DecoderModelVerifier *verifier, case AV2_DM_CONTEXT_OUTPUT: { Av2DmOutputEvent output = event->output; av2_decoder_model_output_frame(run->model, &output); - Av2DmState state; + Av2DmState state = { 0 }; if (av2_decoder_model_get_state(run->model, &state) && state.last_presentation_offset_valid) { + bool copied = true; if (verifier->replay_last_presentation_offset_valid) { - verifier->replay_previous_presentation_offset = - verifier->replay_last_presentation_offset; - verifier->replay_previous_presentation_offset_valid = true; + copied = av2_dm_rational_copy( + &verifier->replay_previous_presentation_offset, + &verifier->replay_last_presentation_offset); + verifier->replay_previous_presentation_offset_valid = copied; + } + copied = copied && av2_dm_rational_copy( + &verifier->replay_last_presentation_offset, + &state.last_presentation_offset); + verifier->replay_last_presentation_offset_valid = copied; + if (!copied) { + mark_exact_operation_failed(verifier); } - verifier->replay_last_presentation_offset = - state.last_presentation_offset; - verifier->replay_last_presentation_offset_valid = true; } + av2_dm_state_destroy(&state); break; } case AV2_DM_CONTEXT_RECOVERY_RESET: break; @@ -3166,7 +3395,11 @@ static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, mark_allocation_failed(verifier); return NULL; } - run->config = start_frame->config; + if (!av2_dm_config_copy(&run->config, &start_frame->config)) { + avm_free(run); + mark_exact_operation_failed(verifier); + return NULL; + } run->stream_generation = start_frame->stream_generation; run->reason = start_frame->indeterminate_reason; if (!start_frame->config_present || run->reason != AV2_DM_REASON_NONE) { @@ -3195,16 +3428,18 @@ static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, run->report.max_display_rate = run->config.level_limits.max_display_rate; run->report.max_decode_rate = run->config.level_limits.max_decode_rate; } else { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits = { 0 }; if (av2_dm_get_level_limits(run->config.level_idx, run->config.tier, run->config.profile, &limits)) { run->report.max_display_rate = limits.max_display_rate; run->report.max_decode_rate = limits.max_decode_rate; } + av2_dm_level_limits_destroy(&limits); } run->model = av2_decoder_model_create( &run->config, report_decoder_model_violation, &run->report); if (run->model == NULL) { + av2_dm_config_destroy(&run->config); avm_free(run); mark_allocation_failed(verifier); return NULL; @@ -3212,6 +3447,7 @@ static Av2DmLiveRun *create_live_run(Av2DecoderModelVerifier *verifier, context->runs[context->run_count] = run; if (!increment_size(verifier, &context->run_count)) { av2_decoder_model_destroy(run->model); + av2_dm_config_destroy(&run->config); avm_free(run); return NULL; } @@ -3254,6 +3490,7 @@ static void finish_context_runs(Av2DecoderModelVerifier *verifier, } release_run_cvs_ownership(verifier, run); av2_decoder_model_destroy(run->model); + av2_dm_config_destroy(&run->config); avm_free(run); } context->run_count = 0; @@ -3280,6 +3517,7 @@ static void finish_partial_context_runs(Av2DecoderModelVerifier *verifier, } release_run_cvs_ownership(verifier, run); av2_decoder_model_destroy(run->model); + av2_dm_config_destroy(&run->config); avm_free(run); } context->run_count = 0; @@ -3288,6 +3526,7 @@ static void finish_partial_context_runs(Av2DecoderModelVerifier *verifier, static void destroy_context_runs(Av2DmContext *context) { for (size_t i = 0; i < context->run_count; ++i) { av2_decoder_model_destroy(context->runs[i]->model); + av2_dm_config_destroy(&context->runs[i]->config); avm_free(context->runs[i]); } context->run_count = 0; @@ -3306,8 +3545,24 @@ static bool prefix_event_applies(const Av2DmLiveRun *run, return true; } -static void update_live_run_parameters(Av2DmLiveRun *run, +static void update_live_run_parameters(Av2DecoderModelVerifier *verifier, + Av2DmLiveRun *run, const Av2DmContextEvent *event) { + Av2DmConfig updated_config = { 0 }; + if (!av2_dm_config_copy(&updated_config, &event->config)) { + mark_exact_operation_failed(verifier); + return; + } + const bool ras_start = run->config.ras_start; + const bool ras_seed_complete = run->config.ras_seed_complete; + const uint32_t ras_seed_count = run->config.ras_seed_count; + Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; + memcpy(ras_seeds, run->config.ras_seeds, sizeof(ras_seeds)); + updated_config.initial_display_delay = run->config.initial_display_delay; + updated_config.ras_start = ras_start; + updated_config.ras_seed_complete = ras_seed_complete; + updated_config.ras_seed_count = ras_seed_count; + memcpy(updated_config.ras_seeds, ras_seeds, sizeof(updated_config.ras_seeds)); run->report.current_event = *event; run->report.current_event_valid = true; const Av2DmParameterUpdateDisposition disposition = @@ -3320,29 +3575,31 @@ static void update_live_run_parameters(Av2DmLiveRun *run, memset(&run->report.current_event, 0, sizeof(run->report.current_event)); if (!updated) { Av2DmResult result; + const bool have_result = av2_decoder_model_get_result(run->model, &result); const bool non_conformant = - av2_decoder_model_get_result(run->model, &result) && - result.status == AV2_DM_RESULT_NON_CONFORMANT; + have_result && result.status == AV2_DM_RESULT_NON_CONFORMANT; if (!non_conformant && run->reason == AV2_DM_REASON_NONE) { - run->reason = - disposition == AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION - ? AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION - : AV2_DM_REASON_MISSING_REQUIRED_INPUT; + if ((have_result && + (result.allocation_failed || result.arithmetic_failed)) || + disposition == AV2_DM_PARAMETER_UPDATE_INTERNAL_FAILURE) { + if (have_result && result.allocation_failed) { + mark_allocation_failed(verifier); + } else { + mark_arithmetic_failed(verifier); + } + run->reason = AV2_DM_REASON_INTERNAL_FAILURE; + } else { + run->reason = + disposition == AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION + ? AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION + : AV2_DM_REASON_MISSING_REQUIRED_INPUT; + } } + av2_dm_config_destroy(&updated_config); return; } - const bool ras_start = run->config.ras_start; - const bool ras_seed_complete = run->config.ras_seed_complete; - const uint32_t ras_seed_count = run->config.ras_seed_count; - Av2DmRasSeed ras_seeds[AV2_DM_MAX_REF_FRAMES]; - memcpy(ras_seeds, run->config.ras_seeds, sizeof(ras_seeds)); - const uint32_t initial_display_delay = run->config.initial_display_delay; - run->config = event->config; - run->config.initial_display_delay = initial_display_delay; - run->config.ras_start = ras_start; - run->config.ras_seed_complete = ras_seed_complete; - run->config.ras_seed_count = ras_seed_count; - memcpy(run->config.ras_seeds, ras_seeds, sizeof(run->config.ras_seeds)); + av2_dm_config_destroy(&run->config); + run->config = updated_config; run->report.mode = run->config.mode; run->report.level_idx = run->config.level_idx; run->report.tier = run->config.tier; @@ -3352,12 +3609,13 @@ static void update_live_run_parameters(Av2DmLiveRun *run, run->report.max_display_rate = run->config.level_limits.max_display_rate; run->report.max_decode_rate = run->config.level_limits.max_decode_rate; } else { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits = { 0 }; if (av2_dm_get_level_limits(run->config.level_idx, run->config.tier, run->config.profile, &limits)) { run->report.max_display_rate = limits.max_display_rate; run->report.max_decode_rate = limits.max_decode_rate; } + av2_dm_level_limits_destroy(&limits); } } @@ -3437,7 +3695,13 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, mark_failed(verifier); return; } - context->prefix_events[context->prefix_event_count] = *event; + memset(&context->prefix_events[context->prefix_event_count], 0, + sizeof(*context->prefix_events)); + if (!copy_context_event( + &context->prefix_events[context->prefix_event_count], event)) { + mark_exact_operation_failed(verifier); + return; + } if (!increment_size(verifier, &context->prefix_event_count)) return; } return; @@ -3455,14 +3719,13 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, const bool config_changed = context->last_config_present && (context->last_stream_generation != event->stream_generation || - memcmp(&context->last_config, &event->config, sizeof(event->config)) != - 0); + !config_equal(&context->last_config, &event->config)); if (config_changed) { if (context->last_stream_generation == event->stream_generation && event->frame.random_access_point && event->frame.decoder_model_parameters_updated) { for (size_t i = 0; i < context->run_count; ++i) { - update_live_run_parameters(context->runs[i], event); + update_live_run_parameters(verifier, context->runs[i], event); } } else { // Annex E resets FirstBitArrival only when a new parameter set is @@ -3521,9 +3784,12 @@ static void dispatch_context_event(Av2DecoderModelVerifier *verifier, ++i) { apply_event_to_run(verifier, context->runs[i], event); } - context->last_config_present = true; - context->last_config = event->config; - context->last_stream_generation = event->stream_generation; + if (av2_dm_config_copy(&context->last_config, &event->config)) { + context->last_config_present = true; + context->last_stream_generation = event->stream_generation; + } else { + mark_exact_operation_failed(verifier); + } context->last_ras_seed_complete = event->ras_seed_complete; context->last_ras_seed_count = event->ras_seed_count; } @@ -3586,6 +3852,7 @@ void av2_decoder_model_verifier_before_final_output(AV2Decoder *pbi, storage.source_frame_unit_index = verifier->source_frame_unit_index; storage.stream_generation = run->stream_generation; apply_event_to_run(verifier, run, &storage); + destroy_context_event(&storage); } } } @@ -3687,7 +3954,8 @@ static void finish_all_cvs_internal(Av2DecoderModelVerifier *verifier, finish_context_runs(verifier, context); } if (verifier->failed || verifier->fatal_violation) partial = true; - context->prefix_event_count = 0; + clear_context_prefix_events(context); + av2_dm_config_destroy(&context->last_config); context->last_config_present = false; rebuild_incomplete_extraction(verifier, context); } @@ -3830,103 +4098,140 @@ static void add_size_to_saturated_u32(uint32_t *total, size_t value) { } } +void av2_decoder_model_verifier_stats_init(Av2DmVerifierStats *stats) { + if (stats == NULL) return; + memset(stats, 0, sizeof(*stats)); + av2_dm_rational_init(&stats->replay_previous_presentation_offset); + av2_dm_rational_init(&stats->replay_last_presentation_offset); +} + +void av2_decoder_model_verifier_stats_destroy(Av2DmVerifierStats *stats) { + if (stats == NULL) return; + av2_dm_rational_destroy(&stats->replay_previous_presentation_offset); + av2_dm_rational_destroy(&stats->replay_last_presentation_offset); + memset(stats, 0, sizeof(*stats)); +} + +void av2_decoder_model_run_stats_init(Av2DmRunStats *stats) { + if (stats == NULL) return; + memset(stats, 0, sizeof(*stats)); + av2_dm_rational_init(&stats->initial_presentation_delay); +} + +void av2_decoder_model_run_stats_destroy(Av2DmRunStats *stats) { + if (stats == NULL) return; + av2_dm_rational_destroy(&stats->initial_presentation_delay); + memset(stats, 0, sizeof(*stats)); +} + bool av2_decoder_model_verifier_get_stats(const AV2Decoder *pbi, Av2DmVerifierStats *stats) { if (stats == NULL) return false; - memset(stats, 0, sizeof(*stats)); + Av2DmVerifierStats updated; + av2_decoder_model_verifier_stats_init(&updated); if (pbi == NULL) return false; if (pbi->decoder_model_verifier == NULL) { if (!pbi->decoder_model_verifier_allocation_failed) return false; - stats->failed = true; - stats->result_count = + updated.failed = true; + updated.result_count = pbi->decoder_model_verifier_allocation_reported ? 1 : 0; - stats->indeterminate_results = stats->result_count; + updated.indeterminate_results = updated.result_count; + av2_decoder_model_verifier_stats_destroy(stats); + *stats = updated; return true; } const Av2DecoderModelVerifier *const verifier = pbi->decoder_model_verifier; - stats->available = true; - stats->failed = verifier->failed; - stats->check_every_rap = verifier->check_every_rap; - stats->raw_obus = verifier->raw_obus; - stats->raw_bits = verifier->raw_bits; - stats->event_count = verifier->event_count; - stats->temporal_unit_index = verifier->temporal_unit_index; - stats->frame_unit_index = verifier->frame_unit_index; - stats->closed_dfgs = verifier->closed_dfgs; - stats->rap_starts = verifier->rap_start_count; - stats->applicable_rap_starts = verifier->applicable_rap_starts; - stats->rap_runs_started = verifier->rap_runs_started; - stats->rap_runs_skipped = verifier->rap_runs_skipped; - stats->rap_coverage_complete = verifier->rap_runs_skipped == 0; - stats->temporal_points = verifier->temporal_points; - stats->temporal_point_present = verifier->temporal_point_present; - stats->temporal_point = verifier->temporal_point; - stats->contexts = saturate_size_to_u32(verifier->context_count); - stats->frame_starts = verifier->frame_starts; - stats->reference_updates = verifier->reference_updates; - stats->reference_invalidations = verifier->reference_invalidations; - stats->olk_invalidations = verifier->olk_invalidations; - stats->clk_invalidations = verifier->clk_invalidations; - stats->outputs = verifier->outputs; - stats->last_frame_start_event = verifier->last_frame_start_event; - stats->last_reference_update_event = verifier->last_reference_update_event; - stats->last_reference_invalidation_event = + Av2DmVerifierStats *const target = &updated; + target->available = true; + target->failed = verifier->failed; + target->check_every_rap = verifier->check_every_rap; + target->raw_obus = verifier->raw_obus; + target->raw_bits = verifier->raw_bits; + target->event_count = verifier->event_count; + target->temporal_unit_index = verifier->temporal_unit_index; + target->frame_unit_index = verifier->frame_unit_index; + target->closed_dfgs = verifier->closed_dfgs; + target->rap_starts = verifier->rap_start_count; + target->applicable_rap_starts = verifier->applicable_rap_starts; + target->rap_runs_started = verifier->rap_runs_started; + target->rap_runs_skipped = verifier->rap_runs_skipped; + target->rap_coverage_complete = verifier->rap_runs_skipped == 0; + target->temporal_points = verifier->temporal_points; + target->temporal_point_present = verifier->temporal_point_present; + target->temporal_point = verifier->temporal_point; + target->contexts = saturate_size_to_u32(verifier->context_count); + target->frame_starts = verifier->frame_starts; + target->reference_updates = verifier->reference_updates; + target->reference_invalidations = verifier->reference_invalidations; + target->olk_invalidations = verifier->olk_invalidations; + target->clk_invalidations = verifier->clk_invalidations; + target->outputs = verifier->outputs; + target->last_frame_start_event = verifier->last_frame_start_event; + target->last_reference_update_event = verifier->last_reference_update_event; + target->last_reference_invalidation_event = verifier->last_reference_invalidation_event; - stats->last_olk_invalidation_event = verifier->last_olk_invalidation_event; - stats->last_clk_invalidation_event = verifier->last_clk_invalidation_event; - stats->last_output_event = verifier->last_output_event; - stats->last_output_callback_frame_unit = + target->last_olk_invalidation_event = verifier->last_olk_invalidation_event; + target->last_clk_invalidation_event = verifier->last_clk_invalidation_event; + target->last_output_event = verifier->last_output_event; + target->last_output_callback_frame_unit = verifier->last_output_callback_frame_unit; - stats->last_output_presentation_frame_unit = + target->last_output_presentation_frame_unit = verifier->last_output_presentation_frame_unit; - stats->last_output_presentation_temporal_unit = + target->last_output_presentation_temporal_unit = verifier->last_output_presentation_temporal_unit; - stats->last_output_generation = verifier->last_output_generation; - stats->last_output_presentation_xlayer_id = + target->last_output_generation = verifier->last_output_generation; + target->last_output_presentation_xlayer_id = verifier->last_output_presentation_xlayer_id; - stats->last_output_presentation_mlayer_id = + target->last_output_presentation_mlayer_id = verifier->last_output_presentation_mlayer_id; - stats->last_output_presentation_tlayer_id = + target->last_output_presentation_tlayer_id = verifier->last_output_presentation_tlayer_id; - stats->last_output_uses_current_presentation = + target->last_output_uses_current_presentation = verifier->last_output_uses_current_presentation; - stats->replay_previous_presentation_offset_valid = + target->replay_previous_presentation_offset_valid = verifier->replay_previous_presentation_offset_valid; - stats->replay_previous_presentation_offset = - verifier->replay_previous_presentation_offset; - stats->replay_last_presentation_offset_valid = + target->replay_last_presentation_offset_valid = verifier->replay_last_presentation_offset_valid; - stats->replay_last_presentation_offset = - verifier->replay_last_presentation_offset; - stats->finish_event = verifier->finish_event; - stats->result_count = verifier->result_count; - stats->conformant_results = + if ((target->replay_previous_presentation_offset_valid && + !av2_dm_rational_copy(&target->replay_previous_presentation_offset, + &verifier->replay_previous_presentation_offset)) || + (target->replay_last_presentation_offset_valid && + !av2_dm_rational_copy(&target->replay_last_presentation_offset, + &verifier->replay_last_presentation_offset))) { + av2_decoder_model_verifier_stats_destroy(&updated); + return false; + } + target->finish_event = verifier->finish_event; + target->result_count = verifier->result_count; + target->conformant_results = verifier->result_status_count[AV2_DM_RESULT_CONFORMANT]; - stats->non_conformant_results = + target->non_conformant_results = verifier->result_status_count[AV2_DM_RESULT_NON_CONFORMANT]; - stats->indeterminate_results = + target->indeterminate_results = verifier->result_status_count[AV2_DM_RESULT_INDETERMINATE]; - stats->not_applicable_results = + target->not_applicable_results = verifier->result_status_count[AV2_DM_RESULT_NOT_APPLICABLE]; for (size_t i = 0; i < verifier->context_count; ++i) { - add_size_to_saturated_u32(&stats->live_runs, + add_size_to_saturated_u32(&target->live_runs, verifier->contexts[i].run_count); } - stats->live_generations = saturate_size_to_u32(verifier->generation_count); - stats->cvs_aggregates = saturate_size_to_u32(verifier->cvs_count); + target->live_generations = saturate_size_to_u32(verifier->generation_count); + target->cvs_aggregates = saturate_size_to_u32(verifier->cvs_count); for (size_t i = 0; i < verifier->cvs_count; ++i) { - if (verifier->cvs[i].input_open && stats->open_cvs != UINT32_MAX) { - ++stats->open_cvs; + if (verifier->cvs[i].input_open && target->open_cvs != UINT32_MAX) { + ++target->open_cvs; } } - add_size_to_saturated_u32(&stats->parameter_records, + add_size_to_saturated_u32(&target->parameter_records, verifier->sequence_record_count); - add_size_to_saturated_u32(&stats->parameter_records, + add_size_to_saturated_u32(&target->parameter_records, verifier->ops_record_count); - add_size_to_saturated_u32(&stats->parameter_records, + add_size_to_saturated_u32(&target->parameter_records, verifier->brt_record_count); - add_size_to_saturated_u32(&stats->parameter_records, + add_size_to_saturated_u32(&target->parameter_records, verifier->active_record_count); + av2_decoder_model_verifier_stats_destroy(stats); + *stats = updated; return true; } @@ -3992,19 +4297,32 @@ bool av2_decoder_model_verifier_get_run_stats(const AV2Decoder *pbi, if (cvs->xlayer_id != context->key.xlayer_id) return false; Av2DmResult result; if (!av2_decoder_model_get_result(run->model, &result)) return false; - Av2DmState state; - if (!av2_decoder_model_get_state(run->model, &state)) return false; - memset(stats, 0, sizeof(*stats)); - stats->originating_cvs = cvs->number; - stats->stream_generation = run->stream_generation; - stats->rap = run->rap; - stats->decoded_frames = result.decoded_frames; - stats->output_frames = result.output_frames; - stats->active_num_ref_frames = state.buffer_pool.num_ref_frames; - stats->initial_presentation_delay_known = + Av2DmState state = { 0 }; + if (!av2_decoder_model_get_state(run->model, &state)) { + av2_dm_state_destroy(&state); + return false; + } + Av2DmRunStats updated; + av2_decoder_model_run_stats_init(&updated); + updated.originating_cvs = cvs->number; + updated.stream_generation = run->stream_generation; + updated.rap = run->rap; + updated.decoded_frames = result.decoded_frames; + updated.output_frames = result.output_frames; + updated.active_num_ref_frames = state.buffer_pool.num_ref_frames; + updated.initial_presentation_delay_known = state.initial_presentation_delay_known; - stats->initial_presentation_delay = state.initial_presentation_delay; - stats->status = result.status; - stats->reason = run->reason; + if (updated.initial_presentation_delay_known && + !av2_dm_rational_copy(&updated.initial_presentation_delay, + &state.initial_presentation_delay)) { + av2_decoder_model_run_stats_destroy(&updated); + av2_dm_state_destroy(&state); + return false; + } + updated.status = result.status; + updated.reason = run->reason; + av2_dm_state_destroy(&state); + av2_decoder_model_run_stats_destroy(stats); + *stats = updated; return true; } diff --git a/av2/decoder/decoder_model.h b/av2/decoder/decoder_model.h index 88b2d1e7c0..98d5216d8c 100644 --- a/av2/decoder/decoder_model.h +++ b/av2/decoder/decoder_model.h @@ -103,6 +103,10 @@ typedef struct Av2DmVerifierStats { uint32_t parameter_records; } Av2DmVerifierStats; +void av2_decoder_model_verifier_stats_init(Av2DmVerifierStats *stats); +// Stats passed to get_stats must first be initialized. Call destroy when done. +void av2_decoder_model_verifier_stats_destroy(Av2DmVerifierStats *stats); + typedef struct Av2DmContextStats { Av2DmScope scope; bool active; @@ -136,6 +140,11 @@ typedef struct Av2DmRunStats { Av2DmIndeterminateReason reason; } Av2DmRunStats; +void av2_decoder_model_run_stats_init(Av2DmRunStats *stats); +// Stats passed to get_run_stats must first be initialized. Call destroy when +// done. +void av2_decoder_model_run_stats_destroy(Av2DmRunStats *stats); + void av2_decoder_model_verifier_init(struct AV2Decoder *pbi); void av2_decoder_model_verifier_destroy(struct AV2Decoder *pbi); diff --git a/test/brt_test.cc b/test/brt_test.cc index f016c5c814..a4b0066662 100644 --- a/test/brt_test.cc +++ b/test/brt_test.cc @@ -23,9 +23,12 @@ extern "C" { } #include "avm_dsp/bitreader_buffer.h" #include "avm_mem/avm_mem.h" +#include "test/decoder_model_lifecycle.h" namespace { +using Av2DmVerifierStats = libavm_test::ScopedDmVerifierStats; + static void rb_error_handler(void *data, avm_codec_err_t error, const char *detail) { (void)data; @@ -78,7 +81,7 @@ TEST_F(BrtTest, NonOpsDependent) { EXPECT_EQ(pbi_->common.brt_info.br_ops_dependent_flag, 0); EXPECT_EQ(pbi_->common.brt_info.br_time, 42); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.event_count, 1u); av2_decoder_model_verifier_destroy(pbi_); diff --git a/test/decoder_model_integration_test.cc b/test/decoder_model_integration_test.cc index 1996a83a72..6cb3373fd8 100644 --- a/test/decoder_model_integration_test.cc +++ b/test/decoder_model_integration_test.cc @@ -27,12 +27,17 @@ #include "av2/decoder/decoder.h" #include "av2/decoder/decoder_model.h" #include "test/codec_factory.h" +#include "test/decoder_model_lifecycle.h" #include "test/encode_test_driver.h" #include "test/i420_video_source.h" #include "test/y4m_video_source.h" namespace { +using Av2DmLevelLimits = libavm_test::ScopedDmLevelLimits; +using Av2DmRunStats = libavm_test::ScopedDmRunStats; +using Av2DmVerifierStats = libavm_test::ScopedDmVerifierStats; + class DecoderModelAdapterTestBase : public ::testing::Test { protected: void SetUp() override { @@ -173,7 +178,7 @@ size_t CountOccurrences(const std::string &text, const std::string &pattern) { void ExpectAdapterRational(const Av2DmRational &actual, uint64_t numerator, uint64_t denominator) { - Av2DmRational expected; + Av2DmRational expected{}; ASSERT_TRUE(av2_dm_rational_make(numerator, denominator, &expected)); int comparison = 1; ASSERT_TRUE(av2_dm_rational_compare(&actual, &expected, &comparison)); @@ -561,6 +566,46 @@ TEST(DecoderModelDiagnosticTest, FormattingIsBoundedForMaximumWidthValues) { EXPECT_TRUE(fatal_violation); } +TEST(DecoderModelDiagnosticTest, WarningPrintsArbitraryWidthOperandsExactly) { + static const uint64_t primes[] = { + UINT64_C(4294967291), UINT64_C(4294967279), UINT64_C(4294967231), + UINT64_C(4294967197), UINT64_C(4294967189), UINT64_C(4294967161), + UINT64_C(4294967143), UINT64_C(4294967111), UINT64_C(4294967087), + UINT64_C(4294967029), UINT64_C(4294966997), UINT64_C(4294966981), + }; + Av2DmViolation violation{}; + violation.code = AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS; + violation.observed_present = true; + violation.limit_present = true; + Av2DmRational term{}; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &violation.observed)); + for (uint64_t prime : primes) { + ASSERT_TRUE(av2_dm_rational_make(1, prime, &term)); + ASSERT_TRUE( + av2_dm_rational_add(&violation.observed, &term, &violation.observed)); + } + ASSERT_TRUE(av2_dm_rational_make(1, 1, &violation.limit)); + + uint64_t violation_count = 0; + bool fatal_violation = false; + testing::internal::CaptureStderr(); + ASSERT_TRUE(av2_decoder_model_report_violation_for_testing( + &violation, false, &violation_count, &fatal_violation)); + const std::string warning = testing::internal::GetCapturedStderr(); + EXPECT_NE( + warning.find( + "observed=0xbffffb04000e7e5127c0f82dce2fcbf7bda9769bd02e5a8e" + "b2d2866b8b7ed7bf8119376c5889ad77d8e57d440/" + "0xfffff8c000173081d4e547739c5f92b9d618ed5f7f0771f046134f7801995" + "db5e2349daf328f97c26132a32ae7570079 limit=1"), + std::string::npos); + EXPECT_EQ(violation_count, 1u); + EXPECT_FALSE(fatal_violation); + av2_dm_rational_destroy(&term); + av2_dm_rational_destroy(&violation.observed); + av2_dm_rational_destroy(&violation.limit); +} + TEST(DecoderModelDiagnosticTest, FormattingIsIndependentOfNumericLocale) { Av2DmViolation timing{}; timing.code = AV2_DM_VIOLATION_DISPLAY_FRAME_LATE; @@ -593,7 +638,7 @@ TEST_F(DecoderModelHookOrderTest, UpdateAndOutput(); av2_decoder_model_verifier_finish(pbi_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.frame_starts, 1u); EXPECT_EQ(stats.reference_updates, 1u); @@ -612,7 +657,7 @@ TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { pbi_->common.ref_frame_map[0] = nullptr; pbi_->valid_for_referencing[0] = 0; av2_decoder_model_verifier_on_reference_invalidation(pbi_, false); - Av2DmVerifierStats before_start; + Av2DmVerifierStats before_start = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_start)); av2_decoder_model_verifier_record_obu(pbi_, OBU_OPEN_LOOP_KEY, 0, 0, 0, 800); pbi_->common.cur_frame = &second_frame_; @@ -623,7 +668,7 @@ TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { second_frame_.height = 64; av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); - Av2DmVerifierStats after_start; + Av2DmVerifierStats after_start = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_start)); EXPECT_EQ(after_start.reference_invalidations, 1u); EXPECT_EQ(after_start.olk_invalidations, 1u); @@ -644,7 +689,7 @@ TEST_F(DecoderModelHookOrderTest, OlkInvalidationPrecedesOlkFrameStart) { av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, AV2_DM_PRESENTATION_OWNER_CURRENT); - Av2DmVerifierStats live; + Av2DmVerifierStats live = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); EXPECT_EQ(live.live_runs, 2u); EXPECT_EQ(live.result_count, 0u); @@ -690,12 +735,12 @@ TEST_F(DecoderModelHookOrderTest, ClkInvalidationPrecedesClkFrameStartOnce) { frame_.height = 64; av2_decoder_model_verifier_on_reference_invalidation(pbi_, true); - Av2DmVerifierStats before_start; + Av2DmVerifierStats before_start = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_start)); av2_decoder_model_verifier_on_frame_wrapup_start(pbi_); av2_decoder_model_verifier_on_frame_unit_complete(pbi_); - Av2DmVerifierStats after_start; + Av2DmVerifierStats after_start = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_start)); EXPECT_EQ(after_start.reference_invalidations, 1u); EXPECT_EQ(after_start.olk_invalidations, 0u); @@ -708,6 +753,32 @@ TEST_F(DecoderModelHookOrderTest, ClkInvalidationPrecedesClkFrameStartOnce) { after_start.last_frame_start_event); } +TEST_F(DecoderModelHookOrderTest, + ClkGenerationRetirementUsesNewActiveReferenceRange) { + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + pbi_->common.ref_frame_map[0] = nullptr; + pbi_->valid_for_referencing[0] = 0; + pbi_->common.ref_frame_map[7] = &frame_; + pbi_->valid_for_referencing[7] = 1; + Av2DmVerifierStats before = {}; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before)); + ASSERT_EQ(before.live_generations, 1u); + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->seq_list[0][0].ref_frames = 4; + pbi_->common.seq_params = pbi_->seq_list[0][0]; + av2_decoder_model_verifier_on_sequence_header(pbi_, 0, 0); + pbi_->obu_type = OBU_CLOSED_LOOP_KEY; + av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); + + Av2DmVerifierStats after = {}; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after)); + EXPECT_EQ(after.live_generations, 0u); +} + TEST_F(DecoderModelHookOrderTest, DelayedImplicitAndCurrentOutputsKeepSeparateProvenance) { pbi_->seq_list[1][0] = pbi_->seq_list[0][0]; @@ -724,7 +795,7 @@ TEST_F(DecoderModelHookOrderTest, pbi_->valid_for_referencing[0] = 1; av2_decoder_model_verifier_after_reference_update(pbi_, 1); - Av2DmVerifierStats before_output; + Av2DmVerifierStats before_output = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_output)); EXPECT_EQ(before_output.outputs, 0u); @@ -738,7 +809,7 @@ TEST_F(DecoderModelHookOrderTest, av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, AV2_DM_PRESENTATION_OWNER_IMPLICIT); - Av2DmVerifierStats implicit_stats; + Av2DmVerifierStats implicit_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &implicit_stats)); EXPECT_EQ(implicit_stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(implicit_stats.last_output_presentation_frame_unit, 0u); @@ -751,7 +822,7 @@ TEST_F(DecoderModelHookOrderTest, av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, AV2_DM_PRESENTATION_OWNER_CURRENT); - Av2DmVerifierStats current_stats; + Av2DmVerifierStats current_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, ¤t_stats)); EXPECT_EQ(current_stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(current_stats.last_output_presentation_frame_unit, 1u); @@ -794,7 +865,7 @@ TEST_F(DecoderModelHookOrderTest, EXPECT_TRUE(frame_.frame_output_done); EXPECT_TRUE(second_frame_.frame_output_done); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.outputs, 2u); EXPECT_EQ(stats.last_output_callback_frame_unit, 1u); @@ -835,7 +906,7 @@ TEST_F(DecoderModelHookOrderTest, EXPECT_EQ(pbi_->output_frames[0], &second_frame_); EXPECT_EQ(pbi_->output_frames[1], &frame_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.outputs, 2u); EXPECT_EQ(stats.last_output_callback_frame_unit, 1u); @@ -862,7 +933,7 @@ TEST_F(DecoderModelHookOrderTest, ASSERT_EQ(av2_output_frame_buffers(pbi_, 0), 0); ASSERT_EQ(pbi_->num_output_frames, 1u); EXPECT_EQ(pbi_->output_frames[0], &frame_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(stats.last_output_presentation_frame_unit, 0u); @@ -881,7 +952,7 @@ TEST_F(DecoderModelHookOrderTest, FlushUsesPendingImplicitOwner) { ASSERT_EQ(flush_remaining_frames(pbi_, 100), AVM_CODEC_OK); ASSERT_EQ(pbi_->num_output_frames, 1u); EXPECT_EQ(pbi_->output_frames[0], &frame_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.last_output_callback_frame_unit, 0u); EXPECT_EQ(stats.last_output_presentation_frame_unit, 0u); @@ -913,7 +984,7 @@ TEST_F(DecoderModelHookOrderTest, Av2DmContextStats context; ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, 0, &context)); EXPECT_EQ(context.pending_dfg_bits, 800u); - Av2DmVerifierStats boundary; + Av2DmVerifierStats boundary = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &boundary)); EXPECT_EQ(boundary.live_runs, 1u); EXPECT_EQ(boundary.cvs_aggregates, 2u); @@ -926,8 +997,8 @@ TEST_F(DecoderModelHookOrderTest, av2_decoder_model_verifier_on_output(pbi_, -1, &frame_, AV2_DM_PRESENTATION_OWNER_CURRENT); - Av2DmRunStats continuing; - Av2DmRunStats clk_start; + Av2DmRunStats continuing = {}; + Av2DmRunStats clk_start = {}; ASSERT_TRUE( av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &continuing)); ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); @@ -935,7 +1006,7 @@ TEST_F(DecoderModelHookOrderTest, EXPECT_EQ(continuing.decoded_frames, 2u); EXPECT_EQ(clk_start.originating_cvs, 2u); EXPECT_EQ(clk_start.decoded_frames, 1u); - Av2DmVerifierStats delivered; + Av2DmVerifierStats delivered = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &delivered)); EXPECT_LT(boundary.last_output_event, delivered.last_frame_start_event); } @@ -1008,7 +1079,7 @@ TEST_F(DecoderModelHookOrderTest, ClkBoundaryOnlyClosesItsXlayer) { std::string::npos); EXPECT_EQ(diagnostics.find("xlayer=1 cvs=1"), std::string::npos); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.live_runs, 2u); EXPECT_EQ(stats.cvs_aggregates, 3u); @@ -1037,6 +1108,8 @@ TEST_F(DecoderModelHookOrderTest, EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_BITSTREAM_RESULT "), std::string::npos); EXPECT_NE(diagnostics.find("cvs=1 "), std::string::npos); + EXPECT_EQ(diagnostics.find("code=DECODER_BUFFER_DELAY_INCONSISTENT"), + std::string::npos); } TEST_F(DecoderModelResultTest, ConformantResultAndFinishAreIdempotent) { @@ -1045,7 +1118,7 @@ TEST_F(DecoderModelResultTest, ConformantResultAndFinishAreIdempotent) { av2_decoder_model_verifier_finish(pbi_); av2_decoder_model_verifier_finish(pbi_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.conformant_results, 1u); @@ -1074,7 +1147,7 @@ TEST_F(DecoderModelResultTest, Profile5HighTierConfigurationIsConformant) { av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.conformant_results, 1u); @@ -1108,7 +1181,7 @@ TEST_F(DecoderModelResultTest, "violations=0 reason=internal_failure"), std::string::npos); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_FALSE(stats.available); EXPECT_TRUE(stats.failed); @@ -1116,6 +1189,44 @@ TEST_F(DecoderModelResultTest, EXPECT_EQ(stats.indeterminate_results, 1u); } +TEST_F(DecoderModelResultTest, + ExactModelLimbAllocationFailureUsesAllocationDiagnostic) { + static const uint32_t kTimeScales[] = { + UINT32_C(4294967291), UINT32_C(4294967279), UINT32_C(4294967231), + UINT32_C(4294967197), UINT32_C(4294967189), UINT32_C(4294967161), + UINT32_C(4294967143), UINT32_C(4294967111), UINT32_C(4294967087), + }; + testing::internal::CaptureStderr(); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + UpdateAndOutput(); + for (const uint32_t time_scale : kTimeScales) { + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + pbi_->common.ci_params_per_layer[0].timing_info.num_units_in_display_tick = + time_scale / 30; + pbi_->common.ci_params_per_layer[0].timing_info.time_scale = time_scale; + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, + 0, 0, true); + UpdateAndOutput(); + } + + av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, + 8); + av2_dm_rational_set_allocation_failure_after_for_testing(0); + StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, + 0, true); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + av2_decoder_model_verifier_finish(pbi_); + const std::string diagnostics = testing::internal::GetCapturedStderr(); + + EXPECT_NE(diagnostics.find("code=ALLOCATION_FAILURE"), std::string::npos); + EXPECT_EQ(diagnostics.find("code=ARITHMETIC_FAILURE"), std::string::npos); + Av2DmVerifierStats stats{}; + ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); + EXPECT_GT(stats.indeterminate_results, 0u); +} + TEST_F(DecoderModelResultTest, EarlyVerifierFailureIsIndeterminateAndIdempotent) { av2_decoder_model_verifier_destroy(pbi_); @@ -1137,7 +1248,7 @@ TEST_F(DecoderModelResultTest, "violations=0 reason=internal_failure"), std::string::npos); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_TRUE(stats.available); EXPECT_TRUE(stats.failed); @@ -1245,7 +1356,7 @@ TEST_F(DecoderModelResultTest, StaticLevelViolationIsNonConformant) { UpdateAndOutput(); av2_decoder_model_verifier_finish(pbi_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.non_conformant_results, 1u); @@ -1261,7 +1372,7 @@ TEST_F(DecoderModelResultTest, WarningIsReportedWhenViolationOccurs) { testing::internal::CaptureStderr(); UpdateAndOutput(); - Av2DmVerifierStats live; + Av2DmVerifierStats live = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); EXPECT_EQ(live.outputs, 1u); av2_decoder_model_verifier_finish(pbi_); @@ -1272,7 +1383,7 @@ TEST_F(DecoderModelResultTest, WarningIsReportedWhenViolationOccurs) { EXPECT_NE( final.find("AV2_DECODER_MODEL_BITSTREAM_RESULT status=NON_CONFORMANT"), std::string::npos); - Av2DmVerifierStats finished; + Av2DmVerifierStats finished = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &finished)); EXPECT_EQ(finished.live_runs, 0u); } @@ -1326,7 +1437,7 @@ TEST_F(DecoderModelResultTest, 0, true); UpdateAndOutput(); - Av2DmRunStats before; + Av2DmRunStats before = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &before)); EXPECT_EQ(before.originating_cvs, 1u); EXPECT_FALSE(before.initial_presentation_delay_known); @@ -1343,7 +1454,7 @@ TEST_F(DecoderModelResultTest, EXPECT_NE(diagnostics.find("AV2_DECODER_MODEL_CVS_RESULT " "status=CONFORMANT xlayer=0 cvs=1"), std::string::npos); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.outputs, 2u); } @@ -1431,7 +1542,7 @@ TEST_F(DecoderModelResultTest, ConsecutiveCvsRetainRequiredLiveRuns) { StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, 0, true); UpdateAndOutput(); - Av2DmVerifierStats live; + Av2DmVerifierStats live = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); EXPECT_EQ(live.live_runs, static_cast(cvs + 1)); EXPECT_LE(live.live_generations, 1u); @@ -1442,7 +1553,7 @@ TEST_F(DecoderModelResultTest, ConsecutiveCvsRetainRequiredLiveRuns) { av2_decoder_model_verifier_finish(pbi_); (void)testing::internal::GetCapturedStderr(); - Av2DmVerifierStats finished; + Av2DmVerifierStats finished = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &finished)); EXPECT_EQ(finished.live_runs, 0u); EXPECT_LE(finished.live_generations, 1u); @@ -1467,7 +1578,7 @@ TEST_F(DecoderModelResultTest, StartFrame(OBU_OPEN_LOOP_KEY); UpdateAndOutput(); - Av2DmVerifierStats live; + Av2DmVerifierStats live = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); EXPECT_EQ(live.rap_starts, 3u); EXPECT_EQ(live.applicable_rap_starts, 3u); @@ -1546,14 +1657,14 @@ TEST_F(DecoderModelResultTest, StartFrame(OBU_OPEN_LOOP_KEY); UpdateAndOutput(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.rap_starts, 1u); EXPECT_EQ(stats.applicable_rap_starts, 1u); EXPECT_EQ(stats.rap_runs_started, 0u); EXPECT_EQ(stats.rap_runs_skipped, 1u); EXPECT_EQ(stats.live_runs, 1u); - Av2DmRunStats run; + Av2DmRunStats run = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); EXPECT_EQ(run.rap, -1); EXPECT_EQ(run.decoded_frames, 2u); @@ -1568,7 +1679,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_force_rap_coverage_overflow_for_testing(pbi_); StartFrame(OBU_CLOSED_LOOP_KEY); - Av2DmVerifierStats failed; + Av2DmVerifierStats failed = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &failed)); EXPECT_TRUE(failed.failed); EXPECT_EQ(failed.applicable_rap_starts, UINT64_MAX); @@ -1638,7 +1749,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_on_output(pbi_, -1, &second_frame_, AV2_DM_PRESENTATION_OWNER_CURRENT); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.contexts, 3u); EXPECT_EQ(stats.rap_starts, 4u); @@ -1675,7 +1786,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.non_conformant_results, 1u); @@ -1740,7 +1851,7 @@ TEST_F(DecoderModelResultTest, StartFrame(OBU_OPEN_LOOP_KEY); UpdateAndOutput(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.applicable_rap_starts, 3u); EXPECT_EQ(stats.rap_runs_started, 3u); @@ -1772,7 +1883,7 @@ TEST_F(DecoderModelResultTest, EveryRapSelectionScalesWithExactCoverage) { UpdateAndOutput(); } - Av2DmVerifierStats live; + Av2DmVerifierStats live = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &live)); const uint64_t expected_started = check_every_rap ? rap_count : (rap_count > 0 ? 1 : 0); @@ -1826,13 +1937,13 @@ TEST_F(DecoderModelResultTest, 0, 0, true); UpdateAndOutput(); - Av2DmVerifierStats verifier_stats; + Av2DmVerifierStats verifier_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); EXPECT_EQ(verifier_stats.cvs_aggregates, cvs); EXPECT_EQ(verifier_stats.open_cvs, 1u); EXPECT_EQ(verifier_stats.live_runs, cvs); for (uint32_t run = 0; run < cvs; ++run) { - Av2DmRunStats run_stats; + Av2DmRunStats run_stats = {}; ASSERT_TRUE( av2_decoder_model_verifier_get_run_stats(pbi_, 0, run, &run_stats)); EXPECT_EQ(run_stats.originating_cvs, run + 1); @@ -1842,20 +1953,20 @@ TEST_F(DecoderModelResultTest, } } - Av2DmVerifierStats before_end; + Av2DmVerifierStats before_end = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before_end)); av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); - Av2DmVerifierStats after_end; + Av2DmVerifierStats after_end = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after_end)); EXPECT_EQ(after_end.event_count, before_end.event_count + 1); for (uint32_t run = 0; run < 15; ++run) { - Av2DmRunStats run_stats; + Av2DmRunStats run_stats = {}; ASSERT_TRUE( av2_decoder_model_verifier_get_run_stats(pbi_, 0, run, &run_stats)); EXPECT_TRUE(run_stats.initial_presentation_delay_known); } av2_decoder_model_verifier_before_final_output(pbi_, UINT64_MAX, true); - Av2DmVerifierStats repeated; + Av2DmVerifierStats repeated = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &repeated)); EXPECT_EQ(repeated.event_count, after_end.event_count); @@ -1902,7 +2013,7 @@ TEST_F(DecoderModelResultTest, UpdateAndOutput(); } - Av2DmVerifierStats verifier_stats; + Av2DmVerifierStats verifier_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); ASSERT_EQ(verifier_stats.contexts, 2u); EXPECT_EQ(verifier_stats.cvs_aggregates, 3u); @@ -1913,7 +2024,7 @@ TEST_F(DecoderModelResultTest, &context_stats)); EXPECT_EQ(context_stats.scope.xlayer_id, 0); for (uint32_t run = 0; run < 3; ++run) { - Av2DmRunStats run_stats; + Av2DmRunStats run_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, context, run, &run_stats)); EXPECT_EQ(run_stats.originating_cvs, run + 1); @@ -1939,29 +2050,29 @@ TEST_F(DecoderModelResultTest, EndOfInputTargetsOnlyTheEndingStreamGeneration) { 0, true); UpdateAndOutput(); - Av2DmRunStats run; + Av2DmRunStats run = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); EXPECT_EQ(run.stream_generation, 0u); EXPECT_FALSE(run.initial_presentation_delay_known); - Av2DmVerifierStats before; + Av2DmVerifierStats before = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before)); av2_decoder_model_verifier_before_final_output(pbi_, 1, false); - Av2DmVerifierStats wrong_generation; + Av2DmVerifierStats wrong_generation = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &wrong_generation)); EXPECT_EQ(wrong_generation.event_count, before.event_count); ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); EXPECT_FALSE(run.initial_presentation_delay_known); av2_decoder_model_verifier_before_final_output(pbi_, 0, false); - Av2DmVerifierStats ending_generation; + Av2DmVerifierStats ending_generation = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &ending_generation)); EXPECT_EQ(ending_generation.event_count, before.event_count + 1); ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &run)); EXPECT_TRUE(run.initial_presentation_delay_known); av2_decoder_model_verifier_before_final_output(pbi_, 0, false); - Av2DmVerifierStats repeated; + Av2DmVerifierStats repeated = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &repeated)); EXPECT_EQ(repeated.event_count, ending_generation.event_count); } @@ -1996,7 +2107,7 @@ TEST_F(DecoderModelResultTest, UnalignedXlayerClkBoundariesRemainIndependent) { 0, true); UpdateAndOutput(); - Av2DmVerifierStats verifier_stats; + Av2DmVerifierStats verifier_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); ASSERT_EQ(verifier_stats.contexts, 2u); EXPECT_EQ(verifier_stats.cvs_aggregates, 3u); @@ -2006,13 +2117,13 @@ TEST_F(DecoderModelResultTest, UnalignedXlayerClkBoundariesRemainIndependent) { Av2DmContextStats context_stats; ASSERT_TRUE(av2_decoder_model_verifier_get_context_stats(pbi_, context, &context_stats)); - Av2DmRunStats first_run; + Av2DmRunStats first_run = {}; ASSERT_TRUE( av2_decoder_model_verifier_get_run_stats(pbi_, context, 0, &first_run)); EXPECT_EQ(first_run.originating_cvs, 1u); if (context_stats.scope.xlayer_id == 0) { EXPECT_EQ(first_run.decoded_frames, 2u); - Av2DmRunStats second_run; + Av2DmRunStats second_run = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, context, 1, &second_run)); EXPECT_EQ(second_run.originating_cvs, 2u); @@ -2020,15 +2131,14 @@ TEST_F(DecoderModelResultTest, UnalignedXlayerClkBoundariesRemainIndependent) { } else { EXPECT_EQ(context_stats.scope.xlayer_id, 1); EXPECT_EQ(first_run.decoded_frames, 1u); - Av2DmRunStats unused; + Av2DmRunStats unused = {}; EXPECT_FALSE( av2_decoder_model_verifier_get_run_stats(pbi_, context, 1, &unused)); } } } -TEST_F(DecoderModelResultTest, - IncompatibleClkTransitionOnlyMakesOlderRunIndeterminate) { +TEST_F(DecoderModelResultTest, DecCtClkTransitionKeepsContinuousRunVerifiable) { StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, 0, true); UpdateAndOutput(); @@ -2040,13 +2150,15 @@ TEST_F(DecoderModelResultTest, 0, true); UpdateAndOutput(); - Av2DmRunStats older; - Av2DmRunStats clk_start; + Av2DmRunStats older = {}; + Av2DmRunStats clk_start = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); EXPECT_EQ(older.originating_cvs, 1u); - EXPECT_EQ(older.reason, AV2_DM_REASON_INCOMPATIBLE_CONFIGURATION_TRANSITION); - EXPECT_EQ(older.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_EQ(older.reason, AV2_DM_REASON_NONE); + EXPECT_EQ(older.status, AV2_DM_RESULT_CONFORMANT); + EXPECT_EQ(older.decoded_frames, 2u); + EXPECT_EQ(older.output_frames, 2u); EXPECT_EQ(clk_start.originating_cvs, 2u); EXPECT_EQ(clk_start.reason, AV2_DM_REASON_NONE); EXPECT_EQ(clk_start.decoded_frames, 1u); @@ -2054,10 +2166,10 @@ TEST_F(DecoderModelResultTest, testing::internal::CaptureStderr(); av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - EXPECT_NE(diagnostics.find("status=INDETERMINATE xlayer=0 ops=-1 op=-1 " - "rap=0 mode=resource decoded=1 outputs=1 " + EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 " + "rap=0 mode=resource decoded=2 outputs=2 " "reordered_outputs=0 violations=0 " - "reason=incompatible_configuration_transition"), + "reason=none"), std::string::npos); EXPECT_NE(diagnostics.find("status=CONFORMANT xlayer=0 ops=-1 op=-1 rap=1 " "mode=resource decoded=1 outputs=1"), @@ -2077,7 +2189,7 @@ TEST_F(DecoderModelResultTest, 0, true); UpdateAndOutput(); - Av2DmRunStats older; + Av2DmRunStats older = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); EXPECT_EQ(older.originating_cvs, 1u); EXPECT_EQ(older.reason, AV2_DM_REASON_MISSING_REQUIRED_INPUT); @@ -2122,7 +2234,7 @@ TEST_F(DecoderModelResultTest, 100000000); av2_decoder_model_verifier_on_frame_unit_complete(pbi_); UpdateAndOutput(); - Av2DmRunStats before_update; + Av2DmRunStats before_update = {}; ASSERT_TRUE( av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &before_update)); EXPECT_EQ(before_update.status, AV2_DM_RESULT_CONFORMANT); @@ -2142,7 +2254,7 @@ TEST_F(DecoderModelResultTest, 0, true, false, true); EXPECT_TRUE(av2_decoder_model_verifier_should_stop(pbi_)); - Av2DmRunStats older; + Av2DmRunStats older = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); EXPECT_EQ(older.originating_cvs, 1u); EXPECT_EQ(older.status, AV2_DM_RESULT_NON_CONFORMANT); @@ -2159,10 +2271,15 @@ TEST_F(DecoderModelResultTest, } TEST_F(DecoderModelResultTest, - CompatibleClkReferenceCountUpdateKeepsOlderRunVerifiable) { + ActiveConfigurationUpdatePrecedesClkInvalidationAndPreservesRunState) { StartFrame(OBU_CLOSED_LOOP_KEY, 64, 64, &frame_, 0, KEY_FRAME, false, true, 0, 0, true); UpdateAndOutput(); + Av2DmRunStats before = {}; + ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &before)); + EXPECT_EQ(before.decoded_frames, 1u); + EXPECT_EQ(before.output_frames, 1u); + EXPECT_EQ(before.active_num_ref_frames, 8u); av2_decoder_model_verifier_record_obu(pbi_, OBU_TEMPORAL_DELIMITER, 0, 0, 0, 8); @@ -2174,21 +2291,24 @@ TEST_F(DecoderModelResultTest, 0, true, false, true); UpdateAndOutput(); - Av2DmVerifierStats verifier_stats; + Av2DmVerifierStats verifier_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); EXPECT_EQ(verifier_stats.clk_invalidations, 1u); EXPECT_EQ(verifier_stats.live_runs, 2u); - Av2DmRunStats older; - Av2DmRunStats clk_start; + Av2DmRunStats older = {}; + Av2DmRunStats clk_start = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 0, &older)); ASSERT_TRUE(av2_decoder_model_verifier_get_run_stats(pbi_, 0, 1, &clk_start)); EXPECT_EQ(older.originating_cvs, 1u); EXPECT_EQ(older.reason, AV2_DM_REASON_NONE); + EXPECT_EQ(older.status, AV2_DM_RESULT_CONFORMANT); EXPECT_EQ(older.decoded_frames, 2u); + EXPECT_EQ(older.output_frames, 2u); EXPECT_EQ(older.active_num_ref_frames, 16u); EXPECT_EQ(clk_start.originating_cvs, 2u); EXPECT_EQ(clk_start.reason, AV2_DM_REASON_NONE); EXPECT_EQ(clk_start.decoded_frames, 1u); + EXPECT_EQ(clk_start.output_frames, 1u); EXPECT_EQ(clk_start.active_num_ref_frames, 16u); } @@ -2217,14 +2337,14 @@ TEST_F(DecoderModelResultTest, ClkOlkRasClkRunsCoexistAcrossCvsOwnership) { 0, true, false, true); UpdateAndOutput(); - Av2DmVerifierStats verifier_stats; + Av2DmVerifierStats verifier_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); EXPECT_EQ(verifier_stats.cvs_aggregates, 2u); EXPECT_EQ(verifier_stats.open_cvs, 1u); EXPECT_EQ(verifier_stats.live_runs, 4u); const uint64_t expected_decoded_frames[4] = { 4, 3, 2, 1 }; for (uint32_t run = 0; run < 4; ++run) { - Av2DmRunStats run_stats; + Av2DmRunStats run_stats = {}; ASSERT_TRUE( av2_decoder_model_verifier_get_run_stats(pbi_, 0, run, &run_stats)); EXPECT_EQ(run_stats.originating_cvs, run < 3 ? 1u : 2u); @@ -2260,7 +2380,7 @@ TEST_F(DecoderModelResultTest, ExplicitOperatingPointLevelOverridesSequence) { av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 2u); EXPECT_EQ(stats.conformant_results, 1u); @@ -2402,7 +2522,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 2u); EXPECT_EQ(stats.non_conformant_results, 2u); @@ -2439,7 +2559,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.non_conformant_results, 1u); @@ -2489,7 +2609,7 @@ TEST_F(DecoderModelResultTest, UndefinedLowMultistreamLevelIsIndeterminate) { av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.indeterminate_results, 1u); @@ -2506,7 +2626,7 @@ TEST_F(DecoderModelResultTest, MissingActiveConfigurationIsIndeterminate) { UpdateAndOutput(); av2_decoder_model_verifier_finish(pbi_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 1u); EXPECT_EQ(stats.indeterminate_results, 1u); @@ -2529,7 +2649,7 @@ TEST_F(DecoderModelResultTest, testing::internal::CaptureStderr(); av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.indeterminate_results, 1u); EXPECT_EQ(stats.non_conformant_results, 0u); @@ -2554,7 +2674,7 @@ TEST_F(DecoderModelResultTest, testing::internal::CaptureStderr(); av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.indeterminate_results, 1u); EXPECT_EQ(stats.non_conformant_results, 0u); @@ -2588,7 +2708,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, AV2_DM_PRESENTATION_OWNER_IMPLICIT); - Av2DmVerifierStats output_stats; + Av2DmVerifierStats output_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &output_stats)); EXPECT_EQ(output_stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(output_stats.last_output_presentation_frame_unit, 0u); @@ -2619,7 +2739,7 @@ TEST_F(DecoderModelResultTest, MissingOutputGenerationIsIndeterminate) { av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.indeterminate_results, 1u); EXPECT_NE(diagnostics.find("reason=missing_frame_generation"), @@ -2652,7 +2772,7 @@ TEST_F(DecoderModelResultTest, testing::internal::CaptureStderr(); av2_decoder_model_verifier_on_output(pbi_, 0, &second_frame_, AV2_DM_PRESENTATION_OWNER_CURRENT); - Av2DmVerifierStats output_stats; + Av2DmVerifierStats output_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &output_stats)); EXPECT_EQ(output_stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(output_stats.last_output_presentation_frame_unit, 1u); @@ -2690,7 +2810,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, AV2_DM_PRESENTATION_OWNER_CURRENT); - Av2DmVerifierStats current_stats; + Av2DmVerifierStats current_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, ¤t_stats)); EXPECT_EQ(current_stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(current_stats.last_output_presentation_frame_unit, 1u); @@ -2700,7 +2820,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_on_output(pbi_, 0, &frame_, AV2_DM_PRESENTATION_OWNER_IMPLICIT); - Av2DmVerifierStats implicit_stats; + Av2DmVerifierStats implicit_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &implicit_stats)); EXPECT_EQ(implicit_stats.last_output_callback_frame_unit, 1u); EXPECT_EQ(implicit_stats.last_output_presentation_frame_unit, 0u); @@ -2724,7 +2844,7 @@ TEST_F(DecoderModelResultTest, IncompleteRasSeedHasExactReason) { av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.indeterminate_results, 1u); EXPECT_NE(diagnostics.find("status=INDETERMINATE"), std::string::npos); @@ -2748,7 +2868,7 @@ TEST_F(DecoderModelResultTest, av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 2u); EXPECT_EQ(stats.indeterminate_results, 2u); @@ -2822,7 +2942,7 @@ TEST_F(DecoderModelResultTest, CompleteRasSeedIsReplayedFromFreshModel) { av2_decoder_model_verifier_finish(pbi_); const std::string diagnostics = testing::internal::GetCapturedStderr(); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.result_count, 2u); // In the continuous CLK run the TU-0 frame is not output until the RAS @@ -2922,7 +3042,7 @@ TEST_F(DecoderModelResultTest, CopiesEveryRapSelectionIntoVerifierState) { av2_decoder_model_verifier_destroy(pbi_); pbi_->decoder_model_check_every_rap = 1; av2_decoder_model_verifier_init(pbi_); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_TRUE(stats.check_every_rap); @@ -3185,7 +3305,7 @@ class DecoderModelEncodedStreamTest : public ::testing::Test, }; TEST_F(DecoderModelEncodedStreamTest, Level20Control) { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; ASSERT_TRUE( av2_dm_get_level_limits(SEQ_LEVEL_2_0, 0, MAIN_420_10_IP0, &limits)); constexpr uint64_t kPictureSize = 352 * 288; @@ -3225,8 +3345,8 @@ TEST_F(DecoderModelEncodedStreamTest, Level20Control) { } TEST_F(DecoderModelEncodedStreamTest, Level30AndIncorrectLevel21) { - Av2DmLevelLimits level_2_1; - Av2DmLevelLimits level_3_0; + Av2DmLevelLimits level_2_1{}; + Av2DmLevelLimits level_3_0{}; ASSERT_TRUE( av2_dm_get_level_limits(SEQ_LEVEL_2_1, 0, MAIN_420_10_IP0, &level_2_1)); ASSERT_TRUE( diff --git a/test/decoder_model_lifecycle.h b/test/decoder_model_lifecycle.h new file mode 100644 index 0000000000..6339c2474f --- /dev/null +++ b/test/decoder_model_lifecycle.h @@ -0,0 +1,63 @@ +/* + * 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_TEST_DECODER_MODEL_LIFECYCLE_H_ +#define AVM_TEST_DECODER_MODEL_LIFECYCLE_H_ + +#include "av2/common/decoder_model.h" +#include "av2/decoder/decoder_model.h" + +namespace libavm_test { + +class ScopedDmBufferPool : public ::Av2DmBufferPool { + public: + ScopedDmBufferPool() { av2_dm_buffer_pool_init(this); } + ~ScopedDmBufferPool() { av2_dm_buffer_pool_destroy(this); } + ScopedDmBufferPool(const ScopedDmBufferPool &) = delete; + ScopedDmBufferPool &operator=(const ScopedDmBufferPool &) = delete; +}; + +class ScopedDmLevelLimits : public ::Av2DmLevelLimits { + public: + ScopedDmLevelLimits() { av2_dm_level_limits_init(this); } + ~ScopedDmLevelLimits() { av2_dm_level_limits_destroy(this); } + ScopedDmLevelLimits(const ScopedDmLevelLimits &) = delete; + ScopedDmLevelLimits &operator=(const ScopedDmLevelLimits &) = delete; +}; + +class ScopedDmState : public ::Av2DmState { + public: + ScopedDmState() { av2_dm_state_init(this); } + ~ScopedDmState() { av2_dm_state_destroy(this); } + ScopedDmState(const ScopedDmState &) = delete; + ScopedDmState &operator=(const ScopedDmState &) = delete; +}; + +class ScopedDmVerifierStats : public ::Av2DmVerifierStats { + public: + ScopedDmVerifierStats() { av2_decoder_model_verifier_stats_init(this); } + ~ScopedDmVerifierStats() { av2_decoder_model_verifier_stats_destroy(this); } + ScopedDmVerifierStats(const ScopedDmVerifierStats &) = delete; + ScopedDmVerifierStats &operator=(const ScopedDmVerifierStats &) = delete; +}; + +class ScopedDmRunStats : public ::Av2DmRunStats { + public: + ScopedDmRunStats() { av2_decoder_model_run_stats_init(this); } + ~ScopedDmRunStats() { av2_decoder_model_run_stats_destroy(this); } + ScopedDmRunStats(const ScopedDmRunStats &) = delete; + ScopedDmRunStats &operator=(const ScopedDmRunStats &) = delete; +}; + +} // namespace libavm_test + +#endif // AVM_TEST_DECODER_MODEL_LIFECYCLE_H_ diff --git a/test/decoder_model_parser_test.cc b/test/decoder_model_parser_test.cc index 9eefcd27fc..f2a27f30e6 100644 --- a/test/decoder_model_parser_test.cc +++ b/test/decoder_model_parser_test.cc @@ -22,9 +22,12 @@ #include "av2/decoder/annexF.h" #include "av2/decoder/decoder.h" #include "av2/decoder/decoder_model.h" +#include "test/decoder_model_lifecycle.h" namespace { +using Av2DmVerifierStats = libavm_test::ScopedDmVerifierStats; + class DecoderModelParserTest : public ::testing::Test { protected: void SetUp() override { @@ -51,7 +54,7 @@ class DecoderModelParserTest : public ::testing::Test { }; TEST_F(DecoderModelParserTest, LifecycleStartsWithAvailableEmptyState) { - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_TRUE(stats.available); EXPECT_FALSE(stats.failed); @@ -223,7 +226,7 @@ TEST_F(DecoderModelParserTest, RasSeedsAreFilteredPerOperatingPoint) { bool found_excluding_op = false; bool found_including_op = false; - Av2DmVerifierStats verifier_stats; + Av2DmVerifierStats verifier_stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &verifier_stats)); for (uint32_t i = 0; i < verifier_stats.contexts; ++i) { Av2DmContextStats context; @@ -259,7 +262,7 @@ TEST_F(DecoderModelParserTest, GlobalOperatingPointCreatesPerXlayerContexts) { ops->op[0].mlayer_info.ops_tlayer_map[3][2] = 1 << 1; av2_decoder_model_verifier_on_operating_point_set(pbi_, GLOBAL_XLAYER_ID, 2); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); ASSERT_EQ(stats.contexts, 2u); Av2DmContextStats first; @@ -514,11 +517,11 @@ TEST_F(DecoderModelParserTest, StreamBoundaryRelinksIdenticalConfiguration) { AddWholeXlayerContext(0, 0); pbi_->common.seq_params = pbi_->seq_list[0][0]; av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); - Av2DmVerifierStats before; + Av2DmVerifierStats before = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &before)); av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); av2_decoder_model_verifier_on_active_configuration(pbi_, 0, 0); - Av2DmVerifierStats after; + Av2DmVerifierStats after = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &after)); EXPECT_EQ(after.event_count, before.event_count + 2); Av2DmContextStats context; @@ -616,7 +619,7 @@ TEST_F(DecoderModelParserTest, FilteredRapDoesNotSuppressOtherXlayerRap) { av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); av2_decoder_model_verifier_on_obu_filtered(pbi_); av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 2, 0, 0, 40); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.rap_starts, 2u); } @@ -628,7 +631,7 @@ TEST_F(DecoderModelParserTest, FilteredRapDoesNotSuppressNextSourceRap) { av2_decoder_model_verifier_on_source_frame_unit_start(pbi_, 1, 0, 0); av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); av2_decoder_model_verifier_record_obu(pbi_, OBU_CLOSED_LOOP_KEY, 1, 0, 0, 40); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.rap_starts, 2u); } @@ -636,7 +639,7 @@ TEST_F(DecoderModelParserTest, FilteredRapDoesNotSuppressNextSourceRap) { TEST_F(DecoderModelParserTest, TemporalPointRetainsFullUlebValueAndPresence) { constexpr uint64_t kPresentationTime = 0xfedcba98u; av2_decoder_model_verifier_on_temporal_point(pbi_, kPresentationTime); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_TRUE(stats.temporal_point_present); EXPECT_EQ(stats.temporal_point, kPresentationTime); @@ -645,7 +648,7 @@ TEST_F(DecoderModelParserTest, TemporalPointRetainsFullUlebValueAndPresence) { TEST_F(DecoderModelParserTest, ConfigurationBoundaryIsImmutableEvent) { av2_decoder_model_verifier_on_stream_configuration_change(pbi_, false); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.event_count, 1u); } diff --git a/test/decoder_model_test.cc b/test/decoder_model_test.cc index c84730c7c3..c79d01ca00 100644 --- a/test/decoder_model_test.cc +++ b/test/decoder_model_test.cc @@ -23,14 +23,35 @@ extern "C" { #include "av2/common/timing.h" } #include "third_party/googletest/src/googletest/include/gtest/gtest.h" +#include "test/decoder_model_lifecycle.h" namespace { +using Av2DmBufferPool = libavm_test::ScopedDmBufferPool; +using Av2DmLevelLimits = libavm_test::ScopedDmLevelLimits; +using Av2DmState = libavm_test::ScopedDmState; + Av2DmUnsignedWide MakeWide(uint64_t limb3, uint64_t limb2, uint64_t limb1, uint64_t limb0) { return { { limb0, limb1, limb2, limb3 } }; } +bool MakeDynamicRational(Av2DmRational *value) { + static const uint64_t primes[] = { + UINT64_C(4294967291), UINT64_C(4294967279), UINT64_C(4294967231), + UINT64_C(4294967197), UINT64_C(4294967189), UINT64_C(4294967161), + UINT64_C(4294967143), UINT64_C(4294967111), UINT64_C(4294967087), + }; + Av2DmRational term{}; + bool made = av2_dm_rational_make(0, 1, value); + for (uint64_t prime : primes) { + made = made && av2_dm_rational_make(1, prime, &term) && + av2_dm_rational_add(value, &term, value); + } + av2_dm_rational_destroy(&term); + return made; +} + void ExpectRational(const Av2DmRational &value, uint64_t limb3, uint64_t limb2, uint64_t limb1, uint64_t limb0, uint64_t denominator, bool negative = false) { @@ -46,7 +67,7 @@ void ExpectRational(const Av2DmRational &value, uint64_t limb3, uint64_t limb2, } TEST(DecoderModelRationalTest, RejectsZeroDenominatorAndCanonicalizesZero) { - Av2DmRational value; + Av2DmRational value{}; EXPECT_FALSE(av2_dm_rational_make(1, 0, &value)); ASSERT_TRUE( av2_dm_rational_make_wide(MakeWide(0, 0, 0, 0), 123, true, &value)); @@ -54,7 +75,7 @@ TEST(DecoderModelRationalTest, RejectsZeroDenominatorAndCanonicalizesZero) { } TEST(DecoderModelRationalTest, ReducesGoldenVectors) { - Av2DmRational value; + Av2DmRational value{}; ASSERT_TRUE(av2_dm_rational_make(1667000, 1000000, &value)); ExpectRational(value, 0, 0, 0, 1667, 1000); @@ -64,9 +85,9 @@ TEST(DecoderModelRationalTest, ReducesGoldenVectors) { } TEST(DecoderModelRationalTest, AddsAndSubtractsExactly) { - Av2DmRational one_third; - Av2DmRational one_sixth; - Av2DmRational result; + Av2DmRational one_third{}; + Av2DmRational one_sixth{}; + Av2DmRational result{}; ASSERT_TRUE(av2_dm_rational_make(1, 3, &one_third)); ASSERT_TRUE(av2_dm_rational_make(1, 6, &one_sixth)); ASSERT_TRUE(av2_dm_rational_add(&one_third, &one_sixth, &result)); @@ -78,8 +99,8 @@ TEST(DecoderModelRationalTest, AddsAndSubtractsExactly) { } TEST(DecoderModelRationalTest, MultipliesAndDividesWithCrossCancellation) { - Av2DmRational value; - Av2DmRational result; + Av2DmRational value{}; + Av2DmRational result{}; ASSERT_TRUE(av2_dm_rational_make(UINT64_MAX, UINT64_MAX - 1, &value)); ASSERT_TRUE(av2_dm_rational_multiply_u64(&value, UINT64_MAX - 1, &result)); ExpectRational(result, 0, 0, 0, UINT64_MAX, 1); @@ -94,9 +115,9 @@ TEST(DecoderModelRationalTest, MultipliesAndDividesWithCrossCancellation) { } TEST(DecoderModelRationalTest, RetainsDenominatorsWiderThan64Bits) { - Av2DmRational left; - Av2DmRational right; - Av2DmRational result; + Av2DmRational left{}; + Av2DmRational right{}; + Av2DmRational result{}; ASSERT_TRUE(av2_dm_rational_make(1, UINT64_MAX, &left)); ASSERT_TRUE(av2_dm_rational_make(1, UINT64_MAX - 1, &right)); // Golden result generated with Python fractions.Fraction: @@ -122,9 +143,9 @@ TEST(DecoderModelRationalTest, ComparesMaximumWideValuesAtExactBoundary) { MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX); const Av2DmUnsignedWide one_less = MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX - 1); - Av2DmRational left; - Av2DmRational equal; - Av2DmRational lower; + Av2DmRational left{}; + Av2DmRational equal{}; + Av2DmRational lower{}; ASSERT_TRUE(av2_dm_rational_make_wide(maximum, UINT64_MAX, false, &left)); ASSERT_TRUE(av2_dm_rational_make_wide(maximum, UINT64_MAX, false, &equal)); ASSERT_TRUE(av2_dm_rational_make_wide(one_less, UINT64_MAX, false, &lower)); @@ -137,30 +158,38 @@ TEST(DecoderModelRationalTest, ComparesMaximumWideValuesAtExactBoundary) { EXPECT_EQ(comparison, 1); } -TEST(DecoderModelRationalTest, DetectsMagnitudeAndDenominatorOverflow) { - Av2DmRational maximum; - Av2DmRational one; - Av2DmRational result; - ASSERT_TRUE(av2_dm_rational_make_wide( - MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX), 1, false, - &maximum)); - ASSERT_TRUE(av2_dm_rational_make(1, 1, &one)); - EXPECT_FALSE(av2_dm_rational_add(&maximum, &one, &result)); - EXPECT_FALSE(av2_dm_rational_multiply_u64(&maximum, 2, &result)); - - ASSERT_TRUE(av2_dm_rational_make(1, 1, &maximum)); - maximum.denominator = - MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX); - EXPECT_FALSE(av2_dm_rational_divide_u64(&maximum, 2, &result)); +TEST(DecoderModelRationalTest, GrowsBeyondInlineWidthExactly) { + static const uint64_t primes[] = { + UINT64_C(4294967291), UINT64_C(4294967279), UINT64_C(4294967231), + UINT64_C(4294967197), UINT64_C(4294967189), UINT64_C(4294967161), + UINT64_C(4294967143), UINT64_C(4294967111), UINT64_C(4294967087), + }; + Av2DmRational sum{}; + Av2DmRational term{}; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &sum)); + for (uint64_t prime : primes) { + ASSERT_TRUE(av2_dm_rational_make(1, prime, &term)); + ASSERT_TRUE(av2_dm_rational_add(&sum, &term, &sum)); + } + EXPECT_NE(sum.dynamic_limbs, nullptr); + EXPECT_GT(sum.denominator_limb_count, 4u); + for (size_t i = sizeof(primes) / sizeof(primes[0]); i > 0; --i) { + ASSERT_TRUE(av2_dm_rational_make(1, primes[i - 1], &term)); + ASSERT_TRUE(av2_dm_rational_subtract(&sum, &term, &sum)); + } + EXPECT_TRUE(av2_dm_rational_is_zero(&sum)); + av2_dm_rational_destroy(&sum); + av2_dm_rational_destroy(&term); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); } TEST(DecoderModelRationalTest, RebasesLongRunningTimelineExactly) { - Av2DmRational values[3]; - Av2DmRational origin; + Av2DmRational values[3]{}; + Av2DmRational origin{}; const Av2DmUnsignedWide long_time = MakeWide(0, 0, UINT64_C(1) << 20, 12345); ASSERT_TRUE(av2_dm_rational_make_wide(long_time, 90000, false, &origin)); values[0] = origin; - Av2DmRational increment; + Av2DmRational increment{}; ASSERT_TRUE(av2_dm_rational_make(1, 60000, &increment)); ASSERT_TRUE(av2_dm_rational_add(&origin, &increment, &values[1])); ASSERT_TRUE(av2_dm_rational_add(&values[1], &increment, &values[2])); @@ -171,7 +200,7 @@ TEST(DecoderModelRationalTest, RebasesLongRunningTimelineExactly) { } TEST(DecoderModelRationalTest, RebaseCopiesAliasedOrigin) { - Av2DmRational values[2]; + Av2DmRational values[2]{}; ASSERT_TRUE(av2_dm_rational_make(10, 1, &values[0])); ASSERT_TRUE(av2_dm_rational_make(11, 1, &values[1])); ASSERT_TRUE(av2_dm_rational_rebase(values, 2, &values[0])); @@ -180,22 +209,28 @@ TEST(DecoderModelRationalTest, RebaseCopiesAliasedOrigin) { } TEST(DecoderModelRationalTest, RebaseFailureIsAtomic) { - Av2DmRational values[2]; + Av2DmRational values[2]{}; ASSERT_TRUE(av2_dm_rational_make(1, 1, &values[0])); ASSERT_TRUE(av2_dm_rational_make_wide( MakeWide(UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX), 1, false, &values[1])); - Av2DmRational origin; + Av2DmRational origin{}; ASSERT_TRUE( av2_dm_rational_make_wide(MakeWide(0, 0, 0, 1), 1, true, &origin)); const Av2DmRational original_values[2] = { values[0], values[1] }; + av2_dm_rational_set_allocation_failure_after_for_testing(0); EXPECT_FALSE(av2_dm_rational_rebase(values, 2, &origin)); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); EXPECT_EQ(memcmp(values, original_values, sizeof(values)), 0); + av2_dm_rational_destroy(&values[0]); + av2_dm_rational_destroy(&values[1]); + av2_dm_rational_destroy(&origin); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); } TEST(DecoderModelBufferPoolTest, InitializesEightAndSixteenReferencePools) { - Av2DmBufferPool pool; + Av2DmBufferPool pool{}; ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); EXPECT_EQ(pool.pool_size, 10u); EXPECT_EQ(av2_dm_buffer_pool_get_free_buffer(&pool), 0); @@ -219,7 +254,7 @@ TEST(DecoderModelBufferPoolTest, InitializesEightAndSixteenReferencePools) { } TEST(DecoderModelBufferPoolTest, ReferenceSlotsMaintainExactCounts) { - Av2DmBufferPool pool; + Av2DmBufferPool pool{}; ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); ASSERT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 0, 3)); ASSERT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 1, 3)); @@ -234,7 +269,7 @@ TEST(DecoderModelBufferPoolTest, ReferenceSlotsMaintainExactCounts) { } TEST(DecoderModelBufferPoolTest, DetectsFullPoolAndCountUnderflow) { - Av2DmBufferPool pool; + Av2DmBufferPool pool{}; ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 16)); for (uint32_t i = 0; i < pool.pool_size; ++i) { ASSERT_TRUE(av2_dm_buffer_pool_add_player_ref(&pool, i)); @@ -247,7 +282,7 @@ TEST(DecoderModelBufferPoolTest, DetectsFullPoolAndCountUnderflow) { } TEST(DecoderModelBufferPoolTest, RejectsInvalidIndicesWithoutMutation) { - Av2DmBufferPool pool; + Av2DmBufferPool pool{}; ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 8, 0)); EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 0, 10)); @@ -256,9 +291,8 @@ TEST(DecoderModelBufferPoolTest, RejectsInvalidIndicesWithoutMutation) { EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); } -TEST(DecoderModelBufferPoolTest, - InactiveHistoricalBuffersRemainCountedAndReleasable) { - Av2DmBufferPool pool; +TEST(DecoderModelBufferPoolTest, InactiveBackingEntriesRemainInert) { + Av2DmBufferPool pool{}; ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 16)); ASSERT_TRUE(av2_dm_buffer_pool_add_player_ref(&pool, 17)); pool.buffers[17].generation_valid = true; @@ -266,20 +300,109 @@ TEST(DecoderModelBufferPoolTest, pool.num_ref_frames = 8; pool.pool_size = 10; - EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 1u); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 15, 0)); - EXPECT_TRUE(av2_dm_buffer_pool_set_vbi(&pool, 15, -1)); - EXPECT_TRUE(av2_dm_buffer_pool_remove_player_ref(&pool, 17)); + EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 15, -1)); + EXPECT_FALSE(av2_dm_buffer_pool_remove_player_ref(&pool, 17)); EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); + ASSERT_TRUE(av2_dm_buffer_pool_initialize(&pool, 8)); EXPECT_FALSE(pool.buffers[17].generation_valid); } +struct OwnedViolation : Av2DmViolation { + OwnedViolation() { av2_dm_violation_init(this); } + explicit OwnedViolation(const Av2DmViolation *source) : OwnedViolation() { + EXPECT_TRUE(av2_dm_violation_copy(this, source)); + } + OwnedViolation(const OwnedViolation &other) : OwnedViolation(&other) {} + OwnedViolation &operator=(const OwnedViolation &other) { + EXPECT_TRUE(av2_dm_violation_copy(this, &other)); + return *this; + } + OwnedViolation(OwnedViolation &&other) noexcept + : Av2DmViolation(static_cast(other)) { + av2_dm_violation_init(&other); + } + OwnedViolation &operator=(OwnedViolation &&other) noexcept { + av2_dm_violation_destroy(this); + static_cast(*this) = + static_cast(other); + av2_dm_violation_init(&other); + return *this; + } + ~OwnedViolation() { av2_dm_violation_destroy(this); } +}; + struct ViolationCollector { - std::vector violations; + std::vector violations; }; void CollectViolation(void *opaque, const Av2DmViolation *violation) { - static_cast(opaque)->violations.push_back(*violation); + static_cast(opaque)->violations.emplace_back(violation); +} + +void CountViolation(void *opaque, const Av2DmViolation *violation) { + (void)violation; + ++*static_cast(opaque); +} + +TEST(DecoderModelOwnershipTest, DynamicViolationOutlivesCallbackTemporary) { + ASSERT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); + Av2DmViolation source{}; + av2_dm_violation_init(&source); + source.code = AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL; + source.observed_present = true; + source.limit_present = true; + source.detail.kind = AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL; + ASSERT_TRUE(MakeDynamicRational(&source.observed)); + ASSERT_TRUE(av2_dm_rational_copy(&source.limit, &source.observed)); + ASSERT_TRUE(av2_dm_rational_copy(&source.detail.value.frame_interval, + &source.observed)); + ViolationCollector collector; + CollectViolation(&collector, &source); + av2_dm_violation_destroy(&source); + + ASSERT_EQ(collector.violations.size(), 1u); + const Av2DmViolation &saved = collector.violations[0]; + EXPECT_NE(saved.observed.dynamic_limbs, nullptr); + EXPECT_NE(saved.limit.dynamic_limbs, nullptr); + EXPECT_NE(saved.detail.value.frame_interval.dynamic_limbs, nullptr); + int comparison; + ASSERT_TRUE( + av2_dm_rational_compare(&saved.observed, &saved.limit, &comparison)); + EXPECT_EQ(comparison, 0); + collector.violations.clear(); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); +} + +TEST(DecoderModelOwnershipTest, ViolationCopyFailureIsAtomic) { + ASSERT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); + Av2DmViolation source{}; + Av2DmViolation destination{}; + Av2DmViolation original{}; + av2_dm_violation_init(&source); + av2_dm_violation_init(&destination); + av2_dm_violation_init(&original); + source.observed_present = true; + ASSERT_TRUE(MakeDynamicRational(&source.observed)); + destination.observed_present = true; + ASSERT_TRUE(av2_dm_rational_make(7, 11, &destination.observed)); + ASSERT_TRUE(av2_dm_violation_copy(&original, &destination)); + const uint64_t allocations_before = + av2_dm_rational_allocation_count_for_testing(); + + av2_dm_rational_set_allocation_failure_after_for_testing(0); + EXPECT_FALSE(av2_dm_violation_copy(&destination, &source)); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&destination.observed, &original.observed, + &comparison)); + EXPECT_EQ(comparison, 0); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), allocations_before); + av2_dm_violation_destroy(&source); + av2_dm_violation_destroy(&destination); + av2_dm_violation_destroy(&original); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); } bool HasViolation(const ViolationCollector &collector, @@ -312,6 +435,77 @@ bool EqualRational(const Av2DmRational &left, const Av2DmRational &right) { return av2_dm_rational_compare(&left, &right, &comparison) && comparison == 0; } +void ExpectSameState(const Av2DmState &left, const Av2DmState &right) { + EXPECT_TRUE(EqualRational(left.time, right.time)); + EXPECT_EQ(left.last_dfg_valid, right.last_dfg_valid); + EXPECT_TRUE(EqualRational(left.first_bit_arrival, right.first_bit_arrival)); + EXPECT_TRUE(EqualRational(left.last_bit_arrival, right.last_bit_arrival)); + EXPECT_TRUE(EqualRational(left.scheduled_removal, right.scheduled_removal)); + EXPECT_TRUE(EqualRational(left.removal, right.removal)); + EXPECT_TRUE(EqualRational(left.time_to_decode, right.time_to_decode)); + EXPECT_TRUE(EqualRational(left.decode_completion, right.decode_completion)); + EXPECT_EQ(left.last_presentation_valid, right.last_presentation_valid); + EXPECT_TRUE(EqualRational(left.last_presentation, right.last_presentation)); + EXPECT_EQ(left.last_presentation_offset_valid, + right.last_presentation_offset_valid); + EXPECT_TRUE(EqualRational(left.last_presentation_offset, + right.last_presentation_offset)); + EXPECT_EQ(left.last_output_temporal_unit_valid, + right.last_output_temporal_unit_valid); + EXPECT_EQ(left.last_output_temporal_unit, right.last_output_temporal_unit); + EXPECT_EQ(left.last_temporal_unit_output_time_valid, + right.last_temporal_unit_output_time_valid); + EXPECT_TRUE(EqualRational(left.last_temporal_unit_output_time, + right.last_temporal_unit_output_time)); + EXPECT_EQ(left.last_temporal_unit_output_luma_samples, + right.last_temporal_unit_output_luma_samples); + EXPECT_EQ(left.last_temporal_unit_output_frames, + right.last_temporal_unit_output_frames); + EXPECT_EQ(left.initial_presentation_delay_known, + right.initial_presentation_delay_known); + EXPECT_TRUE(EqualRational(left.initial_presentation_delay, + right.initial_presentation_delay)); + EXPECT_EQ(left.current_buffer_index, right.current_buffer_index); + EXPECT_EQ(left.frame_number, right.frame_number); + EXPECT_EQ(left.dfg_number, right.dfg_number); + EXPECT_EQ(left.shown_frame_number, right.shown_frame_number); + EXPECT_EQ(left.buffer_pool.num_ref_frames, right.buffer_pool.num_ref_frames); + EXPECT_EQ(left.buffer_pool.pool_size, right.buffer_pool.pool_size); + EXPECT_EQ(left.buffer_pool.initialized, right.buffer_pool.initialized); + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + EXPECT_EQ(left.buffer_pool.vbi[i], right.buffer_pool.vbi[i]); + } + for (uint32_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + const Av2DmBuffer &left_buffer = left.buffer_pool.buffers[i]; + const Av2DmBuffer &right_buffer = right.buffer_pool.buffers[i]; + EXPECT_EQ(left_buffer.decoder_ref_count, right_buffer.decoder_ref_count); + EXPECT_EQ(left_buffer.player_ref_count, right_buffer.player_ref_count); + EXPECT_EQ(left_buffer.display_index, right_buffer.display_index); + EXPECT_EQ(left_buffer.presentation_time_valid, + right_buffer.presentation_time_valid); + EXPECT_TRUE(EqualRational(left_buffer.presentation_time, + right_buffer.presentation_time)); + EXPECT_EQ(left_buffer.generation_valid, right_buffer.generation_valid); + EXPECT_EQ(left_buffer.generation, right_buffer.generation); + EXPECT_EQ(left_buffer.decode_completion_time_valid, + right_buffer.decode_completion_time_valid); + EXPECT_TRUE(EqualRational(left_buffer.decode_completion_time, + right_buffer.decode_completion_time)); + EXPECT_EQ(left_buffer.decode_order, right_buffer.decode_order); + EXPECT_EQ(left_buffer.rap_epoch, right_buffer.rap_epoch); + EXPECT_EQ(left_buffer.random_access_point, + right_buffer.random_access_point); + EXPECT_EQ(left_buffer.coded_temporal_unit_index, + right_buffer.coded_temporal_unit_index); + EXPECT_EQ(left_buffer.coded_temporal_unit_valid, + right_buffer.coded_temporal_unit_valid); + EXPECT_EQ(left_buffer.equal_picture_interval, + right_buffer.equal_picture_interval); + EXPECT_EQ(left_buffer.ticks_per_picture, right_buffer.ticks_per_picture); + EXPECT_TRUE(EqualRational(left_buffer.disp_ct, right_buffer.disp_ct)); + } +} + void ExpectSameViolationMultiset(const ViolationCollector &online, const ViolationCollector &deferred) { ASSERT_EQ(online.violations.size(), deferred.violations.size()); @@ -358,6 +552,8 @@ Av2DmConfig MakeModelConfig(Av2DmMode mode) { config.num_ref_frames = 8; config.max_frame_width = 64; config.max_frame_height = 64; + config.chroma_format_idc = 1; + config.bit_depth = 8; config.explicit_num_ref_frames = false; config.timing_info_present = true; config.num_units_in_display_tick = 1; @@ -434,7 +630,7 @@ Av2DmOutputEvent Output(uint64_t index, uint64_t generation, int map_index) { void ExpectEqualRational(const Av2DmRational &actual, uint64_t numerator, uint64_t denominator) { - Av2DmRational expected; + Av2DmRational expected{}; ASSERT_TRUE(av2_dm_rational_make(numerator, denominator, &expected)); int comparison; ASSERT_TRUE(av2_dm_rational_compare(&actual, &expected, &comparison)); @@ -447,11 +643,11 @@ TEST(DecoderModelProcessTest, ResourceModeUsesDefaultInitialRemoval) { ASSERT_NE(model, nullptr); const Av2DmFrameEvent frame = MakeFrame(0, 10); av2_decoder_model_start_frame(model, &frame); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.scheduled_removal, 7, 9); ExpectEqualRational(state.time_to_decode, 64 * 64, 1000000); - Av2DmRational expected_completion; + Av2DmRational expected_completion{}; ASSERT_TRUE(av2_dm_rational_add(&state.scheduled_removal, &state.time_to_decode, &expected_completion)); int comparison; @@ -472,6 +668,44 @@ TEST(DecoderModelProcessTest, ResourceModeDoesNotRequireDecodingClock) { av2_decoder_model_destroy(model); } +TEST(DecoderModelProcessTest, + ExpiredPresentationBufferDoesNotMoveResourceTimeBackwards) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.ticks_per_picture = 1; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, 1); + Av2DmOutputEvent output = Output(2, 1, -1); + av2_decoder_model_output_frame(model, &output); + + Av2DmFrameEvent second = MakeFrame(3, 2); + av2_decoder_model_start_frame(model, &second); + av2_decoder_model_update_reference_buffers(model, &refresh); + output = Output(4, 2, -1); + av2_decoder_model_output_frame(model, &output); + av2_decoder_model_invalidate_reference_buffers(model, 0, false); + Av2DmState before{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); + + Av2DmFrameEvent third = MakeFrame(5, 3); + av2_decoder_model_start_frame(model, &third); + Av2DmState after{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &after)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&after.scheduled_removal, &before.time, + &comparison)); + EXPECT_EQ(comparison, 0); + ASSERT_TRUE(av2_dm_rational_compare(&after.time, &before.time, &comparison)); + EXPECT_GT(comparison, 0); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelProcessTest, ScheduleModeFallsBackToSequenceParameters) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); config.scope.whole_xlayer = false; @@ -481,7 +715,7 @@ TEST(DecoderModelProcessTest, ScheduleModeFallsBackToSequenceParameters) { ASSERT_NE(model, nullptr); const Av2DmFrameEvent frame = MakeFrame(0, 1); av2_decoder_model_start_frame(model, &frame); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.scheduled_removal, 1, 10); Av2DmResult result; @@ -500,7 +734,7 @@ TEST(DecoderModelProcessTest, ExplicitOperatingPointParametersTakePrecedence) { ASSERT_NE(model, nullptr); const Av2DmFrameEvent frame = MakeFrame(0, 1); av2_decoder_model_start_frame(model, &frame); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.scheduled_removal, 1, 5); av2_decoder_model_destroy(model); @@ -534,7 +768,7 @@ TEST(DecoderModelProcessTest, LowDelayDefersWithoutUnderflowViolation) { HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_UNDERFLOW), !low_delay); if (low_delay) { - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.removal, 1, 5); } @@ -550,7 +784,7 @@ TEST(DecoderModelProcessTest, OlkInvalidationMirrorsAllInvalidSlots) { av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh01 = Refresh(3, 3); av2_decoder_model_update_reference_buffers(model, &refresh01); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[1]); const int old_buffer = state.buffer_pool.vbi[0]; @@ -567,27 +801,27 @@ TEST(DecoderModelProcessTest, OlkInvalidationMirrorsAllInvalidSlots) { av2_decoder_model_destroy(model); } -TEST(DecoderModelProcessTest, ClkInvalidationClearsEveryPhysicalVbiSlot) { +TEST(DecoderModelProcessTest, ClkInvalidationClearsActiveVbiSlots) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); - config.num_ref_frames = 16; + config.num_ref_frames = 4; config.explicit_num_ref_frames = true; Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); ASSERT_NE(model, nullptr); Av2DmFrameEvent frame = MakeFrame(0, 1); av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent aliases = - Refresh((1u << 0) | (1u << 15), (1u << 0) | (1u << 15)); + Refresh((1u << 0) | (1u << 3), (1u << 0) | (1u << 3)); av2_decoder_model_update_reference_buffers(model, &aliases); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - ASSERT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[15]); + ASSERT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[3]); const int32_t old_buffer = state.buffer_pool.vbi[0]; ASSERT_GE(old_buffer, 0); ASSERT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 2u); av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + for (uint32_t i = 0; i < config.num_ref_frames; ++i) { EXPECT_EQ(state.buffer_pool.vbi[i], -1); } EXPECT_EQ(state.buffer_pool.buffers[old_buffer].decoder_ref_count, 0u); @@ -606,7 +840,7 @@ TEST(DecoderModelProcessTest, av2_decoder_model_start_frame(model, &first); const Av2DmReferenceUpdateEvent refresh7 = Refresh(1u << 7, 1u << 7); av2_decoder_model_update_reference_buffers(model, &refresh7); - Av2DmState before; + Av2DmState before{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); const int32_t invalidated_buffer = before.buffer_pool.vbi[7]; ASSERT_GE(invalidated_buffer, 0); @@ -615,7 +849,7 @@ TEST(DecoderModelProcessTest, second.ref_valid_mask = 0; av2_decoder_model_start_frame(model, &second); - Av2DmState after; + Av2DmState after{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &after)); EXPECT_EQ(after.buffer_pool.vbi[7], invalidated_buffer); EXPECT_EQ(after.buffer_pool.buffers[invalidated_buffer].decoder_ref_count, @@ -636,7 +870,7 @@ TEST(DecoderModelProcessTest, config.ras_seeds[1] = { 3, 77 }; Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); ASSERT_NE(model, nullptr); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.buffer_pool.vbi[0], state.buffer_pool.vbi[3]); EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 1u); @@ -654,6 +888,44 @@ TEST(DecoderModelProcessTest, av2_decoder_model_destroy(model); } +TEST(DecoderModelOwnershipTest, RasSeedInlineCopyDoesNotAllocate) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + int64_t allocations_before_seed = -1; + for (int64_t failure_index = 0; failure_index < 1024; ++failure_index) { + av2_dm_rational_set_allocation_failure_after_for_testing(failure_index); + Av2DecoderModel *model = + av2_decoder_model_create(&config, nullptr, nullptr); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + if (model != nullptr) { + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + if (!result.allocation_failed) allocations_before_seed = failure_index; + av2_decoder_model_destroy(model); + } + if (allocations_before_seed >= 0) break; + } + ASSERT_GE(allocations_before_seed, 0); + + config.ras_start = true; + config.ras_seed_complete = true; + config.ras_seed_count = 1; + config.ras_seeds[0] = { 0, 77 }; + av2_dm_rational_set_allocation_failure_after_for_testing( + allocations_before_seed); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + ASSERT_NE(model, nullptr); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.allocation_failed); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_FALSE(result.missing_required_input); + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 1u); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); ViolationCollector collector; @@ -667,7 +939,7 @@ TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { av2_decoder_model_set_initial_presentation_delay(model, false, 0); Av2DmOutputEvent first_output = Output(0, 10, -1); av2_decoder_model_output_frame(model, &first_output); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_FALSE(state.last_presentation_valid); @@ -690,7 +962,7 @@ TEST(DecoderModelProcessTest, InitialDelayRebasesHistoricalPresentation) { } TEST(DecoderModelProcessTest, - EndOfBitstreamDelayRebasesBuffersOutsideReducedActivePool) { + IncompatibleBoundaryResolvesDelayBeforeStartingNewPool) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); config.num_ref_frames = 16; config.explicit_num_ref_frames = true; @@ -710,7 +982,7 @@ TEST(DecoderModelProcessTest, av2_decoder_model_output_frame(model, &output); } - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_FALSE(state.initial_presentation_delay_known); ASSERT_NE(state.buffer_pool.buffers[10].player_ref_count, 0u); @@ -720,20 +992,21 @@ TEST(DecoderModelProcessTest, Av2DmConfig reduced = config; reduced.num_ref_frames = 8; ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 20, true)); - av2_decoder_model_set_initial_presentation_delay(model, true, 21); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_TRUE(state.initial_presentation_delay_known); EXPECT_EQ(state.buffer_pool.pool_size, 10u); - EXPECT_NE(state.buffer_pool.buffers[10].player_ref_count, 0u); - EXPECT_TRUE(state.buffer_pool.buffers[10].presentation_time_valid); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); + EXPECT_TRUE(state.last_presentation_valid); - const Av2DmRational delay = state.initial_presentation_delay; - av2_decoder_model_set_initial_presentation_delay(model, true, 22); + Av2DmRational delay{}; + ASSERT_TRUE(av2_dm_rational_copy(&delay, &state.initial_presentation_delay)); + av2_decoder_model_set_initial_presentation_delay(model, true, 21); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); int comparison = 1; ASSERT_TRUE(av2_dm_rational_compare(&state.initial_presentation_delay, &delay, &comparison)); EXPECT_EQ(comparison, 0); + av2_dm_rational_destroy(&delay); av2_decoder_model_destroy(model); } @@ -758,7 +1031,7 @@ TEST(DecoderModelProcessTest, const Av2DmReferenceUpdateEvent first_refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &first_refresh); av2_decoder_model_set_initial_presentation_delay(model, false, 2); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_FALSE(state.initial_presentation_delay_known); @@ -856,7 +1129,7 @@ TEST(DecoderModelProcessTest, PeriodicRebasePreservesExactTimeline) { const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); av2_decoder_model_set_initial_presentation_delay(model, false, 0); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.time, 0, 1); ExpectEqualRational(state.decode_completion, 0, 1); @@ -1212,7 +1485,7 @@ TEST(DecoderModelProcessTest, Av2DmOutputEvent output = Output(10 + i, generations[i], map_indices[i]); output.temporal_unit_index = owner_tus[i]; av2_decoder_model_output_frame(model, &output); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_TRUE(state.last_presentation_offset_valid); ExpectEqualRational(state.last_presentation_offset, i, 30); @@ -1253,7 +1526,7 @@ TEST(DecoderModelProcessTest, Av2DmOutputEvent output = Output(10 + i, i + 1, i); output.temporal_unit_index = owner_tus[i]; av2_decoder_model_output_frame(model, &output); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ExpectEqualRational(state.last_presentation_offset, i == 2 ? 1 : 0, i == 2 ? 30 : 1); @@ -1302,7 +1575,7 @@ TEST(DecoderModelProcessTest, VariableRatePresentationUsesRapBasesExactly) { output.presentation_time_present = true; output.presentation_time_ticks = presentation_ticks[i]; av2_decoder_model_output_frame(model, &output); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_TRUE(state.last_presentation_offset_valid); ExpectEqualRational(state.last_presentation_offset, expected_numerators[i], @@ -1359,7 +1632,7 @@ TEST(DecoderModelProcessTest, ExplicitTemporalUnitOutputTimeIsPreserved) { output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &output); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); ASSERT_TRUE(state.last_temporal_unit_output_time_valid); ExpectEqualRational(state.last_temporal_unit_output_time, 7, 3); @@ -1394,7 +1667,7 @@ TEST(DecoderModelProcessTest, LongStreamRebasingPreservesDecisions) { } TEST(DecoderModelConformanceTest, AnnexALevelFactorsAreExact) { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; ASSERT_TRUE(av2_dm_get_level_limits(4, 0, 3, &limits)); ExpectEqualRational(limits.bit_rate, 20004000, 1); EXPECT_EQ(limits.picture_size_profile_factor, 20u); @@ -1456,7 +1729,7 @@ TEST(DecoderModelConformanceTest, AnnexACompressedSizeObuMembershipIsExact) { } TEST(DecoderModelConformanceTest, AnnexAProfile5FactorsAreExact) { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; ASSERT_TRUE(av2_dm_get_level_limits(4, 0, 5, &limits)); ExpectEqualRational(limits.bit_rate, 36000000, 1); ExpectEqualRational(limits.buffer_size, 36000000, 1); @@ -1587,7 +1860,7 @@ TEST(DecoderModelConformanceTest, AnnexATablesMatchSpecification) { EXPECT_EQ(av2_tile_area_scaling_factor[tier][level], static_cast(tile_area_scale[tier][level])); for (uint32_t profile = 0; profile <= 5; ++profile) { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; if (kbps == 0 || cr == 0) { EXPECT_FALSE(av2_dm_get_level_limits(level, tier, profile, &limits)); continue; @@ -1620,7 +1893,7 @@ TEST(DecoderModelConformanceTest, AnnexATablesMatchSpecification) { } } - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; for (uint32_t level = 22; level <= 31; ++level) { EXPECT_FALSE(av2_dm_get_level_limits(level, 0, 0, &limits)); } @@ -1688,7 +1961,7 @@ TEST(DecoderModelConformanceTest, AnnexASubstreamTablesMatchSpecification) { static_cast(rows[group][scale].max_tile_columns)); EXPECT_EQ(spec.max_header_rate_x, 132); - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; ASSERT_TRUE(av2_dm_get_level_limits(level, 0, 0, &limits)); const bool integral_rates = (limits.max_display_rate * scale_denominator[scale]) % @@ -1719,7 +1992,7 @@ TEST(DecoderModelConformanceTest, AnnexASubstreamTablesMatchSpecification) { } TEST(DecoderModelConformanceTest, MultistreamFactorsAreAppliedExactly) { - Av2DmLevelLimits limits; + Av2DmLevelLimits limits{}; ASSERT_TRUE(av2_dm_get_level_limits(21, 0, 0, &limits)); EXPECT_FALSE(av2_dm_apply_multistream_limits(3, 0, 0, 3, 2, &limits)); ASSERT_TRUE(av2_dm_apply_multistream_limits(4, 0, 0, 3, 2, &limits)); @@ -2359,6 +2632,52 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, HeaderRateWindowIsCvsLocal) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_header_rate = 2; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 0; + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(0, 1, &first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + Av2DmFrameEvent first_extra_header = MakeFrame(1, 1); + first_extra_header.temporal_unit_index = first.temporal_unit_index; + first_extra_header.show_existing_frame = true; + first_extra_header.random_access_point = false; + first_extra_header.coded_as_closed_loop_key = false; + av2_decoder_model_start_frame(model, &first_extra_header); + + Av2DmConfig replacement = config; + replacement.tier = 1; + replacement.level_limits.max_header_rate = 1; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 2, true)); + Av2DmFrameEvent boundary = MakeFrame(3, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.frame_is_intra = true; + boundary.coded_bits = 0; + boundary.decoder_model_parameters_updated = true; + boundary.temporal_unit_output_time_present = true; + ASSERT_TRUE(av2_dm_rational_make(1, 2, &boundary.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &boundary); + Av2DmFrameEvent second_extra_header = MakeFrame(4, 2); + second_extra_header.temporal_unit_index = boundary.temporal_unit_index; + second_extra_header.show_existing_frame = true; + second_extra_header.random_access_point = false; + second_extra_header.coded_as_closed_loop_key = false; + av2_decoder_model_start_frame(model, &second_extra_header); + + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_HEADER_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE)); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, GlobalMaximumTileRetainsEachAffectedHeaderWindow) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); @@ -2545,7 +2864,7 @@ TEST(DecoderModelConformanceTest, } } -TEST(DecoderModelConformanceTest, TileHeaderRateUsesGlobalMaximumTile) { +TEST(DecoderModelConformanceTest, TileHeaderRateUsesCvsMaximumTile) { for (const uint32_t header_count : { 1u, 2u, 3u }) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); config.level_limits.max_header_rate = 100; @@ -2565,6 +2884,8 @@ TEST(DecoderModelConformanceTest, TileHeaderRateUsesGlobalMaximumTile) { for (uint32_t i = 0; i < header_count; ++i) { Av2DmFrameEvent frame = MakeFrame(i, i + 1); frame.show_existing_frame = true; + frame.random_access_point = false; + frame.coded_as_closed_loop_key = false; frame.max_tile_area = 1; frame.temporal_unit_output_time_present = true; ASSERT_TRUE(av2_dm_rational_make(i, header_count == 1 ? 1 : 2, @@ -2581,6 +2902,64 @@ TEST(DecoderModelConformanceTest, TileHeaderRateUsesGlobalMaximumTile) { } } +TEST(DecoderModelConformanceTest, TileHeaderRateMaximumIsCvsLocal) { + const auto run_transition = [](uint64_t old_tile_area, uint32_t old_headers, + uint64_t new_tile_area, uint32_t new_headers) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_header_rate = 100; + config.level_limits.max_tile_size_header_rate_product = 50; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + EXPECT_NE(model, nullptr); + if (model == nullptr) return SIZE_MAX; + + Av2DmFrameEvent old_tile = MakeFrame(10, 1); + old_tile.temporal_unit_index = 0; + old_tile.count_frame_header = false; + old_tile.max_tile_area = old_tile_area; + av2_decoder_model_start_frame(model, &old_tile); + for (uint32_t i = 0; i < old_headers; ++i) { + Av2DmFrameEvent header = MakeFrame(11 + i, 10 + i); + header.temporal_unit_index = 1 + i; + header.show_existing_frame = true; + header.random_access_point = false; + header.coded_as_closed_loop_key = false; + header.temporal_unit_output_time_present = true; + EXPECT_TRUE( + av2_dm_rational_make(i, 2, &header.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &header); + } + + Av2DmFrameEvent new_tile = MakeFrame(20, 20); + new_tile.temporal_unit_index = 10; + new_tile.random_access_point = true; + new_tile.coded_as_closed_loop_key = true; + new_tile.count_frame_header = false; + new_tile.max_tile_area = new_tile_area; + av2_decoder_model_start_frame(model, &new_tile); + for (uint32_t i = 0; i < new_headers; ++i) { + Av2DmFrameEvent header = MakeFrame(21 + i, 30 + i); + header.temporal_unit_index = 11 + i; + header.show_existing_frame = true; + header.random_access_point = false; + header.coded_as_closed_loop_key = false; + header.temporal_unit_output_time_present = true; + EXPECT_TRUE( + av2_dm_rational_make(4 + i, 2, &header.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &header); + } + av2_decoder_model_finish(model); + const size_t violations = + CountViolations(collector, AV2_DM_VIOLATION_TILE_SIZE_HEADER_RATE); + av2_decoder_model_destroy(model); + return violations; + }; + + EXPECT_EQ(run_transition(100, 0, 10, 2), 0u); + EXPECT_EQ(run_transition(10, 2, 100, 0), 0u); +} + TEST(DecoderModelConformanceTest, ReferenceFrameBoundaryIsExact) { for (const uint64_t max_picture_size : { UINT64_C(4095), UINT64_C(4096), UINT64_C(4097) }) { @@ -2633,6 +3012,114 @@ TEST(DecoderModelConformanceTest, DecodeCountCanReserveReferenceBuffer) { av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, DecodeCountReservationIsCvsLocal) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.num_ref_frames = 7; + config.max_mlayer_id = 1; + config.level_limits.max_picture_size = 4096; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.allow_global_intrabc = true; + first.inloop_filtering_enabled = true; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.num_ref_frames = 8; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(2, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.frame_is_intra = true; + boundary.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &boundary); + av2_decoder_model_finish(model); + + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 0u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, DeferredDecodeCountReservationIsCvsLocal) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.num_ref_frames = 7; + config.max_mlayer_id = 1; + config.level_limits.max_picture_size = 4096; + config.defer_nonterminal_checks_for_testing = true; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.allow_global_intrabc = true; + first.inloop_filtering_enabled = true; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.num_ref_frames = 8; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(2, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.frame_is_intra = true; + boundary.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &boundary); + av2_decoder_model_finish(model); + + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 0u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + DeferredReferenceLimitIsFinalizedAndRecheckedPerCvs) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.max_mlayer_id = 1; + config.level_limits.max_picture_size = 4096; + config.defer_nonterminal_checks_for_testing = true; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.allow_global_intrabc = true; + first.inloop_filtering_enabled = true; + av2_decoder_model_start_frame(model, &first); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 0u); + + Av2DmConfig replacement = config; + replacement.level_limits.max_picture_size = 4352; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 1u); + Av2DmFrameEvent boundary = MakeFrame(2, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.frame_is_intra = true; + boundary.frame_width = 16; + boundary.frame_height = 16; + boundary.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &boundary); + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 1u); + + Av2DmFrameEvent second = MakeFrame(3, 3); + second.allow_global_intrabc = true; + second.inloop_filtering_enabled = true; + av2_decoder_model_start_frame(model, &second); + av2_decoder_model_finish(model); + + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_REFERENCE_FRAMES), + 2u); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, ReferenceLimitIsNotRepeatedWhenDecodeCountReservesBuffer) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); @@ -2690,6 +3177,71 @@ TEST(DecoderModelConformanceTest, ScheduleDelayZeroAndTooLargeAreReported) { av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, + ZeroScheduleDelayAtNewCvsIsNotArithmeticFailure) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_decoder_buffer_delay = 0; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + const Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &config, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(2, 2, 1); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.frame_is_intra = true; + boundary.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &boundary); + + EXPECT_TRUE( + HasViolation(collector, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO)); + EXPECT_TRUE(HasViolation(collector, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT)); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.allocation_failed); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_EQ(result.decoded_frames, 2u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + SameTemporalUnitClkUsesOrdinaryArrivalWithoutNewCvsChecks) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_decoder_buffer_delay = 0; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 0; + av2_decoder_model_start_frame(model, &first); + + Av2DmFrameEvent second = MakeFrame(2, 2, 27000); + second.temporal_unit_index = first.temporal_unit_index; + second.coded_bits = 0; + second.random_access_point = true; + second.coded_as_closed_loop_key = true; + second.frame_is_intra = true; + av2_decoder_model_start_frame(model, &second); + + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.first_bit_arrival, 1, 5); + EXPECT_EQ( + CountViolations(collector, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_ZERO), + 1u); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_FALSE(result.allocation_failed); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_EQ(result.decoded_frames, 2u); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, DecoderBufferDelayMaximumIsExact) { for (const uint32_t decoder_delay : { 8999u, 9000u, 9001u }) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); @@ -2740,7 +3292,7 @@ TEST(DecoderModelConformanceTest, SmoothingOverflowIsCheckedExactly) { } TEST(DecoderModelConformanceTest, - SmoothingOverflowRetainsEachAffectedDfgAtProvingEvent) { + SmoothingOverflowUsesReachedBreakpointAtProvingEvent) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); ASSERT_TRUE(av2_dm_rational_make(1000000, 1, &config.level_limits.bit_rate)); ASSERT_TRUE(av2_dm_rational_make(1500, 1, &config.level_limits.buffer_size)); @@ -2760,21 +3312,22 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &second); ASSERT_EQ( CountViolations(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW), - 2u); + 1u); for (const Av2DmViolation &violation : collector.violations) { if (violation.code == AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW) { EXPECT_EQ(violation.event_index, 11u); + EXPECT_EQ(violation.affected_index, 11u); } } av2_decoder_model_finish(model); EXPECT_EQ( CountViolations(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW), - 2u); + 1u); av2_decoder_model_destroy(model); } TEST(DecoderModelConformanceTest, - ParameterUpdatePartitionsSmoothingButRetainsAdjacentDfgTiming) { + ParameterUpdateRetainsSmoothingAndAdjacentDfgTiming) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); config.sequence_low_delay_mode = true; ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); @@ -2794,7 +3347,7 @@ TEST(DecoderModelConformanceTest, ASSERT_TRUE( av2_dm_rational_make(2000, 1, &replacement.level_limits.bit_rate)); ASSERT_TRUE( - av2_decoder_model_update_parameters(model, &replacement, 1, false)); + av2_decoder_model_update_parameters(model, &replacement, 1, true)); Av2DmFrameEvent updated = MakeFrame(1, 2); updated.coded_bits = 100; updated.random_access_point = true; @@ -2806,45 +3359,421 @@ TEST(DecoderModelConformanceTest, EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT), 1u); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - ExpectEqualRational(state.first_bit_arrival, 0, 1); - ExpectEqualRational(state.last_bit_arrival, 1, 20); + ExpectEqualRational(state.first_bit_arrival, 1, 10); + ExpectEqualRational(state.last_bit_arrival, 3, 20); ASSERT_NE(state.buffer_pool.vbi[0], -1); EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); av2_decoder_model_destroy(model); } TEST(DecoderModelConformanceTest, - ParameterUpdateToResourceModeUsesContinuousResourceLane) { + NewCvsUsesDecoderDelayThenBothDelaysForArrival) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); - config.scope.whole_xlayer = false; - config.sequence_parameters_present = false; - config.operating_point_parameters_present = true; - config.operating_point_decoder_buffer_delay = 9000; - config.operating_point_encoder_buffer_delay = 9000; - config.sequence_low_delay_mode = true; + config.time_scale = 10; + config.num_units_in_decoding_tick = 1; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); ASSERT_NE(model, nullptr); - Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 100; av2_decoder_model_start_frame(model, &first); - const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); - av2_decoder_model_update_reference_buffers(model, &refresh); Av2DmConfig replacement = config; - replacement.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; - replacement.operating_point_parameters_present = false; + replacement.sequence_decoder_buffer_delay = 18000; + replacement.sequence_encoder_buffer_delay = 27000; ASSERT_TRUE( - av2_decoder_model_update_parameters(model, &replacement, 1, false)); - Av2DmFrameEvent updated = MakeFrame(1, 2); - updated.random_access_point = true; + av2_dm_rational_make(2000, 1, &replacement.level_limits.bit_rate)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2, 5); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.coded_bits = 100; + av2_decoder_model_start_frame(model, &boundary); + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 3, 5); + ExpectEqualRational(state.first_bit_arrival, 2, 5); + ExpectEqualRational(state.last_bit_arrival, 9, 20); + + Av2DmFrameEvent next = MakeFrame(2, 3, 8); + next.coded_bits = 100; + av2_decoder_model_start_frame(model, &next); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 7, 5); + ExpectEqualRational(state.first_bit_arrival, 9, 10); + ExpectEqualRational(state.last_bit_arrival, 19, 20); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + LowDelayClockChangeRoundsFromScheduledRemovalAnchor) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_decoder_buffer_delay = 4500; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 0; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.time_scale = 10; + replacement.num_units_in_decoding_tick = 1; + replacement.sequence_decoder_buffer_delay = 9000; + replacement.sequence_low_delay_mode = true; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2, 1); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.coded_bits = 210; + av2_decoder_model_start_frame(model, &boundary); + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.scheduled_removal, 3, 20); + ExpectEqualRational(state.last_bit_arrival, 13, 50); + ExpectEqualRational(state.removal, 7, 20); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + BufferSizeIncreaseAppliesBeforeNewCvsBitsArrive) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.time_scale = 10; + config.num_units_in_decoding_tick = 1; + config.sequence_low_delay_mode = true; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(100, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 100; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + ASSERT_TRUE( + av2_dm_rational_make(150, 1, &replacement.level_limits.buffer_size)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.coded_bits = 150; + av2_decoder_model_start_frame(model, &boundary); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + BufferSizeDecreaseAppliesImmediatelyAfterBoundaryRemoval) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.time_scale = 20; + config.num_units_in_decoding_tick = 1; + ASSERT_TRUE(av2_dm_rational_make(2000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(150, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 0; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + ASSERT_TRUE( + av2_dm_rational_make(100, 1, &replacement.level_limits.buffer_size)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2, 6); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.coded_bits = 0; + av2_decoder_model_start_frame(model, &boundary); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + + Av2DmFrameEvent next = MakeFrame(2, 3, 1); + next.coded_bits = 120; + av2_decoder_model_start_frame(model, &next); + av2_decoder_model_finish(model); + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->affected_index, boundary.event_index); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + LaterBufferSizeIncreaseSupersedesPendingDecrease) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(150, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 0; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig decreased = config; + ASSERT_TRUE(av2_dm_rational_make(50, 1, &decreased.level_limits.buffer_size)); + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &decreased, 1, true)); + Av2DmFrameEvent decrease_boundary = MakeFrame(1, 2, 100); + decrease_boundary.random_access_point = true; + decrease_boundary.coded_as_closed_loop_key = true; + decrease_boundary.coded_bits = 0; + av2_decoder_model_start_frame(model, &decrease_boundary); + Av2DmState decrease_state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &decrease_state)); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + + Av2DmConfig increased = decreased; + ASSERT_TRUE( + av2_dm_rational_make(200, 1, &increased.level_limits.buffer_size)); + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &increased, 2, true)); + Av2DmFrameEvent increase_boundary = MakeFrame(2, 3, 100); + increase_boundary.random_access_point = true; + increase_boundary.coded_as_closed_loop_key = true; + increase_boundary.coded_bits = 80; + av2_decoder_model_start_frame(model, &increase_boundary); + Av2DmState increase_state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &increase_state)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&increase_state.first_bit_arrival, + &decrease_state.removal, &comparison)); + EXPECT_LT(comparison, 0); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + + Av2DmFrameEvent next = MakeFrame(3, 4, 200); + next.coded_bits = 150; + av2_decoder_model_start_frame(model, &next); + EXPECT_FALSE( + HasViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + BufferSizeIncreaseAtNextArrivalDoesNotChangePriorLastArrival) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + config.defer_nonterminal_checks_for_testing = true; + ASSERT_TRUE(av2_dm_rational_make(1000, 1, &config.level_limits.bit_rate)); + ASSERT_TRUE(av2_dm_rational_make(100, 1, &config.level_limits.buffer_size)); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 150; + av2_decoder_model_start_frame(model, &first); + Av2DmState first_state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &first_state)); + + Av2DmConfig replacement = config; + ASSERT_TRUE( + av2_dm_rational_make(200, 1, &replacement.level_limits.buffer_size)); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2, 100); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + boundary.coded_bits = 0; + av2_decoder_model_start_frame(model, &boundary); + Av2DmState boundary_state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &boundary_state)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&first_state.last_bit_arrival, + &boundary_state.first_bit_arrival, + &comparison)); + EXPECT_EQ(comparison, 0); + av2_decoder_model_finish(model); + + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_SMOOTHING_BUFFER_OVERFLOW); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->affected_index, first.event_index); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + FirstResourceDfgOfNewCvsChecksDecoderDelayConsistency) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.coded_bits = 100000; + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.time_scale += 1; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &boundary); + EXPECT_TRUE(HasViolation(collector, + AV2_DM_VIOLATION_DECODER_BUFFER_DELAY_INCONSISTENT)); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + MinimumDecodeUsesNextModeAndPreviousDfgLimits) { + for (const uint32_t new_time_scale : { 1000000u, 100000u }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.level_limits.max_decode_rate = 1000000000; + config.level_limits.max_header_rate = 1000000000; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.mode = AV2_DM_DECODING_SCHEDULE_MODE; + replacement.time_scale = new_time_scale; + replacement.num_units_in_decoding_tick = 1; + replacement.level_limits.max_header_rate = 1; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent boundary = MakeFrame(1, 2, 1); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &boundary); + EXPECT_EQ(HasViolation(collector, AV2_DM_VIOLATION_MINIMUM_DECODE_TIME), + new_time_scale == 1000000u); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, + OldOutputUsesOldDisplayClockAfterCompatibleBoundary) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.time_scale = 100; + config.num_units_in_display_tick = 1; + config.ticks_per_picture = 10; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh0 = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh0); + av2_decoder_model_set_initial_presentation_delay(model, false, 1); + Av2DmOutputEvent output = Output(2, 1, -1); + av2_decoder_model_output_frame(model, &output); + + Av2DmFrameEvent old_tail = MakeFrame(3, 2); + av2_decoder_model_start_frame(model, &old_tail); + const Av2DmReferenceUpdateEvent refresh1 = Refresh(2, 3); + av2_decoder_model_update_reference_buffers(model, &refresh1); + Av2DmConfig replacement = config; + replacement.time_scale = 10; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 4, true)); + output = Output(5, 2, 1); + av2_decoder_model_output_frame(model, &output); + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.last_presentation_offset, 1, 10); + + Av2DmFrameEvent boundary = MakeFrame(6, 3); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &boundary); + output = Output(7, 3, -1); + av2_decoder_model_output_frame(model, &output); + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + ExpectEqualRational(state.last_presentation_offset, 11, 10); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + CrossBoundaryPresentationIntervalUsesEarlierTuLimits) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.time_scale = 1000; + config.num_units_in_display_tick = 1; + config.ticks_per_picture = 75; + config.level_limits.max_display_rate = 40960; + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, 1); + Av2DmOutputEvent output = Output(2, 1, -1); + av2_decoder_model_output_frame(model, &output); + + Av2DmConfig replacement = config; + replacement.level_limits.max_display_rate = 81920; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 3, true)); + Av2DmFrameEvent boundary = MakeFrame(4, 2); + boundary.random_access_point = true; + boundary.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &boundary); + output = Output(5, 2, -1); + av2_decoder_model_output_frame(model, &output); + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_MINIMUM_PRESENTATION_INTERVAL); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->affected_index, 2u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + ParameterUpdateToResourceModeUsesContinuousResourceLane) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.scope.whole_xlayer = false; + config.sequence_parameters_present = false; + config.operating_point_parameters_present = true; + config.operating_point_decoder_buffer_delay = 9000; + config.operating_point_encoder_buffer_delay = 9000; + config.sequence_low_delay_mode = true; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + Av2DmState before{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); + + Av2DmConfig replacement = config; + replacement.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + replacement.operating_point_parameters_present = false; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent updated = MakeFrame(1, 2); + updated.random_access_point = true; updated.decoder_model_parameters_updated = true; av2_decoder_model_start_frame(model, &updated); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.frame_number, 2u); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&state.scheduled_removal, &before.time, + &comparison)); + EXPECT_EQ(comparison, 0); ASSERT_NE(state.buffer_pool.vbi[0], -1); EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); Av2DmResult result; @@ -2854,6 +3783,39 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, + ParameterUpdateToResourceModeWithNewDpbUsesPrimaryTime) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.sequence_low_delay_mode = true; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + Av2DmState before{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); + + Av2DmConfig replacement = config; + replacement.mode = AV2_DM_RESOURCE_AVAILABILITY_MODE; + replacement.num_ref_frames = 16; + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent updated = MakeFrame(1, 2); + updated.random_access_point = true; + updated.decoder_model_parameters_updated = true; + av2_decoder_model_start_frame(model, &updated); + + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&state.scheduled_removal, &before.time, + &comparison)); + EXPECT_EQ(comparison, 0); + EXPECT_EQ(state.buffer_pool.num_ref_frames, 16u); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, ParameterUpdateKeepsAffectedDfgLimitsWithPreviousFrame) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); @@ -2868,7 +3830,7 @@ TEST(DecoderModelConformanceTest, Av2DmConfig replacement = config; replacement.level_limits.max_decode_rate = 100000; ASSERT_TRUE( - av2_decoder_model_update_parameters(model, &replacement, 1, false)); + av2_decoder_model_update_parameters(model, &replacement, 1, true)); Av2DmFrameEvent updated = MakeFrame(1, 2, 900); updated.random_access_point = true; @@ -2896,15 +3858,16 @@ TEST(DecoderModelConformanceTest, Av2DmConfig replacement = config; replacement.mode = AV2_DM_DECODING_SCHEDULE_MODE; ASSERT_TRUE( - av2_decoder_model_update_parameters(model, &replacement, 1, false)); + av2_decoder_model_update_parameters(model, &replacement, 1, true)); Av2DmFrameEvent updated = MakeFrame(1, 2, 9000); updated.random_access_point = true; updated.decoder_model_parameters_updated = true; av2_decoder_model_start_frame(model, &updated); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.frame_number, 2u); + ExpectEqualRational(state.scheduled_removal, 79, 90); ASSERT_NE(state.buffer_pool.vbi[0], -1); EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); Av2DmResult result; @@ -2914,7 +3877,23 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_destroy(model); } -TEST(DecoderModelConformanceTest, ParameterUpdateRejectsImmutableClockChange) { +TEST(DecoderModelConformanceTest, OperatingModeChangeRequiresClkBoundary) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DmConfig replacement = config; + replacement.mode = AV2_DM_DECODING_SCHEDULE_MODE; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + + EXPECT_EQ( + av2_decoder_model_classify_parameter_update(model, &replacement, false), + AV2_DM_PARAMETER_UPDATE_INCOMPATIBLE_CONFIGURATION); + EXPECT_EQ( + av2_decoder_model_classify_parameter_update(model, &replacement, true), + AV2_DM_PARAMETER_UPDATE_ALLOWED); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, ParameterUpdateAppliesNewClockProspectively) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); ASSERT_NE(model, nullptr); @@ -2923,17 +3902,218 @@ TEST(DecoderModelConformanceTest, ParameterUpdateRejectsImmutableClockChange) { Av2DmConfig replacement = config; replacement.time_scale += 1; - EXPECT_FALSE( - av2_decoder_model_update_parameters(model, &replacement, 1, false)); + EXPECT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmFrameEvent updated = MakeFrame(1, 2); + updated.random_access_point = true; + updated.coded_as_closed_loop_key = true; + av2_decoder_model_start_frame(model, &updated); Av2DmResult result; ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); - EXPECT_EQ(result.status, AV2_DM_RESULT_INDETERMINATE); - EXPECT_TRUE(result.missing_required_input); + EXPECT_NE(result.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_FALSE(result.missing_required_input); av2_decoder_model_destroy(model); } -TEST(DecoderModelConformanceTest, - NumRefFramesUpdateRequiresCompletedClkInvalidation) { +TEST(DecoderModelOwnershipTest, + DynamicStateSnapshotSurvivesModelAndFailedCopyIsAtomic) { + static const uint32_t time_scales[] = { + UINT32_C(4294967291), UINT32_C(4294967279), UINT32_C(4294967231), + UINT32_C(4294967197), UINT32_C(4294967189), UINT32_C(4294967161), + UINT32_C(4294967143), UINT32_C(4294967111), UINT32_C(4294967087), + UINT32_C(4294967029), + }; + ASSERT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.num_units_in_display_tick = 1; + config.ticks_per_picture = 1; + config.time_scale = time_scales[0]; + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + for (uint32_t i = 0; i < sizeof(time_scales) / sizeof(time_scales[0]); ++i) { + if (i != 0) { + Av2DmConfig replacement = config; + replacement.time_scale = time_scales[i]; + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &replacement, + 2 * i, true)); + config.time_scale = time_scales[i]; + } + Av2DmFrameEvent frame = MakeFrame(2 * i, i + 1); + frame.random_access_point = true; + frame.coded_as_closed_loop_key = true; + frame.decoder_model_parameters_updated = i != 0; + av2_decoder_model_start_frame(model, &frame); + if (i == 0) { + av2_decoder_model_set_initial_presentation_delay(model, false, 0); + } + Av2DmOutputEvent output = Output(2 * i + 1, i + 1, -1); + output.temporal_unit_index = i; + av2_decoder_model_output_frame(model, &output); + } + + Av2DmResult result_before; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result_before)); + Av2DmState destination{}; + destination.frame_number = 123; + ASSERT_TRUE(av2_dm_rational_make(7, 11, &destination.time)); + av2_dm_rational_set_allocation_failure_after_for_testing(0); + EXPECT_FALSE(av2_decoder_model_get_state(model, &destination)); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + EXPECT_EQ(destination.frame_number, 123u); + int comparison; + Av2DmRational sentinel{}; + ASSERT_TRUE(av2_dm_rational_make(7, 11, &sentinel)); + ASSERT_TRUE( + av2_dm_rational_compare(&destination.time, &sentinel, &comparison)); + EXPECT_EQ(comparison, 0); + Av2DmResult result_after; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result_after)); + EXPECT_EQ(result_after.arithmetic_failed, result_before.arithmetic_failed); + EXPECT_EQ(result_after.violations, result_before.violations); + + Av2DmState snapshot{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &snapshot)); + ASSERT_TRUE(snapshot.last_presentation_offset_valid); + ASSERT_NE(snapshot.last_presentation_offset.dynamic_limbs, nullptr); + Av2DmRational expected{}; + ASSERT_TRUE( + av2_dm_rational_copy(&expected, &snapshot.last_presentation_offset)); + av2_decoder_model_destroy(model); + ASSERT_TRUE(av2_dm_rational_compare(&snapshot.last_presentation_offset, + &expected, &comparison)); + EXPECT_EQ(comparison, 0); + av2_dm_rational_destroy(&sentinel); + av2_dm_rational_destroy(&expected); + av2_dm_state_destroy(&destination); + av2_dm_state_destroy(&snapshot); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); +} + +TEST(DecoderModelOwnershipTest, + AllocationFailureStopsModelWithoutInventingViolation) { + ASSERT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + ViolationCollector collector; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(MakeDynamicRational(&first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + av2_dm_rational_destroy(&first.temporal_unit_output_time); + Av2DmFrameEvent frame = MakeFrame(1, 2); + frame.temporal_unit_index = 0; + frame.frame_width = config.level_limits.max_horizontal_size + 1; + av2_dm_rational_set_allocation_failure_after_for_testing(0); + av2_decoder_model_start_frame(model, &frame); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_TRUE(result.allocation_failed); + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_EQ(result.violations, 0u); + EXPECT_TRUE(collector.violations.empty()); + av2_decoder_model_destroy(model); + EXPECT_EQ(av2_dm_rational_allocation_count_for_testing(), 0u); +} + +TEST(DecoderModelOwnershipTest, + RationalAllocationFailureRollsBackWholeEventAndDeferredReport) { + bool saw_failure = false; + bool reached_success = false; + for (int64_t failure_index = 0; failure_index < 2750; ++failure_index) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + uint64_t callback_count = 0; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CountViolation, &callback_count); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + first.temporal_unit_output_time_present = true; + ASSERT_TRUE(MakeDynamicRational(&first.temporal_unit_output_time)); + av2_decoder_model_start_frame(model, &first); + av2_dm_rational_destroy(&first.temporal_unit_output_time); + Av2DmState before{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); + Av2DmResult before_result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &before_result)); + + Av2DmFrameEvent violating = MakeFrame(1, 2); + violating.temporal_unit_index = 0; + violating.frame_width = config.level_limits.max_horizontal_size + 1; + av2_dm_rational_set_allocation_failure_after_for_testing(failure_index); + av2_decoder_model_start_frame(model, &violating); + av2_dm_rational_set_allocation_failure_after_for_testing(-1); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + if (result.allocation_failed) { + saw_failure = true; + Av2DmState after{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &after)); + ExpectSameState(before, after); + EXPECT_EQ(result.violations, before_result.violations); + EXPECT_EQ(callback_count, 0u); + } else { + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_EQ(result.violations, before_result.violations + 1); + EXPECT_EQ(callback_count, 1u); + reached_success = true; + av2_decoder_model_destroy(model); + break; + } + av2_decoder_model_destroy(model); + } + EXPECT_TRUE(saw_failure); + EXPECT_TRUE(reached_success); +} + +TEST(DecoderModelOwnershipTest, + InternalAllocationFailureRollsBackWholeEventAndDeferredReport) { + bool saw_failure = false; + bool reached_success = false; + for (int64_t failure_index = 0; failure_index < 7; ++failure_index) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + uint64_t callback_count = 0; + Av2DecoderModel *model = + av2_decoder_model_create(&config, CountViolation, &callback_count); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + Av2DmState before{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &before)); + Av2DmResult before_result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &before_result)); + + Av2DmFrameEvent violating = MakeFrame(1, 2); + violating.frame_width = config.level_limits.max_horizontal_size + 1; + av2_dm_set_internal_allocation_failure_after_for_testing(failure_index); + av2_decoder_model_start_frame(model, &violating); + av2_dm_set_internal_allocation_failure_after_for_testing(-1); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + if (result.allocation_failed) { + saw_failure = true; + Av2DmState after{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &after)); + ExpectSameState(before, after); + EXPECT_EQ(result.violations, before_result.violations); + EXPECT_EQ(callback_count, 0u); + } else { + EXPECT_FALSE(result.arithmetic_failed); + EXPECT_EQ(result.violations, before_result.violations + 1); + EXPECT_EQ(callback_count, 1u); + reached_success = true; + av2_decoder_model_destroy(model); + break; + } + av2_decoder_model_destroy(model); + } + EXPECT_TRUE(saw_failure); + EXPECT_TRUE(reached_success); +} + +TEST(DecoderModelConformanceTest, NumRefFramesUpdateStartsNewActivePoolAtClk) { const Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); Av2DmConfig replacement = config; replacement.num_ref_frames = 16; @@ -2942,7 +4122,7 @@ TEST(DecoderModelConformanceTest, ASSERT_NE(model, nullptr); EXPECT_FALSE( av2_decoder_model_update_parameters(model, &replacement, 1, false)); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.buffer_pool.num_ref_frames, 8u); EXPECT_EQ(state.buffer_pool.pool_size, 10u); @@ -2954,102 +4134,98 @@ TEST(DecoderModelConformanceTest, av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); - EXPECT_FALSE( + EXPECT_TRUE( av2_decoder_model_update_parameters(model, &replacement, 1, true)); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - EXPECT_EQ(state.buffer_pool.num_ref_frames, 8u); - EXPECT_EQ(state.buffer_pool.pool_size, 10u); - EXPECT_NE(state.buffer_pool.vbi[0], -1); + EXPECT_EQ(state.buffer_pool.num_ref_frames, 16u); + EXPECT_EQ(state.buffer_pool.pool_size, 18u); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); + for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { + EXPECT_EQ(state.buffer_pool.vbi[i], -1); + } av2_decoder_model_destroy(model); +} - model = av2_decoder_model_create(&config, nullptr, nullptr); +TEST(DecoderModelConformanceTest, + EachDpbConfigurationChangeStartsNewActivePool) { + const Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + std::vector replacements(5, config); + replacements[0].num_ref_frames = 9; + replacements[1].max_frame_width += 1; + replacements[2].max_frame_height += 1; + replacements[3].chroma_format_idc += 1; + replacements[4].bit_depth += 2; + + for (const Av2DmConfig &replacement : replacements) { + Av2DecoderModel *model = + av2_decoder_model_create(&config, nullptr, nullptr); + ASSERT_NE(model, nullptr); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + ASSERT_TRUE( + av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmState state{}; + ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); + EXPECT_EQ(state.buffer_pool.num_ref_frames, replacement.num_ref_frames); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); + for (uint32_t i = 0; i < state.buffer_pool.num_ref_frames; ++i) { + EXPECT_EQ(state.buffer_pool.vbi[i], -1); + } + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, + CompatibleConfigurationChangeRetainsActivePool) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); ASSERT_NE(model, nullptr); - av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + + Av2DmConfig replacement = config; + replacement.time_scale += 1; ASSERT_TRUE( av2_decoder_model_update_parameters(model, &replacement, 1, true)); + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - EXPECT_EQ(state.buffer_pool.num_ref_frames, 16u); - EXPECT_EQ(state.buffer_pool.pool_size, 18u); - for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { - EXPECT_EQ(state.buffer_pool.vbi[i], -1); - } + ASSERT_NE(state.buffer_pool.vbi[0], -1); + EXPECT_EQ(state.buffer_pool.buffers[state.buffer_pool.vbi[0]].generation, 1u); av2_decoder_model_destroy(model); } TEST(DecoderModelConformanceTest, - FixedBufferPoolSurvivesSixteenEightSixteenTransition) { + NumRefFramesDecreaseAndIncreaseDoNotResurrectOldState) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); config.num_ref_frames = 16; config.explicit_num_ref_frames = true; - config.initial_display_delay = AV2_DM_MAX_BUFFER_POOL_SIZE; Av2DecoderModel *model = av2_decoder_model_create(&config, nullptr, nullptr); ASSERT_NE(model, nullptr); - for (uint64_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { - Av2DmFrameEvent frame = MakeFrame(i, i + 1); - av2_decoder_model_start_frame(model, &frame); - Av2DmOutputEvent output = Output(100 + i, i + 1, -1); - output.presentation_uses_current_frame = true; - output.presentation_random_access_point = i == 0; - av2_decoder_model_output_frame(model, &output); - } - Av2DmState state; + Av2DmFrameEvent frame = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1u << 15, 1u << 15); + av2_decoder_model_update_reference_buffers(model, &refresh); + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - ASSERT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), - static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); - ASSERT_EQ(state.buffer_pool.buffers[17].player_ref_count, 1u); - ASSERT_FALSE(state.buffer_pool.buffers[17].presentation_time_valid); - const Av2DmRational high_presentation_offset = - state.buffer_pool.buffers[17].presentation_time; + ASSERT_NE(state.buffer_pool.vbi[15], -1); - av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); Av2DmConfig reduced = config; reduced.num_ref_frames = 8; - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 200, true)); - av2_decoder_model_set_initial_presentation_delay(model, false, 201); + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 1, true)); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.buffer_pool.pool_size, 10u); - EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), - static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); - EXPECT_TRUE(state.initial_presentation_delay_known); - EXPECT_EQ(state.buffer_pool.buffers[17].player_ref_count, 1u); - EXPECT_TRUE(state.buffer_pool.buffers[17].presentation_time_valid); - Av2DmRational expected_high_presentation; - ASSERT_TRUE(av2_dm_rational_add(&high_presentation_offset, - &state.initial_presentation_delay, - &expected_high_presentation)); - int high_presentation_comparison; - ASSERT_TRUE(av2_dm_rational_compare( - &state.buffer_pool.buffers[17].presentation_time, - &expected_high_presentation, &high_presentation_comparison)); - EXPECT_EQ(high_presentation_comparison, 0); - Av2DmStorageStats storage; - ASSERT_TRUE(av2_decoder_model_get_storage_stats(model, &storage)); - EXPECT_EQ(storage.active_generations, - static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); - EXPECT_GE(storage.active_tus, - static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); - - for (uint64_t i = 0; i < 160; ++i) { - Av2DmFrameEvent frame = - MakeFrame(AV2_DM_MAX_BUFFER_POOL_SIZE + i, 1000 + i); - frame.random_access_point = i == 0; - frame.coded_as_closed_loop_key = i == 0; - frame.decoder_model_parameters_updated = i == 0; - av2_decoder_model_start_frame(model, &frame); - ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - ASSERT_GE(state.current_buffer_index, 0); - EXPECT_LT(state.current_buffer_index, 10); - } - ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - EXPECT_EQ(state.buffer_pool.buffers[17].player_ref_count, 0u); - EXPECT_FALSE(state.buffer_pool.buffers[17].generation_valid); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); - av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &config, 400, true)); + ASSERT_TRUE(av2_decoder_model_update_parameters(model, &config, 2, true)); ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); EXPECT_EQ(state.buffer_pool.num_ref_frames, 16u); EXPECT_EQ(state.buffer_pool.pool_size, 18u); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); for (uint32_t i = 0; i < AV2_DM_MAX_REF_FRAMES; ++i) { EXPECT_EQ(state.buffer_pool.vbi[i], -1); } @@ -3058,15 +4234,15 @@ TEST(DecoderModelConformanceTest, TEST(DecoderModelConformanceTest, ReducedPoolDiagnosticsUseOnlyTheActiveRange) { Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); - config.num_ref_frames = 16; + config.num_ref_frames = 8; config.explicit_num_ref_frames = true; - config.initial_display_delay = AV2_DM_MAX_BUFFER_POOL_SIZE; + config.initial_display_delay = 10; ViolationCollector collector; Av2DecoderModel *model = av2_decoder_model_create(&config, CollectViolation, &collector); ASSERT_NE(model, nullptr); - for (uint64_t i = 0; i < AV2_DM_MAX_BUFFER_POOL_SIZE; ++i) { + for (uint64_t i = 0; i < 10; ++i) { Av2DmFrameEvent frame = MakeFrame(i, i + 1, (uint32_t)(i * 9000)); av2_decoder_model_start_frame(model, &frame); Av2DmOutputEvent output = Output(100 + i, i + 1, -1); @@ -3075,15 +4251,8 @@ TEST(DecoderModelConformanceTest, ReducedPoolDiagnosticsUseOnlyTheActiveRange) { av2_decoder_model_output_frame(model, &output); } av2_decoder_model_set_initial_presentation_delay(model, false, 200); - av2_decoder_model_invalidate_reference_buffers(model, UINT32_MAX, true); - Av2DmConfig reduced = config; - reduced.num_ref_frames = 8; - ASSERT_TRUE(av2_decoder_model_update_parameters(model, &reduced, 201, true)); Av2DmFrameEvent blocked = MakeFrame(202, 1000, 0); - blocked.random_access_point = true; - blocked.coded_as_closed_loop_key = true; - blocked.decoder_model_parameters_updated = true; av2_decoder_model_start_frame(model, &blocked); const Av2DmViolation *const violation = FindViolation( collector, AV2_DM_VIOLATION_DECODE_FRAME_BUFFER_UNAVAILABLE); @@ -3092,10 +4261,9 @@ TEST(DecoderModelConformanceTest, ReducedPoolDiagnosticsUseOnlyTheActiveRange) { EXPECT_EQ(violation->detail.value.buffer_pool.pool_size, 10u); EXPECT_EQ(violation->detail.value.buffer_pool.frames_in_use, 10u); EXPECT_EQ(violation->detail.value.buffer_pool.free_buffers, 0u); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(model, &state)); - EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), - static_cast(AV2_DM_MAX_BUFFER_POOL_SIZE)); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 10u); av2_decoder_model_destroy(model); } diff --git a/test/encoder_decoder_model_test.cc b/test/encoder_decoder_model_test.cc index da8291ffe1..7af93e10e1 100644 --- a/test/encoder_decoder_model_test.cc +++ b/test/encoder_decoder_model_test.cc @@ -28,9 +28,12 @@ extern "C" { #include "av2/encoder/encoder.h" } #include "third_party/googletest/src/googletest/include/gtest/gtest.h" +#include "test/decoder_model_lifecycle.h" namespace { +using Av2DmState = libavm_test::ScopedDmState; + constexpr int kNumRefs = 4; constexpr uint64_t kOutputSamples = 64 * 64; @@ -602,12 +605,36 @@ TEST(EncoderDecoderModelSection7OracleTest, ExpectOracleMatchesEncoder(oracle, encoder); } +struct OwnedViolation : Av2DmViolation { + OwnedViolation() { av2_dm_violation_init(this); } + explicit OwnedViolation(const Av2DmViolation *source) : OwnedViolation() { + EXPECT_TRUE(av2_dm_violation_copy(this, source)); + } + OwnedViolation(const OwnedViolation &other) : OwnedViolation(&other) {} + OwnedViolation &operator=(const OwnedViolation &other) { + EXPECT_TRUE(av2_dm_violation_copy(this, &other)); + return *this; + } + OwnedViolation(OwnedViolation &&other) noexcept + : Av2DmViolation(static_cast(other)) { + av2_dm_violation_init(&other); + } + OwnedViolation &operator=(OwnedViolation &&other) noexcept { + av2_dm_violation_destroy(this); + static_cast(*this) = + static_cast(other); + av2_dm_violation_init(&other); + return *this; + } + ~OwnedViolation() { av2_dm_violation_destroy(this); } +}; + struct ViolationCollector { - std::vector violations; + std::vector violations; }; void CollectViolation(void *opaque, const Av2DmViolation *violation) { - static_cast(opaque)->violations.push_back(*violation); + static_cast(opaque)->violations.emplace_back(violation); } Av2DmConfig MakeCommonModelConfig() { @@ -730,7 +757,7 @@ TEST(EncoderDecoderModelDifferentialTest, for (uint64_t index = 0; index < 3; ++index) { Av2DmFrameEvent frame = MakeCommonFrame(index, valid_mask); av2_decoder_model_start_frame(common, &frame); - Av2DmState state; + Av2DmState state{}; ASSERT_TRUE(av2_decoder_model_get_state(common, &state)); ASSERT_GE(state.current_buffer_index, 0); encoder.CaptureDecodedGeneration( @@ -742,7 +769,7 @@ TEST(EncoderDecoderModelDifferentialTest, av2_decoder_model_set_initial_presentation_delay(common, false, 10 + index); } - Av2DmState common_state; + Av2DmState common_state{}; ASSERT_TRUE(av2_decoder_model_get_state(common, &common_state)); ASSERT_TRUE(common_state.initial_presentation_delay_known); encoder.model()->initial_presentation_delay = @@ -897,28 +924,44 @@ void ExpectRationalMatchesLegacyDouble(const Av2DmRational &exact, class ResourceAvailabilityDifferentialAdapter { public: - ResourceAvailabilityDifferentialAdapter(ResourceAdapterMode mode, - bool timing_info_present = true, - bool still_picture = false, - AV2_LEVEL level = SEQ_LEVEL_4_0) + ResourceAvailabilityDifferentialAdapter( + ResourceAdapterMode mode, bool timing_info_present = true, + bool still_picture = false, AV2_LEVEL level = SEQ_LEVEL_4_0, + bool schedule_mode = false, double multistream_scale = 1.0, + int frame_width = 64, int frame_height = 64, int tier = 0, + bool unsignaled_op_display_delay = false) : mode_(mode), cpi_(new AV2_COMP()), level_(level) { AV2_COMMON *const cm = &cpi_->common; - cm->width = 64; - cm->height = 64; + cm->width = frame_width; + cm->height = frame_height; cm->mi_params.mi_cols = 16; cm->mi_params.mi_rows = 16; cm->tiles.cols = 1; cm->tiles.rows = 1; cm->seq_params.ref_frames = kNumRefs; cm->seq_params.seq_profile_idc = MAIN_420_10_IP0; - cm->seq_params.max_frame_width = 64; - cm->seq_params.max_frame_height = 64; + cm->seq_params.max_frame_width = frame_width; + cm->seq_params.max_frame_height = frame_height; cm->seq_params.max_mlayer_id = 0; cm->seq_params.operating_points_cnt_minus_1 = 0; cm->seq_params.operating_point_idc[0] = 0; cm->seq_params.still_picture = still_picture; cm->seq_params.seq_max_display_model_info_present_flag = 1; cm->seq_params.seq_max_initial_display_delay_minus_1 = 0; + if (unsignaled_op_display_delay) { + cm->seq_params.seq_max_display_model_info_present_flag = 0; + cm->seq_params.op_params[0].display_model_param_present_flag = 1; + cm->seq_params.op_params[0].initial_display_delay = 8; + } + if (schedule_mode) { + cm->seq_params.decoder_model_info_present_flag = 1; + cm->seq_params.decoder_model_info.num_units_in_decoding_tick = 1; + cm->seq_params.op_params[0].decoder_model_param_present_flag = 1; + cm->seq_params.op_params[0].decoder_buffer_delay = 45000; + cm->seq_params.op_params[0].encoder_buffer_delay = 45000; + cm->seq_params.op_params[0].display_model_param_present_flag = 1; + cm->seq_params.op_params[0].initial_display_delay = 1; + } cm->ci_params_encoder.ci_timing_info_present_flag = timing_info_present; cm->ci_params_encoder.timing_info.num_units_in_display_tick = 1; cm->ci_params_encoder.timing_info.time_scale = 90000; @@ -931,9 +974,9 @@ class ResourceAvailabilityDifferentialAdapter { cpi_->framerate = 30.0; cpi_->level_params.keep_level_stats = 1; cpi_->level_params.level_info[0] = &level_info_; - cpi_->level_params.multi_stream_scaling_x = 1.0; + cpi_->level_params.multi_stream_scaling_x = multistream_scale; cpi_->level_params.frame_header_count = 1; - cpi_->tier[0] = 0; + cpi_->tier[0] = tier; for (int candidate = SEQ_LEVEL_2_0; candidate < SEQ_LEVELS; ++candidate) { level_info_.decoder_models[candidate].status = DECODER_MODEL_DISABLED; @@ -1065,6 +1108,12 @@ class ResourceAvailabilityDifferentialAdapter { cpi_->common.ci_params_encoder.timing_info.time_scale = time_scale; } + void SetTier(int tier) { + ASSERT_GE(tier, 0); + ASSERT_LE(tier, 1); + cpi_->tier[0] = tier; + } + void Finish() { if (RunsLegacy()) { av2_encoder_decoder_model_finish_for_operating_points(cpi_.get()); @@ -1074,6 +1123,15 @@ class ResourceAvailabilityDifferentialAdapter { DECODER_MODEL *legacy() { return legacy_; } const DECODER_MODEL *legacy() const { return legacy_; } + AV2LevelInfo *level_info() { return &level_info_; } + int SelectedLevel() const { + int levels[MAX_NUM_OPERATING_POINTS] = {}; + if (av2_get_seq_level_idx(cpi_.get(), &cpi_->common.seq_params, + &cpi_->level_params, levels) != AVM_CODEC_OK) { + return -1; + } + return levels[0]; + } void ClearEncoderReferenceOnly(int ref_index) { ASSERT_GE(ref_index, 0); ASSERT_LT(ref_index, kNumRefs); @@ -1123,7 +1181,7 @@ void ExpectSharedResourceState( ASSERT_NE(adapter.legacy(), nullptr); ASSERT_NE(adapter.common(), nullptr); const DECODER_MODEL &legacy = *adapter.legacy(); - Av2DmState exact; + Av2DmState exact{}; ASSERT_TRUE(av2_decoder_model_get_state(adapter.common(), &exact)); ASSERT_EQ(legacy.status, DECODER_MODEL_OK); @@ -1204,7 +1262,7 @@ void RunNormalResourceTrace(ResourceAvailabilityDifferentialAdapter *adapter) { adapter->Finish(); } -TEST(EncoderDecoderModelTest, ReleasesPlayerOwnedInactiveBuffer) { +TEST(EncoderDecoderModelTest, DoesNotReleaseInactiveBackingBuffer) { ResourceAvailabilityDifferentialAdapter adapter( ResourceAdapterMode::kLegacyOnly); ASSERT_TRUE(adapter.valid()); @@ -1223,10 +1281,10 @@ TEST(EncoderDecoderModelTest, ReleasesPlayerOwnedInactiveBuffer) { adapter.DecodeRefreshAndMaybeOutput(1000, 1, 0, false); ASSERT_EQ(DECODER_MODEL_OK, model->status); - EXPECT_EQ(0u, inactive->player_ref_count); - EXPECT_EQ(-1, inactive->display_index); - EXPECT_DOUBLE_EQ(-1.0, inactive->presentation_time); - EXPECT_FALSE(inactive->presentation.valid); + EXPECT_EQ(1u, inactive->player_ref_count); + EXPECT_EQ(7, inactive->display_index); + EXPECT_DOUBLE_EQ(0.25, inactive->presentation_time); + EXPECT_TRUE(inactive->presentation.valid); } TEST(EncoderDecoderModelTest, OrdinaryFrameDoesNotInvalidateModelReference) { @@ -1277,39 +1335,57 @@ TEST(EncoderDecoderModelTest, ClkSupportsActiveReferenceRangeTransitions) { ResourceAdapterMode::kLegacyOnly); ASSERT_TRUE(adapter.valid()); adapter.SetInitialDisplayDelay(10); - adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.DecodeRefreshAndMaybeOutput(24, 1, 0, true); DECODER_MODEL *const model = adapter.legacy(); ASSERT_NE(model, nullptr); adapter.BeginNewCvs(8); - adapter.DecodeRefreshAndMaybeOutput(1024, 1, 1, true, true); + adapter.DecodeRefreshAndMaybeOutput(24, 1, 1, true, true); ASSERT_EQ(DECODER_MODEL_OK, model->status); EXPECT_EQ(8, model->num_ref_frames); EXPECT_EQ(1, model->num_frame); + Av2DmState exact{}; + ASSERT_TRUE(av2_decoder_model_get_state(model->exact_model, &exact)); + EXPECT_EQ(8u, exact.buffer_pool.num_ref_frames); adapter.BeginNewCvs(4); - adapter.DecodeRefreshAndMaybeOutput(1024, 1, 2, true, true); + adapter.DecodeRefreshAndMaybeOutput(24, 1, 2, true, true); ASSERT_EQ(DECODER_MODEL_OK, model->status); EXPECT_EQ(4, model->num_ref_frames); EXPECT_EQ(2, model->num_frame); + ASSERT_TRUE(av2_decoder_model_get_state(model->exact_model, &exact)); + EXPECT_EQ(4u, exact.buffer_pool.num_ref_frames); } -TEST(EncoderDecoderModelTest, IncompatibleClkClockIsUnavailable) { +TEST(EncoderDecoderModelTest, ClkClockChangeRetainsContinuousModelState) { ResourceAvailabilityDifferentialAdapter adapter( ResourceAdapterMode::kLegacyOnly); ASSERT_TRUE(adapter.valid()); - adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.DecodeRefreshAndMaybeOutput(24, 1, 0, true); DECODER_MODEL *const model = adapter.legacy(); ASSERT_NE(model, nullptr); ASSERT_EQ(DECODER_MODEL_OK, model->status); + const double first_time = model->current_time; + ASSERT_NE(model->exact_model, nullptr); + Av2DmState exact_before{}; + ASSERT_TRUE(av2_decoder_model_get_state(model->exact_model, &exact_before)); adapter.SetDisplayTimeScale(60000); adapter.BeginNewCvs(4); adapter.DecodeRefreshAndMaybeOutput(1024, 1, 1, true, true); - EXPECT_EQ(DECODER_MODEL_UNSUPPORTED, model->status); - EXPECT_EQ(ENCODER_DM_RESULT_UNAVAILABLE, - av2_encoder_decoder_model_classify_status(model->status)); + EXPECT_EQ(DECODER_MODEL_OK, model->status); + EXPECT_GE(model->current_time, first_time); + EXPECT_DOUBLE_EQ(1.0 / 60000.0, model->display_clock_tick); + Av2DmState exact_after{}; + ASSERT_TRUE(av2_decoder_model_get_state(model->exact_model, &exact_after)); + int comparison; + ASSERT_TRUE(av2_dm_rational_compare(&exact_after.time, &exact_before.time, + &comparison)); + EXPECT_GE(comparison, 0); + EXPECT_EQ(2u, exact_after.frame_number); + EXPECT_EQ(2u, exact_after.shown_frame_number); + EXPECT_EQ(4u, exact_after.buffer_pool.num_ref_frames); } TEST(EncoderDecoderModelTest, ClkFlushesImplicitOutputBeforeInvalidation) { @@ -1404,6 +1480,222 @@ TEST(EncoderDecoderModelRationalReuseTest, AV2_DM_RESULT_CONFORMANT); } +TEST(EncoderDecoderModelRationalReuseTest, + ProductionObserverDualRunsResourceEvents) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + for (uint64_t frame = 0; frame < 3; ++frame) { + adapter.DecodeRefreshAndMaybeOutput(1024, 1u << frame, frame, true); + } + adapter.Finish(); + + ASSERT_NE(adapter.legacy()->exact_model, nullptr); + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + EXPECT_EQ(AV2_DM_RESULT_CONFORMANT, result.status); + EXPECT_TRUE(result.finished); + EXPECT_EQ(3u, result.decoded_frames); + EXPECT_EQ(3u, result.output_frames); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ProductionObserverIgnoresUnsignaledLegacyDisplayDelay) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, false, 1.0, + 64, 64, 0, true); + ASSERT_TRUE(adapter.valid()); + EXPECT_EQ(kNumRefs + 2, adapter.legacy()->initial_display_delay); + + for (uint64_t frame = 0; frame < 7; ++frame) { + adapter.DecodeRefreshAndMaybeOutput(1024, 1u << (frame % kNumRefs), frame, + true); + } + adapter.Finish(); + + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + EXPECT_EQ(AV2_DM_RESULT_CONFORMANT, result.status); + EXPECT_TRUE(result.finished); + EXPECT_FALSE(result.missing_required_input); + EXPECT_EQ(7u, result.decoded_frames); + EXPECT_EQ(7u, result.output_frames); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ProductionScheduleObserverWithoutRemovalTimingIsUnavailable) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, true, 1.0, + 1536, 1536); + ASSERT_TRUE(adapter.valid()); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.Finish(); + + ASSERT_NE(adapter.legacy()->exact_model, nullptr); + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + EXPECT_EQ(AV2_DM_DECODING_SCHEDULE_MODE, result.mode); + EXPECT_EQ(AV2_DM_RESULT_INDETERMINATE, result.status); + EXPECT_TRUE(result.missing_required_input); + EXPECT_FALSE(result.violations); + adapter.legacy()->max_decode_rate_satisfy = false; + adapter.level_info()->level_stats.total_time_encoded = 1.0; + adapter.level_info()->level_stats.total_compressed_size = 1.0; + EXPECT_EQ(SEQ_LEVEL_MAX, adapter.SelectedLevel()); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ProductionObserverAppliesMultistreamLimits) { + ResourceAvailabilityDifferentialAdapter unscaled( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, false, 1.0, + 400, 64); + ASSERT_TRUE(unscaled.valid()); + unscaled.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + unscaled.Finish(); + Av2DmResult unscaled_result; + ASSERT_TRUE(av2_decoder_model_get_result(unscaled.legacy()->exact_model, + &unscaled_result)); + EXPECT_EQ(AV2_DM_RESULT_CONFORMANT, unscaled_result.status); + + ResourceAvailabilityDifferentialAdapter scaled( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, false, 9.0, + 400, 64); + ASSERT_TRUE(scaled.valid()); + scaled.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + scaled.Finish(); + Av2DmResult scaled_result; + ASSERT_TRUE(av2_decoder_model_get_result(scaled.legacy()->exact_model, + &scaled_result)); + EXPECT_EQ(AV2_DM_RESULT_NON_CONFORMANT, scaled_result.status); + EXPECT_GT(scaled_result.violations, 0u); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ProductionObserverRejectsNonintegralMultistreamLimits) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_8_0, false, 9.0); + ASSERT_TRUE(adapter.valid()); + ASSERT_NE(adapter.legacy()->exact_model, nullptr); + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + EXPECT_EQ(AV2_DM_RESULT_INDETERMINATE, result.status); + EXPECT_TRUE(result.missing_required_input); + EXPECT_FALSE(result.violations); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ProductionObserverAppliesBufferSizeIncreaseAtFirstArrival) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, false, 1.0, + 64, 64, 0); + ASSERT_TRUE(adapter.valid()); + const double old_capacity = RationalToDouble(adapter.legacy()->buffer_size); + adapter.DecodeRefreshAndMaybeOutput(24, 1, 0, true); + adapter.SetTier(1); + adapter.BeginNewCvs(4); + adapter.DecodeRefreshAndMaybeOutput(20000000, 2, 1, true, true); + adapter.Finish(); + + ASSERT_EQ(DECODER_MODEL_OK, adapter.legacy()->status); + EXPECT_TRUE(adapter.legacy()->exact_buffer_size_history_required); + EXPECT_LT(old_capacity, 20000024.0); + EXPECT_GE(RationalToDouble(adapter.legacy()->buffer_size), 20000024.0); + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + EXPECT_EQ(AV2_DM_RESULT_CONFORMANT, result.status); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ProductionObserverAppliesBufferSizeDecreaseAfterRemoval) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, false, 1.0, + 1536, 1536, 1); + ASSERT_TRUE(adapter.valid()); + for (uint64_t frame = 0; frame < 5; ++frame) { + adapter.DecodeRefreshAndMaybeOutput(908000, 1, frame, true); + } + const uint64_t old_fullness = adapter.legacy()->dfg_interval_queue.total_bits; + const double first_old_removal = + adapter.legacy() + ->dfg_interval_queue.buf[adapter.legacy()->dfg_interval_queue.head] + .removal_time; + const double old_capacity = RationalToDouble(adapter.legacy()->buffer_size); + adapter.SetTier(0); + adapter.BeginNewCvs(4); + adapter.DecodeRefreshAndMaybeOutput(9000000, 2, 5, true, true); + adapter.Finish(); + + ASSERT_EQ(DECODER_MODEL_OK, adapter.legacy()->status); + EXPECT_TRUE(adapter.legacy()->exact_buffer_size_history_required); + const double new_capacity = RationalToDouble(adapter.legacy()->buffer_size); + const double fullness_before_first_old_removal = + old_fullness + + (first_old_removal - adapter.legacy()->first_bit_arrival_time) * + new_capacity; + EXPECT_GT(fullness_before_first_old_removal, new_capacity); + EXPECT_LT(fullness_before_first_old_removal, old_capacity); + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + EXPECT_EQ(AV2_DM_RESULT_CONFORMANT, result.status); +} + +TEST(EncoderDecoderModelRationalReuseTest, + ExactResultIsAuthoritativeAcrossChangedCvsLimits) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly, true, false, SEQ_LEVEL_4_0, false, 1.0, + 1536, 1536, 1); + ASSERT_TRUE(adapter.valid()); + adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, true); + adapter.SetTier(0); + adapter.BeginNewCvs(4); + adapter.DecodeRefreshAndMaybeOutput(1024, 2, 1, true, true); + adapter.Finish(); + + Av2DmResult result; + ASSERT_TRUE( + av2_decoder_model_get_result(adapter.legacy()->exact_model, &result)); + ASSERT_EQ(AV2_DM_RESULT_CONFORMANT, result.status); + ASSERT_EQ(DECODER_MODEL_OK, adapter.legacy()->status); + + // These retained legacy values model the old-CVS rolling operands that were + // formerly evaluated under the new tier. The exact model owns these checks; + // static per-CVS geometry and average bitrate remain independently checked. + adapter.legacy()->max_display_rate = LDBL_MAX; + adapter.legacy()->max_decode_rate_satisfy = false; + adapter.legacy()->max_tile_rate_satisfy = false; + adapter.legacy()->compressed_size_satisfy = false; + adapter.legacy()->frame_symbol_count_satisfy = false; + adapter.legacy()->min_presentation_interval_satisfy = false; + adapter.level_info()->level_spec.max_header_rate = 901; + adapter.level_info()->level_spec.max_tile_rate = 3841; + adapter.level_info()->level_stats.max_tile_size = 1000000; + adapter.level_info()->level_stats.total_time_encoded = 1.0; + adapter.level_info()->level_stats.total_compressed_size = 1.0; + + EXPECT_EQ(SEQ_LEVEL_4_0, adapter.SelectedLevel()); + adapter.legacy()->max_display_rate = 0.0; + adapter.legacy()->max_decode_rate_satisfy = true; + adapter.legacy()->max_tile_rate_satisfy = true; + adapter.legacy()->compressed_size_satisfy = true; + adapter.legacy()->frame_symbol_count_satisfy = true; + adapter.legacy()->min_presentation_interval_satisfy = true; + adapter.level_info()->level_spec.max_header_rate = 1; + adapter.level_info()->level_spec.max_tile_rate = 1; + adapter.level_info()->level_stats.max_tile_size = 4096; + adapter.legacy()->status = DISPLAY_FRAME_LATE; + EXPECT_EQ(SEQ_LEVEL_MAX, adapter.SelectedLevel()); + adapter.legacy()->initialized = false; + adapter.legacy()->status = DECODER_MODEL_INTERNAL_ERROR; + adapter.level_info()->level_spec.max_tile_rate = 3841; + EXPECT_EQ(SEQ_LEVEL_MAX, adapter.SelectedLevel()); +} + TEST(EncoderDecoderModelRationalReuseTest, SingleDfgAndOutputTuTerminalApplicabilityAgree) { ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth); @@ -1444,7 +1736,7 @@ TEST(EncoderDecoderModelRationalReuseTest, ASSERT_TRUE(adapter.valid()); adapter.DecodeRefreshAndMaybeOutput(1024, 1, 0, false); - Av2DmState exact; + Av2DmState exact{}; ASSERT_TRUE(av2_decoder_model_get_state(adapter.common(), &exact)); const double exact_decode_duration = RationalToDouble(exact.time_to_decode); const double legacy_recovered_duration = diff --git a/test/level_test.cc b/test/level_test.cc index d22d5a9306..f315510b7e 100644 --- a/test/level_test.cc +++ b/test/level_test.cc @@ -25,15 +25,19 @@ extern "C" { #include "av2/common/timing.h" #include "av2/encoder/bitstream.h" #include "av2/encoder/encoder.h" +double time_next_buffer_is_free(const DECODER_MODEL *decoder_model); } #include "test/codec_factory.h" #include "test/encode_test_driver.h" #include "test/i420_video_source.h" +#include "test/decoder_model_lifecycle.h" #include "test/util.h" #include "test/y4m_video_source.h" #include "test/yuv_video_source.h" namespace { + +using Av2DmLevelLimits = libavm_test::ScopedDmLevelLimits; #if !CONFIG_SHARED void AppendSection5Obu(std::vector *data, OBU_TYPE type, uint8_t payload_size) { @@ -167,11 +171,12 @@ TEST(LevelDecoderModelTest, DisplayClockTickUsesDisplayTimebaseUnits) { cpi->common.ci_params_encoder.timing_info.num_ticks_per_elemental_duration = 7; - DECODER_MODEL decoder_model; + DECODER_MODEL decoder_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); EXPECT_DOUBLE_EQ(1001.0 / 30000, decoder_model.display_clock_tick); EXPECT_EQ(7, decoder_model.num_ticks_per_picture); + av2_encoder_decoder_model_destroy(&decoder_model); } TEST(LevelDecoderModelTest, InitialDisplayDelayUsesSequenceSemantics) { @@ -181,7 +186,7 @@ TEST(LevelDecoderModelTest, InitialDisplayDelayUsesSequenceSemantics) { cpi->common.seq_params.op_params[0].initial_display_delay = 8; cpi->framerate = 30.0; - DECODER_MODEL decoder_model; + DECODER_MODEL decoder_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); EXPECT_EQ(6, decoder_model.initial_display_delay); @@ -189,6 +194,7 @@ TEST(LevelDecoderModelTest, InitialDisplayDelayUsesSequenceSemantics) { cpi->common.seq_params.seq_max_initial_display_delay_minus_1 = 2; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); EXPECT_EQ(3, decoder_model.initial_display_delay); + av2_encoder_decoder_model_destroy(&decoder_model); } TEST(LevelDecoderModelTest, NewCvsResetPreservesDecoderModelState) { @@ -268,7 +274,7 @@ TEST(LevelDecoderModelTest, BufferPoolInitializationUsesFixedCapacity) { cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; cpi->framerate = 30.0; - DECODER_MODEL model; + DECODER_MODEL model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &model); for (int i = 0; i < REF_FRAMES; ++i) EXPECT_EQ(-1, model.vbi[i]); @@ -281,6 +287,20 @@ TEST(LevelDecoderModelTest, BufferPoolInitializationUsesFixedCapacity) { av2_encoder_decoder_model_destroy(&model); } +TEST(LevelDecoderModelTest, ExpiredPresentationDoesNotMoveTimeBackward) { + DECODER_MODEL model = {}; + model.num_ref_frames = 1; + model.num_decoded_frame = 1; + model.current_time = 10.0; + for (int i = 0; i < model.num_ref_frames + 2; ++i) { + model.frame_buffer_pool[i].player_ref_count = 1; + model.frame_buffer_pool[i].presentation_time = 11.0 + i; + } + model.frame_buffer_pool[1].presentation_time = 9.9; + + EXPECT_DOUBLE_EQ(10.0, time_next_buffer_is_free(&model)); +} + TEST(LevelDecoderModelTest, AnnexABitrateProfileFactorsAreExact) { EXPECT_EQ(12000000, av2_max_level_bitrate(MAIN_420_10_IP0, SEQ_LEVEL_4_0, 0)); EXPECT_EQ(20004000, av2_max_level_bitrate(MAIN_422_10_IP1, SEQ_LEVEL_4_0, 0)); @@ -291,8 +311,8 @@ TEST(LevelDecoderModelTest, AnnexABitrateProfileFactorsAreExact) { EXPECT_EQ(90000000, av2_max_level_bitrate(MAIN_444C_12_IP2, SEQ_LEVEL_4_0, 1)); - Av2DmLevelLimits main_limits; - Av2DmLevelLimits high_limits; + Av2DmLevelLimits main_limits{}; + Av2DmLevelLimits high_limits{}; ASSERT_TRUE(av2_dm_get_level_limits(SEQ_LEVEL_4_0, 0, MAIN_444C_12_IP2, &main_limits)); ASSERT_TRUE(av2_dm_get_level_limits(SEQ_LEVEL_4_0, 1, MAIN_444C_12_IP2, @@ -305,8 +325,8 @@ TEST(LevelDecoderModelTest, AnnexABitrateProfileFactorsAreExact) { cpi->common.seq_params.seq_profile_idc = MAIN_444C_12_IP2; cpi->tier[0] = 0; cpi->tier[1] = 1; - DECODER_MODEL main_model; - DECODER_MODEL high_model; + DECODER_MODEL main_model = {}; + DECODER_MODEL high_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &main_model); av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 1, &high_model); EXPECT_EQ(main_model.status, DECODER_MODEL_OK); @@ -372,15 +392,15 @@ TEST(LevelDecoderModelTest, UsesSelectedOperatingPointTierAndBufferSize) { cpi->tier[0] = 0; cpi->tier[1] = 1; - DECODER_MODEL main_model; - DECODER_MODEL high_model; + DECODER_MODEL main_model = {}; + DECODER_MODEL high_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &main_model); av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 1, &high_model); EXPECT_EQ(0, main_model.operating_point); EXPECT_EQ(0, main_model.tier); - Av2DmRational main_expected; - Av2DmRational high_expected; + Av2DmRational main_expected{}; + Av2DmRational high_expected{}; ASSERT_TRUE(av2_dm_rational_make(12000000, 1, &main_expected)); ASSERT_TRUE(av2_dm_rational_make(30000000, 1, &high_expected)); int comparison; @@ -410,10 +430,10 @@ TEST(LevelDecoderModelTest, PreservesFractionalMultistreamBitrate) { cpi->tier[0] = 0; cpi->level_params.multi_stream_scaling_x = 9.0; - DECODER_MODEL decoder_model; + DECODER_MODEL decoder_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); - Av2DmRational expected; + Av2DmRational expected{}; ASSERT_TRUE(av2_dm_rational_make(4000000, 3, &expected)); int comparison; ASSERT_TRUE( @@ -429,7 +449,7 @@ TEST(LevelDecoderModelTest, RejectsHighTierBelowLevelFour) { cpi->common.seq_params.seq_profile_idc = MAIN_420_10_IP0; cpi->tier[0] = 1; - DECODER_MODEL decoder_model; + DECODER_MODEL decoder_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_2_1, 0, &decoder_model); EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, decoder_model.status); @@ -442,7 +462,7 @@ TEST(LevelDecoderModelTest, ConfigurableProfileNeedsExplicitFactors) { cpi->common.seq_params.seq_profile_idc = CONFIGURABLE; cpi->tier[0] = 0; - DECODER_MODEL decoder_model; + DECODER_MODEL decoder_model = {}; av2_decoder_model_init(cpi.get(), SEQ_LEVEL_4_0, 0, &decoder_model); EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, decoder_model.status); @@ -473,12 +493,12 @@ TEST(LevelDecoderModelTest, ParametersMatchAnnexATableForSupportedScope) { for (int profile = 0; profile < profile_count; ++profile) { cpi->common.seq_params.seq_profile_idc = static_cast(profile); - Av2DmLevelLimits base_limits; + Av2DmLevelLimits base_limits{}; const bool defined = av2_dm_get_level_limits(level, tier, profile, &base_limits); for (const ScalingCase &scaling : scalings) { cpi->level_params.multi_stream_scaling_x = scaling.value; - DECODER_MODEL decoder_model; + DECODER_MODEL decoder_model = {}; av2_decoder_model_init(cpi.get(), static_cast(level), 0, &decoder_model); const bool supported = @@ -489,8 +509,8 @@ TEST(LevelDecoderModelTest, ParametersMatchAnnexATableForSupportedScope) { << "level=" << level << " tier=" << tier << " profile=" << profile; } else { - Av2DmRational scaled; - Av2DmRational expected_bit_rate; + Av2DmRational scaled{}; + Av2DmRational expected_bit_rate{}; ASSERT_TRUE(av2_dm_rational_multiply_u64( &base_limits.bit_rate, scaling.denominator, &scaled)); ASSERT_TRUE(av2_dm_rational_divide_u64(&scaled, scaling.numerator, @@ -502,7 +522,7 @@ TEST(LevelDecoderModelTest, ParametersMatchAnnexATableForSupportedScope) { << "level=" << level << " tier=" << tier << " profile=" << profile << " scale=" << scaling.value; - Av2DmRational expected_buffer_size; + Av2DmRational expected_buffer_size{}; ASSERT_TRUE(av2_dm_rational_multiply_u64( &base_limits.buffer_size, scaling.denominator, &scaled)); ASSERT_TRUE(av2_dm_rational_divide_u64(&scaled, scaling.numerator, @@ -621,11 +641,10 @@ TEST(LevelDecoderModelTest, OlkOperatingPointHookPreservesEncoderReferences) { sizeof(ref_frame_map_before))); } -void ExpectClkInvalidationClearsEveryVbiSlot(int old_num_ref_frames, - int new_num_ref_frames) { +void ExpectClkInvalidationClearsActiveVbiSlots(int num_ref_frames) { AV2_COMMON cm = {}; RefCntBuffer active_reference = {}; - cm.seq_params.ref_frames = new_num_ref_frames; + cm.seq_params.ref_frames = num_ref_frames; cm.ref_frame_map[0] = &active_reference; RefCntBuffer *ref_frame_map_before[REF_FRAMES]; std::memcpy(ref_frame_map_before, cm.ref_frame_map, @@ -633,15 +652,15 @@ void ExpectClkInvalidationClearsEveryVbiSlot(int old_num_ref_frames, DECODER_MODEL model = {}; model.status = DECODER_MODEL_OK; - model.num_ref_frames = old_num_ref_frames; - for (int i = 0; i < REF_FRAMES; ++i) { + model.num_ref_frames = num_ref_frames; + for (int i = 0; i < num_ref_frames; ++i) { model.vbi[i] = i; model.frame_buffer_pool[i].decoder_ref_count = 1; } ASSERT_TRUE( av2_encoder_decoder_model_invalidate_ref_buffers(&cm, &model, true)); - for (int i = 0; i < REF_FRAMES; ++i) { + for (int i = 0; i < num_ref_frames; ++i) { EXPECT_EQ(-1, model.vbi[i]); EXPECT_EQ(0u, model.frame_buffer_pool[i].decoder_ref_count); } @@ -649,12 +668,9 @@ void ExpectClkInvalidationClearsEveryVbiSlot(int old_num_ref_frames, sizeof(ref_frame_map_before))); } -TEST(LevelDecoderModelTest, ClkInvalidationAllowsDecreasedActiveRange) { - ExpectClkInvalidationClearsEveryVbiSlot(8, 4); -} - -TEST(LevelDecoderModelTest, ClkInvalidationAllowsIncreasedActiveRange) { - ExpectClkInvalidationClearsEveryVbiSlot(4, 8); +TEST(LevelDecoderModelTest, ClkInvalidationUsesActiveVbiRange) { + ExpectClkInvalidationClearsActiveVbiSlots(4); + ExpectClkInvalidationClearsActiveVbiSlots(8); } TEST(LevelDecoderModelTest, ReferenceUpdateUsesPostUpdateValidity) { @@ -738,7 +754,7 @@ TEST(LevelDecoderModelTest, InitialDelayRebasesPreviouslyAssignedTimes) { EXPECT_DOUBLE_EQ(5.02, model->frame_buffer_pool[1].presentation_time); } -TEST(LevelDecoderModelTest, InitialDelayCountsAndRebasesInactiveBuffer) { +TEST(LevelDecoderModelTest, InitialDelayIgnoresInactiveBackingBuffer) { std::unique_ptr cpi(new AV2_COMP()); AV2LevelInfo level_info = {}; DECODER_MODEL *const model = @@ -774,8 +790,8 @@ TEST(LevelDecoderModelTest, InitialDelayCountsAndRebasesInactiveBuffer) { av2_decoder_model_update_buffer_and_finish_frame_decode_for_operating_points( cpi.get()); - EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); - EXPECT_DOUBLE_EQ(5.02, inactive->presentation_time); + EXPECT_DOUBLE_EQ(-1.0, model->initial_presentation_delay); + EXPECT_DOUBLE_EQ(0.02, inactive->presentation_time); } TEST(LevelDecoderModelTest, CapturedGenerationIsPrivateAndRecyclable) { @@ -1952,7 +1968,7 @@ TEST(LevelDecoderModelTest, OperatingPointFinishIsIdempotent) { EXPECT_EQ(2000.0L, model->max_display_rate); } -TEST(LevelDecoderModelTest, FinishRebasesEveryPlayerOwnedBuffer) { +TEST(LevelDecoderModelTest, FinishRebasesOnlyActivePlayerOwnedBuffers) { std::unique_ptr cpi(new AV2_COMP()); AV2LevelInfo level_info = {}; cpi->level_params.keep_level_stats = 1; @@ -1981,7 +1997,7 @@ TEST(LevelDecoderModelTest, FinishRebasesEveryPlayerOwnedBuffer) { EXPECT_DOUBLE_EQ(5.0, model->initial_presentation_delay); EXPECT_DOUBLE_EQ(5.04, model->presentation_time); EXPECT_DOUBLE_EQ(5.02, active->presentation_time); - EXPECT_DOUBLE_EQ(5.04, inactive->presentation_time); + EXPECT_DOUBLE_EQ(0.04, inactive->presentation_time); } TEST(LevelDecoderModelTest, FinishPreservesKnownInitialDelay) { diff --git a/test/ops_test.cc b/test/ops_test.cc index 5dc3daa30c..f29640c651 100644 --- a/test/ops_test.cc +++ b/test/ops_test.cc @@ -21,6 +21,7 @@ #include "avm_dsp/bitwriter_buffer.h" #include "avm_dsp/bitreader_buffer.h" #include "avm_mem/avm_mem.h" +#include "test/decoder_model_lifecycle.h" // av2_set_ops_params is declared in bitstream.h which pulls in encoder.h. // Forward-declare it here to avoid the ThreadData conflict. @@ -29,6 +30,8 @@ extern "C" void av2_set_ops_params(struct OperatingPointSet *ops, int xlayer_id, namespace { +using Av2DmVerifierStats = libavm_test::ScopedDmVerifierStats; + static void rb_error_handler(void *data, avm_codec_err_t error, const char *detail) { (void)data; @@ -102,7 +105,7 @@ TEST_F(OpsTest, LocalOpsRoundtrip) { EXPECT_EQ(dop->mlayer_info.ops_mlayer_map[xlayer_id], 0x3); EXPECT_EQ(dop->mlayer_info.OPMLayerCount[xlayer_id], 2); - Av2DmVerifierStats stats; + Av2DmVerifierStats stats = {}; ASSERT_TRUE(av2_decoder_model_verifier_get_stats(pbi_, &stats)); EXPECT_EQ(stats.contexts, 1u); av2_decoder_model_verifier_destroy(pbi_); From 8ab8deacd3f669afb63da08e700f0a89e6d350ec Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Wed, 19 Aug 2026 17:33:58 -0700 Subject: [PATCH 7/8] One frame sequence fix --- av2/common/decoder_model.c | 14 ++++ av2/common/level.c | 44 +++++++++--- test/decoder_model_test.cc | 110 +++++++++++++++++++++++++++-- test/encoder_decoder_model_test.cc | 7 +- test/level_test.cc | 68 ++++++++++++++++-- 5 files changed, 225 insertions(+), 18 deletions(-) diff --git a/av2/common/decoder_model.c b/av2/common/decoder_model.c index d4f17c762c..4d2d07d006 100644 --- a/av2/common/decoder_model.c +++ b/av2/common/decoder_model.c @@ -5754,6 +5754,20 @@ static void decoder_model_finish_internal(Av2DecoderModel *model) { model->shown_frame_number != 0) { incomplete_verification(model); } + if (!model->processing_stopped && model->previous_dfg_valid && + !model->previous_dfg.still_picture && model->dfg_number == 1) { + Av2DmRational frame_parsing_time = { 0 }; + if (!av2_dm_rational_make(model->previous_dfg.limits.max_picture_size, + model->previous_dfg.limits.max_decode_rate, + &frame_parsing_time)) { + arithmetic_failure(model); + } else { + check_frame_parsing_constraints(model, &model->previous_dfg, + &frame_parsing_time, + model->previous_dfg.event_index); + } + av2_dm_rational_destroy(&frame_parsing_time); + } if (!model->processing_stopped && (!model->previous_dfg_valid || !model->previous_dfg.still_picture) && model->dfg_number > 1) { diff --git a/av2/common/level.c b/av2/common/level.c index 542242f240..d42290fa9a 100644 --- a/av2/common/level.c +++ b/av2/common/level.c @@ -1639,14 +1639,13 @@ static bool encoder_dm_rational_less_than_or_equal( // Time after decoding the preceding frame. In that case FrameParsingTime is // exactly LumaSampleCount / MaxDecodeRate. Evaluate all four Annex A limits by // rational cross-products rather than reconstructing that interval from two -// rounded double removal times. +// rounded double removal times. The same exact path handles the single-frame +// MaxPicSize / MaxDecodeRate fallback. static bool check_frame_constraints_at_decode_limit( DECODER_MODEL *decoder_model, const ENCODER_DECODER_MODEL_FRAME *frame, - uint64_t frame_parsing_time_decode_luma_samples) { - const Av2DmLevelLimits *const limits = &decoder_model->level_limits; - const uint32_t scale_numerator = decoder_model->multistream_scale_numerator; - const uint32_t scale_denominator = - decoder_model->multistream_scale_denominator; + uint64_t frame_parsing_time_decode_luma_samples, + const Av2DmLevelLimits *limits, uint32_t scale_numerator, + uint32_t scale_denominator) { Av2DmRational observed = { 0 }; Av2DmRational limit = { 0 }; Av2DmRational dynamic_tile_limit = { 0 }; @@ -1660,7 +1659,12 @@ static bool check_frame_constraints_at_decode_limit( bool success = false; uint64_t picture_units; - if (frame_parsing_time_decode_luma_samples == 0) goto cleanup; + if (limits == NULL || limits->max_decode_rate == 0 || + limits->max_tiles == 0 || limits->picture_size_profile_factor == 0 || + limits->min_compression_basis == 0 || scale_numerator == 0 || + scale_denominator == 0 || frame_parsing_time_decode_luma_samples == 0) { + goto cleanup; + } const long double max_decode_rate = (long double)limits->max_decode_rate * scale_denominator / scale_numerator; const long double observed_decode_rate = @@ -1798,7 +1802,9 @@ bool av2_encoder_decoder_model_check_frame_constraints( } if (frame_parsing_time_at_decode_limit) { if (!check_frame_constraints_at_decode_limit( - decoder_model, frame, frame_parsing_time_decode_luma_samples)) { + decoder_model, frame, frame_parsing_time_decode_luma_samples, + limits, decoder_model->multistream_scale_numerator, + decoder_model->multistream_scale_denominator)) { decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; return false; } @@ -1929,7 +1935,27 @@ void av2_encoder_decoder_model_finalize_frame_constraints( } decoder_model->frame_constraints_finalized = true; if (is_still_picture || !decoder_model->pending_frame.valid) return; - if (decoder_model->applicable_dfg_count == 1) return; + if (decoder_model->applicable_dfg_count == 1) { + Av2DmLevelLimits limits = { 0 }; + if (decoder_model->multistream_scale_numerator == 0 || + decoder_model->multistream_scale_denominator == 0 || + !av2_dm_level_limits_copy(&limits, &decoder_model->level_limits) || + (decoder_model->multistream_scale_numerator != + decoder_model->multistream_scale_denominator && + !av2_dm_apply_multistream_limits( + decoder_model->level, decoder_model->tier, + decoder_model->configured_profile, + decoder_model->multistream_scale_numerator, + decoder_model->multistream_scale_denominator, &limits)) || + limits.max_picture_size == 0 || + !check_frame_constraints_at_decode_limit( + decoder_model, &decoder_model->pending_frame, + limits.max_picture_size, &limits, 1, 1)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } + av2_dm_level_limits_destroy(&limits); + return; + } if (!decoder_model->last_frame_parsing_time_valid) { decoder_model->status = DECODER_MODEL_INCOMPLETE; return; diff --git a/test/decoder_model_test.cc b/test/decoder_model_test.cc index c79d01ca00..a6da64f938 100644 --- a/test/decoder_model_test.cc +++ b/test/decoder_model_test.cc @@ -2419,15 +2419,17 @@ TEST(DecoderModelConformanceTest, } TEST(DecoderModelConformanceTest, - SingleDfgAndOutputTuDoNotRequireTerminalPredecessors) { + SingleDfgUsesPictureDecodeFallbackAndSingleTuNeedsNoDuration) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); config.initial_display_delay = 1; + config.level_limits.max_picture_size = 4096; ViolationCollector collector; Av2DecoderModel *const model = av2_decoder_model_create(&config, CollectViolation, &collector); ASSERT_NE(model, nullptr); - const Av2DmFrameEvent frame = MakeFrame(0, 1); + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.num_tiles = 252; av2_decoder_model_start_frame(model, &frame); const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); av2_decoder_model_update_reference_buffers(model, &refresh); @@ -2435,20 +2437,120 @@ TEST(DecoderModelConformanceTest, Av2DmOutputEvent output = Output(1, 1, -1); output.temporal_unit_index = 0; av2_decoder_model_output_frame(model, &output); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE)); av2_decoder_model_finish(model); EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); - EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE)); + ASSERT_EQ(CountViolations(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE), 1u); + const Av2DmViolation *const violation = + FindViolation(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE); + ASSERT_NE(violation, nullptr); + EXPECT_EQ(violation->event_index, 0u); + EXPECT_EQ(violation->affected_kind, AV2_DM_VIOLATION_AFFECTED_DFG); + EXPECT_EQ(violation->affected_index, 0u); + ASSERT_EQ(violation->detail.kind, AV2_DM_VIOLATION_DETAIL_FRAME_INTERVAL); + ExpectEqualRational(violation->detail.value.frame_interval, 4096, 1000000); EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE)); EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS)); EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_DISPLAY_RATE)); Av2DmResult result; ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); - EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + EXPECT_EQ(result.status, AV2_DM_RESULT_NON_CONFORMANT); EXPECT_FALSE(result.missing_required_input); av2_decoder_model_destroy(model); } +TEST(DecoderModelConformanceTest, + SingleDfgDecodeCountDoesNotDividePictureDecodeFallback) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.level_limits.max_picture_size = 4096; + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.allow_global_intrabc = true; + frame.inloop_filtering_enabled = true; + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); + Av2DmOutputEvent output = Output(1, 1, -1); + output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &output); + av2_decoder_model_finish(model); + + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS)); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, AV2_DM_RESULT_CONFORMANT); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + SingleDfgFallbackIncludesExactFrameSymbolBoundary) { + for (const uint64_t frame_symbols : { UINT64_C(17152), UINT64_C(17153) }) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.initial_display_delay = 1; + config.level_limits.max_picture_size = 4096; + config.level_limits.picture_size_profile_factor = 9; + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.frame_symbol_count = frame_symbols; + av2_decoder_model_start_frame(model, &frame); + const Av2DmReferenceUpdateEvent refresh = Refresh(1, 1); + av2_decoder_model_update_reference_buffers(model, &refresh); + av2_decoder_model_set_initial_presentation_delay(model, false, 0); + Av2DmOutputEvent output = Output(1, 1, -1); + output.temporal_unit_index = 0; + av2_decoder_model_output_frame(model, &output); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS)); + av2_decoder_model_finish(model); + + EXPECT_EQ(CountViolations(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS), + frame_symbols == 17153 ? 1u : 0u); + Av2DmResult result; + ASSERT_TRUE(av2_decoder_model_get_result(model, &result)); + EXPECT_EQ(result.status, frame_symbols == 17153 + ? AV2_DM_RESULT_NON_CONFORMANT + : AV2_DM_RESULT_CONFORMANT); + av2_decoder_model_destroy(model); + } +} + +TEST(DecoderModelConformanceTest, + StillPictureSkipsSingleDfgFrameParsingFallback) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + config.still_picture = true; + config.level_limits.max_picture_size = 4096; + ViolationCollector collector; + Av2DecoderModel *const model = + av2_decoder_model_create(&config, CollectViolation, &collector); + ASSERT_NE(model, nullptr); + + Av2DmFrameEvent frame = MakeFrame(0, 1); + frame.num_tiles = 252; + frame.compressed_size_bytes = 1000000; + frame.frame_symbol_count = 1000000; + av2_decoder_model_start_frame(model, &frame); + av2_decoder_model_finish(model); + + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_TILE_RATE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE)); + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_MAX_FRAME_SYMBOLS)); + av2_decoder_model_destroy(model); +} + TEST(DecoderModelConformanceTest, TwoDfgsAndOutputTusReuseLocalTerminalPredecessors) { Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); diff --git a/test/encoder_decoder_model_test.cc b/test/encoder_decoder_model_test.cc index 7af93e10e1..9c5df07cd6 100644 --- a/test/encoder_decoder_model_test.cc +++ b/test/encoder_decoder_model_test.cc @@ -1697,7 +1697,7 @@ TEST(EncoderDecoderModelRationalReuseTest, } TEST(EncoderDecoderModelRationalReuseTest, - SingleDfgAndOutputTuTerminalApplicabilityAgree) { + SingleDfgFallbackAndSingleTuNonApplicabilityAgree) { ResourceAvailabilityDifferentialAdapter adapter(ResourceAdapterMode::kBoth); ASSERT_TRUE(adapter.valid()); @@ -1707,6 +1707,11 @@ TEST(EncoderDecoderModelRationalReuseTest, ASSERT_NE(adapter.legacy(), nullptr); EXPECT_EQ(1u, adapter.legacy()->applicable_dfg_count); EXPECT_EQ(1u, adapter.legacy()->output_tu_count); + EXPECT_GT(adapter.legacy()->max_decode_rate, 0.0L); + EXPECT_TRUE(adapter.legacy()->max_decode_rate_satisfy); + EXPECT_TRUE(adapter.legacy()->max_tile_rate_satisfy); + EXPECT_TRUE(adapter.legacy()->compressed_size_satisfy); + EXPECT_TRUE(adapter.legacy()->frame_symbol_count_satisfy); ExpectFinalResourceClassification(adapter, ENCODER_DM_RESULT_PASS, AV2_DM_RESULT_CONFORMANT); } diff --git a/test/level_test.cc b/test/level_test.cc index f315510b7e..7577868673 100644 --- a/test/level_test.cc +++ b/test/level_test.cc @@ -48,6 +48,7 @@ void AppendSection5Obu(std::vector *data, OBU_TYPE type, DECODER_MODEL MakeFrameConstraintModel() { DECODER_MODEL model = {}; + av2_dm_level_limits_init(&model.level_limits); model.status = DECODER_MODEL_OK; model.initialized = true; model.max_tile_rate_satisfy = true; @@ -55,6 +56,7 @@ DECODER_MODEL MakeFrameConstraintModel() { model.frame_symbol_count_satisfy = true; model.multistream_scale_numerator = 1; model.multistream_scale_denominator = 1; + model.level_limits.max_picture_size = 1000; model.level_limits.max_decode_rate = 1000; model.level_limits.max_tiles = 4; model.level_limits.picture_size_profile_factor = 8; @@ -1809,6 +1811,13 @@ TEST(LevelDecoderModelTest, RejectsInvalidAnnexALimits) { EXPECT_FALSE(av2_encoder_decoder_model_check_frame_constraints( &model, &frame, std::numeric_limits::infinity(), false, 0)); EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, model.status); + + model = MakeFrameConstraintModel(); + model.level_limits.max_decode_rate = 0; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + EXPECT_EQ(DECODER_MODEL_INTERNAL_ERROR, model.status); } TEST(LevelDecoderModelTest, DefersFrameChecksAndUsesStoredDecodeCount) { @@ -1869,17 +1878,63 @@ TEST(LevelDecoderModelTest, FinalFrameReusesPreviousExactParsingInterval) { EXPECT_EQ(1100, model.max_decode_rate); } -TEST(LevelDecoderModelTest, SingleFrameNeedsNoSubstituteParsingTime) { +TEST(LevelDecoderModelTest, SingleFrameUsesPictureDecodeRateFallback) { DECODER_MODEL model = MakeFrameConstraintModel(); const ENCODER_DECODER_MODEL_FRAME frame = { - true, 1.0, 1000, 1, 1, 1, 1, + true, 1.0, 1001, 1, 5, 501, 3723, }; ASSERT_TRUE( av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); av2_encoder_decoder_model_finalize_frame_constraints(&model, false); EXPECT_EQ(DECODER_MODEL_OK, model.status); EXPECT_EQ(1u, model.applicable_dfg_count); - EXPECT_EQ(0.0L, model.max_decode_rate); + EXPECT_EQ(1001.0L, model.max_decode_rate); + EXPECT_FALSE(model.max_decode_rate_satisfy); + EXPECT_FALSE(model.max_tile_rate_satisfy); + EXPECT_FALSE(model.compressed_size_satisfy); + EXPECT_FALSE(model.frame_symbol_count_satisfy); +} + +TEST(LevelDecoderModelTest, SingleFrameDecodeCountDoesNotDivideFallback) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME frame = { + true, 1.0, 1000, 2, 4, 500, 3722, + }; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + + EXPECT_EQ(DECODER_MODEL_OK, model.status); + EXPECT_EQ(1000.0L, model.max_decode_rate); + EXPECT_TRUE(model.max_decode_rate_satisfy); + EXPECT_TRUE(model.max_tile_rate_satisfy); + EXPECT_TRUE(model.compressed_size_satisfy); + EXPECT_TRUE(model.frame_symbol_count_satisfy); +} + +TEST(LevelDecoderModelTest, SingleFrameFallbackUsesEffectiveMultistreamLimits) { + DECODER_MODEL model = MakeFrameConstraintModel(); + av2_dm_level_limits_destroy(&model.level_limits); + ASSERT_TRUE(av2_dm_get_level_limits(SEQ_LEVEL_4_0, 0, MAIN_420_10_IP0, + &model.level_limits)); + model.level = SEQ_LEVEL_4_0; + model.tier = 0; + model.configured_profile = MAIN_420_10_IP0; + model.multistream_scale_numerator = 3; + model.multistream_scale_denominator = 2; + const ENCODER_DECODER_MODEL_FRAME frame = { + true, 1.0, 1500000, 1, 15, 1, 1, + }; + ASSERT_TRUE( + av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); + + av2_encoder_decoder_model_finalize_frame_constraints(&model, false); + + EXPECT_EQ(DECODER_MODEL_OK, model.status); + EXPECT_FALSE(model.max_decode_rate_satisfy); + EXPECT_TRUE(model.max_tile_rate_satisfy); + av2_encoder_decoder_model_destroy(&model); } TEST(LevelDecoderModelTest, MultipleFramesRequireLocalParsingTime) { @@ -1939,7 +1994,7 @@ TEST(LevelDecoderModelTest, StillPictureNeedsNoPreviousDurations) { DECODER_MODEL model = MakeFrameConstraintModel(); model.display_samples = 1000; const ENCODER_DECODER_MODEL_FRAME frame = { - true, 1.0, 1000, 1, 1, 1, 1, + true, 1.0, 1001, 1, 5, 501, 3723, }; ASSERT_TRUE( av2_encoder_decoder_model_store_frame_constraints(&model, &frame, false)); @@ -1947,6 +2002,11 @@ TEST(LevelDecoderModelTest, StillPictureNeedsNoPreviousDurations) { av2_encoder_decoder_model_finalize(&model, true); EXPECT_EQ(DECODER_MODEL_OK, model.status); EXPECT_TRUE(model.finalized); + EXPECT_EQ(0.0L, model.max_decode_rate); + EXPECT_TRUE(model.max_decode_rate_satisfy); + EXPECT_TRUE(model.max_tile_rate_satisfy); + EXPECT_TRUE(model.compressed_size_satisfy); + EXPECT_TRUE(model.frame_symbol_count_satisfy); } TEST(LevelDecoderModelTest, OperatingPointFinishIsIdempotent) { From e0e771fa10ef3e87c792da550fda725328bf1fe4 Mon Sep 17 00:00:00 2001 From: Andrey Norkin Date: Wed, 19 Aug 2026 22:20:25 -0700 Subject: [PATCH 8/8] Fix decoder model CI failures --- av2/common/decoder_model.c | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/av2/common/decoder_model.c b/av2/common/decoder_model.c index 4d2d07d006..8762de2535 100644 --- a/av2/common/decoder_model.c +++ b/av2/common/decoder_model.c @@ -70,8 +70,6 @@ AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static void multiply_64( *product_low = (middle_2 << 32) | (low & mask); } -#undef AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK - typedef struct Av2DmBigUInt { uint64_t *limbs; uint32_t count; @@ -172,8 +170,9 @@ static void big_uint_trim(Av2DmBigUInt *value) { } static bool big_uint_allocate(Av2DmBigUInt *value, uint32_t capacity) { - if (capacity == 0) return true; +#if SIZE_MAX < UINT64_MAX if ((uint64_t)capacity > SIZE_MAX / sizeof(*value->limbs)) return false; +#endif if (capacity <= AV2_DM_BIG_UINT_INLINE_LIMBS) { value->limbs = value->inline_limbs; } else { @@ -235,8 +234,10 @@ static int big_uint_compare(const Av2DmBigUInt *left, return 0; } -static bool big_uint_add(const Av2DmBigUInt *left, const Av2DmBigUInt *right, - Av2DmBigUInt *result) { +// These helpers intentionally use modulo-2^64 limb arithmetic to propagate +// carries and borrows while constructing an exact multi-limb result. +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool big_uint_add( + const Av2DmBigUInt *left, const Av2DmBigUInt *right, Av2DmBigUInt *result) { const uint32_t maximum = left->count > right->count ? left->count : right->count; if (maximum == UINT32_MAX || !big_uint_allocate(result, maximum + 1)) { @@ -258,8 +259,8 @@ static bool big_uint_add(const Av2DmBigUInt *left, const Av2DmBigUInt *right, return true; } -static bool big_uint_subtract(const Av2DmBigUInt *left, - const Av2DmBigUInt *right, Av2DmBigUInt *result) { +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool big_uint_subtract( + const Av2DmBigUInt *left, const Av2DmBigUInt *right, Av2DmBigUInt *result) { if (big_uint_compare(left, right) < 0 || !big_uint_allocate(result, left->count)) { return false; @@ -279,8 +280,8 @@ static bool big_uint_subtract(const Av2DmBigUInt *left, return true; } -static bool big_uint_multiply(const Av2DmBigUInt *left, - const Av2DmBigUInt *right, Av2DmBigUInt *result) { +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool big_uint_multiply( + const Av2DmBigUInt *left, const Av2DmBigUInt *right, Av2DmBigUInt *result) { if (big_uint_is_zero(left) || big_uint_is_zero(right)) return true; if (UINT32_MAX - left->count < right->count || !big_uint_allocate(result, left->count + right->count)) { @@ -328,7 +329,8 @@ static uint32_t big_uint_bit_count(const Av2DmBigUInt *value) { return (value->count - 1) * 64 + high_bits; } -static bool big_uint_shift_add_bit(Av2DmBigUInt *value, uint64_t bit) { +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool big_uint_shift_add_bit( + Av2DmBigUInt *value, uint64_t bit) { uint64_t carry = bit; for (uint32_t i = 0; i < value->count; ++i) { const uint64_t next_carry = value->limbs[i] >> 63; @@ -346,8 +348,8 @@ static bool big_uint_shift_add_bit(Av2DmBigUInt *value, uint64_t bit) { return true; } -static bool big_uint_subtract_in_place(Av2DmBigUInt *left, - const Av2DmBigUInt *right) { +AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK static bool big_uint_subtract_in_place( + Av2DmBigUInt *left, const Av2DmBigUInt *right) { uint64_t borrow = 0; for (uint32_t i = 0; i < left->count; ++i) { const uint64_t right_limb = i < right->count ? right->limbs[i] : 0; @@ -362,6 +364,8 @@ static bool big_uint_subtract_in_place(Av2DmBigUInt *left, return true; } +#undef AV2_DM_NO_UNSIGNED_OVERFLOW_CHECK + static bool big_uint_divide(const Av2DmBigUInt *dividend, const Av2DmBigUInt *divisor, Av2DmBigUInt *quotient, Av2DmBigUInt *remainder) { @@ -824,10 +828,12 @@ bool av2_dm_rational_compare(const Av2DmRational *left, bool av2_dm_rational_rebase(Av2DmRational *values, uint32_t value_count, const Av2DmRational *origin) { rational_begin_operation(); - if ((values == NULL && value_count != 0) || origin == NULL || - (uint64_t)value_count > SIZE_MAX / sizeof(*values)) { + if ((values == NULL && value_count != 0) || origin == NULL) { return false; } +#if SIZE_MAX < UINT64_MAX + if ((uint64_t)value_count > SIZE_MAX / sizeof(*values)) return false; +#endif Av2DmRational fixed_origin; av2_dm_rational_init(&fixed_origin); Av2DmRational *rebased = NULL;