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

Kotlin中视频帧时间戳与旋转向量传感器数据同步问题

问题背景

功能实现

  • 视频采集:保存视频并将每帧时间戳存入.csv文件
  • 旋转向量传感器数据采集:将yaw、pitch、roll值及时间戳存入另一.csv文件

配置参数

  • 视频采集帧率:30Hz
  • 旋转向量更新频率:250Hz(线程写入文件频率约80Hz)

核心问题

尝试关联视频帧与对应传感器数据时,时间戳无法直接对齐,需给视频时间戳手动添加约160ms偏移才能同步。两者时间戳均通过System.currentTimeMillis()捕获,理论上应具备可比性,但延迟来源不明。

需要解答:

  1. VideoRecordEvent.Status返回的帧采集时间是否不准确?
  2. 视频与传感器时间戳存在160ms差异的原因是什么?
  3. 是否有更精准的延迟校正或补偿方法?

代码片段

视频及时间戳采集代码

val enableAudio = false
recording = videoCapture.output
    .prepareRecording(this, mediaStoreOutput)
    .apply {
        if (enableAudio && ContextCompat.checkSelfPermission(
                this@MainActivity,
                Manifest.permission.RECORD_AUDIO
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            withAudioEnabled()
        }
    }
    .start(ContextCompat.getMainExecutor(this)) { recordEvent ->
        when (recordEvent) {
            is VideoRecordEvent.Start -> {
                base_time = System.currentTimeMillis()
                isRecordingVideo = true
            }
            is VideoRecordEvent.Status -> {
                val time = recordEvent.recordingStats.recordedDurationNanos / 1_000_000
                val uriTimestampCopy = uriTimestamp
                if(uriTimestampCopy != null){
                    addTimestampDataToCsv(uriTimestampCopy, base_time+time)
                }

            }
            is VideoRecordEvent.Finalize -> {
                isRecordingVideo = false

            }
        }
    }

旋转向量数据及时间戳采集代码

private fun CSV_capture(){
    val executor = Executors.newSingleThreadExecutor()
    executor.execute {
        val uriRotVectorCopy = uriRotVector
        while(isRecord && (uriRotVectorCopy != null)) {
            addRotationDataToCsv(uriRotVectorCopy)
        }

    }

}

fun addRotationDataToCsv(uri: Uri) {
    val Lyaw = myaw
    val Lpitch = mpitch
    val Lroll = mroll
    val timestamp = System.currentTimeMillis()
    val resolver = contentResolver
    try {
        val outputStream: OutputStream? = resolver.openOutputStream(uri, "wa")
        outputStream?.let { stream ->
            val data = "$Lyaw,$Lpitch,$Lroll,$timestamp\n"
            stream.write(data.toByteArray())
            stream.flush()
            stream.close()
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

已尝试方法

  • 捕获每帧视频后立即保存旋转向量数据
  • 降低相机分辨率并禁用音频采集以减少延迟

以上尝试均未改善,视频帧时间戳仍与实际帧时间不符。


问题解答

1. VideoRecordEvent.Status返回的帧采集时间是否不准确?

是,这个时间戳并非实际帧捕获的硬件时间。recordedDurationNanos是从录制开始到当前累计的编码输出时长,代表的是视频文件中已写入内容的时长,而非传感器数据那种“事件发生瞬间”的时间戳。另外,VideoRecordEvent.Status在主线程回调,从帧被硬件捕获、编码,到回调触发,中间经过了处理和队列等待,这个过程的时间差会导致它和实际帧捕获时间存在偏差。

2. 视频与传感器时间戳存在160ms差异的原因是什么?

主要来自几个叠加的延迟环节:

  • 视频处理流水线延迟:从相机传感器捕获帧,到ISP处理(降噪、色彩校正)、编码器编码,再到VideoRecordEvent.Status回调触发,整个流水线会产生几十到上百毫秒的延迟,不同设备硬件性能差异会导致这个值波动。
  • 线程调度延迟:传感器数据在后台线程写入文件,但视频回调在主线程执行,主线程可能被UI操作阻塞,进一步拉大时间差。
  • 时间戳基准不一致:你用System.currentTimeMillis()作为视频时间的基准,但recordedDurationNanos是相机内部计时,和系统时钟可能存在微小偏移,叠加后就会出现明显差值。
  • 文件写入异步性:视频帧写入媒体库是异步操作,Status回调触发时,帧可能还没真正写入磁盘,但你已经记录了时间戳;而传感器数据是立即写入,两者的“记录时间”对应的实际事件时间本来就不对齐。

3. 是否有更精准的延迟校正或补偿方法?

可以尝试以下几种方案:

(1)使用相机帧的硬件时间戳

放弃VideoRecordEvent.Status的时长计算,改用ImageAnalysis获取相机帧的原始时间戳。通过ImageProxy.getTimestamp()可以拿到传感器捕获帧的硬件时间(纳秒级,基于系统单调时钟),这个时间戳和传感器事件的event.timestamp基准一致,可直接对齐。

示例思路:

val imageAnalyzer = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()
    .also {
        it.setAnalyzer(executor) { imageProxy ->
            val frameTimestamp = imageProxy.timestamp // 硬件捕获时间,纳秒
            // 将时间戳转换为毫秒存入CSV
            addTimestampDataToCsv(uriTimestamp, frameTimestamp / 1_000_000)
            imageProxy.close()
        }
    }
// 将imageAnalyzer绑定到相机会话
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalyzer, videoCapture)

(2)统一时间戳基准

传感器数据不要用System.currentTimeMillis(),改用传感器事件自带的event.timestamp(单调时钟,纳秒),视频帧也用ImageProxy.getTimestamp(),两者基于同一时钟源,避免系统时钟的偏移问题。

(3)离线校准延迟

录制一段包含明确同步信号的视频(比如快速闪烁的LED,同时触发传感器记录),离线分析视频帧中LED亮起的时间点和传感器中对应信号的时间点,计算出固定延迟值,后续录制时直接套用这个校准后的偏移。

(4)优化线程调度

传感器数据写入不要每次都打开/关闭输出流,保持流打开状态以减少IO操作延迟;同时用更高优先级的线程处理传感器数据,避免被其他任务阻塞。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 20:56:04