使用cameraX 仿一甜相机

前言

1、导入相关库2、绑定LifeCycle生命周期并开启预览流
3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉
4、拍照
5、录像
6、聚焦
7、切换摄像头
8、缩放
9、闪光灯
10、照明、补光
11、Extensions 扩展程序使用

前言

CameraX 是jetpack 组件库中的一个非常重要的API,不同于Camera和Camera2,CameraX 在api解耦性上做出了非常大的调整。其中:

  • 1、新增了生命周期绑定管理,解决了老版本Camera的内存泄漏问题
  • 2、分辨率自动找寻最接近匹配问题,解决了开发者手动去查询支持分辨率列表(从中去找寻最匹配的分辨率)
  • 3、增加ImageAnalysis图像分析,可以在图像输出前对像素进行YUV 转换并且预处理等操作
  • 4、增加了 Extensions 扩展程序包括(AUTO、HDR、焦外成像(BOKEH)、夜景(NIGHT)、脸部照片修复(FACE_RETOUCH))等模式,当然,目前国内手机厂商都还没兼容当前的扩展模式,大部分还只有三星手机支持。期待
    手机厂商抓紧适配,减少不必要的算法融合。

以上的一些新增内容,足够我们去替换老板本的Camera或者Camera2 Api。下面就是CameraX 的基础用法和Extensions 用法。


CameraX仿一甜相机


android yuv 打开相机 android camerax_android yuv 打开相机

android yuv 打开相机 android camerax_kotlin_02

  • 1、导入相关库
def camerax_version = "1.2.0-alpha04"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
    implementation "androidx.camera:camera-lifecycle:${camerax_version}"
    implementation "androidx.camera:camera-video:${camerax_version}"
    implementation "androidx.camera:camera-view:${camerax_version}"
    implementation "androidx.camera:camera-extensions:${camerax_version}"
  • 2、绑定LifeCycle生命周期并开启预览流
fun openCamera(){
     
     val cameraProviderFuture = ProcessCameraProvider.getInstance(mLifecycleOwner!!)

        cameraProviderFuture.addListener({
            // 绑定生命周期
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()
            // Preview 预览流
            val preview = Preview.Builder()
                .build()
                .also {
                    it.setSurfaceProvider(preview?.surfaceProvider)
                }

            //选择后置摄像头
            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                //解绑所有摄像头使用
                cameraProvider.unbindAll()

                // 绑定输出
                camera = cameraProvider.bindToLifecycle(
                    mLifecycleOwner!!, cameraSelector,  preview
                )

            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }

        }, ContextCompat.getMainExecutor(mLifecycleOwner!!))
}
  • 3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉
//图像捕捉
            imageCapture = ImageCapture.Builder()
                .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                .build()

        
         // 绑定输出
                camera = cameraProvider.bindToLifecycle(
                    mLifecycleOwner!!, cameraSelector, imageCapture, preview
                )
  • 4、拍照
override fun takePhoto() {

        val imageCapture = imageCapture ?: return

        val name = SimpleDateFormat("yyyy-MM-dd", Locale.US)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/CameraX-Image")
            }
        }
        val outputOptions = ImageCapture.OutputFileOptions
            .Builder(
                mLifecycleOwner?.contentResolver!!,
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                contentValues
            )
            .build()
        imageCapture.takePicture(
            outputOptions,
            ContextCompat.getMainExecutor(mLifecycleOwner!!),
            object : ImageCapture.OnImageSavedCallback {
                override fun onError(exc: ImageCaptureException) {
                    Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
                }

                override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                    val msg = "Photo capture succeeded: ${output.savedUri}"
                    Toast.makeText(mLifecycleOwner, msg, Toast.LENGTH_SHORT).show()
                    Log.d(TAG, msg)
                }
            }
        )
    }
  • 5、录像
