media_capabilities: Detects camera capabilities

This CL enables media_capabilities to detect camera capabilities.

BUG=chromium:940314
TEST=run media_capabilities on atlas and hana

Change-Id: I6b61fce04a4d198de4c77ea3230674ae8d6d25b4
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform2/+/2377344
Tested-by: Hirokazu Honda <hiroh@chromium.org>
Reviewed-by: Shik Chen <shik@chromium.org>
Commit-Queue: Hirokazu Honda <hiroh@chromium.org>
diff --git a/media_capabilities/camera.cc b/media_capabilities/camera.cc
index 670675c..e40620d 100644
--- a/media_capabilities/camera.cc
+++ b/media_capabilities/camera.cc
@@ -4,8 +4,158 @@
 
 #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() {
-  return {};
+  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;
 }