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/BUILD.gn b/video/BUILD.gn
index 84b2d98..99e82c1 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -273,6 +273,7 @@
"../rtc_base:macromagic",
"../rtc_base:rtc_base_approved",
"../rtc_base/synchronization:mutex",
+ "../rtc_base/system:no_unique_address",
"../rtc_base/task_utils:pending_task_safety_flag",
"../rtc_base/task_utils:to_queued_task",
"../system_wrappers",
diff --git a/video/frame_cadence_adapter.cc b/video/frame_cadence_adapter.cc
index c82ab5a..c467909 100644
--- a/video/frame_cadence_adapter.cc
+++ b/video/frame_cadence_adapter.cc
@@ -18,7 +18,9 @@
#include "api/task_queue/task_queue_base.h"
#include "rtc_base/logging.h"
#include "rtc_base/race_checker.h"
+#include "rtc_base/rate_statistics.h"
#include "rtc_base/synchronization/mutex.h"
+#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/task_utils/pending_task_safety_flag.h"
#include "rtc_base/task_utils/to_queued_task.h"
#include "system_wrappers/include/clock.h"
@@ -28,6 +30,80 @@
namespace webrtc {
namespace {
+// Abstracts concrete modes of the cadence adapter.
+class AdapterMode {
+ public:
+ virtual ~AdapterMode() = default;
+
+ // Called on the worker thread for every frame that enters.
+ virtual void OnFrame(Timestamp post_time,
+ int frames_scheduled_for_processing,
+ const VideoFrame& frame) = 0;
+
+ // Returns the currently estimated input framerate.
+ virtual absl::optional<uint32_t> GetInputFrameRateFps() = 0;
+
+ // Updates the frame rate.
+ virtual void UpdateFrameRate() = 0;
+};
+
+// Implements a pass-through adapter. Single-threaded.
+class PassthroughAdapterMode : public AdapterMode {
+ public:
+ PassthroughAdapterMode(Clock* clock,
+ FrameCadenceAdapterInterface::Callback* callback)
+ : clock_(clock), callback_(callback) {
+ sequence_checker_.Detach();
+ }
+
+ // Adapter overrides.
+ void OnFrame(Timestamp post_time,
+ int frames_scheduled_for_processing,
+ const VideoFrame& frame) override {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ callback_->OnFrame(post_time, frames_scheduled_for_processing, frame);
+ }
+
+ absl::optional<uint32_t> GetInputFrameRateFps() override {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ return input_framerate_.Rate(clock_->TimeInMilliseconds());
+ }
+
+ void UpdateFrameRate() override {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ input_framerate_.Update(1, clock_->TimeInMilliseconds());
+ }
+
+ private:
+ Clock* const clock_;
+ FrameCadenceAdapterInterface::Callback* const callback_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
+ // Input frame rate statistics for use when not in zero-hertz mode.
+ RateStatistics input_framerate_ RTC_GUARDED_BY(sequence_checker_){
+ FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000};
+};
+
+// Implements a frame cadence adapter supporting zero-hertz input.
+class ZeroHertzAdapterMode : public AdapterMode {
+ public:
+ ZeroHertzAdapterMode(FrameCadenceAdapterInterface::Callback* callback,
+ double max_fps);
+
+ // Adapter overrides.
+ void OnFrame(Timestamp post_time,
+ int frames_scheduled_for_processing,
+ const VideoFrame& frame) override;
+ absl::optional<uint32_t> GetInputFrameRateFps() override;
+ void UpdateFrameRate() override {}
+
+ private:
+ FrameCadenceAdapterInterface::Callback* const callback_;
+ // The configured max_fps.
+ // TODO(crbug.com/1255737): support max_fps updates.
+ const double max_fps_;
+ RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
+};
+
class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface {
public:
FrameCadenceAdapterImpl(Clock* clock, TaskQueueBase* queue);
@@ -35,6 +111,8 @@
// FrameCadenceAdapterInterface overrides.
void Initialize(Callback* callback) override;
void SetZeroHertzModeEnabled(bool enabled) override;
+ absl::optional<uint32_t> GetInputFrameRateFps() override;
+ void UpdateFrameRate() override;
// VideoFrameSink overrides.
void OnFrame(const VideoFrame& frame) override;
@@ -48,8 +126,18 @@
int frames_scheduled_for_processing,
const VideoFrame& frame) RTC_RUN_ON(queue_);
+ // Returns true under all of the following conditions:
+ // - constraints min fps set to 0
+ // - constraints max fps set and greater than 0,
+ // - field trial enabled
+ // - zero-hertz mode enabled
+ bool IsZeroHertzScreenshareEnabled() const RTC_RUN_ON(queue_);
+
+ // Handles adapter creation on configuration changes.
+ void MaybeReconfigureAdapters(bool was_zero_hertz_enabled) RTC_RUN_ON(queue_);
+
// Called to report on constraint UMAs.
- void MaybeReportFrameRateConstraintUmas() RTC_RUN_ON(&queue_);
+ void MaybeReportFrameRateConstraintUmas() RTC_RUN_ON(queue_);
Clock* const clock_;
TaskQueueBase* const queue_;
@@ -58,6 +146,12 @@
// 0 Hz.
const bool zero_hertz_screenshare_enabled_;
+ // The two possible modes we're under.
+ absl::optional<PassthroughAdapterMode> passthrough_adapter_;
+ absl::optional<ZeroHertzAdapterMode> zero_hertz_adapter_;
+ // Cache for the current adapter mode.
+ AdapterMode* current_adapter_mode_ = nullptr;
+
// Set up during Initialize.
Callback* callback_ = nullptr;
@@ -80,6 +174,26 @@
ScopedTaskSafetyDetached safety_;
};
+ZeroHertzAdapterMode::ZeroHertzAdapterMode(
+ FrameCadenceAdapterInterface::Callback* callback,
+ double max_fps)
+ : callback_(callback), max_fps_(max_fps) {
+ sequence_checker_.Detach();
+}
+
+void ZeroHertzAdapterMode::OnFrame(Timestamp post_time,
+ int frames_scheduled_for_processing,
+ const VideoFrame& frame) {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ // TODO(crbug.com/1255737): fill with meaningful implementation.
+ callback_->OnFrame(post_time, frames_scheduled_for_processing, frame);
+}
+
+absl::optional<uint32_t> ZeroHertzAdapterMode::GetInputFrameRateFps() {
+ RTC_DCHECK_RUN_ON(&sequence_checker_);
+ return max_fps_;
+}
+
FrameCadenceAdapterImpl::FrameCadenceAdapterImpl(Clock* clock,
TaskQueueBase* queue)
: clock_(clock),
@@ -89,13 +203,30 @@
void FrameCadenceAdapterImpl::Initialize(Callback* callback) {
callback_ = callback;
+ passthrough_adapter_.emplace(clock_, callback);
+ current_adapter_mode_ = &passthrough_adapter_.value();
}
void FrameCadenceAdapterImpl::SetZeroHertzModeEnabled(bool enabled) {
RTC_DCHECK_RUN_ON(queue_);
+ bool was_zero_hertz_enabled = zero_hertz_and_uma_reporting_enabled_;
if (enabled && !zero_hertz_and_uma_reporting_enabled_)
has_reported_screenshare_frame_rate_umas_ = false;
zero_hertz_and_uma_reporting_enabled_ = enabled;
+ MaybeReconfigureAdapters(was_zero_hertz_enabled);
+}
+
+absl::optional<uint32_t> FrameCadenceAdapterImpl::GetInputFrameRateFps() {
+ RTC_DCHECK_RUN_ON(queue_);
+ return current_adapter_mode_->GetInputFrameRateFps();
+}
+
+void FrameCadenceAdapterImpl::UpdateFrameRate() {
+ RTC_DCHECK_RUN_ON(queue_);
+ // The frame rate need not be updated for the zero-hertz adapter. The
+ // passthrough adapter however uses it. Always pass frames into the
+ // passthrough to keep the estimation alive should there be an adapter switch.
+ passthrough_adapter_->UpdateFrameRate();
}
void FrameCadenceAdapterImpl::OnFrame(const VideoFrame& frame) {
@@ -124,7 +255,9 @@
<< constraints.max_fps.value_or(-1);
queue_->PostTask(ToQueuedTask(safety_.flag(), [this, constraints] {
RTC_DCHECK_RUN_ON(queue_);
+ bool was_zero_hertz_enabled = IsZeroHertzScreenshareEnabled();
source_constraints_ = constraints;
+ MaybeReconfigureAdapters(was_zero_hertz_enabled);
}));
}
@@ -133,7 +266,33 @@
Timestamp post_time,
int frames_scheduled_for_processing,
const VideoFrame& frame) {
- callback_->OnFrame(post_time, frames_scheduled_for_processing, frame);
+ current_adapter_mode_->OnFrame(post_time, frames_scheduled_for_processing,
+ frame);
+}
+
+// RTC_RUN_ON(queue_)
+bool FrameCadenceAdapterImpl::IsZeroHertzScreenshareEnabled() const {
+ return zero_hertz_screenshare_enabled_ && source_constraints_.has_value() &&
+ source_constraints_->max_fps.value_or(-1) > 0 &&
+ source_constraints_->min_fps.value_or(-1) == 0 &&
+ zero_hertz_and_uma_reporting_enabled_;
+}
+
+// RTC_RUN_ON(queue_)
+void FrameCadenceAdapterImpl::MaybeReconfigureAdapters(
+ bool was_zero_hertz_enabled) {
+ bool is_zero_hertz_enabled = IsZeroHertzScreenshareEnabled();
+ if (is_zero_hertz_enabled) {
+ if (!was_zero_hertz_enabled) {
+ zero_hertz_adapter_.emplace(callback_,
+ source_constraints_->max_fps.value());
+ }
+ current_adapter_mode_ = &zero_hertz_adapter_.value();
+ } else {
+ if (was_zero_hertz_enabled)
+ zero_hertz_adapter_ = absl::nullopt;
+ current_adapter_mode_ = &passthrough_adapter_.value();
+ }
}
// RTC_RUN_ON(queue_)
diff --git a/video/frame_cadence_adapter.h b/video/frame_cadence_adapter.h
index beb7396..8685f37 100644
--- a/video/frame_cadence_adapter.h
+++ b/video/frame_cadence_adapter.h
@@ -29,6 +29,10 @@
class FrameCadenceAdapterInterface
: public rtc::VideoSinkInterface<VideoFrame> {
public:
+ // Averaging window spanning 90 frames at default 30fps, matching old media
+ // optimization module defaults.
+ static constexpr int64_t kFrameRateAveragingWindowSizeMs = (1000 / 30) * 90;
+
// Callback interface used to inform instance owners.
class Callback {
public:
@@ -66,6 +70,14 @@
// Pass true in |enabled| as a prerequisite to enable zero-hertz operation.
virtual void SetZeroHertzModeEnabled(bool enabled) = 0;
+
+ // Returns the input framerate. This is measured by RateStatistics when
+ // zero-hertz mode is off, and returns the max framerate in zero-hertz mode.
+ virtual absl::optional<uint32_t> GetInputFrameRateFps() = 0;
+
+ // Updates frame rate. This is done unconditionally irrespective of adapter
+ // mode.
+ virtual void UpdateFrameRate() = 0;
};
} // namespace webrtc
diff --git a/video/frame_cadence_adapter_unittest.cc b/video/frame_cadence_adapter_unittest.cc
index a6b6a87..dae0b86 100644
--- a/video/frame_cadence_adapter_unittest.cc
+++ b/video/frame_cadence_adapter_unittest.cc
@@ -16,6 +16,7 @@
#include "api/task_queue/task_queue_base.h"
#include "api/video/nv12_buffer.h"
#include "api/video/video_frame.h"
+#include "rtc_base/rate_statistics.h"
#include "rtc_base/ref_counted_object.h"
#include "system_wrappers/include/metrics.h"
#include "test/field_trial.h"
@@ -56,6 +57,12 @@
: test::ScopedFieldTrials("WebRTC-ZeroHertzScreenshare/Disabled/") {}
};
+class ZeroHertzFieldTrialEnabler : public test::ScopedFieldTrials {
+ public:
+ ZeroHertzFieldTrialEnabler()
+ : test::ScopedFieldTrials("WebRTC-ZeroHertzScreenshare/Enabled/") {}
+};
+
TEST(FrameCadenceAdapterTest,
ForwardsFramesOnConstructionAndUnderDisabledFieldTrial) {
GlobalSimulatedTimeController time_controller(Timestamp::Millis(1));
@@ -93,6 +100,92 @@
time_controller.AdvanceTime(TimeDelta::Zero());
}
+TEST(FrameCadenceAdapterTest, FrameRateFollowsRateStatisticsByDefault) {
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(nullptr);
+
+ // Create an "oracle" rate statistics which should be followed on a sequence
+ // of frames.
+ RateStatistics rate(
+ FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000);
+
+ for (int frame = 0; frame != 10; ++frame) {
+ time_controller.AdvanceTime(TimeDelta::Millis(10));
+ rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
+ adapter->UpdateFrameRate();
+ EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()),
+ adapter->GetInputFrameRateFps())
+ << " failed for frame " << frame;
+ }
+}
+
+TEST(FrameCadenceAdapterTest,
+ FrameRateFollowsRateStatisticsWhenFeatureDisabled) {
+ ZeroHertzFieldTrialDisabler feature_disabler;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(nullptr);
+
+ // Create an "oracle" rate statistics which should be followed on a sequence
+ // of frames.
+ RateStatistics rate(
+ FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000);
+
+ for (int frame = 0; frame != 10; ++frame) {
+ time_controller.AdvanceTime(TimeDelta::Millis(10));
+ rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
+ adapter->UpdateFrameRate();
+ EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()),
+ adapter->GetInputFrameRateFps())
+ << " failed for frame " << frame;
+ }
+}
+
+TEST(FrameCadenceAdapterTest, FrameRateFollowsMaxFpsWhenZeroHertzActivated) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(nullptr);
+ adapter->SetZeroHertzModeEnabled(true);
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1});
+ for (int frame = 0; frame != 10; ++frame) {
+ time_controller.AdvanceTime(TimeDelta::Millis(10));
+ adapter->UpdateFrameRate();
+ EXPECT_EQ(adapter->GetInputFrameRateFps(), 1u);
+ }
+}
+
+TEST(FrameCadenceAdapterTest,
+ FrameRateFollowsRateStatisticsAfterZeroHertzDeactivated) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(nullptr);
+ adapter->SetZeroHertzModeEnabled(true);
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1});
+ RateStatistics rate(
+ FrameCadenceAdapterInterface::kFrameRateAveragingWindowSizeMs, 1000);
+ constexpr int MAX = 10;
+ for (int frame = 0; frame != MAX; ++frame) {
+ time_controller.AdvanceTime(TimeDelta::Millis(10));
+ rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
+ adapter->UpdateFrameRate();
+ }
+ // Turn off zero hertz on the next-last frame; after the last frame we
+ // should see a value that tracks the rate oracle.
+ adapter->SetZeroHertzModeEnabled(false);
+ // Last frame.
+ time_controller.AdvanceTime(TimeDelta::Millis(10));
+ rate.Update(1, time_controller.GetClock()->TimeInMilliseconds());
+ adapter->UpdateFrameRate();
+
+ EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()),
+ adapter->GetInputFrameRateFps());
+}
+
class FrameCadenceAdapterMetricsTest : public ::testing::Test {
public:
FrameCadenceAdapterMetricsTest() : time_controller_(Timestamp::Millis(1)) {
diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc
index 9fd8e69..640c230 100644
--- a/video/video_stream_encoder.cc
+++ b/video/video_stream_encoder.cc
@@ -64,10 +64,6 @@
constexpr char kFrameDropperFieldTrial[] = "WebRTC-FrameDropper";
-// Averaging window spanning 90 frames at default 30fps, matching old media
-// optimization module defaults.
-const int64_t kFrameRateAvergingWindowSizeMs = (1000 / 30) * 90;
-
const size_t kDefaultPayloadSize = 1440;
const int64_t kParameterUpdateIntervalMs = 1000;
@@ -633,7 +629,6 @@
expect_resize_state_(ExpectResizeState::kNoResize),
fec_controller_override_(nullptr),
force_disable_frame_dropper_(false),
- input_framerate_(kFrameRateAvergingWindowSizeMs, 1000),
pending_frame_drops_(0),
cwnd_frame_counter_(0),
next_frame_types_(1, VideoFrameType::kVideoFrameDelta),
@@ -1422,8 +1417,13 @@
uint32_t VideoStreamEncoder::GetInputFramerateFps() {
const uint32_t default_fps = max_framerate_ != -1 ? max_framerate_ : 30;
+
+ // This method may be called after we cleared out the frame_cadence_adapter_
+ // reference in Stop(). In such a situation it's probably not important with a
+ // decent estimate.
absl::optional<uint32_t> input_fps =
- input_framerate_.Rate(clock_->TimeInMilliseconds());
+ frame_cadence_adapter_ ? frame_cadence_adapter_->GetInputFrameRateFps()
+ : absl::nullopt;
if (!input_fps || *input_fps == 0) {
return default_fps;
}
@@ -1525,7 +1525,7 @@
// Poll the rate before updating, otherwise we risk the rate being estimated
// a little too high at the start of the call when then window is small.
uint32_t framerate_fps = GetInputFramerateFps();
- input_framerate_.Update(1u, clock_->TimeInMilliseconds());
+ frame_cadence_adapter_->UpdateFrameRate();
int64_t now_ms = clock_->TimeInMilliseconds();
if (pending_encoder_reconfiguration_) {
diff --git a/video/video_stream_encoder.h b/video/video_stream_encoder.h
index 5194ff3..cd181fc 100644
--- a/video/video_stream_encoder.h
+++ b/video/video_stream_encoder.h
@@ -339,7 +339,6 @@
// trusted rate controller. This is determined on a per-frame basis, as the
// encoder behavior might dynamically change.
bool force_disable_frame_dropper_ RTC_GUARDED_BY(&encoder_queue_);
- RateStatistics input_framerate_ RTC_GUARDED_BY(&encoder_queue_);
// Incremented on worker thread whenever `frame_dropper_` determines that a
// frame should be dropped. Decremented on whichever thread runs
// OnEncodedImage(), which is only called by one thread but not necessarily
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