webrtc/pc/legacy_stats_collector.cc
Philipp Hancke 656817c485 Remove default "unknown" encoderImplementation/decoderImplementation
which means this will not show up in getStats inbound-rtp/outbound-rtp
until the encoder/decoder is known. This has implications in particular
for inbound-rtp where the value is currently "unknown" until video
frames have been received.

This is safe to change as the previous change to gate
decoderImplementation behind getUserMedia access already broke
the assumption that the field is always string.

BUG=webrtc:14906

Change-Id: Ie6040ada3656e80f792c0c32c1b86ad1d6609d3c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/293600
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#40334}
2023-06-22 11:49:58 +00:00

1398 lines
54 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/legacy_stats_collector.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <cmath>
#include <list>
#include <set>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/audio_codecs/audio_encoder.h"
#include "api/candidate.h"
#include "api/data_channel_interface.h"
#include "api/field_trials_view.h"
#include "api/media_types.h"
#include "api/rtp_sender_interface.h"
#include "api/scoped_refptr.h"
#include "api/sequence_checker.h"
#include "api/video/video_content_type.h"
#include "api/video/video_timing.h"
#include "call/call.h"
#include "media/base/media_channel.h"
#include "modules/audio_processing/include/audio_processing_statistics.h"
#include "p2p/base/ice_transport_internal.h"
#include "p2p/base/p2p_constants.h"
#include "pc/channel.h"
#include "pc/channel_interface.h"
#include "pc/data_channel_utils.h"
#include "pc/rtp_receiver.h"
#include "pc/rtp_receiver_proxy.h"
#include "pc/rtp_sender_proxy.h"
#include "pc/rtp_transceiver.h"
#include "pc/transport_stats.h"
#include "rtc_base/checks.h"
#include "rtc_base/ip_address.h"
#include "rtc_base/logging.h"
#include "rtc_base/rtc_certificate.h"
#include "rtc_base/socket_address.h"
#include "rtc_base/ssl_stream_adapter.h"
#include "rtc_base/string_encode.h"
#include "rtc_base/thread.h"
#include "rtc_base/time_utils.h"
#include "rtc_base/trace_event.h"
namespace webrtc {
namespace {
// Field trial which controls whether to report standard-compliant bytes
// sent/received per stream. If enabled, padding and headers are not included
// in bytes sent or received.
constexpr char kUseStandardBytesStats[] = "WebRTC-UseStandardBytesStats";
// 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";
const char* STATSREPORT_ADAPTER_TYPE_WILDCARD = "wildcard";
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* 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 Track>
void CreateTrackReport(const Track* track,
StatsCollection* reports,
TrackIdMap* track_ids) {
const std::string& track_id = track->id();
StatsReport* report = AddTrackReport(reports, track_id);
RTC_DCHECK(report != nullptr);
(*track_ids)[track_id] = report;
}
template <class TrackVector>
void CreateTrackReports(const TrackVector& tracks,
StatsCollection* reports,
TrackIdMap* track_ids) {
for (const auto& track : tracks) {
CreateTrackReport(track.get(), reports, track_ids);
}
}
void ExtractCommonSendProperties(const cricket::MediaSenderInfo& info,
StatsReport* report,
bool use_standard_bytes_stats) {
report->AddString(StatsReport::kStatsValueNameCodecName, info.codec_name);
int64_t bytes_sent = info.payload_bytes_sent;
if (!use_standard_bytes_stats) {
bytes_sent += info.header_and_padding_bytes_sent;
}
report->AddInt64(StatsReport::kStatsValueNameBytesSent, 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,
const AudioProcessingStats& apm_stats) {
if (apm_stats.delay_median_ms) {
report->AddInt(StatsReport::kStatsValueNameEchoDelayMedian,
*apm_stats.delay_median_ms);
}
if (apm_stats.delay_standard_deviation_ms) {
report->AddInt(StatsReport::kStatsValueNameEchoDelayStdDev,
*apm_stats.delay_standard_deviation_ms);
}
if (apm_stats.echo_return_loss) {
report->AddInt(StatsReport::kStatsValueNameEchoReturnLoss,
*apm_stats.echo_return_loss);
}
if (apm_stats.echo_return_loss_enhancement) {
report->AddInt(StatsReport::kStatsValueNameEchoReturnLossEnhancement,
*apm_stats.echo_return_loss_enhancement);
}
if (apm_stats.residual_echo_likelihood) {
report->AddFloat(StatsReport::kStatsValueNameResidualEchoLikelihood,
static_cast<float>(*apm_stats.residual_echo_likelihood));
}
if (apm_stats.residual_echo_likelihood_recent_max) {
report->AddFloat(
StatsReport::kStatsValueNameResidualEchoLikelihoodRecentMax,
static_cast<float>(*apm_stats.residual_echo_likelihood_recent_max));
}
if (apm_stats.divergent_filter_fraction) {
report->AddFloat(StatsReport::kStatsValueNameAecDivergentFilterFraction,
static_cast<float>(*apm_stats.divergent_filter_fraction));
}
}
void ExtractStats(const cricket::VoiceReceiverInfo& info,
StatsReport* report,
bool use_standard_bytes_stats) {
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_received},
{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);
}
if (info.decoding_codec_plc)
report->AddInt(StatsReport::kStatsValueNameDecodingCodecPLC,
info.decoding_codec_plc);
int64_t bytes_received = info.payload_bytes_received;
if (!use_standard_bytes_stats) {
bytes_received += info.header_and_padding_bytes_received;
}
report->AddInt64(StatsReport::kStatsValueNameBytesReceived, bytes_received);
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,
bool use_standard_bytes_stats) {
ExtractCommonSendProperties(info, report, use_standard_bytes_stats);
SetAudioProcessingStats(report, info.apm_statistics);
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,
bool use_standard_bytes_stats) {
ExtractCommonReceiveProperties(info, report);
report->AddString(StatsReport::kStatsValueNameCodecImplementationName,
info.decoder_implementation_name.value_or("unknown"));
int64_t bytes_received = info.payload_bytes_received;
if (!use_standard_bytes_stats) {
bytes_received += info.header_and_padding_bytes_received;
}
report->AddInt64(StatsReport::kStatsValueNameBytesReceived, bytes_received);
if (info.capture_start_ntp_time_ms >= 0) {
report->AddInt64(StatsReport::kStatsValueNameCaptureStartNtpTimeMs,
info.capture_start_ntp_time_ms);
}
if (info.first_frame_received_to_decoded_ms >= 0) {
report->AddInt64(StatsReport::kStatsValueNameFirstFrameReceivedToDecodedMs,
info.first_frame_received_to_decoded_ms);
}
if (info.qp_sum)
report->AddInt64(StatsReport::kStatsValueNameQpSum, *info.qp_sum);
if (info.nacks_sent) {
report->AddInt(StatsReport::kStatsValueNameNacksSent, *info.nacks_sent);
}
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_received},
{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::kStatsValueNamePacketsLost, info.packets_lost},
{StatsReport::kStatsValueNamePacketsReceived, info.packets_received},
{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,
bool use_standard_bytes_stats) {
ExtractCommonSendProperties(info, report, use_standard_bytes_stats);
report->AddString(StatsReport::kStatsValueNameCodecImplementationName,
info.encoder_implementation_name.value_or("unknown"));
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_received},
{StatsReport::kStatsValueNameFrameHeightSent, info.send_frame_height},
{StatsReport::kStatsValueNameFrameRateInput, round(info.framerate_input)},
{StatsReport::kStatsValueNameFrameRateSent, info.framerate_sent},
{StatsReport::kStatsValueNameFrameWidthSent, info.send_frame_width},
{StatsReport::kStatsValueNameNacksReceived, info.nacks_received},
{StatsReport::kStatsValueNamePacketsLost, info.packets_lost},
{StatsReport::kStatsValueNamePacketsSent, info.packets_sent},
{StatsReport::kStatsValueNamePlisReceived, info.plis_received},
{StatsReport::kStatsValueNameFramesEncoded, info.frames_encoded},
{StatsReport::kStatsValueNameHugeFramesSent, info.huge_frames_sent},
};
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.
}
std::string GetTrackIdBySsrc(
uint32_t ssrc,
StatsReport::Direction direction,
const std::map<uint32_t, std::string>& track_id_by_ssrc) {
auto it = track_id_by_ssrc.find(ssrc);
if (it != track_id_by_ssrc.end()) {
return it->second;
}
if (direction == StatsReport::kReceive) {
// If the track ID was not found, this might be an unsignaled receive
// SSRC, so try looking up by the special SSRC 0.
it = track_id_by_ssrc.find(0);
if (it != track_id_by_ssrc.end()) {
RTC_LOG(LS_INFO) << "Assuming SSRC=" << ssrc
<< " is an unsignalled receive stream corresponding "
"to the RtpReceiver with track ID \""
<< it->second << "\".";
return it->second;
}
}
return "";
}
// 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,
LegacyStatsCollector* collector,
StatsReport::Direction direction,
const std::map<uint32_t, std::string>& track_id_by_ssrc) {
for (const auto& d : data) {
uint32_t ssrc = d.ssrc();
std::string track_id = GetTrackIdBySsrc(ssrc, direction, track_id_by_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, track_id, transport_id, direction);
if (report)
ExtractStats(d, report, collector->UseStandardBytesStats());
if (!d.remote_stats.empty()) {
report = collector->PrepareReport(false, ssrc, track_id, 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_DCHECK_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:
case rtc::ADAPTER_TYPE_CELLULAR_2G:
case rtc::ADAPTER_TYPE_CELLULAR_3G:
case rtc::ADAPTER_TYPE_CELLULAR_4G:
case rtc::ADAPTER_TYPE_CELLULAR_5G:
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;
case rtc::ADAPTER_TYPE_ANY:
return STATSREPORT_ADAPTER_TYPE_WILDCARD;
default:
RTC_DCHECK_NOTREACHED();
return "";
}
}
LegacyStatsCollector::LegacyStatsCollector(PeerConnectionInternal* pc)
: pc_(pc),
stats_gathering_started_(0),
use_standard_bytes_stats_(
pc->trials().IsEnabled(kUseStandardBytesStats)) {
RTC_DCHECK(pc_);
}
LegacyStatsCollector::~LegacyStatsCollector() {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
}
// Wallclock time in ms.
double LegacyStatsCollector::GetTimeNow() {
return static_cast<double>(rtc::TimeUTCMillis());
}
// Adds a MediaStream with tracks that can be used as a `selector` in a call
// to GetStats.
void LegacyStatsCollector::AddStream(MediaStreamInterface* stream) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
RTC_DCHECK(stream != NULL);
CreateTrackReports<AudioTrackVector>(stream->GetAudioTracks(), &reports_,
&track_ids_);
CreateTrackReports<VideoTrackVector>(stream->GetVideoTracks(), &reports_,
&track_ids_);
}
void LegacyStatsCollector::AddTrack(MediaStreamTrackInterface* track) {
if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
CreateTrackReport(static_cast<AudioTrackInterface*>(track), &reports_,
&track_ids_);
} else if (track->kind() == MediaStreamTrackInterface::kVideoKind) {
CreateTrackReport(static_cast<VideoTrackInterface*>(track), &reports_,
&track_ids_);
} else {
RTC_DCHECK_NOTREACHED() << "Illegal track kind";
}
}
void LegacyStatsCollector::AddLocalAudioTrack(AudioTrackInterface* audio_track,
uint32_t ssrc) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
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 LegacyStatsCollector::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 LegacyStatsCollector::GetStats(MediaStreamTrackInterface* track,
StatsReports* reports) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
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 LegacyStatsCollector::UpdateStats(
PeerConnectionInterface::StatsOutputLevel level) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
// Calls to UpdateStats() that occur less than kMinGatherStatsPeriodMs apart
// will be ignored. Using a monotonic clock specifically for this, while using
// a UTC clock for the reports themselves.
const int64_t kMinGatherStatsPeriodMs = 50;
int64_t cache_now_ms = rtc::TimeMillis();
if (cache_timestamp_ms_ != 0 &&
cache_timestamp_ms_ + kMinGatherStatsPeriodMs > cache_now_ms) {
return;
}
cache_timestamp_ms_ = cache_now_ms;
stats_gathering_started_ = GetTimeNow();
// TODO(tommi): ExtractSessionInfo now has a single hop to the network thread
// to fetch stats, then applies them on the signaling thread. See if we need
// to do this synchronously or if updating the stats without blocking is safe.
std::map<std::string, std::string> transport_names_by_mid =
ExtractSessionAndDataInfo();
// TODO(tommi): All of these hop over to the worker thread to fetch
// information. We could post a task 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).
ExtractBweInfo();
ExtractMediaInfo(transport_names_by_mid);
ExtractSenderInfo();
UpdateTrackReports();
}
StatsReport* LegacyStatsCollector::PrepareReport(
bool local,
uint32_t ssrc,
const std::string& track_id,
const StatsReport::Id& transport_id,
StatsReport::Direction direction) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
StatsReport::Id id(StatsReport::NewIdWithDirection(
local ? StatsReport::kStatsReportTypeSsrc
: StatsReport::kStatsReportTypeRemoteSsrc,
rtc::ToString(ssrc), direction));
StatsReport* report = reports_.Find(id);
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);
if (!track_id.empty()) {
report->AddString(StatsReport::kStatsValueNameTrackId, track_id);
}
// Add the mapping of SSRC to transport.
report->AddId(StatsReport::kStatsValueNameTransportId, transport_id);
return report;
}
StatsReport* LegacyStatsCollector::PrepareADMReport() {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
StatsReport::Id id(StatsReport::NewTypedId(
StatsReport::kStatsReportTypeSession, pc_->session_id()));
StatsReport* report = reports_.FindOrAddNew(id);
return report;
}
bool LegacyStatsCollector::IsValidTrack(const std::string& track_id) {
return reports_.Find(StatsReport::NewTypedId(
StatsReport::kStatsReportTypeTrack, track_id)) != nullptr;
}
StatsReport* LegacyStatsCollector::AddCertificateReports(
std::unique_ptr<rtc::SSLCertificateStats> cert_stats) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
StatsReport* first_report = nullptr;
StatsReport* prev_report = nullptr;
for (rtc::SSLCertificateStats* stats = cert_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* LegacyStatsCollector::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);
cricket::CandidateStats local_candidate_stats(info.local_candidate);
cricket::CandidateStats remote_candidate_stats(info.remote_candidate);
report->AddId(StatsReport::kStatsValueNameLocalCandidateId,
AddCandidateReport(local_candidate_stats, true)->id());
report->AddId(StatsReport::kStatsValueNameRemoteCandidateId,
AddCandidateReport(remote_candidate_stats, 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());
report->AddString(StatsReport::kStatsValueNameLocalCandidateRelayProtocol,
info.local_candidate.relay_protocol());
return report;
}
StatsReport* LegacyStatsCollector::AddCandidateReport(
const cricket::CandidateStats& candidate_stats,
bool local) {
const auto& candidate = candidate_stats.candidate();
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());
}
report->set_timestamp(stats_gathering_started_);
if (local && candidate_stats.stun_stats().has_value()) {
const auto& stun_stats = candidate_stats.stun_stats().value();
report->AddInt64(StatsReport::kStatsValueNameSentStunKeepaliveRequests,
stun_stats.stun_binding_requests_sent);
report->AddInt64(StatsReport::kStatsValueNameRecvStunKeepaliveResponses,
stun_stats.stun_binding_responses_received);
report->AddFloat(StatsReport::kStatsValueNameStunKeepaliveRttTotal,
stun_stats.stun_binding_rtt_ms_total);
report->AddFloat(StatsReport::kStatsValueNameStunKeepaliveRttSquaredTotal,
stun_stats.stun_binding_rtt_ms_squared_total);
}
return report;
}
std::map<std::string, std::string>
LegacyStatsCollector::ExtractSessionAndDataInfo() {
TRACE_EVENT0("webrtc", "LegacyStatsCollector::ExtractSessionAndDataInfo");
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
SessionStats stats;
StatsCollection::Container data_report_collection;
auto transceivers = pc_->GetTransceiversInternal();
pc_->network_thread()->BlockingCall(
[&, sctp_transport_name = pc_->sctp_transport_name(),
sctp_mid = pc_->sctp_mid()]() mutable {
stats = ExtractSessionInfo_n(
transceivers, std::move(sctp_transport_name), std::move(sctp_mid));
StatsCollection data_reports;
ExtractDataInfo_n(&data_reports);
data_report_collection = data_reports.DetachCollection();
});
reports_.MergeCollection(std::move(data_report_collection));
ExtractSessionInfo_s(stats);
return std::move(stats.transport_names_by_mid);
}
LegacyStatsCollector::SessionStats LegacyStatsCollector::ExtractSessionInfo_n(
const std::vector<rtc::scoped_refptr<
RtpTransceiverProxyWithInternal<RtpTransceiver>>>& transceivers,
absl::optional<std::string> sctp_transport_name,
absl::optional<std::string> sctp_mid) {
TRACE_EVENT0("webrtc", "LegacyStatsCollector::ExtractSessionInfo_n");
RTC_DCHECK_RUN_ON(pc_->network_thread());
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
SessionStats stats;
stats.candidate_stats = pc_->GetPooledCandidateStats();
for (auto& transceiver : transceivers) {
cricket::ChannelInterface* channel = transceiver->internal()->channel();
if (channel) {
stats.transport_names_by_mid[channel->mid()] =
std::string(channel->transport_name());
}
}
if (sctp_transport_name) {
RTC_DCHECK(sctp_mid);
stats.transport_names_by_mid[*sctp_mid] = *sctp_transport_name;
}
std::set<std::string> transport_names;
for (const auto& entry : stats.transport_names_by_mid) {
transport_names.insert(entry.second);
}
std::map<std::string, cricket::TransportStats> transport_stats_by_name =
pc_->GetTransportStatsByNames(transport_names);
for (auto& entry : transport_stats_by_name) {
stats.transport_stats.emplace_back(entry.first, std::move(entry.second));
TransportStats& transport = stats.transport_stats.back();
// 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.name, &certificate)) {
transport.local_cert_stats =
certificate->GetSSLCertificateChain().GetStats();
}
std::unique_ptr<rtc::SSLCertChain> remote_cert_chain =
pc_->GetRemoteSSLCertChain(transport.name);
if (remote_cert_chain) {
transport.remote_cert_stats = remote_cert_chain->GetStats();
}
}
return stats;
}
void LegacyStatsCollector::ExtractSessionInfo_s(SessionStats& session_stats) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
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());
for (const cricket::CandidateStats& stats : session_stats.candidate_stats) {
AddCandidateReport(stats, true);
}
for (auto& transport : session_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;
if (transport.local_cert_stats) {
StatsReport* r =
AddCertificateReports(std::move(transport.local_cert_stats));
if (r)
local_cert_report_id = r->id();
}
if (transport.remote_cert_stats) {
StatsReport* r =
AddCertificateReports(std::move(transport.remote_cert_stats));
if (r)
remote_cert_report_id = r->id();
}
for (const auto& channel_iter : transport.stats.channel_stats) {
StatsReport::Id channel_stats_id(
StatsReport::NewComponentId(transport.name, channel_iter.component));
StatsReport* channel_report = reports_.ReplaceOrAddNew(channel_stats_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::kSrtpInvalidCryptoSuite &&
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::kTlsNullWithNullNull &&
rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite)
.length()) {
channel_report->AddString(
StatsReport::kStatsValueNameDtlsCipher,
rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite));
}
// Collect stats for non-pooled candidates. Note that the reports
// generated here supersedes the candidate reports generated in
// AddConnectionInfoReport below, and they may report candidates that are
// not paired. Also, the candidate report generated in
// AddConnectionInfoReport do not report port stats like StunStats.
for (const cricket::CandidateStats& stats :
channel_iter.ice_transport_stats.candidate_stats_list) {
AddCandidateReport(stats, true);
}
int connection_id = 0;
for (const cricket::ConnectionInfo& info :
channel_iter.ice_transport_stats.connection_infos) {
StatsReport* connection_report = AddConnectionInfoReport(
transport.name, channel_iter.component, connection_id++,
channel_report->id(), info);
if (info.best_connection) {
channel_report->AddId(
StatsReport::kStatsValueNameSelectedCandidatePairId,
connection_report->id());
}
}
}
}
}
void LegacyStatsCollector::ExtractBweInfo() {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
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.
auto transceivers = pc_->GetTransceiversInternal();
std::vector<cricket::VideoMediaSendChannelInterface*> video_media_channels;
for (const auto& transceiver : transceivers) {
if (transceiver->media_type() != cricket::MEDIA_TYPE_VIDEO) {
continue;
}
auto* video_channel = transceiver->internal()->channel();
if (video_channel) {
video_media_channels.push_back(video_channel->video_media_send_channel());
}
}
if (!video_media_channels.empty()) {
pc_->worker_thread()->BlockingCall([&] {
for (const auto& channel : video_media_channels) {
channel->FillBitrateInfo(&bwe_info);
}
});
}
StatsReport::Id report_id(StatsReport::NewBandwidthEstimationId());
StatsReport* report = reports_.FindOrAddNew(report_id);
ExtractStats(bwe_info, stats_gathering_started_, report);
}
namespace {
class ChannelStatsGatherer {
public:
virtual ~ChannelStatsGatherer() = default;
virtual bool GetStatsOnWorkerThread() = 0;
virtual void ExtractStats(LegacyStatsCollector* collector) const = 0;
virtual bool HasRemoteAudio() const = 0;
std::string mid;
std::string transport_name;
std::map<uint32_t, std::string> sender_track_id_by_ssrc;
std::map<uint32_t, std::string> receiver_track_id_by_ssrc;
protected:
template <typename ReceiverT, typename SenderT>
void ExtractSenderReceiverStats(
LegacyStatsCollector* collector,
const std::vector<ReceiverT>& receiver_data,
const std::vector<SenderT>& sender_data) const {
RTC_DCHECK(collector);
StatsReport::Id transport_id = StatsReport::NewComponentId(
transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP);
ExtractStatsFromList(receiver_data, transport_id, collector,
StatsReport::kReceive, receiver_track_id_by_ssrc);
ExtractStatsFromList(sender_data, transport_id, collector,
StatsReport::kSend, sender_track_id_by_ssrc);
}
};
class VoiceChannelStatsGatherer final : public ChannelStatsGatherer {
public:
explicit VoiceChannelStatsGatherer(cricket::VoiceChannel* voice_channel)
: voice_channel_(voice_channel) {
RTC_DCHECK(voice_channel_);
}
bool GetStatsOnWorkerThread() override {
cricket::VoiceMediaSendInfo send_info;
cricket::VoiceMediaReceiveInfo receive_info;
bool success =
voice_channel_->voice_media_send_channel()->GetStats(&send_info);
success &= voice_channel_->voice_media_receive_channel()->GetStats(
&receive_info,
/*get_and_clear_legacy_stats=*/true);
if (success) {
voice_media_info = cricket::VoiceMediaInfo(std::move(send_info),
std::move(receive_info));
}
return success;
}
void ExtractStats(LegacyStatsCollector* collector) const override {
ExtractSenderReceiverStats(collector, voice_media_info.receivers,
voice_media_info.senders);
if (voice_media_info.device_underrun_count == -2 ||
voice_media_info.device_underrun_count > 0) {
StatsReport* report = collector->PrepareADMReport();
report->AddInt(StatsReport::kStatsValueNameAudioDeviceUnderrunCounter,
voice_media_info.device_underrun_count);
}
}
bool HasRemoteAudio() const override {
return !voice_media_info.receivers.empty();
}
private:
cricket::VoiceChannel* voice_channel_;
cricket::VoiceMediaInfo voice_media_info;
};
class VideoChannelStatsGatherer final : public ChannelStatsGatherer {
public:
explicit VideoChannelStatsGatherer(cricket::VideoChannel* video_channel)
: video_channel_(video_channel) {
RTC_DCHECK(video_channel_);
}
bool GetStatsOnWorkerThread() override {
cricket::VideoMediaSendInfo send_info;
cricket::VideoMediaReceiveInfo receive_info;
bool success =
video_channel_->video_media_send_channel()->GetStats(&send_info);
success &=
video_channel_->video_media_receive_channel()->GetStats(&receive_info);
if (success) {
video_media_info = cricket::VideoMediaInfo(std::move(send_info),
std::move(receive_info));
}
return success;
}
void ExtractStats(LegacyStatsCollector* collector) const override {
ExtractSenderReceiverStats(collector, video_media_info.receivers,
video_media_info.aggregated_senders);
}
bool HasRemoteAudio() const override { return false; }
private:
cricket::VideoChannel* video_channel_;
cricket::VideoMediaInfo video_media_info;
};
std::unique_ptr<ChannelStatsGatherer> CreateChannelStatsGatherer(
cricket::ChannelInterface* channel) {
RTC_DCHECK(channel);
if (channel->media_type() == cricket::MEDIA_TYPE_AUDIO) {
return std::make_unique<VoiceChannelStatsGatherer>(
channel->AsVoiceChannel());
} else {
RTC_DCHECK_EQ(channel->media_type(), cricket::MEDIA_TYPE_VIDEO);
return std::make_unique<VideoChannelStatsGatherer>(
channel->AsVideoChannel());
}
}
} // namespace
void LegacyStatsCollector::ExtractMediaInfo(
const std::map<std::string, std::string>& transport_names_by_mid) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
std::vector<std::unique_ptr<ChannelStatsGatherer>> gatherers;
auto transceivers = pc_->GetTransceiversInternal();
{
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
for (const auto& transceiver : transceivers) {
cricket::ChannelInterface* channel = transceiver->internal()->channel();
if (!channel) {
continue;
}
std::unique_ptr<ChannelStatsGatherer> gatherer =
CreateChannelStatsGatherer(channel);
gatherer->mid = channel->mid();
gatherer->transport_name = transport_names_by_mid.at(gatherer->mid);
for (const auto& sender : transceiver->internal()->senders()) {
auto track = sender->track();
std::string track_id = (track ? track->id() : "");
gatherer->sender_track_id_by_ssrc.insert(
std::make_pair(sender->ssrc(), track_id));
}
// Populating `receiver_track_id_by_ssrc` will be done on the worker
// thread as the `ssrc` property of the receiver needs to be accessed
// there.
gatherers.push_back(std::move(gatherer));
}
}
pc_->worker_thread()->BlockingCall([&] {
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
// Populate `receiver_track_id_by_ssrc` for the gatherers.
int i = 0;
for (const auto& transceiver : transceivers) {
cricket::ChannelInterface* channel = transceiver->internal()->channel();
if (!channel)
continue;
ChannelStatsGatherer* gatherer = gatherers[i++].get();
RTC_DCHECK_EQ(gatherer->mid, channel->mid());
for (const auto& receiver : transceiver->internal()->receivers()) {
gatherer->receiver_track_id_by_ssrc.insert(std::make_pair(
receiver->internal()->ssrc().value_or(0), receiver->track()->id()));
}
}
for (auto it = gatherers.begin(); it != gatherers.end();
/* incremented manually */) {
ChannelStatsGatherer* gatherer = it->get();
if (!gatherer->GetStatsOnWorkerThread()) {
RTC_LOG(LS_ERROR) << "Failed to get media channel stats for mid="
<< gatherer->mid;
it = gatherers.erase(it);
continue;
}
++it;
}
});
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
bool has_remote_audio = false;
for (const auto& gatherer : gatherers) {
gatherer->ExtractStats(this);
has_remote_audio |= gatherer->HasRemoteAudio();
}
UpdateStatsFromExistingLocalAudioTracks(has_remote_audio);
}
void LegacyStatsCollector::ExtractSenderInfo() {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
for (const auto& sender : pc_->GetSenders()) {
// TODO(bugs.webrtc.org/8694): 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(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 LegacyStatsCollector::ExtractDataInfo_n(StatsCollection* reports) {
RTC_DCHECK_RUN_ON(pc_->network_thread());
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
std::vector<DataChannelStats> data_stats = pc_->GetDataChannelStats();
for (const auto& stats : data_stats) {
StatsReport::Id id(StatsReport::NewTypedIntId(
StatsReport::kStatsReportTypeDataChannel, stats.id));
StatsReport* report = reports->ReplaceOrAddNew(id);
report->set_timestamp(stats_gathering_started_);
report->AddString(StatsReport::kStatsValueNameLabel, stats.label);
// Filter out the initial id (-1).
if (stats.id >= 0) {
report->AddInt(StatsReport::kStatsValueNameDataChannelId, stats.id);
}
report->AddString(StatsReport::kStatsValueNameProtocol, stats.protocol);
report->AddString(StatsReport::kStatsValueNameState,
DataChannelInterface::DataStateString(stats.state));
}
}
StatsReport* LegacyStatsCollector::GetReport(const StatsReport::StatsType& type,
const std::string& id,
StatsReport::Direction direction) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
RTC_DCHECK(type == StatsReport::kStatsReportTypeSsrc ||
type == StatsReport::kStatsReportTypeRemoteSsrc);
return reports_.Find(StatsReport::NewIdWithDirection(type, id, direction));
}
void LegacyStatsCollector::UpdateStatsFromExistingLocalAudioTracks(
bool has_remote_tracks) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
// 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(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 LegacyStatsCollector::UpdateReportFromAudioTrack(
AudioTrackInterface* track,
StatsReport* report,
bool has_remote_tracks) {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
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.apm_statistics);
}
}
void LegacyStatsCollector::UpdateTrackReports() {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
for (const auto& entry : track_ids_) {
StatsReport* report = entry.second;
report->set_timestamp(stats_gathering_started_);
}
}
void LegacyStatsCollector::InvalidateCache() {
RTC_DCHECK_RUN_ON(pc_->signaling_thread());
cache_timestamp_ms_ = 0;
}
} // namespace webrtc