blob: 64654fdd56b8adced3be7fa02dd798a61998ec43 [file] [log] [blame]
// Copyright (c) 2012 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/video/capture/linux/video_capture_device_linux.h"
#include <errno.h>
#include <fcntl.h>
#if defined(OS_OPENBSD)
#include <sys/videoio.h>
#else
#include <linux/videodev2.h>
#endif
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <list>
#include <string>
#include "base/bind.h"
#include "base/file_util.h"
#include "base/stringprintf.h"
// Workaround for some device. This query of all controls magically brings
// device back to normal from bad state.
// See http://crbug.com/94134.
static void ResetCameraByEnumeratingIoctlsHACK(int fd) {
struct v4l2_queryctrl query_ctrl;
memset(&query_ctrl, 0, sizeof(query_ctrl));
for (query_ctrl.id = V4L2_CID_BASE;
query_ctrl.id < V4L2_CID_LASTP1;
query_ctrl.id++) {
ioctl(fd, VIDIOC_QUERYCTRL, &query_ctrl);
}
}
namespace media {
// Max number of video buffers VideoCaptureDeviceLinux can allocate.
enum { kMaxVideoBuffers = 2 };
// Timeout in microseconds v4l2_thread_ blocks waiting for a frame from the hw.
enum { kCaptureTimeoutUs = 200000 };
// Time to wait in milliseconds before v4l2_thread_ reschedules OnCaptureTask
// if an event is triggered (select) but no video frame is read.
enum { kCaptureSelectWaitMs = 10 };
// MJPEG is prefered if the width or height is larger than this.
enum { kMjpegWidth = 640 };
enum { kMjpegHeight = 480 };
// V4L2 color formats VideoCaptureDeviceLinux support.
static const int32 kV4l2RawFmts[] = {
V4L2_PIX_FMT_YUV420,
V4L2_PIX_FMT_YUYV
};
static VideoCaptureCapability::Format V4l2ColorToVideoCaptureColorFormat(
int32 v4l2_fourcc) {
VideoCaptureCapability::Format result = VideoCaptureCapability::kColorUnknown;
switch (v4l2_fourcc) {
case V4L2_PIX_FMT_YUV420:
result = VideoCaptureCapability::kI420;
break;
case V4L2_PIX_FMT_YUYV:
result = VideoCaptureCapability::kYUY2;
break;
case V4L2_PIX_FMT_MJPEG:
result = VideoCaptureCapability::kMJPEG;
}
DCHECK_NE(result, VideoCaptureCapability::kColorUnknown);
return result;
}
void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
int fd = -1;
// Empty the name list.
device_names->clear();
FilePath path("/dev/");
file_util::FileEnumerator enumerator(
path, false, file_util::FileEnumerator::FILES, "video*");
while (!enumerator.Next().empty()) {
file_util::FileEnumerator::FindInfo info;
enumerator.GetFindInfo(&info);
Name name;
name.unique_id = path.value() + info.filename;
if ((fd = open(name.unique_id.c_str() , O_RDONLY)) < 0) {
// Failed to open this device.
continue;
}
// Test if this is a V4L2 capture device.
v4l2_capability cap;
if ((ioctl(fd, VIDIOC_QUERYCAP, &cap) == 0) &&
(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
!(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) {
// This is a V4L2 video capture device
name.device_name = StringPrintf("%s", cap.card);
device_names->push_back(name);
}
close(fd);
}
}
VideoCaptureDevice* VideoCaptureDevice::Create(const Name& device_name) {
VideoCaptureDeviceLinux* self = new VideoCaptureDeviceLinux(device_name);
if (!self)
return NULL;
// Test opening the device driver. This is to make sure it is available.
// We will reopen it again in our worker thread when someone
// allocates the camera.
int fd = open(device_name.unique_id.c_str(), O_RDONLY);
if (fd < 0) {
DVLOG(1) << "Cannot open device";
delete self;
return NULL;
}
close(fd);
return self;
}
VideoCaptureDeviceLinux::VideoCaptureDeviceLinux(const Name& device_name)
: state_(kIdle),
observer_(NULL),
device_name_(device_name),
device_fd_(-1),
v4l2_thread_("V4L2Thread"),
buffer_pool_(NULL),
buffer_pool_size_(0) {
}
VideoCaptureDeviceLinux::~VideoCaptureDeviceLinux() {
state_ = kIdle;
// Check if the thread is running.
// This means that the device have not been DeAllocated properly.
DCHECK(!v4l2_thread_.IsRunning());
v4l2_thread_.Stop();
if (device_fd_ >= 0) {
close(device_fd_);
}
}
void VideoCaptureDeviceLinux::Allocate(int width,
int height,
int frame_rate,
EventHandler* observer) {
if (v4l2_thread_.IsRunning()) {
return; // Wrong state.
}
v4l2_thread_.Start();
v4l2_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnAllocate, base::Unretained(this),
width, height, frame_rate, observer));
}
void VideoCaptureDeviceLinux::Start() {
if (!v4l2_thread_.IsRunning()) {
return; // Wrong state.
}
v4l2_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnStart, base::Unretained(this)));
}
void VideoCaptureDeviceLinux::Stop() {
if (!v4l2_thread_.IsRunning()) {
return; // Wrong state.
}
v4l2_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnStop, base::Unretained(this)));
}
void VideoCaptureDeviceLinux::DeAllocate() {
if (!v4l2_thread_.IsRunning()) {
return; // Wrong state.
}
v4l2_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnDeAllocate,
base::Unretained(this)));
v4l2_thread_.Stop();
// Make sure no buffers are still allocated.
// This can happen (theoretically) if an error occurs when trying to stop
// the camera.
DeAllocateVideoBuffers();
}
const VideoCaptureDevice::Name& VideoCaptureDeviceLinux::device_name() {
return device_name_;
}
void VideoCaptureDeviceLinux::OnAllocate(int width,
int height,
int frame_rate,
EventHandler* observer) {
DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
observer_ = observer;
// Need to open camera with O_RDWR after Linux kernel 3.3.
if ((device_fd_ = open(device_name_.unique_id.c_str(), O_RDWR)) < 0) {
SetErrorState("Failed to open V4L2 device driver.");
return;
}
// Test if this is a V4L2 capture device.
v4l2_capability cap;
if (!((ioctl(device_fd_, VIDIOC_QUERYCAP, &cap) == 0) &&
(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) &&
!(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT))) {
// This is not a V4L2 video capture device.
close(device_fd_);
device_fd_ = -1;
SetErrorState("This is not a V4L2 video capture device");
return;
}
v4l2_format video_fmt;
memset(&video_fmt, 0, sizeof(video_fmt));
video_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
video_fmt.fmt.pix.sizeimage = 0;
video_fmt.fmt.pix.width = width;
video_fmt.fmt.pix.height = height;
// Some device failed in first VIDIOC_TRY_FMT with EBUSY or EIO.
// But second VIDIOC_TRY_FMT succeeds.
// See http://crbug.com/94134.
// For large resolutions, favour mjpeg over raw formats.
bool format_match = false;
std::list<int> v4l2_formats;
if (width > kMjpegWidth || height > kMjpegHeight) {
v4l2_formats.push_back(V4L2_PIX_FMT_MJPEG);
}
for (size_t i = 0; i < arraysize(kV4l2RawFmts); ++i) {
v4l2_formats.push_back(kV4l2RawFmts[i]);
}
for (std::list<int>::const_iterator it = v4l2_formats.begin();
it != v4l2_formats.end() && !format_match; ++it) {
video_fmt.fmt.pix.pixelformat = *it;
for (int attempt = 0; attempt < 2 && !format_match; ++attempt) {
ResetCameraByEnumeratingIoctlsHACK(device_fd_);
if (ioctl(device_fd_, VIDIOC_TRY_FMT, &video_fmt) < 0) {
if (errno != EIO)
break;
} else {
format_match = true;
}
}
}
if (!format_match) {
SetErrorState("Failed to find supported camera format.");
return;
}
// Set format and frame size now.
if (ioctl(device_fd_, VIDIOC_S_FMT, &video_fmt) < 0) {
SetErrorState("Failed to set camera format");
return;
}
// Store our current width and height.
VideoCaptureCapability current_settings;
current_settings.color = V4l2ColorToVideoCaptureColorFormat(
video_fmt.fmt.pix.pixelformat);
current_settings.width = video_fmt.fmt.pix.width;
current_settings.height = video_fmt.fmt.pix.height;
current_settings.frame_rate = frame_rate;
current_settings.expected_capture_delay = 0;
current_settings.interlaced = false;
state_ = kAllocated;
// Report the resulting frame size to the observer.
observer_->OnFrameInfo(current_settings);
}
void VideoCaptureDeviceLinux::OnDeAllocate() {
DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
// If we are in error state or capturing
// try to stop the camera.
if (state_ == kCapturing) {
OnStop();
}
if (state_ == kAllocated) {
state_ = kIdle;
}
// We need to close and open the device if we want to change the settings
// Otherwise VIDIOC_S_FMT will return error
// Sad but true.
close(device_fd_);
device_fd_ = -1;
}
void VideoCaptureDeviceLinux::OnStart() {
DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
if (state_ != kAllocated) {
return;
}
if (!AllocateVideoBuffers()) {
// Error, We can not recover.
SetErrorState("Allocate buffer failed");
return;
}
// Start UVC camera.
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(device_fd_, VIDIOC_STREAMON, &type) == -1) {
SetErrorState("VIDIOC_STREAMON failed");
return;
}
state_ = kCapturing;
// Post task to start fetching frames from v4l2.
v4l2_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnCaptureTask,
base::Unretained(this)));
}
void VideoCaptureDeviceLinux::OnStop() {
DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
state_ = kAllocated;
v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(device_fd_, VIDIOC_STREAMOFF, &type) < 0) {
SetErrorState("VIDIOC_STREAMOFF failed");
return;
}
// We don't dare to deallocate the buffers if we can't stop
// the capture device.
DeAllocateVideoBuffers();
}
void VideoCaptureDeviceLinux::OnCaptureTask() {
DCHECK_EQ(v4l2_thread_.message_loop(), MessageLoop::current());
if (state_ != kCapturing) {
return;
}
fd_set r_set;
FD_ZERO(&r_set);
FD_SET(device_fd_, &r_set);
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = kCaptureTimeoutUs;
// First argument to select is the highest numbered file descriptor +1.
// Refer to http://linux.die.net/man/2/select for more information.
int result = select(device_fd_ + 1, &r_set, NULL, NULL, &timeout);
// Check if select have failed.
if (result < 0) {
// EINTR is a signal. This is not really an error.
if (errno != EINTR) {
SetErrorState("Select failed");
return;
}
v4l2_thread_.message_loop()->PostDelayedTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnCaptureTask,
base::Unretained(this)),
base::TimeDelta::FromMilliseconds(kCaptureSelectWaitMs));
}
// Check if the driver have filled a buffer.
if (FD_ISSET(device_fd_, &r_set)) {
v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
// Dequeue a buffer.
if (ioctl(device_fd_, VIDIOC_DQBUF, &buffer) == 0) {
observer_->OnIncomingCapturedFrame(
static_cast<uint8*> (buffer_pool_[buffer.index].start),
buffer.bytesused, base::Time::Now());
// Enqueue the buffer again.
if (ioctl(device_fd_, VIDIOC_QBUF, &buffer) == -1) {
SetErrorState(
StringPrintf("Failed to enqueue capture buffer errno %d", errno));
}
} // Else wait for next event.
}
v4l2_thread_.message_loop()->PostTask(
FROM_HERE,
base::Bind(&VideoCaptureDeviceLinux::OnCaptureTask,
base::Unretained(this)));
}
bool VideoCaptureDeviceLinux::AllocateVideoBuffers() {
v4l2_requestbuffers r_buffer;
memset(&r_buffer, 0, sizeof(r_buffer));
r_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
r_buffer.memory = V4L2_MEMORY_MMAP;
r_buffer.count = kMaxVideoBuffers;
if (ioctl(device_fd_, VIDIOC_REQBUFS, &r_buffer) < 0) {
return false;
}
if (r_buffer.count > kMaxVideoBuffers) {
r_buffer.count = kMaxVideoBuffers;
}
buffer_pool_size_ = r_buffer.count;
// Map the buffers.
buffer_pool_ = new Buffer[r_buffer.count];
for (unsigned int i = 0; i < r_buffer.count; i++) {
v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = i;
if (ioctl(device_fd_, VIDIOC_QUERYBUF, &buffer) < 0) {
return false;
}
buffer_pool_[i].start = mmap(NULL, buffer.length, PROT_READ,
MAP_SHARED, device_fd_, buffer.m.offset);
if (buffer_pool_[i].start == MAP_FAILED) {
return false;
}
buffer_pool_[i].length = buffer.length;
// Enqueue the buffer in the drivers incoming queue.
if (ioctl(device_fd_, VIDIOC_QBUF, &buffer) < 0) {
return false;
}
}
return true;
}
void VideoCaptureDeviceLinux::DeAllocateVideoBuffers() {
if (!buffer_pool_)
return;
// Unmaps buffers.
for (int i = 0; i < buffer_pool_size_; i++) {
munmap(buffer_pool_[i].start, buffer_pool_[i].length);
}
v4l2_requestbuffers r_buffer;
memset(&r_buffer, 0, sizeof(r_buffer));
r_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
r_buffer.memory = V4L2_MEMORY_MMAP;
r_buffer.count = 0;
if (ioctl(device_fd_, VIDIOC_REQBUFS, &r_buffer) < 0) {
SetErrorState("Failed to reset buf.");
}
delete [] buffer_pool_;
buffer_pool_ = NULL;
buffer_pool_size_ = 0;
}
void VideoCaptureDeviceLinux::SetErrorState(const std::string& reason) {
DVLOG(1) << reason;
state_ = kError;
observer_->OnError();
}
} // namespace media