Android 10 引入了可選的相機 HAL3緩衝區管理 API,允許您實現緩衝區管理邏輯,以在相機 HAL 實現中實現不同的內存和捕獲延遲權衡。
相機 HAL 需要在其管道中排隊的 N 個請求(其中 N 等於管道深度),但它通常不需要同時所有 N 組輸出緩衝區。
例如,HAL 可能有 8 個請求在管道中排隊,但它只需要管道最後階段的兩個請求的輸出緩衝區。在運行 Android 9 及更低版本的設備上,當請求在 HAL 中排隊時,相機框架會分配緩衝區,因此 HAL 中可能有六組未使用的緩衝區。在 Android 10 中,相機 HAL3 緩衝區管理 API 允許解耦輸出緩衝區以釋放六組緩衝區。這可以在高端設備上節省數百兆字節的內存,對低內存設備也有好處。
圖 1 顯示了適用於運行 Android 9 及更低版本的設備的相機 HAL 接口圖。圖 2 顯示了 Android 10 中的攝像頭 HAL 接口,其中實現了攝像頭 HAL3 緩衝區管理 API。
圖 1. Android 9 及更低版本中的相機 HAL 接口
圖 2. Android 10 中使用緩衝區管理 API 的相機 HAL 接口
實現緩衝區管理 API
要實現緩衝區管理 API,相機 HAL 必須:
- 實施 HIDL
ICameraDevice@3.5
。 - 將相機特徵鍵
android.info.supportedBufferManagementVersion
設置為HIDL_DEVICE_3_5
。
相機 HAL 使用ICameraDeviceCallback.hal
中的requestStreamBuffers
和returnStreamBuffers
方法來請求和返回緩衝區。 HAL 還必須在ICameraDeviceSession.hal
中實現signalStreamFlush
方法,以向相機 HAL 發出信號以返回緩衝區。
請求流緩衝區
使用requestStreamBuffers
方法從相機框架請求緩衝區。使用相機 HAL3 緩衝區管理 API 時,來自相機框架的捕獲請求不包含輸出緩衝區,即StreamBuffer
中的bufferId
字段為0
。因此,相機 HAL 必須使用requestStreamBuffers
向相機框架請求緩衝區。
requestStreamBuffers
方法允許調用者在一次調用中從多個輸出流中請求多個緩衝區,從而減少 HIDL IPC 調用。但是,當同時請求更多緩衝區時,調用會花費更多時間,這可能會對總請求到結果的延遲產生負面影響。此外,由於對requestStreamBuffers
的調用是在相機服務中序列化的,因此建議相機 HAL 使用專用的高優先級線程來請求緩衝區。
如果緩衝區請求失敗,相機 HAL 必須能夠正確處理非致命錯誤。以下列表描述了緩衝區請求失敗的常見原因以及相機 HAL 應如何處理它們。
- 應用程序與輸出流斷開連接:這是一個非致命錯誤。相機 HAL 應針對任何針對斷開連接的流的捕獲請求發送
ERROR_REQUEST
,並準備好正常處理後續請求。 - 超時:當應用程序忙於進行密集處理同時持有一些緩衝區時,可能會發生這種情況。對於因超時錯誤而無法完成的捕獲請求,相機 HAL 應發送
ERROR_REQUEST
,並準備好正常處理後續請求。 - 攝像頭框架正在準備新的流配置:攝像頭 HAL 應等到下一次
configureStreams
調用完成後,才能再次調用requestStreamBuffers
。 - 相機 HAL 已達到其緩衝區限制(
maxBuffers
字段):相機 HAL 應等到它返回至少一個流的緩衝區,然後再再次調用requestStreamBuffers
。
返回流緩衝區
使用returnStreamBuffers
方法將額外的緩衝區返回給相機框架。相機 HAL 通常通過processCaptureResult
方法將緩衝區返回給相機框架,但它只能考慮已發送到相機 HAL 的捕獲請求。使用requestStreamBuffers
方法,相機 HAL 實現可以保留比相機框架請求的緩衝區更多的緩衝區。這是應該使用returnStreamBuffers
方法的時候。如果 HAL 實現永遠不會擁有比請求更多的緩衝區,則相機 HAL 實現不需要調用returnStreamBuffers
方法。
信號流沖洗
相機框架調用signalStreamFlush
方法來通知相機 HAL 返回手頭的所有緩衝區。這通常在相機框架即將調用configureStreams
並且必須耗盡相機捕獲管道時調用。與returnStreamBuffers
方法類似,如果相機 HAL 實現沒有比請求更多的緩衝區,則此方法的實現可能為空。
相機框架調用signalStreamFlush
後,框架停止向相機 HAL 發送新的捕獲請求,直到所有緩衝區都返回給相機框架。當所有緩衝區都返回時, requestStreamBuffers
方法調用失敗,相機框架可以在乾淨的狀態下繼續工作。然後相機框架調用configureStreams
或processCaptureRequest
方法。如果相機框架調用configureStreams
方法,則在configureStreams
調用成功返回後,相機 HAL 可以再次開始請求緩衝區。如果相機框架調用processCaptureRequest
方法,相機 HAL 可以在processCaptureRequest
調用期間開始請求緩衝區。
signalStreamFlush
方法和flush
方法的語義不同。當調用flush
方法時,HAL 可以使用ERROR_REQUEST
中止掛起的捕獲請求,以盡快耗儘管道。當調用signalStreamFlush
方法時,HAL 必須正常完成所有掛起的捕獲請求,並將所有緩衝區返回給相機框架。
signalStreamFlush
方法與其他方法的另一個區別是signalStreamFlush
是一種單向HIDL 方法,這意味著相機框架可能會在 HAL 接收到signalStreamFlush
調用之前調用其他阻塞 API。這意味著signalStreamFlush
方法和其他方法(特別是configureStreams
方法)可能會以不同於在相機框架中調用它們的順序到達相機 HAL。為了解決這個異步問題, streamConfigCounter
字段被添加到StreamConfiguration
並作為參數添加到signalStreamFlush
方法。相機 HAL 實現應使用streamConfigCounter
參數來確定signalStreamFlush
調用是否晚於其對應的configureStreams
調用到達。有關示例,請參見圖 3。
圖 3.相機 HAL 應如何檢測和處理遲到的 signalStreamFlush 調用
實現緩衝區管理 API 時的行為變化
使用緩衝區管理 API 實現緩衝區管理邏輯時,請考慮以下可能對攝像頭和攝像頭 HAL 實現的行為更改:
捕捉請求更快、更頻繁地到達相機 HAL:如果沒有緩衝區管理 API,相機框架會在向相機 HAL 發送捕捉請求之前為每個捕捉請求請求輸出緩衝區。使用緩衝區管理 API 時,相機框架不再需要等待緩衝區,因此可以更早地向相機 HAL 發送捕獲請求。
此外,如果沒有緩衝區管理 API,如果捕獲請求的輸出流之一已達到 HAL 一次可以容納的最大緩衝區數(此值由相機 HAL 在調用
configureStreams
的返回值中的HalStream::maxBuffers
字段)。使用緩衝區管理 API,這種限制行為不再存在,並且當 HAL 排隊的捕獲請求過多時,相機 HAL 實現不得接受processCaptureRequest
調用。requestStreamBuffers
調用延遲差異很大:requestStreamBuffers
調用可能需要比平均時間更長的時間有很多原因。例如:- 對於新創建的流的前幾個緩衝區,調用可能需要更長時間,因為設備需要分配內存。
- 預期延遲與每次調用中請求的緩衝區數量成比例增加。
- 該應用程序正在保存緩衝區並且正忙於處理。由於缺少緩衝區或 CPU 繁忙,這可能會導致緩衝區請求變慢或超時。
緩衝區管理策略
緩衝區管理 API 允許實現不同類型的緩衝區管理策略。一些例子是:
- 向後兼容: HAL 在
processCaptureRequest
調用期間為捕獲請求請求緩衝區。此策略不會節省任何內存,但可以用作緩衝區管理 API 的第一個實現,只需對現有相機 HAL 進行極少的代碼更改。 - 最大限度地節省內存:相機 HAL 僅在需要填充緩衝區之前立即請求輸出緩衝區。這種策略可以最大限度地節省內存。當緩衝區請求需要非常長的時間才能完成時,潛在的缺點是更多的相機管道卡頓。
- 已緩存:相機 HAL 緩存了一些緩衝區,因此不太可能受到偶爾緩慢的緩衝區請求的影響。
相機 HAL 可以針對特定用例採用不同的策略,例如,對使用大量內存的用例使用最大化內存節省策略,對其他用例使用向後兼容策略。
外置攝像頭 HAL 中的示例實現
外部攝像頭 HAL 是在 Android 9 中引入的,可以在hardware/interfaces/camera/device/3.5/
的源代碼樹中找到。在 Android 10 中,它已更新為包含ExternalCameraDeviceSession.cpp
,這是緩衝區管理 API 的實現。這個外置攝像頭 HAL 用幾百行 C++ 代碼實現了緩衝區管理策略中提到的最大化內存節省策略。