深入理解Camera 六(硬件抽象層實現(xiàn))

和你一起終身學習,這里是程序員Android
經(jīng)典好文推薦,通過閱讀本文,您將收獲以下知識點:
一、概覽
二、基本組件概念
三、組件結構關系
四、關鍵流程詳解
相機硬件抽象層實現(xiàn)
一、概覽
回顧高通平臺Camera HAL歷史,之前高通采用的是QCamera & MM-Camera架構,但是為了更精細化控制底層硬件(Sensor/ISP等關鍵硬件),同時方便手機廠商自定義一些功能,現(xiàn)在提出了CamX-CHI架構,由于在CamX-CHI中完全看不到之前老架構的影子,所以它完全是一個全新的架構,它將一些高度統(tǒng)一的功能性接口抽離出來放到CamX中,將可定制化的部分放在CHI中供不同廠商進行修改,實現(xiàn)各自獨有的特色功能,這樣設計的好處顯而易見,那便是即便開發(fā)者對于CamX并不是很了解,但是依然可以很方便的加入自定義的功能,從而降低了開發(fā)者在高通平臺的開發(fā)門檻。
接下來我們以最直觀的目錄結構入手對該架構做一個簡單的認識,以下便是CamX-CHI基本目錄結構:

程序員Android轉于網(wǎng)絡
該部分代碼主要位于 vendor/qcom/proprietary/ 目錄下:
其中 camx 代表了通用功能性接口的代碼實現(xiàn)集合(CamX),chi-cdk代表了可定制化需求的代碼實現(xiàn)集合(CHI),從圖中可以看出Camx部分對上作為HAL3接口的實現(xiàn),對下
通過v4l2框架與Kernel保持通訊,中間通過互相dlopen so庫并獲取對方操作接口的方式保持著與CHI的交互。
camx/中有如下幾個主要目錄:
core/ :用于存放camx的核心實現(xiàn)模塊,其中還包含了主要用于實現(xiàn)hal3接口的hal/目錄,以及負責與CHI進行交互的chi/目錄
csl/:用于存放主要負責camx與camera driver的通訊模塊,為camx提供了統(tǒng)一的Camera driver控制接口
hwl/: 用于存放自身具有獨立運算能力的硬件node,該部分node受csl管理
swl/: 用于存放自身并不具有獨立運算能力,必須依靠CPU才能實現(xiàn)的node
chi-cdk/中有如下幾個主要目錄:
chioverride/: 用于存放CHI實現(xiàn)的核心模塊,負責與camx進行交互并且實現(xiàn)了CHI的總體框架以及具體的業(yè)務處理。
bin/: 用于存放平臺相關的配置項
topology/: 用于存放用戶自定的Usecase xml配置文件
node/: 用于存放用戶自定義功能的node
module/: 用于存放不同sensor的配置文件,該部分在初始化sensor的時候需要用到
tuning/: 用于存放不同場景下的效果參數(shù)的配置文件
sensor/: 用于存放不同sensor的私有信息以及寄存器配置參數(shù)
actuator/: 用于存放不同對焦模塊的配置信息
ois/:用于存放防抖模塊的配置信息
flash/:存放著閃光燈模塊的配置信息
eeprom/: 存放著eeprom外部存儲模塊的配置信息
fd/: 存放了人臉識別模塊的配置信息
二、基本組件概念
1. Usecase
作為CamX-CHI中最大的抽象概念,其中包含了多條實現(xiàn)特定功能的Pipeline,具體實現(xiàn)是在CHI中通過Usecase類完成的,該類主要負責了其中的業(yè)務處理以及資源的管理。
Usecase類,提供了一系列通用接口,作為現(xiàn)有的所有Usecase的基類,其中,AdvancedCameraUsecase又繼承于CameraUsecaseBase,相機中絕大部分場景會通過實例化AdvancedCameraUsecase來完成,它包括了幾個主要接口:
Create(): 該方法是靜態(tài)方法,用于創(chuàng)建一個AdvancedCameraUsecase實例,在其構造方法中會去獲取XML中的相應的Usecase配置信息。
ExecuteCaptureRequest(): 該方法用于下發(fā)一次Request請求。
ProcessResultCb(): 該方法會在創(chuàng)建Session的過程中,作為回調方法注冊到其中,一旦Session數(shù)據(jù)處理完成的時候便會調用該方法將結果發(fā)送到AdvancedCameraUsecase中。
ProcessDriverPartialCaptureResult(): 該方法會在創(chuàng)建Session的過程中,作為回調方法注冊到其中,一旦Session中產生了partial meta data的時候,便會調用該方法將其發(fā)送至AdvancedCameraUsecase中。
ProcessMessageCb(): 該方法會在創(chuàng)建Session的過程中,作為回調方法注冊到其中,一旦Session產生任何事件,便會調用該方法通知到AdvancedCameraUsecase中。
ExecuteFlush(): 該方法用于刷新AdvancedCameraUsecase。
Destroy(): 該方法用于安全銷毀AdvancedCameraUsecase。
Usecase的可定制化部分被抽象出來放在了common_usecase.xml文件中,這里簡單介紹其中的幾個主要的標簽含義:
Usecase
UsecaseName: 代表了該Usecase的名字,后期根據(jù)這個名字找到這個Usecase的定義。
Targets: 用于表示用于輸出的數(shù)據(jù)流的集合,其中包括了數(shù)據(jù)流的格式,輸出Size的范圍等。
Pipeline: 用于定義該Usecase可以是使用的所有Pipeline,這里必須至少定義一條Pipeline。
2. Feature
代表了一個特定的功能,該功能需要多條Pipeline組合起來實現(xiàn),受Usecase統(tǒng)一管理,在CHI中通過Feature類進行實現(xiàn),在XML中沒有對應的定義,具體的Feature選取工作是在Usecase中完成的,通過在創(chuàng)建Feature的時候,傳入Usecase的實例的方式,來和Usecase進行相互訪問各自的資源。
以下是現(xiàn)有的Feature,其中Feature作為基類存在,定義了一系列通用方法。

