L2E: Add more unit tests for GOP API
authorCheng Chen <chengchen@google.com>
Wed, 20 Jul 2022 22:35:33 +0000 (15:35 -0700)
committerCheng Chen <chengchen@google.com>
Fri, 22 Jul 2022 05:46:08 +0000 (22:46 -0700)
Add unit tests for a 4 frame video, which could be considered as a
corner case.

Three different GOP settings are tested and verified as valid.
(1). The first GOP has 3 coding frames, no alt ref.
     The second GOP has 1 coding frame, no alt ref.
     The numer of coding frames is 4.
     Their frame types are: keyframe, inter_frame, inter_frame,
     golden_frame.

(2). The first GOP has 4 coding frames, use alt ref.
     The second GOP has 1 coding frame, which is the overlay of
     the first GOP's alt ref frame.
     The numer of coding frames is 5.
     Their types are: keyframe, alt_ref, inter_frame, inter_frame,
     overlay_frame.

(3). Only one GOP with 4 coding frames, do not use alt ref.
     The numer of coding frames is 4.
     Their types are: keyframe, inter_frame, inter_frame, inter_frame.

Change-Id: I4079ff5065da79834b363b1e1976f65efed3f91f

test/test.mk
test/vp9_ext_ratectrl_test.cc

index 6df4572..f60d8f8 100644 (file)
@@ -59,6 +59,7 @@ LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += svc_test.h
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += svc_end_to_end_test.cc
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += timestamp_test.cc
 LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += vp9_ext_ratectrl_test.cc
+LIBVPX_TEST_SRCS-$(CONFIG_VP9_ENCODER) += ../vp9/simple_encode.h
 
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.cc
 LIBVPX_TEST_SRCS-yes                   += decode_test_driver.h
index 68703b7..c954495 100644 (file)
@@ -16,6 +16,7 @@
 #include "test/util.h"
 #include "test/yuv_video_source.h"
 #include "third_party/googletest/src/include/gtest/gtest.h"
+#include "vp9/simple_encode.h"
 #include "vpx/vpx_ext_ratectrl.h"
 #include "vpx_dsp/vpx_dsp_common.h"
 
@@ -25,6 +26,7 @@ constexpr int kModelMagicNumber = 51396;
 constexpr uintptr_t PrivMagicNumber = 5566;
 constexpr int kFrameNum = 5;
 constexpr int kFrameNumGOP = 30;
+constexpr int kFrameNumGOPShort = 4;
 constexpr int kLosslessCodingIndex = 2;
 constexpr int kFixedGOPSize = 9;
 // The range check in vp9_cx_iface.c shows that the max
@@ -38,6 +40,7 @@ constexpr int kDefaultMaxGfInterval = 16;
 // The numbers below are from manual inspection.
 constexpr int kReadMinGfInterval = 5;
 constexpr int kReadMaxGfInterval = 13;
