blob: 1c3b7a154c55e3c54a93d93a3a49bf8443cf8912 [file] [log] [blame]
// 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