blob: 622028d9cd00bddb920947e4e18141178020dba5 [file] [log] [blame]
/*
* 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 <optional>
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video_codecs/encoder_speed_controller.h"
#include "test/gmock.h"
#include "test/gtest.h"
using ::testing::Eq;
using ReferenceClass = webrtc::EncoderSpeedController::ReferenceClass;
namespace webrtc {
namespace {
constexpr TimeDelta kFrameInterval = TimeDelta::Seconds(1.0 / 30.0);
EncoderSpeedController::Config GetDefaultConfig() {
EncoderSpeedController::Config config;
config.speed_levels = {{.speeds = {5, 5, 5, 5}},
{.speeds = {6, 6, 6, 6}},
{.speeds = {7, 7, 7, 7}}};
config.start_speed_index = 1;
return config;
}
} // namespace
TEST(EncoderSpeedControllerTest, CreateFailsWithEmptySpeedLevels) {
EncoderSpeedController::Config config;
config.speed_levels = {};
EXPECT_EQ(EncoderSpeedController::Create(config, kFrameInterval), nullptr);
}
TEST(EncoderSpeedControllerTest, CreateFailsWithInvalidStartSpeedIndex) {
EncoderSpeedController::Config config;
config.speed_levels = {{.speeds = {5, 5, 5, 5}}};
config.start_speed_index = -1; // Invalid index
EXPECT_EQ(EncoderSpeedController::Create(config, kFrameInterval), nullptr);
config.start_speed_index = 1;
EXPECT_EQ(EncoderSpeedController::Create(config, kFrameInterval), nullptr);
}
TEST(EncoderSpeedControllerTest, CreateFailsWithInvalidFrameInterval) {
EncoderSpeedController::Config config = GetDefaultConfig();
EXPECT_EQ(EncoderSpeedController::Create(config, TimeDelta::Zero()), nullptr);
EXPECT_EQ(EncoderSpeedController::Create(config, TimeDelta::Millis(-1)),
nullptr);
EXPECT_EQ(EncoderSpeedController::Create(config, TimeDelta::PlusInfinity()),
nullptr);
}
TEST(EncoderSpeedControllerTest, GetEncodeSettingsBaseLayers) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.speed_levels[0].min_qp = 25; // Prevent dropping to speed 5 easily
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain, .timestamp = Timestamp::Zero()};
// Starts at index 1 (speed 6)
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 6);
// Simulate high encode time to increase speed
for (int i = 0; i < 10; ++i) {
controller->OnEncodedFrame({.encode_time = kFrameInterval * 0.90,
.qp = 30,
.frame_info = frame_info},
/*baseline_results=*/std::nullopt);
}
// Speed should increase to 7
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 7);
// Simulate low encode time to decrease speed
for (int i = 0; i < 20; ++i) {
controller->OnEncodedFrame({.encode_time = kFrameInterval * 0.10,
.qp = 20,
.frame_info = frame_info},
/*baseline_results=*/std::nullopt);
}
// Speed should decrease to 6
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 6);
}
TEST(EncoderSpeedControllerTest, GetEncodeSettingsKeyFrame) {
EncoderSpeedController::Config config = GetDefaultConfig();
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller
->GetEncodeSettings({.reference_type = ReferenceClass::kKey,
.timestamp = Timestamp::Zero()})
.speed,
6);
}
TEST(EncoderSpeedControllerTest, GetEncodeSettingsWithTemporalLayers) {
EncoderSpeedController::Config config;
config.speed_levels = {{.speeds = {5, 6, 7, 8}}, {.speeds = {9, 10, 11, 12}}};
config.start_speed_index = 0;
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EXPECT_EQ(controller
->GetEncodeSettings({.reference_type = ReferenceClass::kKey,
.timestamp = Timestamp::Zero()})
.speed,
5);
EXPECT_EQ(controller
->GetEncodeSettings({.reference_type = ReferenceClass::kMain,
.timestamp = Timestamp::Zero()})
.speed,
6);
EXPECT_EQ(
controller
->GetEncodeSettings({.reference_type = ReferenceClass::kIntermediate,
.timestamp = Timestamp::Zero()})
.speed,
7);
EXPECT_EQ(
controller
->GetEncodeSettings({.reference_type = ReferenceClass::kNoneReference,
.timestamp = Timestamp::Zero()})
.speed,
8);
}
TEST(EncoderSpeedControllerTest, StaysAtMaxSpeed) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.start_speed_index = 2; // Start at max speed
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain, .timestamp = Timestamp::Zero()};
for (int i = 0; i < 20; ++i) {
controller->OnEncodedFrame({.encode_time = kFrameInterval * 0.95,
.qp = 30,
.frame_info = frame_info},
/*baseline_results=*/std::nullopt);
}
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed,
7); // Still at max speed
}
TEST(EncoderSpeedControllerTest, StaysAtMinSpeed) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.start_speed_index = 0; // Start at min speed
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain, .timestamp = Timestamp::Zero()};
for (int i = 0; i < 20; ++i) {
controller->OnEncodedFrame({.speed = 5, .frame_info = frame_info},
/*baseline_results=*/std::nullopt);
}
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed,
5); // Still at min speed
}
TEST(EncoderSpeedControllerTest, IncreasesSpeedOnLowQp) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.speed_levels[1].min_qp = 20;
config.start_speed_index = 1;
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain, .timestamp = Timestamp::Zero()};
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 6);
// Simulate low QP, normal encode time
for (int i = 0; i < 20; ++i) {
controller->OnEncodedFrame({.encode_time = kFrameInterval * 0.60,
.qp = 10,
.frame_info = frame_info},
/*baseline_results=*/std::nullopt);
}
// Speed should increase to 7 due to low QP
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 7);
}
TEST(EncoderSpeedControllerTest, TriggersRegularPsnrSampling) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.psnr_probing_settings = {
.mode = EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kRegularBaseLayerSampling,
.sampling_interval = TimeDelta::Seconds(5)};
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain, .timestamp = Timestamp::Zero()};
// First frame should always trigger PSNR if configured.
EXPECT_TRUE(controller->GetEncodeSettings(frame_info).calculate_psnr);
// Complete the frame.
controller->OnEncodedFrame(
{.encode_time = kFrameInterval * 0.5, .qp = 30, .frame_info = frame_info},
/*baseline_results=*/std::nullopt);
// Next frame within interval should not trigger PSNR.
frame_info.timestamp += kFrameInterval;
EXPECT_FALSE(controller->GetEncodeSettings(frame_info).calculate_psnr);
// Advance to sampling interval.
frame_info.timestamp += config.psnr_probing_settings->sampling_interval;
EXPECT_TRUE(controller->GetEncodeSettings(frame_info).calculate_psnr);
}
TEST(EncoderSpeedControllerTest, TriggersPsnrProbeForSpeedChange) {
EncoderSpeedController::Config config = GetDefaultConfig();
// Default speed levels = {5, 6, 7}.
// To move from speed 6 to 5, we check speed 5's requirements.
config.speed_levels[0].min_psnr_gain = {
.baseline_speed = 6, // Compare against current speed (6)
.psnr_threshold = 1.0,
};
config.psnr_probing_settings = {
.mode = EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kOnlyWhenProbing,
.sampling_interval = TimeDelta::Seconds(1)};
config.start_speed_index = 1; // Start at speed 6.
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
// Initial state: Speed 6 (index 1).
EXPECT_EQ(controller
->GetEncodeSettings({.reference_type = ReferenceClass::kKey,
.timestamp = Timestamp::Zero()})
.speed,
6);
// Simulate low utilization to trigger speed decrease attempt.
// We need multiple samples to trigger the filter.
constexpr int kNumFrames = 10;
for (int i = 0; i < kNumFrames; ++i) {
controller->OnEncodedFrame(
{.encode_time = kFrameInterval * 0.1,
.qp = 20,
.frame_info = {.reference_type = ReferenceClass::kMain,
.timestamp =
Timestamp::Zero() + (i + 1) * kFrameInterval}},
/*baseline_results=*/std::nullopt);
}
// Next frame should be a probe.
// We expect it to try Speed 5.
EncoderSpeedController::EncodeSettings settings =
controller->GetEncodeSettings(
{.reference_type = ReferenceClass::kMain,
.timestamp = Timestamp::Zero() + kNumFrames * kFrameInterval});
EXPECT_EQ(settings.speed, 5);
EXPECT_TRUE(settings.calculate_psnr);
EXPECT_EQ(settings.baseline_comparison_speed, 6);
}
TEST(EncoderSpeedControllerTest, DecreasesSpeedOnSufficientPsnrGain) {
EncoderSpeedController::Config config = GetDefaultConfig();
// Default speed levels = {5, 6, 7}.
// To move to Speed 5, we need 1.0dB gain over Speed 6.
config.speed_levels[0].min_psnr_gain = {
.baseline_speed = 6,
.psnr_threshold = 1.0,
};
config.psnr_probing_settings = {
.mode = EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kOnlyWhenProbing,
.sampling_interval = TimeDelta::Seconds(1)};
config.start_speed_index = 1; // Start at speed 6.
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
// Trigger probe.
constexpr int kNumFrames = 10;
for (int i = 0; i < kNumFrames; ++i) {
controller->OnEncodedFrame(
{.encode_time = kFrameInterval * 0.1,
.qp = 20,
.frame_info = {.reference_type = ReferenceClass::kMain,
.timestamp =
Timestamp::Zero() + (i + 1) * kFrameInterval}},
/*baseline_results=*/std::nullopt);
}
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain,
.timestamp = Timestamp::Zero() + kNumFrames * kFrameInterval};
// Get settings (verify it's a probe).
EncoderSpeedController::EncodeSettings settings =
controller->GetEncodeSettings(frame_info);
ASSERT_TRUE(settings.baseline_comparison_speed.has_value());
EXPECT_EQ(settings.speed, 5);
EXPECT_EQ(settings.baseline_comparison_speed, 6);
// Feed probe results.
// Result (Speed 5): 37.0dB (Higher quality)
// Baseline (Speed 6): 35.0dB (Lower quality)
// Gain: 2.0dB >= 1.0dB threshold.
EncoderSpeedController::EncodeResults results = {
.speed = settings.speed, // 5
.encode_time = kFrameInterval * 0.1,
.qp = 20,
.psnr = 37.0,
.frame_info = frame_info};
EncoderSpeedController::EncodeResults baseline_results = {
.speed = *settings.baseline_comparison_speed, // 6
.encode_time = kFrameInterval * 0.1,
.qp = 20,
.psnr = 35.0,
.frame_info = frame_info};
controller->OnEncodedFrame(results, baseline_results);
// Speed should decrease to 5.
frame_info.timestamp += kFrameInterval;
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 5);
}
TEST(EncoderSpeedControllerTest, MaintainsSpeedOnInsufficientPsnrGain) {
EncoderSpeedController::Config config = GetDefaultConfig();
// Default speed levels = {5, 6, 7}.
// To move to Speed 5, we need 1.0dB gain over Speed 6.
config.speed_levels[0].min_psnr_gain = {
.baseline_speed = 6,
.psnr_threshold = 1.0,
};
config.psnr_probing_settings = {
.mode = EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kOnlyWhenProbing,
.sampling_interval = TimeDelta::Seconds(1)};
config.start_speed_index = 1; // Start at speed 6.
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
// Trigger probe.
constexpr int kNumFrames = 10;
for (int i = 0; i < kNumFrames; ++i) {
controller->OnEncodedFrame(
{.encode_time = kFrameInterval * 0.1,
.qp = 20,
.frame_info = {.reference_type = ReferenceClass::kMain,
.timestamp =
Timestamp::Zero() + (i + 1) * kFrameInterval}},
/*baseline_results=*/std::nullopt);
}
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain,
.timestamp = Timestamp::Zero() + kNumFrames * kFrameInterval};
// Get settings (verify it's a probe).
EncoderSpeedController::EncodeSettings settings =
controller->GetEncodeSettings(frame_info);
ASSERT_TRUE(settings.baseline_comparison_speed.has_value());
EXPECT_EQ(settings.speed, 5);
EXPECT_EQ(settings.baseline_comparison_speed, 6);
// Feed probe results.
// Result (Speed 5): 35.5dB
// Baseline (Speed 6): 35.0dB
// Gain: 0.5dB < 1.0dB threshold.
EncoderSpeedController::EncodeResults results = {
.speed = settings.speed,
.encode_time = kFrameInterval * 0.1,
.qp = 20,
.psnr = 35.5,
.frame_info = frame_info};
EncoderSpeedController::EncodeResults baseline_results = {
.speed = *settings.baseline_comparison_speed,
.encode_time = kFrameInterval * 0.1,
.qp = 20,
.psnr = 35.0,
.frame_info = frame_info};
controller->OnEncodedFrame(results, baseline_results);
// Speed should stay at 6 (reset to current index because gain was
// insufficient).
frame_info.timestamp += kFrameInterval;
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 6);
}
TEST(EncoderSpeedControllerTest, CreateFailsWithInvalidPsnrSamplingInterval) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.psnr_probing_settings = {
.mode = EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kRegularBaseLayerSampling,
.sampling_interval = TimeDelta::Zero()};
EXPECT_EQ(EncoderSpeedController::Create(config, kFrameInterval), nullptr);
config.psnr_probing_settings->sampling_interval = TimeDelta::PlusInfinity();
EXPECT_EQ(EncoderSpeedController::Create(config, kFrameInterval), nullptr);
}
TEST(EncoderSpeedControllerTest, OnEncodedFrameIgnoresResultWithMissingPsnr) {
EncoderSpeedController::Config config = GetDefaultConfig();
config.speed_levels[0].min_psnr_gain = {
.baseline_speed = 6,
.psnr_threshold = 1.0,
};
config.psnr_probing_settings = {
.mode = EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kOnlyWhenProbing,
.sampling_interval = TimeDelta::Seconds(1)};
config.start_speed_index = 1;
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain, .timestamp = Timestamp::Zero()};
// Trigger probe.
constexpr int kNumFrames = 10;
for (int i = 0; i < kNumFrames; ++i) {
controller->OnEncodedFrame(
{.encode_time = kFrameInterval * 0.1,
.qp = 20,
.frame_info = {.reference_type = ReferenceClass::kMain,
.timestamp =
Timestamp::Zero() + (i + 1) * kFrameInterval}},
/*baseline_results=*/std::nullopt);
}
// Get settings (verify it's a probe).
EncoderSpeedController::EncodeSettings settings =
controller->GetEncodeSettings(
{.reference_type = ReferenceClass::kMain,
.timestamp = Timestamp::Zero() + kNumFrames * kFrameInterval});
ASSERT_TRUE(settings.baseline_comparison_speed.has_value());
EXPECT_EQ(settings.speed, 5);
// Feed probe results with missing PSNR.
frame_info.timestamp = Timestamp::Zero() + kNumFrames * kFrameInterval;
EncoderSpeedController::EncodeResults results = {
.speed = settings.speed,
.encode_time = kFrameInterval * 0.1,
.qp = 20,
.psnr = std::nullopt, // Missing PSNR
.frame_info = frame_info};
EncoderSpeedController::EncodeResults baseline_results = {
.speed = *settings.baseline_comparison_speed,
.encode_time = kFrameInterval * 0.1,
.qp = 20,
.psnr = 35.0,
.frame_info = frame_info};
controller->OnEncodedFrame(results, baseline_results);
// Speed should stay at 6 (reset to current index because probe invalid).
frame_info.timestamp += kFrameInterval;
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 6);
}
TEST(EncoderSpeedControllerTest, WorksWithDefaultInfiniteTimestamp) {
EncoderSpeedController::Config config = GetDefaultConfig();
auto controller = EncoderSpeedController::Create(config, kFrameInterval);
ASSERT_NE(controller, nullptr);
// Default frame_info has timestamp = Timestamp::MinusInfinity().
EncoderSpeedController::FrameEncodingInfo frame_info = {
.reference_type = ReferenceClass::kMain};
EXPECT_TRUE(frame_info.timestamp.IsMinusInfinity());
// Should return a valid speed (start speed 6).
// PSNR calculation should be false because timestamp is not finite.
EncoderSpeedController::EncodeSettings settings =
controller->GetEncodeSettings(frame_info);
EXPECT_EQ(settings.speed, 6);
EXPECT_FALSE(settings.calculate_psnr);
// OnEncodedFrame should also handle it gracefully.
controller->OnEncodedFrame(
{.encode_time = kFrameInterval * 0.5, .qp = 30, .frame_info = frame_info},
/*baseline_results=*/std::nullopt);
// Speed should remain same.
EXPECT_EQ(controller->GetEncodeSettings(frame_info).speed, 6);
}
} // namespace webrtc