diff options
Diffstat (limited to '')
-rw-r--r-- | third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc | 1902 |
1 files changed, 1902 insertions, 0 deletions
diff --git a/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc new file mode 100644 index 0000000000..e2ac5ea390 --- /dev/null +++ b/third_party/libwebrtc/media/engine/simulcast_encoder_adapter_unittest.cc @@ -0,0 +1,1902 @@ +/* + * Copyright (c) 2014 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 "media/engine/simulcast_encoder_adapter.h" + +#include <array> +#include <memory> +#include <vector> + +#include "api/field_trials_view.h" +#include "api/test/create_simulcast_test_fixture.h" +#include "api/test/simulcast_test_fixture.h" +#include "api/test/video/function_video_decoder_factory.h" +#include "api/test/video/function_video_encoder_factory.h" +#include "api/video/video_codec_constants.h" +#include "api/video_codecs/sdp_video_format.h" +#include "api/video_codecs/video_encoder.h" +#include "api/video_codecs/video_encoder_factory.h" +#include "common_video/include/video_frame_buffer.h" +#include "media/base/media_constants.h" +#include "media/engine/internal_encoder_factory.h" +#include "modules/video_coding/codecs/vp8/include/vp8.h" +#include "modules/video_coding/include/video_codec_interface.h" +#include "modules/video_coding/utility/simulcast_test_fixture_impl.h" +#include "rtc_base/checks.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/scoped_key_value_config.h" + +using ::testing::_; +using ::testing::Return; +using EncoderInfo = webrtc::VideoEncoder::EncoderInfo; +using FramerateFractions = + absl::InlinedVector<uint8_t, webrtc::kMaxTemporalStreams>; + +namespace webrtc { +namespace test { + +namespace { + +constexpr int kDefaultWidth = 1280; +constexpr int kDefaultHeight = 720; + +const VideoEncoder::Capabilities kCapabilities(false); +const VideoEncoder::Settings kSettings(kCapabilities, 1, 1200); + +std::unique_ptr<SimulcastTestFixture> CreateSpecificSimulcastTestFixture( + VideoEncoderFactory* internal_encoder_factory) { + std::unique_ptr<VideoEncoderFactory> encoder_factory = + std::make_unique<FunctionVideoEncoderFactory>( + [internal_encoder_factory]() { + return std::make_unique<SimulcastEncoderAdapter>( + internal_encoder_factory, + SdpVideoFormat(cricket::kVp8CodecName)); + }); + std::unique_ptr<VideoDecoderFactory> decoder_factory = + std::make_unique<FunctionVideoDecoderFactory>( + []() { return VP8Decoder::Create(); }); + return CreateSimulcastTestFixture(std::move(encoder_factory), + std::move(decoder_factory), + SdpVideoFormat(cricket::kVp8CodecName)); +} +} // namespace + +TEST(SimulcastEncoderAdapterSimulcastTest, TestKeyFrameRequestsOnAllStreams) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestKeyFrameRequestsOnAllStreams(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestPaddingAllStreams) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestPaddingAllStreams(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestPaddingTwoStreams) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestPaddingTwoStreams(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestPaddingTwoStreamsOneMaxedOut) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestPaddingTwoStreamsOneMaxedOut(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestPaddingOneStream) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestPaddingOneStream(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestPaddingOneStreamTwoMaxedOut) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestPaddingOneStreamTwoMaxedOut(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestSendAllStreams) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestSendAllStreams(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestDisablingStreams) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestDisablingStreams(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestActiveStreams) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestActiveStreams(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestSwitchingToOneStream) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestSwitchingToOneStream(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestSwitchingToOneOddStream) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestSwitchingToOneOddStream(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestStrideEncodeDecode) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestStrideEncodeDecode(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, + TestSpatioTemporalLayers333PatternEncoder) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestSpatioTemporalLayers333PatternEncoder(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, + TestSpatioTemporalLayers321PatternEncoder) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestSpatioTemporalLayers321PatternEncoder(); +} + +TEST(SimulcastEncoderAdapterSimulcastTest, TestDecodeWidthHeightSet) { + InternalEncoderFactory internal_encoder_factory; + auto fixture = CreateSpecificSimulcastTestFixture(&internal_encoder_factory); + fixture->TestDecodeWidthHeightSet(); +} + +class MockVideoEncoder; + +class MockVideoEncoderFactory : public VideoEncoderFactory { + public: + std::vector<SdpVideoFormat> GetSupportedFormats() const override; + + std::unique_ptr<VideoEncoder> CreateVideoEncoder( + const SdpVideoFormat& format) override; + + const std::vector<MockVideoEncoder*>& encoders() const; + void SetEncoderNames(const std::vector<const char*>& encoder_names); + void set_create_video_encode_return_nullptr(bool return_nullptr) { + create_video_encoder_return_nullptr_ = return_nullptr; + } + void set_init_encode_return_value(int32_t value); + void set_requested_resolution_alignments( + std::vector<uint32_t> requested_resolution_alignments) { + requested_resolution_alignments_ = requested_resolution_alignments; + } + void set_supports_simulcast(bool supports_simulcast) { + supports_simulcast_ = supports_simulcast; + } + void set_resolution_bitrate_limits( + std::vector<VideoEncoder::ResolutionBitrateLimits> limits) { + resolution_bitrate_limits_ = limits; + } + + void DestroyVideoEncoder(VideoEncoder* encoder); + + private: + bool create_video_encoder_return_nullptr_ = false; + int32_t init_encode_return_value_ = 0; + std::vector<MockVideoEncoder*> encoders_; + std::vector<const char*> encoder_names_; + // Keep number of entries in sync with `kMaxSimulcastStreams`. + std::vector<uint32_t> requested_resolution_alignments_ = {1, 1, 1}; + bool supports_simulcast_ = false; + std::vector<VideoEncoder::ResolutionBitrateLimits> resolution_bitrate_limits_; +}; + +class MockVideoEncoder : public VideoEncoder { + public: + explicit MockVideoEncoder(MockVideoEncoderFactory* factory) + : factory_(factory), + scaling_settings_(VideoEncoder::ScalingSettings::kOff), + video_format_("unknown"), + callback_(nullptr) {} + + MOCK_METHOD(void, + SetFecControllerOverride, + (FecControllerOverride * fec_controller_override), + (override)); + + int32_t InitEncode(const VideoCodec* codecSettings, + const VideoEncoder::Settings& settings) override { + codec_ = *codecSettings; + return init_encode_return_value_; + } + + MOCK_METHOD(int32_t, + Encode, + (const VideoFrame& inputImage, + const std::vector<VideoFrameType>* frame_types), + (override)); + + int32_t RegisterEncodeCompleteCallback( + EncodedImageCallback* callback) override { + callback_ = callback; + return 0; + } + + MOCK_METHOD(int32_t, Release, (), (override)); + + void SetRates(const RateControlParameters& parameters) { + last_set_rates_ = parameters; + } + + EncoderInfo GetEncoderInfo() const override { + EncoderInfo info; + info.supports_native_handle = supports_native_handle_; + info.implementation_name = implementation_name_; + info.scaling_settings = scaling_settings_; + info.requested_resolution_alignment = requested_resolution_alignment_; + info.apply_alignment_to_all_simulcast_layers = + apply_alignment_to_all_simulcast_layers_; + info.has_trusted_rate_controller = has_trusted_rate_controller_; + info.is_hardware_accelerated = is_hardware_accelerated_; + info.fps_allocation[0] = fps_allocation_; + info.supports_simulcast = supports_simulcast_; + info.is_qp_trusted = is_qp_trusted_; + info.resolution_bitrate_limits = resolution_bitrate_limits; + return info; + } + + virtual ~MockVideoEncoder() { factory_->DestroyVideoEncoder(this); } + + const VideoCodec& codec() const { return codec_; } + + void SendEncodedImage(int width, int height) { + // Sends a fake image of the given width/height. + EncodedImage image; + image._encodedWidth = width; + image._encodedHeight = height; + CodecSpecificInfo codec_specific_info; + codec_specific_info.codecType = webrtc::kVideoCodecVP8; + callback_->OnEncodedImage(image, &codec_specific_info); + } + + void set_supports_native_handle(bool enabled) { + supports_native_handle_ = enabled; + } + + void set_implementation_name(const std::string& name) { + implementation_name_ = name; + } + + void set_init_encode_return_value(int32_t value) { + init_encode_return_value_ = value; + } + + void set_scaling_settings(const VideoEncoder::ScalingSettings& settings) { + scaling_settings_ = settings; + } + + void set_requested_resolution_alignment( + uint32_t requested_resolution_alignment) { + requested_resolution_alignment_ = requested_resolution_alignment; + } + + void set_apply_alignment_to_all_simulcast_layers(bool apply) { + apply_alignment_to_all_simulcast_layers_ = apply; + } + + void set_has_trusted_rate_controller(bool trusted) { + has_trusted_rate_controller_ = trusted; + } + + void set_is_hardware_accelerated(bool is_hardware_accelerated) { + is_hardware_accelerated_ = is_hardware_accelerated; + } + + void set_fps_allocation(const FramerateFractions& fps_allocation) { + fps_allocation_ = fps_allocation; + } + + RateControlParameters last_set_rates() const { return last_set_rates_; } + + void set_supports_simulcast(bool supports_simulcast) { + supports_simulcast_ = supports_simulcast; + } + + void set_video_format(const SdpVideoFormat& video_format) { + video_format_ = video_format; + } + + void set_is_qp_trusted(absl::optional<bool> is_qp_trusted) { + is_qp_trusted_ = is_qp_trusted; + } + + void set_resolution_bitrate_limits( + std::vector<VideoEncoder::ResolutionBitrateLimits> limits) { + resolution_bitrate_limits = limits; + } + + bool supports_simulcast() const { return supports_simulcast_; } + + SdpVideoFormat video_format() const { return video_format_; } + + private: + MockVideoEncoderFactory* const factory_; + bool supports_native_handle_ = false; + std::string implementation_name_ = "unknown"; + VideoEncoder::ScalingSettings scaling_settings_; + uint32_t requested_resolution_alignment_ = 1; + bool apply_alignment_to_all_simulcast_layers_ = false; + bool has_trusted_rate_controller_ = false; + bool is_hardware_accelerated_ = false; + int32_t init_encode_return_value_ = 0; + VideoEncoder::RateControlParameters last_set_rates_; + FramerateFractions fps_allocation_; + bool supports_simulcast_ = false; + absl::optional<bool> is_qp_trusted_; + SdpVideoFormat video_format_; + std::vector<VideoEncoder::ResolutionBitrateLimits> resolution_bitrate_limits; + + VideoCodec codec_; + EncodedImageCallback* callback_; +}; + +std::vector<SdpVideoFormat> MockVideoEncoderFactory::GetSupportedFormats() + const { + std::vector<SdpVideoFormat> formats = {SdpVideoFormat("VP8")}; + return formats; +} + +std::unique_ptr<VideoEncoder> MockVideoEncoderFactory::CreateVideoEncoder( + const SdpVideoFormat& format) { + if (create_video_encoder_return_nullptr_) { + return nullptr; + } + + auto encoder = std::make_unique<::testing::NiceMock<MockVideoEncoder>>(this); + encoder->set_init_encode_return_value(init_encode_return_value_); + const char* encoder_name = encoder_names_.empty() + ? "codec_implementation_name" + : encoder_names_[encoders_.size()]; + encoder->set_implementation_name(encoder_name); + RTC_CHECK_LT(encoders_.size(), requested_resolution_alignments_.size()); + encoder->set_requested_resolution_alignment( + requested_resolution_alignments_[encoders_.size()]); + encoder->set_supports_simulcast(supports_simulcast_); + encoder->set_video_format(format); + encoder->set_resolution_bitrate_limits(resolution_bitrate_limits_); + encoders_.push_back(encoder.get()); + return encoder; +} + +void MockVideoEncoderFactory::DestroyVideoEncoder(VideoEncoder* encoder) { + for (size_t i = 0; i < encoders_.size(); ++i) { + if (encoders_[i] == encoder) { + encoders_.erase(encoders_.begin() + i); + break; + } + } +} + +const std::vector<MockVideoEncoder*>& MockVideoEncoderFactory::encoders() + const { + return encoders_; +} +void MockVideoEncoderFactory::SetEncoderNames( + const std::vector<const char*>& encoder_names) { + encoder_names_ = encoder_names; +} +void MockVideoEncoderFactory::set_init_encode_return_value(int32_t value) { + init_encode_return_value_ = value; +} + +class TestSimulcastEncoderAdapterFakeHelper { + public: + explicit TestSimulcastEncoderAdapterFakeHelper( + bool use_fallback_factory, + const SdpVideoFormat& video_format, + const FieldTrialsView& field_trials) + : primary_factory_(new MockVideoEncoderFactory()), + fallback_factory_(use_fallback_factory ? new MockVideoEncoderFactory() + : nullptr), + video_format_(video_format), + field_trials_(field_trials) {} + + // Can only be called once as the SimulcastEncoderAdapter will take the + // ownership of `factory_`. + VideoEncoder* CreateMockEncoderAdapter() { + return new SimulcastEncoderAdapter(primary_factory_.get(), + fallback_factory_.get(), video_format_, + field_trials_); + } + + MockVideoEncoderFactory* factory() { return primary_factory_.get(); } + MockVideoEncoderFactory* fallback_factory() { + return fallback_factory_.get(); + } + + private: + std::unique_ptr<MockVideoEncoderFactory> primary_factory_; + std::unique_ptr<MockVideoEncoderFactory> fallback_factory_; + SdpVideoFormat video_format_; + const FieldTrialsView& field_trials_; +}; + +static const int kTestTemporalLayerProfile[3] = {3, 2, 1}; + +class TestSimulcastEncoderAdapterFake : public ::testing::Test, + public EncodedImageCallback { + public: + TestSimulcastEncoderAdapterFake() : use_fallback_factory_(false) {} + + virtual ~TestSimulcastEncoderAdapterFake() { + if (adapter_) { + adapter_->Release(); + } + } + + void SetUp() override { + helper_.reset(new TestSimulcastEncoderAdapterFakeHelper( + use_fallback_factory_, SdpVideoFormat("VP8", sdp_video_parameters_), + field_trials_)); + adapter_.reset(helper_->CreateMockEncoderAdapter()); + last_encoded_image_width_ = absl::nullopt; + last_encoded_image_height_ = absl::nullopt; + last_encoded_image_simulcast_index_ = absl::nullopt; + } + + void ReSetUp() { + if (adapter_) { + adapter_->Release(); + // `helper_` owns factories which `adapter_` needs to destroy encoders. + // Release `adapter_` before `helper_` (released in SetUp()). + adapter_.reset(); + } + SetUp(); + } + + Result OnEncodedImage(const EncodedImage& encoded_image, + const CodecSpecificInfo* codec_specific_info) override { + last_encoded_image_width_ = encoded_image._encodedWidth; + last_encoded_image_height_ = encoded_image._encodedHeight; + last_encoded_image_simulcast_index_ = encoded_image.SimulcastIndex(); + + return Result(Result::OK, encoded_image.RtpTimestamp()); + } + + bool GetLastEncodedImageInfo(absl::optional<int>* out_width, + absl::optional<int>* out_height, + absl::optional<int>* out_simulcast_index) { + if (!last_encoded_image_width_.has_value()) { + return false; + } + *out_width = last_encoded_image_width_; + *out_height = last_encoded_image_height_; + *out_simulcast_index = last_encoded_image_simulcast_index_; + return true; + } + + void SetupCodec() { SetupCodec(/*active_streams=*/{true, true, true}); } + + void SetupCodec(std::vector<bool> active_streams) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + ASSERT_LE(active_streams.size(), codec_.numberOfSimulcastStreams); + codec_.numberOfSimulcastStreams = active_streams.size(); + for (size_t stream_idx = 0; stream_idx < kMaxSimulcastStreams; + ++stream_idx) { + if (stream_idx >= codec_.numberOfSimulcastStreams) { + // Reset parameters of unspecified stream. + codec_.simulcastStream[stream_idx] = {0}; + } else { + codec_.simulcastStream[stream_idx].active = active_streams[stream_idx]; + } + } + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + } + + void VerifyCodec(const VideoCodec& ref, int stream_index) { + const VideoCodec& target = + helper_->factory()->encoders()[stream_index]->codec(); + EXPECT_EQ(ref.codecType, target.codecType); + EXPECT_EQ(ref.width, target.width); + EXPECT_EQ(ref.height, target.height); + EXPECT_EQ(ref.startBitrate, target.startBitrate); + EXPECT_EQ(ref.maxBitrate, target.maxBitrate); + EXPECT_EQ(ref.minBitrate, target.minBitrate); + EXPECT_EQ(ref.maxFramerate, target.maxFramerate); + EXPECT_EQ(ref.GetVideoEncoderComplexity(), + target.GetVideoEncoderComplexity()); + EXPECT_EQ(ref.VP8().numberOfTemporalLayers, + target.VP8().numberOfTemporalLayers); + EXPECT_EQ(ref.VP8().denoisingOn, target.VP8().denoisingOn); + EXPECT_EQ(ref.VP8().automaticResizeOn, target.VP8().automaticResizeOn); + EXPECT_EQ(ref.GetFrameDropEnabled(), target.GetFrameDropEnabled()); + EXPECT_EQ(ref.VP8().keyFrameInterval, target.VP8().keyFrameInterval); + EXPECT_EQ(ref.qpMax, target.qpMax); + EXPECT_EQ(0, target.numberOfSimulcastStreams); + EXPECT_EQ(ref.mode, target.mode); + + // No need to compare simulcastStream as numberOfSimulcastStreams should + // always be 0. + } + + void InitRefCodec(int stream_index, + VideoCodec* ref_codec, + bool reverse_layer_order = false) { + *ref_codec = codec_; + ref_codec->VP8()->numberOfTemporalLayers = + kTestTemporalLayerProfile[reverse_layer_order ? 2 - stream_index + : stream_index]; + ref_codec->width = codec_.simulcastStream[stream_index].width; + ref_codec->height = codec_.simulcastStream[stream_index].height; + ref_codec->maxBitrate = codec_.simulcastStream[stream_index].maxBitrate; + ref_codec->minBitrate = codec_.simulcastStream[stream_index].minBitrate; + ref_codec->qpMax = codec_.simulcastStream[stream_index].qpMax; + } + + void VerifyCodecSettings() { + EXPECT_EQ(3u, helper_->factory()->encoders().size()); + VideoCodec ref_codec; + + // stream 0, the lowest resolution stream. + InitRefCodec(0, &ref_codec); + ref_codec.qpMax = 45; + ref_codec.SetVideoEncoderComplexity( + webrtc::VideoCodecComplexity::kComplexityHigher); + ref_codec.VP8()->denoisingOn = false; + ref_codec.startBitrate = 100; // Should equal to the target bitrate. + VerifyCodec(ref_codec, 0); + + // stream 1 + InitRefCodec(1, &ref_codec); + ref_codec.VP8()->denoisingOn = false; + // The start bitrate (300kbit) minus what we have for the lower layers + // (100kbit). + ref_codec.startBitrate = 200; + VerifyCodec(ref_codec, 1); + + // stream 2, the biggest resolution stream. + InitRefCodec(2, &ref_codec); + // We don't have enough bits to send this, so the adapter should have + // configured it to use the min bitrate for this layer (600kbit) but turn + // off sending. + ref_codec.startBitrate = 600; + VerifyCodec(ref_codec, 2); + } + + protected: + std::unique_ptr<TestSimulcastEncoderAdapterFakeHelper> helper_; + std::unique_ptr<VideoEncoder> adapter_; + VideoCodec codec_; + absl::optional<int> last_encoded_image_width_; + absl::optional<int> last_encoded_image_height_; + absl::optional<int> last_encoded_image_simulcast_index_; + std::unique_ptr<SimulcastRateAllocator> rate_allocator_; + bool use_fallback_factory_; + SdpVideoFormat::Parameters sdp_video_parameters_; + test::ScopedKeyValueConfig field_trials_; +}; + +TEST_F(TestSimulcastEncoderAdapterFake, InitEncode) { + SetupCodec(); + VerifyCodecSettings(); +} + +TEST_F(TestSimulcastEncoderAdapterFake, ReleaseWithoutInitEncode) { + EXPECT_EQ(0, adapter_->Release()); +} + +TEST_F(TestSimulcastEncoderAdapterFake, Reinit) { + SetupCodec(); + EXPECT_EQ(0, adapter_->Release()); + + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, EncodedCallbackForDifferentEncoders) { + SetupCodec(); + + // Set bitrates so that we send all layers. + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate(VideoBitrateAllocationParameters(1200, 30)), + 30.0)); + + // At this point, the simulcast encoder adapter should have 3 streams: HD, + // quarter HD, and quarter quarter HD. We're going to mostly ignore the exact + // resolutions, to test that the adapter forwards on the correct resolution + // and simulcast index values, going only off the encoder that generates the + // image. + std::vector<MockVideoEncoder*> encoders = helper_->factory()->encoders(); + ASSERT_EQ(3u, encoders.size()); + encoders[0]->SendEncodedImage(1152, 704); + absl::optional<int> width; + absl::optional<int> height; + absl::optional<int> simulcast_index; + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(width.has_value()); + EXPECT_EQ(1152, width.value()); + ASSERT_TRUE(height.has_value()); + EXPECT_EQ(704, height.value()); + // SEA doesn't intercept frame encode complete callback for the lowest stream. + EXPECT_FALSE(simulcast_index.has_value()); + + encoders[1]->SendEncodedImage(300, 620); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(width.has_value()); + EXPECT_EQ(300, width.value()); + ASSERT_TRUE(height.has_value()); + EXPECT_EQ(620, height.value()); + ASSERT_TRUE(simulcast_index.has_value()); + EXPECT_EQ(1, simulcast_index.value()); + + encoders[2]->SendEncodedImage(120, 240); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(width.has_value()); + EXPECT_EQ(120, width.value()); + ASSERT_TRUE(height.has_value()); + EXPECT_EQ(240, height.value()); + ASSERT_TRUE(simulcast_index.has_value()); + EXPECT_EQ(2, simulcast_index.value()); +} + +// This test verifies that the underlying encoders are reused, when the adapter +// is reinited with different number of simulcast streams. It further checks +// that the allocated encoders are reused in the same order as before, starting +// with the lowest stream. +TEST_F(TestSimulcastEncoderAdapterFake, ReusesEncodersInOrder) { + // Set up common settings for three streams. + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + adapter_->RegisterEncodeCompleteCallback(this); + const uint32_t target_bitrate = + 1000 * (codec_.simulcastStream[0].targetBitrate + + codec_.simulcastStream[1].targetBitrate + + codec_.simulcastStream[2].minBitrate); + + // Input data. + rtc::scoped_refptr<VideoFrameBuffer> buffer(I420Buffer::Create(1280, 720)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + std::vector<VideoFrameType> frame_types; + + // Encode with three streams. + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + VerifyCodecSettings(); + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate( + VideoBitrateAllocationParameters(target_bitrate, 30)), + 30.0)); + + std::vector<MockVideoEncoder*> original_encoders = + helper_->factory()->encoders(); + ASSERT_EQ(3u, original_encoders.size()); + EXPECT_CALL(*original_encoders[0], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[2], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + frame_types.resize(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + EXPECT_CALL(*original_encoders[0], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[2], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_EQ(0, adapter_->Release()); + + // Encode with two streams. + codec_.width /= 2; + codec_.height /= 2; + codec_.numberOfSimulcastStreams = 2; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate( + VideoBitrateAllocationParameters(target_bitrate, 30)), + 30.0)); + std::vector<MockVideoEncoder*> new_encoders = helper_->factory()->encoders(); + ASSERT_EQ(2u, new_encoders.size()); + ASSERT_EQ(original_encoders[0], new_encoders[0]); + EXPECT_CALL(*original_encoders[0], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + ASSERT_EQ(original_encoders[1], new_encoders[1]); + EXPECT_CALL(*original_encoders[1], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + frame_types.resize(2, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + EXPECT_CALL(*original_encoders[0], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_EQ(0, adapter_->Release()); + + // Encode with single stream. + codec_.width /= 2; + codec_.height /= 2; + codec_.numberOfSimulcastStreams = 1; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate( + VideoBitrateAllocationParameters(target_bitrate, 30)), + 30.0)); + new_encoders = helper_->factory()->encoders(); + ASSERT_EQ(1u, new_encoders.size()); + ASSERT_EQ(original_encoders[0], new_encoders[0]); + EXPECT_CALL(*original_encoders[0], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + frame_types.resize(1, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + EXPECT_CALL(*original_encoders[0], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_EQ(0, adapter_->Release()); + + // Encode with three streams, again. + codec_.width *= 4; + codec_.height *= 4; + codec_.numberOfSimulcastStreams = 3; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate( + VideoBitrateAllocationParameters(target_bitrate, 30)), + 30.0)); + new_encoders = helper_->factory()->encoders(); + ASSERT_EQ(3u, new_encoders.size()); + // The first encoder is reused. + ASSERT_EQ(original_encoders[0], new_encoders[0]); + EXPECT_CALL(*original_encoders[0], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + // The second and third encoders are new. + EXPECT_CALL(*new_encoders[1], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*new_encoders[2], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + frame_types.resize(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + EXPECT_CALL(*original_encoders[0], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*new_encoders[1], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*new_encoders[2], Release()) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_EQ(0, adapter_->Release()); +} + +TEST_F(TestSimulcastEncoderAdapterFake, DoesNotLeakEncoders) { + SetupCodec(); + VerifyCodecSettings(); + + EXPECT_EQ(3u, helper_->factory()->encoders().size()); + + // The adapter should destroy all encoders it has allocated. Since + // `helper_->factory()` is owned by `adapter_`, however, we need to rely on + // lsan to find leaks here. + EXPECT_EQ(0, adapter_->Release()); + adapter_.reset(); +} + +// This test verifies that an adapter reinit with the same codec settings as +// before does not change the underlying encoder codec settings. +TEST_F(TestSimulcastEncoderAdapterFake, ReinitDoesNotReorderEncoderSettings) { + SetupCodec(); + VerifyCodecSettings(); + + // Capture current codec settings. + std::vector<MockVideoEncoder*> encoders = helper_->factory()->encoders(); + ASSERT_EQ(3u, encoders.size()); + std::array<VideoCodec, 3> codecs_before; + for (int i = 0; i < 3; ++i) { + codecs_before[i] = encoders[i]->codec(); + } + + // Reinitialize and verify that the new codec settings are the same. + EXPECT_EQ(0, adapter_->Release()); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + for (int i = 0; i < 3; ++i) { + const VideoCodec& codec_before = codecs_before[i]; + const VideoCodec& codec_after = encoders[i]->codec(); + + // webrtc::VideoCodec does not implement operator==. + EXPECT_EQ(codec_before.codecType, codec_after.codecType); + EXPECT_EQ(codec_before.width, codec_after.width); + EXPECT_EQ(codec_before.height, codec_after.height); + EXPECT_EQ(codec_before.startBitrate, codec_after.startBitrate); + EXPECT_EQ(codec_before.maxBitrate, codec_after.maxBitrate); + EXPECT_EQ(codec_before.minBitrate, codec_after.minBitrate); + EXPECT_EQ(codec_before.maxFramerate, codec_after.maxFramerate); + EXPECT_EQ(codec_before.qpMax, codec_after.qpMax); + EXPECT_EQ(codec_before.numberOfSimulcastStreams, + codec_after.numberOfSimulcastStreams); + EXPECT_EQ(codec_before.mode, codec_after.mode); + EXPECT_EQ(codec_before.expect_encode_from_texture, + codec_after.expect_encode_from_texture); + } +} + +// This test is similar to the one above, except that it tests the simulcastIdx +// from the CodecSpecificInfo that is connected to an encoded frame. The +// PayloadRouter demuxes the incoming encoded frames on different RTP modules +// using the simulcastIdx, so it's important that there is no corresponding +// encoder reordering in between adapter reinits as this would lead to PictureID +// discontinuities. +TEST_F(TestSimulcastEncoderAdapterFake, ReinitDoesNotReorderFrameSimulcastIdx) { + SetupCodec(); + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate(VideoBitrateAllocationParameters(1200, 30)), + 30.0)); + VerifyCodecSettings(); + + // Send frames on all streams. + std::vector<MockVideoEncoder*> encoders = helper_->factory()->encoders(); + ASSERT_EQ(3u, encoders.size()); + encoders[0]->SendEncodedImage(1152, 704); + absl::optional<int> width; + absl::optional<int> height; + absl::optional<int> simulcast_index; + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + // SEA doesn't intercept frame encode complete callback for the lowest stream. + EXPECT_FALSE(simulcast_index.has_value()); + + encoders[1]->SendEncodedImage(300, 620); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(simulcast_index.has_value()); + EXPECT_EQ(1, simulcast_index.value()); + + encoders[2]->SendEncodedImage(120, 240); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(simulcast_index.has_value()); + EXPECT_EQ(2, simulcast_index.value()); + + // Reinitialize. + EXPECT_EQ(0, adapter_->Release()); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate(VideoBitrateAllocationParameters(1200, 30)), + 30.0)); + + // Verify that the same encoder sends out frames on the same simulcast index. + encoders[0]->SendEncodedImage(1152, 704); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + EXPECT_FALSE(simulcast_index.has_value()); + + encoders[1]->SendEncodedImage(300, 620); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(simulcast_index.has_value()); + EXPECT_EQ(1, simulcast_index.value()); + + encoders[2]->SendEncodedImage(120, 240); + EXPECT_TRUE(GetLastEncodedImageInfo(&width, &height, &simulcast_index)); + ASSERT_TRUE(simulcast_index.has_value()); + EXPECT_EQ(2, simulcast_index.value()); +} + +TEST_F(TestSimulcastEncoderAdapterFake, SupportsNativeHandleForSingleStreams) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 1; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + helper_->factory()->encoders()[0]->set_supports_native_handle(true); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(adapter_->GetEncoderInfo().supports_native_handle); + helper_->factory()->encoders()[0]->set_supports_native_handle(false); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().supports_native_handle); +} + +TEST_F(TestSimulcastEncoderAdapterFake, SetRatesUnderMinBitrate) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.minBitrate = 50; + codec_.numberOfSimulcastStreams = 1; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + + // Above min should be respected. + VideoBitrateAllocation target_bitrate = rate_allocator_->Allocate( + VideoBitrateAllocationParameters(codec_.minBitrate * 1000, 30)); + adapter_->SetRates(VideoEncoder::RateControlParameters(target_bitrate, 30.0)); + EXPECT_EQ(target_bitrate, + helper_->factory()->encoders()[0]->last_set_rates().bitrate); + + // Below min but non-zero should be replaced with the min bitrate. + VideoBitrateAllocation too_low_bitrate = rate_allocator_->Allocate( + VideoBitrateAllocationParameters((codec_.minBitrate - 1) * 1000, 30)); + adapter_->SetRates( + VideoEncoder::RateControlParameters(too_low_bitrate, 30.0)); + EXPECT_EQ(target_bitrate, + helper_->factory()->encoders()[0]->last_set_rates().bitrate); + + // Zero should be passed on as is, since it means "pause". + adapter_->SetRates( + VideoEncoder::RateControlParameters(VideoBitrateAllocation(), 30.0)); + EXPECT_EQ(VideoBitrateAllocation(), + helper_->factory()->encoders()[0]->last_set_rates().bitrate); +} + +TEST_F(TestSimulcastEncoderAdapterFake, SupportsImplementationName) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + std::vector<const char*> encoder_names; + encoder_names.push_back("codec1"); + encoder_names.push_back("codec2"); + encoder_names.push_back("codec3"); + helper_->factory()->SetEncoderNames(encoder_names); + EXPECT_EQ("SimulcastEncoderAdapter", + adapter_->GetEncoderInfo().implementation_name); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_EQ("SimulcastEncoderAdapter (codec1, codec2, codec3)", + adapter_->GetEncoderInfo().implementation_name); + + // Single streams should not expose "SimulcastEncoderAdapter" in name. + EXPECT_EQ(0, adapter_->Release()); + codec_.numberOfSimulcastStreams = 1; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + EXPECT_EQ("codec1", adapter_->GetEncoderInfo().implementation_name); +} + +TEST_F(TestSimulcastEncoderAdapterFake, RuntimeEncoderInfoUpdate) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + std::vector<const char*> encoder_names; + encoder_names.push_back("codec1"); + encoder_names.push_back("codec2"); + encoder_names.push_back("codec3"); + helper_->factory()->SetEncoderNames(encoder_names); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_EQ("SimulcastEncoderAdapter (codec1, codec2, codec3)", + adapter_->GetEncoderInfo().implementation_name); + + // Change name of first encoder to indicate it has done a fallback to another + // implementation. + helper_->factory()->encoders().front()->set_implementation_name("fallback1"); + EXPECT_EQ("SimulcastEncoderAdapter (fallback1, codec2, codec3)", + adapter_->GetEncoderInfo().implementation_name); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + SupportsNativeHandleForMultipleStreams) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + for (MockVideoEncoder* encoder : helper_->factory()->encoders()) + encoder->set_supports_native_handle(true); + // As long as one encoder supports native handle, it's enabled. + helper_->factory()->encoders()[0]->set_supports_native_handle(false); + EXPECT_TRUE(adapter_->GetEncoderInfo().supports_native_handle); + // Once none do, then the adapter claims no support. + helper_->factory()->encoders()[1]->set_supports_native_handle(false); + helper_->factory()->encoders()[2]->set_supports_native_handle(false); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().supports_native_handle); +} + +class FakeNativeBufferI420 : public VideoFrameBuffer { + public: + FakeNativeBufferI420(int width, int height, bool allow_to_i420) + : width_(width), height_(height), allow_to_i420_(allow_to_i420) {} + + Type type() const override { return Type::kNative; } + int width() const override { return width_; } + int height() const override { return height_; } + + rtc::scoped_refptr<I420BufferInterface> ToI420() override { + if (allow_to_i420_) { + return I420Buffer::Create(width_, height_); + } else { + RTC_DCHECK_NOTREACHED(); + } + return nullptr; + } + + private: + const int width_; + const int height_; + const bool allow_to_i420_; +}; + +TEST_F(TestSimulcastEncoderAdapterFake, + NativeHandleForwardingForMultipleStreams) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + // High start bitrate, so all streams are enabled. + codec_.startBitrate = 3000; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + for (MockVideoEncoder* encoder : helper_->factory()->encoders()) + encoder->set_supports_native_handle(true); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(adapter_->GetEncoderInfo().supports_native_handle); + + rtc::scoped_refptr<VideoFrameBuffer> buffer( + rtc::make_ref_counted<FakeNativeBufferI420>(1280, 720, + /*allow_to_i420=*/false)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + // Expect calls with the given video frame verbatim, since it's a texture + // frame and can't otherwise be modified/resized. + for (MockVideoEncoder* encoder : helper_->factory()->encoders()) + EXPECT_CALL(*encoder, Encode(::testing::Ref(input_frame), _)).Times(1); + std::vector<VideoFrameType> frame_types(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, NativeHandleForwardingOnlyIfSupported) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + // High start bitrate, so all streams are enabled. + codec_.startBitrate = 3000; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + + // QVGA encoders has fallen back to software. + auto& encoders = helper_->factory()->encoders(); + encoders[0]->set_supports_native_handle(false); + encoders[1]->set_supports_native_handle(true); + encoders[2]->set_supports_native_handle(true); + + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(adapter_->GetEncoderInfo().supports_native_handle); + + rtc::scoped_refptr<VideoFrameBuffer> buffer( + rtc::make_ref_counted<FakeNativeBufferI420>(1280, 720, + /*allow_to_i420=*/true)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + // Expect calls with the given video frame verbatim, since it's a texture + // frame and can't otherwise be modified/resized, but only on the two + // streams supporting it... + EXPECT_CALL(*encoders[1], Encode(::testing::Ref(input_frame), _)).Times(1); + EXPECT_CALL(*encoders[2], Encode(::testing::Ref(input_frame), _)).Times(1); + // ...the lowest one gets a software buffer. + EXPECT_CALL(*encoders[0], Encode) + .WillOnce([&](const VideoFrame& frame, + const std::vector<VideoFrameType>* frame_types) { + EXPECT_EQ(frame.video_frame_buffer()->type(), + VideoFrameBuffer::Type::kI420); + return 0; + }); + std::vector<VideoFrameType> frame_types(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, GeneratesKeyFramesOnRequestedLayers) { + // Set up common settings for three streams. + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + adapter_->RegisterEncodeCompleteCallback(this); + + // Input data. + rtc::scoped_refptr<VideoFrameBuffer> buffer(I420Buffer::Create(1280, 720)); + + // Encode with three streams. + codec_.startBitrate = 3000; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + + std::vector<VideoFrameType> frame_types; + frame_types.resize(3, VideoFrameType::kVideoFrameKey); + + std::vector<VideoFrameType> expected_keyframe(1, + VideoFrameType::kVideoFrameKey); + std::vector<VideoFrameType> expected_deltaframe( + 1, VideoFrameType::kVideoFrameDelta); + + std::vector<MockVideoEncoder*> original_encoders = + helper_->factory()->encoders(); + ASSERT_EQ(3u, original_encoders.size()); + EXPECT_CALL(*original_encoders[0], + Encode(_, ::testing::Pointee(::testing::Eq(expected_keyframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], + Encode(_, ::testing::Pointee(::testing::Eq(expected_keyframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[2], + Encode(_, ::testing::Pointee(::testing::Eq(expected_keyframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + VideoFrame first_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(0) + .set_timestamp_ms(0) + .build(); + EXPECT_EQ(0, adapter_->Encode(first_frame, &frame_types)); + + // Request [key, delta, delta]. + EXPECT_CALL(*original_encoders[0], + Encode(_, ::testing::Pointee(::testing::Eq(expected_keyframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], + Encode(_, ::testing::Pointee(::testing::Eq(expected_deltaframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[2], + Encode(_, ::testing::Pointee(::testing::Eq(expected_deltaframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + frame_types[1] = VideoFrameType::kVideoFrameKey; + frame_types[1] = VideoFrameType::kVideoFrameDelta; + frame_types[2] = VideoFrameType::kVideoFrameDelta; + VideoFrame second_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(10000) + .set_timestamp_ms(100000) + .build(); + EXPECT_EQ(0, adapter_->Encode(second_frame, &frame_types)); + + // Request [delta, key, delta]. + EXPECT_CALL(*original_encoders[0], + Encode(_, ::testing::Pointee(::testing::Eq(expected_deltaframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], + Encode(_, ::testing::Pointee(::testing::Eq(expected_keyframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[2], + Encode(_, ::testing::Pointee(::testing::Eq(expected_deltaframe)))) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + frame_types[0] = VideoFrameType::kVideoFrameDelta; + frame_types[1] = VideoFrameType::kVideoFrameKey; + frame_types[2] = VideoFrameType::kVideoFrameDelta; + VideoFrame third_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(20000) + .set_timestamp_ms(200000) + .build(); + EXPECT_EQ(0, adapter_->Encode(third_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, TestFailureReturnCodesFromEncodeCalls) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + // Tell the 2nd encoder to request software fallback. + EXPECT_CALL(*helper_->factory()->encoders()[1], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE)); + + // Send a fake frame and assert the return is software fallback. + rtc::scoped_refptr<I420Buffer> input_buffer = + I420Buffer::Create(kDefaultWidth, kDefaultHeight); + input_buffer->InitializeData(); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(input_buffer) + .set_timestamp_rtp(0) + .set_timestamp_us(0) + .set_rotation(kVideoRotation_0) + .build(); + std::vector<VideoFrameType> frame_types(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE, + adapter_->Encode(input_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, TestInitFailureCleansUpEncoders) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + helper_->factory()->set_init_encode_return_value( + WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE); + EXPECT_EQ(WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE, + adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(helper_->factory()->encoders().empty()); +} + +TEST_F(TestSimulcastEncoderAdapterFake, DoesNotAlterMaxQpForScreenshare) { + const int kHighMaxQp = 56; + const int kLowMaxQp = 46; + + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + codec_.simulcastStream[0].qpMax = kHighMaxQp; + codec_.mode = VideoCodecMode::kScreensharing; + + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_EQ(3u, helper_->factory()->encoders().size()); + + // Just check the lowest stream, which is the one that where the adapter + // might alter the max qp setting. + VideoCodec ref_codec; + InitRefCodec(0, &ref_codec); + ref_codec.qpMax = kHighMaxQp; + ref_codec.SetVideoEncoderComplexity( + webrtc::VideoCodecComplexity::kComplexityHigher); + ref_codec.VP8()->denoisingOn = false; + ref_codec.startBitrate = 100; // Should equal to the target bitrate. + VerifyCodec(ref_codec, 0); + + // Change the max qp and try again. + codec_.simulcastStream[0].qpMax = kLowMaxQp; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_EQ(3u, helper_->factory()->encoders().size()); + ref_codec.qpMax = kLowMaxQp; + VerifyCodec(ref_codec, 0); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + DoesNotAlterMaxQpForScreenshareReversedLayer) { + const int kHighMaxQp = 56; + const int kLowMaxQp = 46; + + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8, true /* reverse_layer_order */); + codec_.numberOfSimulcastStreams = 3; + codec_.simulcastStream[2].qpMax = kHighMaxQp; + codec_.mode = VideoCodecMode::kScreensharing; + + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_EQ(3u, helper_->factory()->encoders().size()); + + // Just check the lowest stream, which is the one that where the adapter + // might alter the max qp setting. + VideoCodec ref_codec; + InitRefCodec(2, &ref_codec, true /* reverse_layer_order */); + ref_codec.qpMax = kHighMaxQp; + ref_codec.SetVideoEncoderComplexity( + webrtc::VideoCodecComplexity::kComplexityHigher); + ref_codec.VP8()->denoisingOn = false; + ref_codec.startBitrate = 100; // Should equal to the target bitrate. + VerifyCodec(ref_codec, 2); + + // Change the max qp and try again. + codec_.simulcastStream[2].qpMax = kLowMaxQp; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_EQ(3u, helper_->factory()->encoders().size()); + ref_codec.qpMax = kLowMaxQp; + VerifyCodec(ref_codec, 2); +} + +TEST_F(TestSimulcastEncoderAdapterFake, ActivatesCorrectStreamsInInitEncode) { + // Set up common settings for three streams. + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + adapter_->RegisterEncodeCompleteCallback(this); + + // Only enough start bitrate for the lowest stream. + ASSERT_EQ(3u, codec_.numberOfSimulcastStreams); + codec_.startBitrate = codec_.simulcastStream[0].targetBitrate + + codec_.simulcastStream[1].minBitrate - 1; + + // Input data. + rtc::scoped_refptr<VideoFrameBuffer> buffer(I420Buffer::Create(1280, 720)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + + // Encode with three streams. + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + std::vector<MockVideoEncoder*> original_encoders = + helper_->factory()->encoders(); + ASSERT_EQ(3u, original_encoders.size()); + // Only first encoder will be active and called. + EXPECT_CALL(*original_encoders[0], Encode(_, _)) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*original_encoders[1], Encode(_, _)).Times(0); + EXPECT_CALL(*original_encoders[2], Encode(_, _)).Times(0); + + std::vector<VideoFrameType> frame_types; + frame_types.resize(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, TrustedRateControl) { + // Set up common settings for three streams. + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + adapter_->RegisterEncodeCompleteCallback(this); + + // Only enough start bitrate for the lowest stream. + ASSERT_EQ(3u, codec_.numberOfSimulcastStreams); + codec_.startBitrate = codec_.simulcastStream[0].targetBitrate + + codec_.simulcastStream[1].minBitrate - 1; + + // Input data. + rtc::scoped_refptr<VideoFrameBuffer> buffer(I420Buffer::Create(1280, 720)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + + // No encoder trusted, so simulcast adapter should not be either. + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().has_trusted_rate_controller); + + // Encode with three streams. + std::vector<MockVideoEncoder*> original_encoders = + helper_->factory()->encoders(); + + // All encoders are trusted, so simulcast adapter should be too. + original_encoders[0]->set_has_trusted_rate_controller(true); + original_encoders[1]->set_has_trusted_rate_controller(true); + original_encoders[2]->set_has_trusted_rate_controller(true); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(adapter_->GetEncoderInfo().has_trusted_rate_controller); + + // One encoder not trusted, so simulcast adapter should not be either. + original_encoders[2]->set_has_trusted_rate_controller(false); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().has_trusted_rate_controller); + + // No encoder trusted, so simulcast adapter should not be either. + original_encoders[0]->set_has_trusted_rate_controller(false); + original_encoders[1]->set_has_trusted_rate_controller(false); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().has_trusted_rate_controller); +} + +TEST_F(TestSimulcastEncoderAdapterFake, ReportsHardwareAccelerated) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + adapter_->RegisterEncodeCompleteCallback(this); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + + // None of the encoders uses HW support, so simulcast adapter reports false. + for (MockVideoEncoder* encoder : helper_->factory()->encoders()) { + encoder->set_is_hardware_accelerated(false); + } + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().is_hardware_accelerated); + + // One encoder uses HW support, so simulcast adapter reports true. + helper_->factory()->encoders()[2]->set_is_hardware_accelerated(true); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(adapter_->GetEncoderInfo().is_hardware_accelerated); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + ReportsLeastCommonMultipleOfRequestedResolutionAlignments) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + helper_->factory()->set_requested_resolution_alignments({2, 4, 7}); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + + EXPECT_EQ(adapter_->GetEncoderInfo().requested_resolution_alignment, 28u); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + ReportsApplyAlignmentToSimulcastLayers) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + + // No encoder has apply_alignment_to_all_simulcast_layers, report false. + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + for (MockVideoEncoder* encoder : helper_->factory()->encoders()) { + encoder->set_apply_alignment_to_all_simulcast_layers(false); + } + EXPECT_FALSE( + adapter_->GetEncoderInfo().apply_alignment_to_all_simulcast_layers); + + // One encoder has apply_alignment_to_all_simulcast_layers, report true. + helper_->factory() + ->encoders()[1] + ->set_apply_alignment_to_all_simulcast_layers(true); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE( + adapter_->GetEncoderInfo().apply_alignment_to_all_simulcast_layers); +} + +TEST_F( + TestSimulcastEncoderAdapterFake, + EncoderInfoFromFieldTrialDoesNotOverrideExistingBitrateLimitsInSinglecast) { + test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-SimulcastEncoderAdapter-GetEncoderInfoOverride/" + "frame_size_pixels:123|456|789," + "min_start_bitrate_bps:11000|22000|33000," + "min_bitrate_bps:44000|55000|66000," + "max_bitrate_bps:77000|88000|99000/"); + SetUp(); + + std::vector<VideoEncoder::ResolutionBitrateLimits> bitrate_limits; + bitrate_limits.push_back( + VideoEncoder::ResolutionBitrateLimits(111, 11100, 44400, 77700)); + bitrate_limits.push_back( + VideoEncoder::ResolutionBitrateLimits(444, 22200, 55500, 88700)); + bitrate_limits.push_back( + VideoEncoder::ResolutionBitrateLimits(777, 33300, 66600, 99900)); + SetUp(); + helper_->factory()->set_resolution_bitrate_limits(bitrate_limits); + + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 1; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + EXPECT_EQ(adapter_->GetEncoderInfo().resolution_bitrate_limits, + bitrate_limits); +} + +TEST_F(TestSimulcastEncoderAdapterFake, EncoderInfoFromFieldTrial) { + test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-SimulcastEncoderAdapter-GetEncoderInfoOverride/" + "requested_resolution_alignment:8," + "apply_alignment_to_all_simulcast_layers/"); + SetUp(); + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + + EXPECT_EQ(8u, adapter_->GetEncoderInfo().requested_resolution_alignment); + EXPECT_TRUE( + adapter_->GetEncoderInfo().apply_alignment_to_all_simulcast_layers); + EXPECT_TRUE(adapter_->GetEncoderInfo().resolution_bitrate_limits.empty()); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + EncoderInfoFromFieldTrialForSingleStream) { + test::ScopedKeyValueConfig field_trials( + field_trials_, + "WebRTC-SimulcastEncoderAdapter-GetEncoderInfoOverride/" + "requested_resolution_alignment:9," + "frame_size_pixels:123|456|789," + "min_start_bitrate_bps:11000|22000|33000," + "min_bitrate_bps:44000|55000|66000," + "max_bitrate_bps:77000|88000|99000/"); + SetUp(); + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 1; + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + + EXPECT_EQ(9u, adapter_->GetEncoderInfo().requested_resolution_alignment); + EXPECT_FALSE( + adapter_->GetEncoderInfo().apply_alignment_to_all_simulcast_layers); + EXPECT_THAT( + adapter_->GetEncoderInfo().resolution_bitrate_limits, + ::testing::ElementsAre( + VideoEncoder::ResolutionBitrateLimits{123, 11000, 44000, 77000}, + VideoEncoder::ResolutionBitrateLimits{456, 22000, 55000, 88000}, + VideoEncoder::ResolutionBitrateLimits{789, 33000, 66000, 99000})); +} + +TEST_F(TestSimulcastEncoderAdapterFake, ReportsIsQpTrusted) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + adapter_->RegisterEncodeCompleteCallback(this); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + + // All encoders have internal source, simulcast adapter reports true. + for (MockVideoEncoder* encoder : helper_->factory()->encoders()) { + encoder->set_is_qp_trusted(true); + } + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_TRUE(adapter_->GetEncoderInfo().is_qp_trusted.value_or(false)); + + // One encoder reports QP not trusted, simulcast adapter reports false. + helper_->factory()->encoders()[2]->set_is_qp_trusted(false); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_FALSE(adapter_->GetEncoderInfo().is_qp_trusted.value_or(true)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, ReportsFpsAllocation) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + adapter_->RegisterEncodeCompleteCallback(this); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + + // Combination of three different supported mode: + // Simulcast stream 0 has undefined fps behavior. + // Simulcast stream 1 has three temporal layers. + // Simulcast stream 2 has 1 temporal layer. + FramerateFractions expected_fps_allocation[kMaxSpatialLayers]; + expected_fps_allocation[1].push_back(EncoderInfo::kMaxFramerateFraction / 4); + expected_fps_allocation[1].push_back(EncoderInfo::kMaxFramerateFraction / 2); + expected_fps_allocation[1].push_back(EncoderInfo::kMaxFramerateFraction); + expected_fps_allocation[2].push_back(EncoderInfo::kMaxFramerateFraction); + + // All encoders have internal source, simulcast adapter reports true. + for (size_t i = 0; i < codec_.numberOfSimulcastStreams; ++i) { + MockVideoEncoder* encoder = helper_->factory()->encoders()[i]; + encoder->set_fps_allocation(expected_fps_allocation[i]); + } + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + EXPECT_THAT(adapter_->GetEncoderInfo().fps_allocation, + ::testing::ElementsAreArray(expected_fps_allocation)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, SetRateDistributesBandwithAllocation) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + const DataRate target_bitrate = + DataRate::KilobitsPerSec(codec_.simulcastStream[0].targetBitrate + + codec_.simulcastStream[1].targetBitrate + + codec_.simulcastStream[2].minBitrate); + const DataRate bandwidth_allocation = + target_bitrate + DataRate::KilobitsPerSec(600); + + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + + // Set bitrates so that we send all layers. + adapter_->SetRates(VideoEncoder::RateControlParameters( + rate_allocator_->Allocate( + VideoBitrateAllocationParameters(target_bitrate.bps(), 30)), + 30.0, bandwidth_allocation)); + + std::vector<MockVideoEncoder*> encoders = helper_->factory()->encoders(); + + ASSERT_EQ(3u, encoders.size()); + + for (size_t i = 0; i < 3; ++i) { + const uint32_t layer_bitrate_bps = + (i < static_cast<size_t>(codec_.numberOfSimulcastStreams) - 1 + ? codec_.simulcastStream[i].targetBitrate + : codec_.simulcastStream[i].minBitrate) * + 1000; + EXPECT_EQ(layer_bitrate_bps, + encoders[i]->last_set_rates().bitrate.get_sum_bps()) + << i; + EXPECT_EQ( + (layer_bitrate_bps * bandwidth_allocation.bps()) / target_bitrate.bps(), + encoders[i]->last_set_rates().bandwidth_allocation.bps()) + << i; + } +} + +TEST_F(TestSimulcastEncoderAdapterFake, CanSetZeroBitrateWithHeadroom) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + + rate_allocator_.reset(new SimulcastRateAllocator(codec_)); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->RegisterEncodeCompleteCallback(this); + + // Set allocated bitrate to 0, but keep (network) bandwidth allocation. + VideoEncoder::RateControlParameters rate_params; + rate_params.framerate_fps = 30; + rate_params.bandwidth_allocation = DataRate::KilobitsPerSec(600); + + adapter_->SetRates(rate_params); + + std::vector<MockVideoEncoder*> encoders = helper_->factory()->encoders(); + + ASSERT_EQ(3u, encoders.size()); + for (size_t i = 0; i < 3; ++i) { + EXPECT_EQ(0u, encoders[i]->last_set_rates().bitrate.get_sum_bps()); + } +} + +TEST_F(TestSimulcastEncoderAdapterFake, SupportsSimulcast) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + + // Indicate that mock encoders internally support simulcast. + helper_->factory()->set_supports_simulcast(true); + adapter_->RegisterEncodeCompleteCallback(this); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + + // Only one encoder should have been produced. + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + + rtc::scoped_refptr<VideoFrameBuffer> buffer(I420Buffer::Create(1280, 720)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + EXPECT_CALL(*helper_->factory()->encoders()[0], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + std::vector<VideoFrameType> frame_types(3, VideoFrameType::kVideoFrameKey); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, PassesSdpVideoFormatToEncoder) { + sdp_video_parameters_ = {{"test_param", "test_value"}}; + SetUp(); + SetupCodec(); + std::vector<MockVideoEncoder*> encoders = helper_->factory()->encoders(); + ASSERT_GT(encoders.size(), 0u); + EXPECT_EQ(encoders[0]->video_format(), + SdpVideoFormat("VP8", sdp_video_parameters_)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, SupportsFallback) { + // Enable support for fallback encoder factory and re-setup. + use_fallback_factory_ = true; + SetUp(); + + SetupCodec(); + + // Make sure we have bitrate for all layers. + DataRate max_bitrate = DataRate::Zero(); + for (int i = 0; i < 3; ++i) { + max_bitrate += + DataRate::KilobitsPerSec(codec_.simulcastStream[i].maxBitrate); + } + const auto rate_settings = VideoEncoder::RateControlParameters( + rate_allocator_->Allocate( + VideoBitrateAllocationParameters(max_bitrate.bps(), 30)), + 30.0, max_bitrate); + adapter_->SetRates(rate_settings); + + std::vector<MockVideoEncoder*> primary_encoders = + helper_->factory()->encoders(); + std::vector<MockVideoEncoder*> fallback_encoders = + helper_->fallback_factory()->encoders(); + + ASSERT_EQ(3u, primary_encoders.size()); + ASSERT_EQ(3u, fallback_encoders.size()); + + // Create frame to test with. + rtc::scoped_refptr<VideoFrameBuffer> buffer(I420Buffer::Create(1280, 720)); + VideoFrame input_frame = VideoFrame::Builder() + .set_video_frame_buffer(buffer) + .set_timestamp_rtp(100) + .set_timestamp_ms(1000) + .set_rotation(kVideoRotation_180) + .build(); + std::vector<VideoFrameType> frame_types(3, VideoFrameType::kVideoFrameKey); + + // All primary encoders used. + for (auto codec : primary_encoders) { + EXPECT_CALL(*codec, Encode).WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + } + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + + // Trigger fallback on first encoder. + primary_encoders[0]->set_init_encode_return_value( + WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(rate_settings); + EXPECT_CALL(*fallback_encoders[0], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*primary_encoders[1], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*primary_encoders[2], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + + // Trigger fallback on all encoder. + primary_encoders[1]->set_init_encode_return_value( + WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE); + primary_encoders[2]->set_init_encode_return_value( + WEBRTC_VIDEO_CODEC_FALLBACK_SOFTWARE); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(rate_settings); + EXPECT_CALL(*fallback_encoders[0], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*fallback_encoders[1], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_CALL(*fallback_encoders[2], Encode) + .WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); + + // Return to primary encoders on all streams. + for (int i = 0; i < 3; ++i) { + primary_encoders[i]->set_init_encode_return_value(WEBRTC_VIDEO_CODEC_OK); + } + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + adapter_->SetRates(rate_settings); + for (auto codec : primary_encoders) { + EXPECT_CALL(*codec, Encode).WillOnce(Return(WEBRTC_VIDEO_CODEC_OK)); + } + EXPECT_EQ(0, adapter_->Encode(input_frame, &frame_types)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, SupportsPerSimulcastLayerMaxFramerate) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + codec_.simulcastStream[0].maxFramerate = 60; + codec_.simulcastStream[1].maxFramerate = 30; + codec_.simulcastStream[2].maxFramerate = 10; + + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + EXPECT_EQ(60u, helper_->factory()->encoders()[0]->codec().maxFramerate); + EXPECT_EQ(30u, helper_->factory()->encoders()[1]->codec().maxFramerate); + EXPECT_EQ(10u, helper_->factory()->encoders()[2]->codec().maxFramerate); +} + +TEST_F(TestSimulcastEncoderAdapterFake, CreatesEncoderOnlyIfStreamIsActive) { + // Legacy singlecast + SetupCodec(/*active_streams=*/{}); + EXPECT_EQ(1u, helper_->factory()->encoders().size()); + + // Simulcast-capable underlaying encoder + ReSetUp(); + helper_->factory()->set_supports_simulcast(true); + SetupCodec(/*active_streams=*/{true, true}); + EXPECT_EQ(1u, helper_->factory()->encoders().size()); + + // Muti-encoder simulcast + ReSetUp(); + helper_->factory()->set_supports_simulcast(false); + SetupCodec(/*active_streams=*/{true, true}); + EXPECT_EQ(2u, helper_->factory()->encoders().size()); + + // Singlecast via layers deactivation. Lowest layer is active. + ReSetUp(); + helper_->factory()->set_supports_simulcast(false); + SetupCodec(/*active_streams=*/{true, false}); + EXPECT_EQ(1u, helper_->factory()->encoders().size()); + + // Singlecast via layers deactivation. Highest layer is active. + ReSetUp(); + helper_->factory()->set_supports_simulcast(false); + SetupCodec(/*active_streams=*/{false, true}); + EXPECT_EQ(1u, helper_->factory()->encoders().size()); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + RecreateEncoderIfPreferTemporalSupportIsEnabled) { + // Normally SEA reuses encoders. But, when TL-based SW fallback is enabled, + // the encoder which served the lowest stream should be recreated before it + // can be used to process an upper layer and vice-versa. + test::ScopedKeyValueConfig field_trials( + field_trials_, "WebRTC-Video-PreferTemporalSupportOnBaseLayer/Enabled/"); + use_fallback_factory_ = true; + ReSetUp(); + + // Legacy singlecast + SetupCodec(/*active_streams=*/{}); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + + // Singlecast, the lowest stream is active. Encoder should be reused. + MockVideoEncoder* prev_encoder = helper_->factory()->encoders()[0]; + SetupCodec(/*active_streams=*/{true, false}); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + EXPECT_EQ(helper_->factory()->encoders()[0], prev_encoder); + + // Singlecast, an upper stream is active. Encoder should be recreated. + EXPECT_CALL(*prev_encoder, Release()).Times(1); + SetupCodec(/*active_streams=*/{false, true}); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + EXPECT_NE(helper_->factory()->encoders()[0], prev_encoder); + + // Singlecast, the lowest stream is active. Encoder should be recreated. + prev_encoder = helper_->factory()->encoders()[0]; + EXPECT_CALL(*prev_encoder, Release()).Times(1); + SetupCodec(/*active_streams=*/{true, false}); + ASSERT_EQ(1u, helper_->factory()->encoders().size()); + EXPECT_NE(helper_->factory()->encoders()[0], prev_encoder); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + UseFallbackEncoderIfCreatePrimaryEncoderFailed) { + // Enable support for fallback encoder factory and re-setup. + use_fallback_factory_ = true; + SetUp(); + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 1; + helper_->factory()->SetEncoderNames({"primary"}); + helper_->fallback_factory()->SetEncoderNames({"fallback"}); + + // Emulate failure at creating of primary encoder and verify that SEA switches + // to fallback encoder. + helper_->factory()->set_create_video_encode_return_nullptr(true); + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(0u, helper_->factory()->encoders().size()); + ASSERT_EQ(1u, helper_->fallback_factory()->encoders().size()); + EXPECT_EQ("fallback", adapter_->GetEncoderInfo().implementation_name); +} + +TEST_F(TestSimulcastEncoderAdapterFake, + InitEncodeReturnsErrorIfEncoderCannotBeCreated) { + // Enable support for fallback encoder factory and re-setup. + use_fallback_factory_ = true; + SetUp(); + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 1; + helper_->factory()->SetEncoderNames({"primary"}); + helper_->fallback_factory()->SetEncoderNames({"fallback"}); + + // Emulate failure at creating of primary and fallback encoders and verify + // that `InitEncode` returns an error. + helper_->factory()->set_create_video_encode_return_nullptr(true); + helper_->fallback_factory()->set_create_video_encode_return_nullptr(true); + EXPECT_EQ(WEBRTC_VIDEO_CODEC_MEMORY, + adapter_->InitEncode(&codec_, kSettings)); +} + +TEST_F(TestSimulcastEncoderAdapterFake, PopulatesScalabilityModeOfSubcodecs) { + SimulcastTestFixtureImpl::DefaultSettings( + &codec_, static_cast<const int*>(kTestTemporalLayerProfile), + kVideoCodecVP8); + codec_.numberOfSimulcastStreams = 3; + codec_.simulcastStream[0].numberOfTemporalLayers = 1; + codec_.simulcastStream[1].numberOfTemporalLayers = 2; + codec_.simulcastStream[2].numberOfTemporalLayers = 3; + + EXPECT_EQ(0, adapter_->InitEncode(&codec_, kSettings)); + ASSERT_EQ(3u, helper_->factory()->encoders().size()); + EXPECT_EQ(helper_->factory()->encoders()[0]->codec().GetScalabilityMode(), + ScalabilityMode::kL1T1); + EXPECT_EQ(helper_->factory()->encoders()[1]->codec().GetScalabilityMode(), + ScalabilityMode::kL1T2); + EXPECT_EQ(helper_->factory()->encoders()[2]->codec().GetScalabilityMode(), + ScalabilityMode::kL1T3); +} + +} // namespace test +} // namespace webrtc |