summaryrefslogtreecommitdiffstats
path: root/third_party/libwebrtc/call/rtp_payload_params_unittest.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /third_party/libwebrtc/call/rtp_payload_params_unittest.cc
parentInitial commit. (diff)
downloadfirefox-43a97878ce14b72f0981164f87f2e35e14151312.tar.xz
firefox-43a97878ce14b72f0981164f87f2e35e14151312.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/call/rtp_payload_params_unittest.cc')
-rw-r--r--third_party/libwebrtc/call/rtp_payload_params_unittest.cc1180
1 files changed, 1180 insertions, 0 deletions
diff --git a/third_party/libwebrtc/call/rtp_payload_params_unittest.cc b/third_party/libwebrtc/call/rtp_payload_params_unittest.cc
new file mode 100644
index 0000000000..6a54ac8f9f
--- /dev/null
+++ b/third_party/libwebrtc/call/rtp_payload_params_unittest.cc
@@ -0,0 +1,1180 @@
+/*
+ * Copyright (c) 2018 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 "call/rtp_payload_params.h"
+
+#include <string.h>
+
+#include <map>
+#include <set>
+
+#include "absl/container/inlined_vector.h"
+#include "absl/types/optional.h"
+#include "absl/types/variant.h"
+#include "api/transport/field_trial_based_config.h"
+#include "api/video/video_content_type.h"
+#include "api/video/video_rotation.h"
+#include "modules/video_coding/codecs/h264/include/h264_globals.h"
+#include "modules/video_coding/codecs/interface/common_constants.h"
+#include "modules/video_coding/codecs/vp8/include/vp8_globals.h"
+#include "modules/video_coding/codecs/vp9/include/vp9_globals.h"
+#include "modules/video_coding/include/video_codec_interface.h"
+#include "test/explicit_key_value_config.h"
+#include "test/gmock.h"
+#include "test/gtest.h"
+#include "test/scoped_key_value_config.h"
+
+namespace webrtc {
+namespace {
+
+using ::testing::AllOf;
+using ::testing::Each;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Field;
+using ::testing::IsEmpty;
+using ::testing::Optional;
+using ::testing::SizeIs;
+
+using GenericDescriptorInfo = RTPVideoHeader::GenericDescriptorInfo;
+
+const uint32_t kSsrc1 = 12345;
+const uint32_t kSsrc2 = 23456;
+const int16_t kPictureId = 123;
+const int16_t kTl0PicIdx = 20;
+const uint8_t kTemporalIdx = 1;
+const int16_t kInitialPictureId1 = 222;
+const int16_t kInitialTl0PicIdx1 = 99;
+const int64_t kDontCare = 0;
+
+TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_Vp8) {
+ RtpPayloadState state2;
+ state2.picture_id = kPictureId;
+ state2.tl0_pic_idx = kTl0PicIdx;
+ std::map<uint32_t, RtpPayloadState> states = {{kSsrc2, state2}};
+
+ RtpPayloadParams params(kSsrc2, &state2, FieldTrialBasedConfig());
+ EncodedImage encoded_image;
+ encoded_image.rotation_ = kVideoRotation_90;
+ encoded_image.content_type_ = VideoContentType::SCREENSHARE;
+ encoded_image.SetSpatialIndex(1);
+
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP8;
+ codec_info.codecSpecific.VP8.temporalIdx = 0;
+ codec_info.codecSpecific.VP8.keyIdx = kNoKeyIdx;
+ codec_info.codecSpecific.VP8.layerSync = false;
+ codec_info.codecSpecific.VP8.nonReference = true;
+
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ codec_info.codecType = kVideoCodecVP8;
+ codec_info.codecSpecific.VP8.temporalIdx = 1;
+ codec_info.codecSpecific.VP8.layerSync = true;
+
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, 1);
+
+ EXPECT_EQ(kVideoRotation_90, header.rotation);
+ EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
+ EXPECT_EQ(1, header.simulcastIdx);
+ EXPECT_EQ(kVideoCodecVP8, header.codec);
+ const auto& vp8_header =
+ absl::get<RTPVideoHeaderVP8>(header.video_type_header);
+ EXPECT_EQ(kPictureId + 2, vp8_header.pictureId);
+ EXPECT_EQ(kTemporalIdx, vp8_header.temporalIdx);
+ EXPECT_EQ(kTl0PicIdx + 1, vp8_header.tl0PicIdx);
+ EXPECT_EQ(kNoKeyIdx, vp8_header.keyIdx);
+ EXPECT_TRUE(vp8_header.layerSync);
+ EXPECT_TRUE(vp8_header.nonReference);
+}
+
+TEST(RtpPayloadParamsTest, InfoMappedToRtpVideoHeader_Vp9) {
+ RtpPayloadState state;
+ state.picture_id = kPictureId;
+ state.tl0_pic_idx = kTl0PicIdx;
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+
+ EncodedImage encoded_image;
+ encoded_image.rotation_ = kVideoRotation_90;
+ encoded_image.content_type_ = VideoContentType::SCREENSHARE;
+ encoded_image.SetSpatialIndex(0);
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP9;
+ codec_info.codecSpecific.VP9.num_spatial_layers = 3;
+ codec_info.codecSpecific.VP9.first_frame_in_picture = true;
+ codec_info.codecSpecific.VP9.temporal_idx = 2;
+ codec_info.end_of_picture = false;
+
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ EXPECT_EQ(kVideoRotation_90, header.rotation);
+ EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
+ EXPECT_EQ(kVideoCodecVP9, header.codec);
+ EXPECT_FALSE(header.color_space);
+ const auto& vp9_header =
+ absl::get<RTPVideoHeaderVP9>(header.video_type_header);
+ EXPECT_EQ(kPictureId + 1, vp9_header.picture_id);
+ EXPECT_EQ(kTl0PicIdx, vp9_header.tl0_pic_idx);
+ EXPECT_EQ(vp9_header.temporal_idx, codec_info.codecSpecific.VP9.temporal_idx);
+ EXPECT_EQ(vp9_header.spatial_idx, encoded_image.SpatialIndex());
+ EXPECT_EQ(vp9_header.num_spatial_layers,
+ codec_info.codecSpecific.VP9.num_spatial_layers);
+ EXPECT_EQ(vp9_header.end_of_picture, codec_info.end_of_picture);
+
+ // Next spatial layer.
+ codec_info.codecSpecific.VP9.first_frame_in_picture = false;
+ codec_info.end_of_picture = true;
+
+ encoded_image.SetSpatialIndex(1);
+ ColorSpace color_space(
+ ColorSpace::PrimaryID::kSMPTE170M, ColorSpace::TransferID::kSMPTE170M,
+ ColorSpace::MatrixID::kSMPTE170M, ColorSpace::RangeID::kFull);
+ encoded_image.SetColorSpace(color_space);
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ EXPECT_EQ(kVideoRotation_90, header.rotation);
+ EXPECT_EQ(VideoContentType::SCREENSHARE, header.content_type);
+ EXPECT_EQ(kVideoCodecVP9, header.codec);
+ EXPECT_EQ(absl::make_optional(color_space), header.color_space);
+ EXPECT_EQ(kPictureId + 1, vp9_header.picture_id);
+ EXPECT_EQ(kTl0PicIdx, vp9_header.tl0_pic_idx);
+ EXPECT_EQ(vp9_header.temporal_idx, codec_info.codecSpecific.VP9.temporal_idx);
+ EXPECT_EQ(vp9_header.spatial_idx, encoded_image.SpatialIndex());
+ EXPECT_EQ(vp9_header.num_spatial_layers,
+ codec_info.codecSpecific.VP9.num_spatial_layers);
+ EXPECT_EQ(vp9_header.end_of_picture, codec_info.end_of_picture);
+}
+
+TEST(RtpPayloadParamsTest, PictureIdIsSetForVp8) {
+ RtpPayloadState state;
+ state.picture_id = kInitialPictureId1;
+ state.tl0_pic_idx = kInitialTl0PicIdx1;
+
+ EncodedImage encoded_image;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP8;
+
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+ EXPECT_EQ(kVideoCodecVP8, header.codec);
+ EXPECT_EQ(kInitialPictureId1 + 1,
+ absl::get<RTPVideoHeaderVP8>(header.video_type_header).pictureId);
+
+ // State should hold latest used picture id and tl0_pic_idx.
+ state = params.state();
+ EXPECT_EQ(kInitialPictureId1 + 1, state.picture_id);
+ EXPECT_EQ(kInitialTl0PicIdx1 + 1, state.tl0_pic_idx);
+}
+
+TEST(RtpPayloadParamsTest, PictureIdWraps) {
+ RtpPayloadState state;
+ state.picture_id = kMaxTwoBytePictureId;
+ state.tl0_pic_idx = kInitialTl0PicIdx1;
+
+ EncodedImage encoded_image;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP8;
+ codec_info.codecSpecific.VP8.temporalIdx = kNoTemporalIdx;
+
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+ EXPECT_EQ(kVideoCodecVP8, header.codec);
+ EXPECT_EQ(0,
+ absl::get<RTPVideoHeaderVP8>(header.video_type_header).pictureId);
+
+ // State should hold latest used picture id and tl0_pic_idx.
+ EXPECT_EQ(0, params.state().picture_id); // Wrapped.
+ EXPECT_EQ(kInitialTl0PicIdx1, params.state().tl0_pic_idx);
+}
+
+TEST(RtpPayloadParamsTest, CreatesGenericDescriptorForVp8) {
+ constexpr auto kSwitch = DecodeTargetIndication::kSwitch;
+ constexpr auto kNotPresent = DecodeTargetIndication::kNotPresent;
+
+ RtpPayloadState state;
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+
+ EncodedImage key_frame_image;
+ key_frame_image._frameType = VideoFrameType::kVideoFrameKey;
+ CodecSpecificInfo key_frame_info;
+ key_frame_info.codecType = kVideoCodecVP8;
+ key_frame_info.codecSpecific.VP8.temporalIdx = 0;
+ RTPVideoHeader key_frame_header = params.GetRtpVideoHeader(
+ key_frame_image, &key_frame_info, /*shared_frame_id=*/123);
+
+ EncodedImage delta_t1_image;
+ delta_t1_image._frameType = VideoFrameType::kVideoFrameDelta;
+ CodecSpecificInfo delta_t1_info;
+ delta_t1_info.codecType = kVideoCodecVP8;
+ delta_t1_info.codecSpecific.VP8.temporalIdx = 1;
+ RTPVideoHeader delta_t1_header = params.GetRtpVideoHeader(
+ delta_t1_image, &delta_t1_info, /*shared_frame_id=*/124);
+
+ EncodedImage delta_t0_image;
+ delta_t0_image._frameType = VideoFrameType::kVideoFrameDelta;
+ CodecSpecificInfo delta_t0_info;
+ delta_t0_info.codecType = kVideoCodecVP8;
+ delta_t0_info.codecSpecific.VP8.temporalIdx = 0;
+ RTPVideoHeader delta_t0_header = params.GetRtpVideoHeader(
+ delta_t0_image, &delta_t0_info, /*shared_frame_id=*/125);
+
+ EXPECT_THAT(
+ key_frame_header,
+ AllOf(Field(&RTPVideoHeader::codec, kVideoCodecVP8),
+ Field(&RTPVideoHeader::frame_type, VideoFrameType::kVideoFrameKey),
+ Field(&RTPVideoHeader::generic,
+ Optional(AllOf(
+ Field(&GenericDescriptorInfo::frame_id, 123),
+ Field(&GenericDescriptorInfo::spatial_index, 0),
+ Field(&GenericDescriptorInfo::temporal_index, 0),
+ Field(&GenericDescriptorInfo::decode_target_indications,
+ ElementsAre(kSwitch, kSwitch, kSwitch, kSwitch)),
+ Field(&GenericDescriptorInfo::dependencies, IsEmpty()),
+ Field(&GenericDescriptorInfo::chain_diffs,
+ ElementsAre(0)))))));
+
+ EXPECT_THAT(
+ delta_t1_header,
+ AllOf(
+ Field(&RTPVideoHeader::codec, kVideoCodecVP8),
+ Field(&RTPVideoHeader::frame_type, VideoFrameType::kVideoFrameDelta),
+ Field(
+ &RTPVideoHeader::generic,
+ Optional(AllOf(
+ Field(&GenericDescriptorInfo::frame_id, 124),
+ Field(&GenericDescriptorInfo::spatial_index, 0),
+ Field(&GenericDescriptorInfo::temporal_index, 1),
+ Field(&GenericDescriptorInfo::decode_target_indications,
+ ElementsAre(kNotPresent, kSwitch, kSwitch, kSwitch)),
+ Field(&GenericDescriptorInfo::dependencies, ElementsAre(123)),
+ Field(&GenericDescriptorInfo::chain_diffs,
+ ElementsAre(1)))))));
+
+ EXPECT_THAT(
+ delta_t0_header,
+ AllOf(
+ Field(&RTPVideoHeader::codec, kVideoCodecVP8),
+ Field(&RTPVideoHeader::frame_type, VideoFrameType::kVideoFrameDelta),
+ Field(
+ &RTPVideoHeader::generic,
+ Optional(AllOf(
+ Field(&GenericDescriptorInfo::frame_id, 125),
+ Field(&GenericDescriptorInfo::spatial_index, 0),
+ Field(&GenericDescriptorInfo::temporal_index, 0),
+ Field(&GenericDescriptorInfo::decode_target_indications,
+ ElementsAre(kSwitch, kSwitch, kSwitch, kSwitch)),
+ Field(&GenericDescriptorInfo::dependencies, ElementsAre(123)),
+ Field(&GenericDescriptorInfo::chain_diffs,
+ ElementsAre(2)))))));
+}
+
+TEST(RtpPayloadParamsTest, Tl0PicIdxUpdatedForVp8) {
+ RtpPayloadState state;
+ state.picture_id = kInitialPictureId1;
+ state.tl0_pic_idx = kInitialTl0PicIdx1;
+
+ EncodedImage encoded_image;
+ // Modules are sending for this test.
+ // OnEncodedImage, temporalIdx: 1.
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP8;
+ codec_info.codecSpecific.VP8.temporalIdx = 1;
+
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ EXPECT_EQ(kVideoCodecVP8, header.codec);
+ const auto& vp8_header =
+ absl::get<RTPVideoHeaderVP8>(header.video_type_header);
+ EXPECT_EQ(kInitialPictureId1 + 1, vp8_header.pictureId);
+ EXPECT_EQ(kInitialTl0PicIdx1, vp8_header.tl0PicIdx);
+
+ // OnEncodedImage, temporalIdx: 0.
+ codec_info.codecSpecific.VP8.temporalIdx = 0;
+
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+ EXPECT_EQ(kVideoCodecVP8, header.codec);
+ EXPECT_EQ(kInitialPictureId1 + 2, vp8_header.pictureId);
+ EXPECT_EQ(kInitialTl0PicIdx1 + 1, vp8_header.tl0PicIdx);
+
+ // State should hold latest used picture id and tl0_pic_idx.
+ EXPECT_EQ(kInitialPictureId1 + 2, params.state().picture_id);
+ EXPECT_EQ(kInitialTl0PicIdx1 + 1, params.state().tl0_pic_idx);
+}
+
+TEST(RtpPayloadParamsTest, Tl0PicIdxUpdatedForVp9) {
+ RtpPayloadState state;
+ state.picture_id = kInitialPictureId1;
+ state.tl0_pic_idx = kInitialTl0PicIdx1;
+
+ EncodedImage encoded_image;
+ // Modules are sending for this test.
+ // OnEncodedImage, temporalIdx: 1.
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP9;
+ codec_info.codecSpecific.VP9.temporal_idx = 1;
+ codec_info.codecSpecific.VP9.first_frame_in_picture = true;
+
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ EXPECT_EQ(kVideoCodecVP9, header.codec);
+ const auto& vp9_header =
+ absl::get<RTPVideoHeaderVP9>(header.video_type_header);
+ EXPECT_EQ(kInitialPictureId1 + 1, vp9_header.picture_id);
+ EXPECT_EQ(kInitialTl0PicIdx1, vp9_header.tl0_pic_idx);
+
+ // OnEncodedImage, temporalIdx: 0.
+ codec_info.codecSpecific.VP9.temporal_idx = 0;
+
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ EXPECT_EQ(kVideoCodecVP9, header.codec);
+ EXPECT_EQ(kInitialPictureId1 + 2, vp9_header.picture_id);
+ EXPECT_EQ(kInitialTl0PicIdx1 + 1, vp9_header.tl0_pic_idx);
+
+ // OnEncodedImage, first_frame_in_picture = false
+ codec_info.codecSpecific.VP9.first_frame_in_picture = false;
+
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, kDontCare);
+
+ EXPECT_EQ(kVideoCodecVP9, header.codec);
+ EXPECT_EQ(kInitialPictureId1 + 2, vp9_header.picture_id);
+ EXPECT_EQ(kInitialTl0PicIdx1 + 1, vp9_header.tl0_pic_idx);
+
+ // State should hold latest used picture id and tl0_pic_idx.
+ EXPECT_EQ(kInitialPictureId1 + 2, params.state().picture_id);
+ EXPECT_EQ(kInitialTl0PicIdx1 + 1, params.state().tl0_pic_idx);
+}
+
+TEST(RtpPayloadParamsTest, PictureIdForOldGenericFormat) {
+ test::ScopedKeyValueConfig field_trials("WebRTC-GenericPictureId/Enabled/");
+ RtpPayloadState state{};
+
+ EncodedImage encoded_image;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecGeneric;
+ encoded_image._frameType = VideoFrameType::kVideoFrameKey;
+
+ RtpPayloadParams params(kSsrc1, &state, field_trials);
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, 10);
+
+ EXPECT_EQ(kVideoCodecGeneric, header.codec);
+ const auto* generic =
+ absl::get_if<RTPVideoHeaderLegacyGeneric>(&header.video_type_header);
+ ASSERT_TRUE(generic);
+ EXPECT_EQ(0, generic->picture_id);
+
+ encoded_image._frameType = VideoFrameType::kVideoFrameDelta;
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, 20);
+ generic =
+ absl::get_if<RTPVideoHeaderLegacyGeneric>(&header.video_type_header);
+ ASSERT_TRUE(generic);
+ EXPECT_EQ(1, generic->picture_id);
+}
+
+TEST(RtpPayloadParamsTest, GenericDescriptorForGenericCodec) {
+ RtpPayloadState state;
+
+ EncodedImage encoded_image;
+ encoded_image._frameType = VideoFrameType::kVideoFrameKey;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecGeneric;
+
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+ RTPVideoHeader header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, 0);
+
+ EXPECT_THAT(header.codec, Eq(kVideoCodecGeneric));
+
+ ASSERT_TRUE(header.generic);
+ EXPECT_THAT(header.generic->frame_id, Eq(0));
+ EXPECT_THAT(header.generic->spatial_index, Eq(0));
+ EXPECT_THAT(header.generic->temporal_index, Eq(0));
+ EXPECT_THAT(header.generic->decode_target_indications,
+ ElementsAre(DecodeTargetIndication::kSwitch));
+ EXPECT_THAT(header.generic->dependencies, IsEmpty());
+ EXPECT_THAT(header.generic->chain_diffs, ElementsAre(0));
+
+ encoded_image._frameType = VideoFrameType::kVideoFrameDelta;
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info, 3);
+ ASSERT_TRUE(header.generic);
+ EXPECT_THAT(header.generic->frame_id, Eq(3));
+ EXPECT_THAT(header.generic->spatial_index, Eq(0));
+ EXPECT_THAT(header.generic->temporal_index, Eq(0));
+ EXPECT_THAT(header.generic->dependencies, ElementsAre(0));
+ EXPECT_THAT(header.generic->decode_target_indications,
+ ElementsAre(DecodeTargetIndication::kSwitch));
+ EXPECT_THAT(header.generic->chain_diffs, ElementsAre(3));
+}
+
+TEST(RtpPayloadParamsTest, SetsGenericFromGenericFrameInfo) {
+ RtpPayloadState state;
+ EncodedImage encoded_image;
+ CodecSpecificInfo codec_info;
+
+ RtpPayloadParams params(kSsrc1, &state, FieldTrialBasedConfig());
+
+ encoded_image._frameType = VideoFrameType::kVideoFrameKey;
+ codec_info.generic_frame_info =
+ GenericFrameInfo::Builder().S(1).T(0).Dtis("S").Build();
+ codec_info.generic_frame_info->encoder_buffers = {
+ {/*id=*/0, /*referenced=*/false, /*updated=*/true}};
+ codec_info.generic_frame_info->part_of_chain = {true, false};
+ RTPVideoHeader key_header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, /*frame_id=*/1);
+
+ ASSERT_TRUE(key_header.generic);
+ EXPECT_EQ(key_header.generic->spatial_index, 1);
+ EXPECT_EQ(key_header.generic->temporal_index, 0);
+ EXPECT_EQ(key_header.generic->frame_id, 1);
+ EXPECT_THAT(key_header.generic->dependencies, IsEmpty());
+ EXPECT_THAT(key_header.generic->decode_target_indications,
+ ElementsAre(DecodeTargetIndication::kSwitch));
+ EXPECT_THAT(key_header.generic->chain_diffs, SizeIs(2));
+
+ encoded_image._frameType = VideoFrameType::kVideoFrameDelta;
+ codec_info.generic_frame_info =
+ GenericFrameInfo::Builder().S(2).T(3).Dtis("D").Build();
+ codec_info.generic_frame_info->encoder_buffers = {
+ {/*id=*/0, /*referenced=*/true, /*updated=*/false}};
+ codec_info.generic_frame_info->part_of_chain = {false, false};
+ RTPVideoHeader delta_header =
+ params.GetRtpVideoHeader(encoded_image, &codec_info, /*frame_id=*/3);
+
+ ASSERT_TRUE(delta_header.generic);
+ EXPECT_EQ(delta_header.generic->spatial_index, 2);
+ EXPECT_EQ(delta_header.generic->temporal_index, 3);
+ EXPECT_EQ(delta_header.generic->frame_id, 3);
+ EXPECT_THAT(delta_header.generic->dependencies, ElementsAre(1));
+ EXPECT_THAT(delta_header.generic->decode_target_indications,
+ ElementsAre(DecodeTargetIndication::kDiscardable));
+ EXPECT_THAT(delta_header.generic->chain_diffs, SizeIs(2));
+}
+
+class RtpPayloadParamsVp8ToGenericTest : public ::testing::Test {
+ public:
+ enum LayerSync { kNoSync, kSync };
+
+ RtpPayloadParamsVp8ToGenericTest()
+ : state_(), params_(123, &state_, trials_config_) {}
+
+ void ConvertAndCheck(int temporal_index,
+ int64_t shared_frame_id,
+ VideoFrameType frame_type,
+ LayerSync layer_sync,
+ const std::set<int64_t>& expected_deps,
+ uint16_t width = 0,
+ uint16_t height = 0) {
+ EncodedImage encoded_image;
+ encoded_image._frameType = frame_type;
+ encoded_image._encodedWidth = width;
+ encoded_image._encodedHeight = height;
+
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP8;
+ codec_info.codecSpecific.VP8.temporalIdx = temporal_index;
+ codec_info.codecSpecific.VP8.layerSync = layer_sync == kSync;
+
+ RTPVideoHeader header =
+ params_.GetRtpVideoHeader(encoded_image, &codec_info, shared_frame_id);
+
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, 0);
+
+ EXPECT_EQ(header.generic->frame_id, shared_frame_id);
+ EXPECT_EQ(header.generic->temporal_index, temporal_index);
+ std::set<int64_t> actual_deps(header.generic->dependencies.begin(),
+ header.generic->dependencies.end());
+ EXPECT_EQ(expected_deps, actual_deps);
+
+ EXPECT_EQ(header.width, width);
+ EXPECT_EQ(header.height, height);
+ }
+
+ protected:
+ FieldTrialBasedConfig trials_config_;
+ RtpPayloadState state_;
+ RtpPayloadParams params_;
+};
+
+TEST_F(RtpPayloadParamsVp8ToGenericTest, Keyframe) {
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+ ConvertAndCheck(0, 1, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(0, 2, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+}
+
+TEST_F(RtpPayloadParamsVp8ToGenericTest, TooHighTemporalIndex) {
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+
+ EncodedImage encoded_image;
+ encoded_image._frameType = VideoFrameType::kVideoFrameDelta;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP8;
+ codec_info.codecSpecific.VP8.temporalIdx =
+ RtpGenericFrameDescriptor::kMaxTemporalLayers;
+ codec_info.codecSpecific.VP8.layerSync = false;
+
+ RTPVideoHeader header =
+ params_.GetRtpVideoHeader(encoded_image, &codec_info, 1);
+ EXPECT_FALSE(header.generic);
+}
+
+TEST_F(RtpPayloadParamsVp8ToGenericTest, LayerSync) {
+ // 02120212 pattern
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+ ConvertAndCheck(2, 1, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(1, 2, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(2, 3, VideoFrameType::kVideoFrameDelta, kNoSync, {0, 1, 2});
+
+ ConvertAndCheck(0, 4, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(2, 5, VideoFrameType::kVideoFrameDelta, kNoSync, {2, 3, 4});
+ ConvertAndCheck(1, 6, VideoFrameType::kVideoFrameDelta, kSync,
+ {4}); // layer sync
+ ConvertAndCheck(2, 7, VideoFrameType::kVideoFrameDelta, kNoSync, {4, 5, 6});
+}
+
+TEST_F(RtpPayloadParamsVp8ToGenericTest, FrameIdGaps) {
+ // 0101 pattern
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+ ConvertAndCheck(1, 1, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+
+ ConvertAndCheck(0, 5, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(1, 10, VideoFrameType::kVideoFrameDelta, kNoSync, {1, 5});
+
+ ConvertAndCheck(0, 15, VideoFrameType::kVideoFrameDelta, kNoSync, {5});
+ ConvertAndCheck(1, 20, VideoFrameType::kVideoFrameDelta, kNoSync, {10, 15});
+}
+
+TEST(RtpPayloadParamsVp9ToGenericTest, NoScalability) {
+ RtpPayloadState state;
+ RtpPayloadParams params(/*ssrc=*/123, &state, FieldTrialBasedConfig());
+
+ EncodedImage encoded_image;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecVP9;
+ codec_info.codecSpecific.VP9.num_spatial_layers = 1;
+ codec_info.codecSpecific.VP9.temporal_idx = kNoTemporalIdx;
+ codec_info.codecSpecific.VP9.first_frame_in_picture = true;
+ codec_info.end_of_picture = true;
+
+ // Key frame.
+ encoded_image._frameType = VideoFrameType::kVideoFrameKey;
+ codec_info.codecSpecific.VP9.inter_pic_predicted = false;
+ codec_info.codecSpecific.VP9.num_ref_pics = 0;
+ RTPVideoHeader header = params.GetRtpVideoHeader(encoded_image, &codec_info,
+ /*shared_frame_id=*/1);
+
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, 0);
+ EXPECT_EQ(header.generic->temporal_index, 0);
+ EXPECT_EQ(header.generic->frame_id, 1);
+ ASSERT_THAT(header.generic->decode_target_indications, Not(IsEmpty()));
+ EXPECT_EQ(header.generic->decode_target_indications[0],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(header.generic->dependencies, IsEmpty());
+ ASSERT_THAT(header.generic->chain_diffs, Not(IsEmpty()));
+ EXPECT_EQ(header.generic->chain_diffs[0], 0);
+
+ // Delta frame.
+ encoded_image._frameType = VideoFrameType::kVideoFrameDelta;
+ codec_info.codecSpecific.VP9.inter_pic_predicted = true;
+ codec_info.codecSpecific.VP9.num_ref_pics = 1;
+ codec_info.codecSpecific.VP9.p_diff[0] = 1;
+ header = params.GetRtpVideoHeader(encoded_image, &codec_info,
+ /*shared_frame_id=*/3);
+
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, 0);
+ EXPECT_EQ(header.generic->temporal_index, 0);
+ EXPECT_EQ(header.generic->frame_id, 3);
+ ASSERT_THAT(header.generic->decode_target_indications, Not(IsEmpty()));
+ EXPECT_EQ(header.generic->decode_target_indications[0],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(header.generic->dependencies, ElementsAre(1));
+ ASSERT_THAT(header.generic->chain_diffs, Not(IsEmpty()));
+ // previous frame in the chain was frame#1,
+ EXPECT_EQ(header.generic->chain_diffs[0], 3 - 1);
+}
+
+TEST(RtpPayloadParamsVp9ToGenericTest, TemporalScalabilityWith2Layers) {
+ // Test with 2 temporal layers structure that is not used by webrtc:
+ // 1---3 5
+ // / / / ...
+ // 0---2---4---
+ RtpPayloadState state;
+ RtpPayloadParams params(/*ssrc=*/123, &state, FieldTrialBasedConfig());
+
+ EncodedImage image;
+ CodecSpecificInfo info;
+ info.codecType = kVideoCodecVP9;
+ info.codecSpecific.VP9.num_spatial_layers = 1;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+ info.end_of_picture = true;
+
+ RTPVideoHeader headers[6];
+ // Key frame.
+ image._frameType = VideoFrameType::kVideoFrameKey;
+ info.codecSpecific.VP9.inter_pic_predicted = false;
+ info.codecSpecific.VP9.num_ref_pics = 0;
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 0;
+ headers[0] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/1);
+
+ // Delta frames.
+ info.codecSpecific.VP9.inter_pic_predicted = true;
+ image._frameType = VideoFrameType::kVideoFrameDelta;
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 1;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ headers[1] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/3);
+
+ info.codecSpecific.VP9.temporal_up_switch = false;
+ info.codecSpecific.VP9.temporal_idx = 0;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 2;
+ headers[2] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/5);
+
+ info.codecSpecific.VP9.temporal_up_switch = false;
+ info.codecSpecific.VP9.temporal_idx = 1;
+ info.codecSpecific.VP9.num_ref_pics = 2;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ info.codecSpecific.VP9.p_diff[1] = 2;
+ headers[3] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/7);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 0;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 2;
+ headers[4] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/9);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 1;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ headers[5] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/11);
+
+ ASSERT_TRUE(headers[0].generic);
+ int num_decode_targets = headers[0].generic->decode_target_indications.size();
+ int num_chains = headers[0].generic->chain_diffs.size();
+ ASSERT_GE(num_decode_targets, 2);
+ ASSERT_GE(num_chains, 1);
+
+ for (int frame_idx = 0; frame_idx < 6; ++frame_idx) {
+ const RTPVideoHeader& header = headers[frame_idx];
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, 0);
+ EXPECT_EQ(header.generic->temporal_index, frame_idx % 2);
+ EXPECT_EQ(header.generic->frame_id, 1 + 2 * frame_idx);
+ ASSERT_THAT(header.generic->decode_target_indications,
+ SizeIs(num_decode_targets));
+ ASSERT_THAT(header.generic->chain_diffs, SizeIs(num_chains));
+ // Expect only T0 frames are needed for the 1st decode target.
+ if (header.generic->temporal_index == 0) {
+ EXPECT_NE(header.generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ } else {
+ EXPECT_EQ(header.generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ }
+ // Expect all frames are needed for the 2nd decode target.
+ EXPECT_NE(header.generic->decode_target_indications[1],
+ DecodeTargetIndication::kNotPresent);
+ }
+
+ // Expect switch at every beginning of the pattern.
+ EXPECT_THAT(headers[0].generic->decode_target_indications[0],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(headers[0].generic->decode_target_indications[1],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(headers[4].generic->decode_target_indications[0],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(headers[4].generic->decode_target_indications[1],
+ DecodeTargetIndication::kSwitch);
+
+ EXPECT_THAT(headers[0].generic->dependencies, IsEmpty()); // T0, 1
+ EXPECT_THAT(headers[1].generic->dependencies, ElementsAre(1)); // T1, 3
+ EXPECT_THAT(headers[2].generic->dependencies, ElementsAre(1)); // T0, 5
+ EXPECT_THAT(headers[3].generic->dependencies, ElementsAre(5, 3)); // T1, 7
+ EXPECT_THAT(headers[4].generic->dependencies, ElementsAre(5)); // T0, 9
+ EXPECT_THAT(headers[5].generic->dependencies, ElementsAre(9)); // T1, 11
+
+ EXPECT_THAT(headers[0].generic->chain_diffs[0], Eq(0));
+ EXPECT_THAT(headers[1].generic->chain_diffs[0], Eq(2));
+ EXPECT_THAT(headers[2].generic->chain_diffs[0], Eq(4));
+ EXPECT_THAT(headers[3].generic->chain_diffs[0], Eq(2));
+ EXPECT_THAT(headers[4].generic->chain_diffs[0], Eq(4));
+ EXPECT_THAT(headers[5].generic->chain_diffs[0], Eq(2));
+}
+
+TEST(RtpPayloadParamsVp9ToGenericTest, TemporalScalabilityWith3Layers) {
+ // Test with 3 temporal layers structure that is not used by webrtc, but used
+ // by chromium: https://imgur.com/pURAGvp
+ RtpPayloadState state;
+ RtpPayloadParams params(/*ssrc=*/123, &state, FieldTrialBasedConfig());
+
+ EncodedImage image;
+ CodecSpecificInfo info;
+ info.codecType = kVideoCodecVP9;
+ info.codecSpecific.VP9.num_spatial_layers = 1;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+ info.end_of_picture = true;
+
+ RTPVideoHeader headers[9];
+ // Key frame.
+ image._frameType = VideoFrameType::kVideoFrameKey;
+ info.codecSpecific.VP9.inter_pic_predicted = false;
+ info.codecSpecific.VP9.num_ref_pics = 0;
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 0;
+ headers[0] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/1);
+
+ // Delta frames.
+ info.codecSpecific.VP9.inter_pic_predicted = true;
+ image._frameType = VideoFrameType::kVideoFrameDelta;
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 2;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ headers[1] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/3);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 1;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 2;
+ headers[2] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/5);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 2;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ headers[3] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/7);
+
+ info.codecSpecific.VP9.temporal_up_switch = false;
+ info.codecSpecific.VP9.temporal_idx = 0;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 4;
+ headers[4] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/9);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 2;
+ info.codecSpecific.VP9.num_ref_pics = 2;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ info.codecSpecific.VP9.p_diff[1] = 3;
+ headers[5] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/11);
+
+ info.codecSpecific.VP9.temporal_up_switch = false;
+ info.codecSpecific.VP9.temporal_idx = 1;
+ info.codecSpecific.VP9.num_ref_pics = 2;
+ info.codecSpecific.VP9.p_diff[0] = 2;
+ info.codecSpecific.VP9.p_diff[1] = 4;
+ headers[6] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/13);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 2;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ headers[7] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/15);
+
+ info.codecSpecific.VP9.temporal_up_switch = true;
+ info.codecSpecific.VP9.temporal_idx = 0;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 4;
+ headers[8] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/17);
+
+ ASSERT_TRUE(headers[0].generic);
+ int num_decode_targets = headers[0].generic->decode_target_indications.size();
+ int num_chains = headers[0].generic->chain_diffs.size();
+ ASSERT_GE(num_decode_targets, 3);
+ ASSERT_GE(num_chains, 1);
+
+ for (int frame_idx = 0; frame_idx < 9; ++frame_idx) {
+ const RTPVideoHeader& header = headers[frame_idx];
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, 0);
+ EXPECT_EQ(header.generic->frame_id, 1 + 2 * frame_idx);
+ ASSERT_THAT(header.generic->decode_target_indications,
+ SizeIs(num_decode_targets));
+ ASSERT_THAT(header.generic->chain_diffs, SizeIs(num_chains));
+ // Expect only T0 frames are needed for the 1st decode target.
+ if (header.generic->temporal_index == 0) {
+ EXPECT_NE(header.generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ } else {
+ EXPECT_EQ(header.generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ }
+ // Expect only T0 and T1 frames are needed for the 2nd decode target.
+ if (header.generic->temporal_index <= 1) {
+ EXPECT_NE(header.generic->decode_target_indications[1],
+ DecodeTargetIndication::kNotPresent);
+ } else {
+ EXPECT_EQ(header.generic->decode_target_indications[1],
+ DecodeTargetIndication::kNotPresent);
+ }
+ // Expect all frames are needed for the 3rd decode target.
+ EXPECT_NE(header.generic->decode_target_indications[2],
+ DecodeTargetIndication::kNotPresent);
+ }
+
+ EXPECT_EQ(headers[0].generic->temporal_index, 0);
+ EXPECT_EQ(headers[1].generic->temporal_index, 2);
+ EXPECT_EQ(headers[2].generic->temporal_index, 1);
+ EXPECT_EQ(headers[3].generic->temporal_index, 2);
+ EXPECT_EQ(headers[4].generic->temporal_index, 0);
+ EXPECT_EQ(headers[5].generic->temporal_index, 2);
+ EXPECT_EQ(headers[6].generic->temporal_index, 1);
+ EXPECT_EQ(headers[7].generic->temporal_index, 2);
+ EXPECT_EQ(headers[8].generic->temporal_index, 0);
+
+ // Expect switch at every beginning of the pattern.
+ EXPECT_THAT(headers[0].generic->decode_target_indications,
+ Each(DecodeTargetIndication::kSwitch));
+ EXPECT_THAT(headers[8].generic->decode_target_indications[0],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(headers[8].generic->decode_target_indications[1],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_THAT(headers[8].generic->decode_target_indications[2],
+ DecodeTargetIndication::kSwitch);
+
+ EXPECT_THAT(headers[0].generic->dependencies, IsEmpty()); // T0, 1
+ EXPECT_THAT(headers[1].generic->dependencies, ElementsAre(1)); // T2, 3
+ EXPECT_THAT(headers[2].generic->dependencies, ElementsAre(1)); // T1, 5
+ EXPECT_THAT(headers[3].generic->dependencies, ElementsAre(5)); // T2, 7
+ EXPECT_THAT(headers[4].generic->dependencies, ElementsAre(1)); // T0, 9
+ EXPECT_THAT(headers[5].generic->dependencies, ElementsAre(9, 5)); // T2, 11
+ EXPECT_THAT(headers[6].generic->dependencies, ElementsAre(9, 5)); // T1, 13
+ EXPECT_THAT(headers[7].generic->dependencies, ElementsAre(13)); // T2, 15
+ EXPECT_THAT(headers[8].generic->dependencies, ElementsAre(9)); // T0, 17
+
+ EXPECT_THAT(headers[0].generic->chain_diffs[0], Eq(0));
+ EXPECT_THAT(headers[1].generic->chain_diffs[0], Eq(2));
+ EXPECT_THAT(headers[2].generic->chain_diffs[0], Eq(4));
+ EXPECT_THAT(headers[3].generic->chain_diffs[0], Eq(6));
+ EXPECT_THAT(headers[4].generic->chain_diffs[0], Eq(8));
+ EXPECT_THAT(headers[5].generic->chain_diffs[0], Eq(2));
+ EXPECT_THAT(headers[6].generic->chain_diffs[0], Eq(4));
+ EXPECT_THAT(headers[7].generic->chain_diffs[0], Eq(6));
+ EXPECT_THAT(headers[8].generic->chain_diffs[0], Eq(8));
+}
+
+TEST(RtpPayloadParamsVp9ToGenericTest, SpatialScalabilityKSvc) {
+ // 1---3--
+ // | ...
+ // 0---2--
+ RtpPayloadState state;
+ RtpPayloadParams params(/*ssrc=*/123, &state, FieldTrialBasedConfig());
+
+ EncodedImage image;
+ CodecSpecificInfo info;
+ info.codecType = kVideoCodecVP9;
+ info.codecSpecific.VP9.num_spatial_layers = 2;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+
+ RTPVideoHeader headers[4];
+ // Key frame.
+ image._frameType = VideoFrameType::kVideoFrameKey;
+ image.SetSpatialIndex(0);
+ info.codecSpecific.VP9.inter_pic_predicted = false;
+ info.codecSpecific.VP9.inter_layer_predicted = false;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = false;
+ info.codecSpecific.VP9.num_ref_pics = 0;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+ info.end_of_picture = false;
+ headers[0] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/1);
+
+ image.SetSpatialIndex(1);
+ info.codecSpecific.VP9.inter_layer_predicted = true;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = true;
+ info.codecSpecific.VP9.first_frame_in_picture = false;
+ info.end_of_picture = true;
+ headers[1] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/3);
+
+ // Delta frames.
+ info.codecSpecific.VP9.inter_pic_predicted = true;
+ image._frameType = VideoFrameType::kVideoFrameDelta;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+
+ image.SetSpatialIndex(0);
+ info.codecSpecific.VP9.inter_layer_predicted = false;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = true;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+ info.end_of_picture = false;
+ headers[2] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/5);
+
+ image.SetSpatialIndex(1);
+ info.codecSpecific.VP9.inter_layer_predicted = false;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = true;
+ info.codecSpecific.VP9.first_frame_in_picture = false;
+ info.end_of_picture = true;
+ headers[3] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/7);
+
+ ASSERT_TRUE(headers[0].generic);
+ int num_decode_targets = headers[0].generic->decode_target_indications.size();
+ // Rely on implementation detail there are always kMaxTemporalStreams temporal
+ // layers assumed, in particular assume Decode Target#0 matches layer S0T0,
+ // and Decode Target#kMaxTemporalStreams matches layer S1T0.
+ ASSERT_GE(num_decode_targets, kMaxTemporalStreams * 2);
+ int num_chains = headers[0].generic->chain_diffs.size();
+ ASSERT_GE(num_chains, 2);
+
+ for (int frame_idx = 0; frame_idx < 4; ++frame_idx) {
+ const RTPVideoHeader& header = headers[frame_idx];
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, frame_idx % 2);
+ EXPECT_EQ(header.generic->temporal_index, 0);
+ EXPECT_EQ(header.generic->frame_id, 1 + 2 * frame_idx);
+ ASSERT_THAT(header.generic->decode_target_indications,
+ SizeIs(num_decode_targets));
+ ASSERT_THAT(header.generic->chain_diffs, SizeIs(num_chains));
+ }
+
+ // Expect S0 key frame is switch for both Decode Targets.
+ EXPECT_EQ(headers[0].generic->decode_target_indications[0],
+ DecodeTargetIndication::kSwitch);
+ EXPECT_EQ(headers[0].generic->decode_target_indications[kMaxTemporalStreams],
+ DecodeTargetIndication::kSwitch);
+ // S1 key frame is only needed for the 2nd Decode Targets.
+ EXPECT_EQ(headers[1].generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ EXPECT_NE(headers[1].generic->decode_target_indications[kMaxTemporalStreams],
+ DecodeTargetIndication::kNotPresent);
+ // Delta frames are only needed for their own Decode Targets.
+ EXPECT_NE(headers[2].generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ EXPECT_EQ(headers[2].generic->decode_target_indications[kMaxTemporalStreams],
+ DecodeTargetIndication::kNotPresent);
+ EXPECT_EQ(headers[3].generic->decode_target_indications[0],
+ DecodeTargetIndication::kNotPresent);
+ EXPECT_NE(headers[3].generic->decode_target_indications[kMaxTemporalStreams],
+ DecodeTargetIndication::kNotPresent);
+
+ EXPECT_THAT(headers[0].generic->dependencies, IsEmpty()); // S0, 1
+ EXPECT_THAT(headers[1].generic->dependencies, ElementsAre(1)); // S1, 3
+ EXPECT_THAT(headers[2].generic->dependencies, ElementsAre(1)); // S0, 5
+ EXPECT_THAT(headers[3].generic->dependencies, ElementsAre(3)); // S1, 7
+
+ EXPECT_THAT(headers[0].generic->chain_diffs[0], Eq(0));
+ EXPECT_THAT(headers[0].generic->chain_diffs[1], Eq(0));
+ EXPECT_THAT(headers[1].generic->chain_diffs[0], Eq(2));
+ EXPECT_THAT(headers[1].generic->chain_diffs[1], Eq(2));
+ EXPECT_THAT(headers[2].generic->chain_diffs[0], Eq(4));
+ EXPECT_THAT(headers[2].generic->chain_diffs[1], Eq(2));
+ EXPECT_THAT(headers[3].generic->chain_diffs[0], Eq(2));
+ EXPECT_THAT(headers[3].generic->chain_diffs[1], Eq(4));
+}
+
+TEST(RtpPayloadParamsVp9ToGenericTest,
+ IncreaseNumberOfSpatialLayersOnDeltaFrame) {
+ // S1 5--
+ // | ...
+ // S0 1---3--
+ RtpPayloadState state;
+ RtpPayloadParams params(/*ssrc=*/123, &state, FieldTrialBasedConfig());
+
+ EncodedImage image;
+ CodecSpecificInfo info;
+ info.codecType = kVideoCodecVP9;
+ info.codecSpecific.VP9.num_spatial_layers = 1;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+
+ RTPVideoHeader headers[3];
+ // Key frame.
+ image._frameType = VideoFrameType::kVideoFrameKey;
+ image.SetSpatialIndex(0);
+ info.codecSpecific.VP9.inter_pic_predicted = false;
+ info.codecSpecific.VP9.inter_layer_predicted = false;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = true;
+ info.codecSpecific.VP9.num_ref_pics = 0;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+ info.end_of_picture = true;
+ headers[0] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/1);
+
+ // S0 delta frame.
+ image._frameType = VideoFrameType::kVideoFrameDelta;
+ info.codecSpecific.VP9.num_spatial_layers = 2;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = false;
+ info.codecSpecific.VP9.first_frame_in_picture = true;
+ info.codecSpecific.VP9.inter_pic_predicted = true;
+ info.codecSpecific.VP9.num_ref_pics = 1;
+ info.codecSpecific.VP9.p_diff[0] = 1;
+ info.end_of_picture = false;
+ headers[1] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/3);
+
+ // S1 delta frame.
+ image.SetSpatialIndex(1);
+ info.codecSpecific.VP9.inter_layer_predicted = true;
+ info.codecSpecific.VP9.non_ref_for_inter_layer_pred = true;
+ info.codecSpecific.VP9.first_frame_in_picture = false;
+ info.codecSpecific.VP9.inter_pic_predicted = false;
+ info.end_of_picture = true;
+ headers[2] = params.GetRtpVideoHeader(image, &info, /*shared_frame_id=*/5);
+
+ ASSERT_TRUE(headers[0].generic);
+ int num_decode_targets = headers[0].generic->decode_target_indications.size();
+ int num_chains = headers[0].generic->chain_diffs.size();
+ // Rely on implementation detail there are always kMaxTemporalStreams temporal
+ // layers. In particular assume Decode Target#0 matches layer S0T0, and
+ // Decode Target#kMaxTemporalStreams matches layer S1T0.
+ static constexpr int kS0T0 = 0;
+ static constexpr int kS1T0 = kMaxTemporalStreams;
+ ASSERT_GE(num_decode_targets, 2);
+ ASSERT_GE(num_chains, 2);
+
+ for (int frame_idx = 0; frame_idx < 3; ++frame_idx) {
+ const RTPVideoHeader& header = headers[frame_idx];
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->temporal_index, 0);
+ EXPECT_EQ(header.generic->frame_id, 1 + 2 * frame_idx);
+ ASSERT_THAT(header.generic->decode_target_indications,
+ SizeIs(num_decode_targets));
+ ASSERT_THAT(header.generic->chain_diffs, SizeIs(num_chains));
+ }
+
+ EXPECT_TRUE(headers[0].generic->active_decode_targets[kS0T0]);
+ EXPECT_FALSE(headers[0].generic->active_decode_targets[kS1T0]);
+
+ EXPECT_TRUE(headers[1].generic->active_decode_targets[kS0T0]);
+ EXPECT_TRUE(headers[1].generic->active_decode_targets[kS1T0]);
+
+ EXPECT_TRUE(headers[2].generic->active_decode_targets[kS0T0]);
+ EXPECT_TRUE(headers[2].generic->active_decode_targets[kS1T0]);
+
+ EXPECT_EQ(headers[0].generic->decode_target_indications[kS0T0],
+ DecodeTargetIndication::kSwitch);
+
+ EXPECT_EQ(headers[1].generic->decode_target_indications[kS0T0],
+ DecodeTargetIndication::kSwitch);
+
+ EXPECT_EQ(headers[2].generic->decode_target_indications[kS0T0],
+ DecodeTargetIndication::kNotPresent);
+ EXPECT_EQ(headers[2].generic->decode_target_indications[kS1T0],
+ DecodeTargetIndication::kSwitch);
+
+ EXPECT_THAT(headers[0].generic->dependencies, IsEmpty()); // S0, 1
+ EXPECT_THAT(headers[1].generic->dependencies, ElementsAre(1)); // S0, 3
+ EXPECT_THAT(headers[2].generic->dependencies, ElementsAre(3)); // S1, 5
+
+ EXPECT_EQ(headers[0].generic->chain_diffs[0], 0);
+
+ EXPECT_EQ(headers[1].generic->chain_diffs[0], 2);
+ EXPECT_EQ(headers[1].generic->chain_diffs[1], 0);
+
+ EXPECT_EQ(headers[2].generic->chain_diffs[0], 2);
+ EXPECT_EQ(headers[2].generic->chain_diffs[1], 2);
+}
+
+class RtpPayloadParamsH264ToGenericTest : public ::testing::Test {
+ public:
+ enum LayerSync { kNoSync, kSync };
+
+ RtpPayloadParamsH264ToGenericTest()
+ : state_(), params_(123, &state_, trials_config_) {}
+
+ void ConvertAndCheck(int temporal_index,
+ int64_t shared_frame_id,
+ VideoFrameType frame_type,
+ LayerSync layer_sync,
+ const std::set<int64_t>& expected_deps,
+ uint16_t width = 0,
+ uint16_t height = 0) {
+ EncodedImage encoded_image;
+ encoded_image._frameType = frame_type;
+ encoded_image._encodedWidth = width;
+ encoded_image._encodedHeight = height;
+
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecH264;
+ codec_info.codecSpecific.H264.temporal_idx = temporal_index;
+ codec_info.codecSpecific.H264.base_layer_sync = layer_sync == kSync;
+
+ RTPVideoHeader header =
+ params_.GetRtpVideoHeader(encoded_image, &codec_info, shared_frame_id);
+
+ ASSERT_TRUE(header.generic);
+ EXPECT_EQ(header.generic->spatial_index, 0);
+
+ EXPECT_EQ(header.generic->frame_id, shared_frame_id);
+ EXPECT_EQ(header.generic->temporal_index, temporal_index);
+ std::set<int64_t> actual_deps(header.generic->dependencies.begin(),
+ header.generic->dependencies.end());
+ EXPECT_EQ(expected_deps, actual_deps);
+
+ EXPECT_EQ(header.width, width);
+ EXPECT_EQ(header.height, height);
+ }
+
+ protected:
+ FieldTrialBasedConfig trials_config_;
+ RtpPayloadState state_;
+ RtpPayloadParams params_;
+};
+
+TEST_F(RtpPayloadParamsH264ToGenericTest, Keyframe) {
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+ ConvertAndCheck(0, 1, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(0, 2, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+}
+
+TEST_F(RtpPayloadParamsH264ToGenericTest, TooHighTemporalIndex) {
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+
+ EncodedImage encoded_image;
+ encoded_image._frameType = VideoFrameType::kVideoFrameDelta;
+ CodecSpecificInfo codec_info;
+ codec_info.codecType = kVideoCodecH264;
+ codec_info.codecSpecific.H264.temporal_idx =
+ RtpGenericFrameDescriptor::kMaxTemporalLayers;
+ codec_info.codecSpecific.H264.base_layer_sync = false;
+
+ RTPVideoHeader header =
+ params_.GetRtpVideoHeader(encoded_image, &codec_info, 1);
+ EXPECT_FALSE(header.generic);
+}
+
+TEST_F(RtpPayloadParamsH264ToGenericTest, LayerSync) {
+ // 02120212 pattern
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+ ConvertAndCheck(2, 1, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(1, 2, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(2, 3, VideoFrameType::kVideoFrameDelta, kNoSync, {0, 1, 2});
+
+ ConvertAndCheck(0, 4, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(2, 5, VideoFrameType::kVideoFrameDelta, kNoSync, {2, 3, 4});
+ ConvertAndCheck(1, 6, VideoFrameType::kVideoFrameDelta, kSync,
+ {4}); // layer sync
+ ConvertAndCheck(2, 7, VideoFrameType::kVideoFrameDelta, kNoSync, {4, 5, 6});
+}
+
+TEST_F(RtpPayloadParamsH264ToGenericTest, FrameIdGaps) {
+ // 0101 pattern
+ ConvertAndCheck(0, 0, VideoFrameType::kVideoFrameKey, kNoSync, {}, 480, 360);
+ ConvertAndCheck(1, 1, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+
+ ConvertAndCheck(0, 5, VideoFrameType::kVideoFrameDelta, kNoSync, {0});
+ ConvertAndCheck(1, 10, VideoFrameType::kVideoFrameDelta, kNoSync, {1, 5});
+
+ ConvertAndCheck(0, 15, VideoFrameType::kVideoFrameDelta, kNoSync, {5});
+ ConvertAndCheck(1, 20, VideoFrameType::kVideoFrameDelta, kNoSync, {10, 15});
+}
+
+} // namespace
+} // namespace webrtc