程序員Android轉于網(wǎng)絡
幾個常用的Feature:
FeatureHDR: 用于實現(xiàn)HDR功能,它負責管理內部的一條或者幾條pipeline的資源以及它們的流轉,最終輸出具有HDR效果的圖像。
FeatureMFNR: 用于實現(xiàn)MFNR功能,內部分為幾個大的流程,分別包括Prefiltering、Blending、Postfilter以及最終的OfflineNoiseReproces(這一個是可選擇使能的),每一個小功能中包含了各自的pipeline。
FeatureASD: 用于AI功能的實現(xiàn),在預覽的時候,接收每一幀數(shù)據(jù),并且進行分析當前場景的AI識別輸出結果,并其通過諸如到metadata方式給到上層,進行后續(xù)的處理。
3. Session
用于管理pipeline的抽象控制單元,一個Session中至少擁有一個pipeine,并且控制著所有的硬件資源,管控著每一個內部pipeline的request的流轉以及數(shù)據(jù)的輸入輸出,它沒有可定制化的部分,所以在CHI中的XML文件中并沒有將Session作為一個獨立的單元進行定義。
Session的實現(xiàn)主要通過CamX中的Session類,其主要接口如下:
Initialize(): 根據(jù)傳入的參數(shù)SessionCreateData進行Session的初始化工作。
NotifyResult(): 內部的Pipeline通過該接口將結果發(fā)送到Session中。
ProcessCaptureRequest(): 該方法用于用戶決定發(fā)送一個Request到Session中的時候調用。
StreamOn(): 通過傳入的Pipeline句柄,開始硬件的數(shù)據(jù)傳輸。
StreamOff(): 通過傳入的Pipeline句柄,停止硬件的數(shù)據(jù)傳輸。
4. Pipeline
作為提供單一特定功能的所有資源的集合,維護著所有硬件資源以及數(shù)據(jù)的流轉,每一個Pipeline包括了其中的Node/Link,在CamX中通過Pipeline類進行實現(xiàn),負責整條Pipeline的軟硬件資源的維護以及業(yè)務邏輯的處理,接下來我們簡單看下該類的幾個主要接口:
Create(): 該方法是一個靜態(tài)方法,根據(jù)傳入的PipelineCreateInputData信息來實例化一個Pipeline對象。
StreamOn(): 通知Pipeline開始硬件的數(shù)據(jù)傳輸
StreamOff(): 通知Pipeline停止硬件的數(shù)據(jù)傳輸
FinalizePipeline(): 用于完成Pipeline的設置工作
OpenRequest(): open一個CSL用于流轉的Request
ProcessRequest(): 開始下發(fā)Request
NotifyNodeMetadataDone(): 該方法是Pipeline提供給Node,當Node內部生成了metadata,便會調用該方法來通知metadata已經(jīng)完成,最后當所有Node都通知Pipeline metadata已經(jīng)完成,Pipeline 便會調用ProcessMetadataRequestIdDone通知Session。
NotifyNodePartialMetadataDone(): 該方法是Pipeline提供給Node,當Node內部生成了partial metadata,便會調用該方法來通知metadata已經(jīng)完成,最后當所有Node都通知Pipeline metadata已經(jīng)完成,Pipeline 便會調用ProcessPartialMetadataRequestIdDone通知Session。
SinkPortFenceSignaled(): 用來通知Session 某個sink port的fence處于被觸發(fā)的狀態(tài)。
NonSinkPortFenceSignaled(): 用來通知Session 某個non sink port的fence處于被觸發(fā)的狀態(tài)。
Pipeline中的Node以及連接方式都在XML中被定義,其主要包含了以下幾個標簽定義:
PipelineName: 用來定義該條Pipeline的名稱
NodeList: 該標簽中定義了該條Pipeline的所有的Node
PortLinkages: 該標簽定義了Node上不同端口之間的連接關系
5. Node
作為單個具有獨立處理功能的抽象模塊,可以是硬件單元也可以是軟件單元,關于Node的具體實現(xiàn)是CamX中的Node類來完成的,其中CamX-CHI中主要分為兩個大類,一個是高通自己實現(xiàn)的Node包括硬件Node,一個是CHI中提供給用戶進行實現(xiàn)的Node,其主要方法如下:
Create(): 該方法是靜態(tài)方法,用于實例化一個Node對象。
ExecuteProcessRequest(): 該方法用于針對hwl node下發(fā)request的操作。
ProcessRequestIdDone(): 一旦該Node當前request已經(jīng)處理完成,便會通過調用該方法通知Pipeline。
ProcessMetadataDone(): 一旦該Node的當前request的metadata已經(jīng)生成,便會通過調用該方法通知到Pipeline。
ProcessPartialMetadataDone(): 一旦該Node的當前request的partial metadata已經(jīng)生成,便會通過調用該方法通知到Pipeline。
CreateImageBufferManager(): 創(chuàng)建ImageBufferManager
其可定制化的部分作為標簽在XML中進行定義:
NodeName:用來定義該Node的名稱
NodeId: 用來指定該Node的ID,其中IPE NodeId為65538,IFE NodeId為65536,用戶自定義的NodeId為255。
NodeInstance: 用于定義該Node的當前實例的名稱。
NodeInstanceId: 用于指定該Node實例的Id。
6. Link
用于定義不同Port的連接,一個Port可以根據(jù)需要建立多條與其它從屬于不同Node的Port的連接,它通過標簽來進行定義,其中包括了作為輸入端口,作為輸出端口。
一個Link中包含了一個SrcPort和一個DstPort,分別代表了輸入端口和輸出端口,然后BufferProperties用于表示兩個端口之間的buffer配置。
7. Port
作為Node的輸入輸出的端口,在XML文件中,標簽用來定義一個輸入端口,標簽用來定義輸出端口,每一個Node都可以根據(jù)需要使用一個或者多個輸入輸出端口,使用OutputPort以及InputPort結構體來進行在代碼中定義。
Port
PortId: 該端口的Id: 該端口的名稱
NodeName: 該端口從屬的Node名稱
NodeId: 該端口從屬的Node的Id
NodeInstance: 該端口從屬的Node的實例名稱
NodeInstanceId: 該端口從屬的Node的實例的Id
三、組件結構關系
通過之前的介紹,我們對于幾個基本組件有了一個比較清晰地認識,但是任何一個框架體系并不是僅靠組件胡亂堆砌而成的,相反,它們都必須基于各自的定位,按照各自所獨有的行為模式,同時按照約定俗稱的一系列規(guī)則組合起來,共同完成整個框架某一特定的功能。所以這里不得不產生一個疑問,在該框架中它們到底是如何組織起來的呢?它們之間的關系又是如何的呢?接下來我們以下圖入手開始進行分析:

