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