blob: 3a0ef104d4b5b96a47e5003d6600d072c49500ef [file] [log] [blame]
// 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, &current) < 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, &current) < 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, &current) < 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, &current) < 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, &current))
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