| /* |
| * 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 "modules/video_coding/svc/simulcast_to_svc_converter.h" |
| |
| #include <cstddef> |
| #include <vector> |
| |
| #include "modules/video_coding/svc/create_scalability_structure.h" |
| #include "test/gtest.h" |
| |
| namespace webrtc { |
| |
| TEST(SimulcastToSvc, ConvertsConfig) { |
| VideoCodec codec; |
| codec.codecType = kVideoCodecVP9; |
| codec.SetScalabilityMode(ScalabilityMode::kL1T3); |
| codec.width = 1280; |
| codec.height = 720; |
| codec.minBitrate = 10; |
| codec.maxBitrate = 2500; |
| codec.numberOfSimulcastStreams = 3; |
| codec.VP9()->numberOfSpatialLayers = 1; |
| codec.VP9()->interLayerPred = InterLayerPredMode::kOff; |
| codec.simulcastStream[0] = {.width = 320, |
| .height = 180, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 100, |
| .targetBitrate = 70, |
| .minBitrate = 50, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[1] = {.width = 640, |
| .height = 360, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 250, |
| .targetBitrate = 150, |
| .minBitrate = 100, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[2] = {.width = 12800, |
| .height = 720, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 1500, |
| .targetBitrate = 1200, |
| .minBitrate = 800, |
| .qpMax = 150, |
| .active = true}; |
| VideoCodec result = codec; |
| |
| SimulcastToSvcConverter converter(codec); |
| result = converter.GetConfig(); |
| |
| EXPECT_EQ(result.numberOfSimulcastStreams, 1); |
| EXPECT_EQ(result.spatialLayers[0], codec.simulcastStream[0]); |
| EXPECT_EQ(result.spatialLayers[1], codec.simulcastStream[1]); |
| EXPECT_EQ(result.spatialLayers[2], codec.simulcastStream[2]); |
| EXPECT_EQ(result.VP9()->numberOfTemporalLayers, 3); |
| EXPECT_EQ(result.VP9()->numberOfSpatialLayers, 3); |
| EXPECT_EQ(result.VP9()->interLayerPred, InterLayerPredMode::kOff); |
| } |
| |
| TEST(SimulcastToSvc, ConvertsEncodedImage) { |
| VideoCodec codec; |
| codec.codecType = kVideoCodecVP9; |
| codec.SetScalabilityMode(ScalabilityMode::kL1T3); |
| codec.width = 1280; |
| codec.height = 720; |
| codec.minBitrate = 10; |
| codec.maxBitrate = 2500; |
| codec.numberOfSimulcastStreams = 3; |
| codec.VP9()->numberOfSpatialLayers = 1; |
| codec.VP9()->interLayerPred = InterLayerPredMode::kOff; |
| codec.simulcastStream[0] = {.width = 320, |
| .height = 180, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 100, |
| .targetBitrate = 70, |
| .minBitrate = 50, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[1] = {.width = 640, |
| .height = 360, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 250, |
| .targetBitrate = 150, |
| .minBitrate = 100, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[2] = {.width = 1280, |
| .height = 720, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 1500, |
| .targetBitrate = 1200, |
| .minBitrate = 800, |
| .qpMax = 150, |
| .active = true}; |
| |
| SimulcastToSvcConverter converter(codec); |
| |
| EncodedImage image; |
| image.SetRtpTimestamp(123); |
| image.SetSpatialIndex(1); |
| image.SetTemporalIndex(0); |
| image._encodedWidth = 640; |
| image._encodedHeight = 360; |
| |
| CodecSpecificInfo codec_specific; |
| codec_specific.codecType = kVideoCodecVP9; |
| codec_specific.end_of_picture = false; |
| codec_specific.codecSpecific.VP9.num_spatial_layers = 3; |
| codec_specific.codecSpecific.VP9.first_active_layer = 0; |
| codec_specific.scalability_mode = ScalabilityMode::kS3T3; |
| |
| converter.EncodeStarted(/*force_keyframe =*/true); |
| converter.ConvertFrame(image, codec_specific); |
| |
| EXPECT_EQ(image.SpatialIndex(), std::nullopt); |
| EXPECT_EQ(image.SimulcastIndex(), 1); |
| EXPECT_EQ(image.TemporalIndex(), 0); |
| |
| EXPECT_EQ(codec_specific.end_of_picture, true); |
| EXPECT_EQ(codec_specific.scalability_mode, ScalabilityMode::kL1T3); |
| } |
| |
| // Checks that ScalableVideoController, which actualle is used by the encoder |
| // in the forced S-mode behaves as SimulcastToSvcConverter assumes. |
| TEST(SimulcastToSvc, PredictsInternalStateCorrectlyOnFrameDrops) { |
| VideoCodec codec; |
| codec.codecType = kVideoCodecVP9; |
| codec.SetScalabilityMode(ScalabilityMode::kL1T3); |
| codec.width = 1280; |
| codec.height = 720; |
| codec.minBitrate = 10; |
| codec.maxBitrate = 2500; |
| codec.numberOfSimulcastStreams = 3; |
| codec.VP9()->numberOfSpatialLayers = 1; |
| codec.VP9()->interLayerPred = InterLayerPredMode::kOff; |
| |
| codec.simulcastStream[0] = {.width = 320, |
| .height = 180, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 100, |
| .targetBitrate = 70, |
| .minBitrate = 50, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[1] = {.width = 640, |
| .height = 360, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 250, |
| .targetBitrate = 150, |
| .minBitrate = 100, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[2] = {.width = 1280, |
| .height = 720, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 1500, |
| .targetBitrate = 1200, |
| .minBitrate = 800, |
| .qpMax = 150, |
| .active = true}; |
| |
| std::unique_ptr<ScalableVideoController> svc_controller = |
| CreateScalabilityStructure(ScalabilityMode::kS3T3); |
| |
| VideoBitrateAllocation dummy_bitrates; |
| for (int sid = 0; sid < 3; ++sid) { |
| for (int tid = 0; tid < 3; ++tid) { |
| dummy_bitrates.SetBitrate(sid, tid, 10000); |
| } |
| } |
| svc_controller->OnRatesUpdated(dummy_bitrates); |
| |
| SimulcastToSvcConverter converter(codec); |
| |
| EncodedImage image; |
| |
| // Simulate complex dropping pattern. |
| const int kDropInterval[3] = {11, 7, 5}; |
| const int kKeyFrameInterval = 13; |
| for (int i = 0; i < 100; ++i) { |
| bool force_restart = ((i + 1) % kKeyFrameInterval == 0) || (i == 0); |
| auto layer_config = svc_controller->NextFrameConfig(force_restart); |
| converter.EncodeStarted(force_restart); |
| for (int sid = 0; sid < 3; ++sid) { |
| if ((i + 1) % kDropInterval[sid] == 0) { |
| continue; |
| } |
| image.SetRtpTimestamp(123 * i); |
| image.SetSpatialIndex(sid); |
| image.SetTemporalIndex(0); |
| image._encodedWidth = 1280 / (1 << sid); |
| image._encodedHeight = 720 / (1 << sid); |
| image.SetSpatialIndex(sid); |
| image.SetTemporalIndex(layer_config[sid].TemporalId()); |
| |
| CodecSpecificInfo codec_specific; |
| codec_specific.codecType = kVideoCodecVP9; |
| codec_specific.end_of_picture = false; |
| codec_specific.codecSpecific.VP9.num_spatial_layers = 3; |
| codec_specific.codecSpecific.VP9.first_active_layer = 0; |
| codec_specific.codecSpecific.VP9.temporal_idx = |
| layer_config[sid].TemporalId(); |
| codec_specific.generic_frame_info = |
| svc_controller->OnEncodeDone(layer_config[sid]); |
| |
| codec_specific.scalability_mode = ScalabilityMode::kS3T3; |
| |
| EXPECT_TRUE(converter.ConvertFrame(image, codec_specific)); |
| |
| EXPECT_EQ(image.SpatialIndex(), std::nullopt); |
| EXPECT_EQ(image.SimulcastIndex(), sid); |
| EXPECT_EQ(image.TemporalIndex(), layer_config[sid].TemporalId()); |
| |
| EXPECT_EQ(codec_specific.scalability_mode, ScalabilityMode::kL1T3); |
| } |
| } |
| } |
| |
| TEST(SimulcastToSvc, SupportsOnlyContinuousActiveStreams) { |
| VideoCodec codec; |
| codec.codecType = kVideoCodecVP9; |
| codec.SetScalabilityMode(ScalabilityMode::kL1T3); |
| codec.width = 1280; |
| codec.height = 720; |
| codec.minBitrate = 10; |
| codec.maxBitrate = 2500; |
| codec.numberOfSimulcastStreams = 3; |
| codec.VP9()->numberOfSpatialLayers = 1; |
| codec.VP9()->interLayerPred = InterLayerPredMode::kOff; |
| |
| codec.simulcastStream[0] = {.width = 320, |
| .height = 180, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 100, |
| .targetBitrate = 70, |
| .minBitrate = 50, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[1] = {.width = 640, |
| .height = 360, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 250, |
| .targetBitrate = 150, |
| .minBitrate = 100, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[2] = {.width = 1280, |
| .height = 720, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 1500, |
| .targetBitrate = 1200, |
| .minBitrate = 800, |
| .qpMax = 150, |
| .active = true}; |
| EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| |
| codec.simulcastStream[0].active = false; |
| codec.simulcastStream[1].active = true; |
| codec.simulcastStream[2].active = true; |
| EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| |
| codec.simulcastStream[0].active = true; |
| codec.simulcastStream[1].active = true; |
| codec.simulcastStream[2].active = false; |
| EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| |
| codec.simulcastStream[0].active = true; |
| codec.simulcastStream[1].active = false; |
| codec.simulcastStream[2].active = true; |
| EXPECT_FALSE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| } |
| |
| TEST(SimulcastToSvc, SupportsOnlySameTemporalStructure) { |
| VideoCodec codec; |
| codec.codecType = kVideoCodecVP9; |
| codec.width = 1280; |
| codec.height = 720; |
| codec.minBitrate = 10; |
| codec.maxBitrate = 2500; |
| codec.numberOfSimulcastStreams = 3; |
| codec.VP9()->numberOfSpatialLayers = 1; |
| codec.VP9()->interLayerPred = InterLayerPredMode::kOff; |
| |
| codec.simulcastStream[0] = {.width = 320, |
| .height = 180, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 100, |
| .targetBitrate = 70, |
| .minBitrate = 50, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[1] = {.width = 640, |
| .height = 360, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 250, |
| .targetBitrate = 150, |
| .minBitrate = 100, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[2] = {.width = 1280, |
| .height = 720, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 1500, |
| .targetBitrate = 1200, |
| .minBitrate = 800, |
| .qpMax = 150, |
| .active = true}; |
| EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| |
| codec.simulcastStream[0].numberOfTemporalLayers = 1; |
| EXPECT_FALSE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| } |
| |
| TEST(SimulcastToSvc, SupportsOnly421Scaling) { |
| VideoCodec codec; |
| codec.codecType = kVideoCodecVP9; |
| codec.width = 1280; |
| codec.height = 720; |
| codec.minBitrate = 10; |
| codec.maxBitrate = 2500; |
| codec.numberOfSimulcastStreams = 3; |
| codec.VP9()->numberOfSpatialLayers = 1; |
| codec.VP9()->interLayerPred = InterLayerPredMode::kOff; |
| |
| codec.simulcastStream[0] = {.width = 320, |
| .height = 180, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 100, |
| .targetBitrate = 70, |
| .minBitrate = 50, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[1] = {.width = 640, |
| .height = 360, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 250, |
| .targetBitrate = 150, |
| .minBitrate = 100, |
| .qpMax = 150, |
| .active = true}; |
| codec.simulcastStream[2] = {.width = 1280, |
| .height = 720, |
| .maxFramerate = 30, |
| .numberOfTemporalLayers = 3, |
| .maxBitrate = 1500, |
| .targetBitrate = 1200, |
| .minBitrate = 800, |
| .qpMax = 150, |
| .active = true}; |
| EXPECT_TRUE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| |
| codec.simulcastStream[0].width = 160; |
| codec.simulcastStream[0].height = 90; |
| EXPECT_FALSE(SimulcastToSvcConverter::IsConfigSupported(codec)); |
| } |
| |
| } // namespace webrtc |