Source code
Revision control
Copy as Markdown
Other Tools
/*
* Copyright (c) 2024 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 <memory>
#include <optional>
#include <utility>
#include "api/video/i420_buffer.h" // IWYU pragma: keep
#include "api/video_codecs/libaom_av1_encoder_factory.h"
#include "api/video_codecs/simple_encoder_wrapper.h"
#include "api/video_codecs/video_encoder_factory_interface.h"
#include "api/video_codecs/video_encoding_general.h"
#include "test/gmock.h"
#include "test/gtest.h"
#include "test/testsupport/file_utils.h"
#include "test/testsupport/frame_reader.h"
namespace webrtc {
using ::testing::Eq;
using ::testing::Gt;
using ::testing::IsEmpty;
using ::testing::Ne;
using ::testing::Not;
using ::testing::NotNull;
using ::testing::UnorderedElementsAre;
using PredictionConstraints =
VideoEncoderFactoryInterface::Capabilities::PredictionConstraints;
namespace {
std::unique_ptr<test::FrameReader> CreateFrameReader() {
return CreateY4mFrameReader(
test::ResourcePath("reference_video_640x360_30fps", "y4m"),
test::YuvFrameReaderImpl::RepeatMode::kPingPong);
}
TEST(SimpleEncoderWrapper, SupportedSvcModesOnlyL1T1) {
PredictionConstraints constraints = {
.num_buffers = 2,
.max_references = 2,
.max_temporal_layers = 1,
.buffer_space_type =
PredictionConstraints::BufferSpaceType::kSingleKeyframe,
.max_spatial_layers = 1,
.scaling_factors = {{1, 1}},
};
EXPECT_THAT(SimpleEncoderWrapper::SupportedWebrtcSvcModes(constraints),
UnorderedElementsAre("L1T1"));
}
TEST(SimpleEncoderWrapper, SupportedSvcModesUpToL1T3) {
PredictionConstraints constraints = {
.num_buffers = 8,
.max_references = 1,
.max_temporal_layers = 3,
.buffer_space_type =
PredictionConstraints::BufferSpaceType::kSingleKeyframe,
.max_spatial_layers = 1,
.scaling_factors = {{1, 1}, {1, 2}},
};
EXPECT_THAT(SimpleEncoderWrapper::SupportedWebrtcSvcModes(constraints),
UnorderedElementsAre("L1T1", "L1T2", "L1T3"));
}
TEST(SimpleEncoderWrapper, SupportedSvcModesUpToL3T3Key) {
PredictionConstraints constraints = {
.num_buffers = 8,
.max_references = 2,
.max_temporal_layers = 3,
.buffer_space_type =
PredictionConstraints::BufferSpaceType::kSingleKeyframe,
.max_spatial_layers = 3,
.scaling_factors = {{1, 1}, {1, 2}},
};
EXPECT_THAT(
SimpleEncoderWrapper::SupportedWebrtcSvcModes(constraints),
UnorderedElementsAre("L1T1", "L1T2", "L1T3", "L2T1", "L2T1_KEY", "L2T2",
"L2T2_KEY", "L2T3", "L2T3_KEY", "L3T1", "L3T1_KEY",
"L3T2", "L3T2_KEY", "L3T3", "L3T3_KEY", "S2T1",
"S2T2", "S2T3", "S3T1", "S3T2", "S3T3"));
}
TEST(SimpleEncoderWrapper, SupportedSvcModesUpToS3T3) {
PredictionConstraints constraints = {
.num_buffers = 8,
.max_references = 2,
.max_temporal_layers = 3,
.buffer_space_type =
PredictionConstraints::BufferSpaceType::kMultiInstance,
.max_spatial_layers = 3,
.scaling_factors = {{1, 1}, {1, 2}},
};
EXPECT_THAT(SimpleEncoderWrapper::SupportedWebrtcSvcModes(constraints),
UnorderedElementsAre("L1T1", "L1T2", "L1T3", "S2T1", "S2T2",
"S2T3", "S3T1", "S3T2", "S3T3"));
}
TEST(SimpleEncoderWrapper, SupportedSvcModesUpToL3T3KeyWithHScaling) {
PredictionConstraints constraints = {
.num_buffers = 8,
.max_references = 2,
.max_temporal_layers = 3,
.buffer_space_type =
PredictionConstraints::BufferSpaceType::kSingleKeyframe,
.max_spatial_layers = 3,
.scaling_factors = {{1, 1}, {1, 2}, {2, 3}},
};
EXPECT_THAT(
SimpleEncoderWrapper::SupportedWebrtcSvcModes(constraints),
UnorderedElementsAre(
"L1T1", "L1T2", "L1T3", "L2T1", "L2T1h", "L2T1_KEY", "L2T1h_KEY",
"L2T2", "L2T2h", "L2T2_KEY", "L2T2h_KEY", "L2T3", "L2T3h", "L2T3_KEY",
"L2T3h_KEY", "L3T1", "L3T1h", "L3T1_KEY", "L3T1h_KEY", "L3T2",
"L3T2h", "L3T2_KEY", "L3T2h_KEY", "L3T3", "L3T3h", "L3T3_KEY",
"L3T3h_KEY", "S2T1", "S2T1h", "S2T2", "S2T2h", "S2T3", "S2T3h",
"S3T1", "S3T1h", "S3T2", "S3T2h", "S3T3", "S3T3h"));
}
// TD: The encoder wrapper shouldn't really use an actual encoder
// implementation for testing, but hey, this is just a PoC.
TEST(SimpleEncoderWrapper, EncodeL1T1) {
auto encoder = LibaomAv1EncoderFactory().CreateEncoder(
{.max_encode_dimensions = {1080, 720},
.encoding_format = {.sub_sampling = EncodingFormat::k420,
.bit_depth = 8},
.rc_mode = VideoEncoderFactoryInterface::StaticEncoderSettings::Cqp(),
.max_number_of_threads = 1},
{});
std::unique_ptr<SimpleEncoderWrapper> simple_encoder =
SimpleEncoderWrapper::Create(std::move(encoder), "L1T1");
ASSERT_THAT(simple_encoder, NotNull());
simple_encoder->SetEncodeQp(30);
simple_encoder->SetEncodeFps(15);
auto frame_reader = CreateFrameReader();
int num_callbacks = 0;
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/true,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.dependency_structure, Ne(std::nullopt));
EXPECT_THAT(result.bitstream_data, Not(IsEmpty()));
EXPECT_THAT(result.frame_type, Eq(FrameType::kKeyframe));
EXPECT_THAT(result.generic_frame_info.spatial_id, Eq(0));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(0));
});
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/false,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.dependency_structure, Eq(std::nullopt));
EXPECT_THAT(result.bitstream_data, Not(IsEmpty()));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.spatial_id, Eq(0));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(0));
});
}
TEST(SimpleEncoderWrapper, EncodeL2T2_KEY) {
auto encoder = LibaomAv1EncoderFactory().CreateEncoder(
{.max_encode_dimensions = {1080, 720},
.encoding_format = {.sub_sampling = EncodingFormat::k420,
.bit_depth = 8},
.rc_mode = VideoEncoderFactoryInterface::StaticEncoderSettings::Cqp(),
.max_number_of_threads = 1},
{});
std::unique_ptr<SimpleEncoderWrapper> simple_encoder =
SimpleEncoderWrapper::Create(std::move(encoder), "L2T2_KEY");
ASSERT_THAT(simple_encoder, NotNull());
simple_encoder->SetEncodeQp(30);
simple_encoder->SetEncodeFps(15);
auto frame_reader = CreateFrameReader();
int num_callbacks = 0;
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/true,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
ASSERT_THAT(result.oh_no, Eq(false));
if (result.generic_frame_info.spatial_id == 0) {
++num_callbacks;
EXPECT_THAT(result.dependency_structure, Ne(std::nullopt));
EXPECT_THAT(result.bitstream_data, Not(IsEmpty()));
EXPECT_THAT(result.frame_type, Eq(FrameType::kKeyframe));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(0));
} else if (result.generic_frame_info.spatial_id == 1) {
++num_callbacks;
EXPECT_THAT(result.dependency_structure, Eq(std::nullopt));
EXPECT_THAT(result.bitstream_data, Not(IsEmpty()));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(0));
}
});
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/false,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
ASSERT_THAT(result.oh_no, Eq(false));
if (result.generic_frame_info.spatial_id == 0) {
++num_callbacks;
EXPECT_THAT(result.dependency_structure, Eq(std::nullopt));
EXPECT_THAT(result.bitstream_data, Not(IsEmpty()));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(1));
} else if (result.generic_frame_info.spatial_id == 1) {
++num_callbacks;
EXPECT_THAT(result.dependency_structure, Eq(std::nullopt));
EXPECT_THAT(result.bitstream_data, Not(IsEmpty()));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(1));
}
});
EXPECT_THAT(num_callbacks, Eq(4));
}
TEST(SimpleEncoderWrapper, EncodeL1T3ForceKeyframe) {
auto encoder = LibaomAv1EncoderFactory().CreateEncoder(
{.max_encode_dimensions = {1080, 720},
.encoding_format = {.sub_sampling = EncodingFormat::k420,
.bit_depth = 8},
.rc_mode = VideoEncoderFactoryInterface::StaticEncoderSettings::Cqp(),
.max_number_of_threads = 1},
{});
std::unique_ptr<SimpleEncoderWrapper> simple_encoder =
SimpleEncoderWrapper::Create(std::move(encoder), "L1T3");
ASSERT_THAT(simple_encoder, NotNull());
simple_encoder->SetEncodeQp(30);
simple_encoder->SetEncodeFps(15);
auto frame_reader = CreateFrameReader();
int num_callbacks = 0;
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/true,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.frame_type, Eq(FrameType::kKeyframe));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(0));
});
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/false,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(2));
});
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/false,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(1));
});
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/true,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.frame_type, Eq(FrameType::kKeyframe));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(0));
});
simple_encoder->Encode(
frame_reader->PullFrame(), /*force_keyframe=*/false,
[&](const SimpleEncoderWrapper::EncodeResult& result) {
++num_callbacks;
ASSERT_THAT(result.oh_no, Eq(false));
EXPECT_THAT(result.frame_type, Eq(FrameType::kDeltaFrame));
EXPECT_THAT(result.generic_frame_info.temporal_id, Eq(2));
});
EXPECT_THAT(num_callbacks, Eq(5));
}
} // namespace
} // namespace webrtc