Google Jetpack 新組件 CameraX 介紹與實踐

近期,Google 的 Jetpack 組件又出了新的庫:CameraX 。android

顧名思義:CameraX 就是用來進行 Camera 開發的官方庫了,並且後續會有 Google 進行維護和升級。這對於廣大 Camera 開發工程師和即將成爲 Camera 的程序員來講,真是個好消息~~~git

原文地址:glumes.com/post/androi…程序員

CameraX 介紹

官方有給出一個示例的工程,我 fork 了以後,加入使用 OpenGL 黑白濾鏡渲染的操做,具體地址以下:github

github.com/glumes/came…api

官方並無提到 CameraX 庫具體如何進行 OpenGL 線程渲染的, 繼續往下看,你會找到答案的~~~微信

關於 CameraX 更多的介紹,建議看看 Google I/O 大會上的視頻記錄,比看文檔能瞭解更多內容~~~app

www.youtube.com/watch?v=kuv…ide

在視頻中提到,目前有不少應用都開始接入了 CameraX,好比 Camera360、Tik Tok 等。post

簡述 Camera 開發

關於 Camera 的開發,以前也有寫過相關的文章🤔性能

Android 相機開發中的尺寸和方向問題

Android Camera 模型及 API 接口演變

對於一個簡單能用的 Camera 應用(Demo 級別)來講,關注兩個方面就行了:預覽和拍攝。

而預覽和拍攝的圖像都受到分辨率、方向的影響。Camera 最必備的功能就是能針對預覽和拍攝提供兩套分辨率,所以就得區分場景去設置。

對於拍攝還好說一點,要得到最好的圖像質量,就選擇同比例中分辨率最大的吧。

而預覽的圖像最終要呈現到 Android 的 Surface 上,所以選擇分辨率的時候要考慮 Surface 的寬高比例,不要出現比例不匹配致使圖像拉伸的現象。

另外,若是要作美顏、濾鏡類的應用,就要把 Camera 預覽的圖像放到 OpenGL 渲染的線程上去,而後由 OpenGL 去作圖像相關的操做,也就沒 Camera 什麼事了。等到拍攝圖片時,能夠由 OpenGL 去獲取圖像內容,也能夠由 Camera 得到圖像內容,而後通過 OpenGL 作離屏處理~~~

至於 Camera 開發的其餘功能,好比對焦、曝光、白平衡、HDR 等操做,不必定全部的 Camera 都可以支持,並且也能夠在上面的基礎上當作 Camera 的一個 feature 去拓展開發,並不算難事,這也是一個 Camera 開發工程師進階所要掌握的內容~~

CameraX 開發實踐

CameraX 目前的版本是 1.0.0-alpha01 ,在使用時要添加以下的依賴:

// CameraX
    def camerax_version = "1.0.0-alpha01"
    implementation "androidx.camera:camera-core:${camerax_version}"
    implementation "androidx.camera:camera-camera2:${camerax_version}"
複製代碼

CameraX 向後兼容到 Android 5.0(API Level 21),而且它是基於 Camera 2.0 的 API 進行封裝的,解決了市面上絕大部分手機的兼容性問題~~~

相比 Camera 2.0 複雜的調用流程,CameraX 就簡化不少,只關心咱們須要的內容就行了,不像前者得本身維護 CameraSession 會話等狀態,而且 CameraX 和 Jetpack 主打的 Lifecycle 綁定在一塊兒了,何時該打開相機,何時該釋放相機,都交給 Lifecycle 生命週期去管理吧

上手 CameraX 主要關注三個方面:

  • 圖像預覽(Image Preview)
  • 圖像分析(Image analysis)
  • 圖像拍攝(Image capture)

預覽

不論是 預覽 仍是 圖像分析、圖像拍攝,CameraX 都是經過一個建造者模式來構建參數 Config 類,再由 Config 類建立預覽、分析器、拍攝的類,並在綁定生命週期時將它們傳過去。

// // Apply declared configs to CameraX using the same lifecycle owner
CameraX.bindToLifecycle(
               lifecycleOwner: this, preview, imageCapture, imageAnalyzer)
複製代碼

