blob: 3e9beeedbee84715578ad2bb3cb7c21cc2406374 [file]
// Copyright (c) 2025 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 "modules/video_coding/codecs/av1/libaom_speed_config_factory.h"
#include <algorithm>
#include <cstddef>
#include <set>
#include <string>
#include "api/environment/environment.h"
#include "api/field_trials.h"
#include "api/units/time_delta.h"
#include "api/video_codecs/encoder_speed_controller.h"
#include "api/video_codecs/video_codec.h"
#include "rtc_base/checks.h"
#include "test/create_test_environment.h"
#include "test/gtest.h"
namespace webrtc {
// Helper to allow SpeedLevel to be used in a set.
bool operator<(const EncoderSpeedController::Config::SpeedLevel& lhs,
const EncoderSpeedController::Config::SpeedLevel& rhs) {
if (lhs.speeds != rhs.speeds) {
return lhs.speeds < rhs.speeds;
}
return lhs.min_qp < rhs.min_qp;
}
namespace {
using ::testing::Values;
// Test that the number of speed levels increases with complexity.
TEST(LibaomSpeedConfigFactoryTest, NumLevelsIncreaseWithComplexity) {
FieldTrials empty_trial("");
LibaomSpeedConfigFactory factory_low(VideoCodecComplexity::kComplexityLow,
VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config_low =
factory_low.GetSpeedConfig(640, 360, 3, empty_trial);
LibaomSpeedConfigFactory factory_normal(
VideoCodecComplexity::kComplexityNormal, VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config_normal =
factory_normal.GetSpeedConfig(640, 360, 3, empty_trial);
LibaomSpeedConfigFactory factory_high(VideoCodecComplexity::kComplexityHigh,
VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config_high =
factory_high.GetSpeedConfig(640, 360, 3, empty_trial);
LibaomSpeedConfigFactory factory_higher(
VideoCodecComplexity::kComplexityHigher, VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config_higher =
factory_higher.GetSpeedConfig(640, 360, 3, empty_trial);
LibaomSpeedConfigFactory factory_max(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config_max =
factory_max.GetSpeedConfig(640, 360, 3, empty_trial);
EXPECT_GE(config_normal.speed_levels.size(), config_low.speed_levels.size());
EXPECT_GE(config_high.speed_levels.size(), config_normal.speed_levels.size());
EXPECT_GE(config_higher.speed_levels.size(), config_high.speed_levels.size());
EXPECT_GE(config_max.speed_levels.size(), config_higher.speed_levels.size());
}
// Test that speeds within each level are monotonic.
TEST(LibaomSpeedConfigFactoryTest, SpeedsAreMonotonic) {
FieldTrials empty_trial("");
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config =
factory.GetSpeedConfig(1280, 720, 3, empty_trial);
for (const auto& level : config.speed_levels) {
// Lower reference class index means more important, so speed should be
// lower or equal.
EXPECT_LE(level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kKey)],
level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kMain)]);
EXPECT_LE(level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kMain)],
level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kIntermediate)]);
EXPECT_LE(level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kIntermediate)],
level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kNoneReference)]);
}
}
// Test that keyframe and base layer speeds between levels are monotonic.
TEST(LibaomSpeedConfigFactoryTest, KeyAndMainSpeedsIncreaseBetweenLevels) {
FieldTrials empty_trial("");
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config =
factory.GetSpeedConfig(1280, 720, 3, empty_trial);
for (size_t i = 0; i < config.speed_levels.size() - 1; ++i) {
const auto& current_level = config.speed_levels[i];
const auto& next_level = config.speed_levels[i + 1];
EXPECT_LE(current_level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kKey)],
next_level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kKey)]);
EXPECT_LE(current_level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kMain)],
next_level.speeds[static_cast<int>(
EncoderSpeedController::ReferenceClass::kMain)]);
}
}
struct ResolutionParams {
int width;
int height;
int expected_start_index_offset; // Offset from the last index
};
class LibaomSpeedConfigFactoryResolutionTest
: public ::testing::TestWithParam<ResolutionParams> {};
INSTANTIATE_TEST_SUITE_P(All,
LibaomSpeedConfigFactoryResolutionTest,
Values(ResolutionParams{320, 180, 1},
ResolutionParams{640, 360, 1},
ResolutionParams{1280, 720, 2},
ResolutionParams{1920, 1080, 3},
ResolutionParams{2560, 1440, 4}));
TEST_P(LibaomSpeedConfigFactoryResolutionTest, GetSpeedConfigStartSpeedIndex) {
const ResolutionParams& params = GetParam();
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
FieldTrials empty_trial("");
EncoderSpeedController::Config config =
factory.GetSpeedConfig(params.width, params.height, 3, empty_trial);
int expected_index =
std::max(0, static_cast<int>(config.speed_levels.size()) -
params.expected_start_index_offset);
EXPECT_EQ(config.start_speed_index, expected_index);
}
void CheckDistinctConfigs(LibaomSpeedConfigFactory& factory,
int num_temporal_layers) {
RTC_DCHECK_GT(num_temporal_layers, 0);
RTC_DCHECK_LE(num_temporal_layers, 3);
FieldTrials empty_trial("");
EncoderSpeedController::Config config =
factory.GetSpeedConfig(640, 360, num_temporal_layers, empty_trial);
std::set<EncoderSpeedController::Config::SpeedLevel> unique_configs(
config.speed_levels.begin(), config.speed_levels.end());
EXPECT_EQ(unique_configs.size(), config.speed_levels.size());
}
TEST(LibaomSpeedConfigFactoryTest, DistinctConfigs1Tl) {
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
CheckDistinctConfigs(factory, 1);
}
TEST(LibaomSpeedConfigFactoryTest, DistinctConfigs2Tl) {
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
CheckDistinctConfigs(factory, 2);
}
TEST(LibaomSpeedConfigFactoryTest, DistinctConfigs3Tl) {
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
CheckDistinctConfigs(factory, 3);
}
TEST(LibaomSpeedConfigFactoryTest, PropagatesPsnrExperimentSettings) {
const std::string kFieldTrials =
"WebRTC-Video-CalculatePsnr/Enabled,sampling_interval:3000ms/";
Environment env = CreateTestEnvironment({.field_trials = kFieldTrials});
LibaomSpeedConfigFactory factory(VideoCodecComplexity::kComplexityMax,
VideoCodecMode::kRealtimeVideo);
EncoderSpeedController::Config config =
factory.GetSpeedConfig(1280, 720, 2, env.field_trials());
ASSERT_TRUE(config.psnr_probing_settings.has_value());
EXPECT_EQ(config.psnr_probing_settings->mode,
EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kRegularBaseLayerSampling);
EXPECT_EQ(config.psnr_probing_settings->sampling_interval,
TimeDelta::Seconds(3));
EXPECT_EQ(config.psnr_probing_settings->average_base_layer_ratio, 0.5);
}
} // namespace
} // namespace webrtc