shithub: libvpx

Download patch

ref: 745979bc2913b4f26847084699d9fddd68b60410
parent: aae27c8f1e4ad38eff29ba9d35a772343c0ce5bf
author: Jerome Jiang <jianj@google.com>
date: Tue Mar 24 11:13:59 EDT 2020

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

--- a/libs.mk
+++ b/libs.mk
@@ -397,6 +397,10 @@
 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 @@
             -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 @@
 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 @@
               -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 @@
     $(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 @@
 
 ## Update the global src list
 SRCS += $(CODEC_SRCS) $(LIBVPX_TEST_SRCS) $(GTEST_SRCS)
+SRCS += $(RC_INTERFACE_TEST_SRCS)
 
 ##
 ## vpxdec/vpxenc tests.
--- /dev/null
+++ b/test/ratectrl_rtc_test.cc
@@ -1,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 <fstream>  // NOLINT
+#include <string>
+
+#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<libvpx::VP9RateControlRTC> 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();
+}
--- a/test/test.mk
+++ b/test/test.mk
@@ -203,6 +203,8 @@
 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
--- a/vp9/encoder/vp9_encoder.c
+++ b/vp9/encoder/vp9_encoder.c
@@ -1523,8 +1523,29 @@
   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 @@
       (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,13 +2017,8 @@
   }
   cpi->encode_breakout = cpi->oxcf.encode_breakout;
 
-  set_rc_buffer_sizes(rc, &cpi->oxcf);
+  vp9_set_rc_buffer_sizes(cpi);
 
-  // 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);
-
   // Set up frame rate and related parameters rate control values.
   vp9_new_framerate(cpi, cpi->framerate);
 
@@ -2057,23 +2078,7 @@
                                            (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;
--- a/vp9/encoder/vp9_encoder.h
+++ b/vp9/encoder/vp9_encoder.h
@@ -1000,6 +1000,14 @@
 
 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];
--- a/vp9/encoder/vp9_ratectrl.c
+++ b/vp9/encoder/vp9_ratectrl.c
@@ -249,7 +249,7 @@
 // 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 @@
     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 @@
   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 @@
       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 @@
           (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 @@
         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 @@
   }
   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 @@
     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 @@
     // 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)
--- a/vp9/encoder/vp9_ratectrl.h
+++ b/vp9/encoder/vp9_ratectrl.h
@@ -252,6 +252,9 @@
 // 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
--- /dev/null
+++ b/vp9/ratectrl_rtc.cc
@@ -1,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 <new>
+
+#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> VP9RateControlRTC::Create(
+    const VP9RateControlRtcConfig &cfg) {
+  std::unique_ptr<VP9RateControlRTC> rc_api(new (std::nothrow)
+                                                VP9RateControlRTC());
+  if (!rc_api) return nullptr;
+  rc_api->cpi_ = static_cast<VP9_COMP *>(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
--- /dev/null
+++ b/vp9/ratectrl_rtc.h
@@ -1,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 <cstdint>
+#include <memory>
+
+#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<VP9RateControlRTC> 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_