blob: ab49f03fd0fdce056161beb0628c6dc0a6eebde8 [file] [log] [blame]
/*
* Copyright 2016 The Chromium OS 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 "hal/usb/v4l2_camera_device.h"
#include <fcntl.h>
#include <linux/videodev2.h>
#include <poll.h>
#include <sys/ioctl.h>
#include <algorithm>
#include <limits>
#include <base/files/file_enumerator.h>
#include <base/files/file_util.h>
#include <base/files/scoped_file.h>
#include <base/posix/safe_strerror.h>
#include <base/strings/pattern.h>
#include <base/strings/stringprintf.h>
#include <base/timer/elapsed_timer.h>
#include <camera/camera_metadata.h>
#include <re2/re2.h>
#include "cros-camera/common.h"
#include "cros-camera/utils/camera_config.h"
#include "hal/usb/camera_characteristics.h"
#include "hal/usb/quirks.h"
namespace cros {
namespace {
// Since cameras might report non-integer fps but in Android Camera 3 API we
// can only set fps range with integer in metadata.
constexpr float kFpsDifferenceThreshold = 1.0f;
// The following exposure type strings are from UVC driver.
constexpr char kExposureTypeMenuStringAuto[] = "Auto Mode";
constexpr char kExposureTypeMenuStringManual[] = "Manual Mode";
constexpr char kExposureTypeMenuStringShutterPriority[] =
"Shutter Priority Mode";
constexpr char kExposureTypeMenuStringAperturePriority[] =
"Aperture Priority Mode";
const int ControlTypeToCid(ControlType type) {
switch (type) {
case kControlAutoWhiteBalance:
return V4L2_CID_AUTO_WHITE_BALANCE;
case kControlBrightness:
return V4L2_CID_BRIGHTNESS;
case kControlContrast:
return V4L2_CID_CONTRAST;
case kControlExposureAuto:
return V4L2_CID_EXPOSURE_AUTO;
case kControlExposureAutoPriority:
return V4L2_CID_EXPOSURE_AUTO_PRIORITY;
case kControlExposureTime:
return V4L2_CID_EXPOSURE_ABSOLUTE;
case kControlFocusAuto:
return V4L2_CID_FOCUS_AUTO;
case kControlFocusDistance:
return V4L2_CID_FOCUS_ABSOLUTE;
case kControlPan:
return V4L2_CID_PAN_ABSOLUTE;
case kControlSaturation:
return V4L2_CID_SATURATION;
case kControlSharpness:
return V4L2_CID_SHARPNESS;
case kControlTilt:
return V4L2_CID_TILT_ABSOLUTE;
case kControlZoom:
return V4L2_CID_ZOOM_ABSOLUTE;
case kControlWhiteBalanceTemperature:
return V4L2_CID_WHITE_BALANCE_TEMPERATURE;
case kControlPrivacy:
return V4L2_CID_PRIVACY;
default:
NOTREACHED() << "Unexpected control type " << type;
return -1;
}
}
const std::string ControlTypeToString(ControlType type) {
switch (type) {
case kControlAutoWhiteBalance:
return "auto white balance";
case kControlBrightness:
return "brightness";
case kControlContrast:
return "contrast";
case kControlExposureAuto:
return "exposure auto (0,3:auto, 1,2:manual)";
case kControlExposureAutoPriority:
return "exposure_auto_priority";
case kControlExposureTime:
return "exposure time";
case kControlFocusAuto:
return "auto focus";
case kControlFocusDistance:
return "focus distance";
case kControlPan:
return "pan";
case kControlSaturation:
return "saturation";
case kControlSharpness:
return "sharpness";
case kControlTilt:
return "tilt";
case kControlZoom:
return "zoom";
case kControlWhiteBalanceTemperature:
return "white balance temperature";
case kControlPrivacy:
return "privacy";
default:
NOTREACHED() << "Unexpected control type " << type;
return "N/A";
}
}
const std::string CidToString(int cid) {
switch (cid) {
case V4L2_CID_AUTO_WHITE_BALANCE:
return "V4L2_CID_AUTO_WHITE_BALANCE";
case V4L2_CID_BRIGHTNESS:
return "V4L2_CID_BRIGHTNESS";
case V4L2_CID_CONTRAST:
return "V4L2_CID_CONTRAST";
case V4L2_CID_EXPOSURE_ABSOLUTE:
return "V4L2_CID_EXPOSURE_ABSOLUTE";
case V4L2_CID_EXPOSURE_AUTO:
return "V4L2_CID_EXPOSURE_AUTO";
case V4L2_CID_EXPOSURE_AUTO_PRIORITY:
return "V4L2_CID_EXPOSURE_AUTO_PRIORITY";
case V4L2_CID_FOCUS_ABSOLUTE:
return "V4L2_CID_FOCUS_ABSOLUTE";
case V4L2_CID_FOCUS_AUTO:
return "V4L2_CID_FOCUS_AUTO";
case V4L2_CID_PAN_ABSOLUTE:
return "V4L2_CID_PAN_ABSOLUTE";
case V4L2_CID_SATURATION:
return "V4L2_CID_SATURATION";
case V4L2_CID_SHARPNESS:
return "V4L2_CID_SHARPNESS";
case V4L2_CID_TILT_ABSOLUTE:
return "V4L2_CID_TILT_ABSOLUTE";
case V4L2_CID_ZOOM_ABSOLUTE:
return "V4L2_CID_ZOOM_ABSOLUTE";
case V4L2_CID_WHITE_BALANCE_TEMPERATURE:
return "V4L2_CID_WHITE_BALANCE_TEMPERATURE";
default:
NOTREACHED() << "Unexpected cid " << cid;
return "N/A";
}
}
} // namespace
V4L2CameraDevice::V4L2CameraDevice()
: stream_on_(false),
device_info_(DeviceInfo()),
event_thread_("V4L2Event") {}
V4L2CameraDevice::V4L2CameraDevice(
const DeviceInfo& device_info,
CameraPrivacySwitchMonitor* privacy_switch_monitor)
: stream_on_(false),
device_info_(device_info),
event_thread_("V4L2 Event Thread"),
privacy_switch_monitor_(privacy_switch_monitor) {}
V4L2CameraDevice::~V4L2CameraDevice() {
device_fd_.reset();
}
int V4L2CameraDevice::Connect(const std::string& device_path) {
VLOGF(1) << "Connecting device path: " << device_path;
base::AutoLock l(lock_);
if (device_fd_.is_valid()) {
LOGF(ERROR) << "A camera device is opened (" << device_fd_.get()
<< "). Please close it first";
return -EIO;
}
// Since device node may be changed after suspend/resume, we allow to use
// symbolic link to access device.
device_fd_.reset(RetryDeviceOpen(device_path, O_RDWR));
if (!device_fd_.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return -errno;
}
if (!IsCameraDevice(device_path)) {
LOGF(ERROR) << device_path << " is not a V4L2 video capture device";
device_fd_.reset();
return -EINVAL;
}
// Get and set format here is used to prevent multiple camera using.
// UVC driver will acquire lock in VIDIOC_S_FMT and VIDIOC_S_SMT will fail if
// the camera is being used by a user. The second user will fail in Connect()
// instead of StreamOn(). Usually apps show better error message if camera
// open fails. If start preview fails, some apps do not handle it well.
int ret;
v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ret = TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_G_FMT, &fmt));
if (ret < 0) {
PLOGF(ERROR) << "Unable to G_FMT";
return -errno;
}
ret = TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_S_FMT, &fmt));
if (ret < 0) {
LOGF(WARNING) << "Unable to S_FMT: " << base::safe_strerror(errno)
<< ", maybe camera is being used by another app.";
return -errno;
}
// Only set power line frequency when the value is correct.
if (device_info_.power_line_frequency != PowerLineFrequency::FREQ_ERROR) {
ret = SetPowerLineFrequency(device_info_.power_line_frequency);
if (ret < 0) {
if (IsExternalCamera()) {
VLOGF(2) << "Ignore SetPowerLineFrequency error for external camera";
} else {
return -EINVAL;
}
}
}
// Initial autofocus state.
int32_t value;
focus_auto_supported_ = IsControlSupported(kControlFocusAuto) &&
GetControlValue(kControlFocusAuto, &value) == 0;
if (focus_auto_supported_) {
LOGF(INFO) << "Device supports auto focus control, current mode is "
<< (value == 0 ? "Off" : "Auto");
}
focus_distance_supported_ = IsControlSupported(kControlFocusDistance);
if (focus_distance_supported_) {
LOGF(INFO) << "Device supports focus distance control";
// Focus distance is valid when focus mode is off.
if (value == 0 && GetControlValue(kControlFocusDistance, &value) == 0) {
LOGF(INFO) << "Current distance is " << value;
}
}
// Query the initial auto white balance state.
white_balance_control_supported_ =
IsControlSupported(kControlAutoWhiteBalance) &&
IsControlSupported(kControlWhiteBalanceTemperature);
if (white_balance_control_supported_) {
if (GetControlValue(kControlAutoWhiteBalance, &value) == 0) {
if (value) {
LOGF(INFO) << "Current white balance control is Auto";
} else if (GetControlValue(kControlWhiteBalanceTemperature, &value) ==
0) {
LOGF(INFO) << "Current white balance temperature is " << value;
}
}
}
ControlInfo info;
ControlRange range;
manual_exposure_time_supported_ =
IsManualExposureTimeSupported(device_path, &range);
if (manual_exposure_time_supported_ &&
QueryControl(kControlExposureAuto, &info) == 0) {
if (GetControlValue(kControlExposureAuto, &value) == 0) {
switch (value) {
case V4L2_EXPOSURE_AUTO:
LOGF(INFO) << "Current exposure type is Auto";
auto_exposure_time_type_ = V4L2_EXPOSURE_AUTO;
// Prefer switching between AUTO<->SHUTTER_PRIORITY
if (base::Contains(info.menu_items,
kExposureTypeMenuStringShutterPriority)) {
manual_exposure_time_type_ = V4L2_EXPOSURE_SHUTTER_PRIORITY;
} else if (base::Contains(info.menu_items,
kExposureTypeMenuStringManual)) {
manual_exposure_time_type_ = V4L2_EXPOSURE_MANUAL;
} else {
NOTREACHED() << "No manual exposure time type supported";
}
break;
case V4L2_EXPOSURE_MANUAL:
LOGF(INFO) << "Current exposure type is Manual";
manual_exposure_time_type_ = V4L2_EXPOSURE_MANUAL;
// Prefer switching between APERTURE_PRIORITY<->MANUAL
if (base::Contains(info.menu_items,
kExposureTypeMenuStringAperturePriority)) {
auto_exposure_time_type_ = V4L2_EXPOSURE_APERTURE_PRIORITY;
} else if (base::Contains(info.menu_items,
kExposureTypeMenuStringAuto)) {
auto_exposure_time_type_ = V4L2_EXPOSURE_AUTO;
} else {
NOTREACHED() << "No auto exposure time type supported";
}
break;
case V4L2_EXPOSURE_SHUTTER_PRIORITY:
LOGF(INFO) << "Current exposure type is Shutter Priority";
manual_exposure_time_type_ = V4L2_EXPOSURE_SHUTTER_PRIORITY;
// Prefer switching between AUTO<->SHUTTER_PRIORITY
if (base::Contains(info.menu_items, kExposureTypeMenuStringAuto)) {
auto_exposure_time_type_ = V4L2_EXPOSURE_AUTO;
} else if (base::Contains(info.menu_items,
kExposureTypeMenuStringAperturePriority)) {
auto_exposure_time_type_ = V4L2_EXPOSURE_APERTURE_PRIORITY;
} else {
NOTREACHED() << "No auto exposure time type supported";
}
break;
case V4L2_EXPOSURE_APERTURE_PRIORITY:
LOGF(INFO) << "Current exposure type is Aperture Priority";
auto_exposure_time_type_ = V4L2_EXPOSURE_APERTURE_PRIORITY;
// Prefer switching between APERTURE_PRIORITY<->MANUAL
if (base::Contains(info.menu_items, kExposureTypeMenuStringManual)) {
manual_exposure_time_type_ = V4L2_EXPOSURE_MANUAL;
} else if (base::Contains(info.menu_items,
kExposureTypeMenuStringShutterPriority)) {
manual_exposure_time_type_ = V4L2_EXPOSURE_SHUTTER_PRIORITY;
} else {
NOTREACHED() << "No manual exposure time type supported";
}
break;
default:
LOGF(WARNING) << "Unknown exposure type " << value;
manual_exposure_time_supported_ = false;
break;
}
}
}
if (IsControlSupported(kControlPrivacy)) {
ret = SubscribePrivacySwitchEvent();
if (ret != 0) {
LOGF(WARNING) << "Failed to subscribe privacy switch event: "
<< base::safe_strerror(-ret);
}
}
// Initialize the capabilities.
if (device_info_.quirks & kQuirkDisableFrameRateSetting) {
can_update_frame_rate_ = false;
} else {
v4l2_streamparm streamparm = {};
streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
can_update_frame_rate_ =
TEMP_FAILURE_RETRY(
ioctl(device_fd_.get(), VIDIOC_G_PARM, &streamparm)) >= 0 &&
(streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME);
}
return 0;
}
void V4L2CameraDevice::Disconnect() {
base::AutoLock l(lock_);
stream_on_ = false;
if (IsControlSupported(kControlPrivacy)) {
int ret = UnsubscribePrivacySwitchEvent();
if (ret != 0) {
LOGF(WARNING) << "Failed to unsubscribe privacy switch event: "
<< base::safe_strerror(-ret);
}
privacy_switch_monitor_->OnStatusChanged(PrivacySwitchState::kUnknown);
}
device_fd_.reset();
buffers_at_client_.clear();
}
int V4L2CameraDevice::StreamOn(uint32_t width,
uint32_t height,
uint32_t pixel_format,
float frame_rate,
std::vector<base::ScopedFD>* fds,
std::vector<uint32_t>* buffer_sizes) {
base::AutoLock l(lock_);
if (!device_fd_.is_valid()) {
LOGF(ERROR) << "Device is not opened";
return -ENODEV;
}
if (stream_on_) {
LOGF(ERROR) << "Device has stream already started";
return -EIO;
}
int ret;
// Some drivers use rational time per frame instead of float frame rate, this
// constant k is used to convert between both: A fps -> [k/k*A] seconds/frame.
v4l2_format fmt = {};
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = width;
fmt.fmt.pix.height = height;
fmt.fmt.pix.pixelformat = pixel_format;
ret = TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_S_FMT, &fmt));
if (ret < 0) {
PLOGF(ERROR) << "Unable to S_FMT";
return -errno;
}
VLOGF(1) << "Actual width: " << fmt.fmt.pix.width
<< ", height: " << fmt.fmt.pix.height
<< ", pixelformat: " << std::hex << fmt.fmt.pix.pixelformat
<< std::dec;
if (width != fmt.fmt.pix.width || height != fmt.fmt.pix.height ||
pixel_format != fmt.fmt.pix.pixelformat) {
LOGF(ERROR) << "Unsupported format: width " << width << ", height "
<< height << ", pixelformat " << pixel_format;
return -EINVAL;
}
if (CanUpdateFrameRate()) {
// We need to set frame rate even if it's same as the previous value, since
// uvcvideo driver will always reset it to the default value after the
// VIDIOC_S_FMT ioctl() call.
ret = SetFrameRate(frame_rate);
if (ret < 0) {
return ret;
}
} else {
// Simply assumes the frame rate is good if the device does not support
// frame rate settings.
frame_rate_ = frame_rate;
LOGF(INFO) << "No fps setting support, " << frame_rate
<< " fps setting is ignored";
}
v4l2_requestbuffers req_buffers;
memset(&req_buffers, 0, sizeof(req_buffers));
req_buffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buffers.memory = V4L2_MEMORY_MMAP;
req_buffers.count = kNumVideoBuffers;
if (TEMP_FAILURE_RETRY(
ioctl(device_fd_.get(), VIDIOC_REQBUFS, &req_buffers)) < 0) {
PLOGF(ERROR) << "REQBUFS fails";
return -errno;
}
VLOGF(1) << "Requested buffer number: " << req_buffers.count;
buffers_at_client_.resize(req_buffers.count);
std::vector<base::ScopedFD> temp_fds;
for (uint32_t i = 0; i < req_buffers.count; i++) {
v4l2_exportbuffer expbuf;
memset(&expbuf, 0, sizeof(expbuf));
expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
expbuf.index = i;
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_EXPBUF, &expbuf)) <
0) {
PLOGF(ERROR) << "EXPBUF (" << i << ") fails";
return -errno;
}
VLOGF(1) << "Exported frame buffer fd: " << expbuf.fd;
temp_fds.push_back(base::ScopedFD(expbuf.fd));
buffers_at_client_[i] = false;
v4l2_buffer buffer = {};
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.index = i;
buffer.memory = V4L2_MEMORY_MMAP;
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) {
PLOGF(ERROR) << "QBUF (" << i << ") fails";
return -errno;
}
buffer_sizes->push_back(buffer.length);
}
v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (TEMP_FAILURE_RETRY(
ioctl(device_fd_.get(), VIDIOC_STREAMON, &capture_type)) < 0) {
PLOGF(ERROR) << "STREAMON fails";
return -errno;
}
for (size_t i = 0; i < temp_fds.size(); i++) {
fds->push_back(std::move(temp_fds[i]));
}
// Query for the initial value of privacy button status while streaming on if
// it is supported.
if (IsControlSupported(kControlPrivacy)) {
int32_t value;
if (GetControlValue(kControlPrivacy, &value) == 0) {
privacy_switch_monitor_->OnStatusChanged(
value != 0 ? PrivacySwitchState::kOn : PrivacySwitchState::kOff);
} else {
LOGF(ERROR)
<< "Failed to get the initial status of camera privacy switch";
}
}
stream_on_ = true;
return 0;
}
int V4L2CameraDevice::StreamOff() {
base::AutoLock l(lock_);
if (!device_fd_.is_valid()) {
LOGF(ERROR) << "Device is not opened";
return -ENODEV;
}
// Because UVC driver cannot allow STREAMOFF after REQBUF(0), adding a check
// here to prevent it.
if (!stream_on_) {
return 0;
}
v4l2_buf_type capture_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (TEMP_FAILURE_RETRY(
ioctl(device_fd_.get(), VIDIOC_STREAMOFF, &capture_type)) < 0) {
PLOGF(ERROR) << "STREAMOFF fails";
return -errno;
}
v4l2_requestbuffers req_buffers;
memset(&req_buffers, 0, sizeof(req_buffers));
req_buffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req_buffers.memory = V4L2_MEMORY_MMAP;
req_buffers.count = 0;
if (TEMP_FAILURE_RETRY(
ioctl(device_fd_.get(), VIDIOC_REQBUFS, &req_buffers)) < 0) {
PLOGF(ERROR) << "REQBUFS fails";
return -errno;
}
buffers_at_client_.clear();
stream_on_ = false;
return 0;
}
int V4L2CameraDevice::GetNextFrameBuffer(uint32_t* buffer_id,
uint32_t* data_size,
uint64_t* v4l2_ts,
uint64_t* user_ts) {
base::AutoLock l(lock_);
if (!device_fd_.is_valid()) {
LOGF(ERROR) << "Device is not opened";
return -ENODEV;
}
if (!stream_on_) {
LOGF(ERROR) << "Streaming is not started";
return -EIO;
}
if (device_info_.quirks & kQuirkRestartOnTimeout) {
pollfd device_pfd = {};
device_pfd.fd = device_fd_.get();
device_pfd.events = POLLIN;
constexpr int kCaptureTimeoutMs = 1000;
const int result =
TEMP_FAILURE_RETRY(poll(&device_pfd, 1, kCaptureTimeoutMs));
if (result < 0) {
PLOGF(ERROR) << "Polling fails";
return -errno;
} else if (result == 0) {
LOGF(ERROR) << "Timed out waiting for captured frame";
return -ETIMEDOUT;
}
if (!(device_pfd.revents & POLLIN)) {
LOGF(ERROR) << "Unexpected event occurred while polling";
return -EIO;
}
}
v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_DQBUF, &buffer)) < 0) {
PLOGF(ERROR) << "DQBUF fails";
return -errno;
}
VLOGF(1) << "DQBUF returns index " << buffer.index << " length "
<< buffer.length;
if (buffer.index >= buffers_at_client_.size() ||
buffers_at_client_[buffer.index]) {
LOGF(ERROR) << "Invalid buffer id " << buffer.index;
return -EINVAL;
}
*buffer_id = buffer.index;
*data_size = buffer.bytesused;
struct timeval tv = buffer.timestamp;
*v4l2_ts = tv.tv_sec * 1'000'000'000LL + tv.tv_usec * 1000;
struct timespec ts;
if (clock_gettime(GetUvcClock(), &ts) < 0) {
LOGF(ERROR) << "Get clock time fails";
return -errno;
}
*user_ts = ts.tv_sec * 1'000'000'000LL + ts.tv_nsec;
buffers_at_client_[buffer.index] = true;
return 0;
}
int V4L2CameraDevice::ReuseFrameBuffer(uint32_t buffer_id) {
base::AutoLock l(lock_);
if (!device_fd_.is_valid()) {
LOGF(ERROR) << "Device is not opened";
return -ENODEV;
}
if (!stream_on_) {
LOGF(ERROR) << "Streaming is not started";
return -EIO;
}
VLOGF(1) << "Reuse buffer id: " << buffer_id;
if (buffer_id >= buffers_at_client_.size() ||
!buffers_at_client_[buffer_id]) {
LOGF(ERROR) << "Invalid buffer id: " << buffer_id;
return -EINVAL;
}
v4l2_buffer buffer;
memset(&buffer, 0, sizeof(buffer));
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = buffer_id;
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_QBUF, &buffer)) < 0) {
PLOGF(ERROR) << "QBUF fails";
return -errno;
}
buffers_at_client_[buffer.index] = false;
return 0;
}
bool V4L2CameraDevice::IsBufferFilled(uint32_t buffer_id) {
v4l2_buffer buffer = {};
buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buffer.memory = V4L2_MEMORY_MMAP;
buffer.index = buffer_id;
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_QUERYBUF, &buffer)) <
0) {
PLOGF(ERROR) << "QUERYBUF fails";
return false;
}
return buffer.flags & V4L2_BUF_FLAG_DONE;
}
int V4L2CameraDevice::SetAutoFocus(bool enable) {
if (!focus_auto_supported_) {
// Off mode is default supported
if (enable) {
LOGF(WARNING)
<< "Setting auto focus while device doesn't support. Ignored";
}
return 0;
}
if (enable && control_values_.count(kControlFocusDistance)) {
control_values_.erase(kControlFocusDistance);
}
return SetControlValue(kControlFocusAuto, enable ? 1 : 0);
}
int V4L2CameraDevice::SetFocusDistance(int32_t distance) {
if (!focus_distance_supported_) {
LOGF(WARNING) << "Setting focus distance while devcie doesn't support. "
<< "Ignored.";
return 0;
}
return SetControlValue(kControlFocusDistance, distance);
}
int V4L2CameraDevice::SetExposureTimeHundredUs(uint32_t exposure_time) {
if (!manual_exposure_time_supported_) {
if (exposure_time != kExposureTimeAuto) {
LOGF(WARNING)
<< "Setting manual exposure time when device doesn't support";
}
return 0;
}
if (exposure_time == kExposureTimeAuto) {
if (control_values_.count(kControlExposureTime))
control_values_.erase(kControlExposureTime);
return SetControlValue(kControlExposureAuto, auto_exposure_time_type_);
}
int ret = SetControlValue(kControlExposureAuto, manual_exposure_time_type_);
if (ret != 0)
return ret;
return SetControlValue(kControlExposureTime, exposure_time);
}
bool V4L2CameraDevice::CanUpdateFrameRate() {
return can_update_frame_rate_;
}
float V4L2CameraDevice::GetFrameRate() {
return frame_rate_;
}
int V4L2CameraDevice::SetFrameRate(float frame_rate) {
const int kFrameRatePrecision = 10000;
if (!device_fd_.is_valid()) {
LOGF(ERROR) << "Device is not opened";
return -ENODEV;
}
v4l2_streamparm streamparm = {};
streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
// The following line checks that the driver knows about framerate get/set.
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_G_PARM, &streamparm)) >=
0) {
// |frame_rate| is float, approximate by a fraction.
streamparm.parm.capture.timeperframe.numerator = kFrameRatePrecision;
streamparm.parm.capture.timeperframe.denominator =
(frame_rate * kFrameRatePrecision);
if (TEMP_FAILURE_RETRY(
ioctl(device_fd_.get(), VIDIOC_S_PARM, &streamparm)) < 0) {
LOGF(ERROR) << "Failed to set camera framerate";
return -errno;
}
VLOGF(1) << "Actual camera driver framerate: "
<< streamparm.parm.capture.timeperframe.denominator << "/"
<< streamparm.parm.capture.timeperframe.numerator;
float fps =
static_cast<float>(streamparm.parm.capture.timeperframe.denominator) /
streamparm.parm.capture.timeperframe.numerator;
if (std::fabs(fps - frame_rate) > kFpsDifferenceThreshold) {
LOGF(ERROR) << "Unsupported frame rate " << frame_rate;
return -EINVAL;
}
VLOGF(1) << "Successfully set the frame rate to: " << fps;
frame_rate_ = frame_rate;
}
return 0;
}
int V4L2CameraDevice::SetColorTemperature(uint32_t color_temperature) {
if (!white_balance_control_supported_) {
if (color_temperature != kColorTemperatureAuto) {
LOGF(WARNING) << "Setting color temperature when device doesn't support";
}
return 0;
}
if (color_temperature == kColorTemperatureAuto) {
if (control_values_.count(kControlWhiteBalanceTemperature))
control_values_.erase(kControlWhiteBalanceTemperature);
return SetControlValue(kControlAutoWhiteBalance, 1);
}
int ret = SetControlValue(kControlAutoWhiteBalance, 0);
if (ret != 0) {
LOGF(WARNING) << "Failed to set white_balance_control to manual";
return ret;
}
return SetControlValue(kControlWhiteBalanceTemperature, color_temperature);
}
int V4L2CameraDevice::SetControlValue(ControlType type, int32_t value) {
auto it = control_values_.find(type);
// Has cached value
if (it != control_values_.end()) {
if (it->second == value)
return 0;
else
control_values_.erase(type);
}
int ret = SetControlValue(device_fd_.get(), type, value);
if (ret != 0)
return ret;
int32_t current_value;
ret = GetControlValue(type, &current_value);
if (ret != 0)
return ret;
if (value == current_value) {
LOGF(INFO) << "Set " << ControlTypeToString(type) << " to " << value;
} else {
LOGF(WARNING) << "Set " << ControlTypeToString(type) << " to " << value
<< " but got " << current_value;
}
return 0;
}
int V4L2CameraDevice::GetControlValue(ControlType type, int32_t* value) {
auto it = control_values_.find(type);
// Has cached value
if (it != control_values_.end()) {
*value = it->second;
return 0;
}
int ret = GetControlValue(device_fd_.get(), type, value);
if (ret != 0)
return ret;
control_values_[type] = *value;
return 0;
}
bool V4L2CameraDevice::IsControlSupported(ControlType type) {
ControlInfo info;
return QueryControl(device_fd_.get(), type, &info) == 0;
}
int V4L2CameraDevice::QueryControl(ControlType type, ControlInfo* info) {
return QueryControl(device_fd_.get(), type, info);
}
// static
const SupportedFormats V4L2CameraDevice::GetDeviceSupportedFormats(
const std::string& device_path) {
VLOGF(1) << "Query supported formats for " << device_path;
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return {};
}
std::vector<std::string> filter_out_resolution_strings =
CameraConfig::Create(constants::kCrosCameraConfigPathString)
->GetStrings(constants::kCrosFilteredOutResolutions,
std::vector<std::string>());
std::vector<Size> filter_out_resolutions;
for (const auto& filter_out_resolution_string :
filter_out_resolution_strings) {
int width, height;
CHECK(RE2::FullMatch(filter_out_resolution_string, R"((\d+)x(\d+))", &width,
&height));
filter_out_resolutions.emplace_back(width, height);
}
SupportedFormats formats;
v4l2_fmtdesc v4l2_format = {};
v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
for (;
TEMP_FAILURE_RETRY(ioctl(fd.get(), VIDIOC_ENUM_FMT, &v4l2_format)) == 0;
++v4l2_format.index) {
SupportedFormat supported_format;
supported_format.fourcc = v4l2_format.pixelformat;
v4l2_frmsizeenum frame_size = {};
frame_size.pixel_format = v4l2_format.pixelformat;
for (; HANDLE_EINTR(ioctl(fd.get(), VIDIOC_ENUM_FRAMESIZES, &frame_size)) ==
0;
++frame_size.index) {
if (frame_size.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
supported_format.width = frame_size.discrete.width;
supported_format.height = frame_size.discrete.height;
} else if (frame_size.type == V4L2_FRMSIZE_TYPE_STEPWISE ||
frame_size.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) {
// TODO(henryhsu): see http://crbug.com/249953, support these devices.
LOGF(ERROR) << "Stepwise and continuous frame size are unsupported";
return formats;
}
bool is_filtered_out =
base::Contains(filter_out_resolutions,
Size(supported_format.width, supported_format.height));
if (is_filtered_out) {
LOGF(INFO) << "Filter " << supported_format.width << "x"
<< supported_format.height;
continue;
}
supported_format.frame_rates = GetFrameRateList(
fd.get(), v4l2_format.pixelformat, frame_size.discrete.width,
frame_size.discrete.height);
formats.push_back(supported_format);
}
}
return formats;
}
// static
int V4L2CameraDevice::QueryControl(int fd,
ControlType type,
ControlInfo* info) {
DCHECK(info);
info->menu_items.clear();
int control_id = ControlTypeToCid(type);
v4l2_queryctrl query_ctrl = {.id = static_cast<__u32>(control_id)};
if (HANDLE_EINTR(ioctl(fd, VIDIOC_QUERYCTRL, &query_ctrl)) < 0) {
VLOGF(1) << "Unsupported control:" << CidToString(control_id);
return -errno;
}
if (query_ctrl.flags & V4L2_CTRL_FLAG_DISABLED) {
LOGF(WARNING) << "Disabled control:" << CidToString(control_id);
return -EPERM;
}
switch (query_ctrl.type) {
case V4L2_CTRL_TYPE_INTEGER:
case V4L2_CTRL_TYPE_BOOLEAN:
case V4L2_CTRL_TYPE_MENU:
case V4L2_CTRL_TYPE_STRING:
case V4L2_CTRL_TYPE_INTEGER_MENU:
case V4L2_CTRL_TYPE_U8:
case V4L2_CTRL_TYPE_U16:
case V4L2_CTRL_TYPE_U32:
break;
case V4L2_CTRL_TYPE_INTEGER64:
LOGF(WARNING) << "Unsupported query V4L2_CTRL_TYPE_INTEGER64:"
<< CidToString(control_id);
return -EINVAL;
default:
info->range.minimum = query_ctrl.minimum;
info->range.maximum = query_ctrl.maximum;
info->range.step = query_ctrl.step;
info->range.default_value = query_ctrl.default_value;
return 0;
}
if (query_ctrl.minimum > query_ctrl.maximum) {
LOGF(WARNING) << CidToString(control_id) << " min " << query_ctrl.minimum
<< " > max " << query_ctrl.maximum;
return -EINVAL;
}
if (query_ctrl.minimum > query_ctrl.default_value) {
LOGF(WARNING) << CidToString(control_id) << " min " << query_ctrl.minimum
<< " > default " << query_ctrl.default_value;
return -EINVAL;
}
if (query_ctrl.maximum < query_ctrl.default_value) {
LOGF(WARNING) << CidToString(control_id) << " max " << query_ctrl.maximum
<< " < default " << query_ctrl.default_value;
return -EINVAL;
}
if (query_ctrl.step <= 0) {
LOGF(WARNING) << CidToString(control_id) << " step " << query_ctrl.step
<< " <= 0";
return -EINVAL;
}
if ((query_ctrl.default_value - query_ctrl.minimum) % query_ctrl.step != 0) {
LOGF(WARNING) << CidToString(control_id) << " step " << query_ctrl.step
<< " can't divide minimum " << query_ctrl.minimum
<< " default_value " << query_ctrl.default_value;
return -EINVAL;
}
if ((query_ctrl.maximum - query_ctrl.minimum) % query_ctrl.step != 0) {
LOGF(WARNING) << CidToString(control_id) << " step " << query_ctrl.step
<< " can't divide minimum " << query_ctrl.minimum
<< " maximum " << query_ctrl.maximum;
return -EINVAL;
}
// Fill the query info
info->range.minimum = query_ctrl.minimum;
info->range.maximum = query_ctrl.maximum;
info->range.step = query_ctrl.step;
info->range.default_value = query_ctrl.default_value;
if (query_ctrl.type == V4L2_CTRL_TYPE_MENU) {
for (int i = query_ctrl.minimum; i <= query_ctrl.maximum; i++) {
v4l2_querymenu qmenu = {};
qmenu.id = query_ctrl.id;
qmenu.index = i;
if (HANDLE_EINTR(ioctl(fd, VIDIOC_QUERYMENU, &qmenu)) == 0) {
info->menu_items.emplace_back(
reinterpret_cast<const char*>(qmenu.name));
}
}
}
return 0;
}
// static
int V4L2CameraDevice::SetControlValue(int fd, ControlType type, int32_t value) {
int control_id = ControlTypeToCid(type);
VLOGF(1) << "Set " << CidToString(control_id) << ", value:" << value;
v4l2_control current = {.id = static_cast<__u32>(control_id), .value = value};
if (HANDLE_EINTR(ioctl(fd, VIDIOC_S_CTRL, &current)) < 0) {
PLOGF(WARNING) << "Failed to set " << CidToString(control_id) << " to "
<< value;
return -errno;
}
return 0;
}
// static
int V4L2CameraDevice::GetControlValue(int fd,
ControlType type,
int32_t* value) {
DCHECK(value);
int control_id = ControlTypeToCid(type);
v4l2_control current = {.id = static_cast<__u32>(control_id)};
if (HANDLE_EINTR(ioctl(fd, VIDIOC_G_CTRL, &current)) < 0) {
PLOGF(WARNING) << "Failed to get " << CidToString(control_id);
return -errno;
}
*value = current.value;
VLOGF(1) << "Get " << CidToString(control_id) << ", value:" << *value;
return 0;
}
// static
std::vector<float> V4L2CameraDevice::GetFrameRateList(int fd,
uint32_t fourcc,
uint32_t width,
uint32_t height) {
std::vector<float> frame_rates;
v4l2_frmivalenum frame_interval = {};
frame_interval.pixel_format = fourcc;
frame_interval.width = width;
frame_interval.height = height;
for (; TEMP_FAILURE_RETRY(
ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frame_interval)) == 0;
++frame_interval.index) {
if (frame_interval.type == V4L2_FRMIVAL_TYPE_DISCRETE) {
if (frame_interval.discrete.numerator != 0) {
frame_rates.push_back(
frame_interval.discrete.denominator /
static_cast<float>(frame_interval.discrete.numerator));
}
} else if (frame_interval.type == V4L2_FRMIVAL_TYPE_CONTINUOUS ||
frame_interval.type == V4L2_FRMIVAL_TYPE_STEPWISE) {
// TODO(henryhsu): see http://crbug.com/249953, support these devices.
LOGF(ERROR) << "Stepwise and continuous frame interval are unsupported";
return frame_rates;
}
}
// Some devices, e.g. Kinect, do not enumerate any frame rates, see
// http://crbug.com/412284. Set their frame_rate to zero.
if (frame_rates.empty()) {
frame_rates.push_back(0);
}
return frame_rates;
}
// static
bool V4L2CameraDevice::IsCameraDevice(const std::string& device_path) {
// RetryDeviceOpen() assumes the device is a camera and waits until the camera
// is ready, so we use open() instead of RetryDeviceOpen() here.
base::ScopedFD fd(TEMP_FAILURE_RETRY(open(device_path.c_str(), O_RDONLY)));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return false;
}
v4l2_capability v4l2_cap;
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), VIDIOC_QUERYCAP, &v4l2_cap)) != 0) {
return false;
}
auto check_mask = [](uint32_t caps) {
const uint32_t kCaptureMask =
V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
// Old drivers use (CAPTURE | OUTPUT) for memory-to-memory video devices.
const uint32_t kOutputMask =
V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE;
const uint32_t kM2mMask = V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE;
return (caps & kCaptureMask) && !(caps & kOutputMask) && !(caps & kM2mMask);
};
// Prefer to use available capabilities of that specific device node instead
// of the physical device as a whole, so we can properly ignore the metadata
// device node.
if (v4l2_cap.capabilities & V4L2_CAP_DEVICE_CAPS) {
return check_mask(v4l2_cap.device_caps);
} else {
return check_mask(v4l2_cap.capabilities);
}
}
// static
std::string V4L2CameraDevice::GetModelName(const std::string& device_path) {
auto get_by_interface = [&](std::string* name) {
base::FilePath real_path;
if (!base::NormalizeFilePath(base::FilePath(device_path), &real_path)) {
return false;
}
if (!base::MatchPattern(real_path.value(), "/dev/video*")) {
return false;
}
// /sys/class/video4linux/video{N}/device is a symlink to the corresponding
// USB device info directory.
auto interface_path = base::FilePath("/sys/class/video4linux")
.Append(real_path.BaseName())
.Append("device/interface");
return base::ReadFileToString(interface_path, name);
};
auto get_by_cap = [&](std::string* name) {
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(WARNING) << "Failed to open " << device_path;
return false;
}
v4l2_capability cap;
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), VIDIOC_QUERYCAP, &cap)) != 0) {
PLOGF(WARNING) << "Failed to query capability of " << device_path;
return false;
}
*name = std::string(reinterpret_cast<const char*>(cap.card));
return true;
};
std::string name;
if (get_by_interface(&name)) {
return name;
}
if (get_by_cap(&name)) {
return name;
}
return "USB Camera";
}
// static
bool V4L2CameraDevice::IsControlSupported(const std::string& device_path,
ControlType type) {
ControlInfo info;
return QueryControl(device_path, type, &info) == 0;
}
// static
int V4L2CameraDevice::QueryControl(const std::string& device_path,
ControlType type,
ControlInfo* info) {
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return -errno;
}
int ret = QueryControl(fd.get(), type, info);
if (ret != 0) {
return ret;
}
VLOGF(1) << ControlTypeToString(type) << "(min,max,step,default) = "
<< "(" << info->range.minimum << "," << info->range.maximum << ","
<< info->range.step << "," << info->range.default_value << ")";
if (!info->menu_items.empty()) {
VLOGF(1) << ControlTypeToString(type) << " " << info->menu_items.size()
<< " menu items:";
for (const auto& item : info->menu_items)
VLOGF(1) << " " << item;
}
return 0;
}
// static
int V4L2CameraDevice::GetControlValue(const std::string& device_path,
ControlType type,
int32_t* value) {
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return -errno;
}
return GetControlValue(fd.get(), type, value);
}
// static
int V4L2CameraDevice::SetControlValue(const std::string& device_path,
ControlType type,
int32_t value) {
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return -errno;
}
return SetControlValue(fd.get(), type, value);
}
// static
int V4L2CameraDevice::RetryDeviceOpen(const std::string& device_path,
int flags) {
const int64_t kDeviceOpenTimeOutInMilliseconds = 2000;
const int64_t kSleepTimeInMilliseconds = 100;
int fd;
base::ElapsedTimer timer;
int64_t elapsed_time = timer.Elapsed().InMillisecondsRoundedUp();
while (elapsed_time < kDeviceOpenTimeOutInMilliseconds) {
fd = TEMP_FAILURE_RETRY(open(device_path.c_str(), flags));
if (fd != -1) {
// Make sure ioctl is ok. Once ioctl failed, we have to re-open the
// device.
struct v4l2_fmtdesc v4l2_format = {};
v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
int ret = TEMP_FAILURE_RETRY(ioctl(fd, VIDIOC_ENUM_FMT, &v4l2_format));
if (ret == -1) {
close(fd);
if (errno != EPERM) {
break;
} else {
VLOGF(1) << "Camera ioctl is not ready";
}
} else {
// Only return fd when ioctl is ready.
if (elapsed_time >= kSleepTimeInMilliseconds) {
LOGF(INFO) << "Opened the camera device after waiting for "
<< elapsed_time << " ms";
}
return fd;
}
} else if (errno != ENOENT) {
break;
}
base::PlatformThread::Sleep(
base::TimeDelta::FromMilliseconds(kSleepTimeInMilliseconds));
elapsed_time = timer.Elapsed().InMillisecondsRoundedUp();
}
PLOGF(ERROR) << "Failed to open " << device_path;
return -1;
}
// static
clockid_t V4L2CameraDevice::GetUvcClock() {
static const clockid_t kUvcClock = [] {
const base::FilePath kClockPath("/sys/module/uvcvideo/parameters/clock");
std::string clock;
if (base::ReadFileToString(kClockPath, &clock)) {
if (clock.find("REALTIME") != std::string::npos) {
return CLOCK_REALTIME;
} else if (clock.find("BOOTTIME") != std::string::npos) {
return CLOCK_BOOTTIME;
} else {
return CLOCK_MONOTONIC;
}
}
// Use UVC default clock.
return CLOCK_MONOTONIC;
}();
return kUvcClock;
}
// static
PowerLineFrequency V4L2CameraDevice::GetPowerLineFrequency(
const std::string& device_path) {
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return PowerLineFrequency::FREQ_ERROR;
}
struct v4l2_queryctrl query = {};
query.id = V4L2_CID_POWER_LINE_FREQUENCY;
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), VIDIOC_QUERYCTRL, &query)) < 0) {
LOGF(ERROR) << "Power line frequency should support auto or 50/60Hz";
return PowerLineFrequency::FREQ_ERROR;
}
PowerLineFrequency frequency = GetPowerLineFrequencyForLocation();
if (frequency == PowerLineFrequency::FREQ_DEFAULT) {
switch (query.default_value) {
case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
frequency = PowerLineFrequency::FREQ_50HZ;
break;
case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
frequency = PowerLineFrequency::FREQ_60HZ;
break;
case V4L2_CID_POWER_LINE_FREQUENCY_AUTO:
frequency = PowerLineFrequency::FREQ_AUTO;
break;
default:
break;
}
}
// Prefer auto setting if camera module supports auto mode.
if (query.maximum == V4L2_CID_POWER_LINE_FREQUENCY_AUTO) {
frequency = PowerLineFrequency::FREQ_AUTO;
} else if (query.minimum >= V4L2_CID_POWER_LINE_FREQUENCY_60HZ) {
// TODO(shik): Handle this more gracefully for external camera
LOGF(ERROR) << "Camera module should at least support 50/60Hz";
return PowerLineFrequency::FREQ_ERROR;
}
return frequency;
}
// static
bool V4L2CameraDevice::IsFocusDistanceSupported(
const std::string& device_path, ControlRange* focus_distance_range) {
DCHECK(focus_distance_range != nullptr);
if (!IsControlSupported(device_path, kControlFocusAuto))
return false;
ControlInfo info;
if (QueryControl(device_path, kControlFocusDistance, &info) != 0) {
return false;
}
*focus_distance_range = info.range;
return true;
}
// static
bool V4L2CameraDevice::IsManualExposureTimeSupported(
const std::string& device_path, ControlRange* exposure_time_range) {
ControlInfo info;
DCHECK(exposure_time_range);
if (QueryControl(device_path, kControlExposureAuto, &info) != 0)
return false;
bool found_manual_type = false;
bool found_auto_type = false;
for (const auto& item : info.menu_items) {
if (item == kExposureTypeMenuStringManual) {
found_manual_type = true;
} else if (item == kExposureTypeMenuStringShutterPriority) {
found_manual_type = true;
} else if (item == kExposureTypeMenuStringAuto) {
found_auto_type = true;
} else if (item == kExposureTypeMenuStringAperturePriority) {
found_auto_type = true;
}
}
if (!found_manual_type || !found_auto_type)
return false;
if (QueryControl(device_path, kControlExposureTime, &info) != 0) {
LOG(WARNING) << "Can't get exposure time range";
return false;
}
*exposure_time_range = info.range;
return true;
}
// static
bool V4L2CameraDevice::IsConstantFrameRateSupported(
const std::string& device_path) {
base::ScopedFD fd(RetryDeviceOpen(device_path, O_RDONLY));
if (!fd.is_valid()) {
PLOGF(ERROR) << "Failed to open " << device_path;
return false;
}
struct v4l2_queryctrl query_ctrl;
query_ctrl.id = V4L2_CID_EXPOSURE_AUTO_PRIORITY;
if (TEMP_FAILURE_RETRY(ioctl(fd.get(), VIDIOC_QUERYCTRL, &query_ctrl)) < 0) {
LOGF(WARNING) << "Failed to query V4L2_CID_EXPOSURE_AUTO_PRIORITY";
return false;
}
return !(query_ctrl.flags & V4L2_CTRL_FLAG_DISABLED);
}
int V4L2CameraDevice::SetPowerLineFrequency(PowerLineFrequency setting) {
int v4l2_freq_setting = V4L2_CID_POWER_LINE_FREQUENCY_DISABLED;
switch (setting) {
case PowerLineFrequency::FREQ_50HZ:
v4l2_freq_setting = V4L2_CID_POWER_LINE_FREQUENCY_50HZ;
break;
case PowerLineFrequency::FREQ_60HZ:
v4l2_freq_setting = V4L2_CID_POWER_LINE_FREQUENCY_60HZ;
break;
case PowerLineFrequency::FREQ_AUTO:
v4l2_freq_setting = V4L2_CID_POWER_LINE_FREQUENCY_AUTO;
break;
default:
LOGF(ERROR) << "Invalid setting for power line frequency: "
<< static_cast<int>(setting);
return -EINVAL;
}
struct v4l2_control control = {};
control.id = V4L2_CID_POWER_LINE_FREQUENCY;
control.value = v4l2_freq_setting;
if (TEMP_FAILURE_RETRY(ioctl(device_fd_.get(), VIDIOC_S_CTRL, &control)) <
0) {
LOGF(ERROR) << "Error setting power line frequency to "
<< v4l2_freq_setting;
return -EINVAL;
}
VLOGF(1) << "Set power line frequency(" << static_cast<int>(setting)
<< ") successfully";
return 0;
}
bool V4L2CameraDevice::IsExternalCamera() {
return device_info_.lens_facing == LensFacing::kExternal;
}
int V4L2CameraDevice::SubscribePrivacySwitchEvent() {
struct v4l2_event_subscription sub = {.type = V4L2_EVENT_CTRL,
.id = V4L2_CID_PRIVACY};
if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_SUBSCRIBE_EVENT, &sub)) < 0) {
LOGF(ERROR) << "Unable to subscribe for privacy status change";
return -errno;
}
if (!base::CreatePipe(&cancel_fd_, &cancel_pipe_, true)) {
LOGF(ERROR) << "Failed to create the cancelation pipe";
return -EINVAL;
}
DCHECK(!event_thread_.IsRunning());
if (!event_thread_.Start()) {
LOGF(ERROR) << "Failed to start V4L2 event thread";
return -EINVAL;
}
event_thread_.task_runner()->PostTask(
FROM_HERE, base::BindRepeating(&V4L2CameraDevice::RunDequeueEventsLoop,
base::Unretained(this)));
return 0;
}
int V4L2CameraDevice::UnsubscribePrivacySwitchEvent() {
cancel_pipe_.reset();
if (event_thread_.IsRunning()) {
event_thread_.Stop();
}
struct v4l2_event_subscription sub = {.type = V4L2_EVENT_CTRL,
.id = V4L2_CID_PRIVACY};
if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_UNSUBSCRIBE_EVENT, &sub)) <
0) {
LOGF(ERROR) << "Unable to unsubscribe for privacy status change";
return -errno;
}
return 0;
}
void V4L2CameraDevice::RunDequeueEventsLoop() {
while (true) {
struct pollfd fds[2] = {
{device_fd_.get(), POLLPRI, 0},
{cancel_fd_.get(), POLLHUP, 0},
};
if (HANDLE_EINTR(poll(fds, base::size(fds), -1)) <= 0) {
LOGF(ERROR) << "Failed to poll to dequeue events";
return;
}
if (fds[1].revents > 0) {
cancel_fd_.reset();
return;
}
if (fds[0].revents > 0) {
struct v4l2_event ev = {};
if (HANDLE_EINTR(ioctl(device_fd_.get(), VIDIOC_DQEVENT, &ev)) == 0) {
if (ev.type == V4L2_EVENT_CTRL && ev.id == V4L2_CID_PRIVACY &&
privacy_switch_monitor_) {
privacy_switch_monitor_->OnStatusChanged(
ev.u.ctrl.value != 0 ? PrivacySwitchState::kOn
: PrivacySwitchState::kOff);
}
} else {
LOGF(ERROR) << "Failed to dequeue event from device";
}
}
}
}
} // namespace cros