1 /* 2 * libjingle 3 * Copyright 2012, Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, 9 * this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright notice, 11 * this list of conditions and the following disclaimer in the documentation 12 * and/or other materials provided with the distribution. 13 * 3. The name of the author may not be used to endorse or promote products 14 * derived from this software without specific prior written permission. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED 17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO 19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 */ 27 28 #include "talk/app/webrtc/statscollector.h" 29 30 #include <utility> 31 #include <vector> 32 33 #include "talk/session/media/channel.h" 34 #include "webrtc/base/base64.h" 35 #include "webrtc/base/scoped_ptr.h" 36 #include "webrtc/base/timing.h" 37 38 namespace webrtc { 39 40 // The items below are in alphabetical order. 41 const char StatsReport::kStatsValueNameActiveConnection[] = 42 "googActiveConnection"; 43 const char StatsReport::kStatsValueNameActualEncBitrate[] = 44 "googActualEncBitrate"; 45 const char StatsReport::kStatsValueNameAudioOutputLevel[] = "audioOutputLevel"; 46 const char StatsReport::kStatsValueNameAudioInputLevel[] = "audioInputLevel"; 47 const char StatsReport::kStatsValueNameAvailableReceiveBandwidth[] = 48 "googAvailableReceiveBandwidth"; 49 const char StatsReport::kStatsValueNameAvailableSendBandwidth[] = 50 "googAvailableSendBandwidth"; 51 const char StatsReport::kStatsValueNameAvgEncodeMs[] = "googAvgEncodeMs"; 52 const char StatsReport::kStatsValueNameBucketDelay[] = "googBucketDelay"; 53 const char StatsReport::kStatsValueNameBytesReceived[] = "bytesReceived"; 54 const char StatsReport::kStatsValueNameBytesSent[] = "bytesSent"; 55 const char StatsReport::kStatsValueNameBandwidthLimitedResolution[] = 56 "googBandwidthLimitedResolution"; 57 const char StatsReport::kStatsValueNameCaptureJitterMs[] = 58 "googCaptureJitterMs"; 59 const char StatsReport::kStatsValueNameCaptureQueueDelayMsPerS[] = 60 "googCaptureQueueDelayMsPerS"; 61 const char StatsReport::kStatsValueNameChannelId[] = "googChannelId"; 62 const char StatsReport::kStatsValueNameCodecName[] = "googCodecName"; 63 const char StatsReport::kStatsValueNameComponent[] = "googComponent"; 64 const char StatsReport::kStatsValueNameContentName[] = "googContentName"; 65 const char StatsReport::kStatsValueNameCpuLimitedResolution[] = 66 "googCpuLimitedResolution"; 67 const char StatsReport::kStatsValueNameDecodingCTSG[] = 68 "googDecodingCTSG"; 69 const char StatsReport::kStatsValueNameDecodingCTN[] = 70 "googDecodingCTN"; 71 const char StatsReport::kStatsValueNameDecodingNormal[] = 72 "googDecodingNormal"; 73 const char StatsReport::kStatsValueNameDecodingPLC[] = 74 "googDecodingPLC"; 75 const char StatsReport::kStatsValueNameDecodingCNG[] = 76 "googDecodingCNG"; 77 const char StatsReport::kStatsValueNameDecodingPLCCNG[] = 78 "googDecodingPLCCNG"; 79 const char StatsReport::kStatsValueNameDer[] = "googDerBase64"; 80 // Echo metrics from the audio processing module. 81 const char StatsReport::kStatsValueNameEchoCancellationQualityMin[] = 82 "googEchoCancellationQualityMin"; 83 const char StatsReport::kStatsValueNameEchoDelayMedian[] = 84 "googEchoCancellationEchoDelayMedian"; 85 const char StatsReport::kStatsValueNameEchoDelayStdDev[] = 86 "googEchoCancellationEchoDelayStdDev"; 87 const char StatsReport::kStatsValueNameEchoReturnLoss[] = 88 "googEchoCancellationReturnLoss"; 89 const char StatsReport::kStatsValueNameEchoReturnLossEnhancement[] = 90 "googEchoCancellationReturnLossEnhancement"; 91 92 const char StatsReport::kStatsValueNameEncodeRelStdDev[] = 93 "googEncodeRelStdDev"; 94 const char StatsReport::kStatsValueNameEncodeUsagePercent[] = 95 "googEncodeUsagePercent"; 96 const char StatsReport::kStatsValueNameExpandRate[] = "googExpandRate"; 97 const char StatsReport::kStatsValueNameFingerprint[] = "googFingerprint"; 98 const char StatsReport::kStatsValueNameFingerprintAlgorithm[] = 99 "googFingerprintAlgorithm"; 100 const char StatsReport::kStatsValueNameFirsReceived[] = "googFirsReceived"; 101 const char StatsReport::kStatsValueNameFirsSent[] = "googFirsSent"; 102 const char StatsReport::kStatsValueNameFrameHeightInput[] = 103 "googFrameHeightInput"; 104 const char StatsReport::kStatsValueNameFrameHeightReceived[] = 105 "googFrameHeightReceived"; 106 const char StatsReport::kStatsValueNameFrameHeightSent[] = 107 "googFrameHeightSent"; 108 const char StatsReport::kStatsValueNameFrameRateReceived[] = 109 "googFrameRateReceived"; 110 const char StatsReport::kStatsValueNameFrameRateDecoded[] = 111 "googFrameRateDecoded"; 112 const char StatsReport::kStatsValueNameFrameRateOutput[] = 113 "googFrameRateOutput"; 114 const char StatsReport::kStatsValueNameDecodeMs[] = "googDecodeMs"; 115 const char StatsReport::kStatsValueNameMaxDecodeMs[] = "googMaxDecodeMs"; 116 const char StatsReport::kStatsValueNameCurrentDelayMs[] = "googCurrentDelayMs"; 117 const char StatsReport::kStatsValueNameTargetDelayMs[] = "googTargetDelayMs"; 118 const char StatsReport::kStatsValueNameJitterBufferMs[] = "googJitterBufferMs"; 119 const char StatsReport::kStatsValueNameMinPlayoutDelayMs[] = 120 "googMinPlayoutDelayMs"; 121 const char StatsReport::kStatsValueNameRenderDelayMs[] = "googRenderDelayMs"; 122 123 const char StatsReport::kStatsValueNameCaptureStartNtpTimeMs[] = 124 "googCaptureStartNtpTimeMs"; 125 126 const char StatsReport::kStatsValueNameFrameRateInput[] = "googFrameRateInput"; 127 const char StatsReport::kStatsValueNameFrameRateSent[] = "googFrameRateSent"; 128 const char StatsReport::kStatsValueNameFrameWidthInput[] = 129 "googFrameWidthInput"; 130 const char StatsReport::kStatsValueNameFrameWidthReceived[] = 131 "googFrameWidthReceived"; 132 const char StatsReport::kStatsValueNameFrameWidthSent[] = "googFrameWidthSent"; 133 const char StatsReport::kStatsValueNameInitiator[] = "googInitiator"; 134 const char StatsReport::kStatsValueNameIssuerId[] = "googIssuerId"; 135 const char StatsReport::kStatsValueNameJitterReceived[] = "googJitterReceived"; 136 const char StatsReport::kStatsValueNameLocalAddress[] = "googLocalAddress"; 137 const char StatsReport::kStatsValueNameLocalCandidateType[] = 138 "googLocalCandidateType"; 139 const char StatsReport::kStatsValueNameLocalCertificateId[] = 140 "googLocalCertificateId"; 141 const char StatsReport::kStatsValueNameAdaptationChanges[] = 142 "googAdaptationChanges"; 143 const char StatsReport::kStatsValueNameNacksReceived[] = "googNacksReceived"; 144 const char StatsReport::kStatsValueNameNacksSent[] = "googNacksSent"; 145 const char StatsReport::kStatsValueNamePlisReceived[] = "googPlisReceived"; 146 const char StatsReport::kStatsValueNamePlisSent[] = "googPlisSent"; 147 const char StatsReport::kStatsValueNamePacketsReceived[] = "packetsReceived"; 148 const char StatsReport::kStatsValueNamePacketsSent[] = "packetsSent"; 149 const char StatsReport::kStatsValueNamePacketsLost[] = "packetsLost"; 150 const char StatsReport::kStatsValueNamePreferredJitterBufferMs[] = 151 "googPreferredJitterBufferMs"; 152 const char StatsReport::kStatsValueNameReadable[] = "googReadable"; 153 const char StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug[] = 154 "googReceivedPacketGroupArrivalTimeDebug"; 155 const char StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug[] = 156 "googReceivedPacketGroupPropagationDeltaDebug"; 157 const char 158 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug[] = 159 "googReceivedPacketGroupPropagationDeltaSumDebug"; 160 const char StatsReport::kStatsValueNameRemoteAddress[] = "googRemoteAddress"; 161 const char StatsReport::kStatsValueNameRemoteCandidateType[] = 162 "googRemoteCandidateType"; 163 const char StatsReport::kStatsValueNameRemoteCertificateId[] = 164 "googRemoteCertificateId"; 165 const char StatsReport::kStatsValueNameRetransmitBitrate[] = 166 "googRetransmitBitrate"; 167 const char StatsReport::kStatsValueNameRtt[] = "googRtt"; 168 const char StatsReport::kStatsValueNameSsrc[] = "ssrc"; 169 const char StatsReport::kStatsValueNameTargetEncBitrate[] = 170 "googTargetEncBitrate"; 171 const char StatsReport::kStatsValueNameTransmitBitrate[] = 172 "googTransmitBitrate"; 173 const char StatsReport::kStatsValueNameTransportId[] = "transportId"; 174 const char StatsReport::kStatsValueNameTransportType[] = "googTransportType"; 175 const char StatsReport::kStatsValueNameTrackId[] = "googTrackId"; 176 const char StatsReport::kStatsValueNameTypingNoiseState[] = 177 "googTypingNoiseState"; 178 const char StatsReport::kStatsValueNameViewLimitedResolution[] = 179 "googViewLimitedResolution"; 180 const char StatsReport::kStatsValueNameWritable[] = "googWritable"; 181 182 const char StatsReport::kStatsReportTypeSession[] = "googLibjingleSession"; 183 const char StatsReport::kStatsReportTypeBwe[] = "VideoBwe"; 184 const char StatsReport::kStatsReportTypeRemoteSsrc[] = "remoteSsrc"; 185 const char StatsReport::kStatsReportTypeSsrc[] = "ssrc"; 186 const char StatsReport::kStatsReportTypeTrack[] = "googTrack"; 187 const char StatsReport::kStatsReportTypeIceCandidate[] = "iceCandidate"; 188 const char StatsReport::kStatsReportTypeTransport[] = "googTransport"; 189 const char StatsReport::kStatsReportTypeComponent[] = "googComponent"; 190 const char StatsReport::kStatsReportTypeCandidatePair[] = "googCandidatePair"; 191 const char StatsReport::kStatsReportTypeCertificate[] = "googCertificate"; 192 193 const char StatsReport::kStatsReportVideoBweId[] = "bweforvideo"; 194 195 // Implementations of functions in statstypes.h 196 void StatsReport::AddValue(StatsReport::StatsValueName name, 197 const std::string& value) { 198 values.push_back(Value(name, value)); 199 } 200 201 void StatsReport::AddValue(StatsReport::StatsValueName name, int64 value) { 202 AddValue(name, rtc::ToString<int64>(value)); 203 } 204 205 template <typename T> 206 void StatsReport::AddValue(StatsReport::StatsValueName name, 207 const std::vector<T>& value) { 208 std::ostringstream oss; 209 oss << "["; 210 for (size_t i = 0; i < value.size(); ++i) { 211 oss << rtc::ToString<T>(value[i]); 212 if (i != value.size() - 1) 213 oss << ", "; 214 } 215 oss << "]"; 216 AddValue(name, oss.str()); 217 } 218 219 void StatsReport::AddBoolean(StatsReport::StatsValueName name, bool value) { 220 AddValue(name, value ? "true" : "false"); 221 } 222 223 void StatsReport::ReplaceValue(StatsReport::StatsValueName name, 224 const std::string& value) { 225 for (Values::iterator it = values.begin(); it != values.end(); ++it) { 226 if ((*it).name == name) { 227 it->value = value; 228 return; 229 } 230 } 231 // It is not reachable here, add an ASSERT to make sure the overwriting is 232 // always a success. 233 ASSERT(false); 234 } 235 236 namespace { 237 238 double GetTimeNow() { 239 return rtc::Timing::WallTimeNow() * rtc::kNumMillisecsPerSec; 240 } 241 242 bool GetTransportIdFromProxy(const cricket::ProxyTransportMap& map, 243 const std::string& proxy, 244 std::string* transport) { 245 // TODO(hta): Remove handling of empty proxy name once tests do not use it. 246 if (proxy.empty()) { 247 transport->clear(); 248 return true; 249 } 250 251 cricket::ProxyTransportMap::const_iterator found = map.find(proxy); 252 if (found == map.end()) { 253 LOG(LS_ERROR) << "No transport ID mapping for " << proxy; 254 return false; 255 } 256 257 std::ostringstream ost; 258 // Component 1 is always used for RTP. 259 ost << "Channel-" << found->second << "-1"; 260 *transport = ost.str(); 261 return true; 262 } 263 264 std::string StatsId(const std::string& type, const std::string& id) { 265 return type + "_" + id; 266 } 267 268 std::string StatsId(const std::string& type, const std::string& id, 269 StatsCollector::TrackDirection direction) { 270 ASSERT(direction == StatsCollector::kSending || 271 direction == StatsCollector::kReceiving); 272 273 // Strings for the direction of the track. 274 const char kSendDirection[] = "send"; 275 const char kRecvDirection[] = "recv"; 276 277 const std::string direction_id = (direction == StatsCollector::kSending) ? 278 kSendDirection : kRecvDirection; 279 return type + "_" + id + "_" + direction_id; 280 } 281 282 bool ExtractValueFromReport( 283 const StatsReport& report, 284 StatsReport::StatsValueName name, 285 std::string* value) { 286 StatsReport::Values::const_iterator it = report.values.begin(); 287 for (; it != report.values.end(); ++it) { 288 if (it->name == name) { 289 *value = it->value; 290 return true; 291 } 292 } 293 return false; 294 } 295 296 void AddTrackReport(StatsSet* reports, const std::string& track_id) { 297 // Adds an empty track report. 298 StatsReport* report = reports->ReplaceOrAddNew( 299 StatsId(StatsReport::kStatsReportTypeTrack, track_id)); 300 report->type = StatsReport::kStatsReportTypeTrack; 301 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id); 302 } 303 304 template <class TrackVector> 305 void CreateTrackReports(const TrackVector& tracks, StatsSet* reports) { 306 for (size_t j = 0; j < tracks.size(); ++j) { 307 webrtc::MediaStreamTrackInterface* track = tracks[j]; 308 AddTrackReport(reports, track->id()); 309 } 310 } 311 312 void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) { 313 report->AddValue(StatsReport::kStatsValueNameAudioOutputLevel, 314 info.audio_level); 315 report->AddValue(StatsReport::kStatsValueNameBytesReceived, 316 info.bytes_rcvd); 317 report->AddValue(StatsReport::kStatsValueNameJitterReceived, 318 info.jitter_ms); 319 report->AddValue(StatsReport::kStatsValueNameJitterBufferMs, 320 info.jitter_buffer_ms); 321 report->AddValue(StatsReport::kStatsValueNamePreferredJitterBufferMs, 322 info.jitter_buffer_preferred_ms); 323 report->AddValue(StatsReport::kStatsValueNameCurrentDelayMs, 324 info.delay_estimate_ms); 325 report->AddValue(StatsReport::kStatsValueNameExpandRate, 326 rtc::ToString<float>(info.expand_rate)); 327 report->AddValue(StatsReport::kStatsValueNamePacketsReceived, 328 info.packets_rcvd); 329 report->AddValue(StatsReport::kStatsValueNamePacketsLost, 330 info.packets_lost); 331 report->AddValue(StatsReport::kStatsValueNameDecodingCTSG, 332 info.decoding_calls_to_silence_generator); 333 report->AddValue(StatsReport::kStatsValueNameDecodingCTN, 334 info.decoding_calls_to_neteq); 335 report->AddValue(StatsReport::kStatsValueNameDecodingNormal, 336 info.decoding_normal); 337 report->AddValue(StatsReport::kStatsValueNameDecodingPLC, 338 info.decoding_plc); 339 report->AddValue(StatsReport::kStatsValueNameDecodingCNG, 340 info.decoding_cng); 341 report->AddValue(StatsReport::kStatsValueNameDecodingPLCCNG, 342 info.decoding_plc_cng); 343 report->AddValue(StatsReport::kStatsValueNameCaptureStartNtpTimeMs, 344 info.capture_start_ntp_time_ms); 345 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name); 346 } 347 348 void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) { 349 report->AddValue(StatsReport::kStatsValueNameAudioInputLevel, 350 info.audio_level); 351 report->AddValue(StatsReport::kStatsValueNameBytesSent, 352 info.bytes_sent); 353 report->AddValue(StatsReport::kStatsValueNamePacketsSent, 354 info.packets_sent); 355 report->AddValue(StatsReport::kStatsValueNamePacketsLost, 356 info.packets_lost); 357 report->AddValue(StatsReport::kStatsValueNameJitterReceived, 358 info.jitter_ms); 359 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms); 360 report->AddValue(StatsReport::kStatsValueNameEchoCancellationQualityMin, 361 rtc::ToString<float>(info.aec_quality_min)); 362 report->AddValue(StatsReport::kStatsValueNameEchoDelayMedian, 363 info.echo_delay_median_ms); 364 report->AddValue(StatsReport::kStatsValueNameEchoDelayStdDev, 365 info.echo_delay_std_ms); 366 report->AddValue(StatsReport::kStatsValueNameEchoReturnLoss, 367 info.echo_return_loss); 368 report->AddValue(StatsReport::kStatsValueNameEchoReturnLossEnhancement, 369 info.echo_return_loss_enhancement); 370 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name); 371 report->AddBoolean(StatsReport::kStatsValueNameTypingNoiseState, 372 info.typing_noise_detected); 373 } 374 375 void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) { 376 report->AddValue(StatsReport::kStatsValueNameBytesReceived, 377 info.bytes_rcvd); 378 report->AddValue(StatsReport::kStatsValueNamePacketsReceived, 379 info.packets_rcvd); 380 report->AddValue(StatsReport::kStatsValueNamePacketsLost, 381 info.packets_lost); 382 383 report->AddValue(StatsReport::kStatsValueNameFirsSent, 384 info.firs_sent); 385 report->AddValue(StatsReport::kStatsValueNamePlisSent, 386 info.plis_sent); 387 report->AddValue(StatsReport::kStatsValueNameNacksSent, 388 info.nacks_sent); 389 report->AddValue(StatsReport::kStatsValueNameFrameWidthReceived, 390 info.frame_width); 391 report->AddValue(StatsReport::kStatsValueNameFrameHeightReceived, 392 info.frame_height); 393 report->AddValue(StatsReport::kStatsValueNameFrameRateReceived, 394 info.framerate_rcvd); 395 report->AddValue(StatsReport::kStatsValueNameFrameRateDecoded, 396 info.framerate_decoded); 397 report->AddValue(StatsReport::kStatsValueNameFrameRateOutput, 398 info.framerate_output); 399 400 report->AddValue(StatsReport::kStatsValueNameDecodeMs, 401 info.decode_ms); 402 report->AddValue(StatsReport::kStatsValueNameMaxDecodeMs, 403 info.max_decode_ms); 404 report->AddValue(StatsReport::kStatsValueNameCurrentDelayMs, 405 info.current_delay_ms); 406 report->AddValue(StatsReport::kStatsValueNameTargetDelayMs, 407 info.target_delay_ms); 408 report->AddValue(StatsReport::kStatsValueNameJitterBufferMs, 409 info.jitter_buffer_ms); 410 report->AddValue(StatsReport::kStatsValueNameMinPlayoutDelayMs, 411 info.min_playout_delay_ms); 412 report->AddValue(StatsReport::kStatsValueNameRenderDelayMs, 413 info.render_delay_ms); 414 415 report->AddValue(StatsReport::kStatsValueNameCaptureStartNtpTimeMs, 416 info.capture_start_ntp_time_ms); 417 } 418 419 void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) { 420 report->AddValue(StatsReport::kStatsValueNameBytesSent, 421 info.bytes_sent); 422 report->AddValue(StatsReport::kStatsValueNamePacketsSent, 423 info.packets_sent); 424 report->AddValue(StatsReport::kStatsValueNamePacketsLost, 425 info.packets_lost); 426 427 report->AddValue(StatsReport::kStatsValueNameFirsReceived, 428 info.firs_rcvd); 429 report->AddValue(StatsReport::kStatsValueNamePlisReceived, 430 info.plis_rcvd); 431 report->AddValue(StatsReport::kStatsValueNameNacksReceived, 432 info.nacks_rcvd); 433 report->AddValue(StatsReport::kStatsValueNameFrameWidthInput, 434 info.input_frame_width); 435 report->AddValue(StatsReport::kStatsValueNameFrameHeightInput, 436 info.input_frame_height); 437 report->AddValue(StatsReport::kStatsValueNameFrameWidthSent, 438 info.send_frame_width); 439 report->AddValue(StatsReport::kStatsValueNameFrameHeightSent, 440 info.send_frame_height); 441 report->AddValue(StatsReport::kStatsValueNameFrameRateInput, 442 info.framerate_input); 443 report->AddValue(StatsReport::kStatsValueNameFrameRateSent, 444 info.framerate_sent); 445 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt_ms); 446 report->AddValue(StatsReport::kStatsValueNameCodecName, info.codec_name); 447 report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution, 448 (info.adapt_reason & 0x1) > 0); 449 report->AddBoolean(StatsReport::kStatsValueNameBandwidthLimitedResolution, 450 (info.adapt_reason & 0x2) > 0); 451 report->AddBoolean(StatsReport::kStatsValueNameViewLimitedResolution, 452 (info.adapt_reason & 0x4) > 0); 453 report->AddValue(StatsReport::kStatsValueNameAdaptationChanges, 454 info.adapt_changes); 455 report->AddValue(StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms); 456 report->AddValue(StatsReport::kStatsValueNameCaptureJitterMs, 457 info.capture_jitter_ms); 458 report->AddValue(StatsReport::kStatsValueNameCaptureQueueDelayMsPerS, 459 info.capture_queue_delay_ms_per_s); 460 report->AddValue(StatsReport::kStatsValueNameEncodeUsagePercent, 461 info.encode_usage_percent); 462 report->AddValue(StatsReport::kStatsValueNameEncodeRelStdDev, 463 info.encode_rsd); 464 } 465 466 void ExtractStats(const cricket::BandwidthEstimationInfo& info, 467 double stats_gathering_started, 468 PeerConnectionInterface::StatsOutputLevel level, 469 StatsReport* report) { 470 ASSERT(report->id == StatsReport::kStatsReportVideoBweId); 471 report->type = StatsReport::kStatsReportTypeBwe; 472 473 // Clear out stats from previous GatherStats calls if any. 474 if (report->timestamp != stats_gathering_started) { 475 report->values.clear(); 476 report->timestamp = stats_gathering_started; 477 } 478 479 report->AddValue(StatsReport::kStatsValueNameAvailableSendBandwidth, 480 info.available_send_bandwidth); 481 report->AddValue(StatsReport::kStatsValueNameAvailableReceiveBandwidth, 482 info.available_recv_bandwidth); 483 report->AddValue(StatsReport::kStatsValueNameTargetEncBitrate, 484 info.target_enc_bitrate); 485 report->AddValue(StatsReport::kStatsValueNameActualEncBitrate, 486 info.actual_enc_bitrate); 487 report->AddValue(StatsReport::kStatsValueNameRetransmitBitrate, 488 info.retransmit_bitrate); 489 report->AddValue(StatsReport::kStatsValueNameTransmitBitrate, 490 info.transmit_bitrate); 491 report->AddValue(StatsReport::kStatsValueNameBucketDelay, 492 info.bucket_delay); 493 if (level >= PeerConnectionInterface::kStatsOutputLevelDebug) { 494 report->AddValue( 495 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaSumDebug, 496 info.total_received_propagation_delta_ms); 497 if (info.recent_received_propagation_delta_ms.size() > 0) { 498 report->AddValue( 499 StatsReport::kStatsValueNameRecvPacketGroupPropagationDeltaDebug, 500 info.recent_received_propagation_delta_ms); 501 report->AddValue( 502 StatsReport::kStatsValueNameRecvPacketGroupArrivalTimeDebug, 503 info.recent_received_packet_group_arrival_time_ms); 504 } 505 } 506 } 507 508 void ExtractRemoteStats(const cricket::MediaSenderInfo& info, 509 StatsReport* report) { 510 report->timestamp = info.remote_stats[0].timestamp; 511 // TODO(hta): Extract some stats here. 512 } 513 514 void ExtractRemoteStats(const cricket::MediaReceiverInfo& info, 515 StatsReport* report) { 516 report->timestamp = info.remote_stats[0].timestamp; 517 // TODO(hta): Extract some stats here. 518 } 519 520 // Template to extract stats from a data vector. 521 // In order to use the template, the functions that are called from it, 522 // ExtractStats and ExtractRemoteStats, must be defined and overloaded 523 // for each type. 524 template<typename T> 525 void ExtractStatsFromList(const std::vector<T>& data, 526 const std::string& transport_id, 527 StatsCollector* collector, 528 StatsCollector::TrackDirection direction) { 529 typename std::vector<T>::const_iterator it = data.begin(); 530 for (; it != data.end(); ++it) { 531 std::string id; 532 uint32 ssrc = it->ssrc(); 533 // Each track can have stats for both local and remote objects. 534 // TODO(hta): Handle the case of multiple SSRCs per object. 535 StatsReport* report = collector->PrepareLocalReport(ssrc, transport_id, 536 direction); 537 if (report) 538 ExtractStats(*it, report); 539 540 if (it->remote_stats.size() > 0) { 541 report = collector->PrepareRemoteReport(ssrc, transport_id, 542 direction); 543 if (!report) { 544 continue; 545 } 546 ExtractRemoteStats(*it, report); 547 } 548 } 549 } 550 551 } // namespace 552 553 StatsCollector::StatsCollector(WebRtcSession* session) 554 : session_(session), stats_gathering_started_(0) { 555 ASSERT(session_); 556 } 557 558 StatsCollector::~StatsCollector() { 559 } 560 561 // Adds a MediaStream with tracks that can be used as a |selector| in a call 562 // to GetStats. 563 void StatsCollector::AddStream(MediaStreamInterface* stream) { 564 ASSERT(stream != NULL); 565 566 CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(), 567 &reports_); 568 CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(), 569 &reports_); 570 } 571 572 void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track, 573 uint32 ssrc) { 574 ASSERT(audio_track != NULL); 575 for (LocalAudioTrackVector::iterator it = local_audio_tracks_.begin(); 576 it != local_audio_tracks_.end(); ++it) { 577 ASSERT(it->first != audio_track || it->second != ssrc); 578 } 579 580 local_audio_tracks_.push_back(std::make_pair(audio_track, ssrc)); 581 582 // Create the kStatsReportTypeTrack report for the new track if there is no 583 // report yet. 584 StatsReport* found = reports_.Find( 585 StatsId(StatsReport::kStatsReportTypeTrack, audio_track->id())); 586 if (!found) 587 AddTrackReport(&reports_, audio_track->id()); 588 } 589 590 void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track, 591 uint32 ssrc) { 592 ASSERT(audio_track != NULL); 593 for (LocalAudioTrackVector::iterator it = local_audio_tracks_.begin(); 594 it != local_audio_tracks_.end(); ++it) { 595 if (it->first == audio_track && it->second == ssrc) { 596 local_audio_tracks_.erase(it); 597 return; 598 } 599 } 600 601 ASSERT(false); 602 } 603 604 void StatsCollector::GetStats(MediaStreamTrackInterface* track, 605 StatsReports* reports) { 606 ASSERT(reports != NULL); 607 ASSERT(reports->empty()); 608 609 if (!track) { 610 StatsSet::const_iterator it; 611 for (it = reports_.begin(); it != reports_.end(); ++it) 612 reports->push_back(&(*it)); 613 return; 614 } 615 616 StatsReport* report = 617 reports_.Find(StatsId(StatsReport::kStatsReportTypeSession, 618 session_->id())); 619 if (report) 620 reports->push_back(report); 621 622 report = reports_.Find( 623 StatsId(StatsReport::kStatsReportTypeTrack, track->id())); 624 625 if (!report) 626 return; 627 628 reports->push_back(report); 629 630 std::string track_id; 631 for (StatsSet::const_iterator it = reports_.begin(); it != reports_.end(); 632 ++it) { 633 if (it->type != StatsReport::kStatsReportTypeSsrc) 634 continue; 635 636 if (ExtractValueFromReport(*it, 637 StatsReport::kStatsValueNameTrackId, 638 &track_id)) { 639 if (track_id == track->id()) { 640 reports->push_back(&(*it)); 641 } 642 } 643 } 644 } 645 646 void 647 StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) { 648 double time_now = GetTimeNow(); 649 // Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of 650 // ms apart will be ignored. 651 const double kMinGatherStatsPeriod = 50; 652 if (stats_gathering_started_ != 0 && 653 stats_gathering_started_ + kMinGatherStatsPeriod > time_now) { 654 return; 655 } 656 stats_gathering_started_ = time_now; 657 658 if (session_) { 659 ExtractSessionInfo(); 660 ExtractVoiceInfo(); 661 ExtractVideoInfo(level); 662 } 663 } 664 665 StatsReport* StatsCollector::PrepareLocalReport( 666 uint32 ssrc, 667 const std::string& transport_id, 668 TrackDirection direction) { 669 const std::string ssrc_id = rtc::ToString<uint32>(ssrc); 670 StatsReport* report = reports_.Find( 671 StatsId(StatsReport::kStatsReportTypeSsrc, ssrc_id, direction)); 672 673 // Use the ID of the track that is currently mapped to the SSRC, if any. 674 std::string track_id; 675 if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) { 676 if (!report) { 677 // The ssrc is not used by any track or existing report, return NULL 678 // in such case to indicate no report is prepared for the ssrc. 679 return NULL; 680 } 681 682 // The ssrc is not used by any existing track. Keeps the old track id 683 // since we want to report the stats for inactive ssrc. 684 ExtractValueFromReport(*report, 685 StatsReport::kStatsValueNameTrackId, 686 &track_id); 687 } 688 689 report = GetOrCreateReport( 690 StatsReport::kStatsReportTypeSsrc, ssrc_id, direction); 691 692 // Clear out stats from previous GatherStats calls if any. 693 // This is required since the report will be returned for the new values. 694 // Having the old values in the report will lead to multiple values with 695 // the same name. 696 // TODO(xians): Consider changing StatsReport to use map instead of vector. 697 report->values.clear(); 698 report->timestamp = stats_gathering_started_; 699 700 report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id); 701 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id); 702 // Add the mapping of SSRC to transport. 703 report->AddValue(StatsReport::kStatsValueNameTransportId, 704 transport_id); 705 return report; 706 } 707 708 StatsReport* StatsCollector::PrepareRemoteReport( 709 uint32 ssrc, 710 const std::string& transport_id, 711 TrackDirection direction) { 712 const std::string ssrc_id = rtc::ToString<uint32>(ssrc); 713 StatsReport* report = reports_.Find( 714 StatsId(StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction)); 715 716 // Use the ID of the track that is currently mapped to the SSRC, if any. 717 std::string track_id; 718 if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) { 719 if (!report) { 720 // The ssrc is not used by any track or existing report, return NULL 721 // in such case to indicate no report is prepared for the ssrc. 722 return NULL; 723 } 724 725 // The ssrc is not used by any existing track. Keeps the old track id 726 // since we want to report the stats for inactive ssrc. 727 ExtractValueFromReport(*report, 728 StatsReport::kStatsValueNameTrackId, 729 &track_id); 730 } 731 732 report = GetOrCreateReport( 733 StatsReport::kStatsReportTypeRemoteSsrc, ssrc_id, direction); 734 735 // Clear out stats from previous GatherStats calls if any. 736 // The timestamp will be added later. Zero it for debugging. 737 report->values.clear(); 738 report->timestamp = 0; 739 740 report->AddValue(StatsReport::kStatsValueNameSsrc, ssrc_id); 741 report->AddValue(StatsReport::kStatsValueNameTrackId, track_id); 742 // Add the mapping of SSRC to transport. 743 report->AddValue(StatsReport::kStatsValueNameTransportId, 744 transport_id); 745 return report; 746 } 747 748 std::string StatsCollector::AddOneCertificateReport( 749 const rtc::SSLCertificate* cert, const std::string& issuer_id) { 750 // TODO(bemasc): Move this computation to a helper class that caches these 751 // values to reduce CPU use in GetStats. This will require adding a fast 752 // SSLCertificate::Equals() method to detect certificate changes. 753 754 std::string digest_algorithm; 755 if (!cert->GetSignatureDigestAlgorithm(&digest_algorithm)) 756 return std::string(); 757 758 rtc::scoped_ptr<rtc::SSLFingerprint> ssl_fingerprint( 759 rtc::SSLFingerprint::Create(digest_algorithm, cert)); 760 761 // SSLFingerprint::Create can fail if the algorithm returned by 762 // SSLCertificate::GetSignatureDigestAlgorithm is not supported by the 763 // implementation of SSLCertificate::ComputeDigest. This currently happens 764 // with MD5- and SHA-224-signed certificates when linked to libNSS. 765 if (!ssl_fingerprint) 766 return std::string(); 767 768 std::string fingerprint = ssl_fingerprint->GetRfc4572Fingerprint(); 769 770 rtc::Buffer der_buffer; 771 cert->ToDER(&der_buffer); 772 std::string der_base64; 773 rtc::Base64::EncodeFromArray( 774 der_buffer.data(), der_buffer.length(), &der_base64); 775 776 StatsReport* report = reports_.ReplaceOrAddNew( 777 StatsId(StatsReport::kStatsReportTypeCertificate, fingerprint)); 778 report->type = StatsReport::kStatsReportTypeCertificate; 779 report->timestamp = stats_gathering_started_; 780 report->AddValue(StatsReport::kStatsValueNameFingerprint, fingerprint); 781 report->AddValue(StatsReport::kStatsValueNameFingerprintAlgorithm, 782 digest_algorithm); 783 report->AddValue(StatsReport::kStatsValueNameDer, der_base64); 784 if (!issuer_id.empty()) 785 report->AddValue(StatsReport::kStatsValueNameIssuerId, issuer_id); 786 return report->id; 787 } 788 789 std::string StatsCollector::AddCertificateReports( 790 const rtc::SSLCertificate* cert) { 791 // Produces a chain of StatsReports representing this certificate and the rest 792 // of its chain, and adds those reports to |reports_|. The return value is 793 // the id of the leaf report. The provided cert must be non-null, so at least 794 // one report will always be provided and the returned string will never be 795 // empty. 796 ASSERT(cert != NULL); 797 798 std::string issuer_id; 799 rtc::scoped_ptr<rtc::SSLCertChain> chain; 800 if (cert->GetChain(chain.accept())) { 801 // This loop runs in reverse, i.e. from root to leaf, so that each 802 // certificate's issuer's report ID is known before the child certificate's 803 // report is generated. The root certificate does not have an issuer ID 804 // value. 805 for (ptrdiff_t i = chain->GetSize() - 1; i >= 0; --i) { 806 const rtc::SSLCertificate& cert_i = chain->Get(i); 807 issuer_id = AddOneCertificateReport(&cert_i, issuer_id); 808 } 809 } 810 // Add the leaf certificate. 811 return AddOneCertificateReport(cert, issuer_id); 812 } 813 814 void StatsCollector::ExtractSessionInfo() { 815 // Extract information from the base session. 816 StatsReport* report = reports_.ReplaceOrAddNew( 817 StatsId(StatsReport::kStatsReportTypeSession, session_->id())); 818 report->type = StatsReport::kStatsReportTypeSession; 819 report->timestamp = stats_gathering_started_; 820 report->values.clear(); 821 report->AddBoolean(StatsReport::kStatsValueNameInitiator, 822 session_->initiator()); 823 824 cricket::SessionStats stats; 825 if (session_->GetStats(&stats)) { 826 // Store the proxy map away for use in SSRC reporting. 827 proxy_to_transport_ = stats.proxy_to_transport; 828 829 for (cricket::TransportStatsMap::iterator transport_iter 830 = stats.transport_stats.begin(); 831 transport_iter != stats.transport_stats.end(); ++transport_iter) { 832 // Attempt to get a copy of the certificates from the transport and 833 // expose them in stats reports. All channels in a transport share the 834 // same local and remote certificates. 835 // 836 // Note that Transport::GetIdentity and Transport::GetRemoteCertificate 837 // invoke method calls on the worker thread and block this thread, but 838 // messages are still processed on this thread, which may blow way the 839 // existing transports. So we cannot reuse |transport| after these calls. 840 std::string local_cert_report_id, remote_cert_report_id; 841 842 cricket::Transport* transport = 843 session_->GetTransport(transport_iter->second.content_name); 844 rtc::scoped_ptr<rtc::SSLIdentity> identity; 845 if (transport && transport->GetIdentity(identity.accept())) { 846 local_cert_report_id = 847 AddCertificateReports(&(identity->certificate())); 848 } 849 850 transport = session_->GetTransport(transport_iter->second.content_name); 851 rtc::scoped_ptr<rtc::SSLCertificate> cert; 852 if (transport && transport->GetRemoteCertificate(cert.accept())) { 853 remote_cert_report_id = AddCertificateReports(cert.get()); 854 } 855 856 for (cricket::TransportChannelStatsList::iterator channel_iter 857 = transport_iter->second.channel_stats.begin(); 858 channel_iter != transport_iter->second.channel_stats.end(); 859 ++channel_iter) { 860 std::ostringstream ostc; 861 ostc << "Channel-" << transport_iter->second.content_name 862 << "-" << channel_iter->component; 863 StatsReport* channel_report = reports_.ReplaceOrAddNew(ostc.str()); 864 channel_report->type = StatsReport::kStatsReportTypeComponent; 865 channel_report->timestamp = stats_gathering_started_; 866 channel_report->AddValue(StatsReport::kStatsValueNameComponent, 867 channel_iter->component); 868 if (!local_cert_report_id.empty()) 869 channel_report->AddValue( 870 StatsReport::kStatsValueNameLocalCertificateId, 871 local_cert_report_id); 872 if (!remote_cert_report_id.empty()) 873 channel_report->AddValue( 874 StatsReport::kStatsValueNameRemoteCertificateId, 875 remote_cert_report_id); 876 for (size_t i = 0; 877 i < channel_iter->connection_infos.size(); 878 ++i) { 879 std::ostringstream ost; 880 ost << "Conn-" << transport_iter->first << "-" 881 << channel_iter->component << "-" << i; 882 StatsReport* report = reports_.ReplaceOrAddNew(ost.str()); 883 report->type = StatsReport::kStatsReportTypeCandidatePair; 884 report->timestamp = stats_gathering_started_; 885 // Link from connection to its containing channel. 886 report->AddValue(StatsReport::kStatsValueNameChannelId, 887 channel_report->id); 888 889 const cricket::ConnectionInfo& info = 890 channel_iter->connection_infos[i]; 891 report->AddValue(StatsReport::kStatsValueNameBytesSent, 892 info.sent_total_bytes); 893 report->AddValue(StatsReport::kStatsValueNameBytesReceived, 894 info.recv_total_bytes); 895 report->AddBoolean(StatsReport::kStatsValueNameWritable, 896 info.writable); 897 report->AddBoolean(StatsReport::kStatsValueNameReadable, 898 info.readable); 899 report->AddBoolean(StatsReport::kStatsValueNameActiveConnection, 900 info.best_connection); 901 report->AddValue(StatsReport::kStatsValueNameLocalAddress, 902 info.local_candidate.address().ToString()); 903 report->AddValue(StatsReport::kStatsValueNameRemoteAddress, 904 info.remote_candidate.address().ToString()); 905 report->AddValue(StatsReport::kStatsValueNameRtt, info.rtt); 906 report->AddValue(StatsReport::kStatsValueNameTransportType, 907 info.local_candidate.protocol()); 908 report->AddValue(StatsReport::kStatsValueNameLocalCandidateType, 909 info.local_candidate.type()); 910 report->AddValue(StatsReport::kStatsValueNameRemoteCandidateType, 911 info.remote_candidate.type()); 912 } 913 } 914 } 915 } 916 } 917 918 void StatsCollector::ExtractVoiceInfo() { 919 if (!session_->voice_channel()) { 920 return; 921 } 922 cricket::VoiceMediaInfo voice_info; 923 if (!session_->voice_channel()->GetStats(&voice_info)) { 924 LOG(LS_ERROR) << "Failed to get voice channel stats."; 925 return; 926 } 927 std::string transport_id; 928 if (!GetTransportIdFromProxy(proxy_to_transport_, 929 session_->voice_channel()->content_name(), 930 &transport_id)) { 931 LOG(LS_ERROR) << "Failed to get transport name for proxy " 932 << session_->voice_channel()->content_name(); 933 return; 934 } 935 ExtractStatsFromList(voice_info.receivers, transport_id, this, kReceiving); 936 ExtractStatsFromList(voice_info.senders, transport_id, this, kSending); 937 938 UpdateStatsFromExistingLocalAudioTracks(); 939 } 940 941 void StatsCollector::ExtractVideoInfo( 942 PeerConnectionInterface::StatsOutputLevel level) { 943 if (!session_->video_channel()) { 944 return; 945 } 946 cricket::StatsOptions options; 947 options.include_received_propagation_stats = 948 (level >= PeerConnectionInterface::kStatsOutputLevelDebug) ? 949 true : false; 950 cricket::VideoMediaInfo video_info; 951 if (!session_->video_channel()->GetStats(options, &video_info)) { 952 LOG(LS_ERROR) << "Failed to get video channel stats."; 953 return; 954 } 955 std::string transport_id; 956 if (!GetTransportIdFromProxy(proxy_to_transport_, 957 session_->video_channel()->content_name(), 958 &transport_id)) { 959 LOG(LS_ERROR) << "Failed to get transport name for proxy " 960 << session_->video_channel()->content_name(); 961 return; 962 } 963 ExtractStatsFromList(video_info.receivers, transport_id, this, kReceiving); 964 ExtractStatsFromList(video_info.senders, transport_id, this, kSending); 965 if (video_info.bw_estimations.size() != 1) { 966 LOG(LS_ERROR) << "BWEs count: " << video_info.bw_estimations.size(); 967 } else { 968 StatsReport* report = 969 reports_.FindOrAddNew(StatsReport::kStatsReportVideoBweId); 970 ExtractStats( 971 video_info.bw_estimations[0], stats_gathering_started_, level, report); 972 } 973 } 974 975 StatsReport* StatsCollector::GetReport(const std::string& type, 976 const std::string& id, 977 TrackDirection direction) { 978 ASSERT(type == StatsReport::kStatsReportTypeSsrc || 979 type == StatsReport::kStatsReportTypeRemoteSsrc); 980 return reports_.Find(StatsId(type, id, direction)); 981 } 982 983 StatsReport* StatsCollector::GetOrCreateReport(const std::string& type, 984 const std::string& id, 985 TrackDirection direction) { 986 ASSERT(type == StatsReport::kStatsReportTypeSsrc || 987 type == StatsReport::kStatsReportTypeRemoteSsrc); 988 StatsReport* report = GetReport(type, id, direction); 989 if (report == NULL) { 990 std::string statsid = StatsId(type, id, direction); 991 report = reports_.FindOrAddNew(statsid); 992 ASSERT(report->id == statsid); 993 report->type = type; 994 } 995 996 return report; 997 } 998 999 void StatsCollector::UpdateStatsFromExistingLocalAudioTracks() { 1000 // Loop through the existing local audio tracks. 1001 for (LocalAudioTrackVector::const_iterator it = local_audio_tracks_.begin(); 1002 it != local_audio_tracks_.end(); ++it) { 1003 AudioTrackInterface* track = it->first; 1004 uint32 ssrc = it->second; 1005 std::string ssrc_id = rtc::ToString<uint32>(ssrc); 1006 StatsReport* report = GetReport(StatsReport::kStatsReportTypeSsrc, 1007 ssrc_id, 1008 kSending); 1009 if (report == NULL) { 1010 // This can happen if a local audio track is added to a stream on the 1011 // fly and the report has not been set up yet. Do nothing in this case. 1012 LOG(LS_ERROR) << "Stats report does not exist for ssrc " << ssrc; 1013 continue; 1014 } 1015 1016 // The same ssrc can be used by both local and remote audio tracks. 1017 std::string track_id; 1018 if (!ExtractValueFromReport(*report, 1019 StatsReport::kStatsValueNameTrackId, 1020 &track_id) || 1021 track_id != track->id()) { 1022 continue; 1023 } 1024 1025 UpdateReportFromAudioTrack(track, report); 1026 } 1027 } 1028 1029 void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track, 1030 StatsReport* report) { 1031 ASSERT(track != NULL); 1032 if (report == NULL) 1033 return; 1034 1035 int signal_level = 0; 1036 if (track->GetSignalLevel(&signal_level)) { 1037 report->ReplaceValue(StatsReport::kStatsValueNameAudioInputLevel, 1038 rtc::ToString<int>(signal_level)); 1039 } 1040 1041 rtc::scoped_refptr<AudioProcessorInterface> audio_processor( 1042 track->GetAudioProcessor()); 1043 if (audio_processor.get() == NULL) 1044 return; 1045 1046 AudioProcessorInterface::AudioProcessorStats stats; 1047 audio_processor->GetStats(&stats); 1048 report->ReplaceValue(StatsReport::kStatsValueNameTypingNoiseState, 1049 stats.typing_noise_detected ? "true" : "false"); 1050 report->ReplaceValue(StatsReport::kStatsValueNameEchoReturnLoss, 1051 rtc::ToString<int>(stats.echo_return_loss)); 1052 report->ReplaceValue( 1053 StatsReport::kStatsValueNameEchoReturnLossEnhancement, 1054 rtc::ToString<int>(stats.echo_return_loss_enhancement)); 1055 report->ReplaceValue(StatsReport::kStatsValueNameEchoDelayMedian, 1056 rtc::ToString<int>(stats.echo_delay_median_ms)); 1057 report->ReplaceValue(StatsReport::kStatsValueNameEchoCancellationQualityMin, 1058 rtc::ToString<float>(stats.aec_quality_min)); 1059 report->ReplaceValue(StatsReport::kStatsValueNameEchoDelayStdDev, 1060 rtc::ToString<int>(stats.echo_delay_std_ms)); 1061 } 1062 1063 bool StatsCollector::GetTrackIdBySsrc(uint32 ssrc, std::string* track_id, 1064 TrackDirection direction) { 1065 if (direction == kSending) { 1066 if (!session_->GetLocalTrackIdBySsrc(ssrc, track_id)) { 1067 LOG(LS_WARNING) << "The SSRC " << ssrc 1068 << " is not associated with a sending track"; 1069 return false; 1070 } 1071 } else { 1072 ASSERT(direction == kReceiving); 1073 if (!session_->GetRemoteTrackIdBySsrc(ssrc, track_id)) { 1074 LOG(LS_WARNING) << "The SSRC " << ssrc 1075 << " is not associated with a receiving track"; 1076 return false; 1077 } 1078 } 1079 1080 return true; 1081 } 1082 1083 void StatsCollector::ClearUpdateStatsCache() { 1084 stats_gathering_started_ = 0; 1085 } 1086 1087 } // namespace webrtc 1088