Jetpack CameraX拍照预览同时存在时,绑定异常问题记录

1 篇文章 0 订阅
1 篇文章 0 订阅

使用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)
    }

}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值