mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-12 21:30:45 +01:00
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:
parent
1f3679884c
commit
acfd279a14
9 changed files with 157 additions and 52 deletions
|
@ -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)),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_;
|
||||
|
|
|
@ -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,
|
||||
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<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,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<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);
|
||||
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<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);
|
||||
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<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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue