RC: Implement send_firstpass and tests

Bug: b:450252793
Change-Id: Ie9135bb4974b886f5b55ee1bb4369e6d903ef016
diff --git a/av1/encoder/av1_ext_ratectrl.c b/av1/encoder/av1_ext_ratectrl.c
index bf24fb9..65e1092 100644
--- a/av1/encoder/av1_ext_ratectrl.c
+++ b/av1/encoder/av1_ext_ratectrl.c
@@ -50,6 +50,61 @@
   return AOM_CODEC_OK;
 }
 
+static void gen_rc_firstpass_stats(const FIRSTPASS_STATS *stats,
+                                   aom_rc_frame_stats_t *rc_frame_stats) {
+  rc_frame_stats->frame = stats->frame;
+  rc_frame_stats->weight = stats->weight;
+  rc_frame_stats->intra_error = stats->intra_error;
+  rc_frame_stats->coded_error = stats->coded_error;
+  rc_frame_stats->sr_coded_error = stats->sr_coded_error;
+  rc_frame_stats->frame_avg_wavelet_energy = stats->frame_avg_wavelet_energy;
+  rc_frame_stats->pcnt_inter = stats->pcnt_inter;
+  rc_frame_stats->pcnt_motion = stats->pcnt_motion;
+  rc_frame_stats->pcnt_second_ref = stats->pcnt_second_ref;
+  rc_frame_stats->pcnt_neutral = stats->pcnt_neutral;
+  rc_frame_stats->intra_skip_pct = stats->intra_skip_pct;
+  rc_frame_stats->inactive_zone_rows = stats->inactive_zone_rows;
+  rc_frame_stats->inactive_zone_cols = stats->inactive_zone_cols;
+  rc_frame_stats->MVr = stats->MVr;
+  rc_frame_stats->mvr_abs = stats->mvr_abs;
+  rc_frame_stats->MVc = stats->MVc;
+  rc_frame_stats->mvc_abs = stats->mvc_abs;
+  rc_frame_stats->MVrv = stats->MVrv;
+  rc_frame_stats->MVcv = stats->MVcv;
+  rc_frame_stats->mv_in_out_count = stats->mv_in_out_count;
+  rc_frame_stats->duration = stats->duration;
+  rc_frame_stats->count = stats->count;
+  rc_frame_stats->new_mv_count = stats->new_mv_count;
+  rc_frame_stats->raw_error_stdev = stats->raw_error_stdev;
+  rc_frame_stats->is_flash = stats->is_flash;
+  rc_frame_stats->noise_var = stats->noise_var;
+  rc_frame_stats->cor_coeff = stats->cor_coeff;
+  rc_frame_stats->log_intra_error = stats->log_intra_error;
+  rc_frame_stats->log_coded_error = stats->log_coded_error;
+}
+
+aom_codec_err_t av1_extrc_send_firstpass_stats(
+    AOM_EXT_RATECTRL *ext_ratectrl, const FIRSTPASS_INFO *first_pass_info) {
+  assert(first_pass_info != NULL);
+  assert(ext_ratectrl != NULL);
+  if (ext_ratectrl->ready) {
+    aom_rc_status_t rc_status;
+    aom_rc_firstpass_stats_t *rc_firstpass_stats =
+        &ext_ratectrl->rc_firstpass_stats;
+    assert(rc_firstpass_stats->num_frames == first_pass_info->stats_buf_size);
+    for (int i = 0; i < rc_firstpass_stats->num_frames; ++i) {
+      gen_rc_firstpass_stats(&first_pass_info->stats_buf[i],
+                             &rc_firstpass_stats->frame_stats[i]);
+    }
+    rc_status = ext_ratectrl->funcs.send_firstpass_stats(ext_ratectrl->model,
+                                                         rc_firstpass_stats);
+    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/pass2_strategy.c b/av1/encoder/pass2_strategy.c
index a1d6640..9cd5225 100644
--- a/av1/encoder/pass2_strategy.c
+++ b/av1/encoder/pass2_strategy.c
@@ -3757,6 +3757,16 @@
     return;
   }
 
+  if (cpi->common.current_frame.frame_number == 0 &&
+      cpi->ext_ratectrl.funcs.send_firstpass_stats != NULL) {
+    const aom_codec_err_t codec_status = av1_extrc_send_firstpass_stats(
+        &cpi->ext_ratectrl, &cpi->ppi->twopass.firstpass_info);
+    if (codec_status != AOM_CODEC_OK) {
+      aom_internal_error(cpi->common.error, codec_status,
+                         "av1_extrc_send_firstpass_stats() failed");
+    }
+  }
+
   const FIRSTPASS_STATS *const start_pos = cpi->twopass_frame.stats_in;
   int update_total_stats = 0;
 
diff --git a/test/ext_ratectrl_test.cc b/test/ext_ratectrl_test.cc
index ef7f92e..a84def7 100644
--- a/test/ext_ratectrl_test.cc
+++ b/test/ext_ratectrl_test.cc
@@ -33,6 +33,9 @@
 // A flag to indicate if delete_model() is called.
 bool is_delete_model_called = false;
 
+// A flag to indicate if send_firstpass_stats() is called.
+bool is_send_firstpass_stats_called = false;
+
 aom_rc_status_t mock_create_model(void *priv,
                                   const aom_rc_config_t *ratectrl_config,
                                   aom_rc_model_t *ratectrl_model) {
@@ -54,8 +57,11 @@
 aom_rc_status_t mock_send_firstpass_stats(
     aom_rc_model_t ratectrl_model,
     const aom_rc_firstpass_stats_t *firstpass_stats) {
-  (void)ratectrl_model;
-  (void)firstpass_stats;
+  EXPECT_NE(ratectrl_model, nullptr);
+  EXPECT_NE(firstpass_stats, nullptr);
+  EXPECT_EQ(firstpass_stats->num_frames, kFrameNum);
+  EXPECT_NE(firstpass_stats->frame_stats, nullptr);
+  is_send_firstpass_stats_called = true;
   return AOM_RC_OK;
 }
 
@@ -94,6 +100,9 @@
     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;
+    is_send_firstpass_stats_called = false;
   }
 
   void PreEncodeFrameHook(::libaom_test::VideoSource *video,
@@ -112,6 +121,7 @@
   ::libaom_test::Y4mVideoSource video("screendata.y4m", 0, kFrameNum);
   ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
   EXPECT_TRUE(is_create_model_called);
+  EXPECT_TRUE(is_send_firstpass_stats_called);
   EXPECT_TRUE(is_delete_model_called);
 }