From 038fd997806230cdcc23137e9c2c2e46fef74873 Mon Sep 17 00:00:00 2001 From: Danil Chapovalov Date: Thu, 21 Nov 2019 14:08:28 +0100 Subject: [PATCH] Add RtpDepacketizerAv1::AssembleFrame function Bug: webrtc:11042 Change-Id: I677fc6a9affacf3b7c80adc2c3493c16806db1f6 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/160003 Commit-Queue: Danil Chapovalov Reviewed-by: Philip Eliasson Cr-Commit-Position: refs/heads/master@{#29862} --- modules/rtp_rtcp/BUILD.gn | 1 + .../rtp_rtcp/source/rtp_depacketizer_av1.cc | 288 ++++++++++++++++++ .../rtp_rtcp/source/rtp_depacketizer_av1.h | 6 + .../source/rtp_depacketizer_av1_unittest.cc | 279 +++++++++++++++++ 4 files changed, 574 insertions(+) diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index 0a1dc4b1e2..a1993ee081 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn @@ -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", diff --git a/modules/rtp_rtcp/source/rtp_depacketizer_av1.cc b/modules/rtp_rtcp/source/rtp_depacketizer_av1.cc index cc92526177..9383ce27d6 100644 --- a/modules/rtp_rtcp/source/rtp_depacketizer_av1.cc +++ b/modules/rtp_rtcp/source/rtp_depacketizer_av1.cc @@ -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, 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 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; + 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> rtp_payloads) { + VectorObuInfo obu_infos; + bool expect_continues_obu = false; + for (rtc::ArrayView rtp_payload : rtp_payloads) { + rtc::ByteBufferReader payload( + reinterpret_cast(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(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(obu_info->payload_size), + obu_info->prefix.data() + obu_info->prefix_size); + return true; +} + } // namespace +rtc::scoped_refptr RtpDepacketizerAv1::AssembleFrame( + rtc::ArrayView> 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 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) { diff --git a/modules/rtp_rtcp/source/rtp_depacketizer_av1.h b/modules/rtp_rtcp/source/rtp_depacketizer_av1.h index e4a6dceb94..f9ed7bf08c 100644 --- a/modules/rtp_rtcp/source/rtp_depacketizer_av1.h +++ b/modules/rtp_rtcp/source/rtp_depacketizer_av1.h @@ -14,6 +14,9 @@ #include #include +#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 AssembleFrame( + rtc::ArrayView> rtp_payloads); + bool Parse(ParsedPayload* parsed_payload, const uint8_t* payload_data, size_t payload_data_length) override; diff --git a/modules/rtp_rtcp/source/rtp_depacketizer_av1_unittest.cc b/modules/rtp_rtcp/source/rtp_depacketizer_av1_unittest.cc index 2520f74279..cf55aaed20 100644 --- a/modules/rtp_rtcp/source/rtp_depacketizer_av1_unittest.cc +++ b/modules/rtp_rtcp/source/rtp_depacketizer_av1_unittest.cc @@ -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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView 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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView 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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView 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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + rtc::ArrayView 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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1, payload2}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1, payload2}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1, payload2}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1, payload2, payload3, + payload4}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1, payload2, payload3, + payload4}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_EQ(frame->size(), 2 + 127u); + rtc::ArrayView 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 payloads[] = {payload1, payload2}; + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_EQ(frame->size(), 3 + 128u); + rtc::ArrayView 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 payloads[] = {payload1, payload2}; + + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*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 payloads[] = {payload1, payload2}; + + auto frame = RtpDepacketizerAv1::AssembleFrame(payloads); + ASSERT_TRUE(frame); + EXPECT_THAT(rtc::ArrayView(*frame), + ElementsAre(0b0'0110'010, 3, 10, 20, 30)); +} + } // namespace } // namespace webrtc