欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android進階CameraX與Camera2使用比對詳解

 更新時間:2023年01月15日 10:27:37   作者:Vector7  
這篇文章主要為大家介紹了Android進階CameraX與Camera2使用比示例對詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

相機,作為手機最重要的一個多媒體工具,被應(yīng)用于眾多app軟件中,如果整個項目中涉及到拍照、直播、錄視頻、掃碼,那么相機就必須要用到。傳統(tǒng)的相機app,一般使用到Camera或者Camera2比較多,但是Google的JectPack框架中引入了CameraX組件作為官方推薦相機架構(gòu),既然推出此框架,那么一定是有它自身的優(yōu)勢之處在的,本文將會從CameraX和Camera2的框架機制出發(fā),分析兩者的不同以及性能差異。

1 Google相機元老 Camera2

其實在早期開發(fā)相機app的時候,有一部分會使用Camera,有一部分會使用Camera2,但是用起來真的是苦不堪言,往往在相機配置時,為了調(diào)出預(yù)覽頁面,至少要寫1000行代碼,而且僅僅是一個預(yù)覽頁面,后面拍照、錄視頻等等,還需要做額外的開發(fā),說這么多,我們先看下Camera2是如何使用的吧。

1.1 Camera2的使用

首先Camera2是Google原生的相機框架,所以不需要引任何框架進來。

第一步:創(chuàng)建承載相機的容器

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextureView
        android:id="@+id/camera_view_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

一般來說,承載相機預(yù)覽頁面的就是TextureView,當(dāng)TextureView創(chuàng)建完成之后,就可以配置相機參數(shù),展示預(yù)覽頁面。

第二步:打開攝像頭的時機

Camera2和Camera不同的是,Camera2是將應(yīng)用層與相機內(nèi)核層做了完全的解耦,需要通過Camera Service來獲取到CameraManager以此調(diào)用相機內(nèi)核層提供的能力。

那么在什么時機才能打開攝像頭呢?就是在TextureView的onSurfaceTextureAvailable回調(diào)的時候,去初始化相機參數(shù),開啟攝像頭。

/**
 * 初始化相機
 * 觸發(fā)時機 TextureView 的 onSurfaceTextureAvailable回調(diào)
 */
private fun initCamera(){
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
    initCamera()
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
    return false
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
}

第三步:搭建應(yīng)用層與相機內(nèi)核的橋梁

/**
 * 開啟攝像頭
 */
@SuppressLint("MissingPermission")
@Synchronized
fun start(textureView: TextureView) {
    val cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
    //建立數(shù)據(jù)傳輸?shù)臉蛄?
    imageReader = ImageReader.newInstance(
        previewWidth,
        previewHeight,
        ImageFormat.YUV_420_888,
        2
    )
    val handlerThread = HandlerThread("Camera2Manager")
    handlerThread.start()
    handler = Handler(handlerThread.looper)
    imageReader?.setOnImageAvailableListener(this, handler)
    //真正地開啟攝像頭
    cameraManager.openCamera("0", this, handler)
}
// setOnImageAvailableListener的回調(diào)
override fun onImageAvailable(reader: ImageReader?) {
}
// Device StateCallback
override fun onOpened(camera: CameraDevice) {
}
override fun onDisconnected(camera: CameraDevice) {
}
override fun onError(camera: CameraDevice, error: Int) {
}

因為應(yīng)用層和相機內(nèi)核層已經(jīng)完全解耦,所以兩者想要進行數(shù)據(jù)傳遞,必須要建立橋梁,那么通過ImageReader就可以完成,可以通過傳入一些參數(shù):預(yù)覽尺寸、圖像格式等,設(shè)置setOnImageAvailableListener,有數(shù)據(jù)發(fā)送過來之后,通過onImageAvailable獲取到數(shù)據(jù)顯示即可。

然后,調(diào)用CameraManager的openCamera方法,才是真正打開攝像頭,那么成功還是失敗呢?就需要通過 CameraDevice.StateCallback的回調(diào)來判斷。

第四步:建立會話,創(chuàng)建預(yù)覽請求

override fun onOpened(camera: CameraDevice) {
    this.cameraDevice = camera
    //建立會話
    createPreviewSession()
}
override fun onDisconnected(camera: CameraDevice) {
    cameraDevice?.close()
    cameraDevice = null
}
override fun onError(camera: CameraDevice, error: Int) {
    cameraDevice?.close()
    cameraDevice = null
}

當(dāng)打開Camera之后,如何判斷是否成功開啟攝像頭呢?就是通過onOpened這個回調(diào)來判斷,當(dāng)成功開啟攝像頭之后,就需要與相機內(nèi)核建立會話,發(fā)起預(yù)覽請求createCaptureRequest。

