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:
parent
07fc398ca8
commit
ce33b6a4cf
@ -74,6 +74,14 @@ struct RTCNetworkType {
|
||||
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 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats {
|
||||
// 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;
|
||||
};
|
||||
|
||||
@ -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 @@ class VideoSendStream {
|
||||
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;
|
||||
|
||||
@ -28,6 +28,7 @@ rtc_static_library("common_video") {
|
||||
"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",
|
||||
|
||||
26
common_video/include/quality_limitation_reason.h
Normal file
26
common_video/include/quality_limitation_reason.h
Normal 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_
|
||||
@ -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 @@ struct VideoSenderInfo : public MediaSenderInfo {
|
||||
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;
|
||||
|
||||
@ -2267,6 +2267,8 @@ VideoSenderInfo WebRtcVideoChannel::WebRtcVideoSendStream::GetVideoSenderInfo(
|
||||
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;
|
||||
|
||||
@ -199,6 +199,20 @@ const char* NetworkAdapterTypeToStatsType(rtc::AdapterType type) {
|
||||
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 @@ void SetOutboundRTPStreamStatsFromVideoSenderInfo(
|
||||
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)
|
||||
|
||||
@ -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_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 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
|
||||
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()));
|
||||
|
||||
@ -834,6 +834,7 @@ class RTCStatsReportVerifier {
|
||||
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 @@ class RTCStatsReportVerifier {
|
||||
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();
|
||||
|
||||
@ -53,6 +53,12 @@ const char* const RTCNetworkType::kWimax = "wimax";
|
||||
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 @@ WEBRTC_RTCSTATS_IMPL(
|
||||
&total_encode_time,
|
||||
&total_encoded_bytes_target,
|
||||
&total_packet_send_delay,
|
||||
&quality_limitation_reason,
|
||||
&content_type)
|
||||
// clang-format on
|
||||
|
||||
@ -701,6 +708,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id,
|
||||
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 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
|
||||
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() {}
|
||||
|
||||
@ -16,6 +16,8 @@ rtc_static_library("video") {
|
||||
"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 @@ if (rtc_include_tests) {
|
||||
"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",
|
||||
|
||||
52
video/quality_limitation_reason_tracker.cc
Normal file
52
video/quality_limitation_reason_tracker.cc
Normal 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
|
||||
53
video/quality_limitation_reason_tracker.h
Normal file
53
video/quality_limitation_reason_tracker.h
Normal 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_
|
||||
115
video/quality_limitation_reason_tracker_unittest.cc
Normal file
115
video/quality_limitation_reason_tracker_unittest.cc
Normal 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
|
||||
@ -137,6 +137,7 @@ SendStatisticsProxy::SendStatisticsProxy(
|
||||
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 @@ VideoSendStream::Stats SendStatisticsProxy::GetStats() {
|
||||
: 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 @@ void SendStatisticsProxy::OnAdaptationChanged(
|
||||
++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 @@ void SendStatisticsProxy::UpdateAdaptationStats(
|
||||
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.
|
||||
|
||||
@ -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 @@ class SendStatisticsProxy : public VideoStreamEncoderObserver,
|
||||
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_);
|
||||
|
||||
|
||||
@ -1067,6 +1067,145 @@ TEST_F(SendStatisticsProxyTest, AdaptChangesReportedAfterContentSwitch) {
|
||||
"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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user