| OLD | NEW | 
|---|
|  | (Empty) | 
| 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 "gfx/size.h" |  | 
| 25 #include "skia/ext/image_operations.h" |  | 
| 26 #include "third_party/skia/include/core/SkBitmap.h" |  | 
| 27 #include "third_party/skia/include/core/SkColorPriv.h" |  | 
| 28 |  | 
| 29 namespace chromeos { |  | 
| 30 |  | 
| 31 namespace { |  | 
| 32 |  | 
| 33 // Logs errno number and its text. |  | 
| 34 void log_errno(const std::string& message) { |  | 
| 35   LOG(ERROR) << message << " errno: " << errno << ", " << strerror(errno); |  | 
| 36 } |  | 
| 37 |  | 
| 38 // Helpful wrapper around ioctl that retries it upon failure in cases when |  | 
| 39 // this is appropriate. |  | 
| 40 int xioctl(int fd, int request, void* arg) { |  | 
| 41   int r; |  | 
| 42   do { |  | 
| 43     r = ioctl(fd, request, arg); |  | 
| 44   } while (r == -1 && errno == EINTR); |  | 
| 45   return r; |  | 
| 46 } |  | 
| 47 |  | 
| 48 // Clips integer value to 1 byte boundaries. Saturates the result on |  | 
| 49 // overflow or underflow. |  | 
| 50 uint8_t clip_to_byte(int value) { |  | 
| 51   if (value > 255) |  | 
| 52     value = 255; |  | 
| 53   if (value < 0) |  | 
| 54     value = 0; |  | 
| 55   return static_cast<uint8_t>(value); |  | 
| 56 } |  | 
| 57 |  | 
| 58 // Converts color from YUV colorspace to RGB. Returns the result in Skia |  | 
| 59 // format suitable for use with SkBitmap. For the formula see |  | 
| 60 // "Converting between YUV and RGB" article on MSDN: |  | 
| 61 // http://msdn.microsoft.com/en-us/library/ms893078.aspx |  | 
| 62 uint32_t convert_yuv_to_rgba(int y, int u, int v) { |  | 
| 63   int c = y - 16; |  | 
| 64   int d = u - 128; |  | 
| 65   int e = v - 128; |  | 
| 66   uint8_t r = clip_to_byte((298 * c + 409 * e + 128) >> 8); |  | 
| 67   uint8_t g = clip_to_byte((298 * c - 100 * d - 208 * e + 128) >> 8); |  | 
| 68   uint8_t b = clip_to_byte((298 * c + 516 * d + 128) >> 8); |  | 
| 69   return SkPackARGB32(255U, r, g, b); |  | 
| 70 } |  | 
| 71 |  | 
| 72 // Enumerates available frame sizes for specified pixel format and picks up the |  | 
| 73 // best one to set for the desired image resolution. |  | 
| 74 gfx::Size get_best_frame_size(int fd, |  | 
| 75                               int pixel_format, |  | 
| 76                               int desired_width, |  | 
| 77                               int desired_height) { |  | 
| 78   v4l2_frmsizeenum size = {}; |  | 
| 79   size.index = 0; |  | 
| 80   size.pixel_format = pixel_format; |  | 
| 81   std::vector<gfx::Size> sizes; |  | 
| 82   int r = xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size); |  | 
| 83   while (r != -1) { |  | 
| 84     if (size.type == V4L2_FRMSIZE_TYPE_DISCRETE) { |  | 
| 85       sizes.push_back(gfx::Size(size.discrete.width, size.discrete.height)); |  | 
| 86     } |  | 
| 87     ++size.index; |  | 
| 88     r = xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &size); |  | 
| 89   } |  | 
| 90   if (sizes.empty()) { |  | 
| 91     NOTREACHED(); |  | 
| 92     return gfx::Size(desired_width, desired_height); |  | 
| 93   } |  | 
| 94   for (size_t i = 0; i < sizes.size(); ++i) { |  | 
| 95     if (sizes[i].width() >= desired_width && |  | 
| 96         sizes[i].height() >= desired_height) |  | 
| 97       return sizes[i]; |  | 
| 98   } |  | 
| 99   // If higher resolution is not available, choose the highest available. |  | 
| 100   size_t max_size_index = 0; |  | 
| 101   int max_area = sizes[0].GetArea(); |  | 
| 102   for (size_t i = 1; i < sizes.size(); ++i) { |  | 
| 103     if (sizes[i].GetArea() > max_area) { |  | 
| 104       max_size_index = i; |  | 
| 105       max_area = sizes[i].GetArea(); |  | 
| 106     } |  | 
| 107   } |  | 
| 108   return sizes[max_size_index]; |  | 
| 109 } |  | 
| 110 |  | 
| 111 // Default camera device name. |  | 
| 112 const char kDeviceName[] = "/dev/video0"; |  | 
| 113 // Default width of each frame received from the camera. |  | 
| 114 const int kFrameWidth = 640; |  | 
| 115 // Default height of each frame received from the camera. |  | 
| 116 const int kFrameHeight = 480; |  | 
| 117 // Number of buffers to request from the device. |  | 
| 118 const int kRequestBuffersCount = 4; |  | 
| 119 |  | 
| 120 }  // namespace |  | 
| 121 |  | 
| 122 /////////////////////////////////////////////////////////////////////////////// |  | 
| 123 // Camera, public members: |  | 
| 124 |  | 
| 125 Camera::Camera(Delegate* delegate) |  | 
| 126     : delegate_(delegate), |  | 
| 127       device_name_(kDeviceName), |  | 
| 128       device_descriptor_(-1), |  | 
| 129       desired_width_(kFrameWidth), |  | 
| 130       desired_height_(kFrameHeight), |  | 
| 131       frame_width_(kFrameWidth), |  | 
| 132       frame_height_(kFrameHeight) { |  | 
| 133 } |  | 
| 134 |  | 
| 135 Camera::~Camera() { |  | 
| 136   Uninitialize(); |  | 
| 137 } |  | 
| 138 |  | 
| 139 bool Camera::Initialize(int desired_width, int desired_height) { |  | 
| 140   if (device_descriptor_ != -1) { |  | 
| 141     LOG(WARNING) << "Camera is initialized already."; |  | 
| 142     return true; |  | 
| 143   } |  | 
| 144   int fd = OpenDevice(device_name_.c_str()); |  | 
| 145   if (fd == -1) |  | 
| 146     return false; |  | 
| 147 |  | 
| 148   v4l2_capability cap; |  | 
| 149   if (xioctl(fd, VIDIOC_QUERYCAP, &cap) == -1) { |  | 
| 150     if (errno == EINVAL) |  | 
| 151       LOG(ERROR) << device_name_ << " is no V4L2 device"; |  | 
| 152     else |  | 
| 153       log_errno("VIDIOC_QUERYCAP failed."); |  | 
| 154     return false; |  | 
| 155   } |  | 
| 156   if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) { |  | 
| 157     LOG(ERROR) << device_name_ << " is no video capture device"; |  | 
| 158     return false; |  | 
| 159   } |  | 
| 160   if (!(cap.capabilities & V4L2_CAP_STREAMING)) { |  | 
| 161     LOG(ERROR) << device_name_ << " does not support streaming i/o"; |  | 
| 162     return false; |  | 
| 163   } |  | 
| 164 |  | 
| 165   gfx::Size frame_size = get_best_frame_size(fd, |  | 
| 166                                              V4L2_PIX_FMT_YUYV, |  | 
| 167                                              desired_width, |  | 
| 168                                              desired_height); |  | 
| 169   v4l2_format format = {}; |  | 
| 170   format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 171   format.fmt.pix.width = frame_size.width(); |  | 
| 172   format.fmt.pix.height = frame_size.height(); |  | 
| 173   format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; |  | 
| 174   format.fmt.pix.field = V4L2_FIELD_INTERLACED; |  | 
| 175   if (xioctl(fd, VIDIOC_S_FMT, &format) == -1) { |  | 
| 176     LOG(ERROR) << "VIDIOC_S_FMT failed."; |  | 
| 177     return false; |  | 
| 178   } |  | 
| 179 |  | 
| 180   if (!InitializeReadingMode(fd)) |  | 
| 181     return false; |  | 
| 182 |  | 
| 183   device_descriptor_ = fd; |  | 
| 184   frame_width_ = frame_size.width(); |  | 
| 185   frame_height_ = frame_size.height(); |  | 
| 186   desired_width_ = desired_width; |  | 
| 187   desired_height_ = desired_height; |  | 
| 188   return true; |  | 
| 189 } |  | 
| 190 |  | 
| 191 void Camera::Uninitialize() { |  | 
| 192   if (device_descriptor_ == -1) { |  | 
| 193     LOG(WARNING) << "Calling uninitialize twice."; |  | 
| 194     return; |  | 
| 195   } |  | 
| 196   StopCapturing(); |  | 
| 197   UnmapVideoBuffers(); |  | 
| 198   if (close(device_descriptor_) == -1) |  | 
| 199     log_errno("Closing the device failed."); |  | 
| 200   device_descriptor_ = -1; |  | 
| 201 } |  | 
| 202 |  | 
| 203 bool Camera::StartCapturing(const base::TimeDelta& interval) { |  | 
| 204   if (timer_.IsRunning()) { |  | 
| 205     LOG(ERROR) << "Capturing is already started."; |  | 
| 206     return false; |  | 
| 207   } |  | 
| 208   for (size_t i = 0; i < buffers_.size(); ++i) { |  | 
| 209     v4l2_buffer buffer = {}; |  | 
| 210     buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 211     buffer.memory = V4L2_MEMORY_MMAP; |  | 
| 212     buffer.index = i; |  | 
| 213     if (xioctl(device_descriptor_, VIDIOC_QBUF, &buffer) == -1) { |  | 
| 214       log_errno("VIDIOC_QBUF failed."); |  | 
| 215       return false; |  | 
| 216     } |  | 
| 217   } |  | 
| 218   v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 219   if (xioctl(device_descriptor_, VIDIOC_STREAMON, &type) == -1) { |  | 
| 220     log_errno("VIDIOC_STREAMON failed."); |  | 
| 221     return false; |  | 
| 222   } |  | 
| 223   timer_.Start(interval, this, &Camera::OnCapture); |  | 
| 224   return true; |  | 
| 225 } |  | 
| 226 |  | 
| 227 void Camera::StopCapturing() { |  | 
| 228   if (!timer_.IsRunning()) { |  | 
| 229     LOG(WARNING) << "Calling StopCapturing twice."; |  | 
| 230     return; |  | 
| 231   } |  | 
| 232   timer_.Stop(); |  | 
| 233   v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 234   if (xioctl(device_descriptor_, VIDIOC_STREAMOFF, &type) == -1) |  | 
| 235     log_errno("VIDIOC_STREAMOFF failed."); |  | 
| 236 } |  | 
| 237 |  | 
| 238 /////////////////////////////////////////////////////////////////////////////// |  | 
| 239 // Camera, private members: |  | 
| 240 |  | 
| 241 int Camera::OpenDevice(const char* device_name) const { |  | 
| 242   struct stat st; |  | 
| 243   if (stat(device_name, &st) == -1) { |  | 
| 244     log_errno(StringPrintf("Cannot identify %s", device_name)); |  | 
| 245     return -1; |  | 
| 246   } |  | 
| 247   if (!S_ISCHR(st.st_mode)) { |  | 
| 248     LOG(ERROR) << device_name << "is not adevice"; |  | 
| 249     return -1; |  | 
| 250   } |  | 
| 251   int fd = open(device_name, O_RDWR | O_NONBLOCK, 0); |  | 
| 252   if (fd == -1) { |  | 
| 253     log_errno(StringPrintf("Cannot open %s", device_name)); |  | 
| 254     return -1; |  | 
| 255   } |  | 
| 256   return fd; |  | 
| 257 } |  | 
| 258 |  | 
| 259 bool Camera::InitializeReadingMode(int fd) { |  | 
| 260   v4l2_requestbuffers req; |  | 
| 261   req.count = kRequestBuffersCount; |  | 
| 262   req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 263   req.memory = V4L2_MEMORY_MMAP; |  | 
| 264   if (xioctl(fd, VIDIOC_REQBUFS, &req) == -1) { |  | 
| 265     if (errno == EINVAL) |  | 
| 266       LOG(ERROR) << device_name_ << " does not support memory mapping."; |  | 
| 267     else |  | 
| 268       log_errno("VIDIOC_REQBUFS failed."); |  | 
| 269     return false; |  | 
| 270   } |  | 
| 271   if (req.count < 2U) { |  | 
| 272     LOG(ERROR) << "Insufficient buffer memory on " << device_name_; |  | 
| 273     return false; |  | 
| 274   } |  | 
| 275   for (unsigned i = 0; i < req.count; ++i) { |  | 
| 276     v4l2_buffer buffer = {}; |  | 
| 277     buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 278     buffer.memory = V4L2_MEMORY_MMAP; |  | 
| 279     buffer.index = i; |  | 
| 280     if (xioctl(fd, VIDIOC_QUERYBUF, &buffer) == -1) { |  | 
| 281       log_errno("VIDIOC_QUERYBUF failed."); |  | 
| 282       return false; |  | 
| 283     } |  | 
| 284     VideoBuffer video_buffer; |  | 
| 285     video_buffer.length = buffer.length; |  | 
| 286     video_buffer.start = mmap( |  | 
| 287         NULL,  // Start anywhere. |  | 
| 288         buffer.length, |  | 
| 289         PROT_READ | PROT_WRITE, |  | 
| 290         MAP_SHARED, |  | 
| 291         fd, |  | 
| 292         buffer.m.offset); |  | 
| 293     if (video_buffer.start == MAP_FAILED) { |  | 
| 294       log_errno("mmap() failed."); |  | 
| 295       UnmapVideoBuffers(); |  | 
| 296       return false; |  | 
| 297     } |  | 
| 298     buffers_.push_back(video_buffer); |  | 
| 299   } |  | 
| 300   return true; |  | 
| 301 } |  | 
| 302 |  | 
| 303 void Camera::UnmapVideoBuffers() { |  | 
| 304   for (size_t i = 0; i < buffers_.size(); ++i) { |  | 
| 305     if (munmap(buffers_[i].start, buffers_[i].length) == -1) |  | 
| 306       log_errno("munmap failed."); |  | 
| 307   } |  | 
| 308 } |  | 
| 309 |  | 
| 310 void Camera::OnCapture() { |  | 
| 311   do { |  | 
| 312     fd_set fds; |  | 
| 313     FD_ZERO(&fds); |  | 
| 314     FD_SET(device_descriptor_, &fds); |  | 
| 315 |  | 
| 316     timeval tv = {}; |  | 
| 317     tv.tv_sec = 2; |  | 
| 318     tv.tv_usec = 0; |  | 
| 319 |  | 
| 320     int result = select(device_descriptor_ + 1, &fds, NULL, NULL, &tv); |  | 
| 321     if (result == -1) { |  | 
| 322       if (errno == EINTR) |  | 
| 323         continue; |  | 
| 324       log_errno("select() failed."); |  | 
| 325       return; |  | 
| 326     } |  | 
| 327     if (result == 0) { |  | 
| 328       LOG(ERROR) << "select() timeout."; |  | 
| 329       return; |  | 
| 330     } |  | 
| 331     // EAGAIN - continue select loop. |  | 
| 332   } while (!ReadFrame()); |  | 
| 333 } |  | 
| 334 |  | 
| 335 bool Camera::ReadFrame() { |  | 
| 336   v4l2_buffer buffer = {}; |  | 
| 337   buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |  | 
| 338   buffer.memory = V4L2_MEMORY_MMAP; |  | 
| 339   if (xioctl(device_descriptor_, VIDIOC_DQBUF, &buffer) == -1) { |  | 
| 340     // Return false only in this case to try again. |  | 
| 341     if (errno == EAGAIN) |  | 
| 342       return false; |  | 
| 343 |  | 
| 344     log_errno("VIDIOC_DQBUF failed."); |  | 
| 345     return true; |  | 
| 346   } |  | 
| 347   if (buffer.index >= buffers_.size()) { |  | 
| 348     LOG(ERROR) << "Index of buffer is out of range."; |  | 
| 349     return true; |  | 
| 350   } |  | 
| 351   ProcessImage(buffers_[buffer.index].start); |  | 
| 352   if (xioctl(device_descriptor_, VIDIOC_QBUF, &buffer) == -1) |  | 
| 353     log_errno("VIDIOC_QBUF failed."); |  | 
| 354   return true; |  | 
| 355 } |  | 
| 356 |  | 
| 357 void Camera::ProcessImage(void* data) { |  | 
| 358   // If desired resolution is higher than available, we crop the available |  | 
| 359   // image to get the same aspect ratio and scale the result. |  | 
| 360   int desired_width = desired_width_; |  | 
| 361   int desired_height = desired_height_; |  | 
| 362   if (desired_width > frame_width_ || desired_height > frame_height_) { |  | 
| 363     // Compare aspect ratios of the desired and available images. |  | 
| 364     // The same as desired_width / desired_height > frame_width / frame_height. |  | 
| 365     if (desired_width_ * frame_height_ > frame_width_ * desired_height_) { |  | 
| 366       desired_width = frame_width_; |  | 
| 367       desired_height = (desired_height_ * frame_width_) / desired_width_; |  | 
| 368     } else { |  | 
| 369       desired_width = (desired_width_ * frame_height_) / desired_height_; |  | 
| 370       desired_height = frame_height_; |  | 
| 371     } |  | 
| 372   } |  | 
| 373   SkBitmap image; |  | 
| 374   int crop_left = (frame_width_ - desired_width) / 2; |  | 
| 375   int crop_right = frame_width_ - crop_left - desired_width; |  | 
| 376   int crop_top = (frame_height_ - desired_height_) / 2; |  | 
| 377   image.setConfig(SkBitmap::kARGB_8888_Config, desired_width, desired_height); |  | 
| 378   image.allocPixels(); |  | 
| 379   { |  | 
| 380     SkAutoLockPixels lock_image(image); |  | 
| 381     uint32_t* dst = image.getAddr32(0, 0); |  | 
| 382     uint32_t* src = reinterpret_cast<uint32_t*>(data) + |  | 
| 383                     crop_top * (frame_width_ / 2); |  | 
| 384     for (int y = 0; y < image.height(); ++y) { |  | 
| 385       src += crop_left / 2; |  | 
| 386       for (int x = 0; x < image.width(); x += 2) { |  | 
| 387         uint32_t yuyv = *src++; |  | 
| 388         uint8_t y0 = yuyv & 0xFF; |  | 
| 389         uint8_t u = (yuyv >> 8) & 0xFF; |  | 
| 390         uint8_t y1 = (yuyv >> 16) & 0xFF; |  | 
| 391         uint8_t v = (yuyv >> 24) & 0xFF; |  | 
| 392         *dst++ = convert_yuv_to_rgba(y0, u, v); |  | 
| 393         *dst++ = convert_yuv_to_rgba(y1, u, v); |  | 
| 394       } |  | 
| 395       src += crop_right / 2; |  | 
| 396     } |  | 
| 397   } |  | 
| 398   if (image.width() < desired_width_ || image.height() < desired_height_) { |  | 
| 399     image = skia::ImageOperations::Resize( |  | 
| 400         image, |  | 
| 401         skia::ImageOperations::RESIZE_LANCZOS3, |  | 
| 402         desired_width_, |  | 
| 403         desired_height_); |  | 
| 404   } |  | 
| 405   image.setIsOpaque(true); |  | 
| 406   if (delegate_) |  | 
| 407     delegate_->OnVideoFrameCaptured(image); |  | 
| 408 } |  | 
| 409 |  | 
| 410 }  // namespace chromeos |  | 
| OLD | NEW | 
|---|