天天看點

Google Jetpack新元件CameraX介紹與實踐

作者:星隕

來源:

音視訊開發進階

CameraX介紹

官方有提供一個示例的工程,我fork了之後,加入使用OpenGL黑白濾鏡渲染的操作,具體位址如下:

https://github.com/glumes/camera

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

關于CameraX更多的介紹,建議看看Google I / O大會上的視訊記錄,比看文檔能了解更多内容~~~

https://www.youtube.com/watch?v=kuv8uK-5CLY

在視訊中提到,目前有很多應用都開始加入了CameraX,例如Camera360,Tik Tok等。

Google Jetpack新元件CameraX介紹與實踐

簡述Camera開發

關于Camera的開發,之前也有寫過相關的文章🤔

Android相機開發中的尺寸和方向問題 https://glumes.com/post/android/android-camera-aspect-ratio-and-orientation/ Android相機模型及API接口演變 https://glumes.com/post/android/android-camrea-api-evolution/

對于一個簡單能用的Camera應用(示範等級)來說,關注兩個方面就好了:預覽和拍攝。

相機最基本的功能就是能針對預覽和拍攝提供兩套分辨率,是以就得區分場景去設定。

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

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

另外,如果要做美顔,濾鏡類的應用,就要把Camera預覽的圖像放到OpenGL渲染的線程上去,然後由OpenGL去做圖像相關的操作,也就沒Camera什麼事了。等到拍攝圖檔時,可以由OpenGL去擷取圖像内容,也可以由Camera獲得圖像内容,然後通過OpenGL做離屏處理~~~

以Camera開發的其他功能,縮小對焦,曝光,白平衡,HDR等操作,不一定所有的Camera都能夠支援,而且也可以在上面的基礎上當做Camera的一個功能去拓展開發,而不是算難事,這也是一個Camera開發工程師進階所要掌握的内容~~

CameraX開發實踐

CameraX目前的版本是

1.0.0-alpha01

,在使用時要添加以下的依賴:

1    // CameraX
2    def camerax_version = "1.0.0-alpha01"
3    implementation "androidx.camera:camera-core:${camerax_version}"
4    implementation "androidx.camera:camera-camera2:${camerax_version}"      

CameraX向後相容到Android 5.0(API級别21),并且它是基于Camera 2.0的API進行封裝的,解決了城市表面絕大部分手機的相容性問題~~~

概述Camera 2.0複雜的調用流程,CameraX就簡化了很多,隻關心我們需要的内容就好了,不像前者得自己維護CameraSession會話等狀态,并且CameraX和Jetpack主打的生命周期綁定在一起了,什麼時候該打開相機,什麼時候該釋放相機,都交給Lifecycle生命周期去管理吧

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

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

預覽

不論是預覽還是圖像分析,圖像拍攝,CameraX都是通過一個構造器模式來構造參數。配置類,再由配置類建立預覽,分析器,拍攝的類,并在綁定生命周期時将其傳遞過去。

1// // Apply declared configs to CameraX using the same lifecycle owner
2CameraX.bindToLifecycle(
3               lifecycleOwner: this, preview, imageCapture, imageAnalyzer)      

既可以綁定Activity的生命周期,也可以綁定Fragment的。

當需要解除綁定時:

1// Unbinds all use cases from the lifecycle and removes them from CameraX.
2 CameraX.unbindAll()      

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

提供我們的目标參數,由CameraX去判斷目前Camera是否支援,并選擇最符合的。

1fun buildPreviewUseCase(): Preview {
 2    val previewConfig = PreviewConfig.Builder()
 3        // 寬高比
 4        .setTargetAspectRatio(aspectRatio)
 5        // 旋轉
 6        .setTargetRotation(rotation)
 7        // 分辨率
 8        .setTargetResolution(resolution)
 9        // 前後攝像頭
10        .setLensFacing(lensFacing)
11        .build()
12
13    // 建立 Preview 對象
14    val preview = Preview(previewConfig)
15    // 設定監聽
16    preview.setOnPreviewOutputUpdateListener { previewOutput ->
17        // PreviewOutput 會傳回一個 SurfaceTexture
18        cameraTextureView.surfaceTexture = previewOutput.surfaceTexture
19    }
20
21    return preview
22}      

通過建造者模式建立

Preview

對象,并且一定要給Preview對象設定

OnPreviewOutputUpdateListener

接口替代。

相機預覽的圖像流是通過SurfaceTexture來傳回的,而在項目示例中,是通過把TextureView的SurfaceTexture替換成CameraX傳回的SurfaceTexture,進而實作了TextureView控件的顯示。

另外,還需要考慮到裝置的選擇方向,當裝置橫屏變成豎屏了,TextureView也要相應的做旋轉。