+const char kTestFileName[] = "bus_352x288_420_f20_b8.yuv";
 
 struct ToyRateCtrl {
   int magic_number;
@@ -50,12 +53,12 @@ struct ToyRateCtrl {
 
 vpx_rc_status_t rc_create_model(void *priv,
                                 const vpx_rc_config_t *ratectrl_config,
-                                vpx_rc_model_t *rate_ctrl_model_pt) {
+                                vpx_rc_model_t *rate_ctrl_model_ptr) {
   ToyRateCtrl *toy_rate_ctrl = new (std::nothrow) ToyRateCtrl;
   if (toy_rate_ctrl == nullptr) return VPX_RC_ERROR;
   toy_rate_ctrl->magic_number = kModelMagicNumber;
   toy_rate_ctrl->coding_index = -1;
-  *rate_ctrl_model_pt = toy_rate_ctrl;
+  *rate_ctrl_model_ptr = toy_rate_ctrl;
   EXPECT_EQ(priv, reinterpret_cast<void *>(PrivMagicNumber));
   EXPECT_EQ(ratectrl_config->frame_width, 352);
   EXPECT_EQ(ratectrl_config->frame_height, 288);
@@ -68,7 +71,7 @@ vpx_rc_status_t rc_create_model(void *priv,
 
 vpx_rc_status_t rc_create_model_gop(void *priv,
                                     const vpx_rc_config_t *ratectrl_config,
-                                    vpx_rc_model_t *rate_ctrl_model_pt) {
+                                    vpx_rc_model_t *rate_ctrl_model_ptr) {
   ToyRateCtrl *toy_rate_ctrl = new (std::nothrow) ToyRateCtrl;
   if (toy_rate_ctrl == nullptr) return VPX_RC_ERROR;
   toy_rate_ctrl->magic_number = kModelMagicNumber;
@@ -76,7 +79,7 @@ vpx_rc_status_t rc_create_model_gop(void *priv,
   toy_rate_ctrl->frames_since_key = 0;
   toy_rate_ctrl->show_index = 0;
   toy_rate_ctrl->coding_index = 0;
-  *rate_ctrl_model_pt = toy_rate_ctrl;
+  *rate_ctrl_model_ptr = toy_rate_ctrl;
   EXPECT_EQ(priv, reinterpret_cast<void *>(PrivMagicNumber));
   EXPECT_EQ(ratectrl_config->frame_width, 640);
   EXPECT_EQ(ratectrl_config->frame_height, 360);
@@ -87,6 +90,27 @@ vpx_rc_status_t rc_create_model_gop(void *priv,
   return VPX_RC_OK;
 }
 
+vpx_rc_status_t rc_create_model_gop_short(
+    void *priv, const vpx_rc_config_t *ratectrl_config,
+    vpx_rc_model_t *rate_ctrl_model_ptr) {
+  ToyRateCtrl *toy_rate_ctrl = new (std::nothrow) ToyRateCtrl;
+  if (toy_rate_ctrl == nullptr) return VPX_RC_ERROR;
+  toy_rate_ctrl->magic_number = kModelMagicNumber;
+  toy_rate_ctrl->gop_global_index = 0;
+  toy_rate_ctrl->frames_since_key = 0;
+  toy_rate_ctrl->show_index = 0;
+  toy_rate_ctrl->coding_index = 0;
+  *rate_ctrl_model_ptr = toy_rate_ctrl;
+  EXPECT_EQ(priv, reinterpret_cast<void *>(PrivMagicNumber));
+  EXPECT_EQ(ratectrl_config->frame_width, 352);
+  EXPECT_EQ(ratectrl_config->frame_height, 288);
+  EXPECT_EQ(ratectrl_config->show_frame_count, kFrameNumGOPShort);
+  EXPECT_EQ(ratectrl_config->target_bitrate_kbps, 500);
+  EXPECT_EQ(ratectrl_config->frame_rate_num, 30);
+  EXPECT_EQ(ratectrl_config->frame_rate_den, 1);
+  return VPX_RC_OK;
+}
+
 vpx_rc_status_t rc_send_firstpass_stats(
     vpx_rc_model_t rate_ctrl_model,
     const vpx_rc_firstpass_stats_t *first_pass_stats) {
@@ -113,6 +137,19 @@ vpx_rc_status_t rc_send_firstpass_stats_gop(
   return VPX_RC_OK;
 }
 
+vpx_rc_status_t rc_send_firstpass_stats_gop_short(
+    vpx_rc_model_t rate_ctrl_model,
+    const vpx_rc_firstpass_stats_t *first_pass_stats) {
+  const ToyRateCtrl *toy_rate_ctrl =
+      static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_EQ(first_pass_stats->num_frames, kFrameNumGOPShort);
+  for (int i = 0; i < first_pass_stats->num_frames; ++i) {
+    EXPECT_DOUBLE_EQ(first_pass_stats->frame_stats[i].frame, i);
+  }
+  return VPX_RC_OK;
+}
+
 vpx_rc_status_t rc_get_encodeframe_decision(
     vpx_rc_model_t rate_ctrl_model,
     const vpx_rc_encodeframe_info_t *encode_frame_info,
@@ -128,19 +165,17 @@ vpx_rc_status_t rc_get_encodeframe_decision(
   if (encode_frame_info->coding_index == 0) {
     EXPECT_EQ(encode_frame_info->show_index, 0);
     EXPECT_EQ(encode_frame_info->gop_index, 0);
-    EXPECT_EQ(encode_frame_info->frame_type, 0 /*kFrameTypeKey*/);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeKey);
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
               0);  // kRefFrameTypeLast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
               0);  // kRefFrameTypePast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
               0);  // kRefFrameTypeFuture
-  }
-
-  if (encode_frame_info->coding_index == 1) {
+  } else if (encode_frame_info->coding_index == 1) {
     EXPECT_EQ(encode_frame_info->show_index, 4);
     EXPECT_EQ(encode_frame_info->gop_index, 1);
-    EXPECT_EQ(encode_frame_info->frame_type, 2 /*kFrameTypeAltRef*/);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeAltRef);
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
               1);  // kRefFrameTypeLast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
@@ -149,19 +184,15 @@ vpx_rc_status_t rc_get_encodeframe_decision(
               0);  // kRefFrameTypeFuture
     EXPECT_EQ(encode_frame_info->ref_frame_coding_indexes[0],
               0);  // kRefFrameTypeLast
-  }
-
-  if (encode_frame_info->coding_index >= 2 &&
-      encode_frame_info->coding_index < 5) {
+  } else if (encode_frame_info->coding_index >= 2 &&
+             encode_frame_info->coding_index < 5) {
     // In the first group of pictures, coding_index and gop_index are equal.
     EXPECT_EQ(encode_frame_info->gop_index, encode_frame_info->coding_index);
-    EXPECT_EQ(encode_frame_info->frame_type, 1 /*kFrameTypeInter*/);
-  }
-
-  if (encode_frame_info->coding_index == 5) {
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+  } else if (encode_frame_info->coding_index == 5) {
     EXPECT_EQ(encode_frame_info->show_index, 4);
     EXPECT_EQ(encode_frame_info->gop_index, 0);
-    EXPECT_EQ(encode_frame_info->frame_type, 3 /*kFrameTypeOverlay*/);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeOverlay);
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
               1);  // kRefFrameTypeLast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
@@ -197,19 +228,17 @@ vpx_rc_status_t rc_get_encodeframe_decision_gop(
   if (encode_frame_info->coding_index == 0) {
     EXPECT_EQ(encode_frame_info->show_index, 0);
     EXPECT_EQ(encode_frame_info->gop_index, 0);
-    EXPECT_EQ(encode_frame_info->frame_type, 0 /*kFrameTypeKey*/);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeKey);
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
               0);  // kRefFrameTypeLast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
               0);  // kRefFrameTypePast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
               0);  // kRefFrameTypeFuture
