| // Copyright 2018 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 "components/mirroring/browser/single_client_video_capture_host.h" |
| |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "mojo/public/cpp/bindings/binding.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::InvokeWithoutArgs; |
| using media::VideoFrameReceiver; |
| |
| namespace mirroring { |
| |
| namespace { |
| |
| class MockVideoCaptureDevice final |
| : public content::LaunchedVideoCaptureDevice { |
| public: |
| MockVideoCaptureDevice() {} |
| ~MockVideoCaptureDevice() override {} |
| void GetPhotoState( |
| VideoCaptureDevice::GetPhotoStateCallback callback) const override {} |
| void SetPhotoOptions( |
| media::mojom::PhotoSettingsPtr settings, |
| VideoCaptureDevice::SetPhotoOptionsCallback callback) override {} |
| void TakePhoto(VideoCaptureDevice::TakePhotoCallback callback) override {} |
| void SetDesktopCaptureWindowIdAsync(gfx::NativeViewId window_id, |
| base::OnceClosure done_cb) override {} |
| MOCK_METHOD0(MaybeSuspendDevice, void()); |
| MOCK_METHOD0(ResumeDevice, void()); |
| MOCK_METHOD0(RequestRefreshFrame, void()); |
| MOCK_METHOD2(OnUtilizationReport, void(int, double)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureDevice); |
| }; |
| |
| class FakeDeviceLauncher final : public content::VideoCaptureDeviceLauncher { |
| public: |
| using DeviceLaunchedCallback = |
| base::OnceCallback<void(base::WeakPtr<VideoFrameReceiver>, |
| MockVideoCaptureDevice*)>; |
| |
| explicit FakeDeviceLauncher(DeviceLaunchedCallback launched_cb) |
| : after_launch_cb_(std::move(launched_cb)), weak_factory_(this) {} |
| |
| ~FakeDeviceLauncher() override {} |
| |
| // content::VideoCaptureDeviceLauncher implementation. |
| void LaunchDeviceAsync(const std::string& device_id, |
| content::MediaStreamType stream_type, |
| const VideoCaptureParams& params, |
| base::WeakPtr<VideoFrameReceiver> receiver, |
| base::OnceClosure connection_lost_cb, |
| Callbacks* callbacks, |
| base::OnceClosure done_cb) override { |
| base::SequencedTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&FakeDeviceLauncher::OnDeviceLaunched, |
| weak_factory_.GetWeakPtr(), receiver, |
| callbacks, std::move(done_cb))); |
| } |
| |
| void AbortLaunch() override {} |
| |
| private: |
| void OnDeviceLaunched(base::WeakPtr<VideoFrameReceiver> receiver, |
| VideoCaptureDeviceLauncher::Callbacks* callbacks, |
| base::OnceClosure done_cb) { |
| auto launched_device = std::make_unique<MockVideoCaptureDevice>(); |
| EXPECT_FALSE(after_launch_cb_.is_null()); |
| std::move(after_launch_cb_).Run(receiver, launched_device.get()); |
| callbacks->OnDeviceLaunched(std::move(launched_device)); |
| std::move(done_cb).Run(); |
| } |
| |
| DeviceLaunchedCallback after_launch_cb_; |
| base::WeakPtrFactory<FakeDeviceLauncher> weak_factory_; |
| DISALLOW_COPY_AND_ASSIGN(FakeDeviceLauncher); |
| }; |
| |
| class StubReadWritePermission final |
| : public VideoCaptureDevice::Client::Buffer::ScopedAccessPermission { |
| public: |
| StubReadWritePermission() {} |
| ~StubReadWritePermission() override {} |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(StubReadWritePermission); |
| }; |
| |
| class MockVideoCaptureObserver final |
| : public media::mojom::VideoCaptureObserver { |
| public: |
| explicit MockVideoCaptureObserver(media::mojom::VideoCaptureHostPtr host) |
| : host_(std::move(host)), binding_(this) {} |
| MOCK_METHOD1(OnBufferCreatedCall, void(int buffer_id)); |
| void OnNewBuffer(int32_t buffer_id, |
| media::mojom::VideoBufferHandlePtr buffer_handle) override { |
| EXPECT_EQ(buffers_.find(buffer_id), buffers_.end()); |
| EXPECT_EQ(frame_infos_.find(buffer_id), frame_infos_.end()); |
| buffers_[buffer_id] = std::move(buffer_handle); |
| OnBufferCreatedCall(buffer_id); |
| } |
| MOCK_METHOD1(OnBufferReadyCall, void(int buffer_id)); |
| void OnBufferReady(int32_t buffer_id, |
| media::mojom::VideoFrameInfoPtr info) override { |
| EXPECT_TRUE(buffers_.find(buffer_id) != buffers_.end()); |
| EXPECT_EQ(frame_infos_.find(buffer_id), frame_infos_.end()); |
| frame_infos_[buffer_id] = std::move(info); |
| OnBufferReadyCall(buffer_id); |
| } |
| |
| MOCK_METHOD1(OnBufferDestroyedCall, void(int buffer_id)); |
| void OnBufferDestroyed(int32_t buffer_id) override { |
| // The consumer should have finished consuming the buffer before it is being |
| // destroyed. |
| EXPECT_TRUE(frame_infos_.find(buffer_id) == frame_infos_.end()); |
| const auto iter = buffers_.find(buffer_id); |
| EXPECT_TRUE(iter != buffers_.end()); |
| buffers_.erase(iter); |
| OnBufferDestroyedCall(buffer_id); |
| } |
| |
| MOCK_METHOD1(OnStateChanged, void(media::mojom::VideoCaptureState state)); |
| |
| void Start() { |
| media::mojom::VideoCaptureObserverPtr observer; |
| binding_.Bind(mojo::MakeRequest(&observer)); |
| host_->Start(0, 0, VideoCaptureParams(), std::move(observer)); |
| } |
| |
| void FinishConsumingBuffer(int32_t buffer_id, double utilization) { |
| EXPECT_TRUE(buffers_.find(buffer_id) != buffers_.end()); |
| const auto iter = frame_infos_.find(buffer_id); |
| EXPECT_TRUE(iter != frame_infos_.end()); |
| frame_infos_.erase(iter); |
| host_->ReleaseBuffer(0, buffer_id, utilization); |
| } |
| |
| void Stop() { host_->Stop(0); } |
| |
| private: |
| media::mojom::VideoCaptureHostPtr host_; |
| mojo::Binding<media::mojom::VideoCaptureObserver> binding_; |
| base::flat_map<int, media::mojom::VideoBufferHandlePtr> buffers_; |
| base::flat_map<int, media::mojom::VideoFrameInfoPtr> frame_infos_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockVideoCaptureObserver); |
| }; |
| |
| media::mojom::VideoFrameInfoPtr GetVideoFrameInfo() { |
| return media::mojom::VideoFrameInfo::New( |
| base::TimeDelta(), base::Value(base::Value::Type::DICTIONARY), |
| media::PIXEL_FORMAT_I420, gfx::Size(320, 180), gfx::Rect(320, 180), |
| gfx::ColorSpace::CreateREC709(), nullptr); |
| } |
| |
| } // namespace |
| |
| class SingleClientVideoCaptureHostTest : public ::testing::Test { |
| public: |
| SingleClientVideoCaptureHostTest() : weak_factory_(this) { |
| auto host_impl = std::make_unique<SingleClientVideoCaptureHost>( |
| std::string(), content::MediaStreamType::MEDIA_GUM_TAB_VIDEO_CAPTURE, |
| base::BindRepeating( |
| &SingleClientVideoCaptureHostTest::CreateDeviceLauncher, |
| base::Unretained(this))); |
| media::mojom::VideoCaptureHostPtr host; |
| mojo::MakeStrongBinding(std::move(host_impl), mojo::MakeRequest(&host)); |
| consumer_ = std::make_unique<MockVideoCaptureObserver>(std::move(host)); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*this, OnDeviceLaunchedCall()) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| consumer_->Start(); |
| run_loop.Run(); |
| |
| // The video capture device is launched. |
| EXPECT_TRUE(!!launched_device_); |
| EXPECT_TRUE(!!frame_receiver_); |
| } |
| |
| ~SingleClientVideoCaptureHostTest() override { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*consumer_, |
| OnStateChanged(media::mojom::VideoCaptureState::ENDED)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| consumer_->Stop(); |
| run_loop.Run(); |
| } |
| |
| protected: |
| void CreateBuffer(int buffer_id, int expected_buffer_context_id) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*consumer_, OnBufferCreatedCall(expected_buffer_context_id)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| media::mojom::VideoBufferHandlePtr stub_buffer_handle = |
| media::mojom::VideoBufferHandle::New(); |
| stub_buffer_handle->set_shared_buffer_handle( |
| mojo::SharedBufferHandle::Create(10)); |
| frame_receiver_->OnNewBuffer(buffer_id, std::move(stub_buffer_handle)); |
| run_loop.Run(); |
| } |
| |
| void FrameReadyInBuffer(int buffer_id, |
| int buffer_context_id, |
| int feedback_id) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*consumer_, OnBufferReadyCall(buffer_context_id)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| frame_receiver_->OnFrameReadyInBuffer( |
| buffer_id, feedback_id, std::make_unique<StubReadWritePermission>(), |
| GetVideoFrameInfo()); |
| run_loop.Run(); |
| } |
| |
| void FinishConsumingBuffer(int buffer_context_id, |
| int feedback_id, |
| double utilization) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*launched_device_, |
| OnUtilizationReport(feedback_id, utilization)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| consumer_->FinishConsumingBuffer(buffer_context_id, utilization); |
| run_loop.Run(); |
| } |
| |
| void RetireBuffer(int buffer_id, int buffer_context_id) { |
| base::RunLoop run_loop; |
| EXPECT_CALL(*consumer_, OnBufferDestroyedCall(buffer_context_id)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| frame_receiver_->OnBufferRetired(buffer_id); |
| run_loop.Run(); |
| } |
| |
| base::test::ScopedTaskEnvironment scoped_task_environment_; |
| std::unique_ptr<MockVideoCaptureObserver> consumer_; |
| base::WeakPtr<VideoFrameReceiver> frame_receiver_; |
| MockVideoCaptureDevice* launched_device_ = nullptr; |
| |
| private: |
| std::unique_ptr<content::VideoCaptureDeviceLauncher> CreateDeviceLauncher() { |
| return std::make_unique<FakeDeviceLauncher>( |
| base::BindOnce(&SingleClientVideoCaptureHostTest::OnDeviceLaunched, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| MOCK_METHOD0(OnDeviceLaunchedCall, void()); |
| void OnDeviceLaunched(base::WeakPtr<VideoFrameReceiver> receiver, |
| MockVideoCaptureDevice* launched_device) { |
| frame_receiver_ = std::move(receiver); |
| launched_device_ = launched_device; |
| OnDeviceLaunchedCall(); |
| } |
| |
| base::WeakPtrFactory<SingleClientVideoCaptureHostTest> weak_factory_; |
| }; |
| |
| TEST_F(SingleClientVideoCaptureHostTest, Basic) { |
| CreateBuffer(1, 0); |
| FrameReadyInBuffer(1, 0, 5); |
| FinishConsumingBuffer(0, 5, 1.0); |
| RetireBuffer(1, 0); |
| } |
| |
| TEST_F(SingleClientVideoCaptureHostTest, ReuseBufferId) { |
| CreateBuffer(0, 0); |
| FrameReadyInBuffer(0, 0, 3); |
| // Retire buffer 0. The consumer is not expected to receive OnBufferDestroyed |
| // since the buffer is not returned yet. |
| { |
| EXPECT_CALL(*consumer_, OnBufferDestroyedCall(0)).Times(0); |
| frame_receiver_->OnBufferRetired(0); |
| scoped_task_environment_.RunUntilIdle(); |
| } |
| |
| // Re-use buffer 0. |
| CreateBuffer(0, 1); |
| FrameReadyInBuffer(0, 1, 7); |
| |
| // Finish consuming frame in the retired buffer 0. |
| FinishConsumingBuffer(0, 3, 1.0); |
| // The retired buffer is expected to be destroyed since the consumer finished |
| // consuming the frame in that buffer. |
| base::RunLoop run_loop; |
| EXPECT_CALL(*consumer_, OnBufferDestroyedCall(0)) |
| .WillOnce(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); |
| run_loop.Run(); |
| |
| FinishConsumingBuffer(1, 7, 0.5); |
| RetireBuffer(0, 1); |
| } |
| |
| TEST_F(SingleClientVideoCaptureHostTest, StopCapturingWhileBuffersInUse) { |
| for (int i = 0; i < 10; ++i) { |
| CreateBuffer(i, i); |
| FrameReadyInBuffer(i, i, i); |
| } |
| } |
| |
| } // namespace mirroring |