summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc249
1 files changed, 246 insertions, 3 deletions
diff --git a/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
index 0fef2400f0..54548de9bb 100644
--- a/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
+++ b/third_party/libwebrtc/video/frame_cadence_adapter_unittest.cc
@@ -14,6 +14,7 @@
#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"
@@ -38,9 +39,11 @@ 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;
@@ -64,8 +67,9 @@ VideoFrame CreateFrameWithTimestamps(
std::unique_ptr<FrameCadenceAdapterInterface> CreateAdapter(
const FieldTrialsView& field_trials,
Clock* clock) {
- return FrameCadenceAdapterInterface::Create(clock, TaskQueueBase::Current(),
- field_trials);
+ return FrameCadenceAdapterInterface::Create(
+ clock, TaskQueueBase::Current(), /*metronome=*/nullptr,
+ /*worker_queue=*/nullptr, field_trials);
}
class MockCallback : public FrameCadenceAdapterInterface::Callback {
@@ -308,6 +312,7 @@ TEST(FrameCadenceAdapterTest, DelayedProcessingUnderHeavyContention) {
}));
adapter->OnFrame(CreateFrame());
time_controller.SkipForwardBy(time_skipped);
+ time_controller.AdvanceTime(TimeDelta::Zero());
}
TEST(FrameCadenceAdapterTest, RepeatsFramesDelayed) {
@@ -593,7 +598,8 @@ TEST(FrameCadenceAdapterTest, IgnoresDropInducedCallbacksPostDestruction) {
auto queue = time_controller.GetTaskQueueFactory()->CreateTaskQueue(
"queue", TaskQueueFactory::Priority::NORMAL);
auto adapter = FrameCadenceAdapterInterface::Create(
- time_controller.GetClock(), queue.get(), enabler);
+ time_controller.GetClock(), queue.get(), /*metronome=*/nullptr,
+ /*worker_queue=*/nullptr, enabler);
queue->PostTask([&adapter, &callback] {
adapter->Initialize(callback.get());
adapter->SetZeroHertzModeEnabled(
@@ -609,6 +615,82 @@ TEST(FrameCadenceAdapterTest, IgnoresDropInducedCallbacksPostDestruction) {
time_controller.AdvanceTime(3 * TimeDelta::Seconds(1) / kMaxFps);
}
+TEST(FrameCadenceAdapterTest, EncodeFramesAreAlignedWithMetronomeTick) {
+ ZeroHertzFieldTrialEnabler enabler;
+ 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);
+ static test::FakeMetronome metronome(kTickPeriod);
+ auto adapter = FrameCadenceAdapterInterface::Create(
+ time_controller.GetClock(), queue.get(), &metronome, worker_queue.get(),
+ enabler);
+ 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);
+}
+
class FrameCadenceAdapterSimulcastLayersParamTest
: public ::testing::TestWithParam<int> {
public:
@@ -1076,5 +1158,166 @@ TEST(FrameCadenceAdapterRealTimeTest,
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