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

Kotlin实现相机延迟预览功能的技术方案咨询

延迟预览相机应用实现方案(Kotlin + CameraX)

选型确认

CameraX是该需求的最优选型,它封装了Camera2的复杂底层逻辑,兼容性覆盖Android 5.0(API 21)及以上设备,原生支持预览帧回调,无需处理不同厂商的相机适配坑,比自行封装Camera2减少80%的适配工作量,完全匹配你的开发需求。

核心实现逻辑

你要的延迟预览本质是环形帧缓存队列的实现,逻辑如下:

  • 启动相机后同时执行两个任务:1、输出实时预览到前台PreviewView,满足用户先看实时画面的需求;2、把每帧画面按时间戳存入固定大小的环形缓存
  • 缓存大小可以动态计算:比如相机预览帧率为30fps,用户设置5秒延迟,那缓存需要预留30*5=150帧的存储空间,缓存满后新帧进入直接覆盖最旧的帧即可
  • 达到用户设置的延迟时长后,停止往界面输出实时预览,转而按原始帧率从环形缓存中按顺序读取旧帧渲染到界面,即可得到连贯的延迟播放效果

具体实现步骤

1. 依赖引入(Module级build.gradle.kts)

dependencies {
    val cameraxVersion = "1.3.0"
    implementation("androidx.camera:camera-core:$cameraxVersion")
    implementation("androidx.camera:camera-camera2:$cameraxVersion")
    implementation("androidx.camera:camera-lifecycle:$cameraxVersion")
    implementation("androidx.camera:camera-view:$cameraxVersion")
}

2. 实现环形帧缓存类

注意不要直接存Bitmap,内存占用过高,优先存YUV格式的ImageProxy或者压缩后的纹理ID,内存占用可降低90%,简化实现示例:

class CircularFrameCache(private val maxFrames: Int) {
    private val cache = ArrayDeque<FrameItem>(maxFrames)

    @Synchronized
    fun addFrame(item: FrameItem) {
        if (cache.size >= maxFrames) {
            cache.removeFirst().close() // 必须关闭ImageProxy避免内存泄漏
        }
        cache.addLast(item)
    }

    @Synchronized
    fun pollFrame(): FrameItem? = cache.removeFirstOrNull()

    data class FrameItem(val timestamp: Long, val image: ImageProxy) {
        fun close() = image.close()
    }
}

3. 绑定CameraX用例,同时处理实时预览和帧采集

需要同时绑定Preview和ImageAnalysis两个用例:

  • Preview用例负责输出实时画面到PreviewView,供用户查看初始实时预览
  • ImageAnalysis用例负责接收每一帧的回调,将帧加入环形缓存,背压策略设置为STRATEGY_KEEP_ONLY_LATEST避免帧积压
// 初始化ImageAnalysis用例
val imageAnalysis = ImageAnalysis.Builder()
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .setTargetResolution(Size(1280, 720)) // 按需调整分辨率,过高会增加内存占用
    .build()

val fps = 30 // 可动态获取相机支持的实际预览帧率
var delaySeconds = 5 // 对应用户自定义的延迟时长
val cacheSize = fps * delaySeconds
val frameCache = CircularFrameCache(cacheSize)

imageAnalysis.setAnalyzer(Dispatchers.IO.asExecutor()) { imageProxy ->
    frameCache.addFrame(CircularFrameCache.FrameItem(imageProxy.imageInfo.timestamp, imageProxy))
}

// 绑定用例到页面生命周期
CameraX.bindToLifecycle(
    this,
    cameraSelector,
    preview, // 实时预览用例
    imageAnalysis // 帧采集用例
)

4. 延迟触发后的播放实现

到达设定的延迟时间后,停止Preview用例的实时输出,开启定时任务按原始帧率从缓存取帧渲染即可:

// 延迟时间到后启动播放逻辑
val playbackInterval = 1000 / fps // 计算每帧的播放间隔(毫秒)
lifecycleScope.launch {
    while (isActive) {
        val frame = frameCache.pollFrame() ?: continue
        // 将YUV格式的帧转成Bitmap或者直接渲染到TextureView/PreviewView
        renderFrame(frame)
        delay(playbackInterval)
        frame.close()
    }
}

你可以在布局中仅保留一个PreviewView,初始状态绑定CameraX的Preview用例输出实时画面,延迟时间到后解绑Preview用例,把PreviewView作为渲染目标输出缓存帧,用户完全感知不到视图切换,体验更流畅。

优化注意事项

  • 内存控制:如果延迟时长超过10秒,建议把帧压缩为JPEG后再存储,720P的JPEG单帧仅几十KB,存储1分钟也仅占用数百M内存,可有效避免OOM
  • 流畅度优化:渲染帧时优先用OpenGL直接渲染YUV数据,不要转Bitmap,可将单帧渲染耗时降到1ms以内,不会出现卡顿
  • 延迟校准:存入帧时记录时间戳,播放时对比实际时间差调整播放间隔,避免相机帧率波动导致的播放速度异常

内容的提问来源于stack exchange,提问作者Kacper Królikowski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 21:15:05