Camerax 入门教程

和你一起终身学习,这里是程序员 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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章