前言
前面研究在Android中获取超广角摄像头:一些获取您的Android设备超广角能力的思路 - 掘金 (juejin.cn)。后续笔者通过查询Android文档发现了一些新的思路,本文将通过文档提到的内容,总结一下自己对于获取超广角的新发现。
逻辑摄像头
概念
参考 Multi-camera API#logical | Android Developers (google.cn)
在Android 9时,Android提出了多摄像头的概念,下图截取了有关逻辑摄像头的概念解释

翻译一下大概意思是手机由于多摄像头的出现,以后置摄像头模组为例,一个物理上的摄像头被称为物理摄像头。多个物理摄像头可以分成一组,每组可以用一个逻辑摄像头来表示。
逻辑摄像头可以理解是在代码上对于多摄像头的抽象,比较常见的例子就是缩放,多摄像头的焦距能够支持更大的缩放范围。
获取
获取逻辑摄像头的方法在文档中的示例代码也有提到,这里再简单说一下
String[] cameraIds = cameraManager.getCameraIdList();
List<String> backCameraIds = new ArrayList<>();
for (String id : cameraIds) {
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
if (cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) != CameraCharacteristics.LENS_FACING_BACK)
continue;
backCameraIds.add(id);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
List<String> logicCameraIds = new ArrayList<>();
for (String id : backCameraIds) {
CameraCharacteristics cameraCharacteristics = cameraManager.getCameraCharacteristics(id);
int[] available = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
for (int i : available) {
if (i == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA) {
logicCameraIds.add(id);
}
}
}
}
上述代码展示的是我们需要从后置摄像头中找到逻辑摄像头。ps:这个过程基于Camera2
- 通过
CameraCharacteristics.LENS_FACING_BACK
筛选出后置摄像头 - Android 9之后的支持库中为我们提供了
CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA
用于标识该摄像头是否为逻辑摄像头,通过CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES
获取的数组中得知。
可以看到逻辑摄像头也是在getCameraIdList
中获取的,只是通过属性区分。
计算视图角的不适配问题解读
在一些获取您的Android设备超广角能力的思路一文中有提到关于通过计算视图角推测出广角摄像头的方案。
但在华为P40中会出现不适配情况,原因在于cameraId = 0
的摄像头,是一个逻辑摄像头,原则上是不需要参与计算的。

上图是之前计算的结果,可以看最后的“back camera result”,如果我们把0的结果排除掉再比较大小的话,最后结果应该是4最大,而4就是华为P40的超广角摄像头了。
多摄像头创建会话
参考
Multi-camera API#multi-physical | Android Developers (google.cn)Multi-camera API#pair-physical | Android Developers (google.cn)
接下来两个小节介绍的是在一个逻辑摄像头下的物理摄像头可以创建一个CameraCaptureSession
进行预览、拍照。依赖的是CameraDevice.createCaptureSession(SessionConfiguration config)
还是以华为P40为例,我们知道在逻辑摄像头0
下,有物理摄像头2、4、6
。

-
我们先默认2、4摄像头是直接可以使用的,不需要通过任何参数筛选
-
将2、4摄像头创建成两个
OutputConfiguration
,当然每个OutputConfiguration
都需要绑定一个Surfaceval physicalCameraId = 2 val config = OutputConfiguration(surface) config.setPhysicalCameraId(physicalCameraId)
-
再使用两个
OutputConfiguration
创建一个SessionConfiguration
val executor = Executors.newCachedThreadPool() val sessionConfig = SessionConfiguration( SessionConfiguration.SESSION_REGULAR, outputConfigs, // Array<OutputConfiguration> executor, captureStateCallback )
-
最后就是创建
CameraCaptureSession
cameraDevice?.createCaptureSession(sessionConfig)
这样我们就可以通过一个CameraCaptureSession
对象管理两个摄像头预览了(2、4摄像头各有一个预览视图)。
这里找了一个github的项目可供参考:
ps:需要补充的是,物理摄像头的列表可通过逻辑摄像头的CameraCharacteristics#getPhysicalCameraIds()
获取。
筛选广角镜头的思路
文档中还列举一个缩放的例子,其实通过上一节的内容,就可以获取到一个从正常视距到较小视距的范围,我们可以通过这个实现一个缩放的效果。实际上就是两个预览视图的切换效果,可以是两个TextureView
。
重点看看原文中获取最短和最长焦距的方法
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)
// Query the focal lengths advertised by each physical camera
val focalLengths1 = characteristics1.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
val focalLengths2 = characteristics2.get(
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS) ?: floatArrayOf(0F)
// Compute the largest difference between min and max focal lengths between cameras
val focalLengthsDiff1 = focalLengths2.maxOrNull()!! - focalLengths1.minOrNull()!!
val focalLengthsDiff2 = focalLengths1.maxOrNull()!! - focalLengths2.minOrNull()!!
// Return the pair of camera IDs and the difference between min and max focal lengths
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
}.maxByOrNull { it.second }?.first
}
这里最终出来的是在一个逻辑摄像头下拥有最短、最长焦距的摄像头组合。当然,在华为P40上面输出的是物理摄像头6、4。6的焦距是最短的,查看官方参数是一枚长焦镜头,应该是用来做高倍数的变焦的。
所以其实这个方法并不是通用的,但笔者的目的只是获取到广角镜头,所以简单来说只需要知道哪个焦距是最短的即可。
还有一个需要注意的是,由于CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
拿到的是一个数组,所以上述代码用到了max
、min
的比较。
畸变矫正问题
参考 Multi-camera API#distortion | Android Developers (google.cn)
之前的文章也有提到,成功获取到超广角镜头之后的问题就是如何处理画面畸变的问题。这里有提及到,部分设备会支持CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY
,这个可以进行校正。可以通过代码获取
int[] correctionAvailableModes = characteristics.get(
CameraCharacteristics.DISTORTION_CORRECTION_AVAILABLE_MODES);
boolean supportsDistortionCorrection = false; // 是否支持失真校正
if (correctionAvailableModes != null) {
for (int mode: correctionAvailableModes) {
if (mode == CameraMetadata.DISTORTION_CORRECTION_MODE_HIGH_QUALITY) {
supportsDistortionCorrection = true;
break;
}
}
}
但笔者手上的华为P40并没有支持,下图是摄像头2、4、6的信息

总结
通过这篇Android官方文档,可以总结出几点:
- Android 9之后有逻辑摄像头的概念,这也是为什么之前计算视图角不能准确确定超广角摄像头的原因
-
CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS
获取的焦距是数组,之前的方案是直接拿第1位计算,可能会存在不准确的地方。 - 如果顺利获取到超广角进行预览、拍照了,还需要处理畸变的问题。
所有的这一切看起来都很美好,但是在笔者实测发现:小米、OPPO的系统(ps:这里不清楚和系统版本是否有直接关系)是无法通过Camera2读取到多摄像头的,也就是说这个方案会直接gg。所以说等待手机厂商推出自己的方案可能才是正途,只是这个可能比较难。。。
作者:Cy13er
链接:https://juejin.cn/post/7187335814609649725
网友评论