RC: Implement using QP from external RC

Bug: b:450252793
Change-Id: I900d66431075c1ba5623d8f6ac33ecbcee669fd3
diff --git a/av1/encoder/av1_ext_ratectrl.c b/av1/encoder/av1_ext_ratectrl.c
index 041a0a5..2b048c8 100644
--- a/av1/encoder/av1_ext_ratectrl.c
+++ b/av1/encoder/av1_ext_ratectrl.c
@@ -119,6 +119,20 @@
   return AOM_CODEC_OK;
 }
 
+aom_codec_err_t av1_extrc_get_encodeframe_decision(
+    AOM_EXT_RATECTRL *ext_ratectrl, int gop_index,
+    aom_rc_encodeframe_decision_t *encode_frame_decision) {
+  assert(ext_ratectrl != NULL);
+  assert(ext_ratectrl->ready && (ext_ratectrl->funcs.rc_type & AOM_RC_QP) != 0);
+  assert(encode_frame_decision != NULL);
+  aom_rc_status_t rc_status = ext_ratectrl->funcs.get_encodeframe_decision(
+      ext_ratectrl->model, gop_index, encode_frame_decision);
+  if (rc_status == AOM_RC_ERROR) {
+    return AOM_CODEC_ERROR;
+  }
+  return AOM_CODEC_OK;
+}
+
 aom_codec_err_t av1_extrc_delete(AOM_EXT_RATECTRL *ext_ratectrl) {
   if (ext_ratectrl == NULL) {
     return AOM_CODEC_INVALID_PARAM;
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 5d47c1c..ed4cdb4 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -3427,6 +3427,24 @@
       }
     }
 
+    if (cpi->ext_ratectrl.ready &&
+        (cpi->ext_ratectrl.funcs.rc_type & AOM_RC_QP) != 0 &&
+        cpi->ext_ratectrl.funcs.get_encodeframe_decision != NULL) {
+      aom_codec_err_t codec_status;
+      aom_rc_encodeframe_decision_t encode_frame_decision;
+      codec_status = av1_extrc_get_encodeframe_decision(
+          &cpi->ext_ratectrl, cpi->gf_frame_index, &encode_frame_decision);
+      if (codec_status != AOM_CODEC_OK) {
+        aom_internal_error(cm->error, codec_status,
+                           "av1_extrc_get_encodeframe_decision() failed");
+      }
+      // If the external model recommends a reserved value, we use the default
+      // q.
+      if (encode_frame_decision.q_index != AOM_DEFAULT_Q) {
+        q = encode_frame_decision.q_index;
+      }
+    }
+
     av1_set_quantizer(cm, q_cfg->qm_minlevel, q_cfg->qm_maxlevel, q,
                       q_cfg->enable_chroma_deltaq, q_cfg->enable_hdr_deltaq,
                       oxcf->mode == ALLINTRA, oxcf->tune_cfg.tuning);
@@ -3568,6 +3586,13 @@
       // Ducky encode currently does not support recode loop.
       loop = 0;
     }
+
+    // Do not recode if external rate control is used.
+    if (cpi->ext_ratectrl.ready &&
+        (cpi->ext_ratectrl.funcs.rc_type & AOM_RC_QP) != 0 &&
+        cpi->ext_ratectrl.funcs.get_encodeframe_decision != NULL) {
+      loop = 0;
+    }
 #if CONFIG_BITRATE_ACCURACY || CONFIG_RD_COMMAND
     loop = 0;  // turn off recode loop when CONFIG_BITRATE_ACCURACY is on
 #endif         // CONFIG_BITRATE_ACCURACY || CONFIG_RD_COMMAND
diff --git a/test/ext_ratectrl_test.cc b/test/ext_ratectrl_test.cc
index c9a5b95..fac4077 100644
--- a/test/ext_ratectrl_test.cc
+++ b/test/ext_ratectrl_test.cc
@@ -78,35 +78,19 @@
   return AOM_RC_OK;
 }
 