程序員Android轉于網(wǎng)絡
由上圖可以看到,幾者是通過包含關系組合起來的,Usecase 包含F(xiàn)eature,而Feature包含了Session,Session又維護了內部的Pipeline的流轉,而每一條pipeline中又通過Link將所有Node都連接了起來,接下我們就這幾種關系詳細講解下:
首先,一個Usecase代表了某個特定的圖像采集場景,比如人像場景,后置拍照場景等等,在初始化的時候通過根據(jù)上層傳入的一些具體信息來進行創(chuàng)建,這個過程中,一方面實例化了特定的Usecase,這個實例是用來管理整個場景的所有資源,同時也負責了其中的業(yè)務處理邏輯,另一方面,獲取了定義在XML中的特定Usecase,獲取了用于實現(xiàn)某些特定功能的pipeline。
其次,在Usecase中,F(xiàn)eature是一個可選項,如果當前用戶選擇了HDR模式或者需要在Zoom下進行拍照等特殊功能的話,在Usecase創(chuàng)建過程中,便會根據(jù)需要創(chuàng)建一個或者多個Feature,一般一個Feature對應著一個特定的功能,如果場景中并不需要任何特定的功能,則也完全可以不使用也不創(chuàng)建任何Feature。
然后,每一個Usecase或者Feature都可以包含一個或者多個Session,每一個Session都是直接管理并負責了內部的Pipeline的數(shù)據(jù)流轉,其中每一次的Request都是Usecase或者Featuret通過Session下發(fā)到內部的Pipeline進行處理,數(shù)據(jù)處理完成之后也是通過Session的方法將結果給到CHI中,之后是直接給到上層還是將數(shù)據(jù)封裝下再次下發(fā)到另一個Session中進行后處理,這都交由CHI來決定。
其中,Session和Pipeline是一對多的關系,通常一個Session只包含了一條Pipeline,用于某個特定圖像處理功能的實現(xiàn),但是也不絕對,比如FeatureMFNR中包含的Session就包括了三條pipeline,又比如后置人像預覽,也是用一個Session包含了兩條分別用于主副雙攝預覽的Pipeline,主要是要看當前功能需要的pipeline數(shù)量以及它們之間是否存在一定關聯(lián)。
同時,根據(jù)上面關于Pipeline的定義,它內部包含了一定數(shù)量的Node,并且實現(xiàn)的功能越復雜,所包含的Node也就越多,同時Node之間的連接也就越錯綜復雜,比如后置人像預覽虛化效果的實現(xiàn)就是將拿到的主副雙攝的圖像通過RTBOfflinePreview這一條Pipeline將兩幀圖像合成一幀具有虛化效果的圖像,從而完成了虛化功能。
最后Pipeline中的Node的連接方式是通過XML文件中的Link來進行描述的,每一個Link定義了一個輸入端和輸出端分別對應著不同Node上面的輸入輸出端口,通過這種方式就將其中的一個Node的輸出端與另外一個Node的輸入端,一個一個串聯(lián)起來,等到圖像數(shù)據(jù)從Pipeline的起始端開始輸入的時候,便可以按照這種定義好的軌跡在一個一個Node之間進行流轉,而在流轉的過程中每經(jīng)過一個Node都會在內部對數(shù)據(jù)進行處理,這樣等到數(shù)據(jù)從起始端一直流轉到最后一個Node的輸出端的時候,數(shù)據(jù)就經(jīng)過了很多次處理,這些處理效果最后疊加在一起便是該Pipeline所要實現(xiàn)的功能,比如降噪、虛化等等。
四、關鍵流程詳解
1. Camera Provider 啟動初始化
當系統(tǒng)啟動的時候,Camera Provider主程序會被運行,在整個程序初始化的過程中會通過獲取到的camera_module_t調用其get_number_of_camera接口獲取底層支持的camera數(shù)量,由于是第一次獲取,所以在CamX-CHI中會伴隨著很多初始化動作,具體操作見下圖:

程序員Android轉于網(wǎng)絡
主要流程如下:
通過HAL3Module::GetInstance()靜態(tài)方法實例化了HAL3Module對象,在其構造方法里面通過HwEnvironment::GetInstance()靜態(tài)方法又實例化了HwEnvironment對象,在其構造方法中,實例化了SettingsManager對象,然后又在它構造方法中通過OverrideSettingsFile對象獲取了位于/vendor/etc/camera/camoverridesettings.txt文件中的平臺相關的配置信息(通過這種Override機制方便平臺廠商加入自定義配置),該配置文件中,可以加入平臺特定的配置項,比如可以通過設置multiCameraEnable的值來表示當前平臺是否支持多攝,或者通過設置overrideLogLevels設置項來配置CamX-CHI部分的Log輸出等級等等。
同時在HwEnvironment構造方法中會調用其Initialize方法,在該方法中實例化了CSLModeManager對象,并通過CSLModeManager提供的接口,獲取了所有底層支持的硬件設備信息,其中包括了Camera Request Manager、CAPS模塊(該驅動模塊主要用于CSL獲取Camera平臺驅動信息,以及IPE/BPS模塊的電源控制)以及Sensor/IPE/Flash等硬件模塊,并且通過調用CSLHwInternalProbeSensorHW方法獲取了當前設備安裝的Sensor模組信息,并且將獲取的信息暫存起來,等待后續(xù)階段使用,總得來說在HwEnvironment初始化的過程中,通過探測方法獲取了所有底層的硬件驅動模塊,并將其信息存儲下來供后續(xù)階段使用。
之后通過調用HwEnvironment對象中的ProbeChiCompoents方法在/vendor/lib64/camera/components路徑下找尋各個Node生成的So庫,并獲取Node提供的標準對外接口,這些Node不但包括CHI部分用戶自定義的模塊,還包括了CamX部分實現(xiàn)的硬件模塊,并最后都將其都存入ExternalComponentInfo對象中,等待后續(xù)階段使用。
另外在初始化階段還有一個比較重要的操作就是CamX 與CHI是通過互相dlopen對方的So庫,獲取了對方的入口方法,最后通過彼此的入口方法獲取了對方操作方法集合,之后再通過這些操作方法與對方進行通訊,其主要流程見下圖:

