Camera2 教程 一概览
從 Android 5.0 開始,Google 引入了一套全新的相機框架 Camera2(android.hardware.camera2)并且廢棄了舊的相機框架 Camera1(android.hardware.Camera)。作為一個專門從事相機應用開發的開發者來說,這一刻我等了太久了,Camera1 那寥寥無幾的 API 和極差的靈活性早已不能滿足日益復雜的相機功能開發。Camera2 的出現給相機應用程序帶來了巨大的變革,因為它的目的是為了給應用層提供更多的相機控制權限,從而構建出更高質量的相機應用程序。本文是 Camera2 教程的開篇作,本章將介紹以下幾個內容:
- 一些 Camera2 的重要概念
- 一些只有 Camera2 才支持的高級特性
- 一些從 Camera1 遷移到 Camera2 的建議
本章涉及的代碼很少,因為我們會在接下來的教程中深入介紹 Camera2 的 API。
1 Pipeline
Camera2 的 API 模型被設計成一個 Pipeline(管道),它按順序處理每一幀的請求并返回請求結果給客戶端。下面這張來自官方的圖展示了 Pipeline 的工作流程,我們會通過一個簡單的例子詳細解釋這張圖。
?
Pipeline 示意圖
為了解釋上面的示意圖,假設我們想要同時拍攝兩張不同尺寸的圖片,并且在拍攝的過程中閃光燈必須亮起來。整個拍攝流程如下:
一個新的 CaptureRequest 會被放入一個被稱作 Pending Request Queue 的隊列中等待被執行,當 In-Flight Capture Queue 隊列空閑的時候就會從 Pending Request Queue 獲取若干個待處理的 CaptureRequest,并且根據每一個 CaptureRequest 的配置進行 Capture 操作。最后我們從不同尺寸的 Surface 中獲取圖片數據并且還會得到一個包含了很多與本次拍照相關的信息的 CaptureResult,流程結束。
2 Supported Hardware Level
相機功能的強大與否和硬件息息相關,不同廠商對 Camera2 的支持程度也不同,所以 Camera2 定義了一個叫做 Supported Hardware Level 的重要概念,其作用是將不同設備上的 Camera2 根據功能的支持情況劃分成多個不同級別以便開發者能夠大概了解當前設備上 Camera2 的支持情況。截止到 Android P 為止,從低到高一共有 LEGACY、LIMITED、FULL 和 LEVEL_3 四個級別:
3 Capture
相機的所有操作和參數配置最終都是服務于圖像捕獲,例如對焦是為了讓某一個區域的圖像更加清晰,調節曝光補償是為了調節圖像的亮度。因此,在 Camera2 里面所有的相機操作和參數配置都被抽象成 Capture(捕獲),所以不要簡單的把 Capture 直接理解成是拍照,因為 Capture 操作可能僅僅是為了讓預覽畫面更清晰而進行對焦而已。如果你熟悉 Camera1,那你可能會問 setFlashMode() 在哪?setFocusMode() 在哪?takePicture() 在哪?告訴你,它們都是通過 Capture 來實現的。
Capture 從執行方式上又被細分為【單次模式】、【多次模式】和【重復模式】三種,我們來一一解釋下:
-
單次模式(One-shot):指的是只執行一次的 Capture 操作,例如設置閃光燈模式、對焦模式和拍一張照片等。多個一次性模式的 Capture 會進入隊列按順序執行。
-
多次模式(Burst):指的是連續多次執行指定的 Capture 操作,該模式和多次執行單次模式的最大區別是連續多次 Capture 期間不允許插入其他任何 Capture 操作,例如連續拍攝 100 張照片,在拍攝這 100 張照片期間任何新的 Capture 請求都會排隊等待,直到拍完 100 張照片。多組多次模式的 Capture 會進入隊列按順序執行。
-
重復模式(Repeating):指的是不斷重復執行指定的 Capture 操作,當有其他模式的 Capture 提交時會暫停該模式,轉而執行其他被模式的 Capture,當其他模式的 Capture 執行完畢后又會自動恢復繼續執行該模式的 Capture,例如顯示預覽畫面就是不斷 Capture 獲取每一幀畫面。該模式的 Capture 是全局唯一的,也就是新提交的重復模式 Capture 會覆蓋舊的重復模式 Capture。
4 CameraManager
CameraManager 是一個負責查詢和建立相機連接的系統服務,它的功能不多,這里列出幾個 CameraManager 的關鍵功能:
5 CameraCharacteristics
CameraCharacteristics 是一個只讀的相機信息提供者,其內部攜帶大量的相機信息,包括代表相機朝向的 LENS_FACING;判斷閃光燈是否可用的 FLASH_INFO_AVAILABLE;獲取所有可用 AE 模式的 CONTROL_AE_AVAILABLE_MODES 等等。如果你對 Camera1 比較熟悉,那么 CameraCharacteristics 有點像 Camera1 的 Camera.CameraInfo 或者 Camera.Parameters。
6 CameraDevice
CameraDevice 代表當前連接的相機設備,它的職責有以下四個:
熟悉 Camera1 的人可能會說 CameraDevice 就是 Camera1 的 Camera 類,實則不是,Camera 類幾乎負責了所有相機的操作,而 CameraDevice 的功能則十分的單一,就是只負責建立相機連接的事務,而更加細化的相機操作則交給了稍后會介紹的 CameraCaptureSession。
7 Surface
Surface 是一塊用于填充圖像數據的內存空間,例如你可以使用 SurfaceView 的 Surface 接收每一幀預覽數據用于顯示預覽畫面,也可以使用 ImageReader 的 Surface 接收 JPEG 或 YUV 數據。每一個 Surface 都可以有自己的尺寸和數據格式,你可以從 CameraCharacteristics 獲取某一個數據格式支持的尺寸列表。
8 CameraCaptureSession
CameraCaptureSession 實際上就是配置了目標 Surface 的 Pipeline 實例,我們在使用相機功能之前必須先創建 CameraCaptureSession 實例。一個 CameraDevice 一次只能開啟一個 CameraCaptureSession,絕大部分的相機操作都是通過向 CameraCaptureSession 提交一個 Capture 請求實現的,例如拍照、連拍、設置閃光燈模式、觸摸對焦、顯示預覽畫面等等。
9 CaptureRequest
CaptureRequest 是向 CameraCaptureSession 提交 Capture 請求時的信息載體,其內部包括了本次 Capture 的參數配置和接收圖像數據的 Surface。CaptureRequest 可以配置的信息非常多,包括圖像格式、圖像分辨率、傳感器控制、閃光燈控制、3A 控制等等,可以說絕大部分的相機參數都是通過 CaptureRequest 配置的。值得注意的是每一個 CaptureRequest 表示一幀畫面的操作,這意味著你可以精確控制每一幀的 Capture 操作。
10 CaptureResult
CaptureResult 是每一次 Capture 操作的結果,里面包括了很多狀態信息,包括閃光燈狀態、對焦狀態、時間戳等等。例如你可以在拍照完成的時候,通過 CaptureResult 獲取本次拍照時的對焦狀態和時間戳。需要注意的是,CaptureResult 并不包含任何圖像數據,前面我們在介紹 Surface 的時候說了,圖像數據都是從 Surface 獲取的。
11 一些只有 Camera2 才支持的高級特性
如果要我給出強有力的理由解釋為什么要使用 Camera2,那么通過 Camera2 提供的高級特性可以構建出更加高質量的相機應用程序應該是最佳理由了。
在開啟相機之前檢查相機信息
出于某些原因,你可能需要先檢查相機信息再決定是否開啟相機,例如檢查閃光燈是否可用。在 Caemra1 上,你無法在開機相機之前檢查詳細的相機信息,因為這些信息都是通過一個已經開啟的相機實例提供的。在 Camera2 上,我們有了和相機實例完全剝離的 CameraCharacteristics 實例專門提供相機信息,所以我們可以在不開啟相機的前提下檢查幾乎所有的相機信息。
在不開啟預覽的情況下拍照
在 Camera1 上,開啟預覽是一個很重要的環節,因為只有在開啟預覽之后才能進行拍照,因此即使顯示預覽畫面與實際業務需求相違背的時候,你也不得不開啟預覽。而 Camera2 則不強制要求你必須先開啟預覽才能拍照。
一次拍攝多張不同格式和尺寸的圖片
在 Camera1 上,一次只能拍攝一張圖片,更不同談多張不同格式和尺寸的圖片了。而 Camera2 則支持一次拍攝多張圖片,甚至是多張格式和尺寸都不同的圖片。例如你可以同時拍攝一張 1440x1080 的 JPEG 圖片和一張全尺寸的 RAW 圖片。
控制曝光時間
在暗環境下拍照的時候,如果能夠適當延長曝光時間,就可以讓圖像畫面的亮度得到提高。在 Camera2 上,你可以在規定的曝光時長范圍內配置拍照的曝光時間,從而實現拍攝長曝光圖片,你甚至可以延長每一幀預覽畫面的曝光時間讓整個預覽畫面在暗環境下也能保證一定的亮度。而在 Camera1 上你只能 YY 一下。
連拍
連拍 30 張圖片這樣的功能在 Camera2 出現之前恐怕只有系統相機才能做到了(通過 OpenGL 截取預覽畫面的做法除外),也可能是出于這個原因,市面上的第三方相機無一例外都不支持連拍。有了 Camera2,你完全可以讓你的相機應用程序支持連拍功能,甚至是連續拍 30 張使用不同曝光時間的圖片。
靈活的 3A 控制
3A(AF、AE、AWB)的控制在 Camera2 上得到了最大化的放權,應用層可以根據業務需求靈活配置 3A 流程并且實時獲取 3A 狀態,而 Camera1 在 3A 的控制和監控方面提供的接口則要少了很多。例如你可以在拍照前進行 AE 操作,并且監聽本這次拍照是否點亮閃光燈。
12 一些從 Camera1 遷移到 Camera2 的建議
如果你熟悉 Camera1,并且打算從 Camera1 遷移到 Camera2 的話,希望以下幾個建議可以對你起到幫助:
Camera1 嚴格區分了預覽和拍照兩個流程,而 Camera2 則把這兩個流程都抽象成了 Capture 行為,只不過一個是不斷重復的 Capture,一個是一次性的 Capture 而已,所以建議你不要帶著過多的 Camera1 思維使用 Camera2,避免因為思維上的束縛而無法充分利用 Camera2 靈活的 API。
如同 Camera1 一樣,Camera2 的一些 API 調用也會耗時,所以建議你使用獨立的線程執行所有的相機操作,盡量避免直接在主線程調用 Camera2 的 API,HandlerThread 是一個不錯的選擇。
Camera2 所有的相機操作都可以注冊相關的回調接口,然后在不同的回調方法里寫業務邏輯,這可能會讓你的代碼因為不夠線性而錯綜復雜,建議你可以嘗試使用子線程的阻塞方式來盡可能地保證代碼的線性執行(熟悉 Dart 的人一定很喜歡它的 async 和 await 操作)。例如在子線程阻塞等待 CaptureResult,然后繼續執行后續的操作,而不是將代碼拆分到到 CaptureCallback.onCaptureCompleted() 方法里。
你可以認為 Camera1 是 Camera2 的一個子集,也就是說 Camera1 能做的事情 Camera2 一定能做,反過來則不一定行得通。
如果你的應用程序需要同時兼容 Camera1 和 Camera2,個人建議分開維護,因為 Camera1 蹩腳的 API 設計很可能讓 Camera2 靈活的 API 無法得到充分的發揮,另外將兩個設計上完全不兼容的東西攪和在一起帶來的痛苦可能遠大于其帶來便利性,多寫一些冗余的代碼也許還更開心。
官方說 Camera2 的性能會更好,這句話聽聽就好,起碼在較早期的一些機器上運行 Camera2 的性能并沒有比 Camera1 好。
當設備的 Supported Hardware Level 低于 FULL 的時候,建議還是使用 Camera1,因為 FULL 級別以下的 Camera2 能提供的功能幾乎和 Camera1 一樣,所以倒不如選擇更加穩定的 Camera1。
總結
以上是生活随笔為你收集整理的Camera2 教程 一概览的全部內容,希望文章能夠幫你解決所遇到的問題。