From bc1c93dc6eba45f6af1c10673a5c8ce37c22af34 Mon Sep 17 00:00:00 2001 From: Alessio Bazzica Date: Fri, 12 Mar 2021 17:45:26 +0100 Subject: [PATCH] Add remote-outbound stats for audio streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add missing members needed to surface `RTCRemoteOutboundRtpStreamStats` via `ChannelReceive::GetRTCPStatistics()` - i.e., audio streams. `GetSenderReportStats()` is added to both `ModuleRtpRtcpImpl` and `ModuleRtpRtcpImpl2` and used by `ChannelReceive::GetRTCPStatistics()`. Bug: webrtc:12529 Change-Id: Ia8f5dfe2e4cfc43e3ddd28f2f1149f5c00f9269d Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/211041 Reviewed-by: Danil Chapovalov Reviewed-by: Åsa Persson Reviewed-by: Gustaf Ullberg Commit-Queue: Alessio Bazzica Cr-Commit-Position: refs/heads/master@{#33452} --- audio/channel_receive.cc | 33 ++++-- audio/channel_receive.h | 7 ++ modules/rtp_rtcp/BUILD.gn | 1 + modules/rtp_rtcp/mocks/mock_rtp_rtcp.h | 4 + modules/rtp_rtcp/source/rtp_rtcp_impl.cc | 21 ++++ modules/rtp_rtcp/source/rtp_rtcp_impl.h | 1 + modules/rtp_rtcp/source/rtp_rtcp_impl2.cc | 21 ++++ modules/rtp_rtcp/source/rtp_rtcp_impl2.h | 1 + .../source/rtp_rtcp_impl2_unittest.cc | 105 ++++++++++++++++++ .../rtp_rtcp/source/rtp_rtcp_impl_unittest.cc | 103 +++++++++++++++++ modules/rtp_rtcp/source/rtp_rtcp_interface.h | 24 ++++ video/rtp_video_stream_receiver.cc | 4 +- video/rtp_video_stream_receiver2.cc | 4 +- 13 files changed, 318 insertions(+), 11 deletions(-) diff --git a/audio/channel_receive.cc b/audio/channel_receive.cc index 90e93bd72b..8b3c924e0a 100644 --- a/audio/channel_receive.cc +++ b/audio/channel_receive.cc @@ -695,8 +695,10 @@ void ChannelReceive::ReceivedRTCPPacket(const uint8_t* data, size_t length) { uint32_t ntp_secs = 0; uint32_t ntp_frac = 0; uint32_t rtp_timestamp = 0; - if (0 != - rtp_rtcp_->RemoteNTP(&ntp_secs, &ntp_frac, NULL, NULL, &rtp_timestamp)) { + if (rtp_rtcp_->RemoteNTP(&ntp_secs, &ntp_frac, + /*rtcp_arrival_time_secs=*/nullptr, + /*rtcp_arrival_time_frac=*/nullptr, + &rtp_timestamp) != 0) { // Waiting for RTCP. return; } @@ -753,11 +755,10 @@ void ChannelReceive::ResetReceiverCongestionControlObjects() { CallReceiveStatistics ChannelReceive::GetRTCPStatistics() const { RTC_DCHECK(worker_thread_checker_.IsCurrent()); - // --- RtcpStatistics CallReceiveStatistics stats; - // The jitter statistics is updated for each received RTP packet and is - // based on received packets. + // The jitter statistics is updated for each received RTP packet and is based + // on received packets. RtpReceiveStats rtp_stats; StreamStatistician* statistician = rtp_receive_statistics_->GetStatistician(remote_ssrc_); @@ -768,10 +769,9 @@ CallReceiveStatistics ChannelReceive::GetRTCPStatistics() const { stats.cumulativeLost = rtp_stats.packets_lost; stats.jitterSamples = rtp_stats.jitter; - // --- RTT stats.rttMs = GetRTT(); - // --- Data counters + // Data counters. if (statistician) { stats.payload_bytes_rcvd = rtp_stats.packet_counter.payload_bytes; @@ -788,11 +788,24 @@ CallReceiveStatistics ChannelReceive::GetRTCPStatistics() const { stats.last_packet_received_timestamp_ms = absl::nullopt; } - // --- Timestamps + // Timestamps. { MutexLock lock(&ts_stats_lock_); stats.capture_start_ntp_time_ms_ = capture_start_ntp_time_ms_; } + + absl::optional rtcp_sr_stats = + rtp_rtcp_->GetSenderReportStats(); + if (rtcp_sr_stats.has_value()) { + stats.last_sender_report_timestamp_ms = + rtcp_sr_stats->last_arrival_timestamp.ToMs(); + stats.last_sender_report_remote_timestamp_ms = + rtcp_sr_stats->last_remote_timestamp.ToMs(); + stats.sender_reports_packets_sent = rtcp_sr_stats->packets_sent; + stats.sender_reports_bytes_sent = rtcp_sr_stats->bytes_sent; + stats.sender_reports_reports_count = rtcp_sr_stats->reports_count; + } + return stats; } @@ -913,7 +926,9 @@ absl::optional ChannelReceive::GetSyncInfo() const { RTC_DCHECK(module_process_thread_checker_.IsCurrent()); Syncable::Info info; if (rtp_rtcp_->RemoteNTP(&info.capture_time_ntp_secs, - &info.capture_time_ntp_frac, nullptr, nullptr, + &info.capture_time_ntp_frac, + /*rtcp_arrival_time_secs=*/nullptr, + /*rtcp_arrival_time_frac=*/nullptr, &info.capture_time_source_clock) != 0) { return absl::nullopt; } diff --git a/audio/channel_receive.h b/audio/channel_receive.h index 32bc813cc5..261357d578 100644 --- a/audio/channel_receive.h +++ b/audio/channel_receive.h @@ -65,6 +65,13 @@ struct CallReceiveStatistics { // local clock when it was received - not the RTP timestamp of that packet. // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-lastpacketreceivedtimestamp absl::optional last_packet_received_timestamp_ms; + // Remote outbound stats derived by the received RTCP sender reports. + // https://w3c.github.io/webrtc-stats/#remoteoutboundrtpstats-dict* + absl::optional last_sender_report_timestamp_ms; + absl::optional 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; }; namespace voe { diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index 4ca7266579..a42c1784ee 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn @@ -338,6 +338,7 @@ rtc_source_set("rtp_rtcp_legacy") { "../../rtc_base:gtest_prod", "../../rtc_base:rtc_base_approved", "../../rtc_base/synchronization:mutex", + "../../system_wrappers", "../remote_bitrate_estimator", ] absl_deps = [ diff --git a/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h b/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h index 77289c993b..999979def8 100644 --- a/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h +++ b/modules/rtp_rtcp/mocks/mock_rtp_rtcp.h @@ -149,6 +149,10 @@ class MockRtpRtcpInterface : public RtpRtcpInterface { GetLatestReportBlockData, (), (const, override)); + MOCK_METHOD(absl::optional, + GetSenderReportStats, + (), + (const, override)); MOCK_METHOD(void, SetRemb, (int64_t bitrate, std::vector ssrcs), diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc index c0c67cc64d..04ef821fb9 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl.cc +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.cc @@ -24,6 +24,7 @@ #include "modules/rtp_rtcp/source/rtp_rtcp_config.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "system_wrappers/include/ntp_time.h" #ifdef _WIN32 // Disable warning C4355: 'this' : used in base member initializer list. @@ -545,6 +546,26 @@ std::vector ModuleRtpRtcpImpl::GetLatestReportBlockData() return rtcp_receiver_.GetLatestReportBlockData(); } +absl::optional +ModuleRtpRtcpImpl::GetSenderReportStats() const { + SenderReportStats stats; + uint32_t remote_timestamp_secs; + uint32_t remote_timestamp_frac; + uint32_t arrival_timestamp_secs; + uint32_t arrival_timestamp_frac; + if (rtcp_receiver_.NTP(&remote_timestamp_secs, &remote_timestamp_frac, + &arrival_timestamp_secs, &arrival_timestamp_frac, + /*rtcp_timestamp=*/nullptr, &stats.packets_sent, + &stats.bytes_sent, &stats.reports_count)) { + stats.last_remote_timestamp.Set(remote_timestamp_secs, + remote_timestamp_frac); + stats.last_arrival_timestamp.Set(arrival_timestamp_secs, + arrival_timestamp_frac); + return stats; + } + return absl::nullopt; +} + // (REMB) Receiver Estimated Max Bitrate. void ModuleRtpRtcpImpl::SetRemb(int64_t bitrate_bps, std::vector ssrcs) { diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl.h b/modules/rtp_rtcp/source/rtp_rtcp_impl.h index 9e5f3970c6..9bcdd0c9e6 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl.h +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl.h @@ -196,6 +196,7 @@ class ModuleRtpRtcpImpl : public RtpRtcp, public RTCPReceiver::ModuleRtpRtcp { // Within this list, the ReportBlockData::RTCPReportBlock::source_ssrc(), // which is the SSRC of the corresponding outbound RTP stream, is unique. std::vector GetLatestReportBlockData() const override; + absl::optional GetSenderReportStats() const override; // (REMB) Receiver Estimated Max Bitrate. void SetRemb(int64_t bitrate_bps, std::vector ssrcs) override; diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc index ae7d9a0d9b..73baab1da7 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl2.cc @@ -24,6 +24,7 @@ #include "modules/rtp_rtcp/source/rtp_rtcp_config.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" +#include "system_wrappers/include/ntp_time.h" #ifdef _WIN32 // Disable warning C4355: 'this' : used in base member initializer list. @@ -510,6 +511,26 @@ std::vector ModuleRtpRtcpImpl2::GetLatestReportBlockData() return rtcp_receiver_.GetLatestReportBlockData(); } +absl::optional +ModuleRtpRtcpImpl2::GetSenderReportStats() const { + SenderReportStats stats; + uint32_t remote_timestamp_secs; + uint32_t remote_timestamp_frac; + uint32_t arrival_timestamp_secs; + uint32_t arrival_timestamp_frac; + if (rtcp_receiver_.NTP(&remote_timestamp_secs, &remote_timestamp_frac, + &arrival_timestamp_secs, &arrival_timestamp_frac, + /*rtcp_timestamp=*/nullptr, &stats.packets_sent, + &stats.bytes_sent, &stats.reports_count)) { + stats.last_remote_timestamp.Set(remote_timestamp_secs, + remote_timestamp_frac); + stats.last_arrival_timestamp.Set(arrival_timestamp_secs, + arrival_timestamp_frac); + return stats; + } + return absl::nullopt; +} + // (REMB) Receiver Estimated Max Bitrate. void ModuleRtpRtcpImpl2::SetRemb(int64_t bitrate_bps, std::vector ssrcs) { diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl2.h b/modules/rtp_rtcp/source/rtp_rtcp_impl2.h index cd216c937c..7b8de1b050 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl2.h +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl2.h @@ -208,6 +208,7 @@ class ModuleRtpRtcpImpl2 final : public RtpRtcpInterface, // Within this list, the ReportBlockData::RTCPReportBlock::source_ssrc(), // which is the SSRC of the corresponding outbound RTP stream, is unique. std::vector GetLatestReportBlockData() const override; + absl::optional GetSenderReportStats() const override; // (REMB) Receiver Estimated Max Bitrate. void SetRemb(int64_t bitrate_bps, std::vector ssrcs) override; diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc index 3b666422b8..193889f2f8 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl2_unittest.cc @@ -14,11 +14,13 @@ #include #include +#include "absl/types/optional.h" #include "api/transport/field_trial_based_config.h" #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "modules/rtp_rtcp/source/rtcp_packet.h" #include "modules/rtp_rtcp/source/rtcp_packet/nack.h" #include "modules/rtp_rtcp/source/rtp_packet_received.h" +#include "modules/rtp_rtcp/source/rtp_rtcp_interface.h" #include "modules/rtp_rtcp/source/rtp_sender_video.h" #include "rtc_base/rate_limiter.h" #include "test/gmock.h" @@ -29,6 +31,11 @@ #include "test/time_controller/simulated_time_controller.h" using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Gt; +using ::testing::Not; +using ::testing::Optional; namespace webrtc { namespace { @@ -631,4 +638,102 @@ TEST_F(RtpRtcpImpl2Test, StoresPacketInfoForSentPackets) { /*is_last=*/1))); } +// Checks that the sender report stats are not available if no RTCP SR was sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsNotAvailable) { + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks that the sender report stats are available if an RTCP SR was sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsAvailable) { + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Not(Eq(absl::nullopt))); +} + +// Checks that the sender report stats are not available if an RTCP SR with an +// unexpected SSRC is received. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsNotUpdatedWithUnexpectedSsrc) { + constexpr uint32_t kUnexpectedSenderSsrc = 0x87654321; + static_assert(kUnexpectedSenderSsrc != kSenderSsrc, ""); + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by an unexpected sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kUnexpectedSenderSsrc); + sr.SetNtp({/*seconds=*/1u, /*fractions=*/1u << 31}); + sr.SetPacketCount(123u); + sr.SetOctetCount(456u); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks the stats derived from the last received RTCP SR are set correctly. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsCheckStatsFromLastReport) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + const NtpTime ntp(/*seconds=*/1u, /*fractions=*/1u << 31); + constexpr uint32_t kPacketCount = 123u; + constexpr uint32_t kOctetCount = 456u; + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by the sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.SetNtp(ntp); + sr.SetPacketCount(kPacketCount); + sr.SetOctetCount(kOctetCount); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + + EXPECT_THAT( + receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::last_remote_timestamp, Eq(ntp)), + Field(&SenderReportStats::packets_sent, Eq(kPacketCount)), + Field(&SenderReportStats::bytes_sent, Eq(kOctetCount))))); +} + +// Checks that the sender report stats count equals the number of sent RTCP SRs. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsCount) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send the first SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(1u)))); + // Send the second SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(2u)))); +} + +// Checks that the sender report stats include a valid arrival time if an RTCP +// SR was sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsArrivalTimestampSet) { + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + auto stats = receiver_.impl_->GetSenderReportStats(); + ASSERT_THAT(stats, Not(Eq(absl::nullopt))); + EXPECT_TRUE(stats->last_arrival_timestamp.Valid()); +} + +// Checks that the packet and byte counters from an RTCP SR are not zero once +// a frame is sent. +TEST_F(RtpRtcpImpl2Test, SenderReportStatsPacketByteCounters) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Gt(0)); + // Advance time otherwise the RTCP SR report will not include any packets + // generated by `SendFrame()`. + AdvanceTimeMs(1); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::packets_sent, Gt(0u)), + Field(&SenderReportStats::bytes_sent, Gt(0u))))); +} + } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc b/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc index 05c6ae1cbf..cc6b76c121 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_rtcp_impl_unittest.cc @@ -27,6 +27,11 @@ #include "test/rtp_header_parser.h" using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::Field; +using ::testing::Gt; +using ::testing::Not; +using ::testing::Optional; namespace webrtc { namespace { @@ -616,4 +621,102 @@ TEST_F(RtpRtcpImplTest, StoresPacketInfoForSentPackets) { /*is_last=*/1))); } +// Checks that the sender report stats are not available if no RTCP SR was sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsNotAvailable) { + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks that the sender report stats are available if an RTCP SR was sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsAvailable) { + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Not(Eq(absl::nullopt))); +} + +// Checks that the sender report stats are not available if an RTCP SR with an +// unexpected SSRC is received. +TEST_F(RtpRtcpImplTest, SenderReportStatsNotUpdatedWithUnexpectedSsrc) { + constexpr uint32_t kUnexpectedSenderSsrc = 0x87654321; + static_assert(kUnexpectedSenderSsrc != kSenderSsrc, ""); + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by an unexpected sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kUnexpectedSenderSsrc); + sr.SetNtp({/*seconds=*/1u, /*fractions=*/1u << 31}); + sr.SetPacketCount(123u); + sr.SetOctetCount(456u); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), Eq(absl::nullopt)); +} + +// Checks the stats derived from the last received RTCP SR are set correctly. +TEST_F(RtpRtcpImplTest, SenderReportStatsCheckStatsFromLastReport) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + const NtpTime ntp(/*seconds=*/1u, /*fractions=*/1u << 31); + constexpr uint32_t kPacketCount = 123u; + constexpr uint32_t kOctetCount = 456u; + // Forge a sender report and pass it to the receiver as if an RTCP SR were + // sent by the sender. + rtcp::SenderReport sr; + sr.SetSenderSsrc(kSenderSsrc); + sr.SetNtp(ntp); + sr.SetPacketCount(kPacketCount); + sr.SetOctetCount(kOctetCount); + auto raw_packet = sr.Build(); + receiver_.impl_->IncomingRtcpPacket(raw_packet.data(), raw_packet.size()); + + EXPECT_THAT( + receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::last_remote_timestamp, Eq(ntp)), + Field(&SenderReportStats::packets_sent, Eq(kPacketCount)), + Field(&SenderReportStats::bytes_sent, Eq(kOctetCount))))); +} + +// Checks that the sender report stats count equals the number of sent RTCP SRs. +TEST_F(RtpRtcpImplTest, SenderReportStatsCount) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send the first SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(1u)))); + // Send the second SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(Field(&SenderReportStats::reports_count, Eq(2u)))); +} + +// Checks that the sender report stats include a valid arrival time if an RTCP +// SR was sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsArrivalTimestampSet) { + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + auto stats = receiver_.impl_->GetSenderReportStats(); + ASSERT_THAT(stats, Not(Eq(absl::nullopt))); + EXPECT_TRUE(stats->last_arrival_timestamp.Valid()); +} + +// Checks that the packet and byte counters from an RTCP SR are not zero once +// a frame is sent. +TEST_F(RtpRtcpImplTest, SenderReportStatsPacketByteCounters) { + using SenderReportStats = RtpRtcpInterface::SenderReportStats; + // Send a frame in order to send an SR. + SendFrame(&sender_, sender_video_.get(), kBaseLayerTid); + ASSERT_THAT(sender_.transport_.rtp_packets_sent_, Gt(0)); + // Advance time otherwise the RTCP SR report will not include any packets + // generated by `SendFrame()`. + clock_.AdvanceTimeMilliseconds(1); + // Send an SR. + ASSERT_THAT(sender_.impl_->SendRTCP(kRtcpReport), Eq(0)); + EXPECT_THAT(receiver_.impl_->GetSenderReportStats(), + Optional(AllOf(Field(&SenderReportStats::packets_sent, Gt(0u)), + Field(&SenderReportStats::bytes_sent, Gt(0u))))); +} + } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_rtcp_interface.h b/modules/rtp_rtcp/source/rtp_rtcp_interface.h index 5bb3eb55e2..4505ba1d48 100644 --- a/modules/rtp_rtcp/source/rtp_rtcp_interface.h +++ b/modules/rtp_rtcp/source/rtp_rtcp_interface.h @@ -28,6 +28,7 @@ #include "modules/rtp_rtcp/source/rtp_sequence_number_map.h" #include "modules/rtp_rtcp/source/video_fec_generator.h" #include "rtc_base/constructor_magic.h" +#include "system_wrappers/include/ntp_time.h" namespace webrtc { @@ -152,6 +153,27 @@ class RtpRtcpInterface : public RtcpFeedbackSenderInterface { RTC_DISALLOW_COPY_AND_ASSIGN(Configuration); }; + // Stats for RTCP sender reports (SR) for a specific SSRC. + // Refer to https://tools.ietf.org/html/rfc3550#section-6.4.1. + struct SenderReportStats { + // Arrival NPT timestamp for the last received RTCP SR. + NtpTime last_arrival_timestamp; + // Received (a.k.a., remote) NTP timestamp for the last received RTCP SR. + NtpTime last_remote_timestamp; + // Total number of RTP data packets transmitted by the sender since starting + // transmission up until the time this SR packet was generated. The count + // should be reset if the sender changes its SSRC identifier. + uint32_t packets_sent; + // Total number of payload octets (i.e., not including header or padding) + // transmitted in RTP data packets by the sender since starting transmission + // up until the time this SR packet was generated. The count should be reset + // if the sender changes its SSRC identifier. + uint64_t bytes_sent; + // Total number of RTCP SR blocks received. + // https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats-reportssent. + uint64_t reports_count; + }; + // ************************************************************************** // Receiver functions // ************************************************************************** @@ -372,6 +394,8 @@ class RtpRtcpInterface : public RtcpFeedbackSenderInterface { // ReportBlockData represents the latest Report Block that was received for // that pair. virtual std::vector GetLatestReportBlockData() const = 0; + // Returns stats based on the received RTCP SRs. + virtual absl::optional GetSenderReportStats() const = 0; // (REMB) Receiver Estimated Max Bitrate. // Schedules sending REMB on next and following sender/receiver reports. diff --git a/video/rtp_video_stream_receiver.cc b/video/rtp_video_stream_receiver.cc index d6a7b41577..11be5aa030 100644 --- a/video/rtp_video_stream_receiver.cc +++ b/video/rtp_video_stream_receiver.cc @@ -377,7 +377,9 @@ void RtpVideoStreamReceiver::AddReceiveCodec( absl::optional RtpVideoStreamReceiver::GetSyncInfo() const { Syncable::Info info; if (rtp_rtcp_->RemoteNTP(&info.capture_time_ntp_secs, - &info.capture_time_ntp_frac, nullptr, nullptr, + &info.capture_time_ntp_frac, + /*rtcp_arrival_time_secs=*/nullptr, + /*rtcp_arrival_time_frac=*/nullptr, &info.capture_time_source_clock) != 0) { return absl::nullopt; } diff --git a/video/rtp_video_stream_receiver2.cc b/video/rtp_video_stream_receiver2.cc index 1e175a3b86..4893d1adb6 100644 --- a/video/rtp_video_stream_receiver2.cc +++ b/video/rtp_video_stream_receiver2.cc @@ -346,7 +346,9 @@ absl::optional RtpVideoStreamReceiver2::GetSyncInfo() const { RTC_DCHECK_RUN_ON(&worker_task_checker_); Syncable::Info info; if (rtp_rtcp_->RemoteNTP(&info.capture_time_ntp_secs, - &info.capture_time_ntp_frac, nullptr, nullptr, + &info.capture_time_ntp_frac, + /*rtcp_arrival_time_secs=*/nullptr, + /*rtcp_arrival_time_frac=*/nullptr, &info.capture_time_source_clock) != 0) { return absl::nullopt; }