Add `RTCRemoteOutboundRtpStreamStats` for audio streams
Changes:
- adding the `RTCRemoteOutboundRtpStreamStats` dictionary (see [1])
- collection of remote outbound stats (only for audio streams)
- adding `remote_id` to the inbound stats and set with the ID of the
corresponding remote outbound stats only if the latter are available
- unit tests
[1] https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats
Tested: verified from chrome://webrtc-internals during an appr.tc call
Bug: webrtc:12529
Change-Id: Ide91dc04a3c387ba439618a9c6b64a95994a1940
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/211042
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33545}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 3b92419..43f4be9 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -388,6 +388,7 @@
RTCRTPStreamStats(std::string&& id, int64_t timestamp_us);
};
+// https://www.w3.org/TR/webrtc-stats/#receivedrtpstats-dict*
class RTC_EXPORT RTCReceivedRtpStreamStats : public RTCRTPStreamStats {
public:
WEBRTC_RTCSTATS_DECL();
@@ -410,6 +411,22 @@
RTCReceivedRtpStreamStats(std::string&& id, int64_t timestamp_us);
};
+// https://www.w3.org/TR/webrtc-stats/#sentrtpstats-dict*
+class RTC_EXPORT RTCSentRtpStreamStats : public RTCRTPStreamStats {
+ public:
+ WEBRTC_RTCSTATS_DECL();
+
+ RTCSentRtpStreamStats(const RTCSentRtpStreamStats& other);
+ ~RTCSentRtpStreamStats() override;
+
+ RTCStatsMember<uint32_t> packets_sent;
+ RTCStatsMember<uint64_t> bytes_sent;
+
+ protected:
+ RTCSentRtpStreamStats(const std::string&& id, int64_t timestamp_us);
+ RTCSentRtpStreamStats(std::string&& id, int64_t timestamp_us);
+};
+
// https://w3c.github.io/webrtc-stats/#inboundrtpstats-dict*
// TODO(hbos): Support the remote case |is_remote = true|.
// https://bugs.webrtc.org/7065
@@ -423,6 +440,7 @@
RTCInboundRTPStreamStats(const RTCInboundRTPStreamStats& other);
~RTCInboundRTPStreamStats() override;
+ RTCStatsMember<std::string> remote_id;
RTCStatsMember<uint32_t> packets_received;
RTCStatsMember<uint64_t> fec_packets_received;
RTCStatsMember<uint64_t> fec_packets_discarded;
@@ -573,6 +591,22 @@
RTCStatsMember<int32_t> round_trip_time_measurements;
};
+// https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict*
+class RTC_EXPORT RTCRemoteOutboundRtpStreamStats final
+ : public RTCSentRtpStreamStats {
+ public:
+ WEBRTC_RTCSTATS_DECL();
+
+ RTCRemoteOutboundRtpStreamStats(const std::string& id, int64_t timestamp_us);
+ RTCRemoteOutboundRtpStreamStats(std::string&& id, int64_t timestamp_us);
+ RTCRemoteOutboundRtpStreamStats(const RTCRemoteOutboundRtpStreamStats& other);
+ ~RTCRemoteOutboundRtpStreamStats() override;
+
+ RTCStatsMember<std::string> local_id;
+ RTCStatsMember<double> remote_timestamp;
+ RTCStatsMember<uint64_t> reports_sent;
+};
+
// https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats
class RTC_EXPORT RTCMediaSourceStats : public RTCStats {
public:
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
index e037ddc..e99e39c 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -264,6 +264,14 @@
stats.decoding_plc_cng = ds.decoded_plc_cng;
stats.decoding_muted_output = ds.decoded_muted_output;
+ stats.last_sender_report_timestamp_ms =
+ call_stats.last_sender_report_timestamp_ms;
+ stats.last_sender_report_remote_timestamp_ms =
+ call_stats.last_sender_report_remote_timestamp_ms;
+ stats.sender_reports_packets_sent = call_stats.sender_reports_packets_sent;
+ stats.sender_reports_bytes_sent = call_stats.sender_reports_bytes_sent;
+ stats.sender_reports_reports_count = call_stats.sender_reports_reports_count;
+
return stats;
}
diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h
index c53791e..6f74492 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -90,6 +90,13 @@
int32_t total_interruption_duration_ms = 0;
// https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-estimatedplayouttimestamp
absl::optional<int64_t> estimated_playout_ntp_timestamp_ms;
+ // Remote outbound stats derived by the received RTCP sender reports.
+ // https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict*
+ absl::optional<int64_t> last_sender_report_timestamp_ms;
+ absl::optional<int64_t> last_sender_report_remote_timestamp_ms;
+ uint32_t sender_reports_packets_sent = 0;
+ uint64_t sender_reports_bytes_sent = 0;
+ uint64_t sender_reports_reports_count = 0;
};
struct Config {
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index c964ce1..9b0ead1 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -536,6 +536,13 @@
// longer than 150 ms).
int32_t interruption_count = 0;
int32_t total_interruption_duration_ms = 0;
+ // Remote outbound stats derived by the received RTCP sender reports.
+ // https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict*
+ absl::optional<int64_t> last_sender_report_timestamp_ms;
+ absl::optional<int64_t> last_sender_report_remote_timestamp_ms;
+ uint32_t sender_reports_packets_sent = 0;
+ uint64_t sender_reports_bytes_sent = 0;
+ uint64_t sender_reports_reports_count = 0;
};
struct VideoSenderInfo : public MediaSenderInfo {
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index a2e6c17..f0ea10d 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -2461,6 +2461,13 @@
stats.relative_packet_arrival_delay_seconds;
rinfo.interruption_count = stats.interruption_count;
rinfo.total_interruption_duration_ms = stats.total_interruption_duration_ms;
+ rinfo.last_sender_report_timestamp_ms =
+ stats.last_sender_report_timestamp_ms;
+ rinfo.last_sender_report_remote_timestamp_ms =
+ stats.last_sender_report_remote_timestamp_ms;
+ rinfo.sender_reports_packets_sent = stats.sender_reports_packets_sent;
+ rinfo.sender_reports_bytes_sent = stats.sender_reports_bytes_sent;
+ rinfo.sender_reports_reports_count = stats.sender_reports_reports_count;
info->receivers.push_back(rinfo);
}
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index c14f414..36ee542 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -109,17 +109,23 @@
return sb.str();
}
-std::string RTCInboundRTPStreamStatsIDFromSSRC(bool audio, uint32_t ssrc) {
+std::string RTCInboundRTPStreamStatsIDFromSSRC(cricket::MediaType media_type,
+ uint32_t ssrc) {
char buf[1024];
rtc::SimpleStringBuilder sb(buf);
- sb << "RTCInboundRTP" << (audio ? "Audio" : "Video") << "Stream_" << ssrc;
+ sb << "RTCInboundRTP"
+ << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+ << "Stream_" << ssrc;
return sb.str();
}
-std::string RTCOutboundRTPStreamStatsIDFromSSRC(bool audio, uint32_t ssrc) {
+std::string RTCOutboundRTPStreamStatsIDFromSSRC(cricket::MediaType media_type,
+ uint32_t ssrc) {
char buf[1024];
rtc::SimpleStringBuilder sb(buf);
- sb << "RTCOutboundRTP" << (audio ? "Audio" : "Video") << "Stream_" << ssrc;
+ sb << "RTCOutboundRTP"
+ << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+ << "Stream_" << ssrc;
return sb.str();
}
@@ -134,6 +140,17 @@
return sb.str();
}
+std::string RTCRemoteOutboundRTPStreamStatsIDFromSSRC(
+ cricket::MediaType media_type,
+ uint32_t source_ssrc) {
+ char buf[1024];
+ rtc::SimpleStringBuilder sb(buf);
+ sb << "RTCRemoteOutboundRTP"
+ << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+ << "Stream_" << source_ssrc;
+ return sb.str();
+}
+
std::string RTCMediaSourceStatsIDFromKindAndAttachment(
cricket::MediaType media_type,
int attachment_id) {
@@ -309,17 +326,21 @@
static_cast<int32_t>(media_receiver_info.packets_lost);
}
-void SetInboundRTPStreamStatsFromVoiceReceiverInfo(
- const std::string& mid,
+std::unique_ptr<RTCInboundRTPStreamStats> CreateInboundAudioStreamStats(
const cricket::VoiceReceiverInfo& voice_receiver_info,
- RTCInboundRTPStreamStats* inbound_audio) {
+ const std::string& mid,
+ int64_t timestamp_us) {
+ auto inbound_audio = std::make_unique<RTCInboundRTPStreamStats>(
+ /*id=*/RTCInboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_AUDIO,
+ voice_receiver_info.ssrc()),
+ timestamp_us);
SetInboundRTPStreamStatsFromMediaReceiverInfo(voice_receiver_info,
- inbound_audio);
+ inbound_audio.get());
inbound_audio->media_type = "audio";
inbound_audio->kind = "audio";
if (voice_receiver_info.codec_payload_type) {
inbound_audio->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
- mid, true, *voice_receiver_info.codec_payload_type);
+ mid, /*inbound=*/true, *voice_receiver_info.codec_payload_type);
}
inbound_audio->jitter = static_cast<double>(voice_receiver_info.jitter_ms) /
rtc::kNumMillisecsPerSec;
@@ -358,6 +379,51 @@
voice_receiver_info.fec_packets_received;
inbound_audio->fec_packets_discarded =
voice_receiver_info.fec_packets_discarded;
+ return inbound_audio;
+}
+
+std::unique_ptr<RTCRemoteOutboundRtpStreamStats>
+CreateRemoteOutboundAudioStreamStats(
+ const cricket::VoiceReceiverInfo& voice_receiver_info,
+ const std::string& mid,
+ const std::string& inbound_audio_id,
+ const std::string& transport_id) {
+ if (!voice_receiver_info.last_sender_report_timestamp_ms.has_value()) {
+ // Cannot create `RTCRemoteOutboundRtpStreamStats` when the RTCP SR arrival
+ // timestamp is not available - i.e., until the first sender report is
+ // received.
+ return nullptr;
+ }
+ RTC_DCHECK_GT(voice_receiver_info.sender_reports_reports_count, 0);
+
+ // Create.
+ auto stats = std::make_unique<RTCRemoteOutboundRtpStreamStats>(
+ /*id=*/RTCRemoteOutboundRTPStreamStatsIDFromSSRC(
+ cricket::MEDIA_TYPE_AUDIO, voice_receiver_info.ssrc()),
+ /*timestamp_us=*/rtc::kNumMicrosecsPerMillisec *
+ voice_receiver_info.last_sender_report_timestamp_ms.value());
+
+ // Populate.
+ // - RTCRtpStreamStats.
+ stats->ssrc = voice_receiver_info.ssrc();
+ stats->kind = "audio";
+ stats->transport_id = transport_id;
+ stats->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
+ mid,
+ /*inbound=*/true, // Remote-outbound same as local-inbound.
+ *voice_receiver_info.codec_payload_type);
+ // - RTCSentRtpStreamStats.
+ stats->packets_sent = voice_receiver_info.sender_reports_packets_sent;
+ stats->bytes_sent = voice_receiver_info.sender_reports_bytes_sent;
+ // - RTCRemoteOutboundRtpStreamStats.
+ stats->local_id = inbound_audio_id;
+ RTC_DCHECK(
+ voice_receiver_info.last_sender_report_remote_timestamp_ms.has_value());
+ stats->remote_timestamp = static_cast<double>(
+ voice_receiver_info.last_sender_report_remote_timestamp_ms.value());
+ stats->reports_sent = voice_receiver_info.sender_reports_reports_count;
+
+ return stats;
}
void SetInboundRTPStreamStatsFromVideoReceiverInfo(
@@ -370,7 +436,7 @@
inbound_video->kind = "video";
if (video_receiver_info.codec_payload_type) {
inbound_video->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
- mid, true, *video_receiver_info.codec_payload_type);
+ mid, /*inbound=*/true, *video_receiver_info.codec_payload_type);
}
inbound_video->jitter = static_cast<double>(video_receiver_info.jitter_ms) /
rtc::kNumMillisecsPerSec;
@@ -454,7 +520,7 @@
outbound_audio->kind = "audio";
if (voice_sender_info.codec_payload_type) {
outbound_audio->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
- mid, false, *voice_sender_info.codec_payload_type);
+ mid, /*inbound=*/false, *voice_sender_info.codec_payload_type);
}
// |fir_count|, |pli_count| and |sli_count| are only valid for video and are
// purposefully left undefined for audio.
@@ -470,7 +536,7 @@
outbound_video->kind = "video";
if (video_sender_info.codec_payload_type) {
outbound_video->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
- mid, false, *video_sender_info.codec_payload_type);
+ mid, /*inbound=*/false, *video_sender_info.codec_payload_type);
}
outbound_video->fir_count =
static_cast<uint32_t>(video_sender_info.firs_rcvd);
@@ -550,8 +616,8 @@
remote_inbound->round_trip_time_measurements =
report_block_data.num_rtts();
- std::string local_id = RTCOutboundRTPStreamStatsIDFromSSRC(
- media_type == cricket::MEDIA_TYPE_AUDIO, report_block.source_ssrc);
+ std::string local_id =
+ RTCOutboundRTPStreamStatsIDFromSSRC(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()) {
@@ -1678,16 +1744,16 @@
std::string mid = *stats.mid;
std::string transport_id = RTCTransportStatsIDFromTransportChannel(
*stats.transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP);
- // Inbound
+ // 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 :
track_media_info_map.voice_media_info()->receivers) {
if (!voice_receiver_info.connected())
continue;
- auto inbound_audio = std::make_unique<RTCInboundRTPStreamStats>(
- RTCInboundRTPStreamStatsIDFromSSRC(true, voice_receiver_info.ssrc()),
- timestamp_us);
- SetInboundRTPStreamStatsFromVoiceReceiverInfo(mid, voice_receiver_info,
- inbound_audio.get());
+ // Inbound.
+ auto inbound_audio =
+ CreateInboundAudioStreamStats(voice_receiver_info, mid, timestamp_us);
// TODO(hta): This lookup should look for the sender, not the track.
rtc::scoped_refptr<AudioTrackInterface> audio_track =
track_media_info_map.GetAudioTrack(voice_receiver_info);
@@ -1698,16 +1764,27 @@
track_media_info_map.GetAttachmentIdByTrack(audio_track).value());
}
inbound_audio->transport_id = transport_id;
+ // Remote-outbound.
+ auto remote_outbound_audio = CreateRemoteOutboundAudioStreamStats(
+ voice_receiver_info, mid, inbound_audio->id(), transport_id);
+ // Add stats.
+ if (remote_outbound_audio) {
+ // When the remote outbound stats are available, the remote ID for the
+ // local inbound stats is set.
+ inbound_audio->remote_id = remote_outbound_audio->id();
+ report->AddStats(std::move(remote_outbound_audio));
+ }
report->AddStats(std::move(inbound_audio));
}
- // Outbound
+ // Outbound.
std::map<std::string, RTCOutboundRTPStreamStats*> audio_outbound_rtps;
for (const cricket::VoiceSenderInfo& voice_sender_info :
track_media_info_map.voice_media_info()->senders) {
if (!voice_sender_info.connected())
continue;
auto outbound_audio = std::make_unique<RTCOutboundRTPStreamStats>(
- RTCOutboundRTPStreamStatsIDFromSSRC(true, voice_sender_info.ssrc()),
+ RTCOutboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_AUDIO,
+ voice_sender_info.ssrc()),
timestamp_us);
SetOutboundRTPStreamStatsFromVoiceSenderInfo(mid, voice_sender_info,
outbound_audio.get());
@@ -1728,7 +1805,7 @@
std::make_pair(outbound_audio->id(), outbound_audio.get()));
report->AddStats(std::move(outbound_audio));
}
- // Remote-inbound
+ // 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
@@ -1765,7 +1842,8 @@
if (!video_receiver_info.connected())
continue;
auto inbound_video = std::make_unique<RTCInboundRTPStreamStats>(
- RTCInboundRTPStreamStatsIDFromSSRC(false, video_receiver_info.ssrc()),
+ RTCInboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_VIDEO,
+ video_receiver_info.ssrc()),
timestamp_us);
SetInboundRTPStreamStatsFromVideoReceiverInfo(mid, video_receiver_info,
inbound_video.get());
@@ -1779,6 +1857,7 @@
}
inbound_video->transport_id = transport_id;
report->AddStats(std::move(inbound_video));
+ // TODO(crbug.com/webrtc/12529): Add remote-outbound stats.
}
// Outbound
std::map<std::string, RTCOutboundRTPStreamStats*> video_outbound_rtps;
@@ -1787,7 +1866,8 @@
if (!video_sender_info.connected())
continue;
auto outbound_video = std::make_unique<RTCOutboundRTPStreamStats>(
- RTCOutboundRTPStreamStatsIDFromSSRC(false, video_sender_info.ssrc()),
+ RTCOutboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_VIDEO,
+ video_sender_info.ssrc()),
timestamp_us);
SetOutboundRTPStreamStatsFromVideoSenderInfo(mid, video_sender_info,
outbound_video.get());
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 35ff48c..897226d 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -119,6 +119,14 @@
const int64_t kGetStatsReportTimeoutMs = 1000;
+// Fake data used by `SetupExampleStatsVoiceGraph()` to fill in remote outbound
+// stats.
+constexpr int64_t kRemoteOutboundStatsTimestampMs = 123;
+constexpr int64_t kRemoteOutboundStatsRemoteTimestampMs = 456;
+constexpr uint32_t kRemoteOutboundStatsPacketsSent = 7u;
+constexpr uint64_t kRemoteOutboundStatsBytesSent = 8u;
+constexpr uint64_t kRemoteOutboundStatsReportsCount = 9u;
+
struct CertificateInfo {
rtc::scoped_refptr<rtc::RTCCertificate> certificate;
std::vector<std::string> ders;
@@ -575,6 +583,11 @@
EXPECT_TRUE_WAIT(callback->report(), kGetStatsReportTimeoutMs);
int64_t after = rtc::TimeUTCMicros();
for (const RTCStats& stats : *callback->report()) {
+ if (stats.type() == RTCRemoteInboundRtpStreamStats::kType ||
+ stats.type() == RTCRemoteOutboundRtpStreamStats::kType) {
+ // Ignore remote timestamps.
+ continue;
+ }
EXPECT_LE(stats.timestamp_us(), after);
}
return callback->report();
@@ -619,6 +632,7 @@
std::string recv_codec_id;
std::string outbound_rtp_id;
std::string inbound_rtp_id;
+ std::string remote_outbound_rtp_id;
std::string transport_id;
std::string sender_track_id;
std::string receiver_track_id;
@@ -627,9 +641,9 @@
std::string media_source_id;
};
- // Sets up the example stats graph (see ASCII art below) used for testing the
- // stats selection algorithm,
- // https://w3c.github.io/webrtc-pc/#dfn-stats-selection-algorithm.
+ // Sets up the example stats graph (see ASCII art below) for a video only
+ // call. The graph is used for testing the stats selection algorithm (see
+ // https://w3c.github.io/webrtc-pc/#dfn-stats-selection-algorithm).
// These tests test the integration of the stats traversal algorithm inside of
// RTCStatsCollector. See rtcstatstraveral_unittest.cc for more stats
// traversal tests.
@@ -731,6 +745,125 @@
return graph;
}
+ // Sets up an example stats graph (see ASCII art below) for an audio only call
+ // and checks that the expected stats are generated.
+ ExampleStatsGraph SetupExampleStatsVoiceGraph(
+ bool add_remote_outbound_stats) {
+ constexpr uint32_t kLocalSsrc = 3;
+ constexpr uint32_t kRemoteSsrc = 4;
+ ExampleStatsGraph graph;
+
+ // codec (send)
+ graph.send_codec_id = "RTCCodec_VoiceMid_Outbound_1";
+ cricket::VoiceMediaInfo media_info;
+ RtpCodecParameters send_codec;
+ send_codec.payload_type = 1;
+ send_codec.clock_rate = 0;
+ media_info.send_codecs.insert(
+ std::make_pair(send_codec.payload_type, send_codec));
+ // codec (recv)
+ graph.recv_codec_id = "RTCCodec_VoiceMid_Inbound_2";
+ RtpCodecParameters recv_codec;
+ recv_codec.payload_type = 2;
+ recv_codec.clock_rate = 0;
+ media_info.receive_codecs.insert(
+ std::make_pair(recv_codec.payload_type, recv_codec));
+ // outbound-rtp
+ graph.outbound_rtp_id = "RTCOutboundRTPAudioStream_3";
+ media_info.senders.push_back(cricket::VoiceSenderInfo());
+ media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo());
+ media_info.senders[0].local_stats[0].ssrc = kLocalSsrc;
+ media_info.senders[0].codec_payload_type = send_codec.payload_type;
+ // inbound-rtp
+ graph.inbound_rtp_id = "RTCInboundRTPAudioStream_4";
+ media_info.receivers.push_back(cricket::VoiceReceiverInfo());
+ media_info.receivers[0].local_stats.push_back(cricket::SsrcReceiverInfo());
+ media_info.receivers[0].local_stats[0].ssrc = kRemoteSsrc;
+ media_info.receivers[0].codec_payload_type = recv_codec.payload_type;
+ // remote-outbound-rtp
+ if (add_remote_outbound_stats) {
+ graph.remote_outbound_rtp_id = "RTCRemoteOutboundRTPAudioStream_4";
+ media_info.receivers[0].last_sender_report_timestamp_ms =
+ kRemoteOutboundStatsTimestampMs;
+ media_info.receivers[0].last_sender_report_remote_timestamp_ms =
+ kRemoteOutboundStatsRemoteTimestampMs;
+ media_info.receivers[0].sender_reports_packets_sent =
+ kRemoteOutboundStatsPacketsSent;
+ media_info.receivers[0].sender_reports_bytes_sent =
+ kRemoteOutboundStatsBytesSent;
+ media_info.receivers[0].sender_reports_reports_count =
+ kRemoteOutboundStatsReportsCount;
+ }
+
+ // transport
+ graph.transport_id = "RTCTransport_TransportName_1";
+ auto* video_media_channel =
+ pc_->AddVoiceChannel("VoiceMid", "TransportName");
+ video_media_channel->SetStats(media_info);
+ // track (sender)
+ graph.sender = stats_->SetupLocalTrackAndSender(
+ cricket::MEDIA_TYPE_AUDIO, "LocalAudioTrackID", kLocalSsrc, false, 50);
+ graph.sender_track_id = "RTCMediaStreamTrack_sender_" +
+ rtc::ToString(graph.sender->AttachmentId());
+ // track (receiver) and stream (remote stream)
+ graph.receiver = stats_->SetupRemoteTrackAndReceiver(
+ cricket::MEDIA_TYPE_AUDIO, "RemoteAudioTrackID", "RemoteStreamId",
+ kRemoteSsrc);
+ graph.receiver_track_id = "RTCMediaStreamTrack_receiver_" +
+ rtc::ToString(graph.receiver->AttachmentId());
+ graph.remote_stream_id = "RTCMediaStream_RemoteStreamId";
+ // peer-connection
+ graph.peer_connection_id = "RTCPeerConnection";
+ // media-source (kind: video)
+ graph.media_source_id =
+ "RTCAudioSource_" + rtc::ToString(graph.sender->AttachmentId());
+
+ // Expected stats graph:
+ //
+ // +--- track (sender) stream (remote stream) ---> track (receiver)
+ // | ^ ^
+ // | | |
+ // | +--------- outbound-rtp inbound-rtp ---------------+
+ // | | | | | |
+ // | | v v v v
+ // | | codec (send) transport codec (recv) peer-connection
+ // v v
+ // media-source
+
+ // Verify the stats graph is set up correctly.
+ graph.full_report = stats_->GetStatsReport();
+ EXPECT_EQ(graph.full_report->size(), add_remote_outbound_stats ? 11u : 10u);
+ EXPECT_TRUE(graph.full_report->Get(graph.send_codec_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.recv_codec_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.outbound_rtp_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.inbound_rtp_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.transport_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.sender_track_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.receiver_track_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.remote_stream_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.peer_connection_id));
+ EXPECT_TRUE(graph.full_report->Get(graph.media_source_id));
+ // `graph.remote_outbound_rtp_id` is omitted on purpose so that expectations
+ // can be added by the caller depending on what value it sets for the
+ // `add_remote_outbound_stats` argument.
+ const auto& sender_track = graph.full_report->Get(graph.sender_track_id)
+ ->cast_to<RTCMediaStreamTrackStats>();
+ EXPECT_EQ(*sender_track.media_source_id, graph.media_source_id);
+ const auto& outbound_rtp = graph.full_report->Get(graph.outbound_rtp_id)
+ ->cast_to<RTCOutboundRTPStreamStats>();
+ EXPECT_EQ(*outbound_rtp.media_source_id, graph.media_source_id);
+ EXPECT_EQ(*outbound_rtp.codec_id, graph.send_codec_id);
+ EXPECT_EQ(*outbound_rtp.track_id, graph.sender_track_id);
+ EXPECT_EQ(*outbound_rtp.transport_id, graph.transport_id);
+ const auto& inbound_rtp = graph.full_report->Get(graph.inbound_rtp_id)
+ ->cast_to<RTCInboundRTPStreamStats>();
+ EXPECT_EQ(*inbound_rtp.codec_id, graph.recv_codec_id);
+ EXPECT_EQ(*inbound_rtp.track_id, graph.receiver_track_id);
+ EXPECT_EQ(*inbound_rtp.transport_id, graph.transport_id);
+
+ return graph;
+ }
+
protected:
rtc::ScopedFakeClock fake_clock_;
rtc::scoped_refptr<FakePeerConnectionForStats> pc_;
@@ -2872,6 +3005,43 @@
::testing::Values(cricket::MEDIA_TYPE_AUDIO, // "/0"
cricket::MEDIA_TYPE_VIDEO)); // "/1"
+// Checks that no remote outbound stats are collected if not available in
+// `VoiceMediaInfo`.
+TEST_F(RTCStatsCollectorTest,
+ RTCRemoteOutboundRtpAudioStreamStatsNotCollected) {
+ ExampleStatsGraph graph =
+ SetupExampleStatsVoiceGraph(/*add_remote_outbound_stats=*/false);
+ EXPECT_FALSE(graph.full_report->Get(graph.remote_outbound_rtp_id));
+ // Also check that no other remote outbound report is created (in case the
+ // expected ID is incorrect).
+ rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+ ASSERT_NE(report->begin(), report->end())
+ << "No reports have been generated.";
+ for (const auto& stats : *report) {
+ SCOPED_TRACE(stats.id());
+ EXPECT_NE(stats.type(), RTCRemoteOutboundRtpStreamStats::kType);
+ }
+}
+
+// Checks that the remote outbound stats are collected when available in
+// `VoiceMediaInfo`.
+TEST_F(RTCStatsCollectorTest, RTCRemoteOutboundRtpAudioStreamStatsCollected) {
+ ExampleStatsGraph graph =
+ SetupExampleStatsVoiceGraph(/*add_remote_outbound_stats=*/true);
+ ASSERT_TRUE(graph.full_report->Get(graph.remote_outbound_rtp_id));
+ const auto& remote_outbound_rtp =
+ graph.full_report->Get(graph.remote_outbound_rtp_id)
+ ->cast_to<RTCRemoteOutboundRtpStreamStats>();
+ EXPECT_EQ(remote_outbound_rtp.timestamp_us(),
+ kRemoteOutboundStatsTimestampMs * rtc::kNumMicrosecsPerMillisec);
+ EXPECT_FLOAT_EQ(*remote_outbound_rtp.remote_timestamp,
+ static_cast<double>(kRemoteOutboundStatsRemoteTimestampMs));
+ EXPECT_EQ(*remote_outbound_rtp.packets_sent, kRemoteOutboundStatsPacketsSent);
+ EXPECT_EQ(*remote_outbound_rtp.bytes_sent, kRemoteOutboundStatsBytesSent);
+ EXPECT_EQ(*remote_outbound_rtp.reports_sent,
+ kRemoteOutboundStatsReportsCount);
+}
+
TEST_F(RTCStatsCollectorTest,
RTCVideoSourceStatsNotCollectedForSenderWithoutTrack) {
const uint32_t kSsrc = 4;
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index a285555..8b12c67 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -399,6 +399,9 @@
} else if (stats.type() == RTCRemoteInboundRtpStreamStats::kType) {
verify_successful &= VerifyRTCRemoteInboundRtpStreamStats(
stats.cast_to<RTCRemoteInboundRtpStreamStats>());
+ } else if (stats.type() == RTCRemoteOutboundRtpStreamStats::kType) {
+ verify_successful &= VerifyRTCRemoteOutboundRTPStreamStats(
+ stats.cast_to<RTCRemoteOutboundRtpStreamStats>());
} else if (stats.type() == RTCAudioSourceStats::kType) {
// RTCAudioSourceStats::kType and RTCVideoSourceStats::kType both have
// the value "media-source", but they are distinguishable with pointer
@@ -769,29 +772,38 @@
}
void VerifyRTCRTPStreamStats(const RTCRTPStreamStats& stream,
- RTCStatsVerifier* verifier) {
- verifier->TestMemberIsDefined(stream.ssrc);
- verifier->TestMemberIsDefined(stream.kind);
+ RTCStatsVerifier& verifier) {
+ verifier.TestMemberIsDefined(stream.ssrc);
+ verifier.TestMemberIsDefined(stream.kind);
// Some legacy metrics are only defined for some of the RTP types in the
// hierarcy.
if (stream.type() == RTCInboundRTPStreamStats::kType ||
stream.type() == RTCOutboundRTPStreamStats::kType) {
- verifier->TestMemberIsDefined(stream.media_type);
- verifier->TestMemberIsIDReference(stream.track_id,
- RTCMediaStreamTrackStats::kType);
+ verifier.TestMemberIsDefined(stream.media_type);
+ verifier.TestMemberIsIDReference(stream.track_id,
+ RTCMediaStreamTrackStats::kType);
} else {
- verifier->TestMemberIsUndefined(stream.media_type);
- verifier->TestMemberIsUndefined(stream.track_id);
+ verifier.TestMemberIsUndefined(stream.media_type);
+ verifier.TestMemberIsUndefined(stream.track_id);
}
- verifier->TestMemberIsIDReference(stream.transport_id,
- RTCTransportStats::kType);
- verifier->TestMemberIsIDReference(stream.codec_id, RTCCodecStats::kType);
+ verifier.TestMemberIsIDReference(stream.transport_id,
+ RTCTransportStats::kType);
+ verifier.TestMemberIsIDReference(stream.codec_id, RTCCodecStats::kType);
+ }
+
+ void VerifyRTCSentRTPStreamStats(const RTCSentRtpStreamStats& sent_stream,
+ RTCStatsVerifier& verifier) {
+ VerifyRTCRTPStreamStats(sent_stream, verifier);
+ verifier.TestMemberIsDefined(sent_stream.packets_sent);
+ verifier.TestMemberIsDefined(sent_stream.bytes_sent);
}
bool VerifyRTCInboundRTPStreamStats(
const RTCInboundRTPStreamStats& inbound_stream) {
RTCStatsVerifier verifier(report_, &inbound_stream);
- VerifyRTCRTPStreamStats(inbound_stream, &verifier);
+ VerifyRTCRTPStreamStats(inbound_stream, verifier);
+ verifier.TestMemberIsOptionalIDReference(
+ inbound_stream.remote_id, RTCRemoteOutboundRtpStreamStats::kType);
if (inbound_stream.media_type.is_defined() &&
*inbound_stream.media_type == "video") {
verifier.TestMemberIsNonNegative<uint64_t>(inbound_stream.qp_sum);
@@ -928,7 +940,7 @@
// TODO(https://crbug.com/webrtc/12532): Invoke
// VerifyRTCReceivedRtpStreamStats() instead of VerifyRTCRTPStreamStats()
// because they have a shared hierarchy now!
- VerifyRTCRTPStreamStats(outbound_stream, &verifier);
+ VerifyRTCRTPStreamStats(outbound_stream, verifier);
if (outbound_stream.media_type.is_defined() &&
*outbound_stream.media_type == "video") {
verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
@@ -1021,16 +1033,16 @@
void VerifyRTCReceivedRtpStreamStats(
const RTCReceivedRtpStreamStats& received_rtp,
- RTCStatsVerifier* verifier) {
+ RTCStatsVerifier& verifier) {
VerifyRTCRTPStreamStats(received_rtp, verifier);
- verifier->TestMemberIsNonNegative<double>(received_rtp.jitter);
- verifier->TestMemberIsDefined(received_rtp.packets_lost);
+ verifier.TestMemberIsNonNegative<double>(received_rtp.jitter);
+ verifier.TestMemberIsDefined(received_rtp.packets_lost);
}
bool VerifyRTCRemoteInboundRtpStreamStats(
const RTCRemoteInboundRtpStreamStats& remote_inbound_stream) {
RTCStatsVerifier verifier(report_, &remote_inbound_stream);
- VerifyRTCReceivedRtpStreamStats(remote_inbound_stream, &verifier);
+ VerifyRTCReceivedRtpStreamStats(remote_inbound_stream, verifier);
verifier.TestMemberIsDefined(remote_inbound_stream.fraction_lost);
verifier.TestMemberIsIDReference(remote_inbound_stream.local_id,
RTCOutboundRTPStreamStats::kType);
@@ -1043,6 +1055,19 @@
return verifier.ExpectAllMembersSuccessfullyTested();
}
+ bool VerifyRTCRemoteOutboundRTPStreamStats(
+ const RTCRemoteOutboundRtpStreamStats& remote_outbound_stream) {
+ RTCStatsVerifier verifier(report_, &remote_outbound_stream);
+ VerifyRTCRTPStreamStats(remote_outbound_stream, verifier);
+ VerifyRTCSentRTPStreamStats(remote_outbound_stream, verifier);
+ verifier.TestMemberIsIDReference(remote_outbound_stream.local_id,
+ RTCOutboundRTPStreamStats::kType);
+ verifier.TestMemberIsNonNegative<double>(
+ remote_outbound_stream.remote_timestamp);
+ verifier.TestMemberIsDefined(remote_outbound_stream.reports_sent);
+ return verifier.ExpectAllMembersSuccessfullyTested();
+ }
+
void VerifyRTCMediaSourceStats(const RTCMediaSourceStats& media_source,
RTCStatsVerifier* verifier) {
verifier->TestMemberIsDefined(media_source.track_identifier);
diff --git a/pc/rtc_stats_traversal.cc b/pc/rtc_stats_traversal.cc
index aa53dde..e579072 100644
--- a/pc/rtc_stats_traversal.cc
+++ b/pc/rtc_stats_traversal.cc
@@ -99,24 +99,36 @@
AddIdIfDefined(track.media_source_id, &neighbor_ids);
} else if (type == RTCPeerConnectionStats::kType) {
// RTCPeerConnectionStats does not have any neighbor references.
- } else if (type == RTCInboundRTPStreamStats::kType ||
- type == RTCOutboundRTPStreamStats::kType) {
- const auto& rtp = static_cast<const RTCRTPStreamStats&>(stats);
- AddIdIfDefined(rtp.track_id, &neighbor_ids);
- AddIdIfDefined(rtp.transport_id, &neighbor_ids);
- AddIdIfDefined(rtp.codec_id, &neighbor_ids);
- if (type == RTCOutboundRTPStreamStats::kType) {
- const auto& outbound_rtp =
- static_cast<const RTCOutboundRTPStreamStats&>(stats);
- AddIdIfDefined(outbound_rtp.media_source_id, &neighbor_ids);
- AddIdIfDefined(outbound_rtp.remote_id, &neighbor_ids);
- }
+ } else if (type == RTCInboundRTPStreamStats::kType) {
+ const auto& inbound_rtp =
+ static_cast<const RTCInboundRTPStreamStats&>(stats);
+ AddIdIfDefined(inbound_rtp.remote_id, &neighbor_ids);
+ AddIdIfDefined(inbound_rtp.track_id, &neighbor_ids);
+ AddIdIfDefined(inbound_rtp.transport_id, &neighbor_ids);
+ AddIdIfDefined(inbound_rtp.codec_id, &neighbor_ids);
+ } else if (type == RTCOutboundRTPStreamStats::kType) {
+ const auto& outbound_rtp =
+ static_cast<const RTCOutboundRTPStreamStats&>(stats);
+ AddIdIfDefined(outbound_rtp.remote_id, &neighbor_ids);
+ AddIdIfDefined(outbound_rtp.track_id, &neighbor_ids);
+ AddIdIfDefined(outbound_rtp.transport_id, &neighbor_ids);
+ AddIdIfDefined(outbound_rtp.codec_id, &neighbor_ids);
+ AddIdIfDefined(outbound_rtp.media_source_id, &neighbor_ids);
} else if (type == RTCRemoteInboundRtpStreamStats::kType) {
const auto& remote_inbound_rtp =
static_cast<const RTCRemoteInboundRtpStreamStats&>(stats);
AddIdIfDefined(remote_inbound_rtp.transport_id, &neighbor_ids);
AddIdIfDefined(remote_inbound_rtp.codec_id, &neighbor_ids);
AddIdIfDefined(remote_inbound_rtp.local_id, &neighbor_ids);
+ } else if (type == RTCRemoteOutboundRtpStreamStats::kType) {
+ const auto& remote_outbound_rtp =
+ static_cast<const RTCRemoteOutboundRtpStreamStats&>(stats);
+ // Inherited from `RTCRTPStreamStats`.
+ AddIdIfDefined(remote_outbound_rtp.track_id, &neighbor_ids);
+ AddIdIfDefined(remote_outbound_rtp.transport_id, &neighbor_ids);
+ AddIdIfDefined(remote_outbound_rtp.codec_id, &neighbor_ids);
+ // Direct members of `RTCRemoteOutboundRtpStreamStats`.
+ AddIdIfDefined(remote_outbound_rtp.local_id, &neighbor_ids);
} else if (type == RTCAudioSourceStats::kType ||
type == RTCVideoSourceStats::kType) {
// RTC[Audio/Video]SourceStats does not have any neighbor references.
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 3a12eea..656cb4a 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -608,7 +608,32 @@
// clang-format off
WEBRTC_RTCSTATS_IMPL(
+ RTCSentRtpStreamStats, RTCRTPStreamStats, "sent-rtp",
+ &packets_sent,
+ &bytes_sent)
+// clang-format on
+
+RTCSentRtpStreamStats::RTCSentRtpStreamStats(const std::string&& id,
+ int64_t timestamp_us)
+ : RTCSentRtpStreamStats(std::string(id), timestamp_us) {}
+
+RTCSentRtpStreamStats::RTCSentRtpStreamStats(std::string&& id,
+ int64_t timestamp_us)
+ : RTCRTPStreamStats(std::move(id), timestamp_us),
+ packets_sent("packetsSent"),
+ bytes_sent("bytesSent") {}
+
+RTCSentRtpStreamStats::RTCSentRtpStreamStats(const RTCSentRtpStreamStats& other)
+ : RTCRTPStreamStats(other),
+ packets_sent(other.packets_sent),
+ bytes_sent(other.bytes_sent) {}
+
+RTCSentRtpStreamStats::~RTCSentRtpStreamStats() {}
+
+// clang-format off
+WEBRTC_RTCSTATS_IMPL(
RTCInboundRTPStreamStats, RTCReceivedRtpStreamStats, "inbound-rtp",
+ &remote_id,
&packets_received,
&fec_packets_received,
&fec_packets_discarded,
@@ -665,6 +690,7 @@
RTCInboundRTPStreamStats::RTCInboundRTPStreamStats(std::string&& id,
int64_t timestamp_us)
: RTCReceivedRtpStreamStats(std::move(id), timestamp_us),
+ remote_id("remoteId"),
packets_received("packetsReceived"),
fec_packets_received("fecPacketsReceived"),
fec_packets_discarded("fecPacketsDiscarded"),
@@ -716,6 +742,7 @@
RTCInboundRTPStreamStats::RTCInboundRTPStreamStats(
const RTCInboundRTPStreamStats& other)
: RTCReceivedRtpStreamStats(other),
+ remote_id(other.remote_id),
packets_received(other.packets_received),
fec_packets_received(other.fec_packets_received),
fec_packets_discarded(other.fec_packets_discarded),
@@ -914,6 +941,37 @@
RTCRemoteInboundRtpStreamStats::~RTCRemoteInboundRtpStreamStats() {}
// clang-format off
+WEBRTC_RTCSTATS_IMPL(
+ RTCRemoteOutboundRtpStreamStats, RTCSentRtpStreamStats,
+ "remote-outbound-rtp",
+ &local_id,
+ &remote_timestamp,
+ &reports_sent)
+// clang-format on
+
+RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
+ const std::string& id,
+ int64_t timestamp_us)
+ : RTCRemoteOutboundRtpStreamStats(std::string(id), timestamp_us) {}
+
+RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
+ std::string&& id,
+ int64_t timestamp_us)
+ : RTCSentRtpStreamStats(std::move(id), timestamp_us),
+ local_id("localId"),
+ remote_timestamp("remoteTimestamp"),
+ reports_sent("reportsSent") {}
+
+RTCRemoteOutboundRtpStreamStats::RTCRemoteOutboundRtpStreamStats(
+ const RTCRemoteOutboundRtpStreamStats& other)
+ : RTCSentRtpStreamStats(other),
+ local_id(other.local_id),
+ remote_timestamp(other.remote_timestamp),
+ reports_sent(other.reports_sent) {}
+
+RTCRemoteOutboundRtpStreamStats::~RTCRemoteOutboundRtpStreamStats() {}
+
+// clang-format off
WEBRTC_RTCSTATS_IMPL(RTCMediaSourceStats, RTCStats, "parent-media-source",
&track_identifier,
&kind)