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

至此,本篇已結束。轉載網絡的文章,小編覺得很優秀,歡迎點擊閱讀原文,支持原創作者,如有侵權,懇請聯繫小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!

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