blob: acd9053f67dbe20c2dfcc2803cc6019392ca0fc3 [file] [log] [blame]
/*
* Copyright (c) 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 "api/adaptation/resource.h"
#include "api/scoped_refptr.h"
#include "api/video/video_adaptation_counters.h"
#include "call/adaptation/resource_adaptation_processor_interface.h"
#include "call/adaptation/test/fake_frame_rate_provider.h"
#include "call/adaptation/test/fake_resource.h"
#include "call/adaptation/video_source_restrictions.h"
#include "call/adaptation/video_stream_input_state_provider.h"
#include "rtc_base/event.h"
#include "rtc_base/gunit.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/task_queue_for_test.h"
#include "test/gtest.h"
#include "test/scoped_key_value_config.h"
namespace webrtc {
namespace {
const int kDefaultFrameRate = 30;
const int kDefaultFrameSize = 1280 * 720;
constexpr TimeDelta kDefaultTimeout = TimeDelta::Seconds(5);
class VideoSourceRestrictionsListenerForTesting
: public VideoSourceRestrictionsListener {
public:
VideoSourceRestrictionsListenerForTesting()
: restrictions_updated_count_(0),
restrictions_(),
adaptation_counters_(),
reason_(nullptr) {}
~VideoSourceRestrictionsListenerForTesting() override {}
size_t restrictions_updated_count() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return restrictions_updated_count_;
}
VideoSourceRestrictions restrictions() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return restrictions_;
}
VideoAdaptationCounters adaptation_counters() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return adaptation_counters_;
}
rtc::scoped_refptr<Resource> reason() const {
RTC_DCHECK_RUN_ON(&sequence_checker_);
return reason_;
}
// VideoSourceRestrictionsListener implementation.
void OnVideoSourceRestrictionsUpdated(
VideoSourceRestrictions restrictions,
const VideoAdaptationCounters& adaptation_counters,
rtc::scoped_refptr<Resource> reason,
const VideoSourceRestrictions& /* unfiltered_restrictions */) override {
RTC_DCHECK_RUN_ON(&sequence_checker_);
++restrictions_updated_count_;
restrictions_ = restrictions;
adaptation_counters_ = adaptation_counters;
reason_ = reason;
}
private:
SequenceChecker sequence_checker_;
size_t restrictions_updated_count_ RTC_GUARDED_BY(&sequence_checker_);
VideoSourceRestrictions restrictions_ RTC_GUARDED_BY(&sequence_checker_);
VideoAdaptationCounters adaptation_counters_
RTC_GUARDED_BY(&sequence_checker_);
rtc::scoped_refptr<Resource> reason_ RTC_GUARDED_BY(&sequence_checker_);
};
class ResourceAdaptationProcessorTest : public ::testing::Test {
public:
ResourceAdaptationProcessorTest()
: frame_rate_provider_(),
input_state_provider_(&frame_rate_provider_),
resource_(FakeResource::Create("FakeResource")),
other_resource_(FakeResource::Create("OtherFakeResource")),
video_stream_adapter_(
std::make_unique<VideoStreamAdapter>(&input_state_provider_,
&frame_rate_provider_,
field_trials_)),
processor_(std::make_unique<ResourceAdaptationProcessor>(
video_stream_adapter_.get())) {
video_stream_adapter_->AddRestrictionsListener(&restrictions_listener_);
processor_->AddResource(resource_);
processor_->AddResource(other_resource_);
}
~ResourceAdaptationProcessorTest() override {
if (processor_) {
DestroyProcessor();
}
}
void SetInputStates(bool has_input, int fps, int frame_size) {
input_state_provider_.OnHasInputChanged(has_input);
frame_rate_provider_.set_fps(fps);
input_state_provider_.OnFrameSizeObserved(frame_size);
}
void RestrictSource(VideoSourceRestrictions restrictions) {
SetInputStates(
true, restrictions.max_frame_rate().value_or(kDefaultFrameRate),
restrictions.target_pixels_per_frame().has_value()
? restrictions.target_pixels_per_frame().value()
: restrictions.max_pixels_per_frame().value_or(kDefaultFrameSize));
}
void DestroyProcessor() {
if (resource_) {
processor_->RemoveResource(resource_);
}
if (other_resource_) {
processor_->RemoveResource(other_resource_);
}
video_stream_adapter_->RemoveRestrictionsListener(&restrictions_listener_);
processor_.reset();
}
static void WaitUntilTaskQueueIdle() {
ASSERT_TRUE(rtc::Thread::Current()->ProcessMessages(0));
}
protected:
rtc::AutoThread main_thread_;
webrtc::test::ScopedKeyValueConfig field_trials_;
FakeFrameRateProvider frame_rate_provider_;
VideoStreamInputStateProvider input_state_provider_;
rtc::scoped_refptr<FakeResource> resource_;
rtc::scoped_refptr<FakeResource> other_resource_;
std::unique_ptr<VideoStreamAdapter> video_stream_adapter_;
std::unique_ptr<ResourceAdaptationProcessor> processor_;
VideoSourceRestrictionsListenerForTesting restrictions_listener_;
};
} // namespace
TEST_F(ResourceAdaptationProcessorTest, DisabledByDefault) {
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
// Adaptation does not happen when disabled.
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
}
TEST_F(ResourceAdaptationProcessorTest, InsufficientInput) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
// Adaptation does not happen if input is insufficient.
// When frame size is missing (OnFrameSizeObserved not called yet).
input_state_provider_.OnHasInputChanged(true);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
// When "has input" is missing.
SetInputStates(false, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
// Note: frame rate cannot be missing, if unset it is 0.
}
// These tests verify that restrictions are applied, but not exactly how much
// the source is restricted. This ensures that the VideoStreamAdapter is wired
// up correctly but not exactly how the VideoStreamAdapter generates
// restrictions. For that, see video_stream_adapter_unittest.cc.
TEST_F(ResourceAdaptationProcessorTest,
OveruseTriggersRestrictingResolutionInMaintainFrameRate) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
EXPECT_TRUE(
restrictions_listener_.restrictions().max_pixels_per_frame().has_value());
}
TEST_F(ResourceAdaptationProcessorTest,
OveruseTriggersRestrictingFrameRateInMaintainResolution) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_RESOLUTION);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
EXPECT_TRUE(
restrictions_listener_.restrictions().max_frame_rate().has_value());
}
TEST_F(ResourceAdaptationProcessorTest,
OveruseTriggersRestrictingFrameRateAndResolutionInBalanced) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::BALANCED);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
// Adapting multiple times eventually resticts both frame rate and
// resolution. Exactly many times we need to adapt depends on
// BalancedDegradationSettings, VideoStreamAdapter and default input
// states. This test requires it to be achieved within 4 adaptations.
for (size_t i = 0; i < 4; ++i) {
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(i + 1, restrictions_listener_.restrictions_updated_count());
RestrictSource(restrictions_listener_.restrictions());
}
EXPECT_TRUE(
restrictions_listener_.restrictions().max_pixels_per_frame().has_value());
EXPECT_TRUE(
restrictions_listener_.restrictions().max_frame_rate().has_value());
}
TEST_F(ResourceAdaptationProcessorTest, AwaitingPreviousAdaptation) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
// If we don't restrict the source then adaptation will not happen again
// due to "awaiting previous adaptation". This prevents "double-adapt".
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
}
TEST_F(ResourceAdaptationProcessorTest, CannotAdaptUpWhenUnrestricted) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
}
TEST_F(ResourceAdaptationProcessorTest, UnderuseTakesUsBackToUnrestricted) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2u, restrictions_listener_.restrictions_updated_count());
EXPECT_EQ(VideoSourceRestrictions(), restrictions_listener_.restrictions());
}
TEST_F(ResourceAdaptationProcessorTest,
ResourcesCanNotAdaptUpIfNeverAdaptedDown) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
RestrictSource(restrictions_listener_.restrictions());
// Other resource signals under-use
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
}
TEST_F(ResourceAdaptationProcessorTest,
ResourcesCanNotAdaptUpIfNotAdaptedDownAfterReset) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1u, restrictions_listener_.restrictions_updated_count());
video_stream_adapter_->ClearRestrictions();
EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// resource_ did not overuse after we reset the restrictions, so adapt
// up should be disallowed.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
}
TEST_F(ResourceAdaptationProcessorTest, OnlyMostLimitedResourceMayAdaptUp) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// `other_resource_` is most limited, resource_ can't adapt up.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// `resource_` and `other_resource_` are now most limited, so both must
// signal underuse to adapt up.
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
}
TEST_F(ResourceAdaptationProcessorTest,
MultipleResourcesCanTriggerMultipleAdaptations) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// resource_ is not most limited so can't adapt from underuse.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// resource_ is still not most limited so can't adapt from underuse.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// However it will be after overuse
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// Now other_resource_ can't adapt up as it is not most restricted.
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(3, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// resource_ is limited at 3 adaptations and other_resource_ 2.
// With the most limited resource signalling underuse in the following
// order we get back to unrestricted video.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// Both resource_ and other_resource_ are most limited.
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// Again both are most limited.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
}
TEST_F(ResourceAdaptationProcessorTest,
MostLimitedResourceAdaptationWorksAfterChangingDegradataionPreference) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
// Adapt down until we can't anymore.
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
int last_total = restrictions_listener_.adaptation_counters().Total();
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_RESOLUTION);
// resource_ can not adapt up since we have never reduced FPS.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(last_total, restrictions_listener_.adaptation_counters().Total());
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(last_total + 1,
restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
// other_resource_ is most limited so should be able to adapt up.
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(last_total, restrictions_listener_.adaptation_counters().Total());
}
TEST_F(ResourceAdaptationProcessorTest,
AdaptsDownWhenOtherResourceIsAlwaysUnderused) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
// Does not trigger adapataion because there's no restriction.
EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kOveruse);
// Adapts down even if other resource asked for adapting up.
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
other_resource_->SetUsageState(ResourceUsageState::kUnderuse);
// Doesn't adapt up because adaptation is due to another resource.
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
}
TEST_F(ResourceAdaptationProcessorTest,
TriggerOveruseNotOnAdaptationTaskQueue) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
TaskQueueForTest resource_task_queue("ResourceTaskQueue");
resource_task_queue.PostTask(
[&]() { resource_->SetUsageState(ResourceUsageState::kOveruse); });
EXPECT_EQ_WAIT(1u, restrictions_listener_.restrictions_updated_count(),
kDefaultTimeout.ms());
}
TEST_F(ResourceAdaptationProcessorTest,
DestroyProcessorWhileResourceListenerDelegateHasTaskInFlight) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
// Wait for `resource_` to signal oversue first so we know that the delegate
// has passed it on to the processor's task queue.
rtc::Event resource_event;
TaskQueueForTest resource_task_queue("ResourceTaskQueue");
resource_task_queue.PostTask([&]() {
resource_->SetUsageState(ResourceUsageState::kOveruse);
resource_event.Set();
});
EXPECT_TRUE(resource_event.Wait(kDefaultTimeout));
// Now destroy the processor while handling the overuse is in flight.
DestroyProcessor();
// Because the processor was destroyed by the time the delegate's task ran,
// the overuse signal must not have been handled.
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
}
TEST_F(ResourceAdaptationProcessorTest,
ResourceOveruseIgnoredWhenSignalledDuringRemoval) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
rtc::Event overuse_event;
TaskQueueForTest resource_task_queue("ResourceTaskQueue");
// Queues task for `resource_` overuse while `processor_` is still listening.
resource_task_queue.PostTask([&]() {
resource_->SetUsageState(ResourceUsageState::kOveruse);
overuse_event.Set();
});
EXPECT_TRUE(overuse_event.Wait(kDefaultTimeout));
// Once we know the overuse task is queued, remove `resource_` so that
// `processor_` is not listening to it.
processor_->RemoveResource(resource_);
// Runs the queued task so `processor_` gets signalled kOveruse from
// `resource_` even though `processor_` was not listening.
WaitUntilTaskQueueIdle();
// No restrictions should change even though `resource_` signaled `kOveruse`.
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingOnlyAdaptedResourceResetsAdaptation) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
RestrictSource(restrictions_listener_.restrictions());
processor_->RemoveResource(resource_);
EXPECT_EQ(0, restrictions_listener_.adaptation_counters().Total());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingMostLimitedResourceSetsAdaptationToNextLimitedLevel) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::BALANCED);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
VideoSourceRestrictions next_limited_restrictions =
restrictions_listener_.restrictions();
VideoAdaptationCounters next_limited_counters =
restrictions_listener_.adaptation_counters();
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
// Removing most limited `resource_` should revert us back to
processor_->RemoveResource(resource_);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
EXPECT_EQ(next_limited_counters,
restrictions_listener_.adaptation_counters());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingMostLimitedResourceSetsAdaptationIfInputStateUnchanged) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
VideoSourceRestrictions next_limited_restrictions =
restrictions_listener_.restrictions();
VideoAdaptationCounters next_limited_counters =
restrictions_listener_.adaptation_counters();
// Overuse twice and underuse once. After the underuse we don't restrict the
// source. Normally this would block future underuses.
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
// Removing most limited `resource_` should revert us back to, even though we
// did not call RestrictSource() after `resource_` was overused. Normally
// adaptation for MAINTAIN_FRAMERATE would be blocked here but for removal we
// allow this anyways.
processor_->RemoveResource(resource_);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
EXPECT_EQ(next_limited_counters,
restrictions_listener_.adaptation_counters());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingResourceNotMostLimitedHasNoEffectOnLimitations) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::BALANCED);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
VideoSourceRestrictions current_restrictions =
restrictions_listener_.restrictions();
VideoAdaptationCounters current_counters =
restrictions_listener_.adaptation_counters();
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
// Removing most limited `resource_` should revert us back to
processor_->RemoveResource(other_resource_);
EXPECT_EQ(current_restrictions, restrictions_listener_.restrictions());
EXPECT_EQ(current_counters, restrictions_listener_.adaptation_counters());
// Delete `other_resource_` for cleanup.
other_resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingMostLimitedResourceAfterSwitchingDegradationPreferences) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
VideoSourceRestrictions next_limited_restrictions =
restrictions_listener_.restrictions();
VideoAdaptationCounters next_limited_counters =
restrictions_listener_.adaptation_counters();
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_RESOLUTION);
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
// Revert to `other_resource_` when removing `resource_` even though the
// degradation preference was different when it was overused.
processor_->RemoveResource(resource_);
EXPECT_EQ(next_limited_counters,
restrictions_listener_.adaptation_counters());
// After switching back to MAINTAIN_FRAMERATE, the next most limited settings
// are restored.
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingMostLimitedResourceSetsNextLimitationsInDisabled) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
VideoSourceRestrictions next_limited_restrictions =
restrictions_listener_.restrictions();
VideoAdaptationCounters next_limited_counters =
restrictions_listener_.adaptation_counters();
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(2, restrictions_listener_.adaptation_counters().Total());
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::DISABLED);
// Revert to `other_resource_` when removing `resource_` even though the
// current degradataion preference is disabled.
processor_->RemoveResource(resource_);
// After switching back to MAINTAIN_FRAMERATE, the next most limited settings
// are restored.
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
EXPECT_EQ(next_limited_restrictions, restrictions_listener_.restrictions());
EXPECT_EQ(next_limited_counters,
restrictions_listener_.adaptation_counters());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovedResourceSignalsIgnoredByProcessor) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
processor_->RemoveResource(resource_);
resource_->SetUsageState(ResourceUsageState::kOveruse);
EXPECT_EQ(0u, restrictions_listener_.restrictions_updated_count());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
RemovingResourceWhenMultipleMostLimtedHasNoEffect) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
// Adapt `resource_` up and then down so that both resource's are most
// limited at 1 adaptation.
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
resource_->SetUsageState(ResourceUsageState::kUnderuse);
RestrictSource(restrictions_listener_.restrictions());
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
// Removing `resource_` has no effect since both `resource_` and
// `other_resource_` are most limited.
processor_->RemoveResource(resource_);
EXPECT_EQ(1, restrictions_listener_.adaptation_counters().Total());
// Delete `resource_` for cleanup.
resource_ = nullptr;
}
TEST_F(ResourceAdaptationProcessorTest,
ResourceOverusedAtLimitReachedWillShareMostLimited) {
video_stream_adapter_->SetDegradationPreference(
DegradationPreference::MAINTAIN_FRAMERATE);
SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize);
bool has_reached_min_pixels = false;
ON_CALL(frame_rate_provider_, OnMinPixelLimitReached())
.WillByDefault(testing::Assign(&has_reached_min_pixels, true));
// Adapt 10 times, which should make us hit the limit.
for (int i = 0; i < 10; ++i) {
resource_->SetUsageState(ResourceUsageState::kOveruse);
RestrictSource(restrictions_listener_.restrictions());
}
EXPECT_TRUE(has_reached_min_pixels);
auto last_update_count = restrictions_listener_.restrictions_updated_count();
other_resource_->SetUsageState(ResourceUsageState::kOveruse);
// Now both `resource_` and `other_resource_` are most limited. Underuse of
// `resource_` will not adapt up.
resource_->SetUsageState(ResourceUsageState::kUnderuse);
EXPECT_EQ(last_update_count,
restrictions_listener_.restrictions_updated_count());
}
} // namespace webrtc