| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/video/video_encode_accelerator_adapter.h" |
| |
| #include <memory> |
| #include <string> |
| |
| #include "base/functional/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/task_environment.h" |
| #include "base/threading/thread.h" |
| #include "base/time/time.h" |
| #include "build/build_config.h" |
| #include "media/base/bitrate.h" |
| #include "media/base/media_util.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_util.h" |
| #include "media/video/fake_video_encode_accelerator.h" |
| #include "media/video/gpu_video_accelerator_factories.h" |
| #include "media/video/mock_gpu_video_accelerator_factories.h" |
| #include "media/video/mock_video_encode_accelerator.h" |
| #include "media/video/video_encoder_info.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/libyuv/include/libyuv.h" |
| #include "ui/gfx/color_space.h" |
| |
| using ::testing::_; |
| using ::testing::AtLeast; |
| using ::testing::Field; |
| using ::testing::Invoke; |
| using ::testing::Return; |
| using ::testing::SaveArg; |
| using ::testing::Values; |
| using ::testing::WithArgs; |
| |
| namespace media { |
| |
| class VideoEncodeAcceleratorAdapterTest |
| : public ::testing::TestWithParam<VideoPixelFormat> { |
| public: |
| VideoEncodeAcceleratorAdapterTest() = default; |
| |
| void SetUp() override { |
| vea_runner_ = base::ThreadPool::CreateSequencedTaskRunner({}); |
| |
| vea_ = new FakeVideoEncodeAccelerator(vea_runner_); |
| gpu_factories_ = |
| std::make_unique<MockGpuVideoAcceleratorFactories>(nullptr); |
| supported_profiles_ = { |
| VideoEncodeAccelerator::SupportedProfile( |
| profile_, |
| /*max_resolution=*/gfx::Size(3840, 2160), |
| /*max_framerate_numerator=*/30, |
| /*max_framerate_denominator=*/1, |
| /*rc_modes=*/VideoEncodeAccelerator::kConstantMode | |
| VideoEncodeAccelerator::kVariableMode), |
| }; |
| |
| EXPECT_CALL(*gpu_factories_.get(), |
| GetVideoEncodeAcceleratorSupportedProfiles()) |
| .WillRepeatedly(Return(supported_profiles_)); |
| EXPECT_CALL(*gpu_factories_.get(), DoCreateVideoEncodeAccelerator()) |
| .WillRepeatedly(Return(vea_.get())); |
| EXPECT_CALL(*gpu_factories_.get(), GetTaskRunner()) |
| .WillRepeatedly(Return(vea_runner_)); |
| |
| auto media_log = std::make_unique<NullMediaLog>(); |
| callback_runner_ = base::SequencedTaskRunner::GetCurrentDefault(); |
| vae_adapter_ = std::make_unique<VideoEncodeAcceleratorAdapter>( |
| gpu_factories_.get(), media_log->Clone(), callback_runner_); |
| } |
| |
| void TearDown() override { |
| vea_runner_->DeleteSoon(FROM_HERE, std::move(vae_adapter_)); |
| RunUntilIdle(); |
| } |
| |
| void RunUntilIdle() { task_environment_.RunUntilIdle(); } |
| VideoEncodeAcceleratorAdapter* adapter() { return vae_adapter_.get(); } |
| FakeVideoEncodeAccelerator* vea() { return vea_; } |
| |
| scoped_refptr<VideoFrame> CreateGreenGpuFrame(gfx::Size size, |
| base::TimeDelta timestamp) { |
| auto gmb = gpu_factories_->CreateGpuMemoryBuffer( |
| size, gfx::BufferFormat::YUV_420_BIPLANAR, |
| gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE); |
| |
| if (!gmb || !gmb->Map()) |
| return nullptr; |
| |
| // Green NV12 frame (Y:0x96, U:0x40, V:0x40) |
| const auto gmb_size = gmb->GetSize(); |
| memset(static_cast<uint8_t*>(gmb->memory(0)), 0x96, |
| gmb->stride(0) * gmb_size.height()); |
| memset(static_cast<uint8_t*>(gmb->memory(1)), 0x28, |
| gmb->stride(1) * gmb_size.height() / 2); |
| gmb->Unmap(); |
| |
| gpu::MailboxHolder empty_mailboxes[media::VideoFrame::kMaxPlanes]; |
| auto frame = VideoFrame::WrapExternalGpuMemoryBuffer( |
| gfx::Rect(gmb_size), size, std::move(gmb), empty_mailboxes, |
| base::NullCallback(), timestamp); |
| frame->set_color_space(kYUVColorSpace); |
| return frame; |
| } |
| |
| scoped_refptr<VideoFrame> CreateGreenCpuFrame(gfx::Size size, |
| base::TimeDelta timestamp) { |
| auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_I420, size, |
| gfx::Rect(size), size, timestamp); |
| |
| // Green I420 frame (Y:0x96, U:0x40, V:0x40) |
| libyuv::I420Rect(frame->writable_data(VideoFrame::kYPlane), |
| frame->stride(VideoFrame::kYPlane), |
| frame->writable_data(VideoFrame::kUPlane), |
| frame->stride(VideoFrame::kUPlane), |
| frame->writable_data(VideoFrame::kVPlane), |
| frame->stride(VideoFrame::kVPlane), |
| 0, // left |
| 0, // top |
| frame->visible_rect().width(), // right |
| frame->visible_rect().height(), // bottom |
| 0x96, // Y color |
| 0x40, // U color |
| 0x40); // V color |
| |
| frame->set_color_space(kYUVColorSpace); |
| return frame; |
| } |
| |
| scoped_refptr<VideoFrame> CreateGreenCpuFrameARGB(gfx::Size size, |
| base::TimeDelta timestamp) { |
| auto frame = VideoFrame::CreateFrame(PIXEL_FORMAT_XRGB, size, |
| gfx::Rect(size), size, timestamp); |
| |
| // Green XRGB frame (R:0x3B, G:0xD9, B:0x24) |
| libyuv::ARGBRect(frame->writable_data(VideoFrame::kARGBPlane), |
| frame->stride(VideoFrame::kARGBPlane), |
| 0, // left |
| 0, // top |
| frame->visible_rect().width(), // right |
| frame->visible_rect().height(), // bottom |
| 0x24D93B00); // V color |
| |
| frame->set_color_space(kRGBColorSpace); |
| return frame; |
| } |
| |
| scoped_refptr<VideoFrame> CreateGreenFrame(gfx::Size size, |
| VideoPixelFormat format, |
| base::TimeDelta timestamp) { |
| switch (format) { |
| case PIXEL_FORMAT_I420: |
| return CreateGreenCpuFrame(size, timestamp); |
| case PIXEL_FORMAT_NV12: |
| return CreateGreenGpuFrame(size, timestamp); |
| case PIXEL_FORMAT_XRGB: |
| return CreateGreenCpuFrameARGB(size, timestamp); |
| default: |
| EXPECT_TRUE(false) << "not supported pixel format"; |
| return nullptr; |
| } |
| } |
| |
| gfx::ColorSpace ExpectedColorSpace(VideoPixelFormat src_format, |
| VideoPixelFormat dst_format) { |
| // Converting between YUV formats doesn't change the color space. |
| if (IsYuvPlanar(src_format) && IsYuvPlanar(dst_format)) { |
| return kYUVColorSpace; |
| } |
| |
| // libyuv's RGB to YUV methods always output BT.601. |
| if (IsRGB(src_format) && IsYuvPlanar(dst_format)) { |
| return gfx::ColorSpace::CreateREC601(); |
| } |
| |
| EXPECT_TRUE(false) << "unexpected formats: src=" << src_format |
| << ", dst=" << dst_format; |
| return gfx::ColorSpace(); |
| } |
| |
| VideoEncoder::EncoderStatusCB ValidatingStatusCB( |
| base::Location loc = FROM_HERE) { |
| struct CallEnforcer { |
| bool called = false; |
| std::string location; |
| ~CallEnforcer() { |
| EXPECT_TRUE(called) << "Callback created: " << location; |
| } |
| }; |
| auto enforcer = std::make_unique<CallEnforcer>(); |
| enforcer->location = loc.ToString(); |
| return base::BindLambdaForTesting( |
| [this, enforcer{std::move(enforcer)}](EncoderStatus s) { |
| EXPECT_TRUE(callback_runner_->RunsTasksInCurrentSequence()); |
| EXPECT_TRUE(s.is_ok()) << " Callback created: " << enforcer->location |
| << " Error: " << s.message(); |
| enforcer->called = true; |
| }); |
| } |
| |
| protected: |
| VideoCodecProfile profile_ = VP8PROFILE_ANY; |
| static constexpr gfx::ColorSpace kRGBColorSpace = |
| gfx::ColorSpace::CreateSRGB(); |
| static constexpr gfx::ColorSpace kYUVColorSpace = |
| gfx::ColorSpace::CreateREC709(); |
| std::vector<VideoEncodeAccelerator::SupportedProfile> supported_profiles_; |
| base::test::TaskEnvironment task_environment_; |
| raw_ptr<FakeVideoEncodeAccelerator> vea_; // owned by |vae_adapter_| |
| std::unique_ptr<MockGpuVideoAcceleratorFactories> gpu_factories_; |
| std::unique_ptr<VideoEncodeAcceleratorAdapter> vae_adapter_; |
| scoped_refptr<base::SequencedTaskRunner> vea_runner_; |
| scoped_refptr<base::SequencedTaskRunner> callback_runner_; |
| }; |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, PreInitialize) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| /*output_cb=*/base::DoNothing(), ValidatingStatusCB()); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, InitializeAfterFirstFrame) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| auto pixel_format = PIXEL_FORMAT_I420; |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, pixel_format); |
| |
| bool info_cb_called = false; |
| VideoEncoder::EncoderInfoCB info_cb = base::BindLambdaForTesting( |
| [&](const VideoEncoderInfo& info) { info_cb_called = true; }); |
| |
| int outputs_count = 0; |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| outputs_count++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_EQ(keyframe, true); |
| EXPECT_EQ(frame->format(), pixel_format); |
| EXPECT_EQ(frame->coded_size(), options.frame_size); |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize(profile_, options, std::move(info_cb), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| auto frame = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| |
| adapter()->Encode(frame, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| vea()->NotifyEncoderInfoChange(VideoEncoderInfo()); |
| RunUntilIdle(); |
| EXPECT_TRUE(info_cb_called); |
| EXPECT_EQ(outputs_count, 1); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, TemporalSvc) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| options.scalability_mode = SVCScalabilityMode::kL1T3; |
| int outputs_count = 0; |
| auto pixel_format = PIXEL_FORMAT_I420; |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, pixel_format); |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| if (output.timestamp == base::Milliseconds(1)) |
| EXPECT_EQ(output.temporal_id, 1); |
| else if (output.timestamp == base::Milliseconds(2)) |
| EXPECT_EQ(output.temporal_id, 1); |
| else if (output.timestamp == base::Milliseconds(3)) |
| EXPECT_EQ(output.temporal_id, 2); |
| else if (output.timestamp == base::Milliseconds(4)) |
| EXPECT_EQ(output.temporal_id, 2); |
| else |
| EXPECT_EQ(output.temporal_id, 2); |
| |
| EXPECT_EQ(output.color_space, expected_color_space); |
| outputs_count++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| BitstreamBufferMetadata result(1, keyframe, frame->timestamp()); |
| if (frame->timestamp() == base::Milliseconds(1)) { |
| result.h264 = H264Metadata(); |
| result.h264->temporal_idx = 1; |
| } else if (frame->timestamp() == base::Milliseconds(2)) { |
| result.vp8 = Vp8Metadata(); |
| result.vp8->temporal_idx = 1; |
| } else if (frame->timestamp() == base::Milliseconds(3)) { |
| result.vp9 = Vp9Metadata(); |
| result.vp9->temporal_idx = 2; |
| } else if (frame->timestamp() == base::Milliseconds(4)) { |
| result.av1 = Av1Metadata(); |
| result.av1->temporal_idx = 2; |
| } else { |
| result.h265 = H265Metadata(); |
| result.h265->temporal_idx = 2; |
| } |
| return result; |
| })); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| auto frame1 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| auto frame2 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(2)); |
| auto frame3 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(3)); |
| auto frame4 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(4)); |
| auto frame5 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(5)); |
| adapter()->Encode(frame1, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| adapter()->Encode(frame2, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| adapter()->Encode(frame3, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| adapter()->Encode(frame4, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| adapter()->Encode(frame5, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| EXPECT_EQ(outputs_count, 5); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, FlushDuringInitialize) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| int outputs_count = 0; |
| auto pixel_format = PIXEL_FORMAT_I420; |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, pixel_format); |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| outputs_count++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_EQ(keyframe, true); |
| EXPECT_EQ(frame->format(), pixel_format); |
| EXPECT_EQ(frame->coded_size(), options.frame_size); |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| auto frame = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| adapter()->Encode(frame, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| adapter()->Flush(base::BindLambdaForTesting([&](EncoderStatus s) { |
| EXPECT_TRUE(s.is_ok()); |
| EXPECT_EQ(outputs_count, 1); |
| })); |
| RunUntilIdle(); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, InitializationError) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| int outputs_count = 0; |
| auto pixel_format = PIXEL_FORMAT_I420; |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput, absl::optional<VideoEncoder::CodecDescription>) { |
| outputs_count++; |
| }); |
| |
| VideoEncoder::EncoderStatusCB expect_error_done_cb = |
| base::BindLambdaForTesting( |
| [&](EncoderStatus s) { EXPECT_FALSE(s.is_ok()); }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_TRUE(false) << "should never come here"; |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize( |
| VIDEO_CODEC_PROFILE_UNKNOWN, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), base::BindLambdaForTesting([](EncoderStatus s) { |
| EXPECT_EQ(s.code(), EncoderStatus::Codes::kEncoderInitializationError); |
| })); |
| |
| auto frame = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| adapter()->Encode(frame, VideoEncoder::EncodeOptions(true), |
| std::move(expect_error_done_cb)); |
| RunUntilIdle(); |
| EXPECT_EQ(outputs_count, 0); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, EncodingError) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| int outputs_count = 0; |
| auto pixel_format = PIXEL_FORMAT_I420; |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput, absl::optional<VideoEncoder::CodecDescription>) { |
| outputs_count++; |
| }); |
| |
| VideoEncoder::EncoderStatusCB expect_error_done_cb = |
| base::BindLambdaForTesting( |
| [&](EncoderStatus s) { EXPECT_FALSE(s.is_ok()); }); |
| |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| vea()->SetWillEncodingSucceed(false); |
| |
| auto frame = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| adapter()->Encode(frame, VideoEncoder::EncodeOptions(true), |
| std::move(expect_error_done_cb)); |
| RunUntilIdle(); |
| EXPECT_EQ(outputs_count, 0); |
| } |
| |
| TEST_P(VideoEncodeAcceleratorAdapterTest, TwoFramesResize) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| int outputs_count = 0; |
| gfx::Size small_size(480, 320); |
| gfx::Size large_size(800, 600); |
| auto pixel_format = GetParam(); |
| auto small_frame = |
| CreateGreenFrame(small_size, pixel_format, base::Milliseconds(1)); |
| auto large_frame = |
| CreateGreenFrame(large_size, pixel_format, base::Milliseconds(2)); |
| |
| VideoPixelFormat expected_input_format = PIXEL_FORMAT_I420; |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) |
| if (pixel_format != PIXEL_FORMAT_I420 || !small_frame->IsMappable()) |
| expected_input_format = PIXEL_FORMAT_NV12; |
| #endif |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, expected_input_format); |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| outputs_count++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_EQ(frame->format(), expected_input_format); |
| EXPECT_EQ(frame->coded_size(), options.frame_size); |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| adapter()->Encode(small_frame, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| adapter()->Encode(large_frame, VideoEncoder::EncodeOptions(false), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| EXPECT_EQ(outputs_count, 2); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, AutomaticResizeSupport) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| int outputs_count = 0; |
| gfx::Size small_size(480, 320); |
| auto pixel_format = PIXEL_FORMAT_NV12; |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, pixel_format); |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| outputs_count++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_EQ(frame->coded_size(), small_size); |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| vea()->SupportResize(); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| auto frame1 = |
| CreateGreenFrame(small_size, pixel_format, base::Milliseconds(1)); |
| auto frame2 = |
| CreateGreenFrame(small_size, pixel_format, base::Milliseconds(2)); |
| adapter()->Encode(frame1, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| adapter()->Encode(frame2, VideoEncoder::EncodeOptions(false), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| EXPECT_EQ(outputs_count, 2); |
| } |
| |
| TEST_P(VideoEncodeAcceleratorAdapterTest, RunWithAllPossibleInputConversions) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| int outputs_count = 0; |
| gfx::Size small_size(480, 320); |
| gfx::Size same_size = options.frame_size; |
| gfx::Size large_size(800, 600); |
| int frames_to_encode = 33; |
| auto pixel_format = GetParam(); |
| auto input_kind = |
| (pixel_format == PIXEL_FORMAT_NV12) |
| ? VideoEncodeAcceleratorAdapter::InputBufferKind::GpuMemBuf |
| : VideoEncodeAcceleratorAdapter::InputBufferKind::CpuMemBuf; |
| adapter()->SetInputBufferPreferenceForTesting(input_kind); |
| |
| const VideoPixelFormat expected_input_format = |
| input_kind == VideoEncodeAcceleratorAdapter::InputBufferKind::GpuMemBuf |
| ? PIXEL_FORMAT_NV12 |
| : PIXEL_FORMAT_I420; |
| |
| constexpr auto get_source_format = [](int i) { |
| // Every 4 frames switch between the 3 supported formats. |
| const int rem = i % 12; |
| auto format = PIXEL_FORMAT_XRGB; |
| if (rem < 4) |
| format = PIXEL_FORMAT_I420; |
| else if (rem < 8) |
| format = PIXEL_FORMAT_NV12; |
| return format; |
| }; |
| |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| VideoPixelFormat source_frame_format = get_source_format(outputs_count); |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(source_frame_format, expected_input_format); |
| EXPECT_EQ(output.color_space, expected_color_space); |
| outputs_count++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_EQ(frame->format(), expected_input_format); |
| EXPECT_EQ(frame->coded_size(), options.frame_size); |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| for (int frame_index = 0; frame_index < frames_to_encode; frame_index++) { |
| gfx::Size size; |
| if (frame_index % 4 == 0) |
| size = large_size; |
| else if (frame_index % 4 == 1) |
| size = small_size; |
| else |
| size = same_size; |
| |
| const auto format = get_source_format(frame_index); |
| bool key = frame_index % 9 == 0; |
| auto frame = |
| CreateGreenFrame(size, format, base::Milliseconds(frame_index)); |
| adapter()->Encode(frame, VideoEncoder::EncodeOptions(key), |
| ValidatingStatusCB()); |
| } |
| |
| RunUntilIdle(); |
| EXPECT_EQ(outputs_count, frames_to_encode); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, DroppedFrame) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| auto pixel_format = PIXEL_FORMAT_I420; |
| std::vector<base::TimeDelta> output_timestamps; |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, pixel_format); |
| VideoEncoder::OutputCB output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| output_timestamps.push_back(output.timestamp); |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| size_t size = keyframe ? 1 : 0; // Drop non-key frame |
| return BitstreamBufferMetadata(size, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(output_cb), ValidatingStatusCB()); |
| |
| auto frame1 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| auto frame2 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(2)); |
| auto frame3 = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(3)); |
| adapter()->Encode(frame1, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| adapter()->Encode(frame2, VideoEncoder::EncodeOptions(false), |
| ValidatingStatusCB()); |
| adapter()->Encode(frame3, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| ASSERT_EQ(output_timestamps.size(), 2u); |
| EXPECT_EQ(output_timestamps[0], base::Milliseconds(1)); |
| EXPECT_EQ(output_timestamps[1], base::Milliseconds(3)); |
| } |
| |
| TEST_F(VideoEncodeAcceleratorAdapterTest, |
| ChangeOptions_ChangeVariableBitrateSmokeTest) { |
| VideoEncoder::Options options; |
| options.frame_size = gfx::Size(640, 480); |
| options.bitrate = Bitrate::VariableBitrate(1111u, 2222u); |
| auto pixel_format = PIXEL_FORMAT_I420; |
| int output_count_before_change = 0; |
| int output_count_after_change = 0; |
| const gfx::ColorSpace expected_color_space = |
| ExpectedColorSpace(pixel_format, pixel_format); |
| VideoEncoder::OutputCB first_output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| output_count_before_change++; |
| }); |
| VideoEncoder::OutputCB second_output_cb = base::BindLambdaForTesting( |
| [&](VideoEncoderOutput output, |
| absl::optional<VideoEncoder::CodecDescription>) { |
| EXPECT_EQ(output.color_space, expected_color_space); |
| output_count_after_change++; |
| }); |
| |
| vea()->SetEncodingCallback(base::BindLambdaForTesting( |
| [&](BitstreamBuffer&, bool keyframe, scoped_refptr<VideoFrame> frame) { |
| EXPECT_EQ(keyframe, true); |
| EXPECT_EQ(frame->format(), pixel_format); |
| EXPECT_EQ(frame->coded_size(), options.frame_size); |
| return BitstreamBufferMetadata(1, keyframe, frame->timestamp()); |
| })); |
| adapter()->Initialize(profile_, options, /*info_cb=*/base::DoNothing(), |
| std::move(first_output_cb), ValidatingStatusCB()); |
| // We must encode one frame before we can change options. |
| auto first_frame = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(1)); |
| adapter()->Encode(first_frame, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| |
| options.bitrate = Bitrate::VariableBitrate(12345u, 23456u); |
| adapter()->ChangeOptions(options, std::move(second_output_cb), |
| ValidatingStatusCB()); |
| auto second_frame = |
| CreateGreenFrame(options.frame_size, pixel_format, base::Milliseconds(2)); |
| adapter()->Encode(second_frame, VideoEncoder::EncodeOptions(true), |
| ValidatingStatusCB()); |
| RunUntilIdle(); |
| |
| EXPECT_EQ(output_count_before_change, 1); |
| EXPECT_EQ(output_count_after_change, 1); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(VideoEncodeAcceleratorAdapterTest, |
| VideoEncodeAcceleratorAdapterTest, |
| ::testing::Values(PIXEL_FORMAT_I420, |
| PIXEL_FORMAT_NV12, |
| PIXEL_FORMAT_XRGB)); |
| |
| } // namespace media |