This CL moves GetAdaptUpTarget(), GetAdaptDownTarget() and ApplyAdaptationTarget() - and related code - to the VideoStreamAdapter. This includes pieces related to calculating how to adapt, including: - DegradationPreference - BalancedDegradationPreference - AdaptationRequest and last_adaptation_request_ - CanAdaptUpResolution() The VideoStreamAdapter's interface has changed: VideoSourceRestrictor methods are now hidden in favor of methods exposing AdaptationTarget. This CL also does some misc moves: - GetEncoderBitrateLimits is moved and renamed to VideoEncoder::EncoderInfo::GetEncoderBitrateLimitsForResolution. - EncoderSettings moved to a separate file. // For api/video_codecs/video_encoder.[cc/h] changes, which is the // moving of a function. TBR=sprang@webrtc.org Bug: webrtc:11393 Change-Id: Ie6bd8ef644ce927d7eca6ab90a0a7bcace682f3c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/169842 Reviewed-by: Henrik Boström <hbos@webrtc.org> Reviewed-by: Evan Shrubsole <eshr@google.com> Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org> Commit-Queue: Henrik Boström <hbos@webrtc.org> Cr-Commit-Position: refs/heads/master@{#30708}
815 lines
32 KiB
C++
815 lines
32 KiB
C++
/*
|
|
* Copyright 2019 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/overuse_frame_detector_resource_adaptation_module.h"
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <utility>
|
|
|
|
#include "absl/algorithm/container.h"
|
|
#include "absl/base/macros.h"
|
|
#include "api/task_queue/task_queue_base.h"
|
|
#include "api/video/video_source_interface.h"
|
|
#include "call/adaptation/resource.h"
|
|
#include "call/adaptation/video_source_restrictions.h"
|
|
#include "rtc_base/logging.h"
|
|
#include "rtc_base/numerics/safe_conversions.h"
|
|
#include "rtc_base/strings/string_builder.h"
|
|
#include "rtc_base/time_utils.h"
|
|
#include "video/video_stream_encoder.h"
|
|
|
|
namespace webrtc {
|
|
|
|
namespace {
|
|
|
|
bool IsResolutionScalingEnabled(DegradationPreference degradation_preference) {
|
|
return degradation_preference == DegradationPreference::MAINTAIN_FRAMERATE ||
|
|
degradation_preference == DegradationPreference::BALANCED;
|
|
}
|
|
|
|
bool IsFramerateScalingEnabled(DegradationPreference degradation_preference) {
|
|
return degradation_preference == DegradationPreference::MAINTAIN_RESOLUTION ||
|
|
degradation_preference == DegradationPreference::BALANCED;
|
|
}
|
|
|
|
// Returns modified restrictions where any constraints that don't apply to the
|
|
// degradation preference are cleared.
|
|
VideoSourceRestrictions ApplyDegradationPreference(
|
|
VideoSourceRestrictions source_restrictions,
|
|
DegradationPreference degradation_preference) {
|
|
switch (degradation_preference) {
|
|
case DegradationPreference::BALANCED:
|
|
break;
|
|
case DegradationPreference::MAINTAIN_FRAMERATE:
|
|
source_restrictions.set_max_frame_rate(absl::nullopt);
|
|
break;
|
|
case DegradationPreference::MAINTAIN_RESOLUTION:
|
|
source_restrictions.set_max_pixels_per_frame(absl::nullopt);
|
|
source_restrictions.set_target_pixels_per_frame(absl::nullopt);
|
|
break;
|
|
case DegradationPreference::DISABLED:
|
|
source_restrictions.set_max_pixels_per_frame(absl::nullopt);
|
|
source_restrictions.set_target_pixels_per_frame(absl::nullopt);
|
|
source_restrictions.set_max_frame_rate(absl::nullopt);
|
|
}
|
|
return source_restrictions;
|
|
}
|
|
|
|
// Returns AdaptationCounters where constraints that don't apply to the
|
|
// degredation preference are cleared. This behaviour must reflect that of
|
|
// ApplyDegredationPreference for SourceRestrictions. Any to that method must
|
|
// also change this one.
|
|
AdaptationCounters ApplyDegradationPreference(
|
|
AdaptationCounters counters,
|
|
DegradationPreference degradation_preference) {
|
|
switch (degradation_preference) {
|
|
case DegradationPreference::BALANCED:
|
|
break;
|
|
case DegradationPreference::MAINTAIN_FRAMERATE:
|
|
counters.fps_adaptations = 0;
|
|
break;
|
|
case DegradationPreference::MAINTAIN_RESOLUTION:
|
|
counters.resolution_adaptations = 0;
|
|
break;
|
|
case DegradationPreference::DISABLED:
|
|
counters.resolution_adaptations = 0;
|
|
counters.fps_adaptations = 0;
|
|
break;
|
|
default:
|
|
RTC_NOTREACHED();
|
|
}
|
|
return counters;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
class OveruseFrameDetectorResourceAdaptationModule::InitialFrameDropper {
|
|
public:
|
|
explicit InitialFrameDropper(QualityScalerResource* quality_scaler_resource)
|
|
: quality_scaler_resource_(quality_scaler_resource),
|
|
quality_scaler_settings_(QualityScalerSettings::ParseFromFieldTrials()),
|
|
has_seen_first_bwe_drop_(false),
|
|
set_start_bitrate_(DataRate::Zero()),
|
|
set_start_bitrate_time_ms_(0),
|
|
initial_framedrop_(0) {
|
|
RTC_DCHECK(quality_scaler_resource_);
|
|
}
|
|
|
|
// Output signal.
|
|
bool DropInitialFrames() const {
|
|
return initial_framedrop_ < kMaxInitialFramedrop;
|
|
}
|
|
|
|
// Input signals.
|
|
void SetStartBitrate(DataRate start_bitrate, int64_t now_ms) {
|
|
set_start_bitrate_ = start_bitrate;
|
|
set_start_bitrate_time_ms_ = now_ms;
|
|
}
|
|
|
|
void SetTargetBitrate(DataRate target_bitrate, int64_t now_ms) {
|
|
if (set_start_bitrate_ > DataRate::Zero() && !has_seen_first_bwe_drop_ &&
|
|
quality_scaler_resource_->is_started() &&
|
|
quality_scaler_settings_.InitialBitrateIntervalMs() &&
|
|
quality_scaler_settings_.InitialBitrateFactor()) {
|
|
int64_t diff_ms = now_ms - set_start_bitrate_time_ms_;
|
|
if (diff_ms <
|
|
quality_scaler_settings_.InitialBitrateIntervalMs().value() &&
|
|
(target_bitrate <
|
|
(set_start_bitrate_ *
|
|
quality_scaler_settings_.InitialBitrateFactor().value()))) {
|
|
RTC_LOG(LS_INFO) << "Reset initial_framedrop_. Start bitrate: "
|
|
<< set_start_bitrate_.bps()
|
|
<< ", target bitrate: " << target_bitrate.bps();
|
|
initial_framedrop_ = 0;
|
|
has_seen_first_bwe_drop_ = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void OnFrameDroppedDueToSize() { ++initial_framedrop_; }
|
|
|
|
void OnMaybeEncodeFrame() { initial_framedrop_ = kMaxInitialFramedrop; }
|
|
|
|
void OnQualityScalerSettingsUpdated() {
|
|
if (quality_scaler_resource_->is_started()) {
|
|
// Restart frame drops due to size.
|
|
initial_framedrop_ = 0;
|
|
} else {
|
|
// Quality scaling disabled so we shouldn't drop initial frames.
|
|
initial_framedrop_ = kMaxInitialFramedrop;
|
|
}
|
|
}
|
|
|
|
private:
|
|
// The maximum number of frames to drop at beginning of stream to try and
|
|
// achieve desired bitrate.
|
|
static const int kMaxInitialFramedrop = 4;
|
|
|
|
const QualityScalerResource* quality_scaler_resource_;
|
|
const QualityScalerSettings quality_scaler_settings_;
|
|
bool has_seen_first_bwe_drop_;
|
|
DataRate set_start_bitrate_;
|
|
int64_t set_start_bitrate_time_ms_;
|
|
// Counts how many frames we've dropped in the initial framedrop phase.
|
|
int initial_framedrop_;
|
|
};
|
|
|
|
OveruseFrameDetectorResourceAdaptationModule::
|
|
OveruseFrameDetectorResourceAdaptationModule(
|
|
Clock* clock,
|
|
bool experiment_cpu_load_estimator,
|
|
std::unique_ptr<OveruseFrameDetector> overuse_detector,
|
|
VideoStreamEncoderObserver* encoder_stats_observer,
|
|
ResourceAdaptationModuleListener* adaptation_listener)
|
|
: adaptation_listener_(adaptation_listener),
|
|
clock_(clock),
|
|
state_(State::kStopped),
|
|
experiment_cpu_load_estimator_(experiment_cpu_load_estimator),
|
|
has_input_video_(false),
|
|
degradation_preference_(DegradationPreference::DISABLED),
|
|
stream_adapter_(std::make_unique<VideoStreamAdapter>()),
|
|
encode_usage_resource_(
|
|
std::make_unique<EncodeUsageResource>(std::move(overuse_detector))),
|
|
quality_scaler_resource_(std::make_unique<QualityScalerResource>()),
|
|
initial_frame_dropper_(std::make_unique<InitialFrameDropper>(
|
|
quality_scaler_resource_.get())),
|
|
quality_scaling_experiment_enabled_(QualityScalingExperiment::Enabled()),
|
|
last_input_frame_size_(absl::nullopt),
|
|
target_frame_rate_(absl::nullopt),
|
|
encoder_target_bitrate_bps_(absl::nullopt),
|
|
quality_rampup_done_(false),
|
|
quality_rampup_experiment_(QualityRampupExperiment::ParseSettings()),
|
|
encoder_settings_(absl::nullopt),
|
|
encoder_stats_observer_(encoder_stats_observer),
|
|
active_counts_() {
|
|
RTC_DCHECK(adaptation_listener_);
|
|
RTC_DCHECK(encoder_stats_observer_);
|
|
AddResource(encode_usage_resource_.get(),
|
|
AdaptationObserverInterface::AdaptReason::kCpu);
|
|
AddResource(quality_scaler_resource_.get(),
|
|
AdaptationObserverInterface::AdaptReason::kQuality);
|
|
}
|
|
|
|
OveruseFrameDetectorResourceAdaptationModule::
|
|
~OveruseFrameDetectorResourceAdaptationModule() {
|
|
RTC_DCHECK_EQ(state_, State::kStopped);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::StartResourceAdaptation(
|
|
ResourceAdaptationModuleListener* adaptation_listener) {
|
|
RTC_DCHECK_EQ(state_, State::kStopped);
|
|
RTC_DCHECK(encoder_settings_.has_value());
|
|
// TODO(https://crbug.com/webrtc/11222): Rethink when the adaptation listener
|
|
// should be passed in and why. If resources are separated from modules then
|
|
// those resources may be started or stopped separately from the module.
|
|
RTC_DCHECK_EQ(adaptation_listener, adaptation_listener_);
|
|
encode_usage_resource_->StartCheckForOveruse(GetCpuOveruseOptions());
|
|
for (auto& resource_and_reason : resources_) {
|
|
resource_and_reason.resource->RegisterListener(this);
|
|
}
|
|
state_ = State::kStarted;
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::StopResourceAdaptation() {
|
|
encode_usage_resource_->StopCheckForOveruse();
|
|
quality_scaler_resource_->StopCheckForOveruse();
|
|
for (auto& resource_and_reason : resources_) {
|
|
resource_and_reason.resource->UnregisterListener(this);
|
|
}
|
|
state_ = State::kStopped;
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::AddResource(
|
|
Resource* resource) {
|
|
return AddResource(resource, AdaptationObserverInterface::AdaptReason::kCpu);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::AddResource(
|
|
Resource* resource,
|
|
AdaptationObserverInterface::AdaptReason reason) {
|
|
RTC_DCHECK(resource);
|
|
RTC_DCHECK(absl::c_find_if(resources_,
|
|
[resource](const ResourceAndReason& r) {
|
|
return r.resource == resource;
|
|
}) == resources_.end())
|
|
<< "Resource " << resource->name() << " already was inserted";
|
|
resources_.emplace_back(resource, reason);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::SetHasInputVideo(
|
|
bool has_input_video) {
|
|
// While false, OnResourceUnderuse() and OnResourceOveruse() are NO-OPS.
|
|
has_input_video_ = has_input_video;
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::SetDegradationPreference(
|
|
DegradationPreference degradation_preference) {
|
|
degradation_preference_ = degradation_preference;
|
|
if (stream_adapter_->SetDegradationPreference(degradation_preference) ==
|
|
VideoStreamAdapter::SetDegradationPreferenceResult::
|
|
kRestrictionsCleared) {
|
|
active_counts_.fill(AdaptationCounters());
|
|
}
|
|
MaybeUpdateVideoSourceRestrictions();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::SetEncoderSettings(
|
|
EncoderSettings encoder_settings) {
|
|
encoder_settings_ = std::move(encoder_settings);
|
|
|
|
quality_rampup_experiment_.SetMaxBitrate(
|
|
LastInputFrameSizeOrDefault(),
|
|
encoder_settings_->video_codec().maxBitrate);
|
|
MaybeUpdateTargetFrameRate();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::SetStartBitrate(
|
|
DataRate start_bitrate) {
|
|
if (!start_bitrate.IsZero())
|
|
encoder_target_bitrate_bps_ = start_bitrate.bps();
|
|
initial_frame_dropper_->SetStartBitrate(start_bitrate,
|
|
clock_->TimeInMicroseconds());
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::SetTargetBitrate(
|
|
DataRate target_bitrate) {
|
|
if (!target_bitrate.IsZero())
|
|
encoder_target_bitrate_bps_ = target_bitrate.bps();
|
|
initial_frame_dropper_->SetTargetBitrate(target_bitrate,
|
|
clock_->TimeInMilliseconds());
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::SetEncoderRates(
|
|
const VideoEncoder::RateControlParameters& encoder_rates) {
|
|
encoder_rates_ = encoder_rates;
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::
|
|
ResetVideoSourceRestrictions() {
|
|
stream_adapter_->ClearRestrictions();
|
|
active_counts_.fill(AdaptationCounters());
|
|
MaybeUpdateVideoSourceRestrictions();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnFrame(
|
|
const VideoFrame& frame) {
|
|
last_input_frame_size_ = frame.size();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnFrameDroppedDueToSize() {
|
|
AdaptationCounters counters_before = stream_adapter_->adaptation_counters();
|
|
OnResourceOveruse(AdaptationObserverInterface::AdaptReason::kQuality);
|
|
if (degradation_preference() == DegradationPreference::BALANCED &&
|
|
stream_adapter_->adaptation_counters().fps_adaptations >
|
|
counters_before.fps_adaptations) {
|
|
// Adapt framerate in same step as resolution.
|
|
OnResourceOveruse(AdaptationObserverInterface::AdaptReason::kQuality);
|
|
}
|
|
if (stream_adapter_->adaptation_counters().resolution_adaptations >
|
|
counters_before.resolution_adaptations) {
|
|
encoder_stats_observer_->OnInitialQualityResolutionAdaptDown();
|
|
}
|
|
initial_frame_dropper_->OnFrameDroppedDueToSize();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnEncodeStarted(
|
|
const VideoFrame& cropped_frame,
|
|
int64_t time_when_first_seen_us) {
|
|
encode_usage_resource_->OnEncodeStarted(cropped_frame,
|
|
time_when_first_seen_us);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnEncodeCompleted(
|
|
const EncodedImage& encoded_image,
|
|
int64_t time_sent_in_us,
|
|
absl::optional<int> encode_duration_us) {
|
|
// Inform |encode_usage_resource_| of the encode completed event.
|
|
uint32_t timestamp = encoded_image.Timestamp();
|
|
int64_t capture_time_us =
|
|
encoded_image.capture_time_ms_ * rtc::kNumMicrosecsPerMillisec;
|
|
encode_usage_resource_->OnEncodeCompleted(
|
|
timestamp, time_sent_in_us, capture_time_us, encode_duration_us);
|
|
// Inform |quality_scaler_resource_| of the encode completed event.
|
|
quality_scaler_resource_->OnEncodeCompleted(encoded_image, time_sent_in_us);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnFrameDropped(
|
|
EncodedImageCallback::DropReason reason) {
|
|
quality_scaler_resource_->OnFrameDropped(reason);
|
|
}
|
|
|
|
bool OveruseFrameDetectorResourceAdaptationModule::DropInitialFrames() const {
|
|
return initial_frame_dropper_->DropInitialFrames();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnMaybeEncodeFrame() {
|
|
initial_frame_dropper_->OnMaybeEncodeFrame();
|
|
MaybePerformQualityRampupExperiment();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::UpdateQualityScalerSettings(
|
|
absl::optional<VideoEncoder::QpThresholds> qp_thresholds) {
|
|
if (qp_thresholds.has_value()) {
|
|
quality_scaler_resource_->StopCheckForOveruse();
|
|
quality_scaler_resource_->StartCheckForOveruse(qp_thresholds.value());
|
|
} else {
|
|
quality_scaler_resource_->StopCheckForOveruse();
|
|
}
|
|
initial_frame_dropper_->OnQualityScalerSettingsUpdated();
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::ConfigureQualityScaler(
|
|
const VideoEncoder::EncoderInfo& encoder_info) {
|
|
const auto scaling_settings = encoder_info.scaling_settings;
|
|
const bool quality_scaling_allowed =
|
|
IsResolutionScalingEnabled(degradation_preference_) &&
|
|
scaling_settings.thresholds;
|
|
|
|
// TODO(https://crbug.com/webrtc/11222): Should this move to
|
|
// QualityScalerResource?
|
|
if (quality_scaling_allowed) {
|
|
if (!quality_scaler_resource_->is_started()) {
|
|
// Quality scaler has not already been configured.
|
|
|
|
// Use experimental thresholds if available.
|
|
absl::optional<VideoEncoder::QpThresholds> experimental_thresholds;
|
|
if (quality_scaling_experiment_enabled_) {
|
|
experimental_thresholds = QualityScalingExperiment::GetQpThresholds(
|
|
GetVideoCodecTypeOrGeneric(encoder_settings_));
|
|
}
|
|
UpdateQualityScalerSettings(experimental_thresholds
|
|
? *experimental_thresholds
|
|
: *(scaling_settings.thresholds));
|
|
}
|
|
} else {
|
|
UpdateQualityScalerSettings(absl::nullopt);
|
|
}
|
|
|
|
// Set the qp-thresholds to the balanced settings if balanced mode.
|
|
if (degradation_preference_ == DegradationPreference::BALANCED &&
|
|
quality_scaler_resource_->is_started()) {
|
|
absl::optional<VideoEncoder::QpThresholds> thresholds =
|
|
stream_adapter_->balanced_settings().GetQpThresholds(
|
|
GetVideoCodecTypeOrGeneric(encoder_settings_),
|
|
LastInputFrameSizeOrDefault());
|
|
if (thresholds) {
|
|
quality_scaler_resource_->SetQpThresholds(*thresholds);
|
|
}
|
|
}
|
|
|
|
encoder_stats_observer_->OnAdaptationChanged(
|
|
VideoStreamEncoderObserver::AdaptationReason::kNone,
|
|
GetActiveCounts(AdaptationObserverInterface::AdaptReason::kCpu),
|
|
GetActiveCounts(AdaptationObserverInterface::AdaptReason::kQuality));
|
|
}
|
|
|
|
ResourceListenerResponse
|
|
OveruseFrameDetectorResourceAdaptationModule::OnResourceUsageStateMeasured(
|
|
const Resource& resource) {
|
|
const auto& registered_resource =
|
|
absl::c_find_if(resources_, [&resource](const ResourceAndReason& r) {
|
|
return r.resource == &resource;
|
|
});
|
|
RTC_DCHECK(registered_resource != resources_.end())
|
|
<< resource.name() << " not found.";
|
|
|
|
const AdaptationObserverInterface::AdaptReason reason =
|
|
registered_resource->reason;
|
|
switch (resource.usage_state()) {
|
|
case ResourceUsageState::kOveruse:
|
|
return OnResourceOveruse(reason);
|
|
case ResourceUsageState::kStable:
|
|
// Do nothing.
|
|
//
|
|
// This module has two resources: |encoude_usage_resource_| and
|
|
// |quality_scaler_resource_|. A smarter adaptation module might not
|
|
// attempt to adapt up unless ALL resources were underused, but this
|
|
// module acts on each resource's measurement in isolation - without
|
|
// taking the current usage of any other resource into account.
|
|
return ResourceListenerResponse::kNothing;
|
|
case ResourceUsageState::kUnderuse:
|
|
OnResourceUnderuse(reason);
|
|
return ResourceListenerResponse::kNothing;
|
|
}
|
|
}
|
|
|
|
absl::optional<VideoStreamAdapter::AdaptationTarget>
|
|
OveruseFrameDetectorResourceAdaptationModule::GetAdaptUpTarget(
|
|
int input_pixels,
|
|
int input_fps,
|
|
AdaptationObserverInterface::AdaptReason reason) const {
|
|
// We can't adapt up if we're already at the highest setting.
|
|
// Note that this only includes counts relevant to the current degradation
|
|
// preference. e.g. we previously adapted resolution, now prefer adpating fps,
|
|
// only count the fps adaptations and not the previous resolution adaptations.
|
|
//
|
|
// TODO(https://crbug.com/webrtc/11394): Checking the counts for reason should
|
|
// be replaced with checking the overuse state of all resources. This is
|
|
// effectively trying to infer if the the Resource specified by |reason| is OK
|
|
// with adapting up by looking at active counters. If the relevant Resources
|
|
// simply told us this directly we wouldn't have to depend on stats counters
|
|
// to abort GetAdaptUpTarget().
|
|
int num_downgrades = ApplyDegradationPreference(active_counts_[reason],
|
|
degradation_preference_)
|
|
.Total();
|
|
RTC_DCHECK_GE(num_downgrades, 0);
|
|
if (num_downgrades == 0)
|
|
return absl::nullopt;
|
|
return stream_adapter_->GetAdaptUpTarget(
|
|
encoder_settings_, encoder_target_bitrate_bps_, GetVideoInputMode(),
|
|
input_pixels, input_fps, reason);
|
|
}
|
|
|
|
absl::optional<VideoStreamAdapter::AdaptationTarget>
|
|
OveruseFrameDetectorResourceAdaptationModule::GetAdaptDownTarget(
|
|
int input_pixels,
|
|
int input_fps,
|
|
int min_pixels_per_frame) const {
|
|
return stream_adapter_->GetAdaptDownTarget(
|
|
encoder_settings_, GetVideoInputMode(), input_pixels, input_fps,
|
|
min_pixels_per_frame, encoder_stats_observer_);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::ApplyAdaptationTarget(
|
|
const VideoStreamAdapter::AdaptationTarget& target,
|
|
int input_pixels,
|
|
int input_fps,
|
|
int min_pixels_per_frame) {
|
|
stream_adapter_->ApplyAdaptationTarget(target, GetVideoInputMode(),
|
|
input_pixels, input_fps,
|
|
min_pixels_per_frame);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnResourceUnderuse(
|
|
AdaptationObserverInterface::AdaptReason reason) {
|
|
int input_pixels = LastInputFrameSizeOrDefault();
|
|
int input_fps = encoder_stats_observer_->GetInputFrameRate();
|
|
int min_pixels_per_frame = MinPixelsPerFrame();
|
|
// Should we adapt, if so to what target?
|
|
absl::optional<VideoStreamAdapter::AdaptationTarget> target =
|
|
GetAdaptUpTarget(input_pixels, input_fps, reason);
|
|
if (!target.has_value())
|
|
return;
|
|
// Apply target.
|
|
ApplyAdaptationTarget(target.value(), input_pixels, input_fps,
|
|
min_pixels_per_frame);
|
|
// Update VideoSourceRestrictions based on adaptation. This also informs the
|
|
// |adaptation_listener_|.
|
|
MaybeUpdateVideoSourceRestrictions();
|
|
// Stats and logging.
|
|
UpdateAdaptationStats(reason);
|
|
RTC_LOG(LS_INFO) << ActiveCountsToString();
|
|
}
|
|
|
|
ResourceListenerResponse
|
|
OveruseFrameDetectorResourceAdaptationModule::OnResourceOveruse(
|
|
AdaptationObserverInterface::AdaptReason reason) {
|
|
if (!has_input_video_)
|
|
return ResourceListenerResponse::kQualityScalerShouldIncreaseFrequency;
|
|
int input_pixels = LastInputFrameSizeOrDefault();
|
|
int input_fps = encoder_stats_observer_->GetInputFrameRate();
|
|
int min_pixels_per_frame = MinPixelsPerFrame();
|
|
// Should we adapt, if so to what target?
|
|
absl::optional<VideoStreamAdapter::AdaptationTarget> target =
|
|
GetAdaptDownTarget(input_pixels, input_fps, min_pixels_per_frame);
|
|
if (!target.has_value())
|
|
return ResourceListenerResponse::kNothing;
|
|
// Apply target.
|
|
ApplyAdaptationTarget(target.value(), input_pixels, input_fps,
|
|
min_pixels_per_frame);
|
|
// Update VideoSourceRestrictions based on adaptation. This also informs the
|
|
// |adaptation_listener_|.
|
|
MaybeUpdateVideoSourceRestrictions();
|
|
// Stats and logging.
|
|
UpdateAdaptationStats(reason);
|
|
RTC_LOG(INFO) << ActiveCountsToString();
|
|
// In BALANCED, if requested FPS is higher or close to input FPS to the target
|
|
// we tell the QualityScaler to increase its frequency.
|
|
if (stream_adapter_->EffectiveDegradationPreference(GetVideoInputMode()) ==
|
|
DegradationPreference::BALANCED &&
|
|
target->action ==
|
|
VideoStreamAdapter::AdaptationAction::kDecreaseFrameRate) {
|
|
absl::optional<int> min_diff =
|
|
stream_adapter_->balanced_settings().MinFpsDiff(input_pixels);
|
|
if (min_diff && input_fps > 0) {
|
|
int fps_diff = input_fps - target->value;
|
|
if (fps_diff < min_diff.value()) {
|
|
return ResourceListenerResponse::kQualityScalerShouldIncreaseFrequency;
|
|
}
|
|
}
|
|
}
|
|
return ResourceListenerResponse::kNothing;
|
|
}
|
|
|
|
// TODO(pbos): Lower these thresholds (to closer to 100%) when we handle
|
|
// pipelining encoders better (multiple input frames before something comes
|
|
// out). This should effectively turn off CPU adaptations for systems that
|
|
// remotely cope with the load right now.
|
|
CpuOveruseOptions
|
|
OveruseFrameDetectorResourceAdaptationModule::GetCpuOveruseOptions() const {
|
|
// This is already ensured by the only caller of this method:
|
|
// StartResourceAdaptation().
|
|
RTC_DCHECK(encoder_settings_.has_value());
|
|
CpuOveruseOptions options;
|
|
// Hardware accelerated encoders are assumed to be pipelined; give them
|
|
// additional overuse time.
|
|
if (encoder_settings_->encoder_info().is_hardware_accelerated) {
|
|
options.low_encode_usage_threshold_percent = 150;
|
|
options.high_encode_usage_threshold_percent = 200;
|
|
}
|
|
if (experiment_cpu_load_estimator_) {
|
|
options.filter_time_ms = 5 * rtc::kNumMillisecsPerSec;
|
|
}
|
|
return options;
|
|
}
|
|
|
|
int OveruseFrameDetectorResourceAdaptationModule::LastInputFrameSizeOrDefault()
|
|
const {
|
|
// The dependency on this hardcoded resolution is inherited from old code,
|
|
// which used this resolution as a stand-in for not knowing the resolution
|
|
// yet.
|
|
// TODO(hbos): Can we simply DCHECK has_value() before usage instead? Having a
|
|
// DCHECK passed all the tests but adding it does change the requirements of
|
|
// this class (= not being allowed to call OnResourceUnderuse() or
|
|
// OnResourceOveruse() before OnFrame()) and deserves a standalone CL.
|
|
return last_input_frame_size_.value_or(
|
|
VideoStreamEncoder::kDefaultLastFrameInfoWidth *
|
|
VideoStreamEncoder::kDefaultLastFrameInfoHeight);
|
|
}
|
|
|
|
int OveruseFrameDetectorResourceAdaptationModule::MinPixelsPerFrame() const {
|
|
return encoder_settings_.has_value()
|
|
? encoder_settings_->encoder_info()
|
|
.scaling_settings.min_pixels_per_frame
|
|
: kDefaultMinPixelsPerFrame;
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::
|
|
MaybeUpdateVideoSourceRestrictions() {
|
|
VideoSourceRestrictions new_restrictions = ApplyDegradationPreference(
|
|
stream_adapter_->source_restrictions(), degradation_preference_);
|
|
if (video_source_restrictions_ != new_restrictions) {
|
|
video_source_restrictions_ = std::move(new_restrictions);
|
|
adaptation_listener_->OnVideoSourceRestrictionsUpdated(
|
|
video_source_restrictions_);
|
|
MaybeUpdateTargetFrameRate();
|
|
}
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::
|
|
MaybeUpdateTargetFrameRate() {
|
|
absl::optional<double> codec_max_frame_rate =
|
|
encoder_settings_.has_value()
|
|
? absl::optional<double>(
|
|
encoder_settings_->video_codec().maxFramerate)
|
|
: absl::nullopt;
|
|
// The current target framerate is the maximum frame rate as specified by
|
|
// the current codec configuration or any limit imposed by the adaptation
|
|
// module. This is used to make sure overuse detection doesn't needlessly
|
|
// trigger in low and/or variable framerate scenarios.
|
|
absl::optional<double> target_frame_rate =
|
|
ApplyDegradationPreference(stream_adapter_->source_restrictions(),
|
|
degradation_preference_)
|
|
.max_frame_rate();
|
|
if (!target_frame_rate.has_value() ||
|
|
(codec_max_frame_rate.has_value() &&
|
|
codec_max_frame_rate.value() < target_frame_rate.value())) {
|
|
target_frame_rate = codec_max_frame_rate;
|
|
}
|
|
encode_usage_resource_->SetTargetFrameRate(target_frame_rate);
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::OnAdaptationCountChanged(
|
|
const AdaptationCounters& adaptation_count,
|
|
AdaptationCounters* active_count,
|
|
AdaptationCounters* other_active) {
|
|
RTC_DCHECK(active_count);
|
|
RTC_DCHECK(other_active);
|
|
const int active_total = active_count->Total();
|
|
const int other_total = other_active->Total();
|
|
const AdaptationCounters prev_total = *active_count + *other_active;
|
|
const AdaptationCounters delta = adaptation_count - prev_total;
|
|
|
|
RTC_DCHECK_EQ(
|
|
std::abs(delta.resolution_adaptations) + std::abs(delta.fps_adaptations),
|
|
1)
|
|
<< "Adaptation took more than one step!";
|
|
|
|
if (delta.resolution_adaptations > 0) {
|
|
++active_count->resolution_adaptations;
|
|
} else if (delta.resolution_adaptations < 0) {
|
|
if (active_count->resolution_adaptations == 0) {
|
|
RTC_DCHECK_GT(active_count->fps_adaptations, 0) << "No downgrades left";
|
|
RTC_DCHECK_GT(other_active->resolution_adaptations, 0)
|
|
<< "No resolution adaptation to borrow from";
|
|
// Lend an fps adaptation to other and take one resolution adaptation.
|
|
--active_count->fps_adaptations;
|
|
++other_active->fps_adaptations;
|
|
--other_active->resolution_adaptations;
|
|
} else {
|
|
--active_count->resolution_adaptations;
|
|
}
|
|
}
|
|
if (delta.fps_adaptations > 0) {
|
|
++active_count->fps_adaptations;
|
|
} else if (delta.fps_adaptations < 0) {
|
|
if (active_count->fps_adaptations == 0) {
|
|
RTC_DCHECK_GT(active_count->resolution_adaptations, 0)
|
|
<< "No downgrades left";
|
|
RTC_DCHECK_GT(other_active->fps_adaptations, 0)
|
|
<< "No fps adaptation to borrow from";
|
|
// Lend a resolution adaptation to other and take one fps adaptation.
|
|
--active_count->resolution_adaptations;
|
|
++other_active->resolution_adaptations;
|
|
--other_active->fps_adaptations;
|
|
} else {
|
|
--active_count->fps_adaptations;
|
|
}
|
|
}
|
|
|
|
RTC_DCHECK(*active_count + *other_active == adaptation_count);
|
|
RTC_DCHECK_EQ(other_active->Total(), other_total);
|
|
RTC_DCHECK_EQ(active_count->Total(), active_total + delta.Total());
|
|
RTC_DCHECK_GE(active_count->resolution_adaptations, 0);
|
|
RTC_DCHECK_GE(active_count->fps_adaptations, 0);
|
|
RTC_DCHECK_GE(other_active->resolution_adaptations, 0);
|
|
RTC_DCHECK_GE(other_active->fps_adaptations, 0);
|
|
}
|
|
|
|
// TODO(nisse): Delete, once AdaptReason and AdaptationReason are merged.
|
|
void OveruseFrameDetectorResourceAdaptationModule::UpdateAdaptationStats(
|
|
AdaptationObserverInterface::AdaptReason reason) {
|
|
// Update active counts
|
|
AdaptationCounters& active_count = active_counts_[reason];
|
|
AdaptationCounters& other_active = active_counts_[(reason + 1) % 2];
|
|
const AdaptationCounters total_counts =
|
|
stream_adapter_->adaptation_counters();
|
|
|
|
OnAdaptationCountChanged(total_counts, &active_count, &other_active);
|
|
|
|
switch (reason) {
|
|
case AdaptationObserverInterface::AdaptReason::kCpu:
|
|
encoder_stats_observer_->OnAdaptationChanged(
|
|
VideoStreamEncoderObserver::AdaptationReason::kCpu,
|
|
GetActiveCounts(AdaptationObserverInterface::AdaptReason::kCpu),
|
|
GetActiveCounts(AdaptationObserverInterface::AdaptReason::kQuality));
|
|
break;
|
|
case AdaptationObserverInterface::AdaptReason::kQuality:
|
|
encoder_stats_observer_->OnAdaptationChanged(
|
|
VideoStreamEncoderObserver::AdaptationReason::kQuality,
|
|
GetActiveCounts(AdaptationObserverInterface::AdaptReason::kCpu),
|
|
GetActiveCounts(AdaptationObserverInterface::AdaptReason::kQuality));
|
|
break;
|
|
}
|
|
}
|
|
|
|
VideoStreamEncoderObserver::AdaptationSteps
|
|
OveruseFrameDetectorResourceAdaptationModule::GetActiveCounts(
|
|
AdaptationObserverInterface::AdaptReason reason) {
|
|
// TODO(https://crbug.com/webrtc/11392) Ideally this shuold be moved out of
|
|
// this class and into the encoder_stats_observer_.
|
|
const AdaptationCounters counters = active_counts_[reason];
|
|
|
|
VideoStreamEncoderObserver::AdaptationSteps counts =
|
|
VideoStreamEncoderObserver::AdaptationSteps();
|
|
counts.num_resolution_reductions = counters.resolution_adaptations;
|
|
counts.num_framerate_reductions = counters.fps_adaptations;
|
|
switch (reason) {
|
|
case AdaptationObserverInterface::AdaptReason::kCpu:
|
|
if (!IsFramerateScalingEnabled(degradation_preference_))
|
|
counts.num_framerate_reductions = absl::nullopt;
|
|
if (!IsResolutionScalingEnabled(degradation_preference_))
|
|
counts.num_resolution_reductions = absl::nullopt;
|
|
break;
|
|
case AdaptationObserverInterface::AdaptReason::kQuality:
|
|
if (!IsFramerateScalingEnabled(degradation_preference_) ||
|
|
!quality_scaler_resource_->is_started()) {
|
|
counts.num_framerate_reductions = absl::nullopt;
|
|
}
|
|
if (!IsResolutionScalingEnabled(degradation_preference_) ||
|
|
!quality_scaler_resource_->is_started()) {
|
|
counts.num_resolution_reductions = absl::nullopt;
|
|
}
|
|
break;
|
|
}
|
|
return counts;
|
|
}
|
|
|
|
VideoStreamAdapter::VideoInputMode
|
|
OveruseFrameDetectorResourceAdaptationModule::GetVideoInputMode() const {
|
|
if (!has_input_video_)
|
|
return VideoStreamAdapter::VideoInputMode::kNoVideo;
|
|
return (encoder_settings_.has_value() &&
|
|
encoder_settings_->encoder_config().content_type ==
|
|
VideoEncoderConfig::ContentType::kScreen)
|
|
? VideoStreamAdapter::VideoInputMode::kScreenshareVideo
|
|
: VideoStreamAdapter::VideoInputMode::kNormalVideo;
|
|
}
|
|
|
|
void OveruseFrameDetectorResourceAdaptationModule::
|
|
MaybePerformQualityRampupExperiment() {
|
|
if (!quality_scaler_resource_->is_started())
|
|
return;
|
|
|
|
if (quality_rampup_done_)
|
|
return;
|
|
|
|
int64_t now_ms = clock_->TimeInMilliseconds();
|
|
uint32_t bw_kbps = encoder_rates_.has_value()
|
|
? encoder_rates_.value().bandwidth_allocation.kbps()
|
|
: 0;
|
|
|
|
bool try_quality_rampup = false;
|
|
if (quality_rampup_experiment_.BwHigh(now_ms, bw_kbps)) {
|
|
// Verify that encoder is at max bitrate and the QP is low.
|
|
if (encoder_settings_ &&
|
|
encoder_target_bitrate_bps_.value_or(0) ==
|
|
encoder_settings_->video_codec().maxBitrate * 1000 &&
|
|
quality_scaler_resource_->QpFastFilterLow()) {
|
|
try_quality_rampup = true;
|
|
}
|
|
}
|
|
// TODO(https://crbug.com/webrtc/11392): See if we can rely on the total
|
|
// counts or the stats, and not the active counts.
|
|
const AdaptationCounters& qp_counts =
|
|
std::get<AdaptationObserverInterface::kQuality>(active_counts_);
|
|
const AdaptationCounters& cpu_counts =
|
|
std::get<AdaptationObserverInterface::kCpu>(active_counts_);
|
|
if (try_quality_rampup && qp_counts.resolution_adaptations > 0 &&
|
|
cpu_counts.Total() == 0) {
|
|
RTC_LOG(LS_INFO) << "Reset quality limitations.";
|
|
ResetVideoSourceRestrictions();
|
|
quality_rampup_done_ = true;
|
|
}
|
|
}
|
|
|
|
std::string OveruseFrameDetectorResourceAdaptationModule::ActiveCountsToString()
|
|
const {
|
|
rtc::StringBuilder ss;
|
|
|
|
ss << "Downgrade counts: fps: {";
|
|
for (size_t reason = 0; reason < active_counts_.size(); ++reason) {
|
|
ss << (reason ? " cpu" : "quality") << ":";
|
|
ss << active_counts_[reason].fps_adaptations;
|
|
}
|
|
ss << "}, resolution {";
|
|
for (size_t reason = 0; reason < active_counts_.size(); ++reason) {
|
|
ss << (reason ? " cpu" : "quality") << ":";
|
|
ss << active_counts_[reason].resolution_adaptations;
|
|
}
|
|
ss << "}";
|
|
|
|
return ss.Release();
|
|
}
|
|
} // namespace webrtc
|