Count disabled due to low bw streams or layers as bw limited quality in GetStats
Bug: webrtc:11015
Change-Id: I65cd890706f765366d89ded8c21fa7507797fc23
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/155964
Commit-Queue: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#29421}
diff --git a/api/video/video_bitrate_allocation.cc b/api/video/video_bitrate_allocation.cc
index 32e7246..e189db1 100644
--- a/api/video/video_bitrate_allocation.cc
+++ b/api/video/video_bitrate_allocation.cc
@@ -18,7 +18,8 @@
namespace webrtc {
-VideoBitrateAllocation::VideoBitrateAllocation() : sum_(0) {}
+VideoBitrateAllocation::VideoBitrateAllocation()
+ : sum_(0), is_bw_limited_(false) {}
bool VideoBitrateAllocation::SetBitrate(size_t spatial_index,
size_t temporal_index,
diff --git a/api/video/video_bitrate_allocation.h b/api/video/video_bitrate_allocation.h
index da58a5b..56c0f64 100644
--- a/api/video/video_bitrate_allocation.h
+++ b/api/video/video_bitrate_allocation.h
@@ -80,9 +80,15 @@
std::string ToString() const;
+ // Indicates if the allocation has some layers/streams disabled due to
+ // low available bandwidth.
+ void set_bw_limited(bool limited) { is_bw_limited_ = limited; }
+ bool is_bw_limited() const { return is_bw_limited_; }
+
private:
uint32_t sum_;
absl::optional<uint32_t> bitrates_[kMaxSpatialLayers][kMaxTemporalStreams];
+ bool is_bw_limited_;
};
} // namespace webrtc
diff --git a/modules/video_coding/codecs/vp9/svc_rate_allocator.cc b/modules/video_coding/codecs/vp9/svc_rate_allocator.cc
index 86b677d..7d5c724 100644
--- a/modules/video_coding/codecs/vp9/svc_rate_allocator.cc
+++ b/modules/video_coding/codecs/vp9/svc_rate_allocator.cc
@@ -211,7 +211,8 @@
}
const size_t first_active_layer = GetFirstActiveLayer(codec_);
- size_t num_spatial_layers = GetNumActiveSpatialLayers(codec_);
+ const size_t num_active_layers = GetNumActiveSpatialLayers(codec_);
+ size_t num_spatial_layers = num_active_layers;
if (num_spatial_layers == 0) {
return VideoBitrateAllocation(); // All layers are deactivated.
@@ -244,13 +245,16 @@
}
last_active_layer_count_ = num_spatial_layers;
+ VideoBitrateAllocation allocation;
if (codec_.mode == VideoCodecMode::kRealtimeVideo) {
- return GetAllocationNormalVideo(total_bitrate, first_active_layer,
- num_spatial_layers);
+ allocation = GetAllocationNormalVideo(total_bitrate, first_active_layer,
+ num_spatial_layers);
} else {
- return GetAllocationScreenSharing(total_bitrate, first_active_layer,
- num_spatial_layers);
+ allocation = GetAllocationScreenSharing(total_bitrate, first_active_layer,
+ num_spatial_layers);
}
+ allocation.set_bw_limited(num_spatial_layers < num_active_layers);
+ return allocation;
}
VideoBitrateAllocation SvcRateAllocator::GetAllocationNormalVideo(
diff --git a/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc b/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc
index 06240a3..6a677a2 100644
--- a/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc
+++ b/modules/video_coding/codecs/vp9/svc_rate_allocator_unittest.cc
@@ -224,6 +224,24 @@
}
}
+TEST(SvcRateAllocatorTest, SignalsBwLimited) {
+ VideoCodec codec = Configure(1280, 720, 3, 1, false);
+ SvcRateAllocator allocator = SvcRateAllocator(codec);
+
+ // Rough estimate calculated by hand.
+ uint32_t min_to_enable_all = 900000;
+
+ EXPECT_TRUE(
+ allocator
+ .Allocate(VideoBitrateAllocationParameters(min_to_enable_all / 2, 30))
+ .is_bw_limited());
+
+ EXPECT_FALSE(
+ allocator
+ .Allocate(VideoBitrateAllocationParameters(min_to_enable_all, 30))
+ .is_bw_limited());
+}
+
TEST(SvcRateAllocatorTest, NoPaddingIfAllLayersAreDeactivated) {
VideoCodec codec = Configure(1280, 720, 3, 1, false);
EXPECT_EQ(codec.VP9()->numberOfSpatialLayers, 3U);
diff --git a/modules/video_coding/utility/simulcast_rate_allocator.cc b/modules/video_coding/utility/simulcast_rate_allocator.cc
index 15b8e54..f074ee9 100644
--- a/modules/video_coding/utility/simulcast_rate_allocator.cc
+++ b/modules/video_coding/utility/simulcast_rate_allocator.cc
@@ -166,6 +166,7 @@
min_bitrate = std::min(hysteresis_factor * min_bitrate, target_bitrate);
}
if (left_in_stable_allocation < min_bitrate) {
+ allocated_bitrates->set_bw_limited(true);
break;
}
diff --git a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc
index eb01481..e85ae3b 100644
--- a/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc
+++ b/modules/video_coding/utility/simulcast_rate_allocator_unittest.cc
@@ -221,6 +221,27 @@
ExpectEqual(expected, GetAllocation(0));
}
+TEST_F(SimulcastRateAllocatorTest, SignalsBwLimited) {
+ // Enough to enable all layers.
+ const int kVeryBigBitrate = 100000;
+ // With simulcast, use the min bitrate from the ss spec instead of the global.
+ SetupCodec3SL3TL({true, true, true});
+ CreateAllocator();
+
+ EXPECT_TRUE(
+ GetAllocation(codec_.simulcastStream[0].minBitrate - 10).is_bw_limited());
+ EXPECT_TRUE(
+ GetAllocation(codec_.simulcastStream[0].targetBitrate).is_bw_limited());
+ EXPECT_TRUE(GetAllocation(codec_.simulcastStream[0].targetBitrate +
+ codec_.simulcastStream[1].minBitrate)
+ .is_bw_limited());
+ EXPECT_FALSE(GetAllocation(codec_.simulcastStream[0].targetBitrate +
+ codec_.simulcastStream[1].targetBitrate +
+ codec_.simulcastStream[2].minBitrate)
+ .is_bw_limited());
+ EXPECT_FALSE(GetAllocation(kVeryBigBitrate).is_bw_limited());
+}
+
TEST_F(SimulcastRateAllocatorTest, SingleSimulcastAboveMax) {
codec_.numberOfSimulcastStreams = 1;
codec_.simulcastStream[0].minBitrate = kMinBitrateKbps;
@@ -655,6 +676,7 @@
EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps, allocation.get_sum_kbps());
EXPECT_EQ(kLegacyScreenshareTargetBitrateKbps,
allocation.GetBitrate(0, 0) / 1000);
+ EXPECT_EQ(allocation.is_bw_limited(), GetParam());
}
TEST_P(ScreenshareRateAllocationTest, BitrateAboveTl0) {
@@ -674,6 +696,7 @@
allocation.GetBitrate(0, 0) / 1000);
EXPECT_EQ(target_bitrate_kbps - kLegacyScreenshareTargetBitrateKbps,
allocation.GetBitrate(0, 1) / 1000);
+ EXPECT_EQ(allocation.is_bw_limited(), GetParam());
}
TEST_F(ScreenshareRateAllocationTest, BitrateAboveTl1) {
@@ -692,6 +715,7 @@
EXPECT_EQ(
kLegacyScreenshareMaxBitrateKbps - kLegacyScreenshareTargetBitrateKbps,
allocation.GetBitrate(0, 1) / 1000);
+ EXPECT_FALSE(allocation.is_bw_limited());
}
// This tests when the screenshare is inactive it should be allocated 0 bitrate
diff --git a/video/send_statistics_proxy.cc b/video/send_statistics_proxy.cc
index 5bf3427..58fb82e 100644
--- a/video/send_statistics_proxy.cc
+++ b/video/send_statistics_proxy.cc
@@ -147,6 +147,7 @@
last_num_spatial_layers_(0),
last_num_simulcast_streams_(0),
last_spatial_layer_use_{},
+ bw_limited_layers_(false),
uma_container_(
new UmaSamplesContainer(GetUmaPrefix(content_type_), stats_, clock)) {
}
@@ -1073,10 +1074,21 @@
break;
}
- bool is_cpu_limited = cpu_counts.num_resolution_reductions > 0 ||
- cpu_counts.num_framerate_reductions > 0;
- bool is_bandwidth_limited = quality_counts.num_resolution_reductions > 0 ||
- quality_counts.num_framerate_reductions > 0;
+ cpu_downscales_ = cpu_counts.num_resolution_reductions.value_or(-1);
+ quality_downscales_ = quality_counts.num_resolution_reductions.value_or(-1);
+
+ cpu_counts_ = cpu_counts;
+ quality_counts_ = quality_counts;
+
+ UpdateAdaptationStats();
+}
+
+void SendStatisticsProxy::UpdateAdaptationStats() {
+ bool is_cpu_limited = cpu_counts_.num_resolution_reductions > 0 ||
+ cpu_counts_.num_framerate_reductions > 0;
+ bool is_bandwidth_limited = quality_counts_.num_resolution_reductions > 0 ||
+ quality_counts_.num_framerate_reductions > 0 ||
+ bw_limited_layers_;
if (is_bandwidth_limited) {
// We may be both CPU limited and bandwidth limited at the same time but
// there is no way to express this in standardized stats. Heuristically,
@@ -1092,21 +1104,27 @@
QualityLimitationReason::kNone);
}
- UpdateAdaptationStats(cpu_counts, quality_counts);
-}
-
-void SendStatisticsProxy::UpdateAdaptationStats(
- const AdaptationSteps& cpu_counts,
- const AdaptationSteps& quality_counts) {
- cpu_downscales_ = cpu_counts.num_resolution_reductions.value_or(-1);
- quality_downscales_ = quality_counts.num_resolution_reductions.value_or(-1);
-
- stats_.cpu_limited_resolution = cpu_counts.num_resolution_reductions > 0;
- stats_.cpu_limited_framerate = cpu_counts.num_framerate_reductions > 0;
- stats_.bw_limited_resolution = quality_counts.num_resolution_reductions > 0;
- stats_.bw_limited_framerate = quality_counts.num_framerate_reductions > 0;
+ stats_.cpu_limited_resolution = cpu_counts_.num_resolution_reductions > 0;
+ stats_.cpu_limited_framerate = cpu_counts_.num_framerate_reductions > 0;
+ stats_.bw_limited_resolution = quality_counts_.num_resolution_reductions > 0;
+ stats_.bw_limited_framerate = quality_counts_.num_framerate_reductions > 0;
+ // If bitrate allocator has disabled some layers frame-rate or resolution are
+ // limited depending on the encoder configuration.
+ if (bw_limited_layers_) {
+ switch (content_type_) {
+ case VideoEncoderConfig::ContentType::kRealtimeVideo: {
+ stats_.bw_limited_resolution = true;
+ break;
+ }
+ case VideoEncoderConfig::ContentType::kScreen: {
+ stats_.bw_limited_framerate = true;
+ break;
+ }
+ }
+ }
stats_.quality_limitation_reason =
quality_limitation_reason_tracker_.current_reason();
+
// |stats_.quality_limitation_durations_ms| depends on the current time
// when it is polled; it is updated in SendStatisticsProxy::GetStats().
}
@@ -1134,6 +1152,9 @@
rtc::CritScope lock(&crit_);
+ bw_limited_layers_ = allocation.is_bw_limited();
+ UpdateAdaptationStats();
+
if (spatial_layers != last_spatial_layer_use_) {
// If the number of spatial layers has changed, the resolution change is
// not due to quality limitations, it is because the configuration
diff --git a/video/send_statistics_proxy.h b/video/send_statistics_proxy.h
index 6955ef6..e690803 100644
--- a/video/send_statistics_proxy.h
+++ b/video/send_statistics_proxy.h
@@ -223,9 +223,7 @@
void SetAdaptTimer(const AdaptationSteps& counts, StatsTimer* timer)
RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
- void UpdateAdaptationStats(const AdaptationSteps& cpu_counts,
- const AdaptationSteps& quality_counts)
- RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
+ void UpdateAdaptationStats() RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
void TryUpdateInitialQualityResolutionAdaptUp(
const AdaptationSteps& quality_counts)
RTC_EXCLUSIVE_LOCKS_REQUIRED(crit_);
@@ -263,6 +261,11 @@
int last_num_simulcast_streams_ RTC_GUARDED_BY(crit_);
std::array<bool, kMaxSpatialLayers> last_spatial_layer_use_
RTC_GUARDED_BY(crit_);
+ // Indicates if the latest bitrate allocation had layers disabled by low
+ // available bandwidth.
+ bool bw_limited_layers_ RTC_GUARDED_BY(crit_);
+ AdaptationSteps cpu_counts_ RTC_GUARDED_BY(crit_);
+ AdaptationSteps quality_counts_ RTC_GUARDED_BY(crit_);
struct EncoderChangeEvent {
std::string previous_encoder_implementation;
diff --git a/video/send_statistics_proxy_unittest.cc b/video/send_statistics_proxy_unittest.cc
index 47ce644..4823e95 100644
--- a/video/send_statistics_proxy_unittest.cc
+++ b/video/send_statistics_proxy_unittest.cc
@@ -1371,6 +1371,79 @@
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
+TEST_F(SendStatisticsProxyTest,
+ QualityLimitationReasonsAreCorrectForContentType) {
+ // Realtime case.
+ // Configure two streams.
+ VideoEncoderConfig config;
+ config.content_type = VideoEncoderConfig::ContentType::kRealtimeVideo;
+ config.number_of_streams = 2;
+ VideoStream stream1;
+ stream1.width = kWidth / 2;
+ stream1.height = kHeight / 2;
+ VideoStream stream2;
+ stream2.width = kWidth;
+ stream2.height = kHeight;
+ statistics_proxy_->OnEncoderReconfigured(config, {stream1, stream2});
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(statistics_proxy_->GetStats().quality_limitation_reason,
+ QualityLimitationReason::kNone);
+ // Bw disabled one layer.
+ VideoCodec codec;
+ codec.numberOfSimulcastStreams = 2;
+ codec.simulcastStream[0].active = true;
+ codec.simulcastStream[1].active = true;
+ VideoBitrateAllocation allocation;
+ // Some positive bitrate only on the first stream.
+ allocation.SetBitrate(0, 0, 10000);
+ allocation.SetBitrate(1, 0, 0);
+ allocation.set_bw_limited(true);
+ statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
+ EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(statistics_proxy_->GetStats().quality_limitation_reason,
+ QualityLimitationReason::kBandwidth);
+ // Bw enabled all layers.
+ allocation.SetBitrate(1, 0, 10000);
+ allocation.set_bw_limited(false);
+ statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_EQ(statistics_proxy_->GetStats().quality_limitation_reason,
+ QualityLimitationReason::kNone);
+
+ // Screencast case
+ // Configure two streams.
+ config.content_type = VideoEncoderConfig::ContentType::kScreen;
+ config.number_of_streams = 2;
+ stream1.width = kWidth;
+ stream1.height = kHeight;
+ statistics_proxy_->OnEncoderReconfigured(config, {stream1, stream2});
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_EQ(statistics_proxy_->GetStats().quality_limitation_reason,
+ QualityLimitationReason::kNone);
+ // Bw disabled one layer.
+ // Some positive bitrate only on the second stream.
+ allocation.SetBitrate(0, 0, 10000);
+ allocation.SetBitrate(1, 0, 0);
+ allocation.set_bw_limited(true);
+ statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
+ EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_framerate);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_EQ(statistics_proxy_->GetStats().quality_limitation_reason,
+ QualityLimitationReason::kBandwidth);
+ // Bw enabled all layers.
+ allocation.SetBitrate(1, 0, 10000);
+ allocation.set_bw_limited(false);
+ statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+ EXPECT_EQ(statistics_proxy_->GetStats().quality_limitation_reason,
+ QualityLimitationReason::kNone);
+}
+
TEST_F(SendStatisticsProxyTest, SwitchContentTypeUpdatesHistograms) {
for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
statistics_proxy_->OnIncomingFrame(kWidth, kHeight);
@@ -1982,6 +2055,7 @@
// Configure two streams.
VideoEncoderConfig config;
config.content_type = VideoEncoderConfig::ContentType::kRealtimeVideo;
+ config.number_of_streams = 2;
VideoStream stream1;
stream1.width = kWidth / 2;
stream1.height = kHeight / 2;
@@ -2044,6 +2118,26 @@
quality_counts);
statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
+
+ // Adapt up.
+ quality_counts.num_resolution_reductions = 0;
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kQuality, cpu_counts,
+ quality_counts);
+ statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
+ EXPECT_FALSE(statistics_proxy_->GetStats().bw_limited_resolution);
+
+ // Bw disabled one layer.
+ VideoCodec codec;
+ codec.numberOfSimulcastStreams = 2;
+ codec.simulcastStream[0].active = true;
+ codec.simulcastStream[1].active = true;
+ VideoBitrateAllocation allocation;
+ // Some positive bitrate only on the second stream.
+ allocation.SetBitrate(1, 0, 10000);
+ allocation.set_bw_limited(true);
+ statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
+ EXPECT_TRUE(statistics_proxy_->GetStats().bw_limited_resolution);
}
TEST_F(SendStatisticsProxyTest, GetStatsReportsTargetMediaBitrate) {