| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/capture/video/linux/v4l2_capture_delegate.h" |
| |
| #include <linux/version.h> |
| #include <linux/videodev2.h> |
| #include <poll.h> |
| #include <sys/fcntl.h> |
| #include <sys/ioctl.h> |
| #include <sys/mman.h> |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "build/build_config.h" |
| #include "media/base/bind_to_current_loop.h" |
| #include "media/base/video_frame.h" |
| #include "media/base/video_types.h" |
| #include "media/capture/mojom/image_capture_types.h" |
| #include "media/capture/video/blob_utils.h" |
| #include "media/capture/video/linux/video_capture_device_linux.h" |
| |
| using media::mojom::MeteringMode; |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0) |
| // 16 bit depth, Realsense F200. |
| #define V4L2_PIX_FMT_Z16 v4l2_fourcc('Z', '1', '6', ' ') |
| #endif |
| |
| // TODO(aleksandar.stojiljkovic): Wrap this with kernel version check once the |
| // format is introduced to kernel. |
| // See https://crbug.com/661877 |
| #ifndef V4L2_PIX_FMT_INVZ |
| // 16 bit depth, Realsense SR300. |
| #define V4L2_PIX_FMT_INVZ v4l2_fourcc('I', 'N', 'V', 'Z') |
| #endif |
| |
| namespace media { |
| |
| namespace { |
| |
| // Desired number of video buffers to allocate. The actual number of allocated |
| // buffers by v4l2 driver can be higher or lower than this number. |
| // kNumVideoBuffers should not be too small, or Chrome may not return enough |
| // buffers back to driver in time. |
| constexpr uint32_t kNumVideoBuffers = 4; |
| // Timeout in milliseconds v4l2_thread_ blocks waiting for a frame from the hw. |
| // This value has been fine tuned. Before changing or modifying it see |
| // https://crbug.com/470717 |
| constexpr int kCaptureTimeoutMs = 1000; |
| // The number of continuous timeouts tolerated before treated as error. |
| constexpr int kContinuousTimeoutLimit = 10; |
| // MJPEG is preferred if the requested width or height is larger than this. |
| constexpr int kMjpegWidth = 640; |
| constexpr int kMjpegHeight = 480; |
| // Typical framerate, in fps |
| constexpr int kTypicalFramerate = 30; |
| |
| // V4L2 color formats supported by V4L2CaptureDelegate derived classes. |
| // This list is ordered by precedence of use -- but see caveats for MJPEG. |
| struct { |
| uint32_t fourcc; |
| VideoPixelFormat pixel_format; |
| size_t num_planes; |
| } constexpr kSupportedFormatsAndPlanarity[] = { |
| {V4L2_PIX_FMT_YUV420, PIXEL_FORMAT_I420, 1}, |
| {V4L2_PIX_FMT_NV12, PIXEL_FORMAT_NV12, 1}, |
| {V4L2_PIX_FMT_Y16, PIXEL_FORMAT_Y16, 1}, |
| {V4L2_PIX_FMT_Z16, PIXEL_FORMAT_Y16, 1}, |
| {V4L2_PIX_FMT_INVZ, PIXEL_FORMAT_Y16, 1}, |
| {V4L2_PIX_FMT_YUYV, PIXEL_FORMAT_YUY2, 1}, |
| {V4L2_PIX_FMT_RGB24, PIXEL_FORMAT_RGB24, 1}, |
| // MJPEG is usually sitting fairly low since we don't want to have to |
| // decode. However, it is needed for large resolutions due to USB bandwidth |
| // limitations, so GetListOfUsableFourCcs() can duplicate it on top, see |
| // that method. |
| {V4L2_PIX_FMT_MJPEG, PIXEL_FORMAT_MJPEG, 1}, |
| // JPEG works as MJPEG on some gspca webcams from field reports, see |
| // https://code.google.com/p/webrtc/issues/detail?id=529, put it as the |
| // least preferred format. |
| {V4L2_PIX_FMT_JPEG, PIXEL_FORMAT_MJPEG, 1}, |
| }; |
| |
| // Maximum number of ioctl retries before giving up trying to reset controls. |
| constexpr int kMaxIOCtrlRetries = 5; |
| |
| // Base id and class identifier for Controls to be reset. |
| struct { |
| uint32_t control_base; |
| uint32_t class_id; |
| } constexpr kControls[] = {{V4L2_CID_USER_BASE, V4L2_CID_USER_CLASS}, |
| {V4L2_CID_CAMERA_CLASS_BASE, V4L2_CID_CAMERA_CLASS}}; |
| |
| // Fill in |format| with the given parameters. |
| void FillV4L2Format(v4l2_format* format, |
| uint32_t width, |
| uint32_t height, |
| uint32_t pixelformat_fourcc) { |
| memset(format, 0, sizeof(*format)); |
| format->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| format->fmt.pix.width = width; |
| format->fmt.pix.height = height; |
| format->fmt.pix.pixelformat = pixelformat_fourcc; |
| } |
| |
| // Fills all parts of |buffer|. |
| void FillV4L2Buffer(v4l2_buffer* buffer, int index) { |
| memset(buffer, 0, sizeof(*buffer)); |
| buffer->memory = V4L2_MEMORY_MMAP; |
| buffer->index = index; |
| buffer->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| } |
| |
| void FillV4L2RequestBuffer(v4l2_requestbuffers* request_buffer, int count) { |
| memset(request_buffer, 0, sizeof(*request_buffer)); |
| request_buffer->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| request_buffer->memory = V4L2_MEMORY_MMAP; |
| request_buffer->count = count; |
| } |
| |
| // Determines if |control_id| is special, i.e. controls another one's state. |
| bool IsSpecialControl(int control_id) { |
| switch (control_id) { |
| case V4L2_CID_AUTO_WHITE_BALANCE: |
| case V4L2_CID_EXPOSURE_AUTO: |
| case V4L2_CID_EXPOSURE_AUTO_PRIORITY: |
| case V4L2_CID_FOCUS_AUTO: |
| return true; |
| } |
| return false; |
| } |
| |
| // Determines if |control_id| should be skipped, https://crbug.com/697885. |
| #if !defined(V4L2_CID_PAN_SPEED) |
| #define V4L2_CID_PAN_SPEED (V4L2_CID_CAMERA_CLASS_BASE + 32) |
| #endif |
| #if !defined(V4L2_CID_TILT_SPEED) |
| #define V4L2_CID_TILT_SPEED (V4L2_CID_CAMERA_CLASS_BASE + 33) |
| #endif |
| #if !defined(V4L2_CID_PANTILT_CMD) |
| #define V4L2_CID_PANTILT_CMD (V4L2_CID_CAMERA_CLASS_BASE + 34) |
| #endif |
| bool IsBlockedControl(int control_id) { |
| switch (control_id) { |
| case V4L2_CID_PAN_RELATIVE: |
| case V4L2_CID_TILT_RELATIVE: |
| case V4L2_CID_PAN_RESET: |
| case V4L2_CID_TILT_RESET: |
| case V4L2_CID_PAN_ABSOLUTE: |
| case V4L2_CID_TILT_ABSOLUTE: |
| case V4L2_CID_ZOOM_ABSOLUTE: |
| case V4L2_CID_ZOOM_RELATIVE: |
| case V4L2_CID_ZOOM_CONTINUOUS: |
| case V4L2_CID_PAN_SPEED: |
| case V4L2_CID_TILT_SPEED: |
| case V4L2_CID_PANTILT_CMD: |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| // Class keeping track of a SPLANE V4L2 buffer, mmap()ed on construction and |
| // munmap()ed on destruction. |
| class V4L2CaptureDelegate::BufferTracker |
| : public base::RefCounted<BufferTracker> { |
| public: |
| explicit BufferTracker(V4L2CaptureDevice* v4l2); |
| |
| // Abstract method to mmap() given |fd| according to |buffer|. |
| bool Init(int fd, const v4l2_buffer& buffer); |
| |
| const uint8_t* start() const { return start_; } |
| size_t payload_size() const { return payload_size_; } |
| void set_payload_size(size_t payload_size) { |
| DCHECK_LE(payload_size, length_); |
| payload_size_ = payload_size; |
| } |
| |
| private: |
| friend class base::RefCounted<BufferTracker>; |
| virtual ~BufferTracker(); |
| |
| V4L2CaptureDevice* const v4l2_; |
| uint8_t* start_; |
| size_t length_; |
| size_t payload_size_; |
| }; |
| |
| // static |
| size_t V4L2CaptureDelegate::GetNumPlanesForFourCc(uint32_t fourcc) { |
| for (const auto& fourcc_and_pixel_format : kSupportedFormatsAndPlanarity) { |
| if (fourcc_and_pixel_format.fourcc == fourcc) |
| return fourcc_and_pixel_format.num_planes; |
| } |
| DVLOG(1) << "Unknown fourcc " << FourccToString(fourcc); |
| return 0; |
| } |
| |
| // static |
| VideoPixelFormat V4L2CaptureDelegate::V4l2FourCcToChromiumPixelFormat( |
| uint32_t v4l2_fourcc) { |
| for (const auto& fourcc_and_pixel_format : kSupportedFormatsAndPlanarity) { |
| if (fourcc_and_pixel_format.fourcc == v4l2_fourcc) |
| return fourcc_and_pixel_format.pixel_format; |
| } |
| // Not finding a pixel format is OK during device capabilities enumeration. |
| // Let the caller decide if PIXEL_FORMAT_UNKNOWN is an error or |
| // not. |
| DVLOG(1) << "Unsupported pixel format: " << FourccToString(v4l2_fourcc); |
| return PIXEL_FORMAT_UNKNOWN; |
| } |
| |
| // static |
| std::vector<uint32_t> V4L2CaptureDelegate::GetListOfUsableFourCcs( |
| bool prefer_mjpeg) { |
| std::vector<uint32_t> supported_formats; |
| supported_formats.reserve(base::size(kSupportedFormatsAndPlanarity)); |
| |
| // Duplicate MJPEG on top of the list depending on |prefer_mjpeg|. |
| if (prefer_mjpeg) |
| supported_formats.push_back(V4L2_PIX_FMT_MJPEG); |
| |
| for (const auto& format : kSupportedFormatsAndPlanarity) |
| supported_formats.push_back(format.fourcc); |
| |
| return supported_formats; |
| } |
| |
| V4L2CaptureDelegate::V4L2CaptureDelegate( |
| V4L2CaptureDevice* v4l2, |
| const VideoCaptureDeviceDescriptor& device_descriptor, |
| const scoped_refptr<base::SingleThreadTaskRunner>& v4l2_task_runner, |
| int power_line_frequency, |
| int rotation) |
| : v4l2_(v4l2), |
| v4l2_task_runner_(v4l2_task_runner), |
| device_descriptor_(device_descriptor), |
| power_line_frequency_(power_line_frequency), |
| device_fd_(v4l2), |
| is_capturing_(false), |
| timeout_count_(0), |
| rotation_(rotation) {} |
| |
| void V4L2CaptureDelegate::AllocateAndStart( |
| int width, |
| int height, |
| float frame_rate, |
| std::unique_ptr<VideoCaptureDevice::Client> client) { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| DCHECK(client); |
| client_ = std::move(client); |
| |
| // Need to open camera with O_RDWR after Linux kernel 3.3. |
| device_fd_.reset( |
| HANDLE_EINTR(v4l2_->open(device_descriptor_.device_id.c_str(), O_RDWR))); |
| if (!device_fd_.is_valid()) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToOpenV4L2DeviceDriverFile, |
| FROM_HERE, "Failed to open V4L2 device driver file."); |
| return; |
| } |
| |
| ResetUserAndCameraControlsToDefault(); |
| |
| // In theory, checking for CAPTURE/OUTPUT in caps.capabilities should only |
| // be done if V4L2_CAP_DEVICE_CAPS is not set. However, this was not done |
| // in the past and it is unclear if it breaks with existing devices. And if |
| // a device is accepted incorrectly then it will not have any usable |
| // formats and is skipped anyways. |
| v4l2_capability cap = {}; |
| if (!(DoIoctl(VIDIOC_QUERYCAP, &cap) == 0 && |
| (((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) && |
| !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) || |
| ((cap.capabilities & V4L2_CAP_DEVICE_CAPS) && |
| (cap.device_caps & V4L2_CAP_VIDEO_CAPTURE) && |
| !(cap.device_caps & V4L2_CAP_VIDEO_OUTPUT))))) { |
| device_fd_.reset(); |
| SetErrorState(VideoCaptureError::kV4L2ThisIsNotAV4L2VideoCaptureDevice, |
| FROM_HERE, "This is not a V4L2 video capture device"); |
| return; |
| } |
| |
| // Get supported video formats in preferred order. For large resolutions, |
| // favour mjpeg over raw formats. |
| const std::vector<uint32_t>& desired_v4l2_formats = |
| GetListOfUsableFourCcs(width > kMjpegWidth || height > kMjpegHeight); |
| auto best = desired_v4l2_formats.end(); |
| |
| v4l2_fmtdesc fmtdesc = {}; |
| fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| for (; DoIoctl(VIDIOC_ENUM_FMT, &fmtdesc) == 0; ++fmtdesc.index) |
| best = std::find(desired_v4l2_formats.begin(), best, fmtdesc.pixelformat); |
| |
| if (best == desired_v4l2_formats.end()) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToFindASupportedCameraFormat, |
| FROM_HERE, "Failed to find a supported camera format."); |
| return; |
| } |
| |
| DVLOG(1) << "Chosen pixel format is " << FourccToString(*best); |
| FillV4L2Format(&video_fmt_, width, height, *best); |
| |
| if (DoIoctl(VIDIOC_S_FMT, &video_fmt_) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToSetVideoCaptureFormat, |
| FROM_HERE, "Failed to set video capture format"); |
| return; |
| } |
| const VideoPixelFormat pixel_format = |
| V4l2FourCcToChromiumPixelFormat(video_fmt_.fmt.pix.pixelformat); |
| if (pixel_format == PIXEL_FORMAT_UNKNOWN) { |
| SetErrorState(VideoCaptureError::kV4L2UnsupportedPixelFormat, FROM_HERE, |
| "Unsupported pixel format"); |
| return; |
| } |
| |
| // Set capture framerate in the form of capture interval. |
| v4l2_streamparm streamparm = {}; |
| streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| // The following line checks that the driver knows about framerate get/set. |
| if (DoIoctl(VIDIOC_G_PARM, &streamparm) >= 0) { |
| // Now check if the device is able to accept a capture framerate set. |
| if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) { |
| // |frame_rate| is float, approximate by a fraction. |
| streamparm.parm.capture.timeperframe.numerator = kFrameRatePrecision; |
| streamparm.parm.capture.timeperframe.denominator = |
| (frame_rate) ? (frame_rate * kFrameRatePrecision) |
| : (kTypicalFramerate * kFrameRatePrecision); |
| |
| if (DoIoctl(VIDIOC_S_PARM, &streamparm) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToSetCameraFramerate, |
| FROM_HERE, "Failed to set camera framerate"); |
| return; |
| } |
| DVLOG(2) << "Actual camera driverframerate: " |
| << streamparm.parm.capture.timeperframe.denominator << "/" |
| << streamparm.parm.capture.timeperframe.numerator; |
| } |
| } |
| // TODO(mcasas): what should be done if the camera driver does not allow |
| // framerate configuration, or the actual one is different from the desired? |
| |
| // Set anti-banding/anti-flicker to 50/60Hz. May fail due to not supported |
| // operation (|errno| == EINVAL in this case) or plain failure. |
| if ((power_line_frequency_ == V4L2_CID_POWER_LINE_FREQUENCY_50HZ) || |
| (power_line_frequency_ == V4L2_CID_POWER_LINE_FREQUENCY_60HZ) || |
| (power_line_frequency_ == V4L2_CID_POWER_LINE_FREQUENCY_AUTO)) { |
| struct v4l2_control control = {}; |
| control.id = V4L2_CID_POWER_LINE_FREQUENCY; |
| control.value = power_line_frequency_; |
| const int retval = DoIoctl(VIDIOC_S_CTRL, &control); |
| if (retval != 0) |
| DVLOG(1) << "Error setting power line frequency removal"; |
| } |
| |
| capture_format_.frame_size.SetSize(video_fmt_.fmt.pix.width, |
| video_fmt_.fmt.pix.height); |
| capture_format_.frame_rate = frame_rate; |
| capture_format_.pixel_format = pixel_format; |
| |
| if (!StartStream()) |
| return; |
| |
| client_->OnStarted(); |
| |
| // Post task to start fetching frames from v4l2. |
| v4l2_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&V4L2CaptureDelegate::DoCapture, GetWeakPtr())); |
| } |
| |
| void V4L2CaptureDelegate::StopAndDeAllocate() { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| StopStream(); |
| // At this point we can close the device. |
| // This is also needed for correctly changing settings later via VIDIOC_S_FMT. |
| device_fd_.reset(); |
| client_.reset(); |
| } |
| |
| void V4L2CaptureDelegate::TakePhoto( |
| VideoCaptureDevice::TakePhotoCallback callback) { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| take_photo_callbacks_.push(std::move(callback)); |
| } |
| |
| void V4L2CaptureDelegate::GetPhotoState( |
| VideoCaptureDevice::GetPhotoStateCallback callback) { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| if (!device_fd_.is_valid() || !is_capturing_) |
| return; |
| |
| mojom::PhotoStatePtr photo_capabilities = mojo::CreateEmptyPhotoState(); |
| |
| photo_capabilities->pan = RetrieveUserControlRange(V4L2_CID_PAN_ABSOLUTE); |
| photo_capabilities->tilt = RetrieveUserControlRange(V4L2_CID_TILT_ABSOLUTE); |
| photo_capabilities->zoom = RetrieveUserControlRange(V4L2_CID_ZOOM_ABSOLUTE); |
| |
| v4l2_queryctrl manual_focus_ctrl = {}; |
| manual_focus_ctrl.id = V4L2_CID_FOCUS_ABSOLUTE; |
| if (RunIoctl(VIDIOC_QUERYCTRL, &manual_focus_ctrl)) |
| photo_capabilities->supported_focus_modes.push_back(MeteringMode::MANUAL); |
| |
| v4l2_queryctrl auto_focus_ctrl = {}; |
| auto_focus_ctrl.id = V4L2_CID_FOCUS_AUTO; |
| if (RunIoctl(VIDIOC_QUERYCTRL, &auto_focus_ctrl)) { |
| photo_capabilities->supported_focus_modes.push_back( |
| MeteringMode::CONTINUOUS); |
| } |
| |
| photo_capabilities->current_focus_mode = MeteringMode::NONE; |
| v4l2_control auto_focus_current = {}; |
| auto_focus_current.id = V4L2_CID_FOCUS_AUTO; |
| if (DoIoctl(VIDIOC_G_CTRL, &auto_focus_current) >= 0) { |
| photo_capabilities->current_focus_mode = auto_focus_current.value |
| ? MeteringMode::CONTINUOUS |
| : MeteringMode::MANUAL; |
| } |
| |
| photo_capabilities->focus_distance = |
| RetrieveUserControlRange(V4L2_CID_FOCUS_ABSOLUTE); |
| |
| v4l2_queryctrl auto_exposure_ctrl = {}; |
| auto_exposure_ctrl.id = V4L2_CID_EXPOSURE_AUTO; |
| if (RunIoctl(VIDIOC_QUERYCTRL, &auto_exposure_ctrl)) { |
| photo_capabilities->supported_exposure_modes.push_back( |
| MeteringMode::MANUAL); |
| photo_capabilities->supported_exposure_modes.push_back( |
| MeteringMode::CONTINUOUS); |
| } |
| |
| photo_capabilities->current_exposure_mode = MeteringMode::NONE; |
| v4l2_control exposure_current = {}; |
| exposure_current.id = V4L2_CID_EXPOSURE_AUTO; |
| if (DoIoctl(VIDIOC_G_CTRL, &exposure_current) >= 0) { |
| photo_capabilities->current_exposure_mode = |
| exposure_current.value == V4L2_EXPOSURE_MANUAL |
| ? MeteringMode::MANUAL |
| : MeteringMode::CONTINUOUS; |
| } |
| |
| // Exposure compensation is valid if V4L2_CID_EXPOSURE_AUTO control is set to |
| // AUTO, SHUTTER_PRIORITY or APERTURE_PRIORITY. Drivers should interpret the |
| // values as 0.001 EV units, where the value 1000 stands for +1 EV. |
| photo_capabilities->exposure_compensation = |
| RetrieveUserControlRange(V4L2_CID_AUTO_EXPOSURE_BIAS); |
| |
| // Determines the exposure time of the camera sensor. Drivers interpret values |
| // as 100 µs units, same as specs. |
| photo_capabilities->exposure_time = |
| RetrieveUserControlRange(V4L2_CID_EXPOSURE_ABSOLUTE); |
| |
| photo_capabilities->color_temperature = |
| RetrieveUserControlRange(V4L2_CID_WHITE_BALANCE_TEMPERATURE); |
| if (photo_capabilities->color_temperature) { |
| photo_capabilities->supported_white_balance_modes.push_back( |
| MeteringMode::MANUAL); |
| } |
| |
| v4l2_queryctrl white_balance_ctrl = {}; |
| white_balance_ctrl.id = V4L2_CID_AUTO_WHITE_BALANCE; |
| if (RunIoctl(VIDIOC_QUERYCTRL, &white_balance_ctrl)) { |
| photo_capabilities->supported_white_balance_modes.push_back( |
| MeteringMode::CONTINUOUS); |
| } |
| |
| photo_capabilities->current_white_balance_mode = MeteringMode::NONE; |
| v4l2_control white_balance_current = {}; |
| white_balance_current.id = V4L2_CID_AUTO_WHITE_BALANCE; |
| if (DoIoctl(VIDIOC_G_CTRL, &white_balance_current) >= 0) { |
| photo_capabilities->current_white_balance_mode = |
| white_balance_current.value ? MeteringMode::CONTINUOUS |
| : MeteringMode::MANUAL; |
| } |
| |
| photo_capabilities->iso = mojom::Range::New(); |
| photo_capabilities->height = mojom::Range::New( |
| capture_format_.frame_size.height(), capture_format_.frame_size.height(), |
| capture_format_.frame_size.height(), 0 /* step */); |
| photo_capabilities->width = mojom::Range::New( |
| capture_format_.frame_size.width(), capture_format_.frame_size.width(), |
| capture_format_.frame_size.width(), 0 /* step */); |
| photo_capabilities->red_eye_reduction = mojom::RedEyeReduction::NEVER; |
| photo_capabilities->torch = false; |
| |
| photo_capabilities->brightness = |
| RetrieveUserControlRange(V4L2_CID_BRIGHTNESS); |
| photo_capabilities->contrast = RetrieveUserControlRange(V4L2_CID_CONTRAST); |
| photo_capabilities->saturation = |
| RetrieveUserControlRange(V4L2_CID_SATURATION); |
| photo_capabilities->sharpness = RetrieveUserControlRange(V4L2_CID_SHARPNESS); |
| |
| std::move(callback).Run(std::move(photo_capabilities)); |
| } |
| |
| void V4L2CaptureDelegate::SetPhotoOptions( |
| mojom::PhotoSettingsPtr settings, |
| VideoCaptureDevice::SetPhotoOptionsCallback callback) { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| if (!device_fd_.is_valid() || !is_capturing_) |
| return; |
| |
| if (settings->has_pan) { |
| v4l2_control pan_current = {}; |
| pan_current.id = V4L2_CID_PAN_ABSOLUTE; |
| pan_current.value = settings->pan; |
| if (DoIoctl(VIDIOC_S_CTRL, &pan_current) < 0) |
| DPLOG(ERROR) << "setting pan to " << settings->pan; |
| } |
| |
| if (settings->has_tilt) { |
| v4l2_control tilt_current = {}; |
| tilt_current.id = V4L2_CID_TILT_ABSOLUTE; |
| tilt_current.value = settings->tilt; |
| if (DoIoctl(VIDIOC_S_CTRL, &tilt_current) < 0) |
| DPLOG(ERROR) << "setting tilt to " << settings->tilt; |
| } |
| |
| if (settings->has_zoom) { |
| v4l2_control zoom_current = {}; |
| zoom_current.id = V4L2_CID_ZOOM_ABSOLUTE; |
| zoom_current.value = settings->zoom; |
| if (DoIoctl(VIDIOC_S_CTRL, &zoom_current) < 0) |
| DPLOG(ERROR) << "setting zoom to " << settings->zoom; |
| } |
| |
| if (settings->has_focus_mode && |
| (settings->focus_mode == mojom::MeteringMode::MANUAL || |
| settings->focus_mode == mojom::MeteringMode::CONTINUOUS)) { |
| v4l2_control auto_focus = {}; |
| auto_focus.id = V4L2_CID_FOCUS_AUTO; |
| auto_focus.value = |
| settings->focus_mode == mojom::MeteringMode::MANUAL ? false : true; |
| if (DoIoctl(VIDIOC_S_CTRL, &auto_focus) < 0) |
| DPLOG(ERROR) << "setting focusMode to " |
| << (settings->focus_mode == mojom::MeteringMode::MANUAL |
| ? "manual" |
| : "continuous"); |
| } |
| |
| if (settings->has_focus_distance && |
| settings->focus_mode == mojom::MeteringMode::MANUAL) { |
| v4l2_control set_focus_distance_ctrl = {}; |
| set_focus_distance_ctrl.id = V4L2_CID_FOCUS_ABSOLUTE; |
| set_focus_distance_ctrl.value = settings->focus_distance; |
| if (DoIoctl(VIDIOC_S_CTRL, &set_focus_distance_ctrl) < 0) |
| DPLOG(ERROR) << "setting focus distance to " << settings->focus_distance; |
| } |
| |
| if (settings->has_white_balance_mode && |
| (settings->white_balance_mode == mojom::MeteringMode::CONTINUOUS || |
| settings->white_balance_mode == mojom::MeteringMode::MANUAL)) { |
| v4l2_control white_balance_set = {}; |
| white_balance_set.id = V4L2_CID_AUTO_WHITE_BALANCE; |
| white_balance_set.value = |
| settings->white_balance_mode == mojom::MeteringMode::CONTINUOUS; |
| DoIoctl(VIDIOC_S_CTRL, &white_balance_set); |
| } |
| |
| if (settings->has_color_temperature) { |
| v4l2_control auto_white_balance_current = {}; |
| auto_white_balance_current.id = V4L2_CID_AUTO_WHITE_BALANCE; |
| const int result = DoIoctl(VIDIOC_G_CTRL, &auto_white_balance_current); |
| // Color temperature can only be applied if Auto White Balance is off. |
| if (result >= 0 && !auto_white_balance_current.value) { |
| v4l2_control set_temperature = {}; |
| set_temperature.id = V4L2_CID_WHITE_BALANCE_TEMPERATURE; |
| set_temperature.value = settings->color_temperature; |
| DoIoctl(VIDIOC_S_CTRL, &set_temperature); |
| } |
| } |
| |
| if (settings->has_exposure_mode && |
| (settings->exposure_mode == mojom::MeteringMode::CONTINUOUS || |
| settings->exposure_mode == mojom::MeteringMode::MANUAL)) { |
| v4l2_control exposure_mode_set = {}; |
| exposure_mode_set.id = V4L2_CID_EXPOSURE_AUTO; |
| exposure_mode_set.value = |
| settings->exposure_mode == mojom::MeteringMode::CONTINUOUS |
| ? V4L2_EXPOSURE_APERTURE_PRIORITY |
| : V4L2_EXPOSURE_MANUAL; |
| DoIoctl(VIDIOC_S_CTRL, &exposure_mode_set); |
| } |
| |
| if (settings->has_exposure_compensation) { |
| v4l2_control auto_exposure_current = {}; |
| auto_exposure_current.id = V4L2_CID_EXPOSURE_AUTO; |
| const int result = DoIoctl(VIDIOC_G_CTRL, &auto_exposure_current); |
| // Exposure Compensation is effective only when V4L2_CID_EXPOSURE_AUTO |
| // control is set to AUTO, SHUTTER_PRIORITY or APERTURE_PRIORITY. |
| if (result >= 0 && auto_exposure_current.value != V4L2_EXPOSURE_MANUAL) { |
| v4l2_control set_exposure = {}; |
| set_exposure.id = V4L2_CID_AUTO_EXPOSURE_BIAS; |
| set_exposure.value = settings->exposure_compensation; |
| DoIoctl(VIDIOC_S_CTRL, &set_exposure); |
| } |
| } |
| |
| if (settings->has_exposure_time) { |
| v4l2_control exposure_time_current = {}; |
| exposure_time_current.id = V4L2_CID_EXPOSURE_AUTO; |
| const int result = DoIoctl(VIDIOC_G_CTRL, &exposure_time_current); |
| // Exposure time can only be applied if V4L2_CID_EXPOSURE_AUTO is set to |
| // V4L2_EXPOSURE_MANUAL or V4L2_EXPOSURE_SHUTTER_PRIORITY. |
| if (result >= 0 && |
| (exposure_time_current.value == V4L2_EXPOSURE_MANUAL || |
| exposure_time_current.value == V4L2_EXPOSURE_SHUTTER_PRIORITY)) { |
| v4l2_control set_exposure_time = {}; |
| set_exposure_time.id = V4L2_CID_EXPOSURE_ABSOLUTE; |
| set_exposure_time.value = settings->exposure_time; |
| DoIoctl(VIDIOC_S_CTRL, &set_exposure_time); |
| } |
| } |
| |
| if (settings->has_brightness) { |
| v4l2_control current = {}; |
| current.id = V4L2_CID_BRIGHTNESS; |
| current.value = settings->brightness; |
| if (DoIoctl(VIDIOC_S_CTRL, ¤t) < 0) |
| DPLOG(ERROR) << "setting brightness to " << settings->brightness; |
| } |
| if (settings->has_contrast) { |
| v4l2_control current = {}; |
| current.id = V4L2_CID_CONTRAST; |
| current.value = settings->contrast; |
| if (DoIoctl(VIDIOC_S_CTRL, ¤t) < 0) |
| DPLOG(ERROR) << "setting contrast to " << settings->contrast; |
| } |
| if (settings->has_saturation) { |
| v4l2_control current = {}; |
| current.id = V4L2_CID_SATURATION; |
| current.value = settings->saturation; |
| if (DoIoctl(VIDIOC_S_CTRL, ¤t) < 0) |
| DPLOG(ERROR) << "setting saturation to " << settings->saturation; |
| } |
| if (settings->has_sharpness) { |
| v4l2_control current = {}; |
| current.id = V4L2_CID_SHARPNESS; |
| current.value = settings->sharpness; |
| if (DoIoctl(VIDIOC_S_CTRL, ¤t) < 0) |
| DPLOG(ERROR) << "setting sharpness to " << settings->sharpness; |
| } |
| |
| std::move(callback).Run(true); |
| } |
| |
| void V4L2CaptureDelegate::SetRotation(int rotation) { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| DCHECK_GE(rotation, 0); |
| DCHECK_LT(rotation, 360); |
| DCHECK_EQ(rotation % 90, 0); |
| rotation_ = rotation; |
| } |
| |
| base::WeakPtr<V4L2CaptureDelegate> V4L2CaptureDelegate::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| V4L2CaptureDelegate::~V4L2CaptureDelegate() = default; |
| |
| bool V4L2CaptureDelegate::RunIoctl(int request, void* argp) { |
| int num_retries = 0; |
| for (; DoIoctl(request, argp) < 0 && num_retries < kMaxIOCtrlRetries; |
| ++num_retries) { |
| DPLOG(WARNING) << "ioctl"; |
| } |
| DPLOG_IF(ERROR, num_retries == kMaxIOCtrlRetries); |
| return num_retries != kMaxIOCtrlRetries; |
| } |
| |
| int V4L2CaptureDelegate::DoIoctl(int request, void* argp) { |
| return HANDLE_EINTR(v4l2_->ioctl(device_fd_.get(), request, argp)); |
| } |
| |
| mojom::RangePtr V4L2CaptureDelegate::RetrieveUserControlRange(int control_id) { |
| mojom::RangePtr capability = mojom::Range::New(); |
| |
| v4l2_queryctrl range = {}; |
| range.id = control_id; |
| range.type = V4L2_CTRL_TYPE_INTEGER; |
| if (!RunIoctl(VIDIOC_QUERYCTRL, &range)) |
| return mojom::Range::New(); |
| capability->max = range.maximum; |
| capability->min = range.minimum; |
| capability->step = range.step; |
| |
| v4l2_control current = {}; |
| current.id = control_id; |
| if (!RunIoctl(VIDIOC_G_CTRL, ¤t)) |
| return mojom::Range::New(); |
| capability->current = current.value; |
| |
| return capability; |
| } |
| |
| void V4L2CaptureDelegate::ResetUserAndCameraControlsToDefault() { |
| // Set V4L2_CID_AUTO_WHITE_BALANCE to false first. |
| v4l2_control auto_white_balance = {}; |
| auto_white_balance.id = V4L2_CID_AUTO_WHITE_BALANCE; |
| auto_white_balance.value = false; |
| if (!RunIoctl(VIDIOC_S_CTRL, &auto_white_balance)) |
| return; |
| |
| std::vector<struct v4l2_ext_control> special_camera_controls; |
| // Set V4L2_CID_EXPOSURE_AUTO to V4L2_EXPOSURE_MANUAL. |
| v4l2_ext_control auto_exposure = {}; |
| auto_exposure.id = V4L2_CID_EXPOSURE_AUTO; |
| auto_exposure.value = V4L2_EXPOSURE_MANUAL; |
| special_camera_controls.push_back(auto_exposure); |
| // Set V4L2_CID_EXPOSURE_AUTO_PRIORITY to false. |
| v4l2_ext_control priority_auto_exposure = {}; |
| priority_auto_exposure.id = V4L2_CID_EXPOSURE_AUTO_PRIORITY; |
| priority_auto_exposure.value = false; |
| special_camera_controls.push_back(priority_auto_exposure); |
| // Set V4L2_CID_FOCUS_AUTO to false. |
| v4l2_ext_control auto_focus = {}; |
| auto_focus.id = V4L2_CID_FOCUS_AUTO; |
| auto_focus.value = false; |
| special_camera_controls.push_back(auto_focus); |
| |
| struct v4l2_ext_controls ext_controls = {}; |
| ext_controls.ctrl_class = V4L2_CID_CAMERA_CLASS; |
| ext_controls.count = special_camera_controls.size(); |
| ext_controls.controls = special_camera_controls.data(); |
| if (DoIoctl(VIDIOC_S_EXT_CTRLS, &ext_controls) < 0) |
| DPLOG(INFO) << "VIDIOC_S_EXT_CTRLS"; |
| |
| std::vector<struct v4l2_ext_control> camera_controls; |
| for (const auto& control : kControls) { |
| std::vector<struct v4l2_ext_control> camera_controls; |
| |
| v4l2_queryctrl range = {}; |
| range.id = control.control_base | V4L2_CTRL_FLAG_NEXT_CTRL; |
| while (0 == DoIoctl(VIDIOC_QUERYCTRL, &range)) { |
| if (V4L2_CTRL_ID2CLASS(range.id) != V4L2_CTRL_ID2CLASS(control.class_id)) |
| break; |
| range.id |= V4L2_CTRL_FLAG_NEXT_CTRL; |
| |
| if (IsSpecialControl(range.id & ~V4L2_CTRL_FLAG_NEXT_CTRL)) |
| continue; |
| if (IsBlockedControl(range.id & ~V4L2_CTRL_FLAG_NEXT_CTRL)) |
| continue; |
| |
| struct v4l2_ext_control ext_control = {}; |
| ext_control.id = range.id & ~V4L2_CTRL_FLAG_NEXT_CTRL; |
| ext_control.value = range.default_value; |
| camera_controls.push_back(ext_control); |
| } |
| |
| if (!camera_controls.empty()) { |
| struct v4l2_ext_controls ext_controls2 = {}; |
| ext_controls2.ctrl_class = control.class_id; |
| ext_controls2.count = camera_controls.size(); |
| ext_controls2.controls = camera_controls.data(); |
| if (DoIoctl(VIDIOC_S_EXT_CTRLS, &ext_controls2) < 0) |
| DPLOG(INFO) << "VIDIOC_S_EXT_CTRLS"; |
| } |
| } |
| |
| // Now set the special flags to the default values |
| v4l2_queryctrl range = {}; |
| range.id = V4L2_CID_AUTO_WHITE_BALANCE; |
| DoIoctl(VIDIOC_QUERYCTRL, &range); |
| auto_white_balance.value = range.default_value; |
| DoIoctl(VIDIOC_S_CTRL, &auto_white_balance); |
| |
| special_camera_controls.clear(); |
| memset(&range, 0, sizeof(range)); |
| range.id = V4L2_CID_EXPOSURE_AUTO; |
| DoIoctl(VIDIOC_QUERYCTRL, &range); |
| auto_exposure.value = range.default_value; |
| special_camera_controls.push_back(auto_exposure); |
| |
| memset(&range, 0, sizeof(range)); |
| range.id = V4L2_CID_EXPOSURE_AUTO_PRIORITY; |
| DoIoctl(VIDIOC_QUERYCTRL, &range); |
| priority_auto_exposure.value = range.default_value; |
| special_camera_controls.push_back(priority_auto_exposure); |
| |
| memset(&range, 0, sizeof(range)); |
| range.id = V4L2_CID_FOCUS_AUTO; |
| DoIoctl(VIDIOC_QUERYCTRL, &range); |
| auto_focus.value = range.default_value; |
| special_camera_controls.push_back(auto_focus); |
| |
| memset(&ext_controls, 0, sizeof(ext_controls)); |
| ext_controls.ctrl_class = V4L2_CID_CAMERA_CLASS; |
| ext_controls.count = special_camera_controls.size(); |
| ext_controls.controls = special_camera_controls.data(); |
| if (DoIoctl(VIDIOC_S_EXT_CTRLS, &ext_controls) < 0) |
| DPLOG(INFO) << "VIDIOC_S_EXT_CTRLS"; |
| } |
| |
| bool V4L2CaptureDelegate::MapAndQueueBuffer(int index) { |
| v4l2_buffer buffer; |
| FillV4L2Buffer(&buffer, index); |
| |
| if (DoIoctl(VIDIOC_QUERYBUF, &buffer) < 0) { |
| DLOG(ERROR) << "Error querying status of a MMAP V4L2 buffer"; |
| return false; |
| } |
| |
| const auto buffer_tracker = base::MakeRefCounted<BufferTracker>(v4l2_); |
| if (!buffer_tracker->Init(device_fd_.get(), buffer)) { |
| DLOG(ERROR) << "Error creating BufferTracker"; |
| return false; |
| } |
| buffer_tracker_pool_.push_back(buffer_tracker); |
| |
| // Enqueue the buffer in the drivers incoming queue. |
| if (DoIoctl(VIDIOC_QBUF, &buffer) < 0) { |
| DLOG(ERROR) << "Error enqueuing a V4L2 buffer back into the driver"; |
| return false; |
| } |
| return true; |
| } |
| |
| bool V4L2CaptureDelegate::StartStream() { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| DCHECK(!is_capturing_); |
| |
| v4l2_requestbuffers r_buffer; |
| FillV4L2RequestBuffer(&r_buffer, kNumVideoBuffers); |
| if (DoIoctl(VIDIOC_REQBUFS, &r_buffer) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2ErrorRequestingMmapBuffers, FROM_HERE, |
| "Error requesting MMAP buffers from V4L2"); |
| return false; |
| } |
| for (unsigned int i = 0; i < r_buffer.count; ++i) { |
| if (!MapAndQueueBuffer(i)) { |
| SetErrorState(VideoCaptureError::kV4L2AllocateBufferFailed, FROM_HERE, |
| "Allocate buffer failed"); |
| return false; |
| } |
| } |
| v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| if (DoIoctl(VIDIOC_STREAMON, &capture_type) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2VidiocStreamonFailed, FROM_HERE, |
| "VIDIOC_STREAMON failed"); |
| return false; |
| } |
| is_capturing_ = true; |
| return true; |
| } |
| |
| void V4L2CaptureDelegate::DoCapture() { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| if (!is_capturing_) |
| return; |
| |
| pollfd device_pfd = {}; |
| device_pfd.fd = device_fd_.get(); |
| device_pfd.events = POLLIN; |
| |
| const int result = |
| HANDLE_EINTR(v4l2_->poll(&device_pfd, 1, kCaptureTimeoutMs)); |
| if (result < 0) { |
| SetErrorState(VideoCaptureError::kV4L2PollFailed, FROM_HERE, "Poll failed"); |
| return; |
| } |
| |
| // Check if poll() timed out; track the amount of times it did in a row and |
| // throw an error if it times out too many times. |
| if (result == 0) { |
| timeout_count_++; |
| if (timeout_count_ == 1) { |
| // TODO(crbug.com/1010557): this is an unfortunate workaround for an issue |
| // with the Huddly GO camera where the device seems to get into a deadlock |
| // state. As best as we can tell for now, there is a synchronization issue |
| // in older kernels, and stopping and starting the stream gets the camera |
| // out of this bad state. Upgrading the kernel is difficult so this is our |
| // way out for now. |
| DLOG(WARNING) << "Restarting camera stream"; |
| if (!StopStream() || !StartStream()) |
| return; |
| v4l2_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&V4L2CaptureDelegate::DoCapture, GetWeakPtr())); |
| return; |
| } else if (timeout_count_ >= kContinuousTimeoutLimit) { |
| SetErrorState( |
| VideoCaptureError::kV4L2MultipleContinuousTimeoutsWhileReadPolling, |
| FROM_HERE, "Multiple continuous timeouts while read-polling."); |
| timeout_count_ = 0; |
| return; |
| } |
| } else { |
| timeout_count_ = 0; |
| } |
| |
| // Deenqueue, send and reenqueue a buffer if the driver has filled one in. |
| if (device_pfd.revents & POLLIN) { |
| v4l2_buffer buffer; |
| FillV4L2Buffer(&buffer, 0); |
| |
| if (DoIoctl(VIDIOC_DQBUF, &buffer) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToDequeueCaptureBuffer, |
| FROM_HERE, "Failed to dequeue capture buffer"); |
| return; |
| } |
| |
| buffer_tracker_pool_[buffer.index]->set_payload_size(buffer.bytesused); |
| const scoped_refptr<BufferTracker>& buffer_tracker = |
| buffer_tracker_pool_[buffer.index]; |
| |
| // There's a wide-spread issue where the kernel does not report accurate, |
| // monotonically-increasing timestamps in the v4l2_buffer::timestamp |
| // field (goo.gl/Nlfamz). |
| // Until this issue is fixed, just use the reference clock as a source of |
| // media timestamps. |
| const base::TimeTicks now = base::TimeTicks::Now(); |
| if (first_ref_time_.is_null()) |
| first_ref_time_ = now; |
| const base::TimeDelta timestamp = now - first_ref_time_; |
| |
| #ifdef V4L2_BUF_FLAG_ERROR |
| bool buf_error_flag_set = buffer.flags & V4L2_BUF_FLAG_ERROR; |
| #else |
| bool buf_error_flag_set = false; |
| #endif |
| if (buf_error_flag_set) { |
| #ifdef V4L2_BUF_FLAG_ERROR |
| LOG(ERROR) << "Dequeued v4l2 buffer contains corrupted data (" |
| << buffer.bytesused << " bytes)."; |
| buffer.bytesused = 0; |
| client_->OnFrameDropped( |
| VideoCaptureFrameDropReason::kV4L2BufferErrorFlagWasSet); |
| #endif |
| } else if (buffer.bytesused < |
| media::VideoFrame::AllocationSize(capture_format_.pixel_format, |
| capture_format_.frame_size)) { |
| LOG(ERROR) << "Dequeued v4l2 buffer contains invalid length (" |
| << buffer.bytesused << " bytes)."; |
| buffer.bytesused = 0; |
| client_->OnFrameDropped( |
| VideoCaptureFrameDropReason::kV4L2InvalidNumberOfBytesInBuffer); |
| } else { |
| // TODO(julien.isorce): build gfx color space from v4l2 color space. |
| // primary = v4l2_format->fmt.pix.colorspace; |
| // range = v4l2_format->fmt.pix.quantization; |
| // matrix = v4l2_format->fmt.pix.ycbcr_enc; |
| // transfer = v4l2_format->fmt.pix.xfer_func; |
| // See http://crbug.com/959919. |
| client_->OnIncomingCapturedData( |
| buffer_tracker->start(), buffer_tracker->payload_size(), |
| capture_format_, gfx::ColorSpace(), rotation_, false /* flip_y */, |
| now, timestamp); |
| } |
| |
| while (!take_photo_callbacks_.empty()) { |
| VideoCaptureDevice::TakePhotoCallback cb = |
| std::move(take_photo_callbacks_.front()); |
| take_photo_callbacks_.pop(); |
| |
| mojom::BlobPtr blob = |
| RotateAndBlobify(buffer_tracker->start(), buffer.bytesused, |
| capture_format_, rotation_); |
| if (blob) |
| std::move(cb).Run(std::move(blob)); |
| } |
| |
| if (DoIoctl(VIDIOC_QBUF, &buffer) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToEnqueueCaptureBuffer, |
| FROM_HERE, "Failed to enqueue capture buffer"); |
| return; |
| } |
| } |
| |
| v4l2_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&V4L2CaptureDelegate::DoCapture, GetWeakPtr())); |
| } |
| |
| bool V4L2CaptureDelegate::StopStream() { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| if (!is_capturing_) |
| return false; |
| |
| is_capturing_ = false; |
| |
| // The order is important: stop streaming, clear |buffer_pool_|, |
| // thus munmap()ing the v4l2_buffers, and then return them to the OS. |
| v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| if (DoIoctl(VIDIOC_STREAMOFF, &capture_type) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2VidiocStreamoffFailed, FROM_HERE, |
| "VIDIOC_STREAMOFF failed"); |
| return false; |
| } |
| |
| buffer_tracker_pool_.clear(); |
| |
| v4l2_requestbuffers r_buffer; |
| FillV4L2RequestBuffer(&r_buffer, 0); |
| if (DoIoctl(VIDIOC_REQBUFS, &r_buffer) < 0) { |
| SetErrorState(VideoCaptureError::kV4L2FailedToVidiocReqbufsWithCount0, |
| FROM_HERE, "Failed to VIDIOC_REQBUFS with count = 0"); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void V4L2CaptureDelegate::SetErrorState(VideoCaptureError error, |
| const base::Location& from_here, |
| const std::string& reason) { |
| DCHECK(v4l2_task_runner_->BelongsToCurrentThread()); |
| client_->OnError(error, from_here, reason); |
| } |
| |
| V4L2CaptureDelegate::BufferTracker::BufferTracker(V4L2CaptureDevice* v4l2) |
| : v4l2_(v4l2) {} |
| |
| V4L2CaptureDelegate::BufferTracker::~BufferTracker() { |
| if (!start_) |
| return; |
| const int result = v4l2_->munmap(start_, length_); |
| PLOG_IF(ERROR, result < 0) << "Error munmap()ing V4L2 buffer"; |
| } |
| |
| bool V4L2CaptureDelegate::BufferTracker::Init(int fd, |
| const v4l2_buffer& buffer) { |
| // Some devices require mmap() to be called with both READ and WRITE. |
| // See http://crbug.com/178582. |
| constexpr int kFlags = PROT_READ | PROT_WRITE; |
| void* const start = v4l2_->mmap(nullptr, buffer.length, kFlags, MAP_SHARED, |
| fd, buffer.m.offset); |
| if (start == MAP_FAILED) { |
| DLOG(ERROR) << "Error mmap()ing a V4L2 buffer into userspace"; |
| return false; |
| } |
| start_ = static_cast<uint8_t*>(start); |
| length_ = buffer.length; |
| payload_size_ = 0; |
| return true; |
| } |
| |
| } // namespace media |