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}
This commit is contained in:
Alessio Bazzica 2021-03-23 17:23:04 +01:00 committed by Commit Bot
parent 26abdaf478
commit f7b1b95f11
10 changed files with 465 additions and 57 deletions

View file

@ -388,6 +388,7 @@ class RTC_EXPORT RTCRTPStreamStats : public RTCStats {
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 @@ class RTC_EXPORT RTCReceivedRtpStreamStats : public RTCRTPStreamStats {
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 @@ class RTC_EXPORT RTCInboundRTPStreamStats final
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 @@ class RTC_EXPORT RTCRemoteInboundRtpStreamStats final
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:

View file

@ -264,6 +264,14 @@ webrtc::AudioReceiveStream::Stats AudioReceiveStream::GetStats(
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;
}

View file

@ -90,6 +90,13 @@ class AudioReceiveStream {
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 {

View file

@ -536,6 +536,13 @@ struct VoiceReceiverInfo : public MediaReceiverInfo {
// 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 {

View file

@ -2461,6 +2461,13 @@ bool WebRtcVoiceMediaChannel::GetStats(VoiceMediaInfo* info,
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);
}

View file

@ -109,17 +109,23 @@ std::string RTCTransportStatsIDFromTransportChannel(
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 @@ std::string RTCRemoteInboundRtpStreamStatsIdFromSourceSsrc(
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 @@ void SetInboundRTPStreamStatsFromMediaReceiverInfo(
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 @@ void SetInboundRTPStreamStatsFromVoiceReceiverInfo(
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 @@ void SetInboundRTPStreamStatsFromVideoReceiverInfo(
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 @@ void SetOutboundRTPStreamStatsFromVoiceSenderInfo(
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 @@ void SetOutboundRTPStreamStatsFromVideoSenderInfo(
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 @@ ProduceRemoteInboundRtpStreamStatsFromReportBlockData(
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 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n(
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 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n(
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 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n(
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 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n(
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 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n(
}
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 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n(
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());

View file

@ -119,6 +119,14 @@ namespace {
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 @@ class RTCStatsCollectorWrapper {
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 @@ class RTCStatsCollectorTest : public ::testing::Test {
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 @@ class RTCStatsCollectorTest : public ::testing::Test {
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 @@ class RTCStatsCollectorTest : public ::testing::Test {
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 @@ INSTANTIATE_TEST_SUITE_P(All,
::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;

View file

@ -399,6 +399,9 @@ class RTCStatsReportVerifier {
} 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 @@ class RTCStatsReportVerifier {
}
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,
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,
verifier.TestMemberIsIDReference(stream.transport_id,
RTCTransportStats::kType);
verifier->TestMemberIsIDReference(stream.codec_id, RTCCodecStats::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 @@ class RTCStatsReportVerifier {
// 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 @@ class RTCStatsReportVerifier {
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 @@ class RTCStatsReportVerifier {
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);

View file

@ -99,24 +99,36 @@ std::vector<const std::string*> GetStatsReferencedIds(const RTCStats& stats) {
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) {
} 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.media_source_id, &neighbor_ids);
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.

View file

@ -606,9 +606,34 @@ RTCReceivedRtpStreamStats::RTCReceivedRtpStreamStats(
RTCReceivedRtpStreamStats::~RTCReceivedRtpStreamStats() {}
// 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(const std::string& id,
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(std::string&& id,
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),
@ -913,6 +940,37 @@ RTCRemoteInboundRtpStreamStats::RTCRemoteInboundRtpStreamStats(
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,