|  | /* | 
|  | *  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 <cstdint> | 
|  | #include <memory> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #include "absl/functional/any_invocable.h" | 
|  | #include "api/metronome/test/fake_metronome.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/logging.h" | 
|  | #include "rtc_base/rate_statistics.h" | 
|  | #include "rtc_base/task_queue_for_test.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::InSequence; | 
|  | using ::testing::Invoke; | 
|  | using ::testing::InvokeWithoutArgs; | 
|  | using ::testing::Mock; | 
|  | using ::testing::NiceMock; | 
|  | using ::testing::Pair; | 
|  | using ::testing::Values; | 
|  |  | 
|  | VideoFrame CreateFrame() { | 
|  | return VideoFrame::Builder() | 
|  | .set_video_frame_buffer( | 
|  | rtc::make_ref_counted<NV12Buffer>(/*width=*/16, /*height=*/16)) | 
|  | .build(); | 
|  | } | 
|  |  | 
|  | VideoFrame CreateFrameWithTimestamps( | 
|  | GlobalSimulatedTimeController* time_controller) { | 
|  | return VideoFrame::Builder() | 
|  | .set_video_frame_buffer( | 
|  | rtc::make_ref_counted<NV12Buffer>(/*width=*/16, /*height=*/16)) | 
|  | .set_ntp_time_ms(time_controller->GetClock()->CurrentNtpInMilliseconds()) | 
|  | .set_timestamp_us(time_controller->GetClock()->CurrentTime().us()) | 
|  | .build(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<FrameCadenceAdapterInterface> CreateAdapter( | 
|  | const FieldTrialsView& field_trials, | 
|  | Clock* clock) { | 
|  | return FrameCadenceAdapterInterface::Create( | 
|  | clock, TaskQueueBase::Current(), /*metronome=*/nullptr, | 
|  | /*worker_queue=*/nullptr, field_trials); | 
|  | } | 
|  |  | 
|  | class MockCallback : public FrameCadenceAdapterInterface::Callback { | 
|  | public: | 
|  | MOCK_METHOD(void, OnFrame, (Timestamp, bool, const VideoFrame&), (override)); | 
|  | MOCK_METHOD(void, OnDiscardedFrame, (), (override)); | 
|  | MOCK_METHOD(void, RequestRefreshFrame, (), (override)); | 
|  | }; | 
|  |  | 
|  | 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(_, true, _)).Times(1); | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1); | 
|  | auto frame = CreateFrame(); | 
|  | adapter->OnFrame(frame); | 
|  | adapter->OnFrame(frame); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).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()); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  |  | 
|  | // 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)); | 
|  | std::optional<int64_t> expected_fps = | 
|  | rate.Rate(time_controller.GetClock()->TimeInMilliseconds()); | 
|  | rate.Update(1, time_controller.GetClock()->TimeInMilliseconds()); | 
|  | // FrameCadanceAdapter::OnFrame post the frame to another sequence. | 
|  | adapter->OnFrame(CreateFrameWithTimestamps(&time_controller)); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(0)); | 
|  | EXPECT_EQ(expected_fps, adapter->GetInputFrameRateFps()) | 
|  | << " failed for frame " << frame; | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, FrameRateFollowsMaxFpsWhenZeroHertzActivated) { | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock()); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  | adapter->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{}); | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); | 
|  | for (int frame = 0; frame != 10; ++frame) { | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(10)); | 
|  | // FrameCadanceAdapter::OnFrame post the frame to another sequence. | 
|  | adapter->OnFrame(CreateFrameWithTimestamps(&time_controller)); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(0)); | 
|  | EXPECT_EQ(adapter->GetInputFrameRateFps(), 1u); | 
|  | } | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, ZeroHertzAdapterSupportsMaxFpsChange) { | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock()); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  | adapter->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{}); | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | EXPECT_EQ(adapter->GetInputFrameRateFps(), 1u); | 
|  | adapter->OnFrame(CreateFrame()); | 
|  | time_controller.AdvanceTime(TimeDelta::Seconds(1)); | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 2}); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | EXPECT_EQ(adapter->GetInputFrameRateFps(), 2u); | 
|  | adapter->OnFrame(CreateFrame()); | 
|  | // Ensure that the max_fps has been changed from 1 to 2 fps even if it was | 
|  | // changed while zero hertz was already active. | 
|  | EXPECT_CALL(callback, OnFrame); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(500)); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, | 
|  | FrameRateFollowsRateStatisticsAfterZeroHertzDeactivated) { | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock()); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  | 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->OnFrame(CreateFrameWithTimestamps(&time_controller)); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(0)); | 
|  | } | 
|  | // 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(std::nullopt); | 
|  | // Last frame. | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(10)); | 
|  | adapter->OnFrame(CreateFrameWithTimestamps(&time_controller)); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(0)); | 
|  |  | 
|  | EXPECT_EQ(rate.Rate(time_controller.GetClock()->TimeInMilliseconds()), | 
|  | adapter->GetInputFrameRateFps()); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, ForwardsFramesDelayed) { | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | 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, 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, bool, | 
|  | 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, DelayedProcessingUnderSlightContention) { | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock()); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  | adapter->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{}); | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); | 
|  |  | 
|  | // Expect frame delivery at 1 sec despite target sequence not running | 
|  | // callbacks for the time skipped. | 
|  | constexpr TimeDelta time_skipped = TimeDelta::Millis(999); | 
|  | EXPECT_CALL(callback, OnFrame).WillOnce(InvokeWithoutArgs([&] { | 
|  | EXPECT_EQ(time_controller.GetClock()->CurrentTime(), | 
|  | Timestamp::Zero() + TimeDelta::Seconds(1)); | 
|  | })); | 
|  | adapter->OnFrame(CreateFrame()); | 
|  | time_controller.SkipForwardBy(time_skipped); | 
|  | time_controller.AdvanceTime(TimeDelta::Seconds(1) - time_skipped); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, DelayedProcessingUnderHeavyContention) { | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, time_controller.GetClock()); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  | adapter->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{}); | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 1}); | 
|  |  | 
|  | // Expect frame delivery at origin + `time_skipped` when the target sequence | 
|  | // is not running callbacks for the initial 1+ sec. | 
|  | constexpr TimeDelta time_skipped = | 
|  | TimeDelta::Seconds(1) + TimeDelta::Micros(1); | 
|  | EXPECT_CALL(callback, OnFrame).WillOnce(InvokeWithoutArgs([&] { | 
|  | EXPECT_EQ(time_controller.GetClock()->CurrentTime(), | 
|  | Timestamp::Zero() + time_skipped); | 
|  | })); | 
|  | adapter->OnFrame(CreateFrame()); | 
|  | time_controller.SkipForwardBy(time_skipped); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | } | 
|  |  | 
|  | 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. | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Millis(47892223)); | 
|  | 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, 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, bool, 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, bool, 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, bool, 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. | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Millis(4711)); | 
|  | 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, 1}); | 
|  |  | 
|  | // Send one frame, expect a repeat. | 
|  | adapter->OnFrame(CreateFrame()); | 
|  | EXPECT_CALL(callback, OnFrame) | 
|  | .WillOnce(Invoke([&](Timestamp post_time, bool, 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, bool, 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. | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | 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, 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, bool, 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) { | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, 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) { | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | 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, 10}); | 
|  | adapter->OnFrame(CreateFrame()); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | EXPECT_CALL(callback, RequestRefreshFrame).Times(0); | 
|  | adapter->ProcessKeyFrameRequest(); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, RequestsRefreshFramesUntilArrival) { | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, 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) { | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, 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) { | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, 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. | 
|  | MockCallback callback; | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = CreateAdapter(no_field_trials, 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); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, IgnoresDropInducedCallbacksPostDestruction) { | 
|  | auto callback = std::make_unique<MockCallback>(); | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | auto queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue( | 
|  | "queue", TaskQueueFactory::Priority::NORMAL); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = FrameCadenceAdapterInterface::Create( | 
|  | time_controller.GetClock(), queue.get(), /*metronome=*/nullptr, | 
|  | /*worker_queue=*/nullptr, no_field_trials); | 
|  | queue->PostTask([&adapter, &callback] { | 
|  | adapter->Initialize(callback.get()); | 
|  | adapter->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{}); | 
|  | }); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | constexpr int kMaxFps = 10; | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFps}); | 
|  | adapter->OnDiscardedFrame(); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | callback = nullptr; | 
|  | queue->PostTask([adapter = std::move(adapter)]() mutable {}); | 
|  | time_controller.AdvanceTime(3 * TimeDelta::Seconds(1) / kMaxFps); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, EncodeFramesAreAlignedWithMetronomeTick) { | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | // Here the metronome interval is 33ms, because the metronome is not | 
|  | // infrequent then the encode tasks are aligned with the tick period. | 
|  | static constexpr TimeDelta kTickPeriod = TimeDelta::Millis(33); | 
|  | auto queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue( | 
|  | "queue", TaskQueueFactory::Priority::NORMAL); | 
|  | auto worker_queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue( | 
|  | "work_queue", TaskQueueFactory::Priority::NORMAL); | 
|  | test::FakeMetronome metronome(kTickPeriod); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = FrameCadenceAdapterInterface::Create( | 
|  | time_controller.GetClock(), queue.get(), &metronome, worker_queue.get(), | 
|  | no_field_trials); | 
|  | MockCallback callback; | 
|  | adapter->Initialize(&callback); | 
|  | auto frame = CreateFrame(); | 
|  |  | 
|  | // `callback->OnFrame()` would not be called if only 32ms went by after | 
|  | // `adapter->OnFrame()`. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(0); | 
|  | adapter->OnFrame(frame); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(32)); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  |  | 
|  | // `callback->OnFrame()` should be called if 33ms went by after | 
|  | // `adapter->OnFrame()`. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(1)); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  |  | 
|  | // `callback->OnFrame()` would not be called if only 32ms went by after | 
|  | // `adapter->OnFrame()`. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(0); | 
|  | // Send two frame before next tick. | 
|  | adapter->OnFrame(frame); | 
|  | adapter->OnFrame(frame); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(32)); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  |  | 
|  | // `callback->OnFrame()` should be called if 33ms went by after | 
|  | // `adapter->OnFrame()`. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(2); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(1)); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  |  | 
|  | // Change the metronome tick period to 67ms (15Hz). | 
|  | metronome.SetTickPeriod(TimeDelta::Millis(67)); | 
|  | // Expect the encode would happen immediately. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1); | 
|  | adapter->OnFrame(frame); | 
|  | time_controller.AdvanceTime(TimeDelta::Zero()); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  |  | 
|  | // Change the metronome tick period to 16ms (60Hz). | 
|  | metronome.SetTickPeriod(TimeDelta::Millis(16)); | 
|  | // Expect the encode would not happen if only 15ms went by after | 
|  | // `adapter->OnFrame()`. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(0); | 
|  | adapter->OnFrame(frame); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(15)); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  | // `callback->OnFrame()` should be called if 16ms went by after | 
|  | // `adapter->OnFrame()`. | 
|  | EXPECT_CALL(callback, OnFrame(_, false, _)).Times(1); | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(1)); | 
|  | Mock::VerifyAndClearExpectations(&callback); | 
|  |  | 
|  | rtc::Event finalized; | 
|  | queue->PostTask([&] { | 
|  | adapter = nullptr; | 
|  | finalized.Set(); | 
|  | }); | 
|  | finalized.Wait(rtc::Event::kForever); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterTest, ShutdownUnderMetronome) { | 
|  | // Regression test for crbug.com/356423094. | 
|  | // The current thread takes the role of worker queue. | 
|  | GlobalSimulatedTimeController time_controller(Timestamp::Zero()); | 
|  | static constexpr TimeDelta kTickPeriod = TimeDelta::Millis(100); | 
|  | auto queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue( | 
|  | "queue", TaskQueueFactory::Priority::NORMAL); | 
|  | test::FakeMetronome metronome(kTickPeriod); | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | auto adapter = FrameCadenceAdapterInterface::Create( | 
|  | time_controller.GetClock(), queue.get(), &metronome, | 
|  | TaskQueueBase::Current(), no_field_trials); | 
|  | MockCallback callback; | 
|  | EXPECT_CALL(callback, OnFrame).Times(0); | 
|  | adapter->Initialize(&callback); | 
|  |  | 
|  | // Pass a frame, this is expected to trigger an encode call in the future. | 
|  | adapter->OnFrame(CreateFrame()); | 
|  |  | 
|  | // Then post destruction of the adapter and destroy the encode queue from the | 
|  | // worker (i.e. current). | 
|  | SendTask(queue.get(), [&] { adapter = nullptr; }); | 
|  | queue = nullptr; | 
|  |  | 
|  | // Now that we advance time, there should be no encoding happening. | 
|  | time_controller.AdvanceTime(TimeDelta::Millis(100)); | 
|  | } | 
|  |  | 
|  | class FrameCadenceAdapterSimulcastLayersParamTest | 
|  | : public ::testing::TestWithParam<int> { | 
|  | 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: | 
|  | test::ScopedKeyValueConfig no_field_trials_; | 
|  | MockCallback callback_; | 
|  | GlobalSimulatedTimeController time_controller_{Timestamp::Zero()}; | 
|  | const std::unique_ptr<FrameCadenceAdapterInterface> adapter_{ | 
|  | CreateAdapter(no_field_trials_, 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; | 
|  | // Restricts non-idle repeat rate to 5 fps (default is 10 fps); | 
|  | static constexpr int kRestrictedMaxFps = 5; | 
|  |  | 
|  | 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<TimeDelta> list) { | 
|  | Timestamp origin = time_controller_.GetClock()->CurrentTime(); | 
|  | for (auto delay : list) { | 
|  | EXPECT_CALL(callback_, OnFrame(origin + delay, false, _)); | 
|  | time_controller_.AdvanceTime(origin + delay - | 
|  | time_controller_.GetClock()->CurrentTime()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void ScheduleDelayed(TimeDelta delay, absl::AnyInvocable<void() &&> task) { | 
|  | TaskQueueBase::Current()->PostDelayedTask(std::move(task), delay); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | test::ScopedKeyValueConfig no_field_trials_; | 
|  | MockCallback callback_; | 
|  | GlobalSimulatedTimeController time_controller_{Timestamp::Zero()}; | 
|  | std::unique_ptr<FrameCadenceAdapterInterface> adapter_{ | 
|  | CreateAdapter(no_field_trials_, 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,  // ... | 
|  | // ... | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzLayerQualityConvergenceTest, | 
|  | UnconvergedRepeatRateAdaptsDownWhenRestricted) { | 
|  | PassFrame(); | 
|  | ScheduleDelayed(1.5 * kMinFrameDelay, [&] { | 
|  | adapter_->UpdateVideoSourceRestrictions(kRestrictedMaxFps); | 
|  | }); | 
|  | ExpectFrameEntriesAtDelaysFromNow({ | 
|  | 1 * kMinFrameDelay,  // Original frame emitted at non-restricted rate. | 
|  |  | 
|  | // 1.5 * kMinFrameDelay: restricts max fps to 5 fps which should result | 
|  | // in a new non-idle repeat delay of 2 * kMinFrameDelay. | 
|  | 2 * kMinFrameDelay,  // Unconverged repeat at non-restricted rate. | 
|  | 4 * kMinFrameDelay,  // Unconverged repeats at restricted rate. This | 
|  | // happens 2 * kMinFrameDelay after the last frame. | 
|  | 6 * kMinFrameDelay,  // ... | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzLayerQualityConvergenceTest, | 
|  | UnconvergedRepeatRateAdaptsUpWhenGoingFromRestrictedToUnrestricted) { | 
|  | PassFrame(); | 
|  | ScheduleDelayed(1.5 * kMinFrameDelay, [&] { | 
|  | adapter_->UpdateVideoSourceRestrictions(kRestrictedMaxFps); | 
|  | }); | 
|  | ScheduleDelayed(5.5 * kMinFrameDelay, [&] { | 
|  | adapter_->UpdateVideoSourceRestrictions(std::nullopt); | 
|  | }); | 
|  | ExpectFrameEntriesAtDelaysFromNow({ | 
|  | 1 * kMinFrameDelay,  // Original frame emitted at non-restricted rate. | 
|  |  | 
|  | // 1.5 * kMinFrameDelay: restricts max fps to 5 fps which should result | 
|  | // in a new non-idle repeat delay of 2 * kMinFrameDelay. | 
|  | 2 * kMinFrameDelay,  // Unconverged repeat at non-restricted rate. | 
|  | 4 * kMinFrameDelay,  // Unconverged repeat at restricted rate. | 
|  |  | 
|  | // 5.5 * kMinFrameDelay: removes frame-rate restriction and we should | 
|  | // then go back to 10 fps as unconverged repeat rate. | 
|  | 6 * kMinFrameDelay,  // Last unconverged repeat at restricted rate. | 
|  | 7 * kMinFrameDelay,  // Back to unconverged repeat at non-restricted rate. | 
|  | 8 * kMinFrameDelay,  // We are now unrestricted. | 
|  | 9 * kMinFrameDelay,  // ... | 
|  | }); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzLayerQualityConvergenceTest, | 
|  | UnconvergedRepeatRateMaintainsRestrictionOnReconfigureToHigherMaxFps) { | 
|  | PassFrame(); | 
|  | ScheduleDelayed(1.5 * kMinFrameDelay, [&] { | 
|  | adapter_->UpdateVideoSourceRestrictions(kRestrictedMaxFps); | 
|  | }); | 
|  | ScheduleDelayed(2.5 * kMinFrameDelay, [&] { | 
|  | adapter_->OnConstraintsChanged(VideoTrackSourceConstraints{ | 
|  | /*min_fps=*/0, /*max_fps=*/2 * TimeDelta::Seconds(1) / kMinFrameDelay}); | 
|  | }); | 
|  | ScheduleDelayed(3 * kMinFrameDelay, [&] { PassFrame(); }); | 
|  | ScheduleDelayed(8 * kMinFrameDelay, [&] { | 
|  | adapter_->OnConstraintsChanged(VideoTrackSourceConstraints{ | 
|  | /*min_fps=*/0, | 
|  | /*max_fps=*/0.2 * TimeDelta::Seconds(1) / kMinFrameDelay}); | 
|  | }); | 
|  | ScheduleDelayed(9 * kMinFrameDelay, [&] { PassFrame(); }); | 
|  | ExpectFrameEntriesAtDelaysFromNow({ | 
|  | 1 * kMinFrameDelay,  // Original frame emitted at non-restricted rate. | 
|  |  | 
|  | // 1.5 * kMinFrameDelay: restricts max fps to 5 fps which should result | 
|  | // in a new non-idle repeat delay of 2 * kMinFrameDelay. | 
|  | 2 * kMinFrameDelay,  // Unconverged repeat at non-restricted rate. | 
|  |  | 
|  | // 2.5 * kMinFrameDelay: new constraint asks for max rate of 20 fps. | 
|  | // The 0Hz adapter is reconstructed for 20 fps but inherits the current | 
|  | // restriction for rate of non-converged frames of 5 fps. | 
|  |  | 
|  | // A new frame is passed at 3 * kMinFrameDelay. The previous repeat | 
|  | // cadence was stopped by the change in constraints. | 
|  | 3.5 * kMinFrameDelay,  // Original frame emitted at non-restricted 20 fps. | 
|  | // The delay is 0.5 * kMinFrameDelay. | 
|  | 5.5 * kMinFrameDelay,  // Unconverged repeat at restricted rate. | 
|  | // The delay is 2 * kMinFrameDelay when restricted. | 
|  | 7.5 * kMinFrameDelay,  // ... | 
|  |  | 
|  | // 8 * kMinFrameDelay: new constraint asks for max rate of 2 fps. | 
|  | // The 0Hz adapter is reconstructed for 2 fps and will therefore not obey | 
|  | // the current restriction for rate of non-converged frames of 5 fps | 
|  | // since the new max rate is lower. | 
|  |  | 
|  | // A new frame is passed at 9 * kMinFrameDelay. The previous repeat | 
|  | // cadence was stopped by the change in constraints. | 
|  | 14 * kMinFrameDelay,  // Original frame emitted at non-restricted 2 fps. | 
|  | // The delay is 5 * kMinFrameDelay. | 
|  | 19 * kMinFrameDelay,  // Unconverged repeat at non-restricted rate. | 
|  | 24 * kMinFrameDelay,  // ... | 
|  | }); | 
|  | } | 
|  |  | 
|  | 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, 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_F(FrameCadenceAdapterMetricsTest, | 
|  | RecordsFrameTimestampMonotonicallyIncreasing) { | 
|  | MockCallback callback; | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | std::unique_ptr<FrameCadenceAdapterInterface> adapter = | 
|  | CreateAdapter(no_field_trials, time_controller_.GetClock()); | 
|  | adapter->Initialize(&callback); | 
|  | time_controller_.AdvanceTime(TimeDelta::Millis(666)); | 
|  | adapter->OnFrame(CreateFrameWithTimestamps(&time_controller_)); | 
|  | adapter->OnFrame(CreateFrameWithTimestamps(&time_controller_)); | 
|  | time_controller_.AdvanceTime(TimeDelta::Zero()); | 
|  | adapter = nullptr; | 
|  | DepleteTaskQueues(); | 
|  | EXPECT_THAT(metrics::Samples( | 
|  | "WebRTC.Video.InputFrameTimestampMonotonicallyIncreasing"), | 
|  | ElementsAre(Pair(false, 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); | 
|  | MockCallback callback; | 
|  | Clock* clock = Clock::GetRealTimeClock(); | 
|  | std::unique_ptr<FrameCadenceAdapterInterface> adapter; | 
|  | int frame_counter = 0; | 
|  | int64_t original_ntp_time_ms; | 
|  | int64_t original_timestamp_us; | 
|  | rtc::Event event; | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | queue->PostTask([&] { | 
|  | adapter = CreateAdapter(no_field_trials, 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, bool, 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); | 
|  | } | 
|  |  | 
|  | TEST(FrameCadenceAdapterRealTimeTest, ScheduledRepeatAllowsForSlowEncode) { | 
|  | // This regression test must be performed in realtime because of limitations | 
|  | // in GlobalSimulatedTimeController. | 
|  | // | 
|  | // We sleep for a long while (but less than max fps) in the first repeated | 
|  | // OnFrame (frame 2). This should not lead to a belated second repeated | 
|  | // OnFrame (frame 3). | 
|  | auto factory = CreateDefaultTaskQueueFactory(); | 
|  | auto queue = | 
|  | factory->CreateTaskQueue("test", TaskQueueFactory::Priority::NORMAL); | 
|  | MockCallback callback; | 
|  | Clock* clock = Clock::GetRealTimeClock(); | 
|  | std::unique_ptr<FrameCadenceAdapterInterface> adapter; | 
|  | int frame_counter = 0; | 
|  | rtc::Event event; | 
|  | std::optional<Timestamp> start_time; | 
|  | test::ScopedKeyValueConfig no_field_trials; | 
|  | queue->PostTask([&] { | 
|  | adapter = CreateAdapter(no_field_trials, clock); | 
|  | adapter->Initialize(&callback); | 
|  | adapter->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{}); | 
|  | adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 2}); | 
|  | auto frame = CreateFrame(); | 
|  | constexpr int kSleepMs = 400; | 
|  | constexpr TimeDelta kAllowedBelate = TimeDelta::Millis(150); | 
|  | EXPECT_CALL(callback, OnFrame) | 
|  | .WillRepeatedly(InvokeWithoutArgs([&, kAllowedBelate] { | 
|  | ++frame_counter; | 
|  | // Avoid the first OnFrame and sleep on the second. | 
|  | if (frame_counter == 2) { | 
|  | start_time = clock->CurrentTime(); | 
|  | SleepMs(kSleepMs); | 
|  | } else if (frame_counter == 3) { | 
|  | TimeDelta diff = | 
|  | clock->CurrentTime() - (*start_time + TimeDelta::Millis(500)); | 
|  | RTC_LOG(LS_ERROR) | 
|  | << "Difference in when frame should vs is appearing: " << diff; | 
|  | EXPECT_LT(diff, kAllowedBelate); | 
|  | event.Set(); | 
|  | } | 
|  | })); | 
|  | adapter->OnFrame(frame); | 
|  | }); | 
|  | event.Wait(rtc::Event::kForever); | 
|  | rtc::Event finalized; | 
|  | queue->PostTask([&] { | 
|  | adapter = nullptr; | 
|  | finalized.Set(); | 
|  | }); | 
|  | finalized.Wait(rtc::Event::kForever); | 
|  | } | 
|  |  | 
|  | class ZeroHertzQueueOverloadTest : public ::testing::Test { | 
|  | public: | 
|  | static constexpr int kMaxFps = 10; | 
|  |  | 
|  | ZeroHertzQueueOverloadTest() { | 
|  | Initialize(); | 
|  | metrics::Reset(); | 
|  | } | 
|  |  | 
|  | void Initialize() { | 
|  | adapter_->Initialize(&callback_); | 
|  | adapter_->SetZeroHertzModeEnabled( | 
|  | FrameCadenceAdapterInterface::ZeroHertzModeParams{ | 
|  | /*num_simulcast_layers=*/1}); | 
|  | adapter_->OnConstraintsChanged( | 
|  | VideoTrackSourceConstraints{/*min_fps=*/0, kMaxFps}); | 
|  | time_controller_.AdvanceTime(TimeDelta::Zero()); | 
|  | } | 
|  |  | 
|  | void ScheduleDelayed(TimeDelta delay, absl::AnyInvocable<void() &&> task) { | 
|  | TaskQueueBase::Current()->PostDelayedTask(std::move(task), delay); | 
|  | } | 
|  |  | 
|  | void PassFrame() { adapter_->OnFrame(CreateFrame()); } | 
|  |  | 
|  | void AdvanceTime(TimeDelta duration) { | 
|  | time_controller_.AdvanceTime(duration); | 
|  | } | 
|  |  | 
|  | void SkipForwardBy(TimeDelta duration) { | 
|  | time_controller_.SkipForwardBy(duration); | 
|  | } | 
|  |  | 
|  | Timestamp CurrentTime() { return time_controller_.GetClock()->CurrentTime(); } | 
|  |  | 
|  | protected: | 
|  | test::ScopedKeyValueConfig field_trials_; | 
|  | NiceMock<MockCallback> callback_; | 
|  | GlobalSimulatedTimeController time_controller_{Timestamp::Zero()}; | 
|  | std::unique_ptr<FrameCadenceAdapterInterface> adapter_{ | 
|  | CreateAdapter(field_trials_, time_controller_.GetClock())}; | 
|  | }; | 
|  |  | 
|  | TEST_F(ZeroHertzQueueOverloadTest, | 
|  | ForwardedFramesDuringTooLongEncodeTimeAreFlaggedWithQueueOverload) { | 
|  | InSequence s; | 
|  | PassFrame(); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] { | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | SkipForwardBy(TimeDelta::Millis(301)); | 
|  | })); | 
|  | EXPECT_CALL(callback_, OnFrame(_, true, _)).Times(3); | 
|  | AdvanceTime(TimeDelta::Millis(100)); | 
|  | EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"), | 
|  | ElementsAre(Pair(false, 1), Pair(true, 3))); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzQueueOverloadTest, | 
|  | ForwardedFramesAfterOverloadBurstAreNotFlaggedWithQueueOverload) { | 
|  | InSequence s; | 
|  | PassFrame(); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] { | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | SkipForwardBy(TimeDelta::Millis(301)); | 
|  | })); | 
|  | EXPECT_CALL(callback_, OnFrame(_, true, _)).Times(3); | 
|  | AdvanceTime(TimeDelta::Millis(100)); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(2); | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | AdvanceTime(TimeDelta::Millis(100)); | 
|  | EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"), | 
|  | ElementsAre(Pair(false, 3), Pair(true, 3))); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzQueueOverloadTest, | 
|  | ForwardedFramesDuringNormalEncodeTimeAreNotFlaggedWithQueueOverload) { | 
|  | InSequence s; | 
|  | PassFrame(); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] { | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | // Long but not too long encode time. | 
|  | SkipForwardBy(TimeDelta::Millis(99)); | 
|  | })); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(3); | 
|  | AdvanceTime(TimeDelta::Millis(199)); | 
|  | EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"), | 
|  | ElementsAre(Pair(false, 4))); | 
|  | } | 
|  |  | 
|  | TEST_F( | 
|  | ZeroHertzQueueOverloadTest, | 
|  | AvoidSettingQueueOverloadAndSendRepeatWhenNoNewPacketsWhileTooLongEncode) { | 
|  | // Receive one frame only and let OnFrame take such a long time that an | 
|  | // overload normally is warranted. But the fact that no new frames arrive | 
|  | // while being blocked should trigger a non-idle repeat to ensure that the | 
|  | // video stream does not freeze and queue overload should be false. | 
|  | PassFrame(); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)) | 
|  | .WillOnce( | 
|  | InvokeWithoutArgs([&] { SkipForwardBy(TimeDelta::Millis(101)); })) | 
|  | .WillOnce(InvokeWithoutArgs([&] { | 
|  | // Non-idle repeat. | 
|  | EXPECT_EQ(CurrentTime(), Timestamp::Zero() + TimeDelta::Millis(201)); | 
|  | })); | 
|  | AdvanceTime(TimeDelta::Millis(100)); | 
|  | EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"), | 
|  | ElementsAre(Pair(false, 2))); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzQueueOverloadTest, | 
|  | EnterFastRepeatAfterQueueOverloadWhenReceivedOnlyOneFrameDuringEncode) { | 
|  | InSequence s; | 
|  | // - Forward one frame frame during high load which triggers queue overload. | 
|  | // - Receive only one new frame while being blocked and verify that the | 
|  | //   cancelled repeat was for the first frame and not the second. | 
|  | // - Fast repeat mode should happen after second frame. | 
|  | PassFrame(); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] { | 
|  | PassFrame(); | 
|  | SkipForwardBy(TimeDelta::Millis(101)); | 
|  | })); | 
|  | EXPECT_CALL(callback_, OnFrame(_, true, _)); | 
|  | AdvanceTime(TimeDelta::Millis(100)); | 
|  |  | 
|  | // Fast repeats should take place from here on. | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(5); | 
|  | AdvanceTime(TimeDelta::Millis(500)); | 
|  | EXPECT_THAT(metrics::Samples("WebRTC.Screenshare.ZeroHz.QueueOverload"), | 
|  | ElementsAre(Pair(false, 6), Pair(true, 1))); | 
|  | } | 
|  |  | 
|  | TEST_F(ZeroHertzQueueOverloadTest, | 
|  | QueueOverloadIsDisabledForZeroHerzWhenKillSwitchIsEnabled) { | 
|  | webrtc::test::ScopedKeyValueConfig field_trials( | 
|  | field_trials_, "WebRTC-ZeroHertzQueueOverload/Disabled/"); | 
|  | adapter_.reset(); | 
|  | adapter_ = CreateAdapter(field_trials, time_controller_.GetClock()); | 
|  | Initialize(); | 
|  |  | 
|  | // Same as ForwardedFramesDuringTooLongEncodeTimeAreFlaggedWithQueueOverload | 
|  | // but this time the queue overload mechanism is disabled. | 
|  | InSequence s; | 
|  | PassFrame(); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).WillOnce(InvokeWithoutArgs([&] { | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | PassFrame(); | 
|  | SkipForwardBy(TimeDelta::Millis(301)); | 
|  | })); | 
|  | EXPECT_CALL(callback_, OnFrame(_, false, _)).Times(3); | 
|  | AdvanceTime(TimeDelta::Millis(100)); | 
|  | EXPECT_EQ(metrics::NumSamples("WebRTC.Screenshare.ZeroHz.QueueOverload"), 0); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  | }  // namespace webrtc |