Android 10(api 29)camera2 api回归与广角相机

Posted

技术标签:

【中文标题】Android 10(api 29)camera2 api回归与广角相机【英文标题】:Android 10 (api 29) camera2 api regression with wide-angle camera 【发布时间】:2020-05-05 11:26:57 【问题描述】:

我在专为 Google Pixel 3 XL 设计的相机应用中使用了 camera2 api。该设备有两个前置摄像头(广角和普通)。由于多摄像头功能,我可以同时访问两个物理摄像头设备,并且我的应用程序具有在这两个摄像头之间切换的功能。在我最近升级到 android 10 之前,我可以准确地看到两个不同的结果,但现在我的广角捕捉帧具有与普通相机几乎相同的 FOV(视野)。因此,Android 9 广角捕获结果中的相同代码、相同 apk 与预期一样宽,并且在 Andoird 10 升级后 - 广角和普通相机显示几乎相同的 FOV。

这是一个代码 sn-p 来演示我如何初始化两个相机和捕捉预览:

MainActivity.kt

 private val surfaceReadyCallback = object: SurfaceHolder.Callback 
        override fun surfaceChanged(p0: SurfaceHolder?, p1: Int, p2: Int, p3: Int)  
        override fun surfaceDestroyed(p0: SurfaceHolder?)  

        override fun surfaceCreated(p0: SurfaceHolder?) 

            // Get the two output targets from the activity / fragment
            val surface1 = surfaceView1.holder.surface  
            val surface2 = surfaceView2.holder.surface 

            val dualCamera = findShortLongCameraPair(cameraManager)!!
            val outputTargets = DualCameraOutputs(
                null, mutableListOf(surface1), mutableListOf(surface2))

            //Open the logical camera, configure the outputs and create a session
            createDualCameraSession(cameraManager, dualCamera, targets = outputTargets)  session ->

                val requestTemplate = CameraDevice.TEMPLATE_PREVIEW
                val captureRequest = session.device.createCaptureRequest(requestTemplate).apply 
                    arrayOf(surface1, surface2).forEach  addTarget(it) 
                .build()

                session.setRepeatingRequest(captureRequest, null, null)
            
        
    


    fun openDualCamera(cameraManager: CameraManager,
                       dualCamera: DualCamera,
                       executor: Executor = SERIAL_EXECUTOR,
                       callback: (CameraDevice) -> Unit) 

        cameraManager.openCamera(
            dualCamera.logicalId, executor, object : CameraDevice.StateCallback() 
                override fun onOpened(device: CameraDevice)  callback(device) 

                override fun onError(device: CameraDevice, error: Int) = onDisconnected(device)
                override fun onDisconnected(device: CameraDevice) = device.close()
            )
    

    fun createDualCameraSession(cameraManager: CameraManager,
                                dualCamera: DualCamera,
                                targets: DualCameraOutputs,
                                executor: Executor = SERIAL_EXECUTOR,
                                callback: (CameraCaptureSession) -> Unit) 

        // Create 3 sets of output configurations: one for the logical camera, and
        // one for each of the physical cameras.
        val outputConfigsLogical = targets.first?.map  OutputConfiguration(it) 
        val outputConfigsPhysical1 = targets.second?.map 
            OutputConfiguration(it).apply  setPhysicalCameraId(dualCamera.physicalId1)  
        val outputConfigsPhysical2 = targets.third?.map 
            OutputConfiguration(it).apply  setPhysicalCameraId(dualCamera.physicalId2)  

        val outputConfigsAll = arrayOf(
            outputConfigsLogical, outputConfigsPhysical1, outputConfigsPhysical2)
            .filterNotNull().flatten()

        val sessionConfiguration = SessionConfiguration(SessionConfiguration.SESSION_REGULAR,
            outputConfigsAll, executor, object : CameraCaptureSession.StateCallback() 
                override fun onConfigured(session: CameraCaptureSession) = callback(session)
                override fun onConfigureFailed(session: CameraCaptureSession) = session.device.close()
            )


        openDualCamera(cameraManager, dualCamera, executor = executor) 
           it.createCaptureSession(sessionConfiguration)
        
    

DualCamera.kt 助手类

data class DualCamera(val logicalId: String, val physicalId1: String, val physicalId2: String)

fun findDualCameras(manager: CameraManager, facing: Int? = null): Array<DualCamera> 
    val dualCameras = ArrayList<DualCamera>()

    manager.cameraIdList.map 
        Pair(manager.getCameraCharacteristics(it), it)
    .filter 
        facing == null || it.first.get(CameraCharacteristics.LENS_FACING) == facing
    .filter 
        it.first.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)!!.contains(
            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA)
    .forEach 
        val physicalCameras = it.first.physicalCameraIds.toTypedArray()
        for (idx1 in 0 until physicalCameras.size) 
            for (idx2 in (idx1 + 1) until physicalCameras.size) 
                dualCameras.add(DualCamera(
                    it.second, physicalCameras[idx1], physicalCameras[idx2]))
            
        
    

    return dualCameras.toTypedArray()


