使用CameraX时,ImageAnalysis默认编码格式为ImageFormat.YUV_420_888,此时同时添加ImageCapture,若ImageCapture编码格式也设置为ImageFormat.YUV_420_888格式,会导致崩溃,报如下错误:
java.lang.IllegalArgumentException: No supported surface combination is found for camera device - Id : 0. May be attempting to bind too many use cases.
ImageCapture默认编码格式为ImageFormat.JPEG,保持默认格式即可。
原因未知,猜测可能编码冲突
// Set up the capture use case to allow users to take photos
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setBufferFormat(ImageFormat.YUV_420_888)//有ImageAnalysis 时不能使用ImageFormat.YUV_420_888格式,可能抢占同编码一通道 会崩溃
.setTargetResolution(Size(1280, 720)).build()
// Attach use cases to the camera with the same lifecycle owner
var imageAnalysis: ImageAnalysis? = null
frameListener?.let { frameListener ->
imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build().apply { setAnalyzer(mCameraExecutor, frameListener) }
}
附完整示例:
package com.gcsdk.cameraxutils
import androidx.camera.lifecycle.ProcessCameraProvider
import kotlin.jvm.JvmOverloads
import androidx.camera.view.PreviewView
import com.gcsdk.cameraxutils.CameraXUtils
import androidx.core.content.ContextCompat
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.ImageFormat
import android.util.Log
import android.util.Size
import androidx.annotation.Nullable
import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.*
import androidx.camera.core.ImageCapture.OnImageCapturedCallback
import androidx.lifecycle.LifecycleOwner
import org.jetbrains.annotations.NotNull
import java.io.File
import java.lang.Exception
import java.lang.RuntimeException
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
/**
* 使用CameraX调用摄像头 拍照预览功能封装
* 默认拍照、预览尺寸1280x720 默认拍照、预览图像数据帧[ImageProxy]格式为[ImageFormat.YUV_420_888]
*/
object CameraXUtils {
private val TAG = CameraXUtils::class.java.simpleName
private var mCameraProvider: ProcessCameraProvider? = null
private var mImageCapture: ImageCapture? = null
private var mCameraSelector: CameraSelector? = null
private val mUseCases: MutableList<UseCase> = mutableListOf()
private var mContext: Context? = null
private var mLifecycleOwner: LifecycleOwner? = null
private val mCameraExecutor = Executors.newSingleThreadExecutor()
/**
* @param cameraIndex 摄像头编号,默认[CameraSelector.LENS_FACING_FRONT]
* @param previewView 预览View [PreviewView] 可空
* @param frameListener 预览帧回调[ImageAnalysis.Analyzer] 可空
* 默认拍照[takePicture]、预览[frameListener]尺寸1280x720
* 默认拍照[takePicture]数据[ImageProxy]格式为[ImageFormat.JPEG]、预览[frameListener]图像数据帧[ImageProxy]格式为[ImageFormat.YUV_420_888]
* Tips: [ImageAnalysis]和[ImageCapture]同时存在时,不能都使用[ImageFormat.YUV_420_888]格式,可能抢占同编码一通道 会崩溃
* @throws:
* [IllegalStateException] – If the use case has already been bound to another lifecycle or method is not called on main thread.
* [IllegalArgumentException] – If the provided camera selector is unable to resolve a camera to be used for the given use cases.
*/
@SuppressLint("RestrictedApi")
fun init(
@NotNull context: Context,
@NotNull lifecycleOwner: LifecycleOwner,
@Nullable previewView: PreviewView? = null,
@NotNull cameraIndex: Int = CameraSelector.LENS_FACING_FRONT,
@Nullable frameListener: ImageAnalysis.Analyzer? = null
) {
// Set up the view finder use case to display camera preview
var preview: Preview? = null
previewView?.let { previewView ->
preview = Preview.Builder()
.build().apply { setSurfaceProvider(previewView.surfaceProvider) }
}
// Set up the capture use case to allow users to take photos
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.setBufferFormat(ImageFormat.JPEG)//有ImageAnalysis 时不能使用ImageFormat.YUV_420_888格式,可能抢占同编码一通道 会崩溃
.setTargetResolution(Size(1280, 720)).build()
// Attach use cases to the camera with the same lifecycle owner
var imageAnalysis: ImageAnalysis? = null
frameListener?.let { frameListener ->
imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build().apply { setAnalyzer(mCameraExecutor, frameListener) }
}
val cameraSelector = CameraSelector.Builder().requireLensFacing(cameraIndex).build()
init(context, lifecycleOwner, preview, imageCapture, cameraSelector, imageAnalysis)
}
/**
* @param preview 预览显示配置[Preview.Builder]
* @param imageCapture 拍照配置[ImageCapture.Builder]
* @param imageAnalysis 预览数据配置[ImageAnalysis.Builder]
* @param cameraSelector 摄像头选择配置[CameraSelector.Builder] 默认[CameraSelector.DEFAULT_FRONT_CAMERA]
* @throws:
* [IllegalStateException] – If the use case has already been bound to another lifecycle or method is not called on main thread.
* [IllegalArgumentException] – If the provided camera selector is unable to resolve a camera to be used for the given use cases.
*/
fun init(
@NotNull context: Context,
@NotNull lifecycleOwner: LifecycleOwner,
@Nullable preview: Preview? = null,
@Nullable imageCapture: ImageCapture? = null,
@NotNull cameraSelector: CameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA,
@Nullable imageAnalysis: ImageAnalysis? = null
) = init(
context,
lifecycleOwner,
cameraSelector,
imageCapture,
listOfNotNull(preview, imageAnalysis)
)
/**
* @param imageCapture 拍照配置[ImageCapture.Builder]
* @param cameraSelector 摄像头选择配置[CameraSelector.Builder] 默认[CameraSelector.DEFAULT_FRONT_CAMERA]
* @param useCases 预览显示[Preview] 预览数据[ImageAnalysis] 等
* @throws:
* [IllegalStateException] – If the use case has already been bound to another lifecycle or method is not called on main thread.
* [IllegalArgumentException] – If the provided camera selector is unable to resolve a camera to be used for the given use cases.
*/
fun init(
@NotNull context: Context,
@NotNull lifecycleOwner: LifecycleOwner,
@NotNull cameraSelector: CameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA,
@Nullable imageCapture: ImageCapture? = null,
@Nullable useCases: List<UseCase>? = null
) {
mContext = context
mLifecycleOwner = lifecycleOwner
mImageCapture = imageCapture
mCameraSelector = cameraSelector
mUseCases.clear()
if (imageCapture != null) mUseCases.add(imageCapture)
if (useCases != null) mUseCases.addAll(useCases)
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
// Camera provider is now guaranteed to be available
mCameraProvider = cameraProviderFuture.get()
checkCameraStates() //检测摄像头状态 目标摄像头不存在自动切换
bindCameraUseCases()
}, ContextCompat.getMainExecutor(context))
}
//指定摄像头不存在 切换到存在的摄像头
private fun checkCameraStates() {
if (!hasCamera(mCameraSelector!!)) switchCameraInternal()
}
// Re-bind use cases
private fun bindCameraUseCases() {
// Must unbind the use-cases before rebinding them
checkNotNull(mCameraProvider)
checkNotNull(mLifecycleOwner)
checkNotNull(mCameraSelector)
mCameraProvider!!.unbindAll()
mCameraProvider!!.bindToLifecycle(
mLifecycleOwner!!,
mCameraSelector!!,
*mUseCases.toTypedArray()
)
}
/**
* 检测是否有指定摄像头
*/
private fun hasCamera(selector: CameraSelector): Boolean {
return try {
checkNotNull(mCameraProvider)
mCameraProvider!!.hasCamera(selector)
} catch (e: CameraInfoUnavailableException) {
e.printStackTrace()
false
}
}
/**
* 切换摄像头
*/
fun switchCamera() {
switchCameraInternal()
// Re-bind use cases to update selected camera
bindCameraUseCases()
}
/**
* 切换摄像头
*/
@SuppressLint("RestrictedApi")
private fun switchCameraInternal() {
checkNotNull(mCameraSelector)
var lensFacing = mCameraSelector!!.lensFacing!!
mCameraSelector = CameraSelector.Builder().requireLensFacing(++lensFacing).build()
if (!hasCamera(mCameraSelector!!)) {
if (hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA))
mCameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
else throw IllegalStateException("未检测到摄像头")
}
}
/**
* 拍照 回调接口返回ImageProxy
* @param onImageCapturedCallback 照片数据回调
*/
fun takePicture(onImageCapturedCallback: OnImageCapturedCallback) {
checkNotNull(mImageCapture)
mImageCapture!!.takePicture(mCameraExecutor, onImageCapturedCallback)
}
/**
* 指定图片路径拍照 回调接口返回储存结果
* @param filePath 照片保存路径
* @param onImageSavedCallback 保存结果回调
*/
fun takePicture(filePath: String, onImageSavedCallback: ImageCapture.OnImageSavedCallback) {
checkNotNull(mImageCapture)
val file = File(filePath)
if (!file.parentFile.exists()) file.mkdirs()
val outputFileOptions = ImageCapture.OutputFileOptions.Builder(file).build()
mImageCapture!!.takePicture(outputFileOptions, mCameraExecutor, onImageSavedCallback)
}
}