rtc: Add dynamic spatial layers to external RC test

Add test to change the number of spatial layers on
the fly. This requires a few fixes in the svc encocder:
1) fix the svc condition in adjust_q_cbr() for adjustment
   based on resolution change
2) add flag to detect change in number of spatial layers,
   and use it to reset the cyclic refresh.

Dynamic dropping/enabling layers based on bitrate
to be added next.

Change-Id: I9d5f7f8e1867f013fd0880408ac45f4f6c715757
diff --git a/av1/encoder/aq_cyclicrefresh.c b/av1/encoder/aq_cyclicrefresh.c
index 68230bb..439f216 100644
--- a/av1/encoder/aq_cyclicrefresh.c
+++ b/av1/encoder/aq_cyclicrefresh.c
@@ -384,6 +384,7 @@
   const PRIMARY_RATE_CONTROL *const p_rc = &cpi->ppi->p_rc;
   const AV1_COMMON *const cm = &cpi->common;
   CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
+  SVC *const svc = &cpi->svc;
   int num4x4bl = cm->mi_params.MBs << 4;
   int target_refresh = 0;
   double weight_segment_target = 0;
@@ -423,10 +424,11 @@
   // should we enable cyclic refresh on this frame.
   cr->apply_cyclic_refresh = 1;
   if (frame_is_intra_only(cm) || is_lossless_requested(&cpi->oxcf.rc_cfg) ||
-      scene_change_detected || cpi->svc.temporal_layer_id > 0 ||
+      scene_change_detected || svc->temporal_layer_id > 0 ||
+      svc->prev_number_spatial_layers != svc->number_spatial_layers ||
       p_rc->avg_frame_qindex[INTER_FRAME] < qp_thresh ||
-      (cpi->svc.number_spatial_layers > 1 &&
-       cpi->svc.layer_context[cpi->svc.temporal_layer_id].is_key_frame) ||
+      (svc->number_spatial_layers > 1 &&
+       svc->layer_context[svc->temporal_layer_id].is_key_frame) ||
       (frames_since_scene_change > 20 &&
        p_rc->avg_frame_qindex[INTER_FRAME] > qp_max_thresh) ||
       (rc->avg_frame_low_motion && rc->avg_frame_low_motion < 30 &&
@@ -436,7 +438,7 @@
   }
 
   // Increase the amount of refresh for #temporal_layers > 2
-  if (cpi->svc.number_temporal_layers > 2)
+  if (svc->number_temporal_layers > 2)
     cr->percent_refresh = 15;
   else
     cr->percent_refresh = 10 + cr->percent_refresh_adjustment;
@@ -459,8 +461,8 @@
   // frames overshot.
   if (cr->percent_refresh > 0) {
     if (cpi->ppi->use_svc || !is_screen_content) {
-      if (frames_since_scene_change < ((4 * cpi->svc.number_temporal_layers) *
-                                       (100 / cr->percent_refresh))) {
+      if (frames_since_scene_change <
+          ((4 * svc->number_temporal_layers) * (100 / cr->percent_refresh))) {
         cr->rate_ratio_qdelta = 3.0 + cr->rate_ratio_qdelta_adjustment;
       } else {
         cr->rate_ratio_qdelta = 2.25 + cr->rate_ratio_qdelta_adjustment;
@@ -534,9 +536,13 @@
   const int layer_depth = AOMMIN(gf_group->layer_depth[cpi->gf_frame_index], 6);
   const FRAME_TYPE frame_type = cm->current_frame.frame_type;
 
+  // Set resolution_change flag: for svc only set it when the
+  // number of spatial layers has not changed.
   const int resolution_change =
-      cm->prev_frame && (cm->width != cm->prev_frame->width ||
-                         cm->height != cm->prev_frame->height);
+      cm->prev_frame &&
+      (cm->width != cm->prev_frame->width ||
+       cm->height != cm->prev_frame->height) &&
+      cpi->svc.prev_number_spatial_layers == cpi->svc.number_spatial_layers;
 
   if (resolution_change) av1_cyclic_refresh_reset_resize(cpi);
   if (!cr->apply_cyclic_refresh) {
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 14ea3c2..499d934 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -3865,6 +3865,9 @@
     cpi->frames_since_last_update = 1;
   }
 
+  if (cpi->svc.spatial_layer_id == cpi->svc.number_spatial_layers - 1)
+    cpi->svc.prev_number_spatial_layers = cpi->svc.number_spatial_layers;
+
   // Clear the one shot update flags for segmentation map and mode/ref loop
   // filter deltas.
   cm->seg.update_map = 0;
diff --git a/av1/encoder/ratectrl.c b/av1/encoder/ratectrl.c
index 264783d..b6a4c1b 100644
--- a/av1/encoder/ratectrl.c
+++ b/av1/encoder/ratectrl.c
@@ -542,9 +542,9 @@
              cpi->oxcf.tune_cfg.content != AOM_CONTENT_SCREEN)
       q = rc->q_1_frame + max_delta_up;
   }
-  // For single spatial layer: if resolution has increased push q closer
+  // For non-svc (single layer): if resolution has increased push q closer
   // to the active_worst to avoid excess overshoot.
-  if (cpi->svc.number_spatial_layers <= 1 && cm->prev_frame &&
+  if (!cpi->ppi->use_svc && cm->prev_frame &&
       (width * height > 1.5 * cm->prev_frame->width * cm->prev_frame->height))
     q = (q + active_worst_quality) >> 1;
   return AOMMAX(AOMMIN(q, cpi->rc.worst_quality), cpi->rc.best_quality);
diff --git a/av1/encoder/svc_layercontext.c b/av1/encoder/svc_layercontext.c
index a2f6cbb..6617f94 100644
--- a/av1/encoder/svc_layercontext.c
+++ b/av1/encoder/svc_layercontext.c
@@ -101,11 +101,11 @@
                                             const int64_t target_bandwidth) {
   const RATE_CONTROL *const rc = &cpi->rc;
   const PRIMARY_RATE_CONTROL *const p_rc = &cpi->ppi->p_rc;
+  AV1_COMMON *const cm = &cpi->common;
   SVC *const svc = &cpi->svc;
   int layer = 0;
   int64_t spatial_layer_target = 0;
   float bitrate_alloc = 1.0;
-  AV1_COMMON *const cm = &cpi->common;
   const int mi_rows = cm->mi_params.mi_rows;
   const int mi_cols = cm->mi_params.mi_cols;
   for (int sl = 0; sl < svc->number_spatial_layers; ++sl) {
@@ -139,9 +139,12 @@
       lrc->rtc_external_ratectrl = rc->rtc_external_ratectrl;
       lrc->worst_quality = av1_quantizer_to_qindex(lc->max_q);
       lrc->best_quality = av1_quantizer_to_qindex(lc->min_q);
-      // Reset the cyclic refresh parameters, if needed (map is NULL).
+      // Reset the cyclic refresh parameters, if needed (map is NULL),
+      // or number of spatial layers has changed.
       // Cyclic refresh is only applied on base temporal layer.
-      if (svc->number_spatial_layers > 1 && tl == 0 && lc->map == NULL) {
+      if (svc->number_spatial_layers > 1 && tl == 0 &&
+          (lc->map == NULL ||
+           svc->prev_number_spatial_layers != svc->number_spatial_layers)) {
         lc->sb_index = 0;
         lc->actual_num_seg1_blocks = 0;
         lc->actual_num_seg2_blocks = 0;
diff --git a/av1/encoder/svc_layercontext.h b/av1/encoder/svc_layercontext.h
index 5e983f6..d55ef7d 100644
--- a/av1/encoder/svc_layercontext.h
+++ b/av1/encoder/svc_layercontext.h
@@ -91,6 +91,7 @@
   int temporal_layer_id;
   int number_spatial_layers;
   int number_temporal_layers;
+  int prev_number_spatial_layers;
   int use_flexible_mode;
   int ksvc_fixed_mode;
   /*!\endcond */
diff --git a/av1/ratectrl_rtc.cc b/av1/ratectrl_rtc.cc
index 4bd861d..8319dda 100644
--- a/av1/ratectrl_rtc.cc
+++ b/av1/ratectrl_rtc.cc
@@ -155,7 +155,6 @@
   AV1_COMMON *cm = &cpi_->common;
   AV1EncoderConfig *oxcf = &cpi_->oxcf;
   RATE_CONTROL *const rc = &cpi_->rc;
-
   initial_width_ = rc_cfg.width;
   initial_height_ = rc_cfg.height;
   cm->width = rc_cfg.width;
@@ -305,6 +304,8 @@
 
 void AV1RateControlRTC::PostEncodeUpdate(uint64_t encoded_frame_size) {
   cpi_->common.current_frame.frame_number++;
+  if (cpi_->svc.spatial_layer_id == cpi_->svc.number_spatial_layers - 1)
+    cpi_->svc.prev_number_spatial_layers = cpi_->svc.number_spatial_layers;
   av1_rc_postencode_update(cpi_, encoded_frame_size);
   if (cpi_->svc.number_spatial_layers > 1 ||
       cpi_->svc.number_temporal_layers > 1)
diff --git a/test/ratectrl_rtc_test.cc b/test/ratectrl_rtc_test.cc
index ceef487..873e132 100644
--- a/test/ratectrl_rtc_test.cc
+++ b/test/ratectrl_rtc_test.cc
@@ -38,7 +38,7 @@
   RcInterfaceTest()
       : EncoderTest(GET_PARAM(0)), aq_mode_(GET_PARAM(1)), key_interval_(3000),
         encoder_exit_(false), layer_frame_cnt_(0), superframe_cnt_(0),
-        dynamic_temporal_layers_(false) {
+        dynamic_temporal_layers_(false), dynamic_spatial_layers_(false) {
     memset(&svc_params_, 0, sizeof(svc_params_));
     memset(&layer_id_, 0, sizeof(layer_id_));
   }
@@ -84,6 +84,7 @@
     frame_params_.frame_type =
         layer_frame_cnt_ % key_int == 0 ? aom::kKeyFrame : aom::kInterFrame;
     encoder_exit_ = video->frame() == kNumFrames;
+    frame_flags_ = 0;
 
     if (dynamic_temporal_layers_) {
       if (superframe_cnt_ == 100 && layer_id_.spatial_layer_id == 0) {
@@ -102,7 +103,38 @@
         encoder->Control(AV1E_SET_SVC_PARAMS, &svc_params_);
         rc_api_->UpdateRateControl(rc_cfg_);
       }
+    } else if (dynamic_spatial_layers_) {
+      // In this example the #spatial layers is modified on the fly,
+      // so we go from (120p,240p,480p) to (240p,480p), etc.
+      if (superframe_cnt_ == 100 && layer_id_.spatial_layer_id == 0) {
+        // Change to 2 spatial layers (240p, 480p).
+        SetConfigSvc(2, 3);
+        encoder->Control(AV1E_SET_SVC_PARAMS, &svc_params_);
+        rc_api_->UpdateRateControl(rc_cfg_);
+      } else if (superframe_cnt_ == 200 && layer_id_.spatial_layer_id == 0) {
+        // Change to 1 spatial layer (480p).
+        SetConfigSvc(1, 3);
+        encoder->Control(AV1E_SET_SVC_PARAMS, &svc_params_);
+        rc_api_->UpdateRateControl(rc_cfg_);
+      } else if (superframe_cnt_ == 300 && layer_id_.spatial_layer_id == 0) {
+        // Go back to 3 spatial layers (120p, 240p, 480p).
+        SetConfigSvc(3, 3);
+        encoder->Control(AV1E_SET_SVC_PARAMS, &svc_params_);
+        // In the fixed SVC mode (which is what is used in this test):
+        // Key frame is required here on SL0 since 120p will try to predict
+        // from LAST which was the 480p, so decoder will throw an error
+        // (reference must be smaller than 4x4). In the flexible mode
+        // (not used here) we can set the frame flags to predict off the 2x2
+        // reference instead,
+        frame_flags_ = AOM_EFLAG_FORCE_KF;
+        frame_params_.frame_type = aom::kKeyFrame;
+        rc_api_->UpdateRateControl(rc_cfg_);
+      }
     }
+    // TODO(marpan): Add dynamic spatial layers based on 0 layer bitrate.
+    // That is actual usage in SW where configuration (#spatial, #temporal)
+    // layers is fixed, but top layer is dropped or re-enabled based on
+    // bitrate. This requires external RC to handle dropped (zero-size) frames.
   }
 
   void PostEncodeFrameHook(::libaom_test::Encoder *encoder) override {
@@ -196,6 +228,20 @@
     ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
   }
 
+  void RunSvcDynamicSpatial() {
+    dynamic_spatial_layers_ = true;
+    key_interval_ = 10000;
+    SetConfigSvc(3, 3);
+    rc_api_ = aom::AV1RateControlRTC::Create(rc_cfg_);
+    frame_params_.spatial_layer_id = 0;
+    frame_params_.temporal_layer_id = 0;
+
+    ::libaom_test::I420VideoSource video("niklas_640_480_30.yuv", 640, 480, 30,
+                                         1, 0, kNumFrames);
+
+    ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+  }
+
  private:
   void SetConfig() {
     rc_cfg_.width = 640;
@@ -374,6 +420,7 @@
   int layer_frame_cnt_;
   int superframe_cnt_;
   bool dynamic_temporal_layers_;
+  bool dynamic_spatial_layers_;
 };
 
 TEST_P(RcInterfaceTest, OneLayer) { RunOneLayer(); }
@@ -386,6 +433,8 @@
 
 TEST_P(RcInterfaceTest, SvcDynamicTemporal) { RunSvcDynamicTemporal(); }
 
+TEST_P(RcInterfaceTest, SvcDynamicSpatial) { RunSvcDynamicSpatial(); }
+
 AV1_INSTANTIATE_TEST_SUITE(RcInterfaceTest, ::testing::Values(0, 3));
 
 }  // namespace