| /* |
| * 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/video_stream_adapter.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "absl/types/optional.h" |
| #include "api/video/video_adaptation_reason.h" |
| #include "api/video_codecs/video_codec.h" |
| #include "api/video_codecs/video_encoder.h" |
| #include "api/video_codecs/video_encoder_config.h" |
| #include "call/adaptation/encoder_settings.h" |
| #include "call/adaptation/video_source_restrictions.h" |
| #include "rtc_base/string_encode.h" |
| #include "test/field_trial.h" |
| #include "test/gmock.h" |
| #include "test/gtest.h" |
| #include "test/testsupport/rtc_expect_death.h" |
| |
| namespace webrtc { |
| |
| namespace { |
| |
| const int kBalancedHighResolutionPixels = 1280 * 720; |
| const int kBalancedHighFrameRateFps = 30; |
| |
| const int kBalancedMediumResolutionPixels = 640 * 480; |
| const int kBalancedMediumFrameRateFps = 20; |
| |
| const int kBalancedLowResolutionPixels = 320 * 240; |
| const int kBalancedLowFrameRateFps = 10; |
| |
| std::string BalancedFieldTrialConfig() { |
| return "WebRTC-Video-BalancedDegradationSettings/pixels:" + |
| rtc::ToString(kBalancedLowResolutionPixels) + "|" + |
| rtc::ToString(kBalancedMediumResolutionPixels) + "|" + |
| rtc::ToString(kBalancedHighResolutionPixels) + |
| ",fps:" + rtc::ToString(kBalancedLowFrameRateFps) + "|" + |
| rtc::ToString(kBalancedMediumFrameRateFps) + "|" + |
| rtc::ToString(kBalancedHighFrameRateFps) + "/"; |
| } |
| |
| VideoStreamInputState InputState(int input_pixels, |
| int input_fps, |
| int min_pixels_per_frame) { |
| VideoStreamInputState input_state; |
| input_state.set_has_input(true); |
| input_state.set_frame_size_pixels(input_pixels); |
| input_state.set_frames_per_second(input_fps); |
| input_state.set_min_pixels_per_frame(min_pixels_per_frame); |
| return input_state; |
| } |
| |
| // Responsible for adjusting the inputs to VideoStreamAdapter (SetInput), such |
| // as pixels and frame rate, according to the most recent source restrictions. |
| // This helps tests that apply adaptations multiple times: if the input is not |
| // adjusted between adaptations, the subsequent adaptations fail with |
| // kAwaitingPreviousAdaptation. |
| class FakeVideoStream { |
| public: |
| FakeVideoStream(VideoStreamAdapter* adapter, |
| int input_pixels, |
| int input_fps, |
| int min_pixels_per_frame) |
| : adapter_(adapter), |
| input_pixels_(input_pixels), |
| input_fps_(input_fps), |
| min_pixels_per_frame_(min_pixels_per_frame) { |
| adapter_->SetInput( |
| InputState(input_pixels_, input_fps_, min_pixels_per_frame_)); |
| } |
| |
| int input_pixels() const { return input_pixels_; } |
| int input_fps() const { return input_fps_; } |
| |
| // Performs ApplyAdaptation() followed by SetInput() with input pixels and |
| // frame rate adjusted according to the resulting restrictions. |
| void ApplyAdaptation(Adaptation adaptation) { |
| adapter_->ApplyAdaptation(adaptation); |
| // Update input pixels and fps according to the resulting restrictions. |
| auto restrictions = adapter_->source_restrictions(); |
| if (restrictions.target_pixels_per_frame().has_value()) { |
| RTC_DCHECK(!restrictions.max_pixels_per_frame().has_value() || |
| restrictions.max_pixels_per_frame().value() >= |
| restrictions.target_pixels_per_frame().value()); |
| input_pixels_ = restrictions.target_pixels_per_frame().value(); |
| } else if (restrictions.max_pixels_per_frame().has_value()) { |
| input_pixels_ = restrictions.max_pixels_per_frame().value(); |
| } |
| if (restrictions.max_frame_rate().has_value()) { |
| input_fps_ = restrictions.max_frame_rate().value(); |
| } |
| adapter_->SetInput( |
| InputState(input_pixels_, input_fps_, min_pixels_per_frame_)); |
| } |
| |
| private: |
| VideoStreamAdapter* adapter_; |
| int input_pixels_; |
| int input_fps_; |
| int min_pixels_per_frame_; |
| }; |
| |
| } // namespace |
| |
| TEST(VideoStreamAdapterTest, NoRestrictionsByDefault) { |
| VideoStreamAdapter adapter; |
| EXPECT_EQ(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_EQ(0, adapter.adaptation_counters().Total()); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainFramerate_DecreasesPixelsToThreeFifths) { |
| const int kInputPixels = 1280 * 720; |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| adapter.SetInput(InputState(kInputPixels, 30, kDefaultMinPixelsPerFrame)); |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| EXPECT_FALSE(adaptation.min_pixel_limit_reached()); |
| adapter.ApplyAdaptation(adaptation); |
| EXPECT_EQ(static_cast<size_t>((kInputPixels * 3) / 5), |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainFramerate_DecreasesPixelsToLimitReached) { |
| const int kMinPixelsPerFrame = 640 * 480; |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| adapter.SetInput(InputState(kMinPixelsPerFrame + 1, 30, kMinPixelsPerFrame)); |
| // Even though we are above kMinPixelsPerFrame, because adapting down would |
| // have exceeded the limit, we are said to have reached the limit already. |
| // This differs from the frame rate adaptation logic, which would have clamped |
| // to the limit in the first step and reported kLimitReached in the second |
| // step. |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kLimitReached, adaptation.status()); |
| EXPECT_TRUE(adaptation.min_pixel_limit_reached()); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainFramerate_IncreasePixelsToFiveThirds) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Go down twice, ensuring going back up is still a restricted resolution. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(2, adapter.adaptation_counters().resolution_adaptations); |
| int input_pixels = fake_stream.input_pixels(); |
| // Go up once. The target is 5/3 and the max is 12/5 of the target. |
| const int target = (input_pixels * 5) / 3; |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(static_cast<size_t>((target * 12) / 5), |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(static_cast<size_t>(target), |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainFramerate_IncreasePixelsToUnrestricted) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // We are unrestricted by default and should not be able to adapt up. |
| EXPECT_EQ(Adaptation::Status::kLimitReached, |
| adapter.GetAdaptationUp().status()); |
| // If we go down once and then back up we should not have any restrictions. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_EQ(0, adapter.adaptation_counters().Total()); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainResolution_DecreasesFpsToTwoThirds) { |
| const int kInputFps = 30; |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| adapter.SetInput( |
| InputState(1280 * 720, kInputFps, kDefaultMinPixelsPerFrame)); |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| adapter.ApplyAdaptation(adaptation); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(static_cast<double>((kInputFps * 2) / 3), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainResolution_DecreasesFpsToLimitReached) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, kMinFrameRateFps + 1, |
| kDefaultMinPixelsPerFrame); |
| // If we are not yet at the limit and the next step would exceed it, the step |
| // is clamped such that we end up exactly on the limit. |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(static_cast<double>(kMinFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| // Having reached the limit, the next adaptation down is not valid. |
| EXPECT_EQ(Adaptation::Status::kLimitReached, |
| adapter.GetAdaptationDown().status()); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainResolution_IncreaseFpsToThreeHalves) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Go down twice, ensuring going back up is still a restricted frame rate. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(2, adapter.adaptation_counters().fps_adaptations); |
| int input_fps = fake_stream.input_fps(); |
| // Go up once. The target is 3/2 of the input. |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(static_cast<double>((input_fps * 3) / 2), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, MaintainResolution_IncreaseFpsToUnrestricted) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // We are unrestricted by default and should not be able to adapt up. |
| EXPECT_EQ(Adaptation::Status::kLimitReached, |
| adapter.GetAdaptationUp().status()); |
| // If we go down once and then back up we should not have any restrictions. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_EQ(0, adapter.adaptation_counters().Total()); |
| } |
| |
| TEST(VideoStreamAdapterTest, Balanced_DecreaseFrameRate) { |
| webrtc::test::ScopedFieldTrials balanced_field_trials( |
| BalancedFieldTrialConfig()); |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::BALANCED); |
| adapter.SetInput(InputState(kBalancedMediumResolutionPixels, |
| kBalancedHighFrameRateFps, |
| kDefaultMinPixelsPerFrame)); |
| // If our frame rate is higher than the frame rate associated with our |
| // resolution we should try to adapt to the frame rate associated with our |
| // resolution: kBalancedMediumFrameRateFps. |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| adapter.ApplyAdaptation(adaptation); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(static_cast<double>(kBalancedMediumFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(0, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, Balanced_DecreaseResolution) { |
| webrtc::test::ScopedFieldTrials balanced_field_trials( |
| BalancedFieldTrialConfig()); |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::BALANCED); |
| FakeVideoStream fake_stream(&adapter, kBalancedHighResolutionPixels, |
| kBalancedHighFrameRateFps, |
| kDefaultMinPixelsPerFrame); |
| // If we are not below the current resolution's frame rate limit, we should |
| // adapt resolution according to "maintain-framerate" logic (three fifths). |
| // |
| // However, since we are unlimited at the start and input frame rate is not |
| // below kBalancedHighFrameRateFps, we first restrict the frame rate to |
| // kBalancedHighFrameRateFps even though that is our current frame rate. This |
| // does prevent the source from going higher, though, so it's technically not |
| // a NO-OP. |
| { |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| } |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(static_cast<double>(kBalancedHighFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(0, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| // Verify "maintain-framerate" logic the second time we adapt: Frame rate |
| // restrictions remains the same and resolution goes down. |
| { |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| } |
| constexpr size_t kReducedPixelsFirstStep = |
| static_cast<size_t>((kBalancedHighResolutionPixels * 3) / 5); |
| EXPECT_EQ(kReducedPixelsFirstStep, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(static_cast<double>(kBalancedHighFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| // If we adapt again, because the balanced settings' proposed frame rate is |
| // still kBalancedHighFrameRateFps, "maintain-framerate" will trigger again. |
| static_assert(kReducedPixelsFirstStep > kBalancedMediumResolutionPixels, |
| "The reduced resolution is still greater than the next lower " |
| "balanced setting resolution"); |
| constexpr size_t kReducedPixelsSecondStep = (kReducedPixelsFirstStep * 3) / 5; |
| { |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| } |
| EXPECT_EQ(kReducedPixelsSecondStep, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| EXPECT_EQ(absl::nullopt, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(static_cast<double>(kBalancedHighFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(2, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| |
| // Testing when to adapt frame rate and when to adapt resolution is quite |
| // entangled, so this test covers both cases. |
| // |
| // There is an asymmetry: When we adapt down we do it in one order, but when we |
| // adapt up we don't do it in the reverse order. Instead we always try to adapt |
| // frame rate first according to balanced settings' configs and only when the |
| // frame rate is already achieved do we adjust the resolution. |
| TEST(VideoStreamAdapterTest, Balanced_IncreaseFrameRateAndResolution) { |
| webrtc::test::ScopedFieldTrials balanced_field_trials( |
| BalancedFieldTrialConfig()); |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::BALANCED); |
| FakeVideoStream fake_stream(&adapter, kBalancedHighResolutionPixels, |
| kBalancedHighFrameRateFps, |
| kDefaultMinPixelsPerFrame); |
| // The desired starting point of this test is having adapted frame rate twice. |
| // This requires performing a number of adaptations. |
| constexpr size_t kReducedPixelsFirstStep = |
| static_cast<size_t>((kBalancedHighResolutionPixels * 3) / 5); |
| constexpr size_t kReducedPixelsSecondStep = (kReducedPixelsFirstStep * 3) / 5; |
| constexpr size_t kReducedPixelsThirdStep = (kReducedPixelsSecondStep * 3) / 5; |
| static_assert(kReducedPixelsFirstStep > kBalancedMediumResolutionPixels, |
| "The first pixel reduction is greater than the balanced " |
| "settings' medium pixel configuration"); |
| static_assert(kReducedPixelsSecondStep > kBalancedMediumResolutionPixels, |
| "The second pixel reduction is greater than the balanced " |
| "settings' medium pixel configuration"); |
| static_assert(kReducedPixelsThirdStep <= kBalancedMediumResolutionPixels, |
| "The third pixel reduction is NOT greater than the balanced " |
| "settings' medium pixel configuration"); |
| // The first adaptation should affect the frame rate: See |
| // Balanced_DecreaseResolution for explanation why. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(static_cast<double>(kBalancedHighFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| // The next three adaptations affects the resolution, because we have to reach |
| // kBalancedMediumResolutionPixels before a lower frame rate is considered by |
| // BalancedDegradationSettings. The number three is derived from the |
| // static_asserts above. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(kReducedPixelsFirstStep, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(kReducedPixelsSecondStep, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(kReducedPixelsThirdStep, |
| adapter.source_restrictions().max_pixels_per_frame()); |
| // Thus, the next adaptation will reduce frame rate to |
| // kBalancedMediumFrameRateFps. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(static_cast<double>(kBalancedMediumFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(3, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(2, adapter.adaptation_counters().fps_adaptations); |
| // Adapt up! |
| // While our resolution is in the medium-range, the frame rate associated with |
| // the next resolution configuration up ("high") is kBalancedHighFrameRateFps |
| // and "balanced" prefers adapting frame rate if not already applied. |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(static_cast<double>(kBalancedHighFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(3, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| // Now that we have already achieved the next frame rate up, we act according |
| // to "maintain-framerate". We go back up in resolution. Due to rounding |
| // errors we don't end up back at kReducedPixelsSecondStep. Rather we get to |
| // kReducedPixelsSecondStepUp, which is off by one compared to |
| // kReducedPixelsSecondStep. |
| constexpr size_t kReducedPixelsSecondStepUp = |
| (kReducedPixelsThirdStep * 5) / 3; |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(kReducedPixelsSecondStepUp, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(2, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| // Now that our resolution is back in the high-range, the next frame rate to |
| // try out is "unlimited". |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(absl::nullopt, adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(2, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(0, adapter.adaptation_counters().fps_adaptations); |
| } |
| // Now only adapting resolution remains. |
| constexpr size_t kReducedPixelsFirstStepUp = |
| (kReducedPixelsSecondStepUp * 5) / 3; |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(kReducedPixelsFirstStepUp, |
| adapter.source_restrictions().target_pixels_per_frame()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(0, adapter.adaptation_counters().fps_adaptations); |
| } |
| // The last step up should make us entirely unrestricted. |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_EQ(0, adapter.adaptation_counters().Total()); |
| } |
| } |
| |
| TEST(VideoStreamAdapterTest, Balanced_LimitReached) { |
| webrtc::test::ScopedFieldTrials balanced_field_trials( |
| BalancedFieldTrialConfig()); |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::BALANCED); |
| FakeVideoStream fake_stream(&adapter, kBalancedLowResolutionPixels, |
| kBalancedLowFrameRateFps, |
| kDefaultMinPixelsPerFrame); |
| // Attempting to adapt up while unrestricted should result in kLimitReached. |
| EXPECT_EQ(Adaptation::Status::kLimitReached, |
| adapter.GetAdaptationUp().status()); |
| // Adapting down once result in restricted frame rate, in this case we reach |
| // the lowest possible frame rate immediately: kBalancedLowFrameRateFps. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(static_cast<double>(kBalancedLowFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| // Any further adaptation must follow "maintain-framerate" rules (these are |
| // covered in more depth by the MaintainFramerate tests). This test does not |
| // assert exactly how resolution is adjusted, only that resolution always |
| // decreases and that we eventually reach kLimitReached. |
| size_t previous_resolution = kBalancedLowResolutionPixels; |
| bool did_reach_limit = false; |
| // If we have not reached the limit within 5 adaptations something is wrong... |
| for (int i = 0; i < 5; i++) { |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| if (adaptation.status() == Adaptation::Status::kLimitReached) { |
| did_reach_limit = true; |
| break; |
| } |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_LT(adapter.source_restrictions().max_pixels_per_frame().value(), |
| previous_resolution); |
| previous_resolution = |
| adapter.source_restrictions().max_pixels_per_frame().value(); |
| } |
| EXPECT_TRUE(did_reach_limit); |
| // Frame rate restrictions are the same as before. |
| EXPECT_EQ(static_cast<double>(kBalancedLowFrameRateFps), |
| adapter.source_restrictions().max_frame_rate()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| } |
| |
| // kAwaitingPreviousAdaptation is only supported in "maintain-framerate". |
| TEST(VideoStreamAdapterTest, MaintainFramerate_AwaitingPreviousAdaptationDown) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| adapter.SetInput(InputState(1280 * 720, 30, kDefaultMinPixelsPerFrame)); |
| // Adapt down once, but don't update the input. |
| adapter.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| { |
| // Having performed the adaptation, but not updated the input based on the |
| // new restrictions, adapting again in the same direction will not work. |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kAwaitingPreviousAdaptation, |
| adaptation.status()); |
| } |
| } |
| |
| // kAwaitingPreviousAdaptation is only supported in "maintain-framerate". |
| TEST(VideoStreamAdapterTest, MaintainFramerate_AwaitingPreviousAdaptationUp) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Perform two adaptation down so that adapting up twice is possible. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(2, adapter.adaptation_counters().resolution_adaptations); |
| // Adapt up once, but don't update the input. |
| adapter.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| { |
| // Having performed the adaptation, but not updated the input based on the |
| // new restrictions, adapting again in the same direction will not work. |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kAwaitingPreviousAdaptation, |
| adaptation.status()); |
| } |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| MaintainResolution_AdaptsUpAfterSwitchingDegradationPreference) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Adapt down in fps for later. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| EXPECT_EQ(0, adapter.adaptation_counters().resolution_adaptations); |
| |
| // We should be able to adapt in framerate one last time after the change of |
| // degradation preference. |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(0, adapter.adaptation_counters().fps_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| MaintainFramerate_AdaptsUpAfterSwitchingDegradationPreference) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Adapt down in resolution for later. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| EXPECT_EQ(0, adapter.adaptation_counters().fps_adaptations); |
| |
| // We should be able to adapt in framerate one last time after the change of |
| // degradation preference. |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(0, adapter.adaptation_counters().resolution_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| PendingResolutionIncreaseAllowsAdaptUpAfterSwitchToMaintainResolution) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Adapt fps down so we can adapt up later in the test. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| // Apply adaptation up but don't update input. |
| adapter.ApplyAdaptation(adapter.GetAdaptationUp()); |
| EXPECT_EQ(Adaptation::Status::kAwaitingPreviousAdaptation, |
| adapter.GetAdaptationUp().status()); |
| |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| MaintainFramerate_AdaptsDownAfterSwitchingDegradationPreference) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Adapt down once, should change FPS. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| // Adaptation down should apply after the degradation prefs change. |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| MaintainResolution_AdaptsDownAfterSwitchingDegradationPreference) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Adapt down once, should change FPS. |
| fake_stream.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| fake_stream.ApplyAdaptation(adaptation); |
| |
| EXPECT_EQ(1, adapter.adaptation_counters().fps_adaptations); |
| EXPECT_EQ(1, adapter.adaptation_counters().resolution_adaptations); |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| PendingResolutionDecreaseAllowsAdaptDownAfterSwitchToMaintainResolution) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // Apply adaptation but don't update the input. |
| adapter.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_EQ(Adaptation::Status::kAwaitingPreviousAdaptation, |
| adapter.GetAdaptationDown().status()); |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| } |
| |
| TEST(VideoStreamAdapterTest, PeekNextRestrictions) { |
| VideoStreamAdapter adapter; |
| // Any non-disabled DegradationPreference will do. |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| FakeVideoStream fake_stream(&adapter, 1280 * 720, 30, |
| kDefaultMinPixelsPerFrame); |
| // When adaptation is not possible. |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kLimitReached, adaptation.status()); |
| EXPECT_EQ(adapter.PeekNextRestrictions(adaptation), |
| adapter.source_restrictions()); |
| } |
| // When we adapt down. |
| { |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| VideoSourceRestrictions next_restrictions = |
| adapter.PeekNextRestrictions(adaptation); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(next_restrictions, adapter.source_restrictions()); |
| } |
| // When we adapt up. |
| { |
| Adaptation adaptation = adapter.GetAdaptationUp(); |
| EXPECT_EQ(Adaptation::Status::kValid, adaptation.status()); |
| VideoSourceRestrictions next_restrictions = |
| adapter.PeekNextRestrictions(adaptation); |
| fake_stream.ApplyAdaptation(adaptation); |
| EXPECT_EQ(next_restrictions, adapter.source_restrictions()); |
| } |
| } |
| |
| TEST(VideoStreamAdapterTest, |
| SetDegradationPreferenceToOrFromBalancedClearsRestrictions) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| adapter.SetInput(InputState(1280 * 720, 30, kDefaultMinPixelsPerFrame)); |
| adapter.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_NE(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_NE(0, adapter.adaptation_counters().Total()); |
| // Changing from non-balanced to balanced clears the restrictions. |
| adapter.SetDegradationPreference(DegradationPreference::BALANCED); |
| EXPECT_EQ(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_EQ(0, adapter.adaptation_counters().Total()); |
| // Apply adaptation again. |
| adapter.ApplyAdaptation(adapter.GetAdaptationDown()); |
| EXPECT_NE(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_NE(0, adapter.adaptation_counters().Total()); |
| // Changing from balanced to non-balanced clears the restrictions. |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| EXPECT_EQ(VideoSourceRestrictions(), adapter.source_restrictions()); |
| EXPECT_EQ(0, adapter.adaptation_counters().Total()); |
| } |
| |
| // Death tests. |
| // Disabled on Android because death tests misbehave on Android, see |
| // base/test/gtest_util.h. |
| #if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) |
| |
| TEST(VideoStreamAdapterDeathTest, |
| SetDegradationPreferenceInvalidatesAdaptations) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE); |
| adapter.SetInput(InputState(1280 * 720, 30, kDefaultMinPixelsPerFrame)); |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| EXPECT_DEATH(adapter.ApplyAdaptation(adaptation), ""); |
| } |
| |
| TEST(VideoStreamAdapterDeathTest, SetInputInvalidatesAdaptations) { |
| VideoStreamAdapter adapter; |
| adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION); |
| adapter.SetInput(InputState(1280 * 720, 30, kDefaultMinPixelsPerFrame)); |
| Adaptation adaptation = adapter.GetAdaptationDown(); |
| adapter.SetInput(InputState(1280 * 720, 31, kDefaultMinPixelsPerFrame)); |
| EXPECT_DEATH(adapter.PeekNextRestrictions(adaptation), ""); |
| } |
| |
| #endif // RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) |
| |
| } // namespace webrtc |