mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00

Bug: webrtc:342905193 No-Try: True Change-Id: Icc968be43b8830038ea9a1f5f604307220457807 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/361021 Auto-Submit: Florent Castelli <orphis@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Florent Castelli <orphis@webrtc.org> Cr-Commit-Position: refs/heads/main@{#42911}
1325 lines
54 KiB
C++
1325 lines
54 KiB
C++
/*
|
|
* 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);
|
|
}
|
|
|
|
// TODO(bugs.webrtc.org/15462) Disable ScheduledRepeatAllowsForSlowEncode for
|
|
// TaskQueueLibevent.
|
|
#if defined(WEBRTC_ENABLE_LIBEVENT)
|
|
#define MAYBE_ScheduledRepeatAllowsForSlowEncode \
|
|
DISABLED_ScheduledRepeatAllowsForSlowEncode
|
|
#else
|
|
#define MAYBE_ScheduledRepeatAllowsForSlowEncode \
|
|
ScheduledRepeatAllowsForSlowEncode
|
|
#endif
|
|
|
|
TEST(FrameCadenceAdapterRealTimeTest,
|
|
MAYBE_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
|