24. 多摄像头协同:多CameraDevice管理、多路预览同步、多摄像头数据融合

多摄像头协同,说白了就是让手机上的两个甚至三个摄像头同时工作。你想想看,现在的旗舰机哪个不是三摄四摄?但让它们各自独立拍照容易,让它们协同工作,嗯,这里面的坑可不少。

我记得第一次做双摄项目时,天真地以为开两个CameraDevice就行。结果一跑起来,预览画面一个快一个慢,颜色还不一样。当时我就意识到——多摄像头管理,绝不是简单的「开两个Camera」那么简单。

24.1 多CameraDevice的管理策略

Android从API 21开始支持同时打开多个摄像头。但有个限制:同一时间只能打开一个CameraDevice实例。想同时用广角和长焦?你得分别创建两个CameraDevice。

我个人习惯用CameraManager来管理所有摄像头。先获取所有摄像头ID,再根据需求筛选。

CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
String[] cameraIds = manager.getCameraIdList();

// 筛选广角和长焦
String wideId = null;
String teleId = null;
for (String id : cameraIds) {
    CameraCharacteristics chars = manager.getCameraCharacteristics(id);
    Integer facing = chars.get(CameraCharacteristics.LENS_FACING);
    if (facing != CameraCharacteristics.LENS_FACING_BACK) continue;
    
    // 通过焦距判断
    float[] focalLengths = chars.get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS);
    if (focalLengths != null && focalLengths.length > 0) {
        if (focalLengths[0] < 2.0f) wideId = id;
        else teleId = id;
    }
}

核心要点:每个CameraDevice独立管理自己的生命周期。打开、关闭、异常处理都要各自维护。我建议用一个Map<String, CameraDevice>来统一管理,key就是摄像头ID。

24.2 多路预览同步的挑战

多路预览同步,这是最头疼的部分。两个摄像头各自跑各自的帧率,画面不同步是常态。

为什么会这样?因为每个CameraDevice有自己的帧率控制、曝光时间、增益调整。哪怕你设置同样的fps,实际输出时间戳也可能差几毫秒到几十毫秒。

我在项目中遇到过这样的问题:广角和长焦的画面叠加显示时,边缘总是有重影。后来发现是两路画面的时间戳差了30ms。解决办法是——以主摄像头的时间戳为基准,副摄像头做帧对齐

// 帧对齐示例
class FrameSynchronizer {
    private long baseTimestamp = -1;
    private final Queue<Image> syncQueue = new LinkedList<>();
    
    public void onFrameAvailable(Image image, long timestamp) {
        if (baseTimestamp == -1) {
            baseTimestamp = timestamp;
            syncQueue.offer(image);
            return;
        }
        
        // 计算时间差
        long diff = timestamp - baseTimestamp;
        if (Math.abs(diff) < 16_000_000) { // 16ms以内算同步
            processSyncedFrames(image);
        } else {
            // 丢弃或等待
            image.close();
        }
    }
}

小技巧:如果硬件支持,可以用CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES中的SYNC能力。但说实话,大部分中低端设备不支持硬件同步,还是得靠软件对齐。

24.3 多摄像头数据融合

数据融合,这是多摄像头的终极目标。常见的融合方式有三种:

融合方式 原理 适用场景
画面拼接 将广角和长焦的画面拼成一张全景图 全景拍照、超广角模式
深度融合 利用两个摄像头的视差计算深度信息 人像模式、AR测距
多帧合成 同时采集多帧,合成高动态范围或低噪点图像 夜景模式、HDR

我个人最常用的是深度融合。原理很简单:两个摄像头位置不同,看到的画面有视差。通过匹配两幅图像中的特征点,就能算出每个像素的深度。

// 深度融合伪代码
public DepthMap computeDepth(Image wideImage, Image teleImage) {
    // 1. 特征点提取
    List<KeyPoint> keyPoints1 = extractORBFeatures(wideImage);
    List<KeyPoint> keyPoints2 = extractORBFeatures(teleImage);
    
    // 2. 特征点匹配
    List<Match> matches = matchFeatures(keyPoints1, keyPoints2);
    
    // 3. 计算基础矩阵
    Mat fundamentalMat = findFundamentalMat(matches);
    
    // 4. 三角测量得到深度
    DepthMap depth = triangulate(matches, fundamentalMat);
    
    return depth;
}

注意:深度融合对摄像头标定要求极高。两个摄像头的内参、外参(相对位置)必须精确。我曾经因为标定参数差了0.5毫米,导致深度图全是噪点。建议用OpenCV的calibrateCamera做标定,或者直接用手机厂商提供的标定文件。

24.4 实战中的避坑指南

做多摄像头项目,有几个坑我踩过,分享给你:

  • 内存爆炸:两路预览同时开,每路都是1080p,内存占用直接翻倍。建议用SurfaceView而不是TextureView,减少内存拷贝。
  • 发热严重:两个ISP同时工作,发热量不是1+1=2,而是1+1=3。我建议在非必要场景下,副摄像头用低分辨率预览。
  • 权限问题:Android 10以上,同时打开两个摄像头需要额外的CAMERA权限声明。别忘了在manifest里加。
  • 兼容性:不是所有设备都支持同时打开两个后置摄像头。建议先检查CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES中的LOGICAL_MULTI_CAMERA能力。

我曾经在一个项目里,因为没检查兼容性,结果在小米某款手机上直接崩溃。后来加了个运行时检测,才解决问题。

24.5 多摄像头协同的架构设计

最后说说架构。我推荐用生产者-消费者模式来管理多路数据流。

多摄像头协同架构图 CameraDevice 1 广角摄像头 CameraDevice 2 长焦摄像头 CameraDevice 3 超广角摄像头 FrameSynchronizer(帧同步器) 时间戳对齐 | 帧缓冲 | 丢帧处理 DataFusion(数据融合) 特征匹配 | 深度计算 | 多帧合成 最终输出(预览/拍照/视频)

这个架构的好处是:每个摄像头独立采集,帧同步器统一对齐,数据融合模块只处理对齐后的帧。各模块职责清晰,出了问题也好排查。

嗯,多摄像头协同这块内容确实不少。但只要你把CameraDevice管理、帧同步、数据融合这三个核心点吃透,大部分场景都能应对。我在实际项目中,这套架构已经稳定运行在好几款机型上了,效果还不错。