mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 13:50:40 +01:00

This new parallel GetStatistics function uses Optionals to indicate if stats are valid or not, and no longer relies on default values. It also takes an argument to indicate if receive streams are present, and if not several stats will not be set. Bug: b/67926135 Change-Id: I175de1c65c414bea6ec9ca8b0b16f07cb2308d9f Reviewed-on: https://webrtc-review.googlesource.com/17942 Commit-Queue: Ivo Creusen <ivoc@webrtc.org> Reviewed-by: Per Kjellander <perkj@webrtc.org> Reviewed-by: Henrik Boström <hbos@webrtc.org> Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org> Cr-Commit-Position: refs/heads/master@{#20789}
1087 lines
42 KiB
C++
1087 lines
42 KiB
C++
/*
|
|
* Copyright 2012 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "pc/statscollector.h"
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "pc/channel.h"
|
|
#include "pc/peerconnection.h"
|
|
#include "rtc_base/base64.h"
|
|
#include "rtc_base/checks.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
// The following is the enum RTCStatsIceCandidateType from
|
|
// http://w3c.github.io/webrtc-stats/#rtcstatsicecandidatetype-enum such that
|
|
// our stats report for ice candidate type could conform to that.
|
|
const char STATSREPORT_LOCAL_PORT_TYPE[] = "host";
|
|
const char STATSREPORT_STUN_PORT_TYPE[] = "serverreflexive";
|
|
const char STATSREPORT_PRFLX_PORT_TYPE[] = "peerreflexive";
|
|
const char STATSREPORT_RELAY_PORT_TYPE[] = "relayed";
|
|
|
|
// Strings used by the stats collector to report adapter types. This fits the
|
|
// general stype of http://w3c.github.io/webrtc-stats than what
|
|
// AdapterTypeToString does.
|
|
const char* STATSREPORT_ADAPTER_TYPE_ETHERNET = "lan";
|
|
const char* STATSREPORT_ADAPTER_TYPE_WIFI = "wlan";
|
|
const char* STATSREPORT_ADAPTER_TYPE_WWAN = "wwan";
|
|
const char* STATSREPORT_ADAPTER_TYPE_VPN = "vpn";
|
|
const char* STATSREPORT_ADAPTER_TYPE_LOOPBACK = "loopback";
|
|
|
|
template<typename ValueType>
|
|
struct TypeForAdd {
|
|
const StatsReport::StatsValueName name;
|
|
const ValueType& value;
|
|
};
|
|
|
|
typedef TypeForAdd<bool> BoolForAdd;
|
|
typedef TypeForAdd<float> FloatForAdd;
|
|
typedef TypeForAdd<int64_t> Int64ForAdd;
|
|
typedef TypeForAdd<int> IntForAdd;
|
|
|
|
StatsReport::Id GetTransportIdFromProxy(
|
|
const std::map<std::string, std::string>& map,
|
|
const std::string& proxy) {
|
|
RTC_DCHECK(!proxy.empty());
|
|
auto found = map.find(proxy);
|
|
if (found == map.end()) {
|
|
return StatsReport::Id();
|
|
}
|
|
|
|
return StatsReport::NewComponentId(
|
|
found->second, cricket::ICE_CANDIDATE_COMPONENT_RTP);
|
|
}
|
|
|
|
StatsReport* AddTrackReport(StatsCollection* reports,
|
|
const std::string& track_id) {
|
|
// Adds an empty track report.
|
|
StatsReport::Id id(
|
|
StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack, track_id));
|
|
StatsReport* report = reports->ReplaceOrAddNew(id);
|
|
report->AddString(StatsReport::kStatsValueNameTrackId, track_id);
|
|
return report;
|
|
}
|
|
|
|
template <class TrackVector>
|
|
void CreateTrackReports(const TrackVector& tracks,
|
|
StatsCollection* reports,
|
|
TrackIdMap* track_ids) {
|
|
for (const auto& track : tracks) {
|
|
const std::string& track_id = track->id();
|
|
StatsReport* report = AddTrackReport(reports, track_id);
|
|
RTC_DCHECK(report != nullptr);
|
|
(*track_ids)[track_id] = report;
|
|
}
|
|
}
|
|
|
|
void ExtractCommonSendProperties(const cricket::MediaSenderInfo& info,
|
|
StatsReport* report) {
|
|
report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name);
|
|
report->AddInt64(StatsReport::kStatsValueNameBytesSent, info.bytes_sent);
|
|
if (info.rtt_ms >= 0) {
|
|
report->AddInt64(StatsReport::kStatsValueNameRtt, info.rtt_ms);
|
|
}
|
|
}
|
|
|
|
void ExtractCommonReceiveProperties(const cricket::MediaReceiverInfo& info,
|
|
StatsReport* report) {
|
|
report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name);
|
|
}
|
|
|
|
void SetAudioProcessingStats(
|
|
StatsReport* report,
|
|
bool typing_noise_detected,
|
|
rtc::Optional<double> echo_return_loss,
|
|
rtc::Optional<double> echo_return_loss_enhancement,
|
|
rtc::Optional<int32_t> echo_delay_median_ms,
|
|
rtc::Optional<double> aec_quality_min,
|
|
rtc::Optional<int32_t> echo_delay_std_ms,
|
|
rtc::Optional<double> residual_echo_likelihood,
|
|
rtc::Optional<double> residual_echo_likelihood_recent_max) {
|
|
report->AddBoolean(StatsReport::kStatsValueNameTypingNoiseState,
|
|
typing_noise_detected);
|
|
// TODO(ivoc): Remove the checks for default values once the whole stat
|
|
// chain uses optionals.
|
|
if (aec_quality_min && *aec_quality_min >= 0.0) {
|
|
report->AddFloat(StatsReport::kStatsValueNameEchoCancellationQualityMin,
|
|
*aec_quality_min);
|
|
}
|
|
if (echo_delay_median_ms && *echo_delay_median_ms >= 0) {
|
|
report->AddInt(StatsReport::kStatsValueNameEchoDelayMedian,
|
|
*echo_delay_median_ms);
|
|
}
|
|
if (echo_delay_std_ms && *echo_delay_std_ms >= 0) {
|
|
report->AddInt(StatsReport::kStatsValueNameEchoDelayStdDev,
|
|
*echo_delay_std_ms);
|
|
}
|
|
// These can take on valid negative values.
|
|
if (echo_return_loss) {
|
|
report->AddInt(StatsReport::kStatsValueNameEchoReturnLoss,
|
|
static_cast<int32_t>(*echo_return_loss));
|
|
}
|
|
if (echo_return_loss_enhancement) {
|
|
report->AddInt(StatsReport::kStatsValueNameEchoReturnLossEnhancement,
|
|
static_cast<int32_t>(*echo_return_loss_enhancement));
|
|
}
|
|
if (residual_echo_likelihood && *residual_echo_likelihood >= 0.0) {
|
|
report->AddFloat(StatsReport::kStatsValueNameResidualEchoLikelihood,
|
|
*residual_echo_likelihood);
|
|
}
|
|
if (residual_echo_likelihood_recent_max &&
|
|
*residual_echo_likelihood_recent_max >= 0.0) {
|
|
report->AddFloat(
|
|
StatsReport::kStatsValueNameResidualEchoLikelihoodRecentMax,
|
|
*residual_echo_likelihood_recent_max);
|
|
}
|
|
}
|
|
|
|
void ExtractStats(const cricket::VoiceReceiverInfo& info, StatsReport* report) {
|
|
ExtractCommonReceiveProperties(info, report);
|
|
const FloatForAdd floats[] = {
|
|
{ StatsReport::kStatsValueNameExpandRate, info.expand_rate },
|
|
{ StatsReport::kStatsValueNameSecondaryDecodedRate,
|
|
info.secondary_decoded_rate },
|
|
{ StatsReport::kStatsValueNameSecondaryDiscardedRate,
|
|
info.secondary_discarded_rate },
|
|
{ StatsReport::kStatsValueNameSpeechExpandRate, info.speech_expand_rate },
|
|
{ StatsReport::kStatsValueNameAccelerateRate, info.accelerate_rate },
|
|
{ StatsReport::kStatsValueNamePreemptiveExpandRate,
|
|
info.preemptive_expand_rate },
|
|
{ StatsReport::kStatsValueNameTotalAudioEnergy, info.total_output_energy },
|
|
{ StatsReport::kStatsValueNameTotalSamplesDuration,
|
|
info.total_output_duration }
|
|
};
|
|
|
|
const IntForAdd ints[] = {
|
|
{ StatsReport::kStatsValueNameCurrentDelayMs, info.delay_estimate_ms },
|
|
{ StatsReport::kStatsValueNameDecodingCNG, info.decoding_cng },
|
|
{ StatsReport::kStatsValueNameDecodingCTN, info.decoding_calls_to_neteq },
|
|
{ StatsReport::kStatsValueNameDecodingCTSG,
|
|
info.decoding_calls_to_silence_generator },
|
|
{ StatsReport::kStatsValueNameDecodingMutedOutput,
|
|
info.decoding_muted_output },
|
|
{ StatsReport::kStatsValueNameDecodingNormal, info.decoding_normal },
|
|
{ StatsReport::kStatsValueNameDecodingPLC, info.decoding_plc },
|
|
{ StatsReport::kStatsValueNameDecodingPLCCNG, info.decoding_plc_cng },
|
|
{ StatsReport::kStatsValueNameJitterBufferMs, info.jitter_buffer_ms },
|
|
{ StatsReport::kStatsValueNameJitterReceived, info.jitter_ms },
|
|
{ StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
|
|
{ StatsReport::kStatsValueNamePacketsReceived, info.packets_rcvd },
|
|
{ StatsReport::kStatsValueNamePreferredJitterBufferMs,
|
|
info.jitter_buffer_preferred_ms },
|
|
};
|
|
|
|
for (const auto& f : floats)
|
|
report->AddFloat(f.name, f.value);
|
|
|
|
for (const auto& i : ints)
|
|
report->AddInt(i.name, i.value);
|
|
if (info.audio_level >= 0) {
|
|
report->AddInt(StatsReport::kStatsValueNameAudioOutputLevel,
|
|
info.audio_level);
|
|
}
|
|
|
|
report->AddInt64(StatsReport::kStatsValueNameBytesReceived,
|
|
info.bytes_rcvd);
|
|
if (info.capture_start_ntp_time_ms >= 0) {
|
|
report->AddInt64(StatsReport::kStatsValueNameCaptureStartNtpTimeMs,
|
|
info.capture_start_ntp_time_ms);
|
|
}
|
|
report->AddString(StatsReport::kStatsValueNameMediaType, "audio");
|
|
}
|
|
|
|
void ExtractStats(const cricket::VoiceSenderInfo& info, StatsReport* report) {
|
|
ExtractCommonSendProperties(info, report);
|
|
|
|
// TODO(ivoc): Update VoiceSenderInfo to pass Optionals all the way from APM.
|
|
SetAudioProcessingStats(
|
|
report, info.typing_noise_detected,
|
|
rtc::Optional<double>(info.echo_return_loss),
|
|
rtc::Optional<double>(info.echo_return_loss_enhancement),
|
|
rtc::Optional<int32_t>(info.echo_delay_median_ms),
|
|
rtc::Optional<double>(info.aec_quality_min),
|
|
rtc::Optional<int32_t>(info.echo_delay_std_ms),
|
|
rtc::Optional<double>(info.residual_echo_likelihood),
|
|
rtc::Optional<double>(info.residual_echo_likelihood_recent_max));
|
|
|
|
const FloatForAdd floats[] = {
|
|
{ StatsReport::kStatsValueNameTotalAudioEnergy, info.total_input_energy },
|
|
{ StatsReport::kStatsValueNameTotalSamplesDuration,
|
|
info.total_input_duration }
|
|
};
|
|
|
|
RTC_DCHECK_GE(info.audio_level, 0);
|
|
const IntForAdd ints[] = {
|
|
{ StatsReport::kStatsValueNameAudioInputLevel, info.audio_level},
|
|
{ StatsReport::kStatsValueNameJitterReceived, info.jitter_ms },
|
|
{ StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
|
|
{ StatsReport::kStatsValueNamePacketsSent, info.packets_sent },
|
|
};
|
|
|
|
for (const auto& f : floats) {
|
|
report->AddFloat(f.name, f.value);
|
|
}
|
|
|
|
for (const auto& i : ints) {
|
|
if (i.value >= 0) {
|
|
report->AddInt(i.name, i.value);
|
|
}
|
|
}
|
|
report->AddString(StatsReport::kStatsValueNameMediaType, "audio");
|
|
if (info.ana_statistics.bitrate_action_counter) {
|
|
report->AddInt(StatsReport::kStatsValueNameAnaBitrateActionCounter,
|
|
*info.ana_statistics.bitrate_action_counter);
|
|
}
|
|
if (info.ana_statistics.channel_action_counter) {
|
|
report->AddInt(StatsReport::kStatsValueNameAnaChannelActionCounter,
|
|
*info.ana_statistics.channel_action_counter);
|
|
}
|
|
if (info.ana_statistics.dtx_action_counter) {
|
|
report->AddInt(StatsReport::kStatsValueNameAnaDtxActionCounter,
|
|
*info.ana_statistics.dtx_action_counter);
|
|
}
|
|
if (info.ana_statistics.fec_action_counter) {
|
|
report->AddInt(StatsReport::kStatsValueNameAnaFecActionCounter,
|
|
*info.ana_statistics.fec_action_counter);
|
|
}
|
|
if (info.ana_statistics.frame_length_increase_counter) {
|
|
report->AddInt(StatsReport::kStatsValueNameAnaFrameLengthIncreaseCounter,
|
|
*info.ana_statistics.frame_length_increase_counter);
|
|
}
|
|
if (info.ana_statistics.frame_length_decrease_counter) {
|
|
report->AddInt(StatsReport::kStatsValueNameAnaFrameLengthDecreaseCounter,
|
|
*info.ana_statistics.frame_length_decrease_counter);
|
|
}
|
|
if (info.ana_statistics.uplink_packet_loss_fraction) {
|
|
report->AddFloat(StatsReport::kStatsValueNameAnaUplinkPacketLossFraction,
|
|
*info.ana_statistics.uplink_packet_loss_fraction);
|
|
}
|
|
}
|
|
|
|
void ExtractStats(const cricket::VideoReceiverInfo& info, StatsReport* report) {
|
|
ExtractCommonReceiveProperties(info, report);
|
|
report->AddString(StatsReport::kStatsValueNameCodecImplementationName,
|
|
info.decoder_implementation_name);
|
|
report->AddInt64(StatsReport::kStatsValueNameBytesReceived,
|
|
info.bytes_rcvd);
|
|
if (info.capture_start_ntp_time_ms >= 0) {
|
|
report->AddInt64(StatsReport::kStatsValueNameCaptureStartNtpTimeMs,
|
|
info.capture_start_ntp_time_ms);
|
|
}
|
|
if (info.qp_sum)
|
|
report->AddInt64(StatsReport::kStatsValueNameQpSum, *info.qp_sum);
|
|
|
|
const IntForAdd ints[] = {
|
|
{ StatsReport::kStatsValueNameCurrentDelayMs, info.current_delay_ms },
|
|
{ StatsReport::kStatsValueNameDecodeMs, info.decode_ms },
|
|
{ StatsReport::kStatsValueNameFirsSent, info.firs_sent },
|
|
{ StatsReport::kStatsValueNameFrameHeightReceived, info.frame_height },
|
|
{ StatsReport::kStatsValueNameFrameRateDecoded, info.framerate_decoded },
|
|
{ StatsReport::kStatsValueNameFrameRateOutput, info.framerate_output },
|
|
{ StatsReport::kStatsValueNameFrameRateReceived, info.framerate_rcvd },
|
|
{ StatsReport::kStatsValueNameFrameWidthReceived, info.frame_width },
|
|
{ StatsReport::kStatsValueNameJitterBufferMs, info.jitter_buffer_ms },
|
|
{ StatsReport::kStatsValueNameMaxDecodeMs, info.max_decode_ms },
|
|
{ StatsReport::kStatsValueNameMinPlayoutDelayMs,
|
|
info.min_playout_delay_ms },
|
|
{ StatsReport::kStatsValueNameNacksSent, info.nacks_sent },
|
|
{ StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
|
|
{ StatsReport::kStatsValueNamePacketsReceived, info.packets_rcvd },
|
|
{ StatsReport::kStatsValueNamePlisSent, info.plis_sent },
|
|
{ StatsReport::kStatsValueNameRenderDelayMs, info.render_delay_ms },
|
|
{ StatsReport::kStatsValueNameTargetDelayMs, info.target_delay_ms },
|
|
{ StatsReport::kStatsValueNameFramesDecoded, info.frames_decoded },
|
|
};
|
|
|
|
for (const auto& i : ints)
|
|
report->AddInt(i.name, i.value);
|
|
report->AddString(StatsReport::kStatsValueNameMediaType, "video");
|
|
|
|
if (info.timing_frame_info) {
|
|
report->AddString(StatsReport::kStatsValueNameTimingFrameInfo,
|
|
info.timing_frame_info->ToString());
|
|
}
|
|
|
|
report->AddInt64(StatsReport::kStatsValueNameInterframeDelayMaxMs,
|
|
info.interframe_delay_max_ms);
|
|
|
|
report->AddString(
|
|
StatsReport::kStatsValueNameContentType,
|
|
webrtc::videocontenttypehelpers::ToString(info.content_type));
|
|
}
|
|
|
|
void ExtractStats(const cricket::VideoSenderInfo& info, StatsReport* report) {
|
|
ExtractCommonSendProperties(info, report);
|
|
|
|
report->AddString(StatsReport::kStatsValueNameCodecImplementationName,
|
|
info.encoder_implementation_name);
|
|
report->AddBoolean(StatsReport::kStatsValueNameBandwidthLimitedResolution,
|
|
(info.adapt_reason & 0x2) > 0);
|
|
report->AddBoolean(StatsReport::kStatsValueNameCpuLimitedResolution,
|
|
(info.adapt_reason & 0x1) > 0);
|
|
report->AddBoolean(StatsReport::kStatsValueNameHasEnteredLowResolution,
|
|
info.has_entered_low_resolution);
|
|
|
|
if (info.qp_sum)
|
|
report->AddInt(StatsReport::kStatsValueNameQpSum, *info.qp_sum);
|
|
|
|
const IntForAdd ints[] = {
|
|
{ StatsReport::kStatsValueNameAdaptationChanges, info.adapt_changes },
|
|
{ StatsReport::kStatsValueNameAvgEncodeMs, info.avg_encode_ms },
|
|
{ StatsReport::kStatsValueNameEncodeUsagePercent,
|
|
info.encode_usage_percent },
|
|
{ StatsReport::kStatsValueNameFirsReceived, info.firs_rcvd },
|
|
{ StatsReport::kStatsValueNameFrameHeightSent, info.send_frame_height },
|
|
{ StatsReport::kStatsValueNameFrameRateInput, info.framerate_input },
|
|
{ StatsReport::kStatsValueNameFrameRateSent, info.framerate_sent },
|
|
{ StatsReport::kStatsValueNameFrameWidthSent, info.send_frame_width },
|
|
{ StatsReport::kStatsValueNameNacksReceived, info.nacks_rcvd },
|
|
{ StatsReport::kStatsValueNamePacketsLost, info.packets_lost },
|
|
{ StatsReport::kStatsValueNamePacketsSent, info.packets_sent },
|
|
{ StatsReport::kStatsValueNamePlisReceived, info.plis_rcvd },
|
|
{ StatsReport::kStatsValueNameFramesEncoded, info.frames_encoded },
|
|
};
|
|
|
|
for (const auto& i : ints)
|
|
report->AddInt(i.name, i.value);
|
|
report->AddString(StatsReport::kStatsValueNameMediaType, "video");
|
|
report->AddString(
|
|
StatsReport::kStatsValueNameContentType,
|
|
webrtc::videocontenttypehelpers::ToString(info.content_type));
|
|
}
|
|
|
|
void ExtractStats(const cricket::BandwidthEstimationInfo& info,
|
|
double stats_gathering_started,
|
|
StatsReport* report) {
|
|
RTC_DCHECK(report->type() == StatsReport::kStatsReportTypeBwe);
|
|
|
|
report->set_timestamp(stats_gathering_started);
|
|
const IntForAdd ints[] = {
|
|
{ StatsReport::kStatsValueNameAvailableSendBandwidth,
|
|
info.available_send_bandwidth },
|
|
{ StatsReport::kStatsValueNameAvailableReceiveBandwidth,
|
|
info.available_recv_bandwidth },
|
|
{ StatsReport::kStatsValueNameTargetEncBitrate, info.target_enc_bitrate },
|
|
{ StatsReport::kStatsValueNameActualEncBitrate, info.actual_enc_bitrate },
|
|
{ StatsReport::kStatsValueNameRetransmitBitrate, info.retransmit_bitrate },
|
|
{ StatsReport::kStatsValueNameTransmitBitrate, info.transmit_bitrate },
|
|
};
|
|
for (const auto& i : ints)
|
|
report->AddInt(i.name, i.value);
|
|
report->AddInt64(StatsReport::kStatsValueNameBucketDelay, info.bucket_delay);
|
|
}
|
|
|
|
void ExtractRemoteStats(const cricket::MediaSenderInfo& info,
|
|
StatsReport* report) {
|
|
report->set_timestamp(info.remote_stats[0].timestamp);
|
|
// TODO(hta): Extract some stats here.
|
|
}
|
|
|
|
void ExtractRemoteStats(const cricket::MediaReceiverInfo& info,
|
|
StatsReport* report) {
|
|
report->set_timestamp(info.remote_stats[0].timestamp);
|
|
// TODO(hta): Extract some stats here.
|
|
}
|
|
|
|
// Template to extract stats from a data vector.
|
|
// In order to use the template, the functions that are called from it,
|
|
// ExtractStats and ExtractRemoteStats, must be defined and overloaded
|
|
// for each type.
|
|
template<typename T>
|
|
void ExtractStatsFromList(const std::vector<T>& data,
|
|
const StatsReport::Id& transport_id,
|
|
StatsCollector* collector,
|
|
StatsReport::Direction direction) {
|
|
for (const auto& d : data) {
|
|
uint32_t ssrc = d.ssrc();
|
|
// Each track can have stats for both local and remote objects.
|
|
// TODO(hta): Handle the case of multiple SSRCs per object.
|
|
StatsReport* report = collector->PrepareReport(true, ssrc, transport_id,
|
|
direction);
|
|
if (report)
|
|
ExtractStats(d, report);
|
|
|
|
if (!d.remote_stats.empty()) {
|
|
report = collector->PrepareReport(false, ssrc, transport_id, direction);
|
|
if (report)
|
|
ExtractRemoteStats(d, report);
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const char* IceCandidateTypeToStatsType(const std::string& candidate_type) {
|
|
if (candidate_type == cricket::LOCAL_PORT_TYPE) {
|
|
return STATSREPORT_LOCAL_PORT_TYPE;
|
|
}
|
|
if (candidate_type == cricket::STUN_PORT_TYPE) {
|
|
return STATSREPORT_STUN_PORT_TYPE;
|
|
}
|
|
if (candidate_type == cricket::PRFLX_PORT_TYPE) {
|
|
return STATSREPORT_PRFLX_PORT_TYPE;
|
|
}
|
|
if (candidate_type == cricket::RELAY_PORT_TYPE) {
|
|
return STATSREPORT_RELAY_PORT_TYPE;
|
|
}
|
|
RTC_NOTREACHED();
|
|
return "unknown";
|
|
}
|
|
|
|
const char* AdapterTypeToStatsType(rtc::AdapterType type) {
|
|
switch (type) {
|
|
case rtc::ADAPTER_TYPE_UNKNOWN:
|
|
return "unknown";
|
|
case rtc::ADAPTER_TYPE_ETHERNET:
|
|
return STATSREPORT_ADAPTER_TYPE_ETHERNET;
|
|
case rtc::ADAPTER_TYPE_WIFI:
|
|
return STATSREPORT_ADAPTER_TYPE_WIFI;
|
|
case rtc::ADAPTER_TYPE_CELLULAR:
|
|
return STATSREPORT_ADAPTER_TYPE_WWAN;
|
|
case rtc::ADAPTER_TYPE_VPN:
|
|
return STATSREPORT_ADAPTER_TYPE_VPN;
|
|
case rtc::ADAPTER_TYPE_LOOPBACK:
|
|
return STATSREPORT_ADAPTER_TYPE_LOOPBACK;
|
|
default:
|
|
RTC_NOTREACHED();
|
|
return "";
|
|
}
|
|
}
|
|
|
|
StatsCollector::StatsCollector(PeerConnection* pc)
|
|
: pc_(pc), stats_gathering_started_(0) {
|
|
RTC_DCHECK(pc_);
|
|
}
|
|
|
|
StatsCollector::~StatsCollector() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
}
|
|
|
|
// Wallclock time in ms.
|
|
double StatsCollector::GetTimeNow() {
|
|
return rtc::TimeUTCMicros() /
|
|
static_cast<double>(rtc::kNumMicrosecsPerMillisec);
|
|
}
|
|
|
|
// Adds a MediaStream with tracks that can be used as a |selector| in a call
|
|
// to GetStats.
|
|
void StatsCollector::AddStream(MediaStreamInterface* stream) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
RTC_DCHECK(stream != NULL);
|
|
|
|
CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(), &reports_,
|
|
&track_ids_);
|
|
CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(), &reports_,
|
|
&track_ids_);
|
|
}
|
|
|
|
void StatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track,
|
|
uint32_t ssrc) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
RTC_DCHECK(audio_track != NULL);
|
|
#if RTC_DCHECK_IS_ON
|
|
for (const auto& track : local_audio_tracks_)
|
|
RTC_DCHECK(track.first != audio_track || track.second != ssrc);
|
|
#endif
|
|
|
|
local_audio_tracks_.push_back(std::make_pair(audio_track, ssrc));
|
|
|
|
// Create the kStatsReportTypeTrack report for the new track if there is no
|
|
// report yet.
|
|
StatsReport::Id id(StatsReport::NewTypedId(StatsReport::kStatsReportTypeTrack,
|
|
audio_track->id()));
|
|
StatsReport* report = reports_.Find(id);
|
|
if (!report) {
|
|
report = reports_.InsertNew(id);
|
|
report->AddString(StatsReport::kStatsValueNameTrackId, audio_track->id());
|
|
}
|
|
}
|
|
|
|
void StatsCollector::RemoveLocalAudioTrack(AudioTrackInterface* audio_track,
|
|
uint32_t ssrc) {
|
|
RTC_DCHECK(audio_track != NULL);
|
|
local_audio_tracks_.erase(
|
|
std::remove_if(
|
|
local_audio_tracks_.begin(), local_audio_tracks_.end(),
|
|
[audio_track, ssrc](const LocalAudioTrackVector::value_type& track) {
|
|
return track.first == audio_track && track.second == ssrc;
|
|
}),
|
|
local_audio_tracks_.end());
|
|
}
|
|
|
|
void StatsCollector::GetStats(MediaStreamTrackInterface* track,
|
|
StatsReports* reports) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
RTC_DCHECK(reports != NULL);
|
|
RTC_DCHECK(reports->empty());
|
|
|
|
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
|
|
|
|
if (!track) {
|
|
reports->reserve(reports_.size());
|
|
for (auto* r : reports_)
|
|
reports->push_back(r);
|
|
return;
|
|
}
|
|
|
|
StatsReport* report = reports_.Find(StatsReport::NewTypedId(
|
|
StatsReport::kStatsReportTypeSession, pc_->session_id()));
|
|
if (report)
|
|
reports->push_back(report);
|
|
|
|
report = reports_.Find(StatsReport::NewTypedId(
|
|
StatsReport::kStatsReportTypeTrack, track->id()));
|
|
|
|
if (!report)
|
|
return;
|
|
|
|
reports->push_back(report);
|
|
|
|
std::string track_id;
|
|
for (const auto* r : reports_) {
|
|
if (r->type() != StatsReport::kStatsReportTypeSsrc)
|
|
continue;
|
|
|
|
const StatsReport::Value* v =
|
|
r->FindValue(StatsReport::kStatsValueNameTrackId);
|
|
if (v && v->string_val() == track->id())
|
|
reports->push_back(r);
|
|
}
|
|
}
|
|
|
|
void
|
|
StatsCollector::UpdateStats(PeerConnectionInterface::StatsOutputLevel level) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
double time_now = GetTimeNow();
|
|
// Calls to UpdateStats() that occur less than kMinGatherStatsPeriod number of
|
|
// ms apart will be ignored.
|
|
const double kMinGatherStatsPeriod = 50;
|
|
if (stats_gathering_started_ != 0 &&
|
|
stats_gathering_started_ + kMinGatherStatsPeriod > time_now) {
|
|
return;
|
|
}
|
|
stats_gathering_started_ = time_now;
|
|
|
|
// TODO(tommi): All of these hop over to the worker thread to fetch
|
|
// information. We could use an AsyncInvoker to run all of these and post
|
|
// the information back to the signaling thread where we can create and
|
|
// update stats reports. That would also clean up the threading story a bit
|
|
// since we'd be creating/updating the stats report objects consistently on
|
|
// the same thread (this class has no locks right now).
|
|
ExtractSessionInfo();
|
|
ExtractBweInfo();
|
|
ExtractVoiceInfo();
|
|
ExtractVideoInfo(level);
|
|
ExtractSenderInfo();
|
|
ExtractDataInfo();
|
|
UpdateTrackReports();
|
|
}
|
|
|
|
StatsReport* StatsCollector::PrepareReport(
|
|
bool local,
|
|
uint32_t ssrc,
|
|
const StatsReport::Id& transport_id,
|
|
StatsReport::Direction direction) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
StatsReport::Id id(StatsReport::NewIdWithDirection(
|
|
local ? StatsReport::kStatsReportTypeSsrc
|
|
: StatsReport::kStatsReportTypeRemoteSsrc,
|
|
rtc::ToString<uint32_t>(ssrc), direction));
|
|
StatsReport* report = reports_.Find(id);
|
|
|
|
// Use the ID of the track that is currently mapped to the SSRC, if any.
|
|
std::string track_id;
|
|
if (!GetTrackIdBySsrc(ssrc, &track_id, direction)) {
|
|
if (!report) {
|
|
// The ssrc is not used by any track or existing report, return NULL
|
|
// in such case to indicate no report is prepared for the ssrc.
|
|
return NULL;
|
|
}
|
|
|
|
// The ssrc is not used by any existing track. Keeps the old track id
|
|
// since we want to report the stats for inactive ssrc.
|
|
const StatsReport::Value* v =
|
|
report->FindValue(StatsReport::kStatsValueNameTrackId);
|
|
if (v)
|
|
track_id = v->string_val();
|
|
}
|
|
|
|
if (!report)
|
|
report = reports_.InsertNew(id);
|
|
|
|
// FYI - for remote reports, the timestamp will be overwritten later.
|
|
report->set_timestamp(stats_gathering_started_);
|
|
|
|
report->AddInt64(StatsReport::kStatsValueNameSsrc, ssrc);
|
|
report->AddString(StatsReport::kStatsValueNameTrackId, track_id);
|
|
// Add the mapping of SSRC to transport.
|
|
report->AddId(StatsReport::kStatsValueNameTransportId, transport_id);
|
|
return report;
|
|
}
|
|
|
|
bool StatsCollector::IsValidTrack(const std::string& track_id) {
|
|
return reports_.Find(StatsReport::NewTypedId(
|
|
StatsReport::kStatsReportTypeTrack, track_id)) != nullptr;
|
|
}
|
|
|
|
StatsReport* StatsCollector::AddCertificateReports(
|
|
const rtc::SSLCertificate* cert) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
RTC_DCHECK(cert != NULL);
|
|
|
|
std::unique_ptr<rtc::SSLCertificateStats> first_stats = cert->GetStats();
|
|
StatsReport* first_report = nullptr;
|
|
StatsReport* prev_report = nullptr;
|
|
for (rtc::SSLCertificateStats* stats = first_stats.get(); stats;
|
|
stats = stats->issuer.get()) {
|
|
StatsReport::Id id(StatsReport::NewTypedId(
|
|
StatsReport::kStatsReportTypeCertificate, stats->fingerprint));
|
|
|
|
StatsReport* report = reports_.ReplaceOrAddNew(id);
|
|
report->set_timestamp(stats_gathering_started_);
|
|
report->AddString(StatsReport::kStatsValueNameFingerprint,
|
|
stats->fingerprint);
|
|
report->AddString(StatsReport::kStatsValueNameFingerprintAlgorithm,
|
|
stats->fingerprint_algorithm);
|
|
report->AddString(StatsReport::kStatsValueNameDer,
|
|
stats->base64_certificate);
|
|
if (!first_report)
|
|
first_report = report;
|
|
else
|
|
prev_report->AddId(StatsReport::kStatsValueNameIssuerId, id);
|
|
prev_report = report;
|
|
}
|
|
return first_report;
|
|
}
|
|
|
|
StatsReport* StatsCollector::AddConnectionInfoReport(
|
|
const std::string& content_name, int component, int connection_id,
|
|
const StatsReport::Id& channel_report_id,
|
|
const cricket::ConnectionInfo& info) {
|
|
StatsReport::Id id(StatsReport::NewCandidatePairId(content_name, component,
|
|
connection_id));
|
|
StatsReport* report = reports_.ReplaceOrAddNew(id);
|
|
report->set_timestamp(stats_gathering_started_);
|
|
|
|
const BoolForAdd bools[] = {
|
|
{StatsReport::kStatsValueNameActiveConnection, info.best_connection},
|
|
{StatsReport::kStatsValueNameReceiving, info.receiving},
|
|
{StatsReport::kStatsValueNameWritable, info.writable},
|
|
};
|
|
for (const auto& b : bools)
|
|
report->AddBoolean(b.name, b.value);
|
|
|
|
report->AddId(StatsReport::kStatsValueNameChannelId, channel_report_id);
|
|
report->AddId(StatsReport::kStatsValueNameLocalCandidateId,
|
|
AddCandidateReport(info.local_candidate, true)->id());
|
|
report->AddId(StatsReport::kStatsValueNameRemoteCandidateId,
|
|
AddCandidateReport(info.remote_candidate, false)->id());
|
|
|
|
const Int64ForAdd int64s[] = {
|
|
{StatsReport::kStatsValueNameBytesReceived, info.recv_total_bytes},
|
|
{StatsReport::kStatsValueNameBytesSent, info.sent_total_bytes},
|
|
{StatsReport::kStatsValueNamePacketsSent, info.sent_total_packets},
|
|
{StatsReport::kStatsValueNameRtt, info.rtt},
|
|
{StatsReport::kStatsValueNameSendPacketsDiscarded,
|
|
info.sent_discarded_packets},
|
|
{StatsReport::kStatsValueNameSentPingRequestsTotal,
|
|
info.sent_ping_requests_total},
|
|
{StatsReport::kStatsValueNameSentPingRequestsBeforeFirstResponse,
|
|
info.sent_ping_requests_before_first_response},
|
|
{StatsReport::kStatsValueNameSentPingResponses, info.sent_ping_responses},
|
|
{StatsReport::kStatsValueNameRecvPingRequests, info.recv_ping_requests},
|
|
{StatsReport::kStatsValueNameRecvPingResponses, info.recv_ping_responses},
|
|
};
|
|
for (const auto& i : int64s)
|
|
report->AddInt64(i.name, i.value);
|
|
|
|
report->AddString(StatsReport::kStatsValueNameLocalAddress,
|
|
info.local_candidate.address().ToString());
|
|
report->AddString(StatsReport::kStatsValueNameLocalCandidateType,
|
|
info.local_candidate.type());
|
|
report->AddString(StatsReport::kStatsValueNameRemoteAddress,
|
|
info.remote_candidate.address().ToString());
|
|
report->AddString(StatsReport::kStatsValueNameRemoteCandidateType,
|
|
info.remote_candidate.type());
|
|
report->AddString(StatsReport::kStatsValueNameTransportType,
|
|
info.local_candidate.protocol());
|
|
|
|
return report;
|
|
}
|
|
|
|
StatsReport* StatsCollector::AddCandidateReport(
|
|
const cricket::Candidate& candidate,
|
|
bool local) {
|
|
StatsReport::Id id(StatsReport::NewCandidateId(local, candidate.id()));
|
|
StatsReport* report = reports_.Find(id);
|
|
if (!report) {
|
|
report = reports_.InsertNew(id);
|
|
report->set_timestamp(stats_gathering_started_);
|
|
if (local) {
|
|
report->AddString(StatsReport::kStatsValueNameCandidateNetworkType,
|
|
AdapterTypeToStatsType(candidate.network_type()));
|
|
}
|
|
report->AddString(StatsReport::kStatsValueNameCandidateIPAddress,
|
|
candidate.address().ipaddr().ToString());
|
|
report->AddString(StatsReport::kStatsValueNameCandidatePortNumber,
|
|
candidate.address().PortAsString());
|
|
report->AddInt(StatsReport::kStatsValueNameCandidatePriority,
|
|
candidate.priority());
|
|
report->AddString(StatsReport::kStatsValueNameCandidateType,
|
|
IceCandidateTypeToStatsType(candidate.type()));
|
|
report->AddString(StatsReport::kStatsValueNameCandidateTransportType,
|
|
candidate.protocol());
|
|
}
|
|
|
|
return report;
|
|
}
|
|
|
|
void StatsCollector::ExtractSessionInfo() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
// Extract information from the base session.
|
|
StatsReport::Id id(StatsReport::NewTypedId(
|
|
StatsReport::kStatsReportTypeSession, pc_->session_id()));
|
|
StatsReport* report = reports_.ReplaceOrAddNew(id);
|
|
report->set_timestamp(stats_gathering_started_);
|
|
report->AddBoolean(StatsReport::kStatsValueNameInitiator,
|
|
pc_->initial_offerer());
|
|
|
|
std::unique_ptr<SessionStats> stats = pc_->GetSessionStats_s();
|
|
if (!stats) {
|
|
return;
|
|
}
|
|
|
|
// Store the proxy map away for use in SSRC reporting.
|
|
// TODO(tommi): This shouldn't be necessary if we post the stats back to the
|
|
// signaling thread after fetching them on the worker thread, then just use
|
|
// the proxy map directly from the session stats.
|
|
// As is, if GetStats() failed, we could be using old (incorrect?) proxy
|
|
// data.
|
|
proxy_to_transport_ = stats->proxy_to_transport;
|
|
|
|
for (const auto& transport_iter : stats->transport_stats) {
|
|
// Attempt to get a copy of the certificates from the transport and
|
|
// expose them in stats reports. All channels in a transport share the
|
|
// same local and remote certificates.
|
|
//
|
|
StatsReport::Id local_cert_report_id, remote_cert_report_id;
|
|
rtc::scoped_refptr<rtc::RTCCertificate> certificate;
|
|
if (pc_->GetLocalCertificate(transport_iter.second.transport_name,
|
|
&certificate)) {
|
|
StatsReport* r = AddCertificateReports(&(certificate->ssl_certificate()));
|
|
if (r)
|
|
local_cert_report_id = r->id();
|
|
}
|
|
|
|
std::unique_ptr<rtc::SSLCertificate> cert =
|
|
pc_->GetRemoteSSLCertificate(transport_iter.second.transport_name);
|
|
if (cert) {
|
|
StatsReport* r = AddCertificateReports(cert.get());
|
|
if (r)
|
|
remote_cert_report_id = r->id();
|
|
}
|
|
|
|
for (const auto& channel_iter : transport_iter.second.channel_stats) {
|
|
StatsReport::Id id(StatsReport::NewComponentId(
|
|
transport_iter.second.transport_name, channel_iter.component));
|
|
StatsReport* channel_report = reports_.ReplaceOrAddNew(id);
|
|
channel_report->set_timestamp(stats_gathering_started_);
|
|
channel_report->AddInt(StatsReport::kStatsValueNameComponent,
|
|
channel_iter.component);
|
|
if (local_cert_report_id.get()) {
|
|
channel_report->AddId(StatsReport::kStatsValueNameLocalCertificateId,
|
|
local_cert_report_id);
|
|
}
|
|
if (remote_cert_report_id.get()) {
|
|
channel_report->AddId(StatsReport::kStatsValueNameRemoteCertificateId,
|
|
remote_cert_report_id);
|
|
}
|
|
int srtp_crypto_suite = channel_iter.srtp_crypto_suite;
|
|
if (srtp_crypto_suite != rtc::SRTP_INVALID_CRYPTO_SUITE &&
|
|
rtc::SrtpCryptoSuiteToName(srtp_crypto_suite).length()) {
|
|
channel_report->AddString(
|
|
StatsReport::kStatsValueNameSrtpCipher,
|
|
rtc::SrtpCryptoSuiteToName(srtp_crypto_suite));
|
|
}
|
|
int ssl_cipher_suite = channel_iter.ssl_cipher_suite;
|
|
if (ssl_cipher_suite != rtc::TLS_NULL_WITH_NULL_NULL &&
|
|
rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite)
|
|
.length()) {
|
|
channel_report->AddString(
|
|
StatsReport::kStatsValueNameDtlsCipher,
|
|
rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite));
|
|
}
|
|
|
|
int connection_id = 0;
|
|
for (const cricket::ConnectionInfo& info :
|
|
channel_iter.connection_infos) {
|
|
StatsReport* connection_report = AddConnectionInfoReport(
|
|
transport_iter.first, channel_iter.component, connection_id++,
|
|
channel_report->id(), info);
|
|
if (info.best_connection) {
|
|
channel_report->AddId(
|
|
StatsReport::kStatsValueNameSelectedCandidatePairId,
|
|
connection_report->id());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void StatsCollector::ExtractBweInfo() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
if (pc_->signaling_state() == PeerConnectionInterface::kClosed)
|
|
return;
|
|
|
|
webrtc::Call::Stats call_stats = pc_->GetCallStats();
|
|
cricket::BandwidthEstimationInfo bwe_info;
|
|
bwe_info.available_send_bandwidth = call_stats.send_bandwidth_bps;
|
|
bwe_info.available_recv_bandwidth = call_stats.recv_bandwidth_bps;
|
|
bwe_info.bucket_delay = call_stats.pacer_delay_ms;
|
|
// Fill in target encoder bitrate, actual encoder bitrate, rtx bitrate, etc.
|
|
// TODO(holmer): Also fill this in for audio.
|
|
if (pc_->video_channel()) {
|
|
pc_->video_channel()->FillBitrateInfo(&bwe_info);
|
|
}
|
|
StatsReport::Id report_id(StatsReport::NewBandwidthEstimationId());
|
|
StatsReport* report = reports_.FindOrAddNew(report_id);
|
|
ExtractStats(bwe_info, stats_gathering_started_, report);
|
|
}
|
|
|
|
void StatsCollector::ExtractVoiceInfo() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
if (!pc_->voice_channel()) {
|
|
return;
|
|
}
|
|
cricket::VoiceMediaInfo voice_info;
|
|
if (!pc_->voice_channel()->GetStats(&voice_info)) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get voice channel stats.";
|
|
return;
|
|
}
|
|
|
|
// TODO(tommi): The above code should run on the worker thread and post the
|
|
// results back to the signaling thread, where we can add data to the reports.
|
|
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
|
|
|
|
StatsReport::Id transport_id(GetTransportIdFromProxy(
|
|
proxy_to_transport_, pc_->voice_channel()->content_name()));
|
|
if (!transport_id.get()) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get transport name for proxy "
|
|
<< pc_->voice_channel()->content_name();
|
|
return;
|
|
}
|
|
|
|
ExtractStatsFromList(voice_info.receivers, transport_id, this,
|
|
StatsReport::kReceive);
|
|
ExtractStatsFromList(voice_info.senders, transport_id, this,
|
|
StatsReport::kSend);
|
|
|
|
UpdateStatsFromExistingLocalAudioTracks(voice_info.receivers.size() > 0);
|
|
}
|
|
|
|
void StatsCollector::ExtractVideoInfo(
|
|
PeerConnectionInterface::StatsOutputLevel level) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
if (!pc_->video_channel())
|
|
return;
|
|
|
|
cricket::VideoMediaInfo video_info;
|
|
if (!pc_->video_channel()->GetStats(&video_info)) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get video channel stats.";
|
|
return;
|
|
}
|
|
|
|
// TODO(tommi): The above code should run on the worker thread and post the
|
|
// results back to the signaling thread, where we can add data to the reports.
|
|
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
|
|
|
|
StatsReport::Id transport_id(GetTransportIdFromProxy(
|
|
proxy_to_transport_, pc_->video_channel()->content_name()));
|
|
if (!transport_id.get()) {
|
|
RTC_LOG(LS_ERROR) << "Failed to get transport name for proxy "
|
|
<< pc_->video_channel()->content_name();
|
|
return;
|
|
}
|
|
ExtractStatsFromList(video_info.receivers, transport_id, this,
|
|
StatsReport::kReceive);
|
|
ExtractStatsFromList(video_info.senders, transport_id, this,
|
|
StatsReport::kSend);
|
|
}
|
|
|
|
void StatsCollector::ExtractSenderInfo() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
for (const auto& sender : pc_->GetSenders()) {
|
|
// TODO(nisse): SSRC == 0 currently means none. Delete check when
|
|
// that is fixed.
|
|
if (!sender->ssrc()) {
|
|
continue;
|
|
}
|
|
const rtc::scoped_refptr<MediaStreamTrackInterface> track(sender->track());
|
|
if (!track || track->kind() != MediaStreamTrackInterface::kVideoKind) {
|
|
continue;
|
|
}
|
|
// Safe, because kind() == kVideoKind implies a subclass of
|
|
// VideoTrackInterface; see mediastreaminterface.h.
|
|
VideoTrackSourceInterface* source =
|
|
static_cast<VideoTrackInterface*>(track.get())->GetSource();
|
|
|
|
VideoTrackSourceInterface::Stats stats;
|
|
if (!source->GetStats(&stats)) {
|
|
continue;
|
|
}
|
|
const StatsReport::Id stats_id = StatsReport::NewIdWithDirection(
|
|
StatsReport::kStatsReportTypeSsrc,
|
|
rtc::ToString<uint32_t>(sender->ssrc()), StatsReport::kSend);
|
|
StatsReport* report = reports_.FindOrAddNew(stats_id);
|
|
report->AddInt(StatsReport::kStatsValueNameFrameWidthInput,
|
|
stats.input_width);
|
|
report->AddInt(StatsReport::kStatsValueNameFrameHeightInput,
|
|
stats.input_height);
|
|
}
|
|
}
|
|
|
|
void StatsCollector::ExtractDataInfo() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
|
|
|
|
for (const auto& dc : pc_->sctp_data_channels()) {
|
|
StatsReport::Id id(StatsReport::NewTypedIntId(
|
|
StatsReport::kStatsReportTypeDataChannel, dc->id()));
|
|
StatsReport* report = reports_.ReplaceOrAddNew(id);
|
|
report->set_timestamp(stats_gathering_started_);
|
|
report->AddString(StatsReport::kStatsValueNameLabel, dc->label());
|
|
// Filter out the initial id (-1).
|
|
if (dc->id() >= 0) {
|
|
report->AddInt(StatsReport::kStatsValueNameDataChannelId, dc->id());
|
|
}
|
|
report->AddString(StatsReport::kStatsValueNameProtocol, dc->protocol());
|
|
report->AddString(StatsReport::kStatsValueNameState,
|
|
DataChannelInterface::DataStateString(dc->state()));
|
|
}
|
|
}
|
|
|
|
StatsReport* StatsCollector::GetReport(const StatsReport::StatsType& type,
|
|
const std::string& id,
|
|
StatsReport::Direction direction) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
RTC_DCHECK(type == StatsReport::kStatsReportTypeSsrc ||
|
|
type == StatsReport::kStatsReportTypeRemoteSsrc);
|
|
return reports_.Find(StatsReport::NewIdWithDirection(type, id, direction));
|
|
}
|
|
|
|
void StatsCollector::UpdateStatsFromExistingLocalAudioTracks(
|
|
bool has_remote_tracks) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
// Loop through the existing local audio tracks.
|
|
for (const auto& it : local_audio_tracks_) {
|
|
AudioTrackInterface* track = it.first;
|
|
uint32_t ssrc = it.second;
|
|
StatsReport* report =
|
|
GetReport(StatsReport::kStatsReportTypeSsrc,
|
|
rtc::ToString<uint32_t>(ssrc), StatsReport::kSend);
|
|
if (report == NULL) {
|
|
// This can happen if a local audio track is added to a stream on the
|
|
// fly and the report has not been set up yet. Do nothing in this case.
|
|
RTC_LOG(LS_ERROR) << "Stats report does not exist for ssrc " << ssrc;
|
|
continue;
|
|
}
|
|
|
|
// The same ssrc can be used by both local and remote audio tracks.
|
|
const StatsReport::Value* v =
|
|
report->FindValue(StatsReport::kStatsValueNameTrackId);
|
|
if (!v || v->string_val() != track->id())
|
|
continue;
|
|
|
|
report->set_timestamp(stats_gathering_started_);
|
|
UpdateReportFromAudioTrack(track, report, has_remote_tracks);
|
|
}
|
|
}
|
|
|
|
void StatsCollector::UpdateReportFromAudioTrack(AudioTrackInterface* track,
|
|
StatsReport* report,
|
|
bool has_remote_tracks) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
RTC_DCHECK(track != NULL);
|
|
|
|
// Don't overwrite report values if they're not available.
|
|
int signal_level;
|
|
if (track->GetSignalLevel(&signal_level)) {
|
|
RTC_DCHECK_GE(signal_level, 0);
|
|
report->AddInt(StatsReport::kStatsValueNameAudioInputLevel, signal_level);
|
|
}
|
|
|
|
auto audio_processor(track->GetAudioProcessor());
|
|
|
|
if (audio_processor.get()) {
|
|
AudioProcessorInterface::AudioProcessorStatistics stats =
|
|
audio_processor->GetStats(has_remote_tracks);
|
|
|
|
SetAudioProcessingStats(
|
|
report, stats.typing_noise_detected, stats.echo_return_loss,
|
|
stats.echo_return_loss_enhancement, stats.echo_delay_median_ms,
|
|
stats.aec_quality_min, stats.echo_delay_std_ms,
|
|
stats.residual_echo_likelihood,
|
|
stats.residual_echo_likelihood_recent_max);
|
|
|
|
if (stats.aec_divergent_filter_fraction) {
|
|
report->AddFloat(StatsReport::kStatsValueNameAecDivergentFilterFraction,
|
|
*stats.aec_divergent_filter_fraction);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool StatsCollector::GetTrackIdBySsrc(uint32_t ssrc,
|
|
std::string* track_id,
|
|
StatsReport::Direction direction) {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
if (direction == StatsReport::kSend) {
|
|
if (!pc_->GetLocalTrackIdBySsrc(ssrc, track_id)) {
|
|
RTC_LOG(LS_WARNING) << "The SSRC " << ssrc
|
|
<< " is not associated with a sending track";
|
|
return false;
|
|
}
|
|
} else {
|
|
RTC_DCHECK(direction == StatsReport::kReceive);
|
|
if (!pc_->GetRemoteTrackIdBySsrc(ssrc, track_id)) {
|
|
RTC_LOG(LS_WARNING) << "The SSRC " << ssrc
|
|
<< " is not associated with a receiving track";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void StatsCollector::UpdateTrackReports() {
|
|
RTC_DCHECK(pc_->signaling_thread()->IsCurrent());
|
|
|
|
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
|
|
|
|
for (const auto& entry : track_ids_) {
|
|
StatsReport* report = entry.second;
|
|
report->set_timestamp(stats_gathering_started_);
|
|
}
|
|
}
|
|
|
|
void StatsCollector::ClearUpdateStatsCacheForTest() {
|
|
stats_gathering_started_ = 0;
|
|
}
|
|
|
|
} // namespace webrtc
|