blob: 3f45f99e4dd2384907e816ab339fbe1ccde52910 [file] [log] [blame]
/*
* Copyright (c) 2024 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/quality_convergence_monitor.h"
#include <vector>
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
namespace webrtc {
namespace {
constexpr int kStaticQpThreshold = 13;
constexpr QualityConvergenceMonitor::Parameters kParametersOnlyStaticThreshold =
{.static_qp_threshold = kStaticQpThreshold,
.dynamic_detection_enabled = false};
constexpr QualityConvergenceMonitor::Parameters
kParametersWithDynamicDetection = {
.static_qp_threshold = kStaticQpThreshold,
.dynamic_detection_enabled = true,
.recent_window_length = 3,
.past_window_length = 9,
.dynamic_qp_threshold = 24};
// Test the basics of the algorithm.
TEST(QualityConvergenceMonitorAlgorithm, StaticThreshold) {
QualityConvergenceMonitor::Parameters p = kParametersOnlyStaticThreshold;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
ASSERT_TRUE(monitor);
for (bool is_refresh_frame : {false, true}) {
// Ramp down from 100. Not at target quality until qp <= static threshold.
for (int qp = 100; qp > p.static_qp_threshold; --qp) {
monitor->AddSample(qp, is_refresh_frame);
EXPECT_FALSE(monitor->AtTargetQuality());
}
monitor->AddSample(p.static_qp_threshold, is_refresh_frame);
EXPECT_TRUE(monitor->AtTargetQuality());
// 100 samples just above the threshold is not at target quality.
for (int i = 0; i < 100; ++i) {
monitor->AddSample(p.static_qp_threshold + 1, is_refresh_frame);
EXPECT_FALSE(monitor->AtTargetQuality());
}
}
}
TEST(QualityConvergenceMonitorAlgorithm,
StaticThresholdWithDynamicDetectionEnabled) {
QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
ASSERT_TRUE(monitor);
for (bool is_refresh_frame : {false, true}) {
// Clear buffer.
monitor->AddSample(-1, /*is_refresh_frame=*/false);
EXPECT_FALSE(monitor->AtTargetQuality());
// Ramp down from 100. Not at target quality until qp <= static threshold.
for (int qp = 100; qp > p.static_qp_threshold; --qp) {
monitor->AddSample(qp, is_refresh_frame);
EXPECT_FALSE(monitor->AtTargetQuality());
}
// A single frame at the static QP threshold is considered to be at target
// quality regardless of if it's a refresh frame or not.
monitor->AddSample(p.static_qp_threshold, is_refresh_frame);
EXPECT_TRUE(monitor->AtTargetQuality());
}
// 100 samples just above the threshold is not at target quality if it's not a
// refresh frame.
for (int i = 0; i < 100; ++i) {
monitor->AddSample(p.static_qp_threshold + 1, /*is_refresh_frame=*/false);
EXPECT_FALSE(monitor->AtTargetQuality());
}
}
TEST(QualityConvergenceMonitorAlgorithm, ConvergenceAtDynamicThreshold) {
QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
ASSERT_TRUE(monitor);
// `recent_window_length` + `past_window_length` refresh frames at the dynamic
// threshold must mean we're at target quality.
for (size_t i = 0; i < p.recent_window_length + p.past_window_length; ++i) {
monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
}
EXPECT_TRUE(monitor->AtTargetQuality());
}
TEST(QualityConvergenceMonitorAlgorithm, NoConvergenceAboveDynamicThreshold) {
QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
ASSERT_TRUE(monitor);
// 100 samples just above the threshold must imply that we're not at target
// quality.
for (int i = 0; i < 100; ++i) {
monitor->AddSample(p.dynamic_qp_threshold + 1, /*is_refresh_frame=*/true);
EXPECT_FALSE(monitor->AtTargetQuality());
}
}
TEST(QualityConvergenceMonitorAlgorithm,
MaintainAtTargetQualityForRefreshFrames) {
QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
ASSERT_TRUE(monitor);
// `recent_window_length` + `past_window_length` refresh frames at the dynamic
// threshold must mean we're at target quality.
for (size_t i = 0; i < p.recent_window_length + p.past_window_length; ++i) {
monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
}
EXPECT_TRUE(monitor->AtTargetQuality());
int qp = p.dynamic_qp_threshold;
for (int i = 0; i < 100; ++i) {
monitor->AddSample(qp++, /*is_refresh_frame=*/true);
EXPECT_TRUE(monitor->AtTargetQuality());
}
// Reset state for first frame that is not a refresh frame.
monitor->AddSample(qp, /*is_refresh_frame=*/false);
EXPECT_FALSE(monitor->AtTargetQuality());
}
// Test corner cases.
TEST(QualityConvergenceMonitorAlgorithm, SufficientData) {
QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
ASSERT_TRUE(monitor);
// Less than `recent_window_length + 1` refresh frame QP values at the dynamic
// threshold is not sufficient.
for (size_t i = 0; i < p.recent_window_length; ++i) {
monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
// Not sufficient data
EXPECT_FALSE(monitor->AtTargetQuality());
}
// However, `recent_window_length + 1` QP values are sufficient.
monitor->AddSample(p.dynamic_qp_threshold, /*is_refresh_frame=*/true);
EXPECT_TRUE(monitor->AtTargetQuality());
}
TEST(QualityConvergenceMonitorAlgorithm,
AtTargetIfQpPastLessThanOrEqualToQpRecent) {
QualityConvergenceMonitor::Parameters p = kParametersWithDynamicDetection;
p.past_window_length = 3;
p.recent_window_length = 3;
auto monitor = std::make_unique<QualityConvergenceMonitor>(p);
// Sequence for which QP_past > QP_recent.
for (int qp : {23, 21, 21, 21, 21, 22}) {
monitor->AddSample(qp, /*is_refresh_frame=*/true);
EXPECT_FALSE(monitor->AtTargetQuality());
}
// Reset QP window.
monitor->AddSample(-1, /*is_refresh_frame=*/false);
EXPECT_FALSE(monitor->AtTargetQuality());
// Sequence for which one additional sample of 22 will make QP_past ==
// QP_recent.
for (int qp : {22, 21, 21, 21, 21}) {
monitor->AddSample(qp, /*is_refresh_frame=*/true);
EXPECT_FALSE(monitor->AtTargetQuality());
}
monitor->AddSample(22, /*is_refresh_frame=*/true);
EXPECT_TRUE(monitor->AtTargetQuality());
// Reset QP window.
monitor->AddSample(-1, /*is_refresh_frame=*/false);
EXPECT_FALSE(monitor->AtTargetQuality());
// Sequence for which one additional sample of 23 will make QP_past <
// QP_recent.
for (int qp : {22, 21, 21, 21, 21}) {
monitor->AddSample(qp, /*is_refresh_frame=*/true);
EXPECT_FALSE(monitor->AtTargetQuality());
}
monitor->AddSample(23, /*is_refresh_frame=*/true);
EXPECT_TRUE(monitor->AtTargetQuality());
}
// Test default values and that they can be overridden with field trials.
TEST(QualityConvergenceMonitorSetup, DefaultParameters) {
test::ScopedKeyValueConfig field_trials;
auto monitor = QualityConvergenceMonitor::Create(
kStaticQpThreshold, kVideoCodecVP8, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters vp8_parameters =
monitor->GetParametersForTesting();
EXPECT_EQ(vp8_parameters.static_qp_threshold, kStaticQpThreshold);
EXPECT_FALSE(vp8_parameters.dynamic_detection_enabled);
monitor = QualityConvergenceMonitor::Create(kStaticQpThreshold,
kVideoCodecVP9, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters vp9_parameters =
monitor->GetParametersForTesting();
EXPECT_EQ(vp9_parameters.static_qp_threshold, kStaticQpThreshold);
EXPECT_TRUE(vp9_parameters.dynamic_detection_enabled);
EXPECT_EQ(vp9_parameters.dynamic_qp_threshold, 28); // 13 + 15.
EXPECT_EQ(vp9_parameters.recent_window_length, 6u);
EXPECT_EQ(vp9_parameters.past_window_length, 6u);
monitor = QualityConvergenceMonitor::Create(kStaticQpThreshold,
kVideoCodecAV1, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters av1_parameters =
monitor->GetParametersForTesting();
EXPECT_EQ(av1_parameters.static_qp_threshold, kStaticQpThreshold);
EXPECT_TRUE(av1_parameters.dynamic_detection_enabled);
EXPECT_EQ(av1_parameters.dynamic_qp_threshold, 28); // 13 + 15.
EXPECT_EQ(av1_parameters.recent_window_length, 6u);
EXPECT_EQ(av1_parameters.past_window_length, 6u);
}
TEST(QualityConvergenceMonitorSetup, OverrideVp8Parameters) {
test::ScopedKeyValueConfig field_trials(
"WebRTC-QCM-Dynamic-VP8/"
"enabled:1,alpha:0.08,recent_length:6,past_length:4/");
auto monitor = QualityConvergenceMonitor::Create(
kStaticQpThreshold, kVideoCodecVP8, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
EXPECT_EQ(p.static_qp_threshold, kStaticQpThreshold);
EXPECT_TRUE(p.dynamic_detection_enabled);
EXPECT_EQ(p.dynamic_qp_threshold, 23); // 13 + 10.
EXPECT_EQ(p.recent_window_length, 6u);
EXPECT_EQ(p.past_window_length, 4u);
}
TEST(QualityConvergenceMonitorSetup, OverrideVp9Parameters) {
test::ScopedKeyValueConfig field_trials(
"WebRTC-QCM-Dynamic-VP9/"
"enabled:1,alpha:0.08,recent_length:6,past_length:4/");
auto monitor = QualityConvergenceMonitor::Create(
kStaticQpThreshold, kVideoCodecVP9, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
EXPECT_EQ(p.static_qp_threshold, kStaticQpThreshold);
EXPECT_TRUE(p.dynamic_detection_enabled);
EXPECT_EQ(p.dynamic_qp_threshold, 33); // 13 + 20.
EXPECT_EQ(p.recent_window_length, 6u);
EXPECT_EQ(p.past_window_length, 4u);
}
TEST(QualityConvergenceMonitorSetup, OverrideAv1Parameters) {
test::ScopedKeyValueConfig field_trials(
"WebRTC-QCM-Dynamic-AV1/"
"enabled:1,alpha:0.10,recent_length:8,past_length:8/");
auto monitor = QualityConvergenceMonitor::Create(
kStaticQpThreshold, kVideoCodecAV1, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
EXPECT_EQ(p.static_qp_threshold, kStaticQpThreshold);
EXPECT_TRUE(p.dynamic_detection_enabled);
EXPECT_EQ(p.dynamic_qp_threshold, 38); // 13 + 25.
EXPECT_EQ(p.recent_window_length, 8u);
EXPECT_EQ(p.past_window_length, 8u);
}
TEST(QualityConvergenceMonitorSetup, DisableVp9Dynamic) {
test::ScopedKeyValueConfig field_trials("WebRTC-QCM-Dynamic-VP9/enabled:0/");
auto monitor = QualityConvergenceMonitor::Create(
kStaticQpThreshold, kVideoCodecVP9, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
EXPECT_FALSE(p.dynamic_detection_enabled);
}
TEST(QualityConvergenceMonitorSetup, DisableAv1Dynamic) {
test::ScopedKeyValueConfig field_trials("WebRTC-QCM-Dynamic-AV1/enabled:0/");
auto monitor = QualityConvergenceMonitor::Create(
kStaticQpThreshold, kVideoCodecAV1, field_trials);
ASSERT_TRUE(monitor);
QualityConvergenceMonitor::Parameters p = monitor->GetParametersForTesting();
EXPECT_FALSE(p.dynamic_detection_enabled);
}
} // namespace
} // namespace webrtc