diff --git a/experiments/field_trials.py b/experiments/field_trials.py index 9920409a03..6cb0d530ec 100755 --- a/experiments/field_trials.py +++ b/experiments/field_trials.py @@ -128,6 +128,9 @@ ACTIVE_FIELD_TRIALS: FrozenSet[FieldTrial] = frozenset([ FieldTrial('WebRTC-VP8-MaxFrameInterval', 42225870, date(2024, 4, 1)), + FieldTrial('WebRTC-Video-AV1EvenPayloadSizes', + 42226301, + date(2024, 11, 1)), FieldTrial('WebRTC-Video-EnableRetransmitAllLayers', 42225262, date(2024, 4, 1)), diff --git a/modules/rtp_rtcp/source/rtp_format.cc b/modules/rtp_rtcp/source/rtp_format.cc index c7534dee40..f4e051460a 100644 --- a/modules/rtp_rtcp/source/rtp_format.cc +++ b/modules/rtp_rtcp/source/rtp_format.cc @@ -33,7 +33,8 @@ std::unique_ptr RtpPacketizer::Create( rtc::ArrayView payload, PayloadSizeLimits limits, // Codec-specific details. - const RTPVideoHeader& rtp_video_header) { + const RTPVideoHeader& rtp_video_header, + bool enable_av1_even_split) { if (!type) { // Use raw packetizer. return std::make_unique(payload, limits); @@ -59,7 +60,7 @@ std::unique_ptr RtpPacketizer::Create( case kVideoCodecAV1: return std::make_unique( payload, limits, rtp_video_header.frame_type, - rtp_video_header.is_last_frame_in_picture); + rtp_video_header.is_last_frame_in_picture, enable_av1_even_split); #ifdef RTC_ENABLE_H265 case kVideoCodecH265: { return std::make_unique(payload, limits); diff --git a/modules/rtp_rtcp/source/rtp_format.h b/modules/rtp_rtcp/source/rtp_format.h index 19abd3feb2..91b0367db2 100644 --- a/modules/rtp_rtcp/source/rtp_format.h +++ b/modules/rtp_rtcp/source/rtp_format.h @@ -40,7 +40,9 @@ class RtpPacketizer { rtc::ArrayView payload, PayloadSizeLimits limits, // Codec-specific details. - const RTPVideoHeader& rtp_video_header); + const RTPVideoHeader& rtp_video_header, + // TODO(bugs.webrtc.org/15927): remove after rollout. + bool enable_av1_even_split = false); virtual ~RtpPacketizer() = default; diff --git a/modules/rtp_rtcp/source/rtp_packetizer_av1.cc b/modules/rtp_rtcp/source/rtp_packetizer_av1.cc index 859b529a47..ebf109eecb 100644 --- a/modules/rtp_rtcp/source/rtp_packetizer_av1.cc +++ b/modules/rtp_rtcp/source/rtp_packetizer_av1.cc @@ -34,6 +34,12 @@ constexpr int kObuTypeTemporalDelimiter = 2; constexpr int kObuTypeTileList = 8; constexpr int kObuTypePadding = 15; +// Overhead introduced by "even distribution" of packet sizes. +constexpr size_t kBytesOverheadEvenDistribution = 1; +// Experimentally determined minimum amount of potential savings per packet to +// make "even distribution" of packet sizes worthwhile. +constexpr size_t kMinBytesSavedPerPacketWithEvenDistribution = 10; + bool ObuHasExtension(uint8_t obu_header) { return obu_header & 0b0'0000'100; } @@ -65,10 +71,12 @@ int MaxFragmentSize(int remaining_bytes) { RtpPacketizerAv1::RtpPacketizerAv1(rtc::ArrayView payload, RtpPacketizer::PayloadSizeLimits limits, VideoFrameType frame_type, - bool is_last_frame_in_picture) + bool is_last_frame_in_picture, + bool even_distribution) : frame_type_(frame_type), obus_(ParseObus(payload)), - packets_(Packetize(obus_, limits)), + packets_(even_distribution ? PacketizeAboutEqually(obus_, limits) + : Packetize(obus_, limits)), is_last_frame_in_picture_(is_last_frame_in_picture) {} std::vector RtpPacketizerAv1::ParseObus( @@ -291,6 +299,54 @@ std::vector RtpPacketizerAv1::Packetize( return packets; } +std::vector RtpPacketizerAv1::PacketizeAboutEqually( + rtc::ArrayView obus, + PayloadSizeLimits limits) { + std::vector packets = Packetize(obus, limits); + if (packets.size() <= 1) { + return packets; + } + size_t packet_index = 0; + size_t packet_size_left_unused = 0; + for (const auto& packet : packets) { + // Every packet has to have an aggregation header of size + // kAggregationHeaderSize. + int available_bytes = limits.max_payload_len - kAggregationHeaderSize; + + if (packet_index == 0) { + available_bytes -= limits.first_packet_reduction_len; + } else if (packet_index == packets.size() - 1) { + available_bytes -= limits.last_packet_reduction_len; + } + if (available_bytes >= packet.packet_size) { + packet_size_left_unused += (available_bytes - packet.packet_size); + } + packet_index++; + } + if (packet_size_left_unused > + packets.size() * kMinBytesSavedPerPacketWithEvenDistribution) { + // Calculate new limits with a reduced max_payload_len. + size_t size_reduction = packet_size_left_unused / packets.size(); + RTC_DCHECK_GT(limits.max_payload_len, size_reduction); + RTC_DCHECK_GT(size_reduction, kBytesOverheadEvenDistribution); + limits.max_payload_len -= (size_reduction - kBytesOverheadEvenDistribution); + if (limits.max_payload_len - limits.last_packet_reduction_len < 3 || + limits.max_payload_len - limits.first_packet_reduction_len < 3) { + return packets; + } + std::vector packets_even = Packetize(obus, limits); + // The number of packets should not change in the second pass. If it does, + // conservatively return the original packets. + if (packets_even.size() == packets.size()) { + return packets_even; + } + RTC_LOG(LS_WARNING) << "AV1 even distribution caused a regression in " + "number of packets from " + << packets.size() << " to " << packets_even.size(); + } + return packets; +} + uint8_t RtpPacketizerAv1::AggregationHeader() const { const Packet& packet = packets_[packet_index_]; uint8_t aggregation_header = 0; diff --git a/modules/rtp_rtcp/source/rtp_packetizer_av1.h b/modules/rtp_rtcp/source/rtp_packetizer_av1.h index 520e746eac..79f9b323d2 100644 --- a/modules/rtp_rtcp/source/rtp_packetizer_av1.h +++ b/modules/rtp_rtcp/source/rtp_packetizer_av1.h @@ -27,7 +27,8 @@ class RtpPacketizerAv1 : public RtpPacketizer { RtpPacketizerAv1(rtc::ArrayView payload, PayloadSizeLimits limits, VideoFrameType frame_type, - bool is_last_frame_in_picture); + bool is_last_frame_in_picture, + bool even_distribution); ~RtpPacketizerAv1() override = default; size_t NumPackets() const override { return packets_.size() - packet_index_; } @@ -57,8 +58,13 @@ class RtpPacketizerAv1 : public RtpPacketizer { // Returns the number of additional bytes needed to store the previous OBU // element if an additonal OBU element is added to the packet. static int AdditionalBytesForPreviousObuElement(const Packet& packet); + // Packetize and try to distribute the payload evenly across packets. + static std::vector PacketizeAboutEqually( + rtc::ArrayView obus, + PayloadSizeLimits limits); static std::vector Packetize(rtc::ArrayView obus, PayloadSizeLimits limits); + uint8_t AggregationHeader() const; const VideoFrameType frame_type_; diff --git a/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc b/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc index 83a2be24ea..509f6a2dd1 100644 --- a/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc @@ -78,11 +78,12 @@ class Av1Frame { std::vector Packetize( rtc::ArrayView payload, RtpPacketizer::PayloadSizeLimits limits, + bool even_distribution, 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); + is_last_frame_in_picture, even_distribution); // Convert result into structure that is easier to run expectation against. std::vector result(packetizer.NumPackets()); for (RtpPayload& rtp_payload : result) { @@ -99,59 +100,63 @@ Av1Frame ReassembleFrame(rtc::ArrayView rtp_payloads) { return Av1Frame(VideoRtpDepacketizerAv1().AssembleFrame(payloads)); } -TEST(RtpPacketizerAv1Test, EmptyPayload) { +class RtpPacketizerAv1Test : public ::testing::TestWithParam {}; + +TEST_P(RtpPacketizerAv1Test, EmptyPayload) { RtpPacketizer::PayloadSizeLimits limits; - RtpPacketizerAv1 packetizer({}, limits, VideoFrameType::kVideoFrameKey, true); + RtpPacketizerAv1 packetizer({}, limits, VideoFrameType::kVideoFrameKey, true, + GetParam()); EXPECT_EQ(packetizer.NumPackets(), 0u); } -TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) { +TEST_P(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) { auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) .WithoutSize() .WithPayload({1, 2, 3, 4, 5, 6, 7})}); - EXPECT_THAT(Packetize(kFrame, {}), + EXPECT_THAT(Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre(0b00'01'0000, // aggregation header kAv1ObuTypeFrame, 1, 2, 3, 4, 5, 6, 7))); } -TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeWithExtension) { +TEST_P(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeWithExtension) { auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) .WithoutSize() .WithExtension(kAv1ObuExtensionS1T1) .WithPayload({2, 3, 4, 5, 6, 7})}); EXPECT_THAT( - Packetize(kFrame, {}), + Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre(0b00'01'0000, // aggregation header kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit, kAv1ObuExtensionS1T1, 2, 3, 4, 5, 6, 7))); } -TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithoutExtension) { +TEST_P(RtpPacketizerAv1Test, RemovesObuSizeFieldWithoutExtension) { auto kFrame = BuildAv1Frame( {Av1Obu(kAv1ObuTypeFrame).WithPayload({11, 12, 13, 14, 15, 16, 17})}); EXPECT_THAT( - Packetize(kFrame, {}), + Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre(0b00'01'0000, // aggregation header kAv1ObuTypeFrame, 11, 12, 13, 14, 15, 16, 17))); } -TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithExtension) { +TEST_P(RtpPacketizerAv1Test, RemovesObuSizeFieldWithExtension) { auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame) .WithExtension(kAv1ObuExtensionS1T1) .WithPayload({1, 2, 3, 4, 5, 6, 7})}); EXPECT_THAT( - Packetize(kFrame, {}), + Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre(0b00'01'0000, // aggregation header kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit, kAv1ObuExtensionS1T1, 1, 2, 3, 4, 5, 6, 7))); } -TEST(RtpPacketizerAv1Test, OmitsSizeForLastObuWhenThreeObusFitsIntoThePacket) { +TEST_P(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, {}), + EXPECT_THAT(Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre( 0b00'11'0000, // aggregation header 7, kAv1ObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, // @@ -159,13 +164,13 @@ TEST(RtpPacketizerAv1Test, OmitsSizeForLastObuWhenThreeObusFitsIntoThePacket) { kAv1ObuTypeFrame, 21, 22, 23, 24, 25, 26))); } -TEST(RtpPacketizerAv1Test, UseSizeForAllObusWhenFourObusFitsIntoThePacket) { +TEST_P(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, {}), + EXPECT_THAT(Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre( 0b00'00'0000, // aggregation header 7, kAv1ObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, // @@ -174,7 +179,7 @@ TEST(RtpPacketizerAv1Test, UseSizeForAllObusWhenFourObusFitsIntoThePacket) { 7, kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36))); } -TEST(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) { +TEST_P(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) { auto kFrame = BuildAv1Frame( {Av1Obu(kAv1ObuTypeTemporalDelimiter), Av1Obu(kAv1ObuTypeMetadata), Av1Obu(kAv1ObuTypeTileList).WithPayload({1, 2, 3, 4, 5, 6}), @@ -182,7 +187,7 @@ TEST(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) { Av1Obu(kAv1ObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})}); EXPECT_THAT( - Packetize(kFrame, {}), + Packetize(kFrame, {}, GetParam()), ElementsAre(ElementsAre(0b00'11'0000, // aggregation header 1, kAv1ObuTypeMetadata, // @@ -191,7 +196,7 @@ TEST(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) { kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36))); } -TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPacketForceSplitObuHeader) { +TEST_P(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPacketForceSplitObuHeader) { // Craft expected payloads so that there is only one way to split original // frame into two packets. const uint8_t kExpectPayload1[6] = { @@ -212,42 +217,45 @@ TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPacketForceSplitObuHeader) { RtpPacketizer::PayloadSizeLimits limits; limits.max_payload_len = 6; - auto payloads = Packetize(kFrame, limits); + auto payloads = Packetize(kFrame, limits, GetParam()); EXPECT_THAT(payloads, ElementsAre(ElementsAreArray(kExpectPayload1), ElementsAreArray(kExpectPayload2))); } -TEST(RtpPacketizerAv1Test, - SetsNbitAtTheFirstPacketOfAKeyFrameWithSequenceHeader) { +TEST_P(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); + auto packets = + Packetize(kFrame, limits, GetParam(), VideoFrameType::kVideoFrameKey); ASSERT_THAT(packets, SizeIs(2)); EXPECT_TRUE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); } -TEST(RtpPacketizerAv1Test, - DoesntSetNbitAtThePacketsOfAKeyFrameWithoutSequenceHeader) { +TEST_P(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); + auto packets = + Packetize(kFrame, limits, GetParam(), VideoFrameType::kVideoFrameKey); ASSERT_THAT(packets, SizeIs(2)); EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); } -TEST(RtpPacketizerAv1Test, DoesntSetNbitAtThePacketsOfADeltaFrame) { +TEST_P(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); + auto packets = + Packetize(kFrame, limits, GetParam(), VideoFrameType::kVideoFrameDelta); ASSERT_THAT(packets, SizeIs(2)); EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit); EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit); @@ -258,27 +266,27 @@ TEST(RtpPacketizerAv1Test, DoesntSetNbitAtThePacketsOfADeltaFrame) { // 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) { +TEST_P(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); + auto payloads = Packetize(kFrame, limits, GetParam()); 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) { +TEST_P(RtpPacketizerAv1Test, SplitSingleObuIntoManyPackets) { auto kFrame = BuildAv1Frame( {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector(1200, 27))}); RtpPacketizer::PayloadSizeLimits limits; limits.max_payload_len = 100; - auto payloads = Packetize(kFrame, limits); + auto payloads = Packetize(kFrame, limits, GetParam()); EXPECT_THAT(payloads, SizeIs(13u)); EXPECT_THAT(payloads, Each(SizeIs(Le(100u)))); @@ -286,35 +294,38 @@ TEST(RtpPacketizerAv1Test, SplitSingleObuIntoManyPackets) { EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); } -TEST(RtpPacketizerAv1Test, SetMarkerBitForLastPacketInEndOfPictureFrame) { +TEST_P(RtpPacketizerAv1Test, SetMarkerBitForLastPacketInEndOfPictureFrame) { auto kFrame = BuildAv1Frame( {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector(200, 27))}); RtpPacketizer::PayloadSizeLimits limits; limits.max_payload_len = 100; - auto payloads = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta, - /*is_last_frame_in_picture=*/true); + auto payloads = + Packetize(kFrame, limits, GetParam(), 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) { +TEST_P(RtpPacketizerAv1Test, + DoesntSetMarkerBitForPacketsNotInEndOfPictureFrame) { auto kFrame = BuildAv1Frame( {Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector(200, 27))}); RtpPacketizer::PayloadSizeLimits limits; limits.max_payload_len = 100; - auto payloads = Packetize(kFrame, limits, VideoFrameType::kVideoFrameDelta, - /*is_last_frame_in_picture=*/false); + auto payloads = + Packetize(kFrame, limits, GetParam(), 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) { +TEST_P(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( @@ -323,26 +334,43 @@ TEST(RtpPacketizerAv1Test, SplitTwoObusIntoTwoPackets) { RtpPacketizer::PayloadSizeLimits limits; limits.max_payload_len = 8; - auto payloads = Packetize(kFrame, limits); + auto payloads = Packetize(kFrame, limits, GetParam()); 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) { +TEST_P(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); + auto payloads = Packetize(kFrame, limits, GetParam()); EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(10u)), SizeIs(Le(10u)))); EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame)); } +INSTANTIATE_TEST_SUITE_P(bool, RtpPacketizerAv1Test, ::testing::Bool()); + +TEST(RtpPacketizerAv1TestEven, EvenDistributionDiffers) { + auto kFrame = BuildAv1Frame({ + Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector(1206, 0)), + Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector(1476, 0)), + Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector(1431, 0)), + }); + EXPECT_THAT( + Packetize(kFrame, {}, /*even_distribution=*/false), + ElementsAre(SizeIs(1200), SizeIs(1200), SizeIs(1200), SizeIs(523))); + + EXPECT_THAT( + Packetize(kFrame, {}, /*even_distribution=*/true), + ElementsAre(SizeIs(1032), SizeIs(1032), SizeIs(1032), SizeIs(1028))); +} + } // namespace } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_sender_video.cc b/modules/rtp_rtcp/source/rtp_sender_video.cc index e02d18cc46..c60305888b 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.cc +++ b/modules/rtp_rtcp/source/rtp_sender_video.cc @@ -158,7 +158,9 @@ RTPSenderVideo::RTPSenderVideo(const Config& config) config.frame_transformer, rtp_sender_->SSRC(), config.task_queue_factory) - : nullptr) { + : nullptr), + enable_av1_even_split_( + config.field_trials->IsEnabled("WebRTC-Video-AV1EvenPayloadSizes")) { if (frame_transformer_delegate_) frame_transformer_delegate_->Init(); } @@ -638,8 +640,8 @@ bool RTPSenderVideo::SendVideo(int payload_type, "one is required since require_frame_encryptor is set"; } - std::unique_ptr packetizer = - RtpPacketizer::Create(codec_type, payload, limits, video_header); + std::unique_ptr packetizer = RtpPacketizer::Create( + codec_type, payload, limits, video_header, enable_av1_even_split_); const size_t num_packets = packetizer->NumPackets(); diff --git a/modules/rtp_rtcp/source/rtp_sender_video.h b/modules/rtp_rtcp/source/rtp_sender_video.h index 9400b436d3..fa5f3b6f0c 100644 --- a/modules/rtp_rtcp/source/rtp_sender_video.h +++ b/modules/rtp_rtcp/source/rtp_sender_video.h @@ -252,6 +252,10 @@ class RTPSenderVideo : public RTPVideoFrameSenderInterface { const rtc::scoped_refptr frame_transformer_delegate_; + + // Whether to do two-pass packetization for AV1 which leads to a set of + // packets with more even size distribution. + const bool enable_av1_even_split_; }; } // namespace webrtc diff --git a/test/fuzzers/rtp_packetizer_av1_fuzzer.cc b/test/fuzzers/rtp_packetizer_av1_fuzzer.cc index e5550c1279..b0c006db13 100644 --- a/test/fuzzers/rtp_packetizer_av1_fuzzer.cc +++ b/test/fuzzers/rtp_packetizer_av1_fuzzer.cc @@ -34,9 +34,12 @@ void FuzzOneInput(const uint8_t* data, size_t size) { VideoFrameType frame_type = fuzz_input.SelectOneOf(kFrameTypes); // Main function under test: RtpPacketizerAv1's constructor. + // "even distribution" is transitional and still exercises the other code path + // so does not require another fuzzer. RtpPacketizerAv1 packetizer(fuzz_input.ReadByteArray(fuzz_input.BytesLeft()), limits, frame_type, - /*is_last_frame_in_picture=*/true); + /*is_last_frame_in_picture=*/true, + /*even_distribution=*/true); size_t num_packets = packetizer.NumPackets(); if (num_packets == 0) {