如何将CameraResult的getFrameNumber与ImageReader获取的Image映射
我之前在高通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
相关产品推荐
相关产品推荐

