| // Copyright 2015 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "extensions/browser/api/webcam_private/v4l2_webcam.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <linux/uvcvideo.h> |
| #include <linux/videodev2.h> |
| #include <stdio.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include "base/logging.h" |
| #include "base/posix/eintr_wrapper.h" |
| |
| #define V4L2_CID_PAN_SPEED (V4L2_CID_CAMERA_CLASS_BASE+32) |
| #define V4L2_CID_TILT_SPEED (V4L2_CID_CAMERA_CLASS_BASE+33) |
| #define V4L2_CID_PANTILT_CMD (V4L2_CID_CAMERA_CLASS_BASE+34) |
| |
| // GUID of the Extension Unit for Logitech CC3300e motor control: |
| // {212de5ff-3080-2c4e-82d9-f587d00540bd} |
| #define UVC_GUID_LOGITECH_CC3000E_MOTORS \ |
| { \ |
| 0x21, 0x2d, 0xe5, 0xff, 0x30, 0x80, 0x2c, 0x4e, 0x82, 0xd9, 0xf5, 0x87, \ |
| 0xd0, 0x05, 0x40, 0xbd \ |
| } |
| |
| // GUID of the Extension Unit for AVER XU1 motor control: |
| // {cb666936-6e26-a047-8451-96ecf60330d6} |
| #define UVC_GUID_AVER \ |
| { \ |
| 0xcb, 0x66, 0x69, 0x36, 0x6e, 0x26, 0xa0, 0x47, 0x84, 0x51, 0x96, 0xec, \ |
| 0xf6, 0x03, 0x30, 0xd6 \ |
| } |
| |
| #define LOGITECH_MOTORCONTROL_PANTILT_CMD 2 |
| #define AVER_UVCX_UCAM_PRESET 0x12 |
| |
| namespace { |
| const int kLogitechMenuIndexGoHome = 1; // {0x02, Go Home} |
| const int kLogitechMenuIndexSetHome = 0; // {0x01, Set Home Preset} |
| const int kAverMenuIndexGoHome = 1; // {0x01, Restore preset 0} |
| const int kAverMenuIndexSetHome = 0; // {0x00, Set Preset 0} |
| |
| // Note: Keep in sync with index constants above i.e kLogitechMenuIndex{Get, Set}Home |
| const uvc_menu_info kLogitechCmdMenu[] = { |
| {0x01, "Set Home Preset"}, |
| {0x02, "Go Home"}, |
| {0x03, "Go Home"}, |
| {0x04, "Set Preset 1"}, |
| {0x0C, "Restore Preset 1"}, |
| {0x05, "Set Preset 2"}, |
| {0x0D, "Restore Preset 2"}, |
| {0x06, "Set Preset 3"}, |
| {0x0E, "Restore Preset 3"}, |
| {0x07, "Set Preset 4"}, |
| {0x0F, "Restore Preset 4"}, |
| {0x08, "Set Preset 5"}, |
| {0x10, "Restore Preset 5"}, |
| {0x09, "Set Preset 6"}, |
| {0x11, "Restore Preset 6"}, |
| {0x0A, "Set Preset 7"}, |
| {0x12, "Restore Preset 7"}, |
| {0x0B, "Set Preset 8"}, |
| {0x13, "Restore Preset 8"}, |
| }; |
| |
| // Preset 0 is equivalent to HOME in Aver cameras. |
| // Note: Keep in sync with index constants above i.e kAverMenuIndex{Go, Set}Home. |
| const uvc_menu_info kAverPresetMenu[] = { |
| /*0*/ {0x0000, "Set Preset 0"}, /*1*/ {0x0001, "Restore preset 0"}, |
| /*2*/ {0x0100, "Set Preset 1"}, /*3*/ {0x0101, "Restore preset 1"}, |
| /*4*/ {0x0200, "Set Preset 2"}, /*5*/ {0x0202, "Restore preset 2"}, |
| /*6*/ {0x0300, "Set Preset 3"}, /*7*/ {0x0303, "Restore preset 3"}, |
| /*8*/ {0x0400, "Set Preset 4"}, /*9*/ {0x0404, "Restore preset 4"}, |
| /*10*/ {0x0500, "Set Preset 5"}, /*11*/ {0x0505, "Restore preset 5"}, |
| /*12*/ {0x0600, "Set Preset 6"}, /*13*/ {0x0606, "Restore preset 6"}, |
| /*14*/ {0x0700, "Set Preset 7"}, /*15*/ {0x0707, "Restore preset 7"}, |
| /*16*/ {0x0800, "Set Preset 8"}, /*17*/ {0x0808, "Restore preset 8"}, |
| /*18*/ {0x0900, "Set Preset 9"}, /*19*/ {0x0909, "Restore preset 9"}, |
| }; |
| |
| const uvc_xu_control_mapping kLogitechCmdMapping = { |
| V4L2_CID_PANTILT_CMD, |
| "Pan/Tilt Go", |
| UVC_GUID_LOGITECH_CC3000E_MOTORS, |
| LOGITECH_MOTORCONTROL_PANTILT_CMD, |
| 8, |
| 0, |
| V4L2_CTRL_TYPE_MENU, |
| UVC_CTRL_DATA_TYPE_ENUM, |
| const_cast<uvc_menu_info*>(&kLogitechCmdMenu[0]), |
| std::size(kLogitechCmdMenu), |
| }; |
| |
| const uvc_xu_control_mapping kLogitechPanAbsoluteMapping = { |
| V4L2_CID_PAN_ABSOLUTE, |
| "Pan (Absolute)", |
| UVC_GUID_LOGITECH_CC3000E_MOTORS, |
| 12, |
| 32, |
| 0, |
| V4L2_CTRL_TYPE_INTEGER, |
| UVC_CTRL_DATA_TYPE_SIGNED, |
| }; |
| |
| const uvc_xu_control_mapping kLogitechTiltAbsoluteMapping = { |
| V4L2_CID_TILT_ABSOLUTE, |
| "Tilt (Absolute)", |
| UVC_GUID_LOGITECH_CC3000E_MOTORS, |
| 12, |
| 32, |
| 32, |
| V4L2_CTRL_TYPE_INTEGER, |
| UVC_CTRL_DATA_TYPE_SIGNED, |
| }; |
| |
| const uvc_xu_control_mapping kAverCmdMapping = { |
| V4L2_CID_PANTILT_CMD, |
| "PTZ Preset", |
| UVC_GUID_AVER, |
| AVER_UVCX_UCAM_PRESET, |
| 24, |
| 0, |
| V4L2_CTRL_TYPE_MENU, |
| UVC_CTRL_DATA_TYPE_ENUM, |
| const_cast<uvc_menu_info*>(kAverPresetMenu), |
| std::size(kAverPresetMenu), |
| }; |
| |
| } // namespace |
| |
| namespace extensions { |
| |
| V4L2Webcam::V4L2Webcam(const std::string& device_id) : device_id_(device_id) { |
| } |
| |
| V4L2Webcam::~V4L2Webcam() { |
| } |
| |
| bool V4L2Webcam::Open() { |
| fd_.reset(HANDLE_EINTR(open(device_id_.c_str(), 0))); |
| return fd_.is_valid(); |
| } |
| |
| bool V4L2Webcam::EnsureLogitechCommandsMapped() { |
| int res = |
| HANDLE_EINTR(ioctl(fd_.get(), UVCIOC_CTRL_MAP, &kLogitechCmdMapping)); |
| // If mapping is successful or it's already mapped, this is a Logitech |
| // camera. |
| // NOTE: On success, occasionally EFAULT is returned. On a real error, |
| // ENOMEM, EPERM, EINVAL, or EOVERFLOW should be returned. |
| return res >= 0 || errno == EEXIST || errno == EFAULT; |
| } |
| |
| bool V4L2Webcam::EnsureAverCommandsMapped() { |
| int res = HANDLE_EINTR(ioctl(fd_.get(), UVCIOC_CTRL_MAP, &kAverCmdMapping)); |
| // If mapping is successful or it's already mapped, this is an Aver camera. |
| // NOTE: On success, occasionally EFAULT is returned. On a real error, |
| // ENOMEM, EPERM, EINVAL, or EOVERFLOW should be returned. |
| return res >= 0 || errno == EEXIST || errno == EFAULT; |
| } |
| |
| bool V4L2Webcam::SetWebcamParameter(int fd, uint32_t control_id, int value) { |
| // Try to map the V4L2 control to the Logitech extension unit. If the |
| // connected camera does not implement these extension unit this will just |
| // silently fail and the standard camera terminal controls will be used. |
| if (control_id == V4L2_CID_PAN_ABSOLUTE) { |
| HANDLE_EINTR(ioctl(fd, UVCIOC_CTRL_MAP, &kLogitechPanAbsoluteMapping)); |
| } else if (control_id == V4L2_CID_TILT_ABSOLUTE) { |
| HANDLE_EINTR(ioctl(fd, UVCIOC_CTRL_MAP, &kLogitechTiltAbsoluteMapping)); |
| } |
| |
| struct v4l2_control v4l2_ctrl = {control_id, value}; |
| int res = HANDLE_EINTR(ioctl(fd, VIDIOC_S_CTRL, &v4l2_ctrl)) >= 0; |
| return res >= 0; |
| } |
| |
| bool V4L2Webcam::GetWebcamParameter(int fd, |
| uint32_t control_id, |
| int* value, |
| int* min_value, |
| int* max_value) { |
| // Try to query current value for |control_id|. The getter fails if not |
| // supported. |
| struct v4l2_control v4l2_ctrl = {control_id}; |
| if (HANDLE_EINTR(ioctl(fd, VIDIOC_G_CTRL, &v4l2_ctrl))) { |
| return false; |
| } |
| *value = v4l2_ctrl.value; |
| |
| // Try to query the valid range for |control_id|. Not supporting a range query |
| // is not a failure. |
| struct v4l2_queryctrl v4l2_range_query = {control_id}; |
| if (HANDLE_EINTR(ioctl(fd, VIDIOC_QUERYCTRL, &v4l2_range_query))) { |
| *min_value = 0; |
| *max_value = 0; |
| } else { |
| *min_value = v4l2_range_query.minimum; |
| *max_value = v4l2_range_query.maximum; |
| } |
| |
| return true; |
| } |
| |
| void V4L2Webcam::GetPan(const GetPTZCompleteCallback& callback) { |
| int value = 0; |
| int min_value = 0; |
| int max_value = 0; |
| bool success = GetWebcamParameter(fd_.get(), V4L2_CID_PAN_ABSOLUTE, &value, |
| &min_value, &max_value); |
| |
| callback.Run(success, value, min_value, max_value); |
| } |
| |
| void V4L2Webcam::GetTilt(const GetPTZCompleteCallback& callback) { |
| int value = 0; |
| int min_value = 0; |
| int max_value = 0; |
| bool success = GetWebcamParameter(fd_.get(), V4L2_CID_TILT_ABSOLUTE, &value, |
| &min_value, &max_value); |
| callback.Run(success, value, min_value, max_value); |
| } |
| |
| void V4L2Webcam::GetZoom(const GetPTZCompleteCallback& callback) { |
| int value = 0; |
| int min_value = 0; |
| int max_value = 0; |
| bool success = GetWebcamParameter(fd_.get(), V4L2_CID_ZOOM_ABSOLUTE, &value, |
| &min_value, &max_value); |
| callback.Run(success, value, min_value, max_value); |
| } |
| |
| void V4L2Webcam::GetFocus(const GetPTZCompleteCallback& callback) { |
| int value = 0; |
| int min_value = 0; |
| int max_value = 0; |
| bool success = GetWebcamParameter(fd_.get(), V4L2_CID_FOCUS_ABSOLUTE, &value, |
| &min_value, &max_value); |
| callback.Run(success, value, min_value, max_value); |
| } |
| |
| void V4L2Webcam::SetPan(int value, |
| int pan_speed, |
| const SetPTZCompleteCallback& callback) { |
| callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_PAN_ABSOLUTE, value)); |
| } |
| |
| void V4L2Webcam::SetTilt(int value, |
| int tilt_speed, |
| const SetPTZCompleteCallback& callback) { |
| callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_TILT_ABSOLUTE, value)); |
| } |
| |
| void V4L2Webcam::SetZoom(int value, const SetPTZCompleteCallback& callback) { |
| callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_ZOOM_ABSOLUTE, value)); |
| } |
| |
| void V4L2Webcam::SetFocus(int value, const SetPTZCompleteCallback& callback) { |
| callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_FOCUS_ABSOLUTE, value)); |
| } |
| |
| void V4L2Webcam::SetAutofocusState(AutofocusState state, |
| const SetPTZCompleteCallback& callback) { |
| const int value = (state == AUTOFOCUS_ON) ? 1 : 0; |
| callback.Run(SetWebcamParameter(fd_.get(), V4L2_CID_FOCUS_AUTO, value)); |
| } |
| |
| void V4L2Webcam::SetPanDirection(PanDirection direction, |
| int pan_speed, |
| const SetPTZCompleteCallback& callback) { |
| int direction_value = 0; |
| switch (direction) { |
| case PAN_STOP: |
| direction_value = 0; |
| break; |
| |
| case PAN_RIGHT: |
| direction_value = 1; |
| break; |
| |
| case PAN_LEFT: |
| direction_value = -1; |
| break; |
| } |
| callback.Run( |
| SetWebcamParameter(fd_.get(), V4L2_CID_PAN_SPEED, direction_value)); |
| } |
| |
| void V4L2Webcam::SetTiltDirection(TiltDirection direction, |
| int tilt_speed, |
| const SetPTZCompleteCallback& callback) { |
| int direction_value = 0; |
| switch (direction) { |
| case TILT_STOP: |
| direction_value = 0; |
| break; |
| |
| case TILT_UP: |
| direction_value = 1; |
| break; |
| |
| case TILT_DOWN: |
| direction_value = -1; |
| break; |
| } |
| callback.Run( |
| SetWebcamParameter(fd_.get(), V4L2_CID_TILT_SPEED, direction_value)); |
| } |
| |
| void V4L2Webcam::SetHome(const SetPTZCompleteCallback& callback) { |
| if (EnsureLogitechCommandsMapped()) { |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, |
| kLogitechMenuIndexSetHome)) { |
| LOG(WARNING) << "Failed to set HOME preset for Logitech webcam"; |
| callback.Run(false); |
| return; |
| } |
| callback.Run(true); |
| return; |
| } |
| if (EnsureAverCommandsMapped()) { |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, |
| kAverMenuIndexSetHome)) { |
| LOG(WARNING) << "Failed to set HOME preset for AVer CAM webcam"; |
| callback.Run(false); |
| return; |
| } |
| callback.Run(true); |
| return; |
| } |
| LOG(WARNING) << "Active webcam does not currently support seting HOME preset"; |
| callback.Run(false); |
| } |
| |
| int getLogitechPresetCmdMenuIndex(int preset_number, bool set_preset) { |
| // Preset must be within range [1, 8] |
| if (preset_number < 1 || preset_number > 8) { |
| LOG(WARNING) << "Preset number index for Logitech camera out of range."; |
| return -1; |
| } |
| |
| int set_preset_index = preset_number * 2 + 1; |
| return set_preset ? set_preset_index : set_preset_index + 1; |
| } |
| |
| int getAverPresetCmdMenuIndex(int preset_number, bool set_preset) { |
| // Preset must be within range [0, 9] |
| if (preset_number < 0 || preset_number > 9) { |
| LOG(WARNING) << "Preset number index for AVer camera out of range."; |
| return -1; |
| } |
| |
| int set_preset_index = 2 * preset_number; |
| return set_preset ? set_preset_index : set_preset_index + 1; |
| } |
| |
| void V4L2Webcam::RestoreCameraPreset(int preset_number, |
| const SetPTZCompleteCallback& callback) { |
| if (EnsureLogitechCommandsMapped()) { |
| int menu_index = getLogitechPresetCmdMenuIndex(preset_number, false); |
| |
| if (menu_index < 0) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, menu_index)) { |
| LOG(WARNING) << "Failed to call preset " << preset_number |
| << " for Logitech webcam"; |
| callback.Run(false); |
| return; |
| } |
| callback.Run(true); |
| return; |
| } else if (EnsureAverCommandsMapped()) { |
| int menu_index = getAverPresetCmdMenuIndex(preset_number, false); |
| |
| if (menu_index < 0) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, menu_index)) { |
| LOG(WARNING) << "Failed to call preset " << preset_number |
| << " for AVer webcam"; |
| callback.Run(false); |
| return; |
| } |
| callback.Run(true); |
| return; |
| } |
| |
| LOG(WARNING) |
| << "Active webcam does not currently support calling camera presets."; |
| callback.Run(false); |
| } |
| |
| void V4L2Webcam::SetCameraPreset(int preset_number, |
| const SetPTZCompleteCallback& callback) { |
| if (EnsureLogitechCommandsMapped()) { |
| int menu_index = getLogitechPresetCmdMenuIndex(preset_number, true); |
| |
| if (menu_index < 0) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, menu_index)) { |
| LOG(WARNING) << "Failed to set preset " << preset_number |
| << " for Logitech webcam"; |
| callback.Run(false); |
| return; |
| } |
| callback.Run(true); |
| return; |
| } else if (EnsureAverCommandsMapped()) { |
| int menu_index = getAverPresetCmdMenuIndex(preset_number, true); |
| |
| if (menu_index < 0) { |
| callback.Run(false); |
| return; |
| } |
| |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, menu_index)) { |
| LOG(WARNING) << "Failed to set preset " << preset_number |
| << " for AVer webcam"; |
| callback.Run(false); |
| return; |
| } |
| callback.Run(true); |
| return; |
| } |
| |
| LOG(WARNING) |
| << "Active webcam does not currently support setting camera presets."; |
| callback.Run(false); |
| } |
| |
| void V4L2Webcam::Reset(bool pan, |
| bool tilt, |
| bool zoom, |
| const SetPTZCompleteCallback& callback) { |
| if (pan || tilt) { |
| if (EnsureLogitechCommandsMapped()) { |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, |
| kLogitechMenuIndexGoHome)) { |
| callback.Run(false); |
| return; |
| } |
| } else if (EnsureAverCommandsMapped()) { |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_PANTILT_CMD, |
| kAverMenuIndexGoHome)) { |
| callback.Run(false); |
| return; |
| } |
| } else { |
| if (pan) { |
| struct v4l2_control v4l2_ctrl = {V4L2_CID_PAN_RESET}; |
| if (!HANDLE_EINTR(ioctl(fd_.get(), VIDIOC_S_CTRL, &v4l2_ctrl))) { |
| callback.Run(false); |
| return; |
| } |
| } |
| |
| if (tilt) { |
| struct v4l2_control v4l2_ctrl = {V4L2_CID_TILT_RESET}; |
| if (!HANDLE_EINTR(ioctl(fd_.get(), VIDIOC_S_CTRL, &v4l2_ctrl))) { |
| callback.Run(false); |
| return; |
| } |
| } |
| } |
| } |
| |
| if (zoom) { |
| const int kDefaultZoom = 100; |
| if (!SetWebcamParameter(fd_.get(), V4L2_CID_ZOOM_ABSOLUTE, kDefaultZoom)) { |
| callback.Run(false); |
| return; |
| } |
| } |
| |
| callback.Run(true); |
| } |
| |
| } // namespace extensions |