| // Copyright 2020 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 <memory> |
| #include <vector> |
| |
| #include "base/run_loop.h" |
| #include "base/threading/thread.h" |
| #include "build/build_config.h" |
| #include "gpu/command_buffer/common/mailbox_holder.h" |
| #include "media/base/decode_status.h" |
| #include "media/base/decoder_buffer.h" |
| #include "media/base/media_util.h" |
| #include "media/base/test_data_util.h" |
| #include "media/base/test_helpers.h" |
| #include "media/base/video_frame.h" |
| #include "media/filters/fake_video_decoder.h" |
| #include "media/mojo/buildflags.h" |
| #include "media/mojo/mojom/interface_factory.mojom.h" |
| #include "media/mojo/mojom/video_decoder.mojom.h" |
| #include "media/mojo/services/interface_factory_impl.h" |
| #include "media/mojo/services/mojo_cdm_service_context.h" |
| #include "media/mojo/services/mojo_video_decoder_service.h" |
| #include "media/video/mock_gpu_video_accelerator_factories.h" |
| #include "mojo/public/cpp/bindings/unique_receiver_set.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| |
| #include "third_party/blink/renderer/modules/webcodecs/video_decoder_broker.h" |
| |
| using ::testing::_; |
| using ::testing::Return; |
| |
| namespace blink { |
| |
| namespace { |
| |
| // Fake decoder intended to simulate platform specific hw accelerated decoders |
| // running in the GPU process. |
| // * Initialize() will succeed for any given config. |
| // * MakeVideoFrame() is overridden to create frames frame with a mailbox and |
| // power_efficient flag. This simulates hw decoder output and satisfies |
| // requirements of MojoVideoDecoder. |
| class FakeGpuVideoDecoder : public media::FakeVideoDecoder { |
| public: |
| FakeGpuVideoDecoder() |
| : FakeVideoDecoder("FakeGpuVideoDecoder" /* display_name */, |
| 0 /* decoding_delay */, |
| 13 /* max_parallel_decoding_requests */, |
| media::BytesDecodedCB()) {} |
| ~FakeGpuVideoDecoder() override = default; |
| |
| scoped_refptr<media::VideoFrame> MakeVideoFrame( |
| const media::DecoderBuffer& buffer) override { |
| gpu::MailboxHolder mailbox_holders[media::VideoFrame::kMaxPlanes]; |
| mailbox_holders[0].mailbox.name[0] = 1; |
| scoped_refptr<media::VideoFrame> frame = |
| media::VideoFrame::WrapNativeTextures( |
| media::PIXEL_FORMAT_ARGB, mailbox_holders, |
| media::VideoFrame::ReleaseMailboxCB(), current_config_.coded_size(), |
| current_config_.visible_rect(), current_config_.natural_size(), |
| buffer.timestamp()); |
| frame->metadata()->power_efficient = true; |
| return frame; |
| } |
| |
| // Override these methods to provide non-default values for testing. |
| bool IsPlatformDecoder() const override { return true; } |
| bool NeedsBitstreamConversion() const override { return true; } |
| bool CanReadWithoutStalling() const override { return false; } |
| }; |
| |
| // Client to MojoVideoDecoderService vended by FakeInterfaceFactory. Creates a |
| // FakeGpuVideoDecoder when requested. |
| class FakeMojoMediaClient : public media::MojoMediaClient { |
| public: |
| // MojoMediaClient implementation. |
| std::unique_ptr<media::VideoDecoder> CreateVideoDecoder( |
| scoped_refptr<base::SingleThreadTaskRunner> task_runner, |
| media::MediaLog* media_log, |
| media::mojom::CommandBufferIdPtr command_buffer_id, |
| media::VideoDecoderImplementation implementation, |
| media::RequestOverlayInfoCB request_overlay_info_cb, |
| const gfx::ColorSpace& target_color_space) override { |
| return std::make_unique<FakeGpuVideoDecoder>(); |
| } |
| }; |
| |
| // Other end of remote InterfaceFactory requested by VideoDecoderBroker. Used |
| // to create our (fake) media::mojom::VideoDecoder. |
| class FakeInterfaceFactory : public media::mojom::InterfaceFactory { |
| public: |
| FakeInterfaceFactory() = default; |
| ~FakeInterfaceFactory() override = default; |
| |
| void BindRequest(mojo::ScopedMessagePipeHandle handle) { |
| receiver_.Bind(mojo::PendingReceiver<media::mojom::InterfaceFactory>( |
| std::move(handle))); |
| receiver_.set_disconnect_handler(WTF::Bind( |
| &FakeInterfaceFactory::OnConnectionError, base::Unretained(this))); |
| } |
| |
| void OnConnectionError() { receiver_.reset(); } |
| |
| // Implement this one interface from mojom::InterfaceFactory. Using the real |
| // MojoVideoDecoderService allows us to reuse buffer conversion code. The |
| // FakeMojoMediaClient will create a FakeGpuVideoDecoder. |
| void CreateVideoDecoder( |
| mojo::PendingReceiver<media::mojom::VideoDecoder> receiver) override { |
| video_decoder_receivers_.Add( |
| std::make_unique<media::MojoVideoDecoderService>(&mojo_media_client_, |
| &cdm_service_context_), |
| std::move(receiver)); |
| } |
| |
| // Stub out other mojom::InterfaceFactory interfaces. |
| void CreateAudioDecoder( |
| mojo::PendingReceiver<media::mojom::AudioDecoder> receiver) override {} |
| void CreateDefaultRenderer( |
| const std::string& audio_device_id, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver) override {} |
| #if BUILDFLAG(ENABLE_CAST_RENDERER) |
| void CreateCastRenderer( |
| const base::UnguessableToken& overlay_plane_id, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver) override {} |
| #endif |
| #if defined(OS_ANDROID) |
| void CreateMediaPlayerRenderer( |
| mojo::PendingRemote<media::mojom::MediaPlayerRendererClientExtension> |
| client_extension_remote, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver, |
| mojo::PendingReceiver<media::mojom::MediaPlayerRendererExtension> |
| renderer_extension_receiver) override {} |
| void CreateFlingingRenderer( |
| const std::string& presentation_id, |
| mojo::PendingRemote<media::mojom::FlingingRendererClientExtension> |
| client_extension, |
| mojo::PendingReceiver<media::mojom::Renderer> receiver) override {} |
| #endif // defined(OS_ANDROID |
| void CreateCdm(const std::string& key_system, |
| const media::CdmConfig& cdm_config, |
| CreateCdmCallback callback) override { |
| std::move(callback).Run(mojo::NullRemote(), base::nullopt, |
| mojo::NullRemote(), "CDM creation not supported"); |
| } |
| |
| private: |
| media::MojoCdmServiceContext cdm_service_context_; |
| FakeMojoMediaClient mojo_media_client_; |
| mojo::Receiver<media::mojom::InterfaceFactory> receiver_{this}; |
| mojo::UniqueReceiverSet<media::mojom::VideoDecoder> video_decoder_receivers_; |
| }; |
| |
| } // namespace |
| |
| class VideoDecoderBrokerTest : public testing::Test { |
| public: |
| VideoDecoderBrokerTest() = default; |
| ~VideoDecoderBrokerTest() override { |
| if (media_thread_) |
| media_thread_->Stop(); |
| } |
| |
| void OnInitWithClosure(base::RepeatingClosure done_cb, media::Status status) { |
| OnInit(status); |
| done_cb.Run(); |
| } |
| void OnDecodeDoneWithClosure(base::RepeatingClosure done_cb, |
| media::Status status) { |
| OnDecodeDone(std::move(status)); |
| done_cb.Run(); |
| } |
| |
| void OnResetDoneWithClosure(base::RepeatingClosure done_cb) { |
| OnResetDone(); |
| done_cb.Run(); |
| } |
| |
| MOCK_METHOD1(OnInit, void(media::Status status)); |
| MOCK_METHOD1(OnDecodeDone, void(media::Status)); |
| MOCK_METHOD0(OnResetDone, void()); |
| |
| void OnOutput(scoped_refptr<media::VideoFrame> frame) { |
| output_frames_.push_back(std::move(frame)); |
| } |
| |
| void SetupMojo(ExecutionContext& execution_context) { |
| // Register FakeInterfaceFactory as impl for media::mojom::InterfaceFactory |
| // required by MojoVideoDecoder. The factory will vend FakeGpuVideoDecoders |
| // that simulate gpu-accelerated decode. |
| interface_factory_ = std::make_unique<FakeInterfaceFactory>(); |
| EXPECT_TRUE( |
| Platform::Current()->GetBrowserInterfaceBroker()->SetBinderForTesting( |
| media::mojom::InterfaceFactory::Name_, |
| WTF::BindRepeating(&FakeInterfaceFactory::BindRequest, |
| base::Unretained(interface_factory_.get())))); |
| |
| // |gpu_factories_| requires API calls be made using it's GetTaskRunner(). |
| // We use a separate |media_thread_| (as opposed to a separate task runner |
| // on the main thread) to simulate cross-thread production behavior. |
| media_thread_ = std::make_unique<base::Thread>("media_thread"); |
| media_thread_->Start(); |
| |
| // |gpu_factories_| is a dependency of MojoVideoDecoder (and associated code |
| // paths). Setup |gpu_factories_| to say "yes" to any decoder config to |
| // ensure MojoVideoDecoder will be selected as the underlying decoder upon |
| // VideoDecoderBroker::Initialize(). The |
| gpu_factories_ = |
| std::make_unique<media::MockGpuVideoAcceleratorFactories>(nullptr); |
| EXPECT_CALL(*gpu_factories_, GetTaskRunner()) |
| .WillRepeatedly(Return(media_thread_->task_runner())); |
| EXPECT_CALL(*gpu_factories_, IsDecoderConfigSupported(_, _)) |
| .WillRepeatedly( |
| Return(media::GpuVideoAcceleratorFactories::Supported::kTrue)); |
| } |
| |
| void ConstructDecoder(ExecutionContext& execution_context) { |
| decoder_broker_ = std::make_unique<VideoDecoderBroker>( |
| execution_context, gpu_factories_.get(), &null_media_log_); |
| } |
| |
| void InitializeDecoder(media::VideoDecoderConfig config) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*this, OnInit(media::SameStatusCode(media::OkStatus()))); |
| decoder_broker_->Initialize( |
| config, false /*low_delay*/, nullptr /* cdm_context */, |
| WTF::Bind(&VideoDecoderBrokerTest::OnInitWithClosure, |
| WTF::Unretained(this), run_loop.QuitClosure()), |
| WTF::BindRepeating(&VideoDecoderBrokerTest::OnOutput, |
| WTF::Unretained(this)), |
| media::WaitingCB()); |
| run_loop.Run(); |
| testing::Mock::VerifyAndClearExpectations(this); |
| } |
| |
| void DecodeBuffer( |
| scoped_refptr<media::DecoderBuffer> buffer, |
| media::StatusCode expected_status = media::StatusCode::kOk) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*this, OnDecodeDone(HasStatusCode(expected_status))); |
| decoder_broker_->Decode( |
| buffer, WTF::Bind(&VideoDecoderBrokerTest::OnDecodeDoneWithClosure, |
| WTF::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| testing::Mock::VerifyAndClearExpectations(this); |
| } |
| |
| void ResetDecoder() { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*this, OnResetDone()); |
| decoder_broker_->Reset( |
| WTF::Bind(&VideoDecoderBrokerTest::OnResetDoneWithClosure, |
| WTF::Unretained(this), run_loop.QuitClosure())); |
| run_loop.Run(); |
| testing::Mock::VerifyAndClearExpectations(this); |
| } |
| |
| std::string GetDisplayName() { return decoder_broker_->GetDisplayName(); } |
| |
| bool IsPlatformDecoder() { return decoder_broker_->IsPlatformDecoder(); } |
| |
| bool NeedsBitstreamConversion() { |
| return decoder_broker_->NeedsBitstreamConversion(); |
| } |
| |
| bool CanReadWithoutStalling() { |
| return decoder_broker_->CanReadWithoutStalling(); |
| } |
| |
| int GetMaxDecodeRequests() { return decoder_broker_->GetMaxDecodeRequests(); } |
| |
| protected: |
| media::NullMediaLog null_media_log_; |
| std::unique_ptr<VideoDecoderBroker> decoder_broker_; |
| std::vector<scoped_refptr<media::VideoFrame>> output_frames_; |
| std::unique_ptr<media::MockGpuVideoAcceleratorFactories> gpu_factories_; |
| std::unique_ptr<FakeInterfaceFactory> interface_factory_; |
| std::unique_ptr<base::Thread> media_thread_; |
| }; |
| |
| TEST_F(VideoDecoderBrokerTest, Decode_Uninitialized) { |
| V8TestingScope v8_scope; |
| |
| ConstructDecoder(*v8_scope.GetExecutionContext()); |
| EXPECT_EQ(GetDisplayName(), "EmptyWebCodecsVideoDecoder"); |
| |
| // No call to Initialize. Other APIs should fail gracefully. |
| |
| DecodeBuffer(media::ReadTestDataFile("vp8-I-frame-320x120"), |
| media::DecodeStatus::DECODE_ERROR); |
| DecodeBuffer(media::DecoderBuffer::CreateEOSBuffer(), |
| media::DecodeStatus::DECODE_ERROR); |
| ASSERT_EQ(0U, output_frames_.size()); |
| |
| ResetDecoder(); |
| } |
| |
| TEST_F(VideoDecoderBrokerTest, Decode_NoMojoDecoder) { |
| V8TestingScope v8_scope; |
| |
| ConstructDecoder(*v8_scope.GetExecutionContext()); |
| EXPECT_EQ(GetDisplayName(), "EmptyWebCodecsVideoDecoder"); |
| |
| InitializeDecoder(media::TestVideoConfig::Normal()); |
| EXPECT_NE(GetDisplayName(), "EmptyWebCodecsVideoDecoder"); |
| |
| DecodeBuffer(media::ReadTestDataFile("vp8-I-frame-320x120")); |
| DecodeBuffer(media::DecoderBuffer::CreateEOSBuffer()); |
| ASSERT_EQ(1U, output_frames_.size()); |
| |
| ResetDecoder(); |
| |
| DecodeBuffer(media::ReadTestDataFile("vp8-I-frame-320x120")); |
| DecodeBuffer(media::DecoderBuffer::CreateEOSBuffer()); |
| ASSERT_EQ(2U, output_frames_.size()); |
| |
| ResetDecoder(); |
| } |
| |
| #if BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER) |
| TEST_F(VideoDecoderBrokerTest, Decode_WithMojoDecoder) { |
| V8TestingScope v8_scope; |
| ExecutionContext* execution_context = v8_scope.GetExecutionContext(); |
| |
| SetupMojo(*execution_context); |
| ConstructDecoder(*execution_context); |
| EXPECT_EQ(GetDisplayName(), "EmptyWebCodecsVideoDecoder"); |
| |
| // Use an extra-large video to ensure we don't get a software decoder |
| media::VideoDecoderConfig config = media::TestVideoConfig::ExtraLarge(); |
| InitializeDecoder(config); |
| EXPECT_EQ(GetDisplayName(), "MojoVideoDecoder"); |
| |
| DecodeBuffer(media::CreateFakeVideoBufferForTest( |
| config, base::TimeDelta(), base::TimeDelta::FromMilliseconds(33))); |
| DecodeBuffer(media::DecoderBuffer::CreateEOSBuffer()); |
| ASSERT_EQ(1U, output_frames_.size()); |
| |
| // Backing FakeVideoDecoder will return interesting values for these APIs. |
| EXPECT_TRUE(IsPlatformDecoder()); |
| EXPECT_TRUE(NeedsBitstreamConversion()); |
| EXPECT_FALSE(CanReadWithoutStalling()); |
| EXPECT_EQ(GetMaxDecodeRequests(), 13); |
| |
| ResetDecoder(); |
| } |
| #endif // BUILDFLAG(ENABLE_MOJO_VIDEO_DECODER) |
| |
| } // namespace blink |