-  }
-
-  if (encode_frame_info->coding_index == 1) {
+  } else if (encode_frame_info->coding_index == 1) {
     EXPECT_EQ(encode_frame_info->show_index, 1);
     EXPECT_EQ(encode_frame_info->gop_index, 1);
-    EXPECT_EQ(encode_frame_info->frame_type, 1 /*kFrameTypeInter*/);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
               1);  // kRefFrameTypeLast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
@@ -218,36 +247,198 @@ vpx_rc_status_t rc_get_encodeframe_decision_gop(
               0);  // kRefFrameTypeFuture
     EXPECT_EQ(encode_frame_info->ref_frame_coding_indexes[0],
               0);  // kRefFrameTypeLast
-  }
-
-  if (encode_frame_info->coding_index == 2) {
+  } else if (encode_frame_info->coding_index == 2) {
     EXPECT_EQ(encode_frame_info->show_index, 2);
     EXPECT_EQ(encode_frame_info->gop_index, 0);
-    EXPECT_EQ(encode_frame_info->frame_type, 0 /*kFrameTypeKey*/);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeKey);
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
               0);  // kRefFrameTypeLast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
               0);  // kRefFrameTypePast
     EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
               0);  // kRefFrameTypeFuture
+  } else if (encode_frame_info->coding_index == 3 ||
+             encode_frame_info->coding_index == 12 ||
+             encode_frame_info->coding_index == 21) {
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeAltRef);
+    EXPECT_EQ(encode_frame_info->gop_index, 1);
+  } else if (encode_frame_info->coding_index == 11 ||
+             encode_frame_info->coding_index == 20 ||
+             encode_frame_info->coding_index == 29) {
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeOverlay);
+    EXPECT_EQ(encode_frame_info->gop_index, 0);
+  } else if (encode_frame_info->coding_index >= 30) {
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
   }
 
