mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00

Used instead of string representation in lower-levels of encoder configuration, to avoid string comparisons (with risk of misspelling) in lots of places. Bug: webrtc:11607 Change-Id: I4d51c2265aac297c29976d2aa601d8ffb33b7326 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/259870 Commit-Queue: Niels Moller <nisse@webrtc.org> Reviewed-by: Harald Alvestrand <hta@webrtc.org> Reviewed-by: Florent Castelli <orphis@webrtc.org> Reviewed-by: Åsa Persson <asapersson@webrtc.org> Cr-Commit-Position: refs/heads/main@{#36706}
350 lines
15 KiB
C++
350 lines
15 KiB
C++
/*
|
|
* 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 <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include <memory>
|
|
#include <ostream>
|
|
#include <string>
|
|
|
|
#include "absl/types/optional.h"
|
|
#include "api/array_view.h"
|
|
#include "api/transport/rtp/dependency_descriptor.h"
|
|
#include "modules/video_coding/svc/create_scalability_structure.h"
|
|
#include "modules/video_coding/svc/scalability_mode_util.h"
|
|
#include "modules/video_coding/svc/scalability_structure_test_helpers.h"
|
|
#include "modules/video_coding/svc/scalable_video_controller.h"
|
|
#include "test/gmock.h"
|
|
#include "test/gtest.h"
|
|
|
|
namespace webrtc {
|
|
namespace {
|
|
|
|
using ::testing::AllOf;
|
|
using ::testing::Contains;
|
|
using ::testing::Each;
|
|
using ::testing::ElementsAreArray;
|
|
using ::testing::Field;
|
|
using ::testing::Ge;
|
|
using ::testing::IsEmpty;
|
|
using ::testing::Le;
|
|
using ::testing::Lt;
|
|
using ::testing::Not;
|
|
using ::testing::NotNull;
|
|
using ::testing::SizeIs;
|
|
using ::testing::TestWithParam;
|
|
using ::testing::Values;
|
|
|
|
struct SvcTestParam {
|
|
friend std::ostream& operator<<(std::ostream& os, const SvcTestParam& param) {
|
|
return os << param.name;
|
|
}
|
|
|
|
ScalabilityMode GetScalabilityMode() const {
|
|
absl::optional<ScalabilityMode> scalability_mode =
|
|
ScalabilityModeFromString(name);
|
|
RTC_CHECK(scalability_mode.has_value());
|
|
return *scalability_mode;
|
|
}
|
|
|
|
std::string name;
|
|
int num_temporal_units;
|
|
};
|
|
|
|
class ScalabilityStructureTest : public TestWithParam<SvcTestParam> {};
|
|
|
|
TEST_P(ScalabilityStructureTest,
|
|
StaticConfigMatchesConfigReturnedByController) {
|
|
std::unique_ptr<ScalableVideoController> controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
absl::optional<ScalableVideoController::StreamLayersConfig> static_config =
|
|
ScalabilityStructureConfig(GetParam().GetScalabilityMode());
|
|
ASSERT_THAT(controller, NotNull());
|
|
ASSERT_NE(static_config, absl::nullopt);
|
|
ScalableVideoController::StreamLayersConfig config =
|
|
controller->StreamConfig();
|
|
EXPECT_EQ(config.num_spatial_layers, static_config->num_spatial_layers);
|
|
EXPECT_EQ(config.num_temporal_layers, static_config->num_temporal_layers);
|
|
EXPECT_THAT(
|
|
rtc::MakeArrayView(config.scaling_factor_num, config.num_spatial_layers),
|
|
ElementsAreArray(static_config->scaling_factor_num,
|
|
static_config->num_spatial_layers));
|
|
EXPECT_THAT(
|
|
rtc::MakeArrayView(config.scaling_factor_den, config.num_spatial_layers),
|
|
ElementsAreArray(static_config->scaling_factor_den,
|
|
static_config->num_spatial_layers));
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest,
|
|
NumberOfDecodeTargetsAndChainsAreInRangeAndConsistent) {
|
|
FrameDependencyStructure structure =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode())
|
|
->DependencyStructure();
|
|
EXPECT_GT(structure.num_decode_targets, 0);
|
|
EXPECT_LE(structure.num_decode_targets,
|
|
DependencyDescriptor::kMaxDecodeTargets);
|
|
EXPECT_GE(structure.num_chains, 0);
|
|
EXPECT_LE(structure.num_chains, structure.num_decode_targets);
|
|
if (structure.num_chains == 0) {
|
|
EXPECT_THAT(structure.decode_target_protected_by_chain, IsEmpty());
|
|
} else {
|
|
EXPECT_THAT(structure.decode_target_protected_by_chain,
|
|
AllOf(SizeIs(structure.num_decode_targets), Each(Ge(0)),
|
|
Each(Lt(structure.num_chains))));
|
|
}
|
|
EXPECT_THAT(structure.templates,
|
|
SizeIs(Lt(size_t{DependencyDescriptor::kMaxTemplates})));
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, TemplatesAreSortedByLayerId) {
|
|
FrameDependencyStructure structure =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode())
|
|
->DependencyStructure();
|
|
ASSERT_THAT(structure.templates, Not(IsEmpty()));
|
|
const auto& first_templates = structure.templates.front();
|
|
EXPECT_EQ(first_templates.spatial_id, 0);
|
|
EXPECT_EQ(first_templates.temporal_id, 0);
|
|
for (size_t i = 1; i < structure.templates.size(); ++i) {
|
|
const auto& prev_template = structure.templates[i - 1];
|
|
const auto& next_template = structure.templates[i];
|
|
if (next_template.spatial_id == prev_template.spatial_id &&
|
|
next_template.temporal_id == prev_template.temporal_id) {
|
|
// Same layer, next_layer_idc == 0
|
|
} else if (next_template.spatial_id == prev_template.spatial_id &&
|
|
next_template.temporal_id == prev_template.temporal_id + 1) {
|
|
// Next temporal layer, next_layer_idc == 1
|
|
} else if (next_template.spatial_id == prev_template.spatial_id + 1 &&
|
|
next_template.temporal_id == 0) {
|
|
// Next spatial layer, next_layer_idc == 2
|
|
} else {
|
|
// everything else is invalid.
|
|
ADD_FAILURE() << "Invalid templates order. Template #" << i
|
|
<< " with layer (" << next_template.spatial_id << ","
|
|
<< next_template.temporal_id
|
|
<< ") follows template with layer ("
|
|
<< prev_template.spatial_id << ","
|
|
<< prev_template.temporal_id << ").";
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, TemplatesMatchNumberOfDecodeTargetsAndChains) {
|
|
FrameDependencyStructure structure =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode())
|
|
->DependencyStructure();
|
|
EXPECT_THAT(
|
|
structure.templates,
|
|
Each(AllOf(Field(&FrameDependencyTemplate::decode_target_indications,
|
|
SizeIs(structure.num_decode_targets)),
|
|
Field(&FrameDependencyTemplate::chain_diffs,
|
|
SizeIs(structure.num_chains)))));
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, FrameInfoMatchesFrameDependencyStructure) {
|
|
std::unique_ptr<ScalableVideoController> svc_controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
FrameDependencyStructure structure = svc_controller->DependencyStructure();
|
|
std::vector<GenericFrameInfo> frame_infos =
|
|
ScalabilityStructureWrapper(*svc_controller)
|
|
.GenerateFrames(GetParam().num_temporal_units);
|
|
for (size_t frame_id = 0; frame_id < frame_infos.size(); ++frame_id) {
|
|
const auto& frame = frame_infos[frame_id];
|
|
EXPECT_GE(frame.spatial_id, 0) << " for frame " << frame_id;
|
|
EXPECT_GE(frame.temporal_id, 0) << " for frame " << frame_id;
|
|
EXPECT_THAT(frame.decode_target_indications,
|
|
SizeIs(structure.num_decode_targets))
|
|
<< " for frame " << frame_id;
|
|
EXPECT_THAT(frame.part_of_chain, SizeIs(structure.num_chains))
|
|
<< " for frame " << frame_id;
|
|
}
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, ThereIsAPerfectTemplateForEachFrame) {
|
|
std::unique_ptr<ScalableVideoController> svc_controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
FrameDependencyStructure structure = svc_controller->DependencyStructure();
|
|
std::vector<GenericFrameInfo> frame_infos =
|
|
ScalabilityStructureWrapper(*svc_controller)
|
|
.GenerateFrames(GetParam().num_temporal_units);
|
|
for (size_t frame_id = 0; frame_id < frame_infos.size(); ++frame_id) {
|
|
EXPECT_THAT(structure.templates, Contains(frame_infos[frame_id]))
|
|
<< " for frame " << frame_id;
|
|
}
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, FrameDependsOnSameOrLowerLayer) {
|
|
std::unique_ptr<ScalableVideoController> svc_controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
std::vector<GenericFrameInfo> frame_infos =
|
|
ScalabilityStructureWrapper(*svc_controller)
|
|
.GenerateFrames(GetParam().num_temporal_units);
|
|
int64_t num_frames = frame_infos.size();
|
|
|
|
for (int64_t frame_id = 0; frame_id < num_frames; ++frame_id) {
|
|
const auto& frame = frame_infos[frame_id];
|
|
for (int frame_diff : frame.frame_diffs) {
|
|
int64_t base_frame_id = frame_id - frame_diff;
|
|
const auto& base_frame = frame_infos[base_frame_id];
|
|
EXPECT_GE(frame.spatial_id, base_frame.spatial_id)
|
|
<< "Frame " << frame_id << " depends on frame " << base_frame_id;
|
|
EXPECT_GE(frame.temporal_id, base_frame.temporal_id)
|
|
<< "Frame " << frame_id << " depends on frame " << base_frame_id;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, NoFrameDependsOnDiscardableOrNotPresent) {
|
|
std::unique_ptr<ScalableVideoController> svc_controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
std::vector<GenericFrameInfo> frame_infos =
|
|
ScalabilityStructureWrapper(*svc_controller)
|
|
.GenerateFrames(GetParam().num_temporal_units);
|
|
int64_t num_frames = frame_infos.size();
|
|
FrameDependencyStructure structure = svc_controller->DependencyStructure();
|
|
|
|
for (int dt = 0; dt < structure.num_decode_targets; ++dt) {
|
|
for (int64_t frame_id = 0; frame_id < num_frames; ++frame_id) {
|
|
const auto& frame = frame_infos[frame_id];
|
|
if (frame.decode_target_indications[dt] ==
|
|
DecodeTargetIndication::kNotPresent) {
|
|
continue;
|
|
}
|
|
for (int frame_diff : frame.frame_diffs) {
|
|
int64_t base_frame_id = frame_id - frame_diff;
|
|
const auto& base_frame = frame_infos[base_frame_id];
|
|
EXPECT_NE(base_frame.decode_target_indications[dt],
|
|
DecodeTargetIndication::kNotPresent)
|
|
<< "Frame " << frame_id << " depends on frame " << base_frame_id
|
|
<< " that is not part of decode target#" << dt;
|
|
EXPECT_NE(base_frame.decode_target_indications[dt],
|
|
DecodeTargetIndication::kDiscardable)
|
|
<< "Frame " << frame_id << " depends on frame " << base_frame_id
|
|
<< " that is discardable for decode target#" << dt;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, NoFrameDependsThroughSwitchIndication) {
|
|
std::unique_ptr<ScalableVideoController> svc_controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
FrameDependencyStructure structure = svc_controller->DependencyStructure();
|
|
std::vector<GenericFrameInfo> frame_infos =
|
|
ScalabilityStructureWrapper(*svc_controller)
|
|
.GenerateFrames(GetParam().num_temporal_units);
|
|
int64_t num_frames = frame_infos.size();
|
|
std::vector<std::set<int64_t>> full_deps(num_frames);
|
|
|
|
// For each frame calculate set of all frames it depends on, both directly and
|
|
// indirectly.
|
|
for (int64_t frame_id = 0; frame_id < num_frames; ++frame_id) {
|
|
std::set<int64_t> all_base_frames;
|
|
for (int frame_diff : frame_infos[frame_id].frame_diffs) {
|
|
int64_t base_frame_id = frame_id - frame_diff;
|
|
all_base_frames.insert(base_frame_id);
|
|
const auto& indirect = full_deps[base_frame_id];
|
|
all_base_frames.insert(indirect.begin(), indirect.end());
|
|
}
|
|
full_deps[frame_id] = std::move(all_base_frames);
|
|
}
|
|
|
|
// Now check the switch indication: frames after the switch indication mustn't
|
|
// depend on any addition frames before the switch indications.
|
|
for (int dt = 0; dt < structure.num_decode_targets; ++dt) {
|
|
for (int64_t switch_frame_id = 0; switch_frame_id < num_frames;
|
|
++switch_frame_id) {
|
|
if (frame_infos[switch_frame_id].decode_target_indications[dt] !=
|
|
DecodeTargetIndication::kSwitch) {
|
|
continue;
|
|
}
|
|
for (int64_t later_frame_id = switch_frame_id + 1;
|
|
later_frame_id < num_frames; ++later_frame_id) {
|
|
if (frame_infos[later_frame_id].decode_target_indications[dt] ==
|
|
DecodeTargetIndication::kNotPresent) {
|
|
continue;
|
|
}
|
|
for (int frame_diff : frame_infos[later_frame_id].frame_diffs) {
|
|
int64_t early_frame_id = later_frame_id - frame_diff;
|
|
if (early_frame_id < switch_frame_id) {
|
|
EXPECT_THAT(full_deps[switch_frame_id], Contains(early_frame_id))
|
|
<< "For decode target #" << dt << " frame " << later_frame_id
|
|
<< " depends on the frame " << early_frame_id
|
|
<< " that switch indication frame " << switch_frame_id
|
|
<< " doesn't directly on indirectly depend on.";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(ScalabilityStructureTest, ProduceNoFrameForDisabledLayers) {
|
|
std::unique_ptr<ScalableVideoController> svc_controller =
|
|
CreateScalabilityStructure(GetParam().GetScalabilityMode());
|
|
ScalableVideoController::StreamLayersConfig structure =
|
|
svc_controller->StreamConfig();
|
|
|
|
VideoBitrateAllocation all_bitrates;
|
|
for (int sid = 0; sid < structure.num_spatial_layers; ++sid) {
|
|
for (int tid = 0; tid < structure.num_temporal_layers; ++tid) {
|
|
all_bitrates.SetBitrate(sid, tid, 100'000);
|
|
}
|
|
}
|
|
|
|
svc_controller->OnRatesUpdated(all_bitrates);
|
|
ScalabilityStructureWrapper wrapper(*svc_controller);
|
|
std::vector<GenericFrameInfo> frames =
|
|
wrapper.GenerateFrames(GetParam().num_temporal_units);
|
|
|
|
for (int sid = 0; sid < structure.num_spatial_layers; ++sid) {
|
|
for (int tid = 0; tid < structure.num_temporal_layers; ++tid) {
|
|
// When all layers were enabled, expect there was a frame for each layer.
|
|
EXPECT_THAT(frames,
|
|
Contains(AllOf(Field(&GenericFrameInfo::spatial_id, sid),
|
|
Field(&GenericFrameInfo::temporal_id, tid))))
|
|
<< "For layer (" << sid << "," << tid << ")";
|
|
// Restore bitrates for all layers before disabling single layer.
|
|
VideoBitrateAllocation bitrates = all_bitrates;
|
|
bitrates.SetBitrate(sid, tid, 0);
|
|
svc_controller->OnRatesUpdated(bitrates);
|
|
// With layer (sid, tid) disabled, expect no frames are produced for it.
|
|
EXPECT_THAT(
|
|
wrapper.GenerateFrames(GetParam().num_temporal_units),
|
|
Not(Contains(AllOf(Field(&GenericFrameInfo::spatial_id, sid),
|
|
Field(&GenericFrameInfo::temporal_id, tid)))))
|
|
<< "For layer (" << sid << "," << tid << ")";
|
|
}
|
|
}
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(
|
|
Svc,
|
|
ScalabilityStructureTest,
|
|
Values(SvcTestParam{"L1T1", /*num_temporal_units=*/3},
|
|
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{"L3T1", /*num_temporal_units=*/3},
|
|
SvcTestParam{"L3T3", /*num_temporal_units=*/8},
|
|
SvcTestParam{"S2T1", /*num_temporal_units=*/3},
|
|
SvcTestParam{"S3T3", /*num_temporal_units=*/8},
|
|
SvcTestParam{"L2T2", /*num_temporal_units=*/4},
|
|
SvcTestParam{"L2T2_KEY", /*num_temporal_units=*/4},
|
|
SvcTestParam{"L2T2_KEY_SHIFT", /*num_temporal_units=*/4},
|
|
SvcTestParam{"L2T3_KEY", /*num_temporal_units=*/8},
|
|
SvcTestParam{"L3T3_KEY", /*num_temporal_units=*/8}),
|
|
[](const testing::TestParamInfo<SvcTestParam>& info) {
|
|
return info.param.name;
|
|
});
|
|
|
|
} // namespace
|
|
} // namespace webrtc
|