blob: 509f6a2dd17c45454d94e659ac1cd4e40f6a993e [file] [log] [blame]
/*
* 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,
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, even_distribution);
// 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));
}
class RtpPacketizerAv1Test : public ::testing::TestWithParam<bool> {};
TEST_P(RtpPacketizerAv1Test, EmptyPayload) {
RtpPacketizer::PayloadSizeLimits limits;
RtpPacketizerAv1 packetizer({}, limits, VideoFrameType::kVideoFrameKey, true,
GetParam());
EXPECT_EQ(packetizer.NumPackets(), 0u);
}
TEST_P(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) {
auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame)
.WithoutSize()
.WithPayload({1, 2, 3, 4, 5, 6, 7})});
EXPECT_THAT(Packetize(kFrame, {}, GetParam()),
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
kAv1ObuTypeFrame, 1, 2, 3, 4, 5, 6, 7)));
}
TEST_P(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeWithExtension) {
auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame)
.WithoutSize()
.WithExtension(kAv1ObuExtensionS1T1)
.WithPayload({2, 3, 4, 5, 6, 7})});
EXPECT_THAT(
Packetize(kFrame, {}, GetParam()),
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit,
kAv1ObuExtensionS1T1, 2, 3, 4, 5, 6, 7)));
}
TEST_P(RtpPacketizerAv1Test, RemovesObuSizeFieldWithoutExtension) {
auto kFrame = BuildAv1Frame(
{Av1Obu(kAv1ObuTypeFrame).WithPayload({11, 12, 13, 14, 15, 16, 17})});
EXPECT_THAT(
Packetize(kFrame, {}, GetParam()),
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
kAv1ObuTypeFrame, 11, 12, 13, 14, 15, 16, 17)));
}
TEST_P(RtpPacketizerAv1Test, RemovesObuSizeFieldWithExtension) {
auto kFrame = BuildAv1Frame({Av1Obu(kAv1ObuTypeFrame)
.WithExtension(kAv1ObuExtensionS1T1)
.WithPayload({1, 2, 3, 4, 5, 6, 7})});
EXPECT_THAT(
Packetize(kFrame, {}, GetParam()),
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
kAv1ObuTypeFrame | kAv1ObuExtensionPresentBit,
kAv1ObuExtensionS1T1, 1, 2, 3, 4, 5, 6, 7)));
}
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, {}, GetParam()),
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_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, {}, GetParam()),
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_P(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, {}, GetParam()),
ElementsAre(ElementsAre(0b00'11'0000, // aggregation header
1,
kAv1ObuTypeMetadata, //
4, kAv1ObuTypeFrameHeader, 21, 22,
23, //
kAv1ObuTypeTileGroup, 31, 32, 33, 34, 35, 36)));
}
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] = {
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, GetParam());
EXPECT_THAT(payloads, ElementsAre(ElementsAreArray(kExpectPayload1),
ElementsAreArray(kExpectPayload2)));
}
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, GetParam(), VideoFrameType::kVideoFrameKey);
ASSERT_THAT(packets, SizeIs(2));
EXPECT_TRUE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit);
EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit);
}
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, GetParam(), VideoFrameType::kVideoFrameKey);
ASSERT_THAT(packets, SizeIs(2));
EXPECT_FALSE(packets[0].aggregation_header() & kNewCodedVideoSequenceBit);
EXPECT_FALSE(packets[1].aggregation_header() & kNewCodedVideoSequenceBit);
}
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, GetParam(), 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_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, GetParam());
EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(8u)), SizeIs(Le(8u))));
// Use RtpDepacketizer to validate the split.
EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame));
}
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, GetParam());
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_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, 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_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, 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_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(
{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, GetParam());
EXPECT_THAT(payloads, ElementsAre(SizeIs(Le(8u)), SizeIs(Le(8u))));
// Use RtpDepacketizer to validate the split.
EXPECT_THAT(ReassembleFrame(payloads), ElementsAreArray(kFrame));
}
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, 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