webrtc/call/rtp_payload_params_unittest.cc
Henrik Boström 2e540a28c0 Introduce EncodedImage.SimulcastIndex().
As part of go/unblocking-vp9-simulcast (Step 1), EncodedImage is being
upgraded to be able to differentiate between what is a simulcast index
and what is a spatial index.

In order not to break existing code assuming that "if codec != VP9,
SpatialIndex() is the simulcast index", SimulcastIndex() has fallback
logic to return the value of spatial_index_ in the event that
SetSimulcastIndex() has not been called. This allows migrating external
code from (Set)SpatialIndex() to (Set)SimulcastIndex(). During this
intermediate time, codec gates are still necessary in some places of
the code, see TODOs added.

In a follow-up CL, after having fixed dependencies, we'll be able to
remove the fallback logic and rely on SimulcastIndex() and
SpatialIndex() actually being the advertised index and "if codec..."
hacks will be a thing of the past!

Bug: webrtc:14884
Change-Id: I70095c091d0ce2336640451150888a3c3841df80
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/293343
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Evan Shrubsole <eshr@webrtc.org>
Reviewed-by: Philip Eliasson <philipel@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#39318}
2023-02-15 15:02:57 +00:00

1180 lines
48 KiB
C++

/*
* 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.SetSimulcastIndex(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