| // 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 |