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:
parent
e59fee87fb
commit
cb237f8822
@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user