1preview.setOnPreviewOutputUpdateListener { previewOutput ->
 2    cameraTextureView.surfaceTexture = previewOutput.surfaceTexture
 3
 4    // Compute the center of preview (TextureView)
 5    val centerX = cameraTextureView.width.toFloat() / 2
 6    val centerY = cameraTextureView.height.toFloat() / 2
 7
 8    // Correct preview output to account for display rotation
 9    val rotationDegrees = when (cameraTextureView.display.rotation) {
10        Surface.ROTATION_0 -> 0
11        Surface.ROTATION_90 -> 90
12        Surface.ROTATION_180 -> 180
13        Surface.ROTATION_270 -> 270
14        else -> return@setOnPreviewOutputUpdateListener
15    }
16
17    val matrix = Matrix()
18    matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
19
20    // Finally, apply transformations to TextureView
21    cameraTextureView.setTransform(matrix)
22}      

TextureView旋轉的設定同樣在

OnPreviewOutputUpdateListener

接口中去完成。

圖像分析

bindToLifecycle

方法中,

imageAnalyzer

參數并不是必需的。

ImageAnalysis

可以幫助我們做一些圖像品質的分析,需要我們去實作

ImageAnalysis.Analyzer

接口的

analyze

方法。

1fun buildImageAnalysisUseCase(): ImageAnalysis {
 2    // 分析器配置 Config 的建造者
 3    val analysisConfig = ImageAnalysisConfig.Builder()
 4        // 寬高比例
 5        .setTargetAspectRatio(aspectRatio)
 6        // 旋轉
 7        .setTargetRotation(rotation)
 8        // 分辨率
 9        .setTargetResolution(resolution)
10        // 圖像渲染模式
11        .setImageReaderMode(readerMode)
12        // 圖像隊列深度
13        .setImageQueueDepth(queueDepth)
14        // 設定回調的線程
15        .setCallbackHandler(handler)
16        .build()
17
18    // 建立分析器 ImageAnalysis 對象
19    val analysis = ImageAnalysis(analysisConfig)
20
21    // setAnalyzer 傳入實作了 analyze 接口的類
22    analysis.setAnalyzer { image, rotationDegrees ->
23        // 可以得到的一些圖像資訊,參見 ImageProxy 類相關方法
24        val rect = image.cropRect
25        val format = image.format
26        val width = image.width
27        val height = image.height
28        val planes = image.planes
29    }
30
31    return analysis
32}      

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

ImageReaderMode

ImageQueueDepth

的設定。

ImageQueueDepth會指定相機管線中圖像的個數,提高ImageQueueDepth的數量相機的性能和記憶體的使用造成影響

其中,ImageReaderMode有兩種模式:

  • ACQUIRE_LATEST_IMAGE
    • 該模式下,獲得圖像像素中最新的圖檔,并且會清空副本已有的舊的圖像。
  • ACQUIRE_NEXT_IMAGE
    • 該模式下,獲得下一張圖像。

在圖像分析的

analyze

方法中,能通過ImageProxy類拿到一些圖像資訊,并基于這些資訊做分析。

拍攝

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

1fun buildImageCaptureUseCase(): ImageCapture {
 2    val captureConfig = ImageCaptureConfig.Builder()
 3        .setTargetAspectRatio(aspectRatio)
 4        .setTargetRotation(rotation)
 5        .setTargetResolution(resolution)
 6        .setFlashMode(flashMode)
 7        // 拍攝模式
 8        .setCaptureMode(captureMode)
 9        .build()
10
11    // 建立 ImageCapture 對象
12    val capture = ImageCapture(captureConfig)
13    cameraCaptureImageButton.setOnClickListener {
14        // Create temporary file
15        val fileName = System.currentTimeMillis().toString()
16        val fileFormat = ".jpg"
17        val imageFile = createTempFile(fileName, fileFormat)
18
19        // Store captured image in the temporary file
20        capture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener {
21            override fun onImageSaved(file: File) {
22                // You may display the image for example using its path file.absolutePath
23            }
24
25            override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {
26                // Display error message
27            }
28        })
29    }
30
31    return capture
32}      

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

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布局線程。

如果你不是很了解的話,建議還是看看上面提供的代碼位址:

也可以關注我的微信公衆号【紙上淺談】,裡面有一些關于OpenGL學習和實踐的文章~~~

CameraX的拓展

如果您看了Google I / O大會的視訊,那肯定了解CameraX的擴充屬性。

在視訊中提到Google也正在和華為,三星,LG,摩托摩拉等廠商進行合作,以便獲得廠商系統相機的一些能力,例如HDR等。

不過考慮到目前的預期,可能和華為的合作難以繼續下去了吧...

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

參考

  1. https://proandroiddev.com/android-camerax-preview-analyze-capture-1b3f403a9395
安卓以及音視訊雜談
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。  
Google Jetpack新元件CameraX介紹與實踐

繼續閱讀