Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,3 @@
eshr@webrtc.org
hbos@webrtc.org
ilnik@webrtc.org

View file

@ -0,0 +1,17 @@
/*
* 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 "call/adaptation/adaptation_constraint.h"
namespace webrtc {
AdaptationConstraint::~AdaptationConstraint() {}
} // namespace webrtc

View file

@ -0,0 +1,41 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_ADAPTATION_CONSTRAINT_H_
#define CALL_ADAPTATION_ADAPTATION_CONSTRAINT_H_
#include <string>
#include "api/adaptation/resource.h"
#include "call/adaptation/video_source_restrictions.h"
#include "call/adaptation/video_stream_input_state.h"
namespace webrtc {
// Adaptation constraints have the ability to prevent applying a proposed
// adaptation (expressed as restrictions before/after adaptation).
class AdaptationConstraint {
public:
virtual ~AdaptationConstraint();
virtual std::string Name() const = 0;
// TODO(https://crbug.com/webrtc/11172): When we have multi-stream adaptation
// support, this interface needs to indicate which stream the adaptation
// applies to.
virtual bool IsAdaptationUpAllowed(
const VideoStreamInputState& input_state,
const VideoSourceRestrictions& restrictions_before,
const VideoSourceRestrictions& restrictions_after) const = 0;
};
} // namespace webrtc
#endif // CALL_ADAPTATION_ADAPTATION_CONSTRAINT_H_

View file

@ -0,0 +1,122 @@
/*
* 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 "call/adaptation/broadcast_resource_listener.h"
#include <algorithm>
#include <string>
#include <utility>
#include "absl/strings/string_view.h"
#include "api/make_ref_counted.h"
#include "rtc_base/checks.h"
#include "rtc_base/synchronization/mutex.h"
namespace webrtc {
// The AdapterResource redirects resource usage measurements from its parent to
// a single ResourceListener.
class BroadcastResourceListener::AdapterResource : public Resource {
public:
explicit AdapterResource(absl::string_view name) : name_(std::move(name)) {}
~AdapterResource() override { RTC_DCHECK(!listener_); }
// The parent is letting us know we have a usage neasurement.
void OnResourceUsageStateMeasured(ResourceUsageState usage_state) {
MutexLock lock(&lock_);
if (!listener_)
return;
listener_->OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource>(this),
usage_state);
}
// Resource implementation.
std::string Name() const override { return name_; }
void SetResourceListener(ResourceListener* listener) override {
MutexLock lock(&lock_);
RTC_DCHECK(!listener_ || !listener);
listener_ = listener;
}
private:
const std::string name_;
Mutex lock_;
ResourceListener* listener_ RTC_GUARDED_BY(lock_) = nullptr;
};
BroadcastResourceListener::BroadcastResourceListener(
rtc::scoped_refptr<Resource> source_resource)
: source_resource_(source_resource), is_listening_(false) {
RTC_DCHECK(source_resource_);
}
BroadcastResourceListener::~BroadcastResourceListener() {
RTC_DCHECK(!is_listening_);
}
rtc::scoped_refptr<Resource> BroadcastResourceListener::SourceResource() const {
return source_resource_;
}
void BroadcastResourceListener::StartListening() {
MutexLock lock(&lock_);
RTC_DCHECK(!is_listening_);
source_resource_->SetResourceListener(this);
is_listening_ = true;
}
void BroadcastResourceListener::StopListening() {
MutexLock lock(&lock_);
RTC_DCHECK(is_listening_);
RTC_DCHECK(adapters_.empty());
source_resource_->SetResourceListener(nullptr);
is_listening_ = false;
}
rtc::scoped_refptr<Resource>
BroadcastResourceListener::CreateAdapterResource() {
MutexLock lock(&lock_);
RTC_DCHECK(is_listening_);
rtc::scoped_refptr<AdapterResource> adapter =
rtc::make_ref_counted<AdapterResource>(source_resource_->Name() +
"Adapter");
adapters_.push_back(adapter);
return adapter;
}
void BroadcastResourceListener::RemoveAdapterResource(
rtc::scoped_refptr<Resource> resource) {
MutexLock lock(&lock_);
auto it = std::find(adapters_.begin(), adapters_.end(), resource);
RTC_DCHECK(it != adapters_.end());
adapters_.erase(it);
}
std::vector<rtc::scoped_refptr<Resource>>
BroadcastResourceListener::GetAdapterResources() {
std::vector<rtc::scoped_refptr<Resource>> resources;
MutexLock lock(&lock_);
for (const auto& adapter : adapters_) {
resources.push_back(adapter);
}
return resources;
}
void BroadcastResourceListener::OnResourceUsageStateMeasured(
rtc::scoped_refptr<Resource> resource,
ResourceUsageState usage_state) {
RTC_DCHECK_EQ(resource, source_resource_);
MutexLock lock(&lock_);
for (const auto& adapter : adapters_) {
adapter->OnResourceUsageStateMeasured(usage_state);
}
}
} // namespace webrtc

View file

@ -0,0 +1,75 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_BROADCAST_RESOURCE_LISTENER_H_
#define CALL_ADAPTATION_BROADCAST_RESOURCE_LISTENER_H_
#include <vector>
#include "api/adaptation/resource.h"
#include "api/scoped_refptr.h"
#include "rtc_base/synchronization/mutex.h"
namespace webrtc {
// Responsible for forwarding 1 resource usage measurement to N listeners by
// creating N "adapter" resources.
//
// Example:
// If we have ResourceA, ResourceListenerX and ResourceListenerY we can create a
// BroadcastResourceListener that listens to ResourceA, use CreateAdapter() to
// spawn adapter resources ResourceX and ResourceY and let ResourceListenerX
// listen to ResourceX and ResourceListenerY listen to ResourceY. When ResourceA
// makes a measurement it will be echoed by both ResourceX and ResourceY.
//
// TODO(https://crbug.com/webrtc/11565): When the ResourceAdaptationProcessor is
// moved to call there will only be one ResourceAdaptationProcessor that needs
// to listen to the injected resources. When this is the case, delete this class
// and DCHECK that a Resource's listener is never overwritten.
class BroadcastResourceListener : public ResourceListener {
public:
explicit BroadcastResourceListener(
rtc::scoped_refptr<Resource> source_resource);
~BroadcastResourceListener() override;
rtc::scoped_refptr<Resource> SourceResource() const;
void StartListening();
void StopListening();
// Creates a Resource that redirects any resource usage measurements that
// BroadcastResourceListener receives to its listener.
rtc::scoped_refptr<Resource> CreateAdapterResource();
// Unregister the adapter from the BroadcastResourceListener; it will no
// longer receive resource usage measurement and will no longer be referenced.
// Use this to prevent memory leaks of old adapters.
void RemoveAdapterResource(rtc::scoped_refptr<Resource> resource);
std::vector<rtc::scoped_refptr<Resource>> GetAdapterResources();
// ResourceListener implementation.
void OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,
ResourceUsageState usage_state) override;
private:
class AdapterResource;
friend class AdapterResource;
const rtc::scoped_refptr<Resource> source_resource_;
Mutex lock_;
bool is_listening_ RTC_GUARDED_BY(lock_);
// The AdapterResource unregisters itself prior to destruction, guaranteeing
// that these pointers are safe to use.
std::vector<rtc::scoped_refptr<AdapterResource>> adapters_
RTC_GUARDED_BY(lock_);
};
} // namespace webrtc
#endif // CALL_ADAPTATION_BROADCAST_RESOURCE_LISTENER_H_

View file

@ -0,0 +1,14 @@
/*
* 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 "call/adaptation/degradation_preference_provider.h"
webrtc::DegradationPreferenceProvider::~DegradationPreferenceProvider() =
default;

View file

@ -0,0 +1,27 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_DEGRADATION_PREFERENCE_PROVIDER_H_
#define CALL_ADAPTATION_DEGRADATION_PREFERENCE_PROVIDER_H_
#include "api/rtp_parameters.h"
namespace webrtc {
class DegradationPreferenceProvider {
public:
virtual ~DegradationPreferenceProvider();
virtual DegradationPreference degradation_preference() const = 0;
};
} // namespace webrtc
#endif // CALL_ADAPTATION_DEGRADATION_PREFERENCE_PROVIDER_H_

View file

@ -0,0 +1,54 @@
/*
* 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 "call/adaptation/encoder_settings.h"
#include <utility>
namespace webrtc {
EncoderSettings::EncoderSettings(VideoEncoder::EncoderInfo encoder_info,
VideoEncoderConfig encoder_config,
VideoCodec video_codec)
: encoder_info_(std::move(encoder_info)),
encoder_config_(std::move(encoder_config)),
video_codec_(std::move(video_codec)) {}
EncoderSettings::EncoderSettings(const EncoderSettings& other)
: encoder_info_(other.encoder_info_),
encoder_config_(other.encoder_config_.Copy()),
video_codec_(other.video_codec_) {}
EncoderSettings& EncoderSettings::operator=(const EncoderSettings& other) {
encoder_info_ = other.encoder_info_;
encoder_config_ = other.encoder_config_.Copy();
video_codec_ = other.video_codec_;
return *this;
}
const VideoEncoder::EncoderInfo& EncoderSettings::encoder_info() const {
return encoder_info_;
}
const VideoEncoderConfig& EncoderSettings::encoder_config() const {
return encoder_config_;
}
const VideoCodec& EncoderSettings::video_codec() const {
return video_codec_;
}
VideoCodecType GetVideoCodecTypeOrGeneric(
const absl::optional<EncoderSettings>& settings) {
return settings.has_value() ? settings->encoder_config().codec_type
: kVideoCodecGeneric;
}
} // namespace webrtc

View file

@ -0,0 +1,48 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_ENCODER_SETTINGS_H_
#define CALL_ADAPTATION_ENCODER_SETTINGS_H_
#include "absl/types/optional.h"
#include "api/video_codecs/video_codec.h"
#include "api/video_codecs/video_encoder.h"
#include "video/config/video_encoder_config.h"
namespace webrtc {
// Information about an encoder available when reconfiguring the encoder.
class EncoderSettings {
public:
EncoderSettings(VideoEncoder::EncoderInfo encoder_info,
VideoEncoderConfig encoder_config,
VideoCodec video_codec);
EncoderSettings(const EncoderSettings& other);
EncoderSettings& operator=(const EncoderSettings& other);
// Encoder capabilities, implementation info, etc.
const VideoEncoder::EncoderInfo& encoder_info() const;
// Configuration parameters, ultimately coming from the API and negotiation.
const VideoEncoderConfig& encoder_config() const;
// Lower level config, heavily based on the VideoEncoderConfig.
const VideoCodec& video_codec() const;
private:
VideoEncoder::EncoderInfo encoder_info_;
VideoEncoderConfig encoder_config_;
VideoCodec video_codec_;
};
VideoCodecType GetVideoCodecTypeOrGeneric(
const absl::optional<EncoderSettings>& settings);
} // namespace webrtc
#endif // CALL_ADAPTATION_ENCODER_SETTINGS_H_

View file

@ -0,0 +1,378 @@
/*
* 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 "call/adaptation/resource_adaptation_processor.h"
#include <algorithm>
#include <string>
#include <utility>
#include "absl/algorithm/container.h"
#include "absl/strings/string_view.h"
#include "api/sequence_checker.h"
#include "api/video/video_adaptation_counters.h"
#include "call/adaptation/video_stream_adapter.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
ResourceAdaptationProcessor::ResourceListenerDelegate::ResourceListenerDelegate(
ResourceAdaptationProcessor* processor)
: task_queue_(TaskQueueBase::Current()), processor_(processor) {
RTC_DCHECK(task_queue_);
}
void ResourceAdaptationProcessor::ResourceListenerDelegate::
OnProcessorDestroyed() {
RTC_DCHECK_RUN_ON(task_queue_);
processor_ = nullptr;
}
void ResourceAdaptationProcessor::ResourceListenerDelegate::
OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,
ResourceUsageState usage_state) {
if (!task_queue_->IsCurrent()) {
task_queue_->PostTask(
[this_ref = rtc::scoped_refptr<ResourceListenerDelegate>(this),
resource, usage_state] {
this_ref->OnResourceUsageStateMeasured(resource, usage_state);
});
return;
}
RTC_DCHECK_RUN_ON(task_queue_);
if (processor_) {
processor_->OnResourceUsageStateMeasured(resource, usage_state);
}
}
ResourceAdaptationProcessor::MitigationResultAndLogMessage::
MitigationResultAndLogMessage()
: result(MitigationResult::kAdaptationApplied), message() {}
ResourceAdaptationProcessor::MitigationResultAndLogMessage::
MitigationResultAndLogMessage(MitigationResult result,
absl::string_view message)
: result(result), message(message) {}
ResourceAdaptationProcessor::ResourceAdaptationProcessor(
VideoStreamAdapter* stream_adapter)
: task_queue_(TaskQueueBase::Current()),
resource_listener_delegate_(
rtc::make_ref_counted<ResourceListenerDelegate>(this)),
resources_(),
stream_adapter_(stream_adapter),
last_reported_source_restrictions_(),
previous_mitigation_results_() {
RTC_DCHECK(task_queue_);
stream_adapter_->AddRestrictionsListener(this);
}
ResourceAdaptationProcessor::~ResourceAdaptationProcessor() {
RTC_DCHECK_RUN_ON(task_queue_);
RTC_DCHECK(resources_.empty())
<< "There are resource(s) attached to a ResourceAdaptationProcessor "
<< "being destroyed.";
stream_adapter_->RemoveRestrictionsListener(this);
resource_listener_delegate_->OnProcessorDestroyed();
}
void ResourceAdaptationProcessor::AddResourceLimitationsListener(
ResourceLimitationsListener* limitations_listener) {
RTC_DCHECK_RUN_ON(task_queue_);
RTC_DCHECK(std::find(resource_limitations_listeners_.begin(),
resource_limitations_listeners_.end(),
limitations_listener) ==
resource_limitations_listeners_.end());
resource_limitations_listeners_.push_back(limitations_listener);
}
void ResourceAdaptationProcessor::RemoveResourceLimitationsListener(
ResourceLimitationsListener* limitations_listener) {
RTC_DCHECK_RUN_ON(task_queue_);
auto it =
std::find(resource_limitations_listeners_.begin(),
resource_limitations_listeners_.end(), limitations_listener);
RTC_DCHECK(it != resource_limitations_listeners_.end());
resource_limitations_listeners_.erase(it);
}
void ResourceAdaptationProcessor::AddResource(
rtc::scoped_refptr<Resource> resource) {
RTC_DCHECK(resource);
{
MutexLock crit(&resources_lock_);
RTC_DCHECK(absl::c_find(resources_, resource) == resources_.end())
<< "Resource \"" << resource->Name() << "\" was already registered.";
resources_.push_back(resource);
}
resource->SetResourceListener(resource_listener_delegate_.get());
RTC_LOG(LS_INFO) << "Registered resource \"" << resource->Name() << "\".";
}
std::vector<rtc::scoped_refptr<Resource>>
ResourceAdaptationProcessor::GetResources() const {
MutexLock crit(&resources_lock_);
return resources_;
}
void ResourceAdaptationProcessor::RemoveResource(
rtc::scoped_refptr<Resource> resource) {
RTC_DCHECK(resource);
RTC_LOG(LS_INFO) << "Removing resource \"" << resource->Name() << "\".";
resource->SetResourceListener(nullptr);
{
MutexLock crit(&resources_lock_);
auto it = absl::c_find(resources_, resource);
RTC_DCHECK(it != resources_.end()) << "Resource \"" << resource->Name()
<< "\" was not a registered resource.";
resources_.erase(it);
}
RemoveLimitationsImposedByResource(std::move(resource));
}
void ResourceAdaptationProcessor::RemoveLimitationsImposedByResource(
rtc::scoped_refptr<Resource> resource) {
if (!task_queue_->IsCurrent()) {
task_queue_->PostTask(
[this, resource]() { RemoveLimitationsImposedByResource(resource); });
return;
}
RTC_DCHECK_RUN_ON(task_queue_);
auto resource_adaptation_limits =
adaptation_limits_by_resources_.find(resource);
if (resource_adaptation_limits != adaptation_limits_by_resources_.end()) {
VideoStreamAdapter::RestrictionsWithCounters adaptation_limits =
resource_adaptation_limits->second;
adaptation_limits_by_resources_.erase(resource_adaptation_limits);
if (adaptation_limits_by_resources_.empty()) {
// Only the resource being removed was adapted so clear restrictions.
stream_adapter_->ClearRestrictions();
return;
}
VideoStreamAdapter::RestrictionsWithCounters most_limited =
FindMostLimitedResources().second;
if (adaptation_limits.counters.Total() <= most_limited.counters.Total()) {
// The removed limitations were less limited than the most limited
// resource. Don't change the current restrictions.
return;
}
// Apply the new most limited resource as the next restrictions.
Adaptation adapt_to = stream_adapter_->GetAdaptationTo(
most_limited.counters, most_limited.restrictions);
RTC_DCHECK_EQ(adapt_to.status(), Adaptation::Status::kValid);
stream_adapter_->ApplyAdaptation(adapt_to, nullptr);
RTC_LOG(LS_INFO)
<< "Most limited resource removed. Restoring restrictions to "
"next most limited restrictions: "
<< most_limited.restrictions.ToString() << " with counters "
<< most_limited.counters.ToString();
}
}
void ResourceAdaptationProcessor::OnResourceUsageStateMeasured(
rtc::scoped_refptr<Resource> resource,
ResourceUsageState usage_state) {
RTC_DCHECK_RUN_ON(task_queue_);
RTC_DCHECK(resource);
// `resource` could have been removed after signalling.
{
MutexLock crit(&resources_lock_);
if (absl::c_find(resources_, resource) == resources_.end()) {
RTC_LOG(LS_INFO) << "Ignoring signal from removed resource \""
<< resource->Name() << "\".";
return;
}
}
MitigationResultAndLogMessage result_and_message;
switch (usage_state) {
case ResourceUsageState::kOveruse:
result_and_message = OnResourceOveruse(resource);
break;
case ResourceUsageState::kUnderuse:
result_and_message = OnResourceUnderuse(resource);
break;
}
// Maybe log the result of the operation.
auto it = previous_mitigation_results_.find(resource.get());
if (it != previous_mitigation_results_.end() &&
it->second == result_and_message.result) {
// This resource has previously reported the same result and we haven't
// successfully adapted since - don't log to avoid spam.
return;
}
RTC_LOG(LS_INFO) << "Resource \"" << resource->Name() << "\" signalled "
<< ResourceUsageStateToString(usage_state) << ". "
<< result_and_message.message;
if (result_and_message.result == MitigationResult::kAdaptationApplied) {
previous_mitigation_results_.clear();
} else {
previous_mitigation_results_.insert(
std::make_pair(resource.get(), result_and_message.result));
}
}
ResourceAdaptationProcessor::MitigationResultAndLogMessage
ResourceAdaptationProcessor::OnResourceUnderuse(
rtc::scoped_refptr<Resource> reason_resource) {
RTC_DCHECK_RUN_ON(task_queue_);
// How can this stream be adapted up?
Adaptation adaptation = stream_adapter_->GetAdaptationUp();
if (adaptation.status() != Adaptation::Status::kValid) {
rtc::StringBuilder message;
message << "Not adapting up because VideoStreamAdapter returned "
<< Adaptation::StatusToString(adaptation.status());
return MitigationResultAndLogMessage(MitigationResult::kRejectedByAdapter,
message.Release());
}
// Check that resource is most limited.
std::vector<rtc::scoped_refptr<Resource>> most_limited_resources;
VideoStreamAdapter::RestrictionsWithCounters most_limited_restrictions;
std::tie(most_limited_resources, most_limited_restrictions) =
FindMostLimitedResources();
// If the most restricted resource is less limited than current restrictions
// then proceed with adapting up.
if (!most_limited_resources.empty() &&
most_limited_restrictions.counters.Total() >=
stream_adapter_->adaptation_counters().Total()) {
// If `reason_resource` is not one of the most limiting resources then abort
// adaptation.
if (absl::c_find(most_limited_resources, reason_resource) ==
most_limited_resources.end()) {
rtc::StringBuilder message;
message << "Resource \"" << reason_resource->Name()
<< "\" was not the most limited resource.";
return MitigationResultAndLogMessage(
MitigationResult::kNotMostLimitedResource, message.Release());
}
if (most_limited_resources.size() > 1) {
// If there are multiple most limited resources, all must signal underuse
// before the adaptation is applied.
UpdateResourceLimitations(reason_resource, adaptation.restrictions(),
adaptation.counters());
rtc::StringBuilder message;
message << "Resource \"" << reason_resource->Name()
<< "\" was not the only most limited resource.";
return MitigationResultAndLogMessage(
MitigationResult::kSharedMostLimitedResource, message.Release());
}
}
// Apply adaptation.
stream_adapter_->ApplyAdaptation(adaptation, reason_resource);
rtc::StringBuilder message;
message << "Adapted up successfully. Unfiltered adaptations: "
<< stream_adapter_->adaptation_counters().ToString();
return MitigationResultAndLogMessage(MitigationResult::kAdaptationApplied,
message.Release());
}
ResourceAdaptationProcessor::MitigationResultAndLogMessage
ResourceAdaptationProcessor::OnResourceOveruse(
rtc::scoped_refptr<Resource> reason_resource) {
RTC_DCHECK_RUN_ON(task_queue_);
// How can this stream be adapted up?
Adaptation adaptation = stream_adapter_->GetAdaptationDown();
if (adaptation.status() == Adaptation::Status::kLimitReached) {
// Add resource as most limited.
VideoStreamAdapter::RestrictionsWithCounters restrictions;
std::tie(std::ignore, restrictions) = FindMostLimitedResources();
UpdateResourceLimitations(reason_resource, restrictions.restrictions,
restrictions.counters);
}
if (adaptation.status() != Adaptation::Status::kValid) {
rtc::StringBuilder message;
message << "Not adapting down because VideoStreamAdapter returned "
<< Adaptation::StatusToString(adaptation.status());
return MitigationResultAndLogMessage(MitigationResult::kRejectedByAdapter,
message.Release());
}
// Apply adaptation.
UpdateResourceLimitations(reason_resource, adaptation.restrictions(),
adaptation.counters());
stream_adapter_->ApplyAdaptation(adaptation, reason_resource);
rtc::StringBuilder message;
message << "Adapted down successfully. Unfiltered adaptations: "
<< stream_adapter_->adaptation_counters().ToString();
return MitigationResultAndLogMessage(MitigationResult::kAdaptationApplied,
message.Release());
}
std::pair<std::vector<rtc::scoped_refptr<Resource>>,
VideoStreamAdapter::RestrictionsWithCounters>
ResourceAdaptationProcessor::FindMostLimitedResources() const {
std::vector<rtc::scoped_refptr<Resource>> most_limited_resources;
VideoStreamAdapter::RestrictionsWithCounters most_limited_restrictions{
VideoSourceRestrictions(), VideoAdaptationCounters()};
for (const auto& resource_and_adaptation_limit_ :
adaptation_limits_by_resources_) {
const auto& restrictions_with_counters =
resource_and_adaptation_limit_.second;
if (restrictions_with_counters.counters.Total() >
most_limited_restrictions.counters.Total()) {
most_limited_restrictions = restrictions_with_counters;
most_limited_resources.clear();
most_limited_resources.push_back(resource_and_adaptation_limit_.first);
} else if (most_limited_restrictions.counters ==
restrictions_with_counters.counters) {
most_limited_resources.push_back(resource_and_adaptation_limit_.first);
}
}
return std::make_pair(std::move(most_limited_resources),
most_limited_restrictions);
}
void ResourceAdaptationProcessor::UpdateResourceLimitations(
rtc::scoped_refptr<Resource> reason_resource,
const VideoSourceRestrictions& restrictions,
const VideoAdaptationCounters& counters) {
auto& adaptation_limits = adaptation_limits_by_resources_[reason_resource];
if (adaptation_limits.restrictions == restrictions &&
adaptation_limits.counters == counters) {
return;
}
adaptation_limits = {restrictions, counters};
std::map<rtc::scoped_refptr<Resource>, VideoAdaptationCounters> limitations;
for (const auto& p : adaptation_limits_by_resources_) {
limitations.insert(std::make_pair(p.first, p.second.counters));
}
for (auto limitations_listener : resource_limitations_listeners_) {
limitations_listener->OnResourceLimitationChanged(reason_resource,
limitations);
}
}
void ResourceAdaptationProcessor::OnVideoSourceRestrictionsUpdated(
VideoSourceRestrictions restrictions,
const VideoAdaptationCounters& adaptation_counters,
rtc::scoped_refptr<Resource> reason,
const VideoSourceRestrictions& unfiltered_restrictions) {
RTC_DCHECK_RUN_ON(task_queue_);
if (reason) {
UpdateResourceLimitations(reason, unfiltered_restrictions,
adaptation_counters);
} else if (adaptation_counters.Total() == 0) {
// Adaptations are cleared.
adaptation_limits_by_resources_.clear();
previous_mitigation_results_.clear();
for (auto limitations_listener : resource_limitations_listeners_) {
limitations_listener->OnResourceLimitationChanged(nullptr, {});
}
}
}
} // namespace webrtc

View file

@ -0,0 +1,167 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_
#define CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/adaptation/resource.h"
#include "api/rtp_parameters.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/task_queue_base.h"
#include "api/video/video_adaptation_counters.h"
#include "api/video/video_frame.h"
#include "call/adaptation/resource_adaptation_processor_interface.h"
#include "call/adaptation/video_source_restrictions.h"
#include "call/adaptation/video_stream_adapter.h"
#include "call/adaptation/video_stream_input_state.h"
#include "call/adaptation/video_stream_input_state_provider.h"
#include "video/video_stream_encoder_observer.h"
namespace webrtc {
// The Resource Adaptation Processor is responsible for reacting to resource
// usage measurements (e.g. overusing or underusing CPU). When a resource is
// overused the Processor is responsible for performing mitigations in order to
// consume less resources.
//
// Today we have one Processor per VideoStreamEncoder and the Processor is only
// capable of restricting resolution or frame rate of the encoded stream. In the
// future we should have a single Processor responsible for all encoded streams,
// and it should be capable of reconfiguring other things than just
// VideoSourceRestrictions (e.g. reduce render frame rate).
// See Resource-Adaptation hotlist:
// https://bugs.chromium.org/u/590058293/hotlists/Resource-Adaptation
//
// The ResourceAdaptationProcessor is single-threaded. It may be constructed on
// any thread but MUST subsequently be used and destroyed on a single sequence,
// i.e. the "resource adaptation task queue". Resources can be added and removed
// from any thread.
class ResourceAdaptationProcessor : public ResourceAdaptationProcessorInterface,
public VideoSourceRestrictionsListener,
public ResourceListener {
public:
explicit ResourceAdaptationProcessor(
VideoStreamAdapter* video_stream_adapter);
~ResourceAdaptationProcessor() override;
// ResourceAdaptationProcessorInterface implementation.
void AddResourceLimitationsListener(
ResourceLimitationsListener* limitations_listener) override;
void RemoveResourceLimitationsListener(
ResourceLimitationsListener* limitations_listener) override;
void AddResource(rtc::scoped_refptr<Resource> resource) override;
std::vector<rtc::scoped_refptr<Resource>> GetResources() const override;
void RemoveResource(rtc::scoped_refptr<Resource> resource) override;
// ResourceListener implementation.
// Triggers OnResourceUnderuse() or OnResourceOveruse().
void OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,
ResourceUsageState usage_state) override;
// VideoSourceRestrictionsListener implementation.
void OnVideoSourceRestrictionsUpdated(
VideoSourceRestrictions restrictions,
const VideoAdaptationCounters& adaptation_counters,
rtc::scoped_refptr<Resource> reason,
const VideoSourceRestrictions& unfiltered_restrictions) override;
private:
// If resource usage measurements happens off the adaptation task queue, this
// class takes care of posting the measurement for the processor to handle it
// on the adaptation task queue.
class ResourceListenerDelegate : public rtc::RefCountInterface,
public ResourceListener {
public:
explicit ResourceListenerDelegate(ResourceAdaptationProcessor* processor);
void OnProcessorDestroyed();
// ResourceListener implementation.
void OnResourceUsageStateMeasured(rtc::scoped_refptr<Resource> resource,
ResourceUsageState usage_state) override;
private:
TaskQueueBase* task_queue_;
ResourceAdaptationProcessor* processor_ RTC_GUARDED_BY(task_queue_);
};
enum class MitigationResult {
kNotMostLimitedResource,
kSharedMostLimitedResource,
kRejectedByAdapter,
kAdaptationApplied,
};
struct MitigationResultAndLogMessage {
MitigationResultAndLogMessage();
MitigationResultAndLogMessage(MitigationResult result,
absl::string_view message);
MitigationResult result;
std::string message;
};
// Performs the adaptation by getting the next target, applying it and
// informing listeners of the new VideoSourceRestriction and adaptation
// counters.
MitigationResultAndLogMessage OnResourceUnderuse(
rtc::scoped_refptr<Resource> reason_resource);
MitigationResultAndLogMessage OnResourceOveruse(
rtc::scoped_refptr<Resource> reason_resource);
void UpdateResourceLimitations(rtc::scoped_refptr<Resource> reason_resource,
const VideoSourceRestrictions& restrictions,
const VideoAdaptationCounters& counters)
RTC_RUN_ON(task_queue_);
// Searches `adaptation_limits_by_resources_` for each resource with the
// highest total adaptation counts. Adaptation up may only occur if the
// resource performing the adaptation is the only most limited resource. This
// function returns the list of all most limited resources as well as the
// corresponding adaptation of that resource.
std::pair<std::vector<rtc::scoped_refptr<Resource>>,
VideoStreamAdapter::RestrictionsWithCounters>
FindMostLimitedResources() const RTC_RUN_ON(task_queue_);
void RemoveLimitationsImposedByResource(
rtc::scoped_refptr<Resource> resource);
TaskQueueBase* task_queue_;
rtc::scoped_refptr<ResourceListenerDelegate> resource_listener_delegate_;
// Input and output.
mutable Mutex resources_lock_;
std::vector<rtc::scoped_refptr<Resource>> resources_
RTC_GUARDED_BY(resources_lock_);
std::vector<ResourceLimitationsListener*> resource_limitations_listeners_
RTC_GUARDED_BY(task_queue_);
// Purely used for statistics, does not ensure mapped resources stay alive.
std::map<rtc::scoped_refptr<Resource>,
VideoStreamAdapter::RestrictionsWithCounters>
adaptation_limits_by_resources_ RTC_GUARDED_BY(task_queue_);
// Responsible for generating and applying possible adaptations.
VideoStreamAdapter* const stream_adapter_ RTC_GUARDED_BY(task_queue_);
VideoSourceRestrictions last_reported_source_restrictions_
RTC_GUARDED_BY(task_queue_);
// Keeps track of previous mitigation results per resource since the last
// successful adaptation. Used to avoid RTC_LOG spam.
std::map<Resource*, MitigationResult> previous_mitigation_results_
RTC_GUARDED_BY(task_queue_);
};
} // namespace webrtc
#endif // CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_

View file

@ -0,0 +1,20 @@
/*
* 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 "call/adaptation/resource_adaptation_processor_interface.h"
namespace webrtc {
ResourceAdaptationProcessorInterface::~ResourceAdaptationProcessorInterface() =
default;
ResourceLimitationsListener::~ResourceLimitationsListener() = default;
} // namespace webrtc

View file

@ -0,0 +1,67 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_INTERFACE_H_
#define CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_INTERFACE_H_
#include <map>
#include <vector>
#include "absl/types/optional.h"
#include "api/adaptation/resource.h"
#include "api/rtp_parameters.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/task_queue_base.h"
#include "api/video/video_adaptation_counters.h"
#include "api/video/video_frame.h"
#include "call/adaptation/adaptation_constraint.h"
#include "call/adaptation/encoder_settings.h"
#include "call/adaptation/video_source_restrictions.h"
namespace webrtc {
class ResourceLimitationsListener {
public:
virtual ~ResourceLimitationsListener();
// The limitations on a resource were changed. This does not mean the current
// video restrictions have changed.
virtual void OnResourceLimitationChanged(
rtc::scoped_refptr<Resource> resource,
const std::map<rtc::scoped_refptr<Resource>, VideoAdaptationCounters>&
resource_limitations) = 0;
};
// The Resource Adaptation Processor is responsible for reacting to resource
// usage measurements (e.g. overusing or underusing CPU). When a resource is
// overused the Processor is responsible for performing mitigations in order to
// consume less resources.
class ResourceAdaptationProcessorInterface {
public:
virtual ~ResourceAdaptationProcessorInterface();
virtual void AddResourceLimitationsListener(
ResourceLimitationsListener* limitations_listener) = 0;
virtual void RemoveResourceLimitationsListener(
ResourceLimitationsListener* limitations_listener) = 0;
// Starts or stops listening to resources, effectively enabling or disabling
// processing. May be called from anywhere.
// TODO(https://crbug.com/webrtc/11172): Automatically register and unregister
// with AddResource() and RemoveResource() instead. When the processor is
// multi-stream aware, stream-specific resouces will get added and removed
// over time.
virtual void AddResource(rtc::scoped_refptr<Resource> resource) = 0;
virtual std::vector<rtc::scoped_refptr<Resource>> GetResources() const = 0;
virtual void RemoveResource(rtc::scoped_refptr<Resource> resource) = 0;
};
} // namespace webrtc
#endif // CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_INTERFACE_H_

View file

@ -0,0 +1,173 @@
/*
* 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 "call/adaptation/video_source_restrictions.h"
#include <algorithm>
#include <limits>
#include "rtc_base/checks.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
VideoSourceRestrictions::VideoSourceRestrictions()
: max_pixels_per_frame_(absl::nullopt),
target_pixels_per_frame_(absl::nullopt),
max_frame_rate_(absl::nullopt) {}
VideoSourceRestrictions::VideoSourceRestrictions(
absl::optional<size_t> max_pixels_per_frame,
absl::optional<size_t> target_pixels_per_frame,
absl::optional<double> max_frame_rate)
: max_pixels_per_frame_(std::move(max_pixels_per_frame)),
target_pixels_per_frame_(std::move(target_pixels_per_frame)),
max_frame_rate_(std::move(max_frame_rate)) {
RTC_DCHECK(!max_pixels_per_frame_.has_value() ||
max_pixels_per_frame_.value() <
static_cast<size_t>(std::numeric_limits<int>::max()));
RTC_DCHECK(!max_frame_rate_.has_value() ||
max_frame_rate_.value() < std::numeric_limits<int>::max());
RTC_DCHECK(!max_frame_rate_.has_value() || max_frame_rate_.value() > 0.0);
}
std::string VideoSourceRestrictions::ToString() const {
rtc::StringBuilder ss;
ss << "{";
if (max_frame_rate_)
ss << " max_fps=" << max_frame_rate_.value();
if (max_pixels_per_frame_)
ss << " max_pixels_per_frame=" << max_pixels_per_frame_.value();
if (target_pixels_per_frame_)
ss << " target_pixels_per_frame=" << target_pixels_per_frame_.value();
ss << " }";
return ss.Release();
}
const absl::optional<size_t>& VideoSourceRestrictions::max_pixels_per_frame()
const {
return max_pixels_per_frame_;
}
const absl::optional<size_t>& VideoSourceRestrictions::target_pixels_per_frame()
const {
return target_pixels_per_frame_;
}
const absl::optional<double>& VideoSourceRestrictions::max_frame_rate() const {
return max_frame_rate_;
}
void VideoSourceRestrictions::set_max_pixels_per_frame(
absl::optional<size_t> max_pixels_per_frame) {
max_pixels_per_frame_ = std::move(max_pixels_per_frame);
}
void VideoSourceRestrictions::set_target_pixels_per_frame(
absl::optional<size_t> target_pixels_per_frame) {
target_pixels_per_frame_ = std::move(target_pixels_per_frame);
}
void VideoSourceRestrictions::set_max_frame_rate(
absl::optional<double> max_frame_rate) {
max_frame_rate_ = std::move(max_frame_rate);
}
void VideoSourceRestrictions::UpdateMin(const VideoSourceRestrictions& other) {
if (max_pixels_per_frame_.has_value()) {
max_pixels_per_frame_ = std::min(*max_pixels_per_frame_,
other.max_pixels_per_frame().value_or(
std::numeric_limits<size_t>::max()));
} else {
max_pixels_per_frame_ = other.max_pixels_per_frame();
}
if (target_pixels_per_frame_.has_value()) {
target_pixels_per_frame_ = std::min(
*target_pixels_per_frame_, other.target_pixels_per_frame().value_or(
std::numeric_limits<size_t>::max()));
} else {
target_pixels_per_frame_ = other.target_pixels_per_frame();
}
if (max_frame_rate_.has_value()) {
max_frame_rate_ = std::min(
*max_frame_rate_,
other.max_frame_rate().value_or(std::numeric_limits<double>::max()));
} else {
max_frame_rate_ = other.max_frame_rate();
}
}
bool DidRestrictionsIncrease(VideoSourceRestrictions before,
VideoSourceRestrictions after) {
bool decreased_resolution = DidDecreaseResolution(before, after);
bool decreased_framerate = DidDecreaseFrameRate(before, after);
bool same_resolution =
before.max_pixels_per_frame() == after.max_pixels_per_frame();
bool same_framerate = before.max_frame_rate() == after.max_frame_rate();
return (decreased_resolution && decreased_framerate) ||
(decreased_resolution && same_framerate) ||
(same_resolution && decreased_framerate);
}
bool DidRestrictionsDecrease(VideoSourceRestrictions before,
VideoSourceRestrictions after) {
bool increased_resolution = DidIncreaseResolution(before, after);
bool increased_framerate = DidIncreaseFrameRate(before, after);
bool same_resolution =
before.max_pixels_per_frame() == after.max_pixels_per_frame();
bool same_framerate = before.max_frame_rate() == after.max_frame_rate();
return (increased_resolution && increased_framerate) ||
(increased_resolution && same_framerate) ||
(same_resolution && increased_framerate);
}
bool DidIncreaseResolution(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after) {
if (!restrictions_before.max_pixels_per_frame().has_value())
return false;
if (!restrictions_after.max_pixels_per_frame().has_value())
return true;
return restrictions_after.max_pixels_per_frame().value() >
restrictions_before.max_pixels_per_frame().value();
}
bool DidDecreaseResolution(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after) {
if (!restrictions_after.max_pixels_per_frame().has_value())
return false;
if (!restrictions_before.max_pixels_per_frame().has_value())
return true;
return restrictions_after.max_pixels_per_frame().value() <
restrictions_before.max_pixels_per_frame().value();
}
bool DidIncreaseFrameRate(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after) {
if (!restrictions_before.max_frame_rate().has_value())
return false;
if (!restrictions_after.max_frame_rate().has_value())
return true;
return restrictions_after.max_frame_rate().value() >
restrictions_before.max_frame_rate().value();
}
bool DidDecreaseFrameRate(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after) {
if (!restrictions_after.max_frame_rate().has_value())
return false;
if (!restrictions_before.max_frame_rate().has_value())
return true;
return restrictions_after.max_frame_rate().value() <
restrictions_before.max_frame_rate().value();
}
} // namespace webrtc

View file

@ -0,0 +1,89 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_VIDEO_SOURCE_RESTRICTIONS_H_
#define CALL_ADAPTATION_VIDEO_SOURCE_RESTRICTIONS_H_
#include <string>
#include <utility>
#include "absl/types/optional.h"
namespace webrtc {
// Describes optional restrictions to the resolution and frame rate of a video
// source.
class VideoSourceRestrictions {
public:
// Constructs without any restrictions.
VideoSourceRestrictions();
// All values must be positive or nullopt.
// TODO(hbos): Support expressing "disable this stream"?
VideoSourceRestrictions(absl::optional<size_t> max_pixels_per_frame,
absl::optional<size_t> target_pixels_per_frame,
absl::optional<double> max_frame_rate);
bool operator==(const VideoSourceRestrictions& rhs) const {
return max_pixels_per_frame_ == rhs.max_pixels_per_frame_ &&
target_pixels_per_frame_ == rhs.target_pixels_per_frame_ &&
max_frame_rate_ == rhs.max_frame_rate_;
}
bool operator!=(const VideoSourceRestrictions& rhs) const {
return !(*this == rhs);
}
std::string ToString() const;
// The source must produce a resolution less than or equal to
// max_pixels_per_frame().
const absl::optional<size_t>& max_pixels_per_frame() const;
// The source should produce a resolution as close to the
// target_pixels_per_frame() as possible, provided this does not exceed
// max_pixels_per_frame().
// The actual pixel count selected depends on the capabilities of the source.
// TODO(hbos): Clarify how "target" is used. One possible implementation: open
// the camera in the smallest resolution that is greater than or equal to the
// target and scale it down to the target if it is greater. Is this an
// accurate description of what this does today, or do we do something else?
const absl::optional<size_t>& target_pixels_per_frame() const;
const absl::optional<double>& max_frame_rate() const;
void set_max_pixels_per_frame(absl::optional<size_t> max_pixels_per_frame);
void set_target_pixels_per_frame(
absl::optional<size_t> target_pixels_per_frame);
void set_max_frame_rate(absl::optional<double> max_frame_rate);
// Update `this` with min(`this`, `other`).
void UpdateMin(const VideoSourceRestrictions& other);
private:
// These map to rtc::VideoSinkWants's `max_pixel_count` and
// `target_pixel_count`.
absl::optional<size_t> max_pixels_per_frame_;
absl::optional<size_t> target_pixels_per_frame_;
absl::optional<double> max_frame_rate_;
};
bool DidRestrictionsIncrease(VideoSourceRestrictions before,
VideoSourceRestrictions after);
bool DidRestrictionsDecrease(VideoSourceRestrictions before,
VideoSourceRestrictions after);
bool DidIncreaseResolution(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after);
bool DidDecreaseResolution(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after);
bool DidIncreaseFrameRate(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after);
bool DidDecreaseFrameRate(VideoSourceRestrictions restrictions_before,
VideoSourceRestrictions restrictions_after);
} // namespace webrtc
#endif // CALL_ADAPTATION_VIDEO_SOURCE_RESTRICTIONS_H_

View file

@ -0,0 +1,753 @@
/*
* 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 "call/adaptation/video_stream_adapter.h"
#include <algorithm>
#include <limits>
#include <utility>
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "api/sequence_checker.h"
#include "api/video/video_adaptation_counters.h"
#include "api/video/video_adaptation_reason.h"
#include "api/video_codecs/video_encoder.h"
#include "call/adaptation/video_source_restrictions.h"
#include "call/adaptation/video_stream_input_state.h"
#include "modules/video_coding/svc/scalability_mode_util.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
const int kMinFrameRateFps = 2;
namespace {
// For frame rate, the steps we take are 2/3 (down) and 3/2 (up).
int GetLowerFrameRateThan(int fps) {
RTC_DCHECK(fps != std::numeric_limits<int>::max());
return (fps * 2) / 3;
}
// TODO(hbos): Use absl::optional<> instead?
int GetHigherFrameRateThan(int fps) {
return fps != std::numeric_limits<int>::max()
? (fps * 3) / 2
: std::numeric_limits<int>::max();
}
int GetIncreasedMaxPixelsWanted(int target_pixels) {
if (target_pixels == std::numeric_limits<int>::max())
return std::numeric_limits<int>::max();
// When we decrease resolution, we go down to at most 3/5 of current pixels.
// Thus to increase resolution, we need 3/5 to get back to where we started.
// When going up, the desired max_pixels_per_frame() has to be significantly
// higher than the target because the source's native resolutions might not
// match the target. We pick 12/5 of the target.
//
// (This value was historically 4 times the old target, which is (3/5)*4 of
// the new target - or 12/5 - assuming the target is adjusted according to
// the above steps.)
RTC_DCHECK(target_pixels != std::numeric_limits<int>::max());
return (target_pixels * 12) / 5;
}
bool CanDecreaseResolutionTo(int target_pixels,
int target_pixels_min,
const VideoStreamInputState& input_state,
const VideoSourceRestrictions& restrictions) {
int max_pixels_per_frame =
rtc::dchecked_cast<int>(restrictions.max_pixels_per_frame().value_or(
std::numeric_limits<int>::max()));
return target_pixels < max_pixels_per_frame &&
target_pixels_min >= input_state.min_pixels_per_frame();
}
bool CanIncreaseResolutionTo(int target_pixels,
const VideoSourceRestrictions& restrictions) {
int max_pixels_wanted = GetIncreasedMaxPixelsWanted(target_pixels);
int max_pixels_per_frame =
rtc::dchecked_cast<int>(restrictions.max_pixels_per_frame().value_or(
std::numeric_limits<int>::max()));
return max_pixels_wanted > max_pixels_per_frame;
}
bool CanDecreaseFrameRateTo(int max_frame_rate,
const VideoSourceRestrictions& restrictions) {
const int fps_wanted = std::max(kMinFrameRateFps, max_frame_rate);
return fps_wanted <
rtc::dchecked_cast<int>(restrictions.max_frame_rate().value_or(
std::numeric_limits<int>::max()));
}
bool CanIncreaseFrameRateTo(int max_frame_rate,
const VideoSourceRestrictions& restrictions) {
return max_frame_rate >
rtc::dchecked_cast<int>(restrictions.max_frame_rate().value_or(
std::numeric_limits<int>::max()));
}
bool MinPixelLimitReached(const VideoStreamInputState& input_state) {
if (input_state.single_active_stream_pixels().has_value()) {
return GetLowerResolutionThan(
input_state.single_active_stream_pixels().value()) <
input_state.min_pixels_per_frame();
}
return input_state.frame_size_pixels().has_value() &&
GetLowerResolutionThan(input_state.frame_size_pixels().value()) <
input_state.min_pixels_per_frame();
}
} // namespace
VideoSourceRestrictionsListener::~VideoSourceRestrictionsListener() = default;
VideoSourceRestrictions FilterRestrictionsByDegradationPreference(
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;
}
// For resolution, the steps we take are 3/5 (down) and 5/3 (up).
// Notice the asymmetry of which restriction property is set depending on if
// we are adapting up or down:
// - VideoSourceRestrictor::DecreaseResolution() sets the max_pixels_per_frame()
// to the desired target and target_pixels_per_frame() to null.
// - VideoSourceRestrictor::IncreaseResolutionTo() sets the
// target_pixels_per_frame() to the desired target, and max_pixels_per_frame()
// is set according to VideoSourceRestrictor::GetIncreasedMaxPixelsWanted().
int GetLowerResolutionThan(int pixel_count) {
RTC_DCHECK(pixel_count != std::numeric_limits<int>::max());
return (pixel_count * 3) / 5;
}
// TODO(hbos): Use absl::optional<> instead?
int GetHigherResolutionThan(int pixel_count) {
return pixel_count != std::numeric_limits<int>::max()
? (pixel_count * 5) / 3
: std::numeric_limits<int>::max();
}
// static
const char* Adaptation::StatusToString(Adaptation::Status status) {
switch (status) {
case Adaptation::Status::kValid:
return "kValid";
case Adaptation::Status::kLimitReached:
return "kLimitReached";
case Adaptation::Status::kAwaitingPreviousAdaptation:
return "kAwaitingPreviousAdaptation";
case Status::kInsufficientInput:
return "kInsufficientInput";
case Status::kAdaptationDisabled:
return "kAdaptationDisabled";
case Status::kRejectedByConstraint:
return "kRejectedByConstraint";
}
RTC_CHECK_NOTREACHED();
}
Adaptation::Adaptation(int validation_id,
VideoSourceRestrictions restrictions,
VideoAdaptationCounters counters,
VideoStreamInputState input_state)
: validation_id_(validation_id),
status_(Status::kValid),
input_state_(std::move(input_state)),
restrictions_(std::move(restrictions)),
counters_(std::move(counters)) {}
Adaptation::Adaptation(int validation_id, Status invalid_status)
: validation_id_(validation_id), status_(invalid_status) {
RTC_DCHECK_NE(status_, Status::kValid);
}
Adaptation::Status Adaptation::status() const {
return status_;
}
const VideoStreamInputState& Adaptation::input_state() const {
return input_state_;
}
const VideoSourceRestrictions& Adaptation::restrictions() const {
return restrictions_;
}
const VideoAdaptationCounters& Adaptation::counters() const {
return counters_;
}
VideoStreamAdapter::VideoStreamAdapter(
VideoStreamInputStateProvider* input_state_provider,
VideoStreamEncoderObserver* encoder_stats_observer,
const FieldTrialsView& field_trials)
: input_state_provider_(input_state_provider),
encoder_stats_observer_(encoder_stats_observer),
balanced_settings_(field_trials),
adaptation_validation_id_(0),
degradation_preference_(DegradationPreference::DISABLED),
awaiting_frame_size_change_(absl::nullopt) {
sequence_checker_.Detach();
RTC_DCHECK(input_state_provider_);
RTC_DCHECK(encoder_stats_observer_);
}
VideoStreamAdapter::~VideoStreamAdapter() {
RTC_DCHECK(adaptation_constraints_.empty())
<< "There are constaint(s) attached to a VideoStreamAdapter being "
"destroyed.";
}
VideoSourceRestrictions VideoStreamAdapter::source_restrictions() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return current_restrictions_.restrictions;
}
const VideoAdaptationCounters& VideoStreamAdapter::adaptation_counters() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return current_restrictions_.counters;
}
void VideoStreamAdapter::ClearRestrictions() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
// Invalidate any previously returned Adaptation.
RTC_LOG(LS_INFO) << "Resetting restrictions";
++adaptation_validation_id_;
current_restrictions_ = {VideoSourceRestrictions(),
VideoAdaptationCounters()};
awaiting_frame_size_change_ = absl::nullopt;
BroadcastVideoRestrictionsUpdate(input_state_provider_->InputState(),
nullptr);
}
void VideoStreamAdapter::AddRestrictionsListener(
VideoSourceRestrictionsListener* restrictions_listener) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(std::find(restrictions_listeners_.begin(),
restrictions_listeners_.end(),
restrictions_listener) == restrictions_listeners_.end());
restrictions_listeners_.push_back(restrictions_listener);
}
void VideoStreamAdapter::RemoveRestrictionsListener(
VideoSourceRestrictionsListener* restrictions_listener) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
auto it = std::find(restrictions_listeners_.begin(),
restrictions_listeners_.end(), restrictions_listener);
RTC_DCHECK(it != restrictions_listeners_.end());
restrictions_listeners_.erase(it);
}
void VideoStreamAdapter::AddAdaptationConstraint(
AdaptationConstraint* adaptation_constraint) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK(std::find(adaptation_constraints_.begin(),
adaptation_constraints_.end(),
adaptation_constraint) == adaptation_constraints_.end());
adaptation_constraints_.push_back(adaptation_constraint);
}
void VideoStreamAdapter::RemoveAdaptationConstraint(
AdaptationConstraint* adaptation_constraint) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
auto it = std::find(adaptation_constraints_.begin(),
adaptation_constraints_.end(), adaptation_constraint);
RTC_DCHECK(it != adaptation_constraints_.end());
adaptation_constraints_.erase(it);
}
void VideoStreamAdapter::SetDegradationPreference(
DegradationPreference degradation_preference) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
if (degradation_preference_ == degradation_preference)
return;
// Invalidate any previously returned Adaptation.
++adaptation_validation_id_;
bool balanced_switch =
degradation_preference == DegradationPreference::BALANCED ||
degradation_preference_ == DegradationPreference::BALANCED;
degradation_preference_ = degradation_preference;
if (balanced_switch) {
// ClearRestrictions() calls BroadcastVideoRestrictionsUpdate(nullptr).
ClearRestrictions();
} else {
BroadcastVideoRestrictionsUpdate(input_state_provider_->InputState(),
nullptr);
}
}
struct VideoStreamAdapter::RestrictionsOrStateVisitor {
Adaptation operator()(const RestrictionsWithCounters& r) const {
return Adaptation(adaptation_validation_id, r.restrictions, r.counters,
input_state);
}
Adaptation operator()(const Adaptation::Status& status) const {
RTC_DCHECK_NE(status, Adaptation::Status::kValid);
return Adaptation(adaptation_validation_id, status);
}
const int adaptation_validation_id;
const VideoStreamInputState& input_state;
};
Adaptation VideoStreamAdapter::RestrictionsOrStateToAdaptation(
VideoStreamAdapter::RestrictionsOrState step_or_state,
const VideoStreamInputState& input_state) const {
RTC_DCHECK(!step_or_state.valueless_by_exception());
return absl::visit(
RestrictionsOrStateVisitor{adaptation_validation_id_, input_state},
step_or_state);
}
Adaptation VideoStreamAdapter::GetAdaptationUp(
const VideoStreamInputState& input_state) const {
RestrictionsOrState step = GetAdaptationUpStep(input_state);
// If an adaptation proposed, check with the constraints that it is ok.
if (absl::holds_alternative<RestrictionsWithCounters>(step)) {
RestrictionsWithCounters restrictions =
absl::get<RestrictionsWithCounters>(step);
for (const auto* constraint : adaptation_constraints_) {
if (!constraint->IsAdaptationUpAllowed(input_state,
current_restrictions_.restrictions,
restrictions.restrictions)) {
RTC_LOG(LS_INFO) << "Not adapting up because constraint \""
<< constraint->Name() << "\" disallowed it";
step = Adaptation::Status::kRejectedByConstraint;
}
}
}
return RestrictionsOrStateToAdaptation(step, input_state);
}
Adaptation VideoStreamAdapter::GetAdaptationUp() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoStreamInputState input_state = input_state_provider_->InputState();
++adaptation_validation_id_;
Adaptation adaptation = GetAdaptationUp(input_state);
return adaptation;
}
VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::GetAdaptationUpStep(
const VideoStreamInputState& input_state) const {
if (!HasSufficientInputForAdaptation(input_state)) {
return Adaptation::Status::kInsufficientInput;
}
// Don't adapt if we're awaiting a previous adaptation to have an effect.
if (awaiting_frame_size_change_ &&
awaiting_frame_size_change_->pixels_increased &&
degradation_preference_ == DegradationPreference::MAINTAIN_FRAMERATE &&
input_state.frame_size_pixels().value() <=
awaiting_frame_size_change_->frame_size_pixels) {
return Adaptation::Status::kAwaitingPreviousAdaptation;
}
// Maybe propose targets based on degradation preference.
switch (degradation_preference_) {
case DegradationPreference::BALANCED: {
// Attempt to increase target frame rate.
RestrictionsOrState increase_frame_rate =
IncreaseFramerate(input_state, current_restrictions_);
if (absl::holds_alternative<RestrictionsWithCounters>(
increase_frame_rate)) {
return increase_frame_rate;
}
// else, increase resolution.
[[fallthrough]];
}
case DegradationPreference::MAINTAIN_FRAMERATE: {
// Attempt to increase pixel count.
return IncreaseResolution(input_state, current_restrictions_);
}
case DegradationPreference::MAINTAIN_RESOLUTION: {
// Scale up framerate.
return IncreaseFramerate(input_state, current_restrictions_);
}
case DegradationPreference::DISABLED:
return Adaptation::Status::kAdaptationDisabled;
}
RTC_CHECK_NOTREACHED();
}
Adaptation VideoStreamAdapter::GetAdaptationDown() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoStreamInputState input_state = input_state_provider_->InputState();
++adaptation_validation_id_;
RestrictionsOrState restrictions_or_state =
GetAdaptationDownStep(input_state, current_restrictions_);
if (MinPixelLimitReached(input_state)) {
encoder_stats_observer_->OnMinPixelLimitReached();
}
// Check for min_fps
if (degradation_preference_ == DegradationPreference::BALANCED &&
absl::holds_alternative<RestrictionsWithCounters>(
restrictions_or_state)) {
restrictions_or_state = AdaptIfFpsDiffInsufficient(
input_state,
absl::get<RestrictionsWithCounters>(restrictions_or_state));
}
return RestrictionsOrStateToAdaptation(restrictions_or_state, input_state);
}
VideoStreamAdapter::RestrictionsOrState
VideoStreamAdapter::AdaptIfFpsDiffInsufficient(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& restrictions) const {
RTC_DCHECK_EQ(degradation_preference_, DegradationPreference::BALANCED);
int frame_size_pixels = input_state.single_active_stream_pixels().value_or(
input_state.frame_size_pixels().value());
absl::optional<int> min_fps_diff =
balanced_settings_.MinFpsDiff(frame_size_pixels);
if (current_restrictions_.counters.fps_adaptations <
restrictions.counters.fps_adaptations &&
min_fps_diff && input_state.frames_per_second() > 0) {
int fps_diff = input_state.frames_per_second() -
restrictions.restrictions.max_frame_rate().value();
if (fps_diff < min_fps_diff.value()) {
return GetAdaptationDownStep(input_state, restrictions);
}
}
return restrictions;
}
VideoStreamAdapter::RestrictionsOrState
VideoStreamAdapter::GetAdaptationDownStep(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) const {
if (!HasSufficientInputForAdaptation(input_state)) {
return Adaptation::Status::kInsufficientInput;
}
// Don't adapt if we're awaiting a previous adaptation to have an effect or
// if we switched degradation preference.
if (awaiting_frame_size_change_ &&
!awaiting_frame_size_change_->pixels_increased &&
degradation_preference_ == DegradationPreference::MAINTAIN_FRAMERATE &&
input_state.frame_size_pixels().value() >=
awaiting_frame_size_change_->frame_size_pixels) {
return Adaptation::Status::kAwaitingPreviousAdaptation;
}
// Maybe propose targets based on degradation preference.
switch (degradation_preference_) {
case DegradationPreference::BALANCED: {
// Try scale down framerate, if lower.
RestrictionsOrState decrease_frame_rate =
DecreaseFramerate(input_state, current_restrictions);
if (absl::holds_alternative<RestrictionsWithCounters>(
decrease_frame_rate)) {
return decrease_frame_rate;
}
// else, decrease resolution.
[[fallthrough]];
}
case DegradationPreference::MAINTAIN_FRAMERATE: {
return DecreaseResolution(input_state, current_restrictions);
}
case DegradationPreference::MAINTAIN_RESOLUTION: {
return DecreaseFramerate(input_state, current_restrictions);
}
case DegradationPreference::DISABLED:
return Adaptation::Status::kAdaptationDisabled;
}
RTC_CHECK_NOTREACHED();
}
VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::DecreaseResolution(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) {
int target_pixels =
GetLowerResolutionThan(input_state.frame_size_pixels().value());
// Use single active stream if set, this stream could be lower than the input.
int target_pixels_min =
GetLowerResolutionThan(input_state.single_active_stream_pixels().value_or(
input_state.frame_size_pixels().value()));
if (!CanDecreaseResolutionTo(target_pixels, target_pixels_min, input_state,
current_restrictions.restrictions)) {
return Adaptation::Status::kLimitReached;
}
RestrictionsWithCounters new_restrictions = current_restrictions;
RTC_LOG(LS_INFO) << "Scaling down resolution, max pixels: " << target_pixels;
new_restrictions.restrictions.set_max_pixels_per_frame(
target_pixels != std::numeric_limits<int>::max()
? absl::optional<size_t>(target_pixels)
: absl::nullopt);
new_restrictions.restrictions.set_target_pixels_per_frame(absl::nullopt);
++new_restrictions.counters.resolution_adaptations;
return new_restrictions;
}
VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::DecreaseFramerate(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) const {
int max_frame_rate;
if (degradation_preference_ == DegradationPreference::MAINTAIN_RESOLUTION) {
max_frame_rate = GetLowerFrameRateThan(input_state.frames_per_second());
} else if (degradation_preference_ == DegradationPreference::BALANCED) {
int frame_size_pixels = input_state.single_active_stream_pixels().value_or(
input_state.frame_size_pixels().value());
max_frame_rate = balanced_settings_.MinFps(input_state.video_codec_type(),
frame_size_pixels);
} else {
RTC_DCHECK_NOTREACHED();
max_frame_rate = GetLowerFrameRateThan(input_state.frames_per_second());
}
if (!CanDecreaseFrameRateTo(max_frame_rate,
current_restrictions.restrictions)) {
return Adaptation::Status::kLimitReached;
}
RestrictionsWithCounters new_restrictions = current_restrictions;
max_frame_rate = std::max(kMinFrameRateFps, max_frame_rate);
RTC_LOG(LS_INFO) << "Scaling down framerate: " << max_frame_rate;
new_restrictions.restrictions.set_max_frame_rate(
max_frame_rate != std::numeric_limits<int>::max()
? absl::optional<double>(max_frame_rate)
: absl::nullopt);
++new_restrictions.counters.fps_adaptations;
return new_restrictions;
}
VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::IncreaseResolution(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) {
int target_pixels = input_state.frame_size_pixels().value();
if (current_restrictions.counters.resolution_adaptations == 1) {
RTC_LOG(LS_INFO) << "Removing resolution down-scaling setting.";
target_pixels = std::numeric_limits<int>::max();
}
target_pixels = GetHigherResolutionThan(target_pixels);
if (!CanIncreaseResolutionTo(target_pixels,
current_restrictions.restrictions)) {
return Adaptation::Status::kLimitReached;
}
int max_pixels_wanted = GetIncreasedMaxPixelsWanted(target_pixels);
RestrictionsWithCounters new_restrictions = current_restrictions;
RTC_LOG(LS_INFO) << "Scaling up resolution, max pixels: "
<< max_pixels_wanted;
new_restrictions.restrictions.set_max_pixels_per_frame(
max_pixels_wanted != std::numeric_limits<int>::max()
? absl::optional<size_t>(max_pixels_wanted)
: absl::nullopt);
new_restrictions.restrictions.set_target_pixels_per_frame(
max_pixels_wanted != std::numeric_limits<int>::max()
? absl::optional<size_t>(target_pixels)
: absl::nullopt);
--new_restrictions.counters.resolution_adaptations;
RTC_DCHECK_GE(new_restrictions.counters.resolution_adaptations, 0);
return new_restrictions;
}
VideoStreamAdapter::RestrictionsOrState VideoStreamAdapter::IncreaseFramerate(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) const {
int max_frame_rate;
if (degradation_preference_ == DegradationPreference::MAINTAIN_RESOLUTION) {
max_frame_rate = GetHigherFrameRateThan(input_state.frames_per_second());
} else if (degradation_preference_ == DegradationPreference::BALANCED) {
int frame_size_pixels = input_state.single_active_stream_pixels().value_or(
input_state.frame_size_pixels().value());
max_frame_rate = balanced_settings_.MaxFps(input_state.video_codec_type(),
frame_size_pixels);
// Temporary fix for cases when there are fewer framerate adaptation steps
// up than down. Make number of down/up steps equal.
if (max_frame_rate == std::numeric_limits<int>::max() &&
current_restrictions.counters.fps_adaptations > 1) {
// Do not unrestrict framerate to allow additional adaptation up steps.
RTC_LOG(LS_INFO) << "Modifying framerate due to remaining fps count.";
max_frame_rate -= current_restrictions.counters.fps_adaptations;
}
// In BALANCED, the max_frame_rate must be checked before proceeding. This
// is because the MaxFps might be the current Fps and so the balanced
// settings may want to scale up the resolution.
if (!CanIncreaseFrameRateTo(max_frame_rate,
current_restrictions.restrictions)) {
return Adaptation::Status::kLimitReached;
}
} else {
RTC_DCHECK_NOTREACHED();
max_frame_rate = GetHigherFrameRateThan(input_state.frames_per_second());
}
if (current_restrictions.counters.fps_adaptations == 1) {
RTC_LOG(LS_INFO) << "Removing framerate down-scaling setting.";
max_frame_rate = std::numeric_limits<int>::max();
}
if (!CanIncreaseFrameRateTo(max_frame_rate,
current_restrictions.restrictions)) {
return Adaptation::Status::kLimitReached;
}
RTC_LOG(LS_INFO) << "Scaling up framerate: " << max_frame_rate;
RestrictionsWithCounters new_restrictions = current_restrictions;
new_restrictions.restrictions.set_max_frame_rate(
max_frame_rate != std::numeric_limits<int>::max()
? absl::optional<double>(max_frame_rate)
: absl::nullopt);
--new_restrictions.counters.fps_adaptations;
RTC_DCHECK_GE(new_restrictions.counters.fps_adaptations, 0);
return new_restrictions;
}
Adaptation VideoStreamAdapter::GetAdaptDownResolution() {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoStreamInputState input_state = input_state_provider_->InputState();
switch (degradation_preference_) {
case DegradationPreference::DISABLED:
return RestrictionsOrStateToAdaptation(
Adaptation::Status::kAdaptationDisabled, input_state);
case DegradationPreference::MAINTAIN_RESOLUTION:
return RestrictionsOrStateToAdaptation(Adaptation::Status::kLimitReached,
input_state);
case DegradationPreference::MAINTAIN_FRAMERATE:
return GetAdaptationDown();
case DegradationPreference::BALANCED: {
return RestrictionsOrStateToAdaptation(
GetAdaptDownResolutionStepForBalanced(input_state), input_state);
}
}
RTC_CHECK_NOTREACHED();
}
VideoStreamAdapter::RestrictionsOrState
VideoStreamAdapter::GetAdaptDownResolutionStepForBalanced(
const VideoStreamInputState& input_state) const {
// Adapt twice if the first adaptation did not decrease resolution.
auto first_step = GetAdaptationDownStep(input_state, current_restrictions_);
if (!absl::holds_alternative<RestrictionsWithCounters>(first_step)) {
return first_step;
}
auto first_restrictions = absl::get<RestrictionsWithCounters>(first_step);
if (first_restrictions.counters.resolution_adaptations >
current_restrictions_.counters.resolution_adaptations) {
return first_step;
}
// We didn't decrease resolution so force it; amend a resolution resuction
// to the existing framerate reduction in `first_restrictions`.
auto second_step = DecreaseResolution(input_state, first_restrictions);
if (absl::holds_alternative<RestrictionsWithCounters>(second_step)) {
return second_step;
}
// If the second step was not successful then settle for the first one.
return first_step;
}
void VideoStreamAdapter::ApplyAdaptation(
const Adaptation& adaptation,
rtc::scoped_refptr<Resource> resource) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
RTC_DCHECK_EQ(adaptation.validation_id_, adaptation_validation_id_);
if (adaptation.status() != Adaptation::Status::kValid)
return;
// Remember the input pixels and fps of this adaptation. Used to avoid
// adapting again before this adaptation has had an effect.
if (DidIncreaseResolution(current_restrictions_.restrictions,
adaptation.restrictions())) {
awaiting_frame_size_change_.emplace(
true, adaptation.input_state().frame_size_pixels().value());
} else if (DidDecreaseResolution(current_restrictions_.restrictions,
adaptation.restrictions())) {
awaiting_frame_size_change_.emplace(
false, adaptation.input_state().frame_size_pixels().value());
} else {
awaiting_frame_size_change_ = absl::nullopt;
}
current_restrictions_ = {adaptation.restrictions(), adaptation.counters()};
BroadcastVideoRestrictionsUpdate(adaptation.input_state(), resource);
}
Adaptation VideoStreamAdapter::GetAdaptationTo(
const VideoAdaptationCounters& counters,
const VideoSourceRestrictions& restrictions) {
// Adapts up/down from the current levels so counters are equal.
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoStreamInputState input_state = input_state_provider_->InputState();
return Adaptation(adaptation_validation_id_, restrictions, counters,
input_state);
}
void VideoStreamAdapter::BroadcastVideoRestrictionsUpdate(
const VideoStreamInputState& input_state,
const rtc::scoped_refptr<Resource>& resource) {
RTC_DCHECK_RUN_ON(&sequence_checker_);
VideoSourceRestrictions filtered = FilterRestrictionsByDegradationPreference(
source_restrictions(), degradation_preference_);
if (last_filtered_restrictions_ == filtered) {
return;
}
for (auto* restrictions_listener : restrictions_listeners_) {
restrictions_listener->OnVideoSourceRestrictionsUpdated(
filtered, current_restrictions_.counters, resource,
source_restrictions());
}
last_video_source_restrictions_ = current_restrictions_.restrictions;
last_filtered_restrictions_ = filtered;
}
bool VideoStreamAdapter::HasSufficientInputForAdaptation(
const VideoStreamInputState& input_state) const {
return input_state.HasInputFrameSizeAndFramesPerSecond() &&
(degradation_preference_ !=
DegradationPreference::MAINTAIN_RESOLUTION ||
input_state.frames_per_second() >= kMinFrameRateFps);
}
VideoStreamAdapter::AwaitingFrameSizeChange::AwaitingFrameSizeChange(
bool pixels_increased,
int frame_size_pixels)
: pixels_increased(pixels_increased),
frame_size_pixels(frame_size_pixels) {}
absl::optional<uint32_t> VideoStreamAdapter::GetSingleActiveLayerPixels(
const VideoCodec& codec) {
int num_active = 0;
absl::optional<uint32_t> pixels;
if (codec.codecType == VideoCodecType::kVideoCodecAV1 &&
codec.GetScalabilityMode().has_value()) {
for (int i = 0;
i < ScalabilityModeToNumSpatialLayers(*(codec.GetScalabilityMode()));
++i) {
if (codec.spatialLayers[i].active) {
++num_active;
pixels = codec.spatialLayers[i].width * codec.spatialLayers[i].height;
}
}
} else if (codec.codecType == VideoCodecType::kVideoCodecVP9) {
for (int i = 0; i < codec.VP9().numberOfSpatialLayers; ++i) {
if (codec.spatialLayers[i].active) {
++num_active;
pixels = codec.spatialLayers[i].width * codec.spatialLayers[i].height;
}
}
} else {
for (int i = 0; i < codec.numberOfSimulcastStreams; ++i) {
if (codec.simulcastStream[i].active) {
++num_active;
pixels =
codec.simulcastStream[i].width * codec.simulcastStream[i].height;
}
}
}
return (num_active > 1) ? absl::nullopt : pixels;
}
} // namespace webrtc

View file

@ -0,0 +1,271 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_VIDEO_STREAM_ADAPTER_H_
#define CALL_ADAPTATION_VIDEO_STREAM_ADAPTER_H_
#include <memory>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "absl/types/variant.h"
#include "api/adaptation/resource.h"
#include "api/field_trials_view.h"
#include "api/rtp_parameters.h"
#include "api/video/video_adaptation_counters.h"
#include "call/adaptation/adaptation_constraint.h"
#include "call/adaptation/degradation_preference_provider.h"
#include "call/adaptation/video_source_restrictions.h"
#include "call/adaptation/video_stream_input_state.h"
#include "call/adaptation/video_stream_input_state_provider.h"
#include "modules/video_coding/utility/quality_scaler.h"
#include "rtc_base/experiments/balanced_degradation_settings.h"
#include "rtc_base/system/no_unique_address.h"
#include "rtc_base/thread_annotations.h"
#include "video/video_stream_encoder_observer.h"
namespace webrtc {
// The listener is responsible for carrying out the reconfiguration of the video
// source such that the VideoSourceRestrictions are fulfilled.
class VideoSourceRestrictionsListener {
public:
virtual ~VideoSourceRestrictionsListener();
// The `restrictions` are filtered by degradation preference but not the
// `adaptation_counters`, which are currently only reported for legacy stats
// calculation purposes.
virtual void OnVideoSourceRestrictionsUpdated(
VideoSourceRestrictions restrictions,
const VideoAdaptationCounters& adaptation_counters,
rtc::scoped_refptr<Resource> reason,
const VideoSourceRestrictions& unfiltered_restrictions) = 0;
};
class VideoStreamAdapter;
extern const int kMinFrameRateFps;
VideoSourceRestrictions FilterRestrictionsByDegradationPreference(
VideoSourceRestrictions source_restrictions,
DegradationPreference degradation_preference);
int GetLowerResolutionThan(int pixel_count);
int GetHigherResolutionThan(int pixel_count);
// Either represents the next VideoSourceRestrictions the VideoStreamAdapter
// will take, or provides a Status code indicating the reason for not adapting
// if the adaptation is not valid.
class Adaptation final {
public:
enum class Status {
// Applying this adaptation will have an effect. All other Status codes
// indicate that adaptation is not possible and why.
kValid,
// Cannot adapt. The minimum or maximum adaptation has already been reached.
// There are no more steps to take.
kLimitReached,
// Cannot adapt. The resolution or frame rate requested by a recent
// adaptation has not yet been reflected in the input resolution or frame
// rate; adaptation is refused to avoid "double-adapting".
kAwaitingPreviousAdaptation,
// Not enough input.
kInsufficientInput,
// Adaptation disabled via degradation preference.
kAdaptationDisabled,
// Adaptation up was rejected by a VideoAdaptationConstraint.
kRejectedByConstraint,
};
static const char* StatusToString(Status status);
Status status() const;
const VideoStreamInputState& input_state() const;
const VideoSourceRestrictions& restrictions() const;
const VideoAdaptationCounters& counters() const;
private:
friend class VideoStreamAdapter;
// Constructs with a valid adaptation. Status is kValid.
Adaptation(int validation_id,
VideoSourceRestrictions restrictions,
VideoAdaptationCounters counters,
VideoStreamInputState input_state);
// Constructor when adaptation is not valid. Status MUST NOT be kValid.
Adaptation(int validation_id, Status invalid_status);
// An Adaptation can become invalidated if the state of VideoStreamAdapter is
// modified before the Adaptation is applied. To guard against this, this ID
// has to match VideoStreamAdapter::adaptation_validation_id_ when applied.
// TODO(https://crbug.com/webrtc/11700): Remove the validation_id_.
const int validation_id_;
const Status status_;
// Input state when adaptation was made.
const VideoStreamInputState input_state_;
const VideoSourceRestrictions restrictions_;
const VideoAdaptationCounters counters_;
};
// Owns the VideoSourceRestriction for a single stream and is responsible for
// adapting it up or down when told to do so. This class serves the following
// purposes:
// 1. Keep track of a stream's restrictions.
// 2. Provide valid ways to adapt up or down the stream's restrictions.
// 3. Modify the stream's restrictions in one of the valid ways.
class VideoStreamAdapter {
public:
VideoStreamAdapter(VideoStreamInputStateProvider* input_state_provider,
VideoStreamEncoderObserver* encoder_stats_observer,
const FieldTrialsView& field_trials);
~VideoStreamAdapter();
VideoSourceRestrictions source_restrictions() const;
const VideoAdaptationCounters& adaptation_counters() const;
void ClearRestrictions();
void AddRestrictionsListener(
VideoSourceRestrictionsListener* restrictions_listener);
void RemoveRestrictionsListener(
VideoSourceRestrictionsListener* restrictions_listener);
void AddAdaptationConstraint(AdaptationConstraint* adaptation_constraint);
void RemoveAdaptationConstraint(AdaptationConstraint* adaptation_constraint);
// TODO(hbos): Setting the degradation preference should not clear
// restrictions! This is not defined in the spec and is unexpected, there is a
// tiny risk that people would discover and rely on this behavior.
void SetDegradationPreference(DegradationPreference degradation_preference);
// Returns an adaptation that we are guaranteed to be able to apply, or a
// status code indicating the reason why we cannot adapt.
Adaptation GetAdaptationUp();
Adaptation GetAdaptationDown();
Adaptation GetAdaptationTo(const VideoAdaptationCounters& counters,
const VideoSourceRestrictions& restrictions);
// Tries to adapt the resolution one step. This is used for initial frame
// dropping. Does nothing if the degradation preference is not BALANCED or
// MAINTAIN_FRAMERATE. In the case of BALANCED, it will try twice to reduce
// the resolution. If it fails twice it gives up.
Adaptation GetAdaptDownResolution();
// Updates source_restrictions() the Adaptation.
void ApplyAdaptation(const Adaptation& adaptation,
rtc::scoped_refptr<Resource> resource);
struct RestrictionsWithCounters {
VideoSourceRestrictions restrictions;
VideoAdaptationCounters counters;
};
static absl::optional<uint32_t> GetSingleActiveLayerPixels(
const VideoCodec& codec);
private:
void BroadcastVideoRestrictionsUpdate(
const VideoStreamInputState& input_state,
const rtc::scoped_refptr<Resource>& resource);
bool HasSufficientInputForAdaptation(const VideoStreamInputState& input_state)
const RTC_RUN_ON(&sequence_checker_);
using RestrictionsOrState =
absl::variant<RestrictionsWithCounters, Adaptation::Status>;
RestrictionsOrState GetAdaptationUpStep(
const VideoStreamInputState& input_state) const
RTC_RUN_ON(&sequence_checker_);
RestrictionsOrState GetAdaptationDownStep(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) const
RTC_RUN_ON(&sequence_checker_);
RestrictionsOrState GetAdaptDownResolutionStepForBalanced(
const VideoStreamInputState& input_state) const
RTC_RUN_ON(&sequence_checker_);
RestrictionsOrState AdaptIfFpsDiffInsufficient(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& restrictions) const
RTC_RUN_ON(&sequence_checker_);
Adaptation GetAdaptationUp(const VideoStreamInputState& input_state) const
RTC_RUN_ON(&sequence_checker_);
Adaptation GetAdaptationDown(const VideoStreamInputState& input_state) const
RTC_RUN_ON(&sequence_checker_);
static RestrictionsOrState DecreaseResolution(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions);
static RestrictionsOrState IncreaseResolution(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions);
// Framerate methods are member functions because they need internal state
// if the degradation preference is BALANCED.
RestrictionsOrState DecreaseFramerate(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) const
RTC_RUN_ON(&sequence_checker_);
RestrictionsOrState IncreaseFramerate(
const VideoStreamInputState& input_state,
const RestrictionsWithCounters& current_restrictions) const
RTC_RUN_ON(&sequence_checker_);
struct RestrictionsOrStateVisitor;
Adaptation RestrictionsOrStateToAdaptation(
RestrictionsOrState step_or_state,
const VideoStreamInputState& input_state) const
RTC_RUN_ON(&sequence_checker_);
RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_
RTC_GUARDED_BY(&sequence_checker_);
// Gets the input state which is the basis of all adaptations.
// Thread safe.
VideoStreamInputStateProvider* input_state_provider_;
// Used to signal when min pixel limit has been reached.
VideoStreamEncoderObserver* const encoder_stats_observer_;
// Decides the next adaptation target in DegradationPreference::BALANCED.
const BalancedDegradationSettings balanced_settings_;
// To guard against applying adaptations that have become invalidated, an
// Adaptation that is applied has to have a matching validation ID.
int adaptation_validation_id_ RTC_GUARDED_BY(&sequence_checker_);
// When deciding the next target up or down, different strategies are used
// depending on the DegradationPreference.
// https://w3c.github.io/mst-content-hint/#dom-rtcdegradationpreference
DegradationPreference degradation_preference_
RTC_GUARDED_BY(&sequence_checker_);
// Used to avoid adapting twice. Stores the resolution at the time of the last
// adaptation.
// TODO(hbos): Can we implement a more general "cooldown" mechanism of
// resources intead? If we already have adapted it seems like we should wait
// a while before adapting again, so that we are not acting on usage
// measurements that are made obsolete/unreliable by an "ongoing" adaptation.
struct AwaitingFrameSizeChange {
AwaitingFrameSizeChange(bool pixels_increased, int frame_size);
const bool pixels_increased;
const int frame_size_pixels;
};
absl::optional<AwaitingFrameSizeChange> awaiting_frame_size_change_
RTC_GUARDED_BY(&sequence_checker_);
// The previous restrictions value. Starts as unrestricted.
VideoSourceRestrictions last_video_source_restrictions_
RTC_GUARDED_BY(&sequence_checker_);
VideoSourceRestrictions last_filtered_restrictions_
RTC_GUARDED_BY(&sequence_checker_);
std::vector<VideoSourceRestrictionsListener*> restrictions_listeners_
RTC_GUARDED_BY(&sequence_checker_);
std::vector<AdaptationConstraint*> adaptation_constraints_
RTC_GUARDED_BY(&sequence_checker_);
RestrictionsWithCounters current_restrictions_
RTC_GUARDED_BY(&sequence_checker_);
};
} // namespace webrtc
#endif // CALL_ADAPTATION_VIDEO_STREAM_ADAPTER_H_

View file

@ -0,0 +1,80 @@
/*
* 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 "call/adaptation/video_stream_input_state.h"
#include "api/video_codecs/video_encoder.h"
namespace webrtc {
VideoStreamInputState::VideoStreamInputState()
: has_input_(false),
frame_size_pixels_(absl::nullopt),
frames_per_second_(0),
video_codec_type_(VideoCodecType::kVideoCodecGeneric),
min_pixels_per_frame_(kDefaultMinPixelsPerFrame),
single_active_stream_pixels_(absl::nullopt) {}
void VideoStreamInputState::set_has_input(bool has_input) {
has_input_ = has_input;
}
void VideoStreamInputState::set_frame_size_pixels(
absl::optional<int> frame_size_pixels) {
frame_size_pixels_ = frame_size_pixels;
}
void VideoStreamInputState::set_frames_per_second(int frames_per_second) {
frames_per_second_ = frames_per_second;
}
void VideoStreamInputState::set_video_codec_type(
VideoCodecType video_codec_type) {
video_codec_type_ = video_codec_type;
}
void VideoStreamInputState::set_min_pixels_per_frame(int min_pixels_per_frame) {
min_pixels_per_frame_ = min_pixels_per_frame;
}
void VideoStreamInputState::set_single_active_stream_pixels(
absl::optional<int> single_active_stream_pixels) {
single_active_stream_pixels_ = single_active_stream_pixels;
}
bool VideoStreamInputState::has_input() const {
return has_input_;
}
absl::optional<int> VideoStreamInputState::frame_size_pixels() const {
return frame_size_pixels_;
}
int VideoStreamInputState::frames_per_second() const {
return frames_per_second_;
}
VideoCodecType VideoStreamInputState::video_codec_type() const {
return video_codec_type_;
}
int VideoStreamInputState::min_pixels_per_frame() const {
return min_pixels_per_frame_;
}
absl::optional<int> VideoStreamInputState::single_active_stream_pixels() const {
return single_active_stream_pixels_;
}
bool VideoStreamInputState::HasInputFrameSizeAndFramesPerSecond() const {
return has_input_ && frame_size_pixels_.has_value();
}
} // namespace webrtc

View file

@ -0,0 +1,53 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_VIDEO_STREAM_INPUT_STATE_H_
#define CALL_ADAPTATION_VIDEO_STREAM_INPUT_STATE_H_
#include "absl/types/optional.h"
#include "api/video/video_codec_type.h"
namespace webrtc {
// The source resolution, frame rate and other properties of a
// VideoStreamEncoder.
class VideoStreamInputState {
public:
VideoStreamInputState();
void set_has_input(bool has_input);
void set_frame_size_pixels(absl::optional<int> frame_size_pixels);
void set_frames_per_second(int frames_per_second);
void set_video_codec_type(VideoCodecType video_codec_type);
void set_min_pixels_per_frame(int min_pixels_per_frame);
void set_single_active_stream_pixels(
absl::optional<int> single_active_stream_pixels);
bool has_input() const;
absl::optional<int> frame_size_pixels() const;
int frames_per_second() const;
VideoCodecType video_codec_type() const;
int min_pixels_per_frame() const;
absl::optional<int> single_active_stream_pixels() const;
bool HasInputFrameSizeAndFramesPerSecond() const;
private:
bool has_input_;
absl::optional<int> frame_size_pixels_;
int frames_per_second_;
VideoCodecType video_codec_type_;
int min_pixels_per_frame_;
absl::optional<int> single_active_stream_pixels_;
};
} // namespace webrtc
#endif // CALL_ADAPTATION_VIDEO_STREAM_INPUT_STATE_H_

View file

@ -0,0 +1,54 @@
/*
* 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 "call/adaptation/video_stream_input_state_provider.h"
#include "call/adaptation/video_stream_adapter.h"
namespace webrtc {
VideoStreamInputStateProvider::VideoStreamInputStateProvider(
VideoStreamEncoderObserver* frame_rate_provider)
: frame_rate_provider_(frame_rate_provider) {}
VideoStreamInputStateProvider::~VideoStreamInputStateProvider() {}
void VideoStreamInputStateProvider::OnHasInputChanged(bool has_input) {
MutexLock lock(&mutex_);
input_state_.set_has_input(has_input);
}
void VideoStreamInputStateProvider::OnFrameSizeObserved(int frame_size_pixels) {
RTC_DCHECK_GT(frame_size_pixels, 0);
MutexLock lock(&mutex_);
input_state_.set_frame_size_pixels(frame_size_pixels);
}
void VideoStreamInputStateProvider::OnEncoderSettingsChanged(
EncoderSettings encoder_settings) {
MutexLock lock(&mutex_);
input_state_.set_video_codec_type(
encoder_settings.encoder_config().codec_type);
input_state_.set_min_pixels_per_frame(
encoder_settings.encoder_info().scaling_settings.min_pixels_per_frame);
input_state_.set_single_active_stream_pixels(
VideoStreamAdapter::GetSingleActiveLayerPixels(
encoder_settings.video_codec()));
}
VideoStreamInputState VideoStreamInputStateProvider::InputState() {
// GetInputFrameRate() is thread-safe.
int input_fps = frame_rate_provider_->GetInputFrameRate();
MutexLock lock(&mutex_);
input_state_.set_frames_per_second(input_fps);
return input_state_;
}
} // namespace webrtc

View file

@ -0,0 +1,41 @@
/*
* 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.
*/
#ifndef CALL_ADAPTATION_VIDEO_STREAM_INPUT_STATE_PROVIDER_H_
#define CALL_ADAPTATION_VIDEO_STREAM_INPUT_STATE_PROVIDER_H_
#include "call/adaptation/encoder_settings.h"
#include "call/adaptation/video_stream_input_state.h"
#include "rtc_base/synchronization/mutex.h"
#include "video/video_stream_encoder_observer.h"
namespace webrtc {
class VideoStreamInputStateProvider {
public:
VideoStreamInputStateProvider(
VideoStreamEncoderObserver* frame_rate_provider);
virtual ~VideoStreamInputStateProvider();
void OnHasInputChanged(bool has_input);
void OnFrameSizeObserved(int frame_size_pixels);
void OnEncoderSettingsChanged(EncoderSettings encoder_settings);
virtual VideoStreamInputState InputState();
private:
Mutex mutex_;
VideoStreamEncoderObserver* const frame_rate_provider_;
VideoStreamInputState input_state_ RTC_GUARDED_BY(mutex_);
};
} // namespace webrtc
#endif // CALL_ADAPTATION_VIDEO_STREAM_INPUT_STATE_PROVIDER_H_