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

CameraX API中ImageReader缓冲区溢出问题解决方法咨询

问题

使用CameraX的ImageAnalysis分析每一帧时,因帧处理耗时过长导致ImageReader缓冲区填满,出现以下错误:

[ImageReader-256x144f23m4-11920-0](id:2e9000000000,api:4,p:651,c:11920) waitForFreeSlotThenRelock: timeout

希望在缓冲区填满时清理ImageReader,同时保持原有帧处理速度。之前尝试丢弃部分帧解决了错误,但处理速度变慢,不符合需求。

相关代码如下:

analyze函数

@SuppressLint("UnsafeOptInUsageError")
override fun analyze(imageProxy: ImageProxy) {
    val image = imageProxy.image

    if (image != null) {
        val inputImage =
            InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees)
        processImage(inputImage, imageProxy)
    }else {
        imageProxy.close()
    }
}

processImage函数

@SuppressLint("UnsafeOptInUsageError")
private fun processImage(image: InputImage, imageProxy: ImageProxy) {
    poseDetector.process(image).addOnSuccessListener { results ->
        posesfromMLKit = getPoseCoordinate(results, imageProxy)
        if (isFullDetectionStarted) {
            getPoints(posesfromMLKit)
        }
        try {
            // Convert Image to BitMap
            val yBuffer = imageProxy.image!!.planes[0].buffer // Y
            val vuBuffer = imageProxy.image!!.planes[2].buffer // VU

            val ySize = yBuffer.remaining()
            val vuSize = vuBuffer.remaining()

            val nv21 = ByteArray(ySize + vuSize)

            yBuffer.get(nv21, 0, ySize)
            vuBuffer.get(nv21, ySize, vuSize)

            val yuvImage =
                YuvImage(nv21, ImageFormat.NV21, image!!.width, image!!.height, null)
            val out = ByteArrayOutputStream()
            yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 50, out)
            val imageBytes = out.toByteArray()
            base64EncodedFrame = Base64.getEncoder().encodeToString(imageBytes).toString()
        } catch (e: Exception) {
            imageProxy.close()
        }
        frameratePerSecond++
        imageProxy.close()
    }.addOnFailureListener { e ->
        imageProxy.close()
    }.addOnCompleteListener {
        imageProxy.close()
//                    CoroutineScope(Dispatchers.IO).launch {
//                        delay(100)
//                        imageProxy.close()
//                    }
    }.addOnCanceledListener {
        imageProxy.close()
    }
}
解决方案

问题核心是异步帧处理导致imageProxy释放不及时,加上图片转码耗时,最终占满ImageReader缓冲区。以下是针对性优化方案:

1. 启用CameraX的最新帧保留策略

直接通过ImageAnalysis配置设置只保留最新帧,自动丢弃旧帧,从根源避免缓冲区堆积:

val imageAnalysis = ImageAnalysis.Builder()
    // 只保留最新的一帧,旧帧自动被丢弃
    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
    .build()

这个策略会让ImageReader仅维护一个有效帧,新帧到来时直接覆盖旧帧,无需手动处理丢弃逻辑。

2. 限制并发帧处理

维护一个处理状态标志,确保同一时间只处理一帧,避免多帧堆积:

private var isProcessingFrame = false

@SuppressLint("UnsafeOptInUsageError")
override fun analyze(imageProxy: ImageProxy) {
    val image = imageProxy.image ?: run {
        imageProxy.close()
        return
    }

    // 如果正在处理前一帧,直接关闭当前帧
    if (isProcessingFrame) {
        imageProxy.close()
        return
    }

    isProcessingFrame = true
    val inputImage = InputImage.fromMediaImage(image, imageProxy.imageInfo.rotationDegrees)
    processImage(inputImage, imageProxy)
}

然后在processImage的所有回调末尾重置状态:

private fun processImage(image: InputImage, imageProxy: ImageProxy) {
    poseDetector.process(image).addOnSuccessListener { results ->
        // ... 原有逻辑 ...
        imageProxy.close()
        isProcessingFrame = false
    }.addOnFailureListener { e ->
        imageProxy.close()
        isProcessingFrame = false
    }.addOnCompleteListener {
        imageProxy.close()
        isProcessingFrame = false
    }.addOnCanceledListener {
        imageProxy.close()
        isProcessingFrame = false
    }
}

3. 将耗时转码操作移到后台线程

图片转码、Base64编码属于IO密集型操作,放在主线程会阻塞imageProxy释放。把这部分逻辑移到IO线程执行,让imageProxy尽快释放缓冲区:

// 修改processImage中的转码逻辑
try {
    // 先将图片数据复制到本地数组(避免imageProxy关闭后数据失效)
    val yBuffer = imageProxy.image!!.planes[0].buffer
    val vuBuffer = imageProxy.image!!.planes[2].buffer
    val ySize = yBuffer.remaining()
    val vuSize = vuBuffer.remaining()
    val nv21 = ByteArray(ySize + vuSize)
    yBuffer.get(nv21, 0, ySize)
    vuBuffer.get(nv21, ySize, vuSize)
    val imgWidth = image.width
    val imgHeight = image.height

    // 后台执行转码和编码
    CoroutineScope(Dispatchers.IO).launch {
        val yuvImage = YuvImage(nv21, ImageFormat.NV21, imgWidth, imgHeight, null)
        val out = ByteArrayOutputStream()
        yuvImage.compressToJpeg(Rect(0, 0, imgWidth, imgHeight), 50, out)
        val imageBytes = out.toByteArray()
        // 更新UI相关数据切回主线程
        withContext(Dispatchers.Main) {
            base64EncodedFrame = Base64.getEncoder().encodeToString(imageBytes)
            frameratePerSecond++
        }
    }
} catch (e: Exception) {
    imageProxy.close()
    isProcessingFrame = false
}

4. 优化PoseDetector性能

调整MLKit PoseDetector的配置,优先保证流处理速度:

val options = PoseDetectorOptions.Builder()
    // 针对视频流优化的模式,提升处理速度
    .setDetectorMode(PoseDetectorOptions.STREAM_MODE)
    // 可选:降低检测精度换取速度,比如只检测上半身
    // .setPoseLandmarkMode(PoseDetectorOptions.LANDMARK_MODE_LITE)
    .build()
val poseDetector = PoseDetection.getClient(options)

效果说明

结合以上方案,既能快速释放imageProxy清空缓冲区,又能保证始终处理最新的帧,不会因为丢弃帧导致有效处理速度下降。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 19:25:02