diff --git a/apps/avmdec.c b/apps/avmdec.c index 1d80f28558..63faba13fb 100644 --- a/apps/avmdec.c +++ b/apps/avmdec.c @@ -113,6 +113,12 @@ 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 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 = @@ -159,6 +165,8 @@ static const arg_def_t *all_args[] = { &help, &fb_arg, &md5arg, &verifyarg, + &checkconformancearg, + &checkconformanceeveryraparg, &framestatsarg, &continuearg, &outbitdeptharg, @@ -654,6 +662,9 @@ 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 decoder_model_check_every_rap = 1; int stop_after = 0, summary = 0, quiet = 1; int arg_skip = 0; int num_streams = 1; @@ -812,6 +823,23 @@ 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, &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) { @@ -1004,6 +1032,21 @@ 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", + 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 +1139,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..d267ef76e2 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,10 @@ 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 decoder_model_check_every_rap; + int compressed_input_started; + int decoder_model_fatal_latched; AVxWorker *frame_worker; @@ -116,6 +121,8 @@ 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; + priv->decoder_model_check_every_rap = 1; init_ibp_info(ctx->priv->ibp_directional_weights); } @@ -401,6 +408,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 +484,13 @@ 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); + } 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 +596,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 +932,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 @@ -944,7 +985,23 @@ 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) { + 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 +1033,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 +1168,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 +2053,51 @@ 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_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); @@ -2039,6 +2163,9 @@ 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 }, // 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..8762de2535 --- /dev/null +++ b/av2/common/decoder_model.c @@ -0,0 +1,6002 @@ +/* + * 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 + +#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" + +_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_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; +} + +#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 + +// 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); +} + +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; + } + void *const allocation = + clear ? avm_calloc(count, size) : avm_malloc(count * size); + if (allocation == NULL) internal_allocation_failed = true; + return allocation; +} + +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; + } + 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; +} + +bool av2_dm_last_failure_was_allocation(void) { + return rational_allocation_failed || internal_allocation_failed; +} + +static void rational_begin_operation(void) { + rational_allocation_failed = false; + internal_allocation_failed = false; +} + +static void big_uint_destroy(Av2DmBigUInt *value) { + if (value->limbs != value->inline_limbs) { + rational_deallocate(value->limbs); + } + memset(value, 0, sizeof(*value)); +} + +static void big_uint_trim(Av2DmBigUInt *value) { + while (value->count != 0 && value->limbs[value->count - 1] == 0) { + --value->count; + } +} + +static bool big_uint_allocate(Av2DmBigUInt *value, uint32_t capacity) { +#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 { + value->limbs = rational_allocate((size_t)capacity * sizeof(*value->limbs)); + if (value->limbs == NULL) return false; + } + memset(value->limbs, 0, (size_t)capacity * sizeof(*value->limbs)); + value->capacity = capacity; + return true; +} + +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)); + } + 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; + } + memset(source, 0, sizeof(*source)); +} + +static bool big_uint_is_zero(const Av2DmBigUInt *value) { + return value->count == 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; +} + +// 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)) { + 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; +} + +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; + } + 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; +} + +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)) { + 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); + 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 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; + } + if (value->count - 1 > (UINT32_MAX - high_bits) / 64) return UINT32_MAX; + return (value->count - 1) * 64 + high_bits; +} + +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; + value->limbs[i] = (value->limbs[i] << 1) | carry; + carry = next_carry; + } + 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; +} + +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; + 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; + } + if (borrow != 0) return false; + big_uint_trim(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) { + 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; +} + +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; + } + *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; + } + 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, + result); +} + +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; + 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) { + 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; + 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; + } + 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) { + 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) { + 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) { + 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; + } 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; + } + } + 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) { + rational_begin_operation(); + 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; + 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; 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]); + } + } + 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) { + 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_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, + uint32_t buffer_index) { + 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; + 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]); + } + 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; +} + +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_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; + } + buffer_reset(buffer); + return true; +} + +bool av2_dm_buffer_pool_add_decoder_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + 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; + return true; +} + +bool av2_dm_buffer_pool_remove_decoder_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + 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; + 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_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; + return true; +} + +bool av2_dm_buffer_pool_remove_player_ref(Av2DmBufferPool *pool, + uint32_t buffer_index) { + 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; + 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 && + !valid_active_buffer_index(pool, (uint32_t)buffer_index))) { + 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; + 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; + 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 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; + 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 cvs_number; + 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 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 { + 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; + +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; + Av2DmRational decoder_buffer_delay; + Av2DmRational encoder_buffer_delay; + uint32_t decoder_buffer_delay_ticks; + 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; + 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; + uint64_t cvs_number; + bool previous_dfg_valid; + Av2DmDfgRecord previous_dfg; + 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; + 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; + 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; + 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; +}; + +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); +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 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); +} + +static bool rational_from_product(uint64_t left, uint64_t right, + Av2DmRational *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) { + 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; + 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, + 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) { + 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 = internal_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; + + 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; + computed.max_tile_area = + (uint64_t)av2_tile_area_scaling_factor[tier][level_idx] * MAX_TILE_AREA / + 4; + computed.max_tile_size_header_rate_product = + (uint64_t)av2_tile_area_scaling_factor[tier][level_idx] * + MAX_TILE_SIZE_HEADER_RATE_PRODUCT / 4; + computed.picture_size_profile_factor = factors.picture_size_profile_factor; + computed.min_compression_basis = compression; + + 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, &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. + 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 = { 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_to_u64(&rational, scaled)) { + av2_dm_rational_destroy(&rational); + return false; + } + av2_dm_rational_destroy(&rational); + 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 = { 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, + 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)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); + 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 < updated.member) \ + updated.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, &updated.bit_rate, + &comparison)) { + av2_dm_level_limits_destroy(&multistream); + av2_dm_level_limits_destroy(&updated); + return false; + } + 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 (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; +} + +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.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; + } +} + +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 allocation_failure(Av2DecoderModel *model) { + model->result.allocation_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); +} + +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); + 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 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, + 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; + av2_dm_violation_init(&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 || + 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; + } +} + +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) { + lane_destroy(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; + } + 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; + } + } + 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) { + 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) || + !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; + } + } + 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 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) { + 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; + 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 = internal_calloc(1, sizeof(*model)); + if (model == NULL) return NULL; + 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; + 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 = { 0 }; + if (!parameter_inputs_valid(config)) { + missing_input(model); + } 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); + } + 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. + if (av2_dm_last_failure_was_allocation()) { + allocation_failure(model); + } else { + missing_input(model); + } + } + } + update_result_status(model); + update_storage_stats(model); + return model; +} + +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 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; + } + } + 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; + } + 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 || av2_dm_rational_is_zero(tick)) { + return false; + } + 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) { + 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, + 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 = 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) { + 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++]; + 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) { + if (!av2_dm_rational_copy(&model->latest_timed_tu_output_time, + output_time)) { + return false; + } + 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 && + !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 = { 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, + 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) { + 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) { + 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); + } + if (!event->non_rightmost_tile_width_valid) { + 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 < 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) { + 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) { + return av2_dm_rational_copy(removal, &lane->time); + } + bool found = false; + 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) { + continue; + } + if (!buffer->presentation_time_valid) { + missing_input(model); + goto cleanup; + } + if (!found) { + if (!av2_dm_rational_copy(&earliest, &buffer->presentation_time)) { + goto cleanup; + } + found = true; + } else { + bool less; + if (!rational_less(&buffer->presentation_time, &earliest, &less)) { + goto cleanup; + } + if (less && + !av2_dm_rational_copy(&earliest, &buffer->presentation_time)) { + goto cleanup; + } + } + } + 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; + 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; + 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; + if (!av2_dm_rational_copy(&buffer->decode_completion_time, &lane->time)) { + buffer_reset(buffer); + return false; + } + 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 = { 0 }; + if (!rational_from_product(event->frame_width, event->frame_height, + &product) || + !rational_to_u64(&product, &samples)) { + av2_dm_rational_destroy(&product); + return false; + } + 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 = { 0 }; + if (!rational_from_product(model->config.max_frame_width, + model->config.max_frame_height, &product) || + !rational_to_u64(&product, &samples)) { + av2_dm_rational_destroy(&product); + return false; + } + av2_dm_rational_destroy(&product); + } + *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 (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 = { 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( + model, AV2_DM_VIOLATION_FRAME_DECODE_RATE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, + &detail); + + 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 (!av2_dm_rational_make(dfg->num_tiles, 1, &observed)) { + arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); + 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); + + if (dfg->luma_samples > UINT64_MAX / limits->picture_size_profile_factor) { + arithmetic_failure(model); + CLEANUP_FRAME_PARSING_RATIONALS(); + 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); + 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; + } + 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( + model, AV2_DM_VIOLATION_MAX_COMPRESSED_SIZE, proving_event_index, + AV2_DM_VIOLATION_AFFECTED_DFG, dfg->event_index, &observed, &limit, + &detail); + + 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); + 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 = { 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_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 (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, + &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); + 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; + 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); + 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) { + 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) 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)) { + goto cleanup; + } + } + if (av2_dm_rational_is_zero(&dfg->limits.bit_rate) || + !av2_dm_rational_make(dfg->coded_bits, 1, &coded_bits)) { + 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, + 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) { + 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 = { 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, + const Av2DmDfgRecord *dfg) { + if (model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE || + (model->dfg_number != 1 && !dfg->first_dfg_of_cvs)) { + return; + } + 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, + 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, + 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->previous_dfg_valid || + (!dfg->first_dfg_of_cvs && + (model->config.mode != AV2_DM_DECODING_SCHEDULE_MODE || + !dfg->random_access_point))) { + return; + } + 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) || + !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; + 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); + 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 { + 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; + if (!av2_dm_rational_copy(&prepared->values[prepared->count], target)) { + return false; + } + ++prepared->count; + return true; +} + +static bool prepare_lane_rebase(Av2DecoderModel *model, Av2DmLane *lane, + bool primary, const Av2DmRational *origin, + Av2DmPreparedRebase *prepared) { + internal_allocation_failed = false; + uint64_t capacity = 2 + 2 * lane->pool.pool_size; + if (primary) { + 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 = + 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 < 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; + } + } + 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)) { + 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) { + 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(Av2DmPreparedRebase *prepared) { + for (uint32_t i = 0; i < prepared->count; ++i) { + av2_dm_rational_move(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 *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); + 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); +} + +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) { + return; + } + if (model->frame_number == UINT64_MAX) { + 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) { + Av2DmTuRecord *const previous_coded = find_tu(model, model->coded_tu); + if (previous_coded == NULL) { + arithmetic_failure(model); + return; + } + 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) { + 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); + 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->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++]; + 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; + 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; + 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->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 + ? 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; + } + 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)) { + 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_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, &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 = { 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; + if (!lane_start_decode(&model->resource_lane, &resource_removal, + &dfg->decode_time, event->generation, + &resource_buffer_index)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&resource_removal); + 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); + av2_dm_rational_destroy(&resource_removal); + 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); + } + 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; + 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; + 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 = + &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; + 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) { + 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, dfg); + check_delay_consistency(model, dfg); + + if (model->dfg_number == 1 || event->random_access_point) { + 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; + } + 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) { + 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) { + 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; + } + 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 = 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; + } + } + return true; +} + +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; + } + 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, + closed_loop_key)) { + arithmetic_failure(model); + } + model_event_complete(model); + end_model_transaction(model, &transaction); +} + +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 = { 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) { + 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; + 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; +} + +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 = { 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; +} + +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 || + (!end_of_bitstream && av2_dm_buffer_pool_frames_in_use(&lane->pool) < + model->config.initial_display_delay)) { + return true; + } + 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 < 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; +} + +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) { + return; + } + if (!set_lane_initial_presentation_delay(model, &model->lane, true, + end_of_bitstream, event_index) || + !set_lane_initial_presentation_delay(model, &model->resource_lane, false, + end_of_bitstream, event_index)) { + arithmetic_failure(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 (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 (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, 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, + 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; + } + 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) { + 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. + 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)) { + 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 = { 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; + if (!av2_dm_rational_compare(&tu->output_time, &previous->output_time, + &ordering)) { + arithmetic_failure(model); + return; + } + output_time_regressed = ordering <= 0; + 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); + 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; + 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) { + 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) { + 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; + } + if (!av2_dm_rational_copy(&free_anchor->presentation_offset, offset)) { + arithmetic_failure(model); + return; + } + free_anchor->valid = true; + free_anchor->rap_epoch = rap_epoch; +} + +static bool calculate_presentation_offset(Av2DecoderModel *model, + const Av2DmOutputEvent *event, + const Av2DmBuffer *buffer, + Av2DmRational *offset) { + 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) { + return av2_dm_rational_copy(offset, &model->last_presentation_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 = { 0 }; + 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) { + 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. + 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 = { 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, + 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]; +} + +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) { + 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 = { 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 + ? 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 = { 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; + } + } + 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; + } + + 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; + } + if (!av2_dm_buffer_pool_add_player_ref(&model->resource_lane.pool, + (uint32_t)resource_buffer_index)) { + arithmetic_failure(model); + CLEANUP_OUTPUT_RATIONALS(); + 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)) { + CLEANUP_OUTPUT_RATIONALS(); + return; + } + } + model->previous_output_decode_order = buffer->decode_order; + model->previous_output_order_valid = true; + } + 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_valid = true; + if (model->lane.initial_presentation_delay_known) { + 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) { + 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) { + CLEANUP_OUTPUT_RATIONALS(); + 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); + 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 = { 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]; + int before_first; + int removal_order; + if (!av2_dm_rational_compare(time, &dfg->first_arrival, &before_first) || + !av2_dm_rational_compare(time, &dfg->removal, &removal_order)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&fullness); + return; + } + 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, &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 (!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, buffer_size, &overflow)) { + arithmetic_failure(model); + av2_dm_rational_destroy(&fullness); + 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, + buffer_size, NULL); + } + av2_dm_rational_destroy(&fullness); +} + +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) { + 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)) { + return; + } + for (uint32_t i = 0; i < model->dfg_count; ++i) { + Av2DmDfgRecord *const breakpoint = &model->dfgs[i]; + 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); + } + } +} + +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_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->chroma_format_idc == b->chroma_format_idc && + a->bit_depth == b->bit_depth; +} + +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 || + !parameter_inputs_valid(config)) { + return AV2_DM_PARAMETER_UPDATE_MISSING_REQUIRED_INPUT; + } + 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; + } + 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; +} + +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 (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 = { 0 }; + if (!resolve_parameters(config, ¶meters)) { + 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) { + // 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; + } + 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; + } + + 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)); + 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 (!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) { + // 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; + } + 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 = { 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; + 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); + } + 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 proving_event_index) { + 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) { + 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); + } + 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) { + 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); + } + av2_dm_rational_destroy(&observed); + av2_dm_rational_destroy(&limit); +} + +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 = { 0 }; + Av2DmRational limit = { 0 }; + if (!rational_from_product(model->maximum_tile_area, + model->retired_max_frame_headers, &observed) || + !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) { + 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); + } + 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 = internal_malloc(allocation_size); + if (source == NULL) return false; + uint32_t *destination = internal_malloc(allocation_size); + if (destination == NULL) { + avm_free(source); + 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) { + const Av2DmTuRecord *const first_tu = &model->tus[source[first]]; + const Av2DmTuRecord *const second_tu = &model->tus[source[second]]; + int 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; + } + 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 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 = { 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) { + 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); + 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; + ++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, + 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 < 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 || + tu->maximum_tile_area_finalized) { + 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; + model->retired_header_limit = tu->limits.max_tile_size_header_rate_product; + } +} + +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); + tu_record_destroy(tu); + continue; + } + if (write_index != i) tu_record_move(&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); + tu_record_destroy(tu); + continue; + } + 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) { + if (!av2_dm_rational_copy(&model->latest_timed_tu_output_time, + ¤t->output_time)) { + arithmetic_failure(model); + } else { + 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 = { 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; + 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); + av2_dm_rational_destroy(&frontier); + return; + } + retire = comparison < 0; + } + } + if (retire) { + remember_retired_tu(model, tu); + tu_record_destroy(tu); + continue; + } + 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 (!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].still_picture && model->tus[i].frame_headers != 0 && + !model->tus[i].output_time_valid) { + incomplete_verification(model); + return; + } + } + } + Av2DmRational one_second = { 0 }; + if (!av2_dm_rational_make(1, 1, &one_second)) { + arithmetic_failure(model); + return; + } + 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, 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); +} + +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) { + 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->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) { + 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); + } + } + 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) { + 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. + 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) { + check_smoothing_buffer_overflow(model, NULL, + 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); +} + +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; + *result = model->result; + 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; + Av2DmState temporary; + av2_dm_state_init(&temporary); + temporary.initial_presentation_delay_known = + model->lane.initial_presentation_delay_known; + 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; + 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) { + temporary.last_presentation_valid = model->last_presentation_valid; + temporary.last_presentation_offset_valid = + model->last_presentation_offset_valid; + 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; + 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) 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; +} + +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..33510df6f0 --- /dev/null +++ b/av2/common/decoder_model.h @@ -0,0 +1,532 @@ +/* + * 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. +// 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, + 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_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; + 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; + bool equal_picture_interval; + uint32_t ticks_per_picture; + Av2DmRational disp_ct; +} 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]; + 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, + 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; + +void av2_dm_level_limits_init(Av2DmLevelLimits *limits); + +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 chroma_format_idc; + uint32_t bit_depth; + 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; + +// 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; + 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; + +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; + Av2DmMode mode; + Av2DmScope scope; + uint64_t decoded_frames; + uint64_t output_frames; + uint64_t reordered_outputs; + uint64_t violations; + bool allocation_failed; + 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; + +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. +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); + +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_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, + 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); +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, + 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_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); +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, + 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..d42290fa9a 100644 --- a/av2/common/level.c +++ b/av2/common/level.c @@ -10,12 +10,20 @@ * 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" +static uint32_t encoder_ref_valid_mask(const AV2_COMMON *cm); + /* clang-format off */ #define UNDEFINED_LEVEL \ { .level = SEQ_LEVEL_MAX, \ @@ -430,6 +438,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 +504,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 +534,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 +548,114 @@ 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 = { 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_limbs_to_long_double(const uint64_t *limbs, + uint32_t limb_count) { + long double result = 0.0L; + for (uint32_t i = limb_count; i > 0; --i) { + result = ldexpl(result, 64) + limbs[i - 1]; + } + 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; + 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_limbs_to_long_double(denominator_limbs, denominator_count); + if (!(denominator > 0.0L)) return false; + *result = unsigned_limbs_to_long_double(magnitude_limbs, magnitude_count) / + denominator; + return is_finite_number(*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 is_finite_number(*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 = { 0 }; + double result; + 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, @@ -577,6 +678,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 (!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; + } + 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 = { 0 }; + int comparison; + 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, + uint64_t coded_bits, + double available_duration, + bool *fits) { + if (decoder_model == NULL || fits == NULL || + !is_finite_number(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 (!is_finite_number(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 || + !is_finite_number(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 (!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 (!is_finite_number(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; + 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); + memset(decoder_model, 0, sizeof(*decoder_model)); +} + +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,14 +889,14 @@ 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) { - 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; } } @@ -608,24 +912,235 @@ 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_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 < 1 || cm->seq_params.ref_frames > REF_FRAMES || + cm->seq_params.ref_frames != decoder_model->num_ref_frames) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + 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) { + 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; +} + +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->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); + } + } +} + +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 +1164,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 +1180,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 +1190,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 +1217,38 @@ 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 = +// 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; - // 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; + } + 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; + } } } #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; } @@ -729,10 +1258,13 @@ double time_next_buffer_is_free(const DECODER_MODEL *const decoder_model) { 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; } @@ -740,31 +1272,184 @@ 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); + } +} + +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 %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 +1459,71 @@ 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(); + 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; + 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; + 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; + 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 = + 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 = { 0 }; + 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; + 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) || + !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 +1540,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 +1567,41 @@ 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) || + !is_finite_number(decoder_model->decode_rate)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + decoder_model->exact_model = create_exact_model(cpi, decoder_model); + decoder_model->initialized = true; } int av2_get_max_level_ref_frames(const AV2_COMMON *const cm, OBU_TYPE obu_type, @@ -863,136 +1627,831 @@ int av2_get_max_level_ref_frames(const AV2_COMMON *const cm, OBU_TYPE obu_type, return max_level_ref_frames; } -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) { - if (!decoder_model || decoder_model->status != DECODER_MODEL_OK) return; +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; +} - avm_clear_system_state(); +// 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. 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 *limits, uint32_t scale_numerator, + uint32_t scale_denominator) { + 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 (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 = + max_decode_rate * frame->luma_sample_count / + frame_parsing_time_decode_luma_samples; + if (!is_finite_number(max_decode_rate) || + !is_finite_number(observed_decode_rate)) { + 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; + + 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)) { + goto cleanup; + } + int comparison; + if (!av2_dm_rational_compare(&dynamic_tile_limit, &one, &comparison)) { + goto cleanup; + } + if (comparison < 0 && !av2_dm_rational_copy(&dynamic_tile_limit, &one)) { + goto cleanup; + } + if (!av2_dm_rational_compare(&dynamic_tile_limit, &max_tile_limit, + &comparison)) { + goto cleanup; + } + if (comparison > 0 && + !av2_dm_rational_copy(&dynamic_tile_limit, &max_tile_limit)) { + goto cleanup; + } + if (!av2_dm_rational_make(frame->num_tiles, 1, &observed) || + !encoder_dm_rational_less_than_or_equal(&observed, &dynamic_tile_limit, + &satisfies)) { + goto cleanup; + } + decoder_model->max_tile_rate_satisfy &= satisfies; - 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 (frame->luma_sample_count > + UINT64_MAX / limits->picture_size_profile_factor) { + goto cleanup; + } + picture_units = + frame->luma_sample_count * limits->picture_size_profile_factor >> 3; + 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)) { + goto cleanup; + } + if (!av2_dm_rational_copy(&limit, comparison <= 0 ? &picture_compressed_limit + : &rate_compressed_limit)) { + goto cleanup; + } + 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)) { + goto cleanup; + } + decoder_model->compressed_size_satisfy &= satisfies; + } - int luma_pic_size; + 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)) { + goto cleanup; + } + decoder_model->frame_symbol_count_satisfy &= satisfies; + 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; +} - const FRAME_TYPE frame_type = cm->current_frame.frame_type; +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) || !is_finite_number(frame_parsing_time)) { + if (decoder_model != NULL && decoder_model->status == DECODER_MODEL_OK) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + } + return false; + } - if (frame_type == KEY_FRAME || frame_type == INTRA_ONLY_FRAME) { - luma_pic_size = cm->width * 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; + 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, + limits, decoder_model->multistream_scale_numerator, + decoder_model->multistream_scale_denominator)) { + 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 (!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; + } + 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 (!is_finite_number(dynamic_tile_limit) || !is_finite_number(tile_limit)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } + decoder_model->max_tile_rate_satisfy &= + (long double)frame->num_tiles <= tile_limit; - const int show_existing_frame = cm->show_existing_frame; - ++decoder_model->num_frame; - if (!show_existing_frame) ++decoder_model->num_decoded_frame; // DfgNum - decoder_model->coded_bits += coded_bits; + 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 (!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; + } + 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 (!is_finite_number(symbol_factor) || !is_finite_number(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; +} - if (!show_existing_frame) { - const double removal_time = get_removal_time(decoder_model); - if (removal_time < 0.0) { - decoder_model->status = DECODE_FRAME_BUF_UNAVAILABLE; - return; +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 || + !is_finite_number(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->applicable_dfg_count == UINT64_MAX) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return false; + } - 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); - // 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 latest_arrival_time = removal_time - buffer_delay; - decoder_model->first_bit_arrival_time = - AVMMAX(decoder_model->last_bit_arrival_time, latest_arrival_time); - decoder_model->last_bit_arrival_time = - decoder_model->first_bit_arrival_time + - (double)decoder_model->coded_bits / decoder_model->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) { - decoder_model->status = SMOOTHING_BUFFER_UNDERFLOW; - return; + 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; } - // Reset the coded bits for the next DFG. - decoder_model->coded_bits = 0; + 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; + } - // 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); + decoder_model->pending_frame = *current_frame; + ++decoder_model->applicable_dfg_count; + 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->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; + } + (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 || !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 (!is_finite_number(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; + const BITSTREAM_PROFILE profile = decoder_model->configuration_snapshot_valid + ? decoder_model->configured_profile + : seq_params->seq_profile_idc; + Av2DmLevelLimits limits = { 0 }; + if (!av2_dm_get_level_limits(decoder_model->level, decoder_model->tier, + profile, &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, profile, scale_numerator, + scale_denominator, &limits))) { + av2_dm_level_limits_destroy(&limits); + return false; + } + + 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) { + 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) { + 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) { + av2_dm_level_limits_destroy(&limits); + 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); + 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, + 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->output_tu_count == 1) 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 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_mlayer_id == + replacement->configured_max_mlayer_id && + current->configured_number_xlayers == + replacement->configured_number_xlayers; +} + +static bool update_decoder_model_parameters_at_clk( + const AV2_COMP *const cpi, DECODER_MODEL *const decoder_model) { + 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 = + replacement.multistream_scale_numerator; + decoder_model->multistream_scale_denominator = + replacement.multistream_scale_denominator; + decoder_model->decode_rate = replacement.decode_rate; + 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->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 == NULL) return; + if (decoder_model->status != DECODER_MODEL_OK) { + av2_decoder_model_mark_incomplete(decoder_model->exact_model); + return; + } + + avm_clear_system_state(); + + const AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + const bool closed_loop_key = + cm->current_frame.cm_obu_type == OBU_CLOSED_LOOP_KEY && + cpi->dm_starts_temporal_unit; + 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) { + 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; + } + + 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 = (uint64_t)cm->width * (uint32_t)cm->height; + } else { + 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 + 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; + } + 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; + 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; } + + decoder_model->removal_time = removal_time; + decoder_model->decode_samples = luma_pic_size; + 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 = + (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); + 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)closed_dfg_bits / bit_rate; + // Smoothing buffer underflows if the last bit arrives after the removal + // time. + 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 && + !exact_model_is_conformant(decoder_model)) { + decoder_model->status = SMOOTHING_BUFFER_UNDERFLOW; + return; + } + // Check if the smoothing buffer overflows. + 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->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) { - decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; + while (queue->size > 0 && + queue->buf[queue->head].removal_time <= last_bit_arrival_time) { + 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; 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) { - decoder_model->status = SMOOTHING_BUFFER_OVERFLOW; + 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; } + 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); @@ -1001,14 +2460,26 @@ 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) 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; const int show_existing_frame = cm->show_existing_frame; @@ -1017,29 +2488,45 @@ 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 (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) - 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); - this_buffer->presentation_time = - get_presentation_time(decoder_model, this_buffer->display_index); - } - } + if (!update_ref_buffers(cm, decoder_model, + current_frame->refresh_frame_flags)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; } + + // Display can begin after the required number of frames have been + // buffered. + decoder_model_set_initial_presentation_delay(decoder_model, false, + 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; @@ -1058,97 +2545,278 @@ 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]); } } } -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]]; + } + 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 ((!anchor->valid || + !rap_epoch_is_live(decoder_model, anchor->rap_epoch)) && + free_anchor == NULL) { + free_anchor = anchor; + } + } + 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 is_finite_number(*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 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, int ref_idx, + 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; + } + observe_exact_model_output(cpi, decoder_model, ref_idx, buffer_index, + presentation); + 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_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; + 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; + 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; } - if (output_frame_ptr->mlayer_id <= decoder_model->last_output_mlayer) { - decoder_model->last_display_index++; - } + ++decoder_model->output_tu_count; } - decoder_model->last_output_mlayer = output_frame_ptr->mlayer_id; - decoder_model->last_output_xlayer = output_frame_ptr->xlayer_id; + 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); - this_buffer->presentation_time = presentation_time; + double presentation_time = presentation_offset; if (decoder_model->initial_presentation_delay >= 0.0) { + presentation_time += decoder_model->initial_presentation_delay; + } + if (!is_finite_number(presentation_time)) { + decoder_model->status = DECODER_MODEL_INTERNAL_ERROR; + return; + } + this_buffer->presentation_time = presentation_time; + 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; 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 +2826,105 @@ 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_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_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) { + 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, i, 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 +2938,288 @@ 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) { + 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 = + &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; + 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; + 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_ref_idx = generation_ref_idx; + trigger_buffer_index = decoder_model->vbi[generation_ref_idx]; + } + decoder_model_observe_output_frame_buffers( + cpi, decoder_model, trigger_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) { + 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. + 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) { + 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]; + 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 (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) { + 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_ref_idx = i; + 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_ref_idx, + 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; + } + 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) || + 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].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); } } } @@ -1196,26 +3234,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; - 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; + av2_encoder_decoder_models_destroy(this_level_info); + 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; @@ -1236,6 +3294,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) { @@ -1257,8 +3327,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 +3351,32 @@ 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) { + 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; } + 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; @@ -1322,7 +3429,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; } @@ -1365,14 +3473,13 @@ static TARGET_LEVEL_FAIL_ID check_level_constraints( fail_id = TILE_WIDTH_INVALID; break; } - if (!is_still_picture) { + 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; 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 +3489,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 +3515,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) || + !is_finite_number(level_stats->total_time_encoded) || + !is_finite_number(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); @@ -1419,7 +3532,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; @@ -1446,9 +3559,55 @@ 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; } +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 +3742,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 +3793,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; + 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; + 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 +3822,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 +3864,21 @@ 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) { + 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, frame_compressed_bytes, + max_tile_size, max_tile_width, tile_width_is_valid, + &decoder_models[level]); + } } } } @@ -1726,8 +3899,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..2e035aec01 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,80 @@ 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; + 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; + 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; + 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; 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. + + // 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; + 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 +250,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 +273,19 @@ 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; + uint64_t applicable_dfg_count; + 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; + uint64_t output_tu_count; + bool finalized; // Tracks whether every inter-TU presentation interval satisfies the minimum // required by the spec (§E.3.2). @@ -217,12 +329,27 @@ 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_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, + 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, @@ -234,15 +361,77 @@ 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); + +// 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 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); 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..ac0089853e 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_reference_invalidation(pbi, false); + } + 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( @@ -8298,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] = @@ -9548,6 +9566,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 +9863,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..92dfd5b43a 100644 --- a/av2/decoder/decoder.h +++ b/av2/decoder/decoder.h @@ -411,6 +411,11 @@ 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; + int decoder_model_check_every_rap; + 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..906ede3aaf --- /dev/null +++ b/av2/decoder/decoder_model.c @@ -0,0 +1,4328 @@ +/* + * 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_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_REFERENCE_INVALIDATION, + AV2_DM_CONTEXT_REFERENCE_UPDATE, + AV2_DM_CONTEXT_OUTPUT, + AV2_DM_CONTEXT_RECOVERY_RESET, + AV2_DM_CONTEXT_END_OF_INPUT +} Av2DmContextEventType; + +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; + bool closed_loop_key_invalidation; + 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; + +static void update_live_run_parameters(Av2DecoderModelVerifier *verifier, + Av2DmLiveRun *run, + const Av2DmContextEvent *event); + +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; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; +} Av2DmContext; + +typedef struct Av2DmCvsAggregate { + 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; + Av2DmIndeterminateReason reason; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; +} 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; + bool check_every_rap; + bool rap_coverage_warning_emitted; + bool defer_nonterminal_checks_for_testing; + 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 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; + 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; + 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; + 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; + 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); +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 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); +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 < num_refs; ++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)); + 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; + event->parameter_generation = verifier->parameter_generation; + 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; +} + +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_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); +} + +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; + config->defer_nonterminal_checks_for_testing = + verifier->defer_nonterminal_checks_for_testing; + + 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; + 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; + 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)) { + if (av2_dm_last_failure_was_allocation()) return false; + 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) { + 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; + for (int i = 0; i < MAX_NUM_XLAYERS; ++i) { + pbi->decoder_model_verifier->current_cvs[i] = SIZE_MAX; + } + } +} + +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]); + 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); + avm_free(verifier->brt_records); + 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; + 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) { + clear_context_prefix_events(&verifier->contexts[i]); + } +} + +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)) { + 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); + 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; + } + 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; + 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); + } + } +} + +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); + 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 && + 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 || + !config_equal(&context->last_config, &model_event->config); + 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); + destroy_context_event(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) { + clear_context_prefix_events(&verifier->contexts[i]); + } + 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_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_REFERENCE_INVALIDATION); + if (adapter_event == NULL) return; + adapter_event->value = ref_valid_mask; + 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( + context, pbi->common.xlayer_id, + pbi->common.mlayer_id, pbi->common.tlayer_id)) { + continue; + } + Av2DmContextEvent 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); + destroy_context_event(event); + } + (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( + 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); + destroy_context_event(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); + destroy_context_event(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); + destroy_context_event(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; + 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, + sizeof(verifier->active_configuration_present)); + } +} + +typedef struct Av2DmRunReport { + Av2DecoderModelVerifier *verifier; + size_t originating_cvs; + 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; + uint64_t stream_generation; + Av2DmIndeterminateReason reason; + size_t originating_cvs; + 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; + } + } +} + +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"; + 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_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"; +} + +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 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 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 (uint32_t i = limb_count - 1; i > 0; --i) { + written = snprintf(text + offset, text_size - offset, "%016" PRIx64, + limbs[i - 1]); + 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; + } + const uint64_t *denominator_limbs; + uint32_t denominator_count; + if (!av2_dm_rational_get_component(value, true, &denominator_limbs, + &denominator_count)) { + return false; + } + 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); + } +} + +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; + 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 (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)denominator_limbs[i - 1]; + } + 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) { + 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; +} + +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 = { 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, + &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); + av2_dm_rational_destroy(&decoder_delay); + av2_dm_rational_destroy(&excess); + if (!appended) 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 = { 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) { + 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_FORMAT_RESULT(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, + &violation->observed)) || + (violation->limit_present && + !append_milliseconds(&builder, descriptor->limit_name, + &violation->limit)) || + (margin_present && + !append_milliseconds(&builder, descriptor->margin_name, &margin))) { + RETURN_FORMAT_RESULT(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 = { 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)) { + av2_dm_rational_destroy(&interval); + RETURN_FORMAT_RESULT(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")) { + 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) { + 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_FORMAT_RESULT(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_FORMAT_RESULT(false); + } + } + append_detail(&builder, " spec=%s", descriptor->spec); + 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, + 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(const Av2DmContextEvent *event) { + switch (event->type) { + case AV2_DM_CONTEXT_FRAME: return "frame"; + 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"; +} + +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), 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; + } + 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="); + 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"); +} + +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 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; + 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, + const Av2DmResult *model_result, int64_t rap, + Av2DmIndeterminateReason reason, + const Av2DmRunReport *report) { + Av2DmResult result = *model_result; + 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 = 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, + 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->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) { + 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_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, false, + event->event_index); + } + break; + case AV2_DM_CONTEXT_OUTPUT: { + Av2DmOutputEvent output = event->output; + av2_decoder_model_output_frame(run->model, &output); + 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) { + 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); + } + } + av2_dm_state_destroy(&state); + 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 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); + return NULL; + } + Av2DmLiveRun *const run = avm_calloc(1, sizeof(*run)); + if (run == NULL) { + mark_allocation_failed(verifier); + return NULL; + } + 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) { + 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->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 = cvs->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 = { 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; + } + 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; + } + ++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) { + 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); + } + 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; +} + +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); + } + 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; +} + +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; +} + +static bool prefix_event_applies(const Av2DmLiveRun *run, + const Av2DmContextEvent *event) { + if (run->olk) { + 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 && + run_seed_contains_generation(run, event->generation); + } + return true; +} + +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 = + 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, + 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) { + Av2DmResult result; + const bool have_result = av2_decoder_model_get_result(run->model, &result); + const bool non_conformant = + have_result && result.status == AV2_DM_RESULT_NON_CONFORMANT; + if (!non_conformant && run->reason == AV2_DM_REASON_NONE) { + 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; + } + 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; + 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 = { 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); + } +} + +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_INCOMPATIBLE_CONFIGURATION_TRANSITION; + } + } +} + +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) { + 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; + } + 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; + } + + 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 || + !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(verifier, 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_for_incompatible_configuration(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; + 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]); + } + } + created_segment = true; + } + if (!created_segment && event->frame.random_access_point) { + 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 (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; +} + +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; + } + } + + 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]; + 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); + destroy_context_event(&storage); + } + } +} + +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; + 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); + 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); + 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 = 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", + 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; +} + +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; + clear_context_prefix_events(context); + av2_dm_config_destroy(&context->last_config); + 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) { + finish_all_cvs_internal(verifier, false); +} + +static void finish_all_cvs_partial(Av2DecoderModelVerifier *verifier) { + finish_all_cvs_internal(verifier, 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, + 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); + 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; +} + +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) { + const size_t current = verifier->current_cvs[i]; + if (current < verifier->cvs_count && verifier->cvs[current].input_open) { + *xlayer_id = i; + *cvs = verifier->cvs[current].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; + } +} + +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; + 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; + updated.failed = true; + updated.result_count = + pbi->decoder_model_verifier_allocation_reported ? 1 : 0; + 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; + 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; + 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; + target->last_output_presentation_frame_unit = + verifier->last_output_presentation_frame_unit; + target->last_output_presentation_temporal_unit = + verifier->last_output_presentation_temporal_unit; + target->last_output_generation = verifier->last_output_generation; + target->last_output_presentation_xlayer_id = + verifier->last_output_presentation_xlayer_id; + target->last_output_presentation_mlayer_id = + verifier->last_output_presentation_mlayer_id; + target->last_output_presentation_tlayer_id = + verifier->last_output_presentation_tlayer_id; + target->last_output_uses_current_presentation = + verifier->last_output_uses_current_presentation; + target->replay_previous_presentation_offset_valid = + verifier->replay_previous_presentation_offset_valid; + target->replay_last_presentation_offset_valid = + verifier->replay_last_presentation_offset_valid; + 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]; + target->non_conformant_results = + verifier->result_status_count[AV2_DM_RESULT_NON_CONFORMANT]; + target->indeterminate_results = + verifier->result_status_count[AV2_DM_RESULT_INDETERMINATE]; + 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(&target->live_runs, + verifier->contexts[i].run_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 && target->open_cvs != UINT32_MAX) { + ++target->open_cvs; + } + } + add_size_to_saturated_u32(&target->parameter_records, + verifier->sequence_record_count); + add_size_to_saturated_u32(&target->parameter_records, + verifier->ops_record_count); + add_size_to_saturated_u32(&target->parameter_records, + verifier->brt_record_count); + add_size_to_saturated_u32(&target->parameter_records, + verifier->active_record_count); + av2_decoder_model_verifier_stats_destroy(stats); + *stats = updated; + 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; + 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; +} + +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 = { 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; + 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 new file mode 100644 index 0000000000..98d5216d8c --- /dev/null +++ b/av2/decoder/decoder_model.h @@ -0,0 +1,228 @@ +/* + * 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 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, + 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; + uint64_t temporal_unit_index; + 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; + 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; + 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 cvs_aggregates; + uint32_t open_cvs; + 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; + 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; + uint64_t applicable_rap_starts; + uint64_t rap_runs_started; + uint64_t rap_runs_skipped; +} 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; + Av2DmResultStatus status; + 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); + +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_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); + +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_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, + 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_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); + +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); +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( + 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..0a03c75703 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++) { @@ -490,7 +513,18 @@ 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 + // 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 +752,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 +827,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 +1124,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 +2412,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. @@ -2395,7 +2442,20 @@ 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 + // 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 +2578,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 +2594,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 +3169,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..4707cd11d7 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; @@ -1345,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 e0d0376d4d..52f8fe85c3 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 * @@ -4636,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; @@ -4646,9 +4663,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 @@ -4679,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; @@ -4710,9 +4729,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 +4774,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 @@ -4944,21 +4965,27 @@ 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_init_level_info(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_prepare_level_info_for_new_cvs(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 +5137,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 +5472,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 +5505,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..a8c0c2076d 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,22 @@ 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, + + /*!\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 */ @@ -510,6 +536,13 @@ 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(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/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/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/brt_test.cc b/test/brt_test.cc index d604b2a811..a4b0066662 100644 --- a/test/brt_test.cc +++ b/test/brt_test.cc @@ -16,15 +16,19 @@ #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" } #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; @@ -59,6 +63,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 +80,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..6cb3373fd8 --- /dev/null +++ b/test/decoder_model_integration_test.cc @@ -0,0 +1,3483 @@ +/* + * 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/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 { + 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_)); + pbi_->decoder_model_check_every_rap = 1; + 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 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) { + 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); + } + 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 (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, 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; + 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_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); + 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.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); + + 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, 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, + 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]; + 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_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) { + 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_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, 2u); + EXPECT_EQ(stats.cvs_aggregates, 3u); + EXPECT_EQ(stats.open_cvs, 2u); +} + +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); + EXPECT_EQ(diagnostics.find("code=DECODER_BUFFER_DELAY_INCONSISTENT"), + 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, + 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_); + 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=NON_CONFORMANT xlayer=0 cvs=1"), + std::string::npos); + 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 "), + 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, + 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=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"), + 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=1"), + 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=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, ConsecutiveCvsRetainRequiredLiveRuns) { + 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, 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(); + + 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, + 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; + 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, + 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); + } + } + + 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, DecCtClkTransitionKeepsContinuousRunVerifiable) { + 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_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); + + 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=0 mode=resource decoded=2 outputs=2 " + "reordered_outputs=0 violations=0 " + "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"), + 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, + 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); + 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.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); +} + +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; + 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=incompatible_configuration_transition"), + 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, + "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 = {}; + 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); +} + +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 { + 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_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 new file mode 100644 index 0000000000..f2a27f30e6 --- /dev/null +++ b/test/decoder_model_parser_test.cc @@ -0,0 +1,767 @@ +/* + * 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" +#include "test/decoder_model_lifecycle.h" + +namespace { + +using Av2DmVerifierStats = libavm_test::ScopedDmVerifierStats; + +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..a6da64f938 --- /dev/null +++ b/test/decoder_model_test.cc @@ -0,0 +1,4755 @@ +/* + * 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" +#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) { + 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, 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{}; + 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] }; + + 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{}; + 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 < 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); + 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, AV2_DM_MAX_BUFFER_POOL_SIZE)); + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); +} + +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; + pool.buffers[17].generation = 17; + + pool.num_ref_frames = 8; + pool.pool_size = 10; + EXPECT_EQ(av2_dm_buffer_pool_frames_in_use(&pool), 0u); + EXPECT_FALSE(av2_dm_buffer_pool_set_vbi(&pool, 15, 0)); + 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; +}; + +void CollectViolation(void *opaque, const Av2DmViolation *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, + 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 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()); + 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.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; + 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, + 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; + 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_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, ClkInvalidationClearsActiveVbiSlots) { + Av2DmConfig config = MakeModelConfig(AV2_DM_RESOURCE_AVAILABILITY_MODE); + 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 << 3), (1u << 0) | (1u << 3)); + 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[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 < 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); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelProcessTest, + FrameStartDoesNotPerformUnspecifiedReferenceInvalidation) { + 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); + 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)); + const int32_t invalidated_buffer = before.buffer_pool.vbi[7]; + ASSERT_GE(invalidated_buffer, 0); + + 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], 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); +} + +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(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; + 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, false, 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, false, 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, + IncompatibleBoundaryResolvesDelayBeforeStartingNewPool) { + 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)); + 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_EQ(av2_dm_buffer_pool_frames_in_use(&state.buffer_pool), 0u); + EXPECT_TRUE(state.last_presentation_valid); + + 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); +} + +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, false, 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, false, 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, false, 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, false, 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, false, 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, false, 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, false, 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, false, 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, false, 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, false, 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, 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); + } + 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, false, 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, false, 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, false, 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, false, 0); + 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)); + 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, false, 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, false, 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, false, 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, false, 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, false, 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, false, 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, + 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); + + 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); + 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_FRAME_TILE_RATE)); + av2_decoder_model_finish(model); + + EXPECT_FALSE(HasViolation(collector, AV2_DM_VIOLATION_FRAME_DECODE_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_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); + config.initial_display_delay = 1; + config.level_limits.max_display_rate = 100000; + 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; + first_output.output_luma_samples = 1; + av2_decoder_model_output_frame(model, &first_output); + + 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); + 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); + + 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_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_NON_CONFORMANT); + EXPECT_FALSE(result.missing_required_input); + 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, false, 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, false, 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, 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); + 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, false, 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, false, 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, false, 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, 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; + 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.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, + &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, 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) }) { + 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, 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); + 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, + 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); + 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, + 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)); + 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), + 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), + 1u); + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, + 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)); + 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, true)); + 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, 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, + NewCvsUsesDecoderDelayThenBothDelaysForArrival) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + 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); + + Av2DmConfig replacement = config; + replacement.sequence_decoder_buffer_delay = 18000; + replacement.sequence_encoder_buffer_delay = 27000; + ASSERT_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{}; + 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; + 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, + 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); + 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, true)); + + 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, 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{}; + 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; + 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, 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); + Av2DmFrameEvent first = MakeFrame(0, 1); + av2_decoder_model_start_frame(model, &first); + + Av2DmConfig replacement = config; + replacement.time_scale += 1; + 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_NE(result.status, AV2_DM_RESULT_INDETERMINATE); + EXPECT_FALSE(result.missing_required_input); + av2_decoder_model_destroy(model); +} + +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; + + 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_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); + 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); +} + +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); + 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)); + 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, + NumRefFramesDecreaseAndIncreaseDoNotResurrectOldState) { + 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 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_NE(state.buffer_pool.vbi[15], -1); + + Av2DmConfig reduced = config; + reduced.num_ref_frames = 8; + 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), 0u); + + 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); + } + av2_decoder_model_destroy(model); +} + +TEST(DecoderModelConformanceTest, ReducedPoolDiagnosticsUseOnlyTheActiveRange) { + Av2DmConfig config = MakeModelConfig(AV2_DM_DECODING_SCHEDULE_MODE); + config.num_ref_frames = 8; + config.explicit_num_ref_frames = true; + 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 < 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); + 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); + + Av2DmFrameEvent blocked = MakeFrame(202, 1000, 0); + 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), 10u); + 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, false, 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, false, 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, false, 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..9c5df07cd6 --- /dev/null +++ b/test/encoder_decoder_model_test.cc @@ -0,0 +1,1849 @@ +/* + * 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" +#include "test/decoder_model_lifecycle.h" + +namespace { + +using Av2DmState = libavm_test::ScopedDmState; + +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 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; +}; + +void CollectViolation(void *opaque, const Av2DmViolation *violation) { + static_cast(opaque)->violations.emplace_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, false, 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, + 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 = 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 = 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; + 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 = multistream_scale; + cpi_->level_params.frame_header_count = 1; + cpi_->tier[0] = tier; + + 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, + 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; + 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 = + 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 = implicit_output_eligible; + 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_, false, + 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 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 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()); + } + if (CommonHasParameters()) av2_decoder_model_finish(common_); + } + + 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); + cpi_->common.ref_frame_map[ref_index] = nullptr; + } + 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(EncoderDecoderModelTest, DoesNotReleaseInactiveBackingBuffer) { + 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(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) { + 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(24, 1, 0, true); + DECODER_MODEL *const model = adapter.legacy(); + ASSERT_NE(model, nullptr); + + adapter.BeginNewCvs(8); + 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(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, ClkClockChangeRetainsContinuousModelState) { + ResourceAvailabilityDifferentialAdapter adapter( + ResourceAdapterMode::kLegacyOnly); + ASSERT_TRUE(adapter.valid()); + 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_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) { + 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); + 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, + 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, + SingleDfgFallbackAndSingleTuNonApplicabilityAgree) { + 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); + 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); +} + +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, + 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..7577868673 100644 --- a/test/level_test.cc +++ b/test/level_test.cc @@ -9,19 +9,2199 @@ * 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" +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) { + 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 = {}; + av2_dm_level_limits_init(&model.level_limits); + 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_picture_size = 1000; + 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; + uint64_t output_tu_count_before_transition; + uint64_t output_tu_count; + bool last_display_duration_valid; + double last_display_duration; +}; + +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 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); + 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, + 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; +} + +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); + av2_encoder_decoder_model_destroy(&decoder_model); +} + +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); + av2_encoder_decoder_model_destroy(&decoder_model); +} + +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, 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)); + 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, OlkInvalidationClearsOnlyInvalidActiveSlots) { + 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; + 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; + model.frame_buffer_pool[2].presentation.valid = true; + model.frame_buffer_pool[2].presentation.generation = 9; + + 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_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 ExpectClkInvalidationClearsActiveVbiSlots(int num_ref_frames) { + AV2_COMMON cm = {}; + RefCntBuffer active_reference = {}; + 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, + sizeof(ref_frame_map_before)); + + DECODER_MODEL model = {}; + model.status = DECODER_MODEL_OK; + 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 < num_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, ClkInvalidationUsesActiveVbiRange) { + ExpectClkInvalidationClearsActiveVbiSlots(4); + ExpectClkInvalidationClearsActiveVbiSlots(8); +} + +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, InitialDelayIgnoresInactiveBackingBuffer) { + 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(-1.0, model->initial_presentation_delay); + EXPECT_DOUBLE_EQ(0.02, inactive->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); + 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, + 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); + + 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) { + 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, SingleFrameUsesPictureDecodeRateFallback) { + DECODER_MODEL model = MakeFrameConstraintModel(); + const ENCODER_DECODER_MODEL_FRAME frame = { + 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(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) { + 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); +} + +TEST(LevelDecoderModelTest, FinalTemporalUnitReusesPreviousDuration) { + DECODER_MODEL model = MakeFrameConstraintModel(); + 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); + 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, 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); +} + +TEST(LevelDecoderModelTest, StillPictureNeedsNoPreviousDurations) { + DECODER_MODEL model = MakeFrameConstraintModel(); + model.display_samples = 1000; + const ENCODER_DECODER_MODEL_FRAME frame = { + 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(&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) { + 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; + model->output_tu_count = 2; + + 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, FinishRebasesOnlyActivePlayerOwnedBuffers) { + 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(0.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 = {}; + 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; + model->output_tu_count = 2; + + 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..f29640c651 100644 --- a/test/ops_test.cc +++ b/test/ops_test.cc @@ -16,10 +16,12 @@ #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" #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. @@ -28,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; @@ -63,6 +67,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 +104,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 +159,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 +206,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