Commit f998ad7f authored by Christian Fremerey's avatar Christian Fremerey Committed by Commit Bot

[Video Capture Linux] Add FakeV4L2Impl and a simple first test case

Adds a class FakeV4L2Impl for replacing the actual V4L2 Apis in unit testing.
Adds a simple first test case for enumerating a single fake device.

This CL is part of a series, see Design Doc at
https://docs.google.com/document/d/1ihGDZloUGdDpZ5XfmiI3AcqsSxOP9kOe5GxXOTqpHW4/edit?usp=sharing

Test: capture_unittests --gtest_filter=VideoCaptureDeviceFactoryLinuxTest
Bug: 768887
Change-Id: I7db4bed17453c4636158b9660ad1a5da4d74781c
Reviewed-on: https://chromium-review.googlesource.com/1123021
Commit-Queue: Christian Fremerey <chfremer@chromium.org>
Reviewed-by: default avatarEmircan Uysaler <emircan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#576138}
parent cc0c047f
...@@ -289,7 +289,10 @@ test("capture_unittests") { ...@@ -289,7 +289,10 @@ test("capture_unittests") {
"video/fake_video_capture_device_unittest.cc", "video/fake_video_capture_device_unittest.cc",
"video/file_video_capture_device_unittest.cc", "video/file_video_capture_device_unittest.cc",
"video/linux/camera_config_chromeos_unittest.cc", "video/linux/camera_config_chromeos_unittest.cc",
"video/linux/fake_v4l2_impl.cc",
"video/linux/fake_v4l2_impl.h",
"video/linux/v4l2_capture_delegate_unittest.cc", "video/linux/v4l2_capture_delegate_unittest.cc",
"video/linux/video_capture_device_factory_linux_unittest.cc",
"video/mac/video_capture_device_factory_mac_unittest.mm", "video/mac/video_capture_device_factory_mac_unittest.mm",
"video/mock_gpu_memory_buffer_manager.cc", "video/mock_gpu_memory_buffer_manager.cc",
"video/mock_gpu_memory_buffer_manager.h", "video/mock_gpu_memory_buffer_manager.h",
......
// 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 "media/capture/video/linux/fake_v4l2_impl.h"
#include <linux/videodev2.h>
#include <string.h>
#include "base/stl_util.h"
#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + (c))
namespace media {
static const int kInvalidId = -1;
static const int kSuccessReturnValue = 0;
static const int kErrorReturnValue = -1;
class FakeV4L2Impl::OpenedDevice {
public:
explicit OpenedDevice(const std::string& device_id, int open_flags)
: device_id_(device_id), open_flags_(open_flags) {}
const std::string& device_id() const { return device_id_; }
int open_flags() const { return open_flags_; }
private:
const std::string device_id_;
const int open_flags_;
};
FakeV4L2Impl::FakeV4L2Impl() : next_id_to_return_from_open_(1) {}
FakeV4L2Impl::~FakeV4L2Impl() = default;
void FakeV4L2Impl::AddDevice(const std::string& device_name,
const FakeV4L2DeviceConfig& config) {
device_configs_.emplace(device_name, config);
}
int FakeV4L2Impl::open(const char* device_name, int flags) {
std::string device_name_as_string(device_name);
if (!base::ContainsKey(device_configs_, device_name_as_string))
return kInvalidId;
auto id_iter = device_name_to_open_id_map_.find(device_name_as_string);
if (id_iter != device_name_to_open_id_map_.end()) {
// Device is already open
return kInvalidId;
}
auto device_id = next_id_to_return_from_open_++;
device_name_to_open_id_map_.emplace(device_name_as_string, device_id);
opened_devices_.emplace(
device_id, std::make_unique<OpenedDevice>(device_name_as_string, flags));
return device_id;
}
int FakeV4L2Impl::close(int fd) {
auto device_iter = opened_devices_.find(fd);
if (device_iter == opened_devices_.end())
return kErrorReturnValue;
device_name_to_open_id_map_.erase(device_iter->second->device_id());
opened_devices_.erase(device_iter->first);
return kSuccessReturnValue;
}
int FakeV4L2Impl::ioctl(int fd, int request, void* argp) {
auto device_iter = opened_devices_.find(fd);
if (device_iter == opened_devices_.end())
return EBADF;
auto& opened_device = device_iter->second;
auto& device_config = device_configs_.at(opened_device->device_id());
switch (request) {
case VIDIOC_ENUM_FMT: {
auto* fmtdesc = reinterpret_cast<v4l2_fmtdesc*>(argp);
if (fmtdesc->index > 0u) {
// We only support a single format for now.
return EINVAL;
}
if (fmtdesc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
// We only support video capture.
return EINVAL;
}
fmtdesc->flags = 0u;
strcpy(reinterpret_cast<char*>(fmtdesc->description), "YUV420");
fmtdesc->pixelformat = V4L2_PIX_FMT_YUV420;
memset(fmtdesc->reserved, 0, 4);
return kSuccessReturnValue;
}
case VIDIOC_QUERYCAP: {
auto* cap = reinterpret_cast<v4l2_capability*>(argp);
strcpy(reinterpret_cast<char*>(cap->driver), "FakeV4L2");
CHECK(device_config.descriptor.display_name().size() < 31);
strcpy(reinterpret_cast<char*>(cap->driver),
device_config.descriptor.display_name().c_str());
cap->bus_info[0] = 0;
// Provide arbitrary version info
cap->version = KERNEL_VERSION(1, 0, 0);
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE;
memset(cap->reserved, 0, 4);
return kSuccessReturnValue;
}
case VIDIOC_STREAMON:
case VIDIOC_STREAMOFF:
NOTIMPLEMENTED();
return kSuccessReturnValue;
case VIDIOC_CROPCAP:
case VIDIOC_DBG_G_REGISTER:
case VIDIOC_DBG_S_REGISTER:
case VIDIOC_ENCODER_CMD:
case VIDIOC_TRY_ENCODER_CMD:
case VIDIOC_ENUMAUDIO:
case VIDIOC_ENUMAUDOUT:
case VIDIOC_ENUM_FRAMESIZES:
case VIDIOC_ENUM_FRAMEINTERVALS:
case VIDIOC_ENUMINPUT:
case VIDIOC_ENUMOUTPUT:
case VIDIOC_ENUMSTD:
case VIDIOC_G_AUDIO:
case VIDIOC_S_AUDIO:
case VIDIOC_G_AUDOUT:
case VIDIOC_S_AUDOUT:
case VIDIOC_G_CROP:
case VIDIOC_S_CROP:
case VIDIOC_G_CTRL:
case VIDIOC_S_CTRL:
case VIDIOC_G_ENC_INDEX:
case VIDIOC_G_EXT_CTRLS:
case VIDIOC_S_EXT_CTRLS:
case VIDIOC_TRY_EXT_CTRLS:
case VIDIOC_G_FBUF:
case VIDIOC_S_FBUF:
case VIDIOC_G_FMT:
case VIDIOC_S_FMT:
case VIDIOC_TRY_FMT:
case VIDIOC_G_FREQUENCY:
case VIDIOC_S_FREQUENCY:
case VIDIOC_G_INPUT:
case VIDIOC_S_INPUT:
case VIDIOC_G_JPEGCOMP:
case VIDIOC_S_JPEGCOMP:
case VIDIOC_G_MODULATOR:
case VIDIOC_S_MODULATOR:
case VIDIOC_G_OUTPUT:
case VIDIOC_S_OUTPUT:
case VIDIOC_G_PARM:
case VIDIOC_S_PARM:
case VIDIOC_G_PRIORITY:
case VIDIOC_S_PRIORITY:
case VIDIOC_G_SLICED_VBI_CAP:
case VIDIOC_G_STD:
case VIDIOC_S_STD:
case VIDIOC_G_TUNER:
case VIDIOC_S_TUNER:
case VIDIOC_LOG_STATUS:
case VIDIOC_OVERLAY:
case VIDIOC_QBUF:
case VIDIOC_DQBUF:
case VIDIOC_QUERYBUF:
case VIDIOC_QUERYCTRL:
case VIDIOC_QUERYMENU:
case VIDIOC_QUERYSTD:
case VIDIOC_REQBUFS:
case VIDIOC_S_HW_FREQ_SEEK:
// Unsupported |request| code.
NOTREACHED() << "Unsupported request code " << request;
return kErrorReturnValue;
}
// Invalid |request|.
NOTREACHED();
return kErrorReturnValue;
}
void* FakeV4L2Impl::mmap(void* start,
size_t length,
int prot,
int flags,
int fd,
off_t offset) {
NOTREACHED();
return nullptr;
}
int FakeV4L2Impl::munmap(void* start, size_t length) {
NOTREACHED();
return kErrorReturnValue;
}
int FakeV4L2Impl::poll(struct pollfd* ufds, unsigned int nfds, int timeout) {
NOTREACHED();
return kErrorReturnValue;
}
} // namespace media
// 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.
#ifndef MEDIA_CAPTURE_VIDEO_LINUX_FAKE_V4L2_IMPL_H_
#define MEDIA_CAPTURE_VIDEO_LINUX_FAKE_V4L2_IMPL_H_
#include <map>
#include <string>
#include "media/capture/capture_export.h"
#include "media/capture/video/linux/v4l2_capture_device.h"
#include "media/capture/video/video_capture_device_descriptor.h"
namespace media {
struct FakeV4L2DeviceConfig {
FakeV4L2DeviceConfig(const VideoCaptureDeviceDescriptor& descriptor)
: descriptor(descriptor) {}
const VideoCaptureDeviceDescriptor descriptor;
};
// Implementation of V4L2CaptureDevice interface that allows configuring fake
// devices useful for testing.
class CAPTURE_EXPORT FakeV4L2Impl : public V4L2CaptureDevice {
public:
FakeV4L2Impl();
void AddDevice(const std::string& device_name,
const FakeV4L2DeviceConfig& config);
// Implementation of V4L2CaptureDevice interface:
int open(const char* device_name, int flags) override;
int close(int fd) override;
int ioctl(int fd, int request, void* argp) override;
void* mmap(void* start,
size_t length,
int prot,
int flags,
int fd,
off_t offset) override;
int munmap(void* start, size_t length) override;
int poll(struct pollfd* ufds, unsigned int nfds, int timeout) override;
protected:
~FakeV4L2Impl() override;
private:
class OpenedDevice;
int next_id_to_return_from_open_;
std::map<std::string, FakeV4L2DeviceConfig> device_configs_;
std::map<std::string, int> device_name_to_open_id_map_;
std::map<int /*value returned by open()*/, std::unique_ptr<OpenedDevice>>
opened_devices_;
};
} // namespace media
#endif // MEDIA_CAPTURE_VIDEO_LINUX_FAKE_V4L2_IMPL_H_
...@@ -118,6 +118,14 @@ VideoCaptureDeviceFactoryLinux::VideoCaptureDeviceFactoryLinux( ...@@ -118,6 +118,14 @@ VideoCaptureDeviceFactoryLinux::VideoCaptureDeviceFactoryLinux(
VideoCaptureDeviceFactoryLinux::~VideoCaptureDeviceFactoryLinux() = default; VideoCaptureDeviceFactoryLinux::~VideoCaptureDeviceFactoryLinux() = default;
void VideoCaptureDeviceFactoryLinux::SetV4L2EnvironmentForTesting(
scoped_refptr<V4L2CaptureDevice> v4l2,
std::unique_ptr<VideoCaptureDeviceFactoryLinux::DeviceProvider>
device_provider) {
v4l2_ = std::move(v4l2);
device_provider_ = std::move(device_provider);
}
std::unique_ptr<VideoCaptureDevice> std::unique_ptr<VideoCaptureDevice>
VideoCaptureDeviceFactoryLinux::CreateDevice( VideoCaptureDeviceFactoryLinux::CreateDevice(
const VideoCaptureDeviceDescriptor& device_descriptor) { const VideoCaptureDeviceDescriptor& device_descriptor) {
......
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
#include "base/macros.h" #include "base/macros.h"
#include "base/single_thread_task_runner.h" #include "base/single_thread_task_runner.h"
#include "media/capture/video/linux/v4l2_capture_device_impl.h" #include "media/capture/video/linux/v4l2_capture_device.h"
#include "media/capture/video_capture_types.h" #include "media/capture/video_capture_types.h"
namespace media { namespace media {
...@@ -33,6 +33,10 @@ class CAPTURE_EXPORT VideoCaptureDeviceFactoryLinux ...@@ -33,6 +33,10 @@ class CAPTURE_EXPORT VideoCaptureDeviceFactoryLinux
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner); scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
~VideoCaptureDeviceFactoryLinux() override; ~VideoCaptureDeviceFactoryLinux() override;
void SetV4L2EnvironmentForTesting(
scoped_refptr<V4L2CaptureDevice> v4l2,
std::unique_ptr<DeviceProvider> device_provider);
std::unique_ptr<VideoCaptureDevice> CreateDevice( std::unique_ptr<VideoCaptureDevice> CreateDevice(
const VideoCaptureDeviceDescriptor& device_descriptor) override; const VideoCaptureDeviceDescriptor& device_descriptor) override;
void GetDeviceDescriptors( void GetDeviceDescriptors(
......
// Copyright 2016 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/linux/video_capture_device_factory_linux.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "media/capture/video/linux/fake_v4l2_impl.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
namespace media {
class DescriptorDeviceProvider
: public VideoCaptureDeviceFactoryLinux::DeviceProvider {
public:
void AddDevice(const VideoCaptureDeviceDescriptor& descriptor) {
descriptors_.emplace_back(descriptor);
}
void GetDeviceIds(std::vector<std::string>* target_container) override {
for (const auto& entry : descriptors_) {
target_container->emplace_back(entry.device_id);
}
}
std::string GetDeviceModelId(const std::string& device_id) override {
auto iter =
std::find_if(descriptors_.begin(), descriptors_.end(),
[&device_id](const VideoCaptureDeviceDescriptor& val) {
return val.device_id == device_id;
});
if (iter == descriptors_.end())
CHECK(false) << "Unknown device_id " << device_id;
return iter->model_id;
}
std::string GetDeviceDisplayName(const std::string& device_id) override {
auto iter =
std::find_if(descriptors_.begin(), descriptors_.end(),
[&device_id](const VideoCaptureDeviceDescriptor& val) {
return val.device_id == device_id;
});
if (iter == descriptors_.end())
CHECK(false) << "Unknown device_id " << device_id;
return iter->display_name();
}
private:
std::vector<VideoCaptureDeviceDescriptor> descriptors_;
};
class VideoCaptureDeviceFactoryLinuxTest : public ::testing::Test {
public:
VideoCaptureDeviceFactoryLinuxTest() {}
~VideoCaptureDeviceFactoryLinuxTest() override = default;
void SetUp() override {
factory_ = std::make_unique<VideoCaptureDeviceFactoryLinux>(
base::ThreadTaskRunnerHandle::Get());
scoped_refptr<FakeV4L2Impl> fake_v4l2(new FakeV4L2Impl());
fake_v4l2_ = fake_v4l2.get();
auto fake_device_provider = std::make_unique<DescriptorDeviceProvider>();
fake_device_provider_ = fake_device_provider.get();
factory_->SetV4L2EnvironmentForTesting(std::move(fake_v4l2),
std::move(fake_device_provider));
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
FakeV4L2Impl* fake_v4l2_;
DescriptorDeviceProvider* fake_device_provider_;
std::unique_ptr<VideoCaptureDeviceFactoryLinux> factory_;
};
// The ChromeOS code path still does some hard-coded reading of files that
// depend on a real device being present.
// TODO(chfremer): Enable this for ChromeOS after making the ChromeOS code path
// testable.
#if !defined(OS_CHROMEOS)
TEST_F(VideoCaptureDeviceFactoryLinuxTest, EnumerateSingleFakeV4L2Device) {
// Setup
const std::string stub_display_name = "Fake Device 0";
const std::string stub_device_id = "/dev/video0";
VideoCaptureDeviceDescriptor descriptor(stub_display_name, stub_device_id);
fake_device_provider_->AddDevice(descriptor);
fake_v4l2_->AddDevice(stub_device_id, FakeV4L2DeviceConfig(descriptor));
// Exercise
VideoCaptureDeviceDescriptors descriptors;
factory_->GetDeviceDescriptors(&descriptors);
// Verification
ASSERT_EQ(1u, descriptors.size());
ASSERT_EQ(stub_device_id, descriptors[0].device_id);
ASSERT_EQ(stub_display_name, descriptors[0].display_name());
}
#endif
}; // namespace media
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment