Home | History | Annotate | Download | only in login
      1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/chromeos/login/camera.h"
      6 
      7 #include <stdlib.h>
      8 #include <fcntl.h>  // low-level i/o
      9 #include <unistd.h>
     10 #include <errno.h>
     11 #include <sys/stat.h>
     12 #include <sys/types.h>
     13 #include <sys/time.h>
     14 #include <sys/mman.h>
     15 #include <sys/ioctl.h>
     16 #include <asm/types.h>  // for videodev2.h
     17 #include <linux/videodev2.h>
     18 
     19 #include <algorithm>
     20 #include <vector>
     21 
     22 #include "base/logging.h"
     23 #include "base/string_util.h"
     24 #include "base/stringprintf.h"
     25 #include "base/threading/thread.h"
     26 #include "base/time.h"
     27 #include "content/browser/browser_thread.h"
     28 #include "skia/ext/image_operations.h"
     29 #include "third_party/skia/include/core/SkBitmap.h"
     30 #include "third_party/skia/include/core/SkColorPriv.h"
     31 #include "ui/gfx/size.h"
     32 
     33 namespace chromeos {
     34 
     35 namespace {
     36 
     37 // Logs errno number and its text.
     38 void log_errno(const std::string& message) {
     39   LOG(ERROR) << message << " errno: " << errno << ", " << strerror(errno);
     40 }
     41 
     42 // Helpful wrapper around ioctl that retries it upon failure in cases when
     43 // this is appropriate.
     44 int xioctl(int fd, int request, void* arg) {
     45   int r;
     46   do {
     47     r = ioctl(fd, request, arg);
     48   } while (r == -1 && errno == EINTR);
     49   return r;
     50 }
     51 
     52 // Clips integer value to 1 byte boundaries. Saturates the result on
     53 // overflow or underflow.
     54 uint8_t clip_to_byte(int value) {
     55   if (value > 255)
     56     value = 255;
     57   if (value < 0)
     58     value = 0;
     59   return static_cast<uint8_t>(value);
     60 }
     61 
     62 // Converts color from YUV colorspace to RGB. Returns the result in Skia
     63 // format suitable for use with SkBitmap. For the formula see
     64 // "Converting between YUV and RGB" article on MSDN:
     65 // http://msdn.microsoft.com/en-us/library/ms893078.aspx
     66 uint32_t convert_yuv_to_rgba(int y, int u, int v) {
     67   int c = y - 16;
     68   int d = u - 128;
     69   int e = v - 128;
     70   uint8_t r = clip_to_byte((298 * c + 409 * e + 128) >> 8);
     71   uint8_t g = clip_to_byte((298 * c - 100 * d - 208 * e + 128) >> 8);
     72   uint8_t b = clip_to_byte((298 * c + 516 * d + 128) >> 8);
     73   return SkPackARGB32(255U, r, g, b);
     74 }
     75 
     76 // Enumerates available frame sizes for specified pixel format and picks up the
     77 // best one to set for the desired image resolution.
     78 gfx::Size get_best_frame_size(int fd,
     79                               int pixel_format,
     80                               int desired_width,
     81                               int desired_height) {
     82   v4l2_frmsizeenum size = {};
     83   size.index = 0;
     84   size.pixel_format = pixel_format;
     85   std::vector<gfx::Size> sizes;
     86   int r = xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size);
     87   while (r != -1) {
     88     if (size.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
     89       sizes.push_back(gfx::Size(size.discrete.width, size.discrete.height));
     90     }
     91     ++size.index;
     92     r = xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size);
     93   }
     94   if (sizes.empty()) {
     95     NOTREACHED();
     96     return gfx::Size(desired_width, desired_height);
     97   }
     98   for (size_t i = 0; i < sizes.size(); ++i) {
     99     if (sizes[i].width() >= desired_width &&
    100         sizes[i].height() >= desired_height)
    101       return sizes[i];
    102   }
    103   // If higher resolution is not available, choose the highest available.
    104   size_t max_size_index = 0;
    105   int max_area = sizes[0].GetArea();
    106   for (size_t i = 1; i < sizes.size(); ++i) {
    107     if (sizes[i].GetArea() > max_area) {
    108       max_size_index = i;
    109       max_area = sizes[i].GetArea();
    110     }
    111   }
    112   return sizes[max_size_index];
    113 }
    114 
    115 // Default camera device name.
    116 const char kDeviceName[] = "/dev/video0";
    117 // Default width of each frame received from the camera.
    118 const int kFrameWidth = 640;
    119 // Default height of each frame received from the camera.
    120 const int kFrameHeight = 480;
    121 // Number of buffers to request from the device.
    122 const int kRequestBuffersCount = 4;
    123 // Timeout for select() call in microseconds.
    124 const long kSelectTimeout = 1 * base::Time::kMicrosecondsPerSecond;
    125 
    126 }  // namespace
    127 
    128 ///////////////////////////////////////////////////////////////////////////////
    129 // Camera, public members:
    130 
    131 Camera::Camera(Delegate* delegate, base::Thread* thread, bool mirrored)
    132     : delegate_(delegate),
    133       thread_(thread),
    134       device_name_(kDeviceName),
    135       device_descriptor_(-1),
    136       is_capturing_(false),
    137       desired_width_(kFrameWidth),
    138       desired_height_(kFrameHeight),
    139       frame_width_(kFrameWidth),
    140       frame_height_(kFrameHeight),
    141       mirrored_(mirrored) {
    142 }
    143 
    144 Camera::~Camera() {
    145   DCHECK_EQ(-1, device_descriptor_) << "Don't forget to uninitialize camera.";
    146 }
    147 
    148 void Camera::ReportFailure() {
    149   DCHECK(IsOnCameraThread());
    150   if (device_descriptor_ == -1) {
    151     BrowserThread::PostTask(
    152         BrowserThread::UI,
    153         FROM_HERE,
    154         NewRunnableMethod(this,
    155                           &Camera::OnInitializeFailure));
    156   } else if (!is_capturing_) {
    157     BrowserThread::PostTask(
    158         BrowserThread::UI,
    159         FROM_HERE,
    160         NewRunnableMethod(this,
    161                           &Camera::OnStartCapturingFailure));
    162   } else {
    163     BrowserThread::PostTask(
    164         BrowserThread::UI,
    165         FROM_HERE,
    166         NewRunnableMethod(this,
    167                           &Camera::OnCaptureFailure));
    168   }
    169 }
    170 
    171 void Camera::Initialize(int desired_width, int desired_height) {
    172   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    173   PostCameraTask(
    174       FROM_HERE,
    175       NewRunnableMethod(this,
    176                         &Camera::DoInitialize,
    177                         desired_width,
    178                         desired_height));
    179 }
    180 
    181 void Camera::DoInitialize(int desired_width, int desired_height) {
    182   DCHECK(IsOnCameraThread());
    183 
    184   if (device_descriptor_ != -1) {
    185     LOG(WARNING) << "Camera is initialized already.";
    186     return;
    187   }
    188 
    189   int fd = OpenDevice(device_name_.c_str());
    190   if (fd == -1) {
    191     ReportFailure();
    192     return;
    193   }
    194 
    195   v4l2_capability cap;
    196   if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) {
    197     if (errno == EINVAL)
    198       LOG(ERROR) << device_name_ << " is no V4L2 device";
    199     else
    200       log_errno("VIDIOC_QUERYCAP failed.");
    201     ReportFailure();
    202     return;
    203   }
    204   if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
    205     LOG(ERROR) << device_name_ << " is no video capture device";
    206     ReportFailure();
    207     return;
    208   }
    209   if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
    210     LOG(ERROR) << device_name_ << " does not support streaming i/o";
    211     ReportFailure();
    212     return;
    213   }
    214 
    215   gfx::Size frame_size = get_best_frame_size(fd,
    216                                              V4L2_PIX_FMT_YUYV,
    217                                              desired_width,
    218                                              desired_height);
    219   v4l2_format format = {};
    220   format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    221   format.fmt.pix.width = frame_size.width();
    222   format.fmt.pix.height = frame_size.height();
    223   format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
    224   format.fmt.pix.field = V4L2_FIELD_INTERLACED;
    225   if (xioctl(fd, VIDIOC_S_FMT, &format) == -1) {
    226     LOG(ERROR) << "VIDIOC_S_FMT failed.";
    227     ReportFailure();
    228     return;
    229   }
    230 
    231   if (!InitializeReadingMode(fd)) {
    232     ReportFailure();
    233     return;
    234   }
    235 
    236   device_descriptor_ = fd;
    237   frame_width_ = format.fmt.pix.width;
    238   frame_height_ = format.fmt.pix.height;
    239   desired_width_ = desired_width;
    240   desired_height_ = desired_height;
    241   BrowserThread::PostTask(
    242       BrowserThread::UI,
    243       FROM_HERE,
    244       NewRunnableMethod(this, &Camera::OnInitializeSuccess));
    245 }
    246 
    247 void Camera::Uninitialize() {
    248   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    249   PostCameraTask(FROM_HERE, NewRunnableMethod(this, &Camera::DoUninitialize));
    250 }
    251 
    252 void Camera::DoUninitialize() {
    253   DCHECK(IsOnCameraThread());
    254   if (device_descriptor_ == -1) {
    255     LOG(WARNING) << "Calling uninitialize for uninitialized camera.";
    256     return;
    257   }
    258   DoStopCapturing();
    259   UnmapVideoBuffers();
    260   if (close(device_descriptor_) == -1)
    261     log_errno("Closing the device failed.");
    262   device_descriptor_ = -1;
    263 }
    264 
    265 void Camera::StartCapturing() {
    266   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    267   PostCameraTask(FROM_HERE,
    268                  NewRunnableMethod(this, &Camera::DoStartCapturing));
    269 }
    270 
    271 void Camera::DoStartCapturing() {
    272   DCHECK(IsOnCameraThread());
    273   if (is_capturing_) {
    274     LOG(WARNING) << "Capturing is already started.";
    275     return;
    276   }
    277 
    278   for (size_t i = 0; i < buffers_.size(); ++i) {
    279     v4l2_buffer buffer = {};
    280     buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    281     buffer.memory = V4L2_MEMORY_MMAP;
    282     buffer.index = i;
    283     if (xioctl(device_descriptor_, VIDIOC_QBUF, &buffer) == -1) {
    284       log_errno("VIDIOC_QBUF failed.");
    285       ReportFailure();
    286       return;
    287     }
    288   }
    289   v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    290   if (xioctl(device_descriptor_, VIDIOC_STREAMON, &type) == -1) {
    291     log_errno("VIDIOC_STREAMON failed.");
    292     ReportFailure();
    293     return;
    294   }
    295   // No need to post DidProcessCameraThreadMethod() as this method is
    296   // being posted instead.
    297   BrowserThread::PostTask(
    298       BrowserThread::UI,
    299       FROM_HERE,
    300       NewRunnableMethod(this,
    301                         &Camera::OnStartCapturingSuccess));
    302   is_capturing_ = true;
    303   PostCameraTask(FROM_HERE,
    304                  NewRunnableMethod(this, &Camera::OnCapture));
    305 }
    306 
    307 void Camera::StopCapturing() {
    308   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    309   PostCameraTask(FROM_HERE,
    310                  NewRunnableMethod(this, &Camera::DoStopCapturing));
    311 }
    312 
    313 void Camera::DoStopCapturing() {
    314   DCHECK(IsOnCameraThread());
    315   if (!is_capturing_) {
    316     LOG(WARNING) << "Calling StopCapturing when capturing is not started.";
    317     return;
    318   }
    319   // OnCapture must exit if this flag is not set.
    320   is_capturing_ = false;
    321   v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    322   if (xioctl(device_descriptor_, VIDIOC_STREAMOFF, &type) == -1)
    323     log_errno("VIDIOC_STREAMOFF failed.");
    324 }
    325 
    326 void Camera::GetFrame(SkBitmap* frame) {
    327   base::AutoLock lock(image_lock_);
    328   frame->swap(frame_image_);
    329 }
    330 
    331 ///////////////////////////////////////////////////////////////////////////////
    332 // Camera, private members:
    333 
    334 int Camera::OpenDevice(const char* device_name) const {
    335   DCHECK(IsOnCameraThread());
    336   struct stat st;
    337   if (stat(device_name, &st) == -1) {
    338     log_errno(base::StringPrintf("Cannot identify %s", device_name));
    339     return -1;
    340   }
    341   if (!S_ISCHR(st.st_mode)) {
    342     LOG(ERROR) << device_name << "is not a device";
    343     return -1;
    344   }
    345   int fd = open(device_name, O_RDWR | O_NONBLOCK, 0);
    346   if (fd == -1) {
    347     log_errno(base::StringPrintf("Cannot open %s", device_name));
    348     return -1;
    349   }
    350   return fd;
    351 }
    352 
    353 bool Camera::InitializeReadingMode(int fd) {
    354   DCHECK(IsOnCameraThread());
    355   v4l2_requestbuffers req;
    356   req.count = kRequestBuffersCount;
    357   req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    358   req.memory = V4L2_MEMORY_MMAP;
    359   if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
    360     if (errno == EINVAL)
    361       LOG(ERROR) << device_name_ << " does not support memory mapping.";
    362     else
    363       log_errno("VIDIOC_REQBUFS failed.");
    364     return false;
    365   }
    366   if (req.count < 2U) {
    367     LOG(ERROR) << "Insufficient buffer memory on " << device_name_;
    368     return false;
    369   }
    370   for (unsigned i = 0; i < req.count; ++i) {
    371     v4l2_buffer buffer = {};
    372     buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    373     buffer.memory = V4L2_MEMORY_MMAP;
    374     buffer.index = i;
    375     if (xioctl(fd, VIDIOC_QUERYBUF, &buffer) == -1) {
    376       log_errno("VIDIOC_QUERYBUF failed.");
    377       return false;
    378     }
    379     VideoBuffer video_buffer;
    380     video_buffer.length = buffer.length;
    381     video_buffer.start = mmap(
    382         NULL,  // Start anywhere.
    383         buffer.length,
    384         PROT_READ | PROT_WRITE,
    385         MAP_SHARED,
    386         fd,
    387         buffer.m.offset);
    388     if (video_buffer.start == MAP_FAILED) {
    389       log_errno("mmap() failed.");
    390       UnmapVideoBuffers();
    391       return false;
    392     }
    393     buffers_.push_back(video_buffer);
    394   }
    395   return true;
    396 }
    397 
    398 void Camera::UnmapVideoBuffers() {
    399   DCHECK(IsOnCameraThread());
    400   for (size_t i = 0; i < buffers_.size(); ++i) {
    401     if (munmap(buffers_[i].start, buffers_[i].length) == -1)
    402       log_errno("munmap failed.");
    403   }
    404 }
    405 
    406 void Camera::OnCapture() {
    407   DCHECK(IsOnCameraThread());
    408   if (!is_capturing_)
    409     return;
    410 
    411   do {
    412     fd_set fds;
    413     FD_ZERO(&fds);
    414     FD_SET(device_descriptor_, &fds);
    415 
    416     timeval tv = {};
    417     tv.tv_sec = kSelectTimeout / base::Time::kMicrosecondsPerSecond;
    418     tv.tv_usec = kSelectTimeout % base::Time::kMicrosecondsPerSecond;
    419 
    420     int result = select(device_descriptor_ + 1, &fds, NULL, NULL, &tv);
    421     if (result == -1) {
    422       if (errno == EINTR)
    423         continue;
    424       log_errno("select() failed.");
    425       ReportFailure();
    426       break;
    427     }
    428     if (result == 0) {
    429       LOG(ERROR) << "select() timeout.";
    430       ReportFailure();
    431       break;
    432     }
    433     // EAGAIN - continue select loop.
    434   } while (!ReadFrame());
    435 
    436   PostCameraTask(FROM_HERE,
    437                  NewRunnableMethod(this, &Camera::OnCapture));
    438 }
    439 
    440 bool Camera::ReadFrame() {
    441   DCHECK(IsOnCameraThread());
    442   v4l2_buffer buffer = {};
    443   buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    444   buffer.memory = V4L2_MEMORY_MMAP;
    445   if (xioctl(device_descriptor_, VIDIOC_DQBUF, &buffer) == -1) {
    446     // Return false only in this case to try again.
    447     if (errno == EAGAIN)
    448       return false;
    449 
    450     log_errno("VIDIOC_DQBUF failed.");
    451     ReportFailure();
    452     return true;
    453   }
    454   if (buffer.index >= buffers_.size()) {
    455     LOG(ERROR) << "Index of buffer is out of range.";
    456     ReportFailure();
    457     return true;
    458   }
    459   ProcessImage(buffers_[buffer.index].start);
    460   if (xioctl(device_descriptor_, VIDIOC_QBUF, &buffer) == -1)
    461     log_errno("VIDIOC_QBUF failed.");
    462   return true;
    463 }
    464 
    465 void Camera::ProcessImage(void* data) {
    466   DCHECK(IsOnCameraThread());
    467   // If desired resolution is higher than available, we crop the available
    468   // image to get the same aspect ratio and scale the result.
    469   int desired_width = desired_width_;
    470   int desired_height = desired_height_;
    471   if (desired_width > frame_width_ || desired_height > frame_height_) {
    472     // Compare aspect ratios of the desired and available images.
    473     // The same as desired_width / desired_height > frame_width / frame_height.
    474     if (desired_width_ * frame_height_ > frame_width_ * desired_height_) {
    475       desired_width = frame_width_;
    476       desired_height = (desired_height_ * frame_width_) / desired_width_;
    477     } else {
    478       desired_width = (desired_width_ * frame_height_) / desired_height_;
    479       desired_height = frame_height_;
    480     }
    481   }
    482   SkBitmap image;
    483   int crop_left = (frame_width_ - desired_width) / 2;
    484   int crop_right = frame_width_ - crop_left - desired_width;
    485   int crop_top = (frame_height_ - desired_height_) / 2;
    486   image.setConfig(SkBitmap::kARGB_8888_Config, desired_width, desired_height);
    487   image.allocPixels();
    488   {
    489     SkAutoLockPixels lock_image(image);
    490     // We should reflect the image from the Y axis depending on the value of
    491     // |mirrored_|. Hence variable increments and origin point.
    492     int dst_x_origin = 0;
    493     int dst_x_increment = 1;
    494     int dst_y_increment = 0;
    495     if (mirrored_) {
    496       dst_x_origin = image.width() - 1;
    497       dst_x_increment = -1;
    498       dst_y_increment = 2 * image.width();
    499     }
    500     uint32_t* dst = image.getAddr32(dst_x_origin, 0);
    501 
    502     uint32_t* src = reinterpret_cast<uint32_t*>(data) +
    503                     crop_top * (frame_width_ / 2);
    504     for (int y = 0; y < image.height(); ++y) {
    505       src += crop_left / 2;
    506       for (int x = 0; x < image.width(); x += 2) {
    507         uint32_t yuyv = *src++;
    508         uint8_t y0 = yuyv & 0xFF;
    509         uint8_t u = (yuyv >> 8) & 0xFF;
    510         uint8_t y1 = (yuyv >> 16) & 0xFF;
    511         uint8_t v = (yuyv >> 24) & 0xFF;
    512         *dst = convert_yuv_to_rgba(y0, u, v);
    513         dst += dst_x_increment;
    514         *dst = convert_yuv_to_rgba(y1, u, v);
    515         dst += dst_x_increment;
    516       }
    517       dst += dst_y_increment;
    518       src += crop_right / 2;
    519     }
    520   }
    521   if (image.width() < desired_width_ || image.height() < desired_height_) {
    522     image = skia::ImageOperations::Resize(
    523         image,
    524         skia::ImageOperations::RESIZE_LANCZOS3,
    525         desired_width_,
    526         desired_height_);
    527   }
    528   image.setIsOpaque(true);
    529   {
    530     base::AutoLock lock(image_lock_);
    531     frame_image_.swap(image);
    532   }
    533   BrowserThread::PostTask(
    534       BrowserThread::UI,
    535       FROM_HERE,
    536       NewRunnableMethod(this, &Camera::OnCaptureSuccess));
    537 }
    538 
    539 void Camera::OnInitializeSuccess() {
    540   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    541   if (delegate_)
    542     delegate_->OnInitializeSuccess();
    543 }
    544 
    545 void Camera::OnInitializeFailure() {
    546   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    547   if (delegate_)
    548     delegate_->OnInitializeFailure();
    549 }
    550 
    551 void Camera::OnStartCapturingSuccess() {
    552   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    553   if (delegate_)
    554     delegate_->OnStartCapturingSuccess();
    555 }
    556 
    557 void Camera::OnStartCapturingFailure() {
    558   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    559   if (delegate_)
    560     delegate_->OnStartCapturingFailure();
    561 }
    562 
    563 void Camera::OnCaptureSuccess() {
    564   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    565   if (delegate_)
    566     delegate_->OnCaptureSuccess();
    567 }
    568 
    569 void Camera::OnCaptureFailure() {
    570   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    571   if (delegate_)
    572     delegate_->OnCaptureFailure();
    573 }
    574 
    575 bool Camera::IsOnCameraThread() const {
    576   base::AutoLock lock(thread_lock_);
    577   return thread_ && MessageLoop::current() == thread_->message_loop();
    578 }
    579 
    580 void Camera::PostCameraTask(const tracked_objects::Location& from_here,
    581                             Task* task) {
    582   base::AutoLock lock(thread_lock_);
    583   if (!thread_)
    584     return;
    585   DCHECK(thread_->IsRunning());
    586   thread_->message_loop()->PostTask(from_here, task);
    587 }
    588 
    589 }  // namespace chromeos
    590