Initial version of ResourceAdaptationProcessor and friends.

This CL adds Resource, ResourceConsumer, ResourceConsumerConfiguration
and ResourceAdaptationProcessor and implements the algorithm outlined
in
https://docs.google.com/presentation/d/13jyqCWNpIa873iKT6yDuB5Q5ma-c0CvxBpX--0tCclY/edit?usp=sharing.

Simply put, if any resource (such as "CPU") is overusing, the most
expensive consumer (e.g. encoded stream) is adapted one step down.
If all resources are underusing, the least expensive consumer is
adapted one step up.

The current resources, consumers and configurations are all fakes;
this CL has no effect on the current adaptation algorithms used in
practise, but it lays down the foundation for future work in this
area.

Bug: webrtc:11167, webrtc:11168, webrtc:11169
Change-Id: I4054ec7728a52a49e137eee6fa67fa27debd9254
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161237
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Evan Shrubsole <eshr@google.com>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30053}
This commit is contained in:
Henrik Boström 2019-12-10 14:14:09 +01:00 committed by Commit Bot
parent f18f9206e5
commit b04b2a1719
16 changed files with 1055 additions and 0 deletions

View File

@ -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",

60
call/adaptation/BUILD.gn Normal file
View File

@ -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",
]
}
}

7
call/adaptation/OWNERS Normal file
View File

@ -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=*

View File

@ -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

View File

@ -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 <string>
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_

View File

@ -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 <limits>
#include <utility>
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
ResourceConsumerConfiguration* FindMostPreferredConfiguration(
const std::vector<ResourceConsumerConfiguration*>& 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<ConsumerConfigurationPair>
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<double>::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

View File

@ -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 <memory>
#include <utility>
#include <vector>
#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<std::unique_ptr<Resource>>& resources() const {
return resources_;
}
const std::vector<std::unique_ptr<ResourceConsumerConfiguration>>&
configurations() const {
return configurations_;
}
const std::vector<std::unique_ptr<ResourceConsumer>>& 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 <typename T>
T* AddResource(std::unique_ptr<T> resource) {
T* resource_ptr = resource.get();
resources_.push_back(std::move(resource));
return resource_ptr;
}
// T = any subclass of ResourceConsumerConfiguration
template <typename T>
T* AddConfiguration(std::unique_ptr<T> configuration) {
T* configuration_ptr = configuration.get();
configurations_.push_back(std::move(configuration));
return configuration_ptr;
}
// T = any subclass of ResourceConsumer
template <typename T>
T* AddConsumer(std::unique_ptr<T> 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<ConsumerConfigurationPair> FindNextConfiguration();
private:
ResourceConsumer* FindMostExpensiveConsumerThatCanBeAdaptedDown();
ResourceConsumer* FindLeastExpensiveConsumerThatCanBeAdaptedUp();
std::vector<std::unique_ptr<Resource>> resources_;
std::vector<std::unique_ptr<ResourceConsumerConfiguration>> configurations_;
std::vector<std::unique_ptr<ResourceConsumer>> consumers_;
};
} // namespace webrtc
#endif // CALL_ADAPTATION_RESOURCE_ADAPTATION_PROCESSOR_H_

View File

@ -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<FakeResourceConsumerConfiguration*>
AddStandardResolutionConfigurations(ResourceAdaptationProcessor* processor) {
std::vector<FakeResourceConsumerConfiguration*> configs;
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1920, 1080, 30.0,
1.0)));
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1280, 720, 30.0,
1.0)));
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(640, 360, 30.0,
1.0)));
configs.push_back(processor->AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(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<FakeResource>(ResourceUsageState::kStable));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k1080pIndex]));
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceAdaptDownOnOveruse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* consumer = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs.back()));
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
}
TEST(ResourceAdaptationProcessorTest,
SingleStreamAndResourceAdaptUpOnUnderuse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kUnderuse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* consumer = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResource>(ResourceUsageState::kUnderuse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"OnlyStream", resolution_configs[k1080pIndex]));
EXPECT_EQ(absl::nullopt, processor.FindNextConfiguration());
}
TEST(ResourceAdaptationProcessorTest,
MultipleStreamsLargestStreamGetsAdaptedDownOnOveruse) {
ResourceAdaptationProcessor processor;
processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* first_stream = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"FirstStream", resolution_configs[k1080pIndex]));
auto* second_stream =
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResource>(ResourceUsageState::kUnderuse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* first_stream = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"FirstStream", resolution_configs[k360pIndex]));
auto* second_stream =
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResource>(ResourceUsageState::kOveruse));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
auto* first_stream = processor.AddConsumer(std::make_unique<ResourceConsumer>(
"FirstStream", resolution_configs[k720pIndex]));
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResource>(ResourceUsageState::kOveruse));
auto* second_resource = processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kStable));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResource>(ResourceUsageState::kUnderuse));
auto* second_resource = processor.AddResource(
std::make_unique<FakeResource>(ResourceUsageState::kStable));
auto resolution_configs = AddStandardResolutionConfigurations(&processor);
processor.AddConsumer(std::make_unique<ResourceConsumer>(
"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<FakeResourceConsumerConfiguration>(1, 1, 1, 1.0));
auto* b = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1, 1, 1, 2.0));
auto* c = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(1, 1, 1, 1.5));
auto* d = processor.AddConfiguration(
std::make_unique<FakeResourceConsumerConfiguration>(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<FakeResource>(ResourceUsageState::kOveruse));
auto* consumer = processor.AddConsumer(
std::make_unique<ResourceConsumer>("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

View File

@ -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 <utility>
#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

View File

@ -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 <string>
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_

View File

@ -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 <utility>
#include "rtc_base/checks.h"
#include "rtc_base/strings/string_builder.h"
namespace webrtc {
ResourceConsumerConfiguration::~ResourceConsumerConfiguration() {}
const std::vector<ResourceConsumerConfiguration*>&
ResourceConsumerConfiguration::upper_neighbors() const {
return upper_neighbors_;
}
const std::vector<ResourceConsumerConfiguration*>&
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

View File

@ -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 <map>
#include <string>
#include <vector>
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<ResourceConsumerConfiguration*>& upper_neighbors() const;
const std::vector<ResourceConsumerConfiguration*>& 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<ResourceConsumerConfiguration*> upper_neighbors_;
// Configurations we can adapt "down" to when we are in |this| configuration,
// such as lower resolutions.
std::vector<ResourceConsumerConfiguration*> lower_neighbors_;
};
} // namespace webrtc
#endif // CALL_ADAPTATION_RESOURCE_CONSUMER_CONFIGURATION_H_

View File

@ -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 <utility>
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

View File

@ -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 <string>
#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_

View File

@ -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

View File

@ -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 <string>
#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_