From 745979bc2913b4f26847084699d9fddd68b60410 Mon Sep 17 00:00:00 2001 From: Jerome Jiang Date: Tue, 24 Mar 2020 15:13:59 -0700 Subject: [PATCH] vp9: add rate control interface for RTC Add vp9 RTC rate control without creating encoder, to allow external codecs to use vp9 rate control. A new library (libvp9rc.a) will be built. Applications using this interface must be linked with the library. BUG=1060775 Change-Id: Ib3e597256725a37d2d104e1e1a1733c469991b03 --- libs.mk | 52 ++++++++++ test/ratectrl_rtc_test.cc | 229 +++++++++++++++++++++++++++++++++++++++++++++ test/test.mk | 2 + vp9/encoder/vp9_encoder.c | 55 ++++++----- vp9/encoder/vp9_encoder.h | 8 ++ vp9/encoder/vp9_ratectrl.c | 22 ++--- vp9/encoder/vp9_ratectrl.h | 3 + vp9/ratectrl_rtc.cc | 173 ++++++++++++++++++++++++++++++++++ vp9/ratectrl_rtc.h | 116 +++++++++++++++++++++++ 9 files changed, 624 insertions(+), 36 deletions(-) create mode 100644 test/ratectrl_rtc_test.cc create mode 100644 vp9/ratectrl_rtc.cc create mode 100644 vp9/ratectrl_rtc.h diff --git a/libs.mk b/libs.mk index 569dbdc..ffb8dee 100644 --- a/libs.mk +++ b/libs.mk @@ -397,6 +397,10 @@ TEST_INTRA_PRED_SPEED_BIN=./test_intra_pred_speed$(EXE_SFX) TEST_INTRA_PRED_SPEED_SRCS=$(addprefix test/,$(call enabled,TEST_INTRA_PRED_SPEED_SRCS)) TEST_INTRA_PRED_SPEED_OBJS := $(sort $(call objs,$(TEST_INTRA_PRED_SPEED_SRCS))) +RC_INTERFACE_TEST_BIN=./test_rc_interface$(EXE_SFX) +RC_INTERFACE_TEST_SRCS=$(addprefix test/,$(call enabled,RC_INTERFACE_TEST_SRCS)) +RC_INTERFACE_TEST_OBJS := $(sort $(call objs,$(RC_INTERFACE_TEST_SRCS))) + libvpx_test_srcs.txt: @echo " [CREATE] $@" @echo $(LIBVPX_TEST_SRCS) | xargs -n1 echo | LC_ALL=C sort -u > $@ @@ -486,6 +490,25 @@ test_intra_pred_speed.$(VCPROJ_SFX): $(TEST_INTRA_PRED_SPEED_SRCS) vpx.$(VCPROJ_ -I. -I"$(SRC_PATH_BARE)/third_party/googletest/src/include" \ -L. -l$(CODEC_LIB) -l$(GTEST_LIB) $^ endif # TEST_INTRA_PRED_SPEED + +ifneq ($(strip $(RC_INTERFACE_TEST_OBJS)),) +PROJECTS-$(CONFIG_MSVS) += test_rc_interface.$(VCPROJ_SFX) +test_rc_interface.$(VCPROJ_SFX): \ + $(RC_INTERFACE_TEST_SRCS) vpx.$(VCPROJ_SFX) gtest.$(VCPROJ_SFX) + @echo " [CREATE] $@" + $(qexec)$(GEN_VCPROJ) \ + --exe \ + --target=$(TOOLCHAIN) \ + --name=test_rc_interface \ + -D_VARIADIC_MAX=10 \ + --proj-guid=CD837F5F-52D8-4314-A370-895D614166A7 \ + --ver=$(CONFIG_VS_VERSION) \ + --src-path-bare="$(SRC_PATH_BARE)" \ + $(if $(CONFIG_STATIC_MSVCRT),--static-crt) \ + --out=$@ $(INTERNAL_CFLAGS) $(CFLAGS) \ + -I.-I"$(SRC_PATH_BARE)/third_party/googletest/src/include" \ + -L. -l$(CODEC_LIB) -l$(GTEST_LIB) -lvp9rc$^ +endif # RC_INTERFACE_TEST endif else @@ -503,6 +526,21 @@ OBJS-yes += $(GTEST_OBJS) LIBS-yes += $(BUILD_PFX)libgtest.a $(BUILD_PFX)libgtest_g.a $(BUILD_PFX)libgtest_g.a: $(GTEST_OBJS) +ifeq ($(CONFIG_VP9_ENCODER),yes) + VP9_PREFIX=vp9/ + include $(SRC_PATH_BARE)/$(VP9_PREFIX)vp9cx.mk + RC_RTC_SRCS := $(addprefix $(VP9_PREFIX),$(call enabled,VP9_CX_SRCS)) + RC_RTC_SRCS += $(VP9_PREFIX)vp9cx.mk vpx/vp8.h vpx/vp8cx.h + RC_RTC_SRCS += $(VP9_PREFIX)ratectrl_rtc.cc + RC_RTC_SRCS += $(VP9_PREFIX)ratectrl_rtc.h + VP9_CX_SRCS-$(CONFIG_VP9_ENCODER) += ratectrl_rtc.cc + VP9_CX_SRCS-$(CONFIG_VP9_ENCODER) += ratectrl_rtc.h + RC_RTC_OBJS=$(call objs,$(RC_RTC_SRCS)) + OBJS-yes += $(RC_RTC_OBJS) + LIBS-yes += $(BUILD_PFX)libvp9rc.a $(BUILD_PFX)libvp9rc_g.a + $(BUILD_PFX)libvp9rc_g.a: $(RC_RTC_OBJS) +endif + LIBVPX_TEST_OBJS=$(sort $(call objs,$(LIBVPX_TEST_SRCS))) $(LIBVPX_TEST_OBJS) $(LIBVPX_TEST_OBJS:.o=.d): CXXFLAGS += $(GTEST_INCLUDES) OBJS-yes += $(LIBVPX_TEST_OBJS) @@ -527,6 +565,18 @@ $(eval $(call linkerxx_template,$(TEST_INTRA_PRED_SPEED_BIN), \ -L. -lvpx -lgtest $(extralibs) -lm)) endif # TEST_INTRA_PRED_SPEED +ifneq ($(strip $(RC_INTERFACE_TEST_OBJS)),) +$(RC_INTERFACE_TEST_OBJS) $(RC_INTERFACE_TEST_OBJS:.o=.d): \ + CXXFLAGS += $(GTEST_INCLUDES) +OBJS-yes += $(RC_INTERFACE_TEST_OBJS) +BINS-yes += $(RC_INTERFACE_TEST_BIN) + +$(RC_INTERFACE_TEST_BIN): $(TEST_LIBS) libvp9rc.a +$(eval $(call linkerxx_template,$(RC_INTERFACE_TEST_BIN), \ + $(RC_INTERFACE_TEST_OBJS) \ + -L. -lvpx -lgtest -lvp9rc $(extralibs) -lm)) +endif # RC_INTERFACE_TEST + endif # CONFIG_UNIT_TESTS # Install test sources only if codec source is included @@ -534,6 +584,7 @@ INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(patsubst $(SRC_PATH_BARE)/%,%,\ $(shell find $(SRC_PATH_BARE)/third_party/googletest -type f)) INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(LIBVPX_TEST_SRCS) INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(TEST_INTRA_PRED_SPEED_SRCS) +INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(RC_INTERFACE_TEST_SRCS) define test_shard_template test:: test_shard.$(1) @@ -574,6 +625,7 @@ endif ## Update the global src list SRCS += $(CODEC_SRCS) $(LIBVPX_TEST_SRCS) $(GTEST_SRCS) +SRCS += $(RC_INTERFACE_TEST_SRCS) ## ## vpxdec/vpxenc tests. diff --git a/test/ratectrl_rtc_test.cc b/test/ratectrl_rtc_test.cc new file mode 100644 index 0000000..e044885 --- /dev/null +++ b/test/ratectrl_rtc_test.cc @@ -0,0 +1,229 @@ +/* + * Copyright (c) 2020 The WebM project authors. 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 "vp9/ratectrl_rtc.h" + +#include // NOLINT +#include + +#include "./vpx_config.h" +#include "third_party/googletest/src/include/gtest/gtest.h" +#include "test/codec_factory.h" +#include "test/encode_test_driver.h" +#include "test/util.h" +#include "test/video_source.h" +#include "vpx/vpx_codec.h" +#include "vpx_ports/bitops.h" + +namespace { + +const size_t kNumFrame = 850; + +struct FrameInfo { + friend std::istream &operator>>(std::istream &is, FrameInfo &info) { + is >> info.frame_id >> info.spatial_id >> info.temporal_id >> info.base_q >> + info.target_bandwidth >> info.buffer_level >> info.filter_level_ >> + info.bytes_used; + return is; + } + int frame_id; + int spatial_id; + int temporal_id; + // Base QP + int base_q; + size_t target_bandwidth; + size_t buffer_level; + // Loopfilter level + int filter_level_; + // Frame size for current frame, used for pose encode update + size_t bytes_used; +}; + +// This test runs the rate control interface and compare against ground truth +// generated by encoders. +// Settings for the encoder: +// For 1 layer: +// +// examples/vpx_temporal_svc_encoder gipsrec_motion1.1280_720.yuv out vp9 +// 1280 720 1 30 7 0 0 1 0 1000 +// +// For SVC (3 temporal layers, 3 spatial layers): +// +// examples/vp9_spatial_svc_encoder -f 10000 -w 1280 -h 720 -t 1/30 -sl 3 +// -k 10000 -bl 100,140,200,250,350,500,450,630,900 -b 1600 --rc-end-usage=1 +// --lag-in-frames=0 --passes=1 --speed=7 --threads=1 +// --temporal-layering-mode=3 -aq 1 -rcstat 1 +// gipsrec_motion1.1280_720.yuv -o out.webm +// +// - AQ_Mode 0 +// - Disable golden refresh +// - Bitrate x 2 at frame/superframe 200 +// - Bitrate / 4 at frame/superframe 400 +// +// The generated file includes: +// frame number, spatial layer ID, temporal layer ID, base QP, target +// bandwidth, buffer level, loopfilter level, encoded frame size +// TODO(jianj): Remove golden files, and run actual encoding in this test. +class RcInterfaceTest : public ::testing::Test { + public: + explicit RcInterfaceTest() {} + + virtual ~RcInterfaceTest() {} + + protected: + void RunOneLayer() { + SetConfigOneLayer(); + rc_api_->Create(rc_cfg_); + FrameInfo frame_info; + libvpx::VP9FrameParamsQpRTC frame_params; + frame_params.frame_type = KEY_FRAME; + frame_params.spatial_layer_id = 0; + frame_params.temporal_layer_id = 0; + std::ifstream one_layer_file; + one_layer_file.open(libvpx_test::GetDataPath() + + "/rc_interface_test_one_layer"); + ASSERT_EQ(one_layer_file.rdstate() & std::ifstream::failbit, 0); + for (size_t i = 0; i < kNumFrame; i++) { + one_layer_file >> frame_info; + if (frame_info.frame_id > 0) frame_params.frame_type = INTER_FRAME; + if (frame_info.frame_id == 200) { + rc_cfg_.target_bandwidth = rc_cfg_.target_bandwidth * 2; + rc_api_->UpdateRateControl(rc_cfg_); + } else if (frame_info.frame_id == 400) { + rc_cfg_.target_bandwidth = rc_cfg_.target_bandwidth / 4; + rc_api_->UpdateRateControl(rc_cfg_); + } + ASSERT_EQ(frame_info.spatial_id, 0); + ASSERT_EQ(frame_info.temporal_id, 0); + rc_api_->ComputeQP(frame_params); + ASSERT_EQ(rc_api_->GetQP(), frame_info.base_q); + ASSERT_EQ(rc_api_->GetLoopfilterLevel(), frame_info.filter_level_); + rc_api_->PostEncodeUpdate(frame_info.bytes_used); + } + } + + void RunSVC() { + SetConfigSVC(); + rc_api_->Create(rc_cfg_); + FrameInfo frame_info; + libvpx::VP9FrameParamsQpRTC frame_params; + frame_params.frame_type = KEY_FRAME; + std::ifstream svc_file; + svc_file.open(std::string(std::getenv("LIBVPX_TEST_DATA_PATH")) + + "/rc_interface_test_svc"); + ASSERT_EQ(svc_file.rdstate() & std::ifstream::failbit, 0); + for (size_t i = 0; i < kNumFrame * rc_cfg_.ss_number_layers; i++) { + svc_file >> frame_info; + if (frame_info.frame_id > 0) frame_params.frame_type = INTER_FRAME; + if (frame_info.frame_id == 200 * rc_cfg_.ss_number_layers) { + for (int layer = 0; + layer < rc_cfg_.ss_number_layers * rc_cfg_.ts_number_layers; + layer++) + rc_cfg_.layer_target_bitrate[layer] *= 2; + rc_cfg_.target_bandwidth *= 2; + rc_api_->UpdateRateControl(rc_cfg_); + } else if (frame_info.frame_id == 400 * rc_cfg_.ss_number_layers) { + for (int layer = 0; + layer < rc_cfg_.ss_number_layers * rc_cfg_.ts_number_layers; + layer++) + rc_cfg_.layer_target_bitrate[layer] /= 4; + rc_cfg_.target_bandwidth /= 4; + rc_api_->UpdateRateControl(rc_cfg_); + } + frame_params.spatial_layer_id = frame_info.spatial_id; + frame_params.temporal_layer_id = frame_info.temporal_id; + rc_api_->ComputeQP(frame_params); + ASSERT_EQ(rc_api_->GetQP(), frame_info.base_q); + ASSERT_EQ(rc_api_->GetLoopfilterLevel(), frame_info.filter_level_); + rc_api_->PostEncodeUpdate(frame_info.bytes_used); + } + } + + private: + void SetConfigOneLayer() { + rc_cfg_.width = 1280; + rc_cfg_.height = 720; + rc_cfg_.max_quantizer = 52; + rc_cfg_.min_quantizer = 2; + rc_cfg_.target_bandwidth = 1000; + rc_cfg_.buf_initial_sz = 600; + rc_cfg_.buf_optimal_sz = 600; + rc_cfg_.buf_sz = 1000; + rc_cfg_.undershoot_pct = 50; + rc_cfg_.overshoot_pct = 50; + rc_cfg_.max_intra_bitrate_pct = 1000; + rc_cfg_.framerate = 30.0; + rc_cfg_.ss_number_layers = 1; + rc_cfg_.ts_number_layers = 1; + rc_cfg_.scaling_factor_num[0] = 1; + rc_cfg_.scaling_factor_den[0] = 1; + rc_cfg_.layer_target_bitrate[0] = 1000; + rc_cfg_.max_quantizers[0] = 52; + rc_cfg_.min_quantizers[0] = 2; + } + + void SetConfigSVC() { + rc_cfg_.width = 1280; + rc_cfg_.height = 720; + rc_cfg_.max_quantizer = 56; + rc_cfg_.min_quantizer = 2; + rc_cfg_.target_bandwidth = 1600; + rc_cfg_.buf_initial_sz = 500; + rc_cfg_.buf_optimal_sz = 600; + rc_cfg_.buf_sz = 1000; + rc_cfg_.undershoot_pct = 50; + rc_cfg_.overshoot_pct = 50; + rc_cfg_.max_intra_bitrate_pct = 900; + rc_cfg_.framerate = 30.0; + rc_cfg_.ss_number_layers = 3; + rc_cfg_.ts_number_layers = 3; + + rc_cfg_.scaling_factor_num[0] = 1; + rc_cfg_.scaling_factor_den[0] = 4; + rc_cfg_.scaling_factor_num[1] = 2; + rc_cfg_.scaling_factor_den[1] = 4; + rc_cfg_.scaling_factor_num[2] = 4; + rc_cfg_.scaling_factor_den[2] = 4; + + rc_cfg_.ts_rate_decimator[0] = 4; + rc_cfg_.ts_rate_decimator[1] = 2; + rc_cfg_.ts_rate_decimator[2] = 1; + + rc_cfg_.layer_target_bitrate[0] = 100; + rc_cfg_.layer_target_bitrate[1] = 140; + rc_cfg_.layer_target_bitrate[2] = 200; + rc_cfg_.layer_target_bitrate[3] = 250; + rc_cfg_.layer_target_bitrate[4] = 350; + rc_cfg_.layer_target_bitrate[5] = 500; + rc_cfg_.layer_target_bitrate[6] = 450; + rc_cfg_.layer_target_bitrate[7] = 630; + rc_cfg_.layer_target_bitrate[8] = 900; + + for (int sl = 0; sl < rc_cfg_.ss_number_layers; ++sl) { + for (int tl = 0; tl < rc_cfg_.ts_number_layers; ++tl) { + const int i = sl * rc_cfg_.ts_number_layers + tl; + rc_cfg_.max_quantizers[i] = 56; + rc_cfg_.min_quantizers[i] = 2; + } + } + } + + std::unique_ptr rc_api_; + libvpx::VP9RateControlRtcConfig rc_cfg_; +}; + +TEST_F(RcInterfaceTest, OneLayer) { RunOneLayer(); } + +TEST_F(RcInterfaceTest, SVC) { RunSVC(); } +} // namespace + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/test/test.mk b/test/test.mk index b4a5ea0..e289a78 100644 --- a/test/test.mk +++ b/test/test.mk @@ -203,6 +203,8 @@ LIBVPX_TEST_SRCS-$(CONFIG_ENCODERS) += sum_squares_test.cc TEST_INTRA_PRED_SPEED_SRCS-yes := test_intra_pred_speed.cc TEST_INTRA_PRED_SPEED_SRCS-yes += ../md5_utils.h ../md5_utils.c +RC_INTERFACE_TEST_SRCS-$(CONFIG_VP9_ENCODER) := ratectrl_rtc_test.cc + endif # CONFIG_SHARED include $(SRC_PATH_BARE)/test/test-data.mk diff --git a/vp9/encoder/vp9_encoder.c b/vp9/encoder/vp9_encoder.c index b15d5f5..f90aee0 100644 --- a/vp9/encoder/vp9_encoder.c +++ b/vp9/encoder/vp9_encoder.c @@ -1523,8 +1523,29 @@ static void init_config(struct VP9_COMP *cpi, const VP9EncoderConfig *oxcf) { vp9_noise_estimate_init(&cpi->noise_estimate, cm->width, cm->height); } -static void set_rc_buffer_sizes(RATE_CONTROL *rc, - const VP9EncoderConfig *oxcf) { +void vp9_check_reset_rc_flag(VP9_COMP *cpi) { + RATE_CONTROL *rc = &cpi->rc; + + if (cpi->common.current_video_frame > + (unsigned int)cpi->svc.number_spatial_layers) { + if (cpi->use_svc) { + vp9_svc_check_reset_layer_rc_flag(cpi); + } else { + if (rc->avg_frame_bandwidth > (3 * rc->last_avg_frame_bandwidth >> 1) || + rc->avg_frame_bandwidth < (rc->last_avg_frame_bandwidth >> 1)) { + rc->rc_1_frame = 0; + rc->rc_2_frame = 0; + rc->bits_off_target = rc->optimal_buffer_level; + rc->buffer_level = rc->optimal_buffer_level; + } + } + } +} + +void vp9_set_rc_buffer_sizes(VP9_COMP *cpi) { + RATE_CONTROL *rc = &cpi->rc; + const VP9EncoderConfig *oxcf = &cpi->oxcf; + const int64_t bandwidth = oxcf->target_bandwidth; const int64_t starting = oxcf->starting_buffer_level_ms; const int64_t optimal = oxcf->optimal_buffer_level_ms; @@ -1535,6 +1556,11 @@ static void set_rc_buffer_sizes(RATE_CONTROL *rc, (optimal == 0) ? bandwidth / 8 : optimal * bandwidth / 1000; rc->maximum_buffer_size = (maximum == 0) ? bandwidth / 8 : maximum * bandwidth / 1000; + + // Under a configuration change, where maximum_buffer_size may change, + // keep buffer level clipped to the maximum allowed buffer size. + rc->bits_off_target = VPXMIN(rc->bits_off_target, rc->maximum_buffer_size); + rc->buffer_level = VPXMIN(rc->buffer_level, rc->maximum_buffer_size); } #if CONFIG_VP9_HIGHBITDEPTH @@ -1991,12 +2017,7 @@ void vp9_change_config(struct VP9_COMP *cpi, const VP9EncoderConfig *oxcf) { } cpi->encode_breakout = cpi->oxcf.encode_breakout; - set_rc_buffer_sizes(rc, &cpi->oxcf); - - // Under a configuration change, where maximum_buffer_size may change, - // keep buffer level clipped to the maximum allowed buffer size. - rc->bits_off_target = VPXMIN(rc->bits_off_target, rc->maximum_buffer_size); - rc->buffer_level = VPXMIN(rc->buffer_level, rc->maximum_buffer_size); + vp9_set_rc_buffer_sizes(cpi); // Set up frame rate and related parameters rate control values. vp9_new_framerate(cpi, cpi->framerate); @@ -2057,23 +2078,7 @@ void vp9_change_config(struct VP9_COMP *cpi, const VP9EncoderConfig *oxcf) { (int)cpi->oxcf.target_bandwidth); } - // Check for resetting the rc flags (rc_1_frame, rc_2_frame) if the - // configuration change has a large change in avg_frame_bandwidth. - // For SVC check for resetting based on spatial layer average bandwidth. - // Also reset buffer level to optimal level. - if (cm->current_video_frame > (unsigned int)cpi->svc.number_spatial_layers) { - if (cpi->use_svc) { - vp9_svc_check_reset_layer_rc_flag(cpi); - } else { - if (rc->avg_frame_bandwidth > (3 * rc->last_avg_frame_bandwidth >> 1) || - rc->avg_frame_bandwidth < (rc->last_avg_frame_bandwidth >> 1)) { - rc->rc_1_frame = 0; - rc->rc_2_frame = 0; - rc->bits_off_target = rc->optimal_buffer_level; - rc->buffer_level = rc->optimal_buffer_level; - } - } - } + vp9_check_reset_rc_flag(cpi); cpi->alt_ref_source = NULL; rc->is_src_frame_alt_ref = 0; diff --git a/vp9/encoder/vp9_encoder.h b/vp9/encoder/vp9_encoder.h index a237b74..1a25a49 100644 --- a/vp9/encoder/vp9_encoder.h +++ b/vp9/encoder/vp9_encoder.h @@ -1000,6 +1000,14 @@ int vp9_set_size_literal(VP9_COMP *cpi, unsigned int width, void vp9_set_svc(VP9_COMP *cpi, int use_svc); +// Check for resetting the rc flags (rc_1_frame, rc_2_frame) if the +// configuration change has a large change in avg_frame_bandwidth. +// For SVC check for resetting based on spatial layer average bandwidth. +// Also reset buffer level to optimal level. +void vp9_check_reset_rc_flag(VP9_COMP *cpi); + +void vp9_set_rc_buffer_sizes(VP9_COMP *cpi); + static INLINE int stack_pop(int *stack, int stack_size) { int idx; const int r = stack[0]; diff --git a/vp9/encoder/vp9_ratectrl.c b/vp9/encoder/vp9_ratectrl.c index ef64cc6..5dd745f 100644 --- a/vp9/encoder/vp9_ratectrl.c +++ b/vp9/encoder/vp9_ratectrl.c @@ -249,7 +249,7 @@ int vp9_rc_clamp_iframe_target_size(const VP9_COMP *const cpi, int target) { // way for CBR mode, for the buffering updates below. Look into removing one // of these (i.e., bits_off_target). // Update the buffer level before encoding with the per-frame-bandwidth, -static void update_buffer_level_preencode(VP9_COMP *cpi) { +void vp9_update_buffer_level_preencode(VP9_COMP *cpi) { RATE_CONTROL *const rc = &cpi->rc; rc->bits_off_target += rc->avg_frame_bandwidth; // Clip the buffer level to the maximum specified buffer size. @@ -2098,7 +2098,7 @@ void vp9_rc_get_one_pass_vbr_params(VP9_COMP *cpi) { vp9_cyclic_refresh_update_parameters(cpi); } -static int calc_pframe_target_size_one_pass_cbr(const VP9_COMP *cpi) { +int vp9_calc_pframe_target_size_one_pass_cbr(const VP9_COMP *cpi) { const VP9EncoderConfig *oxcf = &cpi->oxcf; const RATE_CONTROL *rc = &cpi->rc; const SVC *const svc = &cpi->svc; @@ -2147,7 +2147,7 @@ static int calc_pframe_target_size_one_pass_cbr(const VP9_COMP *cpi) { return VPXMAX(min_frame_target, target); } -static int calc_iframe_target_size_one_pass_cbr(const VP9_COMP *cpi) { +int vp9_calc_iframe_target_size_one_pass_cbr(const VP9_COMP *cpi) { const RATE_CONTROL *rc = &cpi->rc; const VP9EncoderConfig *oxcf = &cpi->oxcf; const SVC *const svc = &cpi->svc; @@ -2253,7 +2253,7 @@ void vp9_rc_get_svc_params(VP9_COMP *cpi) { cpi->ref_frame_flags &= (~VP9_LAST_FLAG & ~VP9_GOLD_FLAG & ~VP9_ALT_FLAG); // Assumption here is that LAST_FRAME is being updated for a keyframe. // Thus no change in update flags. - target = calc_iframe_target_size_one_pass_cbr(cpi); + target = vp9_calc_iframe_target_size_one_pass_cbr(cpi); } } else { cm->frame_type = INTER_FRAME; @@ -2266,7 +2266,7 @@ void vp9_rc_get_svc_params(VP9_COMP *cpi) { (svc->spatial_layer_id == 0 && cm->current_video_frame > 0) ? 0 : svc->layer_context[svc->temporal_layer_id].is_key_frame; - target = calc_pframe_target_size_one_pass_cbr(cpi); + target = vp9_calc_pframe_target_size_one_pass_cbr(cpi); } } @@ -2275,7 +2275,7 @@ void vp9_rc_get_svc_params(VP9_COMP *cpi) { svc->layer_context[layer].is_key_frame == 1) { cm->frame_type = KEY_FRAME; cpi->ref_frame_flags &= (~VP9_LAST_FLAG & ~VP9_GOLD_FLAG & ~VP9_ALT_FLAG); - target = calc_iframe_target_size_one_pass_cbr(cpi); + target = vp9_calc_iframe_target_size_one_pass_cbr(cpi); } // Set the buffer idx and refresh flags for key frames in simulcast mode. // Note the buffer slot for long-term reference is set below (line 2255), @@ -2360,7 +2360,7 @@ void vp9_rc_get_svc_params(VP9_COMP *cpi) { } if (svc->set_intra_only_frame) { set_intra_only_frame(cpi); - target = calc_iframe_target_size_one_pass_cbr(cpi); + target = vp9_calc_iframe_target_size_one_pass_cbr(cpi); } // Any update/change of global cyclic refresh parameters (amount/delta-qp) // should be done here, before the frame qp is selected. @@ -2433,13 +2433,13 @@ void vp9_rc_get_one_pass_cbr_params(VP9_COMP *cpi) { vp9_cyclic_refresh_update_parameters(cpi); if (frame_is_intra_only(cm)) - target = calc_iframe_target_size_one_pass_cbr(cpi); + target = vp9_calc_iframe_target_size_one_pass_cbr(cpi); else - target = calc_pframe_target_size_one_pass_cbr(cpi); + target = vp9_calc_pframe_target_size_one_pass_cbr(cpi); vp9_rc_set_frame_target(cpi, target); - if (cm->show_frame) update_buffer_level_preencode(cpi); + if (cm->show_frame) vp9_update_buffer_level_preencode(cpi); if (cpi->oxcf.resize_mode == RESIZE_DYNAMIC) cpi->resize_pending = vp9_resize_one_pass_cbr(cpi); @@ -2742,7 +2742,7 @@ int vp9_resize_one_pass_cbr(VP9_COMP *cpi) { // Reset buffer level to optimal, update target size. rc->buffer_level = rc->optimal_buffer_level; rc->bits_off_target = rc->optimal_buffer_level; - rc->this_frame_target = calc_pframe_target_size_one_pass_cbr(cpi); + rc->this_frame_target = vp9_calc_pframe_target_size_one_pass_cbr(cpi); // Get the projected qindex, based on the scaled target frame size (scaled // so target_bits_per_mb in vp9_rc_regulate_q will be correct target). target_bits_per_frame = (resize_action >= 0) diff --git a/vp9/encoder/vp9_ratectrl.h b/vp9/encoder/vp9_ratectrl.h index fa070f9..c5ffb15 100644 --- a/vp9/encoder/vp9_ratectrl.h +++ b/vp9/encoder/vp9_ratectrl.h @@ -252,6 +252,9 @@ int vp9_rc_get_default_max_gf_interval(double framerate, int min_gf_interval); // encode_frame_to_data_rate() function. void vp9_rc_get_one_pass_vbr_params(struct VP9_COMP *cpi); void vp9_rc_get_one_pass_cbr_params(struct VP9_COMP *cpi); +int vp9_calc_pframe_target_size_one_pass_cbr(const struct VP9_COMP *cpi); +int vp9_calc_iframe_target_size_one_pass_cbr(const struct VP9_COMP *cpi); +void vp9_update_buffer_level_preencode(struct VP9_COMP *cpi); void vp9_rc_get_svc_params(struct VP9_COMP *cpi); // Post encode update of the rate control parameters based diff --git a/vp9/ratectrl_rtc.cc b/vp9/ratectrl_rtc.cc new file mode 100644 index 0000000..3d6afc5 --- /dev/null +++ b/vp9/ratectrl_rtc.cc @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2020 The WebM project authors. 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 "vp9/ratectrl_rtc.h" + +#include + +#include "vp9/encoder/vp9_encoder.h" +#include "vp9/encoder/vp9_picklpf.h" +#include "vpx/vp8cx.h" +#include "vpx/vpx_codec.h" + +namespace libvpx { + +std::unique_ptr VP9RateControlRTC::Create( + const VP9RateControlRtcConfig &cfg) { + std::unique_ptr rc_api(new (std::nothrow) + VP9RateControlRTC()); + if (!rc_api) return nullptr; + rc_api->cpi_ = static_cast(vpx_memalign(32, sizeof(*cpi_))); + if (rc_api->cpi_ == nullptr) { + return nullptr; + } + rc_api->InitRateControl(cfg); + return rc_api; +} + +void VP9RateControlRTC::InitRateControl(const VP9RateControlRtcConfig &rc_cfg) { + VP9_COMMON *cm = &cpi_->common; + VP9EncoderConfig *oxcf = &cpi_->oxcf; + RATE_CONTROL *const rc = &cpi_->rc; + cm->profile = PROFILE_0; + cm->bit_depth = VPX_BITS_8; + cm->show_frame = 1; + oxcf->rc_mode = VPX_CBR; + oxcf->pass = 0; + oxcf->aq_mode = NO_AQ; + oxcf->content = VP9E_CONTENT_DEFAULT; + oxcf->drop_frames_water_mark = 0; + + UpdateRateControl(rc_cfg); + + cpi_->use_svc = (cpi_->svc.number_spatial_layers > 1 || + cpi_->svc.number_temporal_layers > 1) + ? 1 + : 0; + + rc->rc_1_frame = 0; + rc->rc_2_frame = 0; + vp9_rc_init_minq_luts(); + vp9_rc_init(oxcf, 0, rc); + cpi_->sf.use_nonrd_pick_mode = 1; + cm->current_video_frame = 0; +} + +void VP9RateControlRTC::UpdateRateControl( + const VP9RateControlRtcConfig &rc_cfg) { + VP9_COMMON *cm = &cpi_->common; + VP9EncoderConfig *oxcf = &cpi_->oxcf; + RATE_CONTROL *const rc = &cpi_->rc; + + cm->width = rc_cfg.width; + cm->height = rc_cfg.height; + oxcf->width = rc_cfg.width; + oxcf->height = rc_cfg.height; + oxcf->worst_allowed_q = vp9_quantizer_to_qindex(rc_cfg.max_quantizer); + oxcf->best_allowed_q = vp9_quantizer_to_qindex(rc_cfg.min_quantizer); + rc->worst_quality = oxcf->worst_allowed_q; + rc->best_quality = oxcf->best_allowed_q; + oxcf->target_bandwidth = 1000 * rc_cfg.target_bandwidth; + oxcf->starting_buffer_level_ms = rc_cfg.buf_initial_sz; + oxcf->optimal_buffer_level_ms = rc_cfg.buf_optimal_sz; + oxcf->maximum_buffer_size_ms = rc_cfg.buf_sz; + oxcf->under_shoot_pct = rc_cfg.undershoot_pct; + oxcf->over_shoot_pct = rc_cfg.overshoot_pct; + oxcf->ss_number_layers = rc_cfg.ss_number_layers; + oxcf->ts_number_layers = rc_cfg.ts_number_layers; + oxcf->temporal_layering_mode = (VP9E_TEMPORAL_LAYERING_MODE)( + (rc_cfg.ts_number_layers > 1) ? rc_cfg.ts_number_layers : 0); + + cpi_->oxcf.rc_max_intra_bitrate_pct = rc_cfg.max_intra_bitrate_pct; + cpi_->framerate = rc_cfg.framerate; + cpi_->svc.number_spatial_layers = rc_cfg.ss_number_layers; + cpi_->svc.number_temporal_layers = rc_cfg.ts_number_layers; + + for (int sl = 0; sl < cpi_->svc.number_spatial_layers; ++sl) { + for (int tl = 0; tl < cpi_->svc.number_temporal_layers; ++tl) { + const int layer = + LAYER_IDS_TO_IDX(sl, tl, cpi_->svc.number_temporal_layers); + LAYER_CONTEXT *lc = &cpi_->svc.layer_context[layer]; + RATE_CONTROL *const lrc = &lc->rc; + oxcf->layer_target_bitrate[layer] = + 1000 * rc_cfg.layer_target_bitrate[layer]; + lrc->worst_quality = + vp9_quantizer_to_qindex(rc_cfg.max_quantizers[layer]); + lrc->best_quality = vp9_quantizer_to_qindex(rc_cfg.min_quantizers[layer]); + lc->scaling_factor_num = rc_cfg.scaling_factor_num[sl]; + lc->scaling_factor_den = rc_cfg.scaling_factor_den[sl]; + oxcf->ts_rate_decimator[tl] = rc_cfg.ts_rate_decimator[tl]; + } + } + vp9_set_rc_buffer_sizes(cpi_); + vp9_new_framerate(cpi_, cpi_->framerate); + if (cpi_->svc.number_temporal_layers > 1) { + if (cm->current_video_frame == 0) vp9_init_layer_context(cpi_); + vp9_update_layer_context_change_config(cpi_, + (int)cpi_->oxcf.target_bandwidth); + } + vp9_check_reset_rc_flag(cpi_); +} + +void VP9RateControlRTC::ComputeQP(const VP9FrameParamsQpRTC &frame_params) { + VP9_COMMON *const cm = &cpi_->common; + int width, height; + cpi_->svc.spatial_layer_id = frame_params.spatial_layer_id; + cpi_->svc.temporal_layer_id = frame_params.temporal_layer_id; + if (cpi_->svc.number_spatial_layers > 1) { + const int layer = LAYER_IDS_TO_IDX(cpi_->svc.spatial_layer_id, + cpi_->svc.temporal_layer_id, + cpi_->svc.number_temporal_layers); + LAYER_CONTEXT *lc = &cpi_->svc.layer_context[layer]; + get_layer_resolution(cpi_->oxcf.width, cpi_->oxcf.height, + lc->scaling_factor_num, lc->scaling_factor_den, &width, + &height); + cm->width = width; + cm->height = height; + } + vp9_set_mb_mi(cm, cm->width, cm->height); + cm->frame_type = frame_params.frame_type; + cpi_->refresh_golden_frame = (cm->frame_type == KEY_FRAME) ? 1 : 0; + cpi_->sf.use_nonrd_pick_mode = 1; + if (cpi_->svc.number_spatial_layers == 1 && + cpi_->svc.number_temporal_layers == 1) { + int target; + if (frame_is_intra_only(cm)) + target = vp9_calc_iframe_target_size_one_pass_cbr(cpi_); + else + target = vp9_calc_pframe_target_size_one_pass_cbr(cpi_); + vp9_rc_set_frame_target(cpi_, target); + vp9_update_buffer_level_preencode(cpi_); + } else { + vp9_update_temporal_layer_framerate(cpi_); + vp9_restore_layer_context(cpi_); + vp9_rc_get_svc_params(cpi_); + } + int bottom_index, top_index; + cpi_->common.base_qindex = + vp9_rc_pick_q_and_bounds(cpi_, &bottom_index, &top_index); +} + +int VP9RateControlRTC::GetQP() const { return cpi_->common.base_qindex; } + +int VP9RateControlRTC::GetLoopfilterLevel() const { + struct loopfilter *const lf = &cpi_->common.lf; + vp9_pick_filter_level(NULL, cpi_, LPF_PICK_FROM_Q); + return lf->filter_level; +} + +void VP9RateControlRTC::PostEncodeUpdate(uint64_t encoded_frame_size) { + vp9_rc_postencode_update(cpi_, encoded_frame_size); + if (cpi_->svc.number_spatial_layers > 1 || + cpi_->svc.number_temporal_layers > 1) + vp9_save_layer_context(cpi_); + cpi_->common.current_video_frame++; +} + +} // namespace libvpx diff --git a/vp9/ratectrl_rtc.h b/vp9/ratectrl_rtc.h new file mode 100644 index 0000000..72ea40f --- /dev/null +++ b/vp9/ratectrl_rtc.h @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2020 The WebM project authors. 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 VPX_VP9_RATECTRL_RTC_H_ +#define VPX_VP9_RATECTRL_RTC_H_ + +#include +#include + +#include "vp9/common/vp9_entropymode.h" +#include "vp9/common/vp9_enums.h" +#include "vp9/common/vp9_onyxc_int.h" +#include "vp9/vp9_iface_common.h" +#include "vp9/encoder/vp9_encoder.h" +#include "vp9/encoder/vp9_firstpass.h" +#include "vp9/vp9_cx_iface.h" +#include "vpx_mem/vpx_mem.h" + +namespace libvpx { + +struct VP9RateControlRtcConfig { + int width; + int height; + // 0-63 + int max_quantizer; + int min_quantizer; + int64_t target_bandwidth; + int64_t buf_initial_sz; + int64_t buf_optimal_sz; + int64_t buf_sz; + int undershoot_pct; + int overshoot_pct; + int max_intra_bitrate_pct; + double framerate; + // Number of spatial layers + int ss_number_layers; + // Number of temporal layers + int ts_number_layers; + int max_quantizers[VPX_MAX_LAYERS]; + int min_quantizers[VPX_MAX_LAYERS]; + int scaling_factor_num[VPX_SS_MAX_LAYERS]; + int scaling_factor_den[VPX_SS_MAX_LAYERS]; + int layer_target_bitrate[VPX_MAX_LAYERS]; + int ts_rate_decimator[VPX_TS_MAX_LAYERS]; +}; + +struct VP9FrameParamsQpRTC { + FRAME_TYPE frame_type; + int spatial_layer_id; + int temporal_layer_id; +}; + +// This interface allows using VP9 real-time rate control without initializing +// the encoder. To use this interface, you need to link with libvp9rc.a. +// +// #include "vp9/ratectrl_rtc.h" +// VP9RateControlRTC rc_api; +// VP9RateControlRtcConfig cfg; +// VP9FrameParamsQpRTC frame_params; +// +// YourFunctionToInitializeConfig(cfg); +// rc_api.InitRateControl(cfg); +// // start encoding +// while (frame_to_encode) { +// if (config_changed) +// rc_api.UpdateRateControl(cfg); +// YourFunctionToFillFrameParams(frame_params); +// rc_api.ComputeQP(frame_params); +// YourFunctionToUseQP(rc_api.GetQP()); +// YourFunctionToUseLoopfilter(rc_api.GetLoopfilterLevel()); +// // After encoding +// rc_api.PostEncode(encoded_frame_size); +// } +class VP9RateControlRTC { + public: + static std::unique_ptr Create( + const VP9RateControlRtcConfig &cfg); + ~VP9RateControlRTC() { + if (cpi_) { + for (int sl = 0; sl < cpi_->svc.number_spatial_layers; sl++) { + for (int tl = 0; tl < cpi_->svc.number_temporal_layers; tl++) { + int layer = LAYER_IDS_TO_IDX(sl, tl, cpi_->oxcf.ts_number_layers); + LAYER_CONTEXT *const lc = &cpi_->svc.layer_context[layer]; + vpx_free(lc->map); + vpx_free(lc->last_coded_q_map); + vpx_free(lc->consec_zero_mv); + } + } + vpx_free(cpi_); + } + } + + void UpdateRateControl(const VP9RateControlRtcConfig &rc_cfg); + // GetQP() needs to be called after ComputeQP() to get the latest QP + int GetQP() const; + int GetLoopfilterLevel() const; + void ComputeQP(const VP9FrameParamsQpRTC &frame_params); + // Feedback to rate control with the size of current encoded frame + void PostEncodeUpdate(uint64_t encoded_frame_size); + + private: + VP9RateControlRTC() {} + void InitRateControl(const VP9RateControlRtcConfig &cfg); + VP9_COMP *cpi_; +}; + +} // namespace libvpx + +#endif // VPX_VP9_RATECTRL_RTC_H_ -- 2.7.4