-  if (encode_frame_info->coding_index == 3 ||
-      encode_frame_info->coding_index == 12 ||
-      encode_frame_info->coding_index == 21) {
-    EXPECT_EQ(encode_frame_info->frame_type, 2 /*kFrameTypeAltRef*/);
+  // When the model recommends an invalid q, valid range [0, 255],
+  // the encoder will ignore it and use the default q selected
+  // by libvpx rate control strategy.
+  frame_decision->q_index = VPX_DEFAULT_Q;
+  frame_decision->max_frame_size = 0;
+
+  toy_rate_ctrl->coding_index += 1;
+  return VPX_RC_OK;
+}
+
+vpx_rc_status_t rc_get_encodeframe_decision_gop_short(
+    vpx_rc_model_t rate_ctrl_model,
+    const vpx_rc_encodeframe_info_t *encode_frame_info,
+    vpx_rc_encodeframe_decision_t *frame_decision) {
+  ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_LT(encode_frame_info->show_index, kFrameNumGOPShort);
+  EXPECT_EQ(encode_frame_info->coding_index, toy_rate_ctrl->coding_index);
+
+  if (encode_frame_info->coding_index == 0) {
+    EXPECT_EQ(encode_frame_info->show_index, 0);
+    EXPECT_EQ(encode_frame_info->gop_index, 0);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeKey);
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
+              0);  // kRefFrameTypeLast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
+              0);  // kRefFrameTypePast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
+              0);  // kRefFrameTypeFuture
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 1) {
+    EXPECT_EQ(encode_frame_info->show_index, 1);
     EXPECT_EQ(encode_frame_info->gop_index, 1);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
+              1);  // kRefFrameTypeLast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
+              0);  // kRefFrameTypePast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
+              0);  // kRefFrameTypeFuture
+    EXPECT_EQ(encode_frame_info->ref_frame_coding_indexes[0],
+              0);  // kRefFrameTypeLast
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 2) {
+    EXPECT_EQ(encode_frame_info->show_index, 2);
+    EXPECT_EQ(encode_frame_info->gop_index, 2);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 3) {
+    EXPECT_EQ(encode_frame_info->show_index, 3);
+    EXPECT_EQ(encode_frame_info->gop_index, 0);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeGolden);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 2);
   }
 
-  if (encode_frame_info->coding_index == 11 ||
-      encode_frame_info->coding_index == 20 ||
-      encode_frame_info->coding_index == 29) {
-    EXPECT_EQ(encode_frame_info->frame_type, 3 /*kFrameTypeOverlay*/);
+  // When the model recommends an invalid q, valid range [0, 255],
+  // the encoder will ignore it and use the default q selected
+  // by libvpx rate control strategy.
+  frame_decision->q_index = VPX_DEFAULT_Q;
+  frame_decision->max_frame_size = 0;
+
+  toy_rate_ctrl->coding_index += 1;
+  return VPX_RC_OK;
+}
+
+vpx_rc_status_t rc_get_encodeframe_decision_gop_short_overlay(
+    vpx_rc_model_t rate_ctrl_model,
+    const vpx_rc_encodeframe_info_t *encode_frame_info,
+    vpx_rc_encodeframe_decision_t *frame_decision) {
+  ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_LT(encode_frame_info->show_index, kFrameNumGOPShort);
+  EXPECT_EQ(encode_frame_info->coding_index, toy_rate_ctrl->coding_index);
+
+  if (encode_frame_info->coding_index == 0) {
+    EXPECT_EQ(encode_frame_info->show_index, 0);
+    EXPECT_EQ(encode_frame_info->gop_index, 0);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeKey);
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
+              0);  // kRefFrameTypeLast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
+              0);  // kRefFrameTypePast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
+              0);  // kRefFrameTypeFuture
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 1) {
+    EXPECT_EQ(encode_frame_info->show_index, 3);
+    EXPECT_EQ(encode_frame_info->gop_index, 1);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeAltRef);
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
+              1);  // kRefFrameTypeLast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
+              0);  // kRefFrameTypePast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
+              0);  // kRefFrameTypeFuture
+    EXPECT_EQ(encode_frame_info->ref_frame_coding_indexes[0],
+              0);  // kRefFrameTypeLast
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 2) {
+    EXPECT_EQ(encode_frame_info->show_index, 1);
+    EXPECT_EQ(encode_frame_info->gop_index, 2);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 3) {
+    EXPECT_EQ(encode_frame_info->show_index, 2);
+    EXPECT_EQ(encode_frame_info->gop_index, 3);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 4) {
+    EXPECT_EQ(encode_frame_info->show_index, 3);
     EXPECT_EQ(encode_frame_info->gop_index, 0);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeOverlay);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 2);
   }
 
