Add qualityLimitationResolutionChanges stat

Implements the stat qualityLimitationResolutionChanges [1].

[1] https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationresolutionchanges

Bug: webrtc:10935
Change-Id: I391f2be5958a96b442e32c40ab7043752f3f71dd
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/150882
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Commit-Queue: Evan Shrubsole <eshr@google.com>
Cr-Commit-Position: refs/heads/master@{#29113}
This commit is contained in:
Evan Shrubsole 2019-09-09 11:26:45 +02:00 committed by Commit Bot
parent a8336d3cf4
commit cc62b16658
16 changed files with 262 additions and 2 deletions

View File

@ -481,6 +481,8 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats {
// qualityLimitationDurations. Requires RTCStatsMember support for
// "record<DOMString, double>", see https://crbug.com/webrtc/10685.
RTCStatsMember<std::string> quality_limitation_reason;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationresolutionchanges
RTCStatsMember<uint32_t> quality_limitation_resolution_changes;
// https://henbos.github.io/webrtc-provisional-stats/#dom-rtcoutboundrtpstreamstats-contenttype
RTCStatsMember<std::string> content_type;
// TODO(hbos): This is only implemented for video; implement it for audio as

View File

@ -223,8 +223,10 @@ rtc_source_set("video_stream_encoder") {
]
deps = [
":video_bitrate_allocation",
":video_bitrate_allocator",
":video_bitrate_allocator_factory",
":video_codec_constants",
":video_frame",
"..:rtp_parameters",
"../:fec_controller_api",

View File

@ -15,6 +15,8 @@
#include <vector>
#include "absl/types/optional.h"
#include "api/video/video_bitrate_allocation.h"
#include "api/video/video_codec_constants.h"
#include "api/video_codecs/video_encoder.h"
#include "api/video_codecs/video_encoder_config.h"
@ -88,6 +90,10 @@ class VideoStreamEncoderObserver : public CpuOveruseMetricsObserver {
virtual void OnSuspendChange(bool is_suspended) = 0;
virtual void OnBitrateAllocationUpdated(
const VideoCodec& codec,
const VideoBitrateAllocation& allocation) {}
// TODO(nisse): VideoStreamEncoder wants to query the stats, which makes this
// not a pure observer. GetInputFrameRate is needed for the cpu adaptation, so
// can be deleted if that responsibility is moved out to a VideoStreamAdaptor

View File

@ -99,6 +99,8 @@ class VideoSendStream {
QualityLimitationReason::kNone;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations
std::map<QualityLimitationReason, int64_t> quality_limitation_durations_ms;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationresolutionchanges
uint32_t quality_limitation_resolution_changes = 0;
// Total number of times resolution as been requested to be changed due to
// CPU/quality adaptation.
int number_of_cpu_adapt_changes = 0;

View File

@ -562,6 +562,8 @@ struct VideoSenderInfo : public MediaSenderInfo {
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations
std::map<webrtc::QualityLimitationReason, int64_t>
quality_limitation_durations_ms;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationresolutionchanges
uint32_t quality_limitation_resolution_changes = 0;
int avg_encode_ms = 0;
int encode_usage_percent = 0;
uint32_t frames_encoded = 0;

View File

@ -2343,6 +2343,8 @@ VideoSenderInfo WebRtcVideoChannel::WebRtcVideoSendStream::GetVideoSenderInfo(
info.quality_limitation_reason = stats.quality_limitation_reason;
info.quality_limitation_durations_ms = stats.quality_limitation_durations_ms;
info.quality_limitation_resolution_changes =
stats.quality_limitation_resolution_changes;
info.encoder_implementation_name = stats.encoder_implementation_name;
info.ssrc_groups = ssrc_groups_;
info.framerate_input = stats.input_frame_rate;

View File

@ -398,6 +398,8 @@ void SetOutboundRTPStreamStatsFromVideoSenderInfo(
outbound_video->quality_limitation_reason =
QualityLimitationReasonToRTCQualityLimitationReason(
video_sender_info.quality_limitation_reason);
outbound_video->quality_limitation_resolution_changes =
video_sender_info.quality_limitation_resolution_changes;
// 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)

View File

@ -1966,6 +1966,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
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].quality_limitation_resolution_changes = 56u;
video_media_info.senders[0].qp_sum = absl::nullopt;
video_media_info.senders[0].content_type = VideoContentType::UNSPECIFIED;
video_media_info.senders[0].encoder_implementation_name = "";
@ -2014,6 +2015,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
expected_video.total_encoded_bytes_target = 1234;
expected_video.total_packet_send_delay = 10.0;
expected_video.quality_limitation_reason = "bandwidth";
expected_video.quality_limitation_resolution_changes = 56u;
// |expected_video.content_type| should be undefined.
// |expected_video.qp_sum| should be undefined.
// |expected_video.encoder_implementation| should be undefined.

View File

@ -869,6 +869,8 @@ class RTCStatsReportVerifier {
verifier.TestMemberIsNonNegative<double>(
outbound_stream.total_packet_send_delay);
verifier.TestMemberIsDefined(outbound_stream.quality_limitation_reason);
verifier.TestMemberIsNonNegative<uint32_t>(
outbound_stream.quality_limitation_resolution_changes);
// 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);
@ -882,6 +884,8 @@ class RTCStatsReportVerifier {
// 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.quality_limitation_resolution_changes);
verifier.TestMemberIsUndefined(outbound_stream.content_type);
// TODO(hbos): Implement for audio as well.
verifier.TestMemberIsUndefined(outbound_stream.encoder_implementation);

View File

@ -694,6 +694,7 @@ WEBRTC_RTCSTATS_IMPL(
&total_encoded_bytes_target,
&total_packet_send_delay,
&quality_limitation_reason,
&quality_limitation_resolution_changes,
&content_type,
&encoder_implementation)
// clang-format on
@ -717,6 +718,8 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id,
total_encoded_bytes_target("totalEncodedBytesTarget"),
total_packet_send_delay("totalPacketSendDelay"),
quality_limitation_reason("qualityLimitationReason"),
quality_limitation_resolution_changes(
"qualityLimitationResolutionChanges"),
content_type("contentType"),
encoder_implementation("encoderImplementation") {}
@ -735,6 +738,8 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
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),
quality_limitation_resolution_changes(
other.quality_limitation_resolution_changes),
content_type(other.content_type),
encoder_implementation(other.encoder_implementation) {}

View File

@ -63,6 +63,7 @@ rtc_static_library("video") {
"../api/video:encoded_image",
"../api/video:video_bitrate_allocation",
"../api/video:video_bitrate_allocator",
"../api/video:video_codec_constants",
"../api/video:video_frame",
"../api/video:video_frame_i420",
"../api/video:video_rtp_headers",
@ -192,6 +193,7 @@ rtc_source_set("video_stream_encoder_impl") {
"../api/video:video_bitrate_allocation",
"../api/video:video_bitrate_allocator",
"../api/video:video_bitrate_allocator_factory",
"../api/video:video_codec_constants",
"../api/video:video_frame",
"../api/video:video_frame_i420",
"../api/video:video_rtp_headers",

View File

@ -21,12 +21,15 @@ 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.
// duration of time spent in each reason. See qualityLimitationReason[1],
// qualityLimitationDurations[2], and qualityLimitationResolutionChanges[3] 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
// [3]
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationresolutionchanges
class QualityLimitationReasonTracker {
public:
// The caller is responsible for making sure |clock| outlives the tracker.

View File

@ -11,11 +11,15 @@
#include "video/send_statistics_proxy.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <utility>
#include "absl/algorithm/container.h"
#include "api/video/video_codec_constants.h"
#include "api/video/video_codec_type.h"
#include "api/video_codecs/video_codec.h"
#include "modules/video_coding/include/video_codec_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
@ -140,6 +144,9 @@ SendStatisticsProxy::SendStatisticsProxy(
quality_limitation_reason_tracker_(clock_),
media_byte_rate_tracker_(kBucketSizeMs, kBucketCount),
encoded_frame_rate_tracker_(kBucketSizeMs, kBucketCount),
last_num_spatial_layers_(0),
last_num_simulcast_streams_(0),
last_spatial_layer_use_{},
uma_container_(
new UmaSamplesContainer(GetUmaPrefix(content_type_), stats_, clock)) {
}
@ -1104,6 +1111,43 @@ void SendStatisticsProxy::UpdateAdaptationStats(
// when it is polled; it is updated in SendStatisticsProxy::GetStats().
}
void SendStatisticsProxy::OnBitrateAllocationUpdated(
const VideoCodec& codec,
const VideoBitrateAllocation& allocation) {
int num_spatial_layers = 0;
for (int i = 0; i < kMaxSpatialLayers; i++) {
if (codec.spatialLayers[i].active) {
num_spatial_layers++;
}
}
int num_simulcast_streams = 0;
for (int i = 0; i < kMaxSimulcastStreams; i++) {
if (codec.simulcastStream[i].active) {
num_simulcast_streams++;
}
}
std::array<bool, kMaxSpatialLayers> spatial_layers;
for (int i = 0; i < kMaxSpatialLayers; i++) {
spatial_layers[i] = (allocation.GetSpatialLayerSum(i) > 0);
}
rtc::CritScope lock(&crit_);
if (spatial_layers != last_spatial_layer_use_) {
// If the number of spatial layers has changed, the resolution change is
// not due to quality limitations, it is because the configuration
// changed.
if (last_num_spatial_layers_ == num_spatial_layers &&
last_num_simulcast_streams_ == num_simulcast_streams) {
++stats_.quality_limitation_resolution_changes;
}
last_spatial_layer_use_ = spatial_layers;
}
last_num_spatial_layers_ = num_spatial_layers;
last_num_simulcast_streams_ = num_simulcast_streams;
}
// TODO(asapersson): Include fps changes.
void SendStatisticsProxy::OnInitialQualityResolutionAdaptDown() {
rtc::CritScope lock(&crit_);

View File

@ -11,12 +11,15 @@
#ifndef VIDEO_SEND_STATISTICS_PROXY_H_
#define VIDEO_SEND_STATISTICS_PROXY_H_
#include <array>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "api/video/video_codec_constants.h"
#include "api/video/video_stream_encoder_observer.h"
#include "api/video_codecs/video_encoder_config.h"
#include "call/video_send_stream.h"
#include "modules/rtp_rtcp/include/report_block_data.h"
#include "modules/video_coding/include/video_codec_interface.h"
@ -70,6 +73,10 @@ class SendStatisticsProxy : public VideoStreamEncoderObserver,
const AdaptationSteps& cpu_counts,
const AdaptationSteps& quality_counts) override;
void OnBitrateAllocationUpdated(
const VideoCodec& codec,
const VideoBitrateAllocation& allocation) override;
void OnMinPixelLimitReached() override;
void OnInitialQualityResolutionAdaptDown() override;
@ -251,6 +258,11 @@ class SendStatisticsProxy : public VideoStreamEncoderObserver,
absl::optional<int64_t> last_outlier_timestamp_ RTC_GUARDED_BY(crit_);
int last_num_spatial_layers_ RTC_GUARDED_BY(crit_);
int last_num_simulcast_streams_ RTC_GUARDED_BY(crit_);
std::array<bool, kMaxSpatialLayers> last_spatial_layer_use_
RTC_GUARDED_BY(crit_);
struct EncoderChangeEvent {
std::string previous_encoder_implementation;
std::string new_encoder_implementation;

View File

@ -18,6 +18,10 @@
#include "absl/algorithm/container.h"
#include "api/units/timestamp.h"
#include "api/video/video_bitrate_allocation.h"
#include "api/video/video_codec_type.h"
#include "api/video_codecs/video_codec.h"
#include "api/video_codecs/video_encoder_config.h"
#include "rtc_base/fake_clock.h"
#include "system_wrappers/include/metrics.h"
#include "test/field_trial.h"
@ -1206,6 +1210,167 @@ TEST_F(SendStatisticsProxyTest, QualityLimitationDurationIncreasesWithTime) {
quality_limitation_durations_ms[QualityLimitationReason::kOther]);
}
TEST_F(SendStatisticsProxyTest, QualityLimitationResolutionChangesDefaultZero) {
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionChangesNotChangesWithOnlyDefaultAllocation) {
VideoCodec codec;
VideoBitrateAllocation allocation;
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionChangesDoesNotIncreaseOnFirstAllocation) {
VideoCodec codec;
codec.simulcastStream[0].active = true;
codec.simulcastStream[1].active = true;
codec.simulcastStream[2].active = true;
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionChangesWhenNewLayerGetsBandwidth) {
VideoCodec codec;
codec.simulcastStream[0].active = true;
codec.simulcastStream[1].active = true;
codec.simulcastStream[2].active = true;
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
allocation.SetBitrate(1, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
1u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionDoesNotChangeWhenLayerSame) {
VideoCodec codec;
codec.simulcastStream[0].active = true;
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
// Layer 0 got more bandwidth, but still only one layer on
allocation.SetBitrate(0, 0, 200);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionChangesWithTogglingLayers) {
VideoCodec codec;
codec.simulcastStream[0].active = true;
codec.simulcastStream[1].active = true;
codec.simulcastStream[2].active = true;
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
allocation.SetBitrate(1, 0, 300);
allocation.SetBitrate(2, 0, 500);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
1u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
// Layer 2 off
allocation.SetBitrate(2, 0, 0);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
2u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
// Layer 2 back on
allocation.SetBitrate(2, 0, 500);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
3u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
allocation.SetBitrate(0, 0, 0);
allocation.SetBitrate(1, 0, 0);
allocation.SetBitrate(2, 0, 0);
// All layers off
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
4u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionDoesNotUpdateOnCodecSimulcastStreamChanges) {
VideoCodec codec;
// 3 layers
codec.simulcastStream[0].active = true;
codec.simulcastStream[1].active = true;
codec.simulcastStream[2].active = true;
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 500);
allocation.SetBitrate(1, 0, 500);
allocation.SetBitrate(2, 0, 500);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
// Down to one layer now, triggered by a config change
codec.numberOfSimulcastStreams = 1;
codec.simulcastStream[1].active = false;
codec.simulcastStream[2].active = false;
allocation.SetBitrate(0, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
// Up to 3 layers again.
codec.numberOfSimulcastStreams = 3;
codec.simulcastStream[1].active = true;
codec.simulcastStream[2].active = true;
allocation.SetBitrate(0, 0, 500);
allocation.SetBitrate(1, 0, 500);
allocation.SetBitrate(2, 0, 500);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest,
QualityLimitationResolutionDoesNotUpdateForSpatialLayerChanges) {
VideoCodec codec;
codec.simulcastStream[0].active = true;
codec.spatialLayers[0].active = true;
codec.spatialLayers[1].active = true;
codec.spatialLayers[2].active = true;
VideoBitrateAllocation allocation;
allocation.SetBitrate(0, 0, 500);
allocation.SetBitrate(1, 0, 500);
allocation.SetBitrate(2, 0, 500);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
// Down to one layer now, triggered by a config change
codec.spatialLayers[1].active = false;
codec.spatialLayers[2].active = false;
allocation.SetBitrate(0, 0, 100);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
// Up to 3 layers again.
codec.spatialLayers[1].active = true;
codec.spatialLayers[2].active = true;
allocation.SetBitrate(0, 0, 500);
allocation.SetBitrate(1, 0, 500);
allocation.SetBitrate(2, 0, 500);
statistics_proxy_->OnBitrateAllocationUpdated(codec, allocation);
EXPECT_EQ(
0u, statistics_proxy_->GetStats().quality_limitation_resolution_changes);
}
TEST_F(SendStatisticsProxyTest, SwitchContentTypeUpdatesHistograms) {
for (int i = 0; i < SendStatisticsProxy::kMinRequiredMetricsSamples; ++i)
statistics_proxy_->OnIncomingFrame(kWidth, kHeight);

View File

@ -11,6 +11,7 @@
#include "video/video_stream_encoder.h"
#include <algorithm>
#include <array>
#include <limits>
#include <numeric>
#include <utility>
@ -20,6 +21,7 @@
#include "api/video/encoded_image.h"
#include "api/video/i420_buffer.h"
#include "api/video/video_bitrate_allocator_factory.h"
#include "api/video/video_codec_constants.h"
#include "api/video_codecs/video_encoder.h"
#include "modules/video_coding/codecs/vp9/svc_rate_allocator.h"
#include "modules/video_coding/include/video_codec_initializer.h"
@ -1169,6 +1171,9 @@ VideoStreamEncoder::UpdateBitrateAllocationAndNotifyObserver(
new_rate_settings.bitrate = adjusted_allocation;
}
encoder_stats_observer_->OnBitrateAllocationUpdated(
send_codec_, new_rate_settings.bitrate);
return new_rate_settings;
}