| // 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 <algorithm> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/posix/eintr_wrapper.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "build/build_config.h" |
| #include "media/capture/video/linux/scoped_v4l2_device_fd.h" |
| #include "media/capture/video/linux/video_capture_device_linux.h" |
| |
| #if defined(OS_OPENBSD) |
| #include <sys/videoio.h> |
| #else |
| #include <linux/videodev2.h> |
| #endif |
| |
| namespace media { |
| |
| namespace { |
| |
| bool CompareCaptureDevices(const VideoCaptureDeviceInfo& a, |
| const VideoCaptureDeviceInfo& b) { |
| return a.descriptor < b.descriptor; |
| } |
| |
| // USB VID and PID are both 4 bytes long. |
| const size_t kVidPidSize = 4; |
| const size_t kMaxInterfaceNameSize = 256; |
| |
| // /sys/class/video4linux/video{N}/device is a symlink to the corresponding |
| // USB device info directory. |
| const char kVidPathTemplate[] = "/sys/class/video4linux/%s/device/../idVendor"; |
| const char kPidPathTemplate[] = "/sys/class/video4linux/%s/device/../idProduct"; |
| const char kInterfacePathTemplate[] = |
| "/sys/class/video4linux/%s/device/interface"; |
| |
| 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; |
| } |
| |
| std::string ExtractFileNameFromDeviceId(const std::string& device_id) { |
| // |unique_id| is of the form "/dev/video2". |file_name| is "video2". |
| const char kDevDir[] = "/dev/"; |
| DCHECK(base::StartsWith(device_id, kDevDir, base::CompareCase::SENSITIVE)); |
| return device_id.substr(strlen(kDevDir), device_id.length()); |
| } |
| |
| class DevVideoFilePathsDeviceProvider |
| : public VideoCaptureDeviceFactoryLinux::DeviceProvider { |
| public: |
| void GetDeviceIds(std::vector<std::string>* target_container) override { |
| 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(); |
| target_container->emplace_back(path.value() + info.GetName().value()); |
| } |
| } |
| |
| std::string GetDeviceModelId(const std::string& device_id) override { |
| const std::string file_name = ExtractFileNameFromDeviceId(device_id); |
| std::string usb_id; |
| const std::string vid_path = |
| base::StringPrintf(kVidPathTemplate, file_name.c_str()); |
| if (!ReadIdFile(vid_path, &usb_id)) |
| return usb_id; |
| |
| usb_id.append(":"); |
| const std::string pid_path = |
| base::StringPrintf(kPidPathTemplate, file_name.c_str()); |
| if (!ReadIdFile(pid_path, &usb_id)) |
| usb_id.clear(); |
| |
| return usb_id; |
| } |
| |
| std::string GetDeviceDisplayName(const std::string& device_id) override { |
| const std::string file_name = ExtractFileNameFromDeviceId(device_id); |
| const std::string interface_path = |
| base::StringPrintf(kInterfacePathTemplate, file_name.c_str()); |
| std::string display_name; |
| if (!base::ReadFileToStringWithMaxSize(base::FilePath(interface_path), |
| &display_name, |
| kMaxInterfaceNameSize)) { |
| return std::string(); |
| } |
| return display_name; |
| } |
| }; |
| |
| } // namespace |
| |
| VideoCaptureDeviceFactoryLinux::VideoCaptureDeviceFactoryLinux( |
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) |
| : v4l2_(base::MakeRefCounted<V4L2CaptureDeviceImpl>()), |
| device_provider_(std::make_unique<DevVideoFilePathsDeviceProvider>()), |
| ui_task_runner_(ui_task_runner) {} |
| |
| VideoCaptureDeviceFactoryLinux::~VideoCaptureDeviceFactoryLinux() = default; |
| |
| void VideoCaptureDeviceFactoryLinux::SetV4L2EnvironmentForTesting( |
| scoped_refptr<V4L2CaptureDevice> v4l2, |
| std::unique_ptr<VideoCaptureDeviceFactoryLinux::DeviceProvider> |
| device_provider) { |
| v4l2_ = std::move(v4l2); |
| device_provider_ = std::move(device_provider); |
| } |
| |
| std::unique_ptr<VideoCaptureDevice> |
| VideoCaptureDeviceFactoryLinux::CreateDevice( |
| const VideoCaptureDeviceDescriptor& device_descriptor) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| auto self = |
| std::make_unique<VideoCaptureDeviceLinux>(v4l2_.get(), device_descriptor); |
| |
| // 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. |
| ScopedV4L2DeviceFD fd( |
| v4l2_.get(), |
| HANDLE_EINTR(v4l2_->open(device_descriptor.device_id.c_str(), O_RDONLY))); |
| if (!fd.is_valid()) { |
| DLOG(ERROR) << "Cannot open device"; |
| return nullptr; |
| } |
| |
| return self; |
| } |
| |
| void VideoCaptureDeviceFactoryLinux::GetDevicesInfo( |
| GetDevicesInfoCallback callback) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| std::vector<VideoCaptureDeviceInfo> devices_info; |
| std::vector<std::string> filepaths; |
| device_provider_->GetDeviceIds(&filepaths); |
| for (auto& unique_id : filepaths) { |
| const ScopedV4L2DeviceFD fd( |
| v4l2_.get(), HANDLE_EINTR(v4l2_->open(unique_id.c_str(), O_RDONLY))); |
| if (!fd.is_valid()) { |
| DLOG(ERROR) << "Couldn't open " << unique_id; |
| continue; |
| } |
| // Test if this is a V4L2CaptureDevice 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. |
| // 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(fd.get(), 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))) && |
| HasUsableFormats(fd.get(), cap.capabilities)) { |
| const std::string model_id = |
| device_provider_->GetDeviceModelId(unique_id); |
| std::string display_name = |
| device_provider_->GetDeviceDisplayName(unique_id); |
| if (display_name.empty()) |
| display_name = reinterpret_cast<char*>(cap.card); |
| |
| VideoFacingMode facing_mode = VideoFacingMode::MEDIA_VIDEO_FACING_NONE; |
| |
| VideoCaptureFormats supported_formats; |
| GetSupportedFormatsForV4L2BufferType(fd.get(), &supported_formats); |
| if (supported_formats.empty()) { |
| DVLOG(1) << "No supported formats: " << unique_id; |
| continue; |
| } |
| |
| devices_info.emplace_back(VideoCaptureDeviceDescriptor( |
| display_name, unique_id, model_id, |
| VideoCaptureApi::LINUX_V4L2_SINGLE_PLANE, GetControlSupport(fd.get()), |
| VideoCaptureTransportType::OTHER_TRANSPORT, facing_mode)); |
| |
| devices_info.back().supported_formats = std::move(supported_formats); |
| } |
| } |
| |
| // This is required for some applications that rely on the stable ordering of |
| // devices. |
| std::sort(devices_info.begin(), devices_info.end(), CompareCaptureDevices); |
| |
| std::move(callback).Run(std::move(devices_info)); |
| } |
| |
| int VideoCaptureDeviceFactoryLinux::DoIoctl(int fd, int request, void* argp) { |
| return HANDLE_EINTR(v4l2_->ioctl(fd, request, argp)); |
| } |
| |
| // Check if the video capture device supports pan, tilt and zoom controls. |
| VideoCaptureControlSupport VideoCaptureDeviceFactoryLinux::GetControlSupport( |
| int fd) { |
| VideoCaptureControlSupport control_support; |
| control_support.pan = GetControlSupport(fd, V4L2_CID_PAN_ABSOLUTE); |
| control_support.tilt = GetControlSupport(fd, V4L2_CID_TILT_ABSOLUTE); |
| control_support.zoom = GetControlSupport(fd, V4L2_CID_ZOOM_ABSOLUTE); |
| return control_support; |
| } |
| |
| bool VideoCaptureDeviceFactoryLinux::GetControlSupport(int fd, int control_id) { |
| v4l2_queryctrl range = {}; |
| range.id = control_id; |
| range.type = V4L2_CTRL_TYPE_INTEGER; |
| return DoIoctl(fd, VIDIOC_QUERYCTRL, &range) == 0 && |
| range.minimum < range.maximum; |
| } |
| |
| bool VideoCaptureDeviceFactoryLinux::HasUsableFormats(int fd, |
| uint32_t capabilities) { |
| if (!(capabilities & V4L2_CAP_VIDEO_CAPTURE)) |
| return false; |
| |
| const std::vector<uint32_t>& usable_fourccs = |
| VideoCaptureDeviceLinux::GetListOfUsableFourCCs(false); |
| v4l2_fmtdesc fmtdesc = {}; |
| fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| for (; DoIoctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0; ++fmtdesc.index) { |
| if (base::Contains(usable_fourccs, fmtdesc.pixelformat)) |
| return true; |
| } |
| |
| DVLOG(1) << "No usable formats found"; |
| return false; |
| } |
| |
| std::vector<float> VideoCaptureDeviceFactoryLinux::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 (; DoIoctl(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_LOG_ONCE(); |
| 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; |
| } |
| |
| void VideoCaptureDeviceFactoryLinux::GetSupportedFormatsForV4L2BufferType( |
| int fd, |
| VideoCaptureFormats* supported_formats) { |
| v4l2_fmtdesc v4l2_format = {}; |
| v4l2_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; |
| for (; DoIoctl(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 (; DoIoctl(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_LOG_ONCE(); |
| continue; |
| } |
| |
| const std::vector<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); |
| } |
| } |
| } |
| } |
| |
| } // namespace media |