-  if (encode_frame_info->coding_index >= 30) {
-    EXPECT_EQ(encode_frame_info->frame_type, 1 /*kFrameTypeInter*/);
+  // When the model recommends an invalid q, valid range [0, 255],
+  // the encoder will ignore it and use the default q selected
+  // by libvpx rate control strategy.
+  frame_decision->q_index = VPX_DEFAULT_Q;
+  frame_decision->max_frame_size = 0;
+
+  toy_rate_ctrl->coding_index += 1;
+  return VPX_RC_OK;
+}
+
+vpx_rc_status_t rc_get_encodeframe_decision_gop_short_no_arf(
+    vpx_rc_model_t rate_ctrl_model,
+    const vpx_rc_encodeframe_info_t *encode_frame_info,
+    vpx_rc_encodeframe_decision_t *frame_decision) {
+  ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_LT(encode_frame_info->show_index, kFrameNumGOPShort);
+  EXPECT_EQ(encode_frame_info->coding_index, toy_rate_ctrl->coding_index);
+
+  if (encode_frame_info->coding_index == 0) {
+    EXPECT_EQ(encode_frame_info->show_index, 0);
+    EXPECT_EQ(encode_frame_info->gop_index, 0);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeKey);
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
+              0);  // kRefFrameTypeLast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
+              0);  // kRefFrameTypePast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
+              0);  // kRefFrameTypeFuture
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 1) {
+    EXPECT_EQ(encode_frame_info->show_index, 1);
+    EXPECT_EQ(encode_frame_info->gop_index, 1);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[0],
+              1);  // kRefFrameTypeLast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[1],
+              0);  // kRefFrameTypePast
+    EXPECT_EQ(encode_frame_info->ref_frame_valid_list[2],
+              0);  // kRefFrameTypeFuture
+    EXPECT_EQ(encode_frame_info->ref_frame_coding_indexes[0],
+              0);  // kRefFrameTypeLast
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 2) {
+    EXPECT_EQ(encode_frame_info->show_index, 2);
+    EXPECT_EQ(encode_frame_info->gop_index, 2);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
+  } else if (encode_frame_info->coding_index == 3) {
+    EXPECT_EQ(encode_frame_info->show_index, 3);
+    EXPECT_EQ(encode_frame_info->gop_index, 3);
+    EXPECT_EQ(encode_frame_info->frame_type, vp9::kFrameTypeInter);
+    EXPECT_EQ(toy_rate_ctrl->gop_global_index, 1);
   }
 
   // When the model recommends an invalid q, valid range [0, 255],
@@ -296,6 +487,117 @@ vpx_rc_status_t rc_get_gop_decision(vpx_rc_model_t rate_ctrl_model,
   return VPX_RC_OK;
 }
 