-aom_rc_status_t mock_get_encodeframe_decision(
-    aom_rc_model_t ratectrl_model, const int frame_gop_index,
-    aom_rc_encodeframe_decision_t *frame_decision) {
-  (void)ratectrl_model;
-  (void)frame_gop_index;
-  (void)frame_decision;
-  return AOM_RC_OK;
-}
-
-aom_rc_status_t mock_update_encodeframe_result(
-    aom_rc_model_t ratectrl_model,
-    const aom_rc_encodeframe_result_t *encode_frame_result) {
-  (void)ratectrl_model;
-  (void)encode_frame_result;
-  return AOM_RC_OK;
-}
-
 class ExtRateCtrlTest : public ::libaom_test::EncoderTest,
                         public ::libaom_test::CodecTestWith2Params<int, int> {
  protected:
   ExtRateCtrlTest() : EncoderTest(GET_PARAM(0)), cpu_used_(GET_PARAM(2)) {
     aom_rc_funcs_t *rc_funcs = &rc_funcs_;
     rc_funcs->priv = &g_priv;
+    rc_funcs->rc_type = AOM_RC_QP;
     rc_funcs->create_model = mock_create_model;
     rc_funcs->delete_model = mock_delete_model;
     rc_funcs->send_firstpass_stats = mock_send_firstpass_stats;
     rc_funcs->send_tpl_gop_stats = mock_send_extrc_tpl_gop_stats;
-    rc_funcs->get_encodeframe_decision = mock_get_encodeframe_decision;
-    rc_funcs->update_encodeframe_result = mock_update_encodeframe_result;
+    rc_funcs->get_encodeframe_decision = nullptr;
+    rc_funcs->update_encodeframe_result = nullptr;
   }
   ~ExtRateCtrlTest() override = default;
 
@@ -126,8 +110,10 @@
       encoder->Control(AOME_SET_CPUUSED, cpu_used_);
       encoder->Control(AV1E_SET_EXTERNAL_RATE_CONTROL, &rc_funcs_);
     }
+    current_encoder_ = encoder;
   }
 
+  ::libaom_test::Encoder *current_encoder_;
   aom_rc_funcs_t rc_funcs_;
   int cpu_used_;
 };
@@ -144,4 +130,53 @@
 AV1_INSTANTIATE_TEST_SUITE(ExtRateCtrlTest,
                            ::testing::Values(::libaom_test::kTwoPassGood),
                            ::testing::Values(0));
+
+// Corresponds to QP 43 for 8-bit video.
+const int kFrameQindex = 172;
+
+aom_rc_status_t mock_get_encodeframe_decision_const_q(
+    aom_rc_model_t ratectrl_model, int frame_gop_index,
+    aom_rc_encodeframe_decision_t *frame_decision) {
+  (void)ratectrl_model;
+  (void)frame_gop_index;
+  // Set constant QP.
+  frame_decision->q_index = kFrameQindex;
+  frame_decision->sb_params_list = NULL;  // Initialize to NULL
+  return AOM_RC_OK;
+}
+
+class ExtRateCtrlQpTest : public ExtRateCtrlTest {
+ protected:
+  ExtRateCtrlQpTest() {
+    rc_funcs_.get_encodeframe_decision = mock_get_encodeframe_decision_const_q;
+  }
+  ~ExtRateCtrlQpTest() override = default;
+
+  void SetUp() override {
+    InitializeConfig(static_cast<libaom_test::TestMode>(GET_PARAM(1)));
+    cfg_.g_threads = 1;
+    cfg_.g_limit = kFrameNum;
+    is_create_model_called = false;
+    is_delete_model_called = false;
+  }
+
+  void FramePktHook(const aom_codec_cx_pkt_t *pkt) override {
+    if (pkt->kind != AOM_CODEC_CX_FRAME_PKT) return;
+    int q_index = -1;
+    current_encoder_->Control(AOME_GET_LAST_QUANTIZER, &q_index);
+    std::cout << q_index << std::endl;
+    EXPECT_EQ(q_index, kFrameQindex);
+  }
+};
+
+TEST_P(ExtRateCtrlQpTest, TestExternalRateCtrlConstQp) {
+  ::libaom_test::Y4mVideoSource video("screendata.y4m", 0, kFrameNum);
+  ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  EXPECT_TRUE(is_create_model_called);
+  EXPECT_TRUE(is_delete_model_called);
+}
+
+AV1_INSTANTIATE_TEST_SUITE(ExtRateCtrlQpTest,
+                           ::testing::Values(::libaom_test::kTwoPassGood),
+                           ::testing::Values(0));
 }  // namespace