| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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 "media/capture/video/linux/v4l2_capture_delegate.h" | |
| 6 | |
| 7 #include <poll.h> | |
| 8 #include <sys/fcntl.h> | |
| 9 #include <sys/ioctl.h> | |
| 10 #include <sys/mman.h> | |
| 11 #include <utility> | |
| 12 | |
| 13 #include "base/bind.h" | |
| 14 #include "base/files/file_enumerator.h" | |
| 15 #include "base/posix/eintr_wrapper.h" | |
| 16 #include "base/strings/stringprintf.h" | |
| 17 #include "build/build_config.h" | |
| 18 #include "media/base/bind_to_current_loop.h" | |
| 19 #include "media/capture/video/linux/video_capture_device_linux.h" | |
| 20 | |
| 21 namespace media { | |
| 22 | |
| 23 // Desired number of video buffers to allocate. The actual number of allocated | |
| 24 // buffers by v4l2 driver can be higher or lower than this number. | |
| 25 // kNumVideoBuffers should not be too small, or Chrome may not return enough | |
| 26 // buffers back to driver in time. | |
| 27 const uint32_t kNumVideoBuffers = 4; | |
| 28 // Timeout in milliseconds v4l2_thread_ blocks waiting for a frame from the hw. | |
| 29 // This value has been fine tuned. Before changing or modifying it see | |
| 30 // https://crbug.com/470717 | |
| 31 const int kCaptureTimeoutMs = 1000; | |
| 32 // The number of continuous timeouts tolerated before treated as error. | |
| 33 const int kContinuousTimeoutLimit = 10; | |
| 34 // MJPEG is preferred if the requested width or height is larger than this. | |
| 35 const int kMjpegWidth = 640; | |
| 36 const int kMjpegHeight = 480; | |
| 37 // Typical framerate, in fps | |
| 38 const int kTypicalFramerate = 30; | |
| 39 | |
| 40 // V4L2 color formats supported by V4L2CaptureDelegate derived classes. | |
| 41 // This list is ordered by precedence of use -- but see caveats for MJPEG. | |
| 42 static struct { | |
| 43 uint32_t fourcc; | |
| 44 VideoPixelFormat pixel_format; | |
| 45 size_t num_planes; | |
| 46 } const kSupportedFormatsAndPlanarity[] = { | |
| 47 {V4L2_PIX_FMT_YUV420, PIXEL_FORMAT_I420, 1}, | |
| 48 {V4L2_PIX_FMT_YUYV, PIXEL_FORMAT_YUY2, 1}, | |
| 49 {V4L2_PIX_FMT_UYVY, PIXEL_FORMAT_UYVY, 1}, | |
| 50 {V4L2_PIX_FMT_RGB24, PIXEL_FORMAT_RGB24, 1}, | |
| 51 // MJPEG is usually sitting fairly low since we don't want to have to | |
| 52 // decode. However, it is needed for large resolutions due to USB bandwidth | |
| 53 // limitations, so GetListOfUsableFourCcs() can duplicate it on top, see | |
| 54 // that method. | |
| 55 {V4L2_PIX_FMT_MJPEG, PIXEL_FORMAT_MJPEG, 1}, | |
| 56 // JPEG works as MJPEG on some gspca webcams from field reports, see | |
| 57 // https://code.google.com/p/webrtc/issues/detail?id=529, put it as the | |
| 58 // least preferred format. | |
| 59 {V4L2_PIX_FMT_JPEG, PIXEL_FORMAT_MJPEG, 1}, | |
| 60 }; | |
| 61 | |
| 62 // Fill in |format| with the given parameters. | |
| 63 static void FillV4L2Format(v4l2_format* format, | |
| 64 uint32_t width, | |
| 65 uint32_t height, | |
| 66 uint32_t pixelformat_fourcc) { | |
| 67 memset(format, 0, sizeof(*format)); | |
| 68 format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 69 format->fmt.pix.width = width; | |
| 70 format->fmt.pix.height = height; | |
| 71 format->fmt.pix.pixelformat = pixelformat_fourcc; | |
| 72 } | |
| 73 | |
| 74 // Fills all parts of |buffer|. | |
| 75 static void FillV4L2Buffer(v4l2_buffer* buffer, int index) { | |
| 76 memset(buffer, 0, sizeof(*buffer)); | |
| 77 buffer->memory = V4L2_MEMORY_MMAP; | |
| 78 buffer->index = index; | |
| 79 buffer->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 80 } | |
| 81 | |
| 82 static void FillV4L2RequestBuffer(v4l2_requestbuffers* request_buffer, | |
| 83 int count) { | |
| 84 memset(request_buffer, 0, sizeof(*request_buffer)); | |
| 85 request_buffer->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 86 request_buffer->memory = V4L2_MEMORY_MMAP; | |
| 87 request_buffer->count = count; | |
| 88 } | |
| 89 | |
| 90 // Returns the input |fourcc| as a std::string four char representation. | |
| 91 static std::string FourccToString(uint32_t fourcc) { | |
| 92 return base::StringPrintf("%c%c%c%c", fourcc & 0xFF, (fourcc >> 8) & 0xFF, | |
| 93 (fourcc >> 16) & 0xFF, (fourcc >> 24) & 0xFF); | |
| 94 } | |
| 95 | |
| 96 // Class keeping track of a SPLANE V4L2 buffer, mmap()ed on construction and | |
| 97 // munmap()ed on destruction. | |
| 98 class V4L2CaptureDelegate::BufferTracker | |
| 99 : public base::RefCounted<BufferTracker> { | |
| 100 public: | |
| 101 BufferTracker(); | |
| 102 // Abstract method to mmap() given |fd| according to |buffer|. | |
| 103 bool Init(int fd, const v4l2_buffer& buffer); | |
| 104 | |
| 105 const uint8_t* start() const { return start_; } | |
| 106 size_t payload_size() const { return payload_size_; } | |
| 107 void set_payload_size(size_t payload_size) { | |
| 108 DCHECK_LE(payload_size, length_); | |
| 109 payload_size_ = payload_size; | |
| 110 } | |
| 111 | |
| 112 private: | |
| 113 friend class base::RefCounted<BufferTracker>; | |
| 114 virtual ~BufferTracker(); | |
| 115 | |
| 116 uint8_t* start_; | |
| 117 size_t length_; | |
| 118 size_t payload_size_; | |
| 119 }; | |
| 120 | |
| 121 // static | |
| 122 size_t V4L2CaptureDelegate::GetNumPlanesForFourCc(uint32_t fourcc) { | |
| 123 for (const auto& fourcc_and_pixel_format : kSupportedFormatsAndPlanarity) { | |
| 124 if (fourcc_and_pixel_format.fourcc == fourcc) | |
| 125 return fourcc_and_pixel_format.num_planes; | |
| 126 } | |
| 127 DVLOG(1) << "Unknown fourcc " << FourccToString(fourcc); | |
| 128 return 0; | |
| 129 } | |
| 130 | |
| 131 // static | |
| 132 VideoPixelFormat V4L2CaptureDelegate::V4l2FourCcToChromiumPixelFormat( | |
| 133 uint32_t v4l2_fourcc) { | |
| 134 for (const auto& fourcc_and_pixel_format : kSupportedFormatsAndPlanarity) { | |
| 135 if (fourcc_and_pixel_format.fourcc == v4l2_fourcc) | |
| 136 return fourcc_and_pixel_format.pixel_format; | |
| 137 } | |
| 138 // Not finding a pixel format is OK during device capabilities enumeration. | |
| 139 // Let the caller decide if PIXEL_FORMAT_UNKNOWN is an error or | |
| 140 // not. | |
| 141 DVLOG(1) << "Unsupported pixel format: " << FourccToString(v4l2_fourcc); | |
| 142 return PIXEL_FORMAT_UNKNOWN; | |
| 143 } | |
| 144 | |
| 145 // static | |
| 146 std::list<uint32_t> V4L2CaptureDelegate::GetListOfUsableFourCcs( | |
| 147 bool prefer_mjpeg) { | |
| 148 std::list<uint32_t> supported_formats; | |
| 149 for (const auto& format : kSupportedFormatsAndPlanarity) | |
| 150 supported_formats.push_back(format.fourcc); | |
| 151 | |
| 152 // Duplicate MJPEG on top of the list depending on |prefer_mjpeg|. | |
| 153 if (prefer_mjpeg) | |
| 154 supported_formats.push_front(V4L2_PIX_FMT_MJPEG); | |
| 155 | |
| 156 return supported_formats; | |
| 157 } | |
| 158 | |
| 159 V4L2CaptureDelegate::V4L2CaptureDelegate( | |
| 160 const VideoCaptureDeviceDescriptor& device_descriptor, | |
| 161 const scoped_refptr<base::SingleThreadTaskRunner>& v4l2_task_runner, | |
| 162 int power_line_frequency) | |
| 163 : v4l2_task_runner_(v4l2_task_runner), | |
| 164 device_descriptor_(device_descriptor), | |
| 165 power_line_frequency_(power_line_frequency), | |
| 166 is_capturing_(false), | |
| 167 timeout_count_(0), | |
| 168 rotation_(0) {} | |
| 169 | |
| 170 void V4L2CaptureDelegate::AllocateAndStart( | |
| 171 int width, | |
| 172 int height, | |
| 173 float frame_rate, | |
| 174 std::unique_ptr<VideoCaptureDevice::Client> client) { | |
| 175 DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); | |
| 176 DCHECK(client); | |
| 177 client_ = std::move(client); | |
| 178 | |
| 179 // Need to open camera with O_RDWR after Linux kernel 3.3. | |
| 180 device_fd_.reset( | |
| 181 HANDLE_EINTR(open(device_descriptor_.device_id.c_str(), O_RDWR))); | |
| 182 if (!device_fd_.is_valid()) { | |
| 183 SetErrorState(FROM_HERE, "Failed to open V4L2 device driver file."); | |
| 184 return; | |
| 185 } | |
| 186 | |
| 187 v4l2_capability cap = {}; | |
| 188 if (!((HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QUERYCAP, &cap)) == 0) && | |
| 189 ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) && | |
| 190 !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)))) { | |
| 191 device_fd_.reset(); | |
| 192 SetErrorState(FROM_HERE, "This is not a V4L2 video capture device"); | |
| 193 return; | |
| 194 } | |
| 195 | |
| 196 // Get supported video formats in preferred order. For large resolutions, | |
| 197 // favour mjpeg over raw formats. | |
| 198 const std::list<uint32_t>& desired_v4l2_formats = | |
| 199 GetListOfUsableFourCcs(width > kMjpegWidth || height > kMjpegHeight); | |
| 200 std::list<uint32_t>::const_iterator best = desired_v4l2_formats.end(); | |
| 201 | |
| 202 v4l2_fmtdesc fmtdesc = {}; | |
| 203 fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 204 for (; HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_ENUM_FMT, &fmtdesc)) == 0; | |
| 205 ++fmtdesc.index) { | |
| 206 best = std::find(desired_v4l2_formats.begin(), best, fmtdesc.pixelformat); | |
| 207 } | |
| 208 if (best == desired_v4l2_formats.end()) { | |
| 209 SetErrorState(FROM_HERE, "Failed to find a supported camera format."); | |
| 210 return; | |
| 211 } | |
| 212 | |
| 213 DVLOG(1) << "Chosen pixel format is " << FourccToString(*best); | |
| 214 FillV4L2Format(&video_fmt_, width, height, *best); | |
| 215 | |
| 216 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_S_FMT, &video_fmt_)) < 0) { | |
| 217 SetErrorState(FROM_HERE, "Failed to set video capture format"); | |
| 218 return; | |
| 219 } | |
| 220 const VideoPixelFormat pixel_format = | |
| 221 V4l2FourCcToChromiumPixelFormat(video_fmt_.fmt.pix.pixelformat); | |
| 222 if (pixel_format == PIXEL_FORMAT_UNKNOWN) { | |
| 223 SetErrorState(FROM_HERE, "Unsupported pixel format"); | |
| 224 return; | |
| 225 } | |
| 226 | |
| 227 // Set capture framerate in the form of capture interval. | |
| 228 v4l2_streamparm streamparm = {}; | |
| 229 streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 230 // The following line checks that the driver knows about framerate get/set. | |
| 231 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_G_PARM, &streamparm)) >= 0) { | |
| 232 // Now check if the device is able to accept a capture framerate set. | |
| 233 if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { | |
| 234 // |frame_rate| is float, approximate by a fraction. | |
| 235 streamparm.parm.capture.timeperframe.numerator = | |
| 236 media::kFrameRatePrecision; | |
| 237 streamparm.parm.capture.timeperframe.denominator = | |
| 238 (frame_rate) ? (frame_rate * media::kFrameRatePrecision) | |
| 239 : (kTypicalFramerate * media::kFrameRatePrecision); | |
| 240 | |
| 241 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_S_PARM, &streamparm)) < | |
| 242 0) { | |
| 243 SetErrorState(FROM_HERE, "Failed to set camera framerate"); | |
| 244 return; | |
| 245 } | |
| 246 DVLOG(2) << "Actual camera driverframerate: " | |
| 247 << streamparm.parm.capture.timeperframe.denominator << "/" | |
| 248 << streamparm.parm.capture.timeperframe.numerator; | |
| 249 } | |
| 250 } | |
| 251 // TODO(mcasas): what should be done if the camera driver does not allow | |
| 252 // framerate configuration, or the actual one is different from the desired? | |
| 253 | |
| 254 // Set anti-banding/anti-flicker to 50/60Hz. May fail due to not supported | |
| 255 // operation (|errno| == EINVAL in this case) or plain failure. | |
| 256 if ((power_line_frequency_ == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) || | |
| 257 (power_line_frequency_ == V4L2_CID_POWER_LINE_FREQUENCY_60HZ) || | |
| 258 (power_line_frequency_ == V4L2_CID_POWER_LINE_FREQUENCY_AUTO)) { | |
| 259 struct v4l2_control control = {}; | |
| 260 control.id = V4L2_CID_POWER_LINE_FREQUENCY; | |
| 261 control.value = power_line_frequency_; | |
| 262 const int retval = | |
| 263 HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_S_CTRL, &control)); | |
| 264 if (retval != 0) | |
| 265 DVLOG(1) << "Error setting power line frequency removal"; | |
| 266 } | |
| 267 | |
| 268 capture_format_.frame_size.SetSize(video_fmt_.fmt.pix.width, | |
| 269 video_fmt_.fmt.pix.height); | |
| 270 capture_format_.frame_rate = frame_rate; | |
| 271 capture_format_.pixel_format = pixel_format; | |
| 272 | |
| 273 v4l2_requestbuffers r_buffer; | |
| 274 FillV4L2RequestBuffer(&r_buffer, kNumVideoBuffers); | |
| 275 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_REQBUFS, &r_buffer)) < 0) { | |
| 276 SetErrorState(FROM_HERE, "Error requesting MMAP buffers from V4L2"); | |
| 277 return; | |
| 278 } | |
| 279 for (unsigned int i = 0; i < r_buffer.count; ++i) { | |
| 280 if (!MapAndQueueBuffer(i)) { | |
| 281 SetErrorState(FROM_HERE, "Allocate buffer failed"); | |
| 282 return; | |
| 283 } | |
| 284 } | |
| 285 | |
| 286 v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 287 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_STREAMON, &capture_type)) < | |
| 288 0) { | |
| 289 SetErrorState(FROM_HERE, "VIDIOC_STREAMON failed"); | |
| 290 return; | |
| 291 } | |
| 292 | |
| 293 is_capturing_ = true; | |
| 294 // Post task to start fetching frames from v4l2. | |
| 295 v4l2_task_runner_->PostTask( | |
| 296 FROM_HERE, base::Bind(&V4L2CaptureDelegate::DoCapture, this)); | |
| 297 } | |
| 298 | |
| 299 void V4L2CaptureDelegate::StopAndDeAllocate() { | |
| 300 DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); | |
| 301 // The order is important: stop streaming, clear |buffer_pool_|, | |
| 302 // thus munmap()ing the v4l2_buffers, and then return them to the OS. | |
| 303 v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; | |
| 304 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_STREAMOFF, &capture_type)) < | |
| 305 0) { | |
| 306 SetErrorState(FROM_HERE, "VIDIOC_STREAMOFF failed"); | |
| 307 return; | |
| 308 } | |
| 309 | |
| 310 buffer_tracker_pool_.clear(); | |
| 311 | |
| 312 v4l2_requestbuffers r_buffer; | |
| 313 FillV4L2RequestBuffer(&r_buffer, 0); | |
| 314 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_REQBUFS, &r_buffer)) < 0) | |
| 315 SetErrorState(FROM_HERE, "Failed to VIDIOC_REQBUFS with count = 0"); | |
| 316 | |
| 317 // At this point we can close the device. | |
| 318 // This is also needed for correctly changing settings later via VIDIOC_S_FMT. | |
| 319 device_fd_.reset(); | |
| 320 is_capturing_ = false; | |
| 321 client_.reset(); | |
| 322 } | |
| 323 | |
| 324 void V4L2CaptureDelegate::SetRotation(int rotation) { | |
| 325 DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); | |
| 326 DCHECK(rotation >= 0 && rotation < 360 && rotation % 90 == 0); | |
| 327 rotation_ = rotation; | |
| 328 } | |
| 329 | |
| 330 V4L2CaptureDelegate::~V4L2CaptureDelegate() {} | |
| 331 | |
| 332 bool V4L2CaptureDelegate::MapAndQueueBuffer(int index) { | |
| 333 v4l2_buffer buffer; | |
| 334 FillV4L2Buffer(&buffer, index); | |
| 335 | |
| 336 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QUERYBUF, &buffer)) < 0) { | |
| 337 DLOG(ERROR) << "Error querying status of a MMAP V4L2 buffer"; | |
| 338 return false; | |
| 339 } | |
| 340 | |
| 341 const scoped_refptr<BufferTracker> buffer_tracker(new BufferTracker()); | |
| 342 if (!buffer_tracker->Init(device_fd_.get(), buffer)) { | |
| 343 DLOG(ERROR) << "Error creating BufferTracker"; | |
| 344 return false; | |
| 345 } | |
| 346 buffer_tracker_pool_.push_back(buffer_tracker); | |
| 347 | |
| 348 // Enqueue the buffer in the drivers incoming queue. | |
| 349 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) { | |
| 350 DLOG(ERROR) << "Error enqueuing a V4L2 buffer back into the driver"; | |
| 351 return false; | |
| 352 } | |
| 353 return true; | |
| 354 } | |
| 355 | |
| 356 void V4L2CaptureDelegate::DoCapture() { | |
| 357 DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); | |
| 358 if (!is_capturing_) | |
| 359 return; | |
| 360 | |
| 361 pollfd device_pfd = {}; | |
| 362 device_pfd.fd = device_fd_.get(); | |
| 363 device_pfd.events = POLLIN; | |
| 364 const int result = HANDLE_EINTR(poll(&device_pfd, 1, kCaptureTimeoutMs)); | |
| 365 if (result < 0) { | |
| 366 SetErrorState(FROM_HERE, "Poll failed"); | |
| 367 return; | |
| 368 } | |
| 369 // Check if poll() timed out; track the amount of times it did in a row and | |
| 370 // throw an error if it times out too many times. | |
| 371 if (result == 0) { | |
| 372 timeout_count_++; | |
| 373 if (timeout_count_ >= kContinuousTimeoutLimit) { | |
| 374 SetErrorState(FROM_HERE, | |
| 375 "Multiple continuous timeouts while read-polling."); | |
| 376 timeout_count_ = 0; | |
| 377 return; | |
| 378 } | |
| 379 } else { | |
| 380 timeout_count_ = 0; | |
| 381 } | |
| 382 | |
| 383 // Deenqueue, send and reenqueue a buffer if the driver has filled one in. | |
| 384 if (device_pfd.revents & POLLIN) { | |
| 385 v4l2_buffer buffer; | |
| 386 FillV4L2Buffer(&buffer, 0); | |
| 387 | |
| 388 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_DQBUF, &buffer)) < 0) { | |
| 389 SetErrorState(FROM_HERE, "Failed to dequeue capture buffer"); | |
| 390 return; | |
| 391 } | |
| 392 | |
| 393 buffer_tracker_pool_[buffer.index]->set_payload_size(buffer.bytesused); | |
| 394 const scoped_refptr<BufferTracker>& buffer_tracker = | |
| 395 buffer_tracker_pool_[buffer.index]; | |
| 396 | |
| 397 base::TimeDelta timestamp = | |
| 398 base::TimeDelta::FromSeconds(buffer.timestamp.tv_sec) + | |
| 399 base::TimeDelta::FromMicroseconds(buffer.timestamp.tv_usec); | |
| 400 client_->OnIncomingCapturedData( | |
| 401 buffer_tracker->start(), buffer_tracker->payload_size(), | |
| 402 capture_format_, rotation_, base::TimeTicks::Now(), timestamp); | |
| 403 | |
| 404 if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) { | |
| 405 SetErrorState(FROM_HERE, "Failed to enqueue capture buffer"); | |
| 406 return; | |
| 407 } | |
| 408 } | |
| 409 | |
| 410 v4l2_task_runner_->PostTask( | |
| 411 FROM_HERE, base::Bind(&V4L2CaptureDelegate::DoCapture, this)); | |
| 412 } | |
| 413 | |
| 414 void V4L2CaptureDelegate::SetErrorState( | |
| 415 const tracked_objects::Location& from_here, | |
| 416 const std::string& reason) { | |
| 417 DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); | |
| 418 is_capturing_ = false; | |
| 419 client_->OnError(from_here, reason); | |
| 420 } | |
| 421 | |
| 422 V4L2CaptureDelegate::BufferTracker::BufferTracker() {} | |
| 423 | |
| 424 V4L2CaptureDelegate::BufferTracker::~BufferTracker() { | |
| 425 if (start_ == nullptr) | |
| 426 return; | |
| 427 const int result = munmap(start_, length_); | |
| 428 PLOG_IF(ERROR, result < 0) << "Error munmap()ing V4L2 buffer"; | |
| 429 } | |
| 430 | |
| 431 bool V4L2CaptureDelegate::BufferTracker::Init(int fd, | |
| 432 const v4l2_buffer& buffer) { | |
| 433 // Some devices require mmap() to be called with both READ and WRITE. | |
| 434 // See http://crbug.com/178582. | |
| 435 void* const start = mmap(NULL, buffer.length, PROT_READ | PROT_WRITE, | |
| 436 MAP_SHARED, fd, buffer.m.offset); | |
| 437 if (start == MAP_FAILED) { | |
| 438 DLOG(ERROR) << "Error mmap()ing a V4L2 buffer into userspace"; | |
| 439 return false; | |
| 440 } | |
| 441 start_ = static_cast<uint8_t*>(start); | |
| 442 length_ = buffer.length; | |
| 443 payload_size_ = 0; | |
| 444 return true; | |
| 445 } | |
| 446 | |
| 447 } // namespace media | |
| OLD | NEW |