19、Camera 多摄像头:MultiCamera支持、LogicalCamera与PhysicalCamera、SynchronizedFrameNumber、多摄切换策略
多摄像头,说白了就是让手机背后那一排“眼睛”协同工作。你想想看,从单摄到双摄,再到现在的三摄、四摄甚至五摄,硬件是堆上去了,但系统怎么管好它们?嗯,这就是我们今天要聊的核心。
我个人习惯把多摄系统拆成三个层面来看:硬件抽象、数据同步、切换策略。下面我们一个一个过。
19.1 逻辑相机与物理相机
在 Android Camera Framework 里,有个很重要的概念叫 LogicalCamera 和 PhysicalCamera。我记得刚接触多摄时,看到 HAL 层返回的 camera_id 列表里既有逻辑 ID 又有物理 ID,当时就有点懵。
简单来说:
- PhysicalCamera:就是真实的硬件传感器,比如 IMX586、IMX363。每个物理相机有独立的 ID、独立的特性(如焦距、光圈)。
- LogicalCamera:是一个虚拟的“组长”。它不直接对应硬件,而是把多个 PhysicalCamera 组合成一个统一的接口给上层用。上层 App 打开一个 LogicalCamera,Framework 在背后帮你管理多个 PhysicalCamera。
核心要点:一个 LogicalCamera 可以包含 1~N 个 PhysicalCamera。但同一时刻,只有其中一个 PhysicalCamera 是“活跃”的(输出数据流),其他的可以处于待命或辅助状态。
为什么要这么设计?说白了就是为了让 App 开发者不用关心底层有几个摄像头。你只需要打开一个“后置主摄”的逻辑相机,系统自动帮你决定是用广角还是超广角,或者同时用两个做融合。
我在项目中遇到过一个问题:某个厂商的 HAL 把广角和超广角放在同一个 LogicalCamera 里,但它们的帧同步没做好,导致预览画面偶尔出现“撕裂”。嗯,这就是我们接下来要聊的 SynchronizedFrameNumber 要解决的问题。
19.2 SynchronizedFrameNumber:多摄同步的“时间戳”
多摄最难搞的就是同步。两个摄像头同时拍照,如果快门时间差了几毫秒,拍运动物体时画面就会错位。Android 从 API 28 开始引入了 SynchronizedFrameNumber 机制。
它的工作原理其实不复杂:
- 每个 PhysicalCamera 在产生帧时,都会分配一个递增的 FrameNumber。
- LogicalCamera 会维护一个全局的 SynchronizedFrameNumber,保证所有 PhysicalCamera 在同一时刻输出的帧拥有相同的 SynchronizedFrameNumber。
- App 可以通过
CaptureResult.getSynchronizedFrameNumber()拿到这个值,用来对齐多路数据流。
避坑指南:我曾经在调试双摄虚化时发现,左右眼的帧总是差一帧。后来查了 HAL 日志,发现是 PhysicalCamera 的帧率没对齐。解决办法是在配置 Session 时,强制所有 PhysicalCamera 使用相同的帧持续时间(FrameDuration)。
下面是一个典型的同步帧获取代码片段:
// 在 CaptureCallback 中
@Override
public void onCaptureCompleted(CameraCaptureSession session,
CaptureRequest request,
TotalCaptureResult result) {
long syncFrameNumber = result.getSynchronizedFrameNumber();
// 如果 syncFrameNumber > 0,说明这是一个多摄同步帧
if (syncFrameNumber != 0) {
// 可以用这个值去匹配其他 PhysicalCamera 的帧
Log.d("MultiCam", "SyncFrame: " + syncFrameNumber);
}
}
你可能会问:如果某个 PhysicalCamera 丢帧了怎么办?嗯,Framework 的处理策略是:如果主摄(Primary)的帧正常,副摄丢帧,那么 LogicalCamera 会直接丢弃副摄的帧,并上报一个错误标记。App 层需要根据这个标记决定是否重试。
19.3 多摄切换策略
多摄切换,说白了就是 App 在不同焦距、光线条件下,自动或手动切换使用哪个 PhysicalCamera。Android 提供了两种策略:
| 策略类型 | 触发方式 | 典型场景 |
|---|---|---|
| 自动切换 | 系统根据 zoomRatio、光线、场景自动选择 | 默认相机 App 的“变焦”操作 |
| 手动切换 | App 通过 CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA 枚举物理 ID,然后主动设置 |
专业模式、第三方相机 App |
我个人习惯在自动切换策略里,重点关注两个参数:
- zoomRatio:当 zoomRatio 在 1.0x~2.0x 时,系统倾向于用广角主摄;超过 2.0x 时,切换到长焦物理相机。
- AE/AWB 锁定:切换物理相机时,曝光和白平衡可能会突变。好的策略会在切换前锁定 AE/AWB,切换后再平滑过渡。
注意:不要频繁切换物理相机。我在一个项目里见过 App 每帧都根据 zoomRatio 切换摄像头,结果导致画面闪烁、功耗飙升。建议设置一个切换死区(比如 zoomRatio 变化超过 0.3 倍才触发切换)。
下面是一个手动切换物理相机的示例:
// 获取当前 LogicalCamera 支持的物理 ID
Set<String> physicalIds = characteristics.getPhysicalCameraIds();
for (String id : physicalIds) {
Log.d("MultiCam", "Physical camera: " + id);
}
// 构建请求时指定物理相机
CaptureRequest.Builder builder = session.getDevice().createCaptureRequest(
CameraDevice.TEMPLATE_PREVIEW);
builder.set(CaptureRequest.CONTROL_ZOOM_RATIO, 2.5f);
// 强制使用物理相机 ID "2"(假设是长焦)
builder.setPhysicalCameraId("2");
session.setRepeatingRequest(builder.build(), callback, handler);
19.4 多摄架构总览
为了让你更直观地理解整个多摄框架,我画了一张图。它展示了 App、Framework、HAL 三层之间 LogicalCamera 和 PhysicalCamera 的关系,以及 SynchronizedFrameNumber 的流动路径。
19.5 实战经验总结
最后,分享几个我在多摄开发中踩过的坑:
- 物理相机数量限制:不是所有 LogicalCamera 都支持同时打开所有 PhysicalCamera。有些 HAL 限制最多同时流 2 个物理相机。我曾经在调试时同时打开了 3 个,结果 HAL 直接 crash。
- SynchronizedFrameNumber 为 0:如果 HAL 不支持多摄同步,这个值永远是 0。App 层需要做兼容处理,不能强依赖。
- 切换延迟:从广角切到长焦,中间会有 100~300ms 的黑屏或卡顿。我建议在切换前先预初始化目标 PhysicalCamera 的 Session,可以大幅缩短延迟。
小技巧:在调试多摄同步问题时,可以打开 adb shell dumpsys media.camera,查看每个 PhysicalCamera 的帧计数和同步帧号。如果发现计数不连续,基本就是丢帧了。
好了,多摄像头这部分就聊到这儿。记住一句话:LogicalCamera 是面子,PhysicalCamera 是里子,SynchronizedFrameNumber 是绳子,把里子串起来给面子用。
公众号:蓝海资料掘金营,微信deep3321