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