@SuppressLint("CheckResult")
    override fun takeVideo() {
        val videoCapture = this.videoCapture ?: return


        //如果正在录制,则停止
        if (recording != null) {
            recording?.stop()
            recording = null
            return
        }

        val name = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA)
            .format(System.currentTimeMillis())
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, name)
            put(MediaStore.MediaColumns.MIME_TYPE, "video/mp4")
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
                put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/CameraX-Video")
            }
        }

        val mediaStoreOutputOptions = mLifecycleOwner?.contentResolver?.let {
            MediaStoreOutputOptions
                .Builder(it, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
                .setContentValues(contentValues)
                .build()
        }
        recording = videoCapture.output
            .prepareRecording(mLifecycleOwner!!, mediaStoreOutputOptions!!)
            .apply {
                if (ActivityCompat.checkSelfPermission(
                        mLifecycleOwner!!,
                        Manifest.permission.RECORD_AUDIO
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    //启动音频
                    withAudioEnabled()
                }

            }
            .start(ContextCompat.getMainExecutor(mLifecycleOwner!!)) { recordEvent ->
                when (recordEvent) {
                    is VideoRecordEvent.Start -> {
                        //录制开始
                        Toast.makeText(mLifecycleOwner, "开始录制", Toast.LENGTH_SHORT)
                            .show()
                    }
                    is VideoRecordEvent.Finalize -> {
                        //录制结束
                        if (!recordEvent.hasError()) {
                            val msg = "Video capture succeeded: " +
                                    "${recordEvent.outputResults.outputUri}"
                            Toast.makeText(mLifecycleOwner, msg, Toast.LENGTH_SHORT)
                                .show()
                            Log.d(TAG, msg)
                        } else {
                            recording?.close()
                            recording = null
                            Log.e(
                                TAG, "Video capture ends with error: " +
                                        "${recordEvent.error}"
                            )
                        }

                    }
                }
            }
    }
  • 6、聚焦

聚焦分为三种模式

//使用PreviewView。用于自动聚焦,在previewView中创建一个坐标点
previewView.setOnTouchListener((view, motionEvent) ->  {
val meteringPoint = previewView.meteringPointFactory
    .createPoint(motionEvent.x, motionEvent.y)
…
}

//使用DisplayOrientedMeteringPointFactory如果SurfaceView / TextureView用于
//预览。请注意,如果预览在视图中缩放或裁剪,
//正确转换坐标是应用程序的责任
//这样工厂的宽度和高度代表完整的预览FOV。
//和(x,y)传入创建MeteringPoint可能需要调整

val meteringPointFactory = DisplayOrientedMeteringPointFactory(
     surfaceView.display,
     camera.cameraInfo,
     surfaceView.width,
     surfaceView.height
)

//使用SurfaceOrientedMeteringPointFactory如果点指定在
//图形分析ImageProxy。
val meteringPointFactory = SurfaceOrientedMeteringPointFactory(
     imageWidth,
     imageHeight,
     imageAnalysis)

基于以上的point 创建方式,可以封装为自动聚焦和手动聚焦

/**
     * 聚焦
     * @param auto 聚焦模式
     */
    @SuppressLint("RestrictedApi")
    override fun focus(x: Float, y: Float, auto: Boolean) {
        cameraControl?.cancelFocusAndMetering()
        val createPoint: MeteringPoint = if (auto) {

            val meteringPointFactory = DisplayOrientedMeteringPointFactory(
                preview?.display!!,
                camera?.cameraInfo!!,
                preview?.width?.toFloat()!!,
                preview?.height?.toFloat()!!
            )
            meteringPointFactory.createPoint(x, y)
        } else {
            val meteringPointFactory = preview?.meteringPointFactory
            meteringPointFactory?.createPoint(x, y)!!
        }


        val build = FocusMeteringAction.Builder(createPoint, FLAG_AF)
            .setAutoCancelDuration(3, TimeUnit.SECONDS)
            .build()

        val future = cameraControl?.startFocusAndMetering(build)


        future?.addListener({
            try {

                if (future.get().isFocusSuccessful) {
                    //聚焦成功
                    Log.e(TAG, "聚焦成功")
                } else {
                    //聚焦失败
                    Log.e(TAG, "聚焦失败")
                }
            } catch (e: Exception) {
                Log.e(TAG, "异常" + e.message)
            }

        }, ContextCompat.getMainExecutor(mLifecycleOwner!!))


    }
  • 7、切换摄像头
/**
     * 切换镜头
     */
       override fun switchCamera() {


        mFacingFront = !mFacingFront

        // 解除绑定
        cameraProvider?.unbindAll()


        // 前后置摄像头选择器
        val cameraSelector = CameraSelector.Builder()
            .requireLensFacing(if (mFacingFront) CameraSelector.LENS_FACING_FRONT else CameraSelector.LENS_FACING_BACK)
            .build()
        imageCapture = ImageCapture.Builder()
            .setTargetAspectRatio(AspectRatio.RATIO_4_3)
            .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
            .build()


        // 绑定输出
        camera = cameraProvider?.bindToLifecycle(
            mLifecycleOwner!!,
            cameraSelector,
            imageCapture,
            videoCapture,
            mPreView

        )


    }
  • 8、缩放
/**
     * 缩放
     */
    override fun zoom(out: Boolean) {
        val zoomState = camera?.cameraInfo?.zoomState
        val zoomRatio: Float? = zoomState?.value?.zoomRatio        //当前值
        val maxZoomRatio: Float? = zoomState?.value?.maxZoomRatio//缩放最大值
        val minZoomRatio: Float? = zoomState?.value?.minZoomRatio //缩放最小值

        if (out) {
            //放大
            if (zoomRatio!! < maxZoomRatio!!) {
                cameraControl?.setZoomRatio((zoomRatio + zoomCoefficient))
            }
        } else {
            //缩小
            if (zoomRatio!! > minZoomRatio!!) {
                cameraControl?.setZoomRatio((zoomRatio - zoomCoefficient))
            }
        }
    }
  • 9、闪光灯
//闪光灯模式
        val flashMode =
            if (cameraParams?.mSplashOn == true && cameraParams?.mFacingFront == false) {
                ImageCapture.FLASH_MODE_ON
            } else {
                ImageCapture.FLASH_MODE_OFF
            }

        imageCapture?.flashMode = flashMode
  • 10、照明、补光
cameraControl?.enableTorch(cameraParams?.torchSwitch!!)
  • 11、Extensions 扩展程序使用

以下是Extensions 支持的模式。

public final class ExtensionMode {
    /**正常无效果 */
    public static final int NONE = 0;
    /**
   焦外成像
     */
    public static final int BOKEH = 1;
    /**
     HDR
     */
    public static final int HDR = 2;
    /** 在光线较暗的情况下,尤其是在夜间,获得最好的静止图像 */
    public static final int NIGHT = 3;
    /**在拍摄静态图像时,修饰脸部皮肤色调,几何形状等 */
    public static final int FACE_RETOUCH = 4;
    /**
      自动
     */
    public static final int AUTO = 5;

使用方式

//协程挂起是必须的
      mLifecycleOwner?.lifecycleScope?.launch {
      
                 //获取manager
                val extensionsManager = ExtensionsManager.getInstanceAsync(mLifecycleOwner!!, cameraProvider!!).await()


                //判断设备是否支持扩展程序
                if (extensionsManager.isExtensionAvailable(
                        cameraSelector,
                        cameraParams?.extensionMode!!
                    )
                ) {
                 //如果支持,则传入对应的mode 模式,如BOKEH 
                    Log.d(TAG, "支持" + cameraParams?.extensionMode)
                    val extensionId = extensionsManager.getExtensionEnabledCameraSelector(
                        cameraSelector,
                        cameraParams?.extensionMode!!
                    )
                    startPreView = true
                    bindCameraId(extensionId)

                } else {
                    startPreView = false
                    Log.d(TAG, "不支持" + cameraParams?.extensionMode)
                }

            }

    /**
     * 绑定相机id
     */
    private fun bindCameraId(cameraSelector: CameraSelector) {
        try {

            cameraProvider?.unbindAll()
            // 绑定输出
            camera = cameraProvider?.bindToLifecycle(
                mLifecycleOwner!!,
                cameraSelector,
                mPreView,
                imageCapture,
                videoCapture

            )
            if (!isInt) {
                isInt = true
                callBack?.ratioCallBack(cameraParams?.mRatioType)
            }
            cameraControl = camera?.cameraControl

            focus(preview?.width?.div(2f)!!, preview?.height?.div(2f)!!, true)


        } catch (exc: Exception) {
            Log.e(TAG, "Use case binding failed", exc)
        }


    }

由于本人的设备不支持扩展程序,所以就没有展示效果了,希望国能厂商能升级ROM 支持一下吧。
后续有支持的设备后,会更新效果图上来。