Check that TPL stats are compatible with GopStruct

Also:

1. Added some asserts. Eventually these should be replaced with real
runtime checks, to facilitate debugging and to ensure that errors are
caught in opt builds.

2. Disabled TestGetGopEncodeInfo. With the existing data files, the
GOP structure resulting from the first pass stats doesn't match the
TPL stats, resulting in an error from ValidateTplStats. It should be
reenabled after regenerating data files which are consistent with
each other.

Bug: b/240623286
Change-Id: Iabb0f30bd9dbc82f4b8b91a6373dd3b2b16ce120
diff --git a/av1/ratectrl_qmode.cc b/av1/ratectrl_qmode.cc
index e12f52e..0d9b43e 100644
--- a/av1/ratectrl_qmode.cc
+++ b/av1/ratectrl_qmode.cc
@@ -13,6 +13,7 @@
 #include <algorithm>
 #include <cassert>
 #include <climits>
+#include <functional>
 #include <numeric>
 #include <sstream>
 #include <vector>
@@ -931,6 +932,36 @@
   }
   return { AOM_CODEC_OK, "" };
 }
+
+Status ValidateTplStats(const GopStruct &gop_struct,
+                        const TplGopStats &tpl_gop_stats) {
+  constexpr char kAdvice[] =
+      "Do the current RateControlParam settings match those used to generate "
+      "the TPL stats?";
+  if (gop_struct.gop_frame_list.size() !=
+      tpl_gop_stats.frame_stats_list.size()) {
+    std::ostringstream error_message;
+    error_message << "Frame count of GopStruct ("
+                  << gop_struct.gop_frame_list.size()
+                  << ") doesn't match frame count of TPL stats ("
+                  << tpl_gop_stats.frame_stats_list.size() << "). " << kAdvice;
+    return { AOM_CODEC_INVALID_PARAM, error_message.str() };
+  }
+  for (int i = 0; i < static_cast<int>(gop_struct.gop_frame_list.size()); ++i) {
+    const bool is_ref_frame = gop_struct.gop_frame_list[i].update_ref_idx >= 0;
+    const bool has_tpl_stats =
+        !tpl_gop_stats.frame_stats_list[i].block_stats_list.empty();
+    if (is_ref_frame && !has_tpl_stats) {
+      std::ostringstream error_message;
+      error_message << "The frame with global_coding_idx "
+                    << gop_struct.gop_frame_list[i].global_coding_idx
+                    << " is a reference frame, but has no TPL stats. "
+                    << kAdvice;
+      return { AOM_CODEC_INVALID_PARAM, error_message.str() };
+    }
+  }
+  return { AOM_CODEC_OK, "" };
+}
 }  // namespace
 
 StatusOr<TplFrameDepStats> CreateTplFrameDepStatsWithoutPropagation(
@@ -981,6 +1012,7 @@
     ref_coding_idx_list[i] = -1;
     int ref_frame_index = unit_dep_stats.ref_frame_index[i];
     if (ref_frame_index != -1) {
+      assert(ref_frame_index < static_cast<int>(ref_frame_table.size()));
       ref_coding_idx_list[i] = ref_frame_table[ref_frame_index].coding_idx;
       ref_frame_count++;
     }
@@ -1065,8 +1097,12 @@
       if (ref_frame_count == 0) continue;
       for (int i = 0; i < kBlockRefCount; ++i) {
         if (ref_coding_idx_list[i] == -1) continue;
+        assert(
+            ref_coding_idx_list[i] <
+            static_cast<int>(tpl_gop_dep_stats->frame_dep_stats_list.size()));
         TplFrameDepStats &ref_frame_dep_stats =
             tpl_gop_dep_stats->frame_dep_stats_list[ref_coding_idx_list[i]];
+        assert(!ref_frame_dep_stats.unit_stats.empty());
         const auto &mv = unit_dep_stats.mv[i];
         const int mv_row = GetFullpelValue(mv.row, mv.subpel_bits);
         const int mv_col = GetFullpelValue(mv.col, mv.subpel_bits);
@@ -1116,9 +1152,8 @@
     // It's not the first GOP, so ref_frame_table must be valid.
     assert(static_cast<int>(ref_frame_table.size()) ==
            rc_param_.ref_frame_table_size);
-    assert(std::all_of(
-        ref_frame_table.begin(), ref_frame_table.end(),
-        [](const GopFrame &gop_frame) { return gop_frame.is_valid; }));
+    assert(std::all_of(ref_frame_table.begin(), ref_frame_table.end(),
+                       std::mem_fn(&GopFrame::is_valid)));
     // Reset the frame processing order of the initial ref_frame_table.
     for (GopFrame &gop_frame : ref_frame_table) gop_frame.coding_idx = -1;
   }
@@ -1129,6 +1164,8 @@
     if (gop_frame.is_key_frame) {
       ref_frame_table.assign(rc_param_.ref_frame_table_size, gop_frame);
     } else if (gop_frame.update_ref_idx != -1) {
+      assert(gop_frame.update_ref_idx <
+             static_cast<int>(ref_frame_table.size()));
       ref_frame_table[gop_frame.update_ref_idx] = gop_frame;
     }
     ref_frame_table_list.push_back(ref_frame_table);
@@ -1185,6 +1222,11 @@
 StatusOr<GopEncodeInfo> AV1RateControlQMode::GetGopEncodeInfo(
     const GopStruct &gop_struct, const TplGopStats &tpl_gop_stats,
     const RefFrameTable &ref_frame_table_snapshot_init) {
+  Status status = ValidateTplStats(gop_struct, tpl_gop_stats);
+  if (!status.ok()) {
+    return status;
+  }
+
   const std::vector<RefFrameTable> ref_frame_table_list =
       GetRefFrameTableList(gop_struct, ref_frame_table_snapshot_init);
 
diff --git a/test/ratectrl_qmode_test.cc b/test/ratectrl_qmode_test.cc
index ae516e9..1678beb 100644
--- a/test/ratectrl_qmode_test.cc
+++ b/test/ratectrl_qmode_test.cc
@@ -1036,7 +1036,11 @@
               ElementsAre(21, 9, 30, 30, 16, 14, 21, 9, 30, 12, 16, 2, 30, 10));
 }
 
