Android Camera HAL3 - MultiCamera-HALBufferManager

從 Android10 開始,camera 系統加入了一個可選地 buffer 管理方式,可以在 Vendor HAL 這邊靈活使用這個選項進行 buffer 管理,以此達到減少 buffer 使用峯值,改變 request 執行速度等優點。具體的來說就是對於 HAL request queue 中的每一個 request 來講,並不是每一個 request 的每一個 buffer 都是被使用到的,有些 request 可能就沒有用到 buffer。說到這裏想起關於 buffer 使用量的幾個問題,一個是 buffer 使用總量,一個是 buffer 使用峯值。

Buffer 使用總量顧名思義就是一個系統中最多可能用到的 buffer 數量,這個是系統設計之初就已經定好的,申請 buffer pool 的時候應該申請等量的 buffer 空間,不然就可能會造成處理錯誤。Buffer 使用峯值就是某一時刻整個系統同時在用的 buffer 最大值,這個值是小於等於 Buffer 使用總量這個值的。那我們要優化一個系統就需要同時減少兩者的值,使用總量減少很能夠理解,可以節省空間,但是峯值爲什麼要減少呢?如果某一時刻系統處理達到了峯值,那麼就可能會阻塞接下來的處理請求,因爲要等待空閒出來的 buffer 嘛,想象下如果我們是一個流式處理系統,如果總是一開始就全部申請設置好 buffer,那就會導致大量的 buffer 在大部分時間狀況下沒有用到,這浪費了很多的等待時間,所以也是爲什麼要減少峯值的原因。

對於 Google Android 的文檔介紹,舉了一個例子:假設說 HAL 可能有 8 個 request 正在隊列裏面等待處理,但是隻在最後兩個 request 才需要申請 buffer。在 Android 9 和 9 以下的版本,這 8 個 request 的 buffer 都是提前分配好的,所以這個時候有 6 個 request 的 buffer 都是沒用使用的,Android 10 往後的版本就支持把 buffer 使用和 request 分離開,不用每次都提前綁定,而是用到了才進行綁定。這在高端平臺可以節省很多 buffer 使用,並且對於低內存的平臺也是很有幫助的,至於爲什麼會有幫助,就是上面說的峯值這塊,減少峯值一定程度上也是可以減少最大值的。

下面兩個圖是從官網搞過來的:
D:\我的文章\微信公衆號\Android Camera HAL3\Multi-Camera\Multi-Camera\buffer-management-9.png

Android 10

可以看到第二個圖裏面的 buffer 是實際使用到的時候才進行配對處理的,這樣可以節省很多 buffer 的使用,甚至對於每次申請不同類型 buffer 的 request 來說也可以針對 buffer 類型的不同來申請,因爲同一個 request 裏面的 buffer 是有一定可能並非全部都用得上的,有些用不上的就可以不去申請空間了。

實現 buffer management

對於 HAL 層來講需要實現下面幾個點:

  1. 實現 HIDL 接口,[email protected],這部分其實是 Android HAL 層的部分,Vendor HAL 不用管這裏先。
  2. android.info.supportedBufferManagementVersion 的值改爲 HIDL_DEVICE_3_5。這個值在 /platform/hardware/interfaces/camera/metadata/3.4/types.hal 裏面。

Vendor HAl 可以使用的接口有:requestStreamBuffers, returnStreamBuffers 兩個分別用於請求、釋放 buffer,另外還需要 signalStreamFlush 接口,這個接口是在 Vendor HAL 那邊實現的,前面兩個接口都是 Android HAL 實現好了給到 Vendor HAL 那邊進行回調的,最後一個是 framework 往 Vendor HAL 調用的,用於返還全部 Vendor HAL 持有的 buffer,這個調用明顯就是要在 configure_stream 或者 flush 前後進行的,用於一次性回收全部的 buffer。

requestStreamBuffers

這個是由 Vendor HAL 主動調用的,其函數實體是在 Android HAL 那邊實現的,會在 device 初始化的時候作爲 camera3_callback_ops_t 類型結構體成員傳遞 Vendor HAL,通常情況下這個需要在 process_capture_request 的時候進行調用,用以申請相關所需要的 buffer。如果設置了使用 Android HAL 的 buffer manager,在下發 request 的時候是不會帶 buffer 的,這個時候必須得使用該接口去獲取 buffer,否則會造成致命錯誤。

