/* * 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 reason() const { RTC_DCHECK_RUN_ON(&sequence_checker_); return reason_; } // VideoSourceRestrictionsListener implementation. void OnVideoSourceRestrictionsUpdated( VideoSourceRestrictions restrictions, const VideoAdaptationCounters& adaptation_counters, rtc::scoped_refptr 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 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(&input_state_provider_, &frame_rate_provider_, field_trials_)), processor_(std::make_unique( 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 resource_; rtc::scoped_refptr other_resource_; std::unique_ptr video_stream_adapter_; std::unique_ptr 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