| // Copyright 2021 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 "media_capabilities/camera.h" |
| |
| #include <fcntl.h> |
| #include <linux/media.h> |
| #include <linux/videodev2.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include <base/files/file.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| |
| #include "media_capabilities/common.h" |
| |
| namespace { |
| |
| enum class CameraType { |
| kUnknown, |
| kUVC, |
| kVivid, |
| }; |
| |
| CameraType GetCameraType(int device_fd) { |
| struct v4l2_capability cap; |
| memset(&cap, 0, sizeof(cap)); |
| |
| if (Ioctl(device_fd, VIDIOC_QUERYCAP, &cap)) |
| return CameraType::kUnknown; |
| if (strcmp((const char*)cap.driver, "uvcvideo") == 0) |
| return CameraType::kUVC; |
| if (strcmp((const char*)cap.driver, "vivid") == 0) |
| return CameraType::kVivid; |
| |
| return CameraType::kUnknown; |
| } |
| |
| bool IsBuiltinMipiCamera(int device_fd) { |
| struct media_entity_desc desc; |
| memset(&desc, 0, sizeof(desc)); |
| |
| for (desc.id = MEDIA_ENT_ID_FLAG_NEXT; |
| !Ioctl(device_fd, MEDIA_IOC_ENUM_ENTITIES, &desc); |
| desc.id |= MEDIA_ENT_ID_FLAG_NEXT) { |
| if (desc.type == MEDIA_ENT_T_V4L2_SUBDEV_SENSOR) |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsBuiltinUSBCamera(int device_fd, const base::FilePath& device_path) { |
| if (GetCameraType(device_fd) != CameraType::kUVC) |
| return false; |
| const base::FilePath base_name = device_path.BaseName(); |
| if (base_name.empty()) { |
| LOG(ERROR) << "base file is empty, path=" << device_path; |
| return false; |
| } |
| |
| const base::FilePath vendorid_path = base::FilePath("/sys/class/video4linux/") |
| .Append(base_name) |
| .Append("device/../idVendor"); |
| base::FilePath normalized_vendorid_path; |
| if (!base::NormalizeFilePath(vendorid_path, &normalized_vendorid_path)) { |
| LOG(ERROR) << "Failed to normalize vendor id path: " << vendorid_path; |
| return false; |
| } |
| std::string vendor_id; |
| if (!base::ReadFileToString(normalized_vendorid_path, &vendor_id)) { |
| LOG(ERROR) << "Failed to read vendor id file: " << normalized_vendorid_path; |
| return false; |
| } |
| |
| // Check if the camera is not an external one. The vendor IDs of external |
| // cameras used in the lab need to be listed here. If there are many kinds of |
| // external cameras, we might want to have a list of vid:pid of builtin |
| // cameras instead. |
| const char* kExternalCameraVendorIds[] = { |
| "046d", // Logitech |
| "2bd9", // Huddly GO |
| }; |
| |
| for (const char* external_vendor_id : kExternalCameraVendorIds) { |
| if (vendor_id == external_vendor_id) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool IsVividCamera(int device_fd) { |
| if (GetCameraType(device_fd) != CameraType::kVivid) |
| return false; |
| |
| struct v4l2_capability cap; |
| memset(&cap, 0, sizeof(cap)); |
| if (Ioctl(device_fd, VIDIOC_QUERYCAP, &cap)) { |
| PLOG(FATAL) << "VIDIOC_QUERYCAP failed: "; |
| return false; |
| } |
| |
| // Check if vivid is emulating a video capture device. |
| const uint32_t mask = cap.capabilities; |
| constexpr uint32_t kCaptureMask = |
| V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_CAPTURE_MPLANE; |
| constexpr uint32_t kOutputMask = |
| V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_VIDEO_OUTPUT_MPLANE; |
| constexpr uint32_t kM2mMask = V4L2_CAP_VIDEO_M2M | V4L2_CAP_VIDEO_M2M_MPLANE; |
| |
| return (mask & kCaptureMask) && !(mask & kOutputMask) && !(mask & kM2mMask); |
| } |
| } // namespace |
| |
| std::vector<Capability> DetectCameraCapabilities() { |
| const base::FilePath kVideoDeviceName("/dev/video"); |
| bool has_builtin_usb_camera = false; |
| bool has_vivid_camera = false; |
| for (const base::FilePath& path : GetAllFilesWithPrefix(kVideoDeviceName)) { |
| base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_WRITE); |
| if (!file.IsValid()) |
| continue; |
| const int fd = file.GetPlatformFile(); |
| has_builtin_usb_camera |= IsBuiltinUSBCamera(fd, path); |
| has_vivid_camera |= IsVividCamera(fd); |
| } |
| |
| const base::FilePath kMediaDeviceName("/dev/media"); |
| bool has_builtin_mipi_camera = false; |
| for (const base::FilePath& path : GetAllFilesWithPrefix(kMediaDeviceName)) { |
| base::File file(path, base::File::FLAG_OPEN | base::File::FLAG_READ | |
| base::File::FLAG_WRITE); |
| if (!file.IsValid()) |
| continue; |
| const int fd = file.GetPlatformFile(); |
| has_builtin_mipi_camera |= IsBuiltinMipiCamera(fd); |
| } |
| |
| std::vector<Capability> capabilities; |
| if (has_builtin_usb_camera) |
| capabilities.push_back(Capability(CameraDescription::kBuiltinUSBCamera)); |
| if (has_builtin_mipi_camera) |
| capabilities.push_back(Capability(CameraDescription::kBuiltinMIPICamera)); |
| if (has_vivid_camera) |
| capabilities.push_back(Capability(CameraDescription::kVividCamera)); |
| if (has_builtin_mipi_camera || has_builtin_usb_camera) |
| capabilities.push_back(Capability(CameraDescription::kBuiltinCamera)); |
| if (has_builtin_mipi_camera || has_builtin_usb_camera || has_vivid_camera) { |
| capabilities.push_back( |
| Capability(CameraDescription::kBuiltinOrVividCamera)); |
| } |
| return capabilities; |
| } |