該函數可以一次請求多個 stream 類型的多個 buffer,該函數的參數主要包含了一對輸入,一對輸出,其中關鍵結構體如下所示:

struct camera3_buffer_request {
    camera3_stream_t *stream;
    uint32_t num_buffers_requested;
}

struct camera3_stream_buffer_ret {
    camera3_stream_t *stream;
    camera3_stream_buffer_req_status_t status;
    uint32_t num_output_buffers;
    camera3_stream_buffer_t *output_buffers;
}
  1. 輸入:需請求的 camera2_buffer_request 數量,以及存放該結構體信息的數組。
  2. 輸出:返回的 camera3_stream_buffer_ret 數量,以及存放該結構體信息的數組。

輸入輸出基本上都是一個套路,就是首先區分 stream,不同的 stream buffer 歸攏到一塊,設置其數量,然後把不同 stream 再放在一個結構體裏面排排隊作爲數組傳遞給 Android HAL。需要注意的是返回的信息裏面有一個 camera3_stream_buffer_req_status_t 信息,這個是表明本次 buffer 申請成功與否的標誌,大致上包含以下幾個方面:

  1. OK:不用說看長相就是很成功了。
  2. FAILED_PARTIAL:這個就要 check 下每一個返回的 camera3_stream_buffer_ret 結構體了,說明是有的成功有的失敗了,但是這個不是細化到每一個 camera3_stream_buffer_t 層級的,一個 stream 類型失敗那就說明這個類型下面全部的 buffer 都不可用。
  3. FAILED_CONFIGURING:沒有任何一個 buffer 返回,一個都沒有。據註解是說這個時候可能 Android HAL 那邊正在進行或者將要進行 configure_streams 操作了,Vendor HAL 需要等到最近一次 configure_streams 操作完成之後再去請求。
  4. FAILED_ILLEGAL_ARGUMENTS:是說明我們輸入的參數不對,比如根本找不到輸入的 stream 類型,這個時候也是一個 buffer 返回都沒有的,我們請求的 stream 必須是 configure_streams 的時候傳遞下來的 stream,不能是其它的。
  5. FAILED_UNKNOW:不知名錯誤,或者每一個 stream 的錯誤類型都不一樣,這種錯誤說明也是一個 buffer 都沒回來,需要檢查每一個 stream 對應的錯誤情況。

由於該函數請求在 camera service 那邊是串行化的,也就是會 block 多個的請求,並且請求的 buffer 越多就越可能會導致更長的等待時間,綜合來講,需要 Vendor HAL 以及 Android HAL 那邊使用同一個高優先級的線程進行該調用。對於輸出參數而言,Vendor HAL 需要申請好預先設定的空間,然後交給 Android HAL 去進行數據填充。

該請求可能會出現一些非致命的問題,調用者也需要能夠適當進行處理,下面的錯誤都是整個函數的返回值決定的,返回值類型是 camera3_buffer_request_status_t

  1. App disconnects from the output stream:這個是非致命的錯誤,如果出現該種情況,Vendor HAL 需要返回一個 ERROR_REQUEST 錯誤,只要該 request 包含有 disconnect stream,那就得返回一個這樣的錯誤值。
  2. Timeout:這個可能是因爲 APP 正忙着做別的事情,所以也要返回一個 ERROR_REQUEST 錯誤,但是這個並不影響後續的處理流程,只在當前 request 返回一個錯誤即可,後續的還按照正常的步驟進行處理。
  3. Camera framework is preparing a new stream configuration:這個就需要先等待 configure_streams 跑完之後再進行 buffer 申請。
  4. The camera HAL has reached its buffer limit (the maxBuffers field):說明 buffer 用量達到最大值了,這個時候應該等着,知道有新的 buffer 可用再去申請。關於怎麼知道可用這個問題,一般我們返回 buffer 是用 process_capture_result 返回的,所以可以在每一個 process_capture_result 調用之後喚醒重新請求 buffer。

returnStreamBuffers

該接口可以返回 buffer 給到 framework,正常情況下來講,都是通過 process_capture_result 函數來進行 buffer 返回的,但是有些時候 Vendor HAL 可能請求了多於實際用到的 buffer 數量,這個時候正常返回接口就沒有辦法返回全部的 buffer,只能通過該接口進行剩餘 buffer 的返還操作。如果 Vendor HAL 在實現上就不會 hold 多餘 buffer,那麼本接口就可以忽略掉。

signalStreamFlush

這個有一點類似於 returnStreamBuffers,只不過本接口表明 framework 想要拿回所有的 buffer,此時一般都是在 configure_streams 前後,當然如果 Vendor HAL 也還是從不拿多餘的 buffer,那麼該接口也可以不用實現,就置爲空就可以了。當 framework 調用該接口的時候,就不會再繼續下發 request 了,並且等到所有的 buffer 返回到 framework 之後,Vendor HAL request buffer 也是會失敗的。

本接口與 flush 有所區別,flush 是需要給所有 pending 的 request 返回一個 ERROR_REQUEST 的錯誤,而本接口是需要正常完成所有的 request 的。並且有一個很重要的一點,就是本接口它是相對獨立的一個 HIDL 接口,調用的時候可能本接口先調用,但是 configure_streams 調用先被執行,如果沒有任何同步裝置的話,就會造成執行錯亂繼而引起程序崩潰,一個方法是 framework 在本接口的參數裏面加入了一個 streamConfigCounter 參數,Vendor HAL 需要判斷這個參數來看是否是在 configure_streams 調用之後的時間調用的,看下圖示例:
在這裏插入圖片描述

行爲變化

使用 buffer management API 實現 buffer 管理邏輯,需要考慮下以下的行爲改變:

  • Capture request:framework 會更快的發 request 到 camera Vendor HAL 那邊,如果沒有 buffer management,framework 由於不需要綁定 buffer 到 request 那邊,所以下發 request 的速度會更快。並且沒有 buffer management 的話,framework 在達到 buffer 最大數量的時候會停止下發 request 到 Vendor HAL 那邊,但是有了 buffer management,這個機制就不存在了,也就是說 framework 會持續不斷地下發 request,當然 Vendor HAL 那邊一定要在 request Queue 達到 Vendor HAL 的最大值的時候拒絕接受新的下發 request。
  • requestStreamBuffers 調用延遲:在以下幾種情況下可能會發生調用被阻塞延遲:
    • 對於第一次創建 buffer 的 stream 來說,需要更長時間來分配 buffer 空間。
    • 申請越多的 buffer 就會導致越長的時間延遲。
    • APP 正在忙於其它事情,這個時候可能會造成 buffer request 速度變慢,並且有一定可能觸發 timeout。

Buffer management 策略

Buffer management API 允許不同的策略實現,下面就是一些例子:

  • 後向兼容:Vendor HAL 在 process_capture_request 的時候就去申請 buffer,這樣做並不會節省 buffer 使用,但是可以作爲一個初步的實現,這部分只需要更改很少量的代碼即可。
  • 最大化節省內存:只有在真正用到 buffer 時纔去申請,這個要結合 Vendor HAL 內部 pipeline 的實現去整合,這部分是需要一個系統性的改動的,我立刻就能想到兩種解決方向,一種是在 request 的時候就請求多個 buffer 作爲緩衝池,然後內部 pipeline 在用到的時候去緩衝池裏面取 buffer,這個重點是如何控制緩衝池 buffer 數量。第二種就是用到了的時候去 framework 取,這種需要一個貫穿 Vendor HAL 底層到 framework 的調用來實現纔可以。
  • 緩存 buffer:Vendor HAL 可以 cache 一些 buffer 以避免用到的時候纔去取用會導致的延遲問題(這裏也就凸顯了 signalStreamFlush 的作用)。

End

這個一定程度上來講是個挺有用的 feature,應該是慢慢標準化並且支持進去,現在我看還是可選的一個選項,並且是有一部分示例代碼,還不是標準化流程代碼。說起 buffer 管理這玩意兒,我覺得對於視頻流產品模塊來講,有 70% 以上的代碼都是在實現 buffer 的流轉、管理,掌握一個視頻流模塊框架,一部分程度上等同於掌握其 buffer 管理的設計與實現。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章