程序員Android轉于網(wǎng)絡
從上圖不難看出,在HAL3Module構造方法中會去通過dlopen方法加載com.qti.chi.override.so庫,并通過dlsym映射出CHI部分的入口方法chi_hal_override_entry,并調用該方法將HAL3Module對像中的成員變量m_ChiAppCallbacks(CHIAppCallbacks)傳入CHI中,其中包含了很多函數(shù)指針,這些函數(shù)指針分別對應著CHI部分的操作方法集中的方法,一旦進入到CHI中,就會將CHI本地的操作方法集合中的函數(shù)地址依次賦值給m_ChiAppCallbacks,這樣CamX后續(xù)就可以通過這個成員變量調用到CHI中方法,從而保持了與CHI的通訊。
同樣地,CHI中的ExtensionModule在初始化的時候,其構造方法中也會通過調用dlopen方法加載camera.qcom.so庫,并將其入口方法ChiEntry通過dlsym映射出來,之后調用該方法,將g_chiContextOps(ChiContextOps,該結構體中定義了很多指針函數(shù))作為參數(shù)傳入CamX中,一旦進入CamX中,便會將本地的操作方法地址依次賦值給g_chiContextOps中的每一個函數(shù)指針,這樣CHI之后就可以通過g_chiContextOps訪問到CamX方法。
2. 打開相機設備/初始化相機設備
一旦用戶打開了相機應用,App中便會去調用CameraManager的openCamera方法,該方法之后會最終調用到Camera Service中的CameraService::connectDevice方法,然后通過ICameraDevice::open()這一個HIDL接口通知Provider,然后在Provider內部又通過調用之前獲取的camera_module_t中methods的open方法來獲取一個Camera 設備,對應于HAL中的camera3_device_t結構體,緊接著,在Provider中會繼續(xù)調用獲取到的camera3_device_t的initialize方法進行初始化動作。接下來我們便來詳細分析下CamX-CHI對于open以及initialize的具體實現(xiàn)流程:
a) open
該方法是camera_module_t的標準方法,主要用來獲取camera3_device_t設備結構體的,CamX-CHI對其進行了實現(xiàn),open方法中完成的工作主要有以下幾個:
將當前camera id傳入CHI中進行remap操作,當然這個remap操作邏輯完全是根據(jù)CHI中用戶需求來的,用戶可以根據(jù)自己的需要在CHI中加入自定義remap邏輯。
實例化HALDevice對象,其構造函數(shù)中調用Initialize方法,該方法會填充CamX中自定義的Camera3Device結構體。
將m_HALCallbacks.process_capture_result指向了本地方法ProcessCaptureResult以及m_HALCallbacks.notify_result指向了本地方法Notify(之后會在配置數(shù)據(jù)流的過程中,將m_HALCallbacks注冊到CHI中, 一旦當CHI數(shù)據(jù)處理完成之后,便會通過這兩個回調方法將數(shù)據(jù)或者事件回傳給CamX)。
最后將HALDevice 中的Camera3Device成員變量作為返回值給到Provider中的CameraCaptureSession中。
Camera3Device 其實重定義了camera3_device_t,其中HwDevice對應于camera3_device_t中的hw_device_t,Camera3DeviceOps對應于camera3_device_ops_t,而在HALDevice的初始化過程中,會將CamX實現(xiàn)的HAL3接口的結構體g_camera3DeviceOps賦值給Camera3DeviceOps中。
b) initialize
該方法在調用open后緊接著被調用,主要用于將上層的回調接口傳入HAL中,一旦有數(shù)據(jù)或者事件產生,CamX便會通過這些回調接口將數(shù)據(jù)或者事件上傳至調用者,其內部的實現(xiàn)較為簡單。
initialize方法中有兩個參數(shù),分別是之前通過open方法獲取的camera3_device_t結構體和實現(xiàn)了camera3_callback_ops_t的CameraDevice,很顯然camera3_device_t結構體并不是重點,所以該方法的主要工作是將camera3_callback_ops_t與CamX關聯(lián)上,一旦數(shù)據(jù)準備完成便通過這里camera3_callback_ops_t中回調方法將數(shù)據(jù)回傳到Camera Provider中的CameraDevice中,基本流程可以總結為以下幾點:
實例化了一個Camera3CbOpsRedirect對象并將其加入了g_HAL3Entry.m_cbOpsList隊列中,這樣方便之后需要的時候能夠順利拿到該對象。
將本地的process_capture_result以及notify方法地址分別賦值給Camera3CbOpsRedirect.cbOps中的process_capture_result以及notify函數(shù)指針。
將上層傳入的回調方法結構體指針pCamera3CbOpsAPI賦值給Camera3CbOpsRedirect.pCbOpsAPI,并將Camera3CbOpsRedirect.cbOps賦值給pCamera3CbOpsAPI,通過JumpTableHal3的initialize方法將pCamera3CbOpsAPI傳給HALDevice中的m_pCamera3CbOps成員變量,這樣HALDevice中的m_pCamera3CbOps就指向了CamX中本地方法process_capture_result以及notify。
經(jīng)過這樣的一番操作之后,一旦CHI有數(shù)據(jù)傳入便會首先進入到本地方法ProcessCaptureResult,然后在該方法中獲取到HALDevice的成員變量m_pCamera3CbOps,進而調用m_pCamera3CbOps中的process_capture_result方法,即camxhal3entry.cpp中定義的process_capture_result方法,然后這個方法中會去調用JumpTableHAL3.process_capture_result方法,該方法最終會去調用Camera3CbOpsRedirect.pCbOpsAPI中的process_capture_result方法,這樣就調到從Provider傳入的回調方法,將數(shù)據(jù)順利給到了CameraCaptureSession中。
3. 配置相機設備數(shù)據(jù)流
在打開相機應用過程中,App在獲取并打開相機設備之后,會調用CameraDevice.createCaptureSession來獲取CameraDeviceSession,并且通過Camera api v2標準接口,通知Camera Service,調用其CameraDeviceClient.endConfigure方法,在該方法內部又會去通過HIDL接口ICameraDeviceSession::configureStreams_3_4通知Provider開始處理此次配置需求,在Provider內部,會去通過在調用open流程中獲取的camera3_device_t結構體的configure_streams方法來將數(shù)據(jù)流的配置傳入CamX-CHI中,之后由CamX-CHI完成對數(shù)據(jù)流的配置工作,接下來我們來詳細分析下CamX-CHI對于該標準HAL3接口 configure_streams的具體實現(xiàn):
配置數(shù)據(jù)流是整個CamX-CHI流程比較重要的一環(huán),其中主要包括兩個階段:
選擇UsecaseId
根據(jù)選擇的UsecaseId創(chuàng)建Usecase
接下來我們就這兩個階段分別進行詳細介紹:
① 選擇UsecaseId
不同的UsecaseId分別對應的不同的應用場景,該階段是通過調用UsecaseSelector::GetMatchingUsecase()方法來實現(xiàn)的,該函數(shù)中通過傳入的operation_mode、num_streams配置數(shù)據(jù)流數(shù)量以及當前使用的Sensor個數(shù)來選擇相應的UsecaseId,比如當numPhysicalCameras值大于1同時配置的數(shù)據(jù)流數(shù)量num_streams大于1時選擇的就是UsecaseId::MultiCamera,表示當前采用的是雙攝場景。
② 創(chuàng)建Usecase
根據(jù)之前選擇的UsecaseId,通過UsecaseFactory來創(chuàng)建相應的Usecase,
其中Class Usecase是所有Usecase的基類,其中定義并實現(xiàn)了一些通用接口,CameraUsecaseBase繼承于Usecase,并擴展了部分功能。AdvancedCameraUsecase又繼承于CameraUsecaseBase,作為主要負責大部分場景的Usecase實現(xiàn)類,另外對于多攝場景,現(xiàn)提供了繼承于AdvancedCameraUsecase的UsecaseMultiCamera來負責實現(xiàn)。
除了雙攝場景,其它大部分場景使用的都是AdvancedCameraUsecase類來管理各項資源的,接下來我們重點梳理下AdvancedCameraUsecase::Create()方法。
在AdvancedCameraUsecase::Create方法中做了很多初始化操作,其中包括了以下幾個階段:
獲取XML文件中Usecase配置信息
創(chuàng)建Feature
保存數(shù)據(jù)流,重建Usecase的配置信息
調用父類CameraUsecaseBase的initialize方法,進行一些常規(guī)初始化工作
接下來我們就這幾個階段逐一進行分析:
1. 獲取XML文件中Usecase配置信息
這一部分主要通過調用CameraUsecaseBase::GetXMLUsecaseByName方法進行實現(xiàn)。
該方法的主要操作是從PerNumTargetUsecases數(shù)組中找到匹配到給定的usecaseName的Usecase,并作為返回值返回給調用者,其中這里我們以"UsecaseZSL“為例進行分析,PerNumTargetUsecases的定義是在g_pipeline.h中,該文件是在編譯過程中通過usecaseconverter.pl腳本將定義在個平臺目錄下的common_usecase.xml中的內容轉換生成g_pipeline.h。
2.創(chuàng)建Feature
如果當前場景選取了Feature,則調用FeatureSetup來完成創(chuàng)建工作。
該方法主要是通過諸如operation_mode、camera數(shù)量以及UsecaseId等信息來決定需要選擇哪些Feature,具體邏輯比較清晰,一旦決定需要使用哪一個Feature之后,便調用相應的Feature的Create()方法進行初始化操作。
3.保存數(shù)據(jù)流,重建Usecase的配置信息
從Camera Service 傳入的數(shù)據(jù)流,需要將其存儲下來,供后續(xù)使用,同時高通針對Usecase也加入了Override機制,根據(jù)需要可以選擇性地擴展Usecase,這兩個步驟的實現(xiàn)主要是通過SelectUsecaseConfig方法來實現(xiàn)。
其中主要是調用以下兩個方法來實現(xiàn)的:
ConfigureStream:該方法將從上層配置的數(shù)據(jù)流指針存入AdvancedCameraUsecase中,其中包括了用于預覽的m_pPreviewStream以及用于拍照的m_pSnapshotStream。
BuildUsecase:這個方法用來重新在原有的Usecase上面加入了Feature中所需要的pipeline,并創(chuàng)建了一個新的Usecase,并將其存入AdvancedCameraUsecase中的m_pChiUsecase成員變量中,緊接著通過SetPipelineToSessionMapping方法將pipeline與Session進行關聯(lián)。
4.調用父類CameraUsecaseBase的initialize方法,進行一些常規(guī)初始化工作
該方法中的操作主要有以下三個:
設置Session回調
創(chuàng)建Pipeline
創(chuàng)建Session
設置Session回調
該方法有兩個參數(shù),第二個是缺省的,第一個是ChiCallBacks,該參數(shù)是作為創(chuàng)建的每一條Session的回調方法,當Session中的pipeline全部跑完之后,會回調該方法將數(shù)據(jù)投遞到CHI中。
創(chuàng)建Pipeline
根據(jù)之前獲取的pipeline信息開始創(chuàng)建每一條pipeline,通過調用CreatePipeline()方法實現(xiàn)。
創(chuàng)建Session
創(chuàng)建Session,通過CreateSession()方法實現(xiàn),此時會將AdvancedCameraUsecase端的回調函數(shù)注冊到Session中,一旦Session中數(shù)據(jù)處理完成,便會調用回調將數(shù)據(jù)回傳給AdvancedCameraUsecase。
綜上,整個configure_stream過程,基本可以概括為以下幾點:
根據(jù)operation_mode、camera 個數(shù)以及stream的配置信息選取了對應的UsecaseId
根據(jù)所選取的UsecaseId,使用UsecaseFactory簡單工廠類創(chuàng)建了用于管理整個場景下所有資源的AdvancedCameraUsecase對象。
創(chuàng)建AdvancedCameraUsecase對象是通過調用其Create()方法完成,該方法中獲取了common_usecase.xml定義的關于Usecase的配置信息,之后又根據(jù)需要創(chuàng)建了Feature并選取了Feature所需的pipeline,并通過Override機制將Feature中所需要的Pipeline加入重建后的Usecase中。
最后通過調用CameraUsecaseBaese的initialize方法依次創(chuàng)建了各個pipeline以及Session,并且將AdvancedCameraUsecase的成員方法注冊到Session,用于Session將數(shù)據(jù)返回給Usecase中
4. 處理拍照請求
當用戶打開相機應用進行預覽或者點擊一次拍照操作的時候,便觸發(fā)了一次拍照請求,該動作首先通過CameraDeviceSession的capture或者setRepeatingRequest方法將請求通過Camera api v2接口下發(fā)到Camera Service中,然后在Camera Service內部將此次請求發(fā)送到CameraDevice::RequestThread線程中進行處理,一旦進入到該線程之后,便會最終通過HIDL接口ICameraCaptureSession:processCaptureRequest_3_4將請求發(fā)送至Provider中,之后當Provider收到請求之后,會調用camera3_device_t結構體的process_capture_request開始了HAL針對此次Request的處理,而該處理是由CamX-CHI來負責實現(xiàn),現(xiàn)在我們就來看下CamX-CHI是如何實現(xiàn)該方法的:
首先CamX中會將此次request轉發(fā)到HALDevice中,再通過HALDevice對象調用之前初始化的時候獲取的CHI部分的回調接口m_ChiAppCallbacks.chi_override_process_request方法(chi_override_process_request方法的定義位于chxextensioninterface.cpp中)將request發(fā)送到CHI部分。
在chi_override_process_request方法中會去獲取ExtensionModule對象,并將request發(fā)送到ExtensionModule對象中,該對象中存儲了之前創(chuàng)建的Usecase對象,然后經(jīng)過層層調用,最終會調用AdvancedCameraUsecase的ExecuteCaptureRequest方法,該方法負責處理此次Request,具體流程如下:
在AdvancedCameraUsecase的ExecuteCaptureRequest中會有兩個主要的分支來分別處理:
如果當前并沒有任何Feature需要實現(xiàn),此時便會走默認流程,根據(jù)上面的流程圖所示,這里會調用CameraUsecaseBase::ExecuteCaptureRequest方法,在該方法中,首先會將request取出,重新封裝成CHICAPTUREREQUEST,然后調用CheckAndActivatePipeline方法喚醒pipeline,這一操作到最后會調到Session的StreamOn方法,在喚醒了pipeline之后,繼續(xù)往下執(zhí)行,再將封裝后的Request發(fā)送到CamX中,最終調用到相應的Session::ProcessCaptureRequest方法,此時Request就進入到了Session內部進行流轉了。
如果當前場景需要實現(xiàn)某個Feature,則直接調用Feature的ExecuteProcessRequest方法將此次request送入Feature中處理,最后依然會調用到Session::StreamOn以及Session::ProcessCaptureRequest方法來分別完成喚醒pipeline以及下發(fā)request的到Session的操作。
該流程最終都會調用到兩個比較關鍵的方法Session::StreamOn以及Session::ProcessCaptureRequest,接下來針對這兩個方法重點介紹下:
Session::StreamOn
從方法名稱基本可以知道該方法主要用于開始硬件的數(shù)據(jù)輸出,具體點兒就是進行配置Sensor寄存器,讓其開始出圖,并且將當前的Session的狀態(tài)告知每一Node,讓它們在自己內部也做好處理數(shù)據(jù)的準備,所以之后的相關Request的流轉都是以該方法為前提進行的,所以該方法重要性可見一斑,其操作流程見下圖:
Session的StreamOn方法中主要做了如下兩個工作:
調用FinalizeDeferPipeline()方法,如果當前pipeline并未初始化,則會調用pipeline的FinalizePipeline方法,這里方法里面會去針對每一個從屬于當前pipeline的Node依次做FinalizeInitialization、CreateBufferManager、NotifyPipelineCreated以及PrepareNodeStreamOn操作,F(xiàn)inalizeInitialization用于完成Node的初始化動作,NotifyPipelineCreated用于通知Node當前Pipeline的狀態(tài),此時Node內部可以根據(jù)自身的需要作相應的操作,PrepareNodeStreamOn方法的主要是完成Sensor以及IFE等Node的控制硬件模塊出圖前的配置,其中包括了曝光的參數(shù)的設置,CreateBufferManagers方法涉及到CamX-CHI中的一個非常重要的Buffer管理機制,用于Node的ImageBufferManager的創(chuàng)建,而該類用于管理Node中的output port的buffer申請/流轉/釋放等操作。
調用Pipeline的StreamOn方法,里面會進一步通知CSL部分開啟數(shù)據(jù)流,并且調用每一個Node的OnNodeStreamOn方法,該方法會去調用ImageBufferManager的Activate(),該方法里面會去真正分配用于裝載圖像數(shù)據(jù)的buffer,之后會去調用CHI部分實現(xiàn)的用戶自定義的Nod的pOnStreamOn方法,用戶可以在該方法中做一些自定義的操作。
Session::ProcessCaptureRequest
針對每一次的Request的流轉,都是以該方法為入口開始的,具體流程見下圖:

