diff --git a/modules/video_coding/svc/BUILD.gn b/modules/video_coding/svc/BUILD.gn index 6fcf023306..c0576101dd 100644 --- a/modules/video_coding/svc/BUILD.gn +++ b/modules/video_coding/svc/BUILD.gn @@ -32,20 +32,18 @@ rtc_source_set("scalability_structures") { "create_scalability_structure.h", "scalability_structure_full_svc.cc", "scalability_structure_full_svc.h", + "scalability_structure_key_svc.cc", + "scalability_structure_key_svc.h", "scalability_structure_l1t2.cc", "scalability_structure_l1t2.h", "scalability_structure_l1t3.cc", "scalability_structure_l1t3.h", "scalability_structure_l2t1.cc", "scalability_structure_l2t1.h", - "scalability_structure_l2t1_key.cc", - "scalability_structure_l2t1_key.h", "scalability_structure_l2t1h.cc", "scalability_structure_l2t1h.h", "scalability_structure_l2t2.cc", "scalability_structure_l2t2.h", - "scalability_structure_l2t2_key.cc", - "scalability_structure_l2t2_key.h", "scalability_structure_l2t2_key_shift.cc", "scalability_structure_l2t2_key_shift.h", "scalability_structure_l3t1.cc", @@ -58,6 +56,7 @@ rtc_source_set("scalability_structures") { deps = [ ":scalable_video_controller", "../../../api/transport/rtp:dependency_descriptor", + "../../../api/video:video_bitrate_allocation", "../../../common_video/generic_frame_descriptor", "../../../rtc_base:checks", "../../../rtc_base:logging", @@ -73,6 +72,7 @@ if (rtc_include_tests) { rtc_library("scalability_structure_tests") { testonly = true sources = [ + "scalability_structure_key_svc_unittest.cc", "scalability_structure_l3t3_unittest.cc", "scalability_structure_test_helpers.cc", "scalability_structure_test_helpers.h", @@ -83,6 +83,7 @@ if (rtc_include_tests) { ":scalable_video_controller", "..:chain_diff_calculator", "..:frame_dependencies_calculator", + "../../../api:array_view", "../../../api/transport/rtp:dependency_descriptor", "../../../api/video:video_bitrate_allocation", "../../../api/video:video_frame_type", diff --git a/modules/video_coding/svc/create_scalability_structure.cc b/modules/video_coding/svc/create_scalability_structure.cc index a21fab2703..4b4a23ed24 100644 --- a/modules/video_coding/svc/create_scalability_structure.cc +++ b/modules/video_coding/svc/create_scalability_structure.cc @@ -12,13 +12,12 @@ #include #include "absl/strings/string_view.h" +#include "modules/video_coding/svc/scalability_structure_key_svc.h" #include "modules/video_coding/svc/scalability_structure_l1t2.h" #include "modules/video_coding/svc/scalability_structure_l1t3.h" #include "modules/video_coding/svc/scalability_structure_l2t1.h" -#include "modules/video_coding/svc/scalability_structure_l2t1_key.h" #include "modules/video_coding/svc/scalability_structure_l2t1h.h" #include "modules/video_coding/svc/scalability_structure_l2t2.h" -#include "modules/video_coding/svc/scalability_structure_l2t2_key.h" #include "modules/video_coding/svc/scalability_structure_l2t2_key_shift.h" #include "modules/video_coding/svc/scalability_structure_l3t1.h" #include "modules/video_coding/svc/scalability_structure_l3t3.h" @@ -54,6 +53,7 @@ constexpr NamedStructureFactory kFactories[] = { {"L2T2_KEY_SHIFT", Create}, {"L3T1", Create}, {"L3T3", Create}, + {"L3T3_KEY", Create}, {"S2T1", Create}, }; diff --git a/modules/video_coding/svc/scalability_structure_key_svc.cc b/modules/video_coding/svc/scalability_structure_key_svc.cc new file mode 100644 index 0000000000..cfc89a3794 --- /dev/null +++ b/modules/video_coding/svc/scalability_structure_key_svc.cc @@ -0,0 +1,336 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "modules/video_coding/svc/scalability_structure_key_svc.h" + +#include +#include +#include + +#include "absl/types/optional.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/video/video_bitrate_allocation.h" +#include "common_video/generic_frame_descriptor/generic_frame_info.h" +#include "modules/video_coding/svc/scalable_video_controller.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" + +namespace webrtc { +namespace { +// Values to use as LayerFrameConfig::Id +enum : int { kKey, kDelta }; + +DecodeTargetIndication +Dti(int sid, int tid, const ScalableVideoController::LayerFrameConfig& config) { + if (config.IsKeyframe() || config.Id() == kKey) { + RTC_DCHECK_EQ(config.TemporalId(), 0); + return sid < config.SpatialId() ? DecodeTargetIndication::kNotPresent + : DecodeTargetIndication::kSwitch; + } + + if (sid != config.SpatialId() || tid < config.TemporalId()) { + return DecodeTargetIndication::kNotPresent; + } + if (tid == config.TemporalId() && tid > 0) { + return DecodeTargetIndication::kDiscardable; + } + return DecodeTargetIndication::kSwitch; +} + +} // namespace + +constexpr int ScalabilityStructureKeySvc::kMaxNumSpatialLayers; +constexpr int ScalabilityStructureKeySvc::kMaxNumTemporalLayers; + +ScalabilityStructureKeySvc::ScalabilityStructureKeySvc(int num_spatial_layers, + int num_temporal_layers) + : num_spatial_layers_(num_spatial_layers), + num_temporal_layers_(num_temporal_layers), + active_decode_targets_( + (uint32_t{1} << (num_spatial_layers * num_temporal_layers)) - 1) { + // There is no point to use this structure without spatial scalability. + RTC_DCHECK_GT(num_spatial_layers, 1); + RTC_DCHECK_LE(num_spatial_layers, kMaxNumSpatialLayers); + RTC_DCHECK_LE(num_temporal_layers, kMaxNumTemporalLayers); +} + +ScalabilityStructureKeySvc::~ScalabilityStructureKeySvc() = default; + +ScalableVideoController::StreamLayersConfig +ScalabilityStructureKeySvc::StreamConfig() const { + StreamLayersConfig result; + result.num_spatial_layers = num_spatial_layers_; + result.num_temporal_layers = num_temporal_layers_; + result.scaling_factor_num[num_spatial_layers_ - 1] = 1; + result.scaling_factor_den[num_spatial_layers_ - 1] = 1; + for (int sid = num_spatial_layers_ - 1; sid > 0; --sid) { + result.scaling_factor_num[sid - 1] = 1; + result.scaling_factor_den[sid - 1] = 2 * result.scaling_factor_den[sid]; + } + return result; +} + +bool ScalabilityStructureKeySvc::TemporalLayerIsActive(int tid) const { + if (tid >= num_temporal_layers_) { + return false; + } + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + if (DecodeTargetIsActive(sid, tid)) { + return true; + } + } + return false; +} + +std::vector +ScalabilityStructureKeySvc::KeyframeConfig() { + std::vector configs; + configs.reserve(num_spatial_layers_); + absl::optional spatial_dependency_buffer_id; + spatial_id_is_enabled_.reset(); + // Disallow temporal references cross T0 on higher temporal layers. + can_reference_t1_frame_for_spatial_id_.reset(); + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + if (!DecodeTargetIsActive(sid, /*tid=*/0)) { + continue; + } + configs.emplace_back(); + ScalableVideoController::LayerFrameConfig& config = configs.back(); + config.Id(kKey).S(sid).T(0); + + if (spatial_dependency_buffer_id) { + config.Reference(*spatial_dependency_buffer_id); + } else { + config.Keyframe(); + } + config.Update(BufferIndex(sid, /*tid=*/0)); + + spatial_id_is_enabled_.set(sid); + spatial_dependency_buffer_id = BufferIndex(sid, /*tid=*/0); + } + return configs; +} + +std::vector +ScalabilityStructureKeySvc::T0Config() { + std::vector configs; + configs.reserve(num_spatial_layers_); + // Disallow temporal references cross T0 on higher temporal layers. + can_reference_t1_frame_for_spatial_id_.reset(); + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + if (!DecodeTargetIsActive(sid, /*tid=*/0)) { + spatial_id_is_enabled_.reset(sid); + continue; + } + configs.emplace_back(); + configs.back().Id(kDelta).S(sid).T(0).ReferenceAndUpdate( + BufferIndex(sid, /*tid=*/0)); + } + return configs; +} + +std::vector +ScalabilityStructureKeySvc::T1Config() { + std::vector configs; + configs.reserve(num_spatial_layers_); + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + if (!DecodeTargetIsActive(sid, /*tid=*/1)) { + continue; + } + configs.emplace_back(); + ScalableVideoController::LayerFrameConfig& config = configs.back(); + config.Id(kDelta).S(sid).T(1).Reference(BufferIndex(sid, /*tid=*/0)); + if (num_temporal_layers_ > 2) { + config.Update(BufferIndex(sid, /*tid=*/1)); + can_reference_t1_frame_for_spatial_id_.set(sid); + } + } + return configs; +} + +std::vector +ScalabilityStructureKeySvc::T2Config() { + std::vector configs; + configs.reserve(num_spatial_layers_); + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + if (!DecodeTargetIsActive(sid, /*tid=*/2)) { + continue; + } + configs.emplace_back(); + ScalableVideoController::LayerFrameConfig& config = configs.back(); + config.Id(kDelta).S(sid).T(2); + if (can_reference_t1_frame_for_spatial_id_[sid]) { + config.Reference(BufferIndex(sid, /*tid=*/1)); + } else { + config.Reference(BufferIndex(sid, /*tid=*/0)); + } + } + return configs; +} + +std::vector +ScalabilityStructureKeySvc::NextFrameConfig(bool restart) { + if (active_decode_targets_.none()) { + last_pattern_ = kNone; + return {}; + } + + if (restart) { + last_pattern_ = kNone; + } + + switch (last_pattern_) { + case kNone: + last_pattern_ = kDeltaT0; + return KeyframeConfig(); + case kDeltaT2B: + last_pattern_ = kDeltaT0; + return T0Config(); + case kDeltaT2A: + if (TemporalLayerIsActive(1)) { + last_pattern_ = kDeltaT1; + return T1Config(); + } + last_pattern_ = kDeltaT0; + return T0Config(); + case kDeltaT1: + if (TemporalLayerIsActive(2)) { + last_pattern_ = kDeltaT2B; + return T2Config(); + } + last_pattern_ = kDeltaT0; + return T0Config(); + case kDeltaT0: + if (TemporalLayerIsActive(2)) { + last_pattern_ = kDeltaT2A; + return T2Config(); + } else if (TemporalLayerIsActive(1)) { + last_pattern_ = kDeltaT1; + return T1Config(); + } + last_pattern_ = kDeltaT0; + return T0Config(); + } + RTC_NOTREACHED(); + return {}; +} + +GenericFrameInfo ScalabilityStructureKeySvc::OnEncodeDone( + const LayerFrameConfig& config) { + GenericFrameInfo frame_info; + frame_info.spatial_id = config.SpatialId(); + frame_info.temporal_id = config.TemporalId(); + frame_info.encoder_buffers = config.Buffers(); + frame_info.decode_target_indications.reserve(num_spatial_layers_ * + num_temporal_layers_); + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + for (int tid = 0; tid < num_temporal_layers_; ++tid) { + frame_info.decode_target_indications.push_back(Dti(sid, tid, config)); + } + } + frame_info.part_of_chain.assign(num_spatial_layers_, false); + if (config.IsKeyframe() || config.Id() == kKey) { + RTC_DCHECK_EQ(config.TemporalId(), 0); + for (int sid = config.SpatialId(); sid < num_spatial_layers_; ++sid) { + frame_info.part_of_chain[sid] = true; + } + } else if (config.TemporalId() == 0) { + frame_info.part_of_chain[config.SpatialId()] = true; + } + frame_info.active_decode_targets = active_decode_targets_; + return frame_info; +} + +void ScalabilityStructureKeySvc::OnRatesUpdated( + const VideoBitrateAllocation& bitrates) { + for (int sid = 0; sid < num_spatial_layers_; ++sid) { + // Enable/disable spatial layers independetely. + bool active = bitrates.GetBitrate(sid, /*tid=*/0) > 0; + SetDecodeTargetIsActive(sid, /*tid=*/0, active); + if (!spatial_id_is_enabled_[sid] && active) { + // Key frame is required to reenable any spatial layer. + last_pattern_ = kNone; + } + + for (int tid = 1; tid < num_temporal_layers_; ++tid) { + // To enable temporal layer, require bitrates for lower temporal layers. + active = active && bitrates.GetBitrate(sid, tid) > 0; + SetDecodeTargetIsActive(sid, tid, active); + } + } +} + +ScalabilityStructureL2T1Key::~ScalabilityStructureL2T1Key() = default; + +FrameDependencyStructure ScalabilityStructureL2T1Key::DependencyStructure() + const { + FrameDependencyStructure structure; + structure.num_decode_targets = 2; + structure.num_chains = 2; + structure.decode_target_protected_by_chain = {0, 1}; + structure.templates.resize(4); + structure.templates[0].S(0).Dtis("S-").ChainDiffs({2, 1}).FrameDiffs({2}); + structure.templates[1].S(0).Dtis("SS").ChainDiffs({0, 0}); + structure.templates[2].S(1).Dtis("-S").ChainDiffs({1, 2}).FrameDiffs({2}); + structure.templates[3].S(1).Dtis("-S").ChainDiffs({1, 1}).FrameDiffs({1}); + return structure; +} + +ScalabilityStructureL2T2Key::~ScalabilityStructureL2T2Key() = default; + +FrameDependencyStructure ScalabilityStructureL2T2Key::DependencyStructure() + const { + FrameDependencyStructure structure; + structure.num_decode_targets = 4; + structure.num_chains = 2; + structure.decode_target_protected_by_chain = {0, 0, 1, 1}; + structure.templates.resize(6); + auto& templates = structure.templates; + templates[0].S(0).T(0).Dtis("SSSS").ChainDiffs({0, 0}); + templates[1].S(0).T(0).Dtis("SS--").ChainDiffs({4, 3}).FrameDiffs({4}); + templates[2].S(0).T(1).Dtis("-D--").ChainDiffs({2, 1}).FrameDiffs({2}); + templates[3].S(1).T(0).Dtis("--SS").ChainDiffs({1, 1}).FrameDiffs({1}); + templates[4].S(1).T(0).Dtis("--SS").ChainDiffs({1, 4}).FrameDiffs({4}); + templates[5].S(1).T(1).Dtis("---D").ChainDiffs({3, 2}).FrameDiffs({2}); + return structure; +} + +ScalabilityStructureL3T3Key::~ScalabilityStructureL3T3Key() = default; + +FrameDependencyStructure ScalabilityStructureL3T3Key::DependencyStructure() + const { + FrameDependencyStructure structure; + structure.num_decode_targets = 9; + structure.num_chains = 3; + structure.decode_target_protected_by_chain = {0, 0, 0, 1, 1, 1, 2, 2, 2}; + auto& t = structure.templates; + t.resize(15); + // Templates are shown in the order frames following them appear in the + // stream, but in `structure.templates` array templates are sorted by + // (`spatial_id`, `temporal_id`) since that is a dependency descriptor + // requirement. Indexes are written in hex for nicer alignment. + t[0x0].S(0).T(0).Dtis("SSSSSSSSS").ChainDiffs({0, 0, 0}); + t[0x5].S(1).T(0).Dtis("---SSSSSS").ChainDiffs({1, 1, 1}).FrameDiffs({1}); + t[0xA].S(2).T(0).Dtis("------SSS").ChainDiffs({2, 1, 1}).FrameDiffs({1}); + t[0x3].S(0).T(2).Dtis("--D------").ChainDiffs({3, 2, 1}).FrameDiffs({3}); + t[0x8].S(1).T(2).Dtis("-----D---").ChainDiffs({4, 3, 2}).FrameDiffs({3}); + t[0xD].S(2).T(2).Dtis("--------D").ChainDiffs({5, 4, 3}).FrameDiffs({3}); + t[0x2].S(0).T(1).Dtis("-DS------").ChainDiffs({6, 5, 4}).FrameDiffs({6}); + t[0x7].S(1).T(1).Dtis("----DS---").ChainDiffs({7, 6, 5}).FrameDiffs({6}); + t[0xC].S(2).T(1).Dtis("-------DS").ChainDiffs({8, 7, 6}).FrameDiffs({6}); + t[0x4].S(0).T(2).Dtis("--D------").ChainDiffs({9, 8, 7}).FrameDiffs({3}); + t[0x9].S(1).T(2).Dtis("-----D---").ChainDiffs({10, 9, 8}).FrameDiffs({3}); + t[0xE].S(2).T(2).Dtis("--------D").ChainDiffs({11, 10, 9}).FrameDiffs({3}); + t[0x1].S(0).T(0).Dtis("SSS------").ChainDiffs({12, 11, 10}).FrameDiffs({12}); + t[0x6].S(1).T(0).Dtis("---SSS---").ChainDiffs({1, 12, 11}).FrameDiffs({12}); + t[0xB].S(2).T(0).Dtis("------SSS").ChainDiffs({2, 1, 12}).FrameDiffs({12}); + return structure; +} + +} // namespace webrtc diff --git a/modules/video_coding/svc/scalability_structure_key_svc.h b/modules/video_coding/svc/scalability_structure_key_svc.h new file mode 100644 index 0000000000..1d3277b5cd --- /dev/null +++ b/modules/video_coding/svc/scalability_structure_key_svc.h @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#ifndef MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_KEY_SVC_H_ +#define MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_KEY_SVC_H_ + +#include +#include + +#include "api/transport/rtp/dependency_descriptor.h" +#include "api/video/video_bitrate_allocation.h" +#include "common_video/generic_frame_descriptor/generic_frame_info.h" +#include "modules/video_coding/svc/scalable_video_controller.h" + +namespace webrtc { + +class ScalabilityStructureKeySvc : public ScalableVideoController { + public: + ScalabilityStructureKeySvc(int num_spatial_layers, int num_temporal_layers); + ~ScalabilityStructureKeySvc() override; + + StreamLayersConfig StreamConfig() const override; + + std::vector NextFrameConfig(bool restart) override; + GenericFrameInfo OnEncodeDone(const LayerFrameConfig& config) override; + void OnRatesUpdated(const VideoBitrateAllocation& bitrates) override; + + private: + enum FramePattern { + kNone, + kDeltaT0, + kDeltaT2A, + kDeltaT1, + kDeltaT2B, + }; + static constexpr int kMaxNumSpatialLayers = 3; + static constexpr int kMaxNumTemporalLayers = 3; + + // Index of the buffer to store last frame for layer (`sid`, `tid`) + int BufferIndex(int sid, int tid) const { + return tid * num_spatial_layers_ + sid; + } + bool DecodeTargetIsActive(int sid, int tid) const { + return active_decode_targets_[sid * num_temporal_layers_ + tid]; + } + void SetDecodeTargetIsActive(int sid, int tid, bool value) { + active_decode_targets_.set(sid * num_temporal_layers_ + tid, value); + } + bool TemporalLayerIsActive(int tid) const; + std::vector KeyframeConfig(); + std::vector T0Config(); + std::vector T1Config(); + std::vector T2Config(); + + const int num_spatial_layers_; + const int num_temporal_layers_; + + FramePattern last_pattern_ = kNone; + std::bitset spatial_id_is_enabled_; + std::bitset can_reference_t1_frame_for_spatial_id_; + std::bitset<32> active_decode_targets_; +}; + +// S1 0--0--0- +// | ... +// S0 0--0--0- +class ScalabilityStructureL2T1Key : public ScalabilityStructureKeySvc { + public: + ScalabilityStructureL2T1Key() : ScalabilityStructureKeySvc(2, 1) {} + ~ScalabilityStructureL2T1Key() override; + + FrameDependencyStructure DependencyStructure() const override; +}; + +// S1T1 0 0 +// / / / +// S1T0 0---0---0 +// | ... +// S0T1 | 0 0 +// |/ / / +// S0T0 0---0---0 +// Time-> 0 1 2 3 4 +class ScalabilityStructureL2T2Key : public ScalabilityStructureKeySvc { + public: + ScalabilityStructureL2T2Key() : ScalabilityStructureKeySvc(2, 2) {} + ~ScalabilityStructureL2T2Key() override; + + FrameDependencyStructure DependencyStructure() const override; +}; + +class ScalabilityStructureL3T3Key : public ScalabilityStructureKeySvc { + public: + ScalabilityStructureL3T3Key() : ScalabilityStructureKeySvc(3, 3) {} + ~ScalabilityStructureL3T3Key() override; + + FrameDependencyStructure DependencyStructure() const override; +}; + +} // namespace webrtc + +#endif // MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_KEY_SVC_H_ diff --git a/modules/video_coding/svc/scalability_structure_key_svc_unittest.cc b/modules/video_coding/svc/scalability_structure_key_svc_unittest.cc new file mode 100644 index 0000000000..752f710eb6 --- /dev/null +++ b/modules/video_coding/svc/scalability_structure_key_svc_unittest.cc @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ +#include "modules/video_coding/svc/scalability_structure_key_svc.h" + +#include + +#include "api/array_view.h" +#include "api/transport/rtp/dependency_descriptor.h" +#include "common_video/generic_frame_descriptor/generic_frame_info.h" +#include "modules/video_coding/svc/scalability_structure_test_helpers.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::SizeIs; + +TEST(ScalabilityStructureL3T3KeyTest, + SkipingT1FrameOnOneSpatialLayerKeepsStructureValid) { + ScalabilityStructureL3T3Key structure; + ScalabilityStructureWrapper wrapper(structure); + std::vector frames; + + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/3, /*s1=*/3)); + wrapper.GenerateFrames(/*num_temporal_units=*/2, frames); + EXPECT_THAT(frames, SizeIs(4)); + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/3, /*s1=*/1)); + wrapper.GenerateFrames(/*num_temporal_units=*/1, frames); + EXPECT_THAT(frames, SizeIs(5)); + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/3, /*s1=*/3)); + wrapper.GenerateFrames(/*num_temporal_units=*/1, frames); + ASSERT_THAT(frames, SizeIs(7)); + + EXPECT_EQ(frames[0].temporal_id, 0); + EXPECT_EQ(frames[1].temporal_id, 0); + EXPECT_EQ(frames[2].temporal_id, 2); + EXPECT_EQ(frames[3].temporal_id, 2); + EXPECT_EQ(frames[4].temporal_id, 1); + EXPECT_EQ(frames[5].temporal_id, 2); + EXPECT_EQ(frames[6].temporal_id, 2); + EXPECT_TRUE(wrapper.FrameReferencesAreValid(frames)); +} + +TEST(ScalabilityStructureL3T3KeyTest, + ReenablingSpatialLayerBeforeMissedT0FrameDoesntTriggerAKeyFrame) { + ScalabilityStructureL3T3Key structure; + ScalabilityStructureWrapper wrapper(structure); + std::vector frames; + + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/2, /*s1=*/2)); + wrapper.GenerateFrames(1, frames); + EXPECT_THAT(frames, SizeIs(2)); + // Drop a spatial layer. + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/2, /*s1=*/0)); + wrapper.GenerateFrames(1, frames); + EXPECT_THAT(frames, SizeIs(3)); + // Reenable a spatial layer before T0 frame is encoded. + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/2, /*s1=*/2)); + wrapper.GenerateFrames(1, frames); + EXPECT_THAT(frames, SizeIs(5)); + + EXPECT_EQ(frames[0].temporal_id, 0); + EXPECT_EQ(frames[1].temporal_id, 0); + EXPECT_EQ(frames[2].temporal_id, 1); + EXPECT_EQ(frames[3].temporal_id, 0); + EXPECT_EQ(frames[4].temporal_id, 0); + EXPECT_THAT(frames[3].frame_diffs, SizeIs(1)); + EXPECT_THAT(frames[4].frame_diffs, SizeIs(1)); + EXPECT_TRUE(wrapper.FrameReferencesAreValid(frames)); +} + +TEST(ScalabilityStructureL3T3KeyTest, ReenablingSpatialLayerTriggersKeyFrame) { + ScalabilityStructureL3T3Key structure; + ScalabilityStructureWrapper wrapper(structure); + std::vector frames; + + // Start with all spatial layers enabled. + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/2, /*s1=*/2, /*s2=*/2)); + wrapper.GenerateFrames(3, frames); + EXPECT_THAT(frames, SizeIs(9)); + // Drop a spatial layer. Two remaining spatial layers should just continue. + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/2, /*s1=*/0, /*s2=*/2)); + wrapper.GenerateFrames(2, frames); + EXPECT_THAT(frames, SizeIs(13)); + // Reenable spatial layer, expect a full restart. + structure.OnRatesUpdated(EnableTemporalLayers(/*s0=*/2, /*s1=*/2, /*s2=*/2)); + wrapper.GenerateFrames(1, frames); + ASSERT_THAT(frames, SizeIs(16)); + + // First 3 temporal units with all spatial layers enabled. + EXPECT_EQ(frames[0].temporal_id, 0); + EXPECT_EQ(frames[3].temporal_id, 1); + EXPECT_EQ(frames[6].temporal_id, 0); + // 2 temporal units with spatial layer 1 disabled. + EXPECT_EQ(frames[9].spatial_id, 0); + EXPECT_EQ(frames[9].temporal_id, 1); + EXPECT_EQ(frames[10].spatial_id, 2); + EXPECT_EQ(frames[10].temporal_id, 1); + // T0 frames were encoded while spatial layer 1 is disabled. + EXPECT_EQ(frames[11].spatial_id, 0); + EXPECT_EQ(frames[11].temporal_id, 0); + EXPECT_EQ(frames[12].spatial_id, 2); + EXPECT_EQ(frames[12].temporal_id, 0); + // Key frame to reenable spatial layer 1. + EXPECT_THAT(frames[13].frame_diffs, IsEmpty()); + EXPECT_THAT(frames[14].frame_diffs, ElementsAre(1)); + EXPECT_THAT(frames[15].frame_diffs, ElementsAre(1)); + EXPECT_EQ(frames[13].temporal_id, 0); + EXPECT_EQ(frames[14].temporal_id, 0); + EXPECT_EQ(frames[15].temporal_id, 0); + auto all_frames = rtc::MakeArrayView(frames.data(), frames.size()); + EXPECT_TRUE(wrapper.FrameReferencesAreValid(all_frames.subview(0, 13))); + // Frames starting from the frame#13 should not reference any earlier frames. + EXPECT_TRUE(wrapper.FrameReferencesAreValid(all_frames.subview(13))); +} + +} // namespace +} // namespace webrtc diff --git a/modules/video_coding/svc/scalability_structure_l2t1_key.cc b/modules/video_coding/svc/scalability_structure_l2t1_key.cc deleted file mode 100644 index 4b3f322477..0000000000 --- a/modules/video_coding/svc/scalability_structure_l2t1_key.cc +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ -#include "modules/video_coding/svc/scalability_structure_l2t1_key.h" - -#include -#include - -#include "absl/base/macros.h" -#include "api/transport/rtp/dependency_descriptor.h" -#include "rtc_base/checks.h" -#include "rtc_base/logging.h" - -namespace webrtc { -namespace { - -constexpr auto kNotPresent = DecodeTargetIndication::kNotPresent; -constexpr auto kSwitch = DecodeTargetIndication::kSwitch; - -constexpr DecodeTargetIndication kDtis[3][2] = { - {kSwitch, kSwitch}, // Key, S0 - {kSwitch, kNotPresent}, // Delta, S0 - {kNotPresent, kSwitch}, // Key and Delta, S1 -}; - -} // namespace - -ScalabilityStructureL2T1Key::~ScalabilityStructureL2T1Key() = default; - -ScalableVideoController::StreamLayersConfig -ScalabilityStructureL2T1Key::StreamConfig() const { - StreamLayersConfig result; - result.num_spatial_layers = 2; - result.num_temporal_layers = 1; - result.scaling_factor_num[0] = 1; - result.scaling_factor_den[0] = 2; - return result; -} - -FrameDependencyStructure ScalabilityStructureL2T1Key::DependencyStructure() - const { - FrameDependencyStructure structure; - structure.num_decode_targets = 2; - structure.num_chains = 2; - structure.decode_target_protected_by_chain = {0, 1}; - structure.templates.resize(4); - structure.templates[0].S(0).Dtis("S-").ChainDiffs({2, 1}).FrameDiffs({2}); - structure.templates[1].S(0).Dtis("SS").ChainDiffs({0, 0}); - structure.templates[2].S(1).Dtis("-S").ChainDiffs({1, 2}).FrameDiffs({2}); - structure.templates[3].S(1).Dtis("-S").ChainDiffs({1, 1}).FrameDiffs({1}); - return structure; -} - -ScalableVideoController::LayerFrameConfig -ScalabilityStructureL2T1Key::KeyFrameConfig() const { - return LayerFrameConfig().Id(0).S(0).Keyframe().Update(0); -} - -std::vector -ScalabilityStructureL2T1Key::NextFrameConfig(bool restart) { - std::vector result(2); - - // Buffer0 keeps latest S0T0 frame, Buffer1 keeps latest S1T0 frame. - if (restart || keyframe_) { - result[0] = KeyFrameConfig(); - result[1].Id(2).S(1).Reference(0).Update(1); - keyframe_ = false; - } else { - result[0].Id(1).S(0).ReferenceAndUpdate(0); - result[1].Id(2).S(1).ReferenceAndUpdate(1); - } - return result; -} - -GenericFrameInfo ScalabilityStructureL2T1Key::OnEncodeDone( - const LayerFrameConfig& config) { - RTC_CHECK_GE(config.Id(), 0); - RTC_CHECK_LT(config.Id(), ABSL_ARRAYSIZE(kDtis)); - - GenericFrameInfo frame_info; - frame_info.spatial_id = config.SpatialId(); - frame_info.temporal_id = config.TemporalId(); - frame_info.encoder_buffers = config.Buffers(); - int config_id = config.IsKeyframe() ? 0 : config.Id(); - frame_info.decode_target_indications.assign(std::begin(kDtis[config_id]), - std::end(kDtis[config_id])); - if (config.IsKeyframe()) { - frame_info.part_of_chain = {true, true}; - } else { - frame_info.part_of_chain = {config.SpatialId() == 0, - config.SpatialId() == 1}; - } - return frame_info; -} - -} // namespace webrtc diff --git a/modules/video_coding/svc/scalability_structure_l2t1_key.h b/modules/video_coding/svc/scalability_structure_l2t1_key.h deleted file mode 100644 index adafbb9913..0000000000 --- a/modules/video_coding/svc/scalability_structure_l2t1_key.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ -#ifndef MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_L2T1_KEY_H_ -#define MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_L2T1_KEY_H_ - -#include - -#include "api/transport/rtp/dependency_descriptor.h" -#include "common_video/generic_frame_descriptor/generic_frame_info.h" -#include "modules/video_coding/svc/scalable_video_controller.h" - -namespace webrtc { - -// S1 0--0--0- -// | ... -// S0 0--0--0- -class ScalabilityStructureL2T1Key : public ScalableVideoController { - public: - ~ScalabilityStructureL2T1Key() override; - - StreamLayersConfig StreamConfig() const override; - FrameDependencyStructure DependencyStructure() const override; - - std::vector NextFrameConfig(bool restart) override; - GenericFrameInfo OnEncodeDone(const LayerFrameConfig& config) override; - - private: - LayerFrameConfig KeyFrameConfig() const; - - bool keyframe_ = true; -}; - -} // namespace webrtc - -#endif // MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_L2T1_KEY_H_ diff --git a/modules/video_coding/svc/scalability_structure_l2t2_key.cc b/modules/video_coding/svc/scalability_structure_l2t2_key.cc deleted file mode 100644 index 0d052c1943..0000000000 --- a/modules/video_coding/svc/scalability_structure_l2t2_key.cc +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ -#include "modules/video_coding/svc/scalability_structure_l2t2_key.h" - -#include -#include - -#include "absl/base/macros.h" -#include "api/transport/rtp/dependency_descriptor.h" -#include "rtc_base/checks.h" -#include "rtc_base/logging.h" - -namespace webrtc { -namespace { - -constexpr auto kNotPresent = DecodeTargetIndication::kNotPresent; -constexpr auto kDiscardable = DecodeTargetIndication::kDiscardable; -constexpr auto kSwitch = DecodeTargetIndication::kSwitch; - -// decode targets: S0T0, S0T1, S1T0, S1T1 -constexpr DecodeTargetIndication kDtis[6][4] = { - {kSwitch, kSwitch, kSwitch, kSwitch}, // kKey, S0 - {kNotPresent, kNotPresent, kSwitch, kSwitch}, // kKey, S1 - {kNotPresent, kDiscardable, kNotPresent, kNotPresent}, // kDeltaT1, S0 - {kNotPresent, kNotPresent, kNotPresent, kDiscardable}, // kDeltaT1, S1 - {kSwitch, kSwitch, kNotPresent, kNotPresent}, // kDeltaT0, S0 - {kNotPresent, kNotPresent, kSwitch, kSwitch}, // kDeltaT0, S1 -}; - -} // namespace - -ScalabilityStructureL2T2Key::~ScalabilityStructureL2T2Key() = default; - -ScalableVideoController::StreamLayersConfig -ScalabilityStructureL2T2Key::StreamConfig() const { - StreamLayersConfig result; - result.num_spatial_layers = 2; - result.num_temporal_layers = 2; - result.scaling_factor_num[0] = 1; - result.scaling_factor_den[0] = 2; - return result; -} - -FrameDependencyStructure ScalabilityStructureL2T2Key::DependencyStructure() - const { - FrameDependencyStructure structure; - structure.num_decode_targets = 4; - structure.num_chains = 2; - structure.decode_target_protected_by_chain = {0, 0, 1, 1}; - structure.templates.resize(6); - auto& templates = structure.templates; - templates[0].S(0).T(0).Dtis("SSSS").ChainDiffs({0, 0}); - templates[1].S(0).T(0).Dtis("SS--").ChainDiffs({4, 3}).FrameDiffs({4}); - templates[2].S(0).T(1).Dtis("-D--").ChainDiffs({2, 1}).FrameDiffs({2}); - templates[3].S(1).T(0).Dtis("--SS").ChainDiffs({1, 1}).FrameDiffs({1}); - templates[4].S(1).T(0).Dtis("--SS").ChainDiffs({1, 4}).FrameDiffs({4}); - templates[5].S(1).T(1).Dtis("---D").ChainDiffs({3, 2}).FrameDiffs({2}); - return structure; -} - -ScalableVideoController::LayerFrameConfig -ScalabilityStructureL2T2Key::KeyFrameConfig() const { - return LayerFrameConfig().Id(0).Keyframe().S(0).T(0).Update(0); -} - -std::vector -ScalabilityStructureL2T2Key::NextFrameConfig(bool restart) { - if (restart) { - next_pattern_ = kKey; - } - std::vector result(2); - - // Buffer0 keeps latest S0T0 frame, - // Buffer1 keeps latest S1T0 frame. - switch (next_pattern_) { - case kKey: - result[0] = KeyFrameConfig(); - result[1].Id(1).S(1).T(0).Reference(0).Update(1); - next_pattern_ = kDeltaT1; - break; - case kDeltaT1: - result[0].Id(2).S(0).T(1).Reference(0); - result[1].Id(3).S(1).T(1).Reference(1); - next_pattern_ = kDeltaT0; - break; - case kDeltaT0: - result[0].Id(4).S(0).T(0).ReferenceAndUpdate(0); - result[1].Id(5).S(1).T(0).ReferenceAndUpdate(1); - next_pattern_ = kDeltaT1; - break; - } - return result; -} - -GenericFrameInfo ScalabilityStructureL2T2Key::OnEncodeDone( - const LayerFrameConfig& config) { - RTC_CHECK_GE(config.Id(), 0); - RTC_CHECK_LT(config.Id(), ABSL_ARRAYSIZE(kDtis)); - - GenericFrameInfo frame_info; - frame_info.spatial_id = config.SpatialId(); - frame_info.temporal_id = config.TemporalId(); - frame_info.encoder_buffers = config.Buffers(); - int config_id = config.IsKeyframe() ? 0 : config.Id(); - frame_info.decode_target_indications.assign(std::begin(kDtis[config_id]), - std::end(kDtis[config_id])); - if (config.IsKeyframe()) { - frame_info.part_of_chain = {true, true}; - } else if (config.TemporalId() == 0) { - frame_info.part_of_chain = {config.SpatialId() == 0, - config.SpatialId() == 1}; - } else { - frame_info.part_of_chain = {false, false}; - } - return frame_info; -} - -} // namespace webrtc diff --git a/modules/video_coding/svc/scalability_structure_l2t2_key.h b/modules/video_coding/svc/scalability_structure_l2t2_key.h deleted file mode 100644 index e11d0d4fa0..0000000000 --- a/modules/video_coding/svc/scalability_structure_l2t2_key.h +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2020 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ -#ifndef MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_L2T2_KEY_H_ -#define MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_L2T2_KEY_H_ - -#include - -#include "api/transport/rtp/dependency_descriptor.h" -#include "common_video/generic_frame_descriptor/generic_frame_info.h" -#include "modules/video_coding/svc/scalable_video_controller.h" - -namespace webrtc { - -// S1T1 0 0 -// / / / -// S1T0 0---0---0 -// | ... -// S0T1 | 0 0 -// |/ / / -// S0T0 0---0---0 -// Time-> 0 1 2 3 4 -class ScalabilityStructureL2T2Key : public ScalableVideoController { - public: - ~ScalabilityStructureL2T2Key() override; - - StreamLayersConfig StreamConfig() const override; - FrameDependencyStructure DependencyStructure() const override; - - std::vector NextFrameConfig(bool restart) override; - GenericFrameInfo OnEncodeDone(const LayerFrameConfig& config) override; - - private: - enum FramePattern { - kKey, - kDeltaT1, - kDeltaT0, - }; - LayerFrameConfig KeyFrameConfig() const; - - FramePattern next_pattern_ = kKey; -}; - -} // namespace webrtc - -#endif // MODULES_VIDEO_CODING_SVC_SCALABILITY_STRUCTURE_L2T2_KEY_H_ diff --git a/modules/video_coding/svc/scalability_structure_test_helpers.cc b/modules/video_coding/svc/scalability_structure_test_helpers.cc index f549465d68..2b0393f9cf 100644 --- a/modules/video_coding/svc/scalability_structure_test_helpers.cc +++ b/modules/video_coding/svc/scalability_structure_test_helpers.cc @@ -14,6 +14,7 @@ #include #include +#include "api/array_view.h" #include "api/transport/rtp/dependency_descriptor.h" #include "api/video/video_bitrate_allocation.h" #include "api/video/video_frame_type.h" @@ -38,58 +39,66 @@ VideoBitrateAllocation EnableTemporalLayers(int s0, int s1, int s2) { return bitrate; } -std::vector ScalabilityStructureWrapper::GenerateFrames( +void ScalabilityStructureWrapper::GenerateFrames( int num_temporal_units, - bool restart) { - std::vector frames; + std::vector& frames) { for (int i = 0; i < num_temporal_units; ++i) { - for (auto& layer_frame : structure_controller_.NextFrameConfig(restart)) { + for (auto& layer_frame : + structure_controller_.NextFrameConfig(/*restart=*/false)) { int64_t frame_id = ++frame_id_; bool is_keyframe = layer_frame.IsKeyframe(); - absl::optional frame_info = - structure_controller_.OnEncodeDone(std::move(layer_frame)); - EXPECT_TRUE(frame_info.has_value()); + GenericFrameInfo frame_info = + structure_controller_.OnEncodeDone(layer_frame); if (is_keyframe) { - chain_diff_calculator_.Reset(frame_info->part_of_chain); + chain_diff_calculator_.Reset(frame_info.part_of_chain); } - frame_info->chain_diffs = - chain_diff_calculator_.From(frame_id, frame_info->part_of_chain); + frame_info.chain_diffs = + chain_diff_calculator_.From(frame_id, frame_info.part_of_chain); for (int64_t base_frame_id : frame_deps_calculator_.FromBuffersUsage( is_keyframe ? VideoFrameType::kVideoFrameKey : VideoFrameType::kVideoFrameDelta, - frame_id, frame_info->encoder_buffers)) { - EXPECT_LT(base_frame_id, frame_id); - EXPECT_GE(base_frame_id, 0); - frame_info->frame_diffs.push_back(frame_id - base_frame_id); + frame_id, frame_info.encoder_buffers)) { + frame_info.frame_diffs.push_back(frame_id - base_frame_id); } - frames.push_back(*std::move(frame_info)); + frames.push_back(std::move(frame_info)); } - restart = false; } +} - if (restart) { - buffer_contains_frame_.reset(); - } - for (const GenericFrameInfo& frame : frames) { +bool ScalabilityStructureWrapper::FrameReferencesAreValid( + rtc::ArrayView frames) const { + bool valid = true; + // VP9 and AV1 supports up to 8 buffers. Expect no more buffers are not used. + std::bitset<8> buffer_contains_frame; + for (size_t i = 0; i < frames.size(); ++i) { + const GenericFrameInfo& frame = frames[i]; for (const CodecBufferUsage& buffer_usage : frame.encoder_buffers) { if (buffer_usage.id < 0 || buffer_usage.id >= 8) { ADD_FAILURE() << "Invalid buffer id " << buffer_usage.id + << " for frame#" << i << ". Up to 8 buffers are supported."; + valid = false; continue; } - if (buffer_usage.referenced && !buffer_contains_frame_[buffer_usage.id]) { - ADD_FAILURE() << "buffer " << buffer_usage.id + if (buffer_usage.referenced && !buffer_contains_frame[buffer_usage.id]) { + ADD_FAILURE() << "buffer " << buffer_usage.id << " for frame#" << i << " was reference before updated."; + valid = false; } if (buffer_usage.updated) { - buffer_contains_frame_.set(buffer_usage.id); + buffer_contains_frame.set(buffer_usage.id); + } + } + for (int fdiff : frame.frame_diffs) { + if (fdiff <= 0 || static_cast(fdiff) > i) { + ADD_FAILURE() << "Invalid frame diff " << fdiff << " for frame#" << i; + valid = false; } } } - - return frames; + return valid; } } // namespace webrtc diff --git a/modules/video_coding/svc/scalability_structure_test_helpers.h b/modules/video_coding/svc/scalability_structure_test_helpers.h index 30ebb153e8..d183be4766 100644 --- a/modules/video_coding/svc/scalability_structure_test_helpers.h +++ b/modules/video_coding/svc/scalability_structure_test_helpers.h @@ -14,6 +14,7 @@ #include +#include "api/array_view.h" #include "api/transport/rtp/dependency_descriptor.h" #include "api/video/video_bitrate_allocation.h" #include "common_video/generic_frame_descriptor/generic_frame_info.h" @@ -32,16 +33,22 @@ class ScalabilityStructureWrapper { explicit ScalabilityStructureWrapper(ScalableVideoController& structure) : structure_controller_(structure) {} - std::vector GenerateFrames(int num_tempral_units, - bool restart); - std::vector GenerateFrames(int num_temporal_units) { - return GenerateFrames(num_temporal_units, /*restart=*/false); + std::vector frames; + GenerateFrames(num_temporal_units, frames); + return frames; } + void GenerateFrames(int num_temporal_units, + std::vector& frames); + + // Returns false and ADD_FAILUREs for frames with invalid references. + // In particular validates no frame frame reference to frame before frames[0]. + // In error messages frames are indexed starting with 0. + bool FrameReferencesAreValid( + rtc::ArrayView frames) const; private: ScalableVideoController& structure_controller_; - std::bitset<8> buffer_contains_frame_ = 0; FrameDependenciesCalculator frame_deps_calculator_; ChainDiffCalculator chain_diff_calculator_; int64_t frame_id_ = 0; diff --git a/modules/video_coding/svc/scalability_structure_unittest.cc b/modules/video_coding/svc/scalability_structure_unittest.cc index eab1801ca0..627415bee6 100644 --- a/modules/video_coding/svc/scalability_structure_unittest.cc +++ b/modules/video_coding/svc/scalability_structure_unittest.cc @@ -305,27 +305,29 @@ INSTANTIATE_TEST_SUITE_P( SvcTestParam{"S2T1", /*num_temporal_units=*/3}, SvcTestParam{"L2T2", /*num_temporal_units=*/4}, SvcTestParam{"L2T2_KEY", /*num_temporal_units=*/4}, - SvcTestParam{"L2T2_KEY_SHIFT", /*num_temporal_units=*/4}), + SvcTestParam{"L2T2_KEY_SHIFT", /*num_temporal_units=*/4}, + SvcTestParam{"L3T3_KEY", /*num_temporal_units=*/8}), [](const testing::TestParamInfo& info) { return info.param.name; }); // TODO(danilchap): Merge with ScalabilityStructureTest when the functionality // is implemented for all tested structures. -INSTANTIATE_TEST_SUITE_P(Svc, - ScalabilityStructureSetRatesTest, - Values(SvcTestParam{"L1T2", - /*num_temporal_units=*/4}, - SvcTestParam{"L1T3", /*num_temporal_units=*/8}, - SvcTestParam{"L2T1", - /*num_temporal_units=*/3}, - SvcTestParam{"L2T2", - /*num_temporal_units=*/4}, - SvcTestParam{"L3T1", /*num_temporal_units=*/3}, - SvcTestParam{"L3T3", /*num_temporal_units=*/8}), - [](const testing::TestParamInfo& info) { - return info.param.name; - }); +INSTANTIATE_TEST_SUITE_P( + Svc, + ScalabilityStructureSetRatesTest, + Values(SvcTestParam{"L1T2", /*num_temporal_units=*/4}, + SvcTestParam{"L1T3", /*num_temporal_units=*/8}, + SvcTestParam{"L2T1", /*num_temporal_units=*/3}, + SvcTestParam{"L2T1_KEY", /*num_temporal_units=*/3}, + SvcTestParam{"L2T2", /*num_temporal_units=*/4}, + SvcTestParam{"L2T2_KEY", /*num_temporal_units=*/4}, + SvcTestParam{"L3T1", /*num_temporal_units=*/3}, + SvcTestParam{"L3T3", /*num_temporal_units=*/8}, + SvcTestParam{"L3T3_KEY", /*num_temporal_units=*/8}), + [](const testing::TestParamInfo& info) { + return info.param.name; + }); } // namespace } // namespace webrtc