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

Camera2 API问题:使用ImageReader从SurfaceView捕获图像时返回空值

Camera2 API捕获图像时acquireLatestImage()返回null问题

我使用Camera2 API拍摄图像,通过ImageReader从SurfaceView捕获图像,调用acquireLatestImage()获取图像数据。日志显示“Capture completed”说明捕获流程已触发,但获取到的图像为空,日志输出“Failed to acquire captured image”。

相关代码

捕获按钮点击方法

private fun onCaptureButtonClick(
    captureType: String,
    imageWidth: Int,
    imageHeight: Int,
    controlMode: Int,
    cameraSettings: MutableMap<String, Any?>
) {
    if (captureType == "Image") {
        try {
            //CaptureRequest.Builder to configure the capture request
            val captureRequestBuilder =
                cameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
            captureRequestBuilder?.addTarget(previewSurface!!)
            captureRequestBuilder?.set(CaptureRequest.CONTROL_MODE, controlMode)

            //ImageReader to capture the image
            val imageReader = ImageReader.newInstance(imageWidth, imageHeight, ImageFormat.JPEG, 1)
            Toast.makeText(applicationContext, "Imgereader Started", Toast.LENGTH_SHORT).show()

            if (imageReader != null && cameraDevice != null) {
                // Create a CameraCaptureSession.CaptureCallback to handle capture results
                val captureCallback = object : CameraCaptureSession.CaptureCallback() {
                    override fun onCaptureCompleted(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        result: TotalCaptureResult
                    ) {
                        Log.d(TAG, "Capture completed")
                        super.onCaptureCompleted(session, request, result)

                        //image data from the ImageReader
                        val image = imageReader.acquireLatestImage()

                        if (image != null) {
                            //image data buffer
                            val buffer = image.planes[0].buffer

                            //buffer to a ByteArray
                            val imageData = ByteArray(buffer.remaining())
                            buffer.get(imageData)

                            // Save the image data to a JPEG file
                            val outputFile = File(
                                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                                "my_image.jpg"
                            )
                            try {
                                FileOutputStream(outputFile).use { output ->
                                    output.write(imageData)
                                }
                                Log.d(
                                    TAG,
                                    "Image saved successfully: ${outputFile.absolutePath}"
                                )

                                // Update the gallery so that the image appears in the gallery app
                                MediaScannerConnection.scanFile(
                                    applicationContext,
                                    arrayOf(outputFile.absolutePath),
                                    null,
                                    null
                                )
                            } catch (e: IOException) {
                                e.printStackTrace()
                            } finally {
                                // Close the image after saving
                                image.close()
                            }
                        } else {
                            Log.e(TAG, "Failed to acquire captured image")
                            Toast.makeText(
                                applicationContext,
                                "Failed to acquire captured image",
                                Toast.LENGTH_SHORT
                            ).show()
                        }
                        
                        updateCameraPreview()
                    }

                    override fun onCaptureFailed(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        failure: CaptureFailure
                    ) {
                        super.onCaptureFailed(session, request, failure)
                        // Capture failed, implement your logic here
                        Log.d(TAG, "Capture failed")
                    }
                }
                //apture session with the configured capture request, imageReader surface, and capture callback
                val surfaces = mutableListOf(previewSurface, imageReader.surface)

                if (cameraCaptureSession == null) {
                    //new capture session if the current capture session is null
                    cameraDevice?.createCaptureSession(surfaces, object : CameraCaptureSession.StateCallback() {
                        override fun onConfigured(session: CameraCaptureSession) {

                            // Capture session configured successfully, get the CameraCaptureSession object
                            cameraCaptureSession = session
                            captureRequestBuilder?.build()?.let {
                                cameraCaptureSession?.capture(
                                    it, captureCallback, null
                                )
                            }
                        }
                            override fun onConfigureFailed(session: CameraCaptureSession) {
                                // Failed to configure capture session
                                Log.e(TAG, "Failed to configure capture session")
                            }
                        },
                            null
                            )
                        } else {
                        // Use the existing capture session if it's already created
                        cameraCaptureSession?.capture(
                            captureRequestBuilder!!.build(),
                            captureCallback,
                            null
                        )
                    }
                } else {
                    Log.e(TAG, "Failed to create ImageReader or CameraDevice is null")
                }
            } catch (e: CameraAccessException) {
                e.printStackTrace()
            }
        } else {
            
        }
    }

按钮触发代码

btnCapture.setOnClickListener { onCaptureButtonClick(captureType, imageWidth, (aspectRatio*imageWidth),controlMode, cameraSettings) }

获取相机支持JPEG尺寸的代码

try {
    val cameraCharacteristics = cameraManager.getCameraCharacteristics(cameraId)

    val maxImageSize = cameraCharacteristics.get(
        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
    )?.getOutputSizes(ImageFormat.JPEG)?.maxByOrNull { it.height * it.width }

    val minImageSize = cameraCharacteristics.get(
        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
    )?.getOutputSizes(ImageFormat.JPEG)?.minByOrNull { it.height * it.width }

    if (maxImageSize != null) {
        val maxWidth = maxImageSize.width
        val maxHeight = maxImageSize.height
        Log.d(TAG, "Max Image Size: $maxWidth x $maxHeight")
    }

    if (minImageSize != null) {
        val minWidth = minImageSize.width
        val minHeight = minImageSize.height
        Log.d(TAG, "Min Image Size: $minWidth x $minHeight")
    }

} catch (e: CameraAccessException) {
    e.printStackTrace()
}

问题原因及修复方案

1. 核心问题:CaptureRequest未添加ImageReader的Surface目标

