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