Add RtpDepacketizerAv1::AssembleFrame function
Bug: webrtc:11042 Change-Id: I677fc6a9affacf3b7c80adc2c3493c16806db1f6 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160003 Commit-Queue: Danil Chapovalov <danilchap@webrtc.org> Reviewed-by: Philip Eliasson <philipel@webrtc.org> Cr-Commit-Position: refs/heads/master@{#29862}
This commit is contained in:
parent
ad020f5a5c
commit
038fd99780
@ -240,6 +240,7 @@ rtc_library("rtp_rtcp") {
|
||||
"../../api/units:data_rate",
|
||||
"../../api/units:time_delta",
|
||||
"../../api/units:timestamp",
|
||||
"../../api/video:encoded_image",
|
||||
"../../api/video:video_bitrate_allocation",
|
||||
"../../api/video:video_bitrate_allocator",
|
||||
"../../api/video:video_codec_constants",
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
#include "rtc_base/byte_buffer.h"
|
||||
#include "rtc_base/checks.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/numerics/safe_conversions.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
@ -58,7 +59,111 @@ namespace {
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// | OBU payload |
|
||||
// | ... |
|
||||
class ArrayOfArrayViews {
|
||||
public:
|
||||
class const_iterator;
|
||||
ArrayOfArrayViews() = default;
|
||||
ArrayOfArrayViews(const ArrayOfArrayViews&) = default;
|
||||
ArrayOfArrayViews& operator=(const ArrayOfArrayViews&) = default;
|
||||
~ArrayOfArrayViews() = default;
|
||||
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
bool empty() const { return data_.empty(); }
|
||||
size_t size() const { return size_; }
|
||||
void CopyTo(uint8_t* destination, const_iterator first) const;
|
||||
|
||||
void Append(const uint8_t* data, size_t size) {
|
||||
data_.emplace_back(data, size);
|
||||
size_ += size;
|
||||
}
|
||||
|
||||
private:
|
||||
using Storage = absl::InlinedVector<rtc::ArrayView<const uint8_t>, 2>;
|
||||
|
||||
size_t size_ = 0;
|
||||
Storage data_;
|
||||
};
|
||||
|
||||
class ArrayOfArrayViews::const_iterator {
|
||||
public:
|
||||
const_iterator() = default;
|
||||
const_iterator(const const_iterator&) = default;
|
||||
const_iterator& operator=(const const_iterator&) = default;
|
||||
|
||||
const_iterator& operator++() {
|
||||
if (++inner_ == outer_->size()) {
|
||||
++outer_;
|
||||
inner_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
uint8_t operator*() const { return (*outer_)[inner_]; }
|
||||
|
||||
friend bool operator==(const const_iterator& lhs, const const_iterator& rhs) {
|
||||
return lhs.outer_ == rhs.outer_ && lhs.inner_ == rhs.inner_;
|
||||
}
|
||||
|
||||
private:
|
||||
friend ArrayOfArrayViews;
|
||||
const_iterator(ArrayOfArrayViews::Storage::const_iterator outer, size_t inner)
|
||||
: outer_(outer), inner_(inner) {}
|
||||
|
||||
Storage::const_iterator outer_;
|
||||
size_t inner_;
|
||||
};
|
||||
|
||||
ArrayOfArrayViews::const_iterator ArrayOfArrayViews::begin() const {
|
||||
return const_iterator(data_.begin(), 0);
|
||||
}
|
||||
|
||||
ArrayOfArrayViews::const_iterator ArrayOfArrayViews::end() const {
|
||||
return const_iterator(data_.end(), 0);
|
||||
}
|
||||
|
||||
void ArrayOfArrayViews::CopyTo(uint8_t* destination,
|
||||
const_iterator first) const {
|
||||
if (first == end()) {
|
||||
// Empty OBU payload. E.g. Temporal Delimiters are always empty.
|
||||
return;
|
||||
}
|
||||
size_t first_chunk_size = first.outer_->size() - first.inner_;
|
||||
memcpy(destination, first.outer_->data() + first.inner_, first_chunk_size);
|
||||
destination += first_chunk_size;
|
||||
for (auto it = std::next(first.outer_); it != data_.end(); ++it) {
|
||||
memcpy(destination, it->data(), it->size());
|
||||
destination += it->size();
|
||||
}
|
||||
}
|
||||
|
||||
struct ObuInfo {
|
||||
// Size of the obu_header and obu_size fields in the ouput frame.
|
||||
size_t prefix_size = 0;
|
||||
// obu_header() and obu_size (leb128 encoded payload_size).
|
||||
// obu_header can be up to 2 bytes, obu_size - up to 5.
|
||||
std::array<uint8_t, 7> prefix;
|
||||
// Size of the obu payload in the output frame, i.e. excluding header
|
||||
size_t payload_size = 0;
|
||||
// iterator pointing to the beginning of the obu payload.
|
||||
ArrayOfArrayViews::const_iterator payload_offset;
|
||||
// OBU payloads as written in the rtp packet payloads.
|
||||
ArrayOfArrayViews data;
|
||||
};
|
||||
// Expect that majority of the frame won't use more than 4 obus.
|
||||
// In a simple stream delta frame consist of single Frame OBU, while key frame
|
||||
// also has Sequence Header OBU.
|
||||
using VectorObuInfo = absl::InlinedVector<ObuInfo, 4>;
|
||||
|
||||
constexpr int kObuTypeSequenceHeader = 1;
|
||||
constexpr uint8_t kObuSizePresentBit = 0b0'0000'010;
|
||||
|
||||
bool ObuHasExtension(uint8_t obu_header) {
|
||||
return obu_header & 0b0'0000'100u;
|
||||
}
|
||||
|
||||
bool ObuHasSize(uint8_t obu_header) {
|
||||
return obu_header & kObuSizePresentBit;
|
||||
}
|
||||
|
||||
int ObuType(uint8_t obu_header) {
|
||||
return (obu_header & 0b0'1111'000u) >> 3;
|
||||
@ -74,8 +179,191 @@ int RtpNumObus(uint8_t aggregation_header) { // 0 for any number of obus.
|
||||
return (aggregation_header & 0b0011'0000u) >> 4;
|
||||
}
|
||||
|
||||
// Reorgonizes array of rtp payloads into array of obus:
|
||||
// fills ObuInfo::data field.
|
||||
// Returns empty vector on error.
|
||||
VectorObuInfo ParseObus(
|
||||
rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads) {
|
||||
VectorObuInfo obu_infos;
|
||||
bool expect_continues_obu = false;
|
||||
for (rtc::ArrayView<const uint8_t> rtp_payload : rtp_payloads) {
|
||||
rtc::ByteBufferReader payload(
|
||||
reinterpret_cast<const char*>(rtp_payload.data()), rtp_payload.size());
|
||||
uint8_t aggregation_header;
|
||||
if (!payload.ReadUInt8(&aggregation_header)) {
|
||||
RTC_DLOG(WARNING) << "Failed to find aggregation header in the packet.";
|
||||
return {};
|
||||
}
|
||||
// Z-bit: 1 if the first OBU contained in the packet is a continuation of a
|
||||
// previous OBU.
|
||||
bool continues_obu = RtpStartsWithFragment(aggregation_header);
|
||||
if (continues_obu != expect_continues_obu) {
|
||||
RTC_DLOG(WARNING) << "Unexpected Z-bit " << continues_obu;
|
||||
return {};
|
||||
}
|
||||
int num_expected_obus = RtpNumObus(aggregation_header);
|
||||
if (payload.Length() == 0) {
|
||||
// rtp packet has just the aggregation header. That may be valid only when
|
||||
// there is exactly one fragment in the packet of size 0.
|
||||
if (num_expected_obus != 1) {
|
||||
RTC_DLOG(WARNING) << "Invalid packet with just an aggregation header.";
|
||||
return {};
|
||||
}
|
||||
if (!continues_obu) {
|
||||
// Empty packet just to notify there is a new OBU.
|
||||
obu_infos.emplace_back();
|
||||
}
|
||||
expect_continues_obu = RtpEndsWithFragment(aggregation_header);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int obu_index = 1; payload.Length() > 0; ++obu_index) {
|
||||
ObuInfo& obu_info = (obu_index == 1 && continues_obu)
|
||||
? obu_infos.back()
|
||||
: obu_infos.emplace_back();
|
||||
uint64_t fragment_size;
|
||||
// When num_expected_obus > 0, last OBU (fragment) is not preceeded by
|
||||
// the size field. See W field in
|
||||
// https://aomediacodec.github.io/av1-rtp-spec/#43-av1-aggregation-header
|
||||
bool has_fragment_size = (obu_index != num_expected_obus);
|
||||
if (has_fragment_size) {
|
||||
if (!payload.ReadUVarint(&fragment_size)) {
|
||||
RTC_DLOG(WARNING) << "Failed to read fragment size for obu #"
|
||||
<< obu_index << "/" << num_expected_obus;
|
||||
return {};
|
||||
}
|
||||
if (fragment_size > payload.Length()) {
|
||||
// Malformed input: written size is larger than remaining buffer.
|
||||
RTC_DLOG(WARNING) << "Malformed fragment size " << fragment_size
|
||||
<< " is larger than remaining size "
|
||||
<< payload.Length() << " while reading obu #"
|
||||
<< obu_index << "/" << num_expected_obus;
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
fragment_size = payload.Length();
|
||||
}
|
||||
// While it is in-practical to pass empty fragments, it is still possible.
|
||||
if (fragment_size > 0) {
|
||||
obu_info.data.Append(reinterpret_cast<const uint8_t*>(payload.Data()),
|
||||
fragment_size);
|
||||
payload.Consume(fragment_size);
|
||||
}
|
||||
}
|
||||
// Z flag should be same as Y flag of the next packet.
|
||||
expect_continues_obu = RtpEndsWithFragment(aggregation_header);
|
||||
}
|
||||
if (expect_continues_obu) {
|
||||
RTC_DLOG(WARNING) << "Last packet shouldn't have last obu fragmented.";
|
||||
return {};
|
||||
}
|
||||
return obu_infos;
|
||||
}
|
||||
|
||||
// Returns number of bytes consumed.
|
||||
int WriteLeb128(uint32_t value, uint8_t* buffer) {
|
||||
int size = 0;
|
||||
while (value >= 0x80) {
|
||||
buffer[size] = 0x80 | (value & 0x7F);
|
||||
++size;
|
||||
value >>= 7;
|
||||
}
|
||||
buffer[size] = value;
|
||||
++size;
|
||||
return size;
|
||||
}
|
||||
|
||||
// Calculates sizes for the Obu, i.e. base on ObuInfo::data field calculates
|
||||
// all other fields in the ObuInfo structure.
|
||||
// Returns false if obu found to be misformed.
|
||||
bool CalculateObuSizes(ObuInfo* obu_info) {
|
||||
if (obu_info->data.empty()) {
|
||||
RTC_DLOG(WARNING) << "Invalid bitstream: empty obu provided.";
|
||||
return false;
|
||||
}
|
||||
auto it = obu_info->data.begin();
|
||||
uint8_t obu_header = *it;
|
||||
obu_info->prefix[0] = obu_header | kObuSizePresentBit;
|
||||
obu_info->prefix_size = 1;
|
||||
++it;
|
||||
if (ObuHasExtension(obu_header)) {
|
||||
if (it == obu_info->data.end()) {
|
||||
return false;
|
||||
}
|
||||
obu_info->prefix[1] = *it; // obu_extension_header
|
||||
obu_info->prefix_size = 2;
|
||||
++it;
|
||||
}
|
||||
// Read, validate, and skip size, if present.
|
||||
if (!ObuHasSize(obu_header)) {
|
||||
obu_info->payload_size = obu_info->data.size() - obu_info->prefix_size;
|
||||
} else {
|
||||
// Read leb128 encoded field obu_size.
|
||||
uint64_t obu_size_bytes = 0;
|
||||
// Number of bytes obu_size field occupy in the bitstream.
|
||||
int size_of_obu_size_bytes = 0;
|
||||
uint8_t leb128_byte;
|
||||
do {
|
||||
if (it == obu_info->data.end() || size_of_obu_size_bytes >= 8) {
|
||||
RTC_DLOG(WARNING)
|
||||
<< "Failed to read obu_size. obu_size field is too long: "
|
||||
<< size_of_obu_size_bytes << " bytes processed.";
|
||||
return false;
|
||||
}
|
||||
leb128_byte = *it;
|
||||
obu_size_bytes |= (leb128_byte & 0x7F) << (size_of_obu_size_bytes * 7);
|
||||
++size_of_obu_size_bytes;
|
||||
++it;
|
||||
} while ((leb128_byte & 0x80) != 0);
|
||||
|
||||
obu_info->payload_size =
|
||||
obu_info->data.size() - obu_info->prefix_size - size_of_obu_size_bytes;
|
||||
if (obu_size_bytes != obu_info->payload_size) {
|
||||
// obu_size was present in the bitstream and mismatches calculated size.
|
||||
RTC_DLOG(WARNING) << "Mismatch in obu_size. signaled: " << obu_size_bytes
|
||||
<< ", actual: " << obu_info->payload_size;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
obu_info->payload_offset = it;
|
||||
obu_info->prefix_size +=
|
||||
WriteLeb128(rtc::dchecked_cast<uint32_t>(obu_info->payload_size),
|
||||
obu_info->prefix.data() + obu_info->prefix_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
rtc::scoped_refptr<EncodedImageBuffer> RtpDepacketizerAv1::AssembleFrame(
|
||||
rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads) {
|
||||
VectorObuInfo obu_infos = ParseObus(rtp_payloads);
|
||||
if (obu_infos.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t frame_size = 0;
|
||||
for (ObuInfo& obu_info : obu_infos) {
|
||||
if (!CalculateObuSizes(&obu_info)) {
|
||||
return nullptr;
|
||||
}
|
||||
frame_size += (obu_info.prefix_size + obu_info.payload_size);
|
||||
}
|
||||
|
||||
rtc::scoped_refptr<EncodedImageBuffer> bitstream =
|
||||
EncodedImageBuffer::Create(frame_size);
|
||||
uint8_t* write_at = bitstream->data();
|
||||
for (const ObuInfo& obu_info : obu_infos) {
|
||||
// Copy the obu_header and obu_size fields.
|
||||
memcpy(write_at, obu_info.prefix.data(), obu_info.prefix_size);
|
||||
write_at += obu_info.prefix_size;
|
||||
// Copy the obu payload.
|
||||
obu_info.data.CopyTo(write_at, obu_info.payload_offset);
|
||||
write_at += obu_info.payload_size;
|
||||
}
|
||||
RTC_CHECK_EQ(write_at - bitstream->data(), bitstream->size());
|
||||
return bitstream;
|
||||
}
|
||||
|
||||
bool RtpDepacketizerAv1::Parse(ParsedPayload* parsed_payload,
|
||||
const uint8_t* payload_data,
|
||||
size_t payload_data_length) {
|
||||
|
||||
@ -14,6 +14,9 @@
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "api/array_view.h"
|
||||
#include "api/scoped_refptr.h"
|
||||
#include "api/video/encoded_image.h"
|
||||
#include "modules/rtp_rtcp/source/rtp_format.h"
|
||||
|
||||
namespace webrtc {
|
||||
@ -25,6 +28,9 @@ class RtpDepacketizerAv1 : public RtpDepacketizer {
|
||||
RtpDepacketizerAv1& operator=(const RtpDepacketizerAv1&) = delete;
|
||||
~RtpDepacketizerAv1() override = default;
|
||||
|
||||
static rtc::scoped_refptr<EncodedImageBuffer> AssembleFrame(
|
||||
rtc::ArrayView<const rtc::ArrayView<const uint8_t>> rtp_payloads);
|
||||
|
||||
bool Parse(ParsedPayload* parsed_payload,
|
||||
const uint8_t* payload_data,
|
||||
size_t payload_data_length) override;
|
||||
|
||||
@ -10,10 +10,14 @@
|
||||
|
||||
#include "modules/rtp_rtcp/source/rtp_depacketizer_av1.h"
|
||||
|
||||
#include "test/gmock.h"
|
||||
#include "test/gtest.h"
|
||||
|
||||
namespace webrtc {
|
||||
namespace {
|
||||
|
||||
using ::testing::ElementsAre;
|
||||
|
||||
// Signals number of the OBU (fragments) in the packet.
|
||||
constexpr uint8_t kObuCountAny = 0b0000'0000;
|
||||
constexpr uint8_t kObuCountOne = 0b0001'0000;
|
||||
@ -23,6 +27,8 @@ constexpr uint8_t kObuHeaderSequenceHeader = 0b0'0001'000;
|
||||
constexpr uint8_t kObuHeaderTemporalDelimiter = 0b0'0010'000;
|
||||
constexpr uint8_t kObuHeaderFrame = 0b0'0110'000;
|
||||
|
||||
constexpr uint8_t kObuHeaderHasSize = 0b0'0000'010;
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, ParsePassFullRtpPayloadAsCodecPayload) {
|
||||
const uint8_t packet[] = {(uint8_t{1} << 7) | kObuCountOne, 1, 2, 3, 4};
|
||||
RtpDepacketizerAv1 depacketizer;
|
||||
@ -192,5 +198,278 @@ TEST(RtpDepacketizerAv1Test, ParseSkipsEmptyFragments) {
|
||||
EXPECT_TRUE(parsed.video.frame_type == VideoFrameType::kVideoFrameDelta);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameSetsOBUPayloadSizeWhenAbsent) {
|
||||
const uint8_t payload1[] = {0b00'01'0000, // aggregation header
|
||||
0b0'0110'000, // / Frame
|
||||
20, 30, 40}; // \ OBU
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
rtc::ArrayView<const uint8_t> frame_view(*frame);
|
||||
EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize);
|
||||
EXPECT_EQ(frame_view[1], 3);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameSetsOBUPayloadSizeWhenPresent) {
|
||||
const uint8_t payload1[] = {0b00'01'0000, // aggregation header
|
||||
0b0'0110'010, // / Frame OBU header
|
||||
3, // obu_size
|
||||
20,
|
||||
30,
|
||||
40}; // \ obu_payload
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
rtc::ArrayView<const uint8_t> frame_view(*frame);
|
||||
EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize);
|
||||
EXPECT_EQ(frame_view[1], 3);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test,
|
||||
AssembleFrameSetsOBUPayloadSizeAfterExtensionWhenAbsent) {
|
||||
const uint8_t payload1[] = {0b00'01'0000, // aggregation header
|
||||
0b0'0110'100, // / Frame
|
||||
0b010'01'000, // | extension_header
|
||||
20, 30, 40}; // \ OBU
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
rtc::ArrayView<const uint8_t> frame_view(*frame);
|
||||
EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize);
|
||||
EXPECT_EQ(frame_view[2], 3);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test,
|
||||
AssembleFrameSetsOBUPayloadSizeAfterExtensionWhenPresent) {
|
||||
const uint8_t payload1[] = {0b00'01'0000, // aggregation header
|
||||
0b0'0110'110, // / Frame OBU header
|
||||
0b010'01'000, // | extension_header
|
||||
3, // | obu_size
|
||||
20,
|
||||
30,
|
||||
40}; // \ obu_payload
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
rtc::ArrayView<const uint8_t> frame_view(*frame);
|
||||
EXPECT_TRUE(frame_view[0] & kObuHeaderHasSize);
|
||||
EXPECT_EQ(frame_view[2], 3);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameFromOnePacketWithOneObu) {
|
||||
const uint8_t payload1[] = {0b00'01'0000, // aggregation header
|
||||
0b0'0110'000, // / Frame
|
||||
20}; // \ OBU
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0110'010, 1, 20));
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameFromOnePacketWithTwoObus) {
|
||||
const uint8_t payload1[] = {0b00'10'0000, // aggregation header
|
||||
2, // / Sequence
|
||||
0b0'0001'000, // | Header
|
||||
10, // \ OBU
|
||||
0b0'0110'000, // / Frame
|
||||
20}; // \ OBU
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0001'010, 1, 10, // Sequence Header OBU
|
||||
0b0'0110'010, 1, 20)); // Frame OBU
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameFromTwoPacketsWithOneObu) {
|
||||
const uint8_t payload1[] = {0b01'01'0000, // aggregation header
|
||||
0b0'0110'000, 20, 30};
|
||||
const uint8_t payload2[] = {0b10'01'0000, // aggregation header
|
||||
40};
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0110'010, 3, 20, 30, 40));
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameFromTwoPacketsWithTwoObu) {
|
||||
const uint8_t payload1[] = {0b01'10'0000, // aggregation header
|
||||
2, // / Sequence
|
||||
0b0'0001'000, // | Header
|
||||
10, // \ OBU
|
||||
0b0'0110'000, //
|
||||
20,
|
||||
30}; //
|
||||
const uint8_t payload2[] = {0b10'01'0000, // aggregation header
|
||||
40}; //
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0001'010, 1, 10, // SH
|
||||
0b0'0110'010, 3, 20, 30, 40)); // Frame
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test,
|
||||
AssembleFrameFromTwoPacketsWithManyObusSomeWithExtensions) {
|
||||
const uint8_t payload1[] = {0b01'00'0000, // aggregation header
|
||||
2, // /
|
||||
0b0'0001'000, // | Sequence Header
|
||||
10, // \ OBU
|
||||
2, // /
|
||||
0b0'0101'000, // | Metadata OBU
|
||||
20, // \ without extension
|
||||
4, // /
|
||||
0b0'0101'100, // | Metadata OBU
|
||||
0b001'10'000, // | with extension
|
||||
20, // |
|
||||
30, // \ metadata payload
|
||||
5, // /
|
||||
0b0'0110'100, // | Frame OBU
|
||||
0b001'10'000, // | with extension
|
||||
40, // |
|
||||
50, // |
|
||||
60}; // |
|
||||
const uint8_t payload2[] = {0b10'01'0000, // aggregation header
|
||||
70, 80, 90}; // \ tail of the frame OBU
|
||||
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre( // Sequence header OBU
|
||||
0b0'0001'010, 1, 10,
|
||||
// Metadata OBU without extension
|
||||
0b0'0101'010, 1, 20,
|
||||
// Metadata OBU with extenion
|
||||
0b0'0101'110, 0b001'10'000, 2, 20, 30,
|
||||
// Frame OBU with extension
|
||||
0b0'0110'110, 0b001'10'000, 6, 40, 50, 60, 70, 80, 90));
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameWithOneObuFromManyPackets) {
|
||||
const uint8_t payload1[] = {0b01'01'0000, // aggregation header
|
||||
0b0'0110'000, 11, 12};
|
||||
const uint8_t payload2[] = {0b11'01'0000, // aggregation header
|
||||
13, 14};
|
||||
const uint8_t payload3[] = {0b11'01'0000, // aggregation header
|
||||
15, 16, 17};
|
||||
const uint8_t payload4[] = {0b10'01'0000, // aggregation header
|
||||
18};
|
||||
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2, payload3,
|
||||
payload4};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0110'010, 8, 11, 12, 13, 14, 15, 16, 17, 18));
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test,
|
||||
AssembleFrameFromManyPacketsWithSomeObuBorderAligned) {
|
||||
const uint8_t payload1[] = {0b01'10'0000, // aggregation header
|
||||
3, // size of the 1st fragment
|
||||
0b0'0011'000, // Frame header OBU
|
||||
11,
|
||||
12,
|
||||
0b0'0100'000, // Tile group OBU
|
||||
21,
|
||||
22,
|
||||
23};
|
||||
const uint8_t payload2[] = {0b10'01'0000, // aggregation header
|
||||
24, 25, 26, 27};
|
||||
// payload2 ends an OBU, payload3 starts a new one.
|
||||
const uint8_t payload3[] = {0b01'10'0000, // aggregation header
|
||||
3, // size of the 1st fragment
|
||||
0b0'0111'000, // Redundant frame header OBU
|
||||
11,
|
||||
12,
|
||||
0b0'0100'000, // Tile group OBU
|
||||
31,
|
||||
32};
|
||||
const uint8_t payload4[] = {0b10'01'0000, // aggregation header
|
||||
33, 34, 35, 36};
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2, payload3,
|
||||
payload4};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0011'010, 2, 11, 12, // Frame header
|
||||
0b0'0100'010, 7, 21, 22, 23, 24, 25, 26, 27, //
|
||||
0b0'0111'010, 2, 11, 12, //
|
||||
0b0'0100'010, 6, 31, 32, 33, 34, 35, 36));
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test,
|
||||
AssembleFrameFromOnePacketsOneObuPayloadSize127Bytes) {
|
||||
uint8_t payload1[4 + 127];
|
||||
memset(payload1, 0, sizeof(payload1));
|
||||
payload1[0] = 0b00'00'0000; // aggregation header
|
||||
payload1[1] = 0x80; // leb128 encoded size of 128 bytes
|
||||
payload1[2] = 0x01; // in two bytes
|
||||
payload1[3] = 0b0'0110'000; // obu_header with size and extension bits unset.
|
||||
payload1[4 + 42] = 0x42;
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->size(), 2 + 127u);
|
||||
rtc::ArrayView<const uint8_t> frame_view(*frame);
|
||||
EXPECT_EQ(frame_view[0], 0b0'0110'010); // obu_header with size bit set.
|
||||
EXPECT_EQ(frame_view[1], 127); // obu payload size, 1 byte enough to encode.
|
||||
// Check 'random' byte from the payload is at the same 'random' offset.
|
||||
EXPECT_EQ(frame_view[2 + 42], 0x42);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test,
|
||||
AssembleFrameFromTwoPacketsOneObuPayloadSize128Bytes) {
|
||||
uint8_t payload1[3 + 32];
|
||||
memset(payload1, 0, sizeof(payload1));
|
||||
payload1[0] = 0b01'00'0000; // aggregation header
|
||||
payload1[1] = 33; // leb128 encoded size of 33 bytes in one byte
|
||||
payload1[2] = 0b0'0110'000; // obu_header with size and extension bits unset.
|
||||
payload1[3 + 10] = 0x10;
|
||||
uint8_t payload2[2 + 96];
|
||||
memset(payload2, 0, sizeof(payload2));
|
||||
payload2[0] = 0b10'00'0000; // aggregation header
|
||||
payload2[1] = 96; // leb128 encoded size of 96 bytes in one byte
|
||||
payload2[2 + 20] = 0x20;
|
||||
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2};
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_EQ(frame->size(), 3 + 128u);
|
||||
rtc::ArrayView<const uint8_t> frame_view(*frame);
|
||||
EXPECT_EQ(frame_view[0], 0b0'0110'010); // obu_header with size bit set.
|
||||
EXPECT_EQ(frame_view[1], 0x80); // obu payload size of 128 bytes.
|
||||
EXPECT_EQ(frame_view[2], 0x01); // encoded in two byes
|
||||
// Check two 'random' byte from the payload is at the same 'random' offset.
|
||||
EXPECT_EQ(frame_view[3 + 10], 0x10);
|
||||
EXPECT_EQ(frame_view[3 + 32 + 20], 0x20);
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameFromAlmostEmptyPacketStartingAnOBU) {
|
||||
const uint8_t payload1[] = {0b01'01'0000};
|
||||
const uint8_t payload2[] = {0b10'01'0000, 0b0'0110'000, 10, 20, 30};
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2};
|
||||
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0110'010, 3, 10, 20, 30));
|
||||
}
|
||||
|
||||
TEST(RtpDepacketizerAv1Test, AssembleFrameFromAlmostEmptyPacketFinishingAnOBU) {
|
||||
const uint8_t payload1[] = {0b01'01'0000, 0b0'0110'000, 10, 20, 30};
|
||||
const uint8_t payload2[] = {0b10'01'0000};
|
||||
rtc::ArrayView<const uint8_t> payloads[] = {payload1, payload2};
|
||||
|
||||
auto frame = RtpDepacketizerAv1::AssembleFrame(payloads);
|
||||
ASSERT_TRUE(frame);
|
||||
EXPECT_THAT(rtc::ArrayView<const uint8_t>(*frame),
|
||||
ElementsAre(0b0'0110'010, 3, 10, 20, 30));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace webrtc
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user