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
33namespace chromeos {
34
35namespace {
36
37// Logs errno number and its text.
38void 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.
44int 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.
54uint8_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
66uint32_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.
78gfx::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.
116const char kDeviceName[] = "/dev/video0";
117// Default width of each frame received from the camera.
118const int kFrameWidth = 640;
119// Default height of each frame received from the camera.
120const int kFrameHeight = 480;
121// Number of buffers to request from the device.
122const int kRequestBuffersCount = 4;
123// Timeout for select() call in microseconds.
124const long kSelectTimeout = 1 * base::Time::kMicrosecondsPerSecond;
125
126}  // namespace
127
128///////////////////////////////////////////////////////////////////////////////
129// Camera, public members:
130
131Camera::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
144Camera::~Camera() {
145  DCHECK_EQ(-1, device_descriptor_) << "Don't forget to uninitialize camera.";
146}
147
148void 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
171void 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
181void 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
247void Camera::Uninitialize() {
248  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
249  PostCameraTask(FROM_HERE, NewRunnableMethod(this, &Camera::DoUninitialize));
250}
251
252void 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
265void Camera::StartCapturing() {
266  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
267  PostCameraTask(FROM_HERE,
268                 NewRunnableMethod(this, &Camera::DoStartCapturing));
269}
270
271void 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
307void Camera::StopCapturing() {
308  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
309  PostCameraTask(FROM_HERE,
310                 NewRunnableMethod(this, &Camera::DoStopCapturing));
311}
312
313void 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
326void Camera::GetFrame(SkBitmap* frame) {
327  base::AutoLock lock(image_lock_);
328  frame->swap(frame_image_);
329}
330
331///////////////////////////////////////////////////////////////////////////////
332// Camera, private members:
333
334int 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
353bool 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
398void 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
406void 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
440bool 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
465void 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
539void Camera::OnInitializeSuccess() {
540  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
541  if (delegate_)
542    delegate_->OnInitializeSuccess();
543}
544
545void Camera::OnInitializeFailure() {
546  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
547  if (delegate_)
548    delegate_->OnInitializeFailure();
549}
550
551void Camera::OnStartCapturingSuccess() {
552  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
553  if (delegate_)
554    delegate_->OnStartCapturingSuccess();
555}
556
557void Camera::OnStartCapturingFailure() {
558  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559  if (delegate_)
560    delegate_->OnStartCapturingFailure();
561}
562
563void Camera::OnCaptureSuccess() {
564  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
565  if (delegate_)
566    delegate_->OnCaptureSuccess();
567}
568
569void Camera::OnCaptureFailure() {
570  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
571  if (delegate_)
572    delegate_->OnCaptureFailure();
573}
574
575bool Camera::IsOnCameraThread() const {
576  base::AutoLock lock(thread_lock_);
577  return thread_ && MessageLoop::current() == thread_->message_loop();
578}
579
580void 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