summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/call/adaptation/video_stream_adapter_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/libwebrtc/call/adaptation/video_stream_adapter_unittest.cc')
-rw-r--r--third_party/libwebrtc/call/adaptation/video_stream_adapter_unittest.cc951
1 files changed, 951 insertions, 0 deletions
diff --git a/third_party/libwebrtc/call/adaptation/video_stream_adapter_unittest.cc b/third_party/libwebrtc/call/adaptation/video_stream_adapter_unittest.cc
new file mode 100644
index 0000000000..d4bc650856
--- /dev/null
+++ b/third_party/libwebrtc/call/adaptation/video_stream_adapter_unittest.cc
@@ -0,0 +1,951 @@
+/*
+ * 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/scoped_refptr.h"
+#include "api/video/video_adaptation_reason.h"
+#include "api/video_codecs/video_codec.h"
+#include "api/video_codecs/video_encoder.h"
+#include "call/adaptation/adaptation_constraint.h"
+#include "call/adaptation/encoder_settings.h"
+#include "call/adaptation/test/fake_frame_rate_provider.h"
+#include "call/adaptation/test/fake_resource.h"
+#include "call/adaptation/test/fake_video_stream_input_state_provider.h"
+#include "call/adaptation/video_source_restrictions.h"
+#include "call/adaptation/video_stream_input_state.h"
+#include "rtc_base/string_encode.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+#include "test/testsupport/rtc_expect_death.h"
+#include "video/config/video_encoder_config.h"
+
+namespace webrtc {
+
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SaveArg;
+
+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) + "/";
+}
+
+// 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,
+ FakeVideoStreamInputStateProvider* provider,
+ int input_pixels,
+ int input_fps,
+ int min_pixels_per_frame)
+ : adapter_(adapter),
+ provider_(provider),
+ input_pixels_(input_pixels),
+ input_fps_(input_fps),
+ min_pixels_per_frame_(min_pixels_per_frame) {
+ provider_->SetInputState(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, nullptr);
+ // 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();
+ }
+ provider_->SetInputState(input_pixels_, input_fps_, min_pixels_per_frame_);
+ }
+
+ private:
+ VideoStreamAdapter* adapter_;
+ FakeVideoStreamInputStateProvider* provider_;
+ int input_pixels_;
+ int input_fps_;
+ int min_pixels_per_frame_;
+};
+
+class FakeVideoStreamAdapterListner : public VideoSourceRestrictionsListener {
+ public:
+ void OnVideoSourceRestrictionsUpdated(
+ VideoSourceRestrictions restrictions,
+ const VideoAdaptationCounters& adaptation_counters,
+ rtc::scoped_refptr<Resource> reason,
+ const VideoSourceRestrictions& unfiltered_restrictions) override {
+ calls_++;
+ last_restrictions_ = unfiltered_restrictions;
+ }
+
+ int calls() const { return calls_; }
+
+ VideoSourceRestrictions last_restrictions() const {
+ return last_restrictions_;
+ }
+
+ private:
+ int calls_ = 0;
+ VideoSourceRestrictions last_restrictions_;
+};
+
+class MockAdaptationConstraint : public AdaptationConstraint {
+ public:
+ MOCK_METHOD(bool,
+ IsAdaptationUpAllowed,
+ (const VideoStreamInputState& input_state,
+ const VideoSourceRestrictions& restrictions_before,
+ const VideoSourceRestrictions& restrictions_after),
+ (const, override));
+
+ // MOCK_METHOD(std::string, Name, (), (const, override));
+ std::string Name() const override { return "MockAdaptationConstraint"; }
+};
+
+} // namespace
+
+class VideoStreamAdapterTest : public ::testing::Test {
+ public:
+ VideoStreamAdapterTest()
+ : field_trials_(BalancedFieldTrialConfig()),
+ resource_(FakeResource::Create("FakeResource")),
+ adapter_(&input_state_provider_,
+ &encoder_stats_observer_,
+ field_trials_) {}
+
+ protected:
+ webrtc::test::ScopedKeyValueConfig field_trials_;
+ FakeVideoStreamInputStateProvider input_state_provider_;
+ rtc::scoped_refptr<Resource> resource_;
+ testing::StrictMock<MockVideoStreamEncoderObserver> encoder_stats_observer_;
+ VideoStreamAdapter adapter_;
+};
+
+TEST_F(VideoStreamAdapterTest, NoRestrictionsByDefault) {
+ EXPECT_EQ(VideoSourceRestrictions(), adapter_.source_restrictions());
+ EXPECT_EQ(0, adapter_.adaptation_counters().Total());
+}
+
+TEST_F(VideoStreamAdapterTest, MaintainFramerate_DecreasesPixelsToThreeFifths) {
+ const int kInputPixels = 1280 * 720;
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ input_state_provider_.SetInputState(kInputPixels, 30,
+ kDefaultMinPixelsPerFrame);
+ Adaptation adaptation = adapter_.GetAdaptationDown();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ adapter_.ApplyAdaptation(adaptation, nullptr);
+ 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_F(VideoStreamAdapterTest,
+ MaintainFramerate_DecreasesPixelsToLimitReached) {
+ const int kMinPixelsPerFrame = 640 * 480;
+
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ input_state_provider_.SetInputState(kMinPixelsPerFrame + 1, 30,
+ kMinPixelsPerFrame);
+ EXPECT_CALL(encoder_stats_observer_, OnMinPixelLimitReached());
+ // 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());
+}
+
+TEST_F(VideoStreamAdapterTest, MaintainFramerate_IncreasePixelsToFiveThirds) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, MaintainFramerate_IncreasePixelsToUnrestricted) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, MaintainResolution_DecreasesFpsToTwoThirds) {
+ const int kInputFps = 30;
+
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ input_state_provider_.SetInputState(1280 * 720, kInputFps,
+ kDefaultMinPixelsPerFrame);
+ Adaptation adaptation = adapter_.GetAdaptationDown();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ adapter_.ApplyAdaptation(adaptation, nullptr);
+ 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_F(VideoStreamAdapterTest, MaintainResolution_DecreasesFpsToLimitReached) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, MaintainResolution_IncreaseFpsToThreeHalves) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, MaintainResolution_IncreaseFpsToUnrestricted) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, Balanced_DecreaseFrameRate) {
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ input_state_provider_.SetInputState(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, nullptr);
+ 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_F(VideoStreamAdapterTest, Balanced_DecreaseResolution) {
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ FakeVideoStream fake_stream(
+ &adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, Balanced_IncreaseFrameRateAndResolution) {
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ FakeVideoStream fake_stream(
+ &adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest, Balanced_LimitReached) {
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ FakeVideoStream fake_stream(
+ &adapter_, &input_state_provider_, 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.
+ EXPECT_CALL(encoder_stats_observer_, OnMinPixelLimitReached()).Times(2);
+ 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_F(VideoStreamAdapterTest,
+ MaintainFramerate_AwaitingPreviousAdaptationDown) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ // Adapt down once, but don't update the input.
+ adapter_.ApplyAdaptation(adapter_.GetAdaptationDown(), nullptr);
+ 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_F(VideoStreamAdapterTest, MaintainFramerate_AwaitingPreviousAdaptationUp) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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(), nullptr);
+ 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_F(VideoStreamAdapterTest,
+ MaintainResolution_AdaptsUpAfterSwitchingDegradationPreference) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest,
+ MaintainFramerate_AdaptsUpAfterSwitchingDegradationPreference) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest,
+ PendingResolutionIncreaseAllowsAdaptUpAfterSwitchToMaintainResolution) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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(), nullptr);
+ 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_F(VideoStreamAdapterTest,
+ MaintainFramerate_AdaptsDownAfterSwitchingDegradationPreference) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(VideoStreamAdapterTest,
+ MaintainResolution_AdaptsDownAfterSwitchingDegradationPreference) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 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_F(
+ VideoStreamAdapterTest,
+ PendingResolutionDecreaseAllowsAdaptDownAfterSwitchToMaintainResolution) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ // Apply adaptation but don't update the input.
+ adapter_.ApplyAdaptation(adapter_.GetAdaptationDown(), nullptr);
+ 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_F(VideoStreamAdapterTest, RestrictionBroadcasted) {
+ FakeVideoStreamAdapterListner listener;
+ adapter_.AddRestrictionsListener(&listener);
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ // Not broadcast on invalid ApplyAdaptation.
+ {
+ Adaptation adaptation = adapter_.GetAdaptationUp();
+ adapter_.ApplyAdaptation(adaptation, nullptr);
+ EXPECT_EQ(0, listener.calls());
+ }
+
+ // Broadcast on ApplyAdaptation.
+ {
+ Adaptation adaptation = adapter_.GetAdaptationDown();
+ fake_stream.ApplyAdaptation(adaptation);
+ EXPECT_EQ(1, listener.calls());
+ EXPECT_EQ(adaptation.restrictions(), listener.last_restrictions());
+ }
+
+ // Broadcast on ClearRestrictions().
+ adapter_.ClearRestrictions();
+ EXPECT_EQ(2, listener.calls());
+ EXPECT_EQ(VideoSourceRestrictions(), listener.last_restrictions());
+}
+
+TEST_F(VideoStreamAdapterTest, AdaptationHasNextRestrcitions) {
+ // Any non-disabled DegradationPreference will do.
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ // When adaptation is not possible.
+ {
+ Adaptation adaptation = adapter_.GetAdaptationUp();
+ EXPECT_EQ(Adaptation::Status::kLimitReached, adaptation.status());
+ EXPECT_EQ(adaptation.restrictions(), adapter_.source_restrictions());
+ EXPECT_EQ(0, adaptation.counters().Total());
+ }
+ // When we adapt down.
+ {
+ Adaptation adaptation = adapter_.GetAdaptationDown();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ fake_stream.ApplyAdaptation(adaptation);
+ EXPECT_EQ(adaptation.restrictions(), adapter_.source_restrictions());
+ EXPECT_EQ(adaptation.counters(), adapter_.adaptation_counters());
+ }
+ // When we adapt up.
+ {
+ Adaptation adaptation = adapter_.GetAdaptationUp();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ fake_stream.ApplyAdaptation(adaptation);
+ EXPECT_EQ(adaptation.restrictions(), adapter_.source_restrictions());
+ EXPECT_EQ(adaptation.counters(), adapter_.adaptation_counters());
+ }
+}
+
+TEST_F(VideoStreamAdapterTest,
+ SetDegradationPreferenceToOrFromBalancedClearsRestrictions) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ adapter_.ApplyAdaptation(adapter_.GetAdaptationDown(), nullptr);
+ 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(), nullptr);
+ 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());
+}
+
+TEST_F(VideoStreamAdapterTest,
+ GetAdaptDownResolutionAdaptsResolutionInMaintainFramerate) {
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+
+ auto adaptation = adapter_.GetAdaptDownResolution();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ EXPECT_EQ(1, adaptation.counters().resolution_adaptations);
+ EXPECT_EQ(0, adaptation.counters().fps_adaptations);
+}
+
+TEST_F(VideoStreamAdapterTest,
+ GetAdaptDownResolutionReturnsWithStatusInDisabledAndMaintainResolution) {
+ adapter_.SetDegradationPreference(DegradationPreference::DISABLED);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ EXPECT_EQ(Adaptation::Status::kAdaptationDisabled,
+ adapter_.GetAdaptDownResolution().status());
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ EXPECT_EQ(Adaptation::Status::kLimitReached,
+ adapter_.GetAdaptDownResolution().status());
+}
+
+TEST_F(VideoStreamAdapterTest,
+ GetAdaptDownResolutionAdaptsFpsAndResolutionInBalanced) {
+ // Note: This test depends on BALANCED implementation, but with current
+ // implementation and input state settings, BALANCED will adapt resolution and
+ // frame rate once.
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+
+ auto adaptation = adapter_.GetAdaptDownResolution();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ EXPECT_EQ(1, adaptation.counters().resolution_adaptations);
+ EXPECT_EQ(1, adaptation.counters().fps_adaptations);
+}
+
+TEST_F(
+ VideoStreamAdapterTest,
+ GetAdaptDownResolutionAdaptsOnlyResolutionIfFpsAlreadyAdapterInBalanced) {
+ // Note: This test depends on BALANCED implementation, but with current
+ // implementation and input state settings, BALANCED will adapt resolution
+ // only.
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ input_state_provider_.SetInputState(1280 * 720, 5, kDefaultMinPixelsPerFrame);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+
+ auto first_adaptation = adapter_.GetAdaptationDown();
+ fake_stream.ApplyAdaptation(first_adaptation);
+
+ auto adaptation = adapter_.GetAdaptDownResolution();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ EXPECT_EQ(1, adaptation.counters().resolution_adaptations);
+ EXPECT_EQ(first_adaptation.counters().fps_adaptations,
+ adaptation.counters().fps_adaptations);
+}
+
+TEST_F(VideoStreamAdapterTest,
+ GetAdaptDownResolutionAdaptsOnlyFpsIfResolutionLowInBalanced) {
+ // Note: This test depends on BALANCED implementation, but with current
+ // implementation and input state settings, BALANCED will adapt resolution
+ // only.
+ adapter_.SetDegradationPreference(DegradationPreference::BALANCED);
+ input_state_provider_.SetInputState(kDefaultMinPixelsPerFrame, 30,
+ kDefaultMinPixelsPerFrame);
+
+ auto adaptation = adapter_.GetAdaptDownResolution();
+ EXPECT_EQ(Adaptation::Status::kValid, adaptation.status());
+ EXPECT_EQ(0, adaptation.counters().resolution_adaptations);
+ EXPECT_EQ(1, adaptation.counters().fps_adaptations);
+}
+
+TEST_F(VideoStreamAdapterTest,
+ AdaptationDisabledStatusAlwaysWhenDegradationPreferenceDisabled) {
+ adapter_.SetDegradationPreference(DegradationPreference::DISABLED);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ EXPECT_EQ(Adaptation::Status::kAdaptationDisabled,
+ adapter_.GetAdaptationDown().status());
+ EXPECT_EQ(Adaptation::Status::kAdaptationDisabled,
+ adapter_.GetAdaptationUp().status());
+ EXPECT_EQ(Adaptation::Status::kAdaptationDisabled,
+ adapter_.GetAdaptDownResolution().status());
+}
+
+TEST_F(VideoStreamAdapterTest, AdaptationConstraintAllowsAdaptationsUp) {
+ testing::StrictMock<MockAdaptationConstraint> adaptation_constraint;
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ adapter_.AddAdaptationConstraint(&adaptation_constraint);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ // Adapt down once so we can adapt up later.
+ auto first_adaptation = adapter_.GetAdaptationDown();
+ fake_stream.ApplyAdaptation(first_adaptation);
+
+ EXPECT_CALL(adaptation_constraint,
+ IsAdaptationUpAllowed(_, first_adaptation.restrictions(), _))
+ .WillOnce(Return(true));
+ EXPECT_EQ(Adaptation::Status::kValid, adapter_.GetAdaptationUp().status());
+ adapter_.RemoveAdaptationConstraint(&adaptation_constraint);
+}
+
+TEST_F(VideoStreamAdapterTest, AdaptationConstraintDisallowsAdaptationsUp) {
+ testing::StrictMock<MockAdaptationConstraint> adaptation_constraint;
+ adapter_.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ adapter_.AddAdaptationConstraint(&adaptation_constraint);
+ input_state_provider_.SetInputState(1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ FakeVideoStream fake_stream(&adapter_, &input_state_provider_, 1280 * 720, 30,
+ kDefaultMinPixelsPerFrame);
+ // Adapt down once so we can adapt up later.
+ auto first_adaptation = adapter_.GetAdaptationDown();
+ fake_stream.ApplyAdaptation(first_adaptation);
+
+ EXPECT_CALL(adaptation_constraint,
+ IsAdaptationUpAllowed(_, first_adaptation.restrictions(), _))
+ .WillOnce(Return(false));
+ EXPECT_EQ(Adaptation::Status::kRejectedByConstraint,
+ adapter_.GetAdaptationUp().status());
+ adapter_.RemoveAdaptationConstraint(&adaptation_constraint);
+}
+
+// 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) {
+ webrtc::test::ScopedKeyValueConfig field_trials;
+ FakeVideoStreamInputStateProvider input_state_provider;
+ testing::StrictMock<MockVideoStreamEncoderObserver> encoder_stats_observer_;
+ VideoStreamAdapter adapter(&input_state_provider, &encoder_stats_observer_,
+ field_trials);
+ adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_FRAMERATE);
+ input_state_provider.SetInputState(1280 * 720, 30, kDefaultMinPixelsPerFrame);
+ Adaptation adaptation = adapter.GetAdaptationDown();
+ adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ EXPECT_DEATH(adapter.ApplyAdaptation(adaptation, nullptr), "");
+}
+
+TEST(VideoStreamAdapterDeathTest, AdaptDownInvalidatesAdaptations) {
+ webrtc::test::ScopedKeyValueConfig field_trials;
+ FakeVideoStreamInputStateProvider input_state_provider;
+ testing::StrictMock<MockVideoStreamEncoderObserver> encoder_stats_observer_;
+ VideoStreamAdapter adapter(&input_state_provider, &encoder_stats_observer_,
+ field_trials);
+ adapter.SetDegradationPreference(DegradationPreference::MAINTAIN_RESOLUTION);
+ input_state_provider.SetInputState(1280 * 720, 30, kDefaultMinPixelsPerFrame);
+ Adaptation adaptation = adapter.GetAdaptationDown();
+ adapter.GetAdaptationDown();
+ EXPECT_DEATH(adapter.ApplyAdaptation(adaptation, nullptr), "");
+}
+
+#endif // RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID)
+
+} // namespace webrtc