• Android CameraX 仿一甜相机(录像、拍照、可调节尺寸、聚焦、照明、网格线),最全的CameraX教程


    使用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仿一甜相机

    请添加图片描述

    请添加图片描述

    • 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}"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 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!!))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 3、绑定ImageCapture图形捕捉以及VideoCapture视频帧捕捉
                //图像捕捉
                imageCapture = ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                    .build()
    
            
             // 绑定输出
                    camera = cameraProvider.bindToLifecycle(
                        mLifecycleOwner!!, cameraSelector, imageCapture, preview
                    )
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 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)
                    }
                }
            )
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 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}"
                                )
                            }
    
                        }
                    }
                }
        }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    基于以上的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!!))
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 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
    
            )
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 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))
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 9、闪光灯
           //闪光灯模式
            val flashMode =
                if (cameraParams?.mSplashOn == true && cameraParams?.mFacingFront == false) {
                    ImageCapture.FLASH_MODE_ON
                } else {
                    ImageCapture.FLASH_MODE_OFF
                }
    
            imageCapture?.flashMode = flashMode
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10、照明、补光
           cameraControl?.enableTorch(cameraParams?.torchSwitch!!)
    
    • 1
    • 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;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    使用方式

    
          //协程挂起是必须的
          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)
            }
    
    
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    由于本人的设备不支持扩展程序,所以就没有展示效果了,希望国能厂商能升级ROM 支持一下吧。
    后续有支持的设备后,会更新效果图上来。
    代码已上传:
    https://github.com/ljlstudio/KtMvvm/tree/master/demo/src/main/java/com/kt/ktmvvm/jetpack/camerax

  • 相关阅读:
    Win10开机桌面无限刷新的解决方法
    怎么把PDF转换成图片?教大家两种方式转换
    阿里云CDN架构接入WAF应用防火墙案例实践
    vue3封装接口(自测可用)
    JavaScript学习笔记04
    什么是RPC?RPC 和 HTTP 对比?RPC有什么缺点?市面上常用的RPC框架?
    MacOS上的Pip和Python升级指南
    企业该如何选择数字化转型工具?_光点科技
    320力扣周赛总结
    Fasterkv Epoch 保护框架
  • 原文地址:https://blog.csdn.net/Android_LeeJiaLun/article/details/126116616