Camera2 API问题:使用ImageReader从SurfaceView捕获图像时返回空值
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

