|  | /* | 
|  | *  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 |