程序員Android轉于網(wǎng)絡
上述流程可以總結為以下幾個步驟:
通過調用Session的ProcessCaptureRequest方法進入到Session,然后調用Pipeline中的ProcessRequest方法通知Pipeline開始處理此次Request。
在Pipeline中,會先去調用內部的每一個Node的SetupRequest方法分別設置該Node的Output Port以及Input Port,之后通過調用DRQ(DeferredRequestQueue)的AddDeferredNode方法將所有的Node加入到DRQ中,其中DRQ中有兩個隊列分別是用于保存沒有依賴項的Node的m_readyNodes以及保存處于等待依賴關系滿足的Node的m_deferredNodes,當調用DRQ的DispatchReadyNodes方法后,會開始從m_readyNodes隊列中取出Node調用其ProcessRequest開始進入Node內部處理本次request,在處理過程中會更新meta data數(shù)據(jù),并更新至DRQ中,當該Node處理完成之后,會將處于m_deferredNodes中的已無依賴關系的Node移到m_readyNodes中,并再次調用DispatchReadyNodes方法從m_readyNodes取出Node進行處理。
與此過程中,當Node的數(shù)據(jù)處理完成之后會通過CSLFenceCallback通知到Pipeline,此時Pipeline會判斷當前Node的Output port 是否是Sink Port(輸出到CHI),如果不是,則會更新依賴項到DRQ中,并且將不存在依賴項的Node移到m_readyNodes隊列中,然后調用DispatchReadyNdoes繼續(xù)進入到DRQ中流轉,如果是Sink Port,則表示此Node是整個Pipeline的最末端,調用sinkPortFenceSignaled將數(shù)據(jù)給到Session中,最后通過調用Session中的NotifyResult將結果發(fā)送到CHI中。
DeferredRequestQueue
上述流程里面中涉及到DeferredRequestQueue這個概念,這里簡單介紹下:
DeferredRequestQueue繼承于IPropertyPoolObserver,實現(xiàn)了OnPropertyUpdate/OnMetadataUpdate/OnPropertyFailure/OnMetadataFailure接口,這幾個接口用于接收Meta Data以及Property的更新,另外,DRQ主要包含了以下幾個主要方法:
Create()
該方法用于創(chuàng)建DRQ,其中創(chuàng)建了用于存儲依賴信息的m_pDependencyMap,并將自己注冊到MetadataPool中,一旦有meta data或者property更新便會通過類中實現(xiàn)的幾個接口通知到DRQ。DispatchReadyNodes()
該方法主要用于將處于m_readyNodes隊列的Node取出,將其投遞到m_hDeferredWorker線程中進行處理。AddDeferredNode()
該方法主要用于添加依賴項到m_pDependencyMap中。FenceSignaledCallback()
當Node內部針對某次request處理完成之后,會通過一系列回調通知到DRQ,而其調用的方法便是該方法,在該方法中,會首先調用UpdateDependency更新依賴項,然后調用DispatchReadyNodes觸發(fā)開始對處于ready狀態(tài)的Node開始進行處理OnPropertyUpdate()
該方法是定義于IPropertyPoolObserver接口,DRQ實現(xiàn)了它,主要用于接收Property更新的通知,并在內部調用UpdateDependency更新依賴項。OnMetadataUpdate()
該方法是定義于IPropertyPoolObserver接口,DRQ實現(xiàn)了它,主要用于接收Meta data更新的通知,并在內部調用UpdateDependency更新依賴項。UpdateDependency()
該方法用于更新Node的依賴項信息,并且將沒有依賴的Node從m_deferredNodes隊列中移到m_readyNodes,這樣該Node就可以在之后的某次DispatchReadyNodes調用之后投入運行。DeferredWorkerWrapper()
該方法是m_hDeferredWorker線程的處理函數(shù),主要用于處理需要下發(fā)request的Node,同時再次更新依賴項,最后會再次調用DispatchReadyNodes開始處理。
其中需要注意的是,Pipeline首次針對每一個Node通過調用AddDeferredNode方法加入到DRQ中,此時所有的Node都會加入到m_readyNodes中,然后通過調用dispatchReadyNodes方法,觸發(fā)DRQ開始進行整個內部處理流程,基本流程可以參見下圖,接下來就以該圖進行深入梳理下:

