/* * Copyright 2016 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.
*/
// TODO(https://crbug.com/webrtc/10656): Consider making IDs less predictable.
std::string RTCCertificateIDFromFingerprint(const std::string& fingerprint) { return"CF" + fingerprint;
}
// `direction` is either kDirectionInbound or kDirectionOutbound.
std::string RTCCodecStatsIDFromTransportAndCodecParameters( constchar direction, const std::string& transport_id, const RtpCodecParameters& codec_params) { char buf[1024];
rtc::SimpleStringBuilder sb(buf);
sb << 'C' << direction << transport_id << '_' << codec_params.payload_type; // TODO(https://crbug.com/webrtc/14420): If we stop supporting different FMTP // lines for the same PT and transport, which should be illegal SDP, then we // wouldn't need `fmtp` to be part of the ID here.
rtc::StringBuilder fmtp; if (WriteFmtpParameters(codec_params.parameters, &fmtp)) {
sb << '_' << fmtp.Release();
} return sb.str();
}
constchar* DataStateToRTCDataChannelState(
DataChannelInterface::DataState state) { switch (state) { case DataChannelInterface::kConnecting: return"connecting"; case DataChannelInterface::kOpen: return"open"; case DataChannelInterface::kClosing: return"closing"; case DataChannelInterface::kClosed: return"closed"; default:
RTC_DCHECK_NOTREACHED(); return nullptr;
}
}
constchar* IceCandidatePairStateToRTCStatsIceCandidatePairState(
cricket::IceCandidatePairState state) { switch (state) { case cricket::IceCandidatePairState::WAITING: return"waiting"; case cricket::IceCandidatePairState::IN_PROGRESS: return"in-progress"; case cricket::IceCandidatePairState::SUCCEEDED: return"succeeded"; case cricket::IceCandidatePairState::FAILED: return"failed"; default:
RTC_DCHECK_NOTREACHED(); return nullptr;
}
}
constchar* IceRoleToRTCIceRole(cricket::IceRole role) { switch (role) { case cricket::IceRole::ICEROLE_UNKNOWN: return"unknown"; case cricket::IceRole::ICEROLE_CONTROLLED: return"controlled"; case cricket::IceRole::ICEROLE_CONTROLLING: return"controlling"; default:
RTC_DCHECK_NOTREACHED(); return nullptr;
}
}
constchar* DtlsTransportStateToRTCDtlsTransportState(
DtlsTransportState state) { switch (state) { case DtlsTransportState::kNew: return"new"; case DtlsTransportState::kConnecting: return"connecting"; case DtlsTransportState::kConnected: return"connected"; case DtlsTransportState::kClosed: return"closed"; case DtlsTransportState::kFailed: return"failed"; default:
RTC_CHECK_NOTREACHED(); return nullptr;
}
}
constchar* IceTransportStateToRTCIceTransportState(IceTransportState state) { switch (state) { case IceTransportState::kNew: return"new"; case IceTransportState::kChecking: return"checking"; case IceTransportState::kConnected: return"connected"; case IceTransportState::kCompleted: return"completed"; case IceTransportState::kFailed: return"failed"; case IceTransportState::kDisconnected: return"disconnected"; case IceTransportState::kClosed: return"closed"; default:
RTC_CHECK_NOTREACHED(); return nullptr;
}
}
constchar* NetworkTypeToStatsType(rtc::AdapterType type) { switch (type) { 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"cellular"; case rtc::ADAPTER_TYPE_ETHERNET: return"ethernet"; case rtc::ADAPTER_TYPE_WIFI: return"wifi"; case rtc::ADAPTER_TYPE_VPN: return"vpn"; case rtc::ADAPTER_TYPE_UNKNOWN: case rtc::ADAPTER_TYPE_LOOPBACK: case rtc::ADAPTER_TYPE_ANY: return"unknown";
}
RTC_DCHECK_NOTREACHED(); return nullptr;
}
absl::string_view NetworkTypeToStatsNetworkAdapterType(rtc::AdapterType type) { switch (type) { case rtc::ADAPTER_TYPE_CELLULAR: return"cellular"; case rtc::ADAPTER_TYPE_CELLULAR_2G: return"cellular2g"; case rtc::ADAPTER_TYPE_CELLULAR_3G: return"cellular3g"; case rtc::ADAPTER_TYPE_CELLULAR_4G: return"cellular4g"; case rtc::ADAPTER_TYPE_CELLULAR_5G: return"cellular5g"; case rtc::ADAPTER_TYPE_ETHERNET: return"ethernet"; case rtc::ADAPTER_TYPE_WIFI: return"wifi"; case rtc::ADAPTER_TYPE_UNKNOWN: return"unknown"; case rtc::ADAPTER_TYPE_LOOPBACK: return"loopback"; case rtc::ADAPTER_TYPE_ANY: return"any"; case rtc::ADAPTER_TYPE_VPN: /* should not be handled here. Vpn is modelled as a bool */ break;
}
RTC_DCHECK_NOTREACHED(); return {};
}
constchar* QualityLimitationReasonToRTCQualityLimitationReason(
QualityLimitationReason reason) { switch (reason) { case QualityLimitationReason::kNone: return"none"; case QualityLimitationReason::kCpu: return"cpu"; case QualityLimitationReason::kBandwidth: return"bandwidth"; case QualityLimitationReason::kOther: return"other";
}
RTC_CHECK_NOTREACHED();
}
std::map<std::string, double>
QualityLimitationDurationToRTCQualityLimitationDuration(
std::map<QualityLimitationReason, int64_t> durations_ms) {
std::map<std::string, double> result; // The internal duration is defined in milliseconds while the spec defines // the value in seconds: // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-qualitylimitationdurations for (constauto& elem : durations_ms) {
result[QualityLimitationReasonToRTCQualityLimitationReason(elem.first)] =
elem.second / static_cast<double>(rtc::kNumMillisecsPerSec);
} return result;
}
std::unique_ptr<RTCRemoteInboundRtpStreamStats>
ProduceRemoteInboundRtpStreamStatsFromReportBlockData( const std::string& transport_id, const ReportBlockData& report_block,
cricket::MediaType media_type, const std::map<std::string, RTCOutboundRtpStreamStats*>& outbound_rtps, const RTCStatsReport& report, constbool stats_timestamp_with_environment_clock) { // RTCStats' timestamp generally refers to when the metric was sampled, but // for "remote-[outbound/inbound]-rtp" it refers to the local time when the // Report Block was received.
Timestamp arrival_timestamp = stats_timestamp_with_environment_clock
? report_block.report_block_timestamp()
: report_block.report_block_timestamp_utc(); auto remote_inbound = std::make_unique<RTCRemoteInboundRtpStreamStats>(
RTCRemoteInboundRtpStreamStatsIdFromSourceSsrc(
media_type, report_block.source_ssrc()),
arrival_timestamp);
remote_inbound->ssrc = report_block.source_ssrc();
remote_inbound->kind =
media_type == cricket::MEDIA_TYPE_AUDIO ? "audio" : "video";
remote_inbound->packets_lost = report_block.cumulative_lost();
remote_inbound->fraction_lost = report_block.fraction_lost(); if (report_block.num_rtts() > 0) {
remote_inbound->round_trip_time = report_block.last_rtt().seconds<double>();
}
remote_inbound->total_round_trip_time =
report_block.sum_rtts().seconds<double>();
remote_inbound->round_trip_time_measurements = report_block.num_rtts();
std::string local_id = RTCOutboundRtpStreamStatsIDFromSSRC(
transport_id, media_type, report_block.source_ssrc()); // Look up local stat from `outbound_rtps` where the pointers are non-const. auto local_id_it = outbound_rtps.find(local_id); if (local_id_it != outbound_rtps.end()) {
remote_inbound->local_id = local_id; auto& outbound_rtp = *local_id_it->second;
outbound_rtp.remote_id = remote_inbound->id(); // The RTP/RTCP transport is obtained from the // RTCOutboundRtpStreamStats's transport. constauto* transport_from_id = report.Get(transport_id); if (transport_from_id) { constauto& transport = transport_from_id->cast_to<RTCTransportStats>(); // If RTP and RTCP are not multiplexed, there is a separate RTCP // transport paired with the RTP transport, otherwise the same // transport is used for RTCP and RTP.
remote_inbound->transport_id =
transport.rtcp_transport_stats_id.has_value()
? *transport.rtcp_transport_stats_id
: *outbound_rtp.transport_id;
} // We're assuming the same codec is used on both ends. However if the // codec is switched out on the fly we may have received a Report Block // based on the previous codec and there is no way to tell which point in // time the codec changed for the remote end. constauto* codec_from_id = outbound_rtp.codec_id.has_value()
? report.Get(*outbound_rtp.codec_id)
: nullptr; if (codec_from_id) {
remote_inbound->codec_id = *outbound_rtp.codec_id; constauto& codec = codec_from_id->cast_to<RTCCodecStats>(); if (codec.clock_rate.has_value()) {
remote_inbound->jitter =
report_block.jitter(*codec.clock_rate).seconds<double>();
}
}
} return remote_inbound;
}
void ProduceCertificateStatsFromSSLCertificateStats(
Timestamp timestamp, const rtc::SSLCertificateStats& certificate_stats,
RTCStatsReport* report) {
RTCCertificateStats* prev_certificate_stats = nullptr; for (const rtc::SSLCertificateStats* s = &certificate_stats; s;
s = s->issuer.get()) {
std::string certificate_stats_id =
RTCCertificateIDFromFingerprint(s->fingerprint); // It is possible for the same certificate to show up multiple times, e.g. // if local and remote side use the same certificate in a loopback call. // If the report already contains stats for this certificate, skip it. if (report->Get(certificate_stats_id)) {
RTC_DCHECK_EQ(s, &certificate_stats); break;
}
RTCCertificateStats* certificate_stats = new RTCCertificateStats(certificate_stats_id, timestamp);
certificate_stats->fingerprint = s->fingerprint;
certificate_stats->fingerprint_algorithm = s->fingerprint_algorithm;
certificate_stats->base64_certificate = s->base64_certificate; if (prev_certificate_stats)
prev_certificate_stats->issuer_certificate_id = certificate_stats->id();
report->AddStats(std::unique_ptr<RTCCertificateStats>(certificate_stats));
prev_certificate_stats = certificate_stats;
}
}
// "Now" using a monotonically increasing timer.
int64_t cache_now_us = rtc::TimeMicros(); if (cached_report_ &&
cache_now_us - cache_timestamp_us_ <= cache_lifetime_us_) { // We have a fresh cached report to deliver. Deliver asynchronously, since // the caller may not be expecting a synchronous callback, and it avoids // reentrancy problems.
signaling_thread_->PostTask(
absl::bind_front(&RTCStatsCollector::DeliverCachedReport,
rtc::scoped_refptr<RTCStatsCollector>(this),
cached_report_, std::move(requests_)));
} elseif (!num_pending_partial_reports_) { // Only start gathering stats if we're not already gathering stats. In the // case of already gathering stats, `callback_` will be invoked when there // are no more pending partial reports.
Timestamp timestamp =
stats_timestamp_with_environment_clock_
? // "Now" using a monotonically increasing timer.
env_.clock().CurrentTime()
: // "Now" using a system clock, relative to the UNIX epoch (Jan 1, // 1970, UTC), in microseconds. The system clock could be modified // and is not necessarily monotonically increasing.
Timestamp::Micros(rtc::TimeUTCMicros());
// Prepare `transceiver_stats_infos_` and `call_stats_` for use in // `ProducePartialResultsOnNetworkThread` and // `ProducePartialResultsOnSignalingThread`.
PrepareTransceiverStatsInfosAndCallStats_s_w_n(); // Don't touch `network_report_` on the signaling thread until // ProducePartialResultsOnNetworkThread() has signaled the // `network_report_event_`.
network_report_event_.Reset();
rtc::scoped_refptr<RTCStatsCollector> collector(this);
network_thread_->PostTask([collector,
sctp_transport_name = pc_->sctp_transport_name(),
timestamp]() mutable {
collector->ProducePartialResultsOnNetworkThread(
timestamp, std::move(sctp_transport_name));
});
ProducePartialResultsOnSignalingThread(timestamp);
}
}
void RTCStatsCollector::WaitForPendingRequest() {
RTC_DCHECK_RUN_ON(signaling_thread_); // If a request is pending, blocks until the `network_report_event_` is // signaled and then delivers the result. Otherwise this is a NO-OP.
MergeNetworkReport_s();
}
// ProducePartialResultsOnSignalingThread() is running synchronously on the // signaling thread, so it is always the first partial result delivered on the // signaling thread. The request is not complete until MergeNetworkReport_s() // happens; we don't have to do anything here.
RTC_DCHECK_GT(num_pending_partial_reports_, 1);
--num_pending_partial_reports_;
}
// Touching `network_report_` on this thread is safe by this method because // `network_report_event_` is reset before this method is invoked.
network_report_ = RTCStatsReport::Create(timestamp);
// Signal that it is now safe to touch `network_report_` on the signaling // thread, and post a task to merge it into the final results.
network_report_event_.Set();
rtc::scoped_refptr<RTCStatsCollector> collector(this);
signaling_thread_->PostTask(
[collector] { collector->MergeNetworkReport_s(); });
}
void RTCStatsCollector::MergeNetworkReport_s() {
RTC_DCHECK_RUN_ON(signaling_thread_); // The `network_report_event_` must be signaled for it to be safe to touch // `network_report_`. This is normally not blocking, but if // WaitForPendingRequest() is called while a request is pending, we might have // to wait until the network thread is done touching `network_report_`.
network_report_event_.Wait(rtc::Event::kForever); if (!network_report_) { // Normally, MergeNetworkReport_s() is executed because it is posted from // the network thread. But if WaitForPendingRequest() is called while a // request is pending, an early call to MergeNetworkReport_s() is made, // merging the report and setting `network_report_` to null. If so, when the // previously posted MergeNetworkReport_s() is later executed, the report is // already null and nothing needs to be done here. return;
}
RTC_DCHECK_GT(num_pending_partial_reports_, 0);
RTC_DCHECK(partial_report_);
partial_report_->TakeMembersFrom(network_report_);
network_report_ = nullptr;
--num_pending_partial_reports_; // `network_report_` is currently the only partial report collected // asynchronously, so `num_pending_partial_reports_` must now be 0 and we are // ready to deliver the result.
RTC_DCHECK_EQ(num_pending_partial_reports_, 0);
cache_timestamp_us_ = partial_report_timestamp_us_;
cached_report_ = partial_report_;
partial_report_ = nullptr;
transceiver_stats_infos_.clear(); // Trace WebRTC Stats when getStats is called on Javascript. // This allows access to WebRTC stats from trace logs. To enable them, // select the "webrtc_stats" category when recording traces.
TRACE_EVENT_INSTANT1("webrtc_stats", "webrtc_stats", TRACE_EVENT_SCOPE_GLOBAL, "report", cached_report_->ToJson());
// Produce local candidate stats. If a transport exists these will already // have been produced. for (constauto& candidate_stats :
channel_stats.ice_transport_stats.candidate_stats_list) { constauto& candidate = candidate_stats.candidate();
ProduceIceCandidateStats(timestamp, candidate, true, transport_id,
report);
}
}
}
}
for (const RtpTransceiverStatsInfo& transceiver_stats_info :
transceiver_stats_infos_) { constauto& track_media_info_map =
transceiver_stats_info.track_media_info_map; for (constauto& sender : transceiver_stats_info.transceiver->senders()) { constauto& sender_internal = sender->internal(); constauto& track = sender_internal->track(); if (!track) continue; // TODO(https://crbug.com/webrtc/10771): The same track could be attached // to multiple senders which should result in multiple senders referencing // the same media-source stats. When all media source related metrics are // moved to the track's source (e.g. input frame rate is moved from // cricket::VideoSenderInfo to VideoTrackSourceInterface::Stats and audio // levels are moved to the corresponding audio track/source object), don't // create separate media source stats objects on a per-attachment basis.
std::unique_ptr<RTCMediaSourceStats> media_source_stats; if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
AudioTrackInterface* audio_track = static_cast<AudioTrackInterface*>(track.get()); auto audio_source_stats = std::make_unique<RTCAudioSourceStats>(
RTCMediaSourceStatsIDFromKindAndAttachment(
cricket::MEDIA_TYPE_AUDIO, sender_internal->AttachmentId()),
timestamp); // TODO(https://crbug.com/webrtc/10771): We shouldn't need to have an // SSRC assigned (there shouldn't need to exist a send-stream, created // by an O/A exchange) in order to read audio media-source stats. // TODO(https://crbug.com/webrtc/8694): SSRC 0 shouldn't be a magic // value indicating no SSRC. if (sender_internal->ssrc() != 0) { auto* voice_sender_info =
track_media_info_map.GetVoiceSenderInfoBySsrc(
sender_internal->ssrc()); if (voice_sender_info) {
audio_source_stats->audio_level = DoubleAudioLevelFromIntAudioLevel(
voice_sender_info->audio_level);
audio_source_stats->total_audio_energy =
voice_sender_info->total_input_energy;
audio_source_stats->total_samples_duration =
voice_sender_info->total_input_duration;
SetAudioProcessingStats(audio_source_stats.get(),
voice_sender_info->apm_statistics);
}
} // Audio processor may be attached to either the track or the send // stream, so look in both places. auto audio_processor(audio_track->GetAudioProcessor()); if (audio_processor.get()) { // The `has_remote_tracks` argument is obsolete; makes no difference // if it's set to true or false.
AudioProcessorInterface::AudioProcessorStatistics ap_stats =
audio_processor->GetStats(/*has_remote_tracks=*/false);
SetAudioProcessingStats(audio_source_stats.get(),
ap_stats.apm_statistics);
}
media_source_stats = std::move(audio_source_stats);
} else {
RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind()); auto video_source_stats = std::make_unique<RTCVideoSourceStats>(
RTCMediaSourceStatsIDFromKindAndAttachment(
cricket::MEDIA_TYPE_VIDEO, sender_internal->AttachmentId()),
timestamp); auto* video_track = static_cast<VideoTrackInterface*>(track.get()); auto* video_source = video_track->GetSource();
VideoTrackSourceInterface::Stats source_stats; if (video_source && video_source->GetStats(&source_stats)) {
video_source_stats->width = source_stats.input_width;
video_source_stats->height = source_stats.input_height;
} // TODO(https://crbug.com/webrtc/10771): We shouldn't need to have an // SSRC assigned (there shouldn't need to exist a send-stream, created // by an O/A exchange) in order to get framesPerSecond. // TODO(https://crbug.com/webrtc/8694): SSRC 0 shouldn't be a magic // value indicating no SSRC. if (sender_internal->ssrc() != 0) { auto* video_sender_info =
track_media_info_map.GetVideoSenderInfoBySsrc(
sender_internal->ssrc()); if (video_sender_info) {
video_source_stats->frames_per_second =
video_sender_info->framerate_input;
video_source_stats->frames = video_sender_info->frames;
}
}
media_source_stats = std::move(video_source_stats);
}
media_source_stats->track_identifier = track->id();
media_source_stats->kind = track->kind();
report->AddStats(std::move(media_source_stats));
}
}
}
if (!stats.mid || !stats.transport_name) { return;
}
RTC_DCHECK(stats.track_media_info_map.voice_media_info().has_value());
std::string mid = *stats.mid;
std::string transport_id = RTCTransportStatsIDFromTransportChannel(
*stats.transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP); // Inbound and remote-outbound. // The remote-outbound stats are based on RTCP sender reports sent from the // remote endpoint providing metrics about the remote outbound streams. for (const cricket::VoiceReceiverInfo& voice_receiver_info :
stats.track_media_info_map.voice_media_info()->receivers) { if (!voice_receiver_info.connected()) continue; // Inbound. auto inbound_audio = CreateInboundAudioStreamStats(
*stats.track_media_info_map.voice_media_info(), voice_receiver_info,
transport_id, mid, timestamp, report); // TODO(hta): This lookup should look for the sender, not the track.
rtc::scoped_refptr<AudioTrackInterface> audio_track =
stats.track_media_info_map.GetAudioTrack(voice_receiver_info); if (audio_track) {
inbound_audio->track_identifier = audio_track->id();
} if (audio_device_stats_ && stats.media_type == cricket::MEDIA_TYPE_AUDIO &&
stats.current_direction &&
(*stats.current_direction == RtpTransceiverDirection::kSendRecv ||
*stats.current_direction == RtpTransceiverDirection::kRecvOnly)) {
inbound_audio->playout_id = kAudioPlayoutSingletonId;
} auto* inbound_audio_ptr = report->TryAddStats(std::move(inbound_audio)); if (!inbound_audio_ptr) {
RTC_LOG(LS_ERROR)
<< "Unable to add audio 'inbound-rtp' to report, ID is not unique."; continue;
} // Remote-outbound. auto remote_outbound_audio = CreateRemoteOutboundMediaStreamStats(
voice_receiver_info, mid, cricket::MEDIA_TYPE_AUDIO, *inbound_audio_ptr,
transport_id, stats_timestamp_with_environment_clock_); // Add stats. if (remote_outbound_audio) { // When the remote outbound stats are available, the remote ID for the // local inbound stats is set. auto* remote_outbound_audio_ptr =
report->TryAddStats(std::move(remote_outbound_audio)); if (remote_outbound_audio_ptr) {
inbound_audio_ptr->remote_id = remote_outbound_audio_ptr->id();
} else {
RTC_LOG(LS_ERROR) << "Unable to add audio 'remote-outbound-rtp' to "
<< "report, ID is not unique.";
}
}
} // Outbound.
std::map<std::string, RTCOutboundRtpStreamStats*> audio_outbound_rtps; for (const cricket::VoiceSenderInfo& voice_sender_info :
stats.track_media_info_map.voice_media_info()->senders) { if (!voice_sender_info.connected()) continue; auto outbound_audio = CreateOutboundRTPStreamStatsFromVoiceSenderInfo(
transport_id, mid, *stats.track_media_info_map.voice_media_info(),
voice_sender_info, timestamp, report);
rtc::scoped_refptr<AudioTrackInterface> audio_track =
stats.track_media_info_map.GetAudioTrack(voice_sender_info); if (audio_track) { int attachment_id =
stats.track_media_info_map.GetAttachmentIdByTrack(audio_track.get())
.value();
outbound_audio->media_source_id =
RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_AUDIO,
attachment_id);
} auto audio_outbound_pair =
std::make_pair(outbound_audio->id(), outbound_audio.get()); if (report->TryAddStats(std::move(outbound_audio))) {
audio_outbound_rtps.insert(std::move(audio_outbound_pair));
} else {
RTC_LOG(LS_ERROR)
<< "Unable to add audio 'outbound-rtp' to report, ID is not unique.";
}
} // Remote-inbound. // These are Report Block-based, information sent from the remote endpoint, // providing metrics about our Outbound streams. We take advantage of the fact // that RTCOutboundRtpStreamStats, RTCCodecStats and RTCTransport have already // been added to the report. for (const cricket::VoiceSenderInfo& voice_sender_info :
stats.track_media_info_map.voice_media_info()->senders) { for (constauto& report_block_data : voice_sender_info.report_block_datas) {
report->AddStats(ProduceRemoteInboundRtpStreamStatsFromReportBlockData(
transport_id, report_block_data, cricket::MEDIA_TYPE_AUDIO,
audio_outbound_rtps, *report,
stats_timestamp_with_environment_clock_));
}
}
}
if (!stats.mid || !stats.transport_name) { return;
}
RTC_DCHECK(stats.track_media_info_map.video_media_info().has_value());
std::string mid = *stats.mid;
std::string transport_id = RTCTransportStatsIDFromTransportChannel(
*stats.transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP); // Inbound and remote-outbound. for (const cricket::VideoReceiverInfo& video_receiver_info :
stats.track_media_info_map.video_media_info()->receivers) { if (!video_receiver_info.connected()) continue; auto inbound_video = CreateInboundRTPStreamStatsFromVideoReceiverInfo(
transport_id, mid, *stats.track_media_info_map.video_media_info(),
video_receiver_info, timestamp, report);
rtc::scoped_refptr<VideoTrackInterface> video_track =
stats.track_media_info_map.GetVideoTrack(video_receiver_info); if (video_track) {
inbound_video->track_identifier = video_track->id();
} auto* inbound_video_ptr = report->TryAddStats(std::move(inbound_video)); if (!inbound_video_ptr) {
RTC_LOG(LS_ERROR)
<< "Unable to add video 'inbound-rtp' to report, ID is not unique."; continue;
} // Remote-outbound. auto remote_outbound_video = CreateRemoteOutboundMediaStreamStats(
video_receiver_info, mid, cricket::MEDIA_TYPE_VIDEO, *inbound_video_ptr,
transport_id, stats_timestamp_with_environment_clock_); // Add stats. if (remote_outbound_video) { // When the remote outbound stats are available, the remote ID for the // local inbound stats is set. auto* remote_outbound_video_ptr =
report->TryAddStats(std::move(remote_outbound_video)); if (remote_outbound_video_ptr) {
inbound_video_ptr->remote_id = remote_outbound_video_ptr->id();
} else {
RTC_LOG(LS_ERROR) << "Unable to add video 'remote-outbound-rtp' to "
<< "report, ID is not unique.";
}
}
} // Outbound
std::map<std::string, RTCOutboundRtpStreamStats*> video_outbound_rtps; for (const cricket::VideoSenderInfo& video_sender_info :
stats.track_media_info_map.video_media_info()->senders) { if (!video_sender_info.connected()) continue; auto outbound_video = CreateOutboundRTPStreamStatsFromVideoSenderInfo(
transport_id, mid, *stats.track_media_info_map.video_media_info(),
video_sender_info, timestamp, report);
rtc::scoped_refptr<VideoTrackInterface> video_track =
stats.track_media_info_map.GetVideoTrack(video_sender_info); if (video_track) { int attachment_id =
stats.track_media_info_map.GetAttachmentIdByTrack(video_track.get())
.value();
outbound_video->media_source_id =
RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_VIDEO,
attachment_id);
} auto video_outbound_pair =
std::make_pair(outbound_video->id(), outbound_video.get()); if (report->TryAddStats(std::move(outbound_video))) {
video_outbound_rtps.insert(std::move(video_outbound_pair));
} else {
RTC_LOG(LS_ERROR)
<< "Unable to add video 'outbound-rtp' to report, ID is not unique.";
}
} // Remote-inbound // These are Report Block-based, information sent from the remote endpoint, // providing metrics about our Outbound streams. We take advantage of the fact // that RTCOutboundRtpStreamStats, RTCCodecStats and RTCTransport have already // been added to the report. for (const cricket::VideoSenderInfo& video_sender_info :
stats.track_media_info_map.video_media_info()->senders) { for (constauto& report_block_data : video_sender_info.report_block_datas) {
report->AddStats(ProduceRemoteInboundRtpStreamStatsFromReportBlockData(
transport_id, report_block_data, cricket::MEDIA_TYPE_VIDEO,
video_outbound_rtps, *report,
stats_timestamp_with_environment_clock_));
}
}
}
// Get reference to RTCP channel, if it exists.
std::string rtcp_transport_stats_id; for (const cricket::TransportChannelStats& channel_stats :
transport_stats.channel_stats) { if (channel_stats.component == cricket::ICE_CANDIDATE_COMPONENT_RTCP) {
rtcp_transport_stats_id = RTCTransportStatsIDFromTransportChannel(
transport_name, channel_stats.component); break;
}
}
// Get reference to local and remote certificates of this transport, if they // exist. constauto& certificate_stats_it =
transport_cert_stats.find(transport_name);
std::string local_certificate_id, remote_certificate_id;
RTC_DCHECK(certificate_stats_it != transport_cert_stats.cend()); if (certificate_stats_it != transport_cert_stats.cend()) { if (certificate_stats_it->second.local) {
local_certificate_id = RTCCertificateIDFromFingerprint(
certificate_stats_it->second.local->fingerprint);
} if (certificate_stats_it->second.remote) {
remote_certificate_id = RTCCertificateIDFromFingerprint(
certificate_stats_it->second.remote->fingerprint);
}
}
// There is one transport stats for each channel. for (const cricket::TransportChannelStats& channel_stats :
transport_stats.channel_stats) { auto transport_stats = std::make_unique<RTCTransportStats>(
RTCTransportStatsIDFromTransportChannel(transport_name,
channel_stats.component),
timestamp);
transport_stats->packets_sent =
channel_stats.ice_transport_stats.packets_sent;
transport_stats->packets_received =
channel_stats.ice_transport_stats.packets_received;
transport_stats->bytes_sent =
channel_stats.ice_transport_stats.bytes_sent;
transport_stats->bytes_received =
channel_stats.ice_transport_stats.bytes_received;
transport_stats->dtls_state =
DtlsTransportStateToRTCDtlsTransportState(channel_stats.dtls_state);
transport_stats->selected_candidate_pair_changes =
channel_stats.ice_transport_stats.selected_candidate_pair_changes;
transport_stats->ice_role =
IceRoleToRTCIceRole(channel_stats.ice_transport_stats.ice_role);
transport_stats->ice_local_username_fragment =
channel_stats.ice_transport_stats.ice_local_username_fragment;
transport_stats->ice_state = IceTransportStateToRTCIceTransportState(
channel_stats.ice_transport_stats.ice_state); for (const cricket::ConnectionInfo& info :
channel_stats.ice_transport_stats.connection_infos) { if (info.best_connection) {
transport_stats->selected_candidate_pair_id =
RTCIceCandidatePairStatsIDFromConnectionInfo(info);
}
} if (channel_stats.component != cricket::ICE_CANDIDATE_COMPONENT_RTCP &&
!rtcp_transport_stats_id.empty()) {
transport_stats->rtcp_transport_stats_id = rtcp_transport_stats_id;
} if (!local_certificate_id.empty())
transport_stats->local_certificate_id = local_certificate_id; if (!remote_certificate_id.empty())
transport_stats->remote_certificate_id = remote_certificate_id; // Crypto information if (channel_stats.ssl_version_bytes) { char bytes[5];
snprintf(bytes, sizeof(bytes), "%04X", channel_stats.ssl_version_bytes);
transport_stats->tls_version = bytes;
}
auto remote_cert_chain = pc_->GetRemoteSSLCertChain(transport_name); if (remote_cert_chain) {
certificate_stats_pair.remote = remote_cert_chain->GetStats();
}
transport_cert_stats.insert(
std::make_pair(transport_name, std::move(certificate_stats_pair)));
} // Copy the result into the certificate cache for future reference.
MutexLock lock(&cached_certificates_mutex_); for (constauto& pair : transport_cert_stats) {
cached_certificates_by_transport_.insert(
std::make_pair(pair.first, pair.second.Copy()));
}
} return transport_cert_stats;
}
transceiver_stats_infos_.clear(); // These are used to invoke GetStats for all the media channels together in // one worker thread hop.
std::map<cricket::VoiceMediaSendChannelInterface*,
cricket::VoiceMediaSendInfo>
voice_send_stats;
std::map<cricket::VideoMediaSendChannelInterface*,
cricket::VideoMediaSendInfo>
video_send_stats;
std::map<cricket::VoiceMediaReceiveChannelInterface*,
cricket::VoiceMediaReceiveInfo>
voice_receive_stats;
std::map<cricket::VideoMediaReceiveChannelInterface*,
cricket::VideoMediaReceiveInfo>
video_receive_stats;
auto transceivers = pc_->GetTransceiversInternal();
// TODO(tommi): See if we can avoid synchronously blocking the signaling // thread while we do this (or avoid the BlockingCall at all).
network_thread_->BlockingCall([&] {
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
// Prepare stats entry. The TrackMediaInfoMap will be filled in after the // stats have been fetched on the worker thread.
transceiver_stats_infos_.emplace_back();
RtpTransceiverStatsInfo& stats = transceiver_stats_infos_.back();
stats.transceiver = transceiver;
stats.media_type = media_type;
cricket::ChannelInterface* channel = transceiver->channel(); if (!channel) { // The remaining fields require a BaseChannel. continue;
}
// We jump to the worker thread and call GetStats() on each media channel as // well as GetCallStats(). At the same time we construct the // TrackMediaInfoMaps, which also needs info from the worker thread. This // minimizes the number of thread jumps.
worker_thread_->BlockingCall([&] {
rtc::Thread::ScopedDisallowBlockingCalls no_blocking_calls;
for (auto& pair : voice_send_stats) { if (!pair.first->GetStats(&pair.second)) {
RTC_LOG(LS_WARNING) << "Failed to get voice send stats.";
}
} for (auto& pair : voice_receive_stats) { if (!pair.first->GetStats(&pair.second, /*get_and_clear_legacy_stats=*/false)) {
RTC_LOG(LS_WARNING) << "Failed to get voice receive stats.";
}
} for (auto& pair : video_send_stats) { if (!pair.first->GetStats(&pair.second)) {
RTC_LOG(LS_WARNING) << "Failed to get video send stats.";
}
} for (auto& pair : video_receive_stats) { if (!pair.first->GetStats(&pair.second)) {
RTC_LOG(LS_WARNING) << "Failed to get video receive stats.";
}
}
// Create the TrackMediaInfoMap for each transceiver stats object // and keep track of whether we have at least one audio receiver. bool has_audio_receiver = false; for (auto& stats : transceiver_stats_infos_) { auto transceiver = stats.transceiver;
std::optional<cricket::VoiceMediaInfo> voice_media_info;
std::optional<cricket::VideoMediaInfo> video_media_info; auto channel = transceiver->channel(); if (channel) {
cricket::MediaType media_type = transceiver->media_type(); if (media_type == cricket::MEDIA_TYPE_AUDIO) { auto voice_send_channel = channel->voice_media_send_channel(); auto voice_receive_channel = channel->voice_media_receive_channel();
voice_media_info = cricket::VoiceMediaInfo(
std::move(voice_send_stats[voice_send_channel]),
std::move(voice_receive_stats[voice_receive_channel]));
} elseif (media_type == cricket::MEDIA_TYPE_VIDEO) { auto video_send_channel = channel->video_media_send_channel(); auto video_receive_channel = channel->video_media_receive_channel();
video_media_info = cricket::VideoMediaInfo(
std::move(video_send_stats[video_send_channel]),
std::move(video_receive_stats[video_receive_channel]));
}
}
std::vector<rtc::scoped_refptr<RtpSenderInternal>> senders; for (constauto& sender : transceiver->senders()) {
senders.push_back(
rtc::scoped_refptr<RtpSenderInternal>(sender->internal()));
}
std::vector<rtc::scoped_refptr<RtpReceiverInternal>> receivers; for (constauto& receiver : transceiver->receivers()) {
receivers.push_back(
rtc::scoped_refptr<RtpReceiverInternal>(receiver->internal()));
}
stats.track_media_info_map.Initialize(std::move(voice_media_info),
std::move(video_media_info),
senders, receivers); if (transceiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
has_audio_receiver |= !receivers.empty();
}
}
void RTCStatsCollector::OnSctpDataChannelStateChanged( int channel_id,
DataChannelInterface::DataState state) {
RTC_DCHECK_RUN_ON(signaling_thread_); if (state == DataChannelInterface::DataState::kOpen) { bool result =
internal_record_.opened_data_channels.insert(channel_id).second;
RTC_DCHECK(result);
++internal_record_.data_channels_opened;
} elseif (state == DataChannelInterface::DataState::kClosed) { // Only channels that have been fully opened (and have increased the // `data_channels_opened_` counter) increase the closed counter. if (internal_record_.opened_data_channels.erase(channel_id)) {
++internal_record_.data_channels_closed;
}
}
}
} // namespace webrtc
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.45 Sekunden
(vorverarbeitet am 2026-04-26)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.