| /* |
| * Copyright 2020 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/adaptation/video_stream_encoder_resource_manager.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #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_adaptation_reason.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" |
| |
| namespace webrtc { |
| |
| const int kDefaultInputPixelsWidth = 176; |
| const int kDefaultInputPixelsHeight = 144; |
| |
| 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; |
| } |
| |
| std::string ToString(VideoAdaptationReason reason) { |
| switch (reason) { |
| case VideoAdaptationReason::kQuality: |
| return "quality"; |
| case VideoAdaptationReason::kCpu: |
| return "cpu"; |
| } |
| } |
| |
| VideoAdaptationReason OtherReason(VideoAdaptationReason reason) { |
| switch (reason) { |
| case VideoAdaptationReason::kQuality: |
| return VideoAdaptationReason::kCpu; |
| case VideoAdaptationReason::kCpu: |
| return VideoAdaptationReason::kQuality; |
| } |
| } |
| |
| } // namespace |
| |
| class VideoStreamEncoderResourceManager::InitialFrameDropper { |
| public: |
| explicit InitialFrameDropper( |
| rtc::scoped_refptr<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 rtc::scoped_refptr<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_; |
| }; |
| |
| VideoStreamEncoderResourceManager::PreventAdaptUpDueToActiveCounts:: |
| PreventAdaptUpDueToActiveCounts(VideoStreamEncoderResourceManager* manager) |
| : rtc::RefCountedObject<Resource>(), |
| manager_(manager), |
| adaptation_processor_(nullptr) {} |
| |
| void VideoStreamEncoderResourceManager::PreventAdaptUpDueToActiveCounts:: |
| SetAdaptationProcessor( |
| ResourceAdaptationProcessorInterface* adaptation_processor) { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue()); |
| adaptation_processor_ = adaptation_processor; |
| } |
| |
| bool VideoStreamEncoderResourceManager::PreventAdaptUpDueToActiveCounts:: |
| IsAdaptationUpAllowed(const VideoStreamInputState& input_state, |
| const VideoSourceRestrictions& restrictions_before, |
| const VideoSourceRestrictions& restrictions_after, |
| rtc::scoped_refptr<Resource> reason_resource) const { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue()); |
| RTC_DCHECK(adaptation_processor_); |
| VideoAdaptationReason reason = |
| manager_->GetReasonFromResource(reason_resource); |
| { |
| // This is the same as |resource_adaptation_queue_|, but need to |
| // RTC_DCHECK_RUN_ON() both to avoid compiler error when accessing |
| // |manager_->active_counts_|. |
| RTC_DCHECK_RUN_ON(manager_->resource_adaptation_queue_); |
| // 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(hbos): Why would the reason matter? If a particular resource doesn't |
| // want us to go up it should prevent us from doing so itself rather than to |
| // have this catch-all reason- and stats-based approach. |
| int num_downgrades = |
| FilterVideoAdaptationCountersByDegradationPreference( |
| manager_->active_counts_[reason], |
| adaptation_processor_->effective_degradation_preference()) |
| .Total(); |
| RTC_DCHECK_GE(num_downgrades, 0); |
| return num_downgrades > 0; |
| } |
| } |
| |
| VideoStreamEncoderResourceManager:: |
| PreventIncreaseResolutionDueToBitrateResource:: |
| PreventIncreaseResolutionDueToBitrateResource( |
| VideoStreamEncoderResourceManager* manager) |
| : rtc::RefCountedObject<Resource>(), |
| manager_(manager), |
| encoder_settings_(absl::nullopt), |
| encoder_target_bitrate_bps_(absl::nullopt) {} |
| |
| void VideoStreamEncoderResourceManager:: |
| PreventIncreaseResolutionDueToBitrateResource::OnEncoderSettingsUpdated( |
| absl::optional<EncoderSettings> encoder_settings) { |
| RTC_DCHECK_RUN_ON(encoder_queue()); |
| resource_adaptation_queue()->PostTask( |
| [this_ref = |
| rtc::scoped_refptr<PreventIncreaseResolutionDueToBitrateResource>( |
| this), |
| encoder_settings] { |
| RTC_DCHECK_RUN_ON(this_ref->resource_adaptation_queue()); |
| this_ref->encoder_settings_ = std::move(encoder_settings); |
| }); |
| } |
| |
| void VideoStreamEncoderResourceManager:: |
| PreventIncreaseResolutionDueToBitrateResource:: |
| OnEncoderTargetBitrateUpdated( |
| absl::optional<uint32_t> encoder_target_bitrate_bps) { |
| RTC_DCHECK_RUN_ON(encoder_queue()); |
| resource_adaptation_queue()->PostTask( |
| [this_ref = |
| rtc::scoped_refptr<PreventIncreaseResolutionDueToBitrateResource>( |
| this), |
| encoder_target_bitrate_bps] { |
| RTC_DCHECK_RUN_ON(this_ref->resource_adaptation_queue()); |
| this_ref->encoder_target_bitrate_bps_ = encoder_target_bitrate_bps; |
| }); |
| } |
| |
| bool VideoStreamEncoderResourceManager:: |
| PreventIncreaseResolutionDueToBitrateResource::IsAdaptationUpAllowed( |
| const VideoStreamInputState& input_state, |
| const VideoSourceRestrictions& restrictions_before, |
| const VideoSourceRestrictions& restrictions_after, |
| rtc::scoped_refptr<Resource> reason_resource) const { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue()); |
| VideoAdaptationReason reason = |
| manager_->GetReasonFromResource(reason_resource); |
| // If increasing resolution due to kQuality, make sure bitrate limits are not |
| // violated. |
| // TODO(hbos): Why are we allowing violating bitrate constraints if adapting |
| // due to CPU? Shouldn't this condition be checked regardless of reason? |
| if (reason == VideoAdaptationReason::kQuality && |
| DidIncreaseResolution(restrictions_before, restrictions_after)) { |
| uint32_t bitrate_bps = encoder_target_bitrate_bps_.value_or(0); |
| absl::optional<VideoEncoder::ResolutionBitrateLimits> bitrate_limits = |
| encoder_settings_.has_value() |
| ? encoder_settings_->encoder_info() |
| .GetEncoderBitrateLimitsForResolution( |
| // Need some sort of expected resulting pixels to be used |
| // instead of unrestricted. |
| GetHigherResolutionThan( |
| input_state.frame_size_pixels().value())) |
| : absl::nullopt; |
| if (bitrate_limits.has_value() && bitrate_bps != 0) { |
| RTC_DCHECK_GE(bitrate_limits->frame_size_pixels, |
| input_state.frame_size_pixels().value()); |
| return bitrate_bps >= |
| static_cast<uint32_t>(bitrate_limits->min_start_bitrate_bps); |
| } |
| } |
| return true; |
| } |
| |
| VideoStreamEncoderResourceManager::PreventAdaptUpInBalancedResource:: |
| PreventAdaptUpInBalancedResource(VideoStreamEncoderResourceManager* manager) |
| : rtc::RefCountedObject<Resource>(), |
| manager_(manager), |
| adaptation_processor_(nullptr), |
| encoder_target_bitrate_bps_(absl::nullopt) {} |
| |
| void VideoStreamEncoderResourceManager::PreventAdaptUpInBalancedResource:: |
| SetAdaptationProcessor( |
| ResourceAdaptationProcessorInterface* adaptation_processor) { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue()); |
| adaptation_processor_ = adaptation_processor; |
| } |
| |
| void VideoStreamEncoderResourceManager::PreventAdaptUpInBalancedResource:: |
| OnEncoderTargetBitrateUpdated( |
| absl::optional<uint32_t> encoder_target_bitrate_bps) { |
| RTC_DCHECK_RUN_ON(encoder_queue()); |
| resource_adaptation_queue()->PostTask( |
| [this_ref = rtc::scoped_refptr<PreventAdaptUpInBalancedResource>(this), |
| encoder_target_bitrate_bps] { |
| RTC_DCHECK_RUN_ON(this_ref->resource_adaptation_queue()); |
| this_ref->encoder_target_bitrate_bps_ = encoder_target_bitrate_bps; |
| }); |
| } |
| |
| bool VideoStreamEncoderResourceManager::PreventAdaptUpInBalancedResource:: |
| IsAdaptationUpAllowed(const VideoStreamInputState& input_state, |
| const VideoSourceRestrictions& restrictions_before, |
| const VideoSourceRestrictions& restrictions_after, |
| rtc::scoped_refptr<Resource> reason_resource) const { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue()); |
| RTC_DCHECK(adaptation_processor_); |
| VideoAdaptationReason reason = |
| manager_->GetReasonFromResource(reason_resource); |
| // Don't adapt if BalancedDegradationSettings applies and determines this will |
| // exceed bitrate constraints. |
| // TODO(hbos): Why are we allowing violating balanced settings if adapting due |
| // CPU? Shouldn't this condition be checked regardless of reason? |
| if (reason == VideoAdaptationReason::kQuality && |
| adaptation_processor_->effective_degradation_preference() == |
| DegradationPreference::BALANCED && |
| !manager_->balanced_settings_.CanAdaptUp( |
| input_state.video_codec_type(), |
| input_state.frame_size_pixels().value(), |
| encoder_target_bitrate_bps_.value_or(0))) { |
| return false; |
| } |
| if (reason == VideoAdaptationReason::kQuality && |
| DidIncreaseResolution(restrictions_before, restrictions_after) && |
| !manager_->balanced_settings_.CanAdaptUpResolution( |
| input_state.video_codec_type(), |
| input_state.frame_size_pixels().value(), |
| encoder_target_bitrate_bps_.value_or(0))) { |
| return false; |
| } |
| return true; |
| } |
| |
| VideoStreamEncoderResourceManager::VideoStreamEncoderResourceManager( |
| VideoStreamInputStateProvider* input_state_provider, |
| VideoStreamEncoderObserver* encoder_stats_observer, |
| Clock* clock, |
| bool experiment_cpu_load_estimator, |
| std::unique_ptr<OveruseFrameDetector> overuse_detector) |
| : prevent_adapt_up_due_to_active_counts_( |
| new PreventAdaptUpDueToActiveCounts(this)), |
| prevent_increase_resolution_due_to_bitrate_resource_( |
| new PreventIncreaseResolutionDueToBitrateResource(this)), |
| prevent_adapt_up_in_balanced_resource_( |
| new PreventAdaptUpInBalancedResource(this)), |
| encode_usage_resource_( |
| new EncodeUsageResource(std::move(overuse_detector))), |
| quality_scaler_resource_(new QualityScalerResource()), |
| encoder_queue_(nullptr), |
| resource_adaptation_queue_(nullptr), |
| input_state_provider_(input_state_provider), |
| adaptation_processor_(nullptr), |
| encoder_stats_observer_(encoder_stats_observer), |
| degradation_preference_(DegradationPreference::DISABLED), |
| video_source_restrictions_(), |
| clock_(clock), |
| experiment_cpu_load_estimator_(experiment_cpu_load_estimator), |
| initial_frame_dropper_( |
| std::make_unique<InitialFrameDropper>(quality_scaler_resource_)), |
| quality_scaling_experiment_enabled_(QualityScalingExperiment::Enabled()), |
| encoder_target_bitrate_bps_(absl::nullopt), |
| quality_rampup_done_(false), |
| quality_rampup_experiment_(QualityRampupExperiment::ParseSettings()), |
| encoder_settings_(absl::nullopt), |
| active_counts_() { |
| RTC_DCHECK(encoder_stats_observer_); |
| MapResourceToReason(prevent_adapt_up_due_to_active_counts_, |
| VideoAdaptationReason::kQuality); |
| MapResourceToReason(prevent_increase_resolution_due_to_bitrate_resource_, |
| VideoAdaptationReason::kQuality); |
| MapResourceToReason(prevent_adapt_up_in_balanced_resource_, |
| VideoAdaptationReason::kQuality); |
| MapResourceToReason(encode_usage_resource_, VideoAdaptationReason::kCpu); |
| MapResourceToReason(quality_scaler_resource_, |
| VideoAdaptationReason::kQuality); |
| } |
| |
| VideoStreamEncoderResourceManager::~VideoStreamEncoderResourceManager() {} |
| |
| void VideoStreamEncoderResourceManager::Initialize( |
| rtc::TaskQueue* encoder_queue, |
| rtc::TaskQueue* resource_adaptation_queue) { |
| RTC_DCHECK(!encoder_queue_); |
| RTC_DCHECK(encoder_queue); |
| RTC_DCHECK(!resource_adaptation_queue_); |
| RTC_DCHECK(resource_adaptation_queue); |
| encoder_queue_ = encoder_queue; |
| resource_adaptation_queue_ = resource_adaptation_queue; |
| prevent_adapt_up_due_to_active_counts_->Initialize( |
| encoder_queue_, resource_adaptation_queue_); |
| prevent_increase_resolution_due_to_bitrate_resource_->Initialize( |
| encoder_queue_, resource_adaptation_queue_); |
| prevent_adapt_up_in_balanced_resource_->Initialize( |
| encoder_queue_, resource_adaptation_queue_); |
| encode_usage_resource_->Initialize(encoder_queue_, |
| resource_adaptation_queue_); |
| quality_scaler_resource_->Initialize(encoder_queue_, |
| resource_adaptation_queue_); |
| } |
| |
| void VideoStreamEncoderResourceManager::SetAdaptationProcessor( |
| ResourceAdaptationProcessorInterface* adaptation_processor) { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| adaptation_processor_ = adaptation_processor; |
| prevent_adapt_up_due_to_active_counts_->SetAdaptationProcessor( |
| adaptation_processor); |
| prevent_adapt_up_in_balanced_resource_->SetAdaptationProcessor( |
| adaptation_processor); |
| quality_scaler_resource_->SetAdaptationProcessor(adaptation_processor); |
| } |
| |
| void VideoStreamEncoderResourceManager::SetDegradationPreferences( |
| DegradationPreference degradation_preference) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| degradation_preference_ = degradation_preference; |
| UpdateStatsAdaptationSettings(); |
| } |
| |
| DegradationPreference |
| VideoStreamEncoderResourceManager::degradation_preference() const { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| return degradation_preference_; |
| } |
| |
| void VideoStreamEncoderResourceManager::StartEncodeUsageResource() { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| RTC_DCHECK(!encode_usage_resource_->is_started()); |
| RTC_DCHECK(encoder_settings_.has_value()); |
| encode_usage_resource_->StartCheckForOveruse(GetCpuOveruseOptions()); |
| } |
| |
| void VideoStreamEncoderResourceManager::StopManagedResources() { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| encode_usage_resource_->StopCheckForOveruse(); |
| quality_scaler_resource_->StopCheckForOveruse(); |
| } |
| |
| void VideoStreamEncoderResourceManager::MapResourceToReason( |
| rtc::scoped_refptr<Resource> resource, |
| VideoAdaptationReason reason) { |
| rtc::CritScope crit(&resource_lock_); |
| 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); |
| } |
| |
| std::vector<rtc::scoped_refptr<Resource>> |
| VideoStreamEncoderResourceManager::MappedResources() const { |
| rtc::CritScope crit(&resource_lock_); |
| std::vector<rtc::scoped_refptr<Resource>> resources; |
| for (auto const& resource_and_reason : resources_) { |
| resources.push_back(resource_and_reason.resource); |
| } |
| return resources; |
| } |
| |
| rtc::scoped_refptr<QualityScalerResource> |
| VideoStreamEncoderResourceManager::quality_scaler_resource_for_testing() { |
| rtc::CritScope crit(&resource_lock_); |
| return quality_scaler_resource_; |
| } |
| |
| void VideoStreamEncoderResourceManager::SetEncoderSettings( |
| EncoderSettings encoder_settings) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| encoder_settings_ = std::move(encoder_settings); |
| prevent_increase_resolution_due_to_bitrate_resource_ |
| ->OnEncoderSettingsUpdated(encoder_settings_); |
| |
| quality_rampup_experiment_.SetMaxBitrate( |
| LastInputFrameSizeOrDefault(), |
| encoder_settings_->video_codec().maxBitrate); |
| MaybeUpdateTargetFrameRate(); |
| } |
| |
| void VideoStreamEncoderResourceManager::SetStartBitrate( |
| DataRate start_bitrate) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| if (!start_bitrate.IsZero()) { |
| encoder_target_bitrate_bps_ = start_bitrate.bps(); |
| prevent_increase_resolution_due_to_bitrate_resource_ |
| ->OnEncoderTargetBitrateUpdated(encoder_target_bitrate_bps_); |
| prevent_adapt_up_in_balanced_resource_->OnEncoderTargetBitrateUpdated( |
| encoder_target_bitrate_bps_); |
| } |
| initial_frame_dropper_->SetStartBitrate(start_bitrate, |
| clock_->TimeInMicroseconds()); |
| } |
| |
| void VideoStreamEncoderResourceManager::SetTargetBitrate( |
| DataRate target_bitrate) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| if (!target_bitrate.IsZero()) { |
| encoder_target_bitrate_bps_ = target_bitrate.bps(); |
| prevent_increase_resolution_due_to_bitrate_resource_ |
| ->OnEncoderTargetBitrateUpdated(encoder_target_bitrate_bps_); |
| prevent_adapt_up_in_balanced_resource_->OnEncoderTargetBitrateUpdated( |
| encoder_target_bitrate_bps_); |
| } |
| initial_frame_dropper_->SetTargetBitrate(target_bitrate, |
| clock_->TimeInMilliseconds()); |
| } |
| |
| void VideoStreamEncoderResourceManager::SetEncoderRates( |
| const VideoEncoder::RateControlParameters& encoder_rates) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| encoder_rates_ = encoder_rates; |
| } |
| |
| void VideoStreamEncoderResourceManager::OnFrameDroppedDueToSize() { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| // The VideoStreamEncoder makes the manager outlive the adaptation queue. This |
| // means that if the task gets executed, |this| has not been freed yet. |
| // TODO(https://crbug.com/webrtc/11565): When the manager no longer outlives |
| // the adaptation queue, add logic to prevent use-after-free on |this|. |
| resource_adaptation_queue_->PostTask([this] { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| if (!adaptation_processor_) { |
| // The processor nulled before this task had a chance to execute. This |
| // happens if the processor is destroyed. No action needed. |
| return; |
| } |
| adaptation_processor_->TriggerAdaptationDueToFrameDroppedDueToSize( |
| quality_scaler_resource_); |
| }); |
| initial_frame_dropper_->OnFrameDroppedDueToSize(); |
| } |
| |
| void VideoStreamEncoderResourceManager::OnEncodeStarted( |
| const VideoFrame& cropped_frame, |
| int64_t time_when_first_seen_us) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| encode_usage_resource_->OnEncodeStarted(cropped_frame, |
| time_when_first_seen_us); |
| } |
| |
| void VideoStreamEncoderResourceManager::OnEncodeCompleted( |
| const EncodedImage& encoded_image, |
| int64_t time_sent_in_us, |
| absl::optional<int> encode_duration_us) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| // 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 VideoStreamEncoderResourceManager::OnFrameDropped( |
| EncodedImageCallback::DropReason reason) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| quality_scaler_resource_->OnFrameDropped(reason); |
| } |
| |
| bool VideoStreamEncoderResourceManager::DropInitialFrames() const { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| return initial_frame_dropper_->DropInitialFrames(); |
| } |
| |
| void VideoStreamEncoderResourceManager::OnMaybeEncodeFrame() { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| initial_frame_dropper_->OnMaybeEncodeFrame(); |
| MaybePerformQualityRampupExperiment(); |
| } |
| |
| void VideoStreamEncoderResourceManager::UpdateQualityScalerSettings( |
| absl::optional<VideoEncoder::QpThresholds> qp_thresholds) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| 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 VideoStreamEncoderResourceManager::ConfigureQualityScaler( |
| const VideoEncoder::EncoderInfo& encoder_info) { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| 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 = |
| balanced_settings_.GetQpThresholds( |
| GetVideoCodecTypeOrGeneric(encoder_settings_), |
| LastInputFrameSizeOrDefault()); |
| if (thresholds) { |
| quality_scaler_resource_->SetQpThresholds(*thresholds); |
| } |
| } |
| UpdateStatsAdaptationSettings(); |
| } |
| |
| VideoAdaptationReason VideoStreamEncoderResourceManager::GetReasonFromResource( |
| rtc::scoped_refptr<Resource> resource) const { |
| rtc::CritScope crit(&resource_lock_); |
| 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."; |
| return registered_resource->reason; |
| } |
| |
| // 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 VideoStreamEncoderResourceManager::GetCpuOveruseOptions() |
| const { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| // 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 VideoStreamEncoderResourceManager::LastInputFrameSizeOrDefault() const { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| return input_state_provider_->InputState().frame_size_pixels().value_or( |
| kDefaultInputPixelsWidth * kDefaultInputPixelsHeight); |
| } |
| |
| void VideoStreamEncoderResourceManager::OnVideoSourceRestrictionsUpdated( |
| VideoSourceRestrictions restrictions, |
| const VideoAdaptationCounters& adaptation_counters, |
| rtc::scoped_refptr<Resource> reason) { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| VideoAdaptationCounters previous_adaptation_counters = |
| active_counts_[VideoAdaptationReason::kQuality] + |
| active_counts_[VideoAdaptationReason::kCpu]; |
| int adaptation_counters_total_abs_diff = std::abs( |
| adaptation_counters.Total() - previous_adaptation_counters.Total()); |
| if (reason) { |
| // A resource signal triggered this adaptation. The adaptation counters have |
| // to be updated every time the adaptation counter is incremented or |
| // decremented due to a resource. |
| RTC_DCHECK_EQ(adaptation_counters_total_abs_diff, 1); |
| VideoAdaptationReason reason_type = GetReasonFromResource(reason); |
| UpdateAdaptationStats(adaptation_counters, reason_type); |
| } else if (adaptation_counters.Total() == 0) { |
| // Adaptation was manually reset - clear the per-reason counters too. |
| ResetActiveCounts(); |
| encoder_stats_observer_->ClearAdaptationStats(); |
| } else { |
| // If a reason did not increase or decrease the Total() by 1 and the |
| // restrictions were not just reset, the adaptation counters MUST not have |
| // been modified and there is nothing to do stats-wise. |
| RTC_DCHECK_EQ(adaptation_counters_total_abs_diff, 0); |
| } |
| RTC_LOG(LS_INFO) << ActiveCountsToString(); |
| |
| // The VideoStreamEncoder makes the manager outlive the encoder queue. This |
| // means that if the task gets executed, |this| has not been freed yet. |
| encoder_queue_->PostTask([this, restrictions] { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| video_source_restrictions_ = restrictions; |
| MaybeUpdateTargetFrameRate(); |
| }); |
| } |
| |
| void VideoStreamEncoderResourceManager::MaybeUpdateTargetFrameRate() { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| 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 = |
| video_source_restrictions_.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 VideoStreamEncoderResourceManager::OnAdaptationCountChanged( |
| const VideoAdaptationCounters& adaptation_count, |
| VideoAdaptationCounters* active_count, |
| VideoAdaptationCounters* 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 VideoAdaptationCounters prev_total = *active_count + *other_active; |
| const int delta_resolution_adaptations = |
| adaptation_count.resolution_adaptations - |
| prev_total.resolution_adaptations; |
| const int delta_fps_adaptations = |
| adaptation_count.fps_adaptations - prev_total.fps_adaptations; |
| |
| 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_resolution_adaptations + delta_fps_adaptations); |
| 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); |
| } |
| |
| void VideoStreamEncoderResourceManager::UpdateAdaptationStats( |
| const VideoAdaptationCounters& total_counts, |
| VideoAdaptationReason reason) { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| // Update active counts |
| VideoAdaptationCounters& active_count = active_counts_[reason]; |
| VideoAdaptationCounters& other_active = active_counts_[OtherReason(reason)]; |
| |
| OnAdaptationCountChanged(total_counts, &active_count, &other_active); |
| |
| encoder_stats_observer_->OnAdaptationChanged( |
| reason, active_counts_[VideoAdaptationReason::kCpu], |
| active_counts_[VideoAdaptationReason::kQuality]); |
| } |
| |
| void VideoStreamEncoderResourceManager::UpdateStatsAdaptationSettings() const { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| VideoStreamEncoderObserver::AdaptationSettings cpu_settings( |
| IsResolutionScalingEnabled(degradation_preference_), |
| IsFramerateScalingEnabled(degradation_preference_)); |
| |
| VideoStreamEncoderObserver::AdaptationSettings quality_settings = |
| quality_scaler_resource_->is_started() |
| ? cpu_settings |
| : VideoStreamEncoderObserver::AdaptationSettings(); |
| encoder_stats_observer_->UpdateAdaptationSettings(cpu_settings, |
| quality_settings); |
| } |
| |
| void VideoStreamEncoderResourceManager::MaybePerformQualityRampupExperiment() { |
| RTC_DCHECK_RUN_ON(encoder_queue_); |
| 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; |
| } |
| } |
| if (try_quality_rampup) { |
| // The VideoStreamEncoder makes the manager outlive the adaptation queue. |
| // This means that if the task gets executed, |this| has not been freed yet. |
| // TODO(https://crbug.com/webrtc/11565): When the manager no longer outlives |
| // the adaptation queue, add logic to prevent use-after-free on |this|. |
| resource_adaptation_queue_->PostTask([this] { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| if (!adaptation_processor_) { |
| // The processor nulled before this task had a chance to execute. This |
| // happens if the processor is destroyed. No action needed. |
| return; |
| } |
| // TODO(https://crbug.com/webrtc/11392): See if we can rely on the total |
| // counts or the stats, and not the active counts. |
| const VideoAdaptationCounters& qp_counts = |
| active_counts_[VideoAdaptationReason::kQuality]; |
| const VideoAdaptationCounters& cpu_counts = |
| active_counts_[VideoAdaptationReason::kCpu]; |
| if (!quality_rampup_done_ && qp_counts.resolution_adaptations > 0 && |
| cpu_counts.Total() == 0) { |
| RTC_LOG(LS_INFO) << "Reset quality limitations."; |
| adaptation_processor_->ResetVideoSourceRestrictions(); |
| quality_rampup_done_ = true; |
| } |
| }); |
| } |
| } |
| |
| void VideoStreamEncoderResourceManager::ResetActiveCounts() { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| active_counts_.clear(); |
| active_counts_[VideoAdaptationReason::kCpu] = VideoAdaptationCounters(); |
| active_counts_[VideoAdaptationReason::kQuality] = VideoAdaptationCounters(); |
| } |
| |
| std::string VideoStreamEncoderResourceManager::ActiveCountsToString() const { |
| RTC_DCHECK_RUN_ON(resource_adaptation_queue_); |
| RTC_DCHECK_EQ(2, active_counts_.size()); |
| rtc::StringBuilder ss; |
| |
| ss << "Downgrade counts: fps: {"; |
| for (auto& reason_count : active_counts_) { |
| ss << ToString(reason_count.first) << ":"; |
| ss << reason_count.second.fps_adaptations; |
| } |
| ss << "}, resolution {"; |
| for (auto& reason_count : active_counts_) { |
| ss << ToString(reason_count.first) << ":"; |
| ss << reason_count.second.resolution_adaptations; |
| } |
| ss << "}"; |
| |
| return ss.Release(); |
| } |
| } // namespace webrtc |