/* * Copyright (c) 2021 The WebRTC 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 "video/frame_cadence_adapter.h" #include #include #include "absl/functional/any_invocable.h" #include "api/task_queue/default_task_queue_factory.h" #include "api/task_queue/task_queue_base.h" #include "api/task_queue/task_queue_factory.h" #include "api/units/time_delta.h" #include "api/units/timestamp.h" #include "api/video/nv12_buffer.h" #include "api/video/video_frame.h" #include "rtc_base/event.h" #include "rtc_base/rate_statistics.h" #include "rtc_base/time_utils.h" #include "system_wrappers/include/metrics.h" #include "system_wrappers/include/ntp_time.h" #include "system_wrappers/include/sleep.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/scoped_key_value_config.h" #include "test/time_controller/simulated_time_controller.h" namespace webrtc { namespace { using ::testing::_; using ::testing::ElementsAre; using ::testing::Invoke; using ::testing::Mock; using ::testing::Pair; using ::testing::Values; VideoFrame CreateFrame() { return VideoFrame::Builder() .set_video_frame_buffer( rtc::make_ref_counted(/*width=*/16, /*height=*/16)) .build(); } VideoFrame CreateFrameWithTimestamps( GlobalSimulatedTimeController* time_controller) { return VideoFrame::Builder() .set_video_frame_buffer( rtc::make_ref_counted(/*width=*/16, /*height=*/16)) .set_ntp_time_ms(time_controller->GetClock()->CurrentNtpInMilliseconds()) .set_timestamp_us(time_controller->GetClock()->CurrentTime().us()) .build(); } std::unique_ptr CreateAdapter( const FieldTrialsView& field_trials, Clock* clock) { return FrameCadenceAdapterInterface::Create(clock, TaskQueueBase::Current(), field_trials); } class MockCallback : public FrameCadenceAdapterInterface::Callback { public: MOCK_METHOD(void, OnFrame, (Timestamp, int, const VideoFrame&), (override)); MOCK_METHOD(void, OnDiscardedFrame, (), (override)); MOCK_METHOD(void, RequestRefreshFrame, (), (override)); }; class ZeroHertzFieldTrialDisabler : public test::ScopedKeyValueConfig { public: ZeroHertzFieldTrialDisabler() : test::ScopedKeyValueConfig("WebRTC-ZeroHertzScreenshare/Disabled/") {} }; class ZeroHertzFieldTrialEnabler : public test::ScopedKeyValueConfig { public: ZeroHertzFieldTrialEnabler() : test::ScopedKeyValueConfig("WebRTC-ZeroHertzScreenshare/Enabled/") {} }; TEST(FrameCadenceAdapterTest, ForwardsFramesOnConstructionAndUnderDisabledFieldTrial) { GlobalSimulatedTimeController time_controller(Timestamp::Millis(1)); ZeroHertzFieldTrialDisabler disabled_field_trials; test::ScopedKeyValueConfig no_field_trials; for (int i = 0; i != 2; i++) { MockCallback callback; auto adapter = CreateAdapter(i == 0 ? disabled_field_trials : no_field_trials, time_controller.GetClock()); adapter->Initialize(&callback); VideoFrame frame = CreateFrame(); EXPECT_CALL(callback, OnFrame).Times(1); adapter->OnFrame(frame); time_controller.AdvanceTime(TimeDelta::Zero()); Mock::VerifyAndClearExpectations(&callback); EXPECT_CALL(callback, OnDiscardedFrame).Times(1); adapter->OnDiscardedFrame(); Mock::VerifyAndClearExpectations(&callback); } } TEST(FrameCadenceAdapterTest, CountsOutstandingFramesToProcess) { test::ScopedKeyValueConfig no_field_trials; GlobalSimulatedTimeController time_controller(Timestamp::Millis(1)); MockCallback callback; auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock()); adapter->Initialize(&callback); EXPECT_CALL(callback, OnFrame(_, 2, _)).Times(1); EXPECT_CALL(callback, OnFrame(_, 1, _)).Times(1); auto frame = CreateFrame(); adapter->OnFrame(frame); adapter->OnFrame(frame); time_controller.AdvanceTime(TimeDelta::Zero()); EXPECT_CALL(callback, OnFrame(_, 1, _)).Times(1); adapter->OnFrame(frame); time_controller.AdvanceTime(TimeDelta::Zero()); } TEST(FrameCadenceAdapterTest, FrameRateFollowsRateStatisticsByDefault) { test::ScopedKeyValueConfig no_field_trials; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(no_field_trials, 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::Zero()); auto adapter = CreateAdapter(feature_disabler, 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; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(nullptr); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); 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; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(nullptr); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); 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(absl::nullopt); // 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()); } TEST(FrameCadenceAdapterTest, ForwardsFramesDelayed) { ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); constexpr int kNumFrames = 3; NtpTime original_ntp_time = time_controller.GetClock()->CurrentNtpTime(); auto frame = CreateFrameWithTimestamps(&time_controller); int64_t original_timestamp_us = frame.timestamp_us(); for (int index = 0; index != kNumFrames; ++index) { EXPECT_CALL(callback, OnFrame).Times(0); adapter->OnFrame(frame); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp post_time, int, const VideoFrame& frame) { EXPECT_EQ(post_time, time_controller.GetClock()->CurrentTime()); EXPECT_EQ(frame.timestamp_us(), original_timestamp_us + index * rtc::kNumMicrosecsPerSec); EXPECT_EQ(frame.ntp_time_ms(), original_ntp_time.ToMs() + index * rtc::kNumMillisecsPerSec); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); frame = CreateFrameWithTimestamps(&time_controller); } } TEST(FrameCadenceAdapterTest, RepeatsFramesDelayed) { // Logic in the frame cadence adapter avoids modifying frame NTP and render // timestamps if these timestamps looks unset, which is the case when the // clock is initialized running from 0. For this reason we choose the // `time_controller` initialization constant to something arbitrary which is // not 0. ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Millis(47892223)); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); NtpTime original_ntp_time = time_controller.GetClock()->CurrentNtpTime(); // Send one frame, expect 2 subsequent repeats. auto frame = CreateFrameWithTimestamps(&time_controller); int64_t original_timestamp_us = frame.timestamp_us(); adapter->OnFrame(frame); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp post_time, int, const VideoFrame& frame) { EXPECT_EQ(post_time, time_controller.GetClock()->CurrentTime()); EXPECT_EQ(frame.timestamp_us(), original_timestamp_us); EXPECT_EQ(frame.ntp_time_ms(), original_ntp_time.ToMs()); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp post_time, int, const VideoFrame& frame) { EXPECT_EQ(post_time, time_controller.GetClock()->CurrentTime()); EXPECT_EQ(frame.timestamp_us(), original_timestamp_us + rtc::kNumMicrosecsPerSec); EXPECT_EQ(frame.ntp_time_ms(), original_ntp_time.ToMs() + rtc::kNumMillisecsPerSec); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp post_time, int, const VideoFrame& frame) { EXPECT_EQ(post_time, time_controller.GetClock()->CurrentTime()); EXPECT_EQ(frame.timestamp_us(), original_timestamp_us + 2 * rtc::kNumMicrosecsPerSec); EXPECT_EQ(frame.ntp_time_ms(), original_ntp_time.ToMs() + 2 * rtc::kNumMillisecsPerSec); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); } TEST(FrameCadenceAdapterTest, RepeatsFramesWithoutTimestampsWithUnsetTimestamps) { // Logic in the frame cadence adapter avoids modifying frame NTP and render // timestamps if these timestamps looks unset, which is the case when the // clock is initialized running from 0. In this test we deliberately don't set // it to zero, but select unset timestamps in the frames (via CreateFrame()) // and verify that the timestamp modifying logic doesn't depend on the current // time. ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Millis(4711)); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); // Send one frame, expect a repeat. adapter->OnFrame(CreateFrame()); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp post_time, int, const VideoFrame& frame) { EXPECT_EQ(post_time, time_controller.GetClock()->CurrentTime()); EXPECT_EQ(frame.timestamp_us(), 0); EXPECT_EQ(frame.ntp_time_ms(), 0); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp post_time, int, const VideoFrame& frame) { EXPECT_EQ(post_time, time_controller.GetClock()->CurrentTime()); EXPECT_EQ(frame.timestamp_us(), 0); EXPECT_EQ(frame.ntp_time_ms(), 0); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); } TEST(FrameCadenceAdapterTest, StopsRepeatingFramesDelayed) { // At 1s, the initially scheduled frame appears. // At 2s, the repeated initial frame appears. // At 2.5s, we schedule another new frame. // At 3.5s, we receive this frame. ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); NtpTime original_ntp_time = time_controller.GetClock()->CurrentNtpTime(); // Send one frame, expect 1 subsequent repeat. adapter->OnFrame(CreateFrameWithTimestamps(&time_controller)); EXPECT_CALL(callback, OnFrame).Times(2); time_controller.AdvanceTime(TimeDelta::Seconds(2.5)); Mock::VerifyAndClearExpectations(&callback); // Send the new frame at 2.5s, which should appear after 3.5s. adapter->OnFrame(CreateFrameWithTimestamps(&time_controller)); EXPECT_CALL(callback, OnFrame) .WillOnce(Invoke([&](Timestamp, int, const VideoFrame& frame) { EXPECT_EQ(frame.timestamp_us(), 5 * rtc::kNumMicrosecsPerSec / 2); EXPECT_EQ(frame.ntp_time_ms(), original_ntp_time.ToMs() + 5u * rtc::kNumMillisecsPerSec / 2); })); time_controller.AdvanceTime(TimeDelta::Seconds(1)); } TEST(FrameCadenceAdapterTest, RequestsRefreshFrameOnKeyFrameRequestWhenNew) { ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); constexpr int kMaxFps = 10; adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps}); EXPECT_CALL(callback, RequestRefreshFrame); time_controller.AdvanceTime( TimeDelta::Seconds(1) * FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod / kMaxFps); adapter->ProcessKeyFrameRequest(); } TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestShortlyAfterFrame) { ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 10}); adapter->OnFrame(CreateFrame()); time_controller.AdvanceTime(TimeDelta::Zero()); EXPECT_CALL(callback, RequestRefreshFrame).Times(0); adapter->ProcessKeyFrameRequest(); } TEST(FrameCadenceAdapterTest, RequestsRefreshFramesUntilArrival) { ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); constexpr int kMaxFps = 10; adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps}); // We should see max_fps + 1 - // FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod refresh // frame requests during the one second we wait until we send a single frame, // after which refresh frame requests should cease (we should see no such // requests during a second). EXPECT_CALL(callback, RequestRefreshFrame) .Times(kMaxFps + 1 - FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); adapter->OnFrame(CreateFrame()); EXPECT_CALL(callback, RequestRefreshFrame).Times(0); time_controller.AdvanceTime(TimeDelta::Seconds(1)); } TEST(FrameCadenceAdapterTest, RequestsRefreshAfterFrameDrop) { ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); constexpr int kMaxFps = 10; adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps}); EXPECT_CALL(callback, RequestRefreshFrame).Times(0); // Send a frame through to cancel the initial delayed timer waiting for first // frame entry. adapter->OnFrame(CreateFrame()); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); // Send a dropped frame indication without any following frames received. // After FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod // frame periods, we should receive a first refresh request. adapter->OnDiscardedFrame(); EXPECT_CALL(callback, RequestRefreshFrame); time_controller.AdvanceTime( TimeDelta::Seconds(1) * FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod / kMaxFps); Mock::VerifyAndClearExpectations(&callback); // We will now receive a refresh frame request for every frame period. EXPECT_CALL(callback, RequestRefreshFrame).Times(kMaxFps); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); // After a frame is passed the requests will cease. EXPECT_CALL(callback, RequestRefreshFrame).Times(0); adapter->OnFrame(CreateFrame()); time_controller.AdvanceTime(TimeDelta::Seconds(1)); } TEST(FrameCadenceAdapterTest, OmitsRefreshAfterFrameDropWithTimelyFrameEntry) { ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); constexpr int kMaxFps = 10; adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps}); // Send a frame through to cancel the initial delayed timer waiting for first // frame entry. EXPECT_CALL(callback, RequestRefreshFrame).Times(0); adapter->OnFrame(CreateFrame()); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); // Send a frame drop indication. No refresh frames should be requested // until FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod // intervals pass. Stop short of this. EXPECT_CALL(callback, RequestRefreshFrame).Times(0); adapter->OnDiscardedFrame(); time_controller.AdvanceTime( TimeDelta::Seconds(1) * FrameCadenceAdapterInterface::kOnDiscardedFrameRefreshFramePeriod / kMaxFps - TimeDelta::Micros(1)); Mock::VerifyAndClearExpectations(&callback); // Send a frame. The timer to request the refresh frame should be cancelled by // the reception, so no refreshes should be requested. EXPECT_CALL(callback, RequestRefreshFrame).Times(0); adapter->OnFrame(CreateFrame()); time_controller.AdvanceTime(TimeDelta::Seconds(1)); Mock::VerifyAndClearExpectations(&callback); } TEST(FrameCadenceAdapterTest, AcceptsUnconfiguredLayerFeedback) { // This is a regression test for bugs.webrtc.org/14417. ZeroHertzFieldTrialEnabler enabler; MockCallback callback; GlobalSimulatedTimeController time_controller(Timestamp::Zero()); auto adapter = CreateAdapter(enabler, time_controller.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{.num_simulcast_layers = 1}); constexpr int kMaxFps = 10; adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps}); time_controller.AdvanceTime(TimeDelta::Zero()); adapter->UpdateLayerQualityConvergence(2, false); adapter->UpdateLayerStatus(2, false); } class FrameCadenceAdapterSimulcastLayersParamTest : public ::testing::TestWithParam { public: static constexpr int kMaxFpsHz = 8; static constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(1000 / kMaxFpsHz); static constexpr TimeDelta kIdleFrameDelay = FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod; FrameCadenceAdapterSimulcastLayersParamTest() { adapter_->Initialize(&callback_); adapter_->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz}); time_controller_.AdvanceTime(TimeDelta::Zero()); adapter_->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); const size_t num_spatial_layers = GetParam(); adapter_->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{num_spatial_layers}); } int NumSpatialLayers() const { return GetParam(); } protected: ZeroHertzFieldTrialEnabler enabler_; MockCallback callback_; GlobalSimulatedTimeController time_controller_{Timestamp::Zero()}; const std::unique_ptr adapter_{ CreateAdapter(enabler_, time_controller_.GetClock())}; }; TEST_P(FrameCadenceAdapterSimulcastLayersParamTest, LayerReconfigurationResetsConvergenceInfo) { // Assumes layer reconfiguration has just happened. // Verify the state is unconverged. adapter_->OnFrame(CreateFrame()); EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz); time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay); } TEST_P(FrameCadenceAdapterSimulcastLayersParamTest, IgnoresKeyFrameRequestWhileShortRepeating) { // Plot: // 1. 0 * kMinFrameDelay: Start unconverged. Frame -> adapter. // 2. 1 * kMinFrameDelay: Frame -> callback. // 3. 2 * kMinFrameDelay: 1st short repeat. // Since we're unconverged we assume the process continues. adapter_->OnFrame(CreateFrame()); time_controller_.AdvanceTime(2 * kMinFrameDelay); EXPECT_CALL(callback_, RequestRefreshFrame).Times(0); adapter_->ProcessKeyFrameRequest(); // Expect short repeating as ususal. EXPECT_CALL(callback_, OnFrame).Times(8); time_controller_.AdvanceTime(8 * kMinFrameDelay); } TEST_P(FrameCadenceAdapterSimulcastLayersParamTest, IgnoresKeyFrameRequestJustBeforeIdleRepeating) { // (Only for > 0 spatial layers as we assume not converged with 0 layers) if (NumSpatialLayers() == 0) return; // Plot: // 1. 0 * kMinFrameDelay: Start converged. Frame -> adapter. // 2. 1 * kMinFrameDelay: Frame -> callback. New repeat scheduled at // (kMaxFpsHz + 1) * kMinFrameDelay. // 3. kMaxFpsHz * kMinFrameDelay: Process keyframe. // 4. (kMaxFpsHz + N) * kMinFrameDelay (1 <= N <= kMaxFpsHz): Short repeats // due to not converged. for (int i = 0; i != NumSpatialLayers(); i++) { adapter_->UpdateLayerStatus(i, /*enabled=*/true); adapter_->UpdateLayerQualityConvergence(i, /*converged=*/true); } adapter_->OnFrame(CreateFrame()); time_controller_.AdvanceTime(kIdleFrameDelay); // We process the key frame request kMinFrameDelay before the first idle // repeat should happen. The resulting repeats should happen spaced by // kMinFrameDelay before we get new convergence info. EXPECT_CALL(callback_, RequestRefreshFrame).Times(0); adapter_->ProcessKeyFrameRequest(); EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz); time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay); } TEST_P(FrameCadenceAdapterSimulcastLayersParamTest, IgnoresKeyFrameRequestShortRepeatsBeforeIdleRepeat) { // (Only for > 0 spatial layers as we assume not converged with 0 layers) if (NumSpatialLayers() == 0) return; // Plot: // 1. 0 * kMinFrameDelay: Start converged. Frame -> adapter. // 2. 1 * kMinFrameDelay: Frame -> callback. New repeat scheduled at // (kMaxFpsHz + 1) * kMinFrameDelay. // 3. 2 * kMinFrameDelay: Process keyframe. // 4. (2 + N) * kMinFrameDelay (1 <= N <= kMaxFpsHz): Short repeats due to not // converged. for (int i = 0; i != NumSpatialLayers(); i++) { adapter_->UpdateLayerStatus(i, /*enabled=*/true); adapter_->UpdateLayerQualityConvergence(i, /*converged=*/true); } adapter_->OnFrame(CreateFrame()); time_controller_.AdvanceTime(2 * kMinFrameDelay); // We process the key frame request (kMaxFpsHz - 1) * kMinFrameDelay before // the first idle repeat should happen. The resulting repeats should happen // spaced kMinFrameDelay before we get new convergence info. EXPECT_CALL(callback_, RequestRefreshFrame).Times(0); adapter_->ProcessKeyFrameRequest(); EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz); time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay); } INSTANTIATE_TEST_SUITE_P(, FrameCadenceAdapterSimulcastLayersParamTest, Values(0, 1, 2)); class ZeroHertzLayerQualityConvergenceTest : public ::testing::Test { public: static constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(100); static constexpr TimeDelta kIdleFrameDelay = FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod; ZeroHertzLayerQualityConvergenceTest() { adapter_->Initialize(&callback_); adapter_->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{ /*num_simulcast_layers=*/2}); adapter_->OnConstraintsChanged(VideoTrackSourceConstraints{ /*min_fps=*/0, /*max_fps=*/TimeDelta::Seconds(1) / kMinFrameDelay}); time_controller_.AdvanceTime(TimeDelta::Zero()); } void PassFrame() { adapter_->OnFrame(CreateFrame()); } void ExpectFrameEntriesAtDelaysFromNow( std::initializer_list list) { Timestamp origin = time_controller_.GetClock()->CurrentTime(); for (auto delay : list) { EXPECT_CALL(callback_, OnFrame(origin + delay, _, _)); time_controller_.AdvanceTime(origin + delay - time_controller_.GetClock()->CurrentTime()); } } void ScheduleDelayed(TimeDelta delay, absl::AnyInvocable task) { TaskQueueBase::Current()->PostDelayedTask(std::move(task), delay); } protected: ZeroHertzFieldTrialEnabler field_trial_enabler_; MockCallback callback_; GlobalSimulatedTimeController time_controller_{Timestamp::Zero()}; std::unique_ptr adapter_{ CreateAdapter(field_trial_enabler_, time_controller_.GetClock())}; }; TEST_F(ZeroHertzLayerQualityConvergenceTest, InitialStateUnconverged) { // As the layer count is just configured, assume we start out as unconverged. PassFrame(); ExpectFrameEntriesAtDelaysFromNow({ 1 * kMinFrameDelay, // Original frame emitted 2 * kMinFrameDelay, // Short repeats. 3 * kMinFrameDelay, // ... }); } TEST_F(ZeroHertzLayerQualityConvergenceTest, UnconvergedAfterLayersEnabled) { // With newly enabled layers we assume quality is unconverged. adapter_->UpdateLayerStatus(0, /*enabled=*/true); adapter_->UpdateLayerStatus(1, /*enabled=*/true); PassFrame(); ExpectFrameEntriesAtDelaysFromNow({ kMinFrameDelay, // Original frame emitted 2 * kMinFrameDelay, // Unconverged repeats. 3 * kMinFrameDelay, // ... }); } TEST_F(ZeroHertzLayerQualityConvergenceTest, RepeatsPassedFramesUntilConvergence) { ScheduleDelayed(TimeDelta::Zero(), [&] { adapter_->UpdateLayerStatus(0, /*enabled=*/true); adapter_->UpdateLayerStatus(1, /*enabled=*/true); PassFrame(); }); ScheduleDelayed(2.5 * kMinFrameDelay, [&] { adapter_->UpdateLayerQualityConvergence(/*spatial_index=*/1, true); }); ScheduleDelayed(3.5 * kMinFrameDelay, [&] { adapter_->UpdateLayerQualityConvergence(/*spatial_index=*/0, true); }); ScheduleDelayed(8 * kMinFrameDelay, [&] { PassFrame(); }); ScheduleDelayed(9.5 * kMinFrameDelay, [&] { adapter_->UpdateLayerQualityConvergence(/*spatial_index=*/0, true); }); ScheduleDelayed(10.5 * kMinFrameDelay, [&] { adapter_->UpdateLayerQualityConvergence(/*spatial_index=*/1, true); }); ExpectFrameEntriesAtDelaysFromNow({ kMinFrameDelay, // Original frame emitted 2 * kMinFrameDelay, // Repeat from kMinFrameDelay. // 2.5 * kMinFrameDelay: Converged in layer 1, layer 0 still unconverged. 3 * kMinFrameDelay, // Repeat from 2 * kMinFrameDelay. // 3.5 * kMinFrameDelay: Converged in layer 0 as well. 4 * kMinFrameDelay, // Repeat from 3 * kMinFrameDelay. An idle repeat is // scheduled for kIdleFrameDelay + 3 * // kMinFrameDelay. // A new frame is passed at 8 * kMinFrameDelay. 9 * kMinFrameDelay, // Original frame emitted // 9.5 * kMinFrameDelay: Converged in layer 0, layer 1 still unconverged. 10 * kMinFrameDelay, // Repeat from 9 * kMinFrameDelay. // 10.5 * kMinFrameDelay: Converged in layer 0 as well. 11 * kMinFrameDelay, // Idle repeats from 1000. 11 * kMinFrameDelay + kIdleFrameDelay, // ... 11 * kMinFrameDelay + 2 * kIdleFrameDelay, // ... // ... }); } class FrameCadenceAdapterMetricsTest : public ::testing::Test { public: FrameCadenceAdapterMetricsTest() : time_controller_(Timestamp::Millis(1)) { metrics::Reset(); } void DepleteTaskQueues() { time_controller_.AdvanceTime(TimeDelta::Zero()); } protected: GlobalSimulatedTimeController time_controller_; }; TEST_F(FrameCadenceAdapterMetricsTest, RecordsNoUmasWithNoFrameTransfer) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, nullptr); adapter->Initialize(&callback); adapter->OnConstraintsChanged( VideoTrackSourceConstraints{absl::nullopt, absl::nullopt}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{absl::nullopt, 1}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{2, 3}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{4, 4}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{5, absl::nullopt}); DepleteTaskQueues(); EXPECT_TRUE(metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Exists") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Exists") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Value") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Exists") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Value") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max") .empty()); EXPECT_TRUE( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne") .empty()); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsNoUmasWithoutEnabledContentType) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->OnFrame(CreateFrame()); adapter->OnConstraintsChanged( VideoTrackSourceConstraints{absl::nullopt, absl::nullopt}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{absl::nullopt, 1}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{2, 3}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{4, 4}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{5, absl::nullopt}); DepleteTaskQueues(); EXPECT_TRUE(metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Exists") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Exists") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Value") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Exists") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Value") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max") .empty()); EXPECT_TRUE( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne") .empty()); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsNoConstraintsIfUnsetOnFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Exists"), ElementsAre(Pair(false, 1))); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsEmptyConstraintsIfSetOnFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged( VideoTrackSourceConstraints{absl::nullopt, absl::nullopt}); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Exists"), ElementsAre(Pair(true, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Exists"), ElementsAre(Pair(false, 1))); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Value") .empty()); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Exists"), ElementsAre(Pair(false, 1))); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Value") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max") .empty()); EXPECT_TRUE( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne") .empty()); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsMaxConstraintIfSetOnFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged( VideoTrackSourceConstraints{absl::nullopt, 2.0}); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Exists"), ElementsAre(Pair(false, 1))); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Value") .empty()); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Exists"), ElementsAre(Pair(true, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Value"), ElementsAre(Pair(2.0, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max"), ElementsAre(Pair(2.0, 1))); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max") .empty()); EXPECT_TRUE( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne") .empty()); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsMinConstraintIfSetOnFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged( VideoTrackSourceConstraints{3.0, absl::nullopt}); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Exists"), ElementsAre(Pair(true, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Value"), ElementsAre(Pair(3.0, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Exists"), ElementsAre(Pair(false, 1))); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Value") .empty()); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max") .empty()); EXPECT_TRUE( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne") .empty()); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsMinGtMaxConstraintIfSetOnFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{5.0, 4.0}); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Exists"), ElementsAre(Pair(true, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Min.Value"), ElementsAre(Pair(5.0, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Exists"), ElementsAre(Pair(true, 1))); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.Max.Value"), ElementsAre(Pair(4.0, 1))); EXPECT_TRUE( metrics::Samples("WebRTC.Screenshare.FrameRateConstraints.MinUnset.Max") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min") .empty()); EXPECT_TRUE(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max") .empty()); EXPECT_THAT( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne"), ElementsAre(Pair(60 * 5.0 + 4.0 - 1, 1))); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsMinLtMaxConstraintIfSetOnFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{4.0, 5.0}); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Min"), ElementsAre(Pair(4.0, 1))); EXPECT_THAT(metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.MinLessThanMax.Max"), ElementsAre(Pair(5.0, 1))); EXPECT_THAT( metrics::Samples( "WebRTC.Screenshare.FrameRateConstraints.60MinPlusMaxMinusOne"), ElementsAre(Pair(60 * 4.0 + 5.0 - 1, 1))); } TEST_F(FrameCadenceAdapterMetricsTest, RecordsTimeUntilFirstFrame) { MockCallback callback; test::ScopedKeyValueConfig no_field_trials; auto adapter = CreateAdapter(no_field_trials, time_controller_.GetClock()); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 5.0}); time_controller_.AdvanceTime(TimeDelta::Millis(666)); adapter->OnFrame(CreateFrame()); DepleteTaskQueues(); EXPECT_THAT( metrics::Samples("WebRTC.Screenshare.ZeroHz.TimeUntilFirstFrameMs"), ElementsAre(Pair(666, 1))); } TEST(FrameCadenceAdapterRealTimeTest, TimestampsDoNotDrift) { // This regression test must be performed in realtime because of limitations // in GlobalSimulatedTimeController. // // We sleep for a long while in OnFrame when a repeat was scheduled which // should reflect in accordingly increased ntp_time_ms() and timestamp_us() in // the repeated frames. auto factory = CreateDefaultTaskQueueFactory(); auto queue = factory->CreateTaskQueue("test", TaskQueueFactory::Priority::NORMAL); ZeroHertzFieldTrialEnabler enabler; MockCallback callback; Clock* clock = Clock::GetRealTimeClock(); std::unique_ptr adapter; int frame_counter = 0; int64_t original_ntp_time_ms; int64_t original_timestamp_us; rtc::Event event; queue->PostTask([&] { adapter = CreateAdapter(enabler, clock); adapter->Initialize(&callback); adapter->SetZeroHertzModeEnabled( FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 30}); auto frame = CreateFrame(); original_ntp_time_ms = clock->CurrentNtpInMilliseconds(); frame.set_ntp_time_ms(original_ntp_time_ms); original_timestamp_us = clock->CurrentTime().us(); frame.set_timestamp_us(original_timestamp_us); constexpr int kSleepMs = rtc::kNumMillisecsPerSec / 2; EXPECT_CALL(callback, OnFrame) .WillRepeatedly( Invoke([&](Timestamp, int, const VideoFrame& incoming_frame) { ++frame_counter; // Avoid the first OnFrame and sleep on the second. if (frame_counter == 2) { SleepMs(kSleepMs); } else if (frame_counter == 3) { EXPECT_GE(incoming_frame.ntp_time_ms(), original_ntp_time_ms + kSleepMs); EXPECT_GE(incoming_frame.timestamp_us(), original_timestamp_us + kSleepMs); event.Set(); } })); adapter->OnFrame(frame); }); event.Wait(rtc::Event::kForever); rtc::Event finalized; queue->PostTask([&] { adapter = nullptr; finalized.Set(); }); finalized.Wait(rtc::Event::kForever); } } // namespace } // namespace webrtc