Add support for VP9 level targeting
Constraints on encoder config: -target_bandwidth is no larger than 80% of level bitrate limit -target_bandwidth * (1 + max_over_shoot_pct) is no larger than 88% of level bitrate limit -min_gf_interval is no smaller than level limit -tile_columns is no larger than level limit Constraints on rate control: -current frame size plus previous three frames' size is no larger than the CPB level limit -current frame size is no larger than 50%/40%/20% of the CPB level limit if it's a key/alt-ref/other frame. Change-Id: I84d1a2d6d6e3c82bfd533b3309ce999cfaba2c8b
This commit is contained in:
@@ -66,6 +66,36 @@ class LevelTest
|
||||
int level_;
|
||||
};
|
||||
|
||||
TEST_P(LevelTest, TestTargetLevel11) {
|
||||
ASSERT_NE(encoding_mode_, ::libvpx_test::kRealTime);
|
||||
::libvpx_test::I420VideoSource video("hantro_odd.yuv", 208, 144, 30, 1, 0,
|
||||
90);
|
||||
target_level_ = 11;
|
||||
cfg_.rc_target_bitrate = 150;
|
||||
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
|
||||
ASSERT_EQ(target_level_, level_);
|
||||
}
|
||||
|
||||
TEST_P(LevelTest, TestTargetLevel20) {
|
||||
ASSERT_NE(encoding_mode_, ::libvpx_test::kRealTime);
|
||||
::libvpx_test::I420VideoSource video("hantro_collage_w352h288.yuv", 352, 288,
|
||||
30, 1, 0, 90);
|
||||
target_level_ = 20;
|
||||
cfg_.rc_target_bitrate = 1200;
|
||||
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
|
||||
ASSERT_EQ(target_level_, level_);
|
||||
}
|
||||
|
||||
TEST_P(LevelTest, TestTargetLevel31) {
|
||||
ASSERT_NE(encoding_mode_, ::libvpx_test::kRealTime);
|
||||
::libvpx_test::I420VideoSource video("niklas_1280_720_30.y4m", 1280, 720, 30,
|
||||
1, 0, 60);
|
||||
target_level_ = 31;
|
||||
cfg_.rc_target_bitrate = 8000;
|
||||
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
|
||||
ASSERT_EQ(target_level_, level_);
|
||||
}
|
||||
|
||||
// Test for keeping level stats only
|
||||
TEST_P(LevelTest, TestTargetLevel0) {
|
||||
::libvpx_test::I420VideoSource video("hantro_odd.yuv", 208, 144, 30, 1, 0,
|
||||
@@ -94,6 +124,7 @@ TEST_P(LevelTest, TestTargetLevelApi) {
|
||||
vpx_codec_ctx_t enc;
|
||||
vpx_codec_enc_cfg_t cfg;
|
||||
EXPECT_EQ(VPX_CODEC_OK, vpx_codec_enc_config_default(codec, &cfg, 0));
|
||||
cfg.rc_target_bitrate = 100;
|
||||
EXPECT_EQ(VPX_CODEC_OK, vpx_codec_enc_init(&enc, codec, &cfg, 0));
|
||||
for (int level = 0; level <= 256; ++level) {
|
||||
if (level == 10 || level == 11 || level == 20 || level == 21 ||
|
||||
|
||||
@@ -108,7 +108,7 @@ static int is_psnr_calc_enabled(VP9_COMP *cpi) {
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
static const Vp9LevelSpec vp9_level_defs[VP9_LEVELS] = {
|
||||
const Vp9LevelSpec vp9_level_defs[VP9_LEVELS] = {
|
||||
{ LEVEL_1, 829440, 36864, 200, 400, 2, 1, 4, 8 },
|
||||
{ LEVEL_1_1, 2764800, 73728, 800, 1000, 2, 1, 4, 8 },
|
||||
{ LEVEL_2, 4608000, 122880, 1800, 1500, 2, 1, 4, 8 },
|
||||
@@ -128,6 +128,16 @@ static const Vp9LevelSpec vp9_level_defs[VP9_LEVELS] = {
|
||||
};
|
||||
/* clang-format on */
|
||||
|
||||
static const char *level_fail_messages[TARGET_LEVEL_FAIL_IDS] =
|
||||
{ "The average bit-rate is too high.",
|
||||
"The picture size is too large.",
|
||||
"The luma sample rate is too large.",
|
||||
"The CPB size is too large.",
|
||||
"The compression ratio is too small",
|
||||
"Too many column tiles are used.",
|
||||
"The alt-ref distance is too small.",
|
||||
"Too many reference buffers are used." };
|
||||
|
||||
static INLINE void Scale2Ratio(VPX_SCALING mode, int *hr, int *hs) {
|
||||
switch (mode) {
|
||||
case NORMAL:
|
||||
@@ -224,8 +234,9 @@ VP9_LEVEL vp9_get_level(const Vp9LevelSpec *const level_spec) {
|
||||
|
||||
for (i = 0; i < VP9_LEVELS; ++i) {
|
||||
this_level = &vp9_level_defs[i];
|
||||
if ((double)level_spec->max_luma_sample_rate * (1 + SAMPLE_RATE_GRACE_P) >
|
||||
(double)this_level->max_luma_sample_rate ||
|
||||
if ((double)level_spec->max_luma_sample_rate >
|
||||
(double)this_level->max_luma_sample_rate *
|
||||
(1 + SAMPLE_RATE_GRACE_P) ||
|
||||
level_spec->max_luma_picture_size > this_level->max_luma_picture_size ||
|
||||
level_spec->average_bitrate > this_level->average_bitrate ||
|
||||
level_spec->max_cpb_size > this_level->max_cpb_size ||
|
||||
@@ -878,6 +889,22 @@ static void init_buffer_indices(VP9_COMP *cpi) {
|
||||
cpi->alt_fb_idx = 2;
|
||||
}
|
||||
|
||||
static void init_level_constraint(LevelConstraint *lc) {
|
||||
lc->level_index = -1;
|
||||
lc->max_cpb_size = INT_MAX;
|
||||
lc->max_frame_size = INT_MAX;
|
||||
lc->rc_config_updated = 0;
|
||||
lc->fail_flag = 0;
|
||||
}
|
||||
|
||||
static void set_level_constraint(LevelConstraint *ls, int8_t level_index) {
|
||||
vpx_clear_system_state();
|
||||
ls->level_index = level_index;
|
||||
if (level_index >= 0) {
|
||||
ls->max_cpb_size = vp9_level_defs[level_index].max_cpb_size * (double)1000;
|
||||
}
|
||||
}
|
||||
|
||||
static void init_config(struct VP9_COMP *cpi, VP9EncoderConfig *oxcf) {
|
||||
VP9_COMMON *const cm = &cpi->common;
|
||||
|
||||
@@ -893,6 +920,8 @@ static void init_config(struct VP9_COMP *cpi, VP9EncoderConfig *oxcf) {
|
||||
|
||||
cpi->target_level = oxcf->target_level;
|
||||
cpi->keep_level_stats = oxcf->target_level != LEVEL_MAX;
|
||||
set_level_constraint(&cpi->level_constraint,
|
||||
get_level_index(cpi->target_level));
|
||||
|
||||
cm->width = oxcf->width;
|
||||
cm->height = oxcf->height;
|
||||
@@ -1409,6 +1438,8 @@ void vp9_change_config(struct VP9_COMP *cpi, const VP9EncoderConfig *oxcf) {
|
||||
|
||||
cpi->target_level = oxcf->target_level;
|
||||
cpi->keep_level_stats = oxcf->target_level != LEVEL_MAX;
|
||||
set_level_constraint(&cpi->level_constraint,
|
||||
get_level_index(cpi->target_level));
|
||||
|
||||
if (cm->profile <= PROFILE_1)
|
||||
assert(cm->bit_depth == VPX_BITS_8);
|
||||
@@ -1685,6 +1716,7 @@ VP9_COMP *vp9_create_compressor(VP9EncoderConfig *oxcf,
|
||||
cpi->b_calculate_psnr = CONFIG_INTERNAL_STATS;
|
||||
|
||||
init_level_info(&cpi->level_info);
|
||||
init_level_constraint(&cpi->level_constraint);
|
||||
|
||||
#if CONFIG_INTERNAL_STATS
|
||||
cpi->b_calculate_blockiness = 1;
|
||||
@@ -4313,6 +4345,26 @@ static void adjust_image_stat(double y, double u, double v, double all,
|
||||
}
|
||||
#endif // CONFIG_INTERNAL_STATS
|
||||
|
||||
// Adjust the maximum allowable frame size for the target level.
|
||||
static void level_rc_framerate(VP9_COMP *cpi, int arf_src_index) {
|
||||
RATE_CONTROL *const rc = &cpi->rc;
|
||||
LevelConstraint *const ls = &cpi->level_constraint;
|
||||
VP9_COMMON *const cm = &cpi->common;
|
||||
const double max_cpb_size = ls->max_cpb_size;
|
||||
vpx_clear_system_state();
|
||||
rc->max_frame_bandwidth = VPXMIN(rc->max_frame_bandwidth, ls->max_frame_size);
|
||||
if (frame_is_intra_only(cm)) {
|
||||
rc->max_frame_bandwidth =
|
||||
VPXMIN(rc->max_frame_bandwidth, (int)(max_cpb_size * 0.5));
|
||||
} else if (arf_src_index > 0) {
|
||||
rc->max_frame_bandwidth =
|
||||
VPXMIN(rc->max_frame_bandwidth, (int)(max_cpb_size * 0.4));
|
||||
} else {
|
||||
rc->max_frame_bandwidth =
|
||||
VPXMIN(rc->max_frame_bandwidth, (int)(max_cpb_size * 0.2));
|
||||
}
|
||||
}
|
||||
|
||||
static void update_level_info(VP9_COMP *cpi, size_t *size, int arf_src_index) {
|
||||
VP9_COMMON *const cm = &cpi->common;
|
||||
Vp9LevelInfo *const level_info = &cpi->level_info;
|
||||
@@ -4321,6 +4373,8 @@ static void update_level_info(VP9_COMP *cpi, size_t *size, int arf_src_index) {
|
||||
int i, idx;
|
||||
uint64_t luma_samples, dur_end;
|
||||
const uint32_t luma_pic_size = cm->width * cm->height;
|
||||
LevelConstraint *const level_constraint = &cpi->level_constraint;
|
||||
const int8_t level_index = level_constraint->level_index;
|
||||
double cpb_data_size;
|
||||
|
||||
vpx_clear_system_state();
|
||||
@@ -4431,6 +4485,78 @@ static void update_level_info(VP9_COMP *cpi, size_t *size, int arf_src_index) {
|
||||
if (level_spec->max_col_tiles < (1 << cm->log2_tile_cols)) {
|
||||
level_spec->max_col_tiles = (1 << cm->log2_tile_cols);
|
||||
}
|
||||
|
||||
if (level_index >= 0 && level_constraint->fail_flag == 0) {
|
||||
if (level_spec->max_luma_picture_size >
|
||||
vp9_level_defs[level_index].max_luma_picture_size) {
|
||||
level_constraint->fail_flag |= (1 << LUMA_PIC_SIZE_TOO_LARGE);
|
||||
vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
|
||||
"Failed to encode to the target level %d. %s",
|
||||
vp9_level_defs[level_index].level,
|
||||
level_fail_messages[LUMA_PIC_SIZE_TOO_LARGE]);
|
||||
}
|
||||
|
||||
if ((double)level_spec->max_luma_sample_rate >
|
||||
(double)vp9_level_defs[level_index].max_luma_sample_rate *
|
||||
(1 + SAMPLE_RATE_GRACE_P)) {
|
||||
level_constraint->fail_flag |= (1 << LUMA_SAMPLE_RATE_TOO_LARGE);
|
||||
vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
|
||||
"Failed to encode to the target level %d. %s",
|
||||
vp9_level_defs[level_index].level,
|
||||
level_fail_messages[LUMA_SAMPLE_RATE_TOO_LARGE]);
|
||||
}
|
||||
|
||||
if (level_spec->max_col_tiles > vp9_level_defs[level_index].max_col_tiles) {
|
||||
level_constraint->fail_flag |= (1 << TOO_MANY_COLUMN_TILE);
|
||||
vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
|
||||
"Failed to encode to the target level %d. %s",
|
||||
vp9_level_defs[level_index].level,
|
||||
level_fail_messages[TOO_MANY_COLUMN_TILE]);
|
||||
}
|
||||
|
||||
if (level_spec->min_altref_distance <
|
||||
vp9_level_defs[level_index].min_altref_distance) {
|
||||
level_constraint->fail_flag |= (1 << ALTREF_DIST_TOO_SMALL);
|
||||
vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
|
||||
"Failed to encode to the target level %d. %s",
|
||||
vp9_level_defs[level_index].level,
|
||||
level_fail_messages[ALTREF_DIST_TOO_SMALL]);
|
||||
}
|
||||
|
||||
if (level_spec->max_ref_frame_buffers >
|
||||
vp9_level_defs[level_index].max_ref_frame_buffers) {
|
||||
level_constraint->fail_flag |= (1 << TOO_MANY_REF_BUFFER);
|
||||
vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
|
||||
"Failed to encode to the target level %d. %s",
|
||||
vp9_level_defs[level_index].level,
|
||||
level_fail_messages[TOO_MANY_REF_BUFFER]);
|
||||
}
|
||||
|
||||
if (level_spec->max_cpb_size > vp9_level_defs[level_index].max_cpb_size) {
|
||||
level_constraint->fail_flag |= (1 << CPB_TOO_LARGE);
|
||||
vpx_internal_error(&cm->error, VPX_CODEC_ERROR,
|
||||
"Failed to encode to the target level %d. %s",
|
||||
vp9_level_defs[level_index].level,
|
||||
level_fail_messages[CPB_TOO_LARGE]);
|
||||
}
|
||||
|
||||
// Set an upper bound for the next frame size. It will be used in
|
||||
// level_rc_framerate() before encoding the next frame.
|
||||
cpb_data_size = 0;
|
||||
for (i = 0; i < CPB_WINDOW_SIZE - 1; ++i) {
|
||||
if (i >= level_stats->frame_window_buffer.len) break;
|
||||
idx = (level_stats->frame_window_buffer.start +
|
||||
level_stats->frame_window_buffer.len - 1 - i) %
|
||||
FRAME_WINDOW_SIZE;
|
||||
cpb_data_size += level_stats->frame_window_buffer.buf[idx].size;
|
||||
}
|
||||
cpb_data_size = cpb_data_size / 125.0;
|
||||
level_constraint->max_frame_size =
|
||||
(int)((vp9_level_defs[level_index].max_cpb_size - cpb_data_size) *
|
||||
1000.0);
|
||||
if (level_stats->frame_window_buffer.len < CPB_WINDOW_SIZE - 1)
|
||||
level_constraint->max_frame_size >>= 1;
|
||||
}
|
||||
}
|
||||
|
||||
int vp9_get_compressed_data(VP9_COMP *cpi, unsigned int *frame_flags,
|
||||
@@ -4658,6 +4784,10 @@ int vp9_get_compressed_data(VP9_COMP *cpi, unsigned int *frame_flags,
|
||||
set_frame_size(cpi);
|
||||
}
|
||||
|
||||
if (oxcf->pass != 1 && cpi->level_constraint.level_index >= 0 &&
|
||||
cpi->level_constraint.fail_flag == 0)
|
||||
level_rc_framerate(cpi, arf_src_index);
|
||||
|
||||
if (cpi->oxcf.pass != 0 || cpi->use_svc || frame_is_intra_only(cm) == 1) {
|
||||
for (i = 0; i < MAX_REF_FRAMES; ++i) cpi->scaled_ref_idx[i] = INVALID_IDX;
|
||||
}
|
||||
|
||||
@@ -237,7 +237,7 @@ typedef struct VP9EncoderConfig {
|
||||
|
||||
int max_threads;
|
||||
|
||||
int target_level;
|
||||
unsigned int target_level;
|
||||
|
||||
vpx_fixed_buf_t two_pass_stats_in;
|
||||
struct vpx_codec_pkt_list *output_pkt_list;
|
||||
@@ -341,6 +341,8 @@ typedef struct {
|
||||
uint8_t max_ref_frame_buffers;
|
||||
} Vp9LevelSpec;
|
||||
|
||||
extern const Vp9LevelSpec vp9_level_defs[VP9_LEVELS];
|
||||
|
||||
typedef struct {
|
||||
int64_t ts; // timestamp
|
||||
uint32_t luma_samples;
|
||||
@@ -368,6 +370,26 @@ typedef struct {
|
||||
Vp9LevelSpec level_spec;
|
||||
} Vp9LevelInfo;
|
||||
|
||||
typedef enum {
|
||||
BITRATE_TOO_LARGE = 0,
|
||||
LUMA_PIC_SIZE_TOO_LARGE = 1,
|
||||
LUMA_SAMPLE_RATE_TOO_LARGE = 2,
|
||||
CPB_TOO_LARGE = 3,
|
||||
COMPRESSION_RATIO_TOO_SMALL = 4,
|
||||
TOO_MANY_COLUMN_TILE = 5,
|
||||
ALTREF_DIST_TOO_SMALL = 6,
|
||||
TOO_MANY_REF_BUFFER = 7,
|
||||
TARGET_LEVEL_FAIL_IDS = 8
|
||||
} TARGET_LEVEL_FAIL_ID;
|
||||
|
||||
typedef struct {
|
||||
int8_t level_index;
|
||||
uint8_t rc_config_updated;
|
||||
uint8_t fail_flag;
|
||||
int max_frame_size; // in bits
|
||||
double max_cpb_size; // in bits
|
||||
} LevelConstraint;
|
||||
|
||||
typedef struct VP9_COMP {
|
||||
QUANTS quants;
|
||||
ThreadData td;
|
||||
@@ -611,6 +633,8 @@ typedef struct VP9_COMP {
|
||||
// Previous Partition Info
|
||||
BLOCK_SIZE *prev_partition;
|
||||
int8_t *prev_segment_id;
|
||||
|
||||
LevelConstraint level_constraint;
|
||||
} VP9_COMP;
|
||||
|
||||
void vp9_initialize_enc(void);
|
||||
@@ -766,6 +790,14 @@ static INLINE int *cond_cost_list(const struct VP9_COMP *cpi, int *cost_list) {
|
||||
return cpi->sf.mv.subpel_search_method != SUBPEL_TREE ? cost_list : NULL;
|
||||
}
|
||||
|
||||
static INLINE int get_level_index(VP9_LEVEL level) {
|
||||
int i;
|
||||
for (i = 0; i < VP9_LEVELS; ++i) {
|
||||
if (level == vp9_level_defs[i].level) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
VP9_LEVEL vp9_get_level(const Vp9LevelSpec *const level_spec);
|
||||
|
||||
void vp9_new_framerate(VP9_COMP *cpi, double framerate);
|
||||
|
||||
@@ -390,6 +390,50 @@ static int get_image_bps(const vpx_image_t *img) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Modify the encoder config for the target level.
|
||||
static void config_target_level(VP9EncoderConfig *oxcf) {
|
||||
double max_average_bitrate; // in bits per second
|
||||
int max_over_shoot_pct;
|
||||
const int target_level_index = get_level_index(oxcf->target_level);
|
||||
|
||||
vpx_clear_system_state();
|
||||
assert(target_level_index >= 0);
|
||||
assert(target_level_index < VP9_LEVELS);
|
||||
|
||||
// Maximum target bit-rate is level_limit * 80%.
|
||||
max_average_bitrate =
|
||||
vp9_level_defs[target_level_index].average_bitrate * 800.0;
|
||||
if ((double)oxcf->target_bandwidth > max_average_bitrate)
|
||||
oxcf->target_bandwidth = (int64_t)(max_average_bitrate);
|
||||
if (oxcf->ss_number_layers == 1 && oxcf->pass != 0)
|
||||
oxcf->ss_target_bitrate[0] = (int)oxcf->target_bandwidth;
|
||||
|
||||
// Adjust max over-shoot percentage.
|
||||
max_over_shoot_pct =
|
||||
(int)((max_average_bitrate * 1.10 - (double)oxcf->target_bandwidth) *
|
||||
100 / (double)(oxcf->target_bandwidth));
|
||||
if (oxcf->over_shoot_pct > max_over_shoot_pct)
|
||||
oxcf->over_shoot_pct = max_over_shoot_pct;
|
||||
|
||||
// Adjust worst allowed quantizer.
|
||||
oxcf->worst_allowed_q = vp9_quantizer_to_qindex(63);
|
||||
|
||||
// Adjust minimum art-ref distance.
|
||||
if (oxcf->min_gf_interval <
|
||||
(int)vp9_level_defs[target_level_index].min_altref_distance)
|
||||
oxcf->min_gf_interval =
|
||||
(int)vp9_level_defs[target_level_index].min_altref_distance;
|
||||
|
||||
// Adjust maximum column tiles.
|
||||
if (vp9_level_defs[target_level_index].max_col_tiles <
|
||||
(1 << oxcf->tile_columns)) {
|
||||
while (oxcf->tile_columns > 0 &&
|
||||
vp9_level_defs[target_level_index].max_col_tiles <
|
||||
(1 << oxcf->tile_columns))
|
||||
--oxcf->tile_columns;
|
||||
}
|
||||
}
|
||||
|
||||
static vpx_codec_err_t set_encoder_config(
|
||||
VP9EncoderConfig *oxcf, const vpx_codec_enc_cfg_t *cfg,
|
||||
const struct vp9_extracfg *extra_cfg) {
|
||||
@@ -533,6 +577,8 @@ static vpx_codec_err_t set_encoder_config(
|
||||
} else if (oxcf->ts_number_layers == 1) {
|
||||
oxcf->ts_rate_decimator[0] = 1;
|
||||
}
|
||||
|
||||
if (get_level_index(oxcf->target_level) >= 0) config_target_level(oxcf);
|
||||
/*
|
||||
printf("Current VP9 Settings: \n");
|
||||
printf("target_bandwidth: %d\n", oxcf->target_bandwidth);
|
||||
@@ -1003,6 +1049,28 @@ static vpx_codec_err_t encoder_encode(vpx_codec_alg_priv_t *ctx,
|
||||
|
||||
if (cpi == NULL) return VPX_CODEC_INVALID_PARAM;
|
||||
|
||||
if (cpi->oxcf.pass == 2 && cpi->level_constraint.level_index >= 0 &&
|
||||
!cpi->level_constraint.rc_config_updated) {
|
||||
SVC *const svc = &cpi->svc;
|
||||
const int is_two_pass_svc =
|
||||
(svc->number_spatial_layers > 1) || (svc->number_temporal_layers > 1);
|
||||
const VP9EncoderConfig *const oxcf = &cpi->oxcf;
|
||||
TWO_PASS *const twopass = &cpi->twopass;
|
||||
FIRSTPASS_STATS *stats = &twopass->total_stats;
|
||||
if (is_two_pass_svc) {
|
||||
const double frame_rate = 10000000.0 * stats->count / stats->duration;
|
||||
vp9_update_spatial_layer_framerate(cpi, frame_rate);
|
||||
twopass->bits_left =
|
||||
(int64_t)(stats->duration *
|
||||
svc->layer_context[svc->spatial_layer_id].target_bandwidth /
|
||||
10000000.0);
|
||||
} else {
|
||||
twopass->bits_left =
|
||||
(int64_t)(stats->duration * oxcf->target_bandwidth / 10000000.0);
|
||||
}
|
||||
cpi->level_constraint.rc_config_updated = 1;
|
||||
}
|
||||
|
||||
if (img != NULL) {
|
||||
res = validate_img(ctx, img);
|
||||
if (res == VPX_CODEC_OK) {
|
||||
|
||||
Reference in New Issue
Block a user