ZeroHertzAdapterMode: handle key frame requests.
Under zero-hertz mode, provided that a frame arrived to the
VideoStreamEncoder, the receiver may experience up to a second
between incoming frames. This results in key frame requests getting
serviced with that delay, which is undesired.
What's worse is also the fact that if no frame ever arrived to the
VideoStreamEncoder, it will not service the keyframe requests at all
until the first frame comes.
This change introduces VideoSourceInterface::RequestRefreshFrame
which results in a refresh frame being sent from complying sources.
The method is used under zero-hertz mode from the VideoStreamEncoder
when frames didn't arrive to it yet (with changes to the zero-hertz
adapter).
With this change, when the frame adapter has received at least one
frame, it will conditionally repeat the last frame in response to the
key frame request.
go/rtc-0hz-present
Bug: chromium:1255737
Change-Id: I6f97813b3a938747357d45e5dda54f759129b44d
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/242361
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Markus Handell <handellm@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35562}
diff --git a/video/frame_cadence_adapter_unittest.cc b/video/frame_cadence_adapter_unittest.cc
index 38827d6..8932c7f 100644
--- a/video/frame_cadence_adapter_unittest.cc
+++ b/video/frame_cadence_adapter_unittest.cc
@@ -352,6 +352,111 @@
time_controller.AdvanceTime(TimeDelta::Seconds(1));
}
+TEST(FrameCadenceAdapterTest, RequestsRefreshFrameOnKeyFrameRequestWhenNew) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(&callback);
+ adapter->SetZeroHertzModeEnabled(
+ FrameCadenceAdapterInterface::ZeroHertzModeParams{});
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 10});
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ EXPECT_TRUE(adapter->ProcessKeyFrameRequest());
+}
+
+TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestShortlyAfterFrame) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(&callback);
+ adapter->SetZeroHertzModeEnabled(
+ FrameCadenceAdapterInterface::ZeroHertzModeParams{});
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, 10});
+ adapter->OnFrame(CreateFrame());
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ EXPECT_FALSE(adapter->ProcessKeyFrameRequest());
+}
+
+TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestWhileShortRepeating) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(&callback);
+ adapter->SetZeroHertzModeEnabled(
+ FrameCadenceAdapterInterface::ZeroHertzModeParams{
+ /*num_simulcast_layers=*/1});
+ constexpr int kMaxFpsHz = 10;
+ constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(1000 / kMaxFpsHz);
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz});
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ adapter->UpdateLayerStatus(0, true);
+ adapter->OnFrame(CreateFrame());
+ time_controller.AdvanceTime(2 * kMinFrameDelay);
+ EXPECT_FALSE(adapter->ProcessKeyFrameRequest());
+
+ // Expect repeating as ususal.
+ EXPECT_CALL(callback, OnFrame).Times(8);
+ time_controller.AdvanceTime(8 * kMinFrameDelay);
+}
+
+TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestJustBeforeIdleRepeating) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(&callback);
+ adapter->SetZeroHertzModeEnabled(
+ FrameCadenceAdapterInterface::ZeroHertzModeParams{});
+ constexpr int kMaxFpsHz = 10;
+ constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(1000 / kMaxFpsHz);
+ constexpr TimeDelta kIdleFrameDelay =
+ FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod;
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz});
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ adapter->OnFrame(CreateFrame());
+ time_controller.AdvanceTime(kIdleFrameDelay);
+
+ // We process the key frame request kMinFrameDelay before the first idle
+ // repeat should happen. The repeat should happen at T = kMinFrameDelay +
+ // kIdleFrameDelay as originally expected.
+ EXPECT_FALSE(adapter->ProcessKeyFrameRequest());
+ EXPECT_CALL(callback, OnFrame);
+ time_controller.AdvanceTime(kMinFrameDelay);
+ EXPECT_CALL(callback, OnFrame);
+ time_controller.AdvanceTime(kIdleFrameDelay);
+}
+
+TEST(FrameCadenceAdapterTest,
+ IgnoresKeyFrameRequestShortRepeatsBeforeIdleRepeat) {
+ ZeroHertzFieldTrialEnabler enabler;
+ MockCallback callback;
+ GlobalSimulatedTimeController time_controller(Timestamp::Millis(0));
+ auto adapter = CreateAdapter(time_controller.GetClock());
+ adapter->Initialize(&callback);
+ adapter->SetZeroHertzModeEnabled(
+ FrameCadenceAdapterInterface::ZeroHertzModeParams{});
+ constexpr int kMaxFpsHz = 10;
+ constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(1000 / kMaxFpsHz);
+ constexpr TimeDelta kIdleFrameDelay =
+ FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod;
+ adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz});
+ time_controller.AdvanceTime(TimeDelta::Zero());
+ adapter->OnFrame(CreateFrame());
+ time_controller.AdvanceTime(2 * kMinFrameDelay);
+
+ // We process the key frame request 9 * kMinFrameDelay before the first idle
+ // repeat should happen. We should get a short repeat in kMinFrameDelay and an
+ // idle repeat after that.
+ EXPECT_FALSE(adapter->ProcessKeyFrameRequest());
+ EXPECT_CALL(callback, OnFrame);
+ time_controller.AdvanceTime(kMinFrameDelay);
+ EXPECT_CALL(callback, OnFrame);
+ time_controller.AdvanceTime(kIdleFrameDelay);
+}
+
class ZeroHertzLayerQualityConvergenceTest : public ::testing::Test {
public:
static constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(100);