Commit 164fedb418 for aom

commit 164fedb4187c9bed9eb36c0bca4a4fadfde114a6
Author: James Zern <jzern@google.com>
Date:   Sat Nov 7 21:28:14 2026 -0800

    tpl_model: fix crash on early pass 2 flush

    When the encoder is flushed early during pass 2 (e.g., on process
    termination), the lookahead buffer may have fewer frames than the
    first-pass GOP interval (`gf_intervals[] > 16`). In
    `av1_tpl_setup_stats()` (`approx_gop_eval=1`),
    `init_gop_frames_for_tpl()` stops once lookahead frames are exhausted,
    leaving `tpl_gf_group_frames` at or below `gf_group->arf_index`.

    Guard the `beta[0]`/`beta[1]` calculation in `av1_tpl_setup_stats()`
    with `tpl_gf_group_frames > gf_group->arf_index` to avoid negative
    indexing (`frame_idx_1 = -1`) and reading unpopulated `ARF_UPDATE`
    stats. Also check `gf_frame_index` bounds, `tpl_frame->is_valid`, and
    `tpl_frame->tpl_stats_ptr` in `get_frame_importance()`, returning `1.0`
    when TPL stats are unavailable.

    BUG=573628429

    Change-Id: Ife8267ecc1f71955a7d59aee4633522ab60f801d

diff --git a/av1/encoder/tpl_model.c b/av1/encoder/tpl_model.c
index 246193f5c0..94f4771d57 100644
--- a/av1/encoder/tpl_model.c
+++ b/av1/encoder/tpl_model.c
@@ -2007,7 +2007,9 @@ static inline int skip_tpl_for_frame(const GF_GROUP *gf_group, int frame_idx,
  */
 static double get_frame_importance(const TplParams *tpl_data,
                                    int gf_frame_index) {
+  if (gf_frame_index < 0 || gf_frame_index >= MAX_TPL_FRAME_IDX) return 1.0;
   const TplDepFrame *tpl_frame = &tpl_data->tpl_frame[gf_frame_index];
+  if (!tpl_frame->is_valid || tpl_frame->tpl_stats_ptr == NULL) return 1.0;
   const TplDepStats *tpl_stats = tpl_frame->tpl_stats_ptr;

   const int tpl_stride = tpl_frame->stride;
@@ -2293,18 +2295,20 @@ int av1_tpl_setup_stats(AV1_COMP *cpi, int approx_gop_eval,
   assert(gf_group->arf_index >= 0);

   double beta[2] = { 0.0 };
-  const int frame_idx_0 = gf_group->arf_index;
-  const int frame_idx_1 =
-      AOMMIN(tpl_gf_group_frames - 1, gf_group->arf_index + 1);
-  beta[0] = get_frame_importance(tpl_data, frame_idx_0);
-  beta[1] = get_frame_importance(tpl_data, frame_idx_1);
-  if (approx_gop_eval && cpi->oxcf.speed == 4) {
-    if (frame_params->frame_type == KEY_FRAME) {
-      tpl_data->tpl_stats_buffer[0].use_pred_sad =
-          (cpi->ppi->p_rc.baseline_gf_interval >= 32 && beta[0] <= 1.48);
-    }
-    if (!tpl_data->tpl_stats_buffer[0].use_pred_sad && beta[0] <= 1.42) {
-      beta[0] = 1.40;
+  if (tpl_gf_group_frames > gf_group->arf_index) {
+    const int frame_idx_0 = gf_group->arf_index;
+    const int frame_idx_1 =
+        AOMMIN(tpl_gf_group_frames - 1, gf_group->arf_index + 1);
+    beta[0] = get_frame_importance(tpl_data, frame_idx_0);
+    beta[1] = get_frame_importance(tpl_data, frame_idx_1);
+    if (approx_gop_eval && cpi->oxcf.speed == 4) {
+      if (frame_params->frame_type == KEY_FRAME) {
+        tpl_data->tpl_stats_buffer[0].use_pred_sad =
+            (cpi->ppi->p_rc.baseline_gf_interval >= 32 && beta[0] <= 1.48);
+      }
+      if (!tpl_data->tpl_stats_buffer[0].use_pred_sad && beta[0] <= 1.42) {
+        beta[0] = 1.40;
+      }
     }
   }
 #if CONFIG_COLLECT_COMPONENT_TIMING
diff --git a/test/cancellation_test.cc b/test/cancellation_test.cc
index e0a6f991e9..cdf2e1c11f 100644
--- a/test/cancellation_test.cc
+++ b/test/cancellation_test.cc
@@ -89,6 +89,18 @@ class CancellationTest
     ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
   }

+  void DoSimulateCancellationPass2Static(int limit, int cancel_after) {
+    ::libaom_test::DummyVideoSource raw_video;
+    raw_video.SetSize(256, 256);
+    raw_video.set_limit(limit);
+
+    CancellableVideoSource video(&raw_video);
+    video.set_cancel_after(cancel_after);
+
+    // This should not crash
+    ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  }
+
  private:
   int tile_columns_;
   int tile_rows_;
@@ -104,6 +116,11 @@ TEST_P(CancellationTest, SimulateCancellationPass2Threads4) {
                               /*tile_rows=*/1);
 }

+TEST_P(CancellationTest, SimulateCancellationPass2LongGop) {
+  DoSimulateCancellationPass2Static(/*limit=*/40, /*cancel_after=*/20);
+  DoSimulateCancellationPass2Static(/*limit=*/60, /*cancel_after=*/40);
+}
+
 // Test 2-pass good quality
 AV1_INSTANTIATE_TEST_SUITE(CancellationTest,
                            ::testing::Values(::libaom_test::kTwoPassGood));