libaom_qp_extension Change-Id: Ibe814846db72f9bb6d5a1468fa638a72e11ec466
diff --git a/aom/aomcx.h b/aom/aomcx.h index d1aa4ae..111a394 100644 --- a/aom/aomcx.h +++ b/aom/aomcx.h
@@ -293,7 +293,8 @@ /*!\brief Codec control function to set constrained / constant quality level, * unsigned int parameter * - * Valid range: 0..63 + * Valid range: 0..70. Values 64..70 are only valid in constant quality + * mode (#AOM_Q). * * \attention For this value to be used aom_codec_enc_cfg_t::rc_end_usage * must be set to #AOM_CQ or #AOM_Q.
diff --git a/av1/arg_defs.c b/av1/arg_defs.c index 9a3352c..8433b41 100644 --- a/av1/arg_defs.c +++ b/av1/arg_defs.c
@@ -307,8 +307,9 @@ .dist_metric = ARG_DEF_ENUM( NULL, "dist-metric", 1, "Distortion metric to use for in-block optimization", dist_metric_enum), - .cq_level = - ARG_DEF(NULL, "cq-level", 1, "Constant/Constrained Quality level"), + .cq_level = ARG_DEF(NULL, "cq-level", 1, + "Constant/Constrained Quality level (0..70, values " + "64..70 require --end-usage=q)"), .max_intra_rate_pct = ARG_DEF(NULL, "max-intra-rate", 1, "Max I-frame bitrate (pct)"), #if CONFIG_AV1_ENCODER
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c index b43c96d..858b26c 100644 --- a/av1/av1_cx_iface.c +++ b/av1/av1_cx_iface.c
@@ -805,7 +805,10 @@ RANGE_CHECK_HI(extra_cfg, enable_adaptive_sharpness, 1); RANGE_CHECK_HI(extra_cfg, arnr_max_frames, 15); RANGE_CHECK_HI(extra_cfg, arnr_strength, 6); - RANGE_CHECK_HI(extra_cfg, cq_level, 63); + RANGE_CHECK_HI(extra_cfg, cq_level, 70); + if (extra_cfg->cq_level > 63 && cfg->rc_end_usage != AOM_Q) { + ERROR("--cq-level values above 63 can only be used with --end-usage=q"); + } RANGE_CHECK(cfg, g_bit_depth, AOM_BITS_8, AOM_BITS_12); RANGE_CHECK(cfg, g_input_bit_depth, 8, 12); if (cfg->g_input_bit_depth > (unsigned int)cfg->g_bit_depth) @@ -1264,7 +1267,9 @@ extra_cfg->lossless ? 0 : av1_quantizer_to_qindex(cfg->rc_min_quantizer); rc_cfg->worst_allowed_q = extra_cfg->lossless ? 0 : av1_quantizer_to_qindex(cfg->rc_max_quantizer); - rc_cfg->cq_level = av1_quantizer_to_qindex(extra_cfg->cq_level); + rc_cfg->cq_level = av1_quantizer_to_qindex(AOMMIN(extra_cfg->cq_level, 63)); + rc_cfg->cq_level_extended_qindex_offset = + (extra_cfg->cq_level > 63) ? (int)(extra_cfg->cq_level - 63) * 4 : 0; rc_cfg->under_shoot_pct = cfg->rc_undershoot_pct; rc_cfg->over_shoot_pct = cfg->rc_overshoot_pct; rc_cfg->maximum_buffer_size_ms = cfg->rc_buf_sz;
diff --git a/av1/encoder/aq_cyclicrefresh.c b/av1/encoder/aq_cyclicrefresh.c index 99f90fe..f784670 100644 --- a/av1/encoder/aq_cyclicrefresh.c +++ b/av1/encoder/aq_cyclicrefresh.c
@@ -648,7 +648,7 @@ cpi->ppi->gf_group.update_type[cpi->gf_frame_index], layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + cpi->oxcf.mode, cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); av1_set_segdata(seg, CR_SEGMENT_ID_BOOST1, SEG_LVL_ALT_Q, qindex_delta);
diff --git a/av1/encoder/av1_quantize.c b/av1/encoder/av1_quantize.c index 49acf33..a08887d 100644 --- a/av1/encoder/av1_quantize.c +++ b/av1/encoder/av1_quantize.c
@@ -833,8 +833,8 @@ qindex_rdmult, cm->seq_params->bit_depth, cpi->ppi->gf_group.update_type[cpi->gf_frame_index], layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, - is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); const int qindex_change = x->qindex != qindex; if (qindex_change || do_update) {
diff --git a/av1/encoder/encodeframe_utils.h b/av1/encoder/encodeframe_utils.h index 8cd163a..0de2847 100644 --- a/av1/encoder/encodeframe_utils.h +++ b/av1/encoder/encodeframe_utils.h
@@ -318,7 +318,8 @@ return av1_compute_rd_mult( qindex, bit_depth, update_type, layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, is_stat_consumption_stage(cpi), - cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode); + cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); } static inline int do_split_check(BLOCK_SIZE bsize) {
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h index 82e9cab..d8d2f5a 100644 --- a/av1/encoder/encoder.h +++ b/av1/encoder/encoder.h
@@ -592,6 +592,11 @@ */ int cq_level; /*! + * Qindex-unit offset for cq-level values above the normal 0..63 QP range. + * Only used in AOM_Q mode; signaled qindices remain clamped to MAXQ. + */ + int cq_level_extended_qindex_offset; + /*! * Indicates if the encoding mode is vbr, cbr, constrained quality or * constant quality. */
diff --git a/av1/encoder/encoder_utils.c b/av1/encoder/encoder_utils.c index 7e1db0c..00e0b9f 100644 --- a/av1/encoder/encoder_utils.c +++ b/av1/encoder/encoder_utils.c
@@ -527,7 +527,7 @@ cpi->ppi->gf_group.update_type[cpi->gf_frame_index], 0, 15, INTER_FRAME, cpi->oxcf.q_cfg.use_fixed_qp_offsets, is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + cpi->oxcf.mode, cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); } } else { av1_disable_segmentation(seg); @@ -667,6 +667,18 @@ } #endif // !CONFIG_REALTIME_ONLY +static int adjust_qindex_for_extended_cq(const AV1_COMP *const cpi, + int qindex) { + const RateControlCfg *const rc_cfg = &cpi->oxcf.rc_cfg; + const int extended_offset = rc_cfg->cq_level_extended_qindex_offset; + if (rc_cfg->mode != AOM_Q || extended_offset <= 0) return qindex; + + // Match SVT-AV1's extended-CQP shape: push the selected hierarchy qindex + // toward MAXQ by up to half of its remaining headroom at QP 70. + qindex += ((MAXQ - qindex) * extended_offset) / 56; + return clamp(qindex, MINQ, MAXQ); +} + void av1_set_size_dependent_vars(AV1_COMP *cpi, int *q, int *bottom_index, int *top_index) { AV1_COMMON *const cm = &cpi->common; @@ -743,6 +755,17 @@ #endif } + if (cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset > 0 && + cpi->oxcf.rc_cfg.mode == AOM_Q) { + *q = adjust_qindex_for_extended_cq(cpi, *q); + *top_index = *bottom_index = *q; +#if !CONFIG_REALTIME_ONLY + if (is_frame_tpl_eligible(gf_group, cpi->gf_frame_index)) { + cpi->ppi->p_rc.arf_q = *q; + } +#endif + } + // Configure experimental use of segmentation for enhanced coding of // static regions if indicated. // Only allowed in the second pass of a two pass encode, as it requires
diff --git a/av1/encoder/rd.c b/av1/encoder/rd.c index 9a6e89b..151b32e 100644 --- a/av1/encoder/rd.c +++ b/av1/encoder/rd.c
@@ -450,7 +450,8 @@ const FRAME_TYPE frame_type, const int use_fixed_qp_offsets, const int is_stat_consumption_stage, - const aom_tune_metric tuning, const MODE mode) { + const aom_tune_metric tuning, const MODE mode, + const int cq_level_extended_qindex_offset) { int64_t rdmult = av1_compute_rd_mult_based_on_qindex(bit_depth, update_type, qindex, tuning, mode); if (is_stat_consumption_stage && (use_fixed_qp_offsets == 0) && @@ -460,6 +461,10 @@ // ARF boost adjustment rdmult += ((rdmult * rd_boost_factor[boost_index]) >> 7); } + if (cq_level_extended_qindex_offset > 0) { + const int lambda_weight = 128 + cq_level_extended_qindex_offset * 28; + rdmult = (rdmult * lambda_weight) >> 7; + } return rdmult > 0 ? (int)AOMMIN(rdmult, INT_MAX) : 1; } @@ -521,7 +526,8 @@ layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode) / + cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset) / beta); } #endif // !CONFIG_REALTIME_ONLY @@ -805,8 +811,8 @@ qindex_rdmult, cm->seq_params->bit_depth, cpi->ppi->gf_group.update_type[cpi->gf_frame_index], layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, - is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); #if CONFIG_RD_COMMAND if (cpi->oxcf.pass == 2) { const RD_COMMAND *rd_command = &cpi->rd_command;
diff --git a/av1/encoder/rd.h b/av1/encoder/rd.h index 55b17fc..a47bbe5 100644 --- a/av1/encoder/rd.h +++ b/av1/encoder/rd.h
@@ -273,7 +273,8 @@ const FRAME_TYPE frame_type, const int use_fixed_qp_offsets, const int is_stat_consumption_stage, - const aom_tune_metric tuning, const MODE mode); + const aom_tune_metric tuning, const MODE mode, + const int cq_level_extended_qindex_offset); void av1_initialize_rd_consts(struct AV1_COMP *cpi);
diff --git a/av1/encoder/tpl_model.c b/av1/encoder/tpl_model.c index ba54d55..ea7e28b 100644 --- a/av1/encoder/tpl_model.c +++ b/av1/encoder/tpl_model.c
@@ -1446,8 +1446,8 @@ base_qindex, cm->seq_params->bit_depth, cpi->ppi->gf_group.update_type[cpi->gf_frame_index], layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, - is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); if (rdmult < 1) rdmult = 1; av1_set_error_per_bit(&x->errorperbit, rdmult); @@ -2374,8 +2374,8 @@ orig_qindex_rdmult, cm->seq_params->bit_depth, cpi->ppi->gf_group.update_type[cpi->gf_frame_index], layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, - is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); const int new_qindex_rdmult = quant_params->base_qindex + x->rdmult_delta_qindex + @@ -2384,8 +2384,8 @@ new_qindex_rdmult, cm->seq_params->bit_depth, cpi->ppi->gf_group.update_type[cpi->gf_frame_index], layer_depth, boost_index, frame_type, cpi->oxcf.q_cfg.use_fixed_qp_offsets, - is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, - cpi->oxcf.mode); + is_stat_consumption_stage(cpi), cpi->oxcf.tune_cfg.tuning, cpi->oxcf.mode, + cpi->oxcf.rc_cfg.cq_level_extended_qindex_offset); const double scaling_factor = (double)new_rdmult / (double)orig_rdmult;
diff --git a/doc/dev_guide/av1_encoder.dox b/doc/dev_guide/av1_encoder.dox index 460b105..fdf3ba6 100644 --- a/doc/dev_guide/av1_encoder.dox +++ b/doc/dev_guide/av1_encoder.dox
@@ -542,7 +542,8 @@ - <b>--target-bitrate</b> (\ref RateControlCfg.target_bandwidth) - <b>--min-q</b> (\ref RateControlCfg.best_allowed_q) - <b>--max-q</b> (\ref RateControlCfg.worst_allowed_q) -- <b>--cq-level</b> (\ref RateControlCfg.cq_level) +- <b>--cq-level</b> (\ref RateControlCfg.cq_level; 0..70, values above 63 + only in AOM_Q) - <b>--undershoot-pct</b> (\ref RateControlCfg.under_shoot_pct) - <b>--overshoot-pct</b> (\ref RateControlCfg.over_shoot_pct)
diff --git a/test/use_fixed_qp_offsets_test.cc b/test/use_fixed_qp_offsets_test.cc index 1131f8a..7aa1d70 100644 --- a/test/use_fixed_qp_offsets_test.cc +++ b/test/use_fixed_qp_offsets_test.cc
@@ -10,6 +10,7 @@ */ #include "config/aom_config.h" +#include "av1/encoder/av1_quantize.h" #include "gtest/gtest.h" #include "test/codec_factory.h" #include "test/encode_test_driver.h" @@ -26,9 +27,11 @@ #endif const int kUseFixedQPOffsetsMode[] = { 1, 2 }; +const int kCpuUsed = 6; class UseFixedQPOffsetsTest - : public ::libaom_test::CodecTestWith2Params<libaom_test::TestMode, int>, + : public ::libaom_test::CodecTestWith3Params<libaom_test::TestMode, int, + int>, public ::libaom_test::EncoderTest { protected: UseFixedQPOffsetsTest() : EncoderTest(GET_PARAM(0)) {} @@ -39,46 +42,161 @@ cfg_.kf_max_dist = 9999; cfg_.rc_end_usage = AOM_Q; cfg_.use_fixed_qp_offsets = GET_PARAM(2); + frame_qp_ = GET_PARAM(3); + expected_leaf_qindex_ = + av1_quantizer_to_qindex(frame_qp_ > 63 ? 63 : frame_qp_); } void PreEncodeFrameHook(::libaom_test::VideoSource *video, ::libaom_test::Encoder *encoder) override { if (video->frame() == 0) { - encoder->Control(AOME_SET_CPUUSED, 6); + encoder->Control(AOME_SET_CPUUSED, kCpuUsed); encoder->Control(AOME_SET_CQ_LEVEL, frame_qp_); } } void PostEncodeFrameHook(::libaom_test::Encoder *encoder) override { - int qp = 0; - encoder->Control(AOME_GET_LAST_QUANTIZER_64, &qp); + int q = 0; + encoder->Control(AOME_GET_LAST_QUANTIZER, &q); // If a call to encoder->EncodeFrame() results in a last QP of 0, // interpret as the frame being read into the lookahead buffer. - if (qp == 0) return; + if (q == 0) return; if (cfg_.use_fixed_qp_offsets == 2) { // Setting use_fixed_qp_offsets = 2 means every frame should use the same - // QP - ASSERT_EQ(qp, frame_qp_); + // qindex. + ASSERT_EQ(q, expected_leaf_qindex_); } else { - ASSERT_LE(qp, frame_qp_); + ASSERT_LE(q, expected_leaf_qindex_); } } void DoTest() { ::libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288, 30, 1, 0, 33); - frame_qp_ = 35; ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); } int frame_qp_; + int expected_leaf_qindex_; }; +#if !CONFIG_REALTIME_ONLY +const int kFrameCount = 17; + +class ExtendedCqLevelEndToEndTest + : public ::libaom_test::CodecTestWithParam<libaom_test::TestMode>, + public ::libaom_test::EncoderTest { + protected: + ExtendedCqLevelEndToEndTest() + : EncoderTest(GET_PARAM(0)), encoding_mode_(GET_PARAM(1)) {} + ~ExtendedCqLevelEndToEndTest() override = default; + + void SetUp() override { InitializeConfig(encoding_mode_); } + + void BeginPassHook(unsigned int) override { + encoded_bytes_ = 0; + decoded_frames_ = 0; + } + + void PreEncodeFrameHook(::libaom_test::VideoSource *video, + ::libaom_test::Encoder *encoder) override { + if (video->frame() == 0) { + encoder->Control(AOME_SET_CPUUSED, kCpuUsed); + encoder->Control(AOME_SET_CQ_LEVEL, cq_level_); + encoder->Control(AOME_SET_ENABLEAUTOALTREF, 1); + encoder->Control(AV1E_SET_FRAME_PARALLEL_DECODING, 0); + encoder->Control(AV1E_SET_DELTAQ_MODE, 0); + encoder->Control(AV1E_SET_ENABLE_TPL_MODEL, 0); + encoder->Control(AV1E_SET_ENABLE_KEYFRAME_FILTERING, 1); + encoder->Control(AV1E_SET_MIN_GF_INTERVAL, 16); + encoder->Control(AV1E_SET_MAX_GF_INTERVAL, 16); + encoder->Control(AV1E_SET_GF_MIN_PYRAMID_HEIGHT, 4); + encoder->Control(AV1E_SET_GF_MAX_PYRAMID_HEIGHT, 4); + } + } + + void FramePktHook(const aom_codec_cx_pkt_t *pkt) override { + encoded_bytes_ += pkt->data.frame.sz; + } + + void DecompressedFrameHook(const aom_image_t &, aom_codec_pts_t) override { + ++decoded_frames_; + } + + void EncodeAtCqLevel(int cq_level, size_t *encoded_bytes) { + cq_level_ = cq_level; + InitializeConfig(encoding_mode_); + cfg_.rc_end_usage = AOM_Q; + cfg_.use_fixed_qp_offsets = 1; + cfg_.g_lag_in_frames = 19; + cfg_.kf_min_dist = 65; + cfg_.kf_max_dist = 65; + + ::libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, + 288, 30, 1, 0, kFrameCount); + ASSERT_NO_FATAL_FAILURE(RunLoop(&video)); + EXPECT_EQ(decoded_frames_, kFrameCount); + EXPECT_GT(encoded_bytes_, 0u); + *encoded_bytes = encoded_bytes_; + } + + private: + const ::libaom_test::TestMode encoding_mode_; + int cq_level_; + size_t encoded_bytes_; + int decoded_frames_; +}; +#endif // !CONFIG_REALTIME_ONLY + TEST_P(UseFixedQPOffsetsTest, TestQPOffsets) { DoTest(); } +#if !CONFIG_REALTIME_ONLY +TEST_P(ExtendedCqLevelEndToEndTest, + HighCqLevelsProduceDecodableMonotonicBitstreams) { + size_t previous_encoded_bytes = 0; + ASSERT_NO_FATAL_FAILURE(EncodeAtCqLevel(63, &previous_encoded_bytes)); + for (int cq_level = 64; cq_level <= 70; ++cq_level) { + size_t encoded_bytes = 0; + ASSERT_NO_FATAL_FAILURE(EncodeAtCqLevel(cq_level, &encoded_bytes)); + EXPECT_LT(encoded_bytes, previous_encoded_bytes) + << "cq_level = " << cq_level; + previous_encoded_bytes = encoded_bytes; + } +} +#endif // !CONFIG_REALTIME_ONLY + +TEST(ExtendedCqLevelTest, AcceptsHighCqLevelOnlyInConstantQualityMode) { + aom_codec_enc_cfg_t cfg; + ASSERT_EQ(AOM_CODEC_OK, aom_codec_enc_config_default(aom_codec_av1_cx(), &cfg, + AOM_USAGE_REALTIME)); + + cfg.rc_end_usage = AOM_Q; + aom_codec_ctx_t enc_q; + ASSERT_EQ(AOM_CODEC_OK, + aom_codec_enc_init(&enc_q, aom_codec_av1_cx(), &cfg, 0)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_control(&enc_q, AOME_SET_CQ_LEVEL, 70)); + EXPECT_EQ(AOM_CODEC_INVALID_PARAM, + aom_codec_control(&enc_q, AOME_SET_CQ_LEVEL, 71)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc_q)); + + cfg.rc_end_usage = AOM_CQ; + aom_codec_ctx_t enc_cq; + ASSERT_EQ(AOM_CODEC_OK, + aom_codec_enc_init(&enc_cq, aom_codec_av1_cx(), &cfg, 0)); + EXPECT_EQ(AOM_CODEC_INVALID_PARAM, + aom_codec_control(&enc_cq, AOME_SET_CQ_LEVEL, 70)); + EXPECT_EQ(AOM_CODEC_OK, aom_codec_destroy(&enc_cq)); +} + AV1_INSTANTIATE_TEST_SUITE(UseFixedQPOffsetsTest, ::testing::ValuesIn(kTestMode), - ::testing::ValuesIn(kUseFixedQPOffsetsMode)); + ::testing::ValuesIn(kUseFixedQPOffsetsMode), + ::testing::Values(35, 70)); + +#if !CONFIG_REALTIME_ONLY +AV1_INSTANTIATE_TEST_SUITE(ExtendedCqLevelEndToEndTest, + ::testing::Values(::libaom_test::kOnePassGood)); +#endif // !CONFIG_REALTIME_ONLY } // namespace