text: // 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;
  }
  
  static void GetListOfUsableFourCCs(bool favour_mjpeg, std::list<int>* fourccs) {
    for (size_t i = 0; i < arraysize(kV4l2RawFmts); ++i)
      fourccs->push_back(kV4l2RawFmts[i]);
    if (favour_mjpeg)
      fourccs->push_front(V4L2_PIX_FMT_MJPEG);
    else
      fourccs->push_back(V4L2_PIX_FMT_MJPEG);
  }
  
  static bool HasUsableFormats(int fd) {
    v4l2_fmtdesc fmtdesc;
    std::list<int> usable_fourccs;
  
    GetListOfUsableFourCCs(false, &usable_fourccs);
  
    memset(&fmtdesc, 0, sizeof(v4l2_fmtdesc));
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
  
    while (ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
      if (std::find(usable_fourccs.begin(), usable_fourccs.end(),
                    fmtdesc.pixelformat) != usable_fourccs.end())
        return true;
  
      fmtdesc.index++;
    }
    return false;
  }
  
  void VideoCaptureDevice::GetDeviceNames(Names* device_names) {
    int fd = -1;
  
    // Empty the name list.
    device_names->clear();
  
    base::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
        if (HasUsableFormats(fd)) {
          name.device_name = StringPrintf("%s", cap.card);
          device_names->push_back(name);
        } else {
          DVLOG(1) << "No usable formats reported by " << info.filename;
        }
      }
      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.
    bool format_match = false;
    std::list<int> v4l2_formats;
  
    // For large resolutions, favour mjpeg over raw formats.
    GetListOfUsableFourCCs(width > kMjpegWidth || height > kMjpegHeight,
                           &v4l2_formats);
  
    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(), 0, false, false);
  
        // 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
  