[译] Android 的多摄像头支持

从 Android P 开始,添加了对逻辑多摄像头和 USB 摄像头的支持。这对 Android 开发者来讲意味着什么?html

多摄像头

一台设备有多个摄像头没什么新鲜的,可是直到如今,Android 设备仍然最多只有先后两个摄像头。若是你想要打开第一个摄像头,须要进行如下操做:前端

val cameraDevice = Camera.open(0)
复制代码

可是这些是比较简单的操做。现在多摄像头意味着前置或者后置有两个及两个以上的摄像头。有不少镜头可供选择!android

Camera2 API

因为兼容性问题,尽管旧的 Camera API 已经被废弃很长时间,上述的代码仍然有效。可是随着生态系统的发展,须要更先进的相机功能。所以,Android 5.0(Lollipop)引进了 Camera2,适用于 API 21 及以上。用 Camera2 API 来打开第一个存在的摄像头代码以下所示:ios

val cameraManager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
val cameraId = cameraManager.cameraIdList[0]
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
    override fun onOpened(device: CameraDevice) {
        // Do something with `device`
    }
    override fun onDisconnected(device: CameraDevice) {
        device.close()
    }
    override fun onError(device: CameraDevice, error: Int) {
        onDisconnected(device)
    }
}, null)
复制代码

第一个并非最好的选择

上述代码目前看起来没什么问题。若是咱们所须要的只是一个可以打开第一个存在的摄像头的应用程序,那么它在大部分的 Android 手机上都有效。可是考虑到如下场景:git

  • 若是设备没有摄像头,那么应用程序会崩溃。这看起来彷佛不太可能,可是要知道 Android 运用在各类设备上,包括 Android Things、Android Wear 和 Android TV 等这些有数百万用户的设备。
  • 若是设备至少有一个后置摄像头,它将会映射到列表中的第一个摄像头。可是当应用程序运行在没有后置摄像头的设备上,好比 PixelBooks 或者其余一些 ChromeOS 的笔记本电脑,将会打开惟一一个前置摄像头。

那么咱们应该怎么作?检查摄像头列表和摄像头特性:github

val cameraIdList = cameraManager.cameraIdList // may be empty
val characteristics = cameraManager.getCameraCharacteristics(cameraId)
val cameraLensFacing = characteristics.get(CameraCharacteristics.LENS_FACING)
复制代码

变量 cameraLensFacing 有如下取值:后端

更多有关摄像头配置的信息,请查看文档.bash

合理的默认设置

根据应用程序的使用状况,咱们但愿默认打开特定的相机镜头配置(若是能够提供这样的功能)。好比,自拍应用程序极可能想要打开前置摄像头,而一款加强现实类的应用程序应该但愿打开后置摄像头。咱们能够将这样的一个逻辑包装成一个函数,它能够正确地处理上面提到的状况:ide

fun getFirstCameraIdFacing(cameraManager: CameraManager,
                           facing: Int = CameraMetadata.LENS_FACING_BACK): String? {
    val cameraIds = cameraManager.cameraIdList
    // Iterate over the list of cameras and return the first one matching desired
    // lens-facing configuration
    cameraIds.forEach {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        if (characteristics.get(CameraCharacteristics.LENS_FACING) == facing) {
            return it
        }
    }
    // If no camera matched desired orientation, return the first one from the list
    return cameraIds.firstOrNull()
}
复制代码

切换摄像头

目前为止,咱们讨论了如何基于应用程序的用途选择默认摄像头。不少相机应用程序还为用户提供切换摄像头的功能:函数

Google 相机应用中切换摄像头按钮

要实现这个功能,尝试从CameraManager.getCameraIdList()提供的列表中选择下一个摄像头,可是这并非个好的方式。由于从 Android P 开始,咱们将会看到在一样的状况下更多的设备有多个摄像头,甚至有经过 USB 链接的外部摄像头。若是咱们想要提供给用户切换不一样摄像头的 UI,建议(按照文档)是为每一个可能的镜头配置选择第一个可用的摄像头。

尽管没有一个通用的逻辑能够用来选择下一个摄像头,可是下述代码适用于大部分状况:

fun filterCameraIdsFacing(cameraIds: Array<String>, cameraManager: CameraManager,
                          facing: Int): List<String> {
    return cameraIds.filter {
        val characteristics = cameraManager.getCameraCharacteristics(it)
        characteristics.get(CameraCharacteristics.LENS_FACING) == facing
    }
}

fun getNextCameraId(cameraManager: CameraManager, currCameraId: String? = null): String? {
    // Get all front, back and external cameras in 3 separate lists
    val cameraIds = cameraManager.cameraIdList
    val backCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_BACK)
    val frontCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_FRONT)
    val externalCameras = filterCameraIdsFacing(
            cameraIds, cameraManager, CameraMetadata.LENS_FACING_EXTERNAL)

    // The recommended order of iteration is: all external, first back, first front
    val allCameras = (externalCameras + listOf(
            backCameras.firstOrNull(), frontCameras.firstOrNull())).filterNotNull()

    // Get the index of the currently selected camera in the list
    val cameraIndex = allCameras.indexOf(currCameraId)

    // The selected camera may not be on the list, for example it could be an
    // external camera that has been removed by the user
    return if (cameraIndex == -1) {
        // Return the first camera from the list
        allCameras.getOrNull(0)
    } else {
        // Return the next camera from the list, wrap around if necessary
        allCameras.getOrNull((cameraIndex + 1) % allCameras.size)
    }
}
复制代码

这看起来可能有点复杂,可是咱们须要考虑到大量的有不一样配置的设备。

兼容性行为

对于那些仍然在使用已经废弃的 Camera API 的应用程序,经过 Camera.getNumberOfCameras() 获得的摄像头的数量取决于 OEM 的实现。文档上是这样描述的:

若是系统中有逻辑多摄像头,为了保持应用程序的向后兼容性,这个方法仅为每一个逻辑摄像头和底层的物理摄像头组公开一个摄像头。使用 camera2 API 去查看全部摄像头。

请仔细阅读 其他文档 得到更多信息。一般来讲,相似的建议适用于:使用 Camera.getCameraInfo() API 查询全部的摄像头方向, 在用户切换摄像头时,仅仅只为每一个可用的方向提供一个摄像头。

最佳实践

Android 运行在许多不一样的设备上。你不该该假设你的应用程序老是在有一两个摄像头的传统的手持设备上运行,而是应该为你的应用程序选择最适合的摄像头。若是你不须要特定的摄像头,选择有所需默认配置的第一个摄像头。若是设备链接了外部摄像头,则能够合理的假设用户但愿首先看到这些外部摄像头中的第一个。

若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。


掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 AndroidiOS前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索