From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../video/frame_cadence_adapter_unittest.cc | 1101 ++++++++++++++++++++ 1 file changed, 1101 insertions(+) create mode 100644 third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc (limited to 'third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc') diff --git a/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc new file mode 100644 index 0000000000..afc675ffde --- /dev/null +++ b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc @@ -0,0 +1,1101 @@ +/* + * 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 -- cgit v1.2.3