Implement RTCOutboundRtpStreamStats.totalEncodedBytesTarget.

This is a standardized metric:
https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodedbytestarget

We estimate the target frame size in bytes from the current encoder
target bitrate and encoder framerate.

We would expect that the average bytes produced by the encoder would
over time match the average target, which is calculated by polling
getStats() twice and dividing the delta totalEncodedBytesTarget with
the delta framesEncoded. This is meant to make googTargetEncBitrate
obsolete.

Bug: webrtc:10446
Change-Id: Ib10ce236476a2f965582d5c536f419952926d4e6
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/137200
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28022}
This commit is contained in:
Henrik Boström 2019-05-20 15:15:38 +02:00 committed by Commit Bot
parent 04f39242c2
commit 23aff9b737
11 changed files with 82 additions and 0 deletions

View File

@ -458,6 +458,7 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats {
RTCStatsMember<double> target_bitrate;
RTCStatsMember<uint32_t> frames_encoded;
RTCStatsMember<double> total_encode_time;
RTCStatsMember<uint64_t> total_encoded_bytes_target;
// TODO(https://crbug.com/webrtc/10635): This is only implemented for video;
// implement it for audio as well.
RTCStatsMember<double> total_packet_send_delay;

View File

@ -71,6 +71,8 @@ class VideoSendStream {
uint32_t frames_encoded = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodetime
uint64_t total_encode_time_ms = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodedbytestarget
uint64_t total_encoded_bytes_target = 0;
uint32_t frames_dropped_by_capturer = 0;
uint32_t frames_dropped_by_encoder_queue = 0;
uint32_t frames_dropped_by_rate_limiter = 0;

View File

@ -551,6 +551,8 @@ struct VideoSenderInfo : public MediaSenderInfo {
uint32_t frames_encoded = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodetime
uint64_t total_encode_time_ms = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-totalencodedbytestarget
uint64_t total_encoded_bytes_target = 0;
uint64_t total_packet_send_delay_ms = 0;
bool has_entered_low_resolution = false;
absl::optional<uint64_t> qp_sum;

View File

@ -2261,6 +2261,7 @@ VideoSenderInfo WebRtcVideoChannel::WebRtcVideoSendStream::GetVideoSenderInfo(
info.encode_usage_percent = stats.encode_usage_percent;
info.frames_encoded = stats.frames_encoded;
info.total_encode_time_ms = stats.total_encode_time_ms;
info.total_encoded_bytes_target = stats.total_encoded_bytes_target;
info.qp_sum = stats.qp_sum;
info.nominal_bitrate = stats.media_bitrate_bps;

View File

@ -347,6 +347,8 @@ void SetOutboundRTPStreamStatsFromVideoSenderInfo(
outbound_video->total_encode_time =
static_cast<double>(video_sender_info.total_encode_time_ms) /
rtc::kNumMillisecsPerSec;
outbound_video->total_encoded_bytes_target =
video_sender_info.total_encoded_bytes_target;
outbound_video->total_packet_send_delay =
static_cast<double>(video_sender_info.total_packet_send_delay_ms) /
rtc::kNumMillisecsPerSec;

View File

@ -1849,6 +1849,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
video_media_info.senders[0].codec_payload_type = 42;
video_media_info.senders[0].frames_encoded = 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].qp_sum = absl::nullopt;
video_media_info.senders[0].content_type = VideoContentType::UNSPECIFIED;
@ -1891,6 +1892,7 @@ TEST_F(RTCStatsCollectorTest, CollectRTCOutboundRTPStreamStats_Video) {
expected_video.retransmitted_bytes_sent = 60;
expected_video.frames_encoded = 8;
expected_video.total_encode_time = 9.0;
expected_video.total_encoded_bytes_target = 1234;
expected_video.total_packet_send_delay = 10.0;
// |expected_video.content_type| should be undefined.
// |expected_video.qp_sum| should be undefined.

View File

@ -793,6 +793,8 @@ class RTCStatsReportVerifier {
verifier.TestMemberIsDefined(outbound_stream.frames_encoded);
verifier.TestMemberIsNonNegative<double>(
outbound_stream.total_encode_time);
verifier.TestMemberIsNonNegative<uint64_t>(
outbound_stream.total_encoded_bytes_target);
verifier.TestMemberIsNonNegative<double>(
outbound_stream.total_packet_send_delay);
// The integration test is not set up to test screen share; don't require
@ -801,6 +803,8 @@ class RTCStatsReportVerifier {
} else {
verifier.TestMemberIsUndefined(outbound_stream.frames_encoded);
verifier.TestMemberIsUndefined(outbound_stream.total_encode_time);
verifier.TestMemberIsUndefined(
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.content_type);

View File

@ -675,6 +675,7 @@ WEBRTC_RTCSTATS_IMPL(
&target_bitrate,
&frames_encoded,
&total_encode_time,
&total_encoded_bytes_target,
&total_packet_send_delay,
&content_type)
// clang-format on
@ -693,6 +694,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id,
target_bitrate("targetBitrate"),
frames_encoded("framesEncoded"),
total_encode_time("totalEncodeTime"),
total_encoded_bytes_target("totalEncodedBytesTarget"),
total_packet_send_delay("totalPacketSendDelay"),
content_type("contentType") {}
@ -706,6 +708,7 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
target_bitrate(other.target_bitrate),
frames_encoded(other.frames_encoded),
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),
content_type(other.content_type) {}

View File

@ -548,6 +548,7 @@ if (rtc_include_tests) {
"../api/task_queue:default_task_queue_factory",
"../api/test/video:function_video_factory",
"../api/units:data_rate",
"../api/units:timestamp",
"../api/video:builtin_video_bitrate_allocator_factory",
"../api/video:encoded_image",
"../api/video:video_bitrate_allocation",

View File

@ -896,6 +896,18 @@ void SendStatisticsProxy::OnSendEncodedImage(
rtc::CritScope lock(&crit_);
++stats_.frames_encoded;
// The current encode frame rate is based on previously encoded frames.
double encode_frame_rate = encoded_frame_rate_tracker_.ComputeRate();
// We assume that less than 1 FPS is not a trustworthy estimate - perhaps we
// just started encoding for the first time or after a pause. Assuming frame
// rate is at least 1 FPS is conservative to avoid too large increments.
if (encode_frame_rate < 1.0)
encode_frame_rate = 1.0;
double target_frame_size_bytes =
stats_.target_media_bitrate_bps / (8.0 * encode_frame_rate);
// |stats_.target_media_bitrate_bps| is set in
// SendStatisticsProxy::OnSetEncoderTargetRate.
stats_.total_encoded_bytes_target += round(target_frame_size_bytes);
if (codec_info) {
UpdateEncoderFallbackStats(
codec_info, encoded_image._encodedWidth * encoded_image._encodedHeight,

View File

@ -17,6 +17,8 @@
#include <vector>
#include "absl/algorithm/container.h"
#include "api/units/timestamp.h"
#include "rtc_base/fake_clock.h"
#include "system_wrappers/include/metrics.h"
#include "test/field_trial.h"
#include "test/gtest.h"
@ -366,6 +368,56 @@ TEST_F(SendStatisticsProxyTest, OnSendEncodedImageWithoutQpQpSumWontExist) {
EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum);
}
TEST_F(SendStatisticsProxyTest, TotalEncodedBytesTargetFirstFrame) {
const uint32_t kTargetBytesPerSecond = 100000;
statistics_proxy_->OnSetEncoderTargetRate(kTargetBytesPerSecond * 8);
EXPECT_EQ(0u, statistics_proxy_->GetStats().total_encoded_bytes_target);
EncodedImage encoded_image;
statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
// On the first frame we don't know the frame rate yet, calculation yields
// zero. Our estimate assumes at least 1 FPS, so we expect the frame size to
// increment by a full |kTargetBytesPerSecond|.
EXPECT_EQ(kTargetBytesPerSecond,
statistics_proxy_->GetStats().total_encoded_bytes_target);
}
TEST_F(SendStatisticsProxyTest,
TotalEncodedBytesTargetIncrementsBasedOnFrameRate) {
const uint32_t kTargetBytesPerSecond = 100000;
const int kInterframeDelayMs = 100;
// SendStatisticsProxy uses a RateTracker internally. SendStatisticsProxy uses
// |fake_clock_| for testing, but the RateTracker relies on a global clock.
// This test relies on rtc::ScopedFakeClock to synchronize these two clocks.
// TODO(https://crbug.com/webrtc/10640): When the RateTracker uses a Clock
// this test can stop relying on rtc::ScopedFakeClock.
rtc::ScopedFakeClock fake_global_clock;
fake_global_clock.SetTime(Timestamp::ms(fake_clock_.TimeInMilliseconds()));
statistics_proxy_->OnSetEncoderTargetRate(kTargetBytesPerSecond * 8);
EncodedImage encoded_image;
// First frame
statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
uint64_t first_total_encoded_bytes_target =
statistics_proxy_->GetStats().total_encoded_bytes_target;
// Second frame
fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs);
fake_global_clock.SetTime(Timestamp::ms(fake_clock_.TimeInMilliseconds()));
encoded_image.SetTimestamp(encoded_image.Timestamp() +
90 * kInterframeDelayMs);
statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
auto stats = statistics_proxy_->GetStats();
// By the time the second frame arrives, one frame has previously arrived
// during a |kInterframeDelayMs| interval. The estimated encode frame rate at
// the second frame's arrival should be 10 FPS.
uint64_t delta_encoded_bytes_target =
stats.total_encoded_bytes_target - first_total_encoded_bytes_target;
EXPECT_EQ(kTargetBytesPerSecond / 10, delta_encoded_bytes_target);
}
TEST_F(SendStatisticsProxyTest, GetCpuAdaptationStats) {
SendStatisticsProxy::AdaptationSteps cpu_counts;
SendStatisticsProxy::AdaptationSteps quality_counts;