av1: make packetization generate more evenly sized packets

Implements a two-pass approach to packetization which creates
packets of an even size similar to RtpPacketizer::SplitAboutEqually.
This improves the bandwidth estimation.

The algorithm does a first pass with the existing packetizer, then
iterates through the resulting packet sizes and sums up the bytes left unused in each packet.
It then calculates a new maximum packet length as
  configured_max_packet_len - ((unused_bytes - packets + 1) / packets)
adjusts for the overhead and re-runs the packetization algorithm.

For example, a list of OBUs with sizes
  {1206, 1476, 1431}
currently gets packetized greedily as payload sizes
  {1200, 1200, 1200, 523}
With this change, it gets packetized as
  {1032, 1032, 1032, 1028}

This change is guarded by the field trial
  WebRTC-Video-AV1EvenPayloadSizes
which is acting as a rollout flag.

BUG=webrtc:15927

Co-authored-by: Shyam Sadhwani <shyamsadhwani@meta.com>
Change-Id: I4f0b3c27de6f06104908dd769c4dd1f34115712c
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/348100
Commit-Queue: Philipp Hancke <phancke@meta.com>
Reviewed-by: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Per Kjellander <perkj@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42203}
This commit is contained in:
Philipp Hancke 2024-04-30 07:09:37 -07:00 committed by WebRTC LUCI CQ
parent 1f3679884c
commit acfd279a14
9 changed files with 157 additions and 52 deletions

View file

@ -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)),

View file

@ -33,7 +33,8 @@ std::unique_ptr<RtpPacketizer> RtpPacketizer::Create(
rtc::ArrayView<const uint8_t> 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<RtpPacketizerGeneric>(payload, limits);
@ -59,7 +60,7 @@ std::unique_ptr<RtpPacketizer> RtpPacketizer::Create(
case kVideoCodecAV1:
return std::make_unique<RtpPacketizerAv1>(
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<RtpPacketizerH265>(payload, limits);

View file

@ -40,7 +40,9 @@ class RtpPacketizer {
rtc::ArrayView<const uint8_t> 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;

View file

@ -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<const uint8_t> 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::Obu> RtpPacketizerAv1::ParseObus(
@ -291,6 +299,54 @@ std::vector<RtpPacketizerAv1::Packet> RtpPacketizerAv1::Packetize(
return packets;
}
std::vector<RtpPacketizerAv1::Packet> RtpPacketizerAv1::PacketizeAboutEqually(
rtc::ArrayView<const Obu> obus,
PayloadSizeLimits limits) {
std::vector<Packet> 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<Packet> 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;

View file

@ -27,7 +27,8 @@ class RtpPacketizerAv1 : public RtpPacketizer {
RtpPacketizerAv1(rtc::ArrayView<const uint8_t> 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<Packet> PacketizeAboutEqually(
rtc::ArrayView<const Obu> obus,
PayloadSizeLimits limits);
static std::vector<Packet> Packetize(rtc::ArrayView<const Obu> obus,
PayloadSizeLimits limits);
uint8_t AggregationHeader() const;
const VideoFrameType frame_type_;

View file

@ -78,11 +78,12 @@ class Av1Frame {
std::vector<RtpPayload> Packetize(
rtc::ArrayView<const uint8_t> 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<RtpPayload> result(packetizer.NumPackets());
for (RtpPayload& rtp_payload : result) {
@ -99,59 +100,63 @@ Av1Frame ReassembleFrame(rtc::ArrayView<const RtpPayload> rtp_payloads) {
return Av1Frame(VideoRtpDepacketizerAv1().AssembleFrame(payloads));
}
TEST(RtpPacketizerAv1Test, EmptyPayload) {
class RtpPacketizerAv1Test : public ::testing::TestWithParam<bool> {};
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,
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,
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<uint8_t>(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,13 +294,14 @@ TEST(RtpPacketizerAv1Test, SplitSingleObuIntoManyPackets) {
EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame));
}
TEST(RtpPacketizerAv1Test, SetMarkerBitForLastPacketInEndOfPictureFrame) {
TEST_P(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,
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());
@ -300,13 +309,15 @@ TEST(RtpPacketizerAv1Test, SetMarkerBitForLastPacketInEndOfPictureFrame) {
EXPECT_TRUE(payloads[2].rtp_packet.Marker());
}
TEST(RtpPacketizerAv1Test, DoesntSetMarkerBitForPacketsNotInEndOfPictureFrame) {
TEST_P(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,
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());
@ -314,7 +325,7 @@ TEST(RtpPacketizerAv1Test, DoesntSetMarkerBitForPacketsNotInEndOfPictureFrame) {
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,14 +334,14 @@ 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,
TEST_P(RtpPacketizerAv1Test,
SplitSingleObuIntoTwoPacketsBecauseOfSinglePacketLimit) {
auto kFrame =
BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame)
@ -338,11 +349,28 @@ TEST(RtpPacketizerAv1Test,
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<uint8_t>(1206, 0)),
Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(1476, 0)),
Av1Obu(kAv1ObuTypeFrame).WithPayload(std::vector<uint8_t>(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

View file

@ -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<RtpPacketizer> packetizer =
RtpPacketizer::Create(codec_type, payload, limits, video_header);
std::unique_ptr<RtpPacketizer> packetizer = RtpPacketizer::Create(
codec_type, payload, limits, video_header, enable_av1_even_split_);
const size_t num_packets = packetizer->NumPackets();

View file

@ -252,6 +252,10 @@ class RTPSenderVideo : public RTPVideoFrameSenderInterface {
const rtc::scoped_refptr<RTPSenderVideoFrameTransformerDelegate>
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

View file

@ -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) {