rtc: Layered CBR rate control for SVC
Implement layer context based CBR rate control,
to allow each layer to target its own layer bitrate.
Added datarate unittest for 3 temporal layers.
Temporal layers can be tested with the sample
encoder: svc_encoder_rtc
Change-Id: I5892c0998a81ac82cea8b2035197627f6cd32d19
diff --git a/av1/av1.cmake b/av1/av1.cmake
index d0e6278..0fe5bc4 100644
--- a/av1/av1.cmake
+++ b/av1/av1.cmake
@@ -203,6 +203,8 @@
"${AOM_ROOT}/av1/encoder/segmentation.h"
"${AOM_ROOT}/av1/encoder/speed_features.c"
"${AOM_ROOT}/av1/encoder/speed_features.h"
+ "${AOM_ROOT}/av1/encoder/svc_layercontext.c"
+ "${AOM_ROOT}/av1/encoder/svc_layercontext.h"
"${AOM_ROOT}/av1/encoder/temporal_filter.c"
"${AOM_ROOT}/av1/encoder/temporal_filter.h"
"${AOM_ROOT}/av1/encoder/tokenize.c"
diff --git a/av1/av1_cx_iface.c b/av1/av1_cx_iface.c
index 942650d..f1c8515 100644
--- a/av1/av1_cx_iface.c
+++ b/av1/av1_cx_iface.c
@@ -2162,6 +2162,8 @@
aom_svc_layer_id_t *const data = va_arg(args, aom_svc_layer_id_t *);
ctx->cpi->common.spatial_layer_id = data->spatial_layer_id;
ctx->cpi->common.temporal_layer_id = data->temporal_layer_id;
+ ctx->cpi->svc.spatial_layer_id = data->spatial_layer_id;
+ ctx->cpi->svc.temporal_layer_id = data->temporal_layer_id;
return AOM_CODEC_OK;
}
@@ -2171,6 +2173,8 @@
aom_svc_params_t *const params = va_arg(args, aom_svc_params_t *);
cpi->common.number_spatial_layers = params->number_spatial_layers;
cpi->common.number_temporal_layers = params->number_temporal_layers;
+ cpi->svc.number_spatial_layers = params->number_spatial_layers;
+ cpi->svc.number_temporal_layers = params->number_temporal_layers;
if (cpi->common.number_spatial_layers > 1 ||
cpi->common.number_temporal_layers > 1) {
unsigned int sl, tl;
@@ -2184,10 +2188,14 @@
lc->min_q = params->min_quantizers[layer];
lc->scaling_factor_num = params->scaling_factor_num[sl];
lc->scaling_factor_den = params->scaling_factor_den[sl];
- lc->layer_target_bitrate = params->layer_target_bitrate[layer];
+ lc->layer_target_bitrate = 1000 * params->layer_target_bitrate[layer];
lc->framerate_factor = params->framerate_factor[tl];
}
}
+ if (cpi->common.current_frame.frame_number == 0)
+ av1_init_layer_context(cpi);
+ else
+ av1_update_layer_context_change_config(cpi, cpi->oxcf.target_bandwidth);
}
return AOM_CODEC_OK;
}
diff --git a/av1/encoder/aq_cyclicrefresh.c b/av1/encoder/aq_cyclicrefresh.c
index 9c57c49..cd68425 100644
--- a/av1/encoder/aq_cyclicrefresh.c
+++ b/av1/encoder/aq_cyclicrefresh.c
@@ -19,46 +19,6 @@
#include "aom_dsp/aom_dsp_common.h"
#include "aom_ports/system_state.h"
-struct CYCLIC_REFRESH {
- // Percentage of blocks per frame that are targeted as candidates
- // for cyclic refresh.
- int percent_refresh;
- // Maximum q-delta as percentage of base q.
- int max_qdelta_perc;
- // Superblock starting index for cycling through the frame.
- int sb_index;
- // Controls how long block will need to wait to be refreshed again, in
- // excess of the cycle time, i.e., in the case of all zero motion, block
- // will be refreshed every (100/percent_refresh + time_for_refresh) frames.
- int time_for_refresh;
- // Target number of (4x4) blocks that are set for delta-q.
- int target_num_seg_blocks;
- // Actual number of (4x4) blocks that were applied delta-q.
- int actual_num_seg1_blocks;
- int actual_num_seg2_blocks;
- // RD mult. parameters for segment 1.
- int rdmult;
- // Cyclic refresh map.
- int8_t *map;
- // Map of the last q a block was coded at.
- uint8_t *last_coded_q_map;
- // Thresholds applied to the projected rate/distortion of the coding block,
- // when deciding whether block should be refreshed.
- int64_t thresh_rate_sb;
- int64_t thresh_dist_sb;
- // Threshold applied to the motion vector (in units of 1/8 pel) of the
- // coding block, when deciding whether block should be refreshed.
- int16_t motion_thresh;
- // Rate target ratio to set q delta.
- double rate_ratio_qdelta;
- // Boost factor for rate target ratio, for segment CR_SEGMENT_ID_BOOST2.
- int rate_boost_fac;
- double low_content_avg;
- int qindex_delta[3];
- double weight_segment;
- int apply_cyclic_refresh;
-};
-
CYCLIC_REFRESH *av1_cyclic_refresh_alloc(int mi_rows, int mi_cols) {
size_t last_coded_q_map_size;
CYCLIC_REFRESH *const cr = aom_calloc(1, sizeof(*cr));
@@ -365,6 +325,7 @@
int qp_thresh = AOMMIN(20, rc->best_quality << 1);
cr->apply_cyclic_refresh = 1;
if (frame_is_intra_only(cm) || is_lossless_requested(&cpi->oxcf) ||
+ cpi->svc.temporal_layer_id > 0 ||
rc->avg_frame_qindex[INTER_FRAME] < qp_thresh) {
cr->apply_cyclic_refresh = 0;
return;
diff --git a/av1/encoder/aq_cyclicrefresh.h b/av1/encoder/aq_cyclicrefresh.h
index ddabae6..774b165 100644
--- a/av1/encoder/aq_cyclicrefresh.h
+++ b/av1/encoder/aq_cyclicrefresh.h
@@ -27,9 +27,48 @@
// Maximum rate target ratio for setting segment delta-qp.
#define CR_MAX_RATE_TARGET_RATIO 4.0
+struct CYCLIC_REFRESH {
+ // Percentage of blocks per frame that are targeted as candidates
+ // for cyclic refresh.
+ int percent_refresh;
+ // Maximum q-delta as percentage of base q.
+ int max_qdelta_perc;
+ // Superblock starting index for cycling through the frame.
+ int sb_index;
+ // Controls how long block will need to wait to be refreshed again, in
+ // excess of the cycle time, i.e., in the case of all zero motion, block
+ // will be refreshed every (100/percent_refresh + time_for_refresh) frames.
+ int time_for_refresh;
+ // Target number of (4x4) blocks that are set for delta-q.
+ int target_num_seg_blocks;
+ // Actual number of (4x4) blocks that were applied delta-q.
+ int actual_num_seg1_blocks;
+ int actual_num_seg2_blocks;
+ // RD mult. parameters for segment 1.
+ int rdmult;
+ // Cyclic refresh map.
+ int8_t *map;
+ // Map of the last q a block was coded at.
+ uint8_t *last_coded_q_map;
+ // Thresholds applied to the projected rate/distortion of the coding block,
+ // when deciding whether block should be refreshed.
+ int64_t thresh_rate_sb;
+ int64_t thresh_dist_sb;
+ // Threshold applied to the motion vector (in units of 1/8 pel) of the
+ // coding block, when deciding whether block should be refreshed.
+ int16_t motion_thresh;
+ // Rate target ratio to set q delta.
+ double rate_ratio_qdelta;
+ // Boost factor for rate target ratio, for segment CR_SEGMENT_ID_BOOST2.
+ int rate_boost_fac;
+ double low_content_avg;
+ int qindex_delta[3];
+ double weight_segment;
+ int apply_cyclic_refresh;
+};
+
struct AV1_COMP;
-struct CYCLIC_REFRESH;
typedef struct CYCLIC_REFRESH CYCLIC_REFRESH;
CYCLIC_REFRESH *av1_cyclic_refresh_alloc(int mi_rows, int mi_cols);
diff --git a/av1/encoder/encode_strategy.c b/av1/encoder/encode_strategy.c
index 12dd2aa..06e575f 100644
--- a/av1/encoder/encode_strategy.c
+++ b/av1/encoder/encode_strategy.c
@@ -357,7 +357,7 @@
const int intra_only = frame_params->frame_type == KEY_FRAME ||
frame_params->frame_type == INTRA_ONLY_FRAME;
- if (intra_only || frame_params->error_resilient_mode ||
+ if (intra_only || frame_params->error_resilient_mode || cpi->use_svc ||
cpi->ext_use_primary_ref_none) {
return PRIMARY_REF_NONE;
}
@@ -1459,5 +1459,7 @@
cpi->droppable = is_frame_droppable(cpi);
}
+ if (cpi->use_svc) av1_save_layer_context(cpi);
+
return AOM_CODEC_OK;
}
diff --git a/av1/encoder/encoder.c b/av1/encoder/encoder.c
index 38011d6..059ff57 100644
--- a/av1/encoder/encoder.c
+++ b/av1/encoder/encoder.c
@@ -2609,6 +2609,9 @@
cm->number_spatial_layers > 1 ? cm->number_spatial_layers - 1 : 0;
init_seq_coding_tools(&cm->seq_params, cm, oxcf, cpi->use_svc);
}
+
+ if (cpi->use_svc)
+ av1_update_layer_context_change_config(cpi, oxcf->target_bandwidth);
}
AV1_COMP *av1_create_compressor(AV1EncoderConfig *oxcf,
diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index cb99d57..de153e2 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -39,6 +39,7 @@
#include "av1/encoder/ratectrl.h"
#include "av1/encoder/rd.h"
#include "av1/encoder/speed_features.h"
+#include "av1/encoder/svc_layercontext.h"
#include "av1/encoder/tokenize.h"
#include "av1/encoder/block.h"
@@ -164,25 +165,6 @@
SS_CFG_TOTAL = 2
} UENUM1BYTE(SS_CFG_OFFSET);
-typedef struct {
- int max_q;
- int min_q;
- int framerate_factor;
- int layer_target_bitrate;
- int scaling_factor_num;
- int scaling_factor_den;
- RATE_CONTROL rc;
-} LAYER_CONTEXT;
-
-typedef struct SVC {
- int external_ref_frame_config;
- int non_reference_frame;
- // Layer context used for rate control in one pass temporal CBR mode.
- LAYER_CONTEXT layer_context[AOM_MAX_LAYERS];
- int ref_idx[INTER_REFS_PER_FRAME];
- int refresh[REF_FRAMES];
-} SVC;
-
#define MAX_LENGTH_TPL_FRAME_STATS (27 + 9)
typedef struct TplDepStats {
diff --git a/av1/encoder/ratectrl.c b/av1/encoder/ratectrl.c
index b2505ee..b286227 100644
--- a/av1/encoder/ratectrl.c
+++ b/av1/encoder/ratectrl.c
@@ -215,6 +215,24 @@
return target;
}
+// Update the buffer level for higher temporal layers, given the encoded current
+// temporal layer.
+static void update_layer_buffer_level(SVC *svc, int encoded_frame_size) {
+ const int current_temporal_layer = svc->temporal_layer_id;
+ for (int i = current_temporal_layer + 1; i < svc->number_temporal_layers;
+ ++i) {
+ const int layer =
+ LAYER_IDS_TO_IDX(svc->spatial_layer_id, i, svc->number_temporal_layers);
+ LAYER_CONTEXT *lc = &svc->layer_context[layer];
+ RATE_CONTROL *lrc = &lc->rc;
+ lrc->bits_off_target +=
+ (int)(lc->target_bandwidth / lc->framerate) - encoded_frame_size;
+ // Clip buffer level to maximum buffer size for the layer.
+ lrc->bits_off_target =
+ AOMMIN(lrc->bits_off_target, lrc->maximum_buffer_size);
+ lrc->buffer_level = lrc->bits_off_target;
+ }
+}
// Update the buffer level: leaky bucket model.
static void update_buffer_level(AV1_COMP *cpi, int encoded_frame_size) {
const AV1_COMMON *const cm = &cpi->common;
@@ -229,6 +247,8 @@
// Clip the buffer level to the maximum specified buffer size.
rc->bits_off_target = AOMMIN(rc->bits_off_target, rc->maximum_buffer_size);
rc->buffer_level = rc->bits_off_target;
+
+ if (cpi->use_svc) update_layer_buffer_level(&cpi->svc, encoded_frame_size);
}
int av1_rc_get_default_min_gf_interval(int width, int height,
@@ -378,7 +398,7 @@
rcf = rc->rate_correction_factors[rf_lvl];
} else {
if ((cpi->refresh_alt_ref_frame || cpi->refresh_golden_frame) &&
- !rc->is_src_frame_alt_ref &&
+ !rc->is_src_frame_alt_ref && !cpi->use_svc &&
(cpi->oxcf.rc_mode != AOM_CBR || cpi->oxcf.gf_cbr_boost_pct > 20))
rcf = rc->rate_correction_factors[GF_ARF_STD];
else
@@ -404,7 +424,7 @@
rc->rate_correction_factors[rf_lvl] = factor;
} else {
if ((cpi->refresh_alt_ref_frame || cpi->refresh_golden_frame) &&
- !rc->is_src_frame_alt_ref &&
+ !rc->is_src_frame_alt_ref && !cpi->use_svc &&
(cpi->oxcf.rc_mode != AOM_CBR || cpi->oxcf.gf_cbr_boost_pct > 20))
rc->rate_correction_factors[GF_ARF_STD] = factor;
else
@@ -565,7 +585,7 @@
// In CBR mode, this makes sure q is between oscillating Qs to prevent
// resonance.
- if (cpi->oxcf.rc_mode == AOM_CBR &&
+ if (cpi->oxcf.rc_mode == AOM_CBR && !cpi->use_svc &&
(cpi->rc.rc_1_frame * cpi->rc.rc_2_frame == -1) &&
cpi->rc.q_1_frame != cpi->rc.q_2_frame) {
q = clamp(q, AOMMIN(cpi->rc.q_1_frame, cpi->rc.q_2_frame),
@@ -1783,7 +1803,17 @@
} else {
target = rc->avg_frame_bandwidth;
}
-
+ if (cpi->use_svc) {
+ // Note that for layers, avg_frame_bandwidth is the cumulative
+ // per-frame-bandwidth. For the target size of this frame, use the
+ // layer average frame size (i.e., non-cumulative per-frame-bw).
+ int layer =
+ LAYER_IDS_TO_IDX(cpi->svc.spatial_layer_id, cpi->svc.temporal_layer_id,
+ cpi->svc.number_temporal_layers);
+ const LAYER_CONTEXT *lc = &cpi->svc.layer_context[layer];
+ target = lc->avg_frame_size;
+ min_frame_target = AOMMAX(lc->avg_frame_size >> 4, FRAME_OVERHEAD_BITS);
+ }
if (diff > 0) {
// Lower the target bandwidth for this frame.
const int pct_low = (int)AOMMIN(diff / one_pct_bits, oxcf->under_shoot_pct);
@@ -1832,6 +1862,10 @@
AV1_COMMON *const cm = &cpi->common;
FRAME_UPDATE_TYPE frame_update_type;
int target;
+ if (cpi->use_svc) {
+ av1_update_temporal_layer_framerate(cpi);
+ av1_restore_layer_context(cpi);
+ }
if (rc->frames_to_key == 0 || (frame_flags & FRAMEFLAGS_KEY)) {
frame_params->frame_type = KEY_FRAME;
rc->this_key_frame_forced =
@@ -1840,11 +1874,13 @@
rc->kf_boost = DEFAULT_KF_BOOST_RT;
rc->source_alt_ref_active = 0;
frame_update_type = KF_UPDATE;
+ if (cpi->use_svc && cm->current_frame.frame_number > 0)
+ av1_svc_reset_temporal_layers(cpi, 1);
} else {
frame_params->frame_type = INTER_FRAME;
frame_update_type = LF_UPDATE;
}
- if (rc->frames_till_gf_update_due == 0) {
+ if (rc->frames_till_gf_update_due == 0 && cpi->svc.temporal_layer_id == 0) {
GF_GROUP *const gf_group = &cpi->gf_group;
if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ)
av1_cyclic_refresh_set_golden_update(cpi);
@@ -1852,10 +1888,17 @@
rc->baseline_gf_interval = MAX_GF_INTERVAL;
if (rc->baseline_gf_interval > rc->frames_to_key)
rc->baseline_gf_interval = rc->frames_to_key;
- rc->frames_till_gf_update_due = rc->baseline_gf_interval;
rc->gfu_boost = DEFAULT_GF_BOOST_RT;
rc->constrained_gf_group =
(rc->baseline_gf_interval >= rc->frames_to_key) ? 1 : 0;
+ // SVC does not use GF as periodid boost.
+ // TODO(marpan): Find better way to disable this for SVC.
+ if (cpi->use_svc) {
+ rc->baseline_gf_interval = MAX_STATIC_GF_GROUP_LENGTH;
+ rc->gfu_boost = 1;
+ rc->constrained_gf_group = 0;
+ }
+ rc->frames_till_gf_update_due = rc->baseline_gf_interval;
frame_update_type = GF_UPDATE;
gf_group->index = 0;
gf_group->size = rc->baseline_gf_interval;
diff --git a/av1/encoder/svc_layercontext.c b/av1/encoder/svc_layercontext.c
new file mode 100644
index 0000000..6851422
--- /dev/null
+++ b/av1/encoder/svc_layercontext.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (c) 2019, Alliance for Open Media. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include <math.h>
+
+#include "av1/encoder/encoder.h"
+
+static void swap_ptr(void *a, void *b) {
+ void **a_p = (void **)a;
+ void **b_p = (void **)b;
+ void *c = *a_p;
+ *a_p = *b_p;
+ *b_p = c;
+}
+
+void av1_init_layer_context(AV1_COMP *const cpi) {
+ AV1_COMMON *const cm = &cpi->common;
+ const AV1EncoderConfig *const oxcf = &cpi->oxcf;
+ SVC *const svc = &cpi->svc;
+ int mi_rows = cpi->common.mi_rows;
+ int mi_cols = cpi->common.mi_cols;
+
+ for (int sl = 0; sl < svc->number_spatial_layers; ++sl) {
+ for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
+ int layer = LAYER_IDS_TO_IDX(sl, tl, svc->number_temporal_layers);
+ LAYER_CONTEXT *const lc = &svc->layer_context[layer];
+ RATE_CONTROL *const lrc = &lc->rc;
+ lrc->ni_av_qi = oxcf->worst_allowed_q;
+ lrc->total_actual_bits = 0;
+ lrc->total_target_vs_actual = 0;
+ lrc->ni_tot_qi = 0;
+ lrc->tot_q = 0.0;
+ lrc->avg_q = 0.0;
+ lrc->ni_frames = 0;
+ lrc->decimation_count = 0;
+ lrc->decimation_factor = 0;
+ lrc->worst_quality = av1_quantizer_to_qindex(lc->max_q);
+ lrc->best_quality = av1_quantizer_to_qindex(lc->min_q);
+ for (int i = 0; i < RATE_FACTOR_LEVELS; ++i) {
+ lrc->rate_correction_factors[i] = 1.0;
+ }
+ lc->target_bandwidth = lc->layer_target_bitrate;
+ lrc->last_q[INTER_FRAME] = lrc->worst_quality;
+ lrc->avg_frame_qindex[INTER_FRAME] = lrc->worst_quality;
+ lrc->avg_frame_qindex[KEY_FRAME] = lrc->worst_quality;
+ lrc->buffer_level =
+ oxcf->starting_buffer_level_ms * lc->target_bandwidth / 1000;
+ lrc->bits_off_target = lrc->buffer_level;
+ // Initialize the cyclic refresh parameters. If spatial layers are used
+ // (i.e., ss_number_layers > 1), these need to be updated per spatial
+ // layer. Cyclic refresh is only applied on base temporal layer.
+ if (svc->number_spatial_layers > 1 && tl == 0) {
+ size_t last_coded_q_map_size;
+ lc->sb_index = 0;
+ lc->actual_num_seg1_blocks = 0;
+ lc->actual_num_seg2_blocks = 0;
+ lc->counter_encode_maxq_scene_change = 0;
+ CHECK_MEM_ERROR(cm, lc->map,
+ aom_malloc(mi_rows * mi_cols * sizeof(*lc->map)));
+ memset(lc->map, 0, mi_rows * mi_cols);
+ last_coded_q_map_size =
+ mi_rows * mi_cols * sizeof(*lc->last_coded_q_map);
+ CHECK_MEM_ERROR(cm, lc->last_coded_q_map,
+ aom_malloc(last_coded_q_map_size));
+ assert(MAXQ <= 255);
+ memset(lc->last_coded_q_map, MAXQ, last_coded_q_map_size);
+ }
+ }
+ }
+}
+
+// Update the layer context from a change_config() call.
+void av1_update_layer_context_change_config(AV1_COMP *const cpi,
+ const int target_bandwidth) {
+ const RATE_CONTROL *const rc = &cpi->rc;
+ SVC *const svc = &cpi->svc;
+ int layer = 0;
+ int spatial_layer_target = 0;
+ float bitrate_alloc = 1.0;
+
+ for (int sl = 0; sl < svc->number_spatial_layers; ++sl) {
+ for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
+ layer = LAYER_IDS_TO_IDX(sl, tl, svc->number_temporal_layers);
+ LAYER_CONTEXT *const lc = &svc->layer_context[layer];
+ svc->layer_context[layer].target_bandwidth = lc->layer_target_bitrate;
+ }
+ spatial_layer_target = svc->layer_context[layer].target_bandwidth;
+ for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
+ LAYER_CONTEXT *const lc =
+ &svc->layer_context[sl * svc->number_temporal_layers + tl];
+ RATE_CONTROL *const lrc = &lc->rc;
+ lc->spatial_layer_target_bandwidth = spatial_layer_target;
+ bitrate_alloc = (float)lc->target_bandwidth / target_bandwidth;
+ lrc->starting_buffer_level =
+ (int64_t)(rc->starting_buffer_level * bitrate_alloc);
+ lrc->optimal_buffer_level =
+ (int64_t)(rc->optimal_buffer_level * bitrate_alloc);
+ lrc->maximum_buffer_size =
+ (int64_t)(rc->maximum_buffer_size * bitrate_alloc);
+ lrc->bits_off_target =
+ AOMMIN(lrc->bits_off_target, lrc->maximum_buffer_size);
+ lrc->buffer_level = AOMMIN(lrc->buffer_level, lrc->maximum_buffer_size);
+ lc->framerate = cpi->framerate / lc->framerate_factor;
+ lrc->avg_frame_bandwidth = (int)(lc->target_bandwidth / lc->framerate);
+ lrc->max_frame_bandwidth = rc->max_frame_bandwidth;
+ lrc->worst_quality = av1_quantizer_to_qindex(lc->max_q);
+ lrc->best_quality = av1_quantizer_to_qindex(lc->min_q);
+ }
+ }
+}
+
+static LAYER_CONTEXT *get_layer_context(AV1_COMP *const cpi) {
+ return &cpi->svc.layer_context[cpi->svc.spatial_layer_id *
+ cpi->svc.number_temporal_layers +
+ cpi->svc.temporal_layer_id];
+}
+
+void av1_update_temporal_layer_framerate(AV1_COMP *const cpi) {
+ SVC *const svc = &cpi->svc;
+ LAYER_CONTEXT *const lc = get_layer_context(cpi);
+ RATE_CONTROL *const lrc = &lc->rc;
+ const int tl = svc->temporal_layer_id;
+ lc->framerate = cpi->framerate / lc->framerate_factor;
+ lrc->avg_frame_bandwidth = (int)(lc->target_bandwidth / lc->framerate);
+ lrc->max_frame_bandwidth = cpi->rc.max_frame_bandwidth;
+ // Update the average layer frame size (non-cumulative per-frame-bw).
+ if (tl == 0) {
+ lc->avg_frame_size = lrc->avg_frame_bandwidth;
+ } else {
+ int prev_layer = svc->spatial_layer_id * svc->number_temporal_layers +
+ svc->temporal_layer_id - 1;
+ LAYER_CONTEXT *const lcprev = &svc->layer_context[prev_layer];
+ const double prev_layer_framerate =
+ cpi->framerate / lcprev->framerate_factor;
+ const int prev_layer_target_bandwidth = lcprev->layer_target_bitrate;
+ lc->avg_frame_size =
+ (int)((lc->target_bandwidth - prev_layer_target_bandwidth) /
+ (lc->framerate - prev_layer_framerate));
+ }
+}
+
+void av1_restore_layer_context(AV1_COMP *const cpi) {
+ LAYER_CONTEXT *const lc = get_layer_context(cpi);
+ const int old_frame_since_key = cpi->rc.frames_since_key;
+ const int old_frame_to_key = cpi->rc.frames_to_key;
+ // Restore layer rate control.
+ cpi->rc = lc->rc;
+ cpi->oxcf.target_bandwidth = lc->target_bandwidth;
+ // Reset the frames_since_key and frames_to_key counters to their values
+ // before the layer restore. Keep these defined for the stream (not layer).
+ cpi->rc.frames_since_key = old_frame_since_key;
+ cpi->rc.frames_to_key = old_frame_to_key;
+ // For spatial-svc, allow cyclic-refresh to be applied on the spatial layers,
+ // for the base temporal layer.
+ if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ &&
+ cpi->svc.number_spatial_layers > 1 && cpi->svc.temporal_layer_id == 0) {
+ CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
+ swap_ptr(&cr->map, &lc->map);
+ swap_ptr(&cr->last_coded_q_map, &lc->last_coded_q_map);
+ cr->sb_index = lc->sb_index;
+ cr->actual_num_seg1_blocks = lc->actual_num_seg1_blocks;
+ cr->actual_num_seg2_blocks = lc->actual_num_seg2_blocks;
+ }
+}
+
+void av1_save_layer_context(AV1_COMP *const cpi) {
+ SVC *const svc = &cpi->svc;
+ LAYER_CONTEXT *lc = get_layer_context(cpi);
+ // Reset gf counters on non-base temporal layer.
+ // TODO(marpan): Temporary for now, fix this.
+ if (svc->temporal_layer_id > 0) {
+ cpi->gf_group.index--;
+ if (cpi->rc.frames_till_gf_update_due > 0)
+ cpi->rc.frames_till_gf_update_due++;
+ }
+ lc->rc = cpi->rc;
+ lc->target_bandwidth = (int)cpi->oxcf.target_bandwidth;
+ // For spatial-svc, allow cyclic-refresh to be applied on the spatial layers,
+ // for the base temporal layer.
+ if (cpi->oxcf.aq_mode == CYCLIC_REFRESH_AQ &&
+ cpi->svc.number_spatial_layers > 1 && cpi->svc.temporal_layer_id == 0) {
+ CYCLIC_REFRESH *const cr = cpi->cyclic_refresh;
+ signed char *temp = lc->map;
+ uint8_t *temp2 = lc->last_coded_q_map;
+ lc->map = cr->map;
+ cr->map = temp;
+ lc->last_coded_q_map = cr->last_coded_q_map;
+ cr->last_coded_q_map = temp2;
+ lc->sb_index = cr->sb_index;
+ lc->actual_num_seg1_blocks = cr->actual_num_seg1_blocks;
+ lc->actual_num_seg2_blocks = cr->actual_num_seg2_blocks;
+ }
+}
+
+void av1_free_svc_cyclic_refresh(AV1_COMP *const cpi) {
+ SVC *const svc = &cpi->svc;
+ for (int sl = 0; sl < svc->number_spatial_layers; ++sl) {
+ for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
+ int layer = LAYER_IDS_TO_IDX(sl, tl, svc->number_temporal_layers);
+ LAYER_CONTEXT *const lc = &svc->layer_context[layer];
+ if (lc->map) aom_free(lc->map);
+ if (lc->last_coded_q_map) aom_free(lc->last_coded_q_map);
+ }
+ }
+}
+
+// Reset on key frame: reset counters, references and buffer updates.
+void av1_svc_reset_temporal_layers(AV1_COMP *const cpi, int is_key) {
+ SVC *const svc = &cpi->svc;
+ LAYER_CONTEXT *lc = NULL;
+ for (int sl = 0; sl < svc->number_spatial_layers; ++sl) {
+ for (int tl = 0; tl < svc->number_temporal_layers; ++tl) {
+ lc = &cpi->svc.layer_context[sl * svc->number_temporal_layers + tl];
+ if (is_key) lc->frames_from_key_frame = 0;
+ }
+ }
+ av1_update_temporal_layer_framerate(cpi);
+ av1_restore_layer_context(cpi);
+}
diff --git a/av1/encoder/svc_layercontext.h b/av1/encoder/svc_layercontext.h
new file mode 100644
index 0000000..01d3fdc
--- /dev/null
+++ b/av1/encoder/svc_layercontext.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2019, Alliance for Open Media. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree. An additional intellectual property rights grant can be found
+ * in the file PATENTS. All contributing project authors may
+ * be found in the AUTHORS file in the root of the source tree.
+ */
+
+#ifndef AOM_AV1_ENCODER_SVC_LAYERCONTEXT_H_
+#define AOM_AV1_ENCODER_SVC_LAYERCONTEXT_H_
+
+#include "av1/encoder/aq_cyclicrefresh.h"
+#include "av1/encoder/encoder.h"
+#include "av1/encoder/ratectrl.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct {
+ RATE_CONTROL rc;
+ int framerate_factor;
+ int layer_target_bitrate;
+ int scaling_factor_num;
+ int scaling_factor_den;
+ int target_bandwidth;
+ int spatial_layer_target_bandwidth;
+ double framerate;
+ int avg_frame_size;
+ int max_q;
+ int min_q;
+ int frames_from_key_frame;
+ // Cyclic refresh parameters (aq-mode=3), that need to be updated per-frame.
+ int sb_index;
+ int8_t *map;
+ uint8_t *last_coded_q_map;
+ int actual_num_seg1_blocks;
+ int actual_num_seg2_blocks;
+ int counter_encode_maxq_scene_change;
+ uint8_t speed;
+} LAYER_CONTEXT;
+
+typedef struct SVC {
+ int spatial_layer_id;
+ int temporal_layer_id;
+ int number_spatial_layers;
+ int number_temporal_layers;
+ int external_ref_frame_config;
+ int non_reference_frame;
+ int ref_idx[INTER_REFS_PER_FRAME];
+ int refresh[REF_FRAMES];
+ // Layer context used for rate control in one pass temporal CBR mode or
+ // two pass spatial mode.
+ LAYER_CONTEXT layer_context[AOM_MAX_LAYERS];
+} SVC;
+
+struct AV1_COMP;
+
+// Initialize layer context data from init_config().
+void av1_init_layer_context(struct AV1_COMP *const cpi);
+
+// Update the layer context from a change_config() call.
+void av1_update_layer_context_change_config(struct AV1_COMP *const cpi,
+ const int target_bandwidth);
+
+// Prior to encoding the frame, update framerate-related quantities
+// for the current temporal layer.
+void av1_update_temporal_layer_framerate(struct AV1_COMP *const cpi);
+
+// Prior to encoding the frame, set the layer context, for the current layer
+// to be encoded, to the cpi struct.
+void av1_restore_layer_context(struct AV1_COMP *const cpi);
+
+// Save the layer context after encoding the frame.
+void av1_save_layer_context(struct AV1_COMP *const cpi);
+
+void av1_free_svc_cyclic_refresh(struct AV1_COMP *const cpi);
+
+void av1_svc_reset_temporal_layers(struct AV1_COMP *const cpi, int is_key);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif // AOM_AV1_ENCODER_SVC_LAYERCONTEXT_H_
diff --git a/test/datarate_test.cc b/test/datarate_test.cc
index d2bd161..bdaedfb 100644
--- a/test/datarate_test.cc
+++ b/test/datarate_test.cc
@@ -52,6 +52,79 @@
bits_total_ = 0;
denoiser_offon_test_ = 0;
denoiser_offon_period_ = -1;
+ number_temporal_layers_ = 1;
+ for (int i = 0; i < 3; i++) {
+ target_layer_bitrate_[i] = 0;
+ effective_datarate_tl[i] = 0.0;
+ }
+ memset(&layer_id_, 0, sizeof(aom_svc_layer_id_t));
+ memset(&svc_params_, 0, sizeof(aom_svc_params_t));
+ memset(&ref_frame_config_, 0, sizeof(aom_svc_ref_frame_config_t));
+ }
+
+ // Layer pattern configuration.
+ int set_layer_pattern(int frame_cnt, aom_svc_layer_id_t *layer_id,
+ aom_svc_ref_frame_config_t *ref_frame_config) {
+ // No spatial layers in this test.
+ layer_id->spatial_layer_id = 0;
+ // Set the referende map buffer idx for the 7 references:
+ // LAST_FRAME (0), LAST2_FRAME(1), LAST3_FRAME(2), GOLDEN_FRAME(3),
+ // BWDREF_FRAME(4), ALTREF2_FRAME(5), ALTREF_FRAME(6).
+ for (int i = 0; i < 7; i++) ref_frame_config->ref_idx[i] = i;
+ for (int i = 0; i < 8; i++) ref_frame_config->refresh[i] = 0;
+ // Note only use LAST and GF for prediction in non-rd mode (speed 8).
+ int layer_flags = AOM_EFLAG_NO_REF_LAST2 | AOM_EFLAG_NO_REF_LAST3 |
+ AOM_EFLAG_NO_REF_ARF | AOM_EFLAG_NO_REF_BWD |
+ AOM_EFLAG_NO_REF_ARF2;
+ // 3-layer:
+ // 1 3 5 7
+ // 2 6
+ // 0 4 8
+ if (frame_cnt % 4 == 0) {
+ // Base layer.
+ layer_id->temporal_layer_id = 0;
+ // Update LAST on layer 0, reference LAST and GF.
+ ref_frame_config->refresh[0] = 1;
+ } else if ((frame_cnt - 1) % 4 == 0) {
+ layer_id->temporal_layer_id = 2;
+ // First top layer: no updates, only reference LAST (TL0).
+ layer_flags |= AOM_EFLAG_NO_REF_GF;
+ } else if ((frame_cnt - 2) % 4 == 0) {
+ layer_id->temporal_layer_id = 1;
+ // Middle layer (TL1): update LAST2, only reference LAST (TL0).
+ ref_frame_config->refresh[1] = 1;
+ layer_flags |= AOM_EFLAG_NO_REF_GF;
+ } else if ((frame_cnt - 3) % 4 == 0) {
+ layer_id->temporal_layer_id = 2;
+ // Second top layer: no updates, only reference LAST.
+ // Set buffer idx for LAST to slot 1, since that was the slot
+ // updated in previous frame. So LAST is TL1 frame.
+ ref_frame_config->ref_idx[0] = 1;
+ ref_frame_config->ref_idx[1] = 0;
+ layer_flags |= AOM_EFLAG_NO_REF_GF;
+ }
+ return layer_flags;
+ }
+
+ void initialize_svc(int number_temporal_layers, aom_svc_params *svc_params) {
+ svc_params->number_spatial_layers = 1;
+ svc_params->scaling_factor_num[0] = 1;
+ svc_params->scaling_factor_den[0] = 1;
+ svc_params->number_temporal_layers = number_temporal_layers;
+ for (int i = 0; i < number_temporal_layers; ++i) {
+ svc_params->max_quantizers[i] = 56;
+ svc_params->min_quantizers[i] = 2;
+ svc_params->layer_target_bitrate[i] = target_layer_bitrate_[i];
+ }
+ svc_params->framerate_factor[0] = 1;
+ if (number_temporal_layers == 2) {
+ svc_params->framerate_factor[0] = 2;
+ svc_params->framerate_factor[1] = 1;
+ } else if (number_temporal_layers == 3) {
+ svc_params->framerate_factor[0] = 4;
+ svc_params->framerate_factor[1] = 2;
+ svc_params->framerate_factor[2] = 1;
+ }
}
virtual void PreEncodeFrameHook(::libaom_test::VideoSource *video,
@@ -59,6 +132,10 @@
if (video->frame() == 0) {
encoder->Control(AOME_SET_CPUUSED, set_cpu_used_);
encoder->Control(AV1E_SET_AQ_MODE, aq_mode_);
+ if (number_temporal_layers_ > 1) {
+ initialize_svc(number_temporal_layers_, &svc_params_);
+ encoder->Control(AV1E_SET_SVC_PARAMS, &svc_params_);
+ }
}
if (denoiser_offon_test_) {
@@ -72,6 +149,15 @@
encoder->Control(AV1E_SET_NOISE_SENSITIVITY, denoiser_on_);
+ if (number_temporal_layers_ > 1) {
+ // Set the reference/update flags, layer_id, and reference_map
+ // buffer index.
+ frame_flags_ =
+ set_layer_pattern(video->frame(), &layer_id_, &ref_frame_config_);
+ encoder->Control(AV1E_SET_SVC_LAYER_ID, &layer_id_);
+ encoder->Control(AV1E_SET_SVC_REF_FRAME_CONFIG, &ref_frame_config_);
+ }
+
const aom_rational_t tb = video->timebase();
timebase_ = static_cast<double>(tb.num) / tb.den;
duration_ = 0;
@@ -104,6 +190,12 @@
// Update the total encoded bits.
bits_total_ += frame_size_in_bits;
+ if (number_temporal_layers_ > 1) {
+ // Update the layer cumulative bitrate.
+ for (int i = layer_id_.temporal_layer_id; i < number_temporal_layers_;
+ i++)
+ effective_datarate_tl[i] += 1.0 * frame_size_in_bits;
+ }
// Update the most recent pts.
last_pts_ = pkt->data.frame.pts;
++frame_number_;
@@ -114,6 +206,11 @@
duration_ = (last_pts_ + 1) * timebase_;
// Effective file datarate:
effective_datarate_ = (bits_total_ / 1000.0) / duration_;
+ if (number_temporal_layers_ > 1) {
+ for (int i = 0; i < number_temporal_layers_; i++)
+ effective_datarate_tl[i] =
+ (effective_datarate_tl[i] / 1000) / duration_;
+ }
}
virtual void BasicRateTargetingVBRTest() {
@@ -230,6 +327,39 @@
}
}
+ virtual void BasicRateTargetingCBR3TLTest() {
+ cfg_.rc_buf_initial_sz = 500;
+ cfg_.rc_buf_optimal_sz = 500;
+ cfg_.rc_buf_sz = 1000;
+ cfg_.rc_dropframe_thresh = 0;
+ cfg_.rc_min_quantizer = 0;
+ cfg_.rc_max_quantizer = 63;
+ cfg_.rc_end_usage = AOM_CBR;
+ cfg_.g_lag_in_frames = 0;
+ cfg_.g_usage = AOM_USAGE_REALTIME;
+ cfg_.g_error_resilient = 1;
+
+ ::libaom_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352,
+ 288, 30, 1, 0, 300);
+ const int bitrate_array[2] = { 150, 550 };
+ cfg_.rc_target_bitrate = bitrate_array[GET_PARAM(4)];
+ ResetModel();
+ number_temporal_layers_ = 3;
+ target_layer_bitrate_[0] = 50 * cfg_.rc_target_bitrate / 100;
+ target_layer_bitrate_[1] = 70 * cfg_.rc_target_bitrate / 100;
+ target_layer_bitrate_[2] = cfg_.rc_target_bitrate;
+ framerate_factor_[0] = 4;
+ framerate_factor_[1] = 2;
+ framerate_factor_[2] = 1;
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ for (int i = 0; i < number_temporal_layers_; i++) {
+ ASSERT_GE(effective_datarate_tl[i], target_layer_bitrate_[i] * 0.80)
+ << " The datarate for the file is lower than target by too much!";
+ ASSERT_LE(effective_datarate_tl[i], target_layer_bitrate_[i] * 1.30)
+ << " The datarate for the file is greater than target by too much!";
+ }
+ }
+
aom_codec_pts_t last_pts_;
double timebase_;
int frame_number_; // Counter for number of non-dropped/encoded frames.
@@ -245,6 +375,14 @@
int denoiser_offon_test_;
int denoiser_offon_period_;
unsigned int aq_mode_;
+ int number_temporal_layers_;
+ // Allow for up to 3 temporal layers.
+ int target_layer_bitrate_[3];
+ aom_svc_params_t svc_params_;
+ aom_svc_ref_frame_config_t ref_frame_config_;
+ aom_svc_layer_id_t layer_id_;
+ double effective_datarate_tl[3];
+ int framerate_factor_[3];
}; // namespace
// Check basic rate targeting for VBR mode.
@@ -295,6 +433,11 @@
ChangingDropFrameThreshTest();
}
+// Check basic rate targeting for CBR, for 3 temporal layers.
+TEST_P(DatarateTestRealtime, BasicRateTargetingCBR3TL) {
+ BasicRateTargetingCBR3TLTest();
+}
+
AV1_INSTANTIATE_TEST_CASE(DatarateTestLarge,
::testing::Values(::libaom_test::kOnePassGood,
::libaom_test::kRealTime),
diff --git a/test/encode_test_driver.h b/test/encode_test_driver.h
index 4f3f855..65f8944 100644
--- a/test/encode_test_driver.h
+++ b/test/encode_test_driver.h
@@ -119,6 +119,21 @@
ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError();
}
+ void Control(int ctrl_id, struct aom_svc_layer_id *arg) {
+ const aom_codec_err_t res = aom_codec_control_(&encoder_, ctrl_id, arg);
+ ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError();
+ }
+
+ void Control(int ctrl_id, struct aom_svc_ref_frame_config *arg) {
+ const aom_codec_err_t res = aom_codec_control_(&encoder_, ctrl_id, arg);
+ ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError();
+ }
+
+ void Control(int ctrl_id, struct aom_svc_params *arg) {
+ const aom_codec_err_t res = aom_codec_control_(&encoder_, ctrl_id, arg);
+ ASSERT_EQ(AOM_CODEC_OK, res) << EncoderError();
+ }
+
#if CONFIG_AV1_ENCODER
void Control(int ctrl_id, aom_active_map_t *arg) {
const aom_codec_err_t res = aom_codec_control_(&encoder_, ctrl_id, arg);