| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/cast/net/cast_transport_impl.h" |
| |
| #include <gtest/gtest.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/values.h" |
| #include "media/base/fake_single_thread_task_runner.h" |
| #include "media/cast/net/cast_transport_config.h" |
| #include "media/cast/net/rtcp/rtcp_defines.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace media { |
| namespace cast { |
| |
| namespace { |
| |
| const int64_t kStartMillisecond = INT64_C(12345678900000); |
| const uint32_t kVideoSsrc = 1; |
| const uint32_t kAudioSsrc = 2; |
| |
| class StubRtcpObserver : public RtcpObserver { |
| public: |
| StubRtcpObserver() = default; |
| |
| void OnReceivedCastMessage(const RtcpCastMessage& cast_message) final {} |
| void OnReceivedRtt(base::TimeDelta round_trip_time) final {} |
| void OnReceivedPli() final {} |
| |
| DISALLOW_COPY_AND_ASSIGN(StubRtcpObserver); |
| }; |
| |
| } // namespace |
| |
| class FakePacketSender : public PacketTransport { |
| public: |
| FakePacketSender() : paused_(false), packets_sent_(0), bytes_sent_(0) {} |
| |
| bool SendPacket(PacketRef packet, base::OnceClosure cb) final { |
| if (paused_) { |
| stored_packet_ = packet; |
| callback_ = std::move(cb); |
| return false; |
| } |
| ++packets_sent_; |
| bytes_sent_ += packet->data.size(); |
| return true; |
| } |
| |
| int64_t GetBytesSent() final { return bytes_sent_; } |
| |
| void StartReceiving(PacketReceiverCallbackWithStatus packet_receiver) final {} |
| |
| void StopReceiving() final {} |
| |
| void SetPaused(bool paused) { |
| paused_ = paused; |
| if (!paused && stored_packet_.get()) { |
| SendPacket(stored_packet_, base::OnceClosure()); |
| std::move(callback_).Run(); |
| } |
| } |
| |
| int packets_sent() const { return packets_sent_; } |
| |
| private: |
| bool paused_; |
| base::OnceClosure callback_; |
| PacketRef stored_packet_; |
| int packets_sent_; |
| int64_t bytes_sent_; |
| |
| DISALLOW_COPY_AND_ASSIGN(FakePacketSender); |
| }; |
| |
| class CastTransportImplTest : public ::testing::Test { |
| public: |
| void ReceivedLoggingEvents() { num_times_logging_callback_called_++; } |
| |
| protected: |
| CastTransportImplTest() : num_times_logging_callback_called_(0) { |
| testing_clock_.Advance(base::Milliseconds(kStartMillisecond)); |
| task_runner_ = new FakeSingleThreadTaskRunner(&testing_clock_); |
| } |
| |
| ~CastTransportImplTest() override = default; |
| |
| void InitWithoutLogging(); |
| void InitWithOptions(); |
| void InitWithLogging(); |
| |
| void InitializeVideo() { |
| CastTransportRtpConfig rtp_config; |
| rtp_config.ssrc = kVideoSsrc; |
| rtp_config.feedback_ssrc = 2; |
| rtp_config.rtp_payload_type = RtpPayloadType::VIDEO_VP8; |
| transport_sender_->InitializeStream(rtp_config, |
| std::make_unique<StubRtcpObserver>()); |
| } |
| |
| void InitializeAudio() { |
| CastTransportRtpConfig rtp_config; |
| rtp_config.ssrc = kAudioSsrc; |
| rtp_config.feedback_ssrc = 3; |
| rtp_config.rtp_payload_type = RtpPayloadType::AUDIO_OPUS; |
| transport_sender_->InitializeStream(rtp_config, |
| std::make_unique<StubRtcpObserver>()); |
| } |
| |
| base::SimpleTestTickClock testing_clock_; |
| scoped_refptr<FakeSingleThreadTaskRunner> task_runner_; |
| std::unique_ptr<CastTransportImpl> transport_sender_; |
| FakePacketSender* transport_; // Owned by CastTransport. |
| int num_times_logging_callback_called_; |
| }; |
| |
| namespace { |
| |
| class TransportClient : public CastTransport::Client { |
| public: |
| explicit TransportClient( |
| CastTransportImplTest* cast_transport_sender_impl_test) |
| : cast_transport_sender_impl_test_(cast_transport_sender_impl_test) {} |
| |
| void OnStatusChanged(CastTransportStatus status) final {} |
| void OnLoggingEventsReceived( |
| std::unique_ptr<std::vector<FrameEvent>> frame_events, |
| std::unique_ptr<std::vector<PacketEvent>> packet_events) final { |
| CHECK(cast_transport_sender_impl_test_); |
| cast_transport_sender_impl_test_->ReceivedLoggingEvents(); |
| } |
| void ProcessRtpPacket(std::unique_ptr<Packet> packet) final {} |
| |
| private: |
| CastTransportImplTest* const cast_transport_sender_impl_test_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TransportClient); |
| }; |
| |
| } // namespace |
| |
| void CastTransportImplTest::InitWithoutLogging() { |
| transport_ = new FakePacketSender(); |
| transport_sender_ = std::make_unique<CastTransportImpl>( |
| &testing_clock_, base::TimeDelta(), |
| std::make_unique<TransportClient>(nullptr), base::WrapUnique(transport_), |
| task_runner_); |
| task_runner_->RunTasks(); |
| } |
| |
| void CastTransportImplTest::InitWithOptions() { |
| std::unique_ptr<base::DictionaryValue> options(new base::DictionaryValue); |
| options->SetBoolean("disable_wifi_scan", true); |
| options->SetBoolean("media_streaming_mode", true); |
| options->SetInteger("pacer_target_burst_size", 20); |
| options->SetInteger("pacer_max_burst_size", 100); |
| transport_ = new FakePacketSender(); |
| transport_sender_ = std::make_unique<CastTransportImpl>( |
| &testing_clock_, base::TimeDelta(), |
| std::make_unique<TransportClient>(nullptr), base::WrapUnique(transport_), |
| task_runner_); |
| transport_sender_->SetOptions(*options); |
| task_runner_->RunTasks(); |
| } |
| |
| void CastTransportImplTest::InitWithLogging() { |
| transport_ = new FakePacketSender(); |
| transport_sender_ = std::make_unique<CastTransportImpl>( |
| &testing_clock_, base::Milliseconds(10), |
| std::make_unique<TransportClient>(this), base::WrapUnique(transport_), |
| task_runner_); |
| task_runner_->RunTasks(); |
| } |
| |
| TEST_F(CastTransportImplTest, InitWithoutLogging) { |
| InitWithoutLogging(); |
| task_runner_->Sleep(base::Milliseconds(50)); |
| EXPECT_EQ(0, num_times_logging_callback_called_); |
| } |
| |
| TEST_F(CastTransportImplTest, InitWithOptions) { |
| InitWithOptions(); |
| task_runner_->Sleep(base::Milliseconds(50)); |
| EXPECT_EQ(0, num_times_logging_callback_called_); |
| } |
| |
| TEST_F(CastTransportImplTest, NacksCancelRetransmits) { |
| InitWithLogging(); |
| InitializeVideo(); |
| task_runner_->Sleep(base::Milliseconds(50)); |
| EXPECT_EQ(0, num_times_logging_callback_called_); |
| |
| // A fake frame that will be decomposed into 4 packets. |
| EncodedFrame fake_frame; |
| fake_frame.frame_id = FrameId::first() + 1; |
| fake_frame.referenced_frame_id = FrameId::first() + 1; |
| fake_frame.rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(1)); |
| fake_frame.dependency = EncodedFrame::KEY; |
| fake_frame.data.resize(5000, ' '); |
| |
| transport_sender_->InsertFrame(kVideoSsrc, fake_frame); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(4, transport_->packets_sent()); |
| EXPECT_EQ(1, num_times_logging_callback_called_); |
| |
| // Resend packet 0. |
| MissingFramesAndPacketsMap missing_packets; |
| missing_packets[fake_frame.frame_id].insert(0); |
| missing_packets[fake_frame.frame_id].insert(1); |
| missing_packets[fake_frame.frame_id].insert(2); |
| |
| transport_->SetPaused(true); |
| DedupInfo dedup_info; |
| dedup_info.resend_interval = base::Milliseconds(10); |
| transport_sender_->ResendPackets(kVideoSsrc, missing_packets, true, |
| dedup_info); |
| |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(2, num_times_logging_callback_called_); |
| |
| RtcpCastMessage cast_message; |
| cast_message.remote_ssrc = kVideoSsrc; |
| cast_message.ack_frame_id = FrameId::first() + 1; |
| cast_message.missing_frames_and_packets[fake_frame.frame_id].insert(3); |
| transport_sender_->OnReceivedCastMessage(kVideoSsrc, cast_message); |
| transport_->SetPaused(false); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(3, num_times_logging_callback_called_); |
| |
| // Resend one packet in the socket when unpaused. |
| // Resend one more packet from NACK. |
| EXPECT_EQ(6, transport_->packets_sent()); |
| } |
| |
| TEST_F(CastTransportImplTest, CancelRetransmits) { |
| InitWithLogging(); |
| InitializeVideo(); |
| task_runner_->Sleep(base::Milliseconds(50)); |
| EXPECT_EQ(0, num_times_logging_callback_called_); |
| |
| // A fake frame that will be decomposed into 4 packets. |
| EncodedFrame fake_frame; |
| fake_frame.frame_id = FrameId::first() + 1; |
| fake_frame.referenced_frame_id = FrameId::first() + 1; |
| fake_frame.rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(1)); |
| fake_frame.dependency = EncodedFrame::KEY; |
| fake_frame.data.resize(5000, ' '); |
| |
| transport_sender_->InsertFrame(kVideoSsrc, fake_frame); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(4, transport_->packets_sent()); |
| EXPECT_EQ(1, num_times_logging_callback_called_); |
| |
| // Resend all packets for frame 1. |
| MissingFramesAndPacketsMap missing_packets; |
| missing_packets[fake_frame.frame_id].insert(kRtcpCastAllPacketsLost); |
| |
| transport_->SetPaused(true); |
| DedupInfo dedup_info; |
| dedup_info.resend_interval = base::Milliseconds(10); |
| transport_sender_->ResendPackets(kVideoSsrc, missing_packets, true, |
| dedup_info); |
| |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(2, num_times_logging_callback_called_); |
| |
| std::vector<FrameId> cancel_sending_frames; |
| cancel_sending_frames.push_back(fake_frame.frame_id); |
| transport_sender_->CancelSendingFrames(kVideoSsrc, cancel_sending_frames); |
| transport_->SetPaused(false); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(2, num_times_logging_callback_called_); |
| |
| // Resend one packet in the socket when unpaused. |
| EXPECT_EQ(5, transport_->packets_sent()); |
| } |
| |
| TEST_F(CastTransportImplTest, Kickstart) { |
| InitWithLogging(); |
| InitializeVideo(); |
| task_runner_->Sleep(base::Milliseconds(50)); |
| EXPECT_EQ(0, num_times_logging_callback_called_); |
| |
| // A fake frame that will be decomposed into 4 packets. |
| EncodedFrame fake_frame; |
| fake_frame.frame_id = FrameId::first() + 1; |
| fake_frame.referenced_frame_id = FrameId::first() + 1; |
| fake_frame.rtp_timestamp = RtpTimeTicks().Expand(UINT32_C(1)); |
| fake_frame.dependency = EncodedFrame::KEY; |
| fake_frame.data.resize(5000, ' '); |
| |
| transport_->SetPaused(true); |
| transport_sender_->InsertFrame(kVideoSsrc, fake_frame); |
| transport_sender_->ResendFrameForKickstart(kVideoSsrc, fake_frame.frame_id); |
| transport_->SetPaused(false); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(4, transport_->packets_sent()); |
| EXPECT_EQ(1, num_times_logging_callback_called_); |
| |
| // Resend 2 packets for frame 1. |
| MissingFramesAndPacketsMap missing_packets; |
| missing_packets[fake_frame.frame_id].insert(0); |
| missing_packets[fake_frame.frame_id].insert(1); |
| |
| transport_->SetPaused(true); |
| DedupInfo dedup_info; |
| dedup_info.resend_interval = base::Milliseconds(10); |
| transport_sender_->ResendPackets(kVideoSsrc, missing_packets, true, |
| dedup_info); |
| transport_sender_->ResendFrameForKickstart(kVideoSsrc, fake_frame.frame_id); |
| transport_->SetPaused(false); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| EXPECT_EQ(2, num_times_logging_callback_called_); |
| |
| // Resend one packet in the socket when unpaused. |
| // Two more retransmission packets sent. |
| EXPECT_EQ(7, transport_->packets_sent()); |
| } |
| |
| TEST_F(CastTransportImplTest, DedupRetransmissionWithAudio) { |
| InitWithLogging(); |
| InitializeAudio(); |
| InitializeVideo(); |
| task_runner_->Sleep(base::Milliseconds(50)); |
| EXPECT_EQ(0, num_times_logging_callback_called_); |
| |
| // Send two audio frames. |
| EncodedFrame fake_audio; |
| fake_audio.frame_id = FrameId::first() + 1; |
| fake_audio.referenced_frame_id = FrameId::first() + 1; |
| fake_audio.reference_time = testing_clock_.NowTicks(); |
| fake_audio.dependency = EncodedFrame::KEY; |
| fake_audio.data.resize(100, ' '); |
| transport_sender_->InsertFrame(kAudioSsrc, fake_audio); |
| task_runner_->Sleep(base::Milliseconds(2)); |
| fake_audio.frame_id = FrameId::first() + 2; |
| fake_audio.reference_time = testing_clock_.NowTicks(); |
| transport_sender_->InsertFrame(kAudioSsrc, fake_audio); |
| task_runner_->Sleep(base::Milliseconds(2)); |
| EXPECT_EQ(2, transport_->packets_sent()); |
| |
| // Ack the first audio frame. |
| RtcpCastMessage cast_message; |
| cast_message.remote_ssrc = kAudioSsrc; |
| cast_message.ack_frame_id = FrameId::first() + 1; |
| transport_sender_->OnReceivedCastMessage(kAudioSsrc, cast_message); |
| task_runner_->RunTasks(); |
| EXPECT_EQ(2, transport_->packets_sent()); |
| EXPECT_EQ(0, num_times_logging_callback_called_); // Only 4 ms since last. |
| |
| // Send a fake video frame that will be decomposed into 4 packets. |
| EncodedFrame fake_video; |
| fake_video.frame_id = FrameId::first() + 1; |
| fake_video.referenced_frame_id = FrameId::first() + 1; |
| fake_video.dependency = EncodedFrame::KEY; |
| fake_video.data.resize(5000, ' '); |
| transport_sender_->InsertFrame(kVideoSsrc, fake_video); |
| task_runner_->RunTasks(); |
| EXPECT_EQ(6, transport_->packets_sent()); |
| EXPECT_EQ(0, num_times_logging_callback_called_); // Only 4 ms since last. |
| |
| // Retransmission is reject because audio is not acked yet. |
| cast_message.remote_ssrc = kVideoSsrc; |
| cast_message.ack_frame_id = FrameId::first(); |
| cast_message.missing_frames_and_packets[fake_video.frame_id].insert(3); |
| task_runner_->Sleep(base::Milliseconds(10)); |
| transport_sender_->OnReceivedCastMessage(kVideoSsrc, cast_message); |
| task_runner_->RunTasks(); |
| EXPECT_EQ(6, transport_->packets_sent()); |
| EXPECT_EQ(1, num_times_logging_callback_called_); |
| |
| // Ack the second audio frame. |
| cast_message.remote_ssrc = kAudioSsrc; |
| cast_message.ack_frame_id = FrameId::first() + 2; |
| cast_message.missing_frames_and_packets.clear(); |
| task_runner_->Sleep(base::Milliseconds(2)); |
| transport_sender_->OnReceivedCastMessage(kAudioSsrc, cast_message); |
| task_runner_->RunTasks(); |
| EXPECT_EQ(6, transport_->packets_sent()); |
| EXPECT_EQ(1, num_times_logging_callback_called_); // Only 6 ms since last. |
| |
| // Retransmission of video packet now accepted. |
| cast_message.remote_ssrc = kVideoSsrc; |
| cast_message.ack_frame_id = FrameId::first() + 1; |
| cast_message.missing_frames_and_packets[fake_video.frame_id].insert(3); |
| task_runner_->Sleep(base::Milliseconds(2)); |
| transport_sender_->OnReceivedCastMessage(kVideoSsrc, cast_message); |
| task_runner_->RunTasks(); |
| EXPECT_EQ(7, transport_->packets_sent()); |
| EXPECT_EQ(1, num_times_logging_callback_called_); // Only 8 ms since last. |
| |
| task_runner_->Sleep(base::Milliseconds(2)); |
| EXPECT_EQ(2, num_times_logging_callback_called_); |
| } |
| |
| } // namespace cast |
| } // namespace media |