Implement AV1 RtpPacketizer class
Bug: webrtc:11042 Change-Id: Id1fc0acfa87a4520344f2636f50cb4d4e7284829 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160416 Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Reviewed-by: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29947}
This commit is contained in:
committed by
Commit Bot
parent
4314a494cf
commit
096a46f38f
365
modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc
Normal file
365
modules/rtp_rtcp/source/rtp_packetizer_av1_unittest.cc
Normal file
@ -0,0 +1,365 @@
|
||||
/*
|
||||
* 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_depacketizer_av1.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_packet_to_send.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;
|
||||
// All obu types offset by 3 to take correct position in the obu_header.
|
||||
constexpr uint8_t kObuTypeSequenceHeader = 1 << 3;
|
||||
constexpr uint8_t kObuTypeTemporalDelimiter = 2 << 3;
|
||||
constexpr uint8_t kObuTypeFrameHeader = 3 << 3;
|
||||
constexpr uint8_t kObuTypeTileGroup = 4 << 3;
|
||||
constexpr uint8_t kObuTypeMetadata = 5 << 3;
|
||||
constexpr uint8_t kObuTypeFrame = 6 << 3;
|
||||
constexpr uint8_t kObuTypeTileList = 8 << 3;
|
||||
constexpr uint8_t kObuExtensionPresentBit = 0b0'0000'100;
|
||||
constexpr uint8_t kObuSizePresentBit = 0b0'0000'010;
|
||||
constexpr uint8_t kObuExtensionS1T1 = 0b001'01'000;
|
||||
|
||||
// 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) {
|
||||
// Run code under test.
|
||||
RtpPacketizerAv1 packetizer(payload, limits, frame_type);
|
||||
// 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(RtpDepacketizerAv1::AssembleFrame(payloads));
|
||||
}
|
||||
|
||||
class Obu {
|
||||
public:
|
||||
explicit Obu(uint8_t obu_type) : header_(obu_type | kObuSizePresentBit) {
|
||||
EXPECT_EQ(obu_type & 0b0'1111'000, obu_type);
|
||||
}
|
||||
|
||||
Obu& WithExtension(uint8_t extension) {
|
||||
extension_ = extension;
|
||||
header_ |= kObuExtensionPresentBit;
|
||||
return *this;
|
||||
}
|
||||
Obu& WithoutSize() {
|
||||
header_ &= ~kObuSizePresentBit;
|
||||
return *this;
|
||||
}
|
||||
Obu& WithPayload(std::vector<uint8_t> payload) {
|
||||
payload_ = std::move(payload);
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
friend std::vector<uint8_t> BuildAv1Frame(std::initializer_list<Obu> obus);
|
||||
uint8_t header_;
|
||||
uint8_t extension_ = 0;
|
||||
std::vector<uint8_t> payload_;
|
||||
};
|
||||
|
||||
std::vector<uint8_t> BuildAv1Frame(std::initializer_list<Obu> obus) {
|
||||
std::vector<uint8_t> raw;
|
||||
for (const Obu& obu : obus) {
|
||||
raw.push_back(obu.header_);
|
||||
if (obu.header_ & kObuExtensionPresentBit) {
|
||||
raw.push_back(obu.extension_);
|
||||
}
|
||||
if (obu.header_ & kObuSizePresentBit) {
|
||||
// write size in leb128 format.
|
||||
size_t payload_size = obu.payload_.size();
|
||||
while (payload_size >= 0x80) {
|
||||
raw.push_back(0x80 | (payload_size & 0x7F));
|
||||
payload_size >>= 7;
|
||||
}
|
||||
raw.push_back(payload_size);
|
||||
}
|
||||
raw.insert(raw.end(), obu.payload_.begin(), obu.payload_.end());
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeAndExtension) {
|
||||
auto kFrame = BuildAv1Frame(
|
||||
{Obu(kObuTypeFrame).WithoutSize().WithPayload({1, 2, 3, 4, 5, 6, 7})});
|
||||
EXPECT_THAT(Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
|
||||
kObuTypeFrame, 1, 2, 3, 4, 5, 6, 7)));
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, PacketizeOneObuWithoutSizeWithExtension) {
|
||||
auto kFrame = BuildAv1Frame({Obu(kObuTypeFrame)
|
||||
.WithoutSize()
|
||||
.WithExtension(kObuExtensionS1T1)
|
||||
.WithPayload({2, 3, 4, 5, 6, 7})});
|
||||
EXPECT_THAT(Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
|
||||
kObuTypeFrame | kObuExtensionPresentBit,
|
||||
kObuExtensionS1T1, 2, 3, 4, 5, 6, 7)));
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithoutExtension) {
|
||||
auto kFrame = BuildAv1Frame(
|
||||
{Obu(kObuTypeFrame).WithPayload({11, 12, 13, 14, 15, 16, 17})});
|
||||
EXPECT_THAT(
|
||||
Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
|
||||
kObuTypeFrame, 11, 12, 13, 14, 15, 16, 17)));
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, RemovesObuSizeFieldWithExtension) {
|
||||
auto kFrame = BuildAv1Frame({Obu(kObuTypeFrame)
|
||||
.WithExtension(kObuExtensionS1T1)
|
||||
.WithPayload({1, 2, 3, 4, 5, 6, 7})});
|
||||
EXPECT_THAT(Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'01'0000, // aggregation header
|
||||
kObuTypeFrame | kObuExtensionPresentBit,
|
||||
kObuExtensionS1T1, 1, 2, 3, 4, 5, 6, 7)));
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, OmitsSizeForLastObuWhenThreeObusFitsIntoThePacket) {
|
||||
auto kFrame = BuildAv1Frame(
|
||||
{Obu(kObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6}),
|
||||
Obu(kObuTypeMetadata).WithPayload({11, 12, 13, 14}),
|
||||
Obu(kObuTypeFrame).WithPayload({21, 22, 23, 24, 25, 26})});
|
||||
EXPECT_THAT(
|
||||
Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'11'0000, // aggregation header
|
||||
7, kObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, //
|
||||
5, kObuTypeMetadata, 11, 12, 13, 14, //
|
||||
kObuTypeFrame, 21, 22, 23, 24, 25, 26)));
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, UseSizeForAllObusWhenFourObusFitsIntoThePacket) {
|
||||
auto kFrame = BuildAv1Frame(
|
||||
{Obu(kObuTypeSequenceHeader).WithPayload({1, 2, 3, 4, 5, 6}),
|
||||
Obu(kObuTypeMetadata).WithPayload({11, 12, 13, 14}),
|
||||
Obu(kObuTypeFrameHeader).WithPayload({21, 22, 23}),
|
||||
Obu(kObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})});
|
||||
EXPECT_THAT(
|
||||
Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'00'0000, // aggregation header
|
||||
7, kObuTypeSequenceHeader, 1, 2, 3, 4, 5, 6, //
|
||||
5, kObuTypeMetadata, 11, 12, 13, 14, //
|
||||
4, kObuTypeFrameHeader, 21, 22, 23, //
|
||||
7, kObuTypeTileGroup, 31, 32, 33, 34, 35, 36)));
|
||||
}
|
||||
|
||||
TEST(RtpPacketizerAv1Test, DiscardsTemporalDelimiterAndTileListObu) {
|
||||
auto kFrame = BuildAv1Frame(
|
||||
{Obu(kObuTypeTemporalDelimiter), Obu(kObuTypeMetadata),
|
||||
Obu(kObuTypeTileList).WithPayload({1, 2, 3, 4, 5, 6}),
|
||||
Obu(kObuTypeFrameHeader).WithPayload({21, 22, 23}),
|
||||
Obu(kObuTypeTileGroup).WithPayload({31, 32, 33, 34, 35, 36})});
|
||||
|
||||
EXPECT_THAT(
|
||||
Packetize(kFrame, {}),
|
||||
ElementsAre(ElementsAre(0b00'11'0000, // aggregation header
|
||||
1,
|
||||
kObuTypeMetadata, //
|
||||
4, kObuTypeFrameHeader, 21, 22,
|
||||
23, //
|
||||
kObuTypeTileGroup, 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,
|
||||
kObuTypeFrameHeader | kObuExtensionPresentBit,
|
||||
kObuExtensionS1T1,
|
||||
21, //
|
||||
kObuTypeTileGroup | kObuExtensionPresentBit};
|
||||
const uint8_t kExpectPayload2[6] = {0b10'01'0000, // aggregation_header
|
||||
kObuExtensionS1T1, 11, 12, 13, 14};
|
||||
auto kFrame = BuildAv1Frame({Obu(kObuTypeFrameHeader)
|
||||
.WithExtension(kObuExtensionS1T1)
|
||||
.WithPayload({21}),
|
||||
Obu(kObuTypeTileGroup)
|
||||
.WithExtension(kObuExtensionS1T1)
|
||||
.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(
|
||||
{Obu(kObuTypeSequenceHeader).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({Obu(kObuTypeFrame).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(
|
||||
{Obu(kObuTypeSequenceHeader).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(
|
||||
{Obu(kObuTypeFrame).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(
|
||||
{Obu(kObuTypeFrame).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, 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(
|
||||
{Obu(kObuTypeSequenceHeader).WithPayload({11, 12}),
|
||||
Obu(kObuTypeFrame).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(
|
||||
{Obu(kObuTypeFrame).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
|
||||
Reference in New Issue
Block a user