private fun createPreviewSession() {
    //創(chuàng)建Surface,所有的畫面渲染都是由Surface處理
    val texture = textureView?.surfaceTexture
    texture?.setDefaultBufferSize(previewWidth, previewHeight)
    val surface = Surface(texture)
    //開啟預(yù)覽會話,這樣就可以預(yù)覽數(shù)據(jù)
    val builder = cameraDevice?.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
    builder?.addTarget(surface)
    builder?.set(
        CaptureRequest.CONTROL_AF_MODE,
        CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
    )
    //想要獲取每一幀的數(shù)據(jù),需要給ImageReader設(shè)置Target
    imageReader?.surface?.let {
        builder?.addTarget(it)
    }
    //建立連接,開啟會話
    cameraDevice?.createCaptureSession(
        mutableListOf(surface, imageReader?.surface),
        CaptureSessionCallback(),
        handler
)

我們知道,如果使用TextureView,那么所有畫面的渲染都是通過Surface來渲染,因此在創(chuàng)建Surface之后,就可以添加到cameraDevice中,此時頁面中就會有圖像了,如果想要分析每一幀的數(shù)據(jù),那么也需要給ImageReader設(shè)置Surface.

完成一系列配置之后,調(diào)用CameraDevice的createCaptureSession方法,開啟會話。

inner class CaptureSessionCallback : CameraCaptureSession.StateCallback() {
    override fun onConfigured(session: CameraCaptureSession) {
        mSession = session
        if (cameraDevice == null) return
        session.setRepeatingRequest(requestBuilder?.build()!!, null, handler)
    }
    override fun onConfigureFailed(session: CameraCaptureSession) {
    }
}

因為需要實時的數(shù)據(jù)流一幀一幀地傳遞過來,因此需要發(fā)起重復(fù)請求setRepeatingRequest,以此將實時的圖像幀發(fā)送到手機頁面上進行渲染。

1.2 小結(jié)

至此我們先做一個小小的總結(jié),我們知道如果做一個相機app,最主要的兩個功能:預(yù)覽和分析圖片幀。

我們在打開攝像頭之前,首先會通過ImageReader來完成應(yīng)用層和相機內(nèi)核層的建立,通過監(jiān)聽回調(diào)獲取每一幀的圖像數(shù)據(jù);然后開啟攝像頭之后,又完成的一個工作就是配置Surface,將其掛載到CameraDevice上,然后如果想要請求獲取圖像幀,那么就需要開啟session,重復(fù)發(fā)起預(yù)覽的請求,來獲取到每一幀的數(shù)據(jù),也正是下圖所展示的。

1.3 問題分析

如果我們做到了上圖的配置,我們運行之后發(fā)現(xiàn),只拿到了一幀數(shù)據(jù),緊接著拋出了一個異常,這個是在onError中回調(diào)的。

2023-01-14 14:56:14.220 29697-29803/com.lay.highavailablecamera E/HighAvailableCamera: onOpened
2023-01-14 14:56:14.574 29697-29803/com.lay.highavailablecamera E/HighAvailableCamera: onImageAvailable android.media.ImageReader@f04668
2023-01-14 14:59:14.122 29697-29803/com.lay.highavailablecamera E/HighAvailableCamera: onError 3

如果做過相機應(yīng)用的伙伴應(yīng)該也見到過這個情況,主要原因就是,一幀一幀地數(shù)據(jù)傳遞過來之后,我們沒有處理,導(dǎo)致數(shù)據(jù)一直阻塞沒有關(guān)閉,從而無法處理下一幀數(shù)據(jù),因此在拿到最新的數(shù)據(jù)之后,需要主動調(diào)用close方法,以便下一幀數(shù)據(jù)進入到屏幕中。

override fun onImageAvailable(reader: ImageReader?) {
    Log.e(Constants.TAG, "onImageAvailable $reader")
    val latestImage = reader?.acquireNextImage()
    //NOTE 做數(shù)據(jù)處理
    latestImage?.close()
}

2 CameraX的便捷之處

前面我們用Camera2完成了頁面的預(yù)覽,伙伴們?nèi)绻行枰创a的,可以直接找我。通過前面對于Camera2的使用,我們發(fā)現(xiàn)太麻煩了,就為了完成一個頁面預(yù)覽,寫了很多配置,好處就是這些配置是通用的,不需要頻繁地改動,但是我們想的就是能夠一鍵式搭建一個相機應(yīng)用,那么CameraX就是我們需要的”傻瓜相機“。

2.1 UseCase分類

其實,CameraX對于每個功能都做了詳細的劃分,例如預(yù)覽的PreView、圖像分析的ImageAnalysis等,因此在做Camera配置的時候,可以選擇自己想要的UseCase進行初始化。

private fun initCameraConfig(context: Context) {
    val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
    cameraProviderFuture.addListener({
        // 預(yù)覽配置
        val preview = Preview.Builder()
            .build()
            .also {
                it.setSurfaceProvider(binding.basePreview.surfaceProvider)
            }
        //設(shè)置攝像頭是前置還是后置
        val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
        if (cameraProvider == null) {
            cameraProvider = cameraProviderFuture.get()
            //拍照useCase配置
            if (imageCapture == null) {
                imageCapture = ImageCapture
                    .Builder()
                    .setTargetRotation(Surface.ROTATION_90)
                    .setCaptureMode(ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY)
                    .setFlashMode(ImageCapture.FLASH_MODE_AUTO) 
                    .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                    .build()
            }
            //圖像分析useCase配置
            if (imageAnalysis == null) {
                imageAnalysis = ImageAnalysis.Builder()
                    .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                    .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                    .build()
            }
            //圖像數(shù)據(jù)處
            try {
                cameraProvider?.unbindAll()
                cameraProvider?.bindToLifecycle(
                    lifecycleOwner!!, cameraSelector, preview, imageCapture, imageAnalysis
                )
            } catch (exc: Exception) {
            }
        }
    }, ContextCompat.getMainExecutor(context))
}

其實我們可以看到,這里我們是將拍照的userCase以及圖像分析的useCase都做了配置,如果只需要做一個預(yù)覽配置,也就幾十行代碼就能夠完成,單就使用上,比Camera2要簡單的多的多。

<androidx.camera.view.PreviewView
    android:id="@+id/base_preview"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>

對于預(yù)覽頁面,CameraX中也提供了PreviewView容器,它其實是繼承自FrameLayout。

其實對于CameraX這塊僅僅是講了如何使用,具體的核心實現(xiàn)等下篇文章細細道來,更多關(guān)于Android CameraX與Camera2對比的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論