Implement QualityLimitationReasonTracker and expose "reason".
This CL implements the logic behind qualityLimitationReason[1] and
qualityLimitationDurations[2]
This CL also exposes qualityLimitationReason in the standard getStats()
API, but does not expose qualityLimitationDurations because that is
blocked on supporting the "record<>" type in RTCStatsMember[3].
[1] https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationreason
[2] https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations
[3] https://crbug.com/webrtc/10685
TBR=stefan@webrtc.org
Bug: webrtc:10451, webrtc:10686
Change-Id: Ifff0be4ddd64eaec23d59c02af99fdbb1feb3841
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/138825
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Åsa Persson <asapersson@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28090}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 64bfa19..ff99df5 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -74,6 +74,14 @@
static const char* const kUnknown;
};
+// https://w3c.github.io/webrtc-stats/#dom-rtcqualitylimitationreason
+struct RTCQualityLimitationReason {
+ static const char* const kNone;
+ static const char* const kCpu;
+ static const char* const kBandwidth;
+ static const char* const kOther;
+};
+
// https://webrtc.org/experiments/rtp-hdrext/video-content-type/
struct RTCContentType {
static const char* const kUnspecified;
@@ -464,6 +472,11 @@
// TODO(https://crbug.com/webrtc/10635): This is only implemented for video;
// implement it for audio as well.
RTCStatsMember<double> total_packet_send_delay;
+ // Enum type RTCQualityLimitationReason
+ // TODO(https://crbug.com/webrtc/10686): Also expose
+ // qualityLimitationDurations. Requires RTCStatsMember support for
+ // "record<DOMString, double>", see https://crbug.com/webrtc/10685.
+ RTCStatsMember<std::string> quality_limitation_reason;
// https://henbos.github.io/webrtc-provisional-stats/#dom-rtcoutboundrtpstreamstats-contenttype
RTCStatsMember<std::string> content_type;
};
diff --git a/call/video_send_stream.h b/call/video_send_stream.h
index 929aa88..850996e 100644
--- a/call/video_send_stream.h
+++ b/call/video_send_stream.h
@@ -28,6 +28,7 @@
#include "api/video/video_stream_encoder_settings.h"
#include "api/video_codecs/video_encoder_config.h"
#include "call/rtp_config.h"
+#include "common_video/include/quality_limitation_reason.h"
#include "modules/rtp_rtcp/include/report_block_data.h"
#include "modules/rtp_rtcp/include/rtcp_statistics.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@@ -92,6 +93,11 @@
bool cpu_limited_resolution = false;
bool bw_limited_framerate = false;
bool cpu_limited_framerate = false;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationreason
+ QualityLimitationReason quality_limitation_reason =
+ QualityLimitationReason::kNone;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations
+ std::map<QualityLimitationReason, int64_t> quality_limitation_durations_ms;
// Total number of times resolution as been requested to be changed due to
// CPU/quality adaptation.
int number_of_cpu_adapt_changes = 0;
diff --git a/common_video/BUILD.gn b/common_video/BUILD.gn
index 53bdea0..10a646f 100644
--- a/common_video/BUILD.gn
+++ b/common_video/BUILD.gn
@@ -28,6 +28,7 @@
"include/bitrate_adjuster.h",
"include/i420_buffer_pool.h",
"include/incoming_video_stream.h",
+ "include/quality_limitation_reason.h",
"include/video_frame.h",
"include/video_frame_buffer.h",
"incoming_video_stream.cc",
diff --git a/common_video/include/quality_limitation_reason.h b/common_video/include/quality_limitation_reason.h
new file mode 100644
index 0000000..068136a
--- /dev/null
+++ b/common_video/include/quality_limitation_reason.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef COMMON_VIDEO_INCLUDE_QUALITY_LIMITATION_REASON_H_
+#define COMMON_VIDEO_INCLUDE_QUALITY_LIMITATION_REASON_H_
+
+namespace webrtc {
+
+// https://w3c.github.io/webrtc-stats/#rtcqualitylimitationreason-enum
+enum class QualityLimitationReason {
+ kNone,
+ kCpu,
+ kBandwidth,
+ kOther,
+};
+
+} // namespace webrtc
+
+#endif // COMMON_VIDEO_INCLUDE_QUALITY_LIMITATION_REASON_H_
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 3b9a54c..c991de3 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -31,6 +31,7 @@
#include "api/video/video_source_interface.h"
#include "api/video/video_timing.h"
#include "api/video_codecs/video_encoder_config.h"
+#include "common_video/include/quality_limitation_reason.h"
#include "media/base/codec.h"
#include "media/base/delayable.h"
#include "media/base/media_config.h"
@@ -552,6 +553,12 @@
int nominal_bitrate = 0;
int adapt_reason = 0;
int adapt_changes = 0;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationreason
+ webrtc::QualityLimitationReason quality_limitation_reason =
+ webrtc::QualityLimitationReason::kNone;
+ // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations
+ std::map<webrtc::QualityLimitationReason, int64_t>
+ quality_limitation_durations_ms;
int avg_encode_ms = 0;
int encode_usage_percent = 0;
uint32_t frames_encoded = 0;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 5950204..92ed38d 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -2267,6 +2267,8 @@
if (stats.bw_limited_resolution)
info.adapt_reason |= ADAPTREASON_BANDWIDTH;
+ info.quality_limitation_reason = stats.quality_limitation_reason;
+ info.quality_limitation_durations_ms = stats.quality_limitation_durations_ms;
info.encoder_implementation_name = stats.encoder_implementation_name;
info.ssrc_groups = ssrc_groups_;
info.framerate_input = stats.input_frame_rate;
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index db7ca61..4386579 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -199,6 +199,20 @@
return nullptr;
}
+const char* QualityLimitationReasonToRTCQualityLimitationReason(
+ QualityLimitationReason reason) {
+ switch (reason) {
+ case QualityLimitationReason::kNone:
+ return RTCQualityLimitationReason::kNone;
+ case QualityLimitationReason::kCpu:
+ return RTCQualityLimitationReason::kCpu;
+ case QualityLimitationReason::kBandwidth:
+ return RTCQualityLimitationReason::kBandwidth;
+ case QualityLimitationReason::kOther:
+ return RTCQualityLimitationReason::kOther;
+ }
+}
+
double DoubleAudioLevelFromIntAudioLevel(int audio_level) {
RTC_DCHECK_GE(audio_level, 0);
RTC_DCHECK_LE(audio_level, 32767);
@@ -375,6 +389,9 @@
outbound_video->total_packet_send_delay =
static_cast<double>(video_sender_info.total_packet_send_delay_ms) /
rtc::kNumMillisecsPerSec;
+ outbound_video->quality_limitation_reason =
+ QualityLimitationReasonToRTCQualityLimitationReason(
+ video_sender_info.quality_limitation_reason);
// TODO(https://crbug.com/webrtc/10529): When info's |content_info| is
// optional, support the "unspecified" value.
if (video_sender_info.content_type == VideoContentType::SCREENSHARE)
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index c8876bf..51e6eb9 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -1941,6 +1941,8 @@
video_media_info.senders[0].total_encode_time_ms = 9000;
video_media_info.senders[0].total_encoded_bytes_target = 1234;
video_media_info.senders[0].total_packet_send_delay_ms = 10000;
+ video_media_info.senders[0].quality_limitation_reason =
+ QualityLimitationReason::kBandwidth;
video_media_info.senders[0].qp_sum = absl::nullopt;
video_media_info.senders[0].content_type = VideoContentType::UNSPECIFIED;
@@ -1986,6 +1988,7 @@
expected_video.total_encode_time = 9.0;
expected_video.total_encoded_bytes_target = 1234;
expected_video.total_packet_send_delay = 10.0;
+ expected_video.quality_limitation_reason = "bandwidth";
// |expected_video.content_type| should be undefined.
// |expected_video.qp_sum| should be undefined.
ASSERT_TRUE(report->Get(expected_video.id()));
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index df1d58d..36518c9 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -834,6 +834,7 @@
outbound_stream.total_encoded_bytes_target);
verifier.TestMemberIsNonNegative<double>(
outbound_stream.total_packet_send_delay);
+ verifier.TestMemberIsDefined(outbound_stream.quality_limitation_reason);
// The integration test is not set up to test screen share; don't require
// this to be present.
verifier.MarkMemberTested(outbound_stream.content_type, true);
@@ -844,6 +845,7 @@
outbound_stream.total_encoded_bytes_target);
// TODO(https://crbug.com/webrtc/10635): Implement for audio as well.
verifier.TestMemberIsUndefined(outbound_stream.total_packet_send_delay);
+ verifier.TestMemberIsUndefined(outbound_stream.quality_limitation_reason);
verifier.TestMemberIsUndefined(outbound_stream.content_type);
}
return verifier.ExpectAllMembersSuccessfullyTested();
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index ec2f6e8..8098707 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -53,6 +53,12 @@
const char* const RTCNetworkType::kVpn = "vpn";
const char* const RTCNetworkType::kUnknown = "unknown";
+// https://w3c.github.io/webrtc-stats/#dom-rtcqualitylimitationreason
+const char* const RTCQualityLimitationReason::kNone = "none";
+const char* const RTCQualityLimitationReason::kCpu = "cpu";
+const char* const RTCQualityLimitationReason::kBandwidth = "bandwidth";
+const char* const RTCQualityLimitationReason::kOther = "other";
+
// https://webrtc.org/experiments/rtp-hdrext/video-content-type/
const char* const RTCContentType::kUnspecified = "unspecified";
const char* const RTCContentType::kScreenshare = "screenshare";
@@ -681,6 +687,7 @@
&total_encode_time,
&total_encoded_bytes_target,
&total_packet_send_delay,
+ &quality_limitation_reason,
&content_type)
// clang-format on
@@ -701,6 +708,7 @@
total_encode_time("totalEncodeTime"),
total_encoded_bytes_target("totalEncodedBytesTarget"),
total_packet_send_delay("totalPacketSendDelay"),
+ quality_limitation_reason("qualityLimitationReason"),
content_type("contentType") {}
RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
@@ -716,6 +724,7 @@
total_encode_time(other.total_encode_time),
total_encoded_bytes_target(other.total_encoded_bytes_target),
total_packet_send_delay(other.total_packet_send_delay),
+ quality_limitation_reason(other.quality_limitation_reason),
content_type(other.content_type) {}
RTCOutboundRTPStreamStats::~RTCOutboundRTPStreamStats() {}
diff --git a/video/BUILD.gn b/video/BUILD.gn
index e15b15e..29e1e45 100644
--- a/video/BUILD.gn
+++ b/video/BUILD.gn
@@ -16,6 +16,8 @@
"call_stats.h",
"encoder_rtcp_feedback.cc",
"encoder_rtcp_feedback.h",
+ "quality_limitation_reason_tracker.cc",
+ "quality_limitation_reason_tracker.h",
"quality_threshold.cc",
"quality_threshold.h",
"receive_statistics_proxy.cc",
@@ -520,6 +522,7 @@
"frame_encode_metadata_writer_unittest.cc",
"overuse_frame_detector_unittest.cc",
"picture_id_tests.cc",
+ "quality_limitation_reason_tracker_unittest.cc",
"quality_scaling_tests.cc",
"quality_threshold_unittest.cc",
"receive_statistics_proxy_unittest.cc",
diff --git a/video/quality_limitation_reason_tracker.cc b/video/quality_limitation_reason_tracker.cc
new file mode 100644
index 0000000..c2b2cc4
--- /dev/null
+++ b/video/quality_limitation_reason_tracker.cc
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2019 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 "video/quality_limitation_reason_tracker.h"
+
+#include <utility>
+
+#include "rtc_base/checks.h"
+
+namespace webrtc {
+
+QualityLimitationReasonTracker::QualityLimitationReasonTracker(Clock* clock)
+ : clock_(clock),
+ current_reason_(QualityLimitationReason::kNone),
+ current_reason_updated_timestamp_ms_(clock_->TimeInMilliseconds()),
+ durations_ms_({std::make_pair(QualityLimitationReason::kNone, 0),
+ std::make_pair(QualityLimitationReason::kCpu, 0),
+ std::make_pair(QualityLimitationReason::kBandwidth, 0),
+ std::make_pair(QualityLimitationReason::kOther, 0)}) {}
+
+QualityLimitationReason QualityLimitationReasonTracker::current_reason() const {
+ return current_reason_;
+}
+
+void QualityLimitationReasonTracker::SetReason(QualityLimitationReason reason) {
+ if (reason == current_reason_)
+ return;
+ int64_t now_ms = clock_->TimeInMilliseconds();
+ durations_ms_[current_reason_] +=
+ now_ms - current_reason_updated_timestamp_ms_;
+ current_reason_ = reason;
+ current_reason_updated_timestamp_ms_ = now_ms;
+}
+
+std::map<QualityLimitationReason, int64_t>
+QualityLimitationReasonTracker::DurationsMs() const {
+ std::map<QualityLimitationReason, int64_t> total_durations_ms = durations_ms_;
+ auto it = total_durations_ms.find(current_reason_);
+ RTC_DCHECK(it != total_durations_ms.end());
+ it->second +=
+ clock_->TimeInMilliseconds() - current_reason_updated_timestamp_ms_;
+ return total_durations_ms;
+}
+
+} // namespace webrtc
diff --git a/video/quality_limitation_reason_tracker.h b/video/quality_limitation_reason_tracker.h
new file mode 100644
index 0000000..bd01899
--- /dev/null
+++ b/video/quality_limitation_reason_tracker.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 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.
+ */
+
+#ifndef VIDEO_QUALITY_LIMITATION_REASON_TRACKER_H_
+#define VIDEO_QUALITY_LIMITATION_REASON_TRACKER_H_
+
+#include <map>
+
+#include "common_video/include/quality_limitation_reason.h"
+#include "system_wrappers/include/clock.h"
+
+namespace webrtc {
+
+// A tracker of quality limitation reasons. The quality limitation reason is the
+// primary reason for limiting resolution and/or framerate (such as CPU or
+// bandwidth limitations). The tracker keeps track of the current reason and the
+// duration of time spent in each reason. See qualityLimitationReason[1] and
+// qualityLimitationDurations[2] in the webrtc-stats spec.
+// [1]
+// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationreason
+// [2]
+// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations
+class QualityLimitationReasonTracker {
+ public:
+ // The caller is responsible for making sure |clock| outlives the tracker.
+ explicit QualityLimitationReasonTracker(Clock* clock);
+
+ // The current reason defaults to QualityLimitationReason::kNone.
+ QualityLimitationReason current_reason() const;
+ void SetReason(QualityLimitationReason reason);
+ std::map<QualityLimitationReason, int64_t> DurationsMs() const;
+
+ private:
+ Clock* const clock_;
+ QualityLimitationReason current_reason_;
+ int64_t current_reason_updated_timestamp_ms_;
+ // The total amount of time spent in each reason at time
+ // |current_reason_updated_timestamp_ms_|. To get the total amount duration
+ // so-far, including the time spent in |current_reason_| elapsed since the
+ // last time |current_reason_| was updated, see DurationsMs().
+ std::map<QualityLimitationReason, int64_t> durations_ms_;
+};
+
+} // namespace webrtc
+
+#endif // VIDEO_QUALITY_LIMITATION_REASON_TRACKER_H_
diff --git a/video/quality_limitation_reason_tracker_unittest.cc b/video/quality_limitation_reason_tracker_unittest.cc
new file mode 100644
index 0000000..9756b36
--- /dev/null
+++ b/video/quality_limitation_reason_tracker_unittest.cc
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2019 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 "video/quality_limitation_reason_tracker.h"
+
+#include "common_video/include/quality_limitation_reason.h"
+#include "system_wrappers/include/clock.h"
+#include "test/gtest.h"
+
+namespace webrtc {
+
+class QualityLimitationReasonTrackerTest : public ::testing::Test {
+ public:
+ QualityLimitationReasonTrackerTest()
+ : fake_clock_(1234), tracker_(&fake_clock_) {}
+
+ protected:
+ SimulatedClock fake_clock_;
+ QualityLimitationReasonTracker tracker_;
+};
+
+TEST_F(QualityLimitationReasonTrackerTest, DefaultValues) {
+ EXPECT_EQ(QualityLimitationReason::kNone, tracker_.current_reason());
+ auto durations_ms = tracker_.DurationsMs();
+ EXPECT_EQ(4u, durations_ms.size());
+ EXPECT_EQ(0, durations_ms.find(QualityLimitationReason::kNone)->second);
+ EXPECT_EQ(0, durations_ms.find(QualityLimitationReason::kCpu)->second);
+ EXPECT_EQ(0, durations_ms.find(QualityLimitationReason::kBandwidth)->second);
+ EXPECT_EQ(0, durations_ms.find(QualityLimitationReason::kOther)->second);
+}
+
+TEST_F(QualityLimitationReasonTrackerTest, NoneDurationIncreasesByDefault) {
+ int64_t initial_duration_ms =
+ tracker_.DurationsMs()[QualityLimitationReason::kNone];
+ fake_clock_.AdvanceTimeMilliseconds(9999);
+ EXPECT_EQ(initial_duration_ms + 9999,
+ tracker_.DurationsMs()[QualityLimitationReason::kNone]);
+}
+
+TEST_F(QualityLimitationReasonTrackerTest,
+ RememberDurationAfterSwitchingReason) {
+ tracker_.SetReason(QualityLimitationReason::kCpu);
+ int64_t initial_duration_ms =
+ tracker_.DurationsMs()[QualityLimitationReason::kCpu];
+ fake_clock_.AdvanceTimeMilliseconds(50);
+ tracker_.SetReason(QualityLimitationReason::kOther);
+ fake_clock_.AdvanceTimeMilliseconds(50);
+ EXPECT_EQ(initial_duration_ms + 50,
+ tracker_.DurationsMs()[QualityLimitationReason::kCpu]);
+}
+
+class QualityLimitationReasonTrackerTestWithParamReason
+ : public QualityLimitationReasonTrackerTest,
+ public ::testing::WithParamInterface<QualityLimitationReason> {
+ public:
+ QualityLimitationReasonTrackerTestWithParamReason()
+ : reason_(GetParam()),
+ different_reason_(reason_ != QualityLimitationReason::kCpu
+ ? QualityLimitationReason::kCpu
+ : QualityLimitationReason::kOther) {}
+
+ protected:
+ QualityLimitationReason reason_;
+ QualityLimitationReason different_reason_;
+};
+
+TEST_P(QualityLimitationReasonTrackerTestWithParamReason,
+ DurationIncreasesOverTime) {
+ int64_t initial_duration_ms = tracker_.DurationsMs()[reason_];
+ tracker_.SetReason(reason_);
+ EXPECT_EQ(initial_duration_ms, tracker_.DurationsMs()[reason_]);
+ fake_clock_.AdvanceTimeMilliseconds(4321);
+ EXPECT_EQ(initial_duration_ms + 4321, tracker_.DurationsMs()[reason_]);
+}
+
+TEST_P(QualityLimitationReasonTrackerTestWithParamReason,
+ SwitchBetweenReasonsBackAndForth) {
+ int64_t initial_duration_ms = tracker_.DurationsMs()[reason_];
+ // Spend 100 ms in |different_reason_|.
+ tracker_.SetReason(different_reason_);
+ fake_clock_.AdvanceTimeMilliseconds(100);
+ EXPECT_EQ(initial_duration_ms, tracker_.DurationsMs()[reason_]);
+ // Spend 50 ms in |reason_|.
+ tracker_.SetReason(reason_);
+ fake_clock_.AdvanceTimeMilliseconds(50);
+ EXPECT_EQ(initial_duration_ms + 50, tracker_.DurationsMs()[reason_]);
+ // Spend another 1000 ms in |different_reason_|.
+ tracker_.SetReason(different_reason_);
+ fake_clock_.AdvanceTimeMilliseconds(1000);
+ EXPECT_EQ(initial_duration_ms + 50, tracker_.DurationsMs()[reason_]);
+ // Spend another 100 ms in |reason_|.
+ tracker_.SetReason(reason_);
+ fake_clock_.AdvanceTimeMilliseconds(100);
+ EXPECT_EQ(initial_duration_ms + 150, tracker_.DurationsMs()[reason_]);
+ // Change reason one last time without advancing time.
+ tracker_.SetReason(different_reason_);
+ EXPECT_EQ(initial_duration_ms + 150, tracker_.DurationsMs()[reason_]);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ ,
+ QualityLimitationReasonTrackerTestWithParamReason,
+ ::testing::Values(QualityLimitationReason::kNone, // "/0"
+ QualityLimitationReason::kCpu, // "/1"
+ QualityLimitationReason::kBandwidth, // "/2"
+ QualityLimitationReason::kOther)); // "/3"
+
+} // namespace webrtc
diff --git a/video/send_statistics_proxy.cc b/video/send_statistics_proxy.cc
index 0313316..cf417f5 100644
--- a/video/send_statistics_proxy.cc
+++ b/video/send_statistics_proxy.cc
@@ -137,6 +137,7 @@
encode_time_(kEncodeTimeWeigthFactor),
quality_downscales_(-1),
cpu_downscales_(-1),
+ quality_limitation_reason_tracker_(clock_),
media_byte_rate_tracker_(kBucketSizeMs, kBucketCount),
encoded_frame_rate_tracker_(kBucketSizeMs, kBucketCount),
uma_container_(
@@ -729,6 +730,8 @@
: VideoContentType::SCREENSHARE;
stats_.encode_frame_rate = round(encoded_frame_rate_tracker_.ComputeRate());
stats_.media_bitrate_bps = media_byte_rate_tracker_.ComputeRate() * 8;
+ stats_.quality_limitation_durations_ms =
+ quality_limitation_reason_tracker_.DurationsMs();
return stats_;
}
@@ -1062,6 +1065,26 @@
++stats_.number_of_quality_adapt_changes;
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;
+ 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,
+ // bandwidth is more likely to be a limiting factor than CPU, and more
+ // likely to vary over time, so only when we aren't bandwidth limited do we
+ // want to know about our CPU being the bottleneck.
+ quality_limitation_reason_tracker_.SetReason(
+ QualityLimitationReason::kBandwidth);
+ } else if (is_cpu_limited) {
+ quality_limitation_reason_tracker_.SetReason(QualityLimitationReason::kCpu);
+ } else {
+ quality_limitation_reason_tracker_.SetReason(
+ QualityLimitationReason::kNone);
+ }
+
UpdateAdaptationStats(cpu_counts, quality_counts);
}
@@ -1075,6 +1098,10 @@
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_.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().
}
// TODO(asapersson): Include fps changes.
diff --git a/video/send_statistics_proxy.h b/video/send_statistics_proxy.h
index 51d5b2f..30e8f8b 100644
--- a/video/send_statistics_proxy.h
+++ b/video/send_statistics_proxy.h
@@ -26,6 +26,7 @@
#include "rtc_base/rate_tracker.h"
#include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/clock.h"
+#include "video/quality_limitation_reason_tracker.h"
#include "video/report_block_stats.h"
#include "video/stats_counter.h"
@@ -244,6 +245,8 @@
rtc::ExpFilter encode_time_ RTC_GUARDED_BY(crit_);
int quality_downscales_ RTC_GUARDED_BY(crit_);
int cpu_downscales_ RTC_GUARDED_BY(crit_);
+ QualityLimitationReasonTracker quality_limitation_reason_tracker_
+ RTC_GUARDED_BY(crit_);
rtc::RateTracker media_byte_rate_tracker_ RTC_GUARDED_BY(crit_);
rtc::RateTracker encoded_frame_rate_tracker_ RTC_GUARDED_BY(crit_);
diff --git a/video/send_statistics_proxy_unittest.cc b/video/send_statistics_proxy_unittest.cc
index 58514e5..928bc8b 100644
--- a/video/send_statistics_proxy_unittest.cc
+++ b/video/send_statistics_proxy_unittest.cc
@@ -1067,6 +1067,145 @@
"WebRTC.Video.Screenshare.AdaptChangesPerMinute.Quality"));
}
+TEST_F(SendStatisticsProxyTest,
+ QualityLimitationReasonIsCpuWhenCpuIsResolutionLimited) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ cpu_counts.num_resolution_reductions = 1;
+
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kCpu, cpu_counts,
+ quality_counts);
+
+ EXPECT_EQ(QualityLimitationReason::kCpu,
+ statistics_proxy_->GetStats().quality_limitation_reason);
+}
+
+TEST_F(SendStatisticsProxyTest,
+ QualityLimitationReasonIsCpuWhenCpuIsFramerateLimited) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ cpu_counts.num_framerate_reductions = 1;
+
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kCpu, cpu_counts,
+ quality_counts);
+
+ EXPECT_EQ(QualityLimitationReason::kCpu,
+ statistics_proxy_->GetStats().quality_limitation_reason);
+}
+
+TEST_F(SendStatisticsProxyTest,
+ QualityLimitationReasonIsBandwidthWhenQualityIsResolutionLimited) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ quality_counts.num_resolution_reductions = 1;
+
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kQuality, cpu_counts,
+ quality_counts);
+
+ EXPECT_EQ(QualityLimitationReason::kBandwidth,
+ statistics_proxy_->GetStats().quality_limitation_reason);
+}
+
+TEST_F(SendStatisticsProxyTest,
+ QualityLimitationReasonIsBandwidthWhenQualityIsFramerateLimited) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ quality_counts.num_framerate_reductions = 1;
+
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kQuality, cpu_counts,
+ quality_counts);
+
+ EXPECT_EQ(QualityLimitationReason::kBandwidth,
+ statistics_proxy_->GetStats().quality_limitation_reason);
+}
+
+TEST_F(SendStatisticsProxyTest,
+ QualityLimitationReasonIsBandwidthWhenBothCpuAndQualityIsLimited) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ cpu_counts.num_resolution_reductions = 1;
+ quality_counts.num_resolution_reductions = 1;
+
+ // Even if the last adaptation reason is kCpu, if the counters indicate being
+ // both CPU and quality (=bandwidth) limited, kBandwidth takes precedence.
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kCpu, cpu_counts,
+ quality_counts);
+
+ EXPECT_EQ(QualityLimitationReason::kBandwidth,
+ statistics_proxy_->GetStats().quality_limitation_reason);
+}
+
+TEST_F(SendStatisticsProxyTest, QualityLimitationReasonIsNoneWhenNotLimited) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ // Observe a limitation due to CPU. This makes sure the test doesn't pass
+ // due to "none" being the default value.
+ cpu_counts.num_resolution_reductions = 1;
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kCpu, cpu_counts,
+ quality_counts);
+ // Go back to not being limited.
+ cpu_counts.num_resolution_reductions = 0;
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kNone, cpu_counts,
+ quality_counts);
+
+ EXPECT_EQ(QualityLimitationReason::kNone,
+ statistics_proxy_->GetStats().quality_limitation_reason);
+}
+
+TEST_F(SendStatisticsProxyTest, QualityLimitationDurationIncreasesWithTime) {
+ SendStatisticsProxy::AdaptationSteps cpu_counts;
+ SendStatisticsProxy::AdaptationSteps quality_counts;
+
+ // Not limited for 3000 ms
+ fake_clock_.AdvanceTimeMilliseconds(3000);
+ // CPU limited for 2000 ms
+ cpu_counts.num_resolution_reductions = 1;
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kCpu, cpu_counts,
+ quality_counts);
+ fake_clock_.AdvanceTimeMilliseconds(2000);
+ // Bandwidth limited for 1000 ms
+ cpu_counts.num_resolution_reductions = 0;
+ quality_counts.num_resolution_reductions = 1;
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kQuality, cpu_counts,
+ quality_counts);
+ fake_clock_.AdvanceTimeMilliseconds(1000);
+ // CPU limited for another 2000 ms
+ cpu_counts.num_resolution_reductions = 1;
+ quality_counts.num_resolution_reductions = 0;
+ statistics_proxy_->OnAdaptationChanged(
+ VideoStreamEncoderObserver::AdaptationReason::kCpu, cpu_counts,
+ quality_counts);
+ fake_clock_.AdvanceTimeMilliseconds(2000);
+
+ auto quality_limitation_durations_ms =
+ statistics_proxy_->GetStats().quality_limitation_durations_ms;
+
+ EXPECT_EQ(3000,
+ quality_limitation_durations_ms[QualityLimitationReason::kNone]);
+ EXPECT_EQ(4000,
+ quality_limitation_durations_ms[QualityLimitationReason::kCpu]);
+ EXPECT_EQ(
+ 1000,
+ quality_limitation_durations_ms[QualityLimitationReason::kBandwidth]);
+ EXPECT_EQ(0,
+ quality_limitation_durations_ms[QualityLimitationReason::kOther]);
+}
+
TEST_F(SendStatisticsProxyTest, SwitchContentTypeUpdatesHistograms) {
for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
statistics_proxy_->OnIncomingFrame(kWidth, kHeight);