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
相关产品推荐
相关产品推荐

