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