summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc')
-rw-r--r--third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc1664
1 files changed, 1664 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
new file mode 100644
index 0000000000..e3f205dc1d
--- /dev/null
+++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtcp_transceiver_impl_unittest.cc
@@ -0,0 +1,1664 @@
+/*
+ * 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 <memory>
+#include <utility>
+#include <vector>
+
+#include "absl/memory/memory.h"
+#include "api/rtp_headers.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 "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/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 "system_wrappers/include/clock.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/rtcp_packet_parser.h"
+
+namespace webrtc {
+namespace {
+
+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;
+using ::webrtc::rtcp::Bye;
+using ::webrtc::rtcp::CompoundPacket;
+using ::webrtc::rtcp::ReportBlock;
+using ::webrtc::rtcp::SenderReport;
+using ::webrtc::test::RtcpPacketParser;
+
+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, rtc::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(rtc::ArrayView<const uint8_t>)> AsStdFunction() {
+ return [this](rtc::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(rtc::ArrayView<const uint8_t>)> RtcpParserTransport(
+ RtcpPacketParser& parser) {
+ return [&parser](rtc::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();
+ absl::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();
+ absl::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();
+ absl::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(rtc::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(rtc::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();
+ absl::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(rtc::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 = webrtc::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 = webrtc::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 = webrtc::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);
+
+ 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();
+
+ 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);
+
+ 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();
+
+ 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<webrtc::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 = webrtc::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 = webrtc::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);
+ webrtc::rtcp::ReceiveTimeInfo rti;
+ rti.ssrc = kUnknownSsrc;
+ rti.last_rr = CompactNtp(config.clock->ConvertTimestampToNtpTime(time));
+ webrtc::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, 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(rtc::ArrayView<const uint8_t>)> transport;
+ EXPECT_CALL(transport, Call)
+ .Times(kNumSenders)
+ .WillRepeatedly([&](rtc::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