| /* |
| * 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/video/video_adaptation_counters.h" |
| #include "call/adaptation/resource.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 "test/gtest.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| const int kDefaultFrameRate = 30; |
| const int kDefaultFrameSize = 1280 * 720; |
| |
| class ResourceAdaptationProcessorListenerForTesting |
| : public ResourceAdaptationProcessorListener { |
| public: |
| ResourceAdaptationProcessorListenerForTesting() |
| : restrictions_updated_count_(0), |
| restrictions_(), |
| adaptation_counters_(), |
| reason_(nullptr) {} |
| ~ResourceAdaptationProcessorListenerForTesting() override {} |
| |
| size_t restrictions_updated_count() const { |
| return restrictions_updated_count_; |
| } |
| const VideoSourceRestrictions& restrictions() const { return restrictions_; } |
| const VideoAdaptationCounters& adaptation_counters() const { |
| return adaptation_counters_; |
| } |
| const Resource* reason() const { return reason_; } |
| |
| // ResourceAdaptationProcessorListener implementation. |
| void OnVideoSourceRestrictionsUpdated( |
| VideoSourceRestrictions restrictions, |
| const VideoAdaptationCounters& adaptation_counters, |
| const Resource* reason) override { |
| ++restrictions_updated_count_; |
| restrictions_ = restrictions; |
| adaptation_counters_ = adaptation_counters; |
| reason_ = reason; |
| } |
| |
| private: |
| size_t restrictions_updated_count_; |
| VideoSourceRestrictions restrictions_; |
| VideoAdaptationCounters adaptation_counters_; |
| const Resource* reason_; |
| }; |
| |
| class ResourceAdaptationProcessorTest : public ::testing::Test { |
| public: |
| ResourceAdaptationProcessorTest() |
| : frame_rate_provider_(), |
| input_state_provider_(&frame_rate_provider_), |
| resource_("FakeResource"), |
| other_resource_("OtherFakeResource"), |
| processor_(&input_state_provider_, |
| /*encoder_stats_observer=*/&frame_rate_provider_) { |
| processor_.AddAdaptationListener(&processor_listener_); |
| processor_.AddResource(&resource_); |
| processor_.AddResource(&other_resource_); |
| } |
| ~ResourceAdaptationProcessorTest() override { |
| processor_.StopResourceAdaptation(); |
| } |
| |
| 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)); |
| } |
| |
| protected: |
| FakeFrameRateProvider frame_rate_provider_; |
| VideoStreamInputStateProvider input_state_provider_; |
| FakeResource resource_; |
| FakeResource other_resource_; |
| ResourceAdaptationProcessor processor_; |
| ResourceAdaptationProcessorListenerForTesting processor_listener_; |
| }; |
| |
| } // namespace |
| |
| TEST_F(ResourceAdaptationProcessorTest, DisabledByDefault) { |
| EXPECT_EQ(DegradationPreference::DISABLED, |
| processor_.degradation_preference()); |
| EXPECT_EQ(DegradationPreference::DISABLED, |
| processor_.effective_degradation_preference()); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| processor_.StartResourceAdaptation(); |
| // Adaptation does not happen when disabled. |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(0u, processor_listener_.restrictions_updated_count()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, InsufficientInput) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| // Adaptation does not happen if input is insufficient. |
| // When frame size is missing (OnFrameSizeObserved not called yet). |
| input_state_provider_.OnHasInputChanged(true); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(0u, processor_listener_.restrictions_updated_count()); |
| // When "has input" is missing. |
| SetInputStates(false, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(0u, processor_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) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| EXPECT_TRUE( |
| processor_listener_.restrictions().max_pixels_per_frame().has_value()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, |
| OveruseTriggersRestrictingFrameRateInMaintainResolution) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_RESOLUTION); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| EXPECT_TRUE(processor_listener_.restrictions().max_frame_rate().has_value()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, |
| OveruseTriggersRestrictingFrameRateAndResolutionInBalanced) { |
| processor_.SetDegradationPreference(DegradationPreference::BALANCED); |
| processor_.StartResourceAdaptation(); |
| 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_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(i + 1, processor_listener_.restrictions_updated_count()); |
| RestrictSource(processor_listener_.restrictions()); |
| } |
| EXPECT_TRUE( |
| processor_listener_.restrictions().max_pixels_per_frame().has_value()); |
| EXPECT_TRUE(processor_listener_.restrictions().max_frame_rate().has_value()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, AwaitingPreviousAdaptation) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_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_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, CannotAdaptUpWhenUnrestricted) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(0u, processor_listener_.restrictions_updated_count()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, UnderuseTakesUsBackToUnrestricted) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| RestrictSource(processor_listener_.restrictions()); |
| resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(2u, processor_listener_.restrictions_updated_count()); |
| EXPECT_EQ(VideoSourceRestrictions(), processor_listener_.restrictions()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, ResourcesCanPreventAdaptingUp) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| // Adapt down so that we can adapt up. |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| RestrictSource(processor_listener_.restrictions()); |
| // Adapting up is prevented. |
| resource_.set_is_adaptation_up_allowed(false); |
| resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, |
| ResourcesCanNotAdaptUpIfNeverAdaptedDown) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| RestrictSource(processor_listener_.restrictions()); |
| |
| // Other resource signals under-use |
| other_resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, |
| ResourcesCanNotAdaptUpIfNotAdaptedDownAfterReset) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| |
| processor_.ResetVideoSourceRestrictions(); |
| EXPECT_EQ(0, processor_listener_.adaptation_counters().Total()); |
| other_resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| |
| // resource_ did not overuse after we reset the restrictions, so adapt up |
| // should be disallowed. |
| resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(1, processor_listener_.adaptation_counters().Total()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, |
| MultipleResourcesCanTriggerMultipleAdaptations) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| other_resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(2, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| other_resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(3, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| |
| resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(2, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| // Does not trigger adaptation since resource has no adaptations left. |
| resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(2, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| |
| other_resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(1, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| other_resource_.set_usage_state(ResourceUsageState::kUnderuse); |
| EXPECT_EQ(0, processor_listener_.adaptation_counters().Total()); |
| RestrictSource(processor_listener_.restrictions()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, AdaptingTriggersOnAdaptationApplied) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, resource_.num_adaptations_applied()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, AdaptingClearsResourceUsageState) { |
| processor_.SetDegradationPreference( |
| DegradationPreference::MAINTAIN_FRAMERATE); |
| processor_.StartResourceAdaptation(); |
| SetInputStates(true, kDefaultFrameRate, kDefaultFrameSize); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(1u, processor_listener_.restrictions_updated_count()); |
| EXPECT_FALSE(resource_.usage_state().has_value()); |
| } |
| |
| TEST_F(ResourceAdaptationProcessorTest, |
| FailingAdaptingAlsoClearsResourceUsageState) { |
| processor_.SetDegradationPreference(DegradationPreference::DISABLED); |
| processor_.StartResourceAdaptation(); |
| resource_.set_usage_state(ResourceUsageState::kOveruse); |
| EXPECT_EQ(0u, processor_listener_.restrictions_updated_count()); |
| EXPECT_FALSE(resource_.usage_state().has_value()); |
| } |
| |
| } // namespace webrtc |