Ensure that fps adaptation count can go back to zero when framerate is unrestricted.
Bug: webrtc:12867
Change-Id: I1c11d1a1154ea3d802cdc01e260f72a7e9d17e99
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/221373
Commit-Queue: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Evan Shrubsole <eshr@google.com>
Cr-Commit-Position: refs/heads/master@{#34265}
diff --git a/call/adaptation/video_stream_adapter.cc b/call/adaptation/video_stream_adapter.cc
index a923ce8..64e1a77 100644
--- a/call/adaptation/video_stream_adapter.cc
+++ b/call/adaptation/video_stream_adapter.cc
@@ -568,6 +568,14 @@
input_state.frame_size_pixels().value());
max_frame_rate = balanced_settings_.MaxFps(input_state.video_codec_type(),
frame_size_pixels);
+ // Temporary fix for cases when there are fewer framerate adaptation steps
+ // up than down. Make number of down/up steps equal.
+ if (max_frame_rate == std::numeric_limits<int>::max() &&
+ current_restrictions.counters.fps_adaptations > 1) {
+ // Do not unrestrict framerate to allow additional adaptation up steps.
+ RTC_LOG(LS_INFO) << "Modifying framerate due to remaining fps count.";
+ max_frame_rate -= current_restrictions.counters.fps_adaptations;
+ }
// In BALANCED, the max_frame_rate must be checked before proceeding. This
// is because the MaxFps might be the current Fps and so the balanced
// settings may want to scale up the resolution.
diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc
index 9566101..d359282 100644
--- a/video/video_stream_encoder_unittest.cc
+++ b/video/video_stream_encoder_unittest.cc
@@ -546,6 +546,15 @@
}
}
+ void OnOutputFormatRequest(int width, int height) {
+ absl::optional<std::pair<int, int>> target_aspect_ratio =
+ std::make_pair(width, height);
+ absl::optional<int> max_pixel_count = width * height;
+ absl::optional<int> max_fps;
+ adapter_.OnOutputFormatRequest(target_aspect_ratio, max_pixel_count,
+ max_fps);
+ }
+
void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink,
const rtc::VideoSinkWants& wants) override {
MutexLock lock(&mutex_);
@@ -3344,6 +3353,257 @@
}
TEST_F(VideoStreamEncoderTest,
+ FpsCountReturnsToZeroForFewerAdaptationsUpThanDown) {
+ const int kWidth = 640;
+ const int kHeight = 360;
+ const int64_t kFrameIntervalMs = 150;
+ video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources(
+ DataRate::BitsPerSec(kTargetBitrateBps),
+ DataRate::BitsPerSec(kTargetBitrateBps),
+ DataRate::BitsPerSec(kTargetBitrateBps), 0, 0, 0);
+
+ // Enable BALANCED preference, no initial limitation.
+ AdaptingFrameForwarder source(&time_controller_);
+ source.set_adaptation_enabled(true);
+ video_stream_encoder_->SetSource(&source,
+ webrtc::DegradationPreference::BALANCED);
+
+ int64_t timestamp_ms = kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(kWidth, kHeight);
+ EXPECT_THAT(source.sink_wants(), FpsMaxResolutionMax());
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt down, expect reduced fps (640x360@15fps).
+ video_stream_encoder_->TriggerQualityLow();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(),
+ FpsMatchesResolutionMax(Lt(kDefaultFramerate)));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Source requests 270p, expect reduced resolution (480x270@15fps).
+ source.OnOutputFormatRequest(480, 270);
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(480, 270);
+ EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt down, expect reduced fps (480x270@10fps).
+ video_stream_encoder_->TriggerQualityLow();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsLtResolutionEq(source.last_wants()));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Source requests QVGA, expect reduced resolution (320x180@10fps).
+ source.OnOutputFormatRequest(320, 180);
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(320, 180);
+ EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt down, expect reduced fps (320x180@7fps).
+ video_stream_encoder_->TriggerQualityLow();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsLtResolutionEq(source.last_wants()));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Source requests VGA, expect increased resolution (640x360@7fps).
+ source.OnOutputFormatRequest(640, 360);
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt up, expect increased fps (640x360@(max-2)fps).
+ video_stream_encoder_->TriggerQualityHigh();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsGtResolutionEq(source.last_wants()));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt up, expect increased fps (640x360@(max-1)fps).
+ video_stream_encoder_->TriggerQualityHigh();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsGtResolutionEq(source.last_wants()));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(5, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt up, expect increased fps (640x360@maxfps).
+ video_stream_encoder_->TriggerQualityHigh();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsGtResolutionEq(source.last_wants()));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(6, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ video_stream_encoder_->Stop();
+}
+
+TEST_F(VideoStreamEncoderTest,
+ FpsCountReturnsToZeroForFewerAdaptationsUpThanDownWithTwoResources) {
+ const int kWidth = 1280;
+ const int kHeight = 720;
+ const int64_t kFrameIntervalMs = 150;
+ video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources(
+ DataRate::BitsPerSec(kTargetBitrateBps),
+ DataRate::BitsPerSec(kTargetBitrateBps),
+ DataRate::BitsPerSec(kTargetBitrateBps), 0, 0, 0);
+
+ // Enable BALANCED preference, no initial limitation.
+ AdaptingFrameForwarder source(&time_controller_);
+ source.set_adaptation_enabled(true);
+ video_stream_encoder_->SetSource(&source,
+ webrtc::DegradationPreference::BALANCED);
+
+ int64_t timestamp_ms = kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(kWidth, kHeight);
+ EXPECT_THAT(source.sink_wants(), FpsMaxResolutionMax());
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(0, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt down, expect scaled down resolution (960x540@maxfps).
+ video_stream_encoder_->TriggerQualityLow();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(),
+ FpsMaxResolutionMatches(Lt(kWidth * kHeight)));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(1, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt down, expect scaled down resolution (640x360@maxfps).
+ video_stream_encoder_->TriggerQualityLow();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ sink_.WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsMaxResolutionLt(source.last_wants()));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(2, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Trigger adapt down, expect reduced fps (640x360@15fps).
+ video_stream_encoder_->TriggerQualityLow();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsLtResolutionEq(source.last_wants()));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+
+ // Source requests QVGA, expect reduced resolution (320x180@15fps).
+ source.OnOutputFormatRequest(320, 180);
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(320, 180);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(0, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ // Trigger adapt down, expect reduced fps (320x180@7fps).
+ video_stream_encoder_->TriggerCpuOveruse();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsLtResolutionEq(source.last_wants()));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_framerate);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ // Source requests HD, expect increased resolution (640x360@7fps).
+ source.OnOutputFormatRequest(1280, 720);
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(1, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ // Trigger adapt up, expect increased fps (640x360@(max-1)fps).
+ video_stream_encoder_->TriggerCpuUnderuse();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsGtResolutionEq(source.last_wants()));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
+ EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_framerate);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(2, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ // Trigger adapt up, expect increased fps (640x360@maxfps).
+ video_stream_encoder_->TriggerQualityHigh();
+ video_stream_encoder_->TriggerCpuUnderuse();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsGtResolutionEq(source.last_wants()));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate);
+ EXPECT_EQ(4, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(3, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ // Trigger adapt up, expect increased resolution (960x570@maxfps).
+ video_stream_encoder_->TriggerQualityHigh();
+ video_stream_encoder_->TriggerCpuUnderuse();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsEqResolutionGt(source.last_wants()));
+ EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_TRUE(stats_proxy_->GetStats().cpu_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate);
+ EXPECT_EQ(5, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(4, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ // Trigger adapt up, expect increased resolution (1280x720@maxfps).
+ video_stream_encoder_->TriggerQualityHigh();
+ video_stream_encoder_->TriggerCpuUnderuse();
+ timestamp_ms += kFrameIntervalMs;
+ source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));
+ WaitForEncodedFrame(timestamp_ms);
+ EXPECT_THAT(source.sink_wants(), FpsEqResolutionGt(source.last_wants()));
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_resolution);
+ EXPECT_FALSE(stats_proxy_->GetStats().cpu_limited_framerate);
+ EXPECT_EQ(6, stats_proxy_->GetStats().number_of_quality_adapt_changes);
+ EXPECT_EQ(5, stats_proxy_->GetStats().number_of_cpu_adapt_changes);
+
+ video_stream_encoder_->Stop();
+}
+
+TEST_F(VideoStreamEncoderTest,
NoChangeForInitialNormalUsage_MaintainFramerateMode) {
const int kWidth = 1280;
const int kHeight = 720;
@@ -6352,7 +6612,7 @@
EXPECT_TRUE(stats_proxy_->GetStats().bw_limited_framerate);
EXPECT_EQ(7, stats_proxy_->GetStats().number_of_quality_adapt_changes);
- // Trigger adapt up, expect expect increased fps (320x180@10fps).
+ // Trigger adapt up, expect increased fps (320x180@10fps).
video_stream_encoder_->TriggerQualityHigh();
timestamp_ms += kFrameIntervalMs;
source.IncomingCapturedFrame(CreateFrame(timestamp_ms, kWidth, kHeight));