/* * 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 #include "modules/rtp_rtcp/include/receive_statistics.h" #include "rtc_base/event.h" #include "rtc_base/ptr_util.h" #include "rtc_base/task_queue.h" #include "test/gmock.h" #include "test/gtest.h" #include "test/mock_transport.h" #include "test/rtcp_packet_parser.h" namespace { using ::testing::_; using ::testing::Invoke; using ::testing::Return; using ::testing::SizeIs; using ::webrtc::MockTransport; using ::webrtc::RtcpTransceiverConfig; using ::webrtc::RtcpTransceiverImpl; using ::webrtc::rtcp::ReportBlock; using ::webrtc::test::RtcpPacketParser; class MockReceiveStatisticsProvider : public webrtc::ReceiveStatisticsProvider { public: MOCK_METHOD1(RtcpReportBlocks, std::vector(size_t)); }; // Helper to wait for an rtcp packet produced on a different thread/task queue. class FakeRtcpTransport : public webrtc::Transport { public: FakeRtcpTransport() : sent_rtcp_(false, false) {} bool SendRtcp(const uint8_t* data, size_t size) override { sent_rtcp_.Set(); return true; } bool SendRtp(const uint8_t*, size_t, const webrtc::PacketOptions&) override { ADD_FAILURE() << "RtcpTransciver shouldn't send rtp packets."; return true; } // Returns true if packet was received by this transport before timeout, bool WaitPacket(int64_t timeout_ms) { return sent_rtcp_.Wait(timeout_ms); } private: rtc::Event sent_rtcp_; }; // Posting delayed tasks doesn't promise high precision. constexpr int64_t kTaskQueuePrecisionMs = 15; TEST(RtcpTransceiverImplTest, DelaysSendingFirstCompondPacket) { rtc::TaskQueue queue("rtcp"); FakeRtcpTransport transport; RtcpTransceiverConfig config; config.outgoing_transport = &transport; config.initial_report_delay_ms = 10; config.task_queue = &queue; rtc::Optional rtcp_transceiver; int64_t started_ms = rtc::TimeMillis(); queue.PostTask([&] { rtcp_transceiver.emplace(config); }); EXPECT_TRUE(transport.WaitPacket(config.initial_report_delay_ms + kTaskQueuePrecisionMs)); EXPECT_GE(rtc::TimeMillis() - started_ms, config.initial_report_delay_ms); // Cleanup. rtc::Event done(false, false); queue.PostTask([&] { rtcp_transceiver.reset(); done.Set(); }); ASSERT_TRUE(done.Wait(/*milliseconds=*/100)); } TEST(RtcpTransceiverImplTest, PeriodicallySendsPackets) { rtc::TaskQueue queue("rtcp"); FakeRtcpTransport transport; RtcpTransceiverConfig config; config.outgoing_transport = &transport; config.initial_report_delay_ms = 0; config.report_period_ms = 10; config.task_queue = &queue; rtc::Optional rtcp_transceiver; queue.PostTask([&] { rtcp_transceiver.emplace(config); }); EXPECT_TRUE(transport.WaitPacket(config.initial_report_delay_ms + kTaskQueuePrecisionMs)); int64_t time_of_1st_packet_ms = rtc::TimeMillis(); EXPECT_TRUE( transport.WaitPacket(config.report_period_ms + kTaskQueuePrecisionMs)); int64_t time_of_2nd_packet_ms = rtc::TimeMillis(); EXPECT_GE(time_of_2nd_packet_ms - time_of_1st_packet_ms, config.report_period_ms); // Cleanup. rtc::Event done(false, false); queue.PostTask([&] { rtcp_transceiver.reset(); done.Set(); }); ASSERT_TRUE(done.Wait(/*milliseconds=*/100)); } TEST(RtcpTransceiverImplTest, SendCompoundPacketDelaysPeriodicSendPackets) { rtc::TaskQueue queue("rtcp"); FakeRtcpTransport transport; RtcpTransceiverConfig config; config.outgoing_transport = &transport; config.initial_report_delay_ms = 0; config.report_period_ms = 10; config.task_queue = &queue; rtc::Optional rtcp_transceiver; queue.PostTask([&] { rtcp_transceiver.emplace(config); }); // Wait for first packet. EXPECT_TRUE(transport.WaitPacket(config.initial_report_delay_ms + kTaskQueuePrecisionMs)); // Wait half-period time for next one - it shouldn't be sent. EXPECT_FALSE(transport.WaitPacket(config.report_period_ms / 2)); // Send packet now. queue.PostTask([&] { rtcp_transceiver->SendCompoundPacket(); }); EXPECT_TRUE(transport.WaitPacket(/*timeout_ms=*/1)); int64_t time_of_non_periodic_packet_ms = rtc::TimeMillis(); // Next packet should be sent at least after period_ms. EXPECT_TRUE( transport.WaitPacket(config.report_period_ms + kTaskQueuePrecisionMs)); EXPECT_GE(rtc::TimeMillis() - time_of_non_periodic_packet_ms, config.report_period_ms); // Cleanup. rtc::Event done(false, false); queue.PostTask([&] { rtcp_transceiver.reset(); done.Set(); }); ASSERT_TRUE(done.Wait(/*milliseconds=*/100)); } TEST(RtcpTransceiverImplTest, SendsMinimalCompoundPacket) { const uint32_t kSenderSsrc = 12345; MockTransport outgoing_transport; RtcpTransceiverConfig config; config.feedback_ssrc = kSenderSsrc; config.cname = "cname"; config.outgoing_transport = &outgoing_transport; config.schedule_periodic_compound_packets = false; RtcpTransceiverImpl rtcp_transceiver(config); RtcpPacketParser rtcp_parser; EXPECT_CALL(outgoing_transport, SendRtcp(_, _)) .WillOnce(Invoke(&rtcp_parser, &RtcpPacketParser::Parse)); 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(RtcpTransceiverImplTest, ReceiverReportUsesReceiveStatistics) { const uint32_t kSenderSsrc = 12345; const uint32_t kMediaSsrc = 54321; MockTransport outgoing_transport; RtcpPacketParser rtcp_parser; EXPECT_CALL(outgoing_transport, SendRtcp(_, _)) .WillOnce(Invoke(&rtcp_parser, &RtcpPacketParser::Parse)); MockReceiveStatisticsProvider receive_statistics; std::vector report_blocks(1); report_blocks[0].SetMediaSsrc(kMediaSsrc); EXPECT_CALL(receive_statistics, RtcpReportBlocks(_)) .WillRepeatedly(Return(report_blocks)); RtcpTransceiverConfig config; config.feedback_ssrc = kSenderSsrc; config.outgoing_transport = &outgoing_transport; 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); } } // namespace