-TEST(RateControlQModeTest, TestGetGopEncodeInfo) {
+// TODO(b/240623286): Reenable after regenerating data files which are
+// consistent with each other. With the existing files, the GOP structure
+// resulting from the first pass stats doesn't match the TPL stats, resulting
+// in an error from ValidateTplStats.
+TEST(RateControlQModeTest, DISABLED_TestGetGopEncodeInfo) {
   FirstpassInfo firstpass_info;
   ASSERT_NO_FATAL_FAILURE(
       ReadFirstpassInfo("firstpass_stats", &firstpass_info));
@@ -1075,6 +1079,43 @@
   }
 }
 
+TEST(RateControlQModeTest, GetGopEncodeInfoWrongGopSize) {
+  GopStruct gop_struct;
+  gop_struct.gop_frame_list.assign(7, GopFrameInvalid());
+  TplGopStats tpl_gop_stats;
+  tpl_gop_stats.frame_stats_list.assign(
+      5, CreateToyTplFrameStatsWithDiffSizes(8, 8));
+  AV1RateControlQMode rc;
+  const Status status =
+      rc.GetGopEncodeInfo(gop_struct, tpl_gop_stats, RefFrameTable()).status();
+  EXPECT_EQ(status.code, AOM_CODEC_INVALID_PARAM);
+  EXPECT_THAT(status.message,
+              HasSubstr("Frame count of GopStruct (7) doesn't match frame "
+                        "count of TPL stats (5)"));
+}
+
+TEST(RateControlQModeTest, GetGopEncodeInfoRefFrameMissingBlockStats) {
+  GopStruct gop_struct;
+  for (int i = 0; i < 4; ++i) {
+    gop_struct.gop_frame_list.push_back(
+        GopFrameBasic(0, 0, i, i, 0, i, GopFrameType::kRegularLeaf));
+  }
+  // Only frame 2 is a reference frame.
+  gop_struct.gop_frame_list[2].update_ref_idx = 1;
+
+  // None of the frames have TPL block stats.
+  TplGopStats tpl_gop_stats;
+  tpl_gop_stats.frame_stats_list.assign(4, { 8, 176, 144, {} });
+
+  AV1RateControlQMode rc;
+  const Status status =
+      rc.GetGopEncodeInfo(gop_struct, tpl_gop_stats, RefFrameTable()).status();
+  EXPECT_EQ(status.code, AOM_CODEC_INVALID_PARAM);
+  EXPECT_THAT(status.message,
+              HasSubstr("The frame with global_coding_idx 2 is a reference "
+                        "frame, but has no TPL stats"));
+}
+
 // MockRateControlQMode is provided for the use of clients of libaom, but it's
 // not expected that it will be used in any real libaom tests.
 // This simple "toy" test exists solely to verify the integration of gmock into