| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/test/task_environment.h" |
| #include "gpu/config/gpu_info.h" |
| #include "media/mojo/clients/mojo_video_encode_accelerator.h" |
| #include "media/mojo/mojom/video_encode_accelerator.mojom.h" |
| #include "media/video/video_encode_accelerator.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::InSequence; |
| using ::testing::Invoke; |
| |
| namespace media { |
| |
| static const gfx::Size kInputVisibleSize(64, 48); |
| |
| // Mock implementation of the Mojo "service" side of the VEA dialogue. Upon an |
| // Initialize() call, checks |initialization_success_| and responds to |client| |
| // with a RequireBitstreamBuffers() if so configured; upon Encode(), responds |
| // with a BitstreamBufferReady() with the bitstream buffer id previously |
| // configured by a call to UseOutputBitstreamBuffer(). This mock class only |
| // allows for one bitstream buffer in flight. |
| class MockMojoVideoEncodeAccelerator : public mojom::VideoEncodeAccelerator { |
| public: |
| MockMojoVideoEncodeAccelerator() = default; |
| |
| // mojom::VideoEncodeAccelerator impl. |
| void Initialize( |
| const media::VideoEncodeAccelerator::Config& config, |
| mojo::PendingRemote<mojom::VideoEncodeAcceleratorClient> client, |
| InitializeCallback success_callback) override { |
| if (initialization_success_) { |
| ASSERT_TRUE(client); |
| client_.Bind(std::move(client)); |
| const size_t allocation_size = VideoFrame::AllocationSize( |
| config.input_format, config.input_visible_size); |
| |
| client_->RequireBitstreamBuffers(1, config.input_visible_size, |
| allocation_size); |
| |
| DoInitialize(config.input_format, config.input_visible_size, |
| config.output_profile, config.bitrate, config.content_type, |
| &client_); |
| } |
| std::move(success_callback).Run(initialization_success_); |
| } |
| MOCK_METHOD6(DoInitialize, |
| void(media::VideoPixelFormat, |
| const gfx::Size&, |
| media::VideoCodecProfile, |
| media::Bitrate, |
| media::VideoEncodeAccelerator::Config::ContentType, |
| mojo::Remote<mojom::VideoEncodeAcceleratorClient>*)); |
| |
| void Encode(const scoped_refptr<VideoFrame>& frame, |
| bool keyframe, |
| EncodeCallback callback) override { |
| EXPECT_NE(-1, configured_bitstream_buffer_id_); |
| EXPECT_TRUE(client_); |
| client_->BitstreamBufferReady( |
| configured_bitstream_buffer_id_, |
| BitstreamBufferMetadata(0, keyframe, frame->timestamp())); |
| configured_bitstream_buffer_id_ = -1; |
| |
| DoEncode(frame, keyframe); |
| std::move(callback).Run(); |
| } |
| MOCK_METHOD2(DoEncode, void(const scoped_refptr<VideoFrame>&, bool)); |
| |
| void UseOutputBitstreamBuffer( |
| int32_t bitstream_buffer_id, |
| mojo::ScopedSharedBufferHandle buffer) override { |
| EXPECT_EQ(-1, configured_bitstream_buffer_id_); |
| configured_bitstream_buffer_id_ = bitstream_buffer_id; |
| |
| DoUseOutputBitstreamBuffer(bitstream_buffer_id, &buffer); |
| } |
| MOCK_METHOD2(DoUseOutputBitstreamBuffer, |
| void(int32_t, mojo::ScopedSharedBufferHandle*)); |
| |
| MOCK_METHOD2(RequestEncodingParametersChangeWithLayers, |
| void(const media::VideoBitrateAllocation&, uint32_t)); |
| MOCK_METHOD2(RequestEncodingParametersChangeWithBitrate, |
| void(const media::Bitrate&, uint32_t)); |
| |
| void IsFlushSupported(IsFlushSupportedCallback callback) override { |
| DoIsFlushSupported(); |
| std::move(callback).Run(true); |
| } |
| MOCK_METHOD0(DoIsFlushSupported, void()); |
| void Flush(FlushCallback callback) override { |
| FlushCallback mock_callback; |
| DoFlush(std::move(mock_callback)); |
| // Actually, this callback should run on DoFlush, but in test, manally run |
| // it on Flush. |
| std::move(callback).Run(true); |
| } |
| MOCK_METHOD1(DoFlush, void(FlushCallback)); |
| |
| void set_initialization_success(bool success) { |
| initialization_success_ = success; |
| } |
| |
| private: |
| mojo::Remote<mojom::VideoEncodeAcceleratorClient> client_; |
| int32_t configured_bitstream_buffer_id_ = -1; |
| bool initialization_success_ = true; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockMojoVideoEncodeAccelerator); |
| }; |
| |
| // Mock implementation of the client of MojoVideoEncodeAccelerator. |
| class MockVideoEncodeAcceleratorClient : public VideoEncodeAccelerator::Client { |
| public: |
| MockVideoEncodeAcceleratorClient() = default; |
| |
| MOCK_METHOD3(RequireBitstreamBuffers, |
| void(unsigned int, const gfx::Size&, size_t)); |
| MOCK_METHOD2(BitstreamBufferReady, |
| void(int32_t, const media::BitstreamBufferMetadata&)); |
| MOCK_METHOD1(NotifyError, void(VideoEncodeAccelerator::Error)); |
| MOCK_METHOD1(NotifyEncoderInfoChange, void(const media::VideoEncoderInfo&)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockVideoEncodeAcceleratorClient); |
| }; |
| |
| // Test wrapper for a MojoVideoEncodeAccelerator, which translates between a |
| // pipe to a remote mojom::MojoVideoEncodeAccelerator, and a local |
| // media::VideoEncodeAccelerator::Client. |
| class MojoVideoEncodeAcceleratorTest : public ::testing::Test { |
| public: |
| MojoVideoEncodeAcceleratorTest() = default; |
| |
| void SetUp() override { |
| mojo::PendingRemote<mojom::VideoEncodeAccelerator> mojo_vea; |
| mojo_vea_receiver_ = mojo::MakeSelfOwnedReceiver( |
| std::make_unique<MockMojoVideoEncodeAccelerator>(), |
| mojo_vea.InitWithNewPipeAndPassReceiver()); |
| |
| mojo_vea_ = |
| base::WrapUnique<VideoEncodeAccelerator>(new MojoVideoEncodeAccelerator( |
| std::move(mojo_vea), |
| media::VideoEncodeAccelerator::SupportedProfiles())); |
| } |
| |
| void TearDown() override { |
| // The destruction of a mojo::SelfOwnedReceiver closes the bound message |
| // pipe but does not destroy the implementation object(s): this needs to |
| // happen manually by Close()ing it. |
| mojo_vea_receiver_->Close(); |
| } |
| |
| MockMojoVideoEncodeAccelerator* mock_mojo_vea() { |
| return static_cast<media::MockMojoVideoEncodeAccelerator*>( |
| mojo_vea_receiver_->impl()); |
| } |
| VideoEncodeAccelerator* mojo_vea() { return mojo_vea_.get(); } |
| |
| // This method calls Initialize() with semantically correct parameters and |
| // verifies that the appropriate message goes through the mojo pipe and is |
| // responded by a RequireBitstreamBuffers() on |mock_vea_client|. |
| void Initialize(MockVideoEncodeAcceleratorClient* mock_vea_client) { |
| constexpr VideoCodecProfile kOutputProfile = VIDEO_CODEC_PROFILE_UNKNOWN; |
| constexpr Bitrate kInitialBitrate = Bitrate::ConstantBitrate(100000u); |
| constexpr VideoEncodeAccelerator::Config::ContentType kContentType = |
| VideoEncodeAccelerator::Config::ContentType::kDisplay; |
| |
| EXPECT_CALL(*mock_mojo_vea(), |
| DoInitialize(PIXEL_FORMAT_I420, kInputVisibleSize, |
| kOutputProfile, kInitialBitrate, kContentType, _)); |
| EXPECT_CALL( |
| *mock_vea_client, |
| RequireBitstreamBuffers( |
| _, kInputVisibleSize, |
| VideoFrame::AllocationSize(PIXEL_FORMAT_I420, kInputVisibleSize))); |
| |
| const VideoEncodeAccelerator::Config config( |
| PIXEL_FORMAT_I420, kInputVisibleSize, kOutputProfile, kInitialBitrate, |
| absl::nullopt, absl::nullopt, absl::nullopt, false, absl::nullopt, |
| kContentType); |
| EXPECT_TRUE(mojo_vea()->Initialize(config, mock_vea_client)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| private: |
| base::test::SingleThreadTaskEnvironment task_environment_; |
| |
| // This member holds on to the mock implementation of the "service" side. |
| mojo::SelfOwnedReceiverRef<mojom::VideoEncodeAccelerator> mojo_vea_receiver_; |
| |
| // The class under test, as a generic media::VideoEncodeAccelerator. |
| std::unique_ptr<VideoEncodeAccelerator> mojo_vea_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MojoVideoEncodeAcceleratorTest); |
| }; |
| |
| TEST_F(MojoVideoEncodeAcceleratorTest, CreateAndDestroy) {} |
| |
| // This test verifies the Initialize() communication prologue in isolation. |
| TEST_F(MojoVideoEncodeAcceleratorTest, InitializeAndRequireBistreamBuffers) { |
| std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client = |
| std::make_unique<MockVideoEncodeAcceleratorClient>(); |
| Initialize(mock_vea_client.get()); |
| } |
| |
| // This test verifies the Initialize() communication prologue followed by a |
| // sharing of a single bitstream buffer and the Encode() of one frame. |
| TEST_F(MojoVideoEncodeAcceleratorTest, EncodeOneFrame) { |
| std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client = |
| std::make_unique<MockVideoEncodeAcceleratorClient>(); |
| Initialize(mock_vea_client.get()); |
| |
| const int32_t kBitstreamBufferId = 17; |
| { |
| const int32_t kShMemSize = 10; |
| auto shmem = base::UnsafeSharedMemoryRegion::Create(kShMemSize); |
| EXPECT_CALL(*mock_mojo_vea(), |
| DoUseOutputBitstreamBuffer(kBitstreamBufferId, _)); |
| mojo_vea()->UseOutputBitstreamBuffer(BitstreamBuffer( |
| kBitstreamBufferId, |
| base::UnsafeSharedMemoryRegion::TakeHandleForSerialization( |
| std::move(shmem)), |
| kShMemSize, 0 /* offset */, base::TimeDelta())); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| { |
| base::UnsafeSharedMemoryRegion shmem = |
| base::UnsafeSharedMemoryRegion::Create( |
| VideoFrame::AllocationSize(PIXEL_FORMAT_I420, kInputVisibleSize) * |
| 2); |
| ASSERT_TRUE(shmem.IsValid()); |
| base::WritableSharedMemoryMapping mapping = shmem.Map(); |
| ASSERT_TRUE(mapping.IsValid()); |
| const scoped_refptr<VideoFrame> video_frame = VideoFrame::WrapExternalData( |
| PIXEL_FORMAT_I420, kInputVisibleSize, gfx::Rect(kInputVisibleSize), |
| kInputVisibleSize, mapping.GetMemoryAsSpan<uint8_t>().data(), |
| mapping.size(), base::TimeDelta()); |
| video_frame->BackWithSharedMemory(&shmem); |
| const bool is_keyframe = true; |
| |
| // The remote end of the mojo Pipe doesn't receive |video_frame| itself. |
| EXPECT_CALL(*mock_mojo_vea(), DoEncode(_, is_keyframe)); |
| EXPECT_CALL(*mock_vea_client, BitstreamBufferReady(kBitstreamBufferId, _)) |
| .WillOnce(Invoke([is_keyframe, &video_frame]( |
| int32_t, const BitstreamBufferMetadata& metadata) { |
| EXPECT_EQ(is_keyframe, metadata.key_frame); |
| EXPECT_EQ(metadata.timestamp, video_frame->timestamp()); |
| })); |
| |
| mojo_vea()->Encode(video_frame, is_keyframe); |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| |
| // Tests that a RequestEncodingParametersChange() ripples through correctly. |
| TEST_F(MojoVideoEncodeAcceleratorTest, EncodingParametersChange) { |
| const uint32_t kNewFramerate = 321321u; |
| const uint32_t kNewBitrate = 123123u; |
| Bitrate bitrate = Bitrate::ConstantBitrate(kNewBitrate); |
| |
| // In a real world scenario, we should go through an Initialize() prologue, |
| // but we can skip that in unit testing. |
| |
| EXPECT_CALL(*mock_mojo_vea(), RequestEncodingParametersChangeWithBitrate( |
| bitrate, kNewFramerate)); |
| mojo_vea()->RequestEncodingParametersChange(bitrate, kNewFramerate); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // Tests that a RequestEncodingParametersChange() works with multi-dimensional |
| // bitrate allocatio. |
| TEST_F(MojoVideoEncodeAcceleratorTest, |
| EncodingParametersWithBitrateAllocation) { |
| const uint32_t kNewFramerate = 321321u; |
| const size_t kMaxNumBitrates = VideoBitrateAllocation::kMaxSpatialLayers * |
| VideoBitrateAllocation::kMaxTemporalLayers; |
| |
| // Verify translation of VideoBitrateAllocation into vector of bitrates for |
| // everything from empty array up to max number of layers. |
| VideoBitrateAllocation bitrate_allocation; |
| for (size_t i = 0; i <= kMaxNumBitrates; ++i) { |
| if (i > 0) { |
| int layer_bitrate = i * 1000; |
| const size_t si = (i - 1) / VideoBitrateAllocation::kMaxTemporalLayers; |
| const size_t ti = (i - 1) % VideoBitrateAllocation::kMaxTemporalLayers; |
| bitrate_allocation.SetBitrate(si, ti, layer_bitrate); |
| } |
| |
| EXPECT_CALL(*mock_mojo_vea(), RequestEncodingParametersChangeWithLayers( |
| bitrate_allocation, kNewFramerate)); |
| mojo_vea()->RequestEncodingParametersChange(bitrate_allocation, |
| kNewFramerate); |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| |
| // This test verifies the Initialize() communication prologue fails when the |
| // FakeVEA is configured to do so. |
| TEST_F(MojoVideoEncodeAcceleratorTest, InitializeFailure) { |
| std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client = |
| std::make_unique<MockVideoEncodeAcceleratorClient>(); |
| |
| constexpr Bitrate kInitialBitrate = Bitrate::ConstantBitrate(100000u); |
| |
| mock_mojo_vea()->set_initialization_success(false); |
| |
| const VideoEncodeAccelerator::Config config( |
| PIXEL_FORMAT_I420, kInputVisibleSize, VIDEO_CODEC_PROFILE_UNKNOWN, |
| kInitialBitrate); |
| EXPECT_FALSE(mojo_vea()->Initialize(config, mock_vea_client.get())); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| // This test verifies the IsFlushSupported() and Flush() communication. |
| TEST_F(MojoVideoEncodeAcceleratorTest, IsFlushSupportedAndFlush) { |
| std::unique_ptr<MockVideoEncodeAcceleratorClient> mock_vea_client = |
| std::make_unique<MockVideoEncodeAcceleratorClient>(); |
| Initialize(mock_vea_client.get()); |
| |
| EXPECT_CALL(*mock_mojo_vea(), DoIsFlushSupported()); |
| bool ret = mojo_vea()->IsFlushSupported(); |
| base::RunLoop().RunUntilIdle(); |
| if (ret) { |
| EXPECT_CALL(*mock_mojo_vea(), DoFlush(_)); |
| auto flush_callback = |
| base::BindOnce([](bool status) { EXPECT_EQ(status, true); }); |
| mojo_vea()->Flush(std::move(flush_callback)); |
| base::RunLoop().RunUntilIdle(); |
| } |
| } |
| |
| } // namespace media |