diff --git a/BUILD.gn b/BUILD.gn index 83c3263a3e..3ad6b08bfa 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -605,6 +605,7 @@ if (rtc_include_tests) { # TODO(eladalon): call_tests aren't actually video-specific, so we # should move them to a more appropriate test suite. "call:call_tests", + "call/adaptation:resource_adaptation_tests", "test:test_common", "test:test_main", "test:video_test_common", diff --git a/call/adaptation/BUILD.gn b/call/adaptation/BUILD.gn new file mode 100644 index 0000000000..12d04a18b3 --- /dev/null +++ b/call/adaptation/BUILD.gn @@ -0,0 +1,60 @@ +# Copyright (c) 2019 The WebRTC project authors. All Rights Reserved. +# +# Use of this source code is governed by a BSD-style license +# that can be found in the LICENSE file in the root of the source +# tree. An additional intellectual property rights grant can be found +# in the file PATENTS. All contributing project authors may +# be found in the AUTHORS file in the root of the source tree. + +import("../../webrtc.gni") + +rtc_library("resource_adaptation") { + sources = [ + "resource.cc", + "resource.h", + "resource_adaptation_processor.cc", + "resource_adaptation_processor.h", + "resource_consumer.cc", + "resource_consumer.h", + "resource_consumer_configuration.cc", + "resource_consumer_configuration.h", + ] + deps = [ + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + +if (rtc_include_tests) { + rtc_library("resource_adaptation_tests") { + testonly = true + + sources = [ + "resource_adaptation_processor_unittest.cc", + ] + deps = [ + ":resource_adaptation", + ":resource_adaptation_test_utilities", + "../../rtc_base:checks", + "../../rtc_base:rtc_base_approved", + "../../test:test_support", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + + rtc_source_set("resource_adaptation_test_utilities") { + testonly = true + + sources = [ + "test/fake_resource.cc", + "test/fake_resource.h", + "test/fake_resource_consumer_configuration.cc", + "test/fake_resource_consumer_configuration.h", + ] + deps = [ + ":resource_adaptation", + "../../rtc_base:rtc_base_approved", + ] + } +} diff --git a/call/adaptation/OWNERS b/call/adaptation/OWNERS new file mode 100644 index 0000000000..8a355d83f4 --- /dev/null +++ b/call/adaptation/OWNERS @@ -0,0 +1,7 @@ +hbos@webrtc.org +sprang@webrtc.org + +# These are for the common case of adding or renaming files. If you're doing +# structural changes, please get a review from a reviewer in this file. +per-file *.gn=* +per-file *.gni=* diff --git a/call/adaptation/resource.cc b/call/adaptation/resource.cc new file mode 100644 index 0000000000..e6974b1d9d --- /dev/null +++ b/call/adaptation/resource.cc @@ -0,0 +1,41 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/resource.h" + +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +namespace { + +const char* ResourceUsageStateToString(ResourceUsageState usage_state) { + switch (usage_state) { + case ResourceUsageState::kOveruse: + return "overuse"; + case ResourceUsageState::kStable: + return "stable"; + case ResourceUsageState::kUnderuse: + return "underuse"; + } +} + +} // namespace + +Resource::~Resource() {} + +std::string Resource::ToString() const { + rtc::StringBuilder sb; + sb << Name() << ": " << CurrentUsage() << " " << UsageUnitsOfMeasurement(); + sb << " (" << ResourceUsageStateToString(CurrentUsageState()) << ")"; + return sb.str(); +} + +} // namespace webrtc diff --git a/call/adaptation/resource.h b/call/adaptation/resource.h new file mode 100644 index 0000000000..0bd142168f --- /dev/null +++ b/call/adaptation/resource.h @@ -0,0 +1,58 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CALL_ADAPTATION_RESOURCE_H_ +#define CALL_ADAPTATION_RESOURCE_H_ + +#include + +namespace webrtc { + +enum class ResourceUsageState { + // Action is needed to minimze the load on this resource. + kOveruse, + // No action needed for this resource, increasing the load on this resource + // is not allowed. + kStable, + // Increasing the load on this resource is allowed. + kUnderuse, +}; + +// A Resource is something which can be measured as "overused", "stable" or +// "underused". For example, if we are overusing CPU we may need to lower the +// resolution of one of the streams. In other words, one of the ResourceConumers +// - representing an encoder - needs to be reconfigured with a different +// ResourceConsumerConfiguration - representing a different encoder setting. +// +// This is an abstract class used by the ResourceAdaptationProcessor to make +// decisions about which configurations to use. How a resource is measured or +// what measurements map to different ResourceUsageState values is +// implementation-specific. +class Resource { + public: + virtual ~Resource(); + + // Informational, not formally part of the decision-making process. + virtual std::string Name() const = 0; + virtual std::string UsageUnitsOfMeasurement() const = 0; + // Valid ranges are implementation-specific. + virtual double CurrentUsage() const = 0; + + // The current usage state of this resource. Used by the + // ResourceAdaptationProcessor to calculate the desired consumer + // configurations. + virtual ResourceUsageState CurrentUsageState() const = 0; + + std::string ToString() const; +}; + +} // namespace webrtc + +#endif // CALL_ADAPTATION_RESOURCE_H_ diff --git a/call/adaptation/resource_adaptation_processor.cc b/call/adaptation/resource_adaptation_processor.cc new file mode 100644 index 0000000000..e4f209fe9d --- /dev/null +++ b/call/adaptation/resource_adaptation_processor.cc @@ -0,0 +1,128 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/resource_adaptation_processor.h" + +#include +#include + +#include "rtc_base/checks.h" + +namespace webrtc { + +namespace { + +ResourceConsumerConfiguration* FindMostPreferredConfiguration( + const std::vector& configurations) { + if (configurations.empty()) + return nullptr; + ResourceConsumerConfiguration* most_preferred_configuration = + configurations[0]; + double most_preferred_configuration_preference = + most_preferred_configuration->Preference(); + RTC_DCHECK_GE(most_preferred_configuration_preference, 0.0); + for (size_t i = 1; i < configurations.size(); ++i) { + auto* configuration = configurations[i]; + double preference = configuration->Preference(); + RTC_DCHECK_GE(preference, 0.0); + if (most_preferred_configuration_preference < preference) { + most_preferred_configuration = configuration; + most_preferred_configuration_preference = preference; + } + } + return most_preferred_configuration; +} + +} // namespace + +ConsumerConfigurationPair::ConsumerConfigurationPair( + ResourceConsumer* consumer, + ResourceConsumerConfiguration* configuration) + : consumer(consumer), configuration(configuration) {} + +absl::optional +ResourceAdaptationProcessor::FindNextConfiguration() { + ResourceUsageState overall_usage = ResourceUsageState::kUnderuse; + for (auto& resource : resources_) { + ResourceUsageState resource_usage = resource->CurrentUsageState(); + if (resource_usage == ResourceUsageState::kStable) { + // If any resource is "stable", we are not underusing. + if (overall_usage == ResourceUsageState::kUnderuse) + overall_usage = ResourceUsageState::kStable; + } else if (resource_usage == ResourceUsageState::kOveruse) { + // If any resource is "overuse", we are overusing. + overall_usage = ResourceUsageState::kOveruse; + break; + } + } + // If we are stable we should neither adapt up or down: stay where we are. + if (overall_usage == ResourceUsageState::kStable) + return absl::nullopt; + if (overall_usage == ResourceUsageState::kOveruse) { + // If we are overusing, we adapt down the most expensive consumer to its + // most preferred lower neighbor. + ResourceConsumer* max_cost_consumer = + FindMostExpensiveConsumerThatCanBeAdaptedDown(); + if (!max_cost_consumer) + return absl::nullopt; + ResourceConsumerConfiguration* next_configuration = + FindMostPreferredConfiguration( + max_cost_consumer->configuration()->lower_neighbors()); + RTC_DCHECK(next_configuration); + return ConsumerConfigurationPair(max_cost_consumer, next_configuration); + } else { + RTC_DCHECK_EQ(overall_usage, ResourceUsageState::kUnderuse); + // If we are underusing, we adapt up the least expensive consumer to its + // most preferred upper neighbor. + ResourceConsumer* min_cost_consumer = + FindLeastExpensiveConsumerThatCanBeAdaptedUp(); + if (!min_cost_consumer) + return absl::nullopt; + ResourceConsumerConfiguration* next_configuration = + FindMostPreferredConfiguration( + min_cost_consumer->configuration()->upper_neighbors()); + RTC_DCHECK(next_configuration); + return ConsumerConfigurationPair(min_cost_consumer, next_configuration); + } +} + +ResourceConsumer* +ResourceAdaptationProcessor::FindMostExpensiveConsumerThatCanBeAdaptedDown() { + ResourceConsumer* max_cost_consumer = nullptr; + double max_cost = -1.0; + for (auto& consumer : consumers_) { + if (consumer->configuration()->lower_neighbors().empty()) + continue; + double cost = consumer->configuration()->Cost(); + if (max_cost < cost) { + max_cost_consumer = consumer.get(); + max_cost = cost; + } + } + return max_cost_consumer; +} + +ResourceConsumer* +ResourceAdaptationProcessor::FindLeastExpensiveConsumerThatCanBeAdaptedUp() { + ResourceConsumer* min_cost_consumer = nullptr; + double min_cost = std::numeric_limits::infinity(); + for (auto& consumer : consumers_) { + if (consumer->configuration()->upper_neighbors().empty()) + continue; + double cost = consumer->configuration()->Cost(); + if (min_cost > cost) { + min_cost_consumer = consumer.get(); + min_cost = cost; + } + } + return min_cost_consumer; +} + +} // namespace webrtc diff --git a/call/adaptation/resource_adaptation_processor.h b/call/adaptation/resource_adaptation_processor.h new file mode 100644 index 0000000000..2855302beb --- /dev/null +++ b/call/adaptation/resource_adaptation_processor.h @@ -0,0 +1,118 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_ +#define CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_ + +#include +#include +#include + +#include "absl/types/optional.h" +#include "call/adaptation/resource.h" +#include "call/adaptation/resource_consumer.h" +#include "call/adaptation/resource_consumer_configuration.h" + +namespace webrtc { + +struct ConsumerConfigurationPair { + ConsumerConfigurationPair(ResourceConsumer* consumer, + ResourceConsumerConfiguration* configuration); + + ResourceConsumer* consumer; + ResourceConsumerConfiguration* configuration; +}; + +// Given a set of Resources, ResourceConsumers and +// ResourceConsumerConfigurations, the processor calculates which consumer, if +// any, should be reconfigured and how, in order to adapt to resource +// constraints. +// Example: "CPU" is a resource, a video stream being encoded is a consumer +// and the encoder setting (e.g. VP8/720p/30fps) is a configuration. +// +// A resource can be "overused", "stable" or "underused". The processor +// maximises quality without overusing any resource as follows: +// 1. If we are "overusing" on any resource, find the most expensive consumer +// and adapt it one step "down". +// 2. If we are "underusing" on all resources, find the least expensive consumer +// and adapt it one step "up". +// +// The expensiveness of a consumer is the expensiveness of its current +// configuration and the cost of a configuration is estimated based on pixels +// per second. How a consumer can be reconfigured in terms of one step "up" or +// "down" is expressed as a graph: each configuration has a set of "upper" +// neighbors and "lower" neighbors. When there are multiple options, neighbors +// are chosen based on configuration preferences. +// +// See FindNextConfiguration(). +// +// This class owns all resources, consumers and configurations. As long as it is +// alive, raw pointers to these are safe to use. +class ResourceAdaptationProcessor { + public: + const std::vector>& resources() const { + return resources_; + } + const std::vector>& + configurations() const { + return configurations_; + } + const std::vector>& consumers() const { + return consumers_; + } + + // Takes on ownership of the argument. A raw pointer is returned to the object + // for convenience; it is valid for the lifetime of the + // ResourceAdaptationProcessor. + // T = any subclass of Resource + template + T* AddResource(std::unique_ptr resource) { + T* resource_ptr = resource.get(); + resources_.push_back(std::move(resource)); + return resource_ptr; + } + // T = any subclass of ResourceConsumerConfiguration + template + T* AddConfiguration(std::unique_ptr configuration) { + T* configuration_ptr = configuration.get(); + configurations_.push_back(std::move(configuration)); + return configuration_ptr; + } + // T = any subclass of ResourceConsumer + template + T* AddConsumer(std::unique_ptr consumer) { + T* consumer_ptr = consumer.get(); + consumers_.push_back(std::move(consumer)); + return consumer_ptr; + } + + // Based on the current state of the resources and consumers, finds the + // consumer that should be reconfigured up or down in order to maximies + // quality without overusing any resources, as described in + // ResourceAdaptationProcessor's class description. + // + // When this is used in a real system, care needs to be taken for how often + // FindNextConfiguration() is called. There may be a delay between + // reconfiguring a consumer and the desired effects being observed on resource + // usage. + absl::optional FindNextConfiguration(); + + private: + ResourceConsumer* FindMostExpensiveConsumerThatCanBeAdaptedDown(); + ResourceConsumer* FindLeastExpensiveConsumerThatCanBeAdaptedUp(); + + std::vector> resources_; + std::vector> configurations_; + std::vector> consumers_; +}; + +} // namespace webrtc + +#endif // CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_ diff --git a/call/adaptation/resource_adaptation_processor_unittest.cc b/call/adaptation/resource_adaptation_processor_unittest.cc new file mode 100644 index 0000000000..38f9fa1143 --- /dev/null +++ b/call/adaptation/resource_adaptation_processor_unittest.cc @@ -0,0 +1,261 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/resource_adaptation_processor.h" + +#include "absl/types/optional.h" +#include "call/adaptation/resource.h" +#include "call/adaptation/test/fake_resource.h" +#include "call/adaptation/test/fake_resource_consumer_configuration.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { + +// The indices of different resolutions returned by +// AddStandardResolutionConfigurations(). +static size_t k1080pIndex = 0; +static size_t k720pIndex = 1; +static size_t k360pIndex = 2; +static size_t k180pIndex = 3; + +void ConnectNeighbors(ResourceConsumerConfiguration* upper, + ResourceConsumerConfiguration* lower) { + upper->AddLowerNeighbor(lower); + lower->AddUpperNeighbor(upper); +} + +std::vector +AddStandardResolutionConfigurations(ResourceAdaptationProcessor* processor) { + std::vector configs; + configs.push_back(processor->AddConfiguration( + std::make_unique(1920, 1080, 30.0, + 1.0))); + configs.push_back(processor->AddConfiguration( + std::make_unique(1280, 720, 30.0, + 1.0))); + configs.push_back(processor->AddConfiguration( + std::make_unique(640, 360, 30.0, + 1.0))); + configs.push_back(processor->AddConfiguration( + std::make_unique(320, 180, 30.0, + 1.0))); + for (size_t i = 1; i < configs.size(); ++i) { + ConnectNeighbors(configs[i - 1], configs[i]); + } + return configs; +} + +TEST(ResourceAdaptationProcessorTest, + SingleStreamAndResourceDontAdaptDownWhenStable) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kStable)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs[k1080pIndex])); + EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration()); +} + +TEST(ResourceAdaptationProcessorTest, + SingleStreamAndResourceAdaptDownOnOveruse) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kOveruse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + auto* consumer = processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs[k1080pIndex])); + auto next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(consumer, next_config->consumer); + EXPECT_EQ(resolution_configs[k720pIndex], next_config->configuration); +} + +TEST(ResourceAdaptationProcessorTest, + SingleStreamAndResourceDontAdaptOnOveruseIfMinResolution) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kOveruse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs.back())); + EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration()); +} + +TEST(ResourceAdaptationProcessorTest, + SingleStreamAndResourceAdaptUpOnUnderuse) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kUnderuse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + auto* consumer = processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs[k720pIndex])); + auto next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(consumer, next_config->consumer); + EXPECT_EQ(resolution_configs[k1080pIndex], next_config->configuration); +} + +TEST(ResourceAdaptationProcessorTest, + SingleStreamAndResourceDontAdaptOnUnderuseIfMaxResolution) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kUnderuse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs[k1080pIndex])); + EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration()); +} + +TEST(ResourceAdaptationProcessorTest, + MultipleStreamsLargestStreamGetsAdaptedDownOnOveruse) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kOveruse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + auto* first_stream = processor.AddConsumer(std::make_unique( + "FirstStream", resolution_configs[k1080pIndex])); + auto* second_stream = + processor.AddConsumer(std::make_unique( + "SecondStream", resolution_configs[k720pIndex])); + // When the first stream is larger. + auto next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(first_stream, next_config->consumer); + // When the second stream is larger. + first_stream->SetConfiguration(resolution_configs[k720pIndex]); + second_stream->SetConfiguration(resolution_configs[k1080pIndex]); + next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(second_stream, next_config->consumer); +} + +TEST(ResourceAdaptationProcessorTest, + MultipleStreamsSmallestStreamGetsAdaptedUpOnUnderuse) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kUnderuse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + auto* first_stream = processor.AddConsumer(std::make_unique( + "FirstStream", resolution_configs[k360pIndex])); + auto* second_stream = + processor.AddConsumer(std::make_unique( + "SecondStream", resolution_configs[k180pIndex])); + // When the first stream is larger. + auto next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(second_stream, next_config->consumer); + // When the second stream is larger. + first_stream->SetConfiguration(resolution_configs[k180pIndex]); + second_stream->SetConfiguration(resolution_configs[k360pIndex]); + next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(first_stream, next_config->consumer); +} + +// If both streams are equally valid to adapt down, the first one is preferred. +TEST(ResourceAdaptationProcessorTest, + MultipleStreamsAdaptFirstStreamWhenBothStreamsHaveSameCost) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kOveruse)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + auto* first_stream = processor.AddConsumer(std::make_unique( + "FirstStream", resolution_configs[k720pIndex])); + processor.AddConsumer(std::make_unique( + "SecondStream", resolution_configs[k720pIndex])); + auto next_config = processor.FindNextConfiguration(); + EXPECT_TRUE(next_config.has_value()); + EXPECT_EQ(first_stream, next_config->consumer); +} + +TEST(ResourceAdaptationProcessorTest, + MultipleResourcesAdaptDownIfAnyIsOverused) { + ResourceAdaptationProcessor processor; + auto* first_resource = processor.AddResource( + std::make_unique(ResourceUsageState::kOveruse)); + auto* second_resource = processor.AddResource( + std::make_unique(ResourceUsageState::kStable)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs[k1080pIndex])); + // When the first resource is overused. + EXPECT_TRUE(processor.FindNextConfiguration().has_value()); + // When the second resource is overused. + first_resource->set_usage(ResourceUsageState::kStable); + second_resource->set_usage(ResourceUsageState::kOveruse); + EXPECT_TRUE(processor.FindNextConfiguration().has_value()); +} + +TEST(ResourceAdaptationProcessorTest, + MultipleResourcesAdaptUpIfAllAreUnderused) { + ResourceAdaptationProcessor processor; + processor.AddResource( + std::make_unique(ResourceUsageState::kUnderuse)); + auto* second_resource = processor.AddResource( + std::make_unique(ResourceUsageState::kStable)); + auto resolution_configs = AddStandardResolutionConfigurations(&processor); + processor.AddConsumer(std::make_unique( + "OnlyStream", resolution_configs[k720pIndex])); + // When only the first resource is underused. + EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration()); + // When all resources are underused. + second_resource->set_usage(ResourceUsageState::kUnderuse); + EXPECT_TRUE(processor.FindNextConfiguration().has_value()); +} + +TEST(ResourceAdaptationProcessorTest, + HighestPreferredNeighborIsPickedWhenAdapting) { + ResourceAdaptationProcessor processor; + // Set up the following graph, where (#) is the preference. + // + // Downward arrows Upward arrows + // + // a(1) -----> b(2) a(1) <----- b(2) + // | ^ | ^ / ^ + // | / | | / | + // v / v | v | + // c(1.5) ---> d(2) c(1.5) <--- d(2) + // + auto* a = processor.AddConfiguration( + std::make_unique(1, 1, 1, 1.0)); + auto* b = processor.AddConfiguration( + std::make_unique(1, 1, 1, 2.0)); + auto* c = processor.AddConfiguration( + std::make_unique(1, 1, 1, 1.5)); + auto* d = processor.AddConfiguration( + std::make_unique(1, 1, 1, 2.0)); + ConnectNeighbors(a, b); + ConnectNeighbors(a, c); + ConnectNeighbors(b, d); + ConnectNeighbors(c, b); + ConnectNeighbors(c, d); + + auto* resource = processor.AddResource( + std::make_unique(ResourceUsageState::kOveruse)); + auto* consumer = processor.AddConsumer( + std::make_unique("OnlyStream", a)); + + // We should expect adapting down: a -> b -> d + EXPECT_EQ(b, processor.FindNextConfiguration()->configuration); + consumer->SetConfiguration(b); + EXPECT_EQ(d, processor.FindNextConfiguration()->configuration); + consumer->SetConfiguration(d); + + // We should expect to adapt up: d -> b -> c -> a + resource->set_usage(ResourceUsageState::kUnderuse); + EXPECT_EQ(b, processor.FindNextConfiguration()->configuration); + consumer->SetConfiguration(b); + EXPECT_EQ(c, processor.FindNextConfiguration()->configuration); + consumer->SetConfiguration(c); + EXPECT_EQ(a, processor.FindNextConfiguration()->configuration); +} + +} // namespace webrtc diff --git a/call/adaptation/resource_consumer.cc b/call/adaptation/resource_consumer.cc new file mode 100644 index 0000000000..3f9dfd825f --- /dev/null +++ b/call/adaptation/resource_consumer.cc @@ -0,0 +1,50 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/resource_consumer.h" + +#include + +#include "call/adaptation/resource_consumer_configuration.h" +#include "rtc_base/checks.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +ResourceConsumer::ResourceConsumer(std::string name, + ResourceConsumerConfiguration* configuration) + : name_(std::move(name)), configuration_(configuration) { + RTC_DCHECK(!name_.empty()); + RTC_DCHECK(configuration_); +} + +ResourceConsumer::~ResourceConsumer() {} + +std::string ResourceConsumer::name() const { + return name_; +} + +ResourceConsumerConfiguration* ResourceConsumer::configuration() const { + return configuration_; +} + +void ResourceConsumer::SetConfiguration( + ResourceConsumerConfiguration* configuration) { + RTC_DCHECK(configuration); + configuration_ = configuration; +} + +std::string ResourceConsumer::ToString() const { + rtc::StringBuilder sb; + sb << name_ << ": " << configuration_->Name(); + return sb.str(); +} + +} // namespace webrtc diff --git a/call/adaptation/resource_consumer.h b/call/adaptation/resource_consumer.h new file mode 100644 index 0000000000..131aa45c34 --- /dev/null +++ b/call/adaptation/resource_consumer.h @@ -0,0 +1,49 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CALL_ADAPTATION_RESOURCE_CONSUMER_H_ +#define CALL_ADAPTATION_RESOURCE_CONSUMER_H_ + +#include + +namespace webrtc { + +class ResourceConsumerConfiguration; + +// Something which affects resource consumption. Used by the +// ResourceAdaptationProcessor to calculate which configurations to use. +// +// For example, this could represent an encoder, and valid +// ResourceConsumerConfigurations would be encoder settings. How a consumer +// affects a resource is described by the ResourceConsumerConfiguration. +// +// The functionality provided by the base class is a name and pointer to the +// current configuration. How a consumers and configurations affect real parts +// of the system (like actual encoders) is implementation-specific. +class ResourceConsumer { + public: + ResourceConsumer(std::string name, + ResourceConsumerConfiguration* configuration); + ~ResourceConsumer(); + + std::string name() const; + ResourceConsumerConfiguration* configuration() const; + void SetConfiguration(ResourceConsumerConfiguration* configuration); + + std::string ToString() const; + + private: + std::string name_; + ResourceConsumerConfiguration* configuration_; +}; + +} // namespace webrtc + +#endif // CALL_ADAPTATION_RESOURCE_CONSUMER_H_ diff --git a/call/adaptation/resource_consumer_configuration.cc b/call/adaptation/resource_consumer_configuration.cc new file mode 100644 index 0000000000..ca3462eb4a --- /dev/null +++ b/call/adaptation/resource_consumer_configuration.cc @@ -0,0 +1,42 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/resource_consumer_configuration.h" + +#include + +#include "rtc_base/checks.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +ResourceConsumerConfiguration::~ResourceConsumerConfiguration() {} + +const std::vector& +ResourceConsumerConfiguration::upper_neighbors() const { + return upper_neighbors_; +} + +const std::vector& +ResourceConsumerConfiguration::lower_neighbors() const { + return lower_neighbors_; +} + +void ResourceConsumerConfiguration::AddUpperNeighbor( + ResourceConsumerConfiguration* upper_neighbor) { + upper_neighbors_.push_back(upper_neighbor); +} + +void ResourceConsumerConfiguration::AddLowerNeighbor( + ResourceConsumerConfiguration* lower_neighbor) { + lower_neighbors_.push_back(lower_neighbor); +} + +} // namespace webrtc diff --git a/call/adaptation/resource_consumer_configuration.h b/call/adaptation/resource_consumer_configuration.h new file mode 100644 index 0000000000..462c339439 --- /dev/null +++ b/call/adaptation/resource_consumer_configuration.h @@ -0,0 +1,62 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CALL_ADAPTATION_RESOURCE_CONSUMER_CONFIGURATION_H_ +#define CALL_ADAPTATION_RESOURCE_CONSUMER_CONFIGURATION_H_ + +#include +#include +#include + +namespace webrtc { + +class Resource; + +// Represents a possible state for a ResourceConsumer. For example, if an +// encoder consumer can have the states "HD" and "VGA", there is one +// ResourceConsumerConfiguration for each state. "HD" is an upper neighbor of +// "VGA" and "VGA" is a lower neighbor of "HD". +class ResourceConsumerConfiguration { + public: + virtual ~ResourceConsumerConfiguration(); + + const std::vector& upper_neighbors() const; + const std::vector& lower_neighbors() const; + void AddUpperNeighbor(ResourceConsumerConfiguration* upper_neighbor); + void AddLowerNeighbor(ResourceConsumerConfiguration* lower_neighbor); + + virtual std::string Name() const = 0; + + // How expensive this configuration is. This is an abstract unit used by the + // ResourceAdaptationProcessor to compare configurations. When overusing, the + // consumer with the most expensive configuration will be adapted down. When + // underusing, the consumer with the least expensive configuration will be + // adapted up. The cost generally scales with pixels per second. The value + // must be non-negative. + virtual double Cost() const = 0; + + // How preferable this configuration is. The is an abstract unit used by the + // ResourceAdaptationProcessor to compare configurations. When a consumer is + // reconfigured to a neighbor configuration, the configuration with the + // highest preference value is preferred. The value must be non-negative. + virtual double Preference() const = 0; + + private: + // Configurations we can adapt "up" to when we are in |this| configuration, + // such as higher resolutions. + std::vector upper_neighbors_; + // Configurations we can adapt "down" to when we are in |this| configuration, + // such as lower resolutions. + std::vector lower_neighbors_; +}; + +} // namespace webrtc + +#endif // CALL_ADAPTATION_RESOURCE_CONSUMER_CONFIGURATION_H_ diff --git a/call/adaptation/test/fake_resource.cc b/call/adaptation/test/fake_resource.cc new file mode 100644 index 0000000000..363fc26fe7 --- /dev/null +++ b/call/adaptation/test/fake_resource.cc @@ -0,0 +1,52 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/test/fake_resource.h" + +#include + +namespace webrtc { + +FakeResource::FakeResource(std::string name, ResourceUsageState usage) + : name_(std::move(name)), usage_(usage) {} + +FakeResource::FakeResource(ResourceUsageState usage) + : FakeResource("UnnamedResource", usage) {} + +FakeResource::~FakeResource() {} + +void FakeResource::set_usage(ResourceUsageState usage) { + usage_ = usage; +} + +std::string FakeResource::Name() const { + return name_; +} + +std::string FakeResource::UsageUnitsOfMeasurement() const { + return "%"; +} + +double FakeResource::CurrentUsage() const { + switch (usage_) { + case ResourceUsageState::kOveruse: + return 1.2; + case ResourceUsageState::kStable: + return 0.8; + case ResourceUsageState::kUnderuse: + return 0.4; + } +} + +ResourceUsageState FakeResource::CurrentUsageState() const { + return usage_; +} + +} // namespace webrtc diff --git a/call/adaptation/test/fake_resource.h b/call/adaptation/test/fake_resource.h new file mode 100644 index 0000000000..60291af6ae --- /dev/null +++ b/call/adaptation/test/fake_resource.h @@ -0,0 +1,44 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CALL_ADAPTATION_TEST_FAKE_RESOURCE_H_ +#define CALL_ADAPTATION_TEST_FAKE_RESOURCE_H_ + +#include + +#include "call/adaptation/resource.h" + +namespace webrtc { + +// Fake resource used for testing. ResourceUsageState is controlled with a +// setter. The arbitrarily chosen unit of measurement is percentage, with the +// following current usage reported based on the current usage: kOveruse = 120%, +// kStable = 80% and kUnderuse = 40%. +class FakeResource : public Resource { + public: + FakeResource(std::string name, ResourceUsageState usage); + explicit FakeResource(ResourceUsageState usage); + ~FakeResource() override; + + void set_usage(ResourceUsageState usage); + + std::string Name() const override; + std::string UsageUnitsOfMeasurement() const override; + double CurrentUsage() const override; + ResourceUsageState CurrentUsageState() const override; + + private: + std::string name_; + ResourceUsageState usage_; +}; + +} // namespace webrtc + +#endif // CALL_ADAPTATION_TEST_FAKE_RESOURCE_H_ diff --git a/call/adaptation/test/fake_resource_consumer_configuration.cc b/call/adaptation/test/fake_resource_consumer_configuration.cc new file mode 100644 index 0000000000..afc743cf4c --- /dev/null +++ b/call/adaptation/test/fake_resource_consumer_configuration.cc @@ -0,0 +1,42 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "call/adaptation/test/fake_resource_consumer_configuration.h" + +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { + +FakeResourceConsumerConfiguration::FakeResourceConsumerConfiguration( + int width, + int height, + double frame_rate_hz, + double preference) + : width_(width), + height_(height), + frame_rate_hz_(frame_rate_hz), + preference_(preference) {} + +std::string FakeResourceConsumerConfiguration::Name() const { + rtc::StringBuilder sb; + sb << width_ << "x" << height_ << "@" << rtc::ToString(frame_rate_hz_); + sb << "/" << rtc::ToString(preference_); + return sb.str(); +} + +double FakeResourceConsumerConfiguration::Cost() const { + return width_ * height_ * frame_rate_hz_; +} + +double FakeResourceConsumerConfiguration::Preference() const { + return preference_; +} + +} // namespace webrtc diff --git a/call/adaptation/test/fake_resource_consumer_configuration.h b/call/adaptation/test/fake_resource_consumer_configuration.h new file mode 100644 index 0000000000..d0d25961ed --- /dev/null +++ b/call/adaptation/test/fake_resource_consumer_configuration.h @@ -0,0 +1,40 @@ +/* + * Copyright 2019 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef CALL_ADAPTATION_TEST_FAKE_RESOURCE_CONSUMER_CONFIGURATION_H_ +#define CALL_ADAPTATION_TEST_FAKE_RESOURCE_CONSUMER_CONFIGURATION_H_ + +#include + +#include "call/adaptation/resource_consumer_configuration.h" + +namespace webrtc { + +class FakeResourceConsumerConfiguration : public ResourceConsumerConfiguration { + public: + FakeResourceConsumerConfiguration(int width, + int height, + double frame_rate_hz, + double preference); + + std::string Name() const override; + double Cost() const override; + double Preference() const override; + + private: + int width_; + int height_; + double frame_rate_hz_; + double preference_; +}; + +} // namespace webrtc + +#endif // CALL_ADAPTATION_TEST_FAKE_RESOURCE_CONSUMER_CONFIGURATION_H_