ZeroHertzAdapterMode: reset convergence info on key frame requests.

The QP value of encoded key frames is normally very large. However,
the zero-hertz mode is retaining quality convergence info, leading
to only a single short repeat on key frame request when idle
repeating.

Fix this by resetting quality convergence information on key frame
requests, ensuring zero-hertz mode goes back to idle repeating
only when quality has converged again.

go/rtc-0hz-present

Bug: chromium:1255737
Change-Id: Ia1ad686cc98007f01c8aaef9162011837575938c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/242862
Reviewed-by: Erik Språng <sprang@webrtc.org>
Commit-Queue: Markus Handell <handellm@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35594}
This commit is contained in:
Markus Handell 2021-12-29 21:31:57 +01:00 committed by WebRTC LUCI CQ
parent e59fee87fb
commit cb237f8822
2 changed files with 114 additions and 89 deletions

View File

@ -367,6 +367,11 @@ bool ZeroHertzAdapterMode::ProcessKeyFrameRequest() {
return true; return true;
} }
// The next frame encoded will be a key frame. Reset quality convergence so we
// don't get idle repeats shortly after, because key frames need a lot of
// refinement frames.
ResetQualityConvergenceInfo();
// If we're not repeating, or we're repeating with non-idle duration, we will // If we're not repeating, or we're repeating with non-idle duration, we will
// very soon send out a frame and don't need a refresh frame. // very soon send out a frame and don't need a refresh frame.
if (!scheduled_repeat_.has_value() || !scheduled_repeat_->idle) { if (!scheduled_repeat_.has_value() || !scheduled_repeat_->idle) {
@ -399,7 +404,12 @@ bool ZeroHertzAdapterMode::ProcessKeyFrameRequest() {
// RTC_RUN_ON(&sequence_checker_) // RTC_RUN_ON(&sequence_checker_)
bool ZeroHertzAdapterMode::HasQualityConverged() const { bool ZeroHertzAdapterMode::HasQualityConverged() const {
// 1. Define ourselves as unconverged with no spatial layers configured. This
// is to keep short repeating until the layer configuration comes.
// 2. Unset layers implicitly imply that they're converged to support
// disabling layers when they're not needed.
const bool quality_converged = const bool quality_converged =
!layer_trackers_.empty() &&
absl::c_all_of(layer_trackers_, [](const SpatialLayerTracker& tracker) { absl::c_all_of(layer_trackers_, [](const SpatialLayerTracker& tracker) {
return tracker.quality_converged.value_or(true); return tracker.quality_converged.value_or(true);
}); });

View File

@ -37,6 +37,7 @@ using ::testing::ElementsAre;
using ::testing::Invoke; using ::testing::Invoke;
using ::testing::Mock; using ::testing::Mock;
using ::testing::Pair; using ::testing::Pair;
using ::testing::Values;
VideoFrame CreateFrame() { VideoFrame CreateFrame() {
return VideoFrame::Builder() return VideoFrame::Builder()
@ -379,105 +380,119 @@ TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestShortlyAfterFrame) {
EXPECT_FALSE(adapter->ProcessKeyFrameRequest()); EXPECT_FALSE(adapter->ProcessKeyFrameRequest());
} }
TEST(FrameCadenceAdapterTest, IgnoresKeyFrameRequestWhileShortRepeating) { class FrameCadenceAdapterSimulcastLayersParamTest
ZeroHertzFieldTrialEnabler enabler; : public ::testing::TestWithParam<int> {
MockCallback callback; public:
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0)); static constexpr int kMaxFpsHz = 8;
auto adapter = CreateAdapter(time_controller.GetClock()); static constexpr TimeDelta kMinFrameDelay =
adapter->Initialize(&callback); TimeDelta::Millis(1000 / kMaxFpsHz);
adapter->SetZeroHertzModeEnabled( static constexpr TimeDelta kIdleFrameDelay =
FrameCadenceAdapterInterface::ZeroHertzModeParams{ FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod;
/*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. FrameCadenceAdapterSimulcastLayersParamTest() {
EXPECT_CALL(callback, OnFrame).Times(8); adapter_->Initialize(&callback_);
time_controller.AdvanceTime(8 * kMinFrameDelay); adapter_->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz});
time_controller_.AdvanceTime(TimeDelta::Zero());
adapter_->SetZeroHertzModeEnabled(
FrameCadenceAdapterInterface::ZeroHertzModeParams{});
const int 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::Millis(0)};
const std::unique_ptr<FrameCadenceAdapterInterface> adapter_{
CreateAdapter(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(FrameCadenceAdapterTest, IgnoresKeyFrameRequestJustBeforeIdleRepeating) { TEST_P(FrameCadenceAdapterSimulcastLayersParamTest,
ZeroHertzFieldTrialEnabler enabler; IgnoresKeyFrameRequestWhileShortRepeating) {
MockCallback callback; // Plot:
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0)); // 1. 0 * kMinFrameDelay: Start unconverged. Frame -> adapter.
auto adapter = CreateAdapter(time_controller.GetClock()); // 2. 1 * kMinFrameDelay: Frame -> callback.
adapter->Initialize(&callback); // 3. 2 * kMinFrameDelay: 1st short repeat.
adapter->SetZeroHertzModeEnabled( // Since we're unconverged we assume the process continues.
FrameCadenceAdapterInterface::ZeroHertzModeParams{}); adapter_->OnFrame(CreateFrame());
constexpr int kMaxFpsHz = 10; time_controller_.AdvanceTime(2 * kMinFrameDelay);
constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(1000 / kMaxFpsHz); EXPECT_FALSE(adapter_->ProcessKeyFrameRequest());
constexpr TimeDelta kIdleFrameDelay =
FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod; // Expect short repeating as ususal.
adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz}); EXPECT_CALL(callback_, OnFrame).Times(8);
time_controller.AdvanceTime(TimeDelta::Zero()); time_controller_.AdvanceTime(8 * kMinFrameDelay);
adapter->OnFrame(CreateFrame()); }
time_controller.AdvanceTime(kIdleFrameDelay);
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 // We process the key frame request kMinFrameDelay before the first idle
// repeat should happen. The repeat should happen at T = kMinFrameDelay + // repeat should happen. The resulting repeats should happen spaced by
// kIdleFrameDelay as originally expected. // kMinFrameDelay before we get new convergence info.
EXPECT_FALSE(adapter->ProcessKeyFrameRequest()); EXPECT_FALSE(adapter_->ProcessKeyFrameRequest());
EXPECT_CALL(callback, OnFrame); EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz);
time_controller.AdvanceTime(kMinFrameDelay); time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay);
EXPECT_CALL(callback, OnFrame);
time_controller.AdvanceTime(kIdleFrameDelay);
} }
TEST(FrameCadenceAdapterTest, TEST_P(FrameCadenceAdapterSimulcastLayersParamTest,
IgnoresKeyFrameRequestShortRepeatsBeforeIdleRepeat) { IgnoresKeyFrameRequestShortRepeatsBeforeIdleRepeat) {
ZeroHertzFieldTrialEnabler enabler; // (Only for > 0 spatial layers as we assume not converged with 0 layers)
MockCallback callback; if (NumSpatialLayers() == 0)
GlobalSimulatedTimeController time_controller(Timestamp::Millis(0)); return;
auto adapter = CreateAdapter(time_controller.GetClock()); // Plot:
adapter->Initialize(&callback); // 1. 0 * kMinFrameDelay: Start converged. Frame -> adapter.
adapter->SetZeroHertzModeEnabled( // 2. 1 * kMinFrameDelay: Frame -> callback. New repeat scheduled at
FrameCadenceAdapterInterface::ZeroHertzModeParams{}); // (kMaxFpsHz + 1) * kMinFrameDelay.
constexpr int kMaxFpsHz = 10; // 3. 2 * kMinFrameDelay: Process keyframe.
constexpr TimeDelta kMinFrameDelay = TimeDelta::Millis(1000 / kMaxFpsHz); // 4. (2 + N) * kMinFrameDelay (1 <= N <= kMaxFpsHz): Short repeats due to not
constexpr TimeDelta kIdleFrameDelay = // converged.
FrameCadenceAdapterInterface::kZeroHertzIdleRepeatRatePeriod; for (int i = 0; i != NumSpatialLayers(); i++) {
adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz}); adapter_->UpdateLayerStatus(i, /*enabled=*/true);
time_controller.AdvanceTime(TimeDelta::Zero()); adapter_->UpdateLayerQualityConvergence(i, /*converged=*/true);
adapter->OnFrame(CreateFrame()); }
time_controller.AdvanceTime(2 * kMinFrameDelay); adapter_->OnFrame(CreateFrame());
time_controller_.AdvanceTime(2 * kMinFrameDelay);
// We process the key frame request 9 * kMinFrameDelay before the first idle // We process the key frame request (kMaxFpsHz - 1) * kMinFrameDelay before
// repeat should happen. We should get a short repeat in kMinFrameDelay and an // the first idle repeat should happen. The resulting repeats should happen
// idle repeat after that. // spaced kMinFrameDelay before we get new convergence info.
EXPECT_FALSE(adapter->ProcessKeyFrameRequest()); EXPECT_FALSE(adapter_->ProcessKeyFrameRequest());
EXPECT_CALL(callback, OnFrame); EXPECT_CALL(callback_, OnFrame).Times(kMaxFpsHz);
time_controller.AdvanceTime(kMinFrameDelay); time_controller_.AdvanceTime(kMaxFpsHz * kMinFrameDelay);
EXPECT_CALL(callback, OnFrame);
time_controller.AdvanceTime(kIdleFrameDelay);
} }
TEST(FrameCadenceAdapterTest, LayerReconfigurationResetsConvergenceInfo) { INSTANTIATE_TEST_SUITE_P(,
ZeroHertzFieldTrialEnabler enabler; FrameCadenceAdapterSimulcastLayersParamTest,
MockCallback callback; Values(0, 1, 2));
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);
adapter->OnConstraintsChanged(VideoTrackSourceConstraints{0, kMaxFpsHz});
time_controller.AdvanceTime(TimeDelta::Zero());
// Now setup 2 simulcast layers. The state should be unconverged.
adapter->SetZeroHertzModeEnabled(
FrameCadenceAdapterInterface::ZeroHertzModeParams{
/*num_simulcast_layers=*/2});
adapter->OnFrame(CreateFrame());
EXPECT_CALL(callback, OnFrame).Times(kMaxFpsHz);
time_controller.AdvanceTime(kMaxFpsHz * kMinFrameDelay);
}
class ZeroHertzLayerQualityConvergenceTest : public ::testing::Test { class ZeroHertzLayerQualityConvergenceTest : public ::testing::Test {
public: public: