| // Copyright 2014 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/video_capture_device_factory_linux.h" |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <stdint.h> |
| #include <sys/ioctl.h> |
| |
| #include "base/files/file_enumerator.h" |
| #include "base/files/scoped_file.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| |
| #if defined(OS_OPENBSD) |
| #include <sys/videoio.h> |
| #else |
| #include <linux/videodev2.h> |
| #endif |
| |
| #if defined(OS_CHROMEOS) |
| #include "media/capture/video/linux/camera_config_chromeos.h" |
| #include "media/capture/video/linux/video_capture_device_chromeos.h" |
| #endif |
| #include "media/capture/video/linux/video_capture_device_linux.h" |
| |
| namespace media { |
| |
| // USB VID and PID are both 4 bytes long. |
| static const size_t kVidPidSize = 4; |
| |
| // /sys/class/video4linux/video{N}/device is a symlink to the corresponding |
| // USB device info directory. |
| static const char kVidPathTemplate[] = |
| "/sys/class/video4linux/%s/device/../idVendor"; |
| static const char kPidPathTemplate[] = |
| "/sys/class/video4linux/%s/device/../idProduct"; |
| |
| static bool ReadIdFile(const std::string& path, std::string* id) { |
| char id_buf[kVidPidSize]; |
| FILE* file = fopen(path.c_str(), "rb"); |
| if (!file) |
| return false; |
| const bool success = fread(id_buf, kVidPidSize, 1, file) == 1; |
| fclose(file); |
| if (!success) |
| return false; |
| id->append(id_buf, kVidPidSize); |
| return true; |
| } |
| |
| static bool HasUsableFormats(int fd, uint32_t capabilities) { |
| if (!(capabilities & V4L2_CAP_VIDEO_CAPTURE)) |
| return false; |
| |
| const std::list<uint32_t>& usable_fourccs = |
| VideoCaptureDeviceLinux::GetListOfUsableFourCCs(false); |
| v4l2_fmtdesc fmtdesc = {}; |
| fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| for (; HANDLE_EINTR(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0; |
| ++fmtdesc.index) { |
| if (std::find(usable_fourccs.begin(), usable_fourccs.end(), |
| fmtdesc.pixelformat) != usable_fourccs.end()) { |
| return true; |
| } |
| } |
| |
| DLOG(ERROR) << "No usable formats found"; |
| return false; |
| } |
| |
| static std::list<float> GetFrameRateList(int fd, |
| uint32_t fourcc, |
| uint32_t width, |
| uint32_t height) { |
| std::list<float> frame_rates; |
| |
| v4l2_frmivalenum frame_interval = {}; |
| frame_interval.pixel_format = fourcc; |
| frame_interval.width = width; |
| frame_interval.height = height; |
| for (; HANDLE_EINTR(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(mcasas): see http://crbug.com/249953, support these devices. |
| NOTIMPLEMENTED(); |
| break; |
| } |
| } |
| // 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 void GetSupportedFormatsForV4L2BufferType( |
| int fd, |
| media::VideoCaptureFormats* supported_formats) { |
| v4l2_fmtdesc v4l2_format = {}; |
| v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| for (; HANDLE_EINTR(ioctl(fd, VIDIOC_ENUM_FMT, &v4l2_format)) == 0; |
| ++v4l2_format.index) { |
| VideoCaptureFormat supported_format; |
| supported_format.pixel_format = |
| VideoCaptureDeviceLinux::V4l2FourCcToChromiumPixelFormat( |
| v4l2_format.pixelformat); |
| |
| if (supported_format.pixel_format == PIXEL_FORMAT_UNKNOWN) |
| continue; |
| |
| v4l2_frmsizeenum frame_size = {}; |
| frame_size.pixel_format = v4l2_format.pixelformat; |
| for (; HANDLE_EINTR(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frame_size)) == 0; |
| ++frame_size.index) { |
| if (frame_size.type == V4L2_FRMSIZE_TYPE_DISCRETE) { |
| supported_format.frame_size.SetSize(frame_size.discrete.width, |
| frame_size.discrete.height); |
| } else if (frame_size.type == V4L2_FRMSIZE_TYPE_STEPWISE || |
| frame_size.type == V4L2_FRMSIZE_TYPE_CONTINUOUS) { |
| // TODO(mcasas): see http://crbug.com/249953, support these devices. |
| NOTIMPLEMENTED(); |
| } |
| |
| const std::list<float> frame_rates = GetFrameRateList( |
| fd, v4l2_format.pixelformat, frame_size.discrete.width, |
| frame_size.discrete.height); |
| for (const auto& frame_rate : frame_rates) { |
| supported_format.frame_rate = frame_rate; |
| supported_formats->push_back(supported_format); |
| DVLOG(1) << VideoCaptureFormat::ToString(supported_format); |
| } |
| } |
| } |
| } |
| |
| VideoCaptureDeviceFactoryLinux::VideoCaptureDeviceFactoryLinux( |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) |
| : ui_task_runner_(ui_task_runner) { |
| } |
| |
| VideoCaptureDeviceFactoryLinux::~VideoCaptureDeviceFactoryLinux() { |
| } |
| |
| std::unique_ptr<VideoCaptureDevice> |
| VideoCaptureDeviceFactoryLinux::CreateDevice( |
| const VideoCaptureDeviceDescriptor& device_descriptor) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| #if defined(OS_CHROMEOS) |
| VideoCaptureDeviceChromeOS* self = |
| new VideoCaptureDeviceChromeOS(ui_task_runner_, device_descriptor); |
| #else |
| VideoCaptureDeviceLinux* self = |
| new VideoCaptureDeviceLinux(device_descriptor); |
| #endif |
| if (!self) |
| return std::unique_ptr<VideoCaptureDevice>(); |
| // Test opening the device driver. This is to make sure it is available. |
| // We will reopen it again in our worker thread when someone |
| // allocates the camera. |
| base::ScopedFD fd( |
| HANDLE_EINTR(open(device_descriptor.device_id.c_str(), O_RDONLY))); |
| if (!fd.is_valid()) { |
| DLOG(ERROR) << "Cannot open device"; |
| delete self; |
| return std::unique_ptr<VideoCaptureDevice>(); |
| } |
| |
| return std::unique_ptr<VideoCaptureDevice>(self); |
| } |
| |
| void VideoCaptureDeviceFactoryLinux::GetDeviceDescriptors( |
| VideoCaptureDeviceDescriptors* device_descriptors) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(device_descriptors->empty()); |
| const base::FilePath path("/dev/"); |
| base::FileEnumerator enumerator(path, false, base::FileEnumerator::FILES, |
| "video*"); |
| |
| while (!enumerator.Next().empty()) { |
| const base::FileEnumerator::FileInfo info = enumerator.GetInfo(); |
| const std::string unique_id = path.value() + info.GetName().value(); |
| const base::ScopedFD fd(HANDLE_EINTR(open(unique_id.c_str(), O_RDONLY))); |
| if (!fd.is_valid()) { |
| DLOG(ERROR) << "Couldn't open " << info.GetName().value(); |
| continue; |
| } |
| // Test if this is a V4L2 capture device and if it has at least one |
| // supported capture format. Devices that have capture and output |
| // capabilities at the same time are memory-to-memory and are skipped, see |
| // http://crbug.com/139356. |
| v4l2_capability cap; |
| if ((HANDLE_EINTR(ioctl(fd.get(), VIDIOC_QUERYCAP, &cap)) == 0) && |
| (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE && |
| !(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)) && |
| HasUsableFormats(fd.get(), cap.capabilities)) { |
| const std::string model_id = GetDeviceModelId(unique_id); |
| #if defined(OS_CHROMEOS) |
| static CameraConfigChromeOS* config = new CameraConfigChromeOS(); |
| device_descriptors->emplace_back( |
| reinterpret_cast<char*>(cap.card), unique_id, model_id, |
| VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE, |
| VideoCaptureTransportType::OTHER_TRANSPORT, |
| config->GetCameraFacing(unique_id, model_id)); |
| #else |
| device_descriptors->emplace_back( |
| reinterpret_cast<char*>(cap.card), unique_id, model_id, |
| VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE); |
| #endif |
| } |
| } |
| // Since JS doesn't have API to get camera facing, we sort the list to make |
| // sure apps use the front camera by default. |
| // TODO(henryhsu): remove this after JS API completed (crbug.com/543997). |
| std::sort(device_descriptors->begin(), device_descriptors->end()); |
| } |
| |
| void VideoCaptureDeviceFactoryLinux::GetSupportedFormats( |
| const VideoCaptureDeviceDescriptor& device, |
| VideoCaptureFormats* supported_formats) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (device.device_id.empty()) |
| return; |
| base::ScopedFD fd(HANDLE_EINTR(open(device.device_id.c_str(), O_RDONLY))); |
| if (!fd.is_valid()) // Failed to open this device. |
| return; |
| supported_formats->clear(); |
| |
| DCHECK_NE(device.capture_api, VideoCaptureApi::UNKNOWN); |
| GetSupportedFormatsForV4L2BufferType(fd.get(), supported_formats); |
| } |
| |
| std::string VideoCaptureDeviceFactoryLinux::GetDeviceModelId( |
| const std::string& device_id) { |
| // |unique_id| is of the form "/dev/video2". |file_name| is "video2". |
| const std::string dev_dir = "/dev/"; |
| DCHECK_EQ(0, device_id.compare(0, dev_dir.length(), dev_dir)); |
| const std::string file_name = |
| device_id.substr(dev_dir.length(), device_id.length()); |
| |
| const std::string vidPath = |
| base::StringPrintf(kVidPathTemplate, file_name.c_str()); |
| const std::string pidPath = |
| base::StringPrintf(kPidPathTemplate, file_name.c_str()); |
| |
| std::string usb_id; |
| if (!ReadIdFile(vidPath, &usb_id)) |
| return ""; |
| usb_id.append(":"); |
| if (!ReadIdFile(pidPath, &usb_id)) |
| return ""; |
| |
| return usb_id; |
| } |
| |
| // static |
| VideoCaptureDeviceFactory* |
| VideoCaptureDeviceFactory::CreateVideoCaptureDeviceFactory( |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { |
| return new VideoCaptureDeviceFactoryLinux(ui_task_runner); |
| } |
| |
| } // namespace media |