1/*
2 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
3 *
4 *  Use of this source code is governed by a BSD-style license
5 *  that can be found in the LICENSE file in the root of the source
6 *  tree. An additional intellectual property rights grant can be found
7 *  in the file PATENTS.  All contributing project authors may
8 *  be found in the AUTHORS file in the root of the source tree.
9 */
10
11#include "webrtc/video/send_statistics_proxy.h"
12
13#include <algorithm>
14#include <cmath>
15#include <map>
16
17#include "webrtc/base/checks.h"
18#include "webrtc/base/logging.h"
19#include "webrtc/system_wrappers/include/critical_section_wrapper.h"
20#include "webrtc/system_wrappers/include/metrics.h"
21
22namespace webrtc {
23namespace {
24const float kEncodeTimeWeigthFactor = 0.5f;
25
26// Used by histograms. Values of entries should not be changed.
27enum HistogramCodecType {
28  kVideoUnknown = 0,
29  kVideoVp8 = 1,
30  kVideoVp9 = 2,
31  kVideoH264 = 3,
32  kVideoMax = 64,
33};
34
35const char* GetUmaPrefix(VideoEncoderConfig::ContentType content_type) {
36  switch (content_type) {
37    case VideoEncoderConfig::ContentType::kRealtimeVideo:
38      return "WebRTC.Video.";
39    case VideoEncoderConfig::ContentType::kScreen:
40      return "WebRTC.Video.Screenshare.";
41  }
42  RTC_NOTREACHED();
43  return nullptr;
44}
45
46HistogramCodecType PayloadNameToHistogramCodecType(
47    const std::string& payload_name) {
48  if (payload_name == "VP8") {
49    return kVideoVp8;
50  } else if (payload_name == "VP9") {
51    return kVideoVp9;
52  } else if (payload_name == "H264") {
53    return kVideoH264;
54  } else {
55    return kVideoUnknown;
56  }
57}
58
59void UpdateCodecTypeHistogram(const std::string& payload_name) {
60  RTC_HISTOGRAM_ENUMERATION_SPARSE("WebRTC.Video.Encoder.CodecType",
61      PayloadNameToHistogramCodecType(payload_name), kVideoMax);
62}
63}  // namespace
64
65
66const int SendStatisticsProxy::kStatsTimeoutMs = 5000;
67
68SendStatisticsProxy::SendStatisticsProxy(
69    Clock* clock,
70    const VideoSendStream::Config& config,
71    VideoEncoderConfig::ContentType content_type)
72    : clock_(clock),
73      config_(config),
74      content_type_(content_type),
75      last_sent_frame_timestamp_(0),
76      encode_time_(kEncodeTimeWeigthFactor),
77      uma_container_(new UmaSamplesContainer(GetUmaPrefix(content_type_))) {
78  UpdateCodecTypeHistogram(config_.encoder_settings.payload_name);
79}
80
81SendStatisticsProxy::~SendStatisticsProxy() {}
82
83SendStatisticsProxy::UmaSamplesContainer::UmaSamplesContainer(
84    const char* prefix)
85    : uma_prefix_(prefix),
86      max_sent_width_per_timestamp_(0),
87      max_sent_height_per_timestamp_(0),
88      input_frame_rate_tracker_(100u, 10u),
89      sent_frame_rate_tracker_(100u, 10u) {}
90
91SendStatisticsProxy::UmaSamplesContainer::~UmaSamplesContainer() {
92  UpdateHistograms();
93}
94
95void SendStatisticsProxy::UmaSamplesContainer::UpdateHistograms() {
96  const int kMinRequiredSamples = 200;
97  int in_width = input_width_counter_.Avg(kMinRequiredSamples);
98  int in_height = input_height_counter_.Avg(kMinRequiredSamples);
99  int in_fps = round(input_frame_rate_tracker_.ComputeTotalRate());
100  if (in_width != -1) {
101    RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "InputWidthInPixels",
102                                      in_width);
103    RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "InputHeightInPixels",
104                                      in_height);
105    RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix_ + "InputFramesPerSecond",
106                                    in_fps);
107  }
108  int sent_width = sent_width_counter_.Avg(kMinRequiredSamples);
109  int sent_height = sent_height_counter_.Avg(kMinRequiredSamples);
110  int sent_fps = round(sent_frame_rate_tracker_.ComputeTotalRate());
111  if (sent_width != -1) {
112    RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "SentWidthInPixels",
113                                      sent_width);
114    RTC_HISTOGRAM_COUNTS_SPARSE_10000(uma_prefix_ + "SentHeightInPixels",
115                                      sent_height);
116    RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix_ + "SentFramesPerSecond",
117                                    sent_fps);
118  }
119  int encode_ms = encode_time_counter_.Avg(kMinRequiredSamples);
120  if (encode_ms != -1)
121    RTC_HISTOGRAM_COUNTS_SPARSE_1000(uma_prefix_ + "EncodeTimeInMs", encode_ms);
122
123  int key_frames_permille = key_frame_counter_.Permille(kMinRequiredSamples);
124  if (key_frames_permille != -1) {
125    RTC_HISTOGRAM_COUNTS_SPARSE_1000(uma_prefix_ + "KeyFramesSentInPermille",
126                                     key_frames_permille);
127  }
128  int quality_limited =
129      quality_limited_frame_counter_.Percent(kMinRequiredSamples);
130  if (quality_limited != -1) {
131    RTC_HISTOGRAM_PERCENTAGE_SPARSE(
132        uma_prefix_ + "QualityLimitedResolutionInPercent", quality_limited);
133  }
134  int downscales = quality_downscales_counter_.Avg(kMinRequiredSamples);
135  if (downscales != -1) {
136    RTC_HISTOGRAM_ENUMERATION_SPARSE(
137        uma_prefix_ + "QualityLimitedResolutionDownscales", downscales, 20);
138  }
139  int bw_limited = bw_limited_frame_counter_.Percent(kMinRequiredSamples);
140  if (bw_limited != -1) {
141    RTC_HISTOGRAM_PERCENTAGE_SPARSE(
142        uma_prefix_ + "BandwidthLimitedResolutionInPercent", bw_limited);
143  }
144  int num_disabled = bw_resolutions_disabled_counter_.Avg(kMinRequiredSamples);
145  if (num_disabled != -1) {
146    RTC_HISTOGRAM_ENUMERATION_SPARSE(
147        uma_prefix_ + "BandwidthLimitedResolutionsDisabled", num_disabled, 10);
148  }
149  int delay_ms = delay_counter_.Avg(kMinRequiredSamples);
150  if (delay_ms != -1)
151    RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix_ + "SendSideDelayInMs",
152                                       delay_ms);
153
154  int max_delay_ms = max_delay_counter_.Avg(kMinRequiredSamples);
155  if (max_delay_ms != -1) {
156    RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix_ + "SendSideDelayMaxInMs",
157                                       max_delay_ms);
158  }
159}
160
161void SendStatisticsProxy::SetContentType(
162    VideoEncoderConfig::ContentType content_type) {
163  rtc::CritScope lock(&crit_);
164  if (content_type_ != content_type) {
165    uma_container_.reset(new UmaSamplesContainer(GetUmaPrefix(content_type)));
166    content_type_ = content_type;
167  }
168}
169
170void SendStatisticsProxy::OnEncoderImplementationName(
171    const char* implementation_name) {
172  rtc::CritScope lock(&crit_);
173  stats_.encoder_implementation_name = implementation_name;
174}
175
176void SendStatisticsProxy::OnOutgoingRate(uint32_t framerate, uint32_t bitrate) {
177  rtc::CritScope lock(&crit_);
178  stats_.encode_frame_rate = framerate;
179  stats_.media_bitrate_bps = bitrate;
180}
181
182void SendStatisticsProxy::CpuOveruseMetricsUpdated(
183    const CpuOveruseMetrics& metrics) {
184  rtc::CritScope lock(&crit_);
185  stats_.encode_usage_percent = metrics.encode_usage_percent;
186}
187
188void SendStatisticsProxy::OnSuspendChange(bool is_suspended) {
189  rtc::CritScope lock(&crit_);
190  stats_.suspended = is_suspended;
191}
192
193VideoSendStream::Stats SendStatisticsProxy::GetStats() {
194  rtc::CritScope lock(&crit_);
195  PurgeOldStats();
196  stats_.input_frame_rate =
197      round(uma_container_->input_frame_rate_tracker_.ComputeRate());
198  return stats_;
199}
200
201void SendStatisticsProxy::PurgeOldStats() {
202  int64_t old_stats_ms = clock_->TimeInMilliseconds() - kStatsTimeoutMs;
203  for (std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
204           stats_.substreams.begin();
205       it != stats_.substreams.end(); ++it) {
206    uint32_t ssrc = it->first;
207    if (update_times_[ssrc].resolution_update_ms <= old_stats_ms) {
208      it->second.width = 0;
209      it->second.height = 0;
210    }
211  }
212}
213
214VideoSendStream::StreamStats* SendStatisticsProxy::GetStatsEntry(
215    uint32_t ssrc) {
216  std::map<uint32_t, VideoSendStream::StreamStats>::iterator it =
217      stats_.substreams.find(ssrc);
218  if (it != stats_.substreams.end())
219    return &it->second;
220
221  if (std::find(config_.rtp.ssrcs.begin(), config_.rtp.ssrcs.end(), ssrc) ==
222          config_.rtp.ssrcs.end() &&
223      std::find(config_.rtp.rtx.ssrcs.begin(),
224                config_.rtp.rtx.ssrcs.end(),
225                ssrc) == config_.rtp.rtx.ssrcs.end()) {
226    return nullptr;
227  }
228
229  return &stats_.substreams[ssrc];  // Insert new entry and return ptr.
230}
231
232void SendStatisticsProxy::OnInactiveSsrc(uint32_t ssrc) {
233  rtc::CritScope lock(&crit_);
234  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
235  if (stats == nullptr)
236    return;
237
238  stats->total_bitrate_bps = 0;
239  stats->retransmit_bitrate_bps = 0;
240  stats->height = 0;
241  stats->width = 0;
242}
243
244void SendStatisticsProxy::OnSetRates(uint32_t bitrate_bps, int framerate) {
245  rtc::CritScope lock(&crit_);
246  stats_.target_media_bitrate_bps = bitrate_bps;
247}
248
249void SendStatisticsProxy::OnSendEncodedImage(
250    const EncodedImage& encoded_image,
251    const RTPVideoHeader* rtp_video_header) {
252  size_t simulcast_idx =
253      rtp_video_header != nullptr ? rtp_video_header->simulcastIdx : 0;
254  if (simulcast_idx >= config_.rtp.ssrcs.size()) {
255    LOG(LS_ERROR) << "Encoded image outside simulcast range (" << simulcast_idx
256                  << " >= " << config_.rtp.ssrcs.size() << ").";
257    return;
258  }
259  uint32_t ssrc = config_.rtp.ssrcs[simulcast_idx];
260
261  rtc::CritScope lock(&crit_);
262  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
263  if (stats == nullptr)
264    return;
265
266  stats->width = encoded_image._encodedWidth;
267  stats->height = encoded_image._encodedHeight;
268  update_times_[ssrc].resolution_update_ms = clock_->TimeInMilliseconds();
269
270  uma_container_->key_frame_counter_.Add(encoded_image._frameType ==
271                                         kVideoFrameKey);
272
273  stats_.bw_limited_resolution =
274      encoded_image.adapt_reason_.quality_resolution_downscales > 0 ||
275      encoded_image.adapt_reason_.bw_resolutions_disabled > 0;
276
277  if (encoded_image.adapt_reason_.quality_resolution_downscales != -1) {
278    bool downscaled =
279        encoded_image.adapt_reason_.quality_resolution_downscales > 0;
280    uma_container_->quality_limited_frame_counter_.Add(downscaled);
281    if (downscaled) {
282      uma_container_->quality_downscales_counter_.Add(
283          encoded_image.adapt_reason_.quality_resolution_downscales);
284    }
285  }
286  if (encoded_image.adapt_reason_.bw_resolutions_disabled != -1) {
287    bool bw_limited = encoded_image.adapt_reason_.bw_resolutions_disabled > 0;
288    uma_container_->bw_limited_frame_counter_.Add(bw_limited);
289    if (bw_limited) {
290      uma_container_->bw_resolutions_disabled_counter_.Add(
291          encoded_image.adapt_reason_.bw_resolutions_disabled);
292    }
293  }
294
295  // TODO(asapersson): This is incorrect if simulcast layers are encoded on
296  // different threads and there is no guarantee that one frame of all layers
297  // are encoded before the next start.
298  if (last_sent_frame_timestamp_ > 0 &&
299      encoded_image._timeStamp != last_sent_frame_timestamp_) {
300    uma_container_->sent_frame_rate_tracker_.AddSamples(1);
301    uma_container_->sent_width_counter_.Add(
302        uma_container_->max_sent_width_per_timestamp_);
303    uma_container_->sent_height_counter_.Add(
304        uma_container_->max_sent_height_per_timestamp_);
305    uma_container_->max_sent_width_per_timestamp_ = 0;
306    uma_container_->max_sent_height_per_timestamp_ = 0;
307  }
308  last_sent_frame_timestamp_ = encoded_image._timeStamp;
309  uma_container_->max_sent_width_per_timestamp_ =
310      std::max(uma_container_->max_sent_width_per_timestamp_,
311               static_cast<int>(encoded_image._encodedWidth));
312  uma_container_->max_sent_height_per_timestamp_ =
313      std::max(uma_container_->max_sent_height_per_timestamp_,
314               static_cast<int>(encoded_image._encodedHeight));
315}
316
317void SendStatisticsProxy::OnIncomingFrame(int width, int height) {
318  rtc::CritScope lock(&crit_);
319  uma_container_->input_frame_rate_tracker_.AddSamples(1);
320  uma_container_->input_width_counter_.Add(width);
321  uma_container_->input_height_counter_.Add(height);
322}
323
324void SendStatisticsProxy::OnEncodedFrame(int encode_time_ms) {
325  rtc::CritScope lock(&crit_);
326  uma_container_->encode_time_counter_.Add(encode_time_ms);
327  encode_time_.Apply(1.0f, encode_time_ms);
328  stats_.avg_encode_time_ms = round(encode_time_.filtered());
329}
330
331void SendStatisticsProxy::RtcpPacketTypesCounterUpdated(
332    uint32_t ssrc,
333    const RtcpPacketTypeCounter& packet_counter) {
334  rtc::CritScope lock(&crit_);
335  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
336  if (stats == nullptr)
337    return;
338
339  stats->rtcp_packet_type_counts = packet_counter;
340}
341
342void SendStatisticsProxy::StatisticsUpdated(const RtcpStatistics& statistics,
343                                            uint32_t ssrc) {
344  rtc::CritScope lock(&crit_);
345  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
346  if (stats == nullptr)
347    return;
348
349  stats->rtcp_stats = statistics;
350}
351
352void SendStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) {
353}
354
355void SendStatisticsProxy::DataCountersUpdated(
356    const StreamDataCounters& counters,
357    uint32_t ssrc) {
358  rtc::CritScope lock(&crit_);
359  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
360  RTC_DCHECK(stats != nullptr)
361      << "DataCountersUpdated reported for unknown ssrc: " << ssrc;
362
363  stats->rtp_stats = counters;
364}
365
366void SendStatisticsProxy::Notify(const BitrateStatistics& total_stats,
367                                 const BitrateStatistics& retransmit_stats,
368                                 uint32_t ssrc) {
369  rtc::CritScope lock(&crit_);
370  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
371  if (stats == nullptr)
372    return;
373
374  stats->total_bitrate_bps = total_stats.bitrate_bps;
375  stats->retransmit_bitrate_bps = retransmit_stats.bitrate_bps;
376}
377
378void SendStatisticsProxy::FrameCountUpdated(const FrameCounts& frame_counts,
379                                            uint32_t ssrc) {
380  rtc::CritScope lock(&crit_);
381  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
382  if (stats == nullptr)
383    return;
384
385  stats->frame_counts = frame_counts;
386}
387
388void SendStatisticsProxy::SendSideDelayUpdated(int avg_delay_ms,
389                                               int max_delay_ms,
390                                               uint32_t ssrc) {
391  rtc::CritScope lock(&crit_);
392  VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
393  if (stats == nullptr)
394    return;
395  stats->avg_delay_ms = avg_delay_ms;
396  stats->max_delay_ms = max_delay_ms;
397
398  uma_container_->delay_counter_.Add(avg_delay_ms);
399  uma_container_->max_delay_counter_.Add(max_delay_ms);
400}
401
402void SendStatisticsProxy::SampleCounter::Add(int sample) {
403  sum += sample;
404  ++num_samples;
405}
406
407int SendStatisticsProxy::SampleCounter::Avg(int min_required_samples) const {
408  if (num_samples < min_required_samples || num_samples == 0)
409    return -1;
410  return (sum + (num_samples / 2)) / num_samples;
411}
412
413void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) {
414  if (sample)
415    ++sum;
416  ++num_samples;
417}
418
419int SendStatisticsProxy::BoolSampleCounter::Percent(
420    int min_required_samples) const {
421  return Fraction(min_required_samples, 100.0f);
422}
423
424int SendStatisticsProxy::BoolSampleCounter::Permille(
425    int min_required_samples) const {
426  return Fraction(min_required_samples, 1000.0f);
427}
428
429int SendStatisticsProxy::BoolSampleCounter::Fraction(
430    int min_required_samples, float multiplier) const {
431  if (num_samples < min_required_samples || num_samples == 0)
432    return -1;
433  return static_cast<int>((sum * multiplier / num_samples) + 0.5f);
434}
435}  // namespace webrtc
436