diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc new file mode 100644 index 0000000000..2151a59295 --- /dev/null +++ b/third_party/libwebrtc/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2019 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/rtp_packetizer_av1.h" + +#include <stddef.h> +#include <stdint.h> + +#include <initializer_list> +#include <utility> +#include <vector> + +#include "api/array_view.h" +#include "api/scoped_refptr.h" +#include "api/video/encoded_image.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/rtp_packetizer_av1_test_helper.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_av1.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::Each; +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Le; +using ::testing::SizeIs; + +constexpr uint8_t kNewCodedVideoSequenceBit = 0b00'00'1000; + +// Wrapper around rtp_packet to make it look like container of payload bytes. +struct RtpPayload { + using value_type = rtc::ArrayView<const uint8_t>::value_type; + using const_iterator = rtc::ArrayView<const uint8_t>::const_iterator; + + RtpPayload() : rtp_packet(/*extensions=*/nullptr) {} + RtpPayload& operator=(RtpPayload&&) = default; + RtpPayload(RtpPayload&&) = default; + + const_iterator begin() const { return rtp_packet.payload().begin(); } + const_iterator end() const { return rtp_packet.payload().end(); } + const uint8_t* data() const { return rtp_packet.payload().data(); } + size_t size() const { return rtp_packet.payload().size(); } + + uint8_t aggregation_header() const { return rtp_packet.payload()[0]; } + + RtpPacketToSend rtp_packet; +}; + +// Wrapper around frame pointer to make it look like container of bytes with +// nullptr frame look like empty container. +class Av1Frame { + public: + using value_type = uint8_t; + using const_iterator = const uint8_t*; + + explicit Av1Frame(rtc::scoped_refptr<EncodedImageBuffer> frame) + : frame_(std::move(frame)) {} + + const_iterator begin() const { return frame_ ? frame_->data() : nullptr; } + const_iterator end() const { + return frame_ ? (frame_->data() + frame_->size()) : nullptr; + } + + private: + rtc::scoped_refptr<EncodedImageBuffer> frame_; +}; + +std::vector<RtpPayload> Packetize( + rtc::ArrayView<const uint8_t> payload, + RtpPacketizer::PayloadSizeLimits limits, + VideoFrameType frame_type = VideoFrameType::kVideoFrameDelta, + bool is_last_frame_in_picture = true) { + // Run code under test. + RtpPacketizerAv1 packetizer(payload, limits, frame_type, + is_last_frame_in_picture); + // Convert result into structure that is easier to run expectation against. + std::vector<RtpPayload> result(packetizer.NumPackets()); + for (RtpPayload& rtp_payload : result) { + EXPECT_TRUE(packetizer.NextPacket(&rtp_payload.rtp_packet)); + } + return result; +} + +Av1Frame ReassembleFrame(rtc::ArrayView<const RtpPayload> rtp_payloads) { + std::vector<rtc::ArrayView<const uint8_t>> payloads(rtp_payloads.size()); + for (size_t i = 0; i < rtp_payloads.size(); ++i) { + payloads[i] = rtp_payloads[i]; + } + return Av1Frame(VideoRtpDepacketizerAv1().AssembleFrame(payloads)); +} + +TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) { + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithoutSize() + .WithPayload({1, 2, 3, 4, 5, 6, 7})}); + EXPECT_THAT(Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame, 1, 2, 3, 4, 5, 6, 7))); +} + +TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeWithExtension) { + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithoutSize() + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({2, 3, 4, 5, 6, 7})}); + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit, + kAv1ObuExtensionS1T1, 2, 3, 4, 5, 6, 7))); +} + +TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithoutExtension) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload({11, 12, 13, 14, 15, 16, 17})}); + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame, 11, 12, 13, 14, 15, 16, 17))); +} + +TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithExtension) { + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({1, 2, 3, 4, 5, 6, 7})}); + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'01'0000, // aggregation header + kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit, + kAv1ObuExtensionS1T1, 1, 2, 3, 4, 5, 6, 7))); +} + +TEST(RtpPacketizerAv1Test, OmitsSizeForLastObuWhenThreeObusFitsIntoThePacket) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6}), + Av1Obu(kAv1ObuTypeMetadata).WithPayload({11, 12, 13, 14}), + Av1Obu(kAv1ObuTypeFrame).WithPayload({21, 22, 23, 24, 25, 26})}); + EXPECT_THAT(Packetize(kFrame, {}), + ElementsAre(ElementsAre( + 0b00'11'0000, // aggregation header + 7, kAv1ObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, // + 5, kAv1ObuTypeMetadata, 11, 12, 13, 14, // + kAv1ObuTypeFrame, 21, 22, 23, 24, 25, 26))); +} + +TEST(RtpPacketizerAv1Test, UseSizeForAllObusWhenFourObusFitsIntoThePacket) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6}), + Av1Obu(kAv1ObuTypeMetadata).WithPayload({11, 12, 13, 14}), + Av1Obu(kAv1ObuTypeFrameHeader).WithPayload({21, 22, 23}), + Av1Obu(kAv1ObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})}); + EXPECT_THAT(Packetize(kFrame, {}), + ElementsAre(ElementsAre( + 0b00'00'0000, // aggregation header + 7, kAv1ObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, // + 5, kAv1ObuTypeMetadata, 11, 12, 13, 14, // + 4, kAv1ObuTypeFrameHeader, 21, 22, 23, // + 7, kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36))); +} + +TEST(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeTemporalDelimiter), Av1Obu(kAv1ObuTypeMetadata), + Av1Obu(kAv1ObuTypeTileList).WithPayload({1, 2, 3, 4, 5, 6}), + Av1Obu(kAv1ObuTypeFrameHeader).WithPayload({21, 22, 23}), + Av1Obu(kAv1ObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})}); + + EXPECT_THAT( + Packetize(kFrame, {}), + ElementsAre(ElementsAre(0b00'11'0000, // aggregation header + 1, + kAv1ObuTypeMetadata, // + 4, kAv1ObuTypeFrameHeader, 21, 22, + 23, // + kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36))); +} + +TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPacketForceSplitObuHeader) { + // Craft expected payloads so that there is only one way to split original + // frame into two packets. + const uint8_t kExpectPayload1[6] = { + 0b01'10'0000, // aggregation_header + 3, + kAv1ObuTypeFrameHeader | kAv1ObuExtensionPresentBit, + kAv1ObuExtensionS1T1, + 21, // + kAv1ObuTypeTileGroup | kAv1ObuExtensionPresentBit}; + const uint8_t kExpectPayload2[6] = {0b10'01'0000, // aggregation_header + kAv1ObuExtensionS1T1, 11, 12, 13, 14}; + auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrameHeader) + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({21}), + Av1Obu(kAv1ObuTypeTileGroup) + .WithExtension(kAv1ObuExtensionS1T1) + .WithPayload({11, 12, 13, 14})}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(ElementsAreArray(kExpectPayload1), + ElementsAreArray(kExpectPayload2))); +} + +TEST(RtpPacketizerAv1Test, + SetsNbitAtTheFirstPacketOfAKeyFrameWithSequenceHeader) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6, 7})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto packets = Packetize(kFrame, limits, VideoFrameType::kVideoFrameKey); + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_TRUE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); + EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); +} + +TEST(RtpPacketizerAv1Test, + DoesntSetNbitAtThePacketsOfAKeyFrameWithoutSequenceHeader) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload({1, 2, 3, 4, 5, 6, 7})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto packets = Packetize(kFrame, limits, VideoFrameType::kVideoFrameKey); + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); + EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); +} + +TEST(RtpPacketizerAv1Test, DoesntSetNbitAtThePacketsOfADeltaFrame) { + // Even when that delta frame starts with a (redundant) sequence header. + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6, 7})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 6; + auto packets = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta); + ASSERT_THAT(packets, SizeIs(2)); + EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); + EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); +} + +// There are multiple valid reasonable ways to split payload into multiple +// packets, do not validate current choice, instead use RtpDepacketizer +// to validate frame is reconstracted to the same one. Note: since +// RtpDepacketizer always inserts obu_size fields in the output, use frame where +// each obu has obu_size fields for more streight forward validation. +TEST(RtpPacketizerAv1Test, SplitSingleObuIntoTwoPackets) { + auto kFrame = + BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithPayload({11, 12, 13, 14, 15, 16, 17, 18, 19})}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(8u)), SizeIs(Le(8u)))); + + // Use RtpDepacketizer to validate the split. + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +TEST(RtpPacketizerAv1Test, SplitSingleObuIntoManyPackets) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(1200, 27))}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, SizeIs(13u)); + EXPECT_THAT(payloads, Each(SizeIs(Le(100u)))); + + // Use RtpDepacketizer to validate the split. + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +TEST(RtpPacketizerAv1Test, SetMarkerBitForLastPacketInEndOfPictureFrame) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(200, 27))}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + auto payloads = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta, + /*is_last_frame_in_picture=*/true); + ASSERT_THAT(payloads, SizeIs(3u)); + EXPECT_FALSE(payloads[0].rtp_packet.Marker()); + EXPECT_FALSE(payloads[1].rtp_packet.Marker()); + EXPECT_TRUE(payloads[2].rtp_packet.Marker()); +} + +TEST(RtpPacketizerAv1Test, DoesntSetMarkerBitForPacketsNotInEndOfPictureFrame) { + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(200, 27))}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 100; + auto payloads = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta, + /*is_last_frame_in_picture=*/false); + ASSERT_THAT(payloads, SizeIs(3u)); + EXPECT_FALSE(payloads[0].rtp_packet.Marker()); + EXPECT_FALSE(payloads[1].rtp_packet.Marker()); + EXPECT_FALSE(payloads[2].rtp_packet.Marker()); +} + +TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPackets) { + // 2nd OBU is too large to fit into one packet, so its head would be in the + // same packet as the 1st OBU. + auto kFrame = BuildAv1Frame( + {Av1Obu(kAv1ObuTypeSequenceHeader).WithPayload({11, 12}), + Av1Obu(kAv1ObuTypeFrame).WithPayload({1, 2, 3, 4, 5, 6, 7, 8, 9})}); + + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 8; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(8u)), SizeIs(Le(8u)))); + + // Use RtpDepacketizer to validate the split. + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +TEST(RtpPacketizerAv1Test, + SplitSingleObuIntoTwoPacketsBecauseOfSinglePacketLimit) { + auto kFrame = + BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) + .WithPayload({11, 12, 13, 14, 15, 16, 17, 18, 19})}); + RtpPacketizer::PayloadSizeLimits limits; + limits.max_payload_len = 10; + limits.single_packet_reduction_len = 8; + auto payloads = Packetize(kFrame, limits); + EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(10u)), SizeIs(Le(10u)))); + + EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); +} + +} // namespace +} // namespace webrtc |