fun findShortLongCameraPair(manager: CameraManager, facing: Int? = null): DualCamera? 

    return findDualCameras(manager, facing).map 
        val characteristics1 = manager.getCameraCharacteristics(it.physicalId1)
        val characteristics2 = manager.getCameraCharacteristics(it.physicalId2)

        val focalLengths1 = characteristics1.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
        val focalLengths2 = characteristics2.get(
            CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)

        val focalLengthsDiff1 = focalLengths2.max()!! - focalLengths1.min()!!
        val focalLengthsDiff2 = focalLengths1.max()!! - focalLengths2.min()!!

        if (focalLengthsDiff1 < focalLengthsDiff2) 
            Pair(DualCamera(it.logicalId, it.physicalId1, it.physicalId2), focalLengthsDiff1)
         else 
            Pair(DualCamera(it.logicalId, it.physicalId2, it.physicalId1), focalLengthsDiff2)
        

        // Return only the pair with the largest difference, or null if no pairs are found
    .sortedBy  it.second .reversed().lastOrNull()?.first

您可以在随附的屏幕截图中看到结果,左上角的 FOV 比相同的相机宽得多,但在 Android 10 上运行

这是 Android 10 的已知回归吗?有没有人注意到类似的行为?

【问题讨论】:

您能否更具体地了解您如何访问广角相机以及如何设置广角相机和长焦相机的裁剪区域?由于 Android 改进了其多摄像头 API,一些 Pixel 上的摄像头配置已更新,以更好地与更新的 API 配合使用。也就是说,仍然完全有可能获得您正在寻找的输出。 @EddyTalvala 我正在访问广角(和普通)相机,如下所示:1)从列表中获取物理相机 ID 2)将物理相机 ID 设置为 outputConfiguration 3)从相机特征中获取 activeArraySize并设置 SCALAR_CROP_REGION 以捕获请求 4) 创建 sessionConfiguration 并将 outputConfiguration 添加到其中 5) 使用 sessionConfiguration 创建捕获会话 你能解决这个问题吗? @LevonShirakyan 你找到解决方案了吗? 嗨。我还在 Android 11 上使用 Pixel 5 进行了测试,但它仍然无法正常工作 ***.com/a/66569731/7767664 【参考方案1】:

我的理解: 我在我的 Pixel 3 上遇到了同样的问题。似乎广角相机的帧在组合之前已经在 HAL 层中被裁剪。实际上,FOV 并不完全相同,因为左右相机之间存在一些差异。但是,广角相机的默认缩放级别似乎会根据焦距而变化。

但我找不到任何关于它的官方文档。在 Android 10 中,它声称改进了物理相机的融合: https://developer.android.com/about/versions/10/features#multi-camera

解决方案:

如果您希望从广角前置摄像头访问原始数据,您可以为两个物理摄像头创建 2 个摄像头会话,而不是为逻辑摄像头创建一个会话。

更新:

您可以使用 setPhysicalCameraKey 重置缩放级别 https://developer.android.com/reference/android/hardware/camera2/CaptureRequest.Builder#setPhysicalCameraKey(android.hardware.camera2.CaptureRequest.Key%3CT%3E,%20T,%20java.lang.String)

【讨论】:

camChars.availablePhysicalCameraRequestKeys 返回 null 所以我不能使用 setPhysicalCameraKey,只有 camChars.physicalCameraIds 返回 3 个 id 我打开逻辑摄像头,然后将物理摄像头设置为输出配置,但没有任何改变***.com/a/61652912/7767664【参考方案2】:

在像素 3 (Android 11) 上,使用 CameraManager.getCameraIdList() 探测相机会返回 4 个 ID:0123

0:读取相机:物理相机流 1: 前置摄像头 : 带有两个物理摄像头 ID 的逻辑摄像头 2:前置摄像头正常:物理摄像头流 3:前置摄像头加宽镜头:物理摄像头流

正如用户 DannyLin 建议的那样,打开 2 个物理摄像机流 (2,3) 似乎可以完成这项工作。请注意,其他组合,例如 (0, 1), (1, 2) 等不起作用(只有对 openCamera() 的第一次调用通过,第二次调用失败)。这是两个前置摄像头的物理摄像头流的快照。

【讨论】:

【参考方案3】:

您观察到的回归是 Pixel 3/Pixel 3XL 在 Android 9 和 Android 10 之间的行为变化。这不是 Android API 本身的变化,而是 API 允许设备改变行为的东西;其他设备可能不同。

相机 API 允许裁剪物理相机流以匹配逻辑相机流的视野。

【讨论】:

它不回答任何问题,设置或不设置物理 id 到逻辑相机捕获配置不会影响任何事情。如果您知道如何在 Pixels 上使用 Android 10+ 打开广角镜头相机,那么这将是一个答案,现在不可能***.com/a/61652912/7767664

以上是关于Android 10(api 29)camera2 api回归与广角相机的主要内容,如果未能解决你的问题,请参考以下文章

Android Camera2 API 设置自定义亮度、对比度、伽玛

使用 Camera2(Android 版本 21)API 录制 60fps 视频

android中的camera和camera2权限

只有一个带有 Camera2 和旧相机 API 的 APK

使用 Android L 和 Camera2 API 处理相机预览图像数据

Android中Camera和Camera2的区别