Files
platform-external-webrtc/video/send_statistics_proxy.cc
Niels Möller 2377588c82 Add accessor methods for RTP timestamp of EncodedImage.
Intention is to make the member private, but downstream callers
must be updated to use the accessor methods first.

Bug: webrtc:9378
Change-Id: I3495bd8d545b7234fbea10abfd14f082caa420b6
Reviewed-on: https://webrtc-review.googlesource.com/82160
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Sebastian Jansson <srte@webrtc.org>
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Commit-Queue: Niels Moller <nisse@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#24352}
2018-08-21 09:15:51 +00:00

1247 lines
48 KiB
C++

/*
* Copyright (c) 2013 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/send_statistics_proxy.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include <utility>
#include "common_types.h" // NOLINT(build/include)
#include "modules/video_coding/include/video_codec_interface.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/mod_ops.h"
#include "rtc_base/strings/string_builder.h"
#include "system_wrappers/include/field_trial.h"
#include "system_wrappers/include/metrics.h"
namespace webrtc {
namespace {
const float kEncodeTimeWeigthFactor = 0.5f;
const size_t kMaxEncodedFrameMapSize = 150;
const int64_t kMaxEncodedFrameWindowMs = 800;
const uint32_t kMaxEncodedFrameTimestampDiff = 900000; // 10 sec.
const int64_t kBucketSizeMs = 100;
const size_t kBucketCount = 10;
const char kVp8ForcedFallbackEncoderFieldTrial[] =
"WebRTC-VP8-Forced-Fallback-Encoder-v2";
const char kVp8SwCodecName[] = "libvpx";
// Used by histograms. Values of entries should not be changed.
enum HistogramCodecType {
kVideoUnknown = 0,
kVideoVp8 = 1,
kVideoVp9 = 2,
kVideoH264 = 3,
kVideoMax = 64,
};
const char* kRealtimePrefix = "WebRTC.Video.";
const char* kScreenPrefix = "WebRTC.Video.Screenshare.";
const char* GetUmaPrefix(VideoEncoderConfig::ContentType content_type) {
switch (content_type) {
case VideoEncoderConfig::ContentType::kRealtimeVideo:
return kRealtimePrefix;
case VideoEncoderConfig::ContentType::kScreen:
return kScreenPrefix;
}
RTC_NOTREACHED();
return nullptr;
}
HistogramCodecType PayloadNameToHistogramCodecType(
const std::string& payload_name) {
VideoCodecType codecType = PayloadStringToCodecType(payload_name);
switch (codecType) {
case kVideoCodecVP8:
return kVideoVp8;
case kVideoCodecVP9:
return kVideoVp9;
case kVideoCodecH264:
return kVideoH264;
default:
return kVideoUnknown;
}
}
void UpdateCodecTypeHistogram(const std::string& payload_name) {
RTC_HISTOGRAM_ENUMERATION("WebRTC.Video.Encoder.CodecType",
PayloadNameToHistogramCodecType(payload_name),
kVideoMax);
}
bool IsForcedFallbackPossible(const CodecSpecificInfo* codec_info) {
return codec_info->codecType == kVideoCodecVP8 &&
codec_info->codecSpecific.VP8.simulcastIdx == 0 &&
(codec_info->codecSpecific.VP8.temporalIdx == 0 ||
codec_info->codecSpecific.VP8.temporalIdx == kNoTemporalIdx);
}
absl::optional<int> GetFallbackMaxPixels(const std::string& group) {
if (group.empty())
return absl::nullopt;
int min_pixels;
int max_pixels;
int min_bps;
if (sscanf(group.c_str(), "-%d,%d,%d", &min_pixels, &max_pixels, &min_bps) !=
3) {
return absl::optional<int>();
}
if (min_pixels <= 0 || max_pixels <= 0 || max_pixels < min_pixels)
return absl::optional<int>();
return absl::optional<int>(max_pixels);
}
absl::optional<int> GetFallbackMaxPixelsIfFieldTrialEnabled() {
std::string group =
webrtc::field_trial::FindFullName(kVp8ForcedFallbackEncoderFieldTrial);
return (group.find("Enabled") == 0) ? GetFallbackMaxPixels(group.substr(7))
: absl::optional<int>();
}
absl::optional<int> GetFallbackMaxPixelsIfFieldTrialDisabled() {
std::string group =
webrtc::field_trial::FindFullName(kVp8ForcedFallbackEncoderFieldTrial);
return (group.find("Disabled") == 0) ? GetFallbackMaxPixels(group.substr(8))
: absl::optional<int>();
}
} // namespace
const int SendStatisticsProxy::kStatsTimeoutMs = 5000;
SendStatisticsProxy::SendStatisticsProxy(
Clock* clock,
const VideoSendStream::Config& config,
VideoEncoderConfig::ContentType content_type)
: clock_(clock),
payload_name_(config.rtp.payload_name),
rtp_config_(config.rtp),
fallback_max_pixels_(GetFallbackMaxPixelsIfFieldTrialEnabled()),
fallback_max_pixels_disabled_(GetFallbackMaxPixelsIfFieldTrialDisabled()),
content_type_(content_type),
start_ms_(clock->TimeInMilliseconds()),
encode_time_(kEncodeTimeWeigthFactor),
quality_downscales_(-1),
cpu_downscales_(-1),
media_byte_rate_tracker_(kBucketSizeMs, kBucketCount),
encoded_frame_rate_tracker_(kBucketSizeMs, kBucketCount),
uma_container_(
new UmaSamplesContainer(GetUmaPrefix(content_type_), stats_, clock)) {
}
SendStatisticsProxy::~SendStatisticsProxy() {
rtc::CritScope lock(&crit_);
uma_container_->UpdateHistograms(rtp_config_, stats_);
int64_t elapsed_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000;
RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.SendStreamLifetimeInSeconds",
elapsed_sec);
if (elapsed_sec >= metrics::kMinRunTimeInSeconds)
UpdateCodecTypeHistogram(payload_name_);
}
SendStatisticsProxy::UmaSamplesContainer::UmaSamplesContainer(
const char* prefix,
const VideoSendStream::Stats& stats,
Clock* const clock)
: uma_prefix_(prefix),
clock_(clock),
input_frame_rate_tracker_(100, 10u),
input_fps_counter_(clock, nullptr, true),
sent_fps_counter_(clock, nullptr, true),
total_byte_counter_(clock, nullptr, true),
media_byte_counter_(clock, nullptr, true),
rtx_byte_counter_(clock, nullptr, true),
padding_byte_counter_(clock, nullptr, true),
retransmit_byte_counter_(clock, nullptr, true),
fec_byte_counter_(clock, nullptr, true),
first_rtcp_stats_time_ms_(-1),
first_rtp_stats_time_ms_(-1),
start_stats_(stats),
num_streams_(0),
num_pixels_highest_stream_(0) {
InitializeBitrateCounters(stats);
static_assert(
kMaxEncodedFrameTimestampDiff < std::numeric_limits<uint32_t>::max() / 2,
"has to be smaller than half range");
}
SendStatisticsProxy::UmaSamplesContainer::~UmaSamplesContainer() {}
void SendStatisticsProxy::UmaSamplesContainer::InitializeBitrateCounters(
const VideoSendStream::Stats& stats) {
for (const auto& it : stats.substreams) {
uint32_t ssrc = it.first;
total_byte_counter_.SetLast(it.second.rtp_stats.transmitted.TotalBytes(),
ssrc);
padding_byte_counter_.SetLast(it.second.rtp_stats.transmitted.padding_bytes,
ssrc);
retransmit_byte_counter_.SetLast(
it.second.rtp_stats.retransmitted.TotalBytes(), ssrc);
fec_byte_counter_.SetLast(it.second.rtp_stats.fec.TotalBytes(), ssrc);
if (it.second.is_rtx) {
rtx_byte_counter_.SetLast(it.second.rtp_stats.transmitted.TotalBytes(),
ssrc);
} else {
media_byte_counter_.SetLast(it.second.rtp_stats.MediaPayloadBytes(),
ssrc);
}
}
}
void SendStatisticsProxy::UmaSamplesContainer::RemoveOld(
int64_t now_ms,
bool* is_limited_in_resolution) {
while (!encoded_frames_.empty()) {
auto it = encoded_frames_.begin();
if (now_ms - it->second.send_ms < kMaxEncodedFrameWindowMs)
break;
// Use max per timestamp.
sent_width_counter_.Add(it->second.max_width);
sent_height_counter_.Add(it->second.max_height);
// Check number of encoded streams per timestamp.
if (num_streams_ > it->second.max_simulcast_idx) {
*is_limited_in_resolution = false;
if (num_streams_ > 1) {
int disabled_streams =
static_cast<int>(num_streams_ - 1 - it->second.max_simulcast_idx);
// Can be limited in resolution or framerate.
uint32_t pixels = it->second.max_width * it->second.max_height;
bool bw_limited_resolution =
disabled_streams > 0 && pixels < num_pixels_highest_stream_;
bw_limited_frame_counter_.Add(bw_limited_resolution);
if (bw_limited_resolution) {
bw_resolutions_disabled_counter_.Add(disabled_streams);
*is_limited_in_resolution = true;
}
}
}
encoded_frames_.erase(it);
}
}
bool SendStatisticsProxy::UmaSamplesContainer::InsertEncodedFrame(
const EncodedImage& encoded_frame,
size_t simulcast_idx,
bool* is_limited_in_resolution) {
int64_t now_ms = clock_->TimeInMilliseconds();
RemoveOld(now_ms, is_limited_in_resolution);
if (encoded_frames_.size() > kMaxEncodedFrameMapSize) {
encoded_frames_.clear();
}
// Check for jump in timestamp.
if (!encoded_frames_.empty()) {
uint32_t oldest_timestamp = encoded_frames_.begin()->first;
if (ForwardDiff(oldest_timestamp, encoded_frame.Timestamp()) >
kMaxEncodedFrameTimestampDiff) {
// Gap detected, clear frames to have a sequence where newest timestamp
// is not too far away from oldest in order to distinguish old and new.
encoded_frames_.clear();
}
}
auto it = encoded_frames_.find(encoded_frame._timeStamp);
if (it == encoded_frames_.end()) {
// First frame with this timestamp.
encoded_frames_.insert(
std::make_pair(encoded_frame.Timestamp(),
Frame(now_ms, encoded_frame._encodedWidth,
encoded_frame._encodedHeight, simulcast_idx)));
sent_fps_counter_.Add(1);
return true;
}
it->second.max_width =
std::max(it->second.max_width, encoded_frame._encodedWidth);
it->second.max_height =
std::max(it->second.max_height, encoded_frame._encodedHeight);
it->second.max_simulcast_idx =
std::max(it->second.max_simulcast_idx, simulcast_idx);
return false;
}
void SendStatisticsProxy::UmaSamplesContainer::UpdateHistograms(
const RtpConfig& rtp_config,
const VideoSendStream::Stats& current_stats) {
RTC_DCHECK(uma_prefix_ == kRealtimePrefix || uma_prefix_ == kScreenPrefix);
const int kIndex = uma_prefix_ == kScreenPrefix ? 1 : 0;
const int kMinRequiredPeriodicSamples = 6;
char log_stream_buf[8 * 1024];
rtc::SimpleStringBuilder log_stream(log_stream_buf);
int in_width = input_width_counter_.Avg(kMinRequiredMetricsSamples);
int in_height = input_height_counter_.Avg(kMinRequiredMetricsSamples);
if (in_width != -1) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "InputWidthInPixels",
in_width);
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "InputHeightInPixels",
in_height);
log_stream << uma_prefix_ << "InputWidthInPixels " << in_width << "\n"
<< uma_prefix_ << "InputHeightInPixels " << in_height << "\n";
}
AggregatedStats in_fps = input_fps_counter_.GetStats();
if (in_fps.num_samples >= kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_100(kIndex, uma_prefix_ + "InputFramesPerSecond",
in_fps.average);
log_stream << uma_prefix_ << "InputFramesPerSecond " << in_fps.ToString()
<< "\n";
}
int sent_width = sent_width_counter_.Avg(kMinRequiredMetricsSamples);
int sent_height = sent_height_counter_.Avg(kMinRequiredMetricsSamples);
if (sent_width != -1) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "SentWidthInPixels",
sent_width);
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "SentHeightInPixels",
sent_height);
log_stream << uma_prefix_ << "SentWidthInPixels " << sent_width << "\n"
<< uma_prefix_ << "SentHeightInPixels " << sent_height << "\n";
}
AggregatedStats sent_fps = sent_fps_counter_.GetStats();
if (sent_fps.num_samples >= kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_100(kIndex, uma_prefix_ + "SentFramesPerSecond",
sent_fps.average);
log_stream << uma_prefix_ << "SentFramesPerSecond " << sent_fps.ToString()
<< "\n";
}
if (in_fps.num_samples > kMinRequiredPeriodicSamples &&
sent_fps.num_samples >= kMinRequiredPeriodicSamples) {
int in_fps_avg = in_fps.average;
if (in_fps_avg > 0) {
int sent_fps_avg = sent_fps.average;
int sent_to_in_fps_ratio_percent =
(100 * sent_fps_avg + in_fps_avg / 2) / in_fps_avg;
// If reported period is small, it may happen that sent_fps is larger than
// input_fps briefly on average. This should be treated as 100% sent to
// input ratio.
if (sent_to_in_fps_ratio_percent > 100)
sent_to_in_fps_ratio_percent = 100;
RTC_HISTOGRAMS_PERCENTAGE(kIndex,
uma_prefix_ + "SentToInputFpsRatioPercent",
sent_to_in_fps_ratio_percent);
log_stream << uma_prefix_ << "SentToInputFpsRatioPercent "
<< sent_to_in_fps_ratio_percent << "\n";
}
}
int encode_ms = encode_time_counter_.Avg(kMinRequiredMetricsSamples);
if (encode_ms != -1) {
RTC_HISTOGRAMS_COUNTS_1000(kIndex, uma_prefix_ + "EncodeTimeInMs",
encode_ms);
log_stream << uma_prefix_ << "EncodeTimeInMs " << encode_ms << "\n";
}
int key_frames_permille =
key_frame_counter_.Permille(kMinRequiredMetricsSamples);
if (key_frames_permille != -1) {
RTC_HISTOGRAMS_COUNTS_1000(kIndex, uma_prefix_ + "KeyFramesSentInPermille",
key_frames_permille);
log_stream << uma_prefix_ << "KeyFramesSentInPermille "
<< key_frames_permille << "\n";
}
int quality_limited =
quality_limited_frame_counter_.Percent(kMinRequiredMetricsSamples);
if (quality_limited != -1) {
RTC_HISTOGRAMS_PERCENTAGE(kIndex,
uma_prefix_ + "QualityLimitedResolutionInPercent",
quality_limited);
log_stream << uma_prefix_ << "QualityLimitedResolutionInPercent "
<< quality_limited << "\n";
}
int downscales = quality_downscales_counter_.Avg(kMinRequiredMetricsSamples);
if (downscales != -1) {
RTC_HISTOGRAMS_ENUMERATION(
kIndex, uma_prefix_ + "QualityLimitedResolutionDownscales", downscales,
20);
}
int cpu_limited =
cpu_limited_frame_counter_.Percent(kMinRequiredMetricsSamples);
if (cpu_limited != -1) {
RTC_HISTOGRAMS_PERCENTAGE(
kIndex, uma_prefix_ + "CpuLimitedResolutionInPercent", cpu_limited);
}
int bw_limited =
bw_limited_frame_counter_.Percent(kMinRequiredMetricsSamples);
if (bw_limited != -1) {
RTC_HISTOGRAMS_PERCENTAGE(
kIndex, uma_prefix_ + "BandwidthLimitedResolutionInPercent",
bw_limited);
}
int num_disabled =
bw_resolutions_disabled_counter_.Avg(kMinRequiredMetricsSamples);
if (num_disabled != -1) {
RTC_HISTOGRAMS_ENUMERATION(
kIndex, uma_prefix_ + "BandwidthLimitedResolutionsDisabled",
num_disabled, 10);
}
int delay_ms = delay_counter_.Avg(kMinRequiredMetricsSamples);
if (delay_ms != -1)
RTC_HISTOGRAMS_COUNTS_100000(kIndex, uma_prefix_ + "SendSideDelayInMs",
delay_ms);
int max_delay_ms = max_delay_counter_.Avg(kMinRequiredMetricsSamples);
if (max_delay_ms != -1) {
RTC_HISTOGRAMS_COUNTS_100000(kIndex, uma_prefix_ + "SendSideDelayMaxInMs",
max_delay_ms);
}
for (const auto& it : qp_counters_) {
int qp_vp8 = it.second.vp8.Avg(kMinRequiredMetricsSamples);
if (qp_vp8 != -1) {
int spatial_idx = it.first;
if (spatial_idx == -1) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.Vp8",
qp_vp8);
} else if (spatial_idx == 0) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.Vp8.S0",
qp_vp8);
} else if (spatial_idx == 1) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.Vp8.S1",
qp_vp8);
} else if (spatial_idx == 2) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.Vp8.S2",
qp_vp8);
} else {
RTC_LOG(LS_WARNING)
<< "QP stats not recorded for VP8 spatial idx " << spatial_idx;
}
}
int qp_vp9 = it.second.vp9.Avg(kMinRequiredMetricsSamples);
if (qp_vp9 != -1) {
int spatial_idx = it.first;
if (spatial_idx == -1) {
RTC_HISTOGRAMS_COUNTS_500(kIndex, uma_prefix_ + "Encoded.Qp.Vp9",
qp_vp9);
} else if (spatial_idx == 0) {
RTC_HISTOGRAMS_COUNTS_500(kIndex, uma_prefix_ + "Encoded.Qp.Vp9.S0",
qp_vp9);
} else if (spatial_idx == 1) {
RTC_HISTOGRAMS_COUNTS_500(kIndex, uma_prefix_ + "Encoded.Qp.Vp9.S1",
qp_vp9);
} else if (spatial_idx == 2) {
RTC_HISTOGRAMS_COUNTS_500(kIndex, uma_prefix_ + "Encoded.Qp.Vp9.S2",
qp_vp9);
} else {
RTC_LOG(LS_WARNING)
<< "QP stats not recorded for VP9 spatial layer " << spatial_idx;
}
}
int qp_h264 = it.second.h264.Avg(kMinRequiredMetricsSamples);
if (qp_h264 != -1) {
int spatial_idx = it.first;
if (spatial_idx == -1) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.H264",
qp_h264);
} else if (spatial_idx == 0) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.H264.S0",
qp_h264);
} else if (spatial_idx == 1) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.H264.S1",
qp_h264);
} else if (spatial_idx == 2) {
RTC_HISTOGRAMS_COUNTS_200(kIndex, uma_prefix_ + "Encoded.Qp.H264.S2",
qp_h264);
} else {
RTC_LOG(LS_WARNING)
<< "QP stats not recorded for H264 spatial idx " << spatial_idx;
}
}
}
if (first_rtp_stats_time_ms_ != -1) {
quality_adapt_timer_.Stop(clock_->TimeInMilliseconds());
int64_t elapsed_sec = quality_adapt_timer_.total_ms / 1000;
if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
int quality_changes = current_stats.number_of_quality_adapt_changes -
start_stats_.number_of_quality_adapt_changes;
// Only base stats on changes during a call, discard initial changes.
int initial_changes =
initial_quality_changes_.down + initial_quality_changes_.up;
if (initial_changes <= quality_changes)
quality_changes -= initial_changes;
RTC_HISTOGRAMS_COUNTS_100(kIndex,
uma_prefix_ + "AdaptChangesPerMinute.Quality",
quality_changes * 60 / elapsed_sec);
}
cpu_adapt_timer_.Stop(clock_->TimeInMilliseconds());
elapsed_sec = cpu_adapt_timer_.total_ms / 1000;
if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
int cpu_changes = current_stats.number_of_cpu_adapt_changes -
start_stats_.number_of_cpu_adapt_changes;
RTC_HISTOGRAMS_COUNTS_100(kIndex,
uma_prefix_ + "AdaptChangesPerMinute.Cpu",
cpu_changes * 60 / elapsed_sec);
}
}
if (first_rtcp_stats_time_ms_ != -1) {
int64_t elapsed_sec =
(clock_->TimeInMilliseconds() - first_rtcp_stats_time_ms_) / 1000;
if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
int fraction_lost = report_block_stats_.FractionLostInPercent();
if (fraction_lost != -1) {
RTC_HISTOGRAMS_PERCENTAGE(
kIndex, uma_prefix_ + "SentPacketsLostInPercent", fraction_lost);
log_stream << uma_prefix_ << "SentPacketsLostInPercent "
<< fraction_lost;
}
// The RTCP packet type counters, delivered via the
// RtcpPacketTypeCounterObserver interface, are aggregates over the entire
// life of the send stream and are not reset when switching content type.
// For the purpose of these statistics though, we want new counts when
// switching since we switch histogram name. On every reset of the
// UmaSamplesContainer, we save the initial state of the counters, so that
// we can calculate the delta here and aggregate over all ssrcs.
RtcpPacketTypeCounter counters;
for (uint32_t ssrc : rtp_config.ssrcs) {
auto kv = current_stats.substreams.find(ssrc);
if (kv == current_stats.substreams.end())
continue;
RtcpPacketTypeCounter stream_counters =
kv->second.rtcp_packet_type_counts;
kv = start_stats_.substreams.find(ssrc);
if (kv != start_stats_.substreams.end())
stream_counters.Subtract(kv->second.rtcp_packet_type_counts);
counters.Add(stream_counters);
}
RTC_HISTOGRAMS_COUNTS_10000(kIndex,
uma_prefix_ + "NackPacketsReceivedPerMinute",
counters.nack_packets * 60 / elapsed_sec);
RTC_HISTOGRAMS_COUNTS_10000(kIndex,
uma_prefix_ + "FirPacketsReceivedPerMinute",
counters.fir_packets * 60 / elapsed_sec);
RTC_HISTOGRAMS_COUNTS_10000(kIndex,
uma_prefix_ + "PliPacketsReceivedPerMinute",
counters.pli_packets * 60 / elapsed_sec);
if (counters.nack_requests > 0) {
RTC_HISTOGRAMS_PERCENTAGE(
kIndex, uma_prefix_ + "UniqueNackRequestsReceivedInPercent",
counters.UniqueNackRequestsInPercent());
}
}
}
if (first_rtp_stats_time_ms_ != -1) {
int64_t elapsed_sec =
(clock_->TimeInMilliseconds() - first_rtp_stats_time_ms_) / 1000;
if (elapsed_sec >= metrics::kMinRunTimeInSeconds) {
RTC_HISTOGRAMS_COUNTS_100(kIndex, uma_prefix_ + "NumberOfPauseEvents",
target_rate_updates_.pause_resume_events);
log_stream << uma_prefix_ << "NumberOfPauseEvents "
<< target_rate_updates_.pause_resume_events << "\n";
int paused_time_percent =
paused_time_counter_.Percent(metrics::kMinRunTimeInSeconds * 1000);
if (paused_time_percent != -1) {
RTC_HISTOGRAMS_PERCENTAGE(kIndex, uma_prefix_ + "PausedTimeInPercent",
paused_time_percent);
log_stream << uma_prefix_ << "PausedTimeInPercent "
<< paused_time_percent << "\n";
}
}
}
if (fallback_info_.is_possible) {
// Double interval since there is some time before fallback may occur.
const int kMinRunTimeMs = 2 * metrics::kMinRunTimeInSeconds * 1000;
int64_t elapsed_ms = fallback_info_.elapsed_ms;
int fallback_time_percent = fallback_active_counter_.Percent(kMinRunTimeMs);
if (fallback_time_percent != -1 && elapsed_ms >= kMinRunTimeMs) {
RTC_HISTOGRAMS_PERCENTAGE(
kIndex, uma_prefix_ + "Encoder.ForcedSwFallbackTimeInPercent.Vp8",
fallback_time_percent);
RTC_HISTOGRAMS_COUNTS_100(
kIndex, uma_prefix_ + "Encoder.ForcedSwFallbackChangesPerMinute.Vp8",
fallback_info_.on_off_events * 60 / (elapsed_ms / 1000));
}
}
AggregatedStats total_bytes_per_sec = total_byte_counter_.GetStats();
if (total_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "BitrateSentInKbps",
total_bytes_per_sec.average * 8 / 1000);
log_stream << uma_prefix_ << "BitrateSentInBps "
<< total_bytes_per_sec.ToStringWithMultiplier(8) << "\n";
}
AggregatedStats media_bytes_per_sec = media_byte_counter_.GetStats();
if (media_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "MediaBitrateSentInKbps",
media_bytes_per_sec.average * 8 / 1000);
log_stream << uma_prefix_ << "MediaBitrateSentInBps "
<< media_bytes_per_sec.ToStringWithMultiplier(8) << "\n";
}
AggregatedStats padding_bytes_per_sec = padding_byte_counter_.GetStats();
if (padding_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex,
uma_prefix_ + "PaddingBitrateSentInKbps",
padding_bytes_per_sec.average * 8 / 1000);
log_stream << uma_prefix_ << "PaddingBitrateSentInBps "
<< padding_bytes_per_sec.ToStringWithMultiplier(8) << "\n";
}
AggregatedStats retransmit_bytes_per_sec =
retransmit_byte_counter_.GetStats();
if (retransmit_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex,
uma_prefix_ + "RetransmittedBitrateSentInKbps",
retransmit_bytes_per_sec.average * 8 / 1000);
log_stream << uma_prefix_ << "RetransmittedBitrateSentInBps "
<< retransmit_bytes_per_sec.ToStringWithMultiplier(8) << "\n";
}
if (!rtp_config.rtx.ssrcs.empty()) {
AggregatedStats rtx_bytes_per_sec = rtx_byte_counter_.GetStats();
int rtx_bytes_per_sec_avg = -1;
if (rtx_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
rtx_bytes_per_sec_avg = rtx_bytes_per_sec.average;
log_stream << uma_prefix_ << "RtxBitrateSentInBps "
<< rtx_bytes_per_sec.ToStringWithMultiplier(8) << "\n";
} else if (total_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
rtx_bytes_per_sec_avg = 0; // RTX enabled but no RTX data sent, record 0.
}
if (rtx_bytes_per_sec_avg != -1) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "RtxBitrateSentInKbps",
rtx_bytes_per_sec_avg * 8 / 1000);
}
}
if (rtp_config.flexfec.payload_type != -1 ||
rtp_config.ulpfec.red_payload_type != -1) {
AggregatedStats fec_bytes_per_sec = fec_byte_counter_.GetStats();
if (fec_bytes_per_sec.num_samples > kMinRequiredPeriodicSamples) {
RTC_HISTOGRAMS_COUNTS_10000(kIndex, uma_prefix_ + "FecBitrateSentInKbps",
fec_bytes_per_sec.average * 8 / 1000);
log_stream << uma_prefix_ << "FecBitrateSentInBps "
<< fec_bytes_per_sec.ToStringWithMultiplier(8) << "\n";
}
}
log_stream << "Frames encoded " << current_stats.frames_encoded << "\n"
<< uma_prefix_ << "DroppedFrames.Capturer "
<< current_stats.frames_dropped_by_capturer << "\n";
RTC_HISTOGRAMS_COUNTS_1000(kIndex, uma_prefix_ + "DroppedFrames.Capturer",
current_stats.frames_dropped_by_capturer);
log_stream << uma_prefix_ << "DroppedFrames.EncoderQueue "
<< current_stats.frames_dropped_by_encoder_queue << "\n";
RTC_HISTOGRAMS_COUNTS_1000(kIndex, uma_prefix_ + "DroppedFrames.EncoderQueue",
current_stats.frames_dropped_by_encoder_queue);
log_stream << uma_prefix_ << "DroppedFrames.Encoder "
<< current_stats.frames_dropped_by_encoder << "\n";
RTC_HISTOGRAMS_COUNTS_1000(kIndex, uma_prefix_ + "DroppedFrames.Encoder",
current_stats.frames_dropped_by_encoder);
log_stream << uma_prefix_ << "DroppedFrames.Ratelimiter "
<< current_stats.frames_dropped_by_rate_limiter;
RTC_HISTOGRAMS_COUNTS_1000(kIndex, uma_prefix_ + "DroppedFrames.Ratelimiter",
current_stats.frames_dropped_by_rate_limiter);
RTC_LOG(LS_INFO) << log_stream.str();
}
void SendStatisticsProxy::OnEncoderReconfigured(
const VideoEncoderConfig& config,
const std::vector<VideoStream>& streams) {
rtc::CritScope lock(&crit_);
if (content_type_ != config.content_type) {
uma_container_->UpdateHistograms(rtp_config_, stats_);
uma_container_.reset(new UmaSamplesContainer(
GetUmaPrefix(config.content_type), stats_, clock_));
content_type_ = config.content_type;
}
uma_container_->encoded_frames_.clear();
uma_container_->num_streams_ = streams.size();
uma_container_->num_pixels_highest_stream_ =
streams.empty() ? 0 : (streams.back().width * streams.back().height);
}
void SendStatisticsProxy::OnEncodedFrameTimeMeasured(int encode_time_ms,
int encode_usage_percent) {
rtc::CritScope lock(&crit_);
uma_container_->encode_time_counter_.Add(encode_time_ms);
encode_time_.Apply(1.0f, encode_time_ms);
stats_.avg_encode_time_ms = round(encode_time_.filtered());
stats_.encode_usage_percent = encode_usage_percent;
}
void SendStatisticsProxy::OnSuspendChange(bool is_suspended) {
int64_t now_ms = clock_->TimeInMilliseconds();
rtc::CritScope lock(&crit_);
stats_.suspended = is_suspended;
if (is_suspended) {
// Pause framerate (add min pause time since there may be frames/packets
// that are not yet sent).
const int64_t kMinMs = 500;
uma_container_->input_fps_counter_.ProcessAndPauseForDuration(kMinMs);
uma_container_->sent_fps_counter_.ProcessAndPauseForDuration(kMinMs);
// Pause bitrate stats.
uma_container_->total_byte_counter_.ProcessAndPauseForDuration(kMinMs);
uma_container_->media_byte_counter_.ProcessAndPauseForDuration(kMinMs);
uma_container_->rtx_byte_counter_.ProcessAndPauseForDuration(kMinMs);
uma_container_->padding_byte_counter_.ProcessAndPauseForDuration(kMinMs);
uma_container_->retransmit_byte_counter_.ProcessAndPauseForDuration(kMinMs);
uma_container_->fec_byte_counter_.ProcessAndPauseForDuration(kMinMs);
// Stop adaptation stats.
uma_container_->cpu_adapt_timer_.Stop(now_ms);
uma_container_->quality_adapt_timer_.Stop(now_ms);
} else {
// Start adaptation stats if scaling is enabled.
if (cpu_downscales_ >= 0)
uma_container_->cpu_adapt_timer_.Start(now_ms);
if (quality_downscales_ >= 0)
uma_container_->quality_adapt_timer_.Start(now_ms);
// Stop pause explicitly for stats that may be zero/not updated for some
// time.
uma_container_->rtx_byte_counter_.ProcessAndStopPause();
uma_container_->padding_byte_counter_.ProcessAndStopPause();
uma_container_->retransmit_byte_counter_.ProcessAndStopPause();
uma_container_->fec_byte_counter_.ProcessAndStopPause();
}
}
VideoSendStream::Stats SendStatisticsProxy::GetStats() {
rtc::CritScope lock(&crit_);
PurgeOldStats();
stats_.input_frame_rate =
round(uma_container_->input_frame_rate_tracker_.ComputeRate());
stats_.content_type =
content_type_ == VideoEncoderConfig::ContentType::kRealtimeVideo
? VideoContentType::UNSPECIFIED
: VideoContentType::SCREENSHARE;
stats_.encode_frame_rate = round(encoded_frame_rate_tracker_.ComputeRate());
stats_.media_bitrate_bps = media_byte_rate_tracker_.ComputeRate() * 8;
return stats_;
}
void SendStatisticsProxy::PurgeOldStats() {
int64_t old_stats_ms = clock_->TimeInMilliseconds() - kStatsTimeoutMs;
for (std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
stats_.substreams.begin();
it != stats_.substreams.end(); ++it) {
uint32_t ssrc = it->first;
if (update_times_[ssrc].resolution_update_ms <= old_stats_ms) {
it->second.width = 0;
it->second.height = 0;
}
}
}
VideoSendStream::StreamStats* SendStatisticsProxy::GetStatsEntry(
uint32_t ssrc) {
std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
stats_.substreams.find(ssrc);
if (it != stats_.substreams.end())
return &it->second;
bool is_media = std::find(rtp_config_.ssrcs.begin(), rtp_config_.ssrcs.end(),
ssrc) != rtp_config_.ssrcs.end();
bool is_flexfec = rtp_config_.flexfec.payload_type != -1 &&
ssrc == rtp_config_.flexfec.ssrc;
bool is_rtx =
std::find(rtp_config_.rtx.ssrcs.begin(), rtp_config_.rtx.ssrcs.end(),
ssrc) != rtp_config_.rtx.ssrcs.end();
if (!is_media && !is_flexfec && !is_rtx)
return nullptr;
// Insert new entry and return ptr.
VideoSendStream::StreamStats* entry = &stats_.substreams[ssrc];
entry->is_rtx = is_rtx;
entry->is_flexfec = is_flexfec;
return entry;
}
void SendStatisticsProxy::OnInactiveSsrc(uint32_t ssrc) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
stats->total_bitrate_bps = 0;
stats->retransmit_bitrate_bps = 0;
stats->height = 0;
stats->width = 0;
}
void SendStatisticsProxy::OnSetEncoderTargetRate(uint32_t bitrate_bps) {
rtc::CritScope lock(&crit_);
if (uma_container_->target_rate_updates_.last_ms == -1 && bitrate_bps == 0)
return; // Start on first non-zero bitrate, may initially be zero.
int64_t now = clock_->TimeInMilliseconds();
if (uma_container_->target_rate_updates_.last_ms != -1) {
bool was_paused = stats_.target_media_bitrate_bps == 0;
int64_t diff_ms = now - uma_container_->target_rate_updates_.last_ms;
uma_container_->paused_time_counter_.Add(was_paused, diff_ms);
// Use last to not include update when stream is stopped and video disabled.
if (uma_container_->target_rate_updates_.last_paused_or_resumed)
++uma_container_->target_rate_updates_.pause_resume_events;
// Check if video is paused/resumed.
uma_container_->target_rate_updates_.last_paused_or_resumed =
(bitrate_bps == 0) != was_paused;
}
uma_container_->target_rate_updates_.last_ms = now;
stats_.target_media_bitrate_bps = bitrate_bps;
}
void SendStatisticsProxy::UpdateEncoderFallbackStats(
const CodecSpecificInfo* codec_info,
int pixels) {
UpdateFallbackDisabledStats(codec_info, pixels);
if (!fallback_max_pixels_ || !uma_container_->fallback_info_.is_possible) {
return;
}
if (!IsForcedFallbackPossible(codec_info)) {
uma_container_->fallback_info_.is_possible = false;
return;
}
FallbackEncoderInfo* fallback_info = &uma_container_->fallback_info_;
const int64_t now_ms = clock_->TimeInMilliseconds();
bool is_active = fallback_info->is_active;
if (codec_info->codec_name != stats_.encoder_implementation_name) {
// Implementation changed.
is_active = strcmp(codec_info->codec_name, kVp8SwCodecName) == 0;
if (!is_active && stats_.encoder_implementation_name != kVp8SwCodecName) {
// First or not a VP8 SW change, update stats on next call.
return;
}
if (is_active && (pixels > *fallback_max_pixels_)) {
// Pixels should not be above |fallback_max_pixels_|. If above skip to
// avoid fallbacks due to failure.
fallback_info->is_possible = false;
return;
}
stats_.has_entered_low_resolution = true;
++fallback_info->on_off_events;
}
if (fallback_info->last_update_ms) {
int64_t diff_ms = now_ms - *(fallback_info->last_update_ms);
// If the time diff since last update is greater than |max_frame_diff_ms|,
// video is considered paused/muted and the change is not included.
if (diff_ms < fallback_info->max_frame_diff_ms) {
uma_container_->fallback_active_counter_.Add(fallback_info->is_active,
diff_ms);
fallback_info->elapsed_ms += diff_ms;
}
}
fallback_info->is_active = is_active;
fallback_info->last_update_ms.emplace(now_ms);
}
void SendStatisticsProxy::UpdateFallbackDisabledStats(
const CodecSpecificInfo* codec_info,
int pixels) {
if (!fallback_max_pixels_disabled_ ||
!uma_container_->fallback_info_disabled_.is_possible ||
stats_.has_entered_low_resolution) {
return;
}
if (!IsForcedFallbackPossible(codec_info) ||
strcmp(codec_info->codec_name, kVp8SwCodecName) == 0) {
uma_container_->fallback_info_disabled_.is_possible = false;
return;
}
if (pixels <= *fallback_max_pixels_disabled_ ||
uma_container_->fallback_info_disabled_.min_pixel_limit_reached) {
stats_.has_entered_low_resolution = true;
}
}
void SendStatisticsProxy::OnMinPixelLimitReached() {
rtc::CritScope lock(&crit_);
uma_container_->fallback_info_disabled_.min_pixel_limit_reached = true;
}
void SendStatisticsProxy::OnSendEncodedImage(
const EncodedImage& encoded_image,
const CodecSpecificInfo* codec_info) {
size_t simulcast_idx = 0;
rtc::CritScope lock(&crit_);
++stats_.frames_encoded;
if (codec_info) {
if (codec_info->codecType == kVideoCodecVP8) {
simulcast_idx = codec_info->codecSpecific.VP8.simulcastIdx;
} else if (codec_info->codecType == kVideoCodecH264) {
simulcast_idx = codec_info->codecSpecific.H264.simulcast_idx;
} else if (codec_info->codecType == kVideoCodecGeneric) {
simulcast_idx = codec_info->codecSpecific.generic.simulcast_idx;
}
if (codec_info->codec_name) {
UpdateEncoderFallbackStats(codec_info, encoded_image._encodedWidth *
encoded_image._encodedHeight);
stats_.encoder_implementation_name = codec_info->codec_name;
}
}
if (simulcast_idx >= rtp_config_.ssrcs.size()) {
RTC_LOG(LS_ERROR) << "Encoded image outside simulcast range ("
<< simulcast_idx << " >= " << rtp_config_.ssrcs.size()
<< ").";
return;
}
uint32_t ssrc = rtp_config_.ssrcs[simulcast_idx];
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
// TODO(ssilkin): Fix stats reporting for spatial layers in SVC.
stats->width = encoded_image._encodedWidth;
stats->height = encoded_image._encodedHeight;
update_times_[ssrc].resolution_update_ms = clock_->TimeInMilliseconds();
uma_container_->key_frame_counter_.Add(encoded_image._frameType ==
kVideoFrameKey);
if (encoded_image.qp_ != -1) {
if (!stats_.qp_sum)
stats_.qp_sum = 0;
*stats_.qp_sum += encoded_image.qp_;
if (codec_info) {
if (codec_info->codecType == kVideoCodecVP8) {
int spatial_idx = (rtp_config_.ssrcs.size() == 1)
? -1
: static_cast<int>(simulcast_idx);
uma_container_->qp_counters_[spatial_idx].vp8.Add(encoded_image.qp_);
} else if (codec_info->codecType == kVideoCodecVP9) {
int spatial_idx =
(codec_info->codecSpecific.VP9.num_spatial_layers == 1)
? -1
: codec_info->codecSpecific.VP9.spatial_idx;
uma_container_->qp_counters_[spatial_idx].vp9.Add(encoded_image.qp_);
} else if (codec_info->codecType == kVideoCodecH264) {
int spatial_idx = (rtp_config_.ssrcs.size() == 1)
? -1
: static_cast<int>(simulcast_idx);
uma_container_->qp_counters_[spatial_idx].h264.Add(encoded_image.qp_);
}
}
}
// If any of the simulcast streams have a huge frame, it should be counted
// as a single difficult input frame.
// https://w3c.github.io/webrtc-stats/#dom-rtcvideosenderstats-hugeframessent
if (encoded_image.timing_.flags & VideoSendTiming::kTriggeredBySize) {
if (!last_outlier_timestamp_ ||
*last_outlier_timestamp_ < encoded_image.capture_time_ms_) {
last_outlier_timestamp_.emplace(encoded_image.capture_time_ms_);
++stats_.huge_frames_sent;
}
}
media_byte_rate_tracker_.AddSamples(encoded_image._length);
// Initialize to current since |is_limited_in_resolution| is only updated
// when an encoded frame is removed from the EncodedFrameMap.
bool is_limited_in_resolution = stats_.bw_limited_resolution;
if (uma_container_->InsertEncodedFrame(encoded_image, simulcast_idx,
&is_limited_in_resolution)) {
encoded_frame_rate_tracker_.AddSamples(1);
}
stats_.bw_limited_resolution =
is_limited_in_resolution || quality_downscales_ > 0;
if (quality_downscales_ != -1) {
uma_container_->quality_limited_frame_counter_.Add(quality_downscales_ > 0);
if (quality_downscales_ > 0)
uma_container_->quality_downscales_counter_.Add(quality_downscales_);
}
}
int SendStatisticsProxy::GetInputFrameRate() const {
rtc::CritScope lock(&crit_);
return round(uma_container_->input_frame_rate_tracker_.ComputeRate());
}
int SendStatisticsProxy::GetSendFrameRate() const {
rtc::CritScope lock(&crit_);
return round(encoded_frame_rate_tracker_.ComputeRate());
}
void SendStatisticsProxy::OnIncomingFrame(int width, int height) {
rtc::CritScope lock(&crit_);
uma_container_->input_frame_rate_tracker_.AddSamples(1);
uma_container_->input_fps_counter_.Add(1);
uma_container_->input_width_counter_.Add(width);
uma_container_->input_height_counter_.Add(height);
if (cpu_downscales_ >= 0) {
uma_container_->cpu_limited_frame_counter_.Add(
stats_.cpu_limited_resolution);
}
if (encoded_frame_rate_tracker_.TotalSampleCount() == 0) {
// Set start time now instead of when first key frame is encoded to avoid a
// too high initial estimate.
encoded_frame_rate_tracker_.AddSamples(0);
}
}
void SendStatisticsProxy::OnFrameDropped(DropReason reason) {
rtc::CritScope lock(&crit_);
switch (reason) {
case DropReason::kSource:
++stats_.frames_dropped_by_capturer;
break;
case DropReason::kEncoderQueue:
++stats_.frames_dropped_by_encoder_queue;
break;
case DropReason::kEncoder:
++stats_.frames_dropped_by_encoder;
break;
case DropReason::kMediaOptimization:
++stats_.frames_dropped_by_rate_limiter;
break;
}
}
void SendStatisticsProxy::OnAdaptationChanged(
AdaptationReason reason,
const AdaptationSteps& cpu_counts,
const AdaptationSteps& quality_counts) {
rtc::CritScope lock(&crit_);
switch (reason) {
case AdaptationReason::kNone:
SetAdaptTimer(cpu_counts, &uma_container_->cpu_adapt_timer_);
SetAdaptTimer(quality_counts, &uma_container_->quality_adapt_timer_);
break;
case AdaptationReason::kCpu:
++stats_.number_of_cpu_adapt_changes;
break;
case AdaptationReason::kQuality:
TryUpdateInitialQualityResolutionAdaptUp(quality_counts);
++stats_.number_of_quality_adapt_changes;
break;
}
UpdateAdaptationStats(cpu_counts, quality_counts);
}
void SendStatisticsProxy::UpdateAdaptationStats(
const AdaptationSteps& cpu_counts,
const AdaptationSteps& quality_counts) {
cpu_downscales_ = cpu_counts.num_resolution_reductions.value_or(-1);
quality_downscales_ = quality_counts.num_resolution_reductions.value_or(-1);
stats_.cpu_limited_resolution = cpu_counts.num_resolution_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_framerate = quality_counts.num_framerate_reductions > 0;
}
// TODO(asapersson): Include fps changes.
void SendStatisticsProxy::OnInitialQualityResolutionAdaptDown() {
rtc::CritScope lock(&crit_);
++uma_container_->initial_quality_changes_.down;
}
void SendStatisticsProxy::TryUpdateInitialQualityResolutionAdaptUp(
const AdaptationSteps& quality_counts) {
if (uma_container_->initial_quality_changes_.down == 0)
return;
if (quality_downscales_ > 0 &&
quality_counts.num_resolution_reductions.value_or(-1) <
quality_downscales_) {
// Adapting up in quality.
if (uma_container_->initial_quality_changes_.down >
uma_container_->initial_quality_changes_.up) {
++uma_container_->initial_quality_changes_.up;
}
}
}
void SendStatisticsProxy::SetAdaptTimer(const AdaptationSteps& counts,
StatsTimer* timer) {
if (counts.num_resolution_reductions || counts.num_framerate_reductions) {
// Adaptation enabled.
if (!stats_.suspended)
timer->Start(clock_->TimeInMilliseconds());
return;
}
timer->Stop(clock_->TimeInMilliseconds());
}
void SendStatisticsProxy::RtcpPacketTypesCounterUpdated(
uint32_t ssrc,
const RtcpPacketTypeCounter& packet_counter) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
stats->rtcp_packet_type_counts = packet_counter;
if (uma_container_->first_rtcp_stats_time_ms_ == -1)
uma_container_->first_rtcp_stats_time_ms_ = clock_->TimeInMilliseconds();
}
void SendStatisticsProxy::StatisticsUpdated(const RtcpStatistics& statistics,
uint32_t ssrc) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
stats->rtcp_stats = statistics;
uma_container_->report_block_stats_.Store(statistics, 0, ssrc);
}
void SendStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) {}
void SendStatisticsProxy::DataCountersUpdated(
const StreamDataCounters& counters,
uint32_t ssrc) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
RTC_DCHECK(stats) << "DataCountersUpdated reported for unknown ssrc " << ssrc;
if (stats->is_flexfec) {
// The same counters are reported for both the media ssrc and flexfec ssrc.
// Bitrate stats are summed for all SSRCs. Use fec stats from media update.
return;
}
stats->rtp_stats = counters;
if (uma_container_->first_rtp_stats_time_ms_ == -1) {
int64_t now_ms = clock_->TimeInMilliseconds();
uma_container_->first_rtp_stats_time_ms_ = now_ms;
uma_container_->cpu_adapt_timer_.Restart(now_ms);
uma_container_->quality_adapt_timer_.Restart(now_ms);
}
uma_container_->total_byte_counter_.Set(counters.transmitted.TotalBytes(),
ssrc);
uma_container_->padding_byte_counter_.Set(counters.transmitted.padding_bytes,
ssrc);
uma_container_->retransmit_byte_counter_.Set(
counters.retransmitted.TotalBytes(), ssrc);
uma_container_->fec_byte_counter_.Set(counters.fec.TotalBytes(), ssrc);
if (stats->is_rtx) {
uma_container_->rtx_byte_counter_.Set(counters.transmitted.TotalBytes(),
ssrc);
} else {
uma_container_->media_byte_counter_.Set(counters.MediaPayloadBytes(), ssrc);
}
}
void SendStatisticsProxy::Notify(uint32_t total_bitrate_bps,
uint32_t retransmit_bitrate_bps,
uint32_t ssrc) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
stats->total_bitrate_bps = total_bitrate_bps;
stats->retransmit_bitrate_bps = retransmit_bitrate_bps;
}
void SendStatisticsProxy::FrameCountUpdated(const FrameCounts& frame_counts,
uint32_t ssrc) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
stats->frame_counts = frame_counts;
}
void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms,
int max_delay_ms,
uint32_t ssrc) {
rtc::CritScope lock(&crit_);
VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
if (!stats)
return;
stats->avg_delay_ms = avg_delay_ms;
stats->max_delay_ms = max_delay_ms;
uma_container_->delay_counter_.Add(avg_delay_ms);
uma_container_->max_delay_counter_.Add(max_delay_ms);
}
void SendStatisticsProxy::StatsTimer::Start(int64_t now_ms) {
if (start_ms == -1)
start_ms = now_ms;
}
void SendStatisticsProxy::StatsTimer::Stop(int64_t now_ms) {
if (start_ms != -1) {
total_ms += now_ms - start_ms;
start_ms = -1;
}
}
void SendStatisticsProxy::StatsTimer::Restart(int64_t now_ms) {
total_ms = 0;
if (start_ms != -1)
start_ms = now_ms;
}
void SendStatisticsProxy::SampleCounter::Add(int sample) {
sum += sample;
++num_samples;
}
int SendStatisticsProxy::SampleCounter::Avg(
int64_t min_required_samples) const {
if (num_samples < min_required_samples || num_samples == 0)
return -1;
return static_cast<int>((sum + (num_samples / 2)) / num_samples);
}
void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) {
if (sample)
++sum;
++num_samples;
}
void SendStatisticsProxy::BoolSampleCounter::Add(bool sample, int64_t count) {
if (sample)
sum += count;
num_samples += count;
}
int SendStatisticsProxy::BoolSampleCounter::Percent(
int64_t min_required_samples) const {
return Fraction(min_required_samples, 100.0f);
}
int SendStatisticsProxy::BoolSampleCounter::Permille(
int64_t min_required_samples) const {
return Fraction(min_required_samples, 1000.0f);
}
int SendStatisticsProxy::BoolSampleCounter::Fraction(
int64_t min_required_samples,
float multiplier) const {
if (num_samples < min_required_samples || num_samples == 0)
return -1;
return static_cast<int>((sum * multiplier / num_samples) + 0.5f);
}
} // namespace webrtc