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