History

Add vp8 support to rc lib

For 1 layer CBR only. Support for temporal layers comes later. Rename the library to libvpxrc Bug: b/188853141

Jerome Jiang committed 5 years ago

Commit 65a17511 parent ca40ca913 files · +646 −72Browse files

Unified · Split

13 files
  1. libs.mk +33 −21
  2. test/test.mk +5 −3
  3. test/test_rc_interface.cc +6 −0
  4. test/vp8_ratectrl_rtc_test.cc +180 −0
  5. test/vp9_ratectrl_rtc_test.cc +0 −5
  6. vp8/encoder/onyx_if.c +3 −3
  7. vp8/encoder/ratectrl.c +2 −1
  8. vp8/vp8_ratectrl_rtc.cc +288 −0
  9. vp8/vp8_ratectrl_rtc.h +62 −0
  10. vp9/ratectrl_rtc.h +3 −38
  11. vpx/internal/vpx_ratectrl_rtc.h +62 −0
  12. vpx/vp8cx.h +1 −1
  13. vpx/vpx_codec.mk +1 −0
libs.mk+33−21
@@ -94,15 +94,28 @@ ifeq ($(CONFIG_VP9_ENCODER),yes) · 93 unmodified lines
9494 INSTALL_MAPS += include/vpx/% $(SRC_PATH_BARE)/$(VP9_PREFIX)/%
9595 CODEC_DOC_SRCS += vpx/vp8.h vpx/vp8cx.h vpx/vpx_ext_ratectrl.h
9696 CODEC_DOC_SECTIONS += vp9 vp9_encoder
97+endif
9798
98− RC_RTC_SRCS := $(addprefix $(VP9_PREFIX),$(call enabled,VP9_CX_SRCS))
99− RC_RTC_SRCS += $(VP9_PREFIX)vp9cx.mk vpx/vp8.h vpx/vp8cx.h
100− RC_RTC_SRCS += vpx/vpx_ext_ratectrl.h
99+RC_RTC_SRCS := vpx/vp8.h vpx/vp8cx.h
100+RC_RTC_SRCS += vpx/vpx_ext_ratectrl.h
101+RC_RTC_SRCS += vpx/internal/vpx_ratectrl_rtc.h
102+ifeq ($(CONFIG_VP9_ENCODER),yes)
103+ VP9_PREFIX=vp9/
104+ RC_RTC_SRCS += $(addprefix $(VP9_PREFIX),$(call enabled,VP9_CX_SRCS))
105+ RC_RTC_SRCS += $(VP9_PREFIX)vp9cx.mk
101106 RC_RTC_SRCS += $(VP9_PREFIX)ratectrl_rtc.cc
102107 RC_RTC_SRCS += $(VP9_PREFIX)ratectrl_rtc.h
103108 INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(VP9_PREFIX)ratectrl_rtc.cc
104109 INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(VP9_PREFIX)ratectrl_rtc.h
105110 endif
111+ifeq ($(CONFIG_VP8_ENCODER),yes)
112+ VP8_PREFIX=vp8/
113+ RC_RTC_SRCS += $(addprefix $(VP8_PREFIX),$(call enabled,VP8_CX_SRCS))
114+ RC_RTC_SRCS += $(VP8_PREFIX)vp8_ratectrl_rtc.cc
115+ RC_RTC_SRCS += $(VP8_PREFIX)vp8_ratectrl_rtc.h
116+ INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(VP8_PREFIX)vp8_ratectrl_rtc.cc
117+ INSTALL-SRCS-$(CONFIG_CODEC_SRCS) += $(VP8_PREFIX)vp8_ratectrl_rtc.h
118+endif
106119
107120 ifeq ($(CONFIG_VP9_DECODER),yes)
108121 VP9_PREFIX=vp9/
@@ -126,7 +139,7 @@ endif · 17 unmodified lines
126139 ifeq ($(CONFIG_MSVS),yes)
127140 CODEC_LIB=$(if $(CONFIG_STATIC_MSVCRT),vpxmt,vpxmd)
128141 GTEST_LIB=$(if $(CONFIG_STATIC_MSVCRT),gtestmt,gtestmd)
129−RC_RTC_LIB=$(if $(CONFIG_STATIC_MSVCRT),vp9rcmt,vp9rcmd)
142+RC_RTC_LIB=$(if $(CONFIG_STATIC_MSVCRT),vpxrcmt,vpxrcmd)
130143 # This variable uses deferred expansion intentionally, since the results of
131144 # $(wildcard) may change during the course of the Make.
132145 VS_PLATFORMS = $(foreach d,$(wildcard */Release/$(CODEC_LIB).lib),$(word 1,$(subst /, ,$(d))))
@@ -249,16 +262,16 @@ PROJECTS-yes += vpx.$(VCPROJ_SFX) · 116 unmodified lines
249262 vpx.$(VCPROJ_SFX): vpx_config.asm
250263 vpx.$(VCPROJ_SFX): $(RTCD)
251264
252−vp9rc.$(VCPROJ_SFX): \
265+vpxrc.$(VCPROJ_SFX): \
253266 VCPROJ_SRCS=$(filter-out $(addprefix %, $(ASM_INCLUDES)), $^)
254267
255−vp9rc.$(VCPROJ_SFX): $(RC_RTC_SRCS)
268+vpxrc.$(VCPROJ_SFX): $(RC_RTC_SRCS)
256269 @echo " [CREATE] $@"
257270 $(qexec)$(GEN_VCPROJ) \
258271 $(if $(CONFIG_SHARED),--dll,--lib) \
259272 --target=$(TOOLCHAIN) \
260273 $(if $(CONFIG_STATIC_MSVCRT),--static-crt) \
261− --name=vp9rc \
274+ --name=vpxrc \
262275 --proj-guid=C26FF952-9494-4838-9A3F-7F3D4F613385 \
263276 --ver=$(CONFIG_VS_VERSION) \
264277 --src-path-bare="$(SRC_PATH_BARE)" \
@@ -275,10 +288,10 @@ vp9rc.$(VCPROJ_SFX): $(RC_RTC_SRCS) · 10 unmodified lines
275288 $(VCPROJ_SRCS)) \
276289 --src-path-bare="$(SRC_PATH_BARE)" \
277290
278−PROJECTS-yes += vp9rc.$(VCPROJ_SFX)
291+PROJECTS-yes += vpxrc.$(VCPROJ_SFX)
279292
280−vp9rc.$(VCPROJ_SFX): vpx_config.asm
281−vp9rc.$(VCPROJ_SFX): $(RTCD)
293+vpxrc.$(VCPROJ_SFX): vpx_config.asm
294+vpxrc.$(VCPROJ_SFX): $(RTCD)
282295
283296 endif # ifeq ($(CONFIG_MSVS),yes)
284297 else # ifeq ($(CONFIG_EXTERNAL_BUILD),yes)
@@ -398,12 +411,11 @@ INSTALL-LIBS-yes += $(LIBSUBDIR)/pkgconfig/vpx.pc · 113 unmodified lines
398411 INSTALL_MAPS += $(LIBSUBDIR)/pkgconfig/%.pc %.pc
399412 CLEAN-OBJS += vpx.pc
400413
401−ifeq ($(CONFIG_VP9_ENCODER),yes)
402− RC_RTC_OBJS=$(call objs,$(RC_RTC_SRCS))
414+ifeq ($(CONFIG_ENCODERS),yes)
403415 RC_RTC_OBJS=$(call objs,$(RC_RTC_SRCS))
404416 OBJS-yes += $(RC_RTC_OBJS)
405− LIBS-yes += $(BUILD_PFX)libvp9rc.a $(BUILD_PFX)libvp9rc_g.a
406− $(BUILD_PFX)libvp9rc_g.a: $(RC_RTC_OBJS)
417+ LIBS-yes += $(BUILD_PFX)libvpxrc.a $(BUILD_PFX)libvpxrc_g.a
418+ $(BUILD_PFX)libvpxrc_g.a: $(RC_RTC_OBJS)
407419 endif
408420
409421 ifeq ($(CONFIG_VP9_ENCODER)$(CONFIG_RATE_CTRL),yesyes)
@@ -493,7 +505,7 @@ TEST_INTRA_PRED_SPEED_SRCS=$(call addprefix_clean,test/,\ · 83 unmodified lines
493505 $(call enabled,TEST_INTRA_PRED_SPEED_SRCS))
494506 TEST_INTRA_PRED_SPEED_OBJS := $(sort $(call objs,$(TEST_INTRA_PRED_SPEED_SRCS)))
495507
496−ifeq ($(CONFIG_VP9_ENCODER),yes)
508+ifeq ($(CONFIG_ENCODERS),yes)
497509 RC_INTERFACE_TEST_BIN=./test_rc_interface$(EXE_SFX)
498510 RC_INTERFACE_TEST_SRCS=$(call addprefix_clean,test/,\
499511 $(call enabled,RC_INTERFACE_TEST_SRCS))
@@ -599,11 +611,11 @@ test_intra_pred_speed.$(VCPROJ_SFX): $(TEST_INTRA_PRED_SPEED_SRCS) vpx.$(VCPROJ_ · 99 unmodified lines
599611 -L. -l$(CODEC_LIB) -l$(GTEST_LIB) $^
600612 endif # TEST_INTRA_PRED_SPEED
601613
602−ifeq ($(CONFIG_VP9_ENCODER),yes)
614+ifeq ($(CONFIG_ENCODERS),yes)
603615 ifneq ($(strip $(RC_INTERFACE_TEST_OBJS)),)
604616 PROJECTS-$(CONFIG_MSVS) += test_rc_interface.$(VCPROJ_SFX)
605617 test_rc_interface.$(VCPROJ_SFX): $(RC_INTERFACE_TEST_SRCS) vpx.$(VCPROJ_SFX) \
606− vp9rc.$(VCPROJ_SFX) gtest.$(VCPROJ_SFX)
618+ vpxrc.$(VCPROJ_SFX) gtest.$(VCPROJ_SFX)
607619 @echo " [CREATE] $@"
608620 $(qexec)$(GEN_VCPROJ) \
609621 --exe \
@@ -661,19 +673,19 @@ $(eval $(call linkerxx_template,$(TEST_INTRA_PRED_SPEED_BIN), \ · 51 unmodified lines
661673 -L. -lvpx -lgtest $(extralibs) -lm))
662674 endif # TEST_INTRA_PRED_SPEED
663675
664−ifeq ($(CONFIG_VP9_ENCODER),yes)
676+ifeq ($(CONFIG_ENCODERS),yes)
665677 ifneq ($(strip $(RC_INTERFACE_TEST_OBJS)),)
666678 $(RC_INTERFACE_TEST_OBJS) $(RC_INTERFACE_TEST_OBJS:.o=.d): \
667679 CXXFLAGS += $(GTEST_INCLUDES)
668680 OBJS-yes += $(RC_INTERFACE_TEST_OBJS)
669681 BINS-yes += $(RC_INTERFACE_TEST_BIN)
670682
671−$(RC_INTERFACE_TEST_BIN): $(TEST_LIBS) libvp9rc.a
683+$(RC_INTERFACE_TEST_BIN): $(TEST_LIBS) libvpxrc.a
672684 $(eval $(call linkerxx_template,$(RC_INTERFACE_TEST_BIN), \
673685 $(RC_INTERFACE_TEST_OBJS) \
674− -L. -lvpx -lgtest -lvp9rc $(extralibs) -lm))
686+ -L. -lvpx -lgtest -lvpxrc $(extralibs) -lm))
675687 endif # RC_INTERFACE_TEST
676−endif # CONFIG_VP9_ENCODER
688+endif # CONFIG_ENCODERS
677689
678690 ifneq ($(strip $(SIMPLE_ENCODE_TEST_OBJS)),)
679691 $(SIMPLE_ENCODE_TEST_OBJS) $(SIMPLE_ENCODE_TEST_OBJS:.o=.d): \
test/test.mk+5−3
@@ -213,9 +213,11 @@ endif · 212 unmodified lines
213213 TEST_INTRA_PRED_SPEED_SRCS-yes := test_intra_pred_speed.cc
214214 TEST_INTRA_PRED_SPEED_SRCS-yes += ../md5_utils.h ../md5_utils.c
215215
216−RC_INTERFACE_TEST_SRCS-$(CONFIG_VP9_ENCODER) := ratectrl_rtc_test.cc
217−RC_INTERFACE_TEST_SRCS-$(CONFIG_VP9_ENCODER) += encode_test_driver.cc
218−RC_INTERFACE_TEST_SRCS-$(CONFIG_VP9_ENCODER) += encode_test_driver.h
216+RC_INTERFACE_TEST_SRCS-yes := test_rc_interface.cc
217+RC_INTERFACE_TEST_SRCS-$(CONFIG_VP9_ENCODER) += vp9_ratectrl_rtc_test.cc
218+RC_INTERFACE_TEST_SRCS-$(CONFIG_VP8_ENCODER) += vp8_ratectrl_rtc_test.cc
219+RC_INTERFACE_TEST_SRCS-$(CONFIG_ENCODERS) += encode_test_driver.cc
220+RC_INTERFACE_TEST_SRCS-$(CONFIG_ENCODERS) += encode_test_driver.h
219221 RC_INTERFACE_TEST_SRCS-yes += decode_test_driver.cc
220222 RC_INTERFACE_TEST_SRCS-yes += decode_test_driver.h
221223 RC_INTERFACE_TEST_SRCS-yes += codec_factory.h
test/test_rc_interface.cc+6−0
@@ -0,0 +1,6 @@
1+#include "third_party/googletest/src/include/gtest/gtest.h"
2+
3+int main(int argc, char **argv) {
4+ ::testing::InitGoogleTest(&argc, argv);
5+ return RUN_ALL_TESTS();
6+}
test/vp8_ratectrl_rtc_test.cc+180−0
@@ -0,0 +1,180 @@
1+/*
2+ * Copyright (c) 2021 The WebM project authors. All Rights Reserved.
3+ *
4+ * Use of this source code is governed by a BSD-style license
5+ * that can be found in the LICENSE file in the root of the source
6+ * tree. An additional intellectual property rights grant can be found
7+ * in the file PATENTS. All contributing project authors may
8+ * be found in the AUTHORS file in the root of the source tree.
9+ */
10+
11+#include <fstream> // NOLINT
12+#include <string>
13+
14+#include "./vpx_config.h"
15+#include "third_party/googletest/src/include/gtest/gtest.h"
16+#include "test/codec_factory.h"
17+#include "test/encode_test_driver.h"
18+#include "test/i420_video_source.h"
19+#include "test/util.h"
20+#include "test/video_source.h"
21+#include "vp8/vp8_ratectrl_rtc.h"
22+#include "vpx/vpx_codec.h"
23+#include "vpx_ports/bitops.h"
24+
25+namespace {
26+
27+struct Vp8RCTestVideo {
28+ Vp8RCTestVideo() {}
29+ Vp8RCTestVideo(const char *name_, int width_, int height_,
30+ unsigned int frames_)
31+ : name(name_), width(width_), height(height_), frames(frames_) {}
32+
33+ friend std::ostream &operator<<(std::ostream &os,
34+ const Vp8RCTestVideo &video) {
35+ os << video.name << " " << video.width << " " << video.height << " "
36+ << video.frames;
37+ return os;
38+ }
39+ const char *name;
40+ int width;
41+ int height;
42+ unsigned int frames;
43+};
44+
45+const Vp8RCTestVideo kVp8RCTestVectors[] = {
46+ Vp8RCTestVideo("niklas_640_480_30.yuv", 640, 480, 470),
47+ Vp8RCTestVideo("desktop_office1.1280_720-020.yuv", 1280, 720, 300),
48+};
49+
50+class Vp8RcInterfaceTest
51+ : public ::libvpx_test::EncoderTest,
52+ public ::libvpx_test::CodecTestWith2Params<int, Vp8RCTestVideo> {
53+ public:
54+ Vp8RcInterfaceTest()
55+ : EncoderTest(GET_PARAM(0)), key_interval_(3000), encoder_exit_(false) {}
56+ virtual ~Vp8RcInterfaceTest() {}
57+
58+ protected:
59+ virtual void SetUp() {
60+ InitializeConfig();
61+ SetMode(::libvpx_test::kRealTime);
62+ }
63+
64+ virtual void PreEncodeFrameHook(::libvpx_test::VideoSource *video,
65+ ::libvpx_test::Encoder *encoder) {
66+ if (video->frame() == 0) {
67+ encoder->Control(VP8E_SET_CPUUSED, -6);
68+ encoder->Control(VP8E_SET_RTC_EXTERNAL_RATECTRL, 1);
69+ encoder->Control(VP8E_SET_MAX_INTRA_BITRATE_PCT, 1000);
70+ }
71+ frame_params_.frame_type =
72+ video->frame() % key_interval_ == 0 ? KEY_FRAME : INTER_FRAME;
73+ if (frame_params_.frame_type == INTER_FRAME) {
74+ // Disable golden frame update.
75+ frame_flags_ |= VP8_EFLAG_NO_UPD_GF;
76+ frame_flags_ |= VP8_EFLAG_NO_UPD_ARF;
77+ }
78+ encoder_exit_ = video->frame() == test_video_.frames;
79+ }
80+
81+ virtual void PostEncodeFrameHook(::libvpx_test::Encoder *encoder) {
82+ if (encoder_exit_) {
83+ return;
84+ }
85+ int qp;
86+ encoder->Control(VP8E_GET_LAST_QUANTIZER, &qp);
87+ rc_api_->ComputeQP(frame_params_);
88+ ASSERT_EQ(rc_api_->GetQP(), qp);
89+ }
90+
91+ virtual void FramePktHook(const vpx_codec_cx_pkt_t *pkt) {
92+ rc_api_->PostEncodeUpdate(pkt->data.frame.sz);
93+ }
94+
95+ void RunOneLayer() {
96+ test_video_ = GET_PARAM(2);
97+ target_bitrate_ = GET_PARAM(1);
98+ if (test_video_.width == 1280 && target_bitrate_ == 200) return;
99+ if (test_video_.width == 640 && target_bitrate_ == 1000) return;
100+ SetConfig();
101+ rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
102+ rc_api_->UpdateRateControl(rc_cfg_);
103+
104+ ::libvpx_test::I420VideoSource video(test_video_.name, test_video_.width,
105+ test_video_.height, 30, 1, 0,
106+ test_video_.frames);
107+
108+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
109+ }
110+
111+ void RunPeriodicKey() {
112+ test_video_ = GET_PARAM(2);
113+ target_bitrate_ = GET_PARAM(1);
114+ if (test_video_.width == 1280 && target_bitrate_ == 200) return;
115+ if (test_video_.width == 640 && target_bitrate_ == 1000) return;
116+ key_interval_ = 100;
117+ SetConfig();
118+ rc_api_ = libvpx::VP8RateControlRTC::Create(rc_cfg_);
119+ rc_api_->UpdateRateControl(rc_cfg_);
120+
121+ ::libvpx_test::I420VideoSource video(test_video_.name, test_video_.width,
122+ test_video_.height, 30, 1, 0,
123+ test_video_.frames);
124+
125+ ASSERT_NO_FATAL_FAILURE(RunLoop(&video));
126+ }
127+
128+ private:
129+ void SetConfig() {
130+ rc_cfg_.width = test_video_.width;
131+ rc_cfg_.height = test_video_.height;
132+ rc_cfg_.max_quantizer = 60;
133+ rc_cfg_.min_quantizer = 2;
134+ rc_cfg_.target_bandwidth = target_bitrate_;
135+ rc_cfg_.buf_initial_sz = 600;
136+ rc_cfg_.buf_optimal_sz = 600;
137+ rc_cfg_.buf_sz = target_bitrate_;
138+ rc_cfg_.undershoot_pct = 50;
139+ rc_cfg_.overshoot_pct = 50;
140+ rc_cfg_.max_intra_bitrate_pct = 1000;
141+ rc_cfg_.framerate = 30.0;
142+ rc_cfg_.layer_target_bitrate[0] = target_bitrate_;
143+
144+ // Encoder settings for ground truth.
145+ cfg_.g_w = test_video_.width;
146+ cfg_.g_h = test_video_.height;
147+ cfg_.rc_undershoot_pct = 50;
148+ cfg_.rc_overshoot_pct = 50;
149+ cfg_.rc_buf_initial_sz = 600;
150+ cfg_.rc_buf_optimal_sz = 600;
151+ cfg_.rc_buf_sz = target_bitrate_;
152+ cfg_.rc_dropframe_thresh = 0;
153+ cfg_.rc_min_quantizer = 2;
154+ cfg_.rc_max_quantizer = 60;
155+ cfg_.rc_end_usage = VPX_CBR;
156+ cfg_.g_lag_in_frames = 0;
157+ cfg_.g_error_resilient = 1;
158+ cfg_.rc_target_bitrate = target_bitrate_;
159+ cfg_.kf_min_dist = key_interval_;
160+ cfg_.kf_max_dist = key_interval_;
161+ }
162+
163+ std::unique_ptr<libvpx::VP8RateControlRTC> rc_api_;
164+ libvpx::VP8RateControlRtcConfig rc_cfg_;
165+ int key_interval_;
166+ int target_bitrate_;
167+ Vp8RCTestVideo test_video_;
168+ libvpx::VP8FrameParamsQpRTC frame_params_;
169+ bool encoder_exit_;
170+};
171+
172+TEST_P(Vp8RcInterfaceTest, OneLayer) { RunOneLayer(); }
173+
174+TEST_P(Vp8RcInterfaceTest, OneLayerPeriodicKey) { RunPeriodicKey(); }
175+
176+VP8_INSTANTIATE_TEST_SUITE(Vp8RcInterfaceTest,
177+ ::testing::Values(200, 400, 1000),
178+ ::testing::ValuesIn(kVp8RCTestVectors));
179+
180+} // namespace
test/vp9_ratectrl_rtc_test.ccrenamed from test/ratectrl_rtc_test.cc+0−5
@@ -371,8 +371,3 @@ VP9_INSTANTIATE_TEST_SUITE(RcInterfaceTest, ::testing::Values(0, 3), · 370 unmodified lines
371371 ::testing::Values(VPX_CBR, VPX_VBR));
372372 VP9_INSTANTIATE_TEST_SUITE(RcInterfaceSvcTest, ::testing::Values(0, 3));
373373 } // namespace
374−
375−int main(int argc, char **argv) {
376− ::testing::InitGoogleTest(&argc, argv);
377− return RUN_ALL_TESTS();
378−}
vp8/encoder/onyx_if.c+3−3
@@ -4017,7 +4017,8 @@ static void encode_frame_to_data_rate(VP8_COMP *cpi, size_t *size, · 4016 unmodified lines
40174017 if (frame_over_shoot_limit == 0) frame_over_shoot_limit = 1;
40184018
40194019 /* Are we are overshooting and up against the limit of active max Q. */
4020− if (((cpi->pass != 2) ||
4020+ if (!cpi->rt_always_update_correction_factor &&
4021+ ((cpi->pass != 2) ||
40214022 (cpi->oxcf.end_usage == USAGE_STREAM_FROM_SERVER)) &&
40224023 (Q == cpi->active_worst_quality) &&
40234024 (cpi->active_worst_quality < cpi->worst_quality) &&
@@ -4446,8 +4447,7 @@ static void encode_frame_to_data_rate(VP8_COMP *cpi, size_t *size, · 422 unmodified lines
44464447 }
44474448 }
44484449
4449− if (cpi->rt_always_update_correction_factor || !active_worst_qchanged)
4450− vp8_update_rate_correction_factors(cpi, 2);
4450+ if (!active_worst_qchanged) vp8_update_rate_correction_factors(cpi, 2);
44514451
44524452 cpi->last_q[cm->frame_type] = cm->base_qindex;
44534453
vp8/encoder/ratectrl.c+2−1
@@ -327,7 +327,8 @@ static void calc_iframe_target_size(VP8_COMP *cpi) { · 326 unmodified lines
327327 int initial_boost = 32; /* |3.0 * per_frame_bandwidth| */
328328 /* Boost depends somewhat on frame rate: only used for 1 layer case. */
329329 if (cpi->oxcf.number_of_layers == 1) {
330− kf_boost = VPXMAX(initial_boost, (int)(2 * cpi->output_framerate - 16));
330+ kf_boost =
331+ VPXMAX(initial_boost, (int)round(2 * cpi->output_framerate - 16));
331332 } else {
332333 /* Initial factor: set target size to: |3.0 * per_frame_bandwidth|. */
333334 kf_boost = initial_boost;
vp8/vp8_ratectrl_rtc.cc+288−0
@@ -0,0 +1,288 @@
1+/*
2+ * Copyright (c) 2021 The WebM project authors. All Rights Reserved.
3+ *
4+ * Use of this source code is governed by a BSD-style license
5+ * that can be found in the LICENSE file in the root of the source
6+ * tree. An additional intellectual property rights grant can be found
7+ * in the file PATENTS. All contributing project authors may
8+ * be found in the AUTHORS file in the root of the source tree.
9+ */
10+
11+#include <new>
12+#include "vp8/vp8_ratectrl_rtc.h"
13+#include "vp8/encoder/ratectrl.h"
14+
15+namespace libvpx {
16+/* Quant MOD */
17+static const int kQTrans[] = {
18+ 0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 12, 13, 15, 17, 18, 19,
19+ 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 35, 37, 39, 41,
20+ 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 64, 67, 70, 73, 76, 79,
21+ 82, 85, 88, 91, 94, 97, 100, 103, 106, 109, 112, 115, 118, 121, 124, 127,
22+};
23+
24+static const unsigned char kf_high_motion_minq[QINDEX_RANGE] = {
25+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
26+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
27+ 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5,
28+ 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 8, 8, 8, 8, 9, 9, 10, 10,
29+ 10, 10, 11, 11, 11, 11, 12, 12, 13, 13, 13, 13, 14, 14, 15, 15, 15, 15, 16,
30+ 16, 16, 16, 17, 17, 18, 18, 18, 18, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21,
31+ 22, 22, 23, 23, 24, 25, 25, 26, 26, 27, 28, 28, 29, 30
32+};
33+
34+static const unsigned char inter_minq[QINDEX_RANGE] = {
35+ 0, 0, 1, 1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 8, 8, 9, 9, 10, 11,
36+ 11, 12, 13, 13, 14, 15, 15, 16, 17, 17, 18, 19, 20, 20, 21, 22, 22, 23, 24,
37+ 24, 25, 26, 27, 27, 28, 29, 30, 30, 31, 32, 33, 33, 34, 35, 36, 36, 37, 38,
38+ 39, 39, 40, 41, 42, 42, 43, 44, 45, 46, 46, 47, 48, 49, 50, 50, 51, 52, 53,
39+ 54, 55, 55, 56, 57, 58, 59, 60, 60, 61, 62, 63, 64, 65, 66, 67, 67, 68, 69,
40+ 70, 71, 72, 73, 74, 75, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 86,
41+ 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100
42+};
43+
44+static int rescale(int val, int num, int denom) {
45+ int64_t llnum = num;
46+ int64_t llden = denom;
47+ int64_t llval = val;
48+
49+ return (int)(llval * llnum / llden);
50+}
51+
52+std::unique_ptr<VP8RateControlRTC> VP8RateControlRTC::Create(
53+ const VP8RateControlRtcConfig &cfg) {
54+ std::unique_ptr<VP8RateControlRTC> rc_api(new (std::nothrow)
55+ VP8RateControlRTC());
56+ if (!rc_api) return nullptr;
57+ rc_api->cpi_ = static_cast<VP8_COMP *>(vpx_memalign(32, sizeof(*cpi_)));
58+ if (!rc_api->cpi_) return nullptr;
59+ vp8_zero(*rc_api->cpi_);
60+
61+ rc_api->InitRateControl(cfg);
62+
63+ return rc_api;
64+}
65+
66+void VP8RateControlRTC::InitRateControl(const VP8RateControlRtcConfig &rc_cfg) {
67+ VP8_COMMON *cm = &cpi_->common;
68+ VP8_CONFIG *oxcf = &cpi_->oxcf;
69+ oxcf->end_usage = USAGE_STREAM_FROM_SERVER;
70+ cpi_->pass = 0;
71+ cm->show_frame = 1;
72+ oxcf->drop_frames_water_mark = 0;
73+ cm->current_video_frame = 0;
74+ cpi_->auto_gold = 1;
75+ cpi_->key_frame_count = 1;
76+ cpi_->rate_correction_factor = 1.0;
77+ cpi_->key_frame_rate_correction_factor = 1.0;
78+ cpi_->cyclic_refresh_mode_enabled = 0;
79+ cpi_->auto_worst_q = 1;
80+ cpi_->kf_overspend_bits = 0;
81+ cpi_->kf_bitrate_adjustment = 0;
82+ cpi_->gf_overspend_bits = 0;
83+ cpi_->non_gf_bitrate_adjustment = 0;
84+ UpdateRateControl(rc_cfg);
85+ cpi_->buffer_level = oxcf->starting_buffer_level;
86+ cpi_->bits_off_target = oxcf->starting_buffer_level;
87+}
88+
89+void VP8RateControlRTC::UpdateRateControl(
90+ const VP8RateControlRtcConfig &rc_cfg) {
91+ VP8_COMMON *cm = &cpi_->common;
92+ VP8_CONFIG *oxcf = &cpi_->oxcf;
93+
94+ cm->Width = rc_cfg.width;
95+ cm->Height = rc_cfg.height;
96+ oxcf->Width = rc_cfg.width;
97+ oxcf->Height = rc_cfg.height;
98+ oxcf->worst_allowed_q = kQTrans[rc_cfg.max_quantizer];
99+ oxcf->best_allowed_q = kQTrans[rc_cfg.min_quantizer];
100+ cpi_->worst_quality = oxcf->worst_allowed_q;
101+ cpi_->best_quality = oxcf->best_allowed_q;
102+ cpi_->output_framerate = rc_cfg.framerate;
103+ oxcf->target_bandwidth = 1000 * rc_cfg.target_bandwidth;
104+ oxcf->fixed_q = -1;
105+ oxcf->error_resilient_mode = 1;
106+ oxcf->starting_buffer_level_in_ms = rc_cfg.buf_initial_sz;
107+ oxcf->optimal_buffer_level_in_ms = rc_cfg.buf_optimal_sz;
108+ oxcf->maximum_buffer_size_in_ms = rc_cfg.buf_sz;
109+ oxcf->starting_buffer_level = rc_cfg.buf_initial_sz;
110+ oxcf->optimal_buffer_level = rc_cfg.buf_optimal_sz;
111+ oxcf->maximum_buffer_size = rc_cfg.buf_sz;
112+ oxcf->number_of_layers = 1;
113+ cpi_->buffered_mode = oxcf->optimal_buffer_level > 0;
114+ oxcf->under_shoot_pct = rc_cfg.undershoot_pct;
115+ oxcf->over_shoot_pct = rc_cfg.overshoot_pct;
116+ cpi_->oxcf.rc_max_intra_bitrate_pct = rc_cfg.max_intra_bitrate_pct;
117+ cpi_->framerate = rc_cfg.framerate;
118+ for (int i = 0; i < KEY_FRAME_CONTEXT; ++i) {
119+ cpi_->prior_key_frame_distance[i] =
120+ static_cast<int>(cpi_->output_framerate);
121+ }
122+
123+ cpi_->total_actual_bits = 0;
124+ cpi_->total_target_vs_actual = 0;
125+
126+ cm->mb_rows = cm->Height >> 4;
127+ cm->mb_cols = cm->Width >> 4;
128+ cm->MBs = cm->mb_rows * cm->mb_cols;
129+ cm->mode_info_stride = cm->mb_cols + 1;
130+
131+ oxcf->starting_buffer_level =
132+ rescale((int)oxcf->starting_buffer_level, oxcf->target_bandwidth, 1000);
133+ /* Set or reset optimal and maximum buffer levels. */
134+ if (oxcf->optimal_buffer_level == 0) {
135+ oxcf->optimal_buffer_level = oxcf->target_bandwidth / 8;
136+ } else {
137+ oxcf->optimal_buffer_level =
138+ rescale((int)oxcf->optimal_buffer_level, oxcf->target_bandwidth, 1000);
139+ }
140+ if (oxcf->maximum_buffer_size == 0) {
141+ oxcf->maximum_buffer_size = oxcf->target_bandwidth / 8;
142+ } else {
143+ oxcf->maximum_buffer_size =
144+ rescale((int)oxcf->maximum_buffer_size, oxcf->target_bandwidth, 1000);
145+ }
146+
147+ if (cpi_->bits_off_target > oxcf->maximum_buffer_size) {
148+ cpi_->bits_off_target = oxcf->maximum_buffer_size;
149+ cpi_->buffer_level = cpi_->bits_off_target;
150+ }
151+
152+ vp8_new_framerate(cpi_, cpi_->framerate);
153+}
154+
155+void VP8RateControlRTC::ComputeQP(const VP8FrameParamsQpRTC &frame_params) {
156+ VP8_COMMON *const cm = &cpi_->common;
157+ cm->frame_type = frame_params.frame_type;
158+ cm->refresh_golden_frame = (cm->frame_type == KEY_FRAME) ? 1 : 0;
159+ cm->refresh_alt_ref_frame = (cm->frame_type == KEY_FRAME) ? 1 : 0;
160+ if (cm->frame_type == KEY_FRAME && cpi_->common.current_video_frame > 0) {
161+ cpi_->common.frame_flags |= FRAMEFLAGS_KEY;
162+ }
163+
164+ vp8_pick_frame_size(cpi_);
165+
166+ if (cpi_->buffer_level >= cpi_->oxcf.optimal_buffer_level &&
167+ cpi_->buffered_mode) {
168+ /* Max adjustment is 1/4 */
169+ int Adjustment = cpi_->active_worst_quality / 4;
170+ if (Adjustment) {
171+ int buff_lvl_step;
172+ if (cpi_->buffer_level < cpi_->oxcf.maximum_buffer_size) {
173+ buff_lvl_step = (int)((cpi_->oxcf.maximum_buffer_size -
174+ cpi_->oxcf.optimal_buffer_level) /
175+ Adjustment);
176+ if (buff_lvl_step) {
177+ Adjustment =
178+ (int)((cpi_->buffer_level - cpi_->oxcf.optimal_buffer_level) /
179+ buff_lvl_step);
180+ } else {
181+ Adjustment = 0;
182+ }
183+ }
184+ cpi_->active_worst_quality -= Adjustment;
185+ if (cpi_->active_worst_quality < cpi_->active_best_quality) {
186+ cpi_->active_worst_quality = cpi_->active_best_quality;
187+ }
188+ }
189+ }
190+
191+ if (cpi_->ni_frames > 150) {
192+ int q = cpi_->active_worst_quality;
193+ if (cm->frame_type == KEY_FRAME) {
194+ cpi_->active_best_quality = kf_high_motion_minq[q];
195+ } else {
196+ cpi_->active_best_quality = inter_minq[q];
197+ }
198+
199+ if (cpi_->buffer_level >= cpi_->oxcf.maximum_buffer_size) {
200+ cpi_->active_best_quality = cpi_->best_quality;
201+
202+ } else if (cpi_->buffer_level > cpi_->oxcf.optimal_buffer_level) {
203+ int Fraction =
204+ (int)(((cpi_->buffer_level - cpi_->oxcf.optimal_buffer_level) * 128) /
205+ (cpi_->oxcf.maximum_buffer_size -
206+ cpi_->oxcf.optimal_buffer_level));
207+ int min_qadjustment =
208+ ((cpi_->active_best_quality - cpi_->best_quality) * Fraction) / 128;
209+
210+ cpi_->active_best_quality -= min_qadjustment;
211+ }
212+ }
213+
214+ /* Clip the active best and worst quality values to limits */
215+ if (cpi_->active_worst_quality > cpi_->worst_quality) {
216+ cpi_->active_worst_quality = cpi_->worst_quality;
217+ }
218+ if (cpi_->active_best_quality < cpi_->best_quality) {
219+ cpi_->active_best_quality = cpi_->best_quality;
220+ }
221+ if (cpi_->active_worst_quality < cpi_->active_best_quality) {
222+ cpi_->active_worst_quality = cpi_->active_best_quality;
223+ }
224+
225+ q_ = vp8_regulate_q(cpi_, cpi_->this_frame_target);
226+ vp8_set_quantizer(cpi_, q_);
227+}
228+
229+int VP8RateControlRTC::GetQP() const { return q_; }
230+
231+void VP8RateControlRTC::PostEncodeUpdate(uint64_t encoded_frame_size) {
232+ VP8_COMMON *const cm = &cpi_->common;
233+
234+ cpi_->total_byte_count += encoded_frame_size;
235+ cpi_->projected_frame_size = static_cast<int>(encoded_frame_size << 3);
236+
237+ vp8_update_rate_correction_factors(cpi_, 2);
238+
239+ cpi_->last_q[cm->frame_type] = cm->base_qindex;
240+
241+ if (cm->frame_type == KEY_FRAME) {
242+ vp8_adjust_key_frame_context(cpi_);
243+ }
244+
245+ /* Keep a record of ambient average Q. */
246+ if (cm->frame_type != KEY_FRAME) {
247+ cpi_->avg_frame_qindex =
248+ (2 + 3 * cpi_->avg_frame_qindex + cm->base_qindex) >> 2;
249+ }
250+ /* Keep a record from which we can calculate the average Q excluding
251+ * key frames.
252+ */
253+ if (cm->frame_type != KEY_FRAME) {
254+ cpi_->ni_frames++;
255+ /* Damp value for first few frames */
256+ if (cpi_->ni_frames > 150) {
257+ cpi_->ni_tot_qi += q_;
258+ cpi_->ni_av_qi = (cpi_->ni_tot_qi / cpi_->ni_frames);
259+ } else {
260+ cpi_->ni_tot_qi += q_;
261+ cpi_->ni_av_qi =
262+ ((cpi_->ni_tot_qi / cpi_->ni_frames) + cpi_->worst_quality + 1) / 2;
263+ }
264+
265+ /* If the average Q is higher than what was used in the last
266+ * frame (after going through the recode loop to keep the frame
267+ * size within range) then use the last frame value - 1. The -1
268+ * is designed to stop Q and hence the data rate, from
269+ * progressively falling away during difficult sections, but at
270+ * the same time reduce the number of itterations around the
271+ * recode loop.
272+ */
273+ if (q_ > cpi_->ni_av_qi) cpi_->ni_av_qi = q_ - 1;
274+ }
275+
276+ cpi_->bits_off_target +=
277+ cpi_->av_per_frame_bandwidth - cpi_->projected_frame_size;
278+ if (cpi_->bits_off_target > cpi_->oxcf.maximum_buffer_size) {
279+ cpi_->bits_off_target = cpi_->oxcf.maximum_buffer_size;
280+ }
281+
282+ cpi_->total_actual_bits += cpi_->projected_frame_size;
283+ cpi_->buffer_level = cpi_->bits_off_target;
284+
285+ cpi_->common.current_video_frame++;
286+ cpi_->frames_since_key++;
287+}
288+} // namespace libvpx
vp8/vp8_ratectrl_rtc.h+62−0
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (c) 2021 The WebM project authors. All Rights Reserved.
3+ *
4+ * Use of this source code is governed by a BSD-style license
5+ * that can be found in the LICENSE file in the root of the source
6+ * tree. An additional intellectual property rights grant can be found
7+ * in the file PATENTS. All contributing project authors may
8+ * be found in the AUTHORS file in the root of the source tree.
9+ */
10+
11+#ifndef VPX_VP8_RATECTRL_RTC_H_
12+#define VPX_VP8_RATECTRL_RTC_H_
13+
14+#include <cstdint>
15+#include <memory>
16+
17+#include "vp8/encoder/onyx_int.h"
18+#include "vp8/common/common.h"
19+#include "vpx/internal/vpx_ratectrl_rtc.h"
20+
21+namespace libvpx {
22+struct VP8RateControlRtcConfig : public VpxRateControlRtcConfig {
23+ public:
24+ VP8RateControlRtcConfig() {
25+ vp8_zero(layer_target_bitrate);
26+ vp8_zero(ts_rate_decimator);
27+ }
28+};
29+
30+struct VP8FrameParamsQpRTC {
31+ FRAME_TYPE frame_type;
32+};
33+
34+class VP8RateControlRTC {
35+ public:
36+ static std::unique_ptr<VP8RateControlRTC> Create(
37+ const VP8RateControlRtcConfig &cfg);
38+ ~VP8RateControlRTC() {
39+ if (cpi_) {
40+ vpx_free(cpi_->gf_active_flags);
41+ vpx_free(cpi_);
42+ }
43+ }
44+
45+ void UpdateRateControl(const VP8RateControlRtcConfig &rc_cfg);
46+ // GetQP() needs to be called after ComputeQP() to get the latest QP
47+ int GetQP() const;
48+ // int GetLoopfilterLevel() const;
49+ void ComputeQP(const VP8FrameParamsQpRTC &frame_params);
50+ // Feedback to rate control with the size of current encoded frame
51+ void PostEncodeUpdate(uint64_t encoded_frame_size);
52+
53+ private:
54+ VP8RateControlRTC() {}
55+ void InitRateControl(const VP8RateControlRtcConfig &cfg);
56+ VP8_COMP *cpi_;
57+ int q_;
58+};
59+
60+} // namespace libvpx
61+
62+#endif // VPX_VP8_RATECTRL_RTC_H_
vp9/ratectrl_rtc.h+3−38
@@ -22,28 +22,14 @@ · 21 unmodified lines
2222 #include "vp9/encoder/vp9_encoder.h"
2323 #include "vp9/encoder/vp9_firstpass.h"
2424 #include "vp9/vp9_cx_iface.h"
25+#include "vpx/internal/vpx_ratectrl_rtc.h"
2526 #include "vpx_mem/vpx_mem.h"
2627
2728 namespace libvpx {
2829
29−struct VP9RateControlRtcConfig {
30+struct VP9RateControlRtcConfig : public VpxRateControlRtcConfig {
3031 public:
3132 VP9RateControlRtcConfig() {
32− width = 1280;
33− height = 720;
34− max_quantizer = 63;
35− min_quantizer = 2;
36− target_bandwidth = 1000;
37− buf_initial_sz = 600;
38− buf_optimal_sz = 600;
39− buf_sz = 1000;
40− undershoot_pct = overshoot_pct = 50;
41− max_intra_bitrate_pct = 50;
42− max_inter_bitrate_pct = 0;
43− framerate = 30.0;
44− ss_number_layers = ts_number_layers = 1;
45− rc_mode = VPX_CBR;
46− aq_mode = 0;
4733 vp9_zero(max_quantizers);
4834 vp9_zero(min_quantizers);
4935 vp9_zero(scaling_factor_den);
@@ -52,26 +38,10 @@ struct VP9RateControlRtcConfig { · 2 unmodified lines
5238 vp9_zero(ts_rate_decimator);
5339 scaling_factor_num[0] = 1;
5440 scaling_factor_den[0] = 1;
55− layer_target_bitrate[0] = static_cast<int>(target_bandwidth);
5641 max_quantizers[0] = max_quantizer;
5742 min_quantizers[0] = min_quantizer;
58− ts_rate_decimator[0] = 1;
5943 }
6044
61− int width;
62− int height;
63− // 0-63
64− int max_quantizer;
65− int min_quantizer;
66− int64_t target_bandwidth;
67− int64_t buf_initial_sz;
68− int64_t buf_optimal_sz;
69− int64_t buf_sz;
70− int undershoot_pct;
71− int overshoot_pct;
72− int max_intra_bitrate_pct;
73− int max_inter_bitrate_pct;
74− double framerate;
7545 // Number of spatial layers
7646 int ss_number_layers;
7747 // Number of temporal layers
@@ -80,11 +50,6 @@ struct VP9RateControlRtcConfig { · 2 unmodified lines
8050 int min_quantizers[VPX_MAX_LAYERS];
8151 int scaling_factor_num[VPX_SS_MAX_LAYERS];
8252 int scaling_factor_den[VPX_SS_MAX_LAYERS];
83− int layer_target_bitrate[VPX_MAX_LAYERS];
84− int ts_rate_decimator[VPX_TS_MAX_LAYERS];
85− // vbr, cbr
86− enum vpx_rc_mode rc_mode;
87− int aq_mode;
8853 };
8954
9055 struct VP9FrameParamsQpRTC {
@@ -94,7 +59,7 @@ struct VP9FrameParamsQpRTC { · 3 unmodified lines
9459 };
9560
9661 // This interface allows using VP9 real-time rate control without initializing
97−// the encoder. To use this interface, you need to link with libvp9rc.a.
62+// the encoder. To use this interface, you need to link with libvpxrc.a.
9863 //
9964 // #include "vp9/ratectrl_rtc.h"
10065 // VP9RateControlRTC rc_api;
vpx/internal/vpx_ratectrl_rtc.h+62−0
@@ -0,0 +1,62 @@
1+/*
2+ * Copyright (c) 2021 The WebM project authors. All Rights Reserved.
3+ *
4+ * Use of this source code is governed by a BSD-style license
5+ * that can be found in the LICENSE file in the root of the source
6+ * tree. An additional intellectual property rights grant can be found
7+ * in the file PATENTS. All contributing project authors may
8+ * be found in the AUTHORS file in the root of the source tree.
9+ */
10+
11+#ifndef VPX_VPX_RATECTRL_RTC_H_
12+#define VPX_VPX_RATECTRL_RTC_H_
13+
14+#include "vpx/vpx_encoder.h"
15+
16+namespace libvpx {
17+struct VpxRateControlRtcConfig {
18+ public:
19+ VpxRateControlRtcConfig() {
20+ width = 1280;
21+ height = 720;
22+ max_quantizer = 63;
23+ min_quantizer = 2;
24+ target_bandwidth = 1000;
25+ buf_initial_sz = 600;
26+ buf_optimal_sz = 600;
27+ buf_sz = 1000;
28+ undershoot_pct = overshoot_pct = 50;
29+ max_intra_bitrate_pct = 50;
30+ max_inter_bitrate_pct = 0;
31+ framerate = 30.0;
32+ ts_number_layers = 1;
33+ rc_mode = VPX_CBR;
34+ aq_mode = 0;
35+ layer_target_bitrate[0] = static_cast<int>(target_bandwidth);
36+ ts_rate_decimator[0] = 1;
37+ }
38+
39+ int width;
40+ int height;
41+ // 0-63
42+ int max_quantizer;
43+ int min_quantizer;
44+ int64_t target_bandwidth;
45+ int64_t buf_initial_sz;
46+ int64_t buf_optimal_sz;
47+ int64_t buf_sz;
48+ int undershoot_pct;
49+ int overshoot_pct;
50+ int max_intra_bitrate_pct;
51+ int max_inter_bitrate_pct;
52+ double framerate;
53+ // Number of temporal layers
54+ int ts_number_layers;
55+ int layer_target_bitrate[VPX_MAX_LAYERS];
56+ int ts_rate_decimator[VPX_TS_MAX_LAYERS];
57+ // vbr, cbr
58+ enum vpx_rc_mode rc_mode;
59+ int aq_mode;
60+};
61+} // namespace libvpx
62+#endif
vpx/vp8cx.h+1−1
@@ -747,7 +747,7 @@ enum vp8e_enc_control_id { · 746 unmodified lines
747747 *
748748 * This will turn off cyclic refresh for vp8.
749749 *
750− * With those, the rate control is expected to work exactly the same as the
750+ * With this, the rate control is expected to work exactly the same as the
751751 * interface provided in vp8_ratectrl_rtc.cc/h
752752 *
753753 * Supported in codecs: VP8
vpx/vpx_codec.mk+1−0
@@ -33,6 +33,7 @@ API_SRCS-yes += vpx_decoder.h · 32 unmodified lines
3333 API_SRCS-yes += src/vpx_encoder.c
3434 API_SRCS-yes += vpx_encoder.h
3535 API_SRCS-yes += internal/vpx_codec_internal.h
36+API_SRCS-yes += internal/vpx_ratectrl_rtc.h
3637 API_SRCS-yes += src/vpx_codec.c
3738 API_SRCS-yes += src/vpx_image.c
3839 API_SRCS-yes += vpx_codec.h

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t