美文网首页
Camerax 入门教程

Camerax 入门教程

作者: 程序员Android1 | 来源:发表于2021-08-17 13:33 被阅读0次

    和你一起终身学习,这里是程序员 Android

    经典好文推荐,通过阅读本文,您将收获以下知识点:

    一、Camera的预备知识
    二、CameraX是什么,能解决什么问题
    三、 CameraX如何使用
    四、CameraX的一些思考

    基于官网demo,增加了对焦、手势缩放、手电、闪光灯等操作

    一、Camera的预备知识

    熟悉的大佬可以跳过

    Surface、SurfaceView、SurfaceHolder这三个是啥
    Surface是什么?一句话来说Surface 是一块用于填充图像数据的内存空间。

    可以再深入一点,了解下它包含的东西:咱后面可以重点关注下Buffer转纹理的细节实际上用openGL修改流我们下一章分析


    SurfaceView是什么?一句话来描述的话那就是:它一个可以显示surface的view!在App端它仍在View hierachy中,但在WMS中(可以理解为Server端),它与宿主窗口是分离的。这样的好处是对这个Surface的渲染可以放到单独线程去做,不会影响主线程对事件的响应。缺点也很明显,它的显示也不受View的属性控制,所以不能进行平移,缩放等变换(7.0之前)。

    SurfaceHolder是什么?它是一个接口,给持有surface的对象使用,可以控制surface的大小和格式,编辑surface中的像素,以及监听surface的变化,这个接口通过SurfaceView获得。下面这张图可以说明一切

    二、CameraX是什么,能解决什么问题

    Jetpack的一个支持库,最低版本要求Android5.0
    默认的相机功能还是Camera2的能力,当然API都变了,同时提供CameraX Extensions拓展库可以添加各种特效,例如人像、HDR、夜间和美颜模式(从上图也可以看出,这是依赖OEM的)
    绑定生命周期,所以Camera本身无需在生命周期中调用什么onPause onResume之类的样板代码,且忘记后会造成各种问题
    抹平设备兼容性问题,无需在代码库中添加设备专属代码

    三、 CameraX如何使用

    第一步

    引入依赖

        // CameraX core library
        def camerax_version = '1.0.0-rc03'
    //    def camerax_version = '1.1.0-alpha03'目前最新版,但是为了稳定我们还是选择rc版
        implementation "androidx.camera:camera-core:$camerax_version"
        // CameraX Camera2 extensions[可选]拓展库可实现人像、HDR、夜间和美颜、滤镜但依赖于OEM
        implementation "androidx.camera:camera-camera2:$camerax_version"
        // CameraX Lifecycle library[可选]避免手动在生命周期释放和销毁数据
        implementation "androidx.camera:camera-lifecycle:$camerax_version"
        // CameraX View class[可选]最佳实践,最好用里面的PreviewView,它会自行判断用SurfaceView还是TextureView来实现
        implementation 'androidx.camera:camera-view:1.0.0-alpha23'
    
    

    第二步

    添加布局控件

    <androidx.camera.view.PreviewView
        android:id="@+id/view_finder"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    
    

    第三步

    初始化cameraProviderFuture并得到cameraProvider

        /** Initialize CameraX, and prepare to bind the camera use cases  */
            //UseCase实际上是一个抽象类,相机中最核心的几个类的父类ImageAnalysis/ImageCapture/Preview/VideoCapture
        private fun setUpCamera() {
            val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
            cameraProviderFuture.addListener(Runnable {
                // CameraProvider
                cameraProvider = cameraProviderFuture.get()
                // 选择默认摄像头给后续使用
                lensFacing = when {
                    hasBackCamera() -> CameraSelector.LENS_FACING_BACK
                    hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
                    else -> throw IllegalStateException("Back and front camera are unavailable")
                }
                // Enable or disable switching between cameras
                updateCameraSwitchButton()
                // Build and bind the camera use cases
                bindCameraUseCases()
            }, ContextCompat.getMainExecutor(requireContext()))
        }
    
    

    得到cameraProvider后初始化Preview、ImageCapture、ImageAnalysis可以看到他们3个是单独配置的,所以完全解耦。最后可以根据需要设置0个或多个到camera中

    
        /** Declare and bind preview, capture and analysis use cases */
        private fun bindCameraUseCases() {
            // CameraSelector
            val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
            // Preview
            preview = Preview.Builder()
                    .setTargetAspectRatio(screenAspectRatio)
                    .setTargetRotation(rotation)
                    .build()
            // ImageCapture
            imageCapture = ImageCapture.Builder()
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
                    .setTargetAspectRatio(screenAspectRatio)
                    .setTargetRotation(rotation)
                    .build()
            // ImageAnalysis
            imageAnalyzer = ImageAnalysis.Builder()
                    .setTargetAspectRatio(screenAspectRatio)
                    .setImageQueueDepth(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .setTargetRotation(rotation)
                    .build()
                    .also {
                        it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
                                //这里拿到字节数组可以为 所 欲 为 !
                                val buffer = image.planes[0].buffer
                                val data = buffer.toByteArray()                                     
                        })
                    }
            // Must unbind the use-cases before rebinding them
            cameraProvider.unbindAll()
            try {
                //这里拿到camera对象可以取出两个重要的对象来用
                camera = cameraProvider.bindToLifecycle(
                        this, cameraSelector, preview, imageCapture, imageAnalyzer)
                //用来聚焦、手势、闪光灯、手电等操作
                mCameraInfo = camera?.cameraInfo
                mCameraControl = camera?.cameraControl
                initCameraListener()
                preview?.setSurfaceProvider(viewFinder.surfaceProvider)
            } catch (exc: Exception) {
                Log.e(TAG, "Use case binding failed", exc)
            }
        }
    
    

    然后是拍照

    // Get a stable reference of the modifiable image capture use case
    //takePicture有重载方法,源码的注释写的比较清楚了:
            /*
             * We need to chain the following callbacks to save the image to disk:
             *
             * +-----------------------+
             * |                       |
             * |ImageCapture.          |
             * |OnImageCapturedCallback|
             * |                       |
             * +-----------+-----------+
             *             |
             *             |
             * +-----------v-----------+      +----------------------+
             * |                       |      |                      |
             * | ImageSaver.           |      | ImageCapture.        |
             * | OnImageSavedCallback  +------> OnImageSavedCallback |
             * |                       |      |                      |
             * +-----------------------+      +----------------------+
             */
    imageCapture?.let { imageCapture ->
        imageCapture.takePicture(
                outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
            override fun onError(exc: ImageCaptureException) {
                Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
            }
            override fun onImageSaved(output: ImageCapture.OutputFileResults) {
                val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
                Log.d(TAG, "Photo capture succeeded: $savedUri")
            }
        })
    }
    
    

    第四步

    手电筒、闪光灯、前后摄像头、聚焦、手势

    手电筒:

    //通过拿到的Camera接口的两个方法拿到后进行设置即可
    cameraControl = camera.cameraControl
    cameraInfo = camera.cameraInfo
    ....
    private fun toggleTorch() {
        if (cameraInfo?.torchState?.value == TorchState.ON) {
            cameraControl?.enableTorch(false)
        } else {
            cameraControl?.enableTorch(true)
        }
    }
    
    

    闪光灯模式

    mImageCapture.flashMode = ImageCapture.FLASH_MODE_ON
    
    

    切换摄像头

    lensFacing = when {
        hasBackCamera() -> CameraSelector.LENS_FACING_BACK
        hasFrontCamera() -> CameraSelector.LENS_FACING_FRONT
        else -> throw IllegalStateException("Back and front camera are unavailable")
    }
    // CameraSelector
    val cameraSelector = CameraSelector.Builder().requireLensFacing(lensFacing).build()
    
    

    聚焦和手势,由于新版的PreviewView已经被final修饰,无法被继承然后重写方法了,所以只能通过setOnTouchListener进行手势监听

    val zoomState = mCameraInfo?.zoomState
    val cameraXPreviewViewTouchListener = CameraXPreviewViewTouchListener(requireContext())
    cameraXPreviewViewTouchListener.setCustomTouchListener(object : CameraXPreviewViewTouchListener.CustomTouchListener {
        override fun zoom(delta: Float) {
            zoomState?.value?.let {
                val currentZoomRatio = it.zoomRatio
                mCameraControl?.setZoomRatio(currentZoomRatio * delta)
            }
        }
        override fun click(x: Float, y: Float) {
            val factory = viewFinder.meteringPointFactory
            val point = factory.createPoint(x, y)
            val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
                .setAutoCancelDuration(3, TimeUnit.SECONDS)
                .build()
            focus_view.startFocus(Point(x.toInt(), y.toInt()))
            val future = mCameraControl?.startFocusAndMetering(action)
            future?.let {
                it.addListener({
                    try {
                        val result = it.get() as FocusMeteringResult
                        if (result.isFocusSuccessful) {
                            focus_view.onFocusSuccess()
                        } else {
                            focus_view.onFocusFailed()
                        }
                    } catch (e: Exception) {
    
                    }
                }, cameraExecutor)
            }
        }
    
        override fun doubleClick(x: Float, y: Float) {
            // 双击放大缩小
            zoomState?.value?.let {
                val currentZoomRatio = it.zoomRatio
                if (currentZoomRatio > it.minZoomRatio) {
                    mCameraControl?.setLinearZoom(0f)
                } else {
                    mCameraControl?.setLinearZoom(0.5f)
                }
            }
        }
    
        override fun longClick(x: Float, y: Float) = Unit
    })
    view_finder.setOnTouchListener(cameraXPreviewViewTouchListener)
    
    
    

    四、CameraX的一些思考

    优点
    下面我们项目代码中兼容性问题以及相应的special code可以去掉了
    代码在解耦层面做的优秀,以及链式的数据解析都更容易开发者使用
    可拓展的美颜、滤镜、人像、HDR可以较为轻量的实现
    缺点
    目前还在rc3中,稳定性难说
    虽然使用的能力是基于camera2的,但是API都变掉了(估计是为了他们内部解耦),这意味着对本身项目的侵入性是较大的。

            //这种代码可以消失了
            if (forceUseCamera1 || CameraHelper.getInstance(getContext()).shouldUseCamera1()) {//只使用Camera1的方案
                CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera1 (for previous experience)", Build.VERSION.SDK_INT);
                return new Camera1(callbackBridge, preview, context, ratio);
            } else {//根据版本可能使用Camera2的方案
                if (Build.VERSION.SDK_INT < 21) {
                    CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera1", Build.VERSION.SDK_INT);
                    return new Camera1(callbackBridge, preview, context, ratio);
                } else if (Build.VERSION.SDK_INT < 23) {
                    CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera2", Build.VERSION.SDK_INT);
                    return new Camera2(callbackBridge, preview, context, ratio);
                } else {
                    CameraLog.d(TAG, "createCameraViewImpl, sdk version = %d, create Camera2Api23", Build.VERSION.SDK_INT);
                    return new Camera2Api23(callbackBridge, preview, context, ratio);
                }
            }
    
    

    参考链接:https://blog.csdn.net/u012346890/article/details/116020542

    至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

    相关文章

      网友评论

          本文标题:Camerax 入门教程

          本文链接:https://www.haomeiwen.com/subject/iizbbltx.html