程序員Android轉于網(wǎng)絡
當調用了DRQ的dispatchReadyNodes方法后,會從m_readyNodes鏈表里面依次取出Dependency,將其投遞到DeferredWorkerWrapper線程中,在該線程會從Dependency取出Node調用其ProcessRequest方法開始在Node內部處理本次request,處理完成之后如果當前Node依然存在依賴項,則調用AddDeferredNode方法將Node再次加入到m_deferredNodes鏈表中,并且加入新的依賴項,存入m_pDependencyMap hash表中。
在Node處理request的過程中,會持續(xù)更新meta data以及property,此時會通過調用MetadataSlot的PublishMetadata方法更新到MetadataPool中,此時MetadataPool會調用之前在DRQ初始化時候注冊的幾個回調方法OnPropertyUpdate以及OnMetadataUpdate方法通知DRQ,此時有新的meta data 和property更新,接下來會在這兩個方法中調用UpdateDependency方法,去更新meta data 和property到m_pDependencyMap中,并且將沒有任何依賴項的Node從m_deferredNodes取出加入到m_readyNodes,等待處理。
與此同時,Node的處理結果也會通過ProcessFenceCallback方法通知pipeline,并且調用pipeline的NonSinkPortFenceSignaled方法,在該方法內部又會去調用DRQ的FenceSignaledCallback方法,而該方法又會調用UpdateDependency更新依賴,并將依賴項都滿足的Node從m_deferredNodes取出加入到m_readyNodes,然后調用dispatchReadyNodes繼續(xù)進行處理。
5. 上傳拍照結果
在用戶開啟了相機應用,相機框架收到某次Request請求之后會開始對其進行處理,一旦有圖像數(shù)據(jù)產生便會通過層層回調最終返回到應用層進行顯示,這里我們針對CamX-CHI部分對于拍照結果的上傳流程進行一個簡單的梳理:
每一個Request對應了三個Result,分別是partial metadata、metadata以及image data,對于每一個Result,上傳過程可以大致分為以下兩個階段:
Session內部完成圖像數(shù)據(jù)的處理,將結果發(fā)送至Usecase中
Usecase接收到來自Session的數(shù)據(jù),并將其上傳至Provider
首先來看下Session內部完成圖像數(shù)據(jù)的處理后是如何將結果發(fā)送至Usecase的:

