近期,Google 的 Jetpack 組件又出了新的庫:CameraX 。
顧名思義:CameraX 就是用來進行 Camera 開發的官方庫了,而且後續會有 Google 進行維護和升級。這對於廣大 Camera 開發工程師和即將成為 Camera 的程序員來說,真是個好消息~~~
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 介紹與實踐](http://p2.ttnews.xyz/loading.gif)
簡述 Camera 開發
關於 Camera 的開發,之前也有寫過相關的文章
Android 相機開發中的尺寸和方向問題
https://glumes.com/post/android/android-camera-aspect-ratio-and-orientation/
Android Camera 模型及 API 接口演變
https://glumes.com/post/android/android-camrea-api-evolution/
對於一個簡單能用的 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 ,在使用時要添加如下的依賴:
<code>1 // CameraX2 def camerax_version = "1.0.0-alpha01"3 implementation "androidx.camera:camera-core:${camerax_version}"4 implementation "androidx.camera:camera-camera2:${camerax_version}"/<code>
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 類創建預覽、分析器、拍攝的類,並在綁定生命週期時將它們傳過去。
<code>1// // Apply declared configs to CameraX using the same lifecycle owner2CameraX.bindToLifecycle(3 lifecycleOwner: this, preview, imageCapture, imageAnalyzer)/<code>
既可以綁定 Activity 的 Lifecycle,也可以綁定 Fragment 的。
當需要解除綁定時:
<code>1// Unbinds all use cases from the lifecycle and removes them from CameraX.2 CameraX.unbindAll()/<code>
關於預覽的參數配置,如果你有看過之前的文章:Android 相機開發中的尺寸和方向問題 想必就會很瞭解了。
提供我們的目標參數,由 CameraX 去判斷當前 Camera 是否支持,並選擇最符合的。
<code> 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()1213 // 創建 Preview 對象14 val preview = Preview(previewConfig)15 // 設置監聽16 preview.setOnPreviewOutputUpdateListener { previewOutput ->17 // PreviewOutput 會返回一個 SurfaceTexture18 cameraTextureView.surfaceTexture = previewOutput.surfaceTexture19 }2021 return preview22}/<code>
通過建造者模式創建 Preview 對象,並且一定要給 Preview 對象設置 OnPreviewOutputUpdateListener 接口回調。
相機預覽的圖像流是通過 SurfaceTexture 來返回的,而在項目例子中,是通過把 TextureView 的 SurfaceTexture 替換成 CameraX 返回的 SurfaceTexture,這樣實現了 TextureView 控件顯示 Camera 預覽內容。
另外,還需要考慮到設備的選擇方向,當設備橫屏變為豎屏了,TextureView 也要相應的做旋轉。
<code> 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 -> 011 Surface.ROTATION_90 -> 9012 Surface.ROTATION_180 -> 18013 Surface.ROTATION_270 -> 27014 else -> return@setOnPreviewOutputUpdateListener15 }1617 val matrix = Matrix()18 matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)1920 // Finally, apply transformations to TextureView21 cameraTextureView.setTransform(matrix)22}/<code>
TextureView 旋轉的設置同樣在 OnPreviewOutputUpdateListener 接口中去完成。
圖像分析
在 bindToLifecycle 方法中,imageAnalyzer 參數並不是必需的。
ImageAnalysis 可以幫助我們做一些圖像質量的分析,需要我們去實現 ImageAnalysis.Analyzer 接口的 analyze 方法。
<code> 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()1718 // 創建分析器 ImageAnalysis 對象19 val analysis = ImageAnalysis(analysisConfig)2021 // setAnalyzer 傳入實現了 analyze 接口的類22 analysis.setAnalyzer { image, rotationDegrees ->23 // 可以得到的一些圖像信息,參見 ImageProxy 類相關方法24 val rect = image.cropRect25 val format = image.format26 val width = image.width27 val height = image.height28 val planes = image.planes29 }3031 return analysis32}/<code>
在圖像分析器的相關配置中,有個 ImageReaderMode 和 ImageQueueDepth 的設置。
ImageQueueDepth 會指定相機管線中圖像的個數,提高 ImageQueueDepth 的數量會對相機的性能和內存的使用造成影響
其中,ImageReaderMode 有兩種模式:
- ACQUIRE_LATEST_IMAGE
- 該模式下,獲得圖像隊列中最新的圖片,並且會清空隊列已有的舊的圖像。
- ACQUIRE_NEXT_IMAGE
- 該模式下,獲得下一張圖像。
在圖像分析的 analyze 方法中,能通過 ImageProxy 類拿到一些圖像信息,並基於這些信息做分析。
拍攝
拍攝同樣有一個 Config 參數構建者類,而且設定的參數和預覽相差不大,也是圖像寬高比例、旋轉方向、分辨率,除此之外還有閃光燈等配置項。
<code> 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()1011 // 創建 ImageCapture 對象12 val capture = ImageCapture(captureConfig)13 cameraCaptureImageButton.setOnClickListener {14 // Create temporary file15 val fileName = System.currentTimeMillis().toString()16 val fileFormat = ".jpg"17 val imageFile = createTempFile(fileName, fileFormat)1819 // Store captured image in the temporary file20 capture.takePicture(imageFile, object : ImageCapture.OnImageSavedListener {21 override fun onImageSaved(file: File) {22 // You may display the image for example using its path file.absolutePath23 }2425 override fun onError(useCaseError: ImageCapture.UseCaseError, message: String, cause: Throwable?) {26 // Display error message27 }28 })29 }3031 return capture32}/<code>
在圖像拍攝的相關配置中,也有個 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 繪製線程。
如果你不是很理解的話,建議還是看看上面提供的代碼地址:
https://github.com/glumes/camera
也可以關注我的微信公眾號 【紙上淺談】,裡面有一些關於 OpenGL 學習和實踐的文章~~~
CameraX 的拓展
如果你看了 Google I/O 大會的視頻,那肯定了解 CameraX 的拓展屬性。
在視頻中提到 Google 也正在和華為、三星、LG、摩托摩拉等廠商進行合作,為了獲得廠商系統相機的一些能力,比如 HDR 等。
不過考慮到目前的形勢,可能和華為的合作難以繼續下去了吧…
但還是期待 CameraX 能給帶來更多的新特性吧~~~
參考
- https://www.youtube.com/watch?v=kuv8uK-5CLY
- https://proandroiddev.com/android-camerax-preview-analyze-capture-1b3f403a939
閱讀更多 技術開發進階 的文章