From 56db9ff1e1565fa8584be338ea30741d33dfa2e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Bostr=C3=B6m?= Date: Wed, 24 Mar 2021 09:06:45 +0100 Subject: [PATCH] VideoStreamEncoder: Don't map kNative video frame buffers. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up CL to VP8 and VP9 encoders taking care of mapping. Context again: This CL is part of Optimized Scaling efforts. In Chromium, the native frame buffer is getting an optimized CropAndScale() implementation. To support HW accelerated scaling, returning pre-scaled images and skipping unnecessary intermediate downscales, WebRTC needs to 1) use CropAndScale instead of libyuv::XXXXScale and 2) only map buffers it actually intends to encode. In this CL, VideoStreamEncoder no longer calls GetMappedFrameBuffer() on behalf of the encoders, since the encoders are now able to either do the mapping or performs ToI420() anyway. - Tests for old VSE behaviors are updated to test the new behavior (i.e. that native frames are pretty much always forwarded). - The "having to call ToI420() twice" workaround to Android bug https://crbug.com/webrtc/12602 is added to H264 and AV1 encoders. Bug: webrtc:12469 Change-Id: Ibdc2e138d4782a140f433c8330950e61b9829f43 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/211940 Commit-Queue: Henrik Boström Reviewed-by: Ilya Nikolaevskiy Reviewed-by: Evan Shrubsole Cr-Commit-Position: refs/heads/master@{#33548} --- .../codecs/av1/libaom_av1_encoder.cc | 15 +- .../codecs/h264/h264_encoder_impl.cc | 13 +- video/BUILD.gn | 2 + video/video_stream_encoder.cc | 59 +--- video/video_stream_encoder_unittest.cc | 255 ++++++++++++++++-- 5 files changed, 280 insertions(+), 64 deletions(-) diff --git a/modules/video_coding/codecs/av1/libaom_av1_encoder.cc b/modules/video_coding/codecs/av1/libaom_av1_encoder.cc index cd52906d6c..ddb52cb3dc 100644 --- a/modules/video_coding/codecs/av1/libaom_av1_encoder.cc +++ b/modules/video_coding/codecs/av1/libaom_av1_encoder.cc @@ -578,9 +578,22 @@ int32_t LibaomAv1Encoder::Encode( // Convert input frame to I420, if needed. VideoFrame prepped_input_frame = frame; if (prepped_input_frame.video_frame_buffer()->type() != - VideoFrameBuffer::Type::kI420) { + VideoFrameBuffer::Type::kI420 && + prepped_input_frame.video_frame_buffer()->type() != + VideoFrameBuffer::Type::kI420A) { rtc::scoped_refptr converted_buffer( prepped_input_frame.video_frame_buffer()->ToI420()); + // The buffer should now be a mapped I420 or I420A format, but some buffer + // implementations incorrectly return the wrong buffer format, such as + // kNative. As a workaround to this, we perform ToI420() a second time. + // TODO(https://crbug.com/webrtc/12602): When Android buffers have a correct + // ToI420() implementaion, remove his workaround. + if (converted_buffer->type() != VideoFrameBuffer::Type::kI420 && + converted_buffer->type() != VideoFrameBuffer::Type::kI420A) { + converted_buffer = converted_buffer->ToI420(); + RTC_CHECK(converted_buffer->type() == VideoFrameBuffer::Type::kI420 || + converted_buffer->type() == VideoFrameBuffer::Type::kI420A); + } prepped_input_frame = VideoFrame(converted_buffer, frame.timestamp(), frame.render_time_ms(), frame.rotation()); } diff --git a/modules/video_coding/codecs/h264/h264_encoder_impl.cc b/modules/video_coding/codecs/h264/h264_encoder_impl.cc index 3f4f660ffa..949c51bafa 100644 --- a/modules/video_coding/codecs/h264/h264_encoder_impl.cc +++ b/modules/video_coding/codecs/h264/h264_encoder_impl.cc @@ -373,8 +373,19 @@ int32_t H264EncoderImpl::Encode( return WEBRTC_VIDEO_CODEC_UNINITIALIZED; } - rtc::scoped_refptr frame_buffer = + rtc::scoped_refptr frame_buffer = input_frame.video_frame_buffer()->ToI420(); + // The buffer should now be a mapped I420 or I420A format, but some buffer + // implementations incorrectly return the wrong buffer format, such as + // kNative. As a workaround to this, we perform ToI420() a second time. + // TODO(https://crbug.com/webrtc/12602): When Android buffers have a correct + // ToI420() implementaion, remove his workaround. + if (frame_buffer->type() != VideoFrameBuffer::Type::kI420 && + frame_buffer->type() != VideoFrameBuffer::Type::kI420A) { + frame_buffer = frame_buffer->ToI420(); + RTC_CHECK(frame_buffer->type() == VideoFrameBuffer::Type::kI420 || + frame_buffer->type() == VideoFrameBuffer::Type::kI420A); + } bool send_key_frame = false; for (size_t i = 0; i < configurations_.size(); ++i) { diff --git a/video/BUILD.gn b/video/BUILD.gn index acf1ba2445..e4c1986ffc 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -643,6 +643,7 @@ if (rtc_include_tests) { "../api:libjingle_peerconnection_api", "../api:mock_fec_controller_override", "../api:mock_frame_decryptor", + "../api:mock_video_codec_factory", "../api:mock_video_encoder", "../api:rtp_headers", "../api:rtp_parameters", @@ -702,6 +703,7 @@ if (rtc_include_tests) { "../modules/video_coding:webrtc_multiplex", "../modules/video_coding:webrtc_vp8", "../modules/video_coding:webrtc_vp9", + "../modules/video_coding/codecs/av1:libaom_av1_encoder", "../rtc_base", "../rtc_base:checks", "../rtc_base:gunit_helpers", diff --git a/video/video_stream_encoder.cc b/video/video_stream_encoder.cc index 610b06c4f6..c5a3b98756 100644 --- a/video/video_stream_encoder.cc +++ b/video/video_stream_encoder.cc @@ -1643,45 +1643,12 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame, last_encode_info_ms_ = clock_->TimeInMilliseconds(); VideoFrame out_frame(video_frame); - if (out_frame.video_frame_buffer()->type() == - VideoFrameBuffer::Type::kNative && - !info.supports_native_handle) { - // This module only supports software encoding. - rtc::scoped_refptr buffer = - out_frame.video_frame_buffer()->GetMappedFrameBuffer( - info.preferred_pixel_formats); - bool buffer_was_converted = false; - if (!buffer) { - buffer = out_frame.video_frame_buffer()->ToI420(); - // TODO(https://crbug.com/webrtc/12021): Once GetI420 is pure virtual, - // this just true as an I420 buffer would return from - // GetMappedFrameBuffer. - buffer_was_converted = - (out_frame.video_frame_buffer()->GetI420() == nullptr); - } - if (!buffer) { - RTC_LOG(LS_ERROR) << "Frame conversion failed, dropping frame."; - return; - } - - VideoFrame::UpdateRect update_rect = out_frame.update_rect(); - if (!update_rect.IsEmpty() && - out_frame.video_frame_buffer()->GetI420() == nullptr) { - // UpdatedRect is reset to full update if it's not empty, and buffer was - // converted, therefore we can't guarantee that pixels outside of - // UpdateRect didn't change comparing to the previous frame. - update_rect = - VideoFrame::UpdateRect{0, 0, out_frame.width(), out_frame.height()}; - } - out_frame.set_video_frame_buffer(buffer); - out_frame.set_update_rect(update_rect); - } - - // Crop frame if needed. + // Crop or scale the frame if needed. Dimension may be reduced to fit encoder + // requirements, e.g. some encoders may require them to be divisible by 4. if ((crop_width_ > 0 || crop_height_ > 0) && - out_frame.video_frame_buffer()->type() != - VideoFrameBuffer::Type::kNative) { - // If the frame can't be converted to I420, drop it. + (out_frame.video_frame_buffer()->type() != + VideoFrameBuffer::Type::kNative || + !info.supports_native_handle)) { int cropped_width = video_frame.width() - crop_width_; int cropped_height = video_frame.height() - crop_height_; rtc::scoped_refptr cropped_buffer; @@ -1689,6 +1656,7 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame, // happen after SinkWants signaled correctly from ReconfigureEncoder. VideoFrame::UpdateRect update_rect = video_frame.update_rect(); if (crop_width_ < 4 && crop_height_ < 4) { + // The difference is small, crop without scaling. cropped_buffer = video_frame.video_frame_buffer()->CropAndScale( crop_width_ / 2, crop_height_ / 2, cropped_width, cropped_height, cropped_width, cropped_height); @@ -1698,6 +1666,7 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame, VideoFrame::UpdateRect{0, 0, cropped_width, cropped_height}); } else { + // The difference is large, scale it. cropped_buffer = video_frame.video_frame_buffer()->Scale(cropped_width, cropped_height); if (!update_rect.IsEmpty()) { @@ -1742,14 +1711,12 @@ void VideoStreamEncoder::EncodeVideoFrame(const VideoFrame& video_frame, stream_resource_manager_.OnEncodeStarted(out_frame, time_when_posted_us); - RTC_DCHECK_LE(send_codec_.width, out_frame.width()); - RTC_DCHECK_LE(send_codec_.height, out_frame.height()); - // Native frames should be scaled by the client. - // For internal encoders we scale everything in one place here. - RTC_DCHECK((out_frame.video_frame_buffer()->type() == - VideoFrameBuffer::Type::kNative) || - (send_codec_.width == out_frame.width() && - send_codec_.height == out_frame.height())); + // The encoder should get the size that it expects. + RTC_DCHECK(send_codec_.width <= out_frame.width() && + send_codec_.height <= out_frame.height()) + << "Encoder configured to " << send_codec_.width << "x" + << send_codec_.height << " received a too small frame " + << out_frame.width() << "x" << out_frame.height(); TRACE_EVENT1("webrtc", "VCMGenericEncoder::Encode", "timestamp", out_frame.timestamp()); diff --git a/video/video_stream_encoder_unittest.cc b/video/video_stream_encoder_unittest.cc index e665d809e3..fcfa6778ae 100644 --- a/video/video_stream_encoder_unittest.cc +++ b/video/video_stream_encoder_unittest.cc @@ -13,17 +13,20 @@ #include #include #include +#include #include #include "absl/memory/memory.h" #include "api/task_queue/default_task_queue_factory.h" #include "api/test/mock_fec_controller_override.h" #include "api/test/mock_video_encoder.h" +#include "api/test/mock_video_encoder_factory.h" #include "api/video/builtin_video_bitrate_allocator_factory.h" #include "api/video/i420_buffer.h" #include "api/video/nv12_buffer.h" #include "api/video/video_adaptation_reason.h" #include "api/video/video_bitrate_allocation.h" +#include "api/video_codecs/sdp_video_format.h" #include "api/video_codecs/video_encoder.h" #include "api/video_codecs/vp8_temporal_layers.h" #include "api/video_codecs/vp8_temporal_layers_factory.h" @@ -33,6 +36,11 @@ #include "common_video/include/video_frame_buffer.h" #include "media/base/video_adapter.h" #include "media/engine/webrtc_video_engine.h" +#include "modules/video_coding/codecs/av1/libaom_av1_encoder.h" +#include "modules/video_coding/codecs/h264/include/h264.h" +#include "modules/video_coding/codecs/multiplex/include/multiplex_encoder_adapter.h" +#include "modules/video_coding/codecs/vp8/include/vp8.h" +#include "modules/video_coding/codecs/vp9/include/vp9.h" #include "modules/video_coding/codecs/vp9/include/vp9_globals.h" #include "modules/video_coding/utility/quality_scaler.h" #include "modules/video_coding/utility/simulcast_rate_allocator.h" @@ -50,6 +58,7 @@ #include "test/frame_forwarder.h" #include "test/gmock.h" #include "test/gtest.h" +#include "test/mappable_native_buffer.h" #include "test/time_controller/simulated_time_controller.h" #include "test/video_encoder_proxy_factory.h" #include "video/send_statistics_proxy.h" @@ -116,7 +125,8 @@ class TestBuffer : public webrtc::I420Buffer { rtc::Event* const event_; }; -// A fake native buffer that can't be converted to I420. +// A fake native buffer that can't be converted to I420. Upon scaling, it +// produces another FakeNativeBuffer. class FakeNativeBuffer : public webrtc::VideoFrameBuffer { public: FakeNativeBuffer(rtc::Event* event, int width, int height) @@ -127,6 +137,16 @@ class FakeNativeBuffer : public webrtc::VideoFrameBuffer { rtc::scoped_refptr ToI420() override { return nullptr; } + rtc::scoped_refptr CropAndScale( + int offset_x, + int offset_y, + int crop_width, + int crop_height, + int scaled_width, + int scaled_height) override { + return new rtc::RefCountedObject(nullptr, scaled_width, + scaled_height); + } private: friend class rtc::RefCountedObject; @@ -989,6 +1009,16 @@ class VideoStreamEncoderTest : public ::testing::Test { return settings; } + int GetLastInputWidth() const { + MutexLock lock(&local_mutex_); + return last_input_width_; + } + + int GetLastInputHeight() const { + MutexLock lock(&local_mutex_); + return last_input_height_; + } + absl::optional GetLastInputPixelFormat() { MutexLock lock(&local_mutex_); return last_input_pixel_format_; @@ -1555,7 +1585,7 @@ TEST_F(VideoStreamEncoderBlockedTest, DropsPendingFramesOnSlowEncode) { EXPECT_EQ(1, dropped_count); } -TEST_F(VideoStreamEncoderTest, DropFrameWithFailedI420Conversion) { +TEST_F(VideoStreamEncoderTest, NativeFrameWithoutI420SupportGetsDelivered) { video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( DataRate::BitsPerSec(kTargetBitrateBps), DataRate::BitsPerSec(kTargetBitrateBps), @@ -1564,12 +1594,18 @@ TEST_F(VideoStreamEncoderTest, DropFrameWithFailedI420Conversion) { rtc::Event frame_destroyed_event; video_source_.IncomingCapturedFrame( CreateFakeNativeFrame(1, &frame_destroyed_event)); - ExpectDroppedFrame(); - EXPECT_TRUE(frame_destroyed_event.Wait(kDefaultTimeoutMs)); + WaitForEncodedFrame(1); + EXPECT_EQ(VideoFrameBuffer::Type::kNative, + fake_encoder_.GetLastInputPixelFormat()); + EXPECT_EQ(fake_encoder_.codec_config().width, + fake_encoder_.GetLastInputWidth()); + EXPECT_EQ(fake_encoder_.codec_config().height, + fake_encoder_.GetLastInputHeight()); video_stream_encoder_->Stop(); } -TEST_F(VideoStreamEncoderTest, DropFrameWithFailedI420ConversionWithCrop) { +TEST_F(VideoStreamEncoderTest, + NativeFrameWithoutI420SupportGetsCroppedIfNecessary) { // Use the cropping factory. video_encoder_config_.video_stream_factory = new rtc::RefCountedObject(); @@ -1594,8 +1630,13 @@ TEST_F(VideoStreamEncoderTest, DropFrameWithFailedI420ConversionWithCrop) { rtc::Event frame_destroyed_event; video_source_.IncomingCapturedFrame(CreateFakeNativeFrame( 2, &frame_destroyed_event, codec_width_ + 1, codec_height_ + 1)); - ExpectDroppedFrame(); - EXPECT_TRUE(frame_destroyed_event.Wait(kDefaultTimeoutMs)); + WaitForEncodedFrame(2); + EXPECT_EQ(VideoFrameBuffer::Type::kNative, + fake_encoder_.GetLastInputPixelFormat()); + EXPECT_EQ(fake_encoder_.codec_config().width, + fake_encoder_.GetLastInputWidth()); + EXPECT_EQ(fake_encoder_.codec_config().height, + fake_encoder_.GetLastInputHeight()); video_stream_encoder_->Stop(); } @@ -1613,8 +1654,7 @@ TEST_F(VideoStreamEncoderTest, NonI420FramesShouldNotBeConvertedToI420) { video_stream_encoder_->Stop(); } -TEST_F(VideoStreamEncoderTest, - NativeFrameIsConvertedToI420IfNoFrameTypePreference) { +TEST_F(VideoStreamEncoderTest, NativeFrameGetsDelivered_NoFrameTypePreference) { video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( DataRate::BitsPerSec(kTargetBitrateBps), DataRate::BitsPerSec(kTargetBitrateBps), @@ -1626,12 +1666,13 @@ TEST_F(VideoStreamEncoderTest, video_source_.IncomingCapturedFrame(CreateFakeNV12NativeFrame( 1, &frame_destroyed_event, codec_width_, codec_height_)); WaitForEncodedFrame(1); - EXPECT_EQ(VideoFrameBuffer::Type::kI420, + EXPECT_EQ(VideoFrameBuffer::Type::kNative, fake_encoder_.GetLastInputPixelFormat()); video_stream_encoder_->Stop(); } -TEST_F(VideoStreamEncoderTest, NativeFrameMappedToPreferredPixelFormat) { +TEST_F(VideoStreamEncoderTest, + NativeFrameGetsDelivered_PixelFormatPreferenceMatches) { video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( DataRate::BitsPerSec(kTargetBitrateBps), DataRate::BitsPerSec(kTargetBitrateBps), @@ -1643,12 +1684,12 @@ TEST_F(VideoStreamEncoderTest, NativeFrameMappedToPreferredPixelFormat) { video_source_.IncomingCapturedFrame(CreateFakeNV12NativeFrame( 1, &frame_destroyed_event, codec_width_, codec_height_)); WaitForEncodedFrame(1); - EXPECT_EQ(VideoFrameBuffer::Type::kNV12, + EXPECT_EQ(VideoFrameBuffer::Type::kNative, fake_encoder_.GetLastInputPixelFormat()); video_stream_encoder_->Stop(); } -TEST_F(VideoStreamEncoderTest, NativeFrameConvertedToI420IfMappingNotFeasible) { +TEST_F(VideoStreamEncoderTest, NativeFrameGetsDelivered_MappingIsNotFeasible) { video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( DataRate::BitsPerSec(kTargetBitrateBps), DataRate::BitsPerSec(kTargetBitrateBps), @@ -1661,12 +1702,12 @@ TEST_F(VideoStreamEncoderTest, NativeFrameConvertedToI420IfMappingNotFeasible) { video_source_.IncomingCapturedFrame(CreateFakeNV12NativeFrame( 1, &frame_destroyed_event, codec_width_, codec_height_)); WaitForEncodedFrame(1); - EXPECT_EQ(VideoFrameBuffer::Type::kI420, + EXPECT_EQ(VideoFrameBuffer::Type::kNative, fake_encoder_.GetLastInputPixelFormat()); video_stream_encoder_->Stop(); } -TEST_F(VideoStreamEncoderTest, NativeFrameBackedByNV12FrameIsEncodedFromI420) { +TEST_F(VideoStreamEncoderTest, NativeFrameGetsDelivered_BackedByNV12) { video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( DataRate::BitsPerSec(kTargetBitrateBps), DataRate::BitsPerSec(kTargetBitrateBps), @@ -1676,7 +1717,7 @@ TEST_F(VideoStreamEncoderTest, NativeFrameBackedByNV12FrameIsEncodedFromI420) { video_source_.IncomingCapturedFrame(CreateFakeNV12NativeFrame( 1, &frame_destroyed_event, codec_width_, codec_height_)); WaitForEncodedFrame(1); - EXPECT_EQ(VideoFrameBuffer::Type::kI420, + EXPECT_EQ(VideoFrameBuffer::Type::kNative, fake_encoder_.GetLastInputPixelFormat()); video_stream_encoder_->Stop(); } @@ -7934,4 +7975,186 @@ TEST_F(VideoStreamEncoderTest, QualityScalingAllowed_QualityScalingEnabled) { } #endif +// Test parameters: (VideoCodecType codec, bool allow_i420_conversion) +class VideoStreamEncoderWithRealEncoderTest + : public VideoStreamEncoderTest, + public ::testing::WithParamInterface> { + public: + VideoStreamEncoderWithRealEncoderTest() + : VideoStreamEncoderTest(), + codec_type_(std::get<0>(GetParam())), + allow_i420_conversion_(std::get<1>(GetParam())) {} + + void SetUp() override { + VideoStreamEncoderTest::SetUp(); + std::unique_ptr encoder; + switch (codec_type_) { + case kVideoCodecVP8: + encoder = VP8Encoder::Create(); + break; + case kVideoCodecVP9: + encoder = VP9Encoder::Create(); + break; + case kVideoCodecAV1: + encoder = CreateLibaomAv1Encoder(); + break; + case kVideoCodecH264: + encoder = + H264Encoder::Create(cricket::VideoCodec(cricket::kH264CodecName)); + break; + case kVideoCodecMultiplex: + mock_encoder_factory_for_multiplex_ = + std::make_unique(); + EXPECT_CALL(*mock_encoder_factory_for_multiplex_, Die); + EXPECT_CALL(*mock_encoder_factory_for_multiplex_, CreateVideoEncoder) + .WillRepeatedly([] { return VP8Encoder::Create(); }); + encoder = std::make_unique( + mock_encoder_factory_for_multiplex_.get(), SdpVideoFormat("VP8"), + false); + break; + default: + RTC_NOTREACHED(); + } + ConfigureEncoderAndBitrate(codec_type_, std::move(encoder)); + } + + void TearDown() override { + video_stream_encoder_->Stop(); + // Ensure |video_stream_encoder_| is destroyed before + // |encoder_proxy_factory_|. + video_stream_encoder_.reset(); + VideoStreamEncoderTest::TearDown(); + } + + protected: + void ConfigureEncoderAndBitrate(VideoCodecType codec_type, + std::unique_ptr encoder) { + // Configure VSE to use the encoder. + encoder_ = std::move(encoder); + encoder_proxy_factory_ = std::make_unique( + encoder_.get(), &encoder_selector_); + video_send_config_.encoder_settings.encoder_factory = + encoder_proxy_factory_.get(); + VideoEncoderConfig video_encoder_config; + test::FillEncoderConfiguration(codec_type, 1, &video_encoder_config); + video_encoder_config_ = video_encoder_config.Copy(); + ConfigureEncoder(video_encoder_config_.Copy()); + + // Set bitrate to ensure frame is not dropped. + video_stream_encoder_->OnBitrateUpdatedAndWaitForManagedResources( + DataRate::BitsPerSec(kTargetBitrateBps), + DataRate::BitsPerSec(kTargetBitrateBps), + DataRate::BitsPerSec(kTargetBitrateBps), 0, 0, 0); + } + + const VideoCodecType codec_type_; + const bool allow_i420_conversion_; + NiceMock encoder_selector_; + std::unique_ptr encoder_proxy_factory_; + std::unique_ptr encoder_; + std::unique_ptr mock_encoder_factory_for_multiplex_; +}; + +TEST_P(VideoStreamEncoderWithRealEncoderTest, EncoderMapsNativeI420) { + auto native_i420_frame = test::CreateMappableNativeFrame( + 1, VideoFrameBuffer::Type::kI420, codec_width_, codec_height_); + video_source_.IncomingCapturedFrame(native_i420_frame); + WaitForEncodedFrame(codec_width_, codec_height_); + + auto mappable_native_buffer = + test::GetMappableNativeBufferFromVideoFrame(native_i420_frame); + std::vector> mapped_frame_buffers = + mappable_native_buffer->GetMappedFramedBuffers(); + ASSERT_EQ(mapped_frame_buffers.size(), 1u); + EXPECT_EQ(mapped_frame_buffers[0]->width(), codec_width_); + EXPECT_EQ(mapped_frame_buffers[0]->height(), codec_height_); + EXPECT_EQ(mapped_frame_buffers[0]->type(), VideoFrameBuffer::Type::kI420); +} + +TEST_P(VideoStreamEncoderWithRealEncoderTest, EncoderMapsNativeNV12) { + auto native_nv12_frame = test::CreateMappableNativeFrame( + 1, VideoFrameBuffer::Type::kNV12, codec_width_, codec_height_); + video_source_.IncomingCapturedFrame(native_nv12_frame); + WaitForEncodedFrame(codec_width_, codec_height_); + + auto mappable_native_buffer = + test::GetMappableNativeBufferFromVideoFrame(native_nv12_frame); + std::vector> mapped_frame_buffers = + mappable_native_buffer->GetMappedFramedBuffers(); + ASSERT_EQ(mapped_frame_buffers.size(), 1u); + EXPECT_EQ(mapped_frame_buffers[0]->width(), codec_width_); + EXPECT_EQ(mapped_frame_buffers[0]->height(), codec_height_); + EXPECT_EQ(mapped_frame_buffers[0]->type(), VideoFrameBuffer::Type::kNV12); + + if (!allow_i420_conversion_) { + EXPECT_FALSE(mappable_native_buffer->DidConvertToI420()); + } +} + +std::string TestParametersVideoCodecAndAllowI420ConversionToString( + testing::TestParamInfo> info) { + VideoCodecType codec_type = std::get<0>(info.param); + bool allow_i420_conversion = std::get<1>(info.param); + std::string str; + switch (codec_type) { + case kVideoCodecGeneric: + str = "Generic"; + break; + case kVideoCodecVP8: + str = "VP8"; + break; + case kVideoCodecVP9: + str = "VP9"; + break; + case kVideoCodecAV1: + str = "AV1"; + break; + case kVideoCodecH264: + str = "H264"; + break; + case kVideoCodecMultiplex: + str = "Multiplex"; + break; + default: + RTC_NOTREACHED(); + } + str += allow_i420_conversion ? "_AllowToI420" : "_DisallowToI420"; + return str; +} + +constexpr std::pair kVP8DisallowConversion = + std::make_pair(kVideoCodecVP8, /*allow_i420_conversion=*/false); +constexpr std::pair kVP9DisallowConversion = + std::make_pair(kVideoCodecVP9, /*allow_i420_conversion=*/false); +constexpr std::pair kAV1AllowConversion = + std::make_pair(kVideoCodecAV1, /*allow_i420_conversion=*/true); +constexpr std::pair kMultiplexDisallowConversion = + std::make_pair(kVideoCodecMultiplex, /*allow_i420_conversion=*/false); +#if defined(WEBRTC_USE_H264) +constexpr std::pair kH264AllowConversion = + std::make_pair(kVideoCodecH264, /*allow_i420_conversion=*/true); + +// The windows compiler does not tolerate #if statements inside the +// INSTANTIATE_TEST_SUITE_P() macro, so we have to have two definitions (with +// and without H264). +INSTANTIATE_TEST_SUITE_P( + All, + VideoStreamEncoderWithRealEncoderTest, + ::testing::Values(kVP8DisallowConversion, + kVP9DisallowConversion, + kAV1AllowConversion, + kMultiplexDisallowConversion, + kH264AllowConversion), + TestParametersVideoCodecAndAllowI420ConversionToString); +#else +INSTANTIATE_TEST_SUITE_P( + All, + VideoStreamEncoderWithRealEncoderTest, + ::testing::Values(kVP8DisallowConversion, + kVP9DisallowConversion, + kAV1AllowConversion, + kMultiplexDisallowConversion), + TestParametersVideoCodecAndAllowI420ConversionToString); +#endif + } // namespace webrtc