程序員Android轉于網(wǎng)絡
在整個requets流轉的過程中,一旦Node中有Partial Meta Data產生,便會調用Node的ProcessPartialMetadataDone方法去通知從屬的Pipeline,其內部又調用了pipeline的NotifyNodePartialMetadataDone方法。每次調用Pipeline的NotifyNodePartialMetadataDone方法都會去將pPerRequestInfo→numNodesPartialMetadataDone加一并且判斷當前值是否等于pipeline中的Node數(shù)量,一旦相等,便說明當前所有的Node都完成了partial meta data的更新動作,此時,便會調用ProcessPartialMetadataRequestIdDone方法,里面會去取出partial meta data,并且重新封裝成ResultsData結構體,將其作為參數(shù)通過Session的NotifyResult方法傳入Session中,之后在Session中經(jīng)過層層調用最終會調用到內部成員變量m_chiCallBacks的ChiProcessPartialCaptureResult方法,該方法正是創(chuàng)建Session的時候,傳入Session中的Usecase的方法(AdvancedCameraUsecase::ProcessDriverPartialCaptureResultCb),通過該方法就將meta data返回到了CHI中。
同樣地,Meta data的邏輯和Partial Meta Data很相似,每個Node在處理request的過程中,會調用ProcessMetadataDone方法將數(shù)據(jù)發(fā)送到Pipeline中,一旦所有的Node的meta data否發(fā)送完成了,pipeline會調用NotifyNodeMetadataDone方法,將最終的結果發(fā)送至Session中,最后經(jīng)過層層調用,會調用Session 中成員變量m_chiCallBacks的ChiProcessCaptureResult方法,將結果發(fā)送到CHI中Usecase中。
圖像數(shù)據(jù)的流轉和前兩個meta data的流轉有點兒差異,一旦Node內部圖像數(shù)據(jù)處理完成后便會調用其ProcessFenceCallback方法,在該方法中會去檢查當前輸出是否是SInk Buffer,如果是則會調用Pipeline的SinkPortFenceSignaled方法將數(shù)據(jù)發(fā)送到Pipeline中,在該方法中Pipeline又會將數(shù)據(jù)發(fā)送至Session中,最后經(jīng)過層層調用,會調用Session 中成員變量m_chiCallBacks的ChiProcessCaptureResult方法,將結果發(fā)送到CHI中Usecase中。
接下來我們來看下一旦Usecase接收到Session的數(shù)據(jù),是如何發(fā)送至Provider的:
我們以常用的AdvancedCameraUsecase為例進行代碼的梳理:

