Bug: chromium:328598314 Change-Id: I132b4c30f132ace2bbef6359edd994c1ad75c9ad Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/362620 Reviewed-by: Johannes Kron <kron@webrtc.org> Commit-Queue: Sergey Silkin <ssilkin@webrtc.org> Cr-Commit-Position: refs/heads/main@{#43035}
304 lines
12 KiB
C++
304 lines
12 KiB
C++
|
|
/*
|
|
* 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
|