// 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( const char direction, const std::string& transport_id, const RtpCodecParameters& codec_params) {
StringBuilder sb;
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.
StringBuilder fmtp;
if (WriteFmtpParameters(codec_params.parameters, fmtp)) {
sb << '_' << fmtp.Release();
} return sb.Release();
}
const char* 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;
}
}
const char* IceCandidatePairStateToRTCStatsIceCandidatePairState(
IceCandidatePairState state) { switch (state) { case IceCandidatePairState::WAITING: return"waiting"; case IceCandidatePairState::IN_PROGRESS: return"in-progress"; case IceCandidatePairState::SUCCEEDED: return"succeeded"; case IceCandidatePairState::FAILED: return"failed"; default:
RTC_DCHECK_NOTREACHED(); return nullptr;
}
}
const char* IceRoleToRTCIceRole(IceRole role) { switch (role) { case IceRole::ICEROLE_UNKNOWN: return"unknown"; case IceRole::ICEROLE_CONTROLLED: return"controlled"; case IceRole::ICEROLE_CONTROLLING: return"controlling"; default:
RTC_DCHECK_NOTREACHED(); return nullptr;
}
}
const char* 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;
}
}
const char* 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;
}
}
const char* NetworkTypeToStatsType(AdapterType type) { switch (type) { case ADAPTER_TYPE_CELLULAR: case ADAPTER_TYPE_CELLULAR_2G: case ADAPTER_TYPE_CELLULAR_3G: case ADAPTER_TYPE_CELLULAR_4G: case ADAPTER_TYPE_CELLULAR_5G: return"cellular"; case ADAPTER_TYPE_ETHERNET: return"ethernet"; case ADAPTER_TYPE_WIFI: return"wifi"; case ADAPTER_TYPE_VPN: return"vpn"; case ADAPTER_TYPE_UNKNOWN: case ADAPTER_TYPE_LOOPBACK: case ADAPTER_TYPE_ANY: return"unknown";
}
RTC_DCHECK_NOTREACHED(); return nullptr;
}
absl::string_view NetworkTypeToStatsNetworkAdapterType(AdapterType type) { switch (type) { case ADAPTER_TYPE_CELLULAR: return"cellular"; case ADAPTER_TYPE_CELLULAR_2G: return"cellular2g"; case ADAPTER_TYPE_CELLULAR_3G: return"cellular3g"; case ADAPTER_TYPE_CELLULAR_4G: return"cellular4g"; case ADAPTER_TYPE_CELLULAR_5G: return"cellular5g"; case ADAPTER_TYPE_ETHERNET: return"ethernet"; case ADAPTER_TYPE_WIFI: return"wifi"; case ADAPTER_TYPE_UNKNOWN: return"unknown"; case ADAPTER_TYPE_LOOPBACK: return"loopback"; case ADAPTER_TYPE_ANY: return"any"; case ADAPTER_TYPE_VPN: /* should not be handled here. Vpn is modelled as a bool */ break;
}
RTC_DCHECK_NOTREACHED(); return {};
}
absl::string_view NetworkSliceToStatsNetworkSlice(NetworkSlice slice) { switch (slice) { case NetworkSlice::UNIFIED_COMMUNICATIONS: return"unified-communications"; case NetworkSlice::NO_SLICE: /* Should not be handled here. Report empty slice in stats instead. */ break;
}
RTC_DCHECK_NOTREACHED(); return {};
}
const char* 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>(kNumMillisecsPerSec);
} return result;
}
std::unique_ptr<RTCRemoteInboundRtpStreamStats>
ProduceRemoteInboundRtpStreamStats( const std::string& transport_id, const ReportBlockData& report_block,
MediaType media_type, const std::map<std::string, RTCOutboundRtpStreamStats*>& outbound_rtps, const RTCStatsReport& report, const Call::Stats& call_stats, 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);
uint32_t ssrc = report_block.source_ssrc();
remote_inbound->ssrc = ssrc;
remote_inbound->kind = media_type == MediaType::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();
if (auto it = call_stats.received_ccfb_stats_per_ssrc.find(ssrc);
it != call_stats.received_ccfb_stats_per_ssrc.end()) {
remote_inbound->packets_received_with_ect1 =
it->second.num_packets_received_with_ect1;
remote_inbound->packets_received_with_ce =
it->second.num_packets_received_with_ce;
remote_inbound->packets_reported_as_lost =
it->second.num_packets_reported_as_lost;
remote_inbound->packets_reported_as_lost_but_recovered =
it->second.num_packets_reported_as_lost_but_recovered;
remote_inbound->packets_with_bleached_ect1_marking =
it->second.num_packets_with_bleached_ect1_marking;
}
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 SSLCertificateStats& certificate_stats,
RTCStatsReport* report) {
RTCCertificateStats* prev_certificate_stats = nullptr;
for (const 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* current_certificate_stats =
new RTCCertificateStats(certificate_stats_id, timestamp);
current_certificate_stats->fingerprint = s->fingerprint;
current_certificate_stats->fingerprint_algorithm = s->fingerprint_algorithm;
current_certificate_stats->base64_certificate = s->base64_certificate;
if (prev_certificate_stats)
prev_certificate_stats->issuer_certificate_id =
current_certificate_stats->id();
report->AddStats(
std::unique_ptr<RTCCertificateStats>(current_certificate_stats));
prev_certificate_stats = current_certificate_stats;
}
}
// Reports that are produced on the signaling thread or the network thread are // merged into this report. It is only touched on the signaling thread. Once // all partial reports are merged this is the result of a request.
scoped_refptr<RTCStatsReport> partial_report;
};
// "Now" using a monotonically increasing timer.
int64_t cache_now_us = env_.clock().TimeInMicroseconds();
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(
SafeTask(signaling_safety_, [this, request = std::move(request),
report = cached_report_]() mutable {
DeliverReport(request, report);
})); return;
}
requests_.push_back(std::move(request));
if (collection_context_) { // A stats gathering operation is already in progress. return;
}
// 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.
// Initialize common variables for the stats gather operation.
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(TimeUTCMicros());
// Prepare `transceiver_stats_infos` and `call_stats` for use in // `ProducePartialResultsOnNetworkThread` and // `ProducePartialResultsOnSignalingThread`. auto worker_task = PrepareTransceiverStatsInfosAndCallStats_s_w();
auto signaling_task = [this](WorkerThreadResult worker_result) mutable {
RTC_DCHECK_RUN_ON(signaling_thread_); // Create the initial `partial_report_` for the gathering operation.
ProducePartialResultsOnSignalingThread(
worker_result.results.transceiver_stats_infos,
worker_result.transceiver_references,
worker_result.results.audio_device_stats);
Timestamp timestamp = collection_context_->partial_report->timestamp();
std::set<std::string> transport_names; auto sctp_transport_name = pc_->sctp_transport_name();
if (sctp_transport_name) {
transport_names.emplace(std::move(*sctp_transport_name));
}
for (constauto& info : worker_result.results.transceiver_stats_infos) {
if (info.transport_name)
transport_names.insert(*info.transport_name);
}
// 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());
// Clear `requests_` before the loop in case a callback adds a request. auto requests = std::move(requests_);
for (const RequestInfo& request : requests) {
DeliverReport(request, cached_report_);
}
}
for (constauto& stats : data_stats) { auto data_channel_stats = std::make_unique<RTCDataChannelStats>( "D" + absl::StrCat(stats.internal_id), timestamp);
data_channel_stats->label = std::move(stats.label);
data_channel_stats->protocol = std::move(stats.protocol);
if (stats.id >= 0) { // Do not set this value before the DTLS handshake is finished // and filter out the magic value -1.
data_channel_stats->data_channel_identifier = stats.id;
}
data_channel_stats->state = DataStateToRTCDataChannelState(stats.state);
data_channel_stats->messages_sent = stats.messages_sent;
data_channel_stats->bytes_sent = stats.bytes_sent;
data_channel_stats->messages_received = stats.messages_received;
data_channel_stats->bytes_received = stats.bytes_received;
report->AddStats(std::move(data_channel_stats));
}
}
// 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);
}
}
}
}
RTC_DCHECK_EQ(transceiver_stats_infos.size(), transceiver_references.size());
for (size_t i = 0; i < transceiver_stats_infos.size(); ++i) { const RtpTransceiverStatsInfo& transceiver_stats_info =
transceiver_stats_infos[i]; const TransceiverReferences& refs = transceiver_references[i];
// The transceiver will still exist but in a stopped state after pc.close().
if (transceiver_stats_info.current_direction ==
RtpTransceiverDirection::kStopped) { continue;
}
RTC_DCHECK(transceiver_stats_info.track_media_info_map); const TrackMediaInfoMap& track_media_info_map =
*transceiver_stats_info.track_media_info_map;
RTC_ALLOW_PLAN_B_DEPRECATION_BEGIN()
for (constauto& sender : refs.transceiver->senders()) {
RTC_ALLOW_PLAN_B_DEPRECATION_END() 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 // webrtc::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 (sender_internal->media_type() == MediaType::AUDIO) {
AudioTrackInterface* audio_track = static_cast<AudioTrackInterface*>(track.get()); auto audio_source_stats = std::make_unique<RTCAudioSourceStats>(
RTCMediaSourceStatsIDFromKindAndAttachment(
MediaType::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) { // 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(
MediaType::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));
}
}
}
std::string mid = *stats.mid;
std::string transport_id = RTCTransportStatsIDFromTransportChannel(
*stats.transport_name, 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 VoiceReceiverInfo& voice_receiver_info :
stats.track_media_info_map->voice_media_info()->receivers) {
if (!voice_receiver_info.connected()) { continue; // The SSRC is not known yet.
} // Check both packets received and samples received to handle the Insertable // Streams use case of receiving media without receiving packets.
if (is_unified_plan_ && voice_receiver_info.packets_received == 0 &&
voice_receiver_info.total_samples_received == 0) { // The SSRC is known despite not receiving any packets. This happens if // SSRC is signalled in the SDP which we should not rely on for getStats. continue;
} // Inbound.
std::unique_ptr<RTCInboundRtpStreamStats> inbound_audio =
CreateInboundAudioStreamStats(
*stats.track_media_info_map->voice_media_info(),
voice_receiver_info, transport_id, mid, timestamp, report);
AppendCallStats(call_stats, *inbound_audio); // TODO(hta): This lookup should look for the sender, not the track. auto track_id = stats.track_media_info_map->GetReceiverTrackIdBySsrc(
voice_receiver_info.ssrc(), MediaType::AUDIO);
if (track_id.has_value()) {
inbound_audio->track_identifier = *track_id;
}
if (audio_device_stats && stats.media_type == MediaType::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, MediaType::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 VoiceSenderInfo& voice_sender_info :
stats.track_media_info_map->voice_media_info()->senders) {
if (!voice_sender_info.connected()) { continue; // The SSRC is not known yet.
}
if (is_unified_plan_ && !stats.current_direction.has_value()) { continue; // The SSRC is known but the O/A has not completed.
} auto outbound_audio = CreateOutboundRTPStreamStatsFromVoiceSenderInfo(
transport_id, mid, *stats.track_media_info_map->voice_media_info(),
voice_sender_info, timestamp, report);
if (!voice_sender_info.local_stats.empty()) { auto attachment_id = stats.track_media_info_map->GetAttachmentIdBySsrc(
voice_sender_info.local_stats[0].ssrc, MediaType::AUDIO, /*is_sender=*/true);
if (attachment_id.has_value()) {
outbound_audio->media_source_id =
RTCMediaSourceStatsIDFromKindAndAttachment(MediaType::AUDIO,
attachment_id.value());
}
} 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 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(ProduceRemoteInboundRtpStreamStats(
transport_id, report_block_data, MediaType::AUDIO,
audio_outbound_rtps, *report, call_stats,
stats_timestamp_with_environment_clock_));
}
}
}
std::string mid = *stats.mid;
std::string transport_id = RTCTransportStatsIDFromTransportChannel(
*stats.transport_name, ICE_CANDIDATE_COMPONENT_RTP); // Inbound and remote-outbound.
for (const VideoReceiverInfo& video_receiver_info :
stats.track_media_info_map->video_media_info()->receivers) {
if (!video_receiver_info.connected()) { continue; // The SSRC is not known yet.
} // Check both packets received and frames received to handle the Insertable // Streams use case of receiving media without receiving packets.
if (is_unified_plan_ && video_receiver_info.packets_received == 0 &&
video_receiver_info.frames_received == 0) { // The SSRC is known despite not receiving any packets. This happens if // SSRC is signalled in the SDP which we should not rely on for getStats. continue;
}
std::unique_ptr<RTCInboundRtpStreamStats> inbound_video =
CreateInboundRTPStreamStatsFromVideoReceiverInfo(
transport_id, mid, *stats.track_media_info_map->video_media_info(),
video_receiver_info, timestamp, report);
AppendCallStats(call_stats, *inbound_video); auto track_id = stats.track_media_info_map->GetReceiverTrackIdBySsrc(
video_receiver_info.ssrc(), MediaType::VIDEO);
if (track_id.has_value()) {
inbound_video->track_identifier = *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, MediaType::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 VideoSenderInfo& video_sender_info :
stats.track_media_info_map->video_media_info()->senders) {
if (!video_sender_info.connected()) { continue; // The SSRC is not known yet.
}
if (is_unified_plan_ && !stats.current_direction.has_value()) { continue; // The SSRC is known but the O/A has not completed.
} auto outbound_video = CreateOutboundRTPStreamStatsFromVideoSenderInfo(
transport_id, mid, *stats.track_media_info_map->video_media_info(),
video_sender_info, timestamp, report);
if (!video_sender_info.local_stats.empty()) { auto attachment_id = stats.track_media_info_map->GetAttachmentIdBySsrc(
video_sender_info.local_stats[0].ssrc, MediaType::VIDEO, /*is_sender=*/true);
if (attachment_id.has_value()) {
outbound_video->media_source_id =
RTCMediaSourceStatsIDFromKindAndAttachment(MediaType::VIDEO,
attachment_id.value());
}
} 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 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(ProduceRemoteInboundRtpStreamStats(
transport_id, report_block_data, MediaType::VIDEO,
video_outbound_rtps, *report, call_stats,
stats_timestamp_with_environment_clock_));
}
}
}
// Get reference to RTCP channel, if it exists.
std::string rtcp_transport_stats_id;
for (const TransportChannelStats& channel_stats :
transport_stats.channel_stats) {
if (channel_stats.component == 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 TransportChannelStats& channel_stats :
transport_stats.channel_stats) { auto channel_transport_stats = std::make_unique<RTCTransportStats>(
RTCTransportStatsIDFromTransportChannel(transport_name,
channel_stats.component),
timestamp);
channel_transport_stats->packets_sent =
channel_stats.ice_transport_stats.packets_sent;
channel_transport_stats->packets_received =
channel_stats.ice_transport_stats.packets_received;
channel_transport_stats->bytes_sent =
channel_stats.ice_transport_stats.bytes_sent;
channel_transport_stats->bytes_received =
channel_stats.ice_transport_stats.bytes_received;
channel_transport_stats->dtls_state =
DtlsTransportStateToRTCDtlsTransportState(channel_stats.dtls_state);
channel_transport_stats->selected_candidate_pair_changes =
channel_stats.ice_transport_stats.selected_candidate_pair_changes;
channel_transport_stats->ice_role =
IceRoleToRTCIceRole(channel_stats.ice_transport_stats.ice_role);
if (!channel_stats.ice_transport_stats.ice_local_username_fragment
.empty()) {
channel_transport_stats->ice_local_username_fragment =
channel_stats.ice_transport_stats.ice_local_username_fragment;
}
channel_transport_stats->ice_state =
IceTransportStateToRTCIceTransportState(
channel_stats.ice_transport_stats.ice_state);
for (const ConnectionInfo& info :
channel_stats.ice_transport_stats.connection_infos) {
if (info.best_connection) {
channel_transport_stats->selected_candidate_pair_id =
RTCIceCandidatePairStatsIDFromConnectionInfo(info);
}
}
if (channel_stats.component != ICE_CANDIDATE_COMPONENT_RTCP &&
!rtcp_transport_stats_id.empty()) {
channel_transport_stats->rtcp_transport_stats_id =
rtcp_transport_stats_id;
}
if (!local_certificate_id.empty())
channel_transport_stats->local_certificate_id = local_certificate_id;
if (!remote_certificate_id.empty())
channel_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);
channel_transport_stats->tls_version = bytes;
}
std::map<std::string, CertificateStatsPair> transport_cert_stats; // Copy the certificate info from the cache, avoiding expensive // webrtc::SSLCertChain::GetStats() calls.
for (constauto& pair : cached_certificates_by_transport_) {
transport_cert_stats.insert(std::make_pair(pair.first, pair.second.Copy()));
}
if (transport_cert_stats.empty()) { // Collect certificate info.
for (constauto& entry : transport_stats_by_name) { const std::string& transport_name = entry.first;
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.
for (constauto& pair : transport_cert_stats) {
cached_certificates_by_transport_.insert(
std::make_pair(pair.first, pair.second.Copy()));
}
} return transport_cert_stats;
}
// Embed the collected information into this lambda which will run on the // worker thread. There, we'll 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. return [this, transceiver_stats_infos = std::move(transceiver_stats_infos),
transceiver_references =
std::move(transceiver_references)]() mutable {
Thread::ScopedDisallowBlockingCalls no_blocking_calls;
// 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;
RTC_DCHECK_EQ(worker_result.results.transceiver_stats_infos.size(),
worker_result.transceiver_references.size());
for (size_t i = 0; i < worker_result.results.transceiver_stats_infos.size();
++i) { auto& stats = worker_result.results.transceiver_stats_infos[i]; auto& refs = worker_result.transceiver_references[i]; // The transceiver will still exist but in a stopped state after // pc.close().
if (stats.current_direction == RtpTransceiverDirection::kStopped) { continue;
}
std::vector<RtpParameters> sender_parameters;
for (constauto& sender : stats.sender_infos) {
RtpParameters params;
if (sender.ssrc != 0 && stats.has_channel) {
params = refs.get_send_parameters(sender.ssrc);
} // `sender_parameters` must be the same size as `stats.sender_infos` to // allow 1:1 mapping when TrackMediaInfoMap is constructed. Empty params // are safely ignored since they have no encodings/SSRCs.
sender_parameters.push_back(std::move(params));
}
std::vector<RtpParameters> receiver_parameters;
for (constauto& receiver : refs.receivers) {
receiver_parameters.push_back(receiver->GetParameters());
}
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;
} else if (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;
}
}
}
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.