From eae6896f76fd3baf202e3f0c8ee212e937a85004 Mon Sep 17 00:00:00 2001 From: Danil Chapovalov Date: Wed, 11 Dec 2019 11:24:37 +0100 Subject: [PATCH] Move vp8 rtp depacketization to VideoRtpDepacketizerVp8 Bug: webrtc:11152 Change-Id: Ic2b7fd091cb4d095ce29fbe06196f6424c08fce1 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/161451 Reviewed-by: Markus Handell Commit-Queue: Danil Chapovalov Cr-Commit-Position: refs/heads/master@{#30088} --- modules/rtp_rtcp/BUILD.gn | 3 + .../source/create_video_rtp_depacketizer.cc | 8 +- modules/rtp_rtcp/source/rtp_format_vp8.cc | 213 +--------------- .../source/video_rtp_depacketizer_vp8.cc | 198 +++++++++++++++ .../source/video_rtp_depacketizer_vp8.h | 42 ++++ .../video_rtp_depacketizer_vp8_unittest.cc | 236 ++++++++++++++++++ 6 files changed, 494 insertions(+), 206 deletions(-) create mode 100644 modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc create mode 100644 modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h create mode 100644 modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc diff --git a/modules/rtp_rtcp/BUILD.gn b/modules/rtp_rtcp/BUILD.gn index 39b9180869..1cf0982b58 100644 --- a/modules/rtp_rtcp/BUILD.gn +++ b/modules/rtp_rtcp/BUILD.gn @@ -215,6 +215,8 @@ rtc_library("rtp_rtcp") { "source/video_rtp_depacketizer.h", "source/video_rtp_depacketizer_raw.cc", "source/video_rtp_depacketizer_raw.h", + "source/video_rtp_depacketizer_vp8.cc", + "source/video_rtp_depacketizer_vp8.h", ] if (rtc_enable_bwe_test_logging) { @@ -480,6 +482,7 @@ if (rtc_include_tests) { "source/ulpfec_header_reader_writer_unittest.cc", "source/ulpfec_receiver_unittest.cc", "source/video_rtp_depacketizer_raw_unittest.cc", + "source/video_rtp_depacketizer_vp8_unittest.cc", ] deps = [ ":fec_test_helper", diff --git a/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc b/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc index 8946ec0180..52edab0f83 100644 --- a/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc +++ b/modules/rtp_rtcp/source/create_video_rtp_depacketizer.cc @@ -16,6 +16,7 @@ #include "absl/types/optional.h" #include "modules/rtp_rtcp/source/rtp_format.h" #include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h" #include "rtc_base/checks.h" #include "rtc_base/copy_on_write_buffer.h" @@ -56,7 +57,12 @@ std::unique_ptr CreateVideoRtpDepacketizer( VideoCodecType codec) { // TODO(bugs.webrtc.org/11152): switch on codec and create specialized // VideoRtpDepacketizers when they are migrated to new interface. - return std::make_unique(codec); + switch (codec) { + case kVideoCodecVP8: + return std::make_unique(); + default: + return std::make_unique(codec); + } } } // namespace webrtc diff --git a/modules/rtp_rtcp/source/rtp_format_vp8.cc b/modules/rtp_rtcp/source/rtp_format_vp8.cc index bf7b9694ae..c31be7db8c 100644 --- a/modules/rtp_rtcp/source/rtp_format_vp8.cc +++ b/modules/rtp_rtcp/source/rtp_format_vp8.cc @@ -16,6 +16,7 @@ #include #include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h" #include "modules/video_coding/codecs/interface/common_constants.h" #include "rtc_base/checks.h" #include "rtc_base/logging.h" @@ -33,119 +34,6 @@ constexpr int kTBit = 0x20; constexpr int kKBit = 0x10; constexpr int kYBit = 0x20; -int ParseVP8PictureID(RTPVideoHeaderVP8* vp8, - const uint8_t** data, - size_t* data_length, - size_t* parsed_bytes) { - if (*data_length == 0) - return -1; - - vp8->pictureId = (**data & 0x7F); - if (**data & 0x80) { - (*data)++; - (*parsed_bytes)++; - if (--(*data_length) == 0) - return -1; - // PictureId is 15 bits - vp8->pictureId = (vp8->pictureId << 8) + **data; - } - (*data)++; - (*parsed_bytes)++; - (*data_length)--; - return 0; -} - -int ParseVP8Tl0PicIdx(RTPVideoHeaderVP8* vp8, - const uint8_t** data, - size_t* data_length, - size_t* parsed_bytes) { - if (*data_length == 0) - return -1; - - vp8->tl0PicIdx = **data; - (*data)++; - (*parsed_bytes)++; - (*data_length)--; - return 0; -} - -int ParseVP8TIDAndKeyIdx(RTPVideoHeaderVP8* vp8, - const uint8_t** data, - size_t* data_length, - size_t* parsed_bytes, - bool has_tid, - bool has_key_idx) { - if (*data_length == 0) - return -1; - - if (has_tid) { - vp8->temporalIdx = ((**data >> 6) & 0x03); - vp8->layerSync = (**data & 0x20) ? true : false; // Y bit - } - if (has_key_idx) { - vp8->keyIdx = (**data & 0x1F); - } - (*data)++; - (*parsed_bytes)++; - (*data_length)--; - return 0; -} - -int ParseVP8Extension(RTPVideoHeaderVP8* vp8, - const uint8_t* data, - size_t data_length) { - RTC_DCHECK_GT(data_length, 0); - size_t parsed_bytes = 0; - // Optional X field is present. - bool has_picture_id = (*data & 0x80) ? true : false; // I bit - bool has_tl0_pic_idx = (*data & 0x40) ? true : false; // L bit - bool has_tid = (*data & 0x20) ? true : false; // T bit - bool has_key_idx = (*data & 0x10) ? true : false; // K bit - - // Advance data and decrease remaining payload size. - data++; - parsed_bytes++; - data_length--; - - if (has_picture_id) { - if (ParseVP8PictureID(vp8, &data, &data_length, &parsed_bytes) != 0) { - return -1; - } - } - - if (has_tl0_pic_idx) { - if (ParseVP8Tl0PicIdx(vp8, &data, &data_length, &parsed_bytes) != 0) { - return -1; - } - } - - if (has_tid || has_key_idx) { - if (ParseVP8TIDAndKeyIdx(vp8, &data, &data_length, &parsed_bytes, has_tid, - has_key_idx) != 0) { - return -1; - } - } - return static_cast(parsed_bytes); -} - -int ParseVP8FrameSize(RtpDepacketizer::ParsedPayload* parsed_payload, - const uint8_t* data, - size_t data_length) { - if (parsed_payload->video_header().frame_type != - VideoFrameType::kVideoFrameKey) { - // Included in payload header for I-frames. - return 0; - } - if (data_length < 10) { - // For an I-frame we should always have the uncompressed VP8 header - // in the beginning of the partition. - return -1; - } - parsed_payload->video_header().width = ((data[7] << 8) + data[6]) & 0x3FFF; - parsed_payload->video_header().height = ((data[9] << 8) + data[8]) & 0x3FFF; - return 0; -} - bool ValidateHeader(const RTPVideoHeaderVP8& hdr_info) { if (hdr_info.pictureId != kNoPictureId) { RTC_DCHECK_GE(hdr_info.pictureId, 0); @@ -275,104 +163,19 @@ RtpPacketizerVp8::RawHeader RtpPacketizerVp8::BuildHeader( return result; } -// -// VP8 format: -// -// Payload descriptor -// 0 1 2 3 4 5 6 7 -// +-+-+-+-+-+-+-+-+ -// |X|R|N|S|PartID | (REQUIRED) -// +-+-+-+-+-+-+-+-+ -// X: |I|L|T|K| RSV | (OPTIONAL) -// +-+-+-+-+-+-+-+-+ -// I: | PictureID | (OPTIONAL) -// +-+-+-+-+-+-+-+-+ -// L: | TL0PICIDX | (OPTIONAL) -// +-+-+-+-+-+-+-+-+ -// T/K: |TID:Y| KEYIDX | (OPTIONAL) -// +-+-+-+-+-+-+-+-+ -// -// Payload header (considered part of the actual payload, sent to decoder) -// 0 1 2 3 4 5 6 7 -// +-+-+-+-+-+-+-+-+ -// |Size0|H| VER |P| -// +-+-+-+-+-+-+-+-+ -// | ... | -// + + bool RtpDepacketizerVp8::Parse(ParsedPayload* parsed_payload, const uint8_t* payload_data, size_t payload_data_length) { RTC_DCHECK(parsed_payload); - if (payload_data_length == 0) { - RTC_LOG(LS_ERROR) << "Empty payload."; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload( + rtc::MakeArrayView(payload_data, payload_data_length), + &parsed_payload->video); + if (offset == 0) { return false; } - - // Parse mandatory first byte of payload descriptor. - bool extension = (*payload_data & 0x80) ? true : false; // X bit - bool beginning_of_partition = (*payload_data & 0x10) ? true : false; // S bit - int partition_id = (*payload_data & 0x0F); // PartID field - - parsed_payload->video_header().width = 0; - parsed_payload->video_header().height = 0; - parsed_payload->video_header().is_first_packet_in_frame = - beginning_of_partition && (partition_id == 0); - parsed_payload->video_header().simulcastIdx = 0; - parsed_payload->video_header().codec = kVideoCodecVP8; - auto& vp8_header = parsed_payload->video_header() - .video_type_header.emplace(); - vp8_header.nonReference = (*payload_data & 0x20) ? true : false; // N bit - vp8_header.partitionId = partition_id; - vp8_header.beginningOfPartition = beginning_of_partition; - vp8_header.pictureId = kNoPictureId; - vp8_header.tl0PicIdx = kNoTl0PicIdx; - vp8_header.temporalIdx = kNoTemporalIdx; - vp8_header.layerSync = false; - vp8_header.keyIdx = kNoKeyIdx; - - if (partition_id > 8) { - // Weak check for corrupt payload_data: PartID MUST NOT be larger than 8. - return false; - } - - // Advance payload_data and decrease remaining payload size. - payload_data++; - if (payload_data_length <= 1) { - RTC_LOG(LS_ERROR) << "Error parsing VP8 payload descriptor!"; - return false; - } - payload_data_length--; - - if (extension) { - const int parsed_bytes = - ParseVP8Extension(&vp8_header, payload_data, payload_data_length); - if (parsed_bytes < 0) - return false; - payload_data += parsed_bytes; - payload_data_length -= parsed_bytes; - if (payload_data_length == 0) { - RTC_LOG(LS_ERROR) << "Error parsing VP8 payload descriptor!"; - return false; - } - } - - // Read P bit from payload header (only at beginning of first partition). - if (beginning_of_partition && partition_id == 0) { - parsed_payload->video_header().frame_type = - (*payload_data & 0x01) ? VideoFrameType::kVideoFrameDelta - : VideoFrameType::kVideoFrameKey; - } else { - parsed_payload->video_header().frame_type = - VideoFrameType::kVideoFrameDelta; - } - - if (ParseVP8FrameSize(parsed_payload, payload_data, payload_data_length) != - 0) { - return false; - } - - parsed_payload->payload = payload_data; - parsed_payload->payload_length = payload_data_length; + parsed_payload->payload = payload_data + offset; + parsed_payload->payload_length = payload_data_length - offset; return true; } + } // namespace webrtc diff --git a/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc b/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc new file mode 100644 index 0000000000..7c128fe2bc --- /dev/null +++ b/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.cc @@ -0,0 +1,198 @@ +/* + * 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/video_rtp_depacketizer_vp8.h" + +#include +#include + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +// VP8 format: +// +// Payload descriptor +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|PartID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T|K| RSV | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: | PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T/K: |TID:Y| KEYIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// +// Payload header (considered part of the actual payload, sent to decoder) +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Size0|H| VER |P| +// +-+-+-+-+-+-+-+-+ +// | ... | +// + + +namespace webrtc { +namespace { + +constexpr int kFailedToParse = 0; + +int ParseVP8Descriptor(RTPVideoHeaderVP8* vp8, + const uint8_t* data, + size_t data_length) { + RTC_DCHECK_GT(data_length, 0); + int parsed_bytes = 0; + // Parse mandatory first byte of payload descriptor. + bool extension = (*data & 0x80) ? true : false; // X bit + vp8->nonReference = (*data & 0x20) ? true : false; // N bit + vp8->beginningOfPartition = (*data & 0x10) ? true : false; // S bit + vp8->partitionId = (*data & 0x0F); // PartID field + + data++; + parsed_bytes++; + data_length--; + + if (!extension) + return parsed_bytes; + + if (data_length == 0) + return kFailedToParse; + // Optional X field is present. + bool has_picture_id = (*data & 0x80) ? true : false; // I bit + bool has_tl0_pic_idx = (*data & 0x40) ? true : false; // L bit + bool has_tid = (*data & 0x20) ? true : false; // T bit + bool has_key_idx = (*data & 0x10) ? true : false; // K bit + + // Advance data and decrease remaining payload size. + data++; + parsed_bytes++; + data_length--; + + if (has_picture_id) { + if (data_length == 0) + return kFailedToParse; + + vp8->pictureId = (*data & 0x7F); + if (*data & 0x80) { + data++; + parsed_bytes++; + if (--data_length == 0) + return kFailedToParse; + // PictureId is 15 bits + vp8->pictureId = (vp8->pictureId << 8) + *data; + } + data++; + parsed_bytes++; + data_length--; + } + + if (has_tl0_pic_idx) { + if (data_length == 0) + return kFailedToParse; + + vp8->tl0PicIdx = *data; + data++; + parsed_bytes++; + data_length--; + } + + if (has_tid || has_key_idx) { + if (data_length == 0) + return kFailedToParse; + + if (has_tid) { + vp8->temporalIdx = ((*data >> 6) & 0x03); + vp8->layerSync = (*data & 0x20) ? true : false; // Y bit + } + if (has_key_idx) { + vp8->keyIdx = *data & 0x1F; + } + data++; + parsed_bytes++; + data_length--; + } + return parsed_bytes; +} + +} // namespace + +absl::optional +VideoRtpDepacketizerVp8::Parse(rtc::CopyOnWriteBuffer rtp_payload) { + rtc::ArrayView payload(rtp_payload.cdata(), + rtp_payload.size()); + absl::optional result(absl::in_place); + int offset = ParseRtpPayload(payload, &result->video_header); + if (offset == kFailedToParse) + return absl::nullopt; + RTC_DCHECK_LT(offset, rtp_payload.size()); + result->video_payload = + rtp_payload.Slice(offset, rtp_payload.size() - offset); + return result; +} + +int VideoRtpDepacketizerVp8::ParseRtpPayload( + rtc::ArrayView rtp_payload, + RTPVideoHeader* video_header) { + RTC_DCHECK(video_header); + if (rtp_payload.empty()) { + RTC_LOG(LS_ERROR) << "Empty rtp payload."; + return kFailedToParse; + } + + video_header->simulcastIdx = 0; + video_header->codec = kVideoCodecVP8; + auto& vp8_header = + video_header->video_type_header.emplace(); + vp8_header.InitRTPVideoHeaderVP8(); + + const int descriptor_size = + ParseVP8Descriptor(&vp8_header, rtp_payload.data(), rtp_payload.size()); + if (descriptor_size == kFailedToParse) + return kFailedToParse; + + if (vp8_header.partitionId > 8) { + // Weak check for corrupt payload_data: PartID MUST NOT be larger than 8. + return kFailedToParse; + } + video_header->is_first_packet_in_frame = + vp8_header.beginningOfPartition && vp8_header.partitionId == 0; + + int vp8_payload_size = rtp_payload.size() - descriptor_size; + if (vp8_payload_size == 0) { + RTC_LOG(LS_WARNING) << "Empty vp8 payload."; + return kFailedToParse; + } + const uint8_t* vp8_payload = rtp_payload.data() + descriptor_size; + + // Read P bit from payload header (only at beginning of first partition). + if (video_header->is_first_packet_in_frame && (*vp8_payload & 0x01) == 0) { + video_header->frame_type = VideoFrameType::kVideoFrameKey; + + if (vp8_payload_size < 10) { + // For an I-frame we should always have the uncompressed VP8 header + // in the beginning of the partition. + return kFailedToParse; + } + video_header->width = ((vp8_payload[7] << 8) + vp8_payload[6]) & 0x3FFF; + video_header->height = ((vp8_payload[9] << 8) + vp8_payload[8]) & 0x3FFF; + } else { + video_header->frame_type = VideoFrameType::kVideoFrameDelta; + + video_header->width = 0; + video_header->height = 0; + } + + return descriptor_size; +} + +} // namespace webrtc diff --git a/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h b/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h new file mode 100644 index 0000000000..a7573993f7 --- /dev/null +++ b/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP8_H_ +#define MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP8_H_ + +#include + +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_video_header.h" +#include "modules/rtp_rtcp/source/video_rtp_depacketizer.h" +#include "rtc_base/copy_on_write_buffer.h" + +namespace webrtc { + +class VideoRtpDepacketizerVp8 : public VideoRtpDepacketizer { + public: + VideoRtpDepacketizerVp8() = default; + VideoRtpDepacketizerVp8(const VideoRtpDepacketizerVp8&) = delete; + VideoRtpDepacketizerVp8& operator=(VideoRtpDepacketizerVp8&) = delete; + ~VideoRtpDepacketizerVp8() override = default; + + // Parses vp8 rtp payload descriptor. + // Returns zero on error or vp8 payload header offset on success. + static int ParseRtpPayload(rtc::ArrayView rtp_payload, + RTPVideoHeader* video_header); + + absl::optional Parse( + rtc::CopyOnWriteBuffer rtp_payload) override; +}; + +} // namespace webrtc + +#endif // MODULES_RTP_RTCP_SOURCE_VIDEO_RTP_DEPACKETIZER_VP8_H_ diff --git a/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc b/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc new file mode 100644 index 0000000000..4837ecae25 --- /dev/null +++ b/modules/rtp_rtcp/source/video_rtp_depacketizer_vp8_unittest.cc @@ -0,0 +1,236 @@ +/* + * 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/video_rtp_depacketizer_vp8.h" + +#include "api/array_view.h" +#include "modules/rtp_rtcp/source/rtp_format_vp8.h" +#include "modules/rtp_rtcp/source/rtp_packet_to_send.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +// Payload descriptor +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |X|R|N|S|PartID | (REQUIRED) +// +-+-+-+-+-+-+-+-+ +// X: |I|L|T|K| RSV | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// I: | PictureID | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// L: | TL0PICIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// T/K: |TID:Y| KEYIDX | (OPTIONAL) +// +-+-+-+-+-+-+-+-+ +// +// Payload header +// 0 1 2 3 4 5 6 7 +// +-+-+-+-+-+-+-+-+ +// |Size0|H| VER |P| +// +-+-+-+-+-+-+-+-+ +// : : +TEST(VideoRtpDepacketizerVp8Test, BasicHeader) { + uint8_t packet[4] = {0}; + packet[0] = 0b0001'0100; // S = 1, PartID = 4. + packet[1] = 0x01; // P frame. + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 1); + EXPECT_EQ(video_header.frame_type, VideoFrameType::kVideoFrameDelta); + EXPECT_EQ(video_header.codec, kVideoCodecVP8); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_FALSE(vp8_header.nonReference); + EXPECT_TRUE(vp8_header.beginningOfPartition); + EXPECT_EQ(vp8_header.partitionId, 4); + EXPECT_EQ(vp8_header.pictureId, kNoPictureId); + EXPECT_EQ(vp8_header.tl0PicIdx, kNoTl0PicIdx); + EXPECT_EQ(vp8_header.temporalIdx, kNoTemporalIdx); + EXPECT_EQ(vp8_header.keyIdx, kNoKeyIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, OneBytePictureID) { + const uint8_t kPictureId = 17; + uint8_t packet[10] = {0}; + packet[0] = 0b1010'0000; + packet[1] = 0b1000'0000; + packet[2] = kPictureId; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_EQ(vp8_header.pictureId, kPictureId); +} + +TEST(VideoRtpDepacketizerVp8Test, TwoBytePictureID) { + const uint16_t kPictureId = 0x1234; + uint8_t packet[10] = {0}; + packet[0] = 0b1010'0000; + packet[1] = 0b1000'0000; + packet[2] = 0x80 | 0x12; + packet[3] = 0x34; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 4); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_EQ(vp8_header.pictureId, kPictureId); +} + +TEST(VideoRtpDepacketizerVp8Test, Tl0PicIdx) { + const uint8_t kTl0PicIdx = 17; + uint8_t packet[13] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b0100'0000; + packet[2] = kTl0PicIdx; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_EQ(vp8_header.tl0PicIdx, kTl0PicIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, TIDAndLayerSync) { + uint8_t packet[10] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b0010'0000; + packet[2] = 0b10'0'00000; // TID(2) + LayerSync(false) + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_EQ(vp8_header.temporalIdx, 2); + EXPECT_FALSE(vp8_header.layerSync); +} + +TEST(VideoRtpDepacketizerVp8Test, KeyIdx) { + const uint8_t kKeyIdx = 17; + uint8_t packet[10] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b0001'0000; + packet[2] = kKeyIdx; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 3); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_EQ(vp8_header.keyIdx, kKeyIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, MultipleExtensions) { + uint8_t packet[10] = {0}; + packet[0] = 0b1010'0110; // X and N bit set, partID = 6 + packet[1] = 0b1111'0000; + packet[2] = 0x80 | 0x12; // PictureID, high 7 bits. + packet[3] = 0x34; // PictureID, low 8 bits. + packet[4] = 42; // Tl0PicIdx. + packet[5] = 0b01'1'10001; + + RTPVideoHeader video_header; + int offset = VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &video_header); + + EXPECT_EQ(offset, 6); + const auto& vp8_header = + absl::get(video_header.video_type_header); + EXPECT_TRUE(vp8_header.nonReference); + EXPECT_EQ(vp8_header.partitionId, 0b0110); + EXPECT_EQ(vp8_header.pictureId, 0x1234); + EXPECT_EQ(vp8_header.tl0PicIdx, 42); + EXPECT_EQ(vp8_header.temporalIdx, 1); + EXPECT_TRUE(vp8_header.layerSync); + EXPECT_EQ(vp8_header.keyIdx, 0b10001); +} + +TEST(VideoRtpDepacketizerVp8Test, TooShortHeader) { + uint8_t packet[4] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b1111'0000; // All extensions are enabled... + packet[2] = 0x80 | 17; // ... but only 2 bytes PictureID is provided. + packet[3] = 17; // PictureID, low 8 bits. + + RTPVideoHeader unused; + EXPECT_EQ(VideoRtpDepacketizerVp8::ParseRtpPayload(packet, &unused), 0); +} + +TEST(VideoRtpDepacketizerVp8Test, WithPacketizer) { + uint8_t data[10] = {0}; + RtpPacketToSend packet(/*extenions=*/nullptr); + RTPVideoHeaderVP8 input_header; + input_header.nonReference = true; + input_header.pictureId = 300; + input_header.temporalIdx = 1; + input_header.layerSync = false; + input_header.tl0PicIdx = kNoTl0PicIdx; // Disable. + input_header.keyIdx = 31; + RtpPacketizerVp8 packetizer(data, /*limits=*/{}, input_header); + EXPECT_EQ(packetizer.NumPackets(), 1u); + ASSERT_TRUE(packetizer.NextPacket(&packet)); + + VideoRtpDepacketizerVp8 depacketizer; + absl::optional parsed = + depacketizer.Parse(packet.PayloadBuffer()); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_header.codec, kVideoCodecVP8); + const auto& vp8_header = + absl::get(parsed->video_header.video_type_header); + EXPECT_EQ(vp8_header.nonReference, input_header.nonReference); + EXPECT_EQ(vp8_header.pictureId, input_header.pictureId); + EXPECT_EQ(vp8_header.tl0PicIdx, input_header.tl0PicIdx); + EXPECT_EQ(vp8_header.temporalIdx, input_header.temporalIdx); + EXPECT_EQ(vp8_header.layerSync, input_header.layerSync); + EXPECT_EQ(vp8_header.keyIdx, input_header.keyIdx); +} + +TEST(VideoRtpDepacketizerVp8Test, ReferencesInputCopyOnWriteBuffer) { + constexpr size_t kHeaderSize = 5; + uint8_t packet[16] = {0}; + packet[0] = 0b1000'0000; + packet[1] = 0b1111'0000; // with all extensions, + packet[2] = 15; // and one-byte picture id. + + rtc::CopyOnWriteBuffer rtp_payload(packet); + VideoRtpDepacketizerVp8 depacketizer; + absl::optional parsed = + depacketizer.Parse(rtp_payload); + ASSERT_TRUE(parsed); + + EXPECT_EQ(parsed->video_payload.size(), rtp_payload.size() - kHeaderSize); + // Compare pointers to check there was no copy on write buffer unsharing. + EXPECT_EQ(parsed->video_payload.cdata(), rtp_payload.cdata() + kHeaderSize); +} + +TEST(VideoRtpDepacketizerVp8Test, FailsOnEmptyPayload) { + rtc::ArrayView empty; + RTPVideoHeader video_header; + EXPECT_EQ(VideoRtpDepacketizerVp8::ParseRtpPayload(empty, &video_header), 0); +} + +} // namespace +} // namespace webrtc