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
22 namespace webrtc {
23 namespace {
24 const float kEncodeTimeWeigthFactor = 0.5f;
25
26 // Used by histograms. Values of entries should not be changed.
27 enum HistogramCodecType {
28 kVideoUnknown = 0,
29 kVideoVp8 = 1,
30 kVideoVp9 = 2,
31 kVideoH264 = 3,
32 kVideoMax = 64,
33 };
34
GetUmaPrefix(VideoEncoderConfig::ContentType content_type)35 const 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
PayloadNameToHistogramCodecType(const std::string & payload_name)46 HistogramCodecType 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
UpdateCodecTypeHistogram(const std::string & payload_name)59 void 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
66 const int SendStatisticsProxy::kStatsTimeoutMs = 5000;
67
SendStatisticsProxy(Clock * clock,const VideoSendStream::Config & config,VideoEncoderConfig::ContentType content_type)68 SendStatisticsProxy::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
~SendStatisticsProxy()81 SendStatisticsProxy::~SendStatisticsProxy() {}
82
UmaSamplesContainer(const char * prefix)83 SendStatisticsProxy::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
~UmaSamplesContainer()91 SendStatisticsProxy::UmaSamplesContainer::~UmaSamplesContainer() {
92 UpdateHistograms();
93 }
94
UpdateHistograms()95 void 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
SetContentType(VideoEncoderConfig::ContentType content_type)161 void 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
OnEncoderImplementationName(const char * implementation_name)170 void SendStatisticsProxy::OnEncoderImplementationName(
171 const char* implementation_name) {
172 rtc::CritScope lock(&crit_);
173 stats_.encoder_implementation_name = implementation_name;
174 }
175
OnOutgoingRate(uint32_t framerate,uint32_t bitrate)176 void 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
CpuOveruseMetricsUpdated(const CpuOveruseMetrics & metrics)182 void SendStatisticsProxy::CpuOveruseMetricsUpdated(
183 const CpuOveruseMetrics& metrics) {
184 rtc::CritScope lock(&crit_);
185 stats_.encode_usage_percent = metrics.encode_usage_percent;
186 }
187
OnSuspendChange(bool is_suspended)188 void SendStatisticsProxy::OnSuspendChange(bool is_suspended) {
189 rtc::CritScope lock(&crit_);
190 stats_.suspended = is_suspended;
191 }
192
GetStats()193 VideoSendStream::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
PurgeOldStats()201 void 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
GetStatsEntry(uint32_t ssrc)214 VideoSendStream::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
OnInactiveSsrc(uint32_t ssrc)232 void 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
OnSetRates(uint32_t bitrate_bps,int framerate)244 void SendStatisticsProxy::OnSetRates(uint32_t bitrate_bps, int framerate) {
245 rtc::CritScope lock(&crit_);
246 stats_.target_media_bitrate_bps = bitrate_bps;
247 }
248
OnSendEncodedImage(const EncodedImage & encoded_image,const RTPVideoHeader * rtp_video_header)249 void 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
OnIncomingFrame(int width,int height)317 void 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
OnEncodedFrame(int encode_time_ms)324 void 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
RtcpPacketTypesCounterUpdated(uint32_t ssrc,const RtcpPacketTypeCounter & packet_counter)331 void 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
StatisticsUpdated(const RtcpStatistics & statistics,uint32_t ssrc)342 void 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
CNameChanged(const char * cname,uint32_t ssrc)352 void SendStatisticsProxy::CNameChanged(const char* cname, uint32_t ssrc) {
353 }
354
DataCountersUpdated(const StreamDataCounters & counters,uint32_t ssrc)355 void 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
Notify(const BitrateStatistics & total_stats,const BitrateStatistics & retransmit_stats,uint32_t ssrc)366 void 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
FrameCountUpdated(const FrameCounts & frame_counts,uint32_t ssrc)378 void 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
SendSideDelayUpdated(int avg_delay_ms,int max_delay_ms,uint32_t ssrc)388 void 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
Add(int sample)402 void SendStatisticsProxy::SampleCounter::Add(int sample) {
403 sum += sample;
404 ++num_samples;
405 }
406
Avg(int min_required_samples) const407 int 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
Add(bool sample)413 void SendStatisticsProxy::BoolSampleCounter::Add(bool sample) {
414 if (sample)
415 ++sum;
416 ++num_samples;
417 }
418
Percent(int min_required_samples) const419 int SendStatisticsProxy::BoolSampleCounter::Percent(
420 int min_required_samples) const {
421 return Fraction(min_required_samples, 100.0f);
422 }
423
Permille(int min_required_samples) const424 int SendStatisticsProxy::BoolSampleCounter::Permille(
425 int min_required_samples) const {
426 return Fraction(min_required_samples, 1000.0f);
427 }
428
Fraction(int min_required_samples,float multiplier) const429 int 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