| // 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 "media/capture/video/chromeos/camera_device_delegate.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/task_environment.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/capture/video/chromeos/camera_buffer_factory.h" |
| #include "media/capture/video/chromeos/camera_device_context.h" |
| #include "media/capture/video/chromeos/camera_hal_delegate.h" |
| #include "media/capture/video/chromeos/mock_camera_module.h" |
| #include "media/capture/video/chromeos/mock_vendor_tag_ops.h" |
| #include "media/capture/video/chromeos/mock_video_capture_client.h" |
| #include "media/capture/video/chromeos/video_capture_device_factory_chromeos.h" |
| #include "media/capture/video/mock_gpu_memory_buffer_manager.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using testing::_; |
| using testing::A; |
| using testing::AtLeast; |
| using testing::AtMost; |
| using testing::Invoke; |
| using testing::InvokeWithoutArgs; |
| |
| namespace media { |
| |
| namespace { |
| |
| class MockCameraDevice : public cros::mojom::Camera3DeviceOps { |
| public: |
| MockCameraDevice() = default; |
| |
| MockCameraDevice(const MockCameraDevice&) = delete; |
| MockCameraDevice& operator=(const MockCameraDevice&) = delete; |
| |
| ~MockCameraDevice() = default; |
| |
| void Initialize( |
| mojo::PendingRemote<cros::mojom::Camera3CallbackOps> callback_ops, |
| InitializeCallback callback) override { |
| DoInitialize(std::move(callback_ops), callback); |
| } |
| MOCK_METHOD2( |
| DoInitialize, |
| void(mojo::PendingRemote<cros::mojom::Camera3CallbackOps> callback_ops, |
| InitializeCallback& callback)); |
| |
| void ConfigureStreams(cros::mojom::Camera3StreamConfigurationPtr config, |
| ConfigureStreamsCallback callback) override { |
| DoConfigureStreams(config, callback); |
| } |
| MOCK_METHOD2(DoConfigureStreams, |
| void(cros::mojom::Camera3StreamConfigurationPtr& config, |
| ConfigureStreamsCallback& callback)); |
| |
| void ConstructDefaultRequestSettings( |
| cros::mojom::Camera3RequestTemplate type, |
| ConstructDefaultRequestSettingsCallback callback) override { |
| DoConstructDefaultRequestSettings(type, callback); |
| } |
| MOCK_METHOD2(DoConstructDefaultRequestSettings, |
| void(cros::mojom::Camera3RequestTemplate type, |
| ConstructDefaultRequestSettingsCallback& callback)); |
| |
| void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr request, |
| ProcessCaptureRequestCallback callback) override { |
| DoProcessCaptureRequest(request, callback); |
| } |
| MOCK_METHOD2(DoProcessCaptureRequest, |
| void(cros::mojom::Camera3CaptureRequestPtr& request, |
| ProcessCaptureRequestCallback& callback)); |
| |
| void Dump(mojo::ScopedHandle fd) override { DoDump(fd); } |
| MOCK_METHOD1(DoDump, void(mojo::ScopedHandle& fd)); |
| |
| void Flush(FlushCallback callback) override { DoFlush(callback); } |
| MOCK_METHOD1(DoFlush, void(FlushCallback& callback)); |
| |
| void RegisterBuffer(uint64_t buffer_id, |
| cros::mojom::Camera3DeviceOps::BufferType type, |
| std::vector<mojo::ScopedHandle> fds, |
| uint32_t drm_format, |
| cros::mojom::HalPixelFormat hal_pixel_format, |
| uint32_t width, |
| uint32_t height, |
| const std::vector<uint32_t>& strides, |
| const std::vector<uint32_t>& offsets, |
| RegisterBufferCallback callback) override {} |
| |
| void Close(CloseCallback callback) override { DoClose(callback); } |
| MOCK_METHOD1(DoClose, void(CloseCallback& callback)); |
| |
| void ConfigureStreamsAndGetAllocatedBuffers( |
| cros::mojom::Camera3StreamConfigurationPtr config, |
| ConfigureStreamsAndGetAllocatedBuffersCallback callback) override { |
| DoConfigureStreamsAndGetAllocatedBuffers(config, callback); |
| } |
| MOCK_METHOD2(DoConfigureStreamsAndGetAllocatedBuffers, |
| void(cros::mojom::Camera3StreamConfigurationPtr& config, |
| ConfigureStreamsAndGetAllocatedBuffersCallback& callback)); |
| }; |
| |
| constexpr int32_t kJpegMaxBufferSize = 1024; |
| constexpr size_t kDefaultWidth = 1280, kDefaultHeight = 720; |
| constexpr int32_t kDefaultMinFrameRate = 1, kDefaultMaxFrameRate = 30; |
| |
| base::flat_map<ClientType, VideoCaptureParams> GetDefaultCaptureParams() { |
| VideoCaptureParams params; |
| base::flat_map<ClientType, VideoCaptureParams> capture_params; |
| params.requested_format = {gfx::Size(kDefaultWidth, kDefaultHeight), |
| float{kDefaultMaxFrameRate}, PIXEL_FORMAT_I420}; |
| capture_params[ClientType::kPreviewClient] = params; |
| return capture_params; |
| } |
| |
| } // namespace |
| |
| class CameraDeviceDelegateTest : public ::testing::Test { |
| public: |
| CameraDeviceDelegateTest() |
| : mock_camera_device_receiver_(&mock_camera_device_), |
| device_delegate_thread_("DeviceDelegateThread"), |
| hal_delegate_thread_("HalDelegateThread") {} |
| |
| void SetUp() override { |
| VideoCaptureDeviceFactoryChromeOS::SetGpuBufferManager( |
| &mock_gpu_memory_buffer_manager_); |
| hal_delegate_thread_.Start(); |
| camera_hal_delegate_ = |
| new CameraHalDelegate(hal_delegate_thread_.task_runner()); |
| auto get_camera_info = base::BindRepeating( |
| &CameraHalDelegate::GetCameraInfoFromDeviceId, camera_hal_delegate_); |
| camera_hal_delegate_->SetCameraModule( |
| mock_camera_module_.GetPendingRemote()); |
| } |
| |
| void TearDown() override { |
| camera_hal_delegate_->Reset(); |
| hal_delegate_thread_.Stop(); |
| } |
| |
| void AllocateDevice() { |
| ASSERT_FALSE(device_delegate_thread_.IsRunning()); |
| ASSERT_FALSE(camera_device_delegate_); |
| |
| std::vector<VideoCaptureDeviceInfo> devices_info; |
| base::RunLoop run_loop; |
| camera_hal_delegate_->GetDevicesInfo(base::BindLambdaForTesting( |
| [&devices_info, &run_loop](std::vector<VideoCaptureDeviceInfo> result) { |
| devices_info = std::move(result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| ASSERT_EQ(devices_info.size(), 1u); |
| device_delegate_thread_.Start(); |
| |
| camera_device_delegate_ = std::make_unique<CameraDeviceDelegate>( |
| devices_info[0].descriptor, camera_hal_delegate_, |
| device_delegate_thread_.task_runner()); |
| } |
| |
| void GetNumberOfFakeCameras( |
| cros::mojom::CameraModule::GetNumberOfCamerasCallback& cb) { |
| std::move(cb).Run(1); |
| } |
| |
| void GetFakeVendorTagOps( |
| mojo::PendingReceiver<cros::mojom::VendorTagOps> vendor_tag_ops_receiver, |
| cros::mojom::CameraModule::GetVendorTagOpsCallback& cb) { |
| mock_vendor_tag_ops_.Bind(std::move(vendor_tag_ops_receiver)); |
| } |
| |
| void GetFakeCameraInfo(uint32_t camera_id, |
| cros::mojom::CameraModule::GetCameraInfoCallback& cb) { |
| cros::mojom::CameraInfoPtr camera_info = cros::mojom::CameraInfo::New(); |
| cros::mojom::CameraMetadataPtr static_metadata = |
| cros::mojom::CameraMetadata::New(); |
| |
| static_metadata->entry_count = 5; |
| static_metadata->entry_capacity = 5; |
| static_metadata->entries = |
| std::vector<cros::mojom::CameraMetadataEntryPtr>(); |
| |
| cros::mojom::CameraMetadataEntryPtr entry = |
| cros::mojom::CameraMetadataEntry::New(); |
| entry->index = 0; |
| entry->tag = cros::mojom::CameraMetadataTag:: |
| ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS; |
| entry->type = cros::mojom::EntryType::TYPE_INT32; |
| entry->count = 12; |
| std::vector<int32_t> stream_configurations(entry->count); |
| stream_configurations[0] = static_cast<int32_t>( |
| cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED); |
| stream_configurations[1] = kDefaultWidth; |
| stream_configurations[2] = kDefaultHeight; |
| stream_configurations[3] = static_cast<int32_t>( |
| cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT); |
| stream_configurations[4] = static_cast<int32_t>( |
| cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_YCbCr_420_888); |
| stream_configurations[5] = kDefaultWidth; |
| stream_configurations[6] = kDefaultHeight; |
| stream_configurations[7] = static_cast<int32_t>( |
| cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT); |
| stream_configurations[8] = static_cast<int32_t>( |
| cros::mojom::HalPixelFormat::HAL_PIXEL_FORMAT_BLOB); |
| stream_configurations[9] = kDefaultWidth; |
| stream_configurations[10] = kDefaultHeight; |
| stream_configurations[11] = static_cast<int32_t>( |
| cros::mojom::Camera3StreamType::CAMERA3_STREAM_OUTPUT); |
| uint8_t* as_int8 = reinterpret_cast<uint8_t*>(stream_configurations.data()); |
| entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t)); |
| static_metadata->entries->push_back(std::move(entry)); |
| |
| entry = cros::mojom::CameraMetadataEntry::New(); |
| entry->index = 1; |
| entry->tag = cros::mojom::CameraMetadataTag::ANDROID_SENSOR_ORIENTATION; |
| entry->type = cros::mojom::EntryType::TYPE_INT32; |
| entry->count = 1; |
| entry->data = std::vector<uint8_t>(4, 0); |
| static_metadata->entries->push_back(std::move(entry)); |
| |
| entry = cros::mojom::CameraMetadataEntry::New(); |
| entry->index = 2; |
| entry->tag = cros::mojom::CameraMetadataTag::ANDROID_JPEG_MAX_SIZE; |
| entry->type = cros::mojom::EntryType::TYPE_INT32; |
| entry->count = 1; |
| int32_t jpeg_max_size = kJpegMaxBufferSize; |
| as_int8 = reinterpret_cast<uint8_t*>(&jpeg_max_size); |
| entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t)); |
| static_metadata->entries->push_back(std::move(entry)); |
| |
| entry = cros::mojom::CameraMetadataEntry::New(); |
| entry->index = 3; |
| entry->tag = |
| cros::mojom::CameraMetadataTag::ANDROID_REQUEST_PIPELINE_MAX_DEPTH; |
| entry->type = cros::mojom::EntryType::TYPE_BYTE; |
| entry->count = 1; |
| uint8_t pipeline_max_depth = 1; |
| entry->data.assign(&pipeline_max_depth, |
| &pipeline_max_depth + entry->count * sizeof(uint8_t)); |
| static_metadata->entries->push_back(std::move(entry)); |
| |
| entry = cros::mojom::CameraMetadataEntry::New(); |
| entry->index = 4; |
| entry->tag = cros::mojom::CameraMetadataTag:: |
| ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES; |
| entry->type = cros::mojom::EntryType::TYPE_INT32; |
| entry->count = 2; |
| std::vector<int32_t> available_fps_ranges = {kDefaultMinFrameRate, |
| kDefaultMaxFrameRate}; |
| as_int8 = reinterpret_cast<uint8_t*>(available_fps_ranges.data()); |
| entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t)); |
| static_metadata->entries->push_back(std::move(entry)); |
| |
| entry = cros::mojom::CameraMetadataEntry::New(); |
| entry->index = 5; |
| entry->tag = |
| cros::mojom::CameraMetadataTag::ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE; |
| entry->type = cros::mojom::EntryType::TYPE_INT32; |
| entry->count = 4; |
| std::vector<int32_t> active_array_size = {0, 0, 1920, 1080}; |
| as_int8 = reinterpret_cast<uint8_t*>(active_array_size.data()); |
| entry->data.assign(as_int8, as_int8 + entry->count * sizeof(int32_t)); |
| static_metadata->entries->push_back(std::move(entry)); |
| |
| switch (camera_id) { |
| case 0: |
| camera_info->facing = cros::mojom::CameraFacing::CAMERA_FACING_FRONT; |
| camera_info->orientation = 0; |
| camera_info->static_camera_characteristics = std::move(static_metadata); |
| break; |
| default: |
| FAIL() << "Invalid camera id"; |
| } |
| std::move(cb).Run(0, std::move(camera_info)); |
| } |
| |
| void OpenMockCameraDevice( |
| int32_t camera_id, |
| mojo::PendingReceiver<cros::mojom::Camera3DeviceOps> device_ops_receiver, |
| base::OnceCallback<void(int32_t)>& callback) { |
| mock_camera_device_receiver_.Bind(std::move(device_ops_receiver)); |
| std::move(callback).Run(0); |
| } |
| |
| void InitializeMockCameraDevice( |
| mojo::PendingRemote<cros::mojom::Camera3CallbackOps> callback_ops, |
| base::OnceCallback<void(int32_t)>& callback) { |
| callback_ops_.Bind(std::move(callback_ops)); |
| std::move(callback).Run(0); |
| } |
| |
| void ConfigureFakeStreams( |
| cros::mojom::Camera3StreamConfigurationPtr& config, |
| base::OnceCallback<void(int32_t, |
| cros::mojom::Camera3StreamConfigurationPtr)>& |
| callback) { |
| ASSERT_GE(2u, config->streams.size()); |
| ASSERT_LT(0u, config->streams.size()); |
| for (size_t i = 0; i < config->streams.size(); ++i) { |
| config->streams[i]->usage = 0; |
| config->streams[i]->max_buffers = 1; |
| } |
| std::move(callback).Run(0, std::move(config)); |
| } |
| |
| void ConstructFakeRequestSettings( |
| cros::mojom::Camera3RequestTemplate type, |
| base::OnceCallback<void(cros::mojom::CameraMetadataPtr)>& callback) { |
| cros::mojom::CameraMetadataPtr fake_settings = |
| cros::mojom::CameraMetadata::New(); |
| fake_settings->entry_count = 1; |
| fake_settings->entry_capacity = 1; |
| fake_settings->entries = std::vector<cros::mojom::CameraMetadataEntryPtr>(); |
| std::move(callback).Run(std::move(fake_settings)); |
| } |
| |
| void ProcessCaptureRequest(cros::mojom::Camera3CaptureRequestPtr& request, |
| base::OnceCallback<void(int32_t)>& callback) { |
| std::move(callback).Run(0); |
| |
| cros::mojom::Camera3NotifyMsgPtr msg = cros::mojom::Camera3NotifyMsg::New(); |
| msg->type = cros::mojom::Camera3MsgType::CAMERA3_MSG_SHUTTER; |
| msg->message = cros::mojom::Camera3NotifyMsgMessage::New(); |
| cros::mojom::Camera3ShutterMsgPtr shutter_msg = |
| cros::mojom::Camera3ShutterMsg::New(); |
| shutter_msg->timestamp = base::TimeTicks::Now().ToInternalValue(); |
| msg->message->set_shutter(std::move(shutter_msg)); |
| callback_ops_->Notify(std::move(msg)); |
| |
| cros::mojom::Camera3CaptureResultPtr result = |
| cros::mojom::Camera3CaptureResult::New(); |
| result->frame_number = request->frame_number; |
| result->result = cros::mojom::CameraMetadata::New(); |
| result->output_buffers = std::move(request->output_buffers); |
| result->partial_result = 1; |
| callback_ops_->ProcessCaptureResult(std::move(result)); |
| } |
| |
| void CloseMockCameraDevice(base::OnceCallback<void(int32_t)>& callback) { |
| mock_camera_device_receiver_.reset(); |
| callback_ops_.reset(); |
| std::move(callback).Run(0); |
| } |
| |
| void SetUpExpectationForHalDelegate() { |
| EXPECT_CALL(mock_camera_module_, DoGetNumberOfCameras(_)) |
| .Times(1) |
| .WillOnce( |
| Invoke(this, &CameraDeviceDelegateTest::GetNumberOfFakeCameras)); |
| EXPECT_CALL(mock_camera_module_, DoSetCallbacksAssociated(_, _)).Times(1); |
| EXPECT_CALL(mock_camera_module_, DoGetVendorTagOps(_, _)) |
| .Times(1) |
| .WillOnce(Invoke(this, &CameraDeviceDelegateTest::GetFakeVendorTagOps)); |
| EXPECT_CALL(mock_camera_module_, DoGetCameraInfo(0, _)) |
| .Times(1) |
| .WillOnce(Invoke(this, &CameraDeviceDelegateTest::GetFakeCameraInfo)); |
| } |
| |
| void SetUpExpectationUntilInitialized() { |
| SetUpExpectationForHalDelegate(); |
| EXPECT_CALL(mock_camera_module_, DoOpenDevice(0, _, _)) |
| .Times(1) |
| .WillOnce( |
| Invoke(this, &CameraDeviceDelegateTest::OpenMockCameraDevice)); |
| EXPECT_CALL(mock_camera_device_, DoInitialize(_, _)) |
| .Times(1) |
| .WillOnce(Invoke( |
| this, &CameraDeviceDelegateTest::InitializeMockCameraDevice)); |
| } |
| |
| void SetUpExpectationUntilStreamConfigured() { |
| SetUpExpectationUntilInitialized(); |
| EXPECT_CALL(mock_camera_device_, DoConfigureStreams(_, _)) |
| .Times(1) |
| .WillOnce( |
| Invoke(this, &CameraDeviceDelegateTest::ConfigureFakeStreams)); |
| EXPECT_CALL(mock_gpu_memory_buffer_manager_, |
| CreateGpuMemoryBuffer( |
| _, gfx::BufferFormat::YUV_420_BIPLANAR, |
| gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE, |
| gpu::kNullSurfaceHandle, nullptr)) |
| .Times(1) |
| .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager:: |
| CreateFakeGpuMemoryBuffer)); |
| EXPECT_CALL( |
| mock_gpu_memory_buffer_manager_, |
| CreateGpuMemoryBuffer(_, gfx::BufferFormat::R_8, |
| gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE, |
| gpu::kNullSurfaceHandle, nullptr)) |
| .Times(AtMost(1)) |
| .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager:: |
| CreateFakeGpuMemoryBuffer)); |
| EXPECT_CALL(mock_gpu_memory_buffer_manager_, |
| CreateGpuMemoryBuffer( |
| gfx::Size(kDefaultWidth, kDefaultHeight), |
| gfx::BufferFormat::YUV_420_BIPLANAR, |
| gfx::BufferUsage::VEA_READ_CAMERA_AND_CPU_READ_WRITE, |
| gpu::kNullSurfaceHandle, nullptr)) |
| .Times(1) |
| .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager:: |
| CreateFakeGpuMemoryBuffer)); |
| EXPECT_CALL(mock_gpu_memory_buffer_manager_, |
| CreateGpuMemoryBuffer( |
| gfx::Size(kJpegMaxBufferSize, 1), gfx::BufferFormat::R_8, |
| gfx::BufferUsage::CAMERA_AND_CPU_READ_WRITE, |
| gpu::kNullSurfaceHandle, nullptr)) |
| .Times(AtMost(1)) |
| .WillOnce(Invoke(&unittest_internal::MockGpuMemoryBufferManager:: |
| CreateFakeGpuMemoryBuffer)); |
| } |
| |
| void SetUpExpectationUntilCapturing( |
| unittest_internal::MockVideoCaptureClient* mock_client) { |
| SetUpExpectationUntilStreamConfigured(); |
| EXPECT_CALL(mock_camera_device_, DoConstructDefaultRequestSettings(_, _)) |
| .Times(1) |
| .WillOnce(Invoke( |
| this, &CameraDeviceDelegateTest::ConstructFakeRequestSettings)); |
| EXPECT_CALL(*mock_client, OnStarted()).Times(1); |
| } |
| |
| void SetUpExpectationForCaptureLoop() { |
| EXPECT_CALL(mock_camera_device_, DoProcessCaptureRequest(_, _)) |
| .Times(AtLeast(1)) |
| .WillOnce( |
| Invoke(this, &CameraDeviceDelegateTest::ProcessCaptureRequest)) |
| .WillRepeatedly( |
| Invoke(this, &CameraDeviceDelegateTest::ProcessCaptureRequest)); |
| } |
| |
| void SetUpExpectationForClose() { |
| EXPECT_CALL(mock_camera_device_, DoClose(_)) |
| .Times(1) |
| .WillOnce( |
| Invoke(this, &CameraDeviceDelegateTest::CloseMockCameraDevice)); |
| } |
| |
| void WaitForDeviceToClose() { |
| base::WaitableEvent device_closed( |
| base::WaitableEvent::ResetPolicy::MANUAL, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::StopAndDeAllocate, |
| camera_device_delegate_->GetWeakPtr(), |
| base::BindOnce( |
| [](base::WaitableEvent* device_closed) { |
| device_closed->Signal(); |
| }, |
| base::Unretained(&device_closed)))); |
| base::TimeDelta kWaitTimeoutSecs = base::Seconds(3); |
| EXPECT_TRUE(device_closed.TimedWait(kWaitTimeoutSecs)); |
| EXPECT_EQ(CameraDeviceContext::State::kStopped, GetState()); |
| } |
| |
| unittest_internal::NiceMockVideoCaptureClient* ResetDeviceContext() { |
| client_type_ = ClientType::kPreviewClient; |
| auto mock_client = |
| std::make_unique<unittest_internal::NiceMockVideoCaptureClient>(); |
| auto* client_ptr = mock_client.get(); |
| device_context_ = std::make_unique<CameraDeviceContext>(); |
| device_context_->AddClient(client_type_, std::move(mock_client)); |
| return client_ptr; |
| } |
| |
| void ResetDevice() { |
| device_context_->RemoveClient(client_type_); |
| ASSERT_TRUE(device_delegate_thread_.IsRunning()); |
| ASSERT_TRUE(camera_device_delegate_); |
| ASSERT_TRUE(device_delegate_thread_.task_runner()->DeleteSoon( |
| FROM_HERE, std::move(camera_device_delegate_))); |
| device_delegate_thread_.Stop(); |
| } |
| |
| void DoLoop() { |
| run_loop_ = std::make_unique<base::RunLoop>(); |
| run_loop_->Run(); |
| } |
| |
| void QuitRunLoop() { |
| VLOG(2) << "quit!"; |
| if (run_loop_) { |
| run_loop_->Quit(); |
| } |
| } |
| |
| CameraDeviceContext::State GetState() { |
| if (camera_device_delegate_->device_context_) { |
| return camera_device_delegate_->device_context_->GetState(); |
| } else { |
| // No device context means the VCD is either not started yet or already |
| // stopped. |
| return CameraDeviceContext::State::kStopped; |
| } |
| } |
| |
| protected: |
| base::test::TaskEnvironment task_environment_; |
| scoped_refptr<CameraHalDelegate> camera_hal_delegate_; |
| std::unique_ptr<CameraDeviceDelegate> camera_device_delegate_; |
| |
| testing::StrictMock<unittest_internal::MockCameraModule> mock_camera_module_; |
| testing::NiceMock<unittest_internal::MockVendorTagOps> mock_vendor_tag_ops_; |
| unittest_internal::MockGpuMemoryBufferManager mock_gpu_memory_buffer_manager_; |
| |
| testing::StrictMock<MockCameraDevice> mock_camera_device_; |
| mojo::Receiver<cros::mojom::Camera3DeviceOps> mock_camera_device_receiver_; |
| mojo::Remote<cros::mojom::Camera3CallbackOps> callback_ops_; |
| |
| base::Thread device_delegate_thread_; |
| |
| std::unique_ptr<CameraDeviceContext> device_context_; |
| ClientType client_type_; |
| |
| private: |
| base::Thread hal_delegate_thread_; |
| std::unique_ptr<base::RunLoop> run_loop_; |
| DISALLOW_COPY_AND_ASSIGN(CameraDeviceDelegateTest); |
| }; |
| |
| // Test the complete capture flow: initialize, configure stream, capture one |
| // frame, and close the device. |
| TEST_F(CameraDeviceDelegateTest, AllocateCaptureAndStop) { |
| auto* mock_client = ResetDeviceContext(); |
| mock_client->SetFrameCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| mock_client->SetQuitCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| SetUpExpectationUntilCapturing(mock_client); |
| SetUpExpectationForCaptureLoop(); |
| |
| AllocateDevice(); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::AllocateAndStart, |
| camera_device_delegate_->GetWeakPtr(), |
| GetDefaultCaptureParams(), |
| base::Unretained(device_context_.get()))); |
| |
| // Wait until a frame is received. MockVideoCaptureClient calls QuitRunLoop() |
| // to stop the run loop. |
| DoLoop(); |
| EXPECT_EQ(CameraDeviceContext::State::kCapturing, GetState()); |
| |
| SetUpExpectationForClose(); |
| |
| WaitForDeviceToClose(); |
| |
| ResetDevice(); |
| } |
| |
| // Test that the camera device delegate closes properly when StopAndDeAllocate() |
| // is called when the device is opening. The timeline is roughly: |
| // 1. AllocateAndStart() |
| // 2. Async IPC call OpenDevice() started |
| // 3. StopAndDeAllocate() |
| // 4. Async IPC call OpenDevice() finished |
| TEST_F(CameraDeviceDelegateTest, StopBeforeOpened) { |
| auto* mock_client = ResetDeviceContext(); |
| mock_client->SetQuitCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| SetUpExpectationForHalDelegate(); |
| |
| AllocateDevice(); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::AllocateAndStart, |
| camera_device_delegate_->GetWeakPtr(), |
| GetDefaultCaptureParams(), |
| base::Unretained(device_context_.get()))); |
| |
| base::WaitableEvent stop_posted; |
| auto open_device_quit_loop_cb = |
| [&](int32_t camera_id, |
| mojo::PendingReceiver<cros::mojom::Camera3DeviceOps> |
| device_ops_receiver, |
| base::OnceCallback<void(int32_t)>& callback) { |
| QuitRunLoop(); |
| // Make sure StopAndDeAllocate() is called before the device opened |
| // callback. |
| stop_posted.Wait(); |
| OpenMockCameraDevice(camera_id, std::move(device_ops_receiver), |
| callback); |
| }; |
| EXPECT_CALL(mock_camera_module_, DoOpenDevice(0, _, _)) |
| .Times(1) |
| .WillOnce(Invoke(open_device_quit_loop_cb)); |
| |
| // Wait until the QuitRunLoop() call in |mock_camera_module_->OpenDevice()|. |
| DoLoop(); |
| |
| SetUpExpectationForClose(); |
| |
| base::WaitableEvent device_closed; |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&CameraDeviceDelegate::StopAndDeAllocate, |
| camera_device_delegate_->GetWeakPtr(), |
| base::BindOnce(&base::WaitableEvent::Signal, |
| base::Unretained(&device_closed)))); |
| stop_posted.Signal(); |
| EXPECT_TRUE(device_closed.TimedWait(base::Seconds(3))); |
| EXPECT_EQ(CameraDeviceContext::State::kStopped, GetState()); |
| |
| ResetDevice(); |
| } |
| |
| // Test that the camera device delegate closes properly when StopAndDeAllocate |
| // is called right after the device is initialized. |
| TEST_F(CameraDeviceDelegateTest, StopAfterInitialized) { |
| auto* mock_client = ResetDeviceContext(); |
| mock_client->SetQuitCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| SetUpExpectationUntilInitialized(); |
| |
| AllocateDevice(); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::AllocateAndStart, |
| camera_device_delegate_->GetWeakPtr(), |
| GetDefaultCaptureParams(), |
| base::Unretained(device_context_.get()))); |
| |
| EXPECT_CALL(mock_camera_device_, DoConfigureStreams(_, _)) |
| .Times(1) |
| .WillOnce(Invoke( |
| [this](cros::mojom::Camera3StreamConfigurationPtr& config, |
| base::OnceCallback<void( |
| int32_t, cros::mojom::Camera3StreamConfigurationPtr)>& |
| callback) { |
| EXPECT_EQ(CameraDeviceContext::State::kInitialized, |
| this->GetState()); |
| std::move(callback).Run(-ENODEV, {}); |
| this->QuitRunLoop(); |
| })); |
| |
| // Wait until the QuitRunLoop call in |mock_camera_device_->ConfigureStreams|. |
| DoLoop(); |
| |
| SetUpExpectationForClose(); |
| |
| WaitForDeviceToClose(); |
| |
| ResetDevice(); |
| } |
| |
| // Test that the camera device delegate closes properly when StopAndDeAllocate |
| // is called right after the stream is configured. |
| TEST_F(CameraDeviceDelegateTest, StopAfterStreamConfigured) { |
| auto* mock_client = ResetDeviceContext(); |
| mock_client->SetQuitCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| SetUpExpectationUntilStreamConfigured(); |
| |
| AllocateDevice(); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::AllocateAndStart, |
| camera_device_delegate_->GetWeakPtr(), |
| GetDefaultCaptureParams(), |
| base::Unretained(device_context_.get()))); |
| |
| EXPECT_CALL(mock_camera_device_, DoConstructDefaultRequestSettings(_, _)) |
| .Times(1) |
| .WillOnce(Invoke( |
| [this](cros::mojom::Camera3RequestTemplate type, |
| base::OnceCallback<void(cros::mojom::CameraMetadataPtr)>& |
| callback) { |
| EXPECT_EQ(CameraDeviceContext::State::kStreamConfigured, |
| this->GetState()); |
| std::move(callback).Run({}); |
| this->QuitRunLoop(); |
| })); |
| |
| // Wait until the QuitRunLoop call in |mock_camera_device_->ConfigureStreams|. |
| DoLoop(); |
| |
| SetUpExpectationForClose(); |
| |
| WaitForDeviceToClose(); |
| |
| ResetDevice(); |
| } |
| |
| // Test that the camera device delegate handles camera device open failures |
| // correctly. |
| TEST_F(CameraDeviceDelegateTest, FailToOpenDevice) { |
| SetUpExpectationForHalDelegate(); |
| |
| AllocateDevice(); |
| |
| auto* mock_client = ResetDeviceContext(); |
| |
| auto stop_on_error = [&]() { |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::StopAndDeAllocate, |
| camera_device_delegate_->GetWeakPtr(), |
| BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, |
| base::Unretained(this))))); |
| }; |
| EXPECT_CALL(*mock_client, OnError(_, _, _)) |
| .Times(AtLeast(1)) |
| .WillRepeatedly(InvokeWithoutArgs(stop_on_error)); |
| |
| // Hold the |device_ops_receiver| to make the behavior of CameraDeviceDelegate |
| // deterministic. Otherwise the connection error handler would race with the |
| // callback of OpenDevice(), because they are in different mojo channels. |
| mojo::PendingReceiver<cros::mojom::Camera3DeviceOps> |
| device_ops_receiver_holder; |
| auto open_device_with_error_cb = |
| [&](int32_t camera_id, |
| mojo::PendingReceiver<cros::mojom::Camera3DeviceOps> |
| device_ops_receiver, |
| base::OnceCallback<void(int32_t)>& callback) { |
| device_ops_receiver_holder = std::move(device_ops_receiver); |
| std::move(callback).Run(-ENODEV); |
| }; |
| EXPECT_CALL(mock_camera_module_, DoOpenDevice(0, _, _)) |
| .Times(1) |
| .WillOnce(Invoke(open_device_with_error_cb)); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::AllocateAndStart, |
| camera_device_delegate_->GetWeakPtr(), |
| GetDefaultCaptureParams(), |
| base::Unretained(device_context_.get()))); |
| |
| // Wait unitl |camera_device_delegate_->StopAndDeAllocate| calls the |
| // QuitRunLoop callback. |
| DoLoop(); |
| |
| ResetDevice(); |
| } |
| |
| // Test that the class handles it correctly when StopAndDeAllocate is called |
| // multiple times. |
| TEST_F(CameraDeviceDelegateTest, DoubleStopAndDeAllocate) { |
| auto* mock_client = ResetDeviceContext(); |
| mock_client->SetFrameCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| mock_client->SetQuitCb(BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, base::Unretained(this)))); |
| SetUpExpectationUntilCapturing(mock_client); |
| SetUpExpectationForCaptureLoop(); |
| |
| AllocateDevice(); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::AllocateAndStart, |
| camera_device_delegate_->GetWeakPtr(), |
| GetDefaultCaptureParams(), |
| base::Unretained(device_context_.get()))); |
| |
| // Wait until a frame is received. MockVideoCaptureClient calls QuitRunLoop() |
| // to stop the run loop. |
| DoLoop(); |
| |
| EXPECT_EQ(CameraDeviceContext::State::kCapturing, GetState()); |
| |
| SetUpExpectationForClose(); |
| |
| WaitForDeviceToClose(); |
| |
| device_delegate_thread_.task_runner()->PostTask( |
| FROM_HERE, base::BindOnce(&CameraDeviceDelegate::StopAndDeAllocate, |
| camera_device_delegate_->GetWeakPtr(), |
| BindToCurrentLoop(base::BindOnce( |
| &CameraDeviceDelegateTest::QuitRunLoop, |
| base::Unretained(this))))); |
| DoLoop(); |
| |
| ResetDevice(); |
| } |
| |
| } // namespace media |