diff --git a/video/BUILD.gn b/video/BUILD.gn index 84b2d98f19..99e82c1f75 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -273,6 +273,7 @@ rtc_library("frame_cadence_adapter") { "../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 c82ab5a445..c46790992c 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 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 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 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 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface { // FrameCadenceAdapterInterface overrides. void Initialize(Callback* callback) override; void SetZeroHertzModeEnabled(bool enabled) override; + absl::optional GetInputFrameRateFps() override; + void UpdateFrameRate() override; // VideoFrameSink overrides. void OnFrame(const VideoFrame& frame) override; @@ -48,8 +126,18 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface { 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 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface { // 0 Hz. const bool zero_hertz_screenshare_enabled_; + // The two possible modes we're under. + absl::optional passthrough_adapter_; + absl::optional 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 @@ class FrameCadenceAdapterImpl : public FrameCadenceAdapterInterface { 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 ZeroHertzAdapterMode::GetInputFrameRateFps() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + return max_fps_; +} + FrameCadenceAdapterImpl::FrameCadenceAdapterImpl(Clock* clock, TaskQueueBase* queue) : clock_(clock), @@ -89,13 +203,30 @@ FrameCadenceAdapterImpl::FrameCadenceAdapterImpl(Clock* clock, 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 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 @@ void FrameCadenceAdapterImpl::OnConstraintsChanged( << 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 @@ void FrameCadenceAdapterImpl::OnFrameOnMainQueue( 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 beb73963ea..8685f37f97 100644 --- a/video/frame_cadence_adapter.h +++ b/video/frame_cadence_adapter.h @@ -29,6 +29,10 @@ namespace webrtc { class FrameCadenceAdapterInterface : public rtc::VideoSinkInterface { 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 @@ class FrameCadenceAdapterInterface // 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 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 a6b6a87f0e..dae0b864a8 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 @@ class ZeroHertzFieldTrialDisabler : public test::ScopedFieldTrials { : 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 @@ TEST(FrameCadenceAdapterTest, CountsOutstandingFramesToProcess) { 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 9fd8e69ec8..640c230c68 100644 --- a/video/video_stream_encoder.cc +++ b/video/video_stream_encoder.cc @@ -64,10 +64,6 @@ const int64_t kPendingFrameTimeoutMs = 1000; 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 @@ VideoStreamEncoder::VideoStreamEncoder( 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 @@ VideoStreamEncoder::UpdateBitrateAllocation( 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 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 @@ void VideoStreamEncoder::MaybeEncodeVideoFrame(const VideoFrame& video_frame, // 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 5194ff3397..cd181fc6fa 100644 --- a/video/video_stream_encoder.h +++ b/video/video_stream_encoder.h @@ -339,7 +339,6 @@ class VideoStreamEncoder : public VideoStreamEncoderInterface, // 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 32d4f94e8c..c5ff292a00 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -76,6 +76,7 @@ using ::testing::Eq; 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 @@ class SimpleVideoStreamEncoderFactory { ~AdaptedVideoStreamEncoder() { Stop(); } }; - SimpleVideoStreamEncoderFactory() - : time_controller_(Timestamp::Millis(0)), - task_queue_factory_(time_controller_.CreateTaskQueueFactory()), - stats_proxy_(std::make_unique( - 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 Create( - std::unique_ptr zero_hertz_adapter) { + std::unique_ptr 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( time_controller_.GetClock(), /*number_of_cores=*/1, /*stats_proxy=*/stats_proxy_.get(), encoder_settings_, std::make_unique(/*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 @@ class SimpleVideoStreamEncoderFactory { } }; - GlobalSimulatedTimeController time_controller_; - std::unique_ptr task_queue_factory_; - std::unique_ptr stats_proxy_; - VideoStreamEncoderSettings encoder_settings_; - test::FakeEncoder fake_encoder_; - test::VideoEncoderProxyFactory encoder_factory_; + GlobalSimulatedTimeController time_controller_{Timestamp::Millis(0)}; + std::unique_ptr task_queue_factory_{ + time_controller_.CreateTaskQueueFactory()}; + std::unique_ptr stats_proxy_ = + std::make_unique( + time_controller_.GetClock(), + VideoSendStream::Config(nullptr), + webrtc::VideoEncoderConfig::ContentType::kRealtimeVideo); + std::unique_ptr 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 @@ class MockFrameCadenceAdapter : public FrameCadenceAdapterInterface { MOCK_METHOD(void, Initialize, (Callback * callback), (override)); MOCK_METHOD(void, SetZeroHertzModeEnabled, (bool), (override)); MOCK_METHOD(void, OnFrame, (const VideoFrame&), (override)); + MOCK_METHOD(absl::optional, GetInputFrameRateFps, (), (override)); + MOCK_METHOD(void, UpdateFrameRate, (), (override)); }; class MockEncoderSelector @@ -8746,4 +8753,46 @@ TEST(VideoStreamEncoderFrameCadenceTest, .build()); } +TEST(VideoStreamEncoderFrameCadenceTest, UsesFrameCadenceAdapterForFrameRate) { + auto adapter = std::make_unique(); + 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(/*width=*/16, /*height=*/16)) + .set_ntp_time_ms(0) + .set_timestamp_ms(0) + .set_rotation(kVideoRotation_0) + .build()); + })); + factory.DepleteTaskQueues(); +} + } // namespace webrtc