• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
log_errno(const std::string & message)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.
xioctl(int fd,int request,void * arg)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.
clip_to_byte(int value)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
convert_yuv_to_rgba(int y,int u,int v)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.
get_best_frame_size(int fd,int pixel_format,int desired_width,int desired_height)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 
Camera(Delegate * delegate,base::Thread * thread,bool mirrored)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 
~Camera()144 Camera::~Camera() {
145   DCHECK_EQ(-1, device_descriptor_) << "Don't forget to uninitialize camera.";
146 }
147 
ReportFailure()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 
Initialize(int desired_width,int desired_height)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 
DoInitialize(int desired_width,int desired_height)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 
Uninitialize()247 void Camera::Uninitialize() {
248   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
249   PostCameraTask(FROM_HERE, NewRunnableMethod(this, &Camera::DoUninitialize));
250 }
251 
DoUninitialize()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 
StartCapturing()265 void Camera::StartCapturing() {
266   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
267   PostCameraTask(FROM_HERE,
268                  NewRunnableMethod(this, &Camera::DoStartCapturing));
269 }
270 
DoStartCapturing()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 
StopCapturing()307 void Camera::StopCapturing() {
308   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
309   PostCameraTask(FROM_HERE,
310                  NewRunnableMethod(this, &Camera::DoStopCapturing));
311 }
312 
DoStopCapturing()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 
GetFrame(SkBitmap * frame)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 
OpenDevice(const char * device_name) const334 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 
InitializeReadingMode(int fd)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 
UnmapVideoBuffers()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 
OnCapture()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 
ReadFrame()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 
ProcessImage(void * data)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 
OnInitializeSuccess()539 void Camera::OnInitializeSuccess() {
540   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
541   if (delegate_)
542     delegate_->OnInitializeSuccess();
543 }
544 
OnInitializeFailure()545 void Camera::OnInitializeFailure() {
546   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
547   if (delegate_)
548     delegate_->OnInitializeFailure();
549 }
550 
OnStartCapturingSuccess()551 void Camera::OnStartCapturingSuccess() {
552   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
553   if (delegate_)
554     delegate_->OnStartCapturingSuccess();
555 }
556 
OnStartCapturingFailure()557 void Camera::OnStartCapturingFailure() {
558   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
559   if (delegate_)
560     delegate_->OnStartCapturingFailure();
561 }
562 
OnCaptureSuccess()563 void Camera::OnCaptureSuccess() {
564   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
565   if (delegate_)
566     delegate_->OnCaptureSuccess();
567 }
568 
OnCaptureFailure()569 void Camera::OnCaptureFailure() {
570   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
571   if (delegate_)
572     delegate_->OnCaptureFailure();
573 }
574 
IsOnCameraThread() const575 bool Camera::IsOnCameraThread() const {
576   base::AutoLock lock(thread_lock_);
577   return thread_ && MessageLoop::current() == thread_->message_loop();
578 }
579 
PostCameraTask(const tracked_objects::Location & from_here,Task * task)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