diff --git a/av2/av2_cx_iface.c b/av2/av2_cx_iface.c index 86283a57e5..893b8b5ad6 100644 --- a/av2/av2_cx_iface.c +++ b/av2/av2_cx_iface.c @@ -741,6 +741,58 @@ static avm_codec_err_t validate_config(avm_codec_alg_priv_t *ctx, RANGE_CHECK_HI(extra_cfg, crop_win_right_offset, 65535); RANGE_CHECK_HI(extra_cfg, crop_win_top_offset, 65535); RANGE_CHECK_HI(extra_cfg, crop_win_bottom_offset, 65535); + if (extra_cfg->enable_cropping_window) { + // The decoder validates the conformance window in + // av2_validate_seq_conformance_window() and + // av2_validate_frame_level_conformance(), rejecting the stream with + // AVM_CODEC_UNSUP_BITSTREAM. Apply the same constraints here so an + // undecodable configuration is refused rather than producing a bitstream + // the reference decoder cannot decode. + const int left = extra_cfg->crop_win_left_offset; + const int right = extra_cfg->crop_win_right_offset; + const int top = extra_cfg->crop_win_top_offset; + const int bottom = extra_cfg->crop_win_bottom_offset; + const int max_width = cfg->g_forced_max_frame_width + ? (int)cfg->g_forced_max_frame_width + : (int)cfg->g_w; + const int max_height = cfg->g_forced_max_frame_height + ? (int)cfg->g_forced_max_frame_height + : (int)cfg->g_h; + // Mirrors av2_validate_seq_conformance_window() in the decoder: each offset + // must be less than the sequence maximum. The left+right and top+bottom + // terms additionally keep conformance requirements 3 and 4 satisfied at + // every coded frame size, which the checks below can only cover for g_w and + // g_h. + if (left >= max_width || right >= max_width || left + right >= max_width) { + ERROR( + "crop_win_left_offset and crop_win_right_offset must each be less " + "than the frame width, and must not together span it"); + } + if (top >= max_height || bottom >= max_height || + top + bottom >= max_height) { + ERROR( + "crop_win_top_offset and crop_win_bottom_offset must each be less " + "than the frame height, and must not together span it"); + } + const int LeftPosX = (left * (int)cfg->g_w) / max_width; + const int RightPosX = + (int)cfg->g_w - 1 - ((right * (int)cfg->g_w) / max_width); + const int TopPosY = (top * (int)cfg->g_h) / max_height; + const int BottomPosY = + (int)cfg->g_h - 1 - ((bottom * (int)cfg->g_h) / max_height); + // Conformance requirement 3: LeftPosX<= RightPosX + if (LeftPosX > RightPosX) { + ERROR( + "crop_win_left_offset and crop_win_right_offset leave no samples in " + "the frame"); + } + // Conformance requirement 4: TopPosY <= BottomPosY + if (TopPosY > BottomPosY) { + ERROR( + "crop_win_top_offset and crop_win_bottom_offset leave no samples in " + "the frame"); + } + } RANGE_CHECK_HI(extra_cfg, sharpness, 7); RANGE_CHECK_HI(extra_cfg, arnr_max_frames, 15); @@ -3341,6 +3393,12 @@ static avm_codec_err_t encoder_encode(avm_codec_alg_priv_t *ctx, av2_check_initial_width(cpi_lap, subsampling_x, subsampling_y); } + // The coded subsampling is only settled by av2_check_initial_width() + // above, so the cropping window's chroma alignment can only be checked + // from here on. + av2_validate_crop_window_chroma_alignment(cpi, cpi->common.width, + cpi->common.height); + // Store the original flags in to the frame buffer. Will extract the // key frame flag when we actually encode this frame. if (av2_receive_raw_frame(cpi, flags | ctx->next_frame_flags, &sd, diff --git a/av2/encoder/encoder.c b/av2/encoder/encoder.c index 78d765ae4e..8c2d05ec8b 100644 --- a/av2/encoder/encoder.c +++ b/av2/encoder/encoder.c @@ -2501,6 +2501,40 @@ static void init_ref_frame_bufs(AV2_COMP *cpi) { } } +void av2_validate_crop_window_chroma_alignment(AV2_COMP *cpi, int frame_width, + int frame_height) { + AV2_COMMON *const cm = &cpi->common; + const SequenceHeader *const seq_params = &cm->seq_params; + const struct CropWindow *const conf = &seq_params->conf; + if (!conf->conf_win_enabled_flag) return; + + const int SubX = seq_params->subsampling_x + 1; + const int SubY = seq_params->subsampling_y + 1; + const int LeftPosX = + (conf->conf_win_left_offset * frame_width) / seq_params->max_frame_width; + const int TopPosY = + (conf->conf_win_top_offset * frame_height) / seq_params->max_frame_height; + + // Conformance requirement 1: LeftPosX == SubX * (LeftPosX/Subx) + if (!seq_params->monochrome && SubX > 1 && + LeftPosX != SubX * (LeftPosX / SubX)) { + avm_internal_error(&cm->error, AVM_CODEC_INVALID_PARAM, + "crop_win_left_offset puts the conformance window left " + "position %d off the chroma grid at coded width %d: it " + "must be a multiple of SubX=%d", + LeftPosX, frame_width, SubX); + } + // Conformance requirement 2: TopPosY == SubY * (TopPosY / SubY) + if (!seq_params->monochrome && SubY > 1 && + TopPosY != SubY * (TopPosY / SubY)) { + avm_internal_error(&cm->error, AVM_CODEC_INVALID_PARAM, + "crop_win_top_offset puts the conformance window top " + "position %d off the chroma grid at coded height %d: it " + "must be a multiple of SubY=%d", + TopPosY, frame_height, SubY); + } +} + void av2_check_initial_width(AV2_COMP *cpi, int subsampling_x, int subsampling_y) { AV2_COMMON *const cm = &cpi->common; @@ -2616,6 +2650,9 @@ void av2_set_frame_size(AV2_COMP *cpi, int width, int height) { av2_noise_estimate_init(&cpi->noise_estimate, cm->width, cm->height); } + + av2_validate_crop_window_chroma_alignment(cpi, cm->width, cm->height); + if (cm->bridge_frame_info.is_bridge_frame) { cm->bridge_frame_info.bridge_frame_max_height = cm->height; cm->bridge_frame_info.bridge_frame_max_width = cm->width; diff --git a/av2/encoder/encoder.h b/av2/encoder/encoder.h index a0b03ae661..5a0d50b0bc 100644 --- a/av2/encoder/encoder.h +++ b/av2/encoder/encoder.h @@ -3123,6 +3123,9 @@ void av2_change_config(AV2_COMP *cpi, const AV2EncoderConfig *oxcf); void av2_check_initial_width(AV2_COMP *cpi, int subsampling_x, int subsampling_y); +void av2_validate_crop_window_chroma_alignment(AV2_COMP *cpi, int frame_width, + int frame_height); + void av2_init_seq_coding_tools(AV2_COMP *cpi, SequenceHeader *seq, AV2_COMMON *cm, const AV2EncoderConfig *oxcf);