/* * 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" // VP8 payload descriptor // https://datatracker.ietf.org/doc/html/rfc7741#section-4.2 // // 0 1 2 3 4 5 6 7 // +-+-+-+-+-+-+-+-+ // |X|R|N|S|R| PID | (REQUIRED) // +-+-+-+-+-+-+-+-+ // X: |I|L|T|K| RSV | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // I: |M| PictureID | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // | PictureID | // +-+-+-+-+-+-+-+-+ // L: | TL0PICIDX | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // T/K: |TID|Y| KEYIDX | (OPTIONAL) // +-+-+-+-+-+-+-+-+ // // VP8 payload header. Considered part of the actual payload, sent to decoder. // https://datatracker.ietf.org/doc/html/rfc7741#section-4.3 // // 0 1 2 3 4 5 6 7 // +-+-+-+-+-+-+-+-+ // |Size0|H| VER |P| // +-+-+-+-+-+-+-+-+ // : ... : // +-+-+-+-+-+-+-+-+ namespace webrtc { namespace { TEST(VideoRtpDepacketizerVp8Test, BasicHeader) { uint8_t packet[4] = {0}; packet[0] = 0b0001'0100; // S = 1, partition ID = 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, partition ID = 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