既能夠綁定 Activity 的 Lifecycle,也能夠綁定 Fragment 的。

當須要解除綁定時:

// Unbinds all use cases from the lifecycle and removes them from CameraX.
 CameraX.unbindAll()
複製代碼

關於預覽的參數配置,若是你有看過以前的文章:Android 相機開發中的尺寸和方向問題 想必就會很瞭解了。

提供咱們的目標參數,由 CameraX 去判斷當前 Camera 是否支持,並選擇最符合的。

fun buildPreviewUseCase(): Preview {
    val previewConfig = PreviewConfig.Builder()
        // 寬高比
        .setTargetAspectRatio(aspectRatio)
        // 旋轉
        .setTargetRotation(rotation)
        // 分辨率
        .setTargetResolution(resolution)
        // 先後攝像頭
        .setLensFacing(lensFacing)
        .build()
    
    // 建立 Preview 對象
    val preview = Preview(previewConfig)
    // 設置監聽
    preview.setOnPreviewOutputUpdateListener { previewOutput ->
        // PreviewOutput 會返回一個 SurfaceTexture
        cameraTextureView.surfaceTexture = previewOutput.surfaceTexture
    }

    return preview
}
複製代碼

經過建造者模式建立 Preview 對象,而且必定要給 Preview 對象設置 OnPreviewOutputUpdateListener 接口回調。

相機預覽的圖像流是經過 SurfaceTexture 來返回的,而在項目例子中,是經過把 TextureView 的 SurfaceTexture 替換成 CameraX 返回的 SurfaceTexture,這樣實現了 TextureView 控件顯示 Camera 預覽內容。

另外,還須要考慮到設備的選擇方向,當設備橫屏變爲豎屏了,TextureView 也要相應的作旋轉。

preview.setOnPreviewOutputUpdateListener { previewOutput ->
    cameraTextureView.surfaceTexture = previewOutput.surfaceTexture

    // Compute the center of preview (TextureView)
    val centerX = cameraTextureView.width.toFloat() / 2
    val centerY = cameraTextureView.height.toFloat() / 2

    // Correct preview output to account for display rotation
    val rotationDegrees = when (cameraTextureView.display.rotation) {
        Surface.ROTATION_0 -> 0
        Surface.ROTATION_90 -> 90
        Surface.ROTATION_180 -> 180
        Surface.ROTATION_270 -> 270
        else -> return@setOnPreviewOutputUpdateListener
    }

    val matrix = Matrix()
    matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)

    // Finally, apply transformations to TextureView
    cameraTextureView.setTransform(matrix)
}
複製代碼

TextureView 旋轉的設置一樣在 OnPreviewOutputUpdateListener 接口中去完成。

圖像分析

bindToLifecycle 方法中,imageAnalyzer 參數並非必需的。

ImageAnalysis 能夠幫助咱們作一些圖像質量的分析,須要咱們去實現 ImageAnalysis.Analyzer 接口的 analyze 方法。

fun buildImageAnalysisUseCase(): ImageAnalysis {
    // 分析器配置 Config 的建造者
    val analysisConfig = ImageAnalysisConfig.Builder()
        // 寬高比例
        .setTargetAspectRatio(aspectRatio)
        // 旋轉
        .setTargetRotation(rotation)
        // 分辨率
        .setTargetResolution(resolution)
        // 圖像渲染模式
        .setImageReaderMode(readerMode)
        // 圖像隊列深度
        .setImageQueueDepth(queueDepth)
        // 設置回調的線程
        .setCallbackHandler(handler)
        .build()
    
    // 建立分析器 ImageAnalysis 對象
    val analysis = ImageAnalysis(analysisConfig)
    
    // setAnalyzer 傳入實現了 analyze 接口的類
    analysis.setAnalyzer { image, rotationDegrees ->
        // 能夠獲得的一些圖像信息,參見 ImageProxy 類相關方法
        val rect = image.cropRect
        val format = image.format
        val width = image.width
        val height = image.height
        val planes = image.planes
    }

    return analysis
}
複製代碼

在圖像分析器的相關配置中,有個 ImageReaderModeImageQueueDepth 的設置。