当前代码仅给captureRequestBuilder添加了previewSurface,未将imageReader.surface加入捕获目标,导致相机不会将图像数据输出到ImageReader,因此acquireLatestImage()返回null。

修复:创建CaptureRequest.Builder后添加目标:

captureRequestBuilder?.addTarget(imageReader.surface)

2. ImageReader生命周期与Session兼容性问题

每次点击按钮创建新的ImageReader时,若使用已有CameraCaptureSession,新的ImageReader Surface未包含在Session的Surface列表中,Session无法输出图像到该Surface。

修复:

  • 提前初始化ImageReader并将其Surface加入CameraCaptureSession的Surface列表,而非每次点击都创建新实例;
  • 若必须动态创建ImageReader,需关闭原有Session并重新创建包含新Surface的Session。

3. 图像获取时机错误

onCaptureCompleted仅表示捕获请求完成,此时ImageReader可能尚未接收完图像数据,直接调用acquireLatestImage()可能拿不到数据。应使用ImageReader的OnImageAvailableListener在图像可用时再获取:

修复:

imageReader.setOnImageAvailableListener({ reader ->
    val image = reader.acquireLatestImage()
    // 处理图像逻辑
    image?.close()
}, Handler(Looper.getMainLooper()))

4. 尺寸匹配验证

确保传入ImageReader的宽高是相机支持的JPEG尺寸,且与CaptureRequest的输出尺寸一致。注意Camera2的尺寸为传感器方向的宽高,可能与显示方向不同,需根据设备方向做转换。

修改后的核心代码示例

private fun onCaptureButtonClick(
    captureType: String,
    imageWidth: Int,
    imageHeight: Int,
    controlMode: Int,
    cameraSettings: MutableMap<String, Any?>
) {
    if (captureType == "Image") {
        try {
            val captureRequestBuilder = cameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE)
            // 增加缓存数量避免丢帧
            val imageReader = ImageReader.newInstance(imageWidth, imageHeight, ImageFormat.JPEG, 2)
            
            // 设置图像可用监听器处理图像数据
            imageReader.setOnImageAvailableListener({ reader ->
                val image = reader.acquireLatestImage()
                image?.let {
                    try {
                        val buffer = it.planes[0].buffer
                        val imageData = ByteArray(buffer.remaining())
                        buffer.get(imageData)
                        val outputFile = File(
                            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                            "my_image.jpg"
                        )
                        FileOutputStream(outputFile).use { output ->
                            output.write(imageData)
                        }
                        Log.d(TAG, "Image saved successfully: ${outputFile.absolutePath}")
                        MediaScannerConnection.scanFile(
                            applicationContext,
                            arrayOf(outputFile.absolutePath),
                            null,
                            null
                        )
                    } catch (e: IOException) {
                        e.printStackTrace()
                    } finally {
                        it.close()
                    }
                } ?: run {
                    Log.e(TAG, "Failed to acquire captured image")
                    Toast.makeText(applicationContext, "Failed to acquire captured image", Toast.LENGTH_SHORT).show()
                }
                updateCameraPreview()
            }, Handler(Looper.getMainLooper()))

            if (imageReader != null && cameraDevice != null) {
                // 同时添加预览和ImageReader的Surface到捕获目标
                captureRequestBuilder?.addTarget(previewSurface!!)
                captureRequestBuilder?.addTarget(imageReader.surface)
                captureRequestBuilder?.set(CaptureRequest.CONTROL_MODE, controlMode)

                val captureCallback = object : CameraCaptureSession.CaptureCallback() {
                    override fun onCaptureCompleted(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        result: TotalCaptureResult
                    ) {
                        Log.d(TAG, "Capture completed")
                        super.onCaptureCompleted(session, request, result)
                    }

                    override fun onCaptureFailed(
                        session: CameraCaptureSession,
                        request: CaptureRequest,
                        failure: CaptureFailure
                    ) {
                        super.onCaptureFailed(session, request, failure)
                        Log.d(TAG, "Capture failed")
                    }
                }

                val surfaces = mutableListOf(previewSurface, imageReader.surface)
                if (cameraCaptureSession == null) {
                    cameraDevice?.createCaptureSession(surfaces, object : CameraCaptureSession.StateCallback() {
                        override fun onConfigured(session: CameraCaptureSession) {
                            cameraCaptureSession = session
                            captureRequestBuilder?.build()?.let {
                                cameraCaptureSession?.capture(it, captureCallback, null)
                            }
                        }

                        override fun onConfigureFailed(session: CameraCaptureSession) {
                            Log.e(TAG, "Failed to configure capture session")
                        }
                    }, null)
                } else {
                    // 检查已有Session是否包含当前ImageReader的Surface,不包含则重建Session
                    val sessionSurfaces = cameraCaptureSession?.surfaces ?: emptyList()
                    if (!sessionSurfaces.contains(imageReader.surface)) {
                        cameraCaptureSession?.close()
                        cameraDevice?.createCaptureSession(surfaces, object : CameraCaptureSession.StateCallback() {
                            override fun onConfigured(session: CameraCaptureSession) {
                                cameraCaptureSession = session
                                captureRequestBuilder?.build()?.let {
                                    cameraCaptureSession?.capture(it, captureCallback, null)
                                }
                            }

                            override fun onConfigureFailed(session: CameraCaptureSession) {
                                Log.e(TAG, "Failed to configure capture session")
                            }
                        }, null)
                    } else {
                        cameraCaptureSession?.capture(captureRequestBuilder!!.build(), captureCallback, null)
                    }
                }
            } else {
                Log.e(TAG, "Failed to create ImageReader or CameraDevice is null")
            }
        } catch (e: CameraAccessException) {
            e.printStackTrace()
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 04:34:55