public ::libvpx_test::CodecTestWith2Params<int, Vp8RCTestVideo> {
public:
Vp8RcInterfaceTest()
- : EncoderTest(GET_PARAM(0)), key_interval_(3000), encoder_exit_(false) {}
+ : EncoderTest(GET_PARAM(0)), key_interval_(3000), encoder_exit_(false),
+ frame_drop_thresh_(0) {}
~Vp8RcInterfaceTest() override = default;
protected:
}
int qp;
encoder->Control(VP8E_GET_LAST_QUANTIZER, &qp);
- rc_api_->ComputeQP(frame_params_);
- ASSERT_EQ(rc_api_->GetQP(), qp);
+ if (rc_api_->ComputeQP(frame_params_) == libvpx::FrameDropDecision::kOk) {
+ ASSERT_EQ(rc_api_->GetQP(), qp);
+ } else {
+ num_drops_++;
+ }
}
void FramePktHook(const vpx_codec_cx_pkt_t *pkt) override {
void RunOneLayer() {
test_video_ = GET_PARAM(2);
target_bitrate_ = GET_PARAM(1);
- if (test_video_.width == 1280 && target_bitrate_ == 200) return;
- if (test_video_.width == 640 && target_bitrate_ == 1000) return;
SetConfig();
rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
ASSERT_TRUE(rc_api_->UpdateRateControl(rc_cfg_));
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
}
+ void RunOneLayerDropFrames() {
+ test_video_ = GET_PARAM(2);
+ target_bitrate_ = GET_PARAM(1);
+ frame_drop_thresh_ = 30;
+ num_drops_ = 0;
+ // Use lower target_bitrate and max_quantizer to trigger drops.
+ target_bitrate_ = target_bitrate_ >> 2;
+ SetConfig();
+ rc_cfg_.max_quantizer = 56;
+ cfg_.rc_max_quantizer = 56;
+ rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
+ ASSERT_TRUE(rc_api_->UpdateRateControl(rc_cfg_));
+
+ ::libvpx_test::I420VideoSource video(test_video_.name, test_video_.width,
+ test_video_.height, 30, 1, 0,
+ test_video_.frames);
+
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ // Check that some frames were dropped, otherwise test has no value.
+ ASSERT_GE(num_drops_, 1);
+ }
+
void RunPeriodicKey() {
test_video_ = GET_PARAM(2);
target_bitrate_ = GET_PARAM(1);
- if (test_video_.width == 1280 && target_bitrate_ == 200) return;
- if (test_video_.width == 640 && target_bitrate_ == 1000) return;
key_interval_ = 100;
+ frame_drop_thresh_ = 30;
SetConfig();
rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
ASSERT_TRUE(rc_api_->UpdateRateControl(rc_cfg_));
void RunTemporalLayers2TL() {
test_video_ = GET_PARAM(2);
target_bitrate_ = GET_PARAM(1);
- if (test_video_.width == 1280 && target_bitrate_ == 200) return;
- if (test_video_.width == 640 && target_bitrate_ == 1000) return;
SetConfigTemporalLayers(2);
rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
ASSERT_TRUE(rc_api_->UpdateRateControl(rc_cfg_));
void RunTemporalLayers3TL() {
test_video_ = GET_PARAM(2);
target_bitrate_ = GET_PARAM(1);
- if (test_video_.width == 1280 && target_bitrate_ == 200) return;
- if (test_video_.width == 640 && target_bitrate_ == 1000) return;
SetConfigTemporalLayers(3);
rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
ASSERT_TRUE(rc_api_->UpdateRateControl(rc_cfg_));
ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
}
+ void RunTemporalLayers3TLDropFrames() {
+ test_video_ = GET_PARAM(2);
+ target_bitrate_ = GET_PARAM(1);
+ frame_drop_thresh_ = 30;
+ num_drops_ = 0;
+ // Use lower target_bitrate and max_quantizer to trigger drops.
+ target_bitrate_ = target_bitrate_ >> 2;
+ SetConfigTemporalLayers(3);
+ rc_cfg_.max_quantizer = 56;
+ cfg_.rc_max_quantizer = 56;
+ rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
+ ASSERT_TRUE(rc_api_->UpdateRateControl(rc_cfg_));
+
+ ::libvpx_test::I420VideoSource video(test_video_.name, test_video_.width,
+ test_video_.height, 30, 1, 0,
+ test_video_.frames);
+
+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
+ // Check that some frames were dropped, otherwise test has no value.
+ ASSERT_GE(num_drops_, 1);
+ }
+
private:
void SetConfig() {
rc_cfg_.width = test_video_.width;
rc_cfg_.max_intra_bitrate_pct = 1000;
rc_cfg_.framerate = 30.0;
rc_cfg_.layer_target_bitrate[0] = target_bitrate_;
+ rc_cfg_.frame_drop_thresh = frame_drop_thresh_;
// Encoder settings for ground truth.
cfg_.g_w = test_video_.width;
cfg_.rc_target_bitrate = target_bitrate_;
cfg_.kf_min_dist = key_interval_;
cfg_.kf_max_dist = key_interval_;
+ cfg_.rc_dropframe_thresh = frame_drop_thresh_;
}
void SetConfigTemporalLayers(int temporal_layers) {
rc_cfg_.overshoot_pct = 50;
rc_cfg_.max_intra_bitrate_pct = 1000;
rc_cfg_.framerate = 30.0;
+ rc_cfg_.frame_drop_thresh = frame_drop_thresh_;
if (temporal_layers == 2) {
rc_cfg_.layer_target_bitrate[0] = 60 * target_bitrate_ / 100;
rc_cfg_.layer_target_bitrate[1] = target_bitrate_;
cfg_.rc_target_bitrate = target_bitrate_;
cfg_.kf_min_dist = key_interval_;
cfg_.kf_max_dist = key_interval_;
+ cfg_.rc_dropframe_thresh = frame_drop_thresh_;
// 2 Temporal layers, no spatial layers, CBR mode.
cfg_.ss_number_layers = 1;
cfg_.ts_number_layers = temporal_layers;
Vp8RCTestVideo test_video_;
libvpx::VP8FrameParamsQpRTC frame_params_;
bool encoder_exit_;
+ int frame_drop_thresh_;
+ int num_drops_;
};
TEST_P(Vp8RcInterfaceTest, OneLayer) { RunOneLayer(); }
+TEST_P(Vp8RcInterfaceTest, OneLayerDropFrames) { RunOneLayerDropFrames(); }
+
TEST_P(Vp8RcInterfaceTest, OneLayerPeriodicKey) { RunPeriodicKey(); }
TEST_P(Vp8RcInterfaceTest, TemporalLayers2TL) { RunTemporalLayers2TL(); }
TEST_P(Vp8RcInterfaceTest, TemporalLayers3TL) { RunTemporalLayers3TL(); }
+TEST_P(Vp8RcInterfaceTest, TemporalLayers3TLDropFrames) {
+ RunTemporalLayers3TLDropFrames();
+}
+
VP8_INSTANTIATE_TEST_SUITE(Vp8RcInterfaceTest,
::testing::Values(200, 400, 1000),
::testing::ValuesIn(kVp8RCTestVectors));
cpi->force_maxqp = 0;
cpi->frames_since_last_drop_overshoot = 0;
cpi->rt_always_update_correction_factor = 0;
+ cpi->rt_drop_recode_on_overshoot = 1;
cpi->b_calculate_psnr = CONFIG_INTERNAL_STATS;
#if CONFIG_INTERNAL_STATS
vp8_yv12_extend_frame_borders(cm->frame_to_show);
}
+// Return 1 if frame is to be dropped. Update frame drop decimation
+// counters.
+int vp8_check_drop_buffer(VP8_COMP *cpi) {
+ VP8_COMMON *cm = &cpi->common;
+ int drop_mark = (int)(cpi->oxcf.drop_frames_water_mark *
+ cpi->oxcf.optimal_buffer_level / 100);
+ int drop_mark75 = drop_mark * 2 / 3;
+ int drop_mark50 = drop_mark / 4;
+ int drop_mark25 = drop_mark / 8;
+ if (cpi->drop_frames_allowed) {
+ /* The reset to decimation 0 is only done here for one pass.
+ * Once it is set two pass leaves decimation on till the next kf.
+ */
+ if (cpi->buffer_level > drop_mark && cpi->decimation_factor > 0) {
+ cpi->decimation_factor--;
+ }
+
+ if (cpi->buffer_level > drop_mark75 && cpi->decimation_factor > 0) {
+ cpi->decimation_factor = 1;
+
+ } else if (cpi->buffer_level < drop_mark25 &&
+ (cpi->decimation_factor == 2 || cpi->decimation_factor == 3)) {
+ cpi->decimation_factor = 3;
+ } else if (cpi->buffer_level < drop_mark50 &&
+ (cpi->decimation_factor == 1 || cpi->decimation_factor == 2)) {
+ cpi->decimation_factor = 2;
+ } else if (cpi->buffer_level < drop_mark75 &&
+ (cpi->decimation_factor == 0 || cpi->decimation_factor == 1)) {
+ cpi->decimation_factor = 1;
+ }
+ }
+
+ /* The following decimates the frame rate according to a regular
+ * pattern (i.e. to 1/2 or 2/3 frame rate) This can be used to help
+ * prevent buffer under-run in CBR mode. Alternatively it might be
+ * desirable in some situations to drop frame rate but throw more bits
+ * at each frame.
+ *
+ * Note that dropping a key frame can be problematic if spatial
+ * resampling is also active
+ */
+ if (cpi->decimation_factor > 0 && cpi->drop_frames_allowed) {
+ switch (cpi->decimation_factor) {
+ case 1:
+ cpi->per_frame_bandwidth = cpi->per_frame_bandwidth * 3 / 2;
+ break;
+ case 2:
+ cpi->per_frame_bandwidth = cpi->per_frame_bandwidth * 5 / 4;
+ break;
+ case 3:
+ cpi->per_frame_bandwidth = cpi->per_frame_bandwidth * 5 / 4;
+ break;
+ }
+
+ /* Note that we should not throw out a key frame (especially when
+ * spatial resampling is enabled).
+ */
+ if (cm->frame_type == KEY_FRAME) {
+ cpi->decimation_count = cpi->decimation_factor;
+ } else if (cpi->decimation_count > 0) {
+ cpi->decimation_count--;
+
+ cpi->bits_off_target += cpi->av_per_frame_bandwidth;
+ if (cpi->bits_off_target > cpi->oxcf.maximum_buffer_size) {
+ cpi->bits_off_target = cpi->oxcf.maximum_buffer_size;
+ }
+
+#if CONFIG_MULTI_RES_ENCODING
+ vp8_store_drop_frame_info(cpi);
+#endif
+
+ cm->current_video_frame++;
+ cpi->frames_since_key++;
+ cpi->ext_refresh_frame_flags_pending = 0;
+ // We advance the temporal pattern for dropped frames.
+ cpi->temporal_pattern_counter++;
+
+#if CONFIG_INTERNAL_STATS
+ cpi->count++;
+#endif
+
+ cpi->buffer_level = cpi->bits_off_target;
+
+ if (cpi->oxcf.number_of_layers > 1) {
+ unsigned int i;
+
+ /* Propagate bits saved by dropping the frame to higher
+ * layers
+ */
+ for (i = cpi->current_layer + 1; i < cpi->oxcf.number_of_layers; ++i) {
+ LAYER_CONTEXT *lc = &cpi->layer_context[i];
+ lc->bits_off_target += (int)(lc->target_bandwidth / lc->framerate);
+ if (lc->bits_off_target > lc->maximum_buffer_size) {
+ lc->bits_off_target = lc->maximum_buffer_size;
+ }
+ lc->buffer_level = lc->bits_off_target;
+ }
+ }
+ return 1;
+ } else {
+ cpi->decimation_count = cpi->decimation_factor;
+ }
+ } else {
+ cpi->decimation_count = 0;
+ }
+ return 0;
+}
static void encode_frame_to_data_rate(VP8_COMP *cpi, size_t *size,
unsigned char *dest,
int undershoot_seen = 0;
#endif
- int drop_mark = (int)(cpi->oxcf.drop_frames_water_mark *
- cpi->oxcf.optimal_buffer_level / 100);
- int drop_mark75 = drop_mark * 2 / 3;
- int drop_mark50 = drop_mark / 4;
- int drop_mark25 = drop_mark / 8;
-
/* Clear down mmx registers to allow floating point in what follows */
vpx_clear_system_state();
update_rd_ref_frame_probs(cpi);
- if (cpi->drop_frames_allowed) {
- /* The reset to decimation 0 is only done here for one pass.
- * Once it is set two pass leaves decimation on till the next kf.
- */
- if ((cpi->buffer_level > drop_mark) && (cpi->decimation_factor > 0)) {
- cpi->decimation_factor--;
- }
-
- if (cpi->buffer_level > drop_mark75 && cpi->decimation_factor > 0) {
- cpi->decimation_factor = 1;
-
- } else if (cpi->buffer_level < drop_mark25 &&
- (cpi->decimation_factor == 2 || cpi->decimation_factor == 3)) {
- cpi->decimation_factor = 3;
- } else if (cpi->buffer_level < drop_mark50 &&
- (cpi->decimation_factor == 1 || cpi->decimation_factor == 2)) {
- cpi->decimation_factor = 2;
- } else if (cpi->buffer_level < drop_mark75 &&
- (cpi->decimation_factor == 0 || cpi->decimation_factor == 1)) {
- cpi->decimation_factor = 1;
- }
- }
-
- /* The following decimates the frame rate according to a regular
- * pattern (i.e. to 1/2 or 2/3 frame rate) This can be used to help
- * prevent buffer under-run in CBR mode. Alternatively it might be
- * desirable in some situations to drop frame rate but throw more bits
- * at each frame.
- *
- * Note that dropping a key frame can be problematic if spatial
- * resampling is also active
- */
- if (cpi->decimation_factor > 0 && cpi->drop_frames_allowed) {
- switch (cpi->decimation_factor) {
- case 1:
- cpi->per_frame_bandwidth = cpi->per_frame_bandwidth * 3 / 2;
- break;
- case 2:
- cpi->per_frame_bandwidth = cpi->per_frame_bandwidth * 5 / 4;
- break;
- case 3:
- cpi->per_frame_bandwidth = cpi->per_frame_bandwidth * 5 / 4;
- break;
- }
-
- /* Note that we should not throw out a key frame (especially when
- * spatial resampling is enabled).
- */
- if (cm->frame_type == KEY_FRAME) {
- cpi->decimation_count = cpi->decimation_factor;
- } else if (cpi->decimation_count > 0) {
- cpi->decimation_count--;
-
- cpi->bits_off_target += cpi->av_per_frame_bandwidth;
- if (cpi->bits_off_target > cpi->oxcf.maximum_buffer_size) {
- cpi->bits_off_target = cpi->oxcf.maximum_buffer_size;
- }
-
-#if CONFIG_MULTI_RES_ENCODING
- vp8_store_drop_frame_info(cpi);
-#endif
-
- cm->current_video_frame++;
- cpi->frames_since_key++;
- cpi->ext_refresh_frame_flags_pending = 0;
- // We advance the temporal pattern for dropped frames.
- cpi->temporal_pattern_counter++;
-
-#if CONFIG_INTERNAL_STATS
- cpi->count++;
-#endif
-
- cpi->buffer_level = cpi->bits_off_target;
-
- if (cpi->oxcf.number_of_layers > 1) {
- unsigned int i;
-
- /* Propagate bits saved by dropping the frame to higher
- * layers
- */
- for (i = cpi->current_layer + 1; i < cpi->oxcf.number_of_layers; ++i) {
- LAYER_CONTEXT *lc = &cpi->layer_context[i];
- lc->bits_off_target += (int)(lc->target_bandwidth / lc->framerate);
- if (lc->bits_off_target > lc->maximum_buffer_size) {
- lc->bits_off_target = lc->maximum_buffer_size;
- }
- lc->buffer_level = lc->bits_off_target;
- }
- }
-
- return;
- } else {
- cpi->decimation_count = cpi->decimation_factor;
- }
- } else {
- cpi->decimation_count = 0;
+ if (vp8_check_drop_buffer(cpi)) {
+ return;
}
/* Decide how big to make the frame */
/* transform / motion compensation build reconstruction frame */
vp8_encode_frame(cpi);
- if (cpi->pass == 0 && cpi->oxcf.end_usage == USAGE_STREAM_FROM_SERVER) {
+ if (cpi->pass == 0 && cpi->oxcf.end_usage == USAGE_STREAM_FROM_SERVER &&
+ cpi->rt_drop_recode_on_overshoot == 1) {
if (vp8_drop_encodedframe_overshoot(cpi, Q)) {
vpx_clear_system_state();
return;
// Always update correction factor used for rate control after each frame for
// realtime encoding.
int rt_always_update_correction_factor;
+
+ // Flag to indicate frame may be dropped due to large expected overshoot,
+ // and re-encoded on next frame at max_qp.
+ int rt_drop_recode_on_overshoot;
} VP8_COMP;
void vp8_initialize_enc(void);
void vp8_set_speed_features(VP8_COMP *cpi);
+int vp8_check_drop_buffer(VP8_COMP *cpi);
+
#ifdef __cplusplus
} // extern "C"
#endif
static vpx_codec_err_t ctrl_set_rtc_external_ratectrl(vpx_codec_alg_priv_t *ctx,
va_list args) {
VP8_COMP *cpi = ctx->cpi;
- const unsigned int data = CAST(VP8E_SET_GF_CBR_BOOST_PCT, args);
+ const unsigned int data = CAST(VP8E_SET_RTC_EXTERNAL_RATECTRL, args);
if (data) {
cpi->cyclic_refresh_mode_enabled = 0;
cpi->rt_always_update_correction_factor = 1;
+ cpi->rt_drop_recode_on_overshoot = 0;
}
return VPX_CODEC_OK;
}
cpi_->buffered_mode = oxcf->optimal_buffer_level > 0;
oxcf->under_shoot_pct = rc_cfg.undershoot_pct;
oxcf->over_shoot_pct = rc_cfg.overshoot_pct;
+ oxcf->drop_frames_water_mark = rc_cfg.frame_drop_thresh;
+ if (oxcf->drop_frames_water_mark > 0) cpi_->drop_frames_allowed = 1;
cpi_->oxcf.rc_max_intra_bitrate_pct = rc_cfg.max_intra_bitrate_pct;
cpi_->framerate = rc_cfg.framerate;
for (int i = 0; i < KEY_FRAME_CONTEXT; ++i) {
return true;
}
-void VP8RateControlRTC::ComputeQP(const VP8FrameParamsQpRTC &frame_params) {
+FrameDropDecision VP8RateControlRTC::ComputeQP(
+ const VP8FrameParamsQpRTC &frame_params) {
VP8_COMMON *const cm = &cpi_->common;
vpx_clear_system_state();
if (cpi_->oxcf.number_of_layers > 1) {
cpi_->common.frame_flags |= FRAMEFLAGS_KEY;
}
- vp8_pick_frame_size(cpi_);
+ cpi_->per_frame_bandwidth = static_cast<int>(
+ round(cpi_->oxcf.target_bandwidth / cpi_->output_framerate));
+ if (vp8_check_drop_buffer(cpi_)) {
+ if (cpi_->oxcf.number_of_layers > 1) vp8_save_layer_context(cpi_);
+ return FrameDropDecision::kDrop;
+ }
+
+ if (!vp8_pick_frame_size(cpi_)) {
+ cm->current_video_frame++;
+ cpi_->frames_since_key++;
+ cpi_->ext_refresh_frame_flags_pending = 0;
+ if (cpi_->oxcf.number_of_layers > 1) vp8_save_layer_context(cpi_);
+ return FrameDropDecision::kDrop;
+ }
if (cpi_->buffer_level >= cpi_->oxcf.optimal_buffer_level &&
cpi_->buffered_mode) {
q_ = vp8_regulate_q(cpi_, cpi_->this_frame_target);
vp8_set_quantizer(cpi_, q_);
vpx_clear_system_state();
+ return FrameDropDecision::kOk;
}
int VP8RateControlRTC::GetQP() const { return q_; }
int temporal_layer_id;
};
+enum class FrameDropDecision {
+ kOk, // Frame is encoded.
+ kDrop, // Frame is dropped.
+};
+
class VP8RateControlRTC {
public:
static std::unique_ptr<VP8RateControlRTC> Create(
// level is calculated from frame qp.
int GetLoopfilterLevel() const;
// int GetLoopfilterLevel() const;
- void ComputeQP(const VP8FrameParamsQpRTC &frame_params);
+ // ComputeQP returns the QP is the frame is not dropped (kOk return),
+ // otherwise it returns kDrop and subsequent GetQP and PostEncodeUpdate
+ // are not to be called.
+ FrameDropDecision ComputeQP(const VP8FrameParamsQpRTC &frame_params);
// Feedback to rate control with the size of current encoded frame
void PostEncodeUpdate(uint64_t encoded_frame_size);