| // Copyright 2020 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 "cast/streaming/sender.h" |
| |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <array> |
| #include <chrono> |
| #include <limits> |
| #include <map> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/types/optional.h" |
| #include "absl/types/span.h" |
| #include "cast/streaming/compound_rtcp_builder.h" |
| #include "cast/streaming/constants.h" |
| #include "cast/streaming/encoded_frame.h" |
| #include "cast/streaming/frame_collector.h" |
| #include "cast/streaming/frame_crypto.h" |
| #include "cast/streaming/frame_id.h" |
| #include "cast/streaming/mock_environment.h" |
| #include "cast/streaming/packet_util.h" |
| #include "cast/streaming/rtcp_session.h" |
| #include "cast/streaming/rtp_defines.h" |
| #include "cast/streaming/rtp_packet_parser.h" |
| #include "cast/streaming/sender_packet_router.h" |
| #include "cast/streaming/sender_report_parser.h" |
| #include "cast/streaming/session_config.h" |
| #include "cast/streaming/ssrc.h" |
| #include "gmock/gmock.h" |
| #include "gtest/gtest.h" |
| #include "platform/test/fake_clock.h" |
| #include "platform/test/fake_task_runner.h" |
| #include "util/alarm.h" |
| #include "util/chrono_helpers.h" |
| #include "util/yet_another_bit_vector.h" |
| |
| using testing::_; |
| using testing::AtLeast; |
| using testing::Invoke; |
| using testing::InvokeWithoutArgs; |
| using testing::Mock; |
| using testing::NiceMock; |
| using testing::Return; |
| using testing::Sequence; |
| |
| namespace openscreen { |
| namespace cast { |
| namespace { |
| |
| // Sender configuration. |
| constexpr Ssrc kSenderSsrc = 1; |
| constexpr Ssrc kReceiverSsrc = 2; |
| constexpr int kRtpTimebase = 48000; |
| constexpr milliseconds kTargetPlayoutDelay{400}; |
| constexpr auto kAesKey = |
| std::array<uint8_t, 16>{{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
| 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}}; |
| constexpr auto kCastIvMask = |
| std::array<uint8_t, 16>{{0xf0, 0xe0, 0xd0, 0xc0, 0xb0, 0xa0, 0x90, 0x80, |
| 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0x00}}; |
| constexpr RtpPayloadType kRtpPayloadType = RtpPayloadType::kVideoVp8; |
| |
| // The number of RTP ticks advanced per frame, for 100 FPS media. |
| constexpr int kRtpTicksPerFrame = kRtpTimebase / 100; |
| |
| // The number of milliseconds advanced per frame, for 100 FPS media. |
| constexpr milliseconds kFrameDuration{1000 / 100}; |
| static_assert(kFrameDuration < (kTargetPlayoutDelay / 10), |
| "Kickstart test assumes frame duration is far less than the " |
| "playout delay."); |
| |
| // An Encoded frame that also holds onto its own copy of data. |
| struct EncodedFrameWithBuffer : public EncodedFrame { |
| // |EncodedFrame::data| always points inside buffer.begin()...buffer.end(). |
| std::vector<uint8_t> buffer; |
| }; |
| |
| // SenderPacketRouter configuration for these tests. |
| constexpr int kNumPacketsPerBurst = 20; |
| constexpr milliseconds kBurstInterval{10}; |
| |
| // An arbitrary value, subtracted from "now," to specify the reference_time on |
| // frames that are about to be enqueued. This simulates that capture+encode |
| // happened in the past, before Sender::EnqueueFrame() is called. |
| constexpr milliseconds kCaptureDelay{11}; |
| |
| // In some tests, the computed time values could be off a little bit due to |
| // imprecision in certain wire-format timestamp types. The following macro |
| // behaves just like Gtest's EXPECT_NEAR(), but works with all the time types |
| // too. |
| #define EXPECT_NEARLY_EQUAL(duration_a, duration_b, epsilon) \ |
| if ((duration_a) >= (duration_b)) { \ |
| EXPECT_LE((duration_a), (duration_b) + (epsilon)); \ |
| } else { \ |
| EXPECT_GE((duration_a), (duration_b) - (epsilon)); \ |
| } |
| |
| // Simulates UDP/IPv6 traffic in one direction (from Sender→Receiver, or |
| // Receiver→Sender), with a settable amount of delay. |
| class SimulatedNetworkPipe { |
| public: |
| SimulatedNetworkPipe(TaskRunner* task_runner, |
| Environment::PacketConsumer* remote) |
| : task_runner_(task_runner), remote_(remote) { |
| // Create a fake IPv6 address using the "documentative purposes" prefix |
| // concatenated with the |this| pointer. |
| std::array<uint16_t, 8> hextets{}; |
| hextets[0] = 0x2001; |
| hextets[1] = 0x0db8; |
| auto* const this_pointer = this; |
| static_assert(sizeof(this_pointer) <= (6 * sizeof(uint16_t)), ""); |
| memcpy(&hextets[2], &this_pointer, sizeof(this_pointer)); |
| local_endpoint_ = IPEndpoint{IPAddress(hextets), 2344}; |
| } |
| |
| const IPEndpoint& local_endpoint() const { return local_endpoint_; } |
| |
| Clock::duration network_delay() const { return network_delay_; } |
| void set_network_delay(Clock::duration delay) { network_delay_ = delay; } |
| |
| // The caller needs to spin the task runner before |packet| will reach the |
| // other side. |
| void StartPacketTransmission(std::vector<uint8_t> packet) { |
| task_runner_->PostTaskWithDelay( |
| [this, packet = std::move(packet)]() mutable { |
| remote_->OnReceivedPacket(local_endpoint_, FakeClock::now(), |
| std::move(packet)); |
| }, |
| network_delay_); |
| } |
| |
| private: |
| TaskRunner* const task_runner_; |
| Environment::PacketConsumer* const remote_; |
| |
| IPEndpoint local_endpoint_; |
| |
| // The amount of time for the packet to transmit over this simulated network |
| // pipe. Defaults to zero to simplify the tests that don't care about delays. |
| Clock::duration network_delay_{}; |
| }; |
| |
| // Processes packets from the Sender under test, allowing unit tests to set |
| // expectations for parsed RTP or RTCP packets, to confirm proper behavior of |
| // the Sender. |
| class MockReceiver : public Environment::PacketConsumer { |
| public: |
| explicit MockReceiver(SimulatedNetworkPipe* pipe_to_sender) |
| : pipe_to_sender_(pipe_to_sender), |
| rtcp_session_(kSenderSsrc, kReceiverSsrc, FakeClock::now()), |
| sender_report_parser_(&rtcp_session_), |
| rtcp_builder_(&rtcp_session_), |
| rtp_parser_(kSenderSsrc), |
| crypto_(kAesKey, kCastIvMask) { |
| rtcp_builder_.SetPlayoutDelay(kTargetPlayoutDelay); |
| } |
| |
| ~MockReceiver() override = default; |
| |
| // Simulate the Receiver ACK'ing all frames up to and including the |
| // |new_checkpoint|. |
| void SetCheckpointFrame(FrameId new_checkpoint) { |
| OSP_CHECK_GE(new_checkpoint, rtcp_builder_.checkpoint_frame()); |
| rtcp_builder_.SetCheckpointFrame(new_checkpoint); |
| } |
| |
| // Automatically advances the checkpoint based on what is found in |
| // |complete_frames_|, returning true if the checkpoint moved forward. |
| bool AutoAdvanceCheckpoint() { |
| const FrameId old_checkpoint = rtcp_builder_.checkpoint_frame(); |
| FrameId new_checkpoint = old_checkpoint; |
| for (auto it = complete_frames_.upper_bound(old_checkpoint); |
| it != complete_frames_.end(); ++it) { |
| if (it->first != new_checkpoint + 1) { |
| break; |
| } |
| ++new_checkpoint; |
| } |
| if (new_checkpoint > old_checkpoint) { |
| rtcp_builder_.SetCheckpointFrame(new_checkpoint); |
| return true; |
| } |
| return false; |
| } |
| |
| void SetPictureLossIndicator(bool picture_is_lost) { |
| rtcp_builder_.SetPictureLossIndicator(picture_is_lost); |
| } |
| |
| void SetReceiverReport(StatusReportId reply_for, |
| RtcpReportBlock::Delay processing_delay) { |
| RtcpReportBlock receiver_report; |
| receiver_report.ssrc = kSenderSsrc; |
| receiver_report.last_status_report_id = reply_for; |
| receiver_report.delay_since_last_report = processing_delay; |
| rtcp_builder_.IncludeReceiverReportInNextPacket(receiver_report); |
| } |
| |
| void SetNacksAndAcks(std::vector<PacketNack> packet_nacks, |
| std::vector<FrameId> frame_acks) { |
| rtcp_builder_.IncludeFeedbackInNextPacket(std::move(packet_nacks), |
| std::move(frame_acks)); |
| } |
| |
| // Builds and sends a RTCP packet containing one or more of: checkpoint, PLI, |
| // Receiver Report, NACKs, ACKs. |
| void TransmitRtcpFeedbackPacket() { |
| uint8_t buffer[kMaxRtpPacketSizeForIpv6UdpOnEthernet]; |
| const absl::Span<uint8_t> packet = |
| rtcp_builder_.BuildPacket(FakeClock::now(), buffer); |
| pipe_to_sender_->StartPacketTransmission( |
| std::vector<uint8_t>(packet.begin(), packet.end())); |
| } |
| |
| // Used by tests to simulate the Receiver not seeing specific packets come in |
| // from the network (e.g., because the network dropped the packets). |
| void SetIgnoreList(std::vector<PacketNack> ignore_list) { |
| ignore_list_ = ignore_list; |
| } |
| |
| // Environment::PacketConsumer implementation. |
| // |
| // Called to process a packet from the Sender, simulating basic RTP frame |
| // collection and Sender Report parsing/handling. |
| void OnReceivedPacket(const IPEndpoint& source, |
| Clock::time_point arrival_time, |
| std::vector<uint8_t> packet) override { |
| const auto type_and_ssrc = InspectPacketForRouting(packet); |
| EXPECT_NE(ApparentPacketType::UNKNOWN, type_and_ssrc.first); |
| EXPECT_EQ(kSenderSsrc, type_and_ssrc.second); |
| if (type_and_ssrc.first == ApparentPacketType::RTP) { |
| const absl::optional<RtpPacketParser::ParseResult> part_of_frame = |
| rtp_parser_.Parse(packet); |
| ASSERT_TRUE(part_of_frame); |
| |
| // Return early if simulating packet drops over the network. |
| if (std::find_if(ignore_list_.begin(), ignore_list_.end(), |
| [&](const PacketNack& baddie) { |
| return ( |
| baddie.frame_id == part_of_frame->frame_id && |
| (baddie.packet_id == kAllPacketsLost || |
| baddie.packet_id == part_of_frame->packet_id)); |
| }) != ignore_list_.end()) { |
| return; |
| } |
| |
| OnRtpPacket(*part_of_frame); |
| CollectRtpPacket(*part_of_frame, std::move(packet)); |
| } else if (type_and_ssrc.first == ApparentPacketType::RTCP) { |
| absl::optional<SenderReportParser::SenderReportWithId> report = |
| sender_report_parser_.Parse(packet); |
| ASSERT_TRUE(report); |
| OnSenderReport(*report); |
| } |
| } |
| |
| std::map<FrameId, EncodedFrameWithBuffer> TakeCompleteFrames() { |
| std::map<FrameId, EncodedFrameWithBuffer> result; |
| result.swap(complete_frames_); |
| return result; |
| } |
| |
| // Tests set expectations on these mocks to monitor events of interest, and/or |
| // invoke additional behaviors. |
| MOCK_METHOD1(OnRtpPacket, |
| void(const RtpPacketParser::ParseResult& parsed_packet)); |
| MOCK_METHOD1(OnFrameComplete, void(FrameId frame_id)); |
| MOCK_METHOD1(OnSenderReport, |
| void(const SenderReportParser::SenderReportWithId& report)); |
| |
| private: |
| // Collects the individual RTP packets until a whole frame can be formed, then |
| // calls OnFrameComplete(). Ignores extra RTP packets that are no longer |
| // needed. |
| void CollectRtpPacket(const RtpPacketParser::ParseResult& part_of_frame, |
| std::vector<uint8_t> packet) { |
| const FrameId frame_id = part_of_frame.frame_id; |
| if (complete_frames_.find(frame_id) != complete_frames_.end()) { |
| return; |
| } |
| FrameCollector& collector = incomplete_frames_[frame_id]; |
| collector.set_frame_id(frame_id); |
| EXPECT_TRUE(collector.CollectRtpPacket(part_of_frame, &packet)); |
| if (!collector.is_complete()) { |
| return; |
| } |
| const EncryptedFrame& encrypted = collector.PeekAtAssembledFrame(); |
| EncodedFrameWithBuffer* const decrypted = &complete_frames_[frame_id]; |
| // Note: Not setting decrypted->reference_time here since the logic around |
| // calculating the playout time is rather complex, and is definitely outside |
| // the scope of the testing being done in this module. Instead, end-to-end |
| // testing should exist elsewhere to confirm frame play-out times with real |
| // Receivers. |
| decrypted->buffer.resize(FrameCrypto::GetPlaintextSize(encrypted)); |
| decrypted->data = absl::Span<uint8_t>(decrypted->buffer); |
| crypto_.Decrypt(encrypted, decrypted); |
| incomplete_frames_.erase(frame_id); |
| OnFrameComplete(frame_id); |
| } |
| |
| SimulatedNetworkPipe* const pipe_to_sender_; |
| RtcpSession rtcp_session_; |
| SenderReportParser sender_report_parser_; |
| CompoundRtcpBuilder rtcp_builder_; |
| RtpPacketParser rtp_parser_; |
| FrameCrypto crypto_; |
| |
| std::vector<PacketNack> ignore_list_; |
| std::map<FrameId, FrameCollector> incomplete_frames_; |
| std::map<FrameId, EncodedFrameWithBuffer> complete_frames_; |
| }; |
| |
| class MockObserver : public Sender::Observer { |
| public: |
| MOCK_METHOD1(OnFrameCanceled, void(FrameId frame_id)); |
| MOCK_METHOD0(OnPictureLost, void()); |
| }; |
| |
| class SenderTest : public testing::Test { |
| public: |
| SenderTest() |
| : fake_clock_(Clock::now()), |
| task_runner_(&fake_clock_), |
| sender_environment_(&FakeClock::now, &task_runner_), |
| sender_packet_router_(&sender_environment_, |
| kNumPacketsPerBurst, |
| kBurstInterval), |
| sender_(&sender_environment_, |
| &sender_packet_router_, |
| {/* .sender_ssrc = */ kSenderSsrc, |
| /* .receiver_ssrc = */ kReceiverSsrc, |
| /* .rtp_timebase = */ kRtpTimebase, |
| /* .channels = */ 2, |
| /* .target_playout_delay = */ kTargetPlayoutDelay, |
| /* .aes_secret_key = */ kAesKey, |
| /* .aes_iv_mask = */ kCastIvMask}, |
| kRtpPayloadType), |
| receiver_to_sender_pipe_(&task_runner_, &sender_packet_router_), |
| receiver_(&receiver_to_sender_pipe_), |
| sender_to_receiver_pipe_(&task_runner_, &receiver_) { |
| sender_environment_.set_socket_error_handler( |
| [](Error error) { ASSERT_TRUE(error.ok()) << error; }); |
| sender_environment_.set_remote_endpoint( |
| receiver_to_sender_pipe_.local_endpoint()); |
| ON_CALL(sender_environment_, SendPacket(_)) |
| .WillByDefault(Invoke([this](absl::Span<const uint8_t> packet) { |
| sender_to_receiver_pipe_.StartPacketTransmission( |
| std::vector<uint8_t>(packet.begin(), packet.end())); |
| })); |
| } |
| |
| ~SenderTest() override = default; |
| |
| Sender* sender() { return &sender_; } |
| MockReceiver* receiver() { return &receiver_; } |
| |
| void SetReceiverToSenderNetworkDelay(Clock::duration delay) { |
| receiver_to_sender_pipe_.set_network_delay(delay); |
| } |
| |
| void SetSenderToReceiverNetworkDelay(Clock::duration delay) { |
| sender_to_receiver_pipe_.set_network_delay(delay); |
| } |
| |
| void SimulateExecution(Clock::duration how_long = Clock::duration::zero()) { |
| fake_clock_.Advance(how_long); |
| } |
| |
| static void PopulateFramePayloadBuffer(int seed, |
| int num_bytes, |
| std::vector<uint8_t>* payload) { |
| payload->clear(); |
| payload->reserve(num_bytes); |
| for (int i = 0; i < num_bytes; ++i) { |
| payload->push_back(static_cast<uint8_t>(seed + i)); |
| } |
| } |
| |
| static void PopulateFrameWithDefaults(FrameId frame_id, |
| Clock::time_point reference_time, |
| int seed, |
| int num_payload_bytes, |
| EncodedFrameWithBuffer* frame) { |
| frame->dependency = (frame_id == FrameId::first()) |
| ? EncodedFrame::KEY_FRAME |
| : EncodedFrame::DEPENDS_ON_ANOTHER; |
| frame->frame_id = frame_id; |
| frame->referenced_frame_id = frame->frame_id; |
| if (frame_id != FrameId::first()) { |
| --frame->referenced_frame_id; |
| } |
| frame->rtp_timestamp = |
| RtpTimeTicks() + (RtpTimeDelta::FromTicks(kRtpTicksPerFrame) * |
| (frame_id - FrameId::first())); |
| frame->reference_time = reference_time; |
| PopulateFramePayloadBuffer(seed, num_payload_bytes, &frame->buffer); |
| frame->data = absl::Span<uint8_t>(frame->buffer); |
| } |
| |
| // Confirms that all |sent_frames| exist in |received_frames|, with identical |
| // data and metadata. |
| static void ExpectFramesReceivedCorrectly( |
| absl::Span<EncodedFrameWithBuffer> sent_frames, |
| const std::map<FrameId, EncodedFrameWithBuffer> received_frames) { |
| ASSERT_EQ(sent_frames.size(), received_frames.size()); |
| |
| for (const EncodedFrameWithBuffer& sent_frame : sent_frames) { |
| SCOPED_TRACE(testing::Message() |
| << "Checking sent frame " << sent_frame.frame_id); |
| const auto received_it = received_frames.find(sent_frame.frame_id); |
| if (received_it == received_frames.end()) { |
| ADD_FAILURE() << "Did not receive frame."; |
| continue; |
| } |
| const EncodedFrame& received_frame = received_it->second; |
| EXPECT_EQ(sent_frame.dependency, received_frame.dependency); |
| EXPECT_EQ(sent_frame.referenced_frame_id, |
| received_frame.referenced_frame_id); |
| EXPECT_EQ(sent_frame.rtp_timestamp, received_frame.rtp_timestamp); |
| EXPECT_TRUE(sent_frame.data == received_frame.data); |
| } |
| } |
| |
| private: |
| FakeClock fake_clock_; |
| FakeTaskRunner task_runner_; |
| NiceMock<MockEnvironment> sender_environment_; |
| SenderPacketRouter sender_packet_router_; |
| Sender sender_; |
| SimulatedNetworkPipe receiver_to_sender_pipe_; |
| NiceMock<MockReceiver> receiver_; |
| SimulatedNetworkPipe sender_to_receiver_pipe_; |
| }; |
| |
| // Tests that the Sender can send EncodedFrames over an ideal network (i.e., low |
| // latency, no loss), and does so without having to transmit the same packet |
| // twice. |
| TEST_F(SenderTest, SendsFramesEfficiently) { |
| constexpr milliseconds kOneWayNetworkDelay{1}; |
| SetSenderToReceiverNetworkDelay(kOneWayNetworkDelay); |
| SetReceiverToSenderNetworkDelay(kOneWayNetworkDelay); |
| |
| // Expect that each packet is only sent once. |
| std::set<std::pair<FrameId, FramePacketId>> received_packets; |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)) |
| .WillRepeatedly( |
| Invoke([&](const RtpPacketParser::ParseResult& parsed_packet) { |
| std::pair<FrameId, FramePacketId> id(parsed_packet.frame_id, |
| parsed_packet.packet_id); |
| const auto insert_result = received_packets.insert(id); |
| EXPECT_TRUE(insert_result.second) |
| << "Received duplicate packet: " << id.first << ':' |
| << static_cast<int>(id.second); |
| })); |
| |
| // Simulate normal frame ACK'ing behavior. |
| ON_CALL(*receiver(), OnFrameComplete(_)).WillByDefault(InvokeWithoutArgs([&] { |
| if (receiver()->AutoAdvanceCheckpoint()) { |
| receiver()->TransmitRtcpFeedbackPacket(); |
| } |
| })); |
| |
| NiceMock<MockObserver> observer; |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first())).Times(1); |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 1)).Times(1); |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 2)).Times(1); |
| sender()->SetObserver(&observer); |
| |
| EncodedFrameWithBuffer frames[3]; |
| constexpr int kFrameDataSizes[] = {8196, 12, 1900}; |
| for (int i = 0; i < 3; ++i) { |
| if (i == 0) { |
| EXPECT_TRUE(sender()->NeedsKeyFrame()); |
| } else { |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| } |
| PopulateFrameWithDefaults(FrameId::first() + i, |
| FakeClock::now() - kCaptureDelay, 0xbf - i, |
| kFrameDataSizes[i], &frames[i]); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i])); |
| SimulateExecution(kFrameDuration); |
| } |
| SimulateExecution(kTargetPlayoutDelay); |
| |
| ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames()); |
| } |
| |
| // Tests that the Sender correctly computes the current in-flight media |
| // duration, a backlog signal for clients. |
| TEST_F(SenderTest, ComputesInFlightMediaDuration) { |
| // With no frames enqueued, the in-flight media duration should be zero. |
| EXPECT_EQ(Clock::duration::zero(), |
| sender()->GetInFlightMediaDuration(RtpTimeTicks())); |
| EXPECT_EQ(Clock::duration::zero(), |
| sender()->GetInFlightMediaDuration( |
| RtpTimeTicks() + RtpTimeDelta::FromTicks(kRtpTicksPerFrame))); |
| |
| // Enqueue a frame. |
| EncodedFrameWithBuffer frame; |
| PopulateFrameWithDefaults(FrameId::first(), FakeClock::now(), 0, |
| 13 /* bytes */, &frame); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame)); |
| |
| // Now, the in-flight media duration should depend on the RTP timestamp of the |
| // next frame. |
| EXPECT_EQ(kFrameDuration, sender()->GetInFlightMediaDuration( |
| frame.rtp_timestamp + |
| RtpTimeDelta::FromTicks(kRtpTicksPerFrame))); |
| EXPECT_EQ(10 * kFrameDuration, |
| sender()->GetInFlightMediaDuration( |
| frame.rtp_timestamp + |
| RtpTimeDelta::FromTicks(10 * kRtpTicksPerFrame))); |
| } |
| |
| // Tests that the Sender computes the maximum in-flight media duration based on |
| // its analysis of current network conditions. By implication, this demonstrates |
| // that the Sender is also measuring the network round-trip time. |
| TEST_F(SenderTest, RespondsToNetworkLatencyChanges) { |
| // The expected maximum error in time calculations is one tick of the RTCP |
| // report block's delay type. |
| constexpr auto kEpsilon = to_nanoseconds(RtcpReportBlock::Delay(1)); |
| |
| // Before the Sender has the necessary information to compute the network |
| // round-trip time, GetMaxInFlightMediaDuration() will return half the target |
| // playout delay. |
| EXPECT_NEARLY_EQUAL(kTargetPlayoutDelay / 2, |
| sender()->GetMaxInFlightMediaDuration(), kEpsilon); |
| |
| // No network is perfect. Simulate different one-way network delays. |
| constexpr milliseconds kOutboundDelay{2}; |
| constexpr milliseconds kInboundDelay{4}; |
| constexpr milliseconds kRoundTripDelay = kOutboundDelay + kInboundDelay; |
| SetSenderToReceiverNetworkDelay(kOutboundDelay); |
| SetReceiverToSenderNetworkDelay(kInboundDelay); |
| |
| // Enqueue a frame in the Sender to start emitting periodic RTCP reports. |
| { |
| EncodedFrameWithBuffer frame; |
| PopulateFrameWithDefaults(FrameId::first(), FakeClock::now(), 0, |
| 1 /* byte */, &frame); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame)); |
| } |
| |
| // Run one network round-trip from Sender→Receiver→Sender. |
| StatusReportId sender_report_id{}; |
| EXPECT_CALL(*receiver(), OnSenderReport(_)) |
| .WillOnce(Invoke( |
| [&](const SenderReportParser::SenderReportWithId& sender_report) { |
| sender_report_id = sender_report.report_id; |
| })); |
| // Simulate the passage of time for the Sender Report to reach the Receiver. |
| SimulateExecution(kOutboundDelay); |
| // The Receiver should have received the Sender Report at this point. |
| Mock::VerifyAndClearExpectations(receiver()); |
| ASSERT_NE(StatusReportId{}, sender_report_id); |
| // Simulate the passage of time in the Receiver doing "other tasks" before |
| // replying back to the Sender. This delay is included in the Receiver Report |
| // so that the Sender can isolate the delays caused by the network. |
| constexpr milliseconds kReceiverProcessingDelay{2}; |
| SimulateExecution(kReceiverProcessingDelay); |
| // Create the Receiver Report "reply," and simulate it being sent across the |
| // network, back to the Sender. |
| receiver()->SetReceiverReport( |
| sender_report_id, std::chrono::duration_cast<RtcpReportBlock::Delay>( |
| kReceiverProcessingDelay)); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(kInboundDelay); |
| |
| // At this point, the Sender should have computed the network round-trip time, |
| // and so GetMaxInFlightMediaDuration() will return half the target playout |
| // delay PLUS half the network round-trip time. |
| EXPECT_NEARLY_EQUAL(kTargetPlayoutDelay / 2 + kRoundTripDelay / 2, |
| sender()->GetMaxInFlightMediaDuration(), kEpsilon); |
| |
| // Increase the outbound delay, which will increase the total round-trip time. |
| constexpr milliseconds kIncreasedOutboundDelay{6}; |
| constexpr milliseconds kIncreasedRoundTripDelay = |
| kIncreasedOutboundDelay + kInboundDelay; |
| SetSenderToReceiverNetworkDelay(kIncreasedOutboundDelay); |
| |
| // With increased network delay, run several more network round-trips. Expect |
| // the Sender to gradually converge towards the new network round-trip time. |
| constexpr int kNumReportIntervals = 50; |
| EXPECT_CALL(*receiver(), OnSenderReport(_)) |
| .Times(kNumReportIntervals) |
| .WillRepeatedly(Invoke( |
| [&](const SenderReportParser::SenderReportWithId& sender_report) { |
| receiver()->SetReceiverReport(sender_report.report_id, |
| RtcpReportBlock::Delay::zero()); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| })); |
| Clock::duration last_max = sender()->GetMaxInFlightMediaDuration(); |
| for (int i = 0; i < kNumReportIntervals; ++i) { |
| SimulateExecution(kRtcpReportInterval); |
| const Clock::duration updated_value = |
| sender()->GetMaxInFlightMediaDuration(); |
| EXPECT_LE(last_max, updated_value); |
| last_max = updated_value; |
| } |
| EXPECT_NEARLY_EQUAL(kTargetPlayoutDelay / 2 + kIncreasedRoundTripDelay / 2, |
| sender()->GetMaxInFlightMediaDuration(), kEpsilon); |
| } |
| |
| // Tests that the Sender rejects frames if too large a span of FrameIds would be |
| // in-flight at once. |
| TEST_F(SenderTest, RejectsEnqueuingBeforeProtocolDesignLimit) { |
| // For this test, use 1000 FPS. This makes the frames all one millisecond |
| // apart to avoid triggering the media-duration rejection logic. |
| constexpr int kFramesPerSecond = 1000; |
| constexpr milliseconds kFrameDuration{1}; |
| |
| const auto OverrideRtpTimestamp = [](int frame_count, EncodedFrame* frame) { |
| const int ticks = frame_count * kRtpTimebase / kFramesPerSecond; |
| frame->rtp_timestamp = RtpTimeTicks() + RtpTimeDelta::FromTicks(ticks); |
| }; |
| |
| // Send the absolute design-limit maximum number of frames. |
| int frame_count = 0; |
| for (; frame_count < kMaxUnackedFrames; ++frame_count) { |
| EncodedFrameWithBuffer frame; |
| PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0, |
| 13 /* bytes */, &frame); |
| OverrideRtpTimestamp(frame_count, &frame); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame)); |
| SimulateExecution(kFrameDuration); |
| } |
| |
| // Now, attempting to enqueue just one more frame should fail. |
| EncodedFrameWithBuffer one_frame_too_much; |
| PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0, |
| 13 /* bytes */, &one_frame_too_much); |
| OverrideRtpTimestamp(frame_count++, &one_frame_too_much); |
| EXPECT_EQ(Sender::REACHED_ID_SPAN_LIMIT, |
| sender()->EnqueueFrame(one_frame_too_much)); |
| SimulateExecution(kFrameDuration); |
| |
| // Now, simulate the Receiver ACKing the first frame, and enqueuing should |
| // then succeed again. |
| receiver()->SetCheckpointFrame(FrameId::first()); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| EXPECT_EQ(Sender::OK, sender()->EnqueueFrame(one_frame_too_much)); |
| SimulateExecution(kFrameDuration); |
| |
| // Finally, attempting to enqueue another frame should fail again. |
| EncodedFrameWithBuffer another_frame_too_much; |
| PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0, |
| 13 /* bytes */, &another_frame_too_much); |
| OverrideRtpTimestamp(frame_count++, &another_frame_too_much); |
| EXPECT_EQ(Sender::REACHED_ID_SPAN_LIMIT, |
| sender()->EnqueueFrame(another_frame_too_much)); |
| SimulateExecution(kFrameDuration); |
| } |
| |
| // Tests that the Sender rejects frames if too-long a media duration is |
| // in-flight. This is the Sender's primary flow control mechanism. |
| TEST_F(SenderTest, RejectsEnqueuingIfTooLongMediaDurationIsInFlight) { |
| // For this test, use 20 FPS. This makes all frames 50 ms apart, which should |
| // make it easy to trigger the media-duration rejection logic. |
| constexpr int kFramesPerSecond = 20; |
| constexpr milliseconds kFrameDuration{50}; |
| |
| const auto OverrideRtpTimestamp = [](int frame_count, EncodedFrame* frame) { |
| const int ticks = frame_count * kRtpTimebase / kFramesPerSecond; |
| frame->rtp_timestamp = RtpTimeTicks() + RtpTimeDelta::FromTicks(ticks); |
| }; |
| |
| // Enqueue frames until one is rejected because the in-flight duration would |
| // be too high. |
| EncodedFrameWithBuffer frame; |
| int frame_count = 0; |
| for (; frame_count < kMaxUnackedFrames; ++frame_count) { |
| PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0, |
| 13 /* bytes */, &frame); |
| OverrideRtpTimestamp(frame_count, &frame); |
| const auto result = sender()->EnqueueFrame(frame); |
| SimulateExecution(kFrameDuration); |
| if (result == Sender::MAX_DURATION_IN_FLIGHT) { |
| break; |
| } |
| ASSERT_EQ(Sender::OK, result); |
| } |
| |
| // Now, simulate the Receiver ACKing the first frame, and enqueuing should |
| // then succeed again. |
| receiver()->SetCheckpointFrame(FrameId::first()); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| EXPECT_EQ(Sender::OK, sender()->EnqueueFrame(frame)); |
| SimulateExecution(kFrameDuration); |
| |
| // However, attempting to enqueue another frame should fail again. |
| EncodedFrameWithBuffer one_frame_too_much; |
| PopulateFrameWithDefaults(sender()->GetNextFrameId(), FakeClock::now(), 0, |
| 13 /* bytes */, &one_frame_too_much); |
| OverrideRtpTimestamp(++frame_count, &one_frame_too_much); |
| EXPECT_EQ(Sender::MAX_DURATION_IN_FLIGHT, |
| sender()->EnqueueFrame(one_frame_too_much)); |
| SimulateExecution(kFrameDuration); |
| } |
| |
| // Tests that the Sender propagates the Receiver's picture loss indicator to the |
| // Observer::OnPictureLost(), and via calls to NeedsKeyFrame(); but only when |
| // producing a key frame is absolutely necessary. |
| TEST_F(SenderTest, ManagesReceiverPictureLossWorkflow) { |
| NiceMock<MockObserver> observer; |
| sender()->SetObserver(&observer); |
| |
| // Send three frames... |
| EncodedFrameWithBuffer frames[6]; |
| for (int i = 0; i < 3; ++i) { |
| if (i == 0) { |
| EXPECT_TRUE(sender()->NeedsKeyFrame()); |
| } else { |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| } |
| PopulateFrameWithDefaults(FrameId::first() + i, |
| FakeClock::now() - kCaptureDelay, 0, |
| 24 /* bytes */, &frames[i]); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i])); |
| SimulateExecution(kFrameDuration); |
| } |
| SimulateExecution(kTargetPlayoutDelay); |
| |
| // Simulate the Receiver ACK'ing the first three frames. |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first())).Times(1); |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 1)).Times(1); |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 2)).Times(1); |
| EXPECT_CALL(observer, OnPictureLost()).Times(0); |
| receiver()->SetCheckpointFrame(frames[2].frame_id); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| Mock::VerifyAndClearExpectations(&observer); |
| |
| // Simulate something going wrong in the Receiver, and have it report picture |
| // loss to the Sender. The Sender should then propagate this to its Observer |
| // and return true when NeedsKeyFrame() is called. |
| EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0); |
| EXPECT_CALL(observer, OnPictureLost()).Times(1); |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| receiver()->SetPictureLossIndicator(true); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_TRUE(sender()->NeedsKeyFrame()); |
| |
| // Send a non-key frame, and expect NeedsKeyFrame() still returns true. The |
| // Observer is not re-notified. This accounts for the case where a client's |
| // media encoder had frames in its processing pipeline before NeedsKeyFrame() |
| // began returning true. |
| EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0); |
| EXPECT_CALL(observer, OnPictureLost()).Times(0); |
| EncodedFrameWithBuffer& nonkey_frame = frames[3]; |
| PopulateFrameWithDefaults(FrameId::first() + 3, |
| FakeClock::now() - kCaptureDelay, 0, 24 /* bytes */, |
| &nonkey_frame); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(nonkey_frame)); |
| SimulateExecution(kFrameDuration); |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_TRUE(sender()->NeedsKeyFrame()); |
| |
| // Now send a key frame, and expect NeedsKeyFrame() returns false. Note that |
| // the Receiver hasn't cleared the PLI condition, but the Sender knows more |
| // key frames won't be needed. |
| EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0); |
| EXPECT_CALL(observer, OnPictureLost()).Times(0); |
| EncodedFrameWithBuffer& recovery_frame = frames[4]; |
| PopulateFrameWithDefaults(FrameId::first() + 4, |
| FakeClock::now() - kCaptureDelay, 0, 24 /* bytes */, |
| &recovery_frame); |
| recovery_frame.dependency = EncodedFrame::KEY_FRAME; |
| recovery_frame.referenced_frame_id = recovery_frame.frame_id; |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(recovery_frame)); |
| SimulateExecution(kFrameDuration); |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| |
| // Let's say the Receiver hasn't received the key frame yet, and it reports |
| // its picture loss again to the Sender. Observer::OnPictureLost() should not |
| // be called, and NeedsKeyFrame() should NOT return true, because the Sender |
| // knows the Receiver hasn't acknowledged the key frame (just sent) yet. |
| EXPECT_CALL(observer, OnFrameCanceled(nonkey_frame.frame_id)).Times(1); |
| EXPECT_CALL(observer, OnPictureLost()).Times(0); |
| receiver()->SetCheckpointFrame(nonkey_frame.frame_id); |
| receiver()->SetPictureLossIndicator(true); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| |
| // Now, simulate the Receiver getting the key frame, but NOT recovering. This |
| // should cause Observer::OnPictureLost() to be called, and cause |
| // NeedsKeyFrame() to return true again. |
| EXPECT_CALL(observer, OnFrameCanceled(recovery_frame.frame_id)).Times(1); |
| EXPECT_CALL(observer, OnPictureLost()).Times(1); |
| receiver()->SetCheckpointFrame(recovery_frame.frame_id); |
| receiver()->SetPictureLossIndicator(true); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_TRUE(sender()->NeedsKeyFrame()); |
| |
| // Send another key frame, and expect NeedsKeyFrame() returns false. |
| EXPECT_CALL(observer, OnFrameCanceled(_)).Times(0); |
| EXPECT_CALL(observer, OnPictureLost()).Times(0); |
| EncodedFrameWithBuffer& another_recovery_frame = frames[5]; |
| PopulateFrameWithDefaults(FrameId::first() + 5, |
| FakeClock::now() - kCaptureDelay, 0, 24 /* bytes */, |
| &another_recovery_frame); |
| another_recovery_frame.dependency = EncodedFrame::KEY_FRAME; |
| another_recovery_frame.referenced_frame_id = another_recovery_frame.frame_id; |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(another_recovery_frame)); |
| SimulateExecution(kFrameDuration); |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| |
| // Now, simulate the Receiver recovering. It will report this to the Sender, |
| // and NeedsKeyFrame() will still return false. |
| EXPECT_CALL(observer, OnFrameCanceled(another_recovery_frame.frame_id)) |
| .Times(1); |
| EXPECT_CALL(observer, OnPictureLost()).Times(0); |
| receiver()->SetCheckpointFrame(another_recovery_frame.frame_id); |
| receiver()->SetPictureLossIndicator(false); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| Mock::VerifyAndClearExpectations(&observer); |
| EXPECT_FALSE(sender()->NeedsKeyFrame()); |
| |
| ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames()); |
| } |
| |
| // Tests that the Receiver should get a Sender Report just before the first RTP |
| // packet, and at regular intervals thereafter. The Sender Report contains the |
| // lip-sync information necessary for play-out timing. |
| TEST_F(SenderTest, ProvidesSenderReports) { |
| std::vector<SenderReportParser::SenderReportWithId> sender_reports; |
| Sequence packet_sequence; |
| EXPECT_CALL(*receiver(), OnSenderReport(_)) |
| .InSequence(packet_sequence) |
| .WillOnce( |
| Invoke([&](const SenderReportParser::SenderReportWithId& report) { |
| sender_reports.push_back(report); |
| })) |
| .RetiresOnSaturation(); |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(1).InSequence(packet_sequence); |
| EXPECT_CALL(*receiver(), OnSenderReport(_)) |
| .Times(3) |
| .InSequence(packet_sequence) |
| .WillRepeatedly( |
| Invoke([&](const SenderReportParser::SenderReportWithId& report) { |
| sender_reports.push_back(report); |
| })); |
| |
| EncodedFrameWithBuffer frame; |
| constexpr int kFrameDataSize = 250; |
| PopulateFrameWithDefaults(FrameId::first(), FakeClock::now(), 0, |
| kFrameDataSize, &frame); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frame)); |
| SimulateExecution(); // Should send one Sender Report + one RTP packet. |
| EXPECT_EQ(size_t{1}, sender_reports.size()); |
| |
| // Have the Receiver ACK the frame to prevent retransmitting the RTP packet. |
| receiver()->SetCheckpointFrame(FrameId::first()); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| |
| // Advance through three more reporting intervals. One Sender Report should be |
| // sent each interval, making a total of 4 reports sent. |
| constexpr auto kThreeReportIntervals = 3 * kRtcpReportInterval; |
| SimulateExecution(kThreeReportIntervals); // Three more Sender Reports. |
| ASSERT_EQ(size_t{4}, sender_reports.size()); |
| |
| // The first report should contain the same timestamps as the frame because |
| // the Clock did not advance. Also, its packet count and octet count fields |
| // should be zero since the report was sent before the RTP packet. |
| EXPECT_EQ(frame.reference_time, sender_reports.front().reference_time); |
| EXPECT_EQ(frame.rtp_timestamp, sender_reports.front().rtp_timestamp); |
| EXPECT_EQ(uint32_t{0}, sender_reports.front().send_packet_count); |
| EXPECT_EQ(uint32_t{0}, sender_reports.front().send_octet_count); |
| |
| // The last report should contain the timestamps extrapolated into the future |
| // because the Clock did move forward. Also, the packet count and octet fields |
| // should now be non-zero because the report was sent after the RTP packet. |
| EXPECT_EQ(frame.reference_time + kThreeReportIntervals, |
| sender_reports.back().reference_time); |
| EXPECT_EQ(frame.rtp_timestamp + |
| RtpTimeDelta::FromDuration(kThreeReportIntervals, kRtpTimebase), |
| sender_reports.back().rtp_timestamp); |
| EXPECT_EQ(uint32_t{1}, sender_reports.back().send_packet_count); |
| EXPECT_EQ(uint32_t{kFrameDataSize}, sender_reports.back().send_octet_count); |
| } |
| |
| // Tests that the Sender provides Kickstart packets whenever the Receiver may |
| // not know about new frames. |
| TEST_F(SenderTest, ProvidesKickstartPacketsIfReceiverDoesNotACK) { |
| // Have the Receiver move the checkpoint forward only for the first frame, and |
| // none of the later frames. This will force the Sender to eventually send a |
| // Kickstart packet. |
| ON_CALL(*receiver(), OnFrameComplete(_)) |
| .WillByDefault(Invoke([&](FrameId frame_id) { |
| if (frame_id == FrameId::first()) { |
| receiver()->SetCheckpointFrame(FrameId::first()); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| } |
| })); |
| |
| // Send three frames, paced to the media. |
| EncodedFrameWithBuffer frames[3]; |
| for (int i = 0; i < 3; ++i) { |
| PopulateFrameWithDefaults(FrameId::first() + i, |
| FakeClock::now() - kCaptureDelay, i, |
| 48 /* bytes */, &frames[i]); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i])); |
| SimulateExecution(kFrameDuration); |
| } |
| |
| // Now, do nothing for a while. Because the Receiver isn't moving the |
| // checkpoint forward, the Sender will have sent all the RTP packets at least |
| // once, and then will start sending just Kickstart packets. |
| SimulateExecution(kTargetPlayoutDelay); |
| |
| // Keep doing nothing for a while, and confirm the Sender is just sending the |
| // same Kickstart packet over and over. The Kickstart packet is supposed to be |
| // the last packet of the latest frame. |
| std::set<std::pair<FrameId, FramePacketId>> unique_received_packet_ids; |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)) |
| .WillRepeatedly( |
| Invoke([&](const RtpPacketParser::ParseResult& parsed_packet) { |
| unique_received_packet_ids.emplace(parsed_packet.frame_id, |
| parsed_packet.packet_id); |
| })); |
| SimulateExecution(kTargetPlayoutDelay); |
| Mock::VerifyAndClearExpectations(receiver()); |
| EXPECT_EQ(size_t{1}, unique_received_packet_ids.size()); |
| EXPECT_EQ(frames[2].frame_id, unique_received_packet_ids.begin()->first); |
| |
| // Now, simulate the Receiver ACKing all the frames. |
| receiver()->SetCheckpointFrame(frames[2].frame_id); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| SimulateExecution(); // RTCP transmitted to Sender. |
| |
| // With all the frames sent, the Sender should not be transmitting anything. |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(0); |
| SimulateExecution(10 * kTargetPlayoutDelay); |
| |
| ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames()); |
| } |
| |
| // Tests that the Sender only retransmits packets specifically NACK'ed by the |
| // Receiver. |
| TEST_F(SenderTest, ResendsIndividuallyNackedPackets) { |
| // Populate the frame data in each frame with enough bytes to force at least |
| // three RTP packets per frame. |
| constexpr int kFrameDataSize = 3 * kMaxRtpPacketSizeForIpv6UdpOnEthernet; |
| |
| // Use a 1ms network delay in each direction to make the sequence of events |
| // clearer in this test. |
| constexpr milliseconds kOneWayNetworkDelay{1}; |
| SetSenderToReceiverNetworkDelay(kOneWayNetworkDelay); |
| SetReceiverToSenderNetworkDelay(kOneWayNetworkDelay); |
| |
| // Simulate that three specific packets will be dropped by the network, one |
| // from each frame (about to be sent). |
| const std::vector<PacketNack> dropped_packets{ |
| {FrameId::first(), FramePacketId{2}}, |
| {FrameId::first() + 1, FramePacketId{1}}, |
| {FrameId::first() + 2, FramePacketId{0}}, |
| }; |
| receiver()->SetIgnoreList(dropped_packets); |
| |
| // Send three frames, paced to the media. The Receiver won't completely |
| // receive any of these frames due to dropped packets. |
| EXPECT_CALL(*receiver(), OnFrameComplete(_)).Times(0); |
| EncodedFrameWithBuffer frames[3]; |
| for (int i = 0; i < 3; ++i) { |
| PopulateFrameWithDefaults(FrameId::first() + i, |
| FakeClock::now() - kCaptureDelay, i, |
| kFrameDataSize, &frames[i]); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i])); |
| SimulateExecution(kFrameDuration); |
| } |
| SimulateExecution(kTargetPlayoutDelay); |
| Mock::VerifyAndClearExpectations(receiver()); |
| EXPECT_EQ(3, sender()->GetInFlightFrameCount()); |
| |
| // The Receiver NACKs the three dropped packets... |
| receiver()->SetNacksAndAcks(dropped_packets, {}); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| |
| // In the meantime, the network recovers (i.e., no more dropped packets)... |
| receiver()->SetIgnoreList({}); |
| |
| // The NACKs reach the Sender, and it acts on them by retransmitting. |
| SimulateExecution(kOneWayNetworkDelay); |
| |
| // As each retransmitted packet arrives at the Receiver, advance the |
| // checkpoint forward to notify the Sender of frames that are now completely |
| // received. Also, confirm that only the three specifically-NACK'ed packets |
| // were retransmitted. |
| EXPECT_CALL(*receiver(), OnFrameComplete(_)) |
| .Times(3) |
| .WillRepeatedly(InvokeWithoutArgs([&] { |
| if (receiver()->AutoAdvanceCheckpoint()) { |
| receiver()->TransmitRtcpFeedbackPacket(); |
| } |
| })); |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)) |
| .Times(3) |
| .WillRepeatedly(Invoke([&](const RtpPacketParser::ParseResult& packet) { |
| EXPECT_FALSE(std::find(dropped_packets.begin(), dropped_packets.end(), |
| PacketNack{packet.frame_id, packet.packet_id}) == |
| dropped_packets.end()); |
| })); |
| SimulateExecution(kOneWayNetworkDelay); |
| Mock::VerifyAndClearExpectations(receiver()); |
| |
| // The Receiver checkpoint feedback(s) travel back to the Sender, and there |
| // should no longer be any frames in-flight. |
| SimulateExecution(kOneWayNetworkDelay); |
| EXPECT_EQ(0, sender()->GetInFlightFrameCount()); |
| |
| // The Sender should not be transmitting anything from now on since all frames |
| // are known to have been completely received. |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(0); |
| SimulateExecution(10 * kTargetPlayoutDelay); |
| |
| ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames()); |
| } |
| |
| // Tests that the Sender retransmits an entire frame if the Receiver requests it |
| // (i.e., a full frame NACK), but does not retransmit any packets for frames |
| // (before or after) that have been acknowledged. |
| TEST_F(SenderTest, ResendsMissingFrames) { |
| // Populate the frame data in each frame with enough bytes to force at least |
| // three RTP packets per frame. |
| constexpr int kFrameDataSize = 3 * kMaxRtpPacketSizeForIpv6UdpOnEthernet; |
| |
| // Use a 1ms network delay in each direction to make the sequence of events |
| // clearer in this test. |
| constexpr milliseconds kOneWayNetworkDelay{1}; |
| SetSenderToReceiverNetworkDelay(kOneWayNetworkDelay); |
| SetReceiverToSenderNetworkDelay(kOneWayNetworkDelay); |
| |
| // Simulate that all of the packets for the second frame will be dropped by |
| // the network, but only the packets for that frame. |
| const std::vector<PacketNack> dropped_packets{ |
| {FrameId::first() + 1, kAllPacketsLost}, |
| }; |
| receiver()->SetIgnoreList(dropped_packets); |
| |
| NiceMock<MockObserver> observer; |
| sender()->SetObserver(&observer); |
| |
| // The expectations below track the story and execute simulated Receiver |
| // responses. The Sender will have three frames enqueued by its client, and |
| // then... |
| // |
| // The first frame is received and the Receiver ACKs it by moving the |
| // checkpoint forward. |
| Sequence completion_sequence; |
| EXPECT_CALL(*receiver(), OnFrameComplete(FrameId::first())) |
| .InSequence(completion_sequence) |
| .WillOnce(InvokeWithoutArgs([&] { |
| receiver()->SetCheckpointFrame(FrameId::first()); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| })); |
| // Since all of the packets for the second frame are being dropped, the third |
| // frame will finish next. The Receiver responds by NACKing the second frame |
| // and ACKing the third frame. The checkpoint does not move forward because |
| // the second frame has not been received yet. |
| // |
| // NETWORK CHANGE: After the third frame is received, stop dropping packets. |
| EXPECT_CALL(*receiver(), OnFrameComplete(FrameId::first() + 2)) |
| .InSequence(completion_sequence) |
| .WillOnce(InvokeWithoutArgs([&] { |
| receiver()->SetNacksAndAcks(dropped_packets, |
| std::vector<FrameId>{FrameId::first() + 2}); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| receiver()->SetIgnoreList({}); |
| })); |
| // Finally, the Sender should respond to the whole-frame NACK by re-sending |
| // all of the packets for the second frame, and so the Receiver should |
| // completely receive the frame. |
| EXPECT_CALL(*receiver(), OnFrameComplete(FrameId::first() + 1)) |
| .InSequence(completion_sequence) |
| .WillOnce(InvokeWithoutArgs([&] { |
| receiver()->SetCheckpointFrame(FrameId::first() + 2); |
| receiver()->TransmitRtcpFeedbackPacket(); |
| })); |
| |
| // From the Sender's perspective, the Receiver will ACK the first frame, then |
| // the third frame, then the second frame. |
| Sequence cancel_sequence; |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first())) |
| .Times(1) |
| .InSequence(cancel_sequence); |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 2)) |
| .Times(1) |
| .InSequence(cancel_sequence); |
| EXPECT_CALL(observer, OnFrameCanceled(FrameId::first() + 1)) |
| .Times(1) |
| .InSequence(cancel_sequence); |
| |
| // With all the expectations/sequences in-place, let 'er rip! |
| EncodedFrameWithBuffer frames[3]; |
| for (int i = 0; i < 3; ++i) { |
| PopulateFrameWithDefaults(FrameId::first() + i, |
| FakeClock::now() - kCaptureDelay, i, |
| kFrameDataSize, &frames[i]); |
| ASSERT_EQ(Sender::OK, sender()->EnqueueFrame(frames[i])); |
| SimulateExecution(kFrameDuration); |
| } |
| SimulateExecution(kTargetPlayoutDelay); |
| Mock::VerifyAndClearExpectations(receiver()); |
| EXPECT_EQ(0, sender()->GetInFlightFrameCount()); |
| |
| // The Sender should not be transmitting anything from now on since all frames |
| // are known to have been completely received. |
| EXPECT_CALL(*receiver(), OnRtpPacket(_)).Times(0); |
| SimulateExecution(10 * kTargetPlayoutDelay); |
| |
| ExpectFramesReceivedCorrectly(frames, receiver()->TakeCompleteFrames()); |
| } |
| |
| } // namespace |
| } // namespace cast |
| } // namespace openscreen |