You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将CameraResult的getFrameNumber与ImageReader获取的Image映射

解决Camera2中ImageReader的Image与CaptureResult的FrameNumber关联问题

我之前在高通820的Android7.1.1设备上也碰到过一模一样的问题——Image.getTimestamp()和CaptureResult.SENSOR_TIMESTAMP差了一大截,根本没法用来关联FrameNumber。其实核心原因是这两个时间戳的基准完全不同,给你分享两个亲测有效的解决方案:

方案一:用Image元数据拿原始传感器时间戳(最直接)

你不知道的是,Image对象的元数据里其实藏着和CaptureResult完全一致的传感器时间戳!直接提取这个值就能建立可靠映射:

第一步:缓存传感器时间戳和FrameNumber的对应关系

用线程安全的ConcurrentHashMap来存,避免多线程回调出问题:

private ConcurrentHashMap<Long, Long> mSensorTimeToFrameNum = new ConcurrentHashMap<>();

CameraCaptureSession.CaptureCallback captureListenerLeft = new CameraCaptureSession.CaptureCallback() { 
    @Override 
    public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { 
        super.onCaptureCompleted(session, request, result); 
        long sensorTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
        long frameNumber = result.getFrameNumber();
        // 把配对关系存进映射表
        mSensorTimeToFrameNum.put(sensorTimestamp, frameNumber);
        
        Log.d(TAG, "CaptureCallback: FrameNumber[" + frameNumber + "] SensorTime[" + sensorTimestamp / 1000000 + "ms]"); 
    } 
};

第二步:在ImageReader回调里提取元数据中的时间戳

从Image的元数据中拿到原始传感器时间戳,再去映射表里找对应的FrameNumber:

ImageReader.OnImageAvailableListener mImgLeftCameraListener=new ImageReader.OnImageAvailableListener() { 
    @Override 
    public void onImageAvailable(ImageReader imageReader) { 
        try{ 
            Image mImage=imageReader.acquireNextImage(); 
            // 关键:从元数据中获取和CaptureResult一致的传感器时间戳
            long sensorTimestamp = mImage.getMetadata().getLong(CaptureResult.SENSOR_TIMESTAMP);
            // 通过时间戳找到对应的FrameNumber
            Long frameNumber = mSensorTimeToFrameNum.get(sensorTimestamp);
            
            if(frameNumber != null){
                Log.d(TAG, "ImageReader: FrameNumber[" + frameNumber + "] SensorTime[" + sensorTimestamp / 1000000 + "ms]");
                // 这里就可以处理你的业务逻辑了
            } else {
                Log.w(TAG, "没找到对应FrameNumber的传感器时间戳,可能是映射表已清理");
            }
            
            mImage.close(); 
        }catch (Exception exc){ 
            exc.printStackTrace(); 
        } 
    } 
};

方案二:用CaptureRequest的Tag做唯一标识(兼容性更强)

如果你的定制系统在元数据读取上有奇怪的限制,那就换个思路——给每个CaptureRequest打个唯一标签,通过标签来关联CaptureResult和Image:

第一步:给每个CaptureRequest设置唯一Tag

用原子计数器生成唯一标签,保证多线程下不会重复:

private AtomicLong mRequestTagCounter = new AtomicLong(0);

// 创建预览请求时设置Tag
CaptureRequest previewRequest = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
long requestTag = mRequestTagCounter.getAndIncrement();
previewRequest.setTag(requestTag);
// 提交请求到会话
mCaptureSession.setRepeatingRequest(previewRequest, captureListenerLeft, mBackgroundHandler);

第二步:缓存Tag与FrameNumber、传感器时间戳的对应关系

这里要把Tag和FrameNumber、传感器时间戳一起存,方便后续匹配:

private ConcurrentHashMap<Long, Pair<Long, Long>> mTagToFrameAndTime = new ConcurrentHashMap<>();

@Override 
public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request, TotalCaptureResult result) { 
    super.onCaptureCompleted(session, request, result); 
    long requestTag = (long) request.getTag();
    long frameNumber = result.getFrameNumber();
    long sensorTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP);
    // 存入Tag对应的FrameNumber和传感器时间戳
    mTagToFrameAndTime.put(requestTag, new Pair<>(frameNumber, sensorTimestamp));
}

第三步:在ImageReader回调中通过时间窗口匹配

因为没法直接从Image拿到Request的Tag,我们可以用传感器时间戳的时间窗口来匹配(比如允许100ms的误差,根据你的设备调整):

@Override 
public void onImageAvailable(ImageReader imageReader) { 
    try{ 
        Image mImage=imageReader.acquireNextImage(); 
        long imageSensorTime = mImage.getMetadata().getLong(CaptureResult.SENSOR_TIMESTAMP);
        // 设置时间阈值,比如100ms(纳秒单位)
        long timeThreshold = 100 * 1000000;
        
        // 遍历映射表找匹配项
        Iterator<Map.Entry<Long, Pair<Long, Long>>> iterator = mTagToFrameAndTime.entrySet().iterator();
        while(iterator.hasNext()){
            Map.Entry<Long, Pair<Long, Long>> entry = iterator.next();
            Pair<Long, Long> frameAndTime = entry.getValue();
            long resultSensorTime = frameAndTime.second;
            
            if(Math.abs(imageSensorTime - resultSensorTime) < timeThreshold){
                long frameNumber = frameAndTime.first;
                Log.d(TAG, "ImageReader匹配到FrameNumber[" + frameNumber + "]");
                // 找到后移除映射,避免重复匹配
                iterator.remove();
                // 处理业务逻辑
                break;
            }
        }
        
        mImage.close(); 
    }catch (Exception exc){ 
        exc.printStackTrace(); 
    } 
}

为啥原来的时间戳不匹配?

在高通820的Android7.1.1系统上:

  • CaptureResult.SENSOR_TIMESTAMP是传感器完成图像捕获的硬件时间戳(纳秒级),完全是硬件层面的数值。
  • Image.getTimestamp()是系统把图像数据推送到ImageReader的系统时间,中间包含了图像从传感器到用户空间的处理延迟,所以两者差值很大,根本没法直接用来关联。

而Image元数据里的CaptureResult.SENSOR_TIMESTAMP是直接从硬件拿的原始值,和CaptureResult里的时间戳完全一致,这才是最靠谱的匹配依据。

内容的提问来源于stack exchange,提问作者Herry

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:45:28