Zero-hertz encoding mode: avoid encoder bitrate overshooting.
The encoders wrapped in VideoStreamEncoder grossly over-estimates
available bitrate when capture FPS falls close to zero, and frames
re-commence highly frequent delivery. Avoid this by moving the input
RateStatistics inside VSE into the frame cadence adapter, and changing
the reported framerate under zero-hertz encoding mode to always return
the configured max FPS.
Bug: chromium:1255737
Change-Id: Iaa71ef51c0755b12e24e435d86d9562122ed494e
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/239126
Commit-Queue: Markus Handell <handellm@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35431}
diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc
index 32d4f94..c5ff292 100644
--- a/video/video_stream_encoder_unittest.cc
+++ b/video/video_stream_encoder_unittest.cc
@@ -76,6 +76,7 @@
using ::testing::Field;
using ::testing::Ge;
using ::testing::Gt;
+using ::testing::Invoke;
using ::testing::Le;
using ::testing::Lt;
using ::testing::Matcher;
@@ -640,30 +641,26 @@
~AdaptedVideoStreamEncoder() { Stop(); }
};
- SimpleVideoStreamEncoderFactory()
- : time_controller_(Timestamp::Millis(0)),
- task_queue_factory_(time_controller_.CreateTaskQueueFactory()),
- stats_proxy_(std::make_unique<MockableSendStatisticsProxy>(
- time_controller_.GetClock(),
- VideoSendStream::Config(nullptr),
- webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo)),
- encoder_settings_(
- VideoEncoder::Capabilities(/*loss_notification=*/false)),
- fake_encoder_(time_controller_.GetClock()),
- encoder_factory_(&fake_encoder_) {
+ SimpleVideoStreamEncoderFactory() {
encoder_settings_.encoder_factory = &encoder_factory_;
+ encoder_settings_.bitrate_allocator_factory =
+ bitrate_allocator_factory_.get();
}
std::unique_ptr<AdaptedVideoStreamEncoder> Create(
- std::unique_ptr<FrameCadenceAdapterInterface> zero_hertz_adapter) {
+ std::unique_ptr<FrameCadenceAdapterInterface> zero_hertz_adapter,
+ TaskQueueBase** encoder_queue_ptr = nullptr) {
+ auto encoder_queue =
+ time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
+ "EncoderQueue", TaskQueueFactory::Priority::NORMAL);
+ if (encoder_queue_ptr)
+ *encoder_queue_ptr = encoder_queue.get();
auto result = std::make_unique<AdaptedVideoStreamEncoder>(
time_controller_.GetClock(),
/*number_of_cores=*/1,
/*stats_proxy=*/stats_proxy_.get(), encoder_settings_,
std::make_unique<CpuOveruseDetectorProxy>(/*stats_proxy=*/nullptr),
- std::move(zero_hertz_adapter),
- time_controller_.GetTaskQueueFactory()->CreateTaskQueue(
- "EncoderQueue", TaskQueueFactory::Priority::NORMAL),
+ std::move(zero_hertz_adapter), std::move(encoder_queue),
VideoStreamEncoder::BitrateAllocationCallbackType::
kVideoBitrateAllocation);
result->SetSink(&sink_, /*rotation_applied=*/false);
@@ -692,12 +689,20 @@
}
};
- GlobalSimulatedTimeController time_controller_;
- std::unique_ptr<TaskQueueFactory> task_queue_factory_;
- std::unique_ptr<MockableSendStatisticsProxy> stats_proxy_;
- VideoStreamEncoderSettings encoder_settings_;
- test::FakeEncoder fake_encoder_;
- test::VideoEncoderProxyFactory encoder_factory_;
+ GlobalSimulatedTimeController time_controller_{Timestamp::Millis(0)};
+ std::unique_ptr<TaskQueueFactory> task_queue_factory_{
+ time_controller_.CreateTaskQueueFactory()};
+ std::unique_ptr<MockableSendStatisticsProxy> stats_proxy_ =
+ std::make_unique<MockableSendStatisticsProxy>(
+ time_controller_.GetClock(),
+ VideoSendStream::Config(nullptr),
+ webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo);
+ std::unique_ptr<VideoBitrateAllocatorFactory> bitrate_allocator_factory_ =
+ CreateBuiltinVideoBitrateAllocatorFactory();
+ VideoStreamEncoderSettings encoder_settings_{
+ VideoEncoder::Capabilities(/*loss_notification=*/false)};
+ test::FakeEncoder fake_encoder_{time_controller_.GetClock()};
+ test::VideoEncoderProxyFactory encoder_factory_{&fake_encoder_};
NullEncoderSink sink_;
};
@@ -706,6 +711,8 @@
MOCK_METHOD(void, Initialize, (Callback * callback), (override));
MOCK_METHOD(void, SetZeroHertzModeEnabled, (bool), (override));
MOCK_METHOD(void, OnFrame, (const VideoFrame&), (override));
+ MOCK_METHOD(absl::optional<uint32_t>, GetInputFrameRateFps, (), (override));
+ MOCK_METHOD(void, UpdateFrameRate, (), (override));
};
class MockEncoderSelector
@@ -8746,4 +8753,46 @@
.build());
}
+TEST(VideoStreamEncoderFrameCadenceTest, UsesFrameCadenceAdapterForFrameRate) {
+ auto adapter = std::make_unique<MockFrameCadenceAdapter>();
+ auto* adapter_ptr = adapter.get();
+ test::FrameForwarder video_source;
+ SimpleVideoStreamEncoderFactory factory;
+ FrameCadenceAdapterInterface::Callback* video_stream_encoder_callback =
+ nullptr;
+ EXPECT_CALL(*adapter_ptr, Initialize)
+ .WillOnce(Invoke([&video_stream_encoder_callback](
+ FrameCadenceAdapterInterface::Callback* callback) {
+ video_stream_encoder_callback = callback;
+ }));
+ TaskQueueBase* encoder_queue = nullptr;
+ auto video_stream_encoder =
+ factory.Create(std::move(adapter), &encoder_queue);
+
+ // This is just to make the VSE operational. We'll feed a frame directly by
+ // the callback interface.
+ video_stream_encoder->SetSource(
+ &video_source, webrtc::DegradationPreference::MAINTAIN_FRAMERATE);
+
+ VideoEncoderConfig video_encoder_config;
+ test::FillEncoderConfiguration(kVideoCodecGeneric, 1, &video_encoder_config);
+ video_stream_encoder->ConfigureEncoder(std::move(video_encoder_config),
+ /*max_data_payload_length=*/1000);
+
+ EXPECT_CALL(*adapter_ptr, GetInputFrameRateFps);
+ EXPECT_CALL(*adapter_ptr, UpdateFrameRate);
+ encoder_queue->PostTask(ToQueuedTask([video_stream_encoder_callback] {
+ video_stream_encoder_callback->OnFrame(
+ Timestamp::Millis(1), 1,
+ VideoFrame::Builder()
+ .set_video_frame_buffer(
+ rtc::make_ref_counted<NV12Buffer>(/*width=*/16, /*height=*/16))
+ .set_ntp_time_ms(0)
+ .set_timestamp_ms(0)
+ .set_rotation(kVideoRotation_0)
+ .build());
+ }));
+ factory.DepleteTaskQueues();
+}
+
} // namespace webrtc