ImageQueueDepth 會指定相機管線中圖像的個數,提升 ImageQueueDepth 的數量會對相機的性能和內存的使用形成影響

其中,ImageReaderMode 有兩種模式:

  • ACQUIRE_LATEST_IMAGE
    • 該模式下,得到圖像隊列中最新的圖片,而且會清空隊列已有的舊的圖像。
  • ACQUIRE_NEXT_IMAGE
    • 該模式下,得到下一張圖像。

在圖像分析的 analyze 方法中,能經過 ImageProxy 類拿到一些圖像信息,並基於這些信息作分析。

拍攝

拍攝一樣有一個 Config 參數構建者類,並且設定的參數和預覽相差不大,也是圖像寬高比例、旋轉方向、分辨率,除此以外還有閃光燈等配置項。

fun buildImageCaptureUseCase(): ImageCapture {
    val captureConfig = ImageCaptureConfig.Builder()
        .setTargetAspectRatio(aspectRatio)
        .setTargetRotation(rotation)
        .setTargetResolution(resolution)
        .setFlashMode(flashMode)
        // 拍攝模式
        .setCaptureMode(captureMode)
        .build()
    
    // 建立 ImageCapture 對象
    val capture = ImageCapture(captureConfig)
    cameraCaptureImageButton.setOnClickListener {
        // Create temporary file
        val fileName = System.currentTimeMillis().toString()
        val fileFormat = ".jpg"
        val imageFile = createTempFile(fileName, fileFormat)
        
        // Store captured image in the temporary file
        capture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener {
            override fun onImageSaved(file: File) {
                // You may display the image for example using its path file.absolutePath
            }

            override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
                // Display error message
            }
        })
    }

    return capture
}
複製代碼

在圖像拍攝的相關配置中,也有個 CaptureMode 的設置。

它有兩種選項:

  • MIN_LATENCY
    • 該模式下,拍攝速度會相對快一點,但圖像質量會打折扣
  • MAX_QUALITY
    • 該模式下,拍攝速度會慢一點,但圖像質量好

OpenGL 渲染

以上是關於 CameraX 的簡單應用方面的內容,更關心的是如何用 CameraX 去作 OpenGL 渲染實現美顏。濾鏡等效果。

還記得在圖像預覽 Preview 的 setOnPreviewOutputUpdateListener 方法中,會返回一個 SurfaceTexture ,相機的圖像流就是經過它返回的。

那麼要實現 OpenGL 線程的渲染,首先就要基於 EGL 去建立 OpenGL 繪製環境,而後利用 SurfaceTexture 的 attachToGLContext 方法,將 SurfaceTexture 添加到 OpenGL 線程去。

attachToGLContext 的參數是一個紋理 ID ,這個紋理就必須是 OES 類型的紋理。

而後再把這紋理 ID 繪製到 OpenGL 對應的 Surface 上,這能夠當作是兩個不一樣的線程在容許,一個 Camera 預覽線程,一個 OpenGL 繪製線程。

若是你不是很理解的話,建議仍是看看上面提供的代碼地址:

github.com/glumes/came…

也能夠關注個人微信公衆號 【紙上淺談】,裏面有一些關於 OpenGL 學習和實踐的文章~~~

CameraX 的拓展

若是你看了 Google I/O 大會的視頻,那確定瞭解 CameraX 的拓展屬性。

在視頻中提到 Google 也正在和華爲、三星、LG、摩托摩拉等廠商進行合做,爲了得到廠商系統相機的一些能力,好比 HDR 等。

不過考慮到目前的形勢,可能和華爲的合做難以繼續下去了吧...

但仍是期待 CameraX 能給帶來更多的新特性吧~~~

參考

  1. www.youtube.com/watch?v=kuv…
  2. proandroiddev.com/android-cam…

文章推薦

  1. 一文讀懂 YUV 的採樣與格式

  2. OpenGL 之 EGL 使用實踐

  3. OpenGL 深度測試與精度值的那些事

以爲文章不錯,歡迎關注和轉發微信公衆號:【紙上淺談】,得到最新文章推送~~~

掃碼關注
相關文章
相關標籤/搜索