+// Test on a 4 frame video.
+// Test a setting of 2 GOPs.
+// The first GOP has 3 coding frames, no alt ref.
+// The second GOP has 1 coding frame, no alt ref.
+vpx_rc_status_t rc_get_gop_decision_short(vpx_rc_model_t rate_ctrl_model,
+                                          const vpx_rc_gop_info_t *gop_info,
+                                          vpx_rc_gop_decision_t *gop_decision) {
+  ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_EQ(gop_info->lag_in_frames, kMaxLagInFrames - 1);
+  EXPECT_EQ(gop_info->min_gf_interval, kDefaultMinGfInterval);
+  EXPECT_EQ(gop_info->max_gf_interval, kDefaultMaxGfInterval);
+  EXPECT_EQ(gop_info->allow_alt_ref, 1);
+  if (gop_info->is_key_frame) {
+    EXPECT_EQ(gop_info->last_gop_use_alt_ref, 0);
+    EXPECT_EQ(gop_info->frames_since_key, 0);
+    EXPECT_EQ(gop_info->gop_global_index, 0);
+    toy_rate_ctrl->gop_global_index = 0;
+    toy_rate_ctrl->frames_since_key = 0;
+  } else {
+    EXPECT_EQ(gop_info->last_gop_use_alt_ref, 0);
+  }
+  EXPECT_EQ(gop_info->gop_global_index, toy_rate_ctrl->gop_global_index);
+  EXPECT_EQ(gop_info->frames_since_key, toy_rate_ctrl->frames_since_key);
+  EXPECT_EQ(gop_info->show_index, toy_rate_ctrl->show_index);
+  EXPECT_EQ(gop_info->coding_index, toy_rate_ctrl->coding_index);
+
+  gop_decision->gop_coding_frames = gop_info->gop_global_index == 0 ? 3 : 1;
+  gop_decision->use_alt_ref = 0;
+  toy_rate_ctrl->frames_since_key +=
+      gop_decision->gop_coding_frames - gop_decision->use_alt_ref;
+  toy_rate_ctrl->show_index +=
+      gop_decision->gop_coding_frames - gop_decision->use_alt_ref;
+  ++toy_rate_ctrl->gop_global_index;
+  return VPX_RC_OK;
+}
+
+// Test on a 4 frame video.
+// Test a setting of 2 GOPs.
+// The first GOP has 4 coding frames. Use alt ref.
+// The second GOP only contains the overlay frame of the first GOP's alt ref
+// frame.
+vpx_rc_status_t rc_get_gop_decision_short_overlay(
+    vpx_rc_model_t rate_ctrl_model, const vpx_rc_gop_info_t *gop_info,
+    vpx_rc_gop_decision_t *gop_decision) {
+  ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_EQ(gop_info->lag_in_frames, kMaxLagInFrames - 1);
+  EXPECT_EQ(gop_info->min_gf_interval, kDefaultMinGfInterval);
+  EXPECT_EQ(gop_info->max_gf_interval, kDefaultMaxGfInterval);
+  EXPECT_EQ(gop_info->allow_alt_ref, 1);
+  if (gop_info->is_key_frame) {
+    EXPECT_EQ(gop_info->last_gop_use_alt_ref, 0);
+    EXPECT_EQ(gop_info->frames_since_key, 0);
+    EXPECT_EQ(gop_info->gop_global_index, 0);
+    toy_rate_ctrl->gop_global_index = 0;
+    toy_rate_ctrl->frames_since_key = 0;
+  } else {
+    EXPECT_EQ(gop_info->last_gop_use_alt_ref, 1);
+  }
+  EXPECT_EQ(gop_info->gop_global_index, toy_rate_ctrl->gop_global_index);
+  EXPECT_EQ(gop_info->frames_since_key, toy_rate_ctrl->frames_since_key);
+  EXPECT_EQ(gop_info->show_index, toy_rate_ctrl->show_index);
+  EXPECT_EQ(gop_info->coding_index, toy_rate_ctrl->coding_index);
+
+  gop_decision->gop_coding_frames = gop_info->gop_global_index == 0 ? 4 : 1;
+  gop_decision->use_alt_ref = gop_info->is_key_frame ? 1 : 0;
+  toy_rate_ctrl->frames_since_key +=
+      gop_decision->gop_coding_frames - gop_decision->use_alt_ref;
+  toy_rate_ctrl->show_index +=
+      gop_decision->gop_coding_frames - gop_decision->use_alt_ref;
+  ++toy_rate_ctrl->gop_global_index;
+  return VPX_RC_OK;
+}
+
+// Test on a 4 frame video.
+// Test a setting of 1 GOP.
+// The GOP has 4 coding frames. Do not use alt ref.
+vpx_rc_status_t rc_get_gop_decision_short_no_arf(
+    vpx_rc_model_t rate_ctrl_model, const vpx_rc_gop_info_t *gop_info,
+    vpx_rc_gop_decision_t *gop_decision) {
+  ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+  EXPECT_EQ(gop_info->lag_in_frames, kMaxLagInFrames - 1);
+  EXPECT_EQ(gop_info->min_gf_interval, kDefaultMinGfInterval);
+  EXPECT_EQ(gop_info->max_gf_interval, kDefaultMaxGfInterval);
+  EXPECT_EQ(gop_info->allow_alt_ref, 1);
+  if (gop_info->is_key_frame) {
+    EXPECT_EQ(gop_info->last_gop_use_alt_ref, 0);
+    EXPECT_EQ(gop_info->frames_since_key, 0);
+    EXPECT_EQ(gop_info->gop_global_index, 0);
+    toy_rate_ctrl->gop_global_index = 0;
+    toy_rate_ctrl->frames_since_key = 0;
+  } else {
+    EXPECT_EQ(gop_info->last_gop_use_alt_ref, 0);
+  }
+  EXPECT_EQ(gop_info->gop_global_index, toy_rate_ctrl->gop_global_index);
+  EXPECT_EQ(gop_info->frames_since_key, toy_rate_ctrl->frames_since_key);
+  EXPECT_EQ(gop_info->show_index, toy_rate_ctrl->show_index);
+  EXPECT_EQ(gop_info->coding_index, toy_rate_ctrl->coding_index);
+
+  gop_decision->gop_coding_frames = gop_info->gop_global_index == 0 ? 4 : 1;
+  gop_decision->use_alt_ref = 0;
+  toy_rate_ctrl->frames_since_key +=
+      gop_decision->gop_coding_frames - gop_decision->use_alt_ref;
+  toy_rate_ctrl->show_index +=
+      gop_decision->gop_coding_frames - gop_decision->use_alt_ref;
+  ++toy_rate_ctrl->gop_global_index;
+  return VPX_RC_OK;
+}
+
 vpx_rc_status_t rc_update_encodeframe_result(
     vpx_rc_model_t rate_ctrl_model,
     const vpx_rc_encodeframe_result_t *encode_frame_result) {
@@ -328,6 +630,18 @@ vpx_rc_status_t rc_update_encodeframe_result_gop(
   return VPX_RC_OK;
 }
 
+vpx_rc_status_t rc_update_encodeframe_result_gop_short(
+    vpx_rc_model_t rate_ctrl_model,
+    const vpx_rc_encodeframe_result_t *encode_frame_result) {
+  const ToyRateCtrl *toy_rate_ctrl =
+      static_cast<ToyRateCtrl *>(rate_ctrl_model);
+  EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
+
+  const int64_t ref_pixel_count = 352 * 288 * 3 / 2;
+  EXPECT_EQ(encode_frame_result->pixel_count, ref_pixel_count);
+  return VPX_RC_OK;
+}
+
 vpx_rc_status_t rc_delete_model(vpx_rc_model_t rate_ctrl_model) {
   ToyRateCtrl *toy_rate_ctrl = static_cast<ToyRateCtrl *>(rate_ctrl_model);
   EXPECT_EQ(toy_rate_ctrl->magic_number, kModelMagicNumber);
@@ -371,7 +685,7 @@ TEST_F(ExtRateCtrlTest, EncodeTest) {
       "bus_352x288_420_f20_b8.yuv", VPX_IMG_FMT_I420, 352, 288, 30, 1, 0,
       kFrameNum));
 
-  ASSERT_NE(video.get(), nullptr);
+  ASSERT_NE(video, nullptr);
   ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
 }
 
