blob: ca0a2ae8f3e3f17adb74c803cdb984a91afe3401 [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 "modules/video_coding/utility/encoder_speed_controller_impl.h"
#include <cstddef>
#include <memory>
#include <optional>
#include "api/units/time_delta.h"
#include "api/units/timestamp.h"
#include "api/video_codecs/encoder_speed_controller.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
namespace webrtc {
namespace {
// We want to increase the speed quickly in case we're overusing,
// but be slower to decrease speed and thus try using more resources.
// Constants governing how we adapt towards slower speed/higher quality.
constexpr double kSlowFilterAlpha = 0.1;
constexpr int kMinSamplesForDecreasedSpeed = 6;
constexpr double kReducedSpeedUtilizationFactorThreshold = 0.50;
// Constants governing how we adapt towards fasted speed/lower quality.
// Allows the utilization up to 95% for the fast reacting smaller window, but
// only up to 75% utilization for the slower and longer window.
constexpr double kFastFilterAlpha = 0.3;
constexpr int kMinSamplesForIncreasedSpeedFastFilter = 4;
constexpr int kMinSamplesForIncreasedSpeedSlowFilter = 10;
constexpr double kIncreasedSpeedUtilizationFactorThresholdSlowFilter = 0.75;
constexpr double kIncreasedSpeedUtilizationFactorThresholdFastFilter = 0.95;
// Exp filter constant for calculating the "current" QP.
constexpr double kQpFilterAlpha = 0.2;
// Keyframes usually take 4-5 times longer to encoder, but they are
// rare (relatively speaking) so divide the encode time by this
// factor in order to not over-react.
constexpr double kKeyframeEncodeTimeCompensator = 3.5;
// If the current speed index (or any faster) has a min PSNR gain factor,
// re-check every (N * psnr probing interval) that the gain is still there.
constexpr int kPsnrGainRecheckingFactor = 5;
} // namespace
EncoderSpeedControllerImpl::EncoderSpeedControllerImpl(
const Config& config,
TimeDelta start_frame_interval)
: config_(config),
frame_interval_(start_frame_interval),
current_speed_index_(config.start_speed_index),
num_samples_(0),
slow_filtered_encode_time_ms_(0),
fast_filtered_encode_time_ms_(0),
filtered_qp_(0),
last_psnr_probe_(Timestamp::MinusInfinity()) {}
std::unique_ptr<webrtc::EncoderSpeedController>
EncoderSpeedControllerImpl::Create(
const webrtc::EncoderSpeedController::Config& config,
TimeDelta start_frame_interval) {
if (config.speed_levels.empty()) {
RTC_LOG(LS_WARNING) << "EncoderSpeedController: No speed levels provided.";
return nullptr;
}
if (config.start_speed_index < 0 ||
static_cast<size_t>(config.start_speed_index) >=
config.speed_levels.size()) {
RTC_LOG(LS_WARNING) << "EncoderSpeedController: Invalid start_speed_index: "
<< config.start_speed_index;
return nullptr;
}
std::optional<int> last_seen_qp_limit = std::nullopt;
for (size_t i = 0; i < config.speed_levels.size(); ++i) {
const auto& speed_level = config.speed_levels[i];
if (speed_level.min_qp.has_value()) {
if (last_seen_qp_limit.has_value() &&
*speed_level.min_qp > *last_seen_qp_limit) {
RTC_LOG(LS_WARNING) << "EncoderSpeedController: Speed level " << i
<< " has min_qp value of " << *speed_level.min_qp
<< " which is higher than the previous limit of "
<< *last_seen_qp_limit;
return nullptr;
}
last_seen_qp_limit = speed_level.min_qp;
}
}
if (config.psnr_probing_settings) {
if (config.psnr_probing_settings->sampling_interval.IsInfinite() ||
config.psnr_probing_settings->sampling_interval.us() <= 0) {
RTC_LOG(LS_WARNING)
<< "EncoderSpeedController: Invalid PSNR sampling interval: "
<< config.psnr_probing_settings->sampling_interval;
return nullptr;
}
}
if (start_frame_interval.IsInfinite() || start_frame_interval.us() <= 0) {
RTC_LOG(LS_WARNING)
<< "EncoderSpeedController: Invalid start frame interval: "
<< start_frame_interval;
return nullptr;
}
return std::unique_ptr<EncoderSpeedControllerImpl>(
new EncoderSpeedControllerImpl(config, start_frame_interval));
}
void EncoderSpeedControllerImpl::ResetStats() {
num_samples_ = 0;
slow_filtered_encode_time_ms_ = 0;
fast_filtered_encode_time_ms_ = 0;
filtered_qp_ = 0;
if (last_psnr_gain_check_.has_value() &&
current_speed_index_ > last_psnr_gain_check_->speed_level) {
// We have moved to a faster speed than what the last PSNR gain check was
// performed at - no need for further re-checks of the gain until the speed
// is decreased again.
last_psnr_gain_check_.reset();
}
}
void EncoderSpeedControllerImpl::IncreaseSpeed() {
if (static_cast<size_t>(current_speed_index_) <
config_.speed_levels.size() - 1) {
RTC_LOG(LS_VERBOSE) << "EncoderSpeedController: Increasing speed from "
<< current_speed_index_ << " to "
<< current_speed_index_ + 1;
++current_speed_index_;
ResetStats();
}
}
void EncoderSpeedControllerImpl::DecreaseSpeed() {
if (current_speed_index_ > 0) {
RTC_LOG(LS_VERBOSE) << "EncoderSpeedController: Decreasing speed from "
<< current_speed_index_ << " to "
<< current_speed_index_ - 1;
--current_speed_index_;
ResetStats();
}
}
void EncoderSpeedControllerImpl::SetFrameInterval(TimeDelta frame_interval) {
frame_interval_ = frame_interval;
}
EncoderSpeedController::EncodeSettings
EncoderSpeedControllerImpl::GetEncodeSettings(
EncoderSpeedController::FrameEncodingInfo frame_info) {
RTC_CHECK(frame_interval_.IsFinite());
EncodeSettings settings;
settings.speed = config_.speed_levels[current_speed_index_]
.speeds[static_cast<int>(frame_info.reference_type)];
settings.baseline_comparison_speed = std::nullopt;
settings.calculate_psnr = false;
if (config_.psnr_probing_settings.has_value() &&
frame_info.timestamp.IsFinite() &&
(frame_info.reference_type == ReferenceClass::kMain ||
frame_info.reference_type == ReferenceClass::kKey) &&
!frame_info.is_repeat_frame) {
bool regular_sampling_due =
config_.psnr_probing_settings->mode ==
EncoderSpeedController::Config::PsnrProbingSettings::Mode::
kRegularBaseLayerSampling &&
frame_info.timestamp >=
(last_psnr_probe_ +
config_.psnr_probing_settings->sampling_interval);
bool probe_needed_for_speed_change =
ShouldDecreaseSpeedDisregardingPsnr() &&
PsnrProbeRequiredForNextSlowerSpeed();
bool should_recheck_psnr_gain = ShouldRecheckPsnrGain(frame_info.timestamp);
if (regular_sampling_due || probe_needed_for_speed_change ||
should_recheck_psnr_gain) {
Timestamp earlist_probe_time =
last_psnr_probe_.IsMinusInfinity()
? frame_info.timestamp
: (last_psnr_probe_ +
(config_.psnr_probing_settings->sampling_interval *
config_.psnr_probing_settings->average_base_layer_ratio));
if (frame_info.timestamp >= earlist_probe_time) {
std::optional<int> psnr_request_at_speed_index;
if (probe_needed_for_speed_change) {
RTC_DCHECK_GT(current_speed_index_, 0);
RTC_DCHECK(config_.speed_levels[current_speed_index_ - 1]
.min_psnr_gain.has_value());
psnr_request_at_speed_index = current_speed_index_ - 1;
} else if (should_recheck_psnr_gain) {
RTC_DCHECK(last_psnr_gain_check_.has_value());
psnr_request_at_speed_index = last_psnr_gain_check_->speed_level;
}
if (psnr_request_at_speed_index.has_value()) {
const Config::SpeedLevel& psnr_request =
config_.speed_levels[*psnr_request_at_speed_index];
settings.baseline_comparison_speed =
psnr_request.min_psnr_gain->baseline_speed;
settings.calculate_psnr = true;
// Potentially override the target speed for this frame if this is a
// PSNR re-checking event.
settings.speed =
psnr_request.speeds[static_cast<int>(ReferenceClass::kMain)];
last_psnr_probe_ = frame_info.timestamp;
RTC_LOG(LS_VERBOSE)
<< "EncoderSpeedController: Initiating PSNR probe "
"for speed "
<< settings.speed << " vs baseline "
<< *settings.baseline_comparison_speed << ".";
last_psnr_gain_check_ = {
.speed_level = *psnr_request_at_speed_index,
.timestamp = frame_info.timestamp,
};
} else if (regular_sampling_due) {
// Regular sampling, no speed change expected, just gather data.
settings.calculate_psnr = true;
last_psnr_probe_ = frame_info.timestamp;
}
}
}
}
return settings;
}
void EncoderSpeedControllerImpl::OnEncodedFrame(
EncoderSpeedController::EncodeResults results,
std::optional<EncodeResults> baseline_results) {
double encode_tims_ms = results.encode_time.us() / 1000.0;
if (results.frame_info.reference_type == ReferenceClass::kKey) {
encode_tims_ms /= kKeyframeEncodeTimeCompensator;
}
if (num_samples_ == 0) {
if (results.frame_info.is_repeat_frame) {
RTC_LOG(LS_WARNING) << "EncoderSpeedController: Try to start "
"measurements with a repeat frame.";
return;
}
slow_filtered_encode_time_ms_ = encode_tims_ms;
fast_filtered_encode_time_ms_ = encode_tims_ms;
filtered_qp_ = results.qp;
++num_samples_;
} else if (!results.frame_info.is_repeat_frame) {
// Add encode time measurement to filtered members. Don't count repeat
// frames as they have artificially low complexity due to zero movement.
++num_samples_;
slow_filtered_encode_time_ms_ =
(kSlowFilterAlpha * encode_tims_ms) +
((1 - kSlowFilterAlpha) * slow_filtered_encode_time_ms_);
fast_filtered_encode_time_ms_ =
(kFastFilterAlpha * encode_tims_ms) +
((1 - kFastFilterAlpha) * fast_filtered_encode_time_ms_);
filtered_qp_ =
(kQpFilterAlpha * results.qp) + ((1 - kQpFilterAlpha) * filtered_qp_);
}
if (baseline_results.has_value()) {
// Results from a PSNR probe have arrived!
last_psnr_probe_ = results.frame_info.timestamp;
RTC_LOG(LS_VERBOSE)
<< "EncoderSpeedController: PSNR Probe result: { baseline speed: "
<< baseline_results->speed
<< ", psnr = " << baseline_results->psnr.value_or(-1)
<< ", qp = " << baseline_results->qp
<< ", encode_time = " << baseline_results->encode_time.ms()
<< "ms } => { speed: " << results.speed
<< ", psnr = " << results.psnr.value_or(-1) << ", qp = " << results.qp
<< ", encode_time = " << results.encode_time.ms() << "ms }.";
}
if (ShouldIncreaseSpeed()) {
// Using too much resources or QP is good enough, try to increase the speed.
IncreaseSpeed();
} else if (current_speed_index_ > 0) {
if (baseline_results.has_value()) {
// Results from a PSNR probe have arrived!
const Config::SpeedLevel& next_speed =
config_.speed_levels[current_speed_index_ - 1];
if (!next_speed.min_psnr_gain.has_value()) {
RTC_LOG(LS_WARNING)
<< "EncoderSpeedController: PSNR probe result received but no "
"threshold set for next level. Ignoring.";
return;
}
const Config::SpeedLevel::PsnrComparison& psnr_settings =
*next_speed.min_psnr_gain;
if (results.speed !=
next_speed.speeds[static_cast<int>(ReferenceClass::kMain)] ||
baseline_results->speed != psnr_settings.baseline_speed) {
// Current speed settings have gone out of sync with requested probe,
// ignore results.
RTC_LOG(LS_WARNING)
<< "EncoderSpeedController: PSNR probe result received but speeds "
"are out of sync with next exptected. Ignoring.";
return;
}
if (!baseline_results->psnr.has_value() || !results.psnr.has_value()) {
RTC_LOG(LS_WARNING)
<< "EncoderSpeedController: PSNR probe result received, but no "
"actual PSNR measurements present. Ignoring.";
return;
}
double psnr_gain = *results.psnr - *baseline_results->psnr;
RTC_LOG(LS_VERBOSE) << "EncoderSpeedController: PSNR gain: " << psnr_gain;
if (psnr_gain >= psnr_settings.psnr_threshold) {
RTC_LOG(LS_VERBOSE)
<< "EncoderSpeedController: Decreasing speed due to PSNR gain.";
DecreaseSpeed();
} else {
RTC_LOG(LS_VERBOSE) << "EncoderSpeedController: Not decreasing speed, "
"PSNR gain too low.";
}
} else if (ShouldDecreaseSpeedDisregardingPsnr() &&
!PsnrProbeRequiredForNextSlowerSpeed()) {
// Headroom exists to reduce speed, and no PSNR requirement present.
DecreaseSpeed();
}
}
}
bool EncoderSpeedControllerImpl::ShouldIncreaseSpeed() const {
if (current_speed_index_ >=
static_cast<int>(config_.speed_levels.size()) - 1) {
// Already at max speed.
return false;
}
if (num_samples_ < kMinSamplesForIncreasedSpeedFastFilter) {
// Too few samples for fast filter.
return false;
}
if (fast_filtered_encode_time_ms_ >
(kIncreasedSpeedUtilizationFactorThresholdFastFilter *
frame_interval_.ms())) {
// Fast filter has detected overuse.
return true;
}
if (num_samples_ < kMinSamplesForIncreasedSpeedSlowFilter) {
// Too few samples for slow filter and filtered QP.
return false;
}
if (slow_filtered_encode_time_ms_ >
(kIncreasedSpeedUtilizationFactorThresholdSlowFilter *
frame_interval_.ms())) {
// Slow filter has detected overuse.
return true;
}
const Config::SpeedLevel& current_level =
config_.speed_levels[current_speed_index_];
if (current_level.min_qp.has_value() &&
filtered_qp_ < *current_level.min_qp) {
// Quality is already high, increase speed.
return true;
}
return false;
}
bool EncoderSpeedControllerImpl::ShouldDecreaseSpeedDisregardingPsnr() const {
if (current_speed_index_ <= 0) {
// Already at slowest speed.
return false;
}
if (num_samples_ < kMinSamplesForDecreasedSpeed) {
// Not enough samples collected.
return false;
}
if (slow_filtered_encode_time_ms_ >
(kReducedSpeedUtilizationFactorThreshold * frame_interval_.ms())) {
// Not enough headroom exists to reduce speed.
return false;
}
// Headroom exists, check conditions of the next slower speed.
const Config::SpeedLevel& next_speed_level =
config_.speed_levels[current_speed_index_ - 1];
if (!next_speed_level.min_qp.has_value() ||
filtered_qp_ >= *next_speed_level.min_qp) {
// No QP limit, or current QP high enough - allow slower speed.
return true;
}
return false;
}
// Returns true if the next slower speed requires a PSNR check.
bool EncoderSpeedControllerImpl::PsnrProbeRequiredForNextSlowerSpeed() const {
return current_speed_index_ > 0 &&
config_.speed_levels[current_speed_index_ - 1]
.min_psnr_gain.has_value();
}
// Returns true if the PSNR gain should be checked again to see if the quality
// benefit is still present. This method is only called once we have already
// moved to a speed requiring PSNR checks.
bool EncoderSpeedControllerImpl::ShouldRecheckPsnrGain(
Timestamp current_time) const {
if (current_speed_index_ <= 0 || !config_.psnr_probing_settings.has_value()) {
return false;
}
const Config::SpeedLevel& current_speed_level =
config_.speed_levels[current_speed_index_];
if (!current_speed_level.min_psnr_gain.has_value()) {
return false;
}
if (!last_psnr_gain_check_.has_value()) {
return false;
}
if (last_psnr_gain_check_->speed_level > current_speed_index_ ||
last_psnr_gain_check_->timestamp.IsInfinite()) {
return false;
}
TimeDelta rechecking_interval =
config_.psnr_probing_settings->sampling_interval *
kPsnrGainRecheckingFactor;
TimeDelta avg_base_layer_frame_interval =
frame_interval_ *
(1.0 - (1 / config_.psnr_probing_settings->average_base_layer_ratio));
return (current_time - last_psnr_gain_check_->timestamp) >=
rechecking_interval - avg_base_layer_frame_interval;
}
} // namespace webrtc