diff --git a/modules/video_coding/video_codec_initializer.cc b/modules/video_coding/video_codec_initializer.cc index fda0005fa7..9c2cdc62b8 100644 --- a/modules/video_coding/video_codec_initializer.cc +++ b/modules/video_coding/video_codec_initializer.cc @@ -32,6 +32,21 @@ #include "rtc_base/numerics/safe_conversions.h" namespace webrtc { +namespace { + +constexpr ScalabilityMode kH265SupportedScalabilityModes[] = { + ScalabilityMode::kL1T1, ScalabilityMode::kL1T2, ScalabilityMode::kL1T3}; + +bool H265SupportsScalabilityMode(ScalabilityMode scalability_mode) { + for (const auto& entry : kH265SupportedScalabilityModes) { + if (entry == scalability_mode) { + return true; + } + } + return false; +} + +} // namespace // TODO(sprang): Split this up and separate the codec specific parts. VideoCodec VideoCodecInitializer::SetupCodec( @@ -328,7 +343,22 @@ VideoCodec VideoCodecInitializer::SetupCodec( break; } case kVideoCodecH265: - // TODO(bugs.webrtc.org/13485) + RTC_DCHECK(!config.encoder_specific_settings) << "No encoder-specific " + "settings for H.265."; + + // Validate specified scalability modes. If some layer has an unsupported + // mode, store it as the top-level scalability mode, which will make + // InitEncode fail with an appropriate error. + for (const auto& stream : streams) { + if (stream.scalability_mode.has_value() && + !H265SupportsScalabilityMode(*stream.scalability_mode)) { + RTC_LOG(LS_WARNING) + << "Invalid scalability mode for H.265: " + << ScalabilityModeToString(*stream.scalability_mode); + video_codec.SetScalabilityMode(*stream.scalability_mode); + break; + } + } break; default: // TODO(pbos): Support encoder_settings codec-agnostically. @@ -345,6 +375,9 @@ VideoCodec VideoCodecInitializer::SetupCodec( video_codec.minBitrate = experimental_min_bitrate_kbps; video_codec.simulcastStream[0].minBitrate = experimental_min_bitrate_kbps; if (video_codec.codecType == kVideoCodecVP9 || +#ifdef RTC_ENABLE_H265 + video_codec.codecType == kVideoCodecH265 || +#endif video_codec.codecType == kVideoCodecAV1) { video_codec.spatialLayers[0].minBitrate = experimental_min_bitrate_kbps; } diff --git a/modules/video_coding/video_codec_initializer_unittest.cc b/modules/video_coding/video_codec_initializer_unittest.cc index d7c91714da..0dbc0f0fb5 100644 --- a/modules/video_coding/video_codec_initializer_unittest.cc +++ b/modules/video_coding/video_codec_initializer_unittest.cc @@ -689,4 +689,66 @@ TEST_F(VideoCodecInitializerTest, UpdatesVp9SpecificFieldsWithScalabilityMode) { EXPECT_EQ(codec.VP9()->interLayerPred, InterLayerPredMode::kOff); } +#ifdef RTC_ENABLE_H265 +TEST_F(VideoCodecInitializerTest, H265SingleSpatialLayerBitratesAreConsistent) { + VideoEncoderConfig config; + config.codec_type = VideoCodecType::kVideoCodecH265; + std::vector streams = {DefaultStream()}; + streams[0].scalability_mode = ScalabilityMode::kL1T2; + + VideoCodec codec = + VideoCodecInitializer::SetupCodec(env_.field_trials(), config, streams); + + EXPECT_GE(codec.spatialLayers[0].targetBitrate, + codec.spatialLayers[0].minBitrate); + EXPECT_LE(codec.spatialLayers[0].targetBitrate, + codec.spatialLayers[0].maxBitrate); +} + +// Test that the H.265 codec initializer carries over invalid simulcast layer +// scalability mode to top level scalability mode setting. +TEST_F(VideoCodecInitializerTest, + H265ScalabilityModeConfiguredToTopLevelWhenNotAllowed) { + VideoEncoderConfig config; + config.codec_type = VideoCodecType::kVideoCodecH265; + + std::vector streams = {DefaultStream()}; + streams[0].scalability_mode = ScalabilityMode::kL3T3; + + VideoCodec codec = + VideoCodecInitializer::SetupCodec(env_.field_trials(), config, streams); + + // Check that an unsupported scalability mode will cause top-level scalability + // to be set to the same unsupported mode. + EXPECT_EQ(codec.GetScalabilityMode(), ScalabilityMode::kL3T3); + EXPECT_EQ(codec.spatialLayers[0].numberOfTemporalLayers, 3); + EXPECT_EQ(codec.simulcastStream[0].numberOfTemporalLayers, 3); +} + +// Test that inconistent scalability mode settings in simulcast streams will +// clear top level scalability mode setting. +TEST_F(VideoCodecInitializerTest, + H265InconsistentScalabilityModesWillClearTopLevelScalability) { + VideoEncoderConfig config; + config.simulcast_layers.resize(2); + config.simulcast_layers[0].active = true; + config.simulcast_layers[1].active = true; + config.codec_type = VideoCodecType::kVideoCodecH265; + + std::vector streams = {DefaultStream(), DefaultStream()}; + streams[0].scalability_mode = ScalabilityMode::kL1T3; + streams[1].scalability_mode = ScalabilityMode::kL1T1; + + VideoCodec codec = + VideoCodecInitializer::SetupCodec(env_.field_trials(), config, streams); + + // Top level scalability mode should be cleared if the simulcast streams have + // different per-stream temporal layer settings. + EXPECT_EQ(codec.GetScalabilityMode(), std::nullopt); + EXPECT_EQ(codec.spatialLayers[0].numberOfTemporalLayers, 3); + EXPECT_EQ(codec.simulcastStream[0].numberOfTemporalLayers, 3); + EXPECT_EQ(codec.simulcastStream[1].numberOfTemporalLayers, 1); +} +#endif + } // namespace webrtc diff --git a/video/config/encoder_stream_factory.cc b/video/config/encoder_stream_factory.cc index 62b00ad043..f77179f564 100644 --- a/video/config/encoder_stream_factory.cc +++ b/video/config/encoder_stream_factory.cc @@ -64,7 +64,8 @@ bool IsScaleFactorsPowerOfTwo(const webrtc::VideoEncoderConfig& config) { bool IsTemporalLayersSupported(webrtc::VideoCodecType codec_type) { return codec_type == webrtc::VideoCodecType::kVideoCodecVP8 || codec_type == webrtc::VideoCodecType::kVideoCodecVP9 || - codec_type == webrtc::VideoCodecType::kVideoCodecAV1; + codec_type == webrtc::VideoCodecType::kVideoCodecAV1 || + codec_type == webrtc::VideoCodecType::kVideoCodecH265; } size_t FindRequiredActiveLayers( diff --git a/video/config/encoder_stream_factory_unittest.cc b/video/config/encoder_stream_factory_unittest.cc index 4638e48d50..25ba15a579 100644 --- a/video/config/encoder_stream_factory_unittest.cc +++ b/video/config/encoder_stream_factory_unittest.cc @@ -433,4 +433,59 @@ INSTANTIATE_TEST_SUITE_P( .scalability_mode = ScalabilityMode::kL1T2})}}), Values(VideoCodecType::kVideoCodecVP8, VideoCodecType::kVideoCodecAV1))); + +TEST(EncoderStreamFactory, VP9TemporalLayerCountTransferToStreamSettings) { + VideoEncoderConfig encoder_config; + VideoCodecVP9 vp9_settings = VideoEncoder::GetDefaultVp9Settings(); + encoder_config.encoder_specific_settings = + rtc::make_ref_counted( + vp9_settings); + encoder_config.codec_type = VideoCodecType::kVideoCodecVP9; + encoder_config.number_of_streams = 1; + encoder_config.simulcast_layers.resize(1); + encoder_config.simulcast_layers[0].num_temporal_layers = 3; + auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, + encoder_config); + ASSERT_THAT(streams, SizeIs(1)); + EXPECT_EQ(streams[0].num_temporal_layers, 3); +} + +TEST(EncoderStreamFactory, AV1TemporalLayerCountTransferToStreamSettings) { + VideoEncoderConfig encoder_config; + encoder_config.codec_type = VideoCodecType::kVideoCodecAV1; + encoder_config.number_of_streams = 1; + encoder_config.simulcast_layers.resize(1); + encoder_config.simulcast_layers[0].num_temporal_layers = 3; + auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, + encoder_config); + ASSERT_THAT(streams, SizeIs(1)); + EXPECT_EQ(streams[0].num_temporal_layers, 3); +} + +TEST(EncoderStreamFactory, H264TemporalLayerCountTransferToStreamSettings) { + VideoEncoderConfig encoder_config; + encoder_config.codec_type = VideoCodecType::kVideoCodecH264; + encoder_config.number_of_streams = 1; + encoder_config.simulcast_layers.resize(1); + encoder_config.simulcast_layers[0].num_temporal_layers = 3; + auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, + encoder_config); + ASSERT_THAT(streams, SizeIs(1)); + EXPECT_EQ(streams[0].num_temporal_layers, std::nullopt); +} + +#ifdef RTC_ENABLE_H265 +TEST(EncoderStreamFactory, H265TemporalLayerCountTransferToStreamSettings) { + VideoEncoderConfig encoder_config; + encoder_config.codec_type = VideoCodecType::kVideoCodecH265; + encoder_config.number_of_streams = 1; + encoder_config.simulcast_layers.resize(1); + encoder_config.simulcast_layers[0].num_temporal_layers = 3; + auto streams = CreateEncoderStreams(ExplicitKeyValueConfig(""), {1280, 720}, + encoder_config); + ASSERT_THAT(streams, SizeIs(1)); + EXPECT_EQ(streams[0].num_temporal_layers, 3); +} +#endif + } // namespace webrtc