@@ -417,7 +731,149 @@ TEST_F(ExtRateCtrlTestGOP, EncodeTest) {
       "noisy_clip_640_360.y4m", VPX_IMG_FMT_I420, 640, 360, 30, 1, 0,
       kFrameNumGOP));
 
-  ASSERT_NE(video.get(), nullptr);
+  ASSERT_NE(video, nullptr);
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
+}
+
+class ExtRateCtrlTestGOPShort : public ::libvpx_test::EncoderTest,
+                                public ::libvpx_test::CodecTestWithParam<int> {
+ protected:
+  ExtRateCtrlTestGOPShort() : EncoderTest(&::libvpx_test::kVP9) {}
+
+  ~ExtRateCtrlTestGOPShort() override = default;
+
+  void SetUp() override {
+    InitializeConfig();
+    SetMode(::libvpx_test::kTwoPassGood);
+  }
+
+  void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
+                          ::libvpx_test::Encoder *encoder) override {
+    if (video->frame() == 0) {
+      encoder->Control(VP9E_SET_MIN_GF_INTERVAL, kDefaultMinGfInterval);
+      encoder->Control(VP9E_SET_MAX_GF_INTERVAL, kDefaultMaxGfInterval);
+
+      vpx_rc_funcs_t rc_funcs;
+      rc_funcs.rc_type = VPX_RC_GOP_QP;
+      rc_funcs.create_model = rc_create_model_gop_short;
+      rc_funcs.send_firstpass_stats = rc_send_firstpass_stats_gop_short;
+      rc_funcs.get_encodeframe_decision = rc_get_encodeframe_decision_gop_short;
+      rc_funcs.get_gop_decision = rc_get_gop_decision_short;
+      rc_funcs.update_encodeframe_result =
+          rc_update_encodeframe_result_gop_short;
+      rc_funcs.delete_model = rc_delete_model;
+      rc_funcs.priv = reinterpret_cast<void *>(PrivMagicNumber);
+      encoder->Control(VP9E_SET_EXTERNAL_RATE_CONTROL, &rc_funcs);
+    }
+  }
+};
+
+TEST_F(ExtRateCtrlTestGOPShort, EncodeTest) {
+  cfg_.rc_target_bitrate = 500;
+  cfg_.g_lag_in_frames = kMaxLagInFrames - 1;
+  cfg_.rc_end_usage = VPX_VBR;
+
+  std::unique_ptr<libvpx_test::VideoSource> video;
+  video.reset(new (std::nothrow) libvpx_test::YUVVideoSource(
+      kTestFileName, VPX_IMG_FMT_I420, 352, 288, 30, 1, 0, kFrameNumGOPShort));
+
+  ASSERT_NE(video, nullptr);
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
+}
+
+class ExtRateCtrlTestGOPShortOverlay
+    : public ::libvpx_test::EncoderTest,
+      public ::libvpx_test::CodecTestWithParam<int> {
+ protected:
+  ExtRateCtrlTestGOPShortOverlay() : EncoderTest(&::libvpx_test::kVP9) {}
+
+  ~ExtRateCtrlTestGOPShortOverlay() override = default;
+
+  void SetUp() override {
+    InitializeConfig();
+    SetMode(::libvpx_test::kTwoPassGood);
+  }
+
+  void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
+                          ::libvpx_test::Encoder *encoder) override {
+    if (video->frame() == 0) {
+      encoder->Control(VP9E_SET_MIN_GF_INTERVAL, kDefaultMinGfInterval);
+      encoder->Control(VP9E_SET_MAX_GF_INTERVAL, kDefaultMaxGfInterval);
+
+      vpx_rc_funcs_t rc_funcs;
+      rc_funcs.rc_type = VPX_RC_GOP_QP;
+      rc_funcs.create_model = rc_create_model_gop_short;
+      rc_funcs.send_firstpass_stats = rc_send_firstpass_stats_gop_short;
+      rc_funcs.get_encodeframe_decision =
+          rc_get_encodeframe_decision_gop_short_overlay;
+      rc_funcs.get_gop_decision = rc_get_gop_decision_short_overlay;
+      rc_funcs.update_encodeframe_result =
+          rc_update_encodeframe_result_gop_short;
+      rc_funcs.delete_model = rc_delete_model;
+      rc_funcs.priv = reinterpret_cast<void *>(PrivMagicNumber);
+      encoder->Control(VP9E_SET_EXTERNAL_RATE_CONTROL, &rc_funcs);
+    }
+  }
+};
+
+TEST_F(ExtRateCtrlTestGOPShortOverlay, EncodeTest) {
+  cfg_.rc_target_bitrate = 500;
+  cfg_.g_lag_in_frames = kMaxLagInFrames - 1;
+  cfg_.rc_end_usage = VPX_VBR;
+
+  std::unique_ptr<libvpx_test::VideoSource> video;
+  video.reset(new (std::nothrow) libvpx_test::YUVVideoSource(
+      kTestFileName, VPX_IMG_FMT_I420, 352, 288, 30, 1, 0, kFrameNumGOPShort));
+
+  ASSERT_NE(video, nullptr);
+  ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
+}
+
+class ExtRateCtrlTestGOPShortNoARF
+    : public ::libvpx_test::EncoderTest,
+      public ::libvpx_test::CodecTestWithParam<int> {
+ protected:
+  ExtRateCtrlTestGOPShortNoARF() : EncoderTest(&::libvpx_test::kVP9) {}
+
+  ~ExtRateCtrlTestGOPShortNoARF() override = default;
+
+  void SetUp() override {
+    InitializeConfig();
+    SetMode(::libvpx_test::kTwoPassGood);
+  }
+
+  void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
+                          ::libvpx_test::Encoder *encoder) override {
+    if (video->frame() == 0) {
+      encoder->Control(VP9E_SET_MIN_GF_INTERVAL, kDefaultMinGfInterval);
+      encoder->Control(VP9E_SET_MAX_GF_INTERVAL, kDefaultMaxGfInterval);
+
+      vpx_rc_funcs_t rc_funcs;
+      rc_funcs.rc_type = VPX_RC_GOP_QP;
+      rc_funcs.create_model = rc_create_model_gop_short;
+      rc_funcs.send_firstpass_stats = rc_send_firstpass_stats_gop_short;
+      rc_funcs.get_encodeframe_decision =
+          rc_get_encodeframe_decision_gop_short_no_arf;
+      rc_funcs.get_gop_decision = rc_get_gop_decision_short_no_arf;
+      rc_funcs.update_encodeframe_result =
+          rc_update_encodeframe_result_gop_short;
+      rc_funcs.delete_model = rc_delete_model;
+      rc_funcs.priv = reinterpret_cast<void *>(PrivMagicNumber);
+      encoder->Control(VP9E_SET_EXTERNAL_RATE_CONTROL, &rc_funcs);
+    }
+  }
+};
+
+TEST_F(ExtRateCtrlTestGOPShortNoARF, EncodeTest) {
+  cfg_.rc_target_bitrate = 500;
+  cfg_.g_lag_in_frames = kMaxLagInFrames - 1;
+  cfg_.rc_end_usage = VPX_VBR;
+
+  std::unique_ptr<libvpx_test::VideoSource> video;
+  video.reset(new (std::nothrow) libvpx_test::YUVVideoSource(
+      kTestFileName, VPX_IMG_FMT_I420, 352, 288, 30, 1, 0, kFrameNumGOPShort));
+
+  ASSERT_NE(video, nullptr);
   ASSERT_NO_FATAL_FAILURE(RunLoop(video.get()));
 }