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