| /* | |
| * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. | |
| * | |
| * Use of this source code is governed by a BSD-style license | |
| * that can be found in the LICENSE file in the root of the source | |
| * tree. An additional intellectual property rights grant can be found | |
| * in the file PATENTS. All contributing project authors may | |
| * be found in the AUTHORS file in the root of the source tree. | |
| */ | |
| #include "modules/rtp_rtcp/source/rtcp_transceiver_impl.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <functional> | |
| #include <memory> | |
| #include <optional> | |
| #include <utility> | |
| #include <vector> | |
| #include "api/array_view.h" | |
| #include "api/rtp_headers.h" | |
| #include "api/task_queue/task_queue_base.h" | |
| #include "api/task_queue/task_queue_factory.h" | |
| #include "api/test/create_time_controller.h" | |
| #include "api/test/time_controller.h" | |
| #include "api/units/data_rate.h" | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "api/video/video_bitrate_allocation.h" | |
| #include "api/video/video_codec_constants.h" | |
| #include "modules/rtp_rtcp/include/receive_statistics.h" | |
| #include "modules/rtp_rtcp/include/report_block_data.h" | |
| #include "modules/rtp_rtcp/mocks/mock_network_link_rtcp_observer.h" | |
| #include "modules/rtp_rtcp/source/ntp_time_util.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/rtcp_packet/congestion_control_feedback.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/dlrr.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/extended_reports.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/fir.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/nack.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/pli.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/receiver_report.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/rrtr.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/sender_report.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/target_bitrate.h" | |
| #include "modules/rtp_rtcp/source/rtcp_packet/transport_feedback.h" | |
| #include "modules/rtp_rtcp/source/rtcp_transceiver_config.h" | |
| #include "system_wrappers/include/clock.h" | |
| #include "system_wrappers/include/ntp_time.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| #include "test/rtcp_packet_parser.h" | |
| namespace webrtc { | |
| namespace { | |
| using rtcp::Bye; | |
| using rtcp::CompoundPacket; | |
| using rtcp::ReportBlock; | |
| using rtcp::SenderReport; | |
| using test::RtcpPacketParser; | |
| using ::testing::_; | |
| using ::testing::ElementsAre; | |
| using ::testing::ElementsAreArray; | |
| using ::testing::Ge; | |
| using ::testing::MockFunction; | |
| using ::testing::NiceMock; | |
| using ::testing::Property; | |
| using ::testing::Return; | |
| using ::testing::SizeIs; | |
| using ::testing::StrictMock; | |
| using ::testing::UnorderedElementsAre; | |
| using ::testing::WithArg; | |
| class MockReceiveStatisticsProvider : public ReceiveStatisticsProvider { | |
| public: | |
| MOCK_METHOD(std::vector<ReportBlock>, RtcpReportBlocks, (size_t), (override)); | |
| }; | |
| class MockMediaReceiverRtcpObserver : public MediaReceiverRtcpObserver { | |
| public: | |
| MOCK_METHOD(void, OnSenderReport, (uint32_t, NtpTime, uint32_t), (override)); | |
| MOCK_METHOD(void, OnBye, (uint32_t), (override)); | |
| MOCK_METHOD(void, | |
| OnBitrateAllocation, | |
| (uint32_t, const VideoBitrateAllocation&), | |
| (override)); | |
| }; | |
| class MockRtpStreamRtcpHandler : public RtpStreamRtcpHandler { | |
| public: | |
| MockRtpStreamRtcpHandler() { | |
| // With each next call increase number of sent packets and bytes to simulate | |
| // active RTP sender. | |
| ON_CALL(*this, SentStats).WillByDefault([this] { | |
| RtpStats stats; | |
| stats.set_num_sent_packets(++num_calls_); | |
| stats.set_num_sent_bytes(1'000 * num_calls_); | |
| return stats; | |
| }); | |
| } | |
| MOCK_METHOD(RtpStats, SentStats, (), (override)); | |
| MOCK_METHOD(void, | |
| OnNack, | |
| (uint32_t, ArrayView<const uint16_t>), | |
| (override)); | |
| MOCK_METHOD(void, OnFir, (uint32_t), (override)); | |
| MOCK_METHOD(void, OnPli, (uint32_t), (override)); | |
| MOCK_METHOD(void, OnReport, (const ReportBlockData&), (override)); | |
| private: | |
| int num_calls_ = 0; | |
| }; | |
| constexpr TimeDelta kReportPeriod = TimeDelta::Seconds(1); | |
| constexpr TimeDelta kAlmostForever = TimeDelta::Seconds(2); | |
| constexpr TimeDelta kTimePrecision = TimeDelta::Millis(1); | |
| MATCHER_P(Near, value, "") { | |
| return arg > value - kTimePrecision && arg < value + kTimePrecision; | |
| } | |
| // Helper to wait for an rtcp packet produced on a different thread/task queue. | |
| class FakeRtcpTransport { | |
| public: | |
| explicit FakeRtcpTransport(TimeController& time) : time_(time) {} | |
| std::function<void(ArrayView<const uint8_t>)> AsStdFunction() { | |
| return [this](ArrayView<const uint8_t>) { sent_rtcp_ = true; }; | |
| } | |
| // Returns true when packet was received by the transport. | |
| bool WaitPacket() { | |
| bool got_packet = time_.Wait([this] { return sent_rtcp_; }, kAlmostForever); | |
| // Clear the 'event' to allow waiting for multiple packets. | |
| sent_rtcp_ = false; | |
| return got_packet; | |
| } | |
| private: | |
| TimeController& time_; | |
| bool sent_rtcp_ = false; | |
| }; | |
| std::function<void(ArrayView<const uint8_t>)> RtcpParserTransport( | |
| RtcpPacketParser& parser) { | |
| return [&parser](ArrayView<const uint8_t> packet) { | |
| return parser.Parse(packet); | |
| }; | |
| } | |
| class RtcpTransceiverImplTest : public ::testing::Test { | |
| public: | |
| 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. | |
| RtcpTransceiverConfig config; | |
| config.clock = time_->GetClock(); | |
| config.schedule_periodic_compound_packets = false; | |
| config.initial_report_delay = kReportPeriod / 2; | |
| config.report_period = kReportPeriod; | |
| return config; | |
| } | |
| TimeController& time_controller() { return *time_; } | |
| Timestamp CurrentTime() { return time_->GetClock()->CurrentTime(); } | |
| void AdvanceTime(TimeDelta time) { time_->AdvanceTime(time); } | |
| std::unique_ptr<TaskQueueBase, TaskQueueDeleter> CreateTaskQueue() { | |
| return time_->GetTaskQueueFactory()->CreateTaskQueue( | |
| "rtcp", TaskQueueFactory::Priority::NORMAL); | |
| } | |
| private: | |
| std::unique_ptr<TimeController> time_ = CreateSimulatedTimeController(); | |
| }; | |
| TEST_F(RtcpTransceiverImplTest, NeedToStopPeriodicTaskToDestroyOnTaskQueue) { | |
| FakeRtcpTransport transport(time_controller()); | |
| auto queue = CreateTaskQueue(); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.task_queue = queue.get(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| auto* rtcp_transceiver = new RtcpTransceiverImpl(config); | |
| // Wait for a periodic packet. | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| bool done = false; | |
| queue->PostTask([rtcp_transceiver, &done] { | |
| rtcp_transceiver->StopPeriodicTask(); | |
| delete rtcp_transceiver; | |
| done = true; | |
| }); | |
| ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CanBeDestroyedRightAfterCreation) { | |
| FakeRtcpTransport transport(time_controller()); | |
| auto queue = CreateTaskQueue(); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.task_queue = queue.get(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| bool done = false; | |
| queue->PostTask([&] { | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.StopPeriodicTask(); | |
| done = true; | |
| }); | |
| ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CanDestroyAfterTaskQueue) { | |
| FakeRtcpTransport transport(time_controller()); | |
| auto queue = CreateTaskQueue(); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.task_queue = queue.get(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| auto* rtcp_transceiver = new RtcpTransceiverImpl(config); | |
| // Wait for a periodic packet. | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| queue = nullptr; | |
| delete rtcp_transceiver; | |
| } | |
| TEST_F(RtcpTransceiverImplTest, DelaysSendingFirstCompondPacket) { | |
| auto queue = CreateTaskQueue(); | |
| FakeRtcpTransport transport(time_controller()); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| config.initial_report_delay = TimeDelta::Millis(10); | |
| config.task_queue = queue.get(); | |
| std::optional<RtcpTransceiverImpl> rtcp_transceiver; | |
| Timestamp started = CurrentTime(); | |
| queue->PostTask([&] { rtcp_transceiver.emplace(config); }); | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| EXPECT_GE(CurrentTime() - started, config.initial_report_delay); | |
| // Cleanup. | |
| bool done = false; | |
| queue->PostTask([&] { | |
| rtcp_transceiver->StopPeriodicTask(); | |
| rtcp_transceiver.reset(); | |
| done = true; | |
| }); | |
| ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, PeriodicallySendsPackets) { | |
| auto queue = CreateTaskQueue(); | |
| FakeRtcpTransport transport(time_controller()); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| config.initial_report_delay = TimeDelta::Zero(); | |
| config.report_period = kReportPeriod; | |
| config.task_queue = queue.get(); | |
| std::optional<RtcpTransceiverImpl> rtcp_transceiver; | |
| Timestamp time_just_before_1st_packet = Timestamp::MinusInfinity(); | |
| queue->PostTask([&] { | |
| // Because initial_report_delay_ms is set to 0, time_just_before_the_packet | |
| // should be very close to the time_of_the_packet. | |
| time_just_before_1st_packet = CurrentTime(); | |
| rtcp_transceiver.emplace(config); | |
| }); | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| Timestamp time_just_after_2nd_packet = CurrentTime(); | |
| EXPECT_GE(time_just_after_2nd_packet - time_just_before_1st_packet, | |
| config.report_period); | |
| // Cleanup. | |
| bool done = false; | |
| queue->PostTask([&] { | |
| rtcp_transceiver->StopPeriodicTask(); | |
| rtcp_transceiver.reset(); | |
| done = true; | |
| }); | |
| ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendCompoundPacketDelaysPeriodicSendPackets) { | |
| auto queue = CreateTaskQueue(); | |
| FakeRtcpTransport transport(time_controller()); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| config.initial_report_delay = TimeDelta::Zero(); | |
| config.report_period = kReportPeriod; | |
| config.task_queue = queue.get(); | |
| std::optional<RtcpTransceiverImpl> rtcp_transceiver; | |
| queue->PostTask([&] { rtcp_transceiver.emplace(config); }); | |
| // Wait for the first packet. | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| // Send non periodic one after half period. | |
| bool non_periodic = false; | |
| Timestamp time_of_non_periodic_packet = Timestamp::MinusInfinity(); | |
| queue->PostDelayedTask( | |
| [&] { | |
| time_of_non_periodic_packet = CurrentTime(); | |
| rtcp_transceiver->SendCompoundPacket(); | |
| non_periodic = true; | |
| }, | |
| config.report_period / 2); | |
| // Though non-periodic packet is scheduled just in between periodic, due to | |
| // small period and task queue flakiness it migth end-up 1ms after next | |
| // periodic packet. To be sure duration after non-periodic packet is tested | |
| // wait for transport after ensuring non-periodic packet was sent. | |
| EXPECT_TRUE( | |
| time_controller().Wait([&] { return non_periodic; }, kAlmostForever)); | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| // Wait for next periodic packet. | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| Timestamp time_of_last_periodic_packet = CurrentTime(); | |
| EXPECT_GE(time_of_last_periodic_packet - time_of_non_periodic_packet, | |
| config.report_period); | |
| // Cleanup. | |
| bool done = false; | |
| queue->PostTask([&] { | |
| rtcp_transceiver->StopPeriodicTask(); | |
| rtcp_transceiver.reset(); | |
| done = true; | |
| }); | |
| ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsNoRtcpWhenNetworkStateIsDown) { | |
| MockFunction<void(ArrayView<const uint8_t>)> mock_transport; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.initial_ready_to_send = false; | |
| config.rtcp_transport = mock_transport.AsStdFunction(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| EXPECT_CALL(mock_transport, Call).Times(0); | |
| const std::vector<uint16_t> sequence_numbers = {45, 57}; | |
| const uint32_t ssrcs[] = {123}; | |
| rtcp_transceiver.SendCompoundPacket(); | |
| rtcp_transceiver.SendNack(ssrcs[0], sequence_numbers); | |
| rtcp_transceiver.SendPictureLossIndication(ssrcs[0]); | |
| rtcp_transceiver.SendFullIntraRequest(ssrcs, true); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsRtcpWhenNetworkStateIsUp) { | |
| MockFunction<void(ArrayView<const uint8_t>)> mock_transport; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.initial_ready_to_send = false; | |
| config.rtcp_transport = mock_transport.AsStdFunction(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetReadyToSend(true); | |
| EXPECT_CALL(mock_transport, Call).Times(4); | |
| const std::vector<uint16_t> sequence_numbers = {45, 57}; | |
| const uint32_t ssrcs[] = {123}; | |
| rtcp_transceiver.SendCompoundPacket(); | |
| rtcp_transceiver.SendNack(ssrcs[0], sequence_numbers); | |
| rtcp_transceiver.SendPictureLossIndication(ssrcs[0]); | |
| rtcp_transceiver.SendFullIntraRequest(ssrcs, true); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsPeriodicRtcpWhenNetworkStateIsUp) { | |
| auto queue = CreateTaskQueue(); | |
| FakeRtcpTransport transport(time_controller()); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = true; | |
| config.initial_ready_to_send = false; | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| config.task_queue = queue.get(); | |
| std::optional<RtcpTransceiverImpl> rtcp_transceiver; | |
| rtcp_transceiver.emplace(config); | |
| queue->PostTask([&] { rtcp_transceiver->SetReadyToSend(true); }); | |
| EXPECT_TRUE(transport.WaitPacket()); | |
| // Cleanup. | |
| bool done = false; | |
| queue->PostTask([&] { | |
| rtcp_transceiver->StopPeriodicTask(); | |
| rtcp_transceiver.reset(); | |
| done = true; | |
| }); | |
| ASSERT_TRUE(time_controller().Wait([&] { return done; }, kAlmostForever)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsMinimalCompoundPacket) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.cname = "cname"; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| // Minimal compound RTCP packet contains sender or receiver report and sdes | |
| // with cname. | |
| ASSERT_GT(rtcp_parser.receiver_report()->num_packets(), 0); | |
| EXPECT_EQ(rtcp_parser.receiver_report()->sender_ssrc(), kSenderSsrc); | |
| ASSERT_GT(rtcp_parser.sdes()->num_packets(), 0); | |
| ASSERT_EQ(rtcp_parser.sdes()->chunks().size(), 1u); | |
| EXPECT_EQ(rtcp_parser.sdes()->chunks()[0].ssrc, kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.sdes()->chunks()[0].cname, config.cname); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, AvoidsEmptyPacketsInReducedMode) { | |
| MockFunction<void(ArrayView<const uint8_t>)> transport; | |
| EXPECT_CALL(transport, Call).Times(0); | |
| NiceMock<MockReceiveStatisticsProvider> receive_statistics; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| config.rtcp_mode = RtcpMode::kReducedSize; | |
| config.receive_statistics = &receive_statistics; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, AvoidsEmptyReceiverReportsInReducedMode) { | |
| RtcpPacketParser rtcp_parser; | |
| NiceMock<MockReceiveStatisticsProvider> receive_statistics; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.rtcp_mode = RtcpMode::kReducedSize; | |
| config.receive_statistics = &receive_statistics; | |
| // Set it to produce something (RRTR) in the "periodic" rtcp packets. | |
| config.non_sender_rtt_measurement = true; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| // Rather than waiting for the right time to produce the periodic packet, | |
| // trigger it manually. | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 0); | |
| EXPECT_GT(rtcp_parser.xr()->num_packets(), 0); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsNoRembInitially) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 0); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SetRembIncludesRembInNextCompoundPacket) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.remb()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); | |
| EXPECT_THAT(rtcp_parser.remb()->ssrcs(), ElementsAre(54321, 64321)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SetRembUpdatesValuesToSend) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); | |
| EXPECT_THAT(rtcp_parser.remb()->ssrcs(), ElementsAre(54321, 64321)); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/70000, /*ssrcs=*/{67321}); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 2); | |
| EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 70000); | |
| EXPECT_THAT(rtcp_parser.remb()->ssrcs(), ElementsAre(67321)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SetRembSendsImmediatelyIfSendRembOnChange) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.send_remb_on_change = true; | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.remb()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); | |
| // If there is no change, the packet is not sent immediately. | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/20000, /*ssrcs=*/{}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 2); | |
| EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 20000); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, | |
| SetRembSendsImmediatelyIfSendRembOnChangeReducedSize) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.send_remb_on_change = true; | |
| config.rtcp_mode = RtcpMode::kReducedSize; | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.remb()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.remb()->bitrate_bps(), 10000); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SetRembIncludesRembInAllCompoundPackets) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{2}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 2); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsNoRembAfterUnset) { | |
| const uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SetRemb(/*bitrate_bps=*/10000, /*ssrcs=*/{54321, 64321}); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| ASSERT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| rtcp_transceiver.UnsetRemb(); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{2}); | |
| EXPECT_EQ(rtcp_parser.remb()->num_packets(), 1); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ReceiverReportUsesReceiveStatistics) { | |
| const uint32_t kSenderSsrc = 12345; | |
| const uint32_t kMediaSsrc = 54321; | |
| MockReceiveStatisticsProvider receive_statistics; | |
| std::vector<ReportBlock> report_blocks(1); | |
| report_blocks[0].SetMediaSsrc(kMediaSsrc); | |
| EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) | |
| .WillRepeatedly(Return(report_blocks)); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.receive_statistics = &receive_statistics; | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| ASSERT_GT(rtcp_parser.receiver_report()->num_packets(), 0); | |
| EXPECT_EQ(rtcp_parser.receiver_report()->sender_ssrc(), kSenderSsrc); | |
| ASSERT_THAT(rtcp_parser.receiver_report()->report_blocks(), | |
| SizeIs(report_blocks.size())); | |
| EXPECT_EQ(rtcp_parser.receiver_report()->report_blocks()[0].source_ssrc(), | |
| kMediaSsrc); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, MultipleObserversOnSameSsrc) { | |
| const uint32_t kRemoteSsrc = 12345; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer1; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer2; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer1); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(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, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, DoesntCallsObserverAfterRemoved) { | |
| const uint32_t kRemoteSsrc = 12345; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer1; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer2; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer1); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer2); | |
| SenderReport sr; | |
| sr.SetSenderSsrc(kRemoteSsrc); | |
| auto raw_packet = sr.Build(); | |
| rtcp_transceiver.RemoveMediaReceiverRtcpObserver(kRemoteSsrc, &observer1); | |
| EXPECT_CALL(observer1, OnSenderReport(_, _, _)).Times(0); | |
| EXPECT_CALL(observer2, OnSenderReport(_, _, _)); | |
| rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CallsObserverOnSenderReportBySenderSsrc) { | |
| const uint32_t kRemoteSsrc1 = 12345; | |
| const uint32_t kRemoteSsrc2 = 22345; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer1; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer2; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc1, &observer1); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(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, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CallsObserverOnByeBySenderSsrc) { | |
| const uint32_t kRemoteSsrc1 = 12345; | |
| const uint32_t kRemoteSsrc2 = 22345; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer1; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer2; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc1, &observer1); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(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, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CallsObserverOnTargetBitrateBySenderSsrc) { | |
| const uint32_t kRemoteSsrc1 = 12345; | |
| const uint32_t kRemoteSsrc2 = 22345; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer1; | |
| StrictMock<MockMediaReceiverRtcpObserver> observer2; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc1, &observer1); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc2, &observer2); | |
| 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); | |
| rtcp::ExtendedReports xr; | |
| xr.SetSenderSsrc(kRemoteSsrc1); | |
| xr.SetTargetBitrate(target_bitrate); | |
| auto raw_packet = xr.Build(); | |
| VideoBitrateAllocation 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, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SkipsIncorrectTargetBitrateEntries) { | |
| const uint32_t kRemoteSsrc = 12345; | |
| MockMediaReceiverRtcpObserver observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer); | |
| rtcp::TargetBitrate target_bitrate; | |
| target_bitrate.AddTargetBitrate(0, 0, /*target_bitrate_kbps=*/10); | |
| target_bitrate.AddTargetBitrate(0, kMaxTemporalStreams, 20); | |
| target_bitrate.AddTargetBitrate(kMaxSpatialLayers, 0, 40); | |
| rtcp::ExtendedReports xr; | |
| xr.SetTargetBitrate(target_bitrate); | |
| xr.SetSenderSsrc(kRemoteSsrc); | |
| auto raw_packet = xr.Build(); | |
| VideoBitrateAllocation expected_allocation; | |
| expected_allocation.SetBitrate(0, 0, /*bitrate_bps=*/10000); | |
| EXPECT_CALL(observer, OnBitrateAllocation(kRemoteSsrc, expected_allocation)); | |
| rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CallsObserverOnByeBehindSenderReport) { | |
| const uint32_t kRemoteSsrc = 12345; | |
| MockMediaReceiverRtcpObserver observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer); | |
| CompoundPacket compound; | |
| auto sr = std::make_unique<SenderReport>(); | |
| sr->SetSenderSsrc(kRemoteSsrc); | |
| compound.Append(std::move(sr)); | |
| auto bye = std::make_unique<Bye>(); | |
| bye->SetSenderSsrc(kRemoteSsrc); | |
| compound.Append(std::move(bye)); | |
| auto raw_packet = compound.Build(); | |
| EXPECT_CALL(observer, OnBye(kRemoteSsrc)); | |
| EXPECT_CALL(observer, OnSenderReport(kRemoteSsrc, _, _)); | |
| rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CallsObserverOnByeBehindUnknownRtcpPacket) { | |
| const uint32_t kRemoteSsrc = 12345; | |
| MockMediaReceiverRtcpObserver observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.AddMediaReceiverRtcpObserver(kRemoteSsrc, &observer); | |
| CompoundPacket compound; | |
| // Use Application-Defined rtcp packet as unknown. | |
| auto app = std::make_unique<rtcp::App>(); | |
| compound.Append(std::move(app)); | |
| auto bye = std::make_unique<Bye>(); | |
| bye->SetSenderSsrc(kRemoteSsrc); | |
| compound.Append(std::move(bye)); | |
| auto raw_packet = compound.Build(); | |
| EXPECT_CALL(observer, OnBye(kRemoteSsrc)); | |
| rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, | |
| WhenSendsReceiverReportSetsLastSenderReportTimestampPerRemoteSsrc) { | |
| const uint32_t kRemoteSsrc1 = 4321; | |
| const uint32_t kRemoteSsrc2 = 5321; | |
| std::vector<ReportBlock> statistics_report_blocks(2); | |
| statistics_report_blocks[0].SetMediaSsrc(kRemoteSsrc1); | |
| statistics_report_blocks[1].SetMediaSsrc(kRemoteSsrc2); | |
| MockReceiveStatisticsProvider receive_statistics; | |
| EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) | |
| .WillOnce(Return(statistics_report_blocks)); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.receive_statistics = &receive_statistics; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| const NtpTime kRemoteNtp(0x9876543211); | |
| // Receive SenderReport for RemoteSsrc1, but no report for RemoteSsrc2. | |
| SenderReport sr; | |
| sr.SetSenderSsrc(kRemoteSsrc1); | |
| sr.SetNtp(kRemoteNtp); | |
| auto raw_packet = sr.Build(); | |
| rtcp_transceiver.ReceivePacket(raw_packet, Timestamp::Micros(0)); | |
| // Trigger sending ReceiverReport. | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 0); | |
| const auto& report_blocks = rtcp_parser.receiver_report()->report_blocks(); | |
| ASSERT_EQ(report_blocks.size(), 2u); | |
| // RtcpTransceiverImpl doesn't guarantee order of the report blocks | |
| // match result of ReceiveStatisticsProvider::RtcpReportBlocks callback, | |
| // but for simplicity of the test asume it is the same. | |
| ASSERT_EQ(report_blocks[0].source_ssrc(), kRemoteSsrc1); | |
| EXPECT_EQ(report_blocks[0].last_sr(), CompactNtp(kRemoteNtp)); | |
| ASSERT_EQ(report_blocks[1].source_ssrc(), kRemoteSsrc2); | |
| // No matching Sender Report for kRemoteSsrc2, LastSR fields has to be 0. | |
| EXPECT_EQ(report_blocks[1].last_sr(), 0u); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, | |
| WhenSendsReceiverReportCalculatesDelaySinceLastSenderReport) { | |
| const uint32_t kRemoteSsrc1 = 4321; | |
| const uint32_t kRemoteSsrc2 = 5321; | |
| std::vector<ReportBlock> statistics_report_blocks(2); | |
| statistics_report_blocks[0].SetMediaSsrc(kRemoteSsrc1); | |
| statistics_report_blocks[1].SetMediaSsrc(kRemoteSsrc2); | |
| MockReceiveStatisticsProvider receive_statistics; | |
| EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) | |
| .WillOnce(Return(statistics_report_blocks)); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.receive_statistics = &receive_statistics; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| auto receive_sender_report = [&](uint32_t remote_ssrc) { | |
| SenderReport sr; | |
| sr.SetSenderSsrc(remote_ssrc); | |
| rtcp_transceiver.ReceivePacket(sr.Build(), CurrentTime()); | |
| }; | |
| receive_sender_report(kRemoteSsrc1); | |
| time_controller().AdvanceTime(TimeDelta::Millis(100)); | |
| receive_sender_report(kRemoteSsrc2); | |
| time_controller().AdvanceTime(TimeDelta::Millis(100)); | |
| // Trigger ReceiverReport back. | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 0); | |
| const auto& report_blocks = rtcp_parser.receiver_report()->report_blocks(); | |
| ASSERT_EQ(report_blocks.size(), 2u); | |
| // RtcpTransceiverImpl doesn't guarantee order of the report blocks | |
| // match result of ReceiveStatisticsProvider::RtcpReportBlocks callback, | |
| // but for simplicity of the test asume it is the same. | |
| ASSERT_EQ(report_blocks[0].source_ssrc(), kRemoteSsrc1); | |
| EXPECT_THAT(CompactNtpRttToTimeDelta(report_blocks[0].delay_since_last_sr()), | |
| Near(TimeDelta::Millis(200))); | |
| ASSERT_EQ(report_blocks[1].source_ssrc(), kRemoteSsrc2); | |
| EXPECT_THAT(CompactNtpRttToTimeDelta(report_blocks[1].delay_since_last_sr()), | |
| Near(TimeDelta::Millis(100))); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, MaySendMultipleReceiverReportInSinglePacket) { | |
| std::vector<ReportBlock> statistics_report_blocks(40); | |
| MockReceiveStatisticsProvider receive_statistics; | |
| EXPECT_CALL(receive_statistics, RtcpReportBlocks(/*max_blocks=*/Ge(40u))) | |
| .WillOnce(Return(statistics_report_blocks)); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.receive_statistics = &receive_statistics; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| // Trigger ReceiverReports. | |
| rtcp_transceiver.SendCompoundPacket(); | |
| // Expect a single RTCP packet with multiple receiver reports in it. | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| // Receiver report may contain up to 31 report blocks, thus 2 reports are | |
| // needed to carry 40 blocks: 31 in the first, 9 in the last. | |
| EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 2); | |
| // RtcpParser remembers just the last receiver report, thus can't check number | |
| // of blocks in the first receiver report. | |
| EXPECT_THAT(rtcp_parser.receiver_report()->report_blocks(), SizeIs(9)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, AttachMaxNumberOfReportBlocksToCompoundPacket) { | |
| MockReceiveStatisticsProvider receive_statistics; | |
| EXPECT_CALL(receive_statistics, RtcpReportBlocks) | |
| .WillOnce([](size_t max_blocks) { | |
| return std::vector<ReportBlock>(max_blocks); | |
| }); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.rtcp_mode = RtcpMode::kCompound; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.receive_statistics = &receive_statistics; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{0}); | |
| // Send some fast feedback message. Because of compound mode, report blocks | |
| // should be attached. | |
| rtcp_transceiver.SendPictureLossIndication(/*ssrc=*/123); | |
| // Expect single RTCP packet with multiple receiver reports and a PLI. | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| EXPECT_GT(rtcp_parser.receiver_report()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.pli()->num_packets(), 1); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsNack) { | |
| const uint32_t kSenderSsrc = 1234; | |
| const uint32_t kRemoteSsrc = 4321; | |
| std::vector<uint16_t> kMissingSequenceNumbers = {34, 37, 38}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendNack(kRemoteSsrc, kMissingSequenceNumbers); | |
| EXPECT_EQ(rtcp_parser.nack()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.nack()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.nack()->media_ssrc(), kRemoteSsrc); | |
| EXPECT_EQ(rtcp_parser.nack()->packet_ids(), kMissingSequenceNumbers); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ReceivesNack) { | |
| static constexpr uint32_t kRemoteSsrc = 4321; | |
| static constexpr uint32_t kMediaSsrc1 = 1234; | |
| static constexpr uint32_t kMediaSsrc2 = 1235; | |
| std::vector<uint16_t> kMissingSequenceNumbers = {34, 37, 38}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_stream1; | |
| MockRtpStreamRtcpHandler local_stream2; | |
| EXPECT_CALL(local_stream1, | |
| OnNack(kRemoteSsrc, ElementsAreArray(kMissingSequenceNumbers))); | |
| EXPECT_CALL(local_stream2, OnNack).Times(0); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); | |
| rtcp::Nack nack; | |
| nack.SetSenderSsrc(kRemoteSsrc); | |
| nack.SetMediaSsrc(kMediaSsrc1); | |
| nack.SetPacketIds(kMissingSequenceNumbers); | |
| rtcp_transceiver.ReceivePacket(nack.Build(), config.clock->CurrentTime()); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, RequestKeyFrameWithPictureLossIndication) { | |
| const uint32_t kSenderSsrc = 1234; | |
| const uint32_t kRemoteSsrc = 4321; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendPictureLossIndication(kRemoteSsrc); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| EXPECT_EQ(rtcp_parser.pli()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.pli()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.pli()->media_ssrc(), kRemoteSsrc); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ReceivesPictureLossIndication) { | |
| static constexpr uint32_t kRemoteSsrc = 4321; | |
| static constexpr uint32_t kMediaSsrc1 = 1234; | |
| static constexpr uint32_t kMediaSsrc2 = 1235; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_stream1; | |
| MockRtpStreamRtcpHandler local_stream2; | |
| EXPECT_CALL(local_stream1, OnPli(kRemoteSsrc)); | |
| EXPECT_CALL(local_stream2, OnPli).Times(0); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); | |
| rtcp::Pli pli; | |
| pli.SetSenderSsrc(kRemoteSsrc); | |
| pli.SetMediaSsrc(kMediaSsrc1); | |
| rtcp_transceiver.ReceivePacket(pli.Build(), config.clock->CurrentTime()); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, RequestKeyFrameWithFullIntraRequest) { | |
| const uint32_t kSenderSsrc = 1234; | |
| const uint32_t kRemoteSsrcs[] = {4321, 5321}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendFullIntraRequest(kRemoteSsrcs, true); | |
| EXPECT_EQ(rtcp_parser.fir()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.fir()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kRemoteSsrcs[0]); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kRemoteSsrcs[1]); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, RequestKeyFrameWithFirIncreaseSeqNoPerSsrc) { | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| const uint32_t kBothRemoteSsrcs[] = {4321, 5321}; | |
| const uint32_t kOneRemoteSsrc[] = {4321}; | |
| rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, true); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); | |
| uint8_t fir_sequence_number0 = rtcp_parser.fir()->requests()[0].seq_nr; | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); | |
| uint8_t fir_sequence_number1 = rtcp_parser.fir()->requests()[1].seq_nr; | |
| rtcp_transceiver.SendFullIntraRequest(kOneRemoteSsrc, true); | |
| ASSERT_EQ(rtcp_parser.fir()->requests().size(), 1u); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, fir_sequence_number0 + 1); | |
| rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, true); | |
| ASSERT_EQ(rtcp_parser.fir()->requests().size(), 2u); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, fir_sequence_number0 + 2); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[1].seq_nr, fir_sequence_number1 + 1); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendFirDoesNotIncreaseSeqNoIfOldRequest) { | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| const uint32_t kBothRemoteSsrcs[] = {4321, 5321}; | |
| rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, true); | |
| ASSERT_EQ(rtcp_parser.fir()->requests().size(), 2u); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); | |
| uint8_t fir_sequence_number0 = rtcp_parser.fir()->requests()[0].seq_nr; | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); | |
| uint8_t fir_sequence_number1 = rtcp_parser.fir()->requests()[1].seq_nr; | |
| rtcp_transceiver.SendFullIntraRequest(kBothRemoteSsrcs, false); | |
| ASSERT_EQ(rtcp_parser.fir()->requests().size(), 2u); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[0].ssrc, kBothRemoteSsrcs[0]); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[0].seq_nr, fir_sequence_number0); | |
| ASSERT_EQ(rtcp_parser.fir()->requests()[1].ssrc, kBothRemoteSsrcs[1]); | |
| EXPECT_EQ(rtcp_parser.fir()->requests()[1].seq_nr, fir_sequence_number1); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ReceivesFir) { | |
| static constexpr uint32_t kRemoteSsrc = 4321; | |
| static constexpr uint32_t kMediaSsrc1 = 1234; | |
| static constexpr uint32_t kMediaSsrc2 = 1235; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_stream1; | |
| MockRtpStreamRtcpHandler local_stream2; | |
| EXPECT_CALL(local_stream1, OnFir(kRemoteSsrc)); | |
| EXPECT_CALL(local_stream2, OnFir).Times(0); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); | |
| rtcp::Fir fir; | |
| fir.SetSenderSsrc(kRemoteSsrc); | |
| fir.AddRequestTo(kMediaSsrc1, /*seq_num=*/13); | |
| rtcp_transceiver.ReceivePacket(fir.Build(), config.clock->CurrentTime()); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, IgnoresReceivedFirWithRepeatedSequenceNumber) { | |
| static constexpr uint32_t kRemoteSsrc = 4321; | |
| static constexpr uint32_t kMediaSsrc1 = 1234; | |
| static constexpr uint32_t kMediaSsrc2 = 1235; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_stream1; | |
| MockRtpStreamRtcpHandler local_stream2; | |
| EXPECT_CALL(local_stream1, OnFir(kRemoteSsrc)).Times(1); | |
| EXPECT_CALL(local_stream2, OnFir(kRemoteSsrc)).Times(2); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc2, &local_stream2)); | |
| rtcp::Fir fir1; | |
| fir1.SetSenderSsrc(kRemoteSsrc); | |
| fir1.AddRequestTo(kMediaSsrc1, /*seq_num=*/132); | |
| fir1.AddRequestTo(kMediaSsrc2, /*seq_num=*/10); | |
| rtcp_transceiver.ReceivePacket(fir1.Build(), config.clock->CurrentTime()); | |
| // Repeat request for MediaSsrc1 - expect it to be ignored, | |
| // Change FIR sequence number for MediaSsrc2 - expect a 2nd callback. | |
| rtcp::Fir fir2; | |
| fir2.SetSenderSsrc(kRemoteSsrc); | |
| fir2.AddRequestTo(kMediaSsrc1, /*seq_num=*/132); | |
| fir2.AddRequestTo(kMediaSsrc2, /*seq_num=*/13); | |
| rtcp_transceiver.ReceivePacket(fir2.Build(), config.clock->CurrentTime()); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ReceivedFirTracksSequenceNumberPerRemoteSsrc) { | |
| static constexpr uint32_t kRemoteSsrc1 = 4321; | |
| static constexpr uint32_t kRemoteSsrc2 = 4323; | |
| static constexpr uint32_t kMediaSsrc = 1234; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_stream; | |
| EXPECT_CALL(local_stream, OnFir(kRemoteSsrc1)); | |
| EXPECT_CALL(local_stream, OnFir(kRemoteSsrc2)); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc, &local_stream)); | |
| rtcp::Fir fir1; | |
| fir1.SetSenderSsrc(kRemoteSsrc1); | |
| fir1.AddRequestTo(kMediaSsrc, /*seq_num=*/13); | |
| rtcp_transceiver.ReceivePacket(fir1.Build(), config.clock->CurrentTime()); | |
| // Use the same FIR sequence number, but different sender SSRC. | |
| rtcp::Fir fir2; | |
| fir2.SetSenderSsrc(kRemoteSsrc2); | |
| fir2.AddRequestTo(kMediaSsrc, /*seq_num=*/13); | |
| rtcp_transceiver.ReceivePacket(fir2.Build(), config.clock->CurrentTime()); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, KeyFrameRequestCreatesCompoundPacket) { | |
| const uint32_t kRemoteSsrcs[] = {4321}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| // Turn periodic off to ensure sent rtcp packet is explicitly requested. | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.rtcp_mode = RtcpMode::kCompound; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendFullIntraRequest(kRemoteSsrcs, true); | |
| // Test sent packet is compound by expecting presense of receiver report. | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 1); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, KeyFrameRequestCreatesReducedSizePacket) { | |
| const uint32_t kRemoteSsrcs[] = {4321}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| // Turn periodic off to ensure sent rtcp packet is explicitly requested. | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.rtcp_mode = RtcpMode::kReducedSize; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendFullIntraRequest(kRemoteSsrcs, true); | |
| // Test sent packet is reduced size by expecting absense of receiver report. | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| EXPECT_EQ(rtcp_parser.receiver_report()->num_packets(), 0); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsXrRrtrWhenEnabled) { | |
| const uint32_t kSenderSsrc = 4321; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.non_sender_rtt_measurement = true; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| NtpTime ntp_time_now = config.clock->CurrentNtpTime(); | |
| EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1); | |
| EXPECT_EQ(rtcp_parser.xr()->sender_ssrc(), kSenderSsrc); | |
| ASSERT_TRUE(rtcp_parser.xr()->rrtr()); | |
| EXPECT_EQ(rtcp_parser.xr()->rrtr()->ntp(), ntp_time_now); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, RepliesToRrtrWhenEnabled) { | |
| static constexpr uint32_t kSenderSsrc[] = {4321, 9876}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.reply_to_non_sender_rtt_measurement = true; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp::ExtendedReports xr; | |
| rtcp::Rrtr rrtr; | |
| rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444})); | |
| xr.SetRrtr(rrtr); | |
| xr.SetSenderSsrc(kSenderSsrc[0]); | |
| rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); | |
| AdvanceTime(TimeDelta::Millis(1'500)); | |
| rrtr.SetNtp(NtpTime(uint64_t{0x4444'5555'6666'7777})); | |
| xr.SetRrtr(rrtr); | |
| xr.SetSenderSsrc(kSenderSsrc[1]); | |
| rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); | |
| AdvanceTime(TimeDelta::Millis(500)); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1); | |
| static constexpr uint32_t kComactNtpOneSecond = 0x0001'0000; | |
| EXPECT_THAT(rtcp_parser.xr()->dlrr().sub_blocks(), | |
| UnorderedElementsAre( | |
| rtcp::ReceiveTimeInfo(kSenderSsrc[0], 0x2222'3333, | |
| /*delay=*/2 * kComactNtpOneSecond), | |
| rtcp::ReceiveTimeInfo(kSenderSsrc[1], 0x5555'6666, | |
| /*delay=*/kComactNtpOneSecond / 2))); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CanReplyToRrtrOnceForAllLocalSsrcs) { | |
| static constexpr uint32_t kRemoteSsrc = 4321; | |
| static constexpr uint32_t kLocalSsrcs[] = {1234, 5678}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.reply_to_non_sender_rtt_measurement = true; | |
| config.reply_to_non_sender_rtt_mesaurments_on_all_ssrcs = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_sender0; | |
| MockRtpStreamRtcpHandler local_sender1; | |
| rtcp_transceiver.AddMediaSender(kLocalSsrcs[0], &local_sender0); | |
| rtcp_transceiver.AddMediaSender(kLocalSsrcs[1], &local_sender1); | |
| rtcp::ExtendedReports xr; | |
| rtcp::Rrtr rrtr; | |
| rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444})); | |
| xr.SetRrtr(rrtr); | |
| xr.SetSenderSsrc(kRemoteSsrc); | |
| rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); | |
| AdvanceTime(TimeDelta::Millis(1'500)); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.xr()->num_packets(), 1); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CanReplyToRrtrForEachLocalSsrc) { | |
| static constexpr uint32_t kRemoteSsrc = 4321; | |
| static constexpr uint32_t kLocalSsrc[] = {1234, 5678}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.reply_to_non_sender_rtt_measurement = true; | |
| config.reply_to_non_sender_rtt_mesaurments_on_all_ssrcs = true; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_sender0; | |
| MockRtpStreamRtcpHandler local_sender1; | |
| rtcp_transceiver.AddMediaSender(kLocalSsrc[0], &local_sender0); | |
| rtcp_transceiver.AddMediaSender(kLocalSsrc[1], &local_sender1); | |
| rtcp::ExtendedReports xr; | |
| rtcp::Rrtr rrtr; | |
| rrtr.SetNtp(NtpTime(uint64_t{0x1111'2222'3333'4444})); | |
| xr.SetRrtr(rrtr); | |
| xr.SetSenderSsrc(kRemoteSsrc); | |
| rtcp_transceiver.ReceivePacket(xr.Build(), CurrentTime()); | |
| AdvanceTime(TimeDelta::Millis(1'500)); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.xr()->num_packets(), 2); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsNoXrRrtrWhenDisabled) { | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.non_sender_rtt_measurement = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| // Extended reports rtcp packet might be included for another reason, | |
| // but it shouldn't contain rrtr block. | |
| EXPECT_FALSE(rtcp_parser.xr()->rrtr()); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, PassRttFromDlrrToLinkObserver) { | |
| const uint32_t kSenderSsrc = 4321; | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.network_link_observer = &link_observer; | |
| config.non_sender_rtt_measurement = true; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| Timestamp send_time = Timestamp::Seconds(5678); | |
| Timestamp receive_time = send_time + TimeDelta::Millis(110); | |
| rtcp::ReceiveTimeInfo rti; | |
| rti.ssrc = kSenderSsrc; | |
| rti.last_rr = CompactNtp(config.clock->ConvertTimestampToNtpTime(send_time)); | |
| rti.delay_since_last_rr = SaturatedToCompactNtp(TimeDelta::Millis(10)); | |
| rtcp::ExtendedReports xr; | |
| xr.AddDlrrItem(rti); | |
| EXPECT_CALL(link_observer, | |
| OnRttUpdate(receive_time, Near(TimeDelta::Millis(100)))); | |
| rtcp_transceiver.ReceivePacket(xr.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, CalculatesRoundTripTimeFromReportBlocks) { | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.network_link_observer = &link_observer; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| TimeDelta rtt = TimeDelta::Millis(100); | |
| Timestamp send_time = Timestamp::Seconds(5678); | |
| Timestamp receive_time = send_time + TimeDelta::Millis(110); | |
| rtcp::ReceiverReport rr; | |
| rtcp::ReportBlock rb1; | |
| rb1.SetLastSr(CompactNtp(config.clock->ConvertTimestampToNtpTime( | |
| receive_time - rtt - TimeDelta::Millis(10)))); | |
| rb1.SetDelayLastSr(SaturatedToCompactNtp(TimeDelta::Millis(10))); | |
| rr.AddReportBlock(rb1); | |
| rtcp::ReportBlock rb2; | |
| rb2.SetLastSr(CompactNtp(config.clock->ConvertTimestampToNtpTime( | |
| receive_time - rtt - TimeDelta::Millis(20)))); | |
| rb2.SetDelayLastSr(SaturatedToCompactNtp(TimeDelta::Millis(20))); | |
| rr.AddReportBlock(rb2); | |
| EXPECT_CALL(link_observer, OnRttUpdate(receive_time, Near(rtt))); | |
| rtcp_transceiver.ReceivePacket(rr.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, IgnoresUnknownSsrcInDlrr) { | |
| const uint32_t kSenderSsrc = 4321; | |
| const uint32_t kUnknownSsrc = 4322; | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kSenderSsrc; | |
| config.schedule_periodic_compound_packets = false; | |
| config.non_sender_rtt_measurement = true; | |
| config.network_link_observer = &link_observer; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| Timestamp time = Timestamp::Micros(12345678); | |
| rtcp::ReceiveTimeInfo rti; | |
| rti.ssrc = kUnknownSsrc; | |
| rti.last_rr = CompactNtp(config.clock->ConvertTimestampToNtpTime(time)); | |
| rtcp::ExtendedReports xr; | |
| xr.AddDlrrItem(rti); | |
| auto raw_packet = xr.Build(); | |
| EXPECT_CALL(link_observer, OnRttUpdate).Times(0); | |
| rtcp_transceiver.ReceivePacket(raw_packet, time + TimeDelta::Millis(100)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ParsesTransportFeedback) { | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.network_link_observer = &link_observer; | |
| Timestamp receive_time = Timestamp::Seconds(5678); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| EXPECT_CALL(link_observer, OnTransportFeedback(receive_time, _)) | |
| .WillOnce(WithArg<1>([](const rtcp::TransportFeedback& message) { | |
| EXPECT_EQ(message.GetBaseSequence(), 321); | |
| EXPECT_THAT(message.GetReceivedPackets(), SizeIs(2)); | |
| })); | |
| rtcp::TransportFeedback tb; | |
| tb.SetBase(/*base_sequence=*/321, Timestamp::Micros(15)); | |
| tb.AddReceivedPacket(/*base_sequence=*/321, Timestamp::Micros(15)); | |
| tb.AddReceivedPacket(/*base_sequence=*/322, Timestamp::Micros(17)); | |
| rtcp_transceiver.ReceivePacket(tb.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ParsesCongestionControlFeedback) { | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.network_link_observer = &link_observer; | |
| const uint32_t receive_time_ntp = 5678; | |
| Timestamp receive_time = Timestamp::Seconds(9843); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| EXPECT_CALL(link_observer, OnCongestionControlFeedback(receive_time, _)) | |
| .WillOnce(WithArg<1>([](const rtcp::CongestionControlFeedback& message) { | |
| EXPECT_EQ(message.report_timestamp_compact_ntp(), 5678u); | |
| EXPECT_THAT(message.packets(), SizeIs(2)); | |
| })); | |
| std::vector<rtcp::CongestionControlFeedback::PacketInfo> packets = { | |
| {.ssrc = 1, | |
| .sequence_number = 321, | |
| .arrival_time_offset = TimeDelta::Millis(15)}, | |
| {.ssrc = 1, | |
| .sequence_number = 322, | |
| .arrival_time_offset = TimeDelta::Millis(17)}}; | |
| rtcp::CongestionControlFeedback ccfb(std::move(packets), receive_time_ntp); | |
| rtcp_transceiver.ReceivePacket(ccfb.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, ParsesRemb) { | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.network_link_observer = &link_observer; | |
| Timestamp receive_time = Timestamp::Seconds(5678); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| EXPECT_CALL(link_observer, | |
| OnReceiverEstimatedMaxBitrate(receive_time, | |
| DataRate::BitsPerSec(1'234'000))); | |
| rtcp::Remb remb; | |
| remb.SetBitrateBps(1'234'000); | |
| rtcp_transceiver.ReceivePacket(remb.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, | |
| CombinesReportBlocksFromSenderAndRecieverReports) { | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.network_link_observer = &link_observer; | |
| Timestamp receive_time = Timestamp::Seconds(5678); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| // Assemble compound packet with multiple rtcp packets in it. | |
| rtcp::CompoundPacket packet; | |
| auto sr = std::make_unique<rtcp::SenderReport>(); | |
| sr->SetSenderSsrc(1234); | |
| sr->SetReportBlocks(std::vector<ReportBlock>(31)); | |
| packet.Append(std::move(sr)); | |
| auto rr1 = std::make_unique<rtcp::ReceiverReport>(); | |
| rr1->SetReportBlocks(std::vector<ReportBlock>(31)); | |
| packet.Append(std::move(rr1)); | |
| auto rr2 = std::make_unique<rtcp::ReceiverReport>(); | |
| rr2->SetReportBlocks(std::vector<ReportBlock>(2)); | |
| packet.Append(std::move(rr2)); | |
| EXPECT_CALL(link_observer, OnReport(receive_time, SizeIs(64))); | |
| rtcp_transceiver.ReceivePacket(packet.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, | |
| CallbackOnReportBlocksFromSenderAndReceiverReports) { | |
| static constexpr uint32_t kRemoteSsrc = 5678; | |
| // Has registered sender, report block attached to sender report. | |
| static constexpr uint32_t kMediaSsrc1 = 1234; | |
| // No registered sender, report block attached to receiver report. | |
| // Such report block shouldn't prevent handling following report block. | |
| static constexpr uint32_t kMediaSsrc2 = 1235; | |
| // Has registered sender, no report block attached. | |
| static constexpr uint32_t kMediaSsrc3 = 1236; | |
| // Has registered sender, report block attached to receiver report. | |
| static constexpr uint32_t kMediaSsrc4 = 1237; | |
| MockNetworkLinkRtcpObserver link_observer; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| Timestamp receive_time = Timestamp::Seconds(5678); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler local_stream1; | |
| MockRtpStreamRtcpHandler local_stream3; | |
| MockRtpStreamRtcpHandler local_stream4; | |
| EXPECT_CALL(local_stream1, | |
| OnReport(Property(&ReportBlockData::sender_ssrc, kRemoteSsrc))); | |
| EXPECT_CALL(local_stream3, OnReport).Times(0); | |
| EXPECT_CALL(local_stream4, | |
| OnReport(Property(&ReportBlockData::sender_ssrc, kRemoteSsrc))); | |
| ASSERT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc1, &local_stream1)); | |
| ASSERT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc3, &local_stream3)); | |
| ASSERT_TRUE(rtcp_transceiver.AddMediaSender(kMediaSsrc4, &local_stream4)); | |
| // Assemble compound packet with multiple RTCP packets in it. | |
| rtcp::CompoundPacket packet; | |
| auto sr = std::make_unique<rtcp::SenderReport>(); | |
| sr->SetSenderSsrc(kRemoteSsrc); | |
| std::vector<ReportBlock> rb(1); | |
| rb[0].SetMediaSsrc(kMediaSsrc1); | |
| sr->SetReportBlocks(std::move(rb)); | |
| packet.Append(std::move(sr)); | |
| auto rr = std::make_unique<rtcp::ReceiverReport>(); | |
| rr->SetSenderSsrc(kRemoteSsrc); | |
| rb = std::vector<ReportBlock>(2); | |
| rb[0].SetMediaSsrc(kMediaSsrc2); | |
| rb[1].SetMediaSsrc(kMediaSsrc4); | |
| rr->SetReportBlocks(std::move(rb)); | |
| packet.Append(std::move(rr)); | |
| rtcp_transceiver.ReceivePacket(packet.Build(), receive_time); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, FailsToRegisterTwoSendersWithTheSameSsrc) { | |
| RtcpTransceiverImpl rtcp_transceiver(DefaultTestConfig()); | |
| MockRtpStreamRtcpHandler sender1; | |
| MockRtpStreamRtcpHandler sender2; | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(/*local_ssrc=*/10001, &sender1)); | |
| EXPECT_FALSE(rtcp_transceiver.AddMediaSender(/*local_ssrc=*/10001, &sender2)); | |
| EXPECT_TRUE(rtcp_transceiver.AddMediaSender(/*local_ssrc=*/10002, &sender2)); | |
| EXPECT_TRUE(rtcp_transceiver.RemoveMediaSender(/*local_ssrc=*/10001)); | |
| EXPECT_FALSE(rtcp_transceiver.RemoveMediaSender(/*local_ssrc=*/10001)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SendsSenderReport) { | |
| static constexpr uint32_t kFeedbackSsrc = 123; | |
| static constexpr uint32_t kSenderSsrc = 12345; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.feedback_ssrc = kFeedbackSsrc; | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.schedule_periodic_compound_packets = false; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| RtpStreamRtcpHandler::RtpStats sender_stats; | |
| sender_stats.set_num_sent_packets(10); | |
| sender_stats.set_num_sent_bytes(1000); | |
| sender_stats.set_last_rtp_timestamp(0x3333); | |
| sender_stats.set_last_capture_time(CurrentTime() - TimeDelta::Seconds(2)); | |
| sender_stats.set_last_clock_rate(0x1000); | |
| MockRtpStreamRtcpHandler sender; | |
| ON_CALL(sender, SentStats).WillByDefault(Return(sender_stats)); | |
| rtcp_transceiver.AddMediaSender(kSenderSsrc, &sender); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| ASSERT_GT(rtcp_parser.sender_report()->num_packets(), 0); | |
| EXPECT_EQ(rtcp_parser.sender_report()->sender_ssrc(), kSenderSsrc); | |
| EXPECT_EQ(rtcp_parser.sender_report()->ntp(), | |
| time_controller().GetClock()->CurrentNtpTime()); | |
| EXPECT_EQ(rtcp_parser.sender_report()->rtp_timestamp(), 0x3333u + 0x2000u); | |
| EXPECT_EQ(rtcp_parser.sender_report()->sender_packet_count(), 10u); | |
| EXPECT_EQ(rtcp_parser.sender_report()->sender_octet_count(), 1000u); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, | |
| MaySendBothSenderReportAndReceiverReportInTheSamePacket) { | |
| RtcpPacketParser rtcp_parser; | |
| std::vector<ReportBlock> statistics_report_blocks(40); | |
| MockReceiveStatisticsProvider receive_statistics; | |
| EXPECT_CALL(receive_statistics, RtcpReportBlocks(/*max_blocks=*/Ge(40u))) | |
| .WillOnce(Return(statistics_report_blocks)); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| config.receive_statistics = &receive_statistics; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| MockRtpStreamRtcpHandler sender; | |
| rtcp_transceiver.AddMediaSender(/*ssrc=*/12345, &sender); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| // Expect a single RTCP packet with a sender and a receiver reports in it. | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| ASSERT_EQ(rtcp_parser.sender_report()->num_packets(), 1); | |
| ASSERT_EQ(rtcp_parser.receiver_report()->num_packets(), 1); | |
| // Sender report may contain up to 31 report blocks, thus remaining 9 report | |
| // block should be attached to the receiver report. | |
| EXPECT_THAT(rtcp_parser.sender_report()->report_blocks(), SizeIs(31)); | |
| EXPECT_THAT(rtcp_parser.receiver_report()->report_blocks(), SizeIs(9)); | |
| } | |
| TEST_F(RtcpTransceiverImplTest, RotatesSendersWhenAllSenderReportDoNotFit) { | |
| // Send 6 compound packet, each should contain 5 sender reports, | |
| // each of 6 senders should be mentioned 5 times. | |
| static constexpr int kNumSenders = 6; | |
| static constexpr uint32_t kSenderSsrc[kNumSenders] = {10, 20, 30, 40, 50, 60}; | |
| static constexpr int kSendersPerPacket = 5; | |
| // RtcpPacketParser remembers only latest block for each type, but this test | |
| // is about sending multiple sender reports in the same packet, thus need | |
| // a more advance parser: RtcpTranceiver | |
| RtcpTransceiverConfig receiver_config = DefaultTestConfig(); | |
| RtcpTransceiverImpl rtcp_receiver(receiver_config); | |
| // Main expectatation: all senders are spread equally across multiple packets. | |
| NiceMock<MockMediaReceiverRtcpObserver> receiver[kNumSenders]; | |
| for (int i = 0; i < kNumSenders; ++i) { | |
| SCOPED_TRACE(i); | |
| EXPECT_CALL(receiver[i], OnSenderReport(kSenderSsrc[i], _, _)) | |
| .Times(kSendersPerPacket); | |
| rtcp_receiver.AddMediaReceiverRtcpObserver(kSenderSsrc[i], &receiver[i]); | |
| } | |
| MockFunction<void(ArrayView<const uint8_t>)> transport; | |
| EXPECT_CALL(transport, Call) | |
| .Times(kNumSenders) | |
| .WillRepeatedly([&](ArrayView<const uint8_t> packet) { | |
| rtcp_receiver.ReceivePacket(packet, CurrentTime()); | |
| return true; | |
| }); | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| config.rtcp_transport = transport.AsStdFunction(); | |
| // Limit packet to have space just for kSendersPerPacket sender reports. | |
| // Sender report without report blocks require 28 bytes. | |
| config.max_packet_size = kSendersPerPacket * 28; | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| NiceMock<MockRtpStreamRtcpHandler> sender[kNumSenders]; | |
| for (int i = 0; i < kNumSenders; ++i) { | |
| rtcp_transceiver.AddMediaSender(kSenderSsrc[i], &sender[i]); | |
| } | |
| for (int i = 1; i <= kNumSenders; ++i) { | |
| SCOPED_TRACE(i); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| } | |
| } | |
| TEST_F(RtcpTransceiverImplTest, SkipsSenderReportForInactiveSender) { | |
| static constexpr uint32_t kSenderSsrc[] = {12345, 23456}; | |
| RtcpTransceiverConfig config = DefaultTestConfig(); | |
| RtcpPacketParser rtcp_parser; | |
| config.rtcp_transport = RtcpParserTransport(rtcp_parser); | |
| RtcpTransceiverImpl rtcp_transceiver(config); | |
| RtpStreamRtcpHandler::RtpStats sender_stats[2]; | |
| NiceMock<MockRtpStreamRtcpHandler> sender[2]; | |
| ON_CALL(sender[0], SentStats).WillByDefault([&] { return sender_stats[0]; }); | |
| ON_CALL(sender[1], SentStats).WillByDefault([&] { return sender_stats[1]; }); | |
| rtcp_transceiver.AddMediaSender(kSenderSsrc[0], &sender[0]); | |
| rtcp_transceiver.AddMediaSender(kSenderSsrc[1], &sender[1]); | |
| // Start with both senders beeing active. | |
| sender_stats[0].set_num_sent_packets(10); | |
| sender_stats[0].set_num_sent_bytes(1'000); | |
| sender_stats[1].set_num_sent_packets(5); | |
| sender_stats[1].set_num_sent_bytes(2'000); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{1}); | |
| EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 2); | |
| // Keep 1st sender active, but make 2nd second look inactive by returning the | |
| // same RtpStats. | |
| sender_stats[0].set_num_sent_packets(15); | |
| sender_stats[0].set_num_sent_bytes(2'000); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{2}); | |
| EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 3); | |
| EXPECT_EQ(rtcp_parser.sender_report()->sender_ssrc(), kSenderSsrc[0]); | |
| // Swap active sender. | |
| sender_stats[1].set_num_sent_packets(20); | |
| sender_stats[1].set_num_sent_bytes(3'000); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{3}); | |
| EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 4); | |
| EXPECT_EQ(rtcp_parser.sender_report()->sender_ssrc(), kSenderSsrc[1]); | |
| // Activate both senders again. | |
| sender_stats[0].set_num_sent_packets(20); | |
| sender_stats[0].set_num_sent_bytes(3'000); | |
| sender_stats[1].set_num_sent_packets(25); | |
| sender_stats[1].set_num_sent_bytes(3'500); | |
| rtcp_transceiver.SendCompoundPacket(); | |
| EXPECT_EQ(rtcp_parser.processed_rtcp_packets(), size_t{4}); | |
| EXPECT_EQ(rtcp_parser.sender_report()->num_packets(), 6); | |
| } | |
| } // namespace | |
| } // namespace webrtc |