From 883eefc59e96da2dcb23a36f178610a92587cd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Bostr=C3=B6m?= Date: Mon, 27 May 2019 13:40:25 +0200 Subject: [PATCH] Implement RTCRemoteInboundRtpStreamStats for both audio and video. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This implements the essentials of RTCRemoteInboundRtpStreamStats. This includes: - ssrc - transportId - codecId - packetsLost - jitter - localId - roundTripTime https://w3c.github.io/webrtc-stats/#remoteinboundrtpstats-dict* The following members are not implemented because they require more work... - From RTCReceivedRtpStreamStats: packetsReceived, packetsDiscarded, packetsRepaired, burstPacketsLost, burstPacketsDiscarded, burstLossCount, burstDiscardCount, burstLossRate, burstDiscardRate, gapLossRate and gapDiscardRate. - From RTCRemoteInboundRtpStreamStats: fractionLost. Bug: webrtc:10455, webrtc:10456 Change-Id: If2ab0da7105d8c93bba58e14aa93bd22ffe57f1d Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/138067 Commit-Queue: Henrik Boström Reviewed-by: Harald Alvestrand Cr-Commit-Position: refs/heads/master@{#28073} --- api/stats/rtcstats_objects.h | 40 +++++ pc/BUILD.gn | 1 + pc/rtc_stats_collector.cc | 105 +++++++++++- pc/rtc_stats_collector.h | 2 + pc/rtc_stats_collector_unittest.cc | 255 +++++++++++++++++++++++++++++ pc/rtc_stats_integrationtest.cc | 23 +++ pc/rtc_stats_traversal.cc | 6 + stats/rtcstats_objects.cc | 45 +++++ 8 files changed, 475 insertions(+), 2 deletions(-) diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h index cbcc8fa37d..64bfa190f6 100644 --- a/api/stats/rtcstats_objects.h +++ b/api/stats/rtcstats_objects.h @@ -468,6 +468,46 @@ class RTC_EXPORT RTCOutboundRTPStreamStats final : public RTCRTPStreamStats { RTCStatsMember content_type; }; +// TODO(https://crbug.com/webrtc/10671): Refactor the stats dictionaries to have +// the same hierarchy as in the spec; implement RTCReceivedRtpStreamStats. +// Several metrics are shared between "outbound-rtp", "remote-inbound-rtp", +// "inbound-rtp" and "remote-outbound-rtp". In the spec there is a hierarchy of +// dictionaries that minimizes defining the same metrics in multiple places. +// From JavaScript this hierarchy is not observable and the spec's hierarchy is +// purely editorial. In C++ non-final classes in the hierarchy could be used to +// refer to different stats objects within the hierarchy. +// https://w3c.github.io/webrtc-stats/#remoteinboundrtpstats-dict* +class RTC_EXPORT RTCRemoteInboundRtpStreamStats final : public RTCStats { + public: + WEBRTC_RTCSTATS_DECL(); + + RTCRemoteInboundRtpStreamStats(const std::string& id, int64_t timestamp_us); + RTCRemoteInboundRtpStreamStats(std::string&& id, int64_t timestamp_us); + RTCRemoteInboundRtpStreamStats(const RTCRemoteInboundRtpStreamStats& other); + ~RTCRemoteInboundRtpStreamStats() override; + + // In the spec RTCRemoteInboundRtpStreamStats inherits from RTCRtpStreamStats + // and RTCReceivedRtpStreamStats. The members here are listed based on where + // they are defined in the spec. + // RTCRtpStreamStats + RTCStatsMember ssrc; + RTCStatsMember kind; + RTCStatsMember transport_id; + RTCStatsMember codec_id; + // RTCReceivedRtpStreamStats + RTCStatsMember packets_lost; + RTCStatsMember jitter; + // TODO(hbos): The following RTCReceivedRtpStreamStats metrics should also be + // implemented: packetsReceived, packetsDiscarded, packetsRepaired, + // burstPacketsLost, burstPacketsDiscarded, burstLossCount, burstDiscardCount, + // burstLossRate, burstDiscardRate, gapLossRate and gapDiscardRate. + // RTCRemoteInboundRtpStreamStats + RTCStatsMember local_id; + RTCStatsMember round_trip_time; + // TODO(hbos): The following RTCRemoteInboundRtpStreamStats metric should also + // be implemented: fractionLost. +}; + // https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats class RTC_EXPORT RTCMediaSourceStats : public RTCStats { public: diff --git a/pc/BUILD.gn b/pc/BUILD.gn index 120d3f83a7..0f44f83681 100644 --- a/pc/BUILD.gn +++ b/pc/BUILD.gn @@ -523,6 +523,7 @@ if (rtc_include_tests) { "../media:rtc_media_config", "../modules/audio_device:audio_device_api", "../modules/audio_processing:audio_processing_statistics", + "../modules/rtp_rtcp:rtp_rtcp_format", "../p2p:fake_port_allocator", "../rtc_base:checks", "../rtc_base:gunit_helpers", diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc index 9f82abe663..db7ca61874 100644 --- a/pc/rtc_stats_collector.cc +++ b/pc/rtc_stats_collector.cc @@ -92,6 +92,18 @@ std::string RTCOutboundRTPStreamStatsIDFromSSRC(bool audio, uint32_t ssrc) { return sb.str(); } +std::string RTCRemoteInboundRtpStreamStatsIdFromSsrcs( + cricket::MediaType media_type, + uint32_t sender_ssrc, + uint32_t source_ssrc) { + char buf[1024]; + rtc::SimpleStringBuilder sb(buf); + sb << "RTCRemoteInboundRtp" + << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video") + << "Stream_" << sender_ssrc << "_" << source_ssrc; + return sb.str(); +} + std::string RTCMediaSourceStatsIDFromKindAndAttachment( cricket::MediaType media_type, int attachment_id) { @@ -369,6 +381,71 @@ void SetOutboundRTPStreamStatsFromVideoSenderInfo( outbound_video->content_type = RTCContentType::kScreenshare; } +std::unique_ptr +ProduceRemoteInboundRtpStreamStatsFromReportBlockData( + const ReportBlockData& report_block_data, + cricket::MediaType media_type, + const RTCStatsReport& report) { + const auto& report_block = report_block_data.report_block(); + // 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. + auto remote_inbound = absl::make_unique( + RTCRemoteInboundRtpStreamStatsIdFromSsrcs( + media_type, report_block.sender_ssrc, report_block.source_ssrc), + /*timestamp=*/report_block_data.report_block_timestamp_utc_us()); + remote_inbound->ssrc = report_block.sender_ssrc; + remote_inbound->kind = + media_type == cricket::MEDIA_TYPE_AUDIO ? "audio" : "video"; + remote_inbound->packets_lost = report_block.packets_lost; + remote_inbound->round_trip_time = + static_cast(report_block_data.last_rtt_ms()) / + rtc::kNumMillisecsPerSec; + + std::string local_id = RTCOutboundRTPStreamStatsIDFromSSRC( + media_type == cricket::MEDIA_TYPE_AUDIO, report_block.source_ssrc); + const auto* local_id_stat = report.Get(local_id); + if (local_id_stat) { + remote_inbound->local_id = local_id; + const auto& outbound_rtp = + local_id_stat->cast_to(); + // The RTP/RTCP transport is obtained from the + // RTCOutboundRtpStreamStats's transport. + const auto* transport_from_id = outbound_rtp.transport_id.is_defined() + ? report.Get(*outbound_rtp.transport_id) + : nullptr; + if (transport_from_id) { + const auto& transport = transport_from_id->cast_to(); + // 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.is_defined() + ? *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. + const auto* codec_from_id = outbound_rtp.codec_id.is_defined() + ? report.Get(*outbound_rtp.codec_id) + : nullptr; + if (codec_from_id) { + remote_inbound->codec_id = *outbound_rtp.codec_id; + const auto& codec = codec_from_id->cast_to(); + if (codec.clock_rate.is_defined()) { + // The Report Block jitter is expressed in RTP timestamp units + // (https://tools.ietf.org/html/rfc3550#section-6.4.1). To convert this + // to seconds we divide by the codec's clock rate. + remote_inbound->jitter = + static_cast(report_block.jitter) / *codec.clock_rate; + } + } + } + return remote_inbound; +} + void ProduceCertificateStatsFromSSLCertificateStats( int64_t timestamp_us, const rtc::SSLCertificateStats& certificate_stats, RTCStatsReport* report) { @@ -984,10 +1061,10 @@ void RTCStatsCollector::ProducePartialResultsOnNetworkThreadImpl( ProduceCodecStats_n(timestamp_us, transceiver_stats_infos_, partial_report); ProduceIceCandidateAndPairStats_n(timestamp_us, transport_stats_by_name, call_stats_, partial_report); - ProduceRTPStreamStats_n(timestamp_us, transceiver_stats_infos_, - partial_report); ProduceTransportStats_n(timestamp_us, transport_stats_by_name, transport_cert_stats, partial_report); + ProduceRTPStreamStats_n(timestamp_us, transceiver_stats_infos_, + partial_report); } void RTCStatsCollector::MergeNetworkReport_s() { @@ -1427,6 +1504,18 @@ void RTCStatsCollector::ProduceAudioRTPStreamStats_n( outbound_audio->transport_id = transport_id; report->AddStats(std::move(outbound_audio)); } + // Remote-inbound + // These are Report Block-based, information sent from the remote endpoint, + // providing metrics about our Outbound streams. We take advantage of the fact + // that RTCOutboundRtpStreamStats, RTCCodecStats and RTCTransport have already + // been added to the report. + for (const cricket::VoiceSenderInfo& voice_sender_info : + track_media_info_map.voice_media_info()->senders) { + for (const auto& report_block_data : voice_sender_info.report_block_datas) { + report->AddStats(ProduceRemoteInboundRtpStreamStatsFromReportBlockData( + report_block_data, cricket::MEDIA_TYPE_AUDIO, *report)); + } + } } void RTCStatsCollector::ProduceVideoRTPStreamStats_n( @@ -1488,6 +1577,18 @@ void RTCStatsCollector::ProduceVideoRTPStreamStats_n( outbound_video->transport_id = transport_id; report->AddStats(std::move(outbound_video)); } + // Remote-inbound + // These are Report Block-based, information sent from the remote endpoint, + // providing metrics about our Outbound streams. We take advantage of the fact + // that RTCOutboundRtpStreamStats, RTCCodecStats and RTCTransport have already + // been added to the report. + for (const cricket::VideoSenderInfo& video_sender_info : + track_media_info_map.video_media_info()->senders) { + for (const auto& report_block_data : video_sender_info.report_block_datas) { + report->AddStats(ProduceRemoteInboundRtpStreamStatsFromReportBlockData( + report_block_data, cricket::MEDIA_TYPE_VIDEO, *report)); + } + } } void RTCStatsCollector::ProduceTransportStats_n( diff --git a/pc/rtc_stats_collector.h b/pc/rtc_stats_collector.h index 82501db038..cd5ec21041 100644 --- a/pc/rtc_stats_collector.h +++ b/pc/rtc_stats_collector.h @@ -190,6 +190,8 @@ class RTCStatsCollector : public virtual rtc::RefCountInterface, void ProducePeerConnectionStats_s(int64_t timestamp_us, RTCStatsReport* report) const; // Produces |RTCInboundRTPStreamStats| and |RTCOutboundRTPStreamStats|. + // This has to be invoked after codecs and transport stats have been created + // because some metrics are calculated through lookup of other metrics. void ProduceRTPStreamStats_n( int64_t timestamp_us, const std::vector& transceiver_stats_infos, diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc index 6cd63196a7..c8876bf216 100644 --- a/pc/rtc_stats_collector_unittest.cc +++ b/pc/rtc_stats_collector_unittest.cc @@ -8,6 +8,9 @@ * be found in the AUTHORS file in the root of the source tree. */ +#include + +#include #include #include #include @@ -21,6 +24,8 @@ #include "api/stats/rtc_stats_report.h" #include "api/stats/rtcstats_objects.h" #include "api/units/time_delta.h" +#include "modules/rtp_rtcp/include/report_block_data.h" +#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "p2p/base/p2p_constants.h" #include "p2p/base/port.h" #include "pc/media_stream.h" @@ -89,6 +94,10 @@ void PrintTo(const RTCOutboundRTPStreamStats& stats, ::std::ostream* os) { *os << stats.ToJson(); } +void PrintTo(const RTCRemoteInboundRtpStreamStats& stats, ::std::ostream* os) { + *os << stats.ToJson(); +} + void PrintTo(const RTCAudioSourceStats& stats, ::std::ostream* os) { *os << stats.ToJson(); } @@ -2342,6 +2351,252 @@ TEST_F(RTCStatsCollectorTest, EXPECT_FALSE(report->Get("RTCAudioSource_42")); } +// Parameterized tests on cricket::MediaType (audio or video). +class RTCStatsCollectorTestWithParamKind + : public RTCStatsCollectorTest, + public ::testing::WithParamInterface { + public: + RTCStatsCollectorTestWithParamKind() : media_type_(GetParam()) { + RTC_DCHECK(media_type_ == cricket::MEDIA_TYPE_AUDIO || + media_type_ == cricket::MEDIA_TYPE_VIDEO); + } + + std::string MediaTypeUpperCase() const { + switch (media_type_) { + case cricket::MEDIA_TYPE_AUDIO: + return "Audio"; + case cricket::MEDIA_TYPE_VIDEO: + return "Video"; + case cricket::MEDIA_TYPE_DATA: + RTC_NOTREACHED(); + return ""; + } + } + + std::string MediaTypeLowerCase() const { + std::string str = MediaTypeUpperCase(); + std::transform(str.begin(), str.end(), str.begin(), ::tolower); + return str; + } + + // Adds a sender and channel of the appropriate kind, creating a sender info + // with the report block's |source_ssrc| and report block data. + void AddSenderInfoAndMediaChannel(std::string transport_name, + ReportBlockData report_block_data, + absl::optional codec) { + switch (media_type_) { + case cricket::MEDIA_TYPE_AUDIO: { + cricket::VoiceMediaInfo voice_media_info; + voice_media_info.senders.push_back(cricket::VoiceSenderInfo()); + voice_media_info.senders[0].local_stats.push_back( + cricket::SsrcSenderInfo()); + voice_media_info.senders[0].local_stats[0].ssrc = + report_block_data.report_block().source_ssrc; + if (codec.has_value()) { + voice_media_info.senders[0].codec_payload_type = codec->payload_type; + voice_media_info.send_codecs.insert( + std::make_pair(codec->payload_type, *codec)); + } + voice_media_info.senders[0].report_block_datas.push_back( + report_block_data); + auto* voice_media_channel = pc_->AddVoiceChannel("mid", transport_name); + voice_media_channel->SetStats(voice_media_info); + return; + } + case cricket::MEDIA_TYPE_VIDEO: { + cricket::VideoMediaInfo video_media_info; + video_media_info.senders.push_back(cricket::VideoSenderInfo()); + video_media_info.senders[0].local_stats.push_back( + cricket::SsrcSenderInfo()); + video_media_info.senders[0].local_stats[0].ssrc = + report_block_data.report_block().source_ssrc; + if (codec.has_value()) { + video_media_info.senders[0].codec_payload_type = codec->payload_type; + video_media_info.send_codecs.insert( + std::make_pair(codec->payload_type, *codec)); + } + video_media_info.senders[0].report_block_datas.push_back( + report_block_data); + auto* video_media_channel = pc_->AddVideoChannel("mid", transport_name); + video_media_channel->SetStats(video_media_info); + return; + } + case cricket::MEDIA_TYPE_DATA: + RTC_NOTREACHED(); + } + } + + protected: + cricket::MediaType media_type_; +}; + +// Verifies RTCRemoteInboundRtpStreamStats members that don't require +// RTCCodecStats (codecId, jitter) and without setting up an RTCP transport. +TEST_P(RTCStatsCollectorTestWithParamKind, + RTCRemoteInboundRtpStreamStatsCollectedFromReportBlock) { + const int64_t kReportBlockTimestampUtcUs = 123456789; + const int64_t kRoundTripTimeMs = 13000; + const double kRoundTripTimeSeconds = 13.0; + + // The report block's timestamp cannot be from the future, set the fake clock + // to match. + fake_clock_.SetTime(Timestamp::us(kReportBlockTimestampUtcUs)); + + RTCPReportBlock report_block; + // The remote-inbound-rtp SSRC, "SSRC of sender of this report". + report_block.sender_ssrc = 8; + // The outbound-rtp SSRC, "SSRC of the RTP packet sender". + report_block.source_ssrc = 12; + report_block.packets_lost = 7; + ReportBlockData report_block_data; + report_block_data.SetReportBlock(report_block, kReportBlockTimestampUtcUs); + report_block_data.AddRoundTripTimeSample(1234); + // Only the last sample should be exposed as the + // |RTCRemoteInboundRtpStreamStats::round_trip_time|. + report_block_data.AddRoundTripTimeSample(kRoundTripTimeMs); + + AddSenderInfoAndMediaChannel("TransportName", report_block_data, + absl::nullopt); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + + RTCRemoteInboundRtpStreamStats expected_remote_inbound_rtp( + "RTCRemoteInboundRtp" + MediaTypeUpperCase() + "Stream_8_12", + kReportBlockTimestampUtcUs); + expected_remote_inbound_rtp.ssrc = 8; + expected_remote_inbound_rtp.kind = MediaTypeLowerCase(); + expected_remote_inbound_rtp.transport_id = + "RTCTransport_TransportName_1"; // 1 for RTP (we have no RTCP transport) + expected_remote_inbound_rtp.packets_lost = 7; + expected_remote_inbound_rtp.local_id = + "RTCOutboundRTP" + MediaTypeUpperCase() + "Stream_12"; + expected_remote_inbound_rtp.round_trip_time = kRoundTripTimeSeconds; + // This test does not set up RTCCodecStats, so |codec_id| and |jitter| are + // expected to be missing. These are tested separately. + + ASSERT_TRUE(report->Get(expected_remote_inbound_rtp.id())); + EXPECT_EQ(report->Get(expected_remote_inbound_rtp.id()) + ->cast_to(), + expected_remote_inbound_rtp); + EXPECT_TRUE(report->Get(*expected_remote_inbound_rtp.transport_id)); + EXPECT_TRUE(report->Get(*expected_remote_inbound_rtp.local_id)); +} + +TEST_P(RTCStatsCollectorTestWithParamKind, + RTCRemoteInboundRtpStreamStatsWithTimestampFromReportBlock) { + const int64_t kReportBlockTimestampUtcUs = 123456789; + fake_clock_.SetTime(Timestamp::us(kReportBlockTimestampUtcUs)); + + RTCPReportBlock report_block; + // The remote-inbound-rtp SSRC, "SSRC of sender of this report". + report_block.sender_ssrc = 8; + // The outbound-rtp SSRC, "SSRC of the RTP packet sender". + report_block.source_ssrc = 12; + ReportBlockData report_block_data; + report_block_data.SetReportBlock(report_block, kReportBlockTimestampUtcUs); + + AddSenderInfoAndMediaChannel("TransportName", report_block_data, + absl::nullopt); + + // Advance time, it should be OK to have fresher reports than report blocks. + fake_clock_.AdvanceTime(TimeDelta::us(1234)); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + + std::string remote_inbound_rtp_id = + "RTCRemoteInboundRtp" + MediaTypeUpperCase() + "Stream_8_12"; + ASSERT_TRUE(report->Get(remote_inbound_rtp_id)); + auto& remote_inbound_rtp = report->Get(remote_inbound_rtp_id) + ->cast_to(); + + // Even though the report time is different, the remote-inbound-rtp timestamp + // is of the time that the report block was received. + EXPECT_EQ(kReportBlockTimestampUtcUs + 1234, report->timestamp_us()); + EXPECT_EQ(kReportBlockTimestampUtcUs, remote_inbound_rtp.timestamp_us()); +} + +TEST_P(RTCStatsCollectorTestWithParamKind, + RTCRemoteInboundRtpStreamStatsWithCodecBasedMembers) { + const int64_t kReportBlockTimestampUtcUs = 123456789; + fake_clock_.SetTime(Timestamp::us(kReportBlockTimestampUtcUs)); + + RTCPReportBlock report_block; + // The remote-inbound-rtp SSRC, "SSRC of sender of this report". + report_block.sender_ssrc = 8; + // The outbound-rtp SSRC, "SSRC of the RTP packet sender". + report_block.source_ssrc = 12; + report_block.jitter = 5000; + ReportBlockData report_block_data; + report_block_data.SetReportBlock(report_block, kReportBlockTimestampUtcUs); + + RtpCodecParameters codec; + codec.payload_type = 3; + codec.kind = media_type_; + codec.clock_rate = 1000; + + AddSenderInfoAndMediaChannel("TransportName", report_block_data, codec); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + + std::string remote_inbound_rtp_id = + "RTCRemoteInboundRtp" + MediaTypeUpperCase() + "Stream_8_12"; + ASSERT_TRUE(report->Get(remote_inbound_rtp_id)); + auto& remote_inbound_rtp = report->Get(remote_inbound_rtp_id) + ->cast_to(); + + EXPECT_TRUE(remote_inbound_rtp.codec_id.is_defined()); + EXPECT_TRUE(report->Get(*remote_inbound_rtp.codec_id)); + + EXPECT_TRUE(remote_inbound_rtp.jitter.is_defined()); + // The jitter (in seconds) is the report block's jitter divided by the codec's + // clock rate. + EXPECT_EQ(5.0, *remote_inbound_rtp.jitter); +} + +TEST_P(RTCStatsCollectorTestWithParamKind, + RTCRemoteInboundRtpStreamStatsWithRtcpTransport) { + const int64_t kReportBlockTimestampUtcUs = 123456789; + fake_clock_.SetTime(Timestamp::us(kReportBlockTimestampUtcUs)); + + RTCPReportBlock report_block; + // The remote-inbound-rtp SSRC, "SSRC of sender of this report". + report_block.sender_ssrc = 8; + // The outbound-rtp SSRC, "SSRC of the RTP packet sender". + report_block.source_ssrc = 12; + ReportBlockData report_block_data; + report_block_data.SetReportBlock(report_block, kReportBlockTimestampUtcUs); + + cricket::TransportChannelStats rtp_transport_channel_stats; + rtp_transport_channel_stats.component = cricket::ICE_CANDIDATE_COMPONENT_RTP; + rtp_transport_channel_stats.dtls_state = cricket::DTLS_TRANSPORT_NEW; + cricket::TransportChannelStats rtcp_transport_channel_stats; + rtcp_transport_channel_stats.component = + cricket::ICE_CANDIDATE_COMPONENT_RTCP; + rtcp_transport_channel_stats.dtls_state = cricket::DTLS_TRANSPORT_NEW; + pc_->SetTransportStats("TransportName", {rtp_transport_channel_stats, + rtcp_transport_channel_stats}); + AddSenderInfoAndMediaChannel("TransportName", report_block_data, + absl::nullopt); + + rtc::scoped_refptr report = stats_->GetStatsReport(); + + std::string remote_inbound_rtp_id = + "RTCRemoteInboundRtp" + MediaTypeUpperCase() + "Stream_8_12"; + ASSERT_TRUE(report->Get(remote_inbound_rtp_id)); + auto& remote_inbound_rtp = report->Get(remote_inbound_rtp_id) + ->cast_to(); + + EXPECT_TRUE(remote_inbound_rtp.transport_id.is_defined()); + EXPECT_EQ("RTCTransport_TransportName_2", // 2 for RTCP + *remote_inbound_rtp.transport_id); + EXPECT_TRUE(report->Get(*remote_inbound_rtp.transport_id)); +} + +INSTANTIATE_TEST_SUITE_P(, + RTCStatsCollectorTestWithParamKind, + ::testing::Values(cricket::MEDIA_TYPE_AUDIO, // "/0" + cricket::MEDIA_TYPE_VIDEO)); // "/1" + TEST_F(RTCStatsCollectorTest, RTCVideoSourceStatsNotCollectedForSenderWithoutTrack) { const uint32_t kSsrc = 4; diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc index 10b17e5986..df1d58d77f 100644 --- a/pc/rtc_stats_integrationtest.cc +++ b/pc/rtc_stats_integrationtest.cc @@ -394,6 +394,9 @@ class RTCStatsReportVerifier { } else if (stats.type() == RTCOutboundRTPStreamStats::kType) { verify_successful &= VerifyRTCOutboundRTPStreamStats( stats.cast_to()); + } else if (stats.type() == RTCRemoteInboundRtpStreamStats::kType) { + verify_successful &= VerifyRTCRemoteInboundRtpStreamStats( + stats.cast_to()); } else if (stats.type() == RTCAudioSourceStats::kType) { // RTCAudioSourceStats::kType and RTCVideoSourceStats::kType both have // the value "media-source", but they are distinguishable with pointer @@ -846,6 +849,26 @@ class RTCStatsReportVerifier { return verifier.ExpectAllMembersSuccessfullyTested(); } + bool VerifyRTCRemoteInboundRtpStreamStats( + const RTCRemoteInboundRtpStreamStats& remote_inbound_stream) { + RTCStatsVerifier verifier(report_, &remote_inbound_stream); + verifier.TestMemberIsDefined(remote_inbound_stream.ssrc); + verifier.TestMemberIsDefined(remote_inbound_stream.kind); + verifier.TestMemberIsIDReference(remote_inbound_stream.transport_id, + RTCTransportStats::kType); + verifier.TestMemberIsIDReference(remote_inbound_stream.codec_id, + RTCCodecStats::kType); + verifier.TestMemberIsDefined(remote_inbound_stream.packets_lost); + // Note that the existance of RTCCodecStats is needed for |codec_id| and + // |jitter| to be present. + verifier.TestMemberIsNonNegative(remote_inbound_stream.jitter); + verifier.TestMemberIsIDReference(remote_inbound_stream.local_id, + RTCOutboundRTPStreamStats::kType); + verifier.TestMemberIsNonNegative( + remote_inbound_stream.round_trip_time); + 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 16a6c9d668..a824675a6b 100644 --- a/pc/rtc_stats_traversal.cc +++ b/pc/rtc_stats_traversal.cc @@ -110,6 +110,12 @@ std::vector GetStatsReferencedIds(const RTCStats& stats) { static_cast(stats); AddIdIfDefined(outbound_rtp.media_source_id, &neighbor_ids); } + } else if (type == RTCRemoteInboundRtpStreamStats::kType) { + const auto& remote_inbound_rtp = + static_cast(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 == 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 f97e23fb04..ec2f6e8626 100644 --- a/stats/rtcstats_objects.cc +++ b/stats/rtcstats_objects.cc @@ -720,6 +720,51 @@ RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats( RTCOutboundRTPStreamStats::~RTCOutboundRTPStreamStats() {} +// clang-format off +WEBRTC_RTCSTATS_IMPL( + RTCRemoteInboundRtpStreamStats, RTCStats, "remote-inbound-rtp", + &ssrc, + &kind, + &transport_id, + &codec_id, + &packets_lost, + &jitter, + &local_id, + &round_trip_time) +// clang-format on + +RTCRemoteInboundRtpStreamStats::RTCRemoteInboundRtpStreamStats( + const std::string& id, + int64_t timestamp_us) + : RTCRemoteInboundRtpStreamStats(std::string(id), timestamp_us) {} + +RTCRemoteInboundRtpStreamStats::RTCRemoteInboundRtpStreamStats( + std::string&& id, + int64_t timestamp_us) + : RTCStats(std::move(id), timestamp_us), + ssrc("ssrc"), + kind("kind"), + transport_id("transportId"), + codec_id("codecId"), + packets_lost("packetsLost"), + jitter("jitter"), + local_id("localId"), + round_trip_time("roundTripTime") {} + +RTCRemoteInboundRtpStreamStats::RTCRemoteInboundRtpStreamStats( + const RTCRemoteInboundRtpStreamStats& other) + : RTCStats(other), + ssrc(other.ssrc), + kind(other.kind), + transport_id(other.transport_id), + codec_id(other.codec_id), + packets_lost(other.packets_lost), + jitter(other.jitter), + local_id(other.local_id), + round_trip_time(other.round_trip_time) {} + +RTCRemoteInboundRtpStreamStats::~RTCRemoteInboundRtpStreamStats() {} + // clang-format off WEBRTC_RTCSTATS_IMPL(RTCMediaSourceStats, RTCStats, "parent-media-source", &track_identifier,