// Copyright 2019 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/frame_collector.h" #include #include #include #include "cast/streaming/encoded_frame.h" #include "cast/streaming/frame_id.h" #include "cast/streaming/rtcp_common.h" #include "cast/streaming/rtp_time.h" #include "gtest/gtest.h" namespace openscreen { namespace cast { namespace { const FrameId kSomeFrameId = FrameId::first() + 39; constexpr RtpTimeTicks kSomeRtpTimestamp = RtpTimeTicks() + RtpTimeDelta::FromTicks(90000); // Convenience macro to check that the |collector| generates an expected set of // NACKs. #define EXPECT_HAS_NACKS(expected_nacks, collector) \ do { \ std::vector nacks; \ (collector).GetMissingPackets(&nacks); \ EXPECT_EQ((expected_nacks), nacks); \ } while (false); TEST(FrameCollectorTest, CollectsFrameWithOnlyOnePart) { FrameCollector collector; // Run for two frames to test that the collector can be re-used. for (int i = 0; i < 2; ++i) { const FrameId frame_id = kSomeFrameId + i; collector.set_frame_id(frame_id); EXPECT_FALSE(collector.is_complete()); // With no packets seen yet, the collector should provide the "all packets // lost" NACK. EXPECT_HAS_NACKS((std::vector{{frame_id, kAllPacketsLost}}), collector); // Collect the single packet of the frame. RtpPacketParser::ParseResult part; part.rtp_timestamp = kSomeRtpTimestamp + (RtpTimeDelta::FromTicks(200) * i); part.frame_id = frame_id; part.packet_id = 0; part.max_packet_id = 0; if (i == 0) { part.is_key_frame = true; // Do not set |part.new_playout_delay|. } else { part.is_key_frame = false; part.new_playout_delay = std::chrono::milliseconds(800); } part.referenced_frame_id = kSomeFrameId; std::vector buffer(255); for (int j = 0; j < 255; ++j) { buffer[j] = static_cast(j); } part.payload = absl::Span(buffer); EXPECT_TRUE(collector.CollectRtpPacket(part, &buffer)); // At this point, the collector should feel complete. EXPECT_TRUE(collector.is_complete()); EXPECT_HAS_NACKS(std::vector(), collector); // Examine the assembled frame, and confirm its metadata and payload match // what was put into the collector via the packet above. const auto& frame = collector.PeekAtAssembledFrame(); if (i == 0) { EXPECT_EQ(EncodedFrame::KEY_FRAME, frame.dependency); EXPECT_EQ(std::chrono::milliseconds(), frame.new_playout_delay); } else { EXPECT_EQ(EncodedFrame::DEPENDS_ON_ANOTHER, frame.dependency); EXPECT_EQ(std::chrono::milliseconds(800), frame.new_playout_delay); } EXPECT_EQ(part.frame_id, frame.frame_id); EXPECT_EQ(kSomeFrameId, frame.referenced_frame_id); EXPECT_EQ(part.rtp_timestamp, frame.rtp_timestamp); for (int j = 0; j < 255; ++j) { EXPECT_EQ(static_cast(j), frame.data[j]); } collector.Reset(); } } TEST(FrameCollectorTest, CollectsFrameWithMultiplePartsArrivingOutOfOrder) { FrameCollector collector; collector.set_frame_id(kSomeFrameId); // With no packets seen yet, the collector should provide the "all packets // lost" NACK. EXPECT_HAS_NACKS((std::vector{{kSomeFrameId, kAllPacketsLost}}), collector); // Prepare the six packet payloads, and the list of remaining NACKs (checked // after each part is collected). const int kPayloadSizes[] = {999, 998, 998, 998, 42, 0}; std::vector payloads[6]; std::vector remaining_nacks; for (int i = 0; i < 6; ++i) { payloads[i].resize(kPayloadSizes[i], static_cast(i)); remaining_nacks.push_back( PacketNack{kSomeFrameId, static_cast(i)}); } // Collect all six packets, out-of-order, and with some duplicates. constexpr FramePacketId kPacketIds[] = {2, 0, 1, 2, 4, 3, 5, 5, 5, 0}; for (FramePacketId packet_id : kPacketIds) { RtpPacketParser::ParseResult part{}; part.rtp_timestamp = kSomeRtpTimestamp; part.is_key_frame = true; part.frame_id = kSomeFrameId; part.packet_id = packet_id; part.max_packet_id = 5; part.referenced_frame_id = kSomeFrameId; // Prepare a copy of the payload to pass into the FrameCollector. Place 24 // bytes of bogus data at the start of the buffer to simulate the // non-payload part of the RTP packet. std::vector buffer(24, uint8_t{0xab}); buffer.insert(buffer.end(), payloads[packet_id].begin(), payloads[packet_id].end()); part.payload = absl::Span(buffer.data() + 24, buffer.size() - 24); EXPECT_TRUE(collector.CollectRtpPacket(part, &buffer)); // Remove the packet from the list of expected remaining NACKs, and then // check that the collector agrees. remaining_nacks.erase( std::remove_if(remaining_nacks.begin(), remaining_nacks.end(), [packet_id](const PacketNack& nack) { return nack.packet_id == packet_id; }), remaining_nacks.end()); EXPECT_HAS_NACKS(remaining_nacks, collector); } // Confirm there are no missing packets and no NACKs generated. EXPECT_TRUE(collector.is_complete()); EXPECT_HAS_NACKS(std::vector(), collector); // Examine the assembled frame, and confirm its metadata and payload match // what was put into the collector via the packets above, and that the payload // bytes are in-order. const auto& frame = collector.PeekAtAssembledFrame(); EXPECT_EQ(EncodedFrame::KEY_FRAME, frame.dependency); EXPECT_EQ(kSomeFrameId, frame.frame_id); EXPECT_EQ(kSomeFrameId, frame.referenced_frame_id); EXPECT_EQ(kSomeRtpTimestamp, frame.rtp_timestamp); absl::Span remaining_data = frame.data; for (int i = 0; i < 6; ++i) { ASSERT_LE(kPayloadSizes[i], static_cast(remaining_data.size())); EXPECT_EQ(absl::Span(payloads[i]), remaining_data.subspan(0, kPayloadSizes[i])) << "i=" << i; remaining_data.remove_prefix(kPayloadSizes[i]); } ASSERT_TRUE(remaining_data.empty()); } TEST(FrameCollectorTest, RejectsInvalidParts) { FrameCollector collector; // Expect the collector rejects a part not having the correct FrameId. collector.set_frame_id(kSomeFrameId + 256); RtpPacketParser::ParseResult part{}; part.rtp_timestamp = kSomeRtpTimestamp; part.is_key_frame = false; part.frame_id = kSomeFrameId; part.packet_id = 0; part.max_packet_id = 3; std::vector buffer(1, 'A'); part.payload = absl::Span(buffer); EXPECT_FALSE(collector.CollectRtpPacket(part, &buffer)); // Note: When CollectRtpPacket() returns false, it does not take ownership of // the buffer memory. ASSERT_TRUE(buffer.size() == 1 && buffer[0] == 'A'); // The collector should accept a part having the correct FrameId. collector.set_frame_id(kSomeFrameId); part.frame_id = kSomeFrameId; EXPECT_TRUE(collector.CollectRtpPacket(part, &buffer)); // Note: Re-assign the buffer and payload pointer since the buffer was just // consumed. buffer.assign(1, 'A'); part.payload = absl::Span(buffer); // The collector should reject a part where the packet_id is greater than the // previously-established max_packet_id. part.packet_id = 5; // BAD, since max_packet_id is 3 (see above). EXPECT_FALSE(collector.CollectRtpPacket(part, &buffer)); ASSERT_TRUE(buffer.size() == 1 && buffer[0] == 'A'); // The collector should reject a part where the max_packet_id disagrees with // previously-established max_packet_id. part.packet_id = 2; part.max_packet_id = 5; // BAD, since max_packet_id is 3 (see above). EXPECT_FALSE(collector.CollectRtpPacket(part, &buffer)); ASSERT_TRUE(buffer.size() == 1 && buffer[0] == 'A'); } } // namespace } // namespace cast } // namespace openscreen