From f79e46ed1cc507b3e75fad26ac5229e21fe5e905 Mon Sep 17 00:00:00 2001 From: Martin Benjak Date: Thu, 6 Aug 2026 14:05:42 -0700 Subject: [PATCH 1/9] Remove encoder-side limitation for restricted switch frames The function prune_ref_by_selective_ref_frame() returned 1 for all blocks that reference restricted references. This caused WARPMV to be the only prediction mode to be considered for restricted references by the encoder, severely hindering the compression performance of restricted switch frames. Removed this limitation and set display_order_hint to REF_RESTRICTED_DOH to ensure that all now accessible prediction modes are correctly encoded. Added is_restricted checks to av2_get_refresh_frame_flags() because the display_order_hint of restricted references is always >= the current display_order_hint. --- av2/encoder/encode_strategy.c | 3 ++- av2/encoder/encoder.c | 1 + av2/encoder/rdopt.h | 14 +++++++------- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index 33054e25de..9d568cc9d5 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -794,6 +794,7 @@ int av2_get_refresh_frame_flags( // been output yet and whose DOH is at least the current // frame's DOH. (DOH requirement) if (cm->ref_frame_map[i] != NULL && + !cm->ref_frame_map[i]->is_restricted && cm->ref_frame_map[i]->implicit_output_picture && !cm->ref_frame_map[i]->frame_output_done && (int)cm->ref_frame_map[i]->display_order_hint >= cur_disp_order) { @@ -847,7 +848,7 @@ int av2_get_refresh_frame_flags( // at least the current frame's DOH. (DOH requirement) for (int i = 0; i < cpi->common.seq_params.ref_frames; i++) { const RefCntBuffer *const buf = cpi->common.ref_frame_map[i]; - if (buf != NULL && buf->implicit_output_picture && + if (buf != NULL && !buf->is_restricted && buf->implicit_output_picture && !buf->frame_output_done && (int)buf->display_order_hint >= cur_disp_order) { olk_flags_to_keep |= (1 << i); diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index e6574c4c80..4c3f04298a 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -5243,6 +5243,7 @@ int av2_encode(AV2_COMP *const cpi, uint8_t *const dest, cm->ref_frame_map[i]->mlayer_id, cm->mlayer_id)) { cm->ref_frame_map[i]->is_restricted = true; + cm->ref_frame_map[i]->display_order_hint = REF_RESTRICTED_DOH; } } } diff --git a/av2/encoder/rdopt.h b/av2/encoder/rdopt.h index bf650d910d..a1d06dbd29 100644 --- a/av2/encoder/rdopt.h +++ b/av2/encoder/rdopt.h @@ -215,13 +215,6 @@ static INLINE int prune_ref_by_selective_ref_frame( assert(ref_frame[0] != NONE_FRAME); if (ref_frame[0] == INTRA_FRAME) return 0; - RefCntBuffer *ref0_buf = get_ref_frame_buf(cm, ref_frame[0]); - if (ref0_buf) - if (ref0_buf->is_restricted) return 1; - RefCntBuffer *ref1_buf = get_ref_frame_buf(cm, ref_frame[1]); - if (ref1_buf) - if (ref1_buf->is_restricted) return 1; - const int comp_pred = is_inter_ref_frame(ref_frame[1]); if (comp_pred && ref_frame[0] >= RANKED_REF0_TO_PRUNE) return 1; @@ -259,6 +252,13 @@ static INLINE int prune_ref_by_selective_ref_frame( } } + RefCntBuffer *ref0_buf = get_ref_frame_buf(cm, ref_frame[0]); + if (ref0_buf) + if (ref0_buf->is_restricted) return 0; + RefCntBuffer *ref1_buf = get_ref_frame_buf(cm, ref_frame[1]); + if (ref1_buf) + if (ref1_buf->is_restricted) return 0; + int dir_refrank0[2] = { -1, -1 }; int dir_refrank1[2] = { -1, -1 }; int d0 = get_dir_rank(cm, ref_frame[0], dir_refrank0); From 77e28268769f4bc74a376e081040c614df97397e Mon Sep 17 00:00:00 2001 From: Martin Benjak Date: Thu, 6 Aug 2026 16:47:32 -0700 Subject: [PATCH 2/9] Add --sframe-replace-kf encoder parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The encoder replaces key frames with switch frames in a repeating pattern, if --sframe-replace-kf is set to a non-zero value. The pattern follows this rule: --sframe-replace-kf =0: disabled --sframe-replace-kf =1: K-S-K-S…, --sframe-replace-kf =2: K-S-S-K-S-S… --sframe-replace-kf =3: … --- apps/avmenc.c | 2 ++ av2/arg_defs.c | 3 +++ av2/arg_defs.h | 1 + av2/av2_cx_iface.c | 3 +++ av2/encoder/encode_strategy.c | 2 ++ av2/encoder/encoder.c | 2 ++ av2/encoder/encoder.h | 6 ++++++ av2/encoder/pass2_strategy.c | 11 +++++++++++ av2/encoder/ratectrl.h | 1 + avm/avm_encoder.h | 8 ++++++++ 10 files changed, 39 insertions(+) diff --git a/apps/avmenc.c b/apps/avmenc.c index 100bdba18e..cb037c8b35 100644 --- a/apps/avmenc.c +++ b/apps/avmenc.c @@ -1185,6 +1185,8 @@ static int parse_stream_params(struct AvxEncoderConfig *global, config->cfg.sframe_mode = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_type, argi)) { config->cfg.sframe_type = arg_parse_uint(&arg); + } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_replace_kf, argi)) { + config->cfg.sframe_replace_kf = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.enable_lcr, argi)) { config->cfg.enable_lcr = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.enable_ops, argi)) { diff --git a/av2/arg_defs.c b/av2/arg_defs.c index 14b58370d8..c8cb0be01d 100644 --- a/av2/arg_defs.c +++ b/av2/arg_defs.c @@ -290,6 +290,9 @@ const av2_codec_arg_definitions_t g_av2_codec_arg_defs = { ARG_DEF(NULL, "sframe-mode", 1, "S-Frame insertion mode (0..2)"), .sframe_type = ARG_DEF(NULL, "sframe-type", 1, "(0: Regular S-Frame (default), 1: RAS frame)"), + .sframe_replace_kf = + ARG_DEF(NULL, "sframe-replace-kf", 1, + "Replace N consecutive keyframes with S-frames (0: off)"), .enable_lcr = ARG_DEF(NULL, "enable-lcr", 1, "Enable layer config record (LCR) OBU (0: off (default), 1: on)"), diff --git a/av2/arg_defs.h b/av2/arg_defs.h index 896c51fa4a..3839525c94 100644 --- a/av2/arg_defs.h +++ b/av2/arg_defs.h @@ -109,6 +109,7 @@ typedef struct av2_codec_arg_definitions { arg_def_t sframe_dist; arg_def_t sframe_mode; arg_def_t sframe_type; + arg_def_t sframe_replace_kf; arg_def_t enable_lcr; arg_def_t enable_ops; arg_def_t num_ops; diff --git a/av2/av2_cx_iface.c b/av2/av2_cx_iface.c index ee9c25210c..40293458de 100644 --- a/av2/av2_cx_iface.c +++ b/av2/av2_cx_iface.c @@ -1594,6 +1594,7 @@ static avm_codec_err_t set_encoder_config(AV2EncoderConfig *oxcf, kf_cfg->sframe_dist = cfg->sframe_dist; kf_cfg->sframe_mode = cfg->sframe_mode; kf_cfg->sframe_type = cfg->sframe_type; + kf_cfg->sframe_replace_kf = cfg->sframe_replace_kf; oxcf->unit_test_cfg.insert_sframe = extra_cfg->enable_sframe; kf_cfg->enable_keyframe_filtering = extra_cfg->enable_keyframe_filtering; @@ -4883,6 +4884,7 @@ static const avm_codec_enc_cfg_t encoder_usage_cfg[] = { 0, // sframe_dist 1, // sframe_mode 0, // sframe_type + 0, // sframe_replace_kf 0, // monochrome 0, // full_still_picture_hdr 1, // enable_tcq @@ -5019,6 +5021,7 @@ static const avm_codec_enc_cfg_t encoder_usage_cfg[] = { 0, // sframe_dist 1, // sframe_mode 0, // sframe_type + 0, // sframe_replace_kf 0, // monochrome 0, // full_still_picture_hdr 0, // enable_tcq diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index 9d568cc9d5..7d896ca9c7 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -1279,6 +1279,8 @@ int av2_encode_strategy(AV2_COMP *const cpi, size_t *const size, cm->restricted_prediction_switch = (cpi->oxcf.kf_cfg.enable_sframe && cpi->oxcf.kf_cfg.sframe_mode == 0) || + (cpi->oxcf.kf_cfg.sframe_replace_kf > 0 && + cpi->oxcf.kf_cfg.sframe_mode == 0) || cpi->oxcf.tool_cfg.g_error_resilient_mode; av2_configure_buffer_updates(cpi, frame_update_type); diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index 4c3f04298a..9c50644b05 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -5227,6 +5227,8 @@ int av2_encode(AV2_COMP *const cpi, uint8_t *const dest, current_frame->mlayer_id = cm->mlayer_id; cm->restricted_prediction_switch = (cpi->oxcf.kf_cfg.enable_sframe && cpi->oxcf.kf_cfg.sframe_mode == 0) || + (cpi->oxcf.kf_cfg.sframe_replace_kf > 0 && + cpi->oxcf.kf_cfg.sframe_mode == 0) || cpi->oxcf.tool_cfg.g_error_resilient_mode; if (current_frame->frame_type == KEY_FRAME) { for (int i = 0; i < cm->seq_params.ref_frames; i++) { diff --git a/av2/encoder/encoder.h b/av2/encoder/encoder.h index aa90b45b5c..d3982fef02 100644 --- a/av2/encoder/encoder.h +++ b/av2/encoder/encoder.h @@ -440,6 +440,12 @@ typedef struct { */ bool enable_sframe; + /*! + * Number of consecutive keyframes to replace with S-frames between actual + * keyframes. + */ + int sframe_replace_kf; + /*! * Indicates if intra block copy prediction mode should be enabled or not. */ diff --git a/av2/encoder/pass2_strategy.c b/av2/encoder/pass2_strategy.c index c5994861bb..c679d58fbe 100644 --- a/av2/encoder/pass2_strategy.c +++ b/av2/encoder/pass2_strategy.c @@ -2931,6 +2931,17 @@ void av2_get_second_pass_params(AV2_COMP *cpi, frame_params->frame_type = KEY_FRAME; if (frame_params->frame_params_obu_type == NUM_OBU_TYPES) frame_params->frame_params_obu_type = OBU_CLOSED_LOOP_KEY; + const int replace_kf = oxcf->kf_cfg.sframe_replace_kf; + if (replace_kf > 0 && cpi->common.current_frame.frame_number > 0) { + rc->sframe_replace_kf_count++; + if (rc->sframe_replace_kf_count <= replace_kf) { + frame_params->frame_type = S_FRAME; + frame_params->frame_params_obu_type = NUM_OBU_TYPES; + cpi->is_ras_frame = (oxcf->kf_cfg.sframe_type == RAS_FRAME); + } else { + rc->sframe_replace_kf_count = 0; + } + } // Define next KF group and assign bits to it. find_next_key_frame(cpi, &this_frame); } diff --git a/av2/encoder/ratectrl.h b/av2/encoder/ratectrl.h index 8a5e8b69b1..311bcb3607 100644 --- a/av2/encoder/ratectrl.h +++ b/av2/encoder/ratectrl.h @@ -186,6 +186,7 @@ typedef struct { int next_key_frame_forced; int is_src_frame_alt_ref; int sframe_due; + int sframe_replace_kf_count; int high_source_sad; uint64_t avg_source_sad; diff --git a/avm/avm_encoder.h b/avm/avm_encoder.h index da30358261..805d6f6b77 100644 --- a/avm/avm_encoder.h +++ b/avm/avm_encoder.h @@ -1071,6 +1071,14 @@ typedef struct avm_codec_enc_cfg { */ unsigned int sframe_type; + /*!\brief sframe_replace_kf + * + * Number of consecutive keyframes to replace with S-frames between actual + * keyframes. 0: disabled, 1: K-S-K-S, 2: K-S-S-K-S-S, etc. + */ + unsigned int sframe_replace_kf; + + /*!\brief Monochrome mode * * If this is nonzero, the encoder will generate a monochrome stream From a757a44e96b6649edb4bd2ea48c4d003623065fb Mon Sep 17 00:00:00 2001 From: Martin Benjak Date: Thu, 6 Aug 2026 16:49:52 -0700 Subject: [PATCH 3/9] Add --sframe-refresh-all encoder flag to control if the switch frame refreshes all RPL slots or just one --- apps/avmenc.c | 4 ++++ av2/arg_defs.c | 3 +++ av2/arg_defs.h | 1 + av2/av2_cx_iface.c | 3 +++ av2/encoder/encode_strategy.c | 3 ++- av2/encoder/encoder.h | 5 +++++ avm/avm_encoder.h | 6 ++++++ 7 files changed, 24 insertions(+), 1 deletion(-) diff --git a/apps/avmenc.c b/apps/avmenc.c index cb037c8b35..b8e0ca38f2 100644 --- a/apps/avmenc.c +++ b/apps/avmenc.c @@ -220,6 +220,7 @@ const arg_def_t *kf_args[] = { &g_av2_codec_arg_defs.fwd_kf_enabled, &g_av2_codec_arg_defs.sframe_dist, &g_av2_codec_arg_defs.sframe_mode, &g_av2_codec_arg_defs.sframe_type, + &g_av2_codec_arg_defs.sframe_refresh_all, NULL }; // TODO(bohanli): Currently all options are supported by the key & value API. @@ -1185,6 +1186,9 @@ static int parse_stream_params(struct AvxEncoderConfig *global, config->cfg.sframe_mode = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_type, argi)) { config->cfg.sframe_type = arg_parse_uint(&arg); + } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_refresh_all, + argi)) { + config->cfg.sframe_refresh_all = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_replace_kf, argi)) { config->cfg.sframe_replace_kf = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.enable_lcr, argi)) { diff --git a/av2/arg_defs.c b/av2/arg_defs.c index c8cb0be01d..88b66feb27 100644 --- a/av2/arg_defs.c +++ b/av2/arg_defs.c @@ -290,6 +290,9 @@ const av2_codec_arg_definitions_t g_av2_codec_arg_defs = { ARG_DEF(NULL, "sframe-mode", 1, "S-Frame insertion mode (0..2)"), .sframe_type = ARG_DEF(NULL, "sframe-type", 1, "(0: Regular S-Frame (default), 1: RAS frame)"), + .sframe_refresh_all = + ARG_DEF(NULL, "sframe-refresh-all", 1, + "S-Frame refreshes all ref slots (0: off (default), 1: on)"), .sframe_replace_kf = ARG_DEF(NULL, "sframe-replace-kf", 1, "Replace N consecutive keyframes with S-frames (0: off)"), diff --git a/av2/arg_defs.h b/av2/arg_defs.h index 3839525c94..449d7488f4 100644 --- a/av2/arg_defs.h +++ b/av2/arg_defs.h @@ -109,6 +109,7 @@ typedef struct av2_codec_arg_definitions { arg_def_t sframe_dist; arg_def_t sframe_mode; arg_def_t sframe_type; + arg_def_t sframe_refresh_all; arg_def_t sframe_replace_kf; arg_def_t enable_lcr; arg_def_t enable_ops; diff --git a/av2/av2_cx_iface.c b/av2/av2_cx_iface.c index 40293458de..485ab36a96 100644 --- a/av2/av2_cx_iface.c +++ b/av2/av2_cx_iface.c @@ -1594,6 +1594,7 @@ static avm_codec_err_t set_encoder_config(AV2EncoderConfig *oxcf, kf_cfg->sframe_dist = cfg->sframe_dist; kf_cfg->sframe_mode = cfg->sframe_mode; kf_cfg->sframe_type = cfg->sframe_type; + kf_cfg->sframe_refresh_all = cfg->sframe_refresh_all; kf_cfg->sframe_replace_kf = cfg->sframe_replace_kf; oxcf->unit_test_cfg.insert_sframe = extra_cfg->enable_sframe; @@ -4884,6 +4885,7 @@ static const avm_codec_enc_cfg_t encoder_usage_cfg[] = { 0, // sframe_dist 1, // sframe_mode 0, // sframe_type + 0, // sframe_refresh_all 0, // sframe_replace_kf 0, // monochrome 0, // full_still_picture_hdr @@ -5021,6 +5023,7 @@ static const avm_codec_enc_cfg_t encoder_usage_cfg[] = { 0, // sframe_dist 1, // sframe_mode 0, // sframe_type + 0, // sframe_refresh_all 0, // sframe_replace_kf 0, // monochrome 0, // full_still_picture_hdr diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index 7d896ca9c7..eccb8375c5 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -762,7 +762,8 @@ int av2_get_refresh_frame_flags( return (1 << cpi->common.seq_params.ref_frames) - 1; } - if (frame_params->frame_type == S_FRAME || + if ((frame_params->frame_type == S_FRAME && + cpi->oxcf.kf_cfg.sframe_refresh_all) || frame_params->frame_type == KEY_FRAME) { AV2_COMMON *const cm = &cpi->common; int refresh_frame_flags = (1 << cpi->common.seq_params.ref_frames) - 1; diff --git a/av2/encoder/encoder.h b/av2/encoder/encoder.h index d3982fef02..144042ebcb 100644 --- a/av2/encoder/encoder.h +++ b/av2/encoder/encoder.h @@ -440,6 +440,11 @@ typedef struct { */ bool enable_sframe; + /*! + * Indicates if S-Frames should refresh all reference slots. + */ + bool sframe_refresh_all; + /*! * Number of consecutive keyframes to replace with S-frames between actual * keyframes. diff --git a/avm/avm_encoder.h b/avm/avm_encoder.h index 805d6f6b77..bd79899d0b 100644 --- a/avm/avm_encoder.h +++ b/avm/avm_encoder.h @@ -1071,6 +1071,12 @@ typedef struct avm_codec_enc_cfg { */ unsigned int sframe_type; + /*!\brief sframe_refresh_all + * + * If nonzero, S-Frames refresh all reference slots. + */ + unsigned int sframe_refresh_all; + /*!\brief sframe_replace_kf * * Number of consecutive keyframes to replace with S-frames between actual From 64973c53d656454fc48735a20b1235d6d917f95d Mon Sep 17 00:00:00 2001 From: Jungsun Kim Date: Wed, 23 Sep 2026 17:08:20 -0700 Subject: [PATCH 4/9] Fix sframe_refresh_all to not disable refresh-all for RAS frames --- av2/encoder/encode_strategy.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index eccb8375c5..99518db13c 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -762,8 +762,14 @@ int av2_get_refresh_frame_flags( return (1 << cpi->common.seq_params.ref_frames) - 1; } + // sframe_refresh_all only gates real OBU_SWITCH frames; RAS frames + // (sframe_type == RAS_FRAME) always refresh all RPL slots, since they are + // random access points. cpi->is_ras_frame is not used here because it is + // only valid for the frame currently being encoded, not reliably for + // every call site reachable from this function. if ((frame_params->frame_type == S_FRAME && - cpi->oxcf.kf_cfg.sframe_refresh_all) || + (cpi->oxcf.kf_cfg.sframe_refresh_all || + cpi->oxcf.kf_cfg.sframe_type == RAS_FRAME)) || frame_params->frame_type == KEY_FRAME) { AV2_COMMON *const cm = &cpi->common; int refresh_frame_flags = (1 << cpi->common.seq_params.ref_frames) - 1; From 68f8b3af3547c6712fd5580715b63440c51addd4 Mon Sep 17 00:00:00 2001 From: Jungsun Kim Date: Wed, 23 Sep 2026 18:51:21 -0700 Subject: [PATCH 5/9] Refresh all RPL slots for non-restricted switch frames regardless of sframe_refresh_all --- av2/encoder/encode_strategy.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/av2/encoder/encode_strategy.c b/av2/encoder/encode_strategy.c index 99518db13c..f2df6249f7 100644 --- a/av2/encoder/encode_strategy.c +++ b/av2/encoder/encode_strategy.c @@ -762,14 +762,18 @@ int av2_get_refresh_frame_flags( return (1 << cpi->common.seq_params.ref_frames) - 1; } - // sframe_refresh_all only gates real OBU_SWITCH frames; RAS frames - // (sframe_type == RAS_FRAME) always refresh all RPL slots, since they are - // random access points. cpi->is_ras_frame is not used here because it is - // only valid for the frame currently being encoded, not reliably for - // every call site reachable from this function. + // sframe_refresh_all only gates real, restricted OBU_SWITCH frames + // (restricted_prediction_switch, i.e. sframe_mode == 0). RAS frames + // (sframe_type == RAS_FRAME) and non-restricted switch frames always + // refresh all RPL slots: RAS frames are random access points, and a + // non-restricted switch frame retains no is_restricted old references to + // make partial refresh meaningful. cpi->is_ras_frame is not used here + // because it is only valid for the frame currently being encoded, not + // reliably for every call site reachable from this function. if ((frame_params->frame_type == S_FRAME && (cpi->oxcf.kf_cfg.sframe_refresh_all || - cpi->oxcf.kf_cfg.sframe_type == RAS_FRAME)) || + cpi->oxcf.kf_cfg.sframe_type == RAS_FRAME || + !cpi->common.restricted_prediction_switch)) || frame_params->frame_type == KEY_FRAME) { AV2_COMMON *const cm = &cpi->common; int refresh_frame_flags = (1 << cpi->common.seq_params.ref_frames) - 1; From fd20e075350296d5b7d9cbd2c3ae53a00f8a9b35 Mon Sep 17 00:00:00 2001 From: Jungsun Kim Date: Wed, 23 Sep 2026 11:56:03 -0700 Subject: [PATCH 6/9] VCWG-133 and 144: 1) add requirement checks to encoder and decoder 1-1) refMVbank=0 1-2) allow_intrabc=0, allow_bawp=0 if refMVbank=0 after a switch frame 1-3) oplf=0 RefineMV=0 2) add a CLI option(--sframe-conformance-option) to apply one of the requirement(constraint) to the encoder --sframe-conformance-option=0 : (default) --enable-refmvbank=0 + BAWP is disabled after a switch frame --sframe-conformance-option=1 : intraBC is disabled after a switch frame if --enable-refmvbank=1 + BAWP is disalbed after a switch frame --sframe-conformance-option=2 : --enable-opfl-refine=0 --enable-refinemv=0 Also check git@github.com:mabenjak/avm.git(Martin Benjak)'s switch_DVMR_experiments branch (commits 47a8c2cde, 31b14ea0c) --- apps/avmenc.c | 4 +++ av2/arg_defs.c | 6 ++++ av2/arg_defs.h | 1 + av2/av2_cx_iface.c | 48 ++++++++++++++++++++++++++++ av2/common/av2_common_int.h | 19 +++++++++++ av2/decoder/decodeframe.c | 63 +++++++++++++++++++++++++++++++++++++ av2/encoder/encoder.c | 57 +++++++++++++++++++++++++++++++++ av2/encoder/encoder.h | 7 +++++ avm/avm_encoder.h | 15 +++++++++ avm/avmcx.h | 10 ++++++ 10 files changed, 230 insertions(+) diff --git a/apps/avmenc.c b/apps/avmenc.c index b8e0ca38f2..a97ba08400 100644 --- a/apps/avmenc.c +++ b/apps/avmenc.c @@ -221,6 +221,7 @@ const arg_def_t *kf_args[] = { &g_av2_codec_arg_defs.fwd_kf_enabled, &g_av2_codec_arg_defs.sframe_mode, &g_av2_codec_arg_defs.sframe_type, &g_av2_codec_arg_defs.sframe_refresh_all, + &g_av2_codec_arg_defs.sframe_conformance_option, NULL }; // TODO(bohanli): Currently all options are supported by the key & value API. @@ -1191,6 +1192,9 @@ static int parse_stream_params(struct AvxEncoderConfig *global, config->cfg.sframe_refresh_all = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_replace_kf, argi)) { config->cfg.sframe_replace_kf = arg_parse_uint(&arg); + } else if (arg_match(&arg, &g_av2_codec_arg_defs.sframe_conformance_option, + argi)) { + config->cfg.sframe_conformance_option = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.enable_lcr, argi)) { config->cfg.enable_lcr = arg_parse_uint(&arg); } else if (arg_match(&arg, &g_av2_codec_arg_defs.enable_ops, argi)) { diff --git a/av2/arg_defs.c b/av2/arg_defs.c index 88b66feb27..56c0d7e4f3 100644 --- a/av2/arg_defs.c +++ b/av2/arg_defs.c @@ -296,6 +296,12 @@ const av2_codec_arg_definitions_t g_av2_codec_arg_defs = { .sframe_replace_kf = ARG_DEF(NULL, "sframe-replace-kf", 1, "Replace N consecutive keyframes with S-frames (0: off)"), + .sframe_conformance_option = ARG_DEF( + NULL, "sframe-conformance-option", 1, + "How to satisfy the BAWP/IBC/OPFL/RefineMV bitstream-conformance " + "requirement around switch frames (0: disable refmvbank (default), " + "1: keep refmvbank, disable BAWP/IntraBC after switch, " + "2: disable OPFL refinement and RefineMV)"), .enable_lcr = ARG_DEF(NULL, "enable-lcr", 1, "Enable layer config record (LCR) OBU (0: off (default), 1: on)"), diff --git a/av2/arg_defs.h b/av2/arg_defs.h index 449d7488f4..0de85c8f40 100644 --- a/av2/arg_defs.h +++ b/av2/arg_defs.h @@ -111,6 +111,7 @@ typedef struct av2_codec_arg_definitions { arg_def_t sframe_type; arg_def_t sframe_refresh_all; arg_def_t sframe_replace_kf; + arg_def_t sframe_conformance_option; arg_def_t enable_lcr; arg_def_t enable_ops; arg_def_t num_ops; diff --git a/av2/av2_cx_iface.c b/av2/av2_cx_iface.c index 485ab36a96..5e8b97f948 100644 --- a/av2/av2_cx_iface.c +++ b/av2/av2_cx_iface.c @@ -1411,6 +1411,13 @@ static avm_codec_err_t set_encoder_config(AV2EncoderConfig *oxcf, tool_cfg->enable_joint_mvd = extra_cfg->enable_joint_mvd; tool_cfg->enable_refinemv = extra_cfg->enable_refinemv; + if (cfg->enable_sframe && cfg->sframe_conformance_option == 2) { + // Option 3: disable RefineMV sequence-wide, applied at the point + // tool_cfg->enable_refinemv is derived (matching the enable_opfl_refine + // override below). + tool_cfg->enable_refinemv = 0; + extra_cfg->enable_refinemv = 0; + } tool_cfg->enable_mvd_sign_derive = extra_cfg->enable_mvd_sign_derive; // Turn off BRU if LA, AI or resize mode tool_cfg->enable_bru = extra_cfg->enable_bru; @@ -1463,6 +1470,24 @@ static avm_codec_err_t set_encoder_config(AV2EncoderConfig *oxcf, tool_cfg->max_drl_refmvs = extra_cfg->max_drl_refmvs; tool_cfg->max_drl_refbvs = extra_cfg->max_drl_refbvs; tool_cfg->enable_refmvbank = extra_cfg->enable_refmvbank; + if (cfg->enable_sframe) { + switch (cfg->sframe_conformance_option) { + case 1: + // Option 2: keep the reference-MV bank on sequence-wide. + tool_cfg->enable_refmvbank = 1; + extra_cfg->enable_refmvbank = 1; + break; + case 2: + // Option 3 doesn't touch enable_refmvbank. + break; + case 0: + default: + // Option 1 (default): disable the reference-MV bank sequence-wide. + tool_cfg->enable_refmvbank = 0; + extra_cfg->enable_refmvbank = 0; + break; + } + } tool_cfg->enable_cropping_window = extra_cfg->enable_cropping_window; tool_cfg->crop_win_left_offset = extra_cfg->crop_win_left_offset; tool_cfg->crop_win_right_offset = extra_cfg->crop_win_right_offset; @@ -1505,6 +1530,13 @@ static avm_codec_err_t set_encoder_config(AV2EncoderConfig *oxcf, tool_cfg->enable_high_motion = extra_cfg->enable_high_motion; tool_cfg->enable_opfl_refine = extra_cfg->enable_opfl_refine; + if (cfg->enable_sframe && cfg->sframe_conformance_option == 2) { + // Option 3: disable OPFL refinement sequence-wide, applied at the point + // tool_cfg->enable_opfl_refine is derived (matching the enable_refinemv + // override above). + tool_cfg->enable_opfl_refine = 0; + extra_cfg->enable_opfl_refine = 0; + } if (tool_cfg->enable_opfl_refine) { if (cfg->g_lag_in_frames == 0) { tool_cfg->enable_opfl_refine = 0; @@ -1596,6 +1628,7 @@ static avm_codec_err_t set_encoder_config(AV2EncoderConfig *oxcf, kf_cfg->sframe_type = cfg->sframe_type; kf_cfg->sframe_refresh_all = cfg->sframe_refresh_all; kf_cfg->sframe_replace_kf = cfg->sframe_replace_kf; + kf_cfg->sframe_conformance_option = cfg->sframe_conformance_option; oxcf->unit_test_cfg.insert_sframe = extra_cfg->enable_sframe; kf_cfg->enable_keyframe_filtering = extra_cfg->enable_keyframe_filtering; @@ -2530,6 +2563,18 @@ static avm_codec_err_t ctrl_set_enable_sframe(avm_codec_alg_priv_t *ctx, return update_extra_cfg(ctx, &extra_cfg); } +static avm_codec_err_t ctrl_set_sframe_conformance_option( + avm_codec_alg_priv_t *ctx, va_list args) { + struct av2_extracfg extra_cfg = ctx->extra_cfg; + // sframe_conformance_option lives in avm_codec_enc_cfg_t, not in + // av2_extracfg, so set it directly on ctx->cfg; set_encoder_config() + // reads it from there into kf_cfg. update_extra_cfg() still runs to + // re-validate and re-apply the full configuration. + ctx->cfg.sframe_conformance_option = + CAST(AV2E_SET_SFRAME_CONFORMANCE_OPTION, args); + return update_extra_cfg(ctx, &extra_cfg); +} + static avm_codec_err_t ctrl_set_frame_parallel_decoding_mode( avm_codec_alg_priv_t *ctx, va_list args) { struct av2_extracfg extra_cfg = ctx->extra_cfg; @@ -4736,6 +4781,7 @@ static avm_codec_ctrl_fn_map_t encoder_ctrl_maps[] = { { AV2E_SET_FRAME_PARALLEL_DECODING, ctrl_set_frame_parallel_decoding_mode }, { AV2E_SET_ENABLE_CDF_AVERAGING, ctrl_set_enable_cdf_averaging }, { AV2E_SET_ENABLE_SFRAME, ctrl_set_enable_sframe }, + { AV2E_SET_SFRAME_CONFORMANCE_OPTION, ctrl_set_sframe_conformance_option }, { AV2E_SET_ENABLE_RECT_PARTITIONS, ctrl_set_enable_rect_partitions }, { AV2E_SET_ENABLE_1TO4_PARTITIONS, ctrl_set_enable_uneven_4way_partitions }, { AV2E_SET_MIN_PARTITION_SIZE, ctrl_set_min_partition_size }, @@ -4887,6 +4933,7 @@ static const avm_codec_enc_cfg_t encoder_usage_cfg[] = { 0, // sframe_type 0, // sframe_refresh_all 0, // sframe_replace_kf + 0, // sframe_conformance_option 0, // monochrome 0, // full_still_picture_hdr 1, // enable_tcq @@ -5025,6 +5072,7 @@ static const avm_codec_enc_cfg_t encoder_usage_cfg[] = { 0, // sframe_type 0, // sframe_refresh_all 0, // sframe_replace_kf + 0, // sframe_conformance_option 0, // monochrome 0, // full_still_picture_hdr 0, // enable_tcq diff --git a/av2/common/av2_common_int.h b/av2/common/av2_common_int.h index 6e39442efe..e35423283c 100644 --- a/av2/common/av2_common_int.h +++ b/av2/common/av2_common_int.h @@ -3026,6 +3026,25 @@ typedef struct AV2Common { */ bool restricted_prediction_switch; + /*! + * Set to 1 for a frame that is within the "risk window" opened by a coded + * frame with obu_type equal to OBU_SWITCH (the switch frame itself, and + * every following frame up to the next random access point). Used to + * enforce the bitstream conformance requirement constraining BAWP/IBC + * around switch frames. + */ + bool in_switch_risk_window; + /*! + * The mlayer_id of the OBU_SWITCH frame that opened the current + * in_switch_risk_window. Only meaningful when in_switch_risk_window is 1. + */ + int switch_risk_window_mlayer_id; + /*! + * The tlayer_id of the OBU_SWITCH frame that opened the current + * in_switch_risk_window. Only meaningful when in_switch_risk_window is 1. + */ + int switch_risk_window_tlayer_id; + } AV2_COMMON; /*!\cond */ diff --git a/av2/decoder/decodeframe.c b/av2/decoder/decodeframe.c index e6cb3b9bb7..5a1136fb4b 100644 --- a/av2/decoder/decodeframe.c +++ b/av2/decoder/decodeframe.c @@ -6778,6 +6778,31 @@ static INLINE void read_intrabc_params(AV2_COMMON *const cm, FeatureFlags *const features = &cm->features; features->allow_intrabc = avm_rb_read_bit(rb); + // Bitstream conformance: for a coded video sequence containing an + // OBU_SWITCH frame, at least one of the BAWP/IBC/OPFL/RefineMV + // constraints must hold for the switch frame and every dependent inter + // frame that follows it (the switch frame itself, frame_type == S_FRAME, + // is excluded from this particular constraint). When enable_refmvbank is + // 1 and OPFL refinement/RefineMV are not both off sequence-wide, + // allow_intrabc must be 0 here. + if (features->allow_intrabc && cm->in_switch_risk_window && + current_frame->frame_type == INTER_FRAME && + cm->seq_params.enable_refmvbank && + !(cm->seq_params.enable_opfl_refine == REFINE_NONE && + cm->seq_params.enable_refinemv == 0) && + is_mlayer_transitively_dependent(&cm->seq_params, cm->mlayer_id, + cm->switch_risk_window_mlayer_id) && + (cm->tlayer_id == cm->switch_risk_window_tlayer_id || + cm->seq_params + .tlayer_dependency_map[cm->mlayer_id][cm->tlayer_id] + [cm->switch_risk_window_tlayer_id])) { + avm_internal_error( + &cm->error, AVM_CODEC_UNSUP_BITSTREAM, + "allow_intrabc must be 0 for an inter frame depending on the layer " + "of a preceding OBU_SWITCH frame when enable_refmvbank is 1, unless " + "enable_opfl_refine is REFINE_NONE and enable_refinemv is 0"); + } + if (features->allow_intrabc) { if (current_frame->frame_type == KEY_FRAME || current_frame->frame_type == INTRA_ONLY_FRAME) { @@ -8074,6 +8099,22 @@ static int read_uncompressed_header(AV2Decoder *pbi, OBU_TYPE obu_type, current_frame->frame_type = avm_rb_read_bit(rb) ? INTER_FRAME : INTRA_ONLY_FRAME; } + + // Track the "risk window" opened by an OBU_SWITCH frame, used below to + // enforce the bitstream conformance requirement constraining BAWP/IBC + // around switch frames (decoder state derived from reconstructed + // samples can diverge across bitstreams spliced at the switch frame + // even though parsing stays deterministic). A CLK/OLK or an + // OBU_RAS_FRAME is a random access point, which closes any risk window + // opened by a prior OBU_SWITCH frame. + if (current_frame->frame_type == KEY_FRAME || obu_type == OBU_RAS_FRAME) { + cm->in_switch_risk_window = false; + } else if (obu_type == OBU_SWITCH) { + cm->in_switch_risk_window = true; + cm->switch_risk_window_mlayer_id = cm->mlayer_id; + cm->switch_risk_window_tlayer_id = cm->tlayer_id; + } + current_frame->long_term_id = -1; if (current_frame->frame_type == KEY_FRAME) { const int long_term_id_plus_1 = @@ -9303,6 +9344,28 @@ static int read_uncompressed_header(AV2Decoder *pbi, OBU_TYPE obu_type, else features->enable_bawp = 0; + // Bitstream conformance: for a coded video sequence containing an + // OBU_SWITCH frame, at least one of the BAWP/IBC/OPFL/RefineMV + // constraints must hold for the switch frame and every dependent inter + // frame that follows it (the switch frame itself, frame_type == S_FRAME, + // is excluded from this particular constraint). If OPFL refinement and + // RefineMV are not both off sequence-wide, then BAWP must be off here. + if (features->enable_bawp && cm->in_switch_risk_window && + current_frame->frame_type == INTER_FRAME && + !(seq_params->enable_opfl_refine == REFINE_NONE && + seq_params->enable_refinemv == 0) && + is_mlayer_transitively_dependent(seq_params, cm->mlayer_id, + cm->switch_risk_window_mlayer_id) && + (cm->tlayer_id == cm->switch_risk_window_tlayer_id || + seq_params->tlayer_dependency_map[cm->mlayer_id][cm->tlayer_id] + [cm->switch_risk_window_tlayer_id])) { + avm_internal_error( + &cm->error, AVM_CODEC_UNSUP_BITSTREAM, + "enable_bawp must be 0 for an inter frame depending on the layer of " + "a preceding OBU_SWITCH frame, unless enable_opfl_refine is " + "REFINE_NONE and enable_refinemv is 0"); + } + features->enable_intra_bawp = seq_params->enable_bawp; features->enable_cwp = seq_params->enable_cwp; diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index 9c50644b05..ee6a165ef1 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -668,6 +668,9 @@ void av2_init_seq_coding_tools(AV2_COMP *cpi, SequenceHeader *seq, seq->base_uv_dc_delta_q == seq->base_uv_ac_delta_q)); seq->enable_refmvbank = tool_cfg->enable_refmvbank; + // Bitstream conformance: a coded video sequence containing an OBU_SWITCH + // frame must satisfy one of the BAWP/IBC/OPFL/RefineMV constraints in the + // spec seq->enable_drl_reorder = tool_cfg->enable_drl_reorder; seq->conf.conf_win_enabled_flag = oxcf->tool_cfg.enable_cropping_window; @@ -4712,6 +4715,35 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, features->allow_warpmv_mode = features->enabled_motion_modes; // temporal set of frame level enable_bawp flag. features->enable_bawp = seq_params->enable_bawp; + // Bitstream conformance around OBU_SWITCH frames: force BAWP (and, when + // the reference-MV bank is on, IntraBC for inter frames) off for the + // switch frame and for every following frame, up to the next random + // access point, whose embedded/temporal layer depends on the switch + // frame's own layer. Skipped when OPFL refinement and RefineMV are + // already off sequence-wide, since that alone satisfies conformance + // In a single-layer encode the layer scoping always evaluates true within the risk window, since mlayer_id/tlayer_id are + // both 0 and trivially match the switch frame's own layer. + const bool switch_frame_layer_dependent = + cm->in_switch_risk_window && + !(seq_params->enable_opfl_refine == REFINE_NONE && + seq_params->enable_refinemv == 0) && + is_mlayer_transitively_dependent(seq_params, cm->mlayer_id, + cm->switch_risk_window_mlayer_id) && + (cm->tlayer_id == cm->switch_risk_window_tlayer_id || + seq_params->tlayer_dependency_map[cm->mlayer_id][cm->tlayer_id] + [cm->switch_risk_window_tlayer_id]); + if (switch_frame_layer_dependent) { + // The switch frame itself (frame_type == S_FRAME) is excluded from the + // BAWP and IntraBC constraints, matching the spec text ("every frame + // ... that follows that frame"); only later dependent inter frames are + // covered. + if (current_frame->frame_type == INTER_FRAME) { + features->enable_bawp = 0; + if (seq_params->enable_refmvbank) { + features->allow_intrabc = 0; + } + } + } features->enable_intra_bawp = seq_params->enable_bawp; features->enable_cwp = seq_params->enable_cwp; @@ -5230,6 +5262,31 @@ int av2_encode(AV2_COMP *const cpi, uint8_t *const dest, (cpi->oxcf.kf_cfg.sframe_replace_kf > 0 && cpi->oxcf.kf_cfg.sframe_mode == 0) || cpi->oxcf.tool_cfg.g_error_resilient_mode; + + // A CLK, or an OBU_RAS_FRAME (frame_type == S_FRAME with is_ras_frame == + // 1), is a random access point that closes any risk window opened by a + // prior OBU_SWITCH frame immediately. An OLK is weaker: being open-loop, + // it does not by itself guarantee the same drift-free reconstruction a + // CLK does, and its leading pictures (and any immediately following + // forward-KF overlay/successor frame) can still reference content from + // before the OLK's own refresh. So the window only closes once we are + // clearly past that span, mirroring the codebase's own olk_encountered + // lifecycle (see bitstream.c). A OBU_SWITCH(frame_type == S_FRAME + !OBU_RAS) (re)opens the risk window for its own layer. + const bool past_olk_leading_span = + current_frame->display_order_hint >= cm->last_olk_disp_order_hint && + cpi->gf_group.update_type[cpi->gf_group.index] != FWD_KF_OVERLAY_UPDATE && + cpi->gf_group.update_type[cpi->gf_group.index] != FWD_KF_SUCCESSOR_UPDATE; + if (current_frame->cm_obu_type == OBU_CLOSED_LOOP_KEY || + (cpi->olk_encountered && current_frame->frame_type != KEY_FRAME && + past_olk_leading_span) || + (current_frame->frame_type == S_FRAME && cpi->is_ras_frame == 1)) { + cm->in_switch_risk_window = false; + } else if (current_frame->frame_type == S_FRAME) { + cm->in_switch_risk_window = true; + cm->switch_risk_window_mlayer_id = cm->mlayer_id; + cm->switch_risk_window_tlayer_id = cm->tlayer_id; + } + if (current_frame->frame_type == KEY_FRAME) { for (int i = 0; i < cm->seq_params.ref_frames; i++) { if (cm->ref_frame_map[i] != NULL) diff --git a/av2/encoder/encoder.h b/av2/encoder/encoder.h index 144042ebcb..7d8968b7ce 100644 --- a/av2/encoder/encoder.h +++ b/av2/encoder/encoder.h @@ -425,6 +425,13 @@ typedef struct { */ int sframe_type; + /*! + * Selects how the encoder satisfies the AV2 bitstream-conformance + * requirement constraining BAWP/IBC/OPFL/RefineMV around OBU_SWITCH + * frames. Only has an effect when enable_sframe is on. + */ + int sframe_conformance_option; + /*! * Indicates if encoder should autodetect cut scenes and set the keyframes. */ diff --git a/avm/avm_encoder.h b/avm/avm_encoder.h index bd79899d0b..cbb64f0bc5 100644 --- a/avm/avm_encoder.h +++ b/avm/avm_encoder.h @@ -1084,6 +1084,21 @@ typedef struct avm_codec_enc_cfg { */ unsigned int sframe_replace_kf; + /*!\brief sframe_conformance_option + * + * Selects how the encoder satisfies the AV2 bitstream-conformance + * requirement constraining BAWP/IBC/OPFL/RefineMV around OBU_SWITCH + * frames. Only has an effect when enable_sframe is on. Values: + * - 0 (default): force enable_refmvbank = 0 sequence-wide, and disable + * BAWP for the switch frame and every dependent frame that follows + * it. + * - 1: force enable_refmvbank = 1 sequence-wide, and disable BAWP and + * IntraBC (for inter frames) for the switch frame and every + * dependent frame that follows it. + * - 2: force enable_opfl_refine = REFINE_NONE and enable_refinemv = 0 + * sequence-wide. + */ + unsigned int sframe_conformance_option; /*!\brief Monochrome mode * diff --git a/avm/avmcx.h b/avm/avmcx.h index 40c9def402..4b9e170243 100644 --- a/avm/avmcx.h +++ b/avm/avmcx.h @@ -1255,6 +1255,13 @@ enum avme_enc_control_id { * (g_usage == AVM_USAGE_GOOD_QUALITY) enables this mode. */ AV2E_SET_ENABLE_LOW_COMPLEXITY_DECODE = 186, + + /*!\brief Codec control function to select how the encoder satisfies the + * bitstream-conformance requirement constraining BAWP/IBC/OPFL/RefineMV + * around OBU_SWITCH frames, unsigned int parameter. Only has an effect + * when S-Frame encoding is enabled. + */ + AV2E_SET_SFRAME_CONFORMANCE_OPTION = 187, }; /*!\brief avm 1-D scaling mode @@ -1607,6 +1614,9 @@ AVM_CTRL_USE_TYPE(AV2E_SET_ENABLE_CDF_AVERAGING, int) AVM_CTRL_USE_TYPE(AV2E_SET_ENABLE_SFRAME, int) #define AVM_CTRL_AV2E_SET_ENABLE_SFRAME +AVM_CTRL_USE_TYPE(AV2E_SET_SFRAME_CONFORMANCE_OPTION, unsigned int) +#define AVM_CTRL_AV2E_SET_SFRAME_CONFORMANCE_OPTION + AVM_CTRL_USE_TYPE(AV2E_SET_AQ_MODE, unsigned int) #define AVM_CTRL_AV2E_SET_AQ_MODE From cccfb3763ca7b0a4e71141b8bd4cf9a70e4d0311 Mon Sep 17 00:00:00 2001 From: Jungsun Kim Date: Wed, 23 Sep 2026 11:58:03 -0700 Subject: [PATCH 7/9] update style --- av2/decoder/decodeframe.c | 2 +- av2/encoder/encoder.c | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/av2/decoder/decodeframe.c b/av2/decoder/decodeframe.c index 5a1136fb4b..97ed35a63d 100644 --- a/av2/decoder/decodeframe.c +++ b/av2/decoder/decodeframe.c @@ -6795,7 +6795,7 @@ static INLINE void read_intrabc_params(AV2_COMMON *const cm, (cm->tlayer_id == cm->switch_risk_window_tlayer_id || cm->seq_params .tlayer_dependency_map[cm->mlayer_id][cm->tlayer_id] - [cm->switch_risk_window_tlayer_id])) { + [cm->switch_risk_window_tlayer_id])) { avm_internal_error( &cm->error, AVM_CODEC_UNSUP_BITSTREAM, "allow_intrabc must be 0 for an inter frame depending on the layer " diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index ee6a165ef1..72a9e5e828 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -4721,8 +4721,9 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, // access point, whose embedded/temporal layer depends on the switch // frame's own layer. Skipped when OPFL refinement and RefineMV are // already off sequence-wide, since that alone satisfies conformance - // In a single-layer encode the layer scoping always evaluates true within the risk window, since mlayer_id/tlayer_id are - // both 0 and trivially match the switch frame's own layer. + // In a single-layer encode the layer scoping always evaluates true within the + // risk window, since mlayer_id/tlayer_id are both 0 and trivially match the + // switch frame's own layer. const bool switch_frame_layer_dependent = cm->in_switch_risk_window && !(seq_params->enable_opfl_refine == REFINE_NONE && @@ -5271,7 +5272,8 @@ int av2_encode(AV2_COMP *const cpi, uint8_t *const dest, // forward-KF overlay/successor frame) can still reference content from // before the OLK's own refresh. So the window only closes once we are // clearly past that span, mirroring the codebase's own olk_encountered - // lifecycle (see bitstream.c). A OBU_SWITCH(frame_type == S_FRAME + !OBU_RAS) (re)opens the risk window for its own layer. + // lifecycle (see bitstream.c). A OBU_SWITCH(frame_type == S_FRAME + !OBU_RAS) + // (re)opens the risk window for its own layer. const bool past_olk_leading_span = current_frame->display_order_hint >= cm->last_olk_disp_order_hint && cpi->gf_group.update_type[cpi->gf_group.index] != FWD_KF_OVERLAY_UPDATE && From 4fee24a642abcc6571df025c547886523a7fb2d9 Mon Sep 17 00:00:00 2001 From: Jungsun Kim Date: Wed, 23 Sep 2026 13:56:25 -0700 Subject: [PATCH 8/9] fix pipeline build error --- av2/decoder/decodeframe.c | 4 ++-- av2/encoder/encoder.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/av2/decoder/decodeframe.c b/av2/decoder/decodeframe.c index 97ed35a63d..e62a727336 100644 --- a/av2/decoder/decodeframe.c +++ b/av2/decoder/decodeframe.c @@ -6788,7 +6788,7 @@ static INLINE void read_intrabc_params(AV2_COMMON *const cm, if (features->allow_intrabc && cm->in_switch_risk_window && current_frame->frame_type == INTER_FRAME && cm->seq_params.enable_refmvbank && - !(cm->seq_params.enable_opfl_refine == REFINE_NONE && + !(cm->seq_params.enable_opfl_refine == AVM_OPFL_REFINE_NONE && cm->seq_params.enable_refinemv == 0) && is_mlayer_transitively_dependent(&cm->seq_params, cm->mlayer_id, cm->switch_risk_window_mlayer_id) && @@ -9352,7 +9352,7 @@ static int read_uncompressed_header(AV2Decoder *pbi, OBU_TYPE obu_type, // RefineMV are not both off sequence-wide, then BAWP must be off here. if (features->enable_bawp && cm->in_switch_risk_window && current_frame->frame_type == INTER_FRAME && - !(seq_params->enable_opfl_refine == REFINE_NONE && + !(seq_params->enable_opfl_refine == AVM_OPFL_REFINE_NONE && seq_params->enable_refinemv == 0) && is_mlayer_transitively_dependent(seq_params, cm->mlayer_id, cm->switch_risk_window_mlayer_id) && diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index 72a9e5e828..f618016e3e 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -4726,7 +4726,7 @@ static int encode_frame_to_data_rate(AV2_COMP *cpi, size_t *size, uint8_t *dest, // switch frame's own layer. const bool switch_frame_layer_dependent = cm->in_switch_risk_window && - !(seq_params->enable_opfl_refine == REFINE_NONE && + !(seq_params->enable_opfl_refine == AVM_OPFL_REFINE_NONE && seq_params->enable_refinemv == 0) && is_mlayer_transitively_dependent(seq_params, cm->mlayer_id, cm->switch_risk_window_mlayer_id) && From 535412de940b1715a4a028cbc0bc91f71cd5d8ca Mon Sep 17 00:00:00 2001 From: Jungsun Kim Date: Wed, 23 Sep 2026 19:04:48 -0700 Subject: [PATCH 9/9] Make MultiLayerTest2Embedded2TemporaSframe use a restricted switch frame When a switch frame is not restricted and its reference frames are not fully refreshed, a decoder crash/mismatch is expected: only a restricted switch frame's reference marking (is_restricted, sample-values-only) makes partial reference refresh well-defined. So a switch frame must either be restricted, or fully refresh its reference frames. This test exercises restricted switch frames, so set sframe_mode = 0 explicitly instead of relying on the default (sframe_mode == 1), which produced a non-restricted switch frame with default partial refresh and triggered exactly the undefined case above. --- test/multi_layer_test.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/test/multi_layer_test.cc b/test/multi_layer_test.cc index 0d1d2bdb03..f2d5c6c960 100644 --- a/test/multi_layer_test.cc +++ b/test/multi_layer_test.cc @@ -574,6 +574,7 @@ TEST_P(MultiLayerTest, MultiLayerTest2Embedded2TemporaSframe) { enable_buffer_refresh_test_ = true; enable_s_frame_ = true; cfg_.enable_sframe = 1; + cfg_.sframe_mode = 0; start_decoding_tl1_ = 11; ASSERT_NO_FATAL_FAILURE(RunLoop(&video_nonsc)); EXPECT_EQ(num_mismatch_, 0);