diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index def1483ae0..6cf158c260 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn @@ -234,6 +234,7 @@ rtc_source_set("rtcp_transceiver") { deps = [ ":rtp_rtcp", ":rtp_rtcp_format", + "../../:webrtc_common", "../../api:array_view", "../../api:optional", "../../api:transport_api", diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_config.h b/modules/rtp_rtcp/source/rtcp_transceiver_config.h index 18efd82644..4b08d89e0e 100644 --- a/modules/rtp_rtcp/source/rtcp_transceiver_config.h +++ b/modules/rtp_rtcp/source/rtcp_transceiver_config.h @@ -13,6 +13,7 @@ #include +#include "common_types.h" // NOLINT(build/include) #include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" #include "rtc_base/task_queue.h" @@ -20,6 +21,21 @@ namespace webrtc { class ReceiveStatisticsProvider; class Transport; +// Interface to watch incoming rtcp packets by media (rtp) receiver. +class MediaReceiverRtcpObserver { + public: + virtual ~MediaReceiverRtcpObserver() = default; + + // All message handlers have default empty implementation. This way user needs + // to implement only those she is interested in. + virtual void OnSenderReport(uint32_t sender_ssrc, + NtpTime ntp_time, + uint32_t rtp_time) {} + virtual void OnBye(uint32_t sender_ssrc) {} + virtual void OnBitrateAllocation(uint32_t sender_ssrc, + const BitrateAllocation& allocation) {} +}; + struct RtcpTransceiverConfig { RtcpTransceiverConfig(); RtcpTransceiverConfig(const RtcpTransceiverConfig&); diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc index 990ca900b4..6a2caab06c 100644 --- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc +++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.cc @@ -16,6 +16,7 @@ #include "modules/rtp_rtcp/include/receive_statistics.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/bye.h" #include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" #include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" #include "modules/rtp_rtcp/source/rtcp_packet/fir.h" @@ -27,6 +28,7 @@ #include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" #include "modules/rtp_rtcp/source/time_util.h" #include "rtc_base/checks.h" +#include "rtc_base/logging.h" #include "rtc_base/ptr_util.h" #include "rtc_base/task_queue.h" #include "rtc_base/timeutils.h" @@ -44,6 +46,7 @@ struct SenderReportTimes { struct RtcpTransceiverImpl::RemoteSenderState { uint8_t fir_sequence_number = 0; rtc::Optional last_received_sender_report; + std::vector observers; }; // Helper to put several RTCP packets into lower layer datagram composing @@ -91,6 +94,27 @@ RtcpTransceiverImpl::RtcpTransceiverImpl(const RtcpTransceiverConfig& config) RtcpTransceiverImpl::~RtcpTransceiverImpl() = default; +void RtcpTransceiverImpl::AddMediaReceiverObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer) { + auto& stored = remote_senders_[remote_ssrc].observers; + RTC_DCHECK(std::find(stored.begin(), stored.end(), observer) == stored.end()); + stored.push_back(observer); +} + +void RtcpTransceiverImpl::RemoveMediaReceiverObserver( + uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer) { + auto remote_sender_it = remote_senders_.find(remote_ssrc); + if (remote_sender_it == remote_senders_.end()) + return; + auto& stored = remote_sender_it->second.observers; + auto it = std::find(stored.begin(), stored.end(), observer); + if (it == stored.end()) + return; + stored.erase(it); +} + void RtcpTransceiverImpl::ReceivePacket(rtc::ArrayView packet, int64_t now_us) { while (!packet.empty()) { @@ -157,6 +181,9 @@ void RtcpTransceiverImpl::HandleReceivedPacket( const rtcp::CommonHeader& rtcp_packet_header, int64_t now_us) { switch (rtcp_packet_header.type()) { + case rtcp::Bye::kPacketType: + HandleBye(rtcp_packet_header); + break; case rtcp::SenderReport::kPacketType: HandleSenderReport(rtcp_packet_header, now_us); break; @@ -166,17 +193,35 @@ void RtcpTransceiverImpl::HandleReceivedPacket( } } +void RtcpTransceiverImpl::HandleBye( + const rtcp::CommonHeader& rtcp_packet_header) { + rtcp::Bye bye; + if (!bye.Parse(rtcp_packet_header)) + return; + auto remote_sender_it = remote_senders_.find(bye.sender_ssrc()); + if (remote_sender_it == remote_senders_.end()) + return; + for (MediaReceiverRtcpObserver* observer : remote_sender_it->second.observers) + observer->OnBye(bye.sender_ssrc()); +} + void RtcpTransceiverImpl::HandleSenderReport( const rtcp::CommonHeader& rtcp_packet_header, int64_t now_us) { rtcp::SenderReport sender_report; if (!sender_report.Parse(rtcp_packet_header)) return; + RemoteSenderState& remote_sender = + remote_senders_[sender_report.sender_ssrc()]; rtc::Optional& last = - remote_senders_[sender_report.sender_ssrc()].last_received_sender_report; + remote_sender.last_received_sender_report; last.emplace(); last->local_received_time_us = now_us; last->remote_sent_time = sender_report.ntp(); + + for (MediaReceiverRtcpObserver* observer : remote_sender.observers) + observer->OnSenderReport(sender_report.sender_ssrc(), sender_report.ntp(), + sender_report.rtp_timestamp()); } void RtcpTransceiverImpl::HandleExtendedReports( @@ -185,23 +230,59 @@ void RtcpTransceiverImpl::HandleExtendedReports( rtcp::ExtendedReports extended_reports; if (!extended_reports.Parse(rtcp_packet_header)) return; - if (extended_reports.dlrr() && config_.non_sender_rtt_measurement && - config_.rtt_observer) { - // Delay and last_rr are transferred using 32bit compact ntp resolution. - // Convert packet arrival time to same format through 64bit ntp format. - uint32_t receive_time_ntp = CompactNtp(TimeMicrosToNtp(now_us)); - for (const rtcp::ReceiveTimeInfo& rti : - extended_reports.dlrr().sub_blocks()) { - if (rti.ssrc != config_.feedback_ssrc) - continue; - uint32_t rtt_ntp = - receive_time_ntp - rti.delay_since_last_rr - rti.last_rr; - int64_t rtt_ms = CompactNtpRttToMs(rtt_ntp); - config_.rtt_observer->OnRttUpdate(rtt_ms); - } + + if (extended_reports.dlrr()) + HandleDlrr(extended_reports.dlrr(), now_us); + + if (extended_reports.target_bitrate()) + HandleTargetBitrate(*extended_reports.target_bitrate(), + extended_reports.sender_ssrc()); +} + +void RtcpTransceiverImpl::HandleDlrr(const rtcp::Dlrr& dlrr, int64_t now_us) { + if (!config_.non_sender_rtt_measurement || config_.rtt_observer == nullptr) + return; + + // Delay and last_rr are transferred using 32bit compact ntp resolution. + // Convert packet arrival time to same format through 64bit ntp format. + uint32_t receive_time_ntp = CompactNtp(TimeMicrosToNtp(now_us)); + for (const rtcp::ReceiveTimeInfo& rti : dlrr.sub_blocks()) { + if (rti.ssrc != config_.feedback_ssrc) + continue; + uint32_t rtt_ntp = receive_time_ntp - rti.delay_since_last_rr - rti.last_rr; + int64_t rtt_ms = CompactNtpRttToMs(rtt_ntp); + config_.rtt_observer->OnRttUpdate(rtt_ms); } } +void RtcpTransceiverImpl::HandleTargetBitrate( + const rtcp::TargetBitrate& target_bitrate, + uint32_t remote_ssrc) { + auto remote_sender_it = remote_senders_.find(remote_ssrc); + if (remote_sender_it == remote_senders_.end() || + remote_sender_it->second.observers.empty()) + return; + + // Convert rtcp::TargetBitrate to BitrateAllocation from common types. + BitrateAllocation bitrate_allocation; + for (const rtcp::TargetBitrate::BitrateItem& item : + target_bitrate.GetTargetBitrates()) { + if (item.spatial_layer >= kMaxSpatialLayers || + item.temporal_layer >= kMaxTemporalStreams) { + RTC_DLOG(LS_WARNING) + << config_.debug_id + << "Invalid incoming TargetBitrate with spatial layer " + << item.spatial_layer << ", temporal layer " << item.temporal_layer; + continue; + } + bitrate_allocation.SetBitrate(item.spatial_layer, item.temporal_layer, + item.target_bitrate_kbps * 1000); + } + + for (MediaReceiverRtcpObserver* observer : remote_sender_it->second.observers) + observer->OnBitrateAllocation(remote_ssrc, bitrate_allocation); +} + void RtcpTransceiverImpl::ReschedulePeriodicCompoundPackets() { if (!config_.schedule_periodic_compound_packets) return; diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h index cfd5de7a1c..c6bb90bdd0 100644 --- a/modules/rtp_rtcp/source/rtcp_transceiver_impl.h +++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl.h @@ -19,8 +19,10 @@ #include "api/array_view.h" #include "api/optional.h" #include "modules/rtp_rtcp/source/rtcp_packet/common_header.h" +#include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" #include "modules/rtp_rtcp/source/rtcp_packet/remb.h" #include "modules/rtp_rtcp/source/rtcp_packet/report_block.h" +#include "modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h" #include "modules/rtp_rtcp/source/rtcp_transceiver_config.h" #include "rtc_base/constructormagic.h" #include "rtc_base/weak_ptr.h" @@ -36,6 +38,11 @@ class RtcpTransceiverImpl { explicit RtcpTransceiverImpl(const RtcpTransceiverConfig& config); ~RtcpTransceiverImpl(); + void AddMediaReceiverObserver(uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer); + void RemoveMediaReceiverObserver(uint32_t remote_ssrc, + MediaReceiverRtcpObserver* observer); + void ReceivePacket(rtc::ArrayView packet, int64_t now_us); void SendCompoundPacket(); @@ -54,10 +61,16 @@ class RtcpTransceiverImpl { void HandleReceivedPacket(const rtcp::CommonHeader& rtcp_packet_header, int64_t now_us); + // Individual rtcp packet handlers. + void HandleBye(const rtcp::CommonHeader& rtcp_packet_header); void HandleSenderReport(const rtcp::CommonHeader& rtcp_packet_header, int64_t now_us); void HandleExtendedReports(const rtcp::CommonHeader& rtcp_packet_header, int64_t now_us); + // Extended Reports blocks handlers. + void HandleDlrr(const rtcp::Dlrr& dlrr, int64_t now_us); + void HandleTargetBitrate(const rtcp::TargetBitrate& target_bitrate, + uint32_t remote_ssrc); void ReschedulePeriodicCompoundPackets(); void SchedulePeriodicCompoundPackets(int64_t delay_ms); @@ -73,6 +86,8 @@ class RtcpTransceiverImpl { const RtcpTransceiverConfig config_; rtc::Optional remb_; + // TODO(danilchap): Remove entries from remote_senders_ that are no longer + // needed. std::map remote_senders_; rtc::WeakPtrFactory ptr_factory_; diff --git a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc index de0582b16c..a08eae7aa2 100644 --- a/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc +++ b/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc @@ -14,6 +14,9 @@ #include "modules/rtp_rtcp/include/receive_statistics.h" #include "modules/rtp_rtcp/mocks/mock_rtcp_rtt_stats.h" +#include "modules/rtp_rtcp/source/rtcp_packet/app.h" +#include "modules/rtp_rtcp/source/rtcp_packet/bye.h" +#include "modules/rtp_rtcp/source/rtcp_packet/compound_packet.h" #include "modules/rtp_rtcp/source/time_util.h" #include "rtc_base/event.h" #include "rtc_base/fakeclock.h" @@ -31,6 +34,8 @@ using ::testing::ElementsAre; using ::testing::Invoke; using ::testing::Return; using ::testing::SizeIs; +using ::testing::StrictMock; +using ::webrtc::BitrateAllocation; using ::webrtc::CompactNtp; using ::webrtc::CompactNtpRttToMs; using ::webrtc::MockRtcpRttStats; @@ -40,6 +45,8 @@ using ::webrtc::RtcpTransceiverConfig; using ::webrtc::RtcpTransceiverImpl; using ::webrtc::SaturatedUsToCompactNtp; using ::webrtc::TimeMicrosToNtp; +using ::webrtc::rtcp::Bye; +using ::webrtc::rtcp::CompoundPacket; using ::webrtc::rtcp::ReportBlock; using ::webrtc::rtcp::SenderReport; using ::webrtc::test::RtcpPacketParser; @@ -49,6 +56,13 @@ class MockReceiveStatisticsProvider : public webrtc::ReceiveStatisticsProvider { MOCK_METHOD1(RtcpReportBlocks, std::vector(size_t)); }; +class MockMediaReceiverRtcpObserver : public webrtc::MediaReceiverRtcpObserver { + public: + MOCK_METHOD3(OnSenderReport, void(uint32_t, NtpTime, uint32_t)); + MOCK_METHOD1(OnBye, void(uint32_t)); + MOCK_METHOD2(OnBitrateAllocation, void(uint32_t, const BitrateAllocation&)); +}; + // Since some tests will need to wait for this period, make it small to avoid // slowing tests too much. As long as there are test bots with high scheduler // granularity, small period should be ok. @@ -108,6 +122,17 @@ class RtcpParserTransport : public webrtc::Transport { int num_packets_ = 0; }; +RtcpTransceiverConfig DefaultTestConfig() { + // RtcpTransceiverConfig default constructor sets default values for prod. + // Test doesn't need to support all key features: Default test config returns + // valid config with all features turned off. + static MockTransport null_transport; + RtcpTransceiverConfig config; + config.outgoing_transport = &null_transport; + config.schedule_periodic_compound_packets = false; + return config; +} + TEST(RtcpTransceiverImplTest, DelaysSendingFirstCompondPacket) { rtc::TaskQueue queue("rtcp"); FakeRtcpTransport transport; @@ -361,9 +386,175 @@ TEST(RtcpTransceiverImplTest, ReceiverReportUsesReceiveStatistics) { kMediaSsrc); } -// TODO(danilchap): Write test ReceivePacket handles several rtcp_packets -// stacked together when callbacks will be implemented that can be used for -// cleaner expectations. +TEST(RtcpTransceiverImplTest, MultipleObserversOnSameSsrc) { + const uint32_t kRemoteSsrc = 12345; + StrictMock observer1; + StrictMock observer2; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer1); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer2); + + const NtpTime kRemoteNtp(0x9876543211); + const uint32_t kRemoteRtp = 0x444555; + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc); + sr.SetNtp(kRemoteNtp); + sr.SetRtpTimestamp(kRemoteRtp); + auto raw_packet = sr.Build(); + + EXPECT_CALL(observer1, OnSenderReport(kRemoteSsrc, kRemoteNtp, kRemoteRtp)); + EXPECT_CALL(observer2, OnSenderReport(kRemoteSsrc, kRemoteNtp, kRemoteRtp)); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, DoesntCallsObserverAfterRemoved) { + const uint32_t kRemoteSsrc = 12345; + StrictMock observer1; + StrictMock observer2; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer1); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer2); + + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc); + auto raw_packet = sr.Build(); + + rtcp_transceiver.RemoveMediaReceiverObserver(kRemoteSsrc, &observer1); + + EXPECT_CALL(observer1, OnSenderReport(_, _, _)).Times(0); + EXPECT_CALL(observer2, OnSenderReport(_, _, _)); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, CallsObserverOnSenderReportBySenderSsrc) { + const uint32_t kRemoteSsrc1 = 12345; + const uint32_t kRemoteSsrc2 = 22345; + StrictMock observer1; + StrictMock observer2; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc1, &observer1); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc2, &observer2); + + const NtpTime kRemoteNtp(0x9876543211); + const uint32_t kRemoteRtp = 0x444555; + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc1); + sr.SetNtp(kRemoteNtp); + sr.SetRtpTimestamp(kRemoteRtp); + auto raw_packet = sr.Build(); + + EXPECT_CALL(observer1, OnSenderReport(kRemoteSsrc1, kRemoteNtp, kRemoteRtp)); + EXPECT_CALL(observer2, OnSenderReport(_, _, _)).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, CallsObserverOnByeBySenderSsrc) { + const uint32_t kRemoteSsrc1 = 12345; + const uint32_t kRemoteSsrc2 = 22345; + StrictMock observer1; + StrictMock observer2; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc1, &observer1); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc2, &observer2); + + Bye bye; + bye.SetSenderSsrc(kRemoteSsrc1); + auto raw_packet = bye.Build(); + + EXPECT_CALL(observer1, OnBye(kRemoteSsrc1)); + EXPECT_CALL(observer2, OnBye(_)).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, CallsObserverOnTargetBitrateBySenderSsrc) { + const uint32_t kRemoteSsrc1 = 12345; + const uint32_t kRemoteSsrc2 = 22345; + StrictMock observer1; + StrictMock observer2; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc1, &observer1); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc2, &observer2); + + webrtc::rtcp::TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, /*target_bitrate_kbps=*/10); + target_bitrate.AddTargetBitrate(0, 1, /*target_bitrate_kbps=*/20); + target_bitrate.AddTargetBitrate(1, 0, /*target_bitrate_kbps=*/40); + target_bitrate.AddTargetBitrate(1, 1, /*target_bitrate_kbps=*/80); + webrtc::rtcp::ExtendedReports xr; + xr.SetSenderSsrc(kRemoteSsrc1); + xr.SetTargetBitrate(target_bitrate); + auto raw_packet = xr.Build(); + + BitrateAllocation bitrate_allocation; + bitrate_allocation.SetBitrate(0, 0, /*bitrate_bps=*/10000); + bitrate_allocation.SetBitrate(0, 1, /*bitrate_bps=*/20000); + bitrate_allocation.SetBitrate(1, 0, /*bitrate_bps=*/40000); + bitrate_allocation.SetBitrate(1, 1, /*bitrate_bps=*/80000); + EXPECT_CALL(observer1, OnBitrateAllocation(kRemoteSsrc1, bitrate_allocation)); + EXPECT_CALL(observer2, OnBitrateAllocation(_, _)).Times(0); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, SkipsIncorrectTargetBitrateEntries) { + const uint32_t kRemoteSsrc = 12345; + MockMediaReceiverRtcpObserver observer; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer); + + webrtc::rtcp::TargetBitrate target_bitrate; + target_bitrate.AddTargetBitrate(0, 0, /*target_bitrate_kbps=*/10); + target_bitrate.AddTargetBitrate(0, webrtc::kMaxTemporalStreams, 20); + target_bitrate.AddTargetBitrate(webrtc::kMaxSpatialLayers, 0, 40); + + webrtc::rtcp::ExtendedReports xr; + xr.SetTargetBitrate(target_bitrate); + xr.SetSenderSsrc(kRemoteSsrc); + auto raw_packet = xr.Build(); + + BitrateAllocation expected_allocation; + expected_allocation.SetBitrate(0, 0, /*bitrate_bps=*/10000); + EXPECT_CALL(observer, OnBitrateAllocation(kRemoteSsrc, expected_allocation)); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, CallsObserverOnByeBehindSenderReport) { + const uint32_t kRemoteSsrc = 12345; + MockMediaReceiverRtcpObserver observer; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer); + + CompoundPacket compound; + SenderReport sr; + sr.SetSenderSsrc(kRemoteSsrc); + compound.Append(&sr); + Bye bye; + bye.SetSenderSsrc(kRemoteSsrc); + compound.Append(&bye); + auto raw_packet = compound.Build(); + + EXPECT_CALL(observer, OnBye(kRemoteSsrc)); + EXPECT_CALL(observer, OnSenderReport(kRemoteSsrc, _, _)); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} + +TEST(RtcpTransceiverImplTest, CallsObserverOnByeBehindUnknownRtcpPacket) { + const uint32_t kRemoteSsrc = 12345; + MockMediaReceiverRtcpObserver observer; + RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); + rtcp_transceiver.AddMediaReceiverObserver(kRemoteSsrc, &observer); + + CompoundPacket compound; + // Use Application-Defined rtcp packet as unknown. + webrtc::rtcp::App app; + compound.Append(&app); + Bye bye; + bye.SetSenderSsrc(kRemoteSsrc); + compound.Append(&bye); + auto raw_packet = compound.Build(); + + EXPECT_CALL(observer, OnBye(kRemoteSsrc)); + rtcp_transceiver.ReceivePacket(raw_packet, /*now_us=*/0); +} TEST(RtcpTransceiverImplTest, WhenSendsReceiverReportSetsLastSenderReportTimestampPerRemoteSsrc) {