程序員Android轉于網(wǎng)絡
如上圖所示,整個result的流轉邏輯還是比較清晰的,CamX通過回調方法將結果回傳給CHI中,而在CHI中,首先判斷是否需要發(fā)送到具體的Feature的, 如果需要,則調用相應Feature的ProcessDriverPartialCaptureResult或者ProcessResult方法將結果發(fā)送到具體的Feature中,一旦處理完成,便會調用調用CameraUsecaseBase的ProcessAndReturnPartialMetadataFinishedResults以及ProcessAndReturnFinishedResults方法將結果發(fā)送到Usecase中,如果當前不需要發(fā)送到Feature進行處理,就在AdvancedCameraUsecase中調用CameraUsecaseBase的SessionCbPartialCaptureResult以及SessionCbCaptureResult方法,然后通過Usecase::ReturnFrameResult方法將結果發(fā)送到ExtensionModule中,之后調用ExtensionModule中存儲的CamX中的回調函數(shù)process_capture_result將結果發(fā)送到CamX中的HALDevice中,之后HALDevice又通過之前存儲的上層傳入的回調方法,將結果最終發(fā)送到CameraDeviceSession中。
通過以上的梳理,可以發(fā)現(xiàn),整個CamX-CHI框架設計的很不錯,目錄結構清晰明確,框架簡單高效,流程控制邏輯分明,比如針對某一圖像請求,整個流程經(jīng)過Usecase、Feature、Session、Pipeline并且給到具體的Node中進行處理,最終輸出結果。另外,相比較之前的QCamera & Mm-Camera框架的針對某個算法的擴展需要在整個流程代碼中嵌入自定義的修改做法而言,CamX-CHI通過將自定義實現(xiàn)的放入CHI中,提高了其擴展性,降低了開發(fā)門檻,使得平臺廠商在并不是很熟悉CamX框架的情況下也可以通過小規(guī)模的修改成功添加新功能。但是人無完人,框架也是一樣,該框架異步化處理太多,加大了定位問題以及解決問題的難度,給開發(fā)者帶來了不小的壓力。另外,框架對于內存的要求較高,所以在一些低端機型尤其是低內存機型上,整個框架的運行效率可能會受到一定的限制,進而導致相機效率低于預期。
原文鏈接:https://blog.csdn.net/u012596975/article/details/107138576
友情推薦:
至此,本篇已結束。轉載網(wǎng)絡的文章,小編覺得很優(yōu)秀,歡迎點擊閱讀原文,支持原創(chuàng)作者,如有侵權,懇請聯(lián)系小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!
點個在看,方便您使用時快速查找!
