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}
This commit is contained in:
Henrik Boström 2019-05-28 17:42:38 +02:00 committed by Commit Bot
parent 07fc398ca8
commit ce33b6a4cf
17 changed files with 478 additions and 0 deletions

View File

@ -74,6 +74,14 @@ struct RTCNetworkType {
static const char* const kUnknown; 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/ // https://webrtc.org/experiments/rtp-hdrext/video-content-type/
struct RTCContentType { struct RTCContentType {
static const char* const kUnspecified; static const char* const kUnspecified;
@ -464,6 +472,11 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats {
// TODO(https://crbug.com/webrtc/10635): This is only implemented for video; // TODO(https://crbug.com/webrtc/10635): This is only implemented for video;
// implement it for audio as well. // implement it for audio as well.
RTCStatsMember<double> total_packet_send_delay; 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 // https://henbos.github.io/webrtc-provisional-stats/#dom-rtcoutboundrtpstreamstats-contenttype
RTCStatsMember<std::string> content_type; RTCStatsMember<std::string> content_type;
}; };

View File

@ -28,6 +28,7 @@
#include "api/video/video_stream_encoder_settings.h" #include "api/video/video_stream_encoder_settings.h"
#include "api/video_codecs/video_encoder_config.h" #include "api/video_codecs/video_encoder_config.h"
#include "call/rtp_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/report_block_data.h"
#include "modules/rtp_rtcp/include/rtcp_statistics.h" #include "modules/rtp_rtcp/include/rtcp_statistics.h"
#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h"
@ -92,6 +93,11 @@ class VideoSendStream {
bool cpu_limited_resolution = false; bool cpu_limited_resolution = false;
bool bw_limited_framerate = false; bool bw_limited_framerate = false;
bool cpu_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 // Total number of times resolution as been requested to be changed due to
// CPU/quality adaptation. // CPU/quality adaptation.
int number_of_cpu_adapt_changes = 0; int number_of_cpu_adapt_changes = 0;

View File

@ -28,6 +28,7 @@ rtc_static_library("common_video") {
"include/bitrate_adjuster.h", "include/bitrate_adjuster.h",
"include/i420_buffer_pool.h", "include/i420_buffer_pool.h",
"include/incoming_video_stream.h", "include/incoming_video_stream.h",
"include/quality_limitation_reason.h",
"include/video_frame.h", "include/video_frame.h",
"include/video_frame_buffer.h", "include/video_frame_buffer.h",
"incoming_video_stream.cc", "incoming_video_stream.cc",

View File

@ -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_

View File

@ -31,6 +31,7 @@
#include "api/video/video_source_interface.h" #include "api/video/video_source_interface.h"
#include "api/video/video_timing.h" #include "api/video/video_timing.h"
#include "api/video_codecs/video_encoder_config.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/codec.h"
#include "media/base/delayable.h" #include "media/base/delayable.h"
#include "media/base/media_config.h" #include "media/base/media_config.h"
@ -552,6 +553,12 @@ struct VideoSenderInfo : public MediaSenderInfo {
int nominal_bitrate = 0; int nominal_bitrate = 0;
int adapt_reason = 0; int adapt_reason = 0;
int adapt_changes = 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 avg_encode_ms = 0;
int encode_usage_percent = 0; int encode_usage_percent = 0;
uint32_t frames_encoded = 0; uint32_t frames_encoded = 0;

View File

@ -2267,6 +2267,8 @@ VideoSenderInfo WebRtcVideoChannel::WebRtcVideoSendStream::GetVideoSenderInfo(
if (stats.bw_limited_resolution) if (stats.bw_limited_resolution)
info.adapt_reason |= ADAPTREASON_BANDWIDTH; 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.encoder_implementation_name = stats.encoder_implementation_name;
info.ssrc_groups = ssrc_groups_; info.ssrc_groups = ssrc_groups_;
info.framerate_input = stats.input_frame_rate; info.framerate_input = stats.input_frame_rate;

View File

@ -199,6 +199,20 @@ const char* NetworkAdapterTypeToStatsType(rtc::AdapterType type) {
return nullptr; 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) { double DoubleAudioLevelFromIntAudioLevel(int audio_level) {
RTC_DCHECK_GE(audio_level, 0); RTC_DCHECK_GE(audio_level, 0);
RTC_DCHECK_LE(audio_level, 32767); RTC_DCHECK_LE(audio_level, 32767);
@ -375,6 +389,9 @@ void SetOutboundRTPStreamStatsFromVideoSenderInfo(
outbound_video->total_packet_send_delay = outbound_video->total_packet_send_delay =
static_cast<double>(video_sender_info.total_packet_send_delay_ms) / static_cast<double>(video_sender_info.total_packet_send_delay_ms) /
rtc::kNumMillisecsPerSec; 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 // TODO(https://crbug.com/webrtc/10529): When info's |content_info| is
// optional, support the "unspecified" value. // optional, support the "unspecified" value.
if (video_sender_info.content_type == VideoContentType::SCREENSHARE) if (video_sender_info.content_type == VideoContentType::SCREENSHARE)

View File

@ -1941,6 +1941,8 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
video_media_info.senders[0].total_encode_time_ms = 9000; 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_encoded_bytes_target = 1234;
video_media_info.senders[0].total_packet_send_delay_ms = 10000; 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].qp_sum = absl::nullopt;
video_media_info.senders[0].content_type = VideoContentType::UNSPECIFIED; video_media_info.senders[0].content_type = VideoContentType::UNSPECIFIED;
@ -1986,6 +1988,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
expected_video.total_encode_time = 9.0; expected_video.total_encode_time = 9.0;
expected_video.total_encoded_bytes_target = 1234; expected_video.total_encoded_bytes_target = 1234;
expected_video.total_packet_send_delay = 10.0; expected_video.total_packet_send_delay = 10.0;
expected_video.quality_limitation_reason = "bandwidth";
// |expected_video.content_type| should be undefined. // |expected_video.content_type| should be undefined.
// |expected_video.qp_sum| should be undefined. // |expected_video.qp_sum| should be undefined.
ASSERT_TRUE(report->Get(expected_video.id())); ASSERT_TRUE(report->Get(expected_video.id()));

View File

@ -834,6 +834,7 @@ class RTCStatsReportVerifier {
outbound_stream.total_encoded_bytes_target); outbound_stream.total_encoded_bytes_target);
verifier.TestMemberIsNonNegative<double>( verifier.TestMemberIsNonNegative<double>(
outbound_stream.total_packet_send_delay); 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 // The integration test is not set up to test screen share; don't require
// this to be present. // this to be present.
verifier.MarkMemberTested(outbound_stream.content_type, true); verifier.MarkMemberTested(outbound_stream.content_type, true);
@ -844,6 +845,7 @@ class RTCStatsReportVerifier {
outbound_stream.total_encoded_bytes_target); outbound_stream.total_encoded_bytes_target);
// TODO(https://crbug.com/webrtc/10635): Implement for audio as well. // TODO(https://crbug.com/webrtc/10635): Implement for audio as well.
verifier.TestMemberIsUndefined(outbound_stream.total_packet_send_delay); verifier.TestMemberIsUndefined(outbound_stream.total_packet_send_delay);
verifier.TestMemberIsUndefined(outbound_stream.quality_limitation_reason);
verifier.TestMemberIsUndefined(outbound_stream.content_type); verifier.TestMemberIsUndefined(outbound_stream.content_type);
} }
return verifier.ExpectAllMembersSuccessfullyTested(); return verifier.ExpectAllMembersSuccessfullyTested();

View File

@ -53,6 +53,12 @@ const char* const RTCNetworkType::kWimax = "wimax";
const char* const RTCNetworkType::kVpn = "vpn"; const char* const RTCNetworkType::kVpn = "vpn";
const char* const RTCNetworkType::kUnknown = "unknown"; 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/ // https://webrtc.org/experiments/rtp-hdrext/video-content-type/
const char* const RTCContentType::kUnspecified = "unspecified"; const char* const RTCContentType::kUnspecified = "unspecified";
const char* const RTCContentType::kScreenshare = "screenshare"; const char* const RTCContentType::kScreenshare = "screenshare";
@ -681,6 +687,7 @@ WEBRTC_RTCSTATS_IMPL(
&total_encode_time, &total_encode_time,
&total_encoded_bytes_target, &total_encoded_bytes_target,
&total_packet_send_delay, &total_packet_send_delay,
&quality_limitation_reason,
&content_type) &content_type)
// clang-format on // clang-format on
@ -701,6 +708,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id,
total_encode_time("totalEncodeTime"), total_encode_time("totalEncodeTime"),
total_encoded_bytes_target("totalEncodedBytesTarget"), total_encoded_bytes_target("totalEncodedBytesTarget"),
total_packet_send_delay("totalPacketSendDelay"), total_packet_send_delay("totalPacketSendDelay"),
quality_limitation_reason("qualityLimitationReason"),
content_type("contentType") {} content_type("contentType") {}
RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats( RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
@ -716,6 +724,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
total_encode_time(other.total_encode_time), total_encode_time(other.total_encode_time),
total_encoded_bytes_target(other.total_encoded_bytes_target), total_encoded_bytes_target(other.total_encoded_bytes_target),
total_packet_send_delay(other.total_packet_send_delay), total_packet_send_delay(other.total_packet_send_delay),
quality_limitation_reason(other.quality_limitation_reason),
content_type(other.content_type) {} content_type(other.content_type) {}
RTCOutboundRTPStreamStats::~RTCOutboundRTPStreamStats() {} RTCOutboundRTPStreamStats::~RTCOutboundRTPStreamStats() {}

View File

@ -16,6 +16,8 @@ rtc_static_library("video") {
"call_stats.h", "call_stats.h",
"encoder_rtcp_feedback.cc", "encoder_rtcp_feedback.cc",
"encoder_rtcp_feedback.h", "encoder_rtcp_feedback.h",
"quality_limitation_reason_tracker.cc",
"quality_limitation_reason_tracker.h",
"quality_threshold.cc", "quality_threshold.cc",
"quality_threshold.h", "quality_threshold.h",
"receive_statistics_proxy.cc", "receive_statistics_proxy.cc",
@ -520,6 +522,7 @@ if (rtc_include_tests) {
"frame_encode_metadata_writer_unittest.cc", "frame_encode_metadata_writer_unittest.cc",
"overuse_frame_detector_unittest.cc", "overuse_frame_detector_unittest.cc",
"picture_id_tests.cc", "picture_id_tests.cc",
"quality_limitation_reason_tracker_unittest.cc",
"quality_scaling_tests.cc", "quality_scaling_tests.cc",
"quality_threshold_unittest.cc", "quality_threshold_unittest.cc",
"receive_statistics_proxy_unittest.cc", "receive_statistics_proxy_unittest.cc",

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -137,6 +137,7 @@ SendStatisticsProxy::SendStatisticsProxy(
encode_time_(kEncodeTimeWeigthFactor), encode_time_(kEncodeTimeWeigthFactor),
quality_downscales_(-1), quality_downscales_(-1),
cpu_downscales_(-1), cpu_downscales_(-1),
quality_limitation_reason_tracker_(clock_),
media_byte_rate_tracker_(kBucketSizeMs, kBucketCount), media_byte_rate_tracker_(kBucketSizeMs, kBucketCount),
encoded_frame_rate_tracker_(kBucketSizeMs, kBucketCount), encoded_frame_rate_tracker_(kBucketSizeMs, kBucketCount),
uma_container_( uma_container_(
@ -729,6 +730,8 @@ VideoSendStream::Stats SendStatisticsProxy::GetStats() {
: VideoContentType::SCREENSHARE; : VideoContentType::SCREENSHARE;
stats_.encode_frame_rate = round(encoded_frame_rate_tracker_.ComputeRate()); stats_.encode_frame_rate = round(encoded_frame_rate_tracker_.ComputeRate());
stats_.media_bitrate_bps = media_byte_rate_tracker_.ComputeRate() * 8; stats_.media_bitrate_bps = media_byte_rate_tracker_.ComputeRate() * 8;
stats_.quality_limitation_durations_ms =
quality_limitation_reason_tracker_.DurationsMs();
return stats_; return stats_;
} }
@ -1062,6 +1065,26 @@ void SendStatisticsProxy::OnAdaptationChanged(
++stats_.number_of_quality_adapt_changes; ++stats_.number_of_quality_adapt_changes;
break; 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); UpdateAdaptationStats(cpu_counts, quality_counts);
} }
@ -1075,6 +1098,10 @@ void SendStatisticsProxy::UpdateAdaptationStats(
stats_.cpu_limited_framerate = cpu_counts.num_framerate_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_resolution = quality_counts.num_resolution_reductions > 0;
stats_.bw_limited_framerate = quality_counts.num_framerate_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. // TODO(asapersson): Include fps changes.

View File

@ -26,6 +26,7 @@
#include "rtc_base/rate_tracker.h" #include "rtc_base/rate_tracker.h"
#include "rtc_base/thread_annotations.h" #include "rtc_base/thread_annotations.h"
#include "system_wrappers/include/clock.h" #include "system_wrappers/include/clock.h"
#include "video/quality_limitation_reason_tracker.h"
#include "video/report_block_stats.h" #include "video/report_block_stats.h"
#include "video/stats_counter.h" #include "video/stats_counter.h"
@ -244,6 +245,8 @@ class SendStatisticsProxy : public VideoStreamEncoderObserver,
rtc::ExpFilter encode_time_ RTC_GUARDED_BY(crit_); rtc::ExpFilter encode_time_ RTC_GUARDED_BY(crit_);
int quality_downscales_ RTC_GUARDED_BY(crit_); int quality_downscales_ RTC_GUARDED_BY(crit_);
int cpu_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 media_byte_rate_tracker_ RTC_GUARDED_BY(crit_);
rtc::RateTracker encoded_frame_rate_tracker_ RTC_GUARDED_BY(crit_); rtc::RateTracker encoded_frame_rate_tracker_ RTC_GUARDED_BY(crit_);

View File

@ -1067,6 +1067,145 @@ TEST_F(SendStatisticsProxyTest, AdaptChangesReportedAfterContentSwitch) {
"WebRTC.Video.Screenshare.AdaptChangesPerMinute.Quality")); "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) { TEST_F(SendStatisticsProxyTest, SwitchContentTypeUpdatesHistograms) {
for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i) for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
statistics_proxy_->OnIncomingFrame(kWidth, kHeight); statistics_proxy_->OnIncomingFrame(kWidth, kHeight);