和你一起終身學習,這里是程序員Android
經典好文推薦,通過閱讀本文,您將收獲以下知識點:
一、概覽
二、基本組件概念
三、組件結構關系
四、關鍵流程詳解
一、概覽
回顧高通平台Camera HAL歷史,之前高通采用的是QCamera & MM-Camera架構,但是為了更精細化控制底層硬件(Sensor/ISP等關鍵硬件),同時方便手機廠商自定義一些功能,現在提出了CamX-CHI架構,由於在CamX-CHI中完全看不到之前老架構的影子,所以它完全是一個全新的架構,它將一些高度統一的功能性接口抽離出來放到CamX中,將可定制化的部分放在CHI中供不同廠商進行修改,實現各自獨有的特色功能,這樣設計的好處顯而易見,那便是即便開發者對於CamX並不是很了解,但是依然可以很方便的加入自定義的功能,從而降低了開發者在高通平台的開發門檻。
接下來我們以最直觀的目錄結構入手對該架構做一個簡單的認識,以下便是CamX-CHI基本目錄結構:
image
該部分代碼主要位於 vendor/qcom/proprietary/ 目錄下:
其中 camx 代表了通用功能性接口的代碼實現集合(CamX),chi-cdk代表了可定制化需求的代碼實現集合(CHI),從圖中可以看出Camx部分對上作為HAL3接口的實現,對下
通過v4l2框架與Kernel保持通訊,中間通過互相dlopen so庫並獲取對方操作接口的方式保持着與CHI的交互。
camx/中有如下幾個主要目錄:
core/ :用於存放camx的核心實現模塊,其中還包含了主要用於實現hal3接口的hal/目錄,以及負責與CHI進行交互的chi/目錄
csl/:用於存放主要負責camx與camera driver的通訊模塊,為camx提供了統一的Camera driver控制接口
hwl/: 用於存放自身具有獨立運算能力的硬件node,該部分node受csl管理
swl/: 用於存放自身並不具有獨立運算能力,必須依靠CPU才能實現的node
chi-cdk/中有如下幾個主要目錄:
chioverride/: 用於存放CHI實現的核心模塊,負責與camx進行交互並且實現了CHI的總體框架以及具體的業務處理。
bin/: 用於存放平台相關的配置項
topology/: 用於存放用戶自定的Usecase xml配置文件
node/: 用於存放用戶自定義功能的node
module/: 用於存放不同sensor的配置文件,該部分在初始化sensor的時候需要用到
tuning/: 用於存放不同場景下的效果參數的配置文件
sensor/: 用於存放不同sensor的私有信息以及寄存器配置參數
actuator/: 用於存放不同對焦模塊的配置信息
ois/:用於存放防抖模塊的配置信息
flash/:存放着閃光燈模塊的配置信息
eeprom/: 存放着eeprom外部存儲模塊的配置信息
fd/: 存放了人臉識別模塊的配置信息
二、基本組件概念
1. Usecase
作為CamX-CHI中最大的抽象概念,其中包含了多條實現特定功能的Pipeline,具體實現是在CHI中通過Usecase類完成的,該類主要負責了其中的業務處理以及資源的管理。
Usecase類,提供了一系列通用接口,作為現有的所有Usecase的基類,其中,AdvancedCameraUsecase又繼承於CameraUsecaseBase,相機中絕大部分場景會通過實例化AdvancedCameraUsecase來完成,它包括了幾個主要接口:
Create(): 該方法是靜態方法,用於創建一個AdvancedCameraUsecase實例,在其構造方法中會去獲取XML中的相應的Usecase配置信息。
ExecuteCaptureRequest(): 該方法用於下發一次Request請求。
ProcessResultCb(): 該方法會在創建Session的過程中,作為回調方法注冊到其中,一旦Session數據處理完成的時候便會調用該方法將結果發送到AdvancedCameraUsecase中。
ProcessDriverPartialCaptureResult(): 該方法會在創建Session的過程中,作為回調方法注冊到其中,一旦Session中產生了partial meta data的時候,便會調用該方法將其發送至AdvancedCameraUsecase中。
ProcessMessageCb(): 該方法會在創建Session的過程中,作為回調方法注冊到其中,一旦Session產生任何事件,便會調用該方法通知到AdvancedCameraUsecase中。
ExecuteFlush(): 該方法用於刷新AdvancedCameraUsecase。
Destroy(): 該方法用於安全銷毀AdvancedCameraUsecase。
Usecase的可定制化部分被抽象出來放在了common_usecase.xml文件中,這里簡單介紹其中的幾個主要的標簽含義:
Usecase
UsecaseName: 代表了該Usecase的名字,后期根據這個名字找到這個Usecase的定義。
Targets: 用於表示用於輸出的數據流的集合,其中包括了數據流的格式,輸出Size的范圍等。
Pipeline: 用於定義該Usecase可以是使用的所有Pipeline,這里必須至少定義一條Pipeline。
2. Feature
代表了一個特定的功能,該功能需要多條Pipeline組合起來實現,受Usecase統一管理,在CHI中通過Feature類進行實現,在XML中沒有對應的定義,具體的Feature選取工作是在Usecase中完成的,通過在創建Feature的時候,傳入Usecase的實例的方式,來和Usecase進行相互訪問各自的資源。
以下是現有的Feature,其中Feature作為基類存在,定義了一系列通用方法。
image
幾個常用的Feature:
FeatureHDR: 用於實現HDR功能,它負責管理內部的一條或者幾條pipeline的資源以及它們的流轉,最終輸出具有HDR效果的圖像。
FeatureMFNR: 用於實現MFNR功能,內部分為幾個大的流程,分別包括Prefiltering、Blending、Postfilter以及最終的OfflineNoiseReproces(這一個是可選擇使能的),每一個小功能中包含了各自的pipeline。
FeatureASD: 用於AI功能的實現,在預覽的時候,接收每一幀數據,並且進行分析當前場景的AI識別輸出結果,並其通過諸如到metadata方式給到上層,進行后續的處理。
3. Session
用於管理pipeline的抽象控制單元,一個Session中至少擁有一個pipeine,並且控制着所有的硬件資源,管控着每一個內部pipeline的request的流轉以及數據的輸入輸出,它沒有可定制化的部分,所以在CHI中的XML文件中並沒有將Session作為一個獨立的單元進行定義。
Session的實現主要通過CamX中的Session類,其主要接口如下:
Initialize(): 根據傳入的參數SessionCreateData進行Session的初始化工作。
NotifyResult(): 內部的Pipeline通過該接口將結果發送到Session中。
ProcessCaptureRequest(): 該方法用於用戶決定發送一個Request到Session中的時候調用。
StreamOn(): 通過傳入的Pipeline句柄,開始硬件的數據傳輸。
StreamOff(): 通過傳入的Pipeline句柄,停止硬件的數據傳輸。
4. Pipeline
作為提供單一特定功能的所有資源的集合,維護着所有硬件資源以及數據的流轉,每一個Pipeline包括了其中的Node/Link,在CamX中通過Pipeline類進行實現,負責整條Pipeline的軟硬件資源的維護以及業務邏輯的處理,接下來我們簡單看下該類的幾個主要接口:
Create(): 該方法是一個靜態方法,根據傳入的PipelineCreateInputData信息來實例化一個Pipeline對象。
StreamOn(): 通知Pipeline開始硬件的數據傳輸
StreamOff(): 通知Pipeline停止硬件的數據傳輸
FinalizePipeline(): 用於完成Pipeline的設置工作
OpenRequest(): open一個CSL用於流轉的Request
ProcessRequest(): 開始下發Request
NotifyNodeMetadataDone(): 該方法是Pipeline提供給Node,當Node內部生成了metadata,便會調用該方法來通知metadata已經完成,最后當所有Node都通知Pipeline metadata已經完成,Pipeline 便會調用ProcessMetadataRequestIdDone通知Session。
NotifyNodePartialMetadataDone(): 該方法是Pipeline提供給Node,當Node內部生成了partial metadata,便會調用該方法來通知metadata已經完成,最后當所有Node都通知Pipeline metadata已經完成,Pipeline 便會調用ProcessPartialMetadataRequestIdDone通知Session。
SinkPortFenceSignaled(): 用來通知Session 某個sink port的fence處於被觸發的狀態。
NonSinkPortFenceSignaled(): 用來通知Session 某個non sink port的fence處於被觸發的狀態。
Pipeline中的Node以及連接方式都在XML中被定義,其主要包含了以下幾個標簽定義:
PipelineName: 用來定義該條Pipeline的名稱
NodeList: 該標簽中定義了該條Pipeline的所有的Node
PortLinkages: 該標簽定義了Node上不同端口之間的連接關系
5. Node
作為單個具有獨立處理功能的抽象模塊,可以是硬件單元也可以是軟件單元,關於Node的具體實現是CamX中的Node類來完成的,其中CamX-CHI中主要分為兩個大類,一個是高通自己實現的Node包括硬件Node,一個是CHI中提供給用戶進行實現的Node,其主要方法如下:
Create(): 該方法是靜態方法,用於實例化一個Node對象。
ExecuteProcessRequest(): 該方法用於針對hwl node下發request的操作。
ProcessRequestIdDone(): 一旦該Node當前request已經處理完成,便會通過調用該方法通知Pipeline。
ProcessMetadataDone(): 一旦該Node的當前request的metadata已經生成,便會通過調用該方法通知到Pipeline。
ProcessPartialMetadataDone(): 一旦該Node的當前request的partial metadata已經生成,便會通過調用該方法通知到Pipeline。
CreateImageBufferManager(): 創建ImageBufferManager
其可定制化的部分作為標簽在XML中進行定義:
NodeName:用來定義該Node的名稱
NodeId: 用來指定該Node的ID,其中IPE NodeId為65538,IFE NodeId為65536,用戶自定義的NodeId為255。
NodeInstance: 用於定義該Node的當前實例的名稱。
NodeInstanceId: 用於指定該Node實例的Id。
6. Link
用於定義不同Port的連接,一個Port可以根據需要建立多條與其它從屬於不同Node的Port的連接,它通過標簽來進行定義,其中包括了作為輸入端口,作為輸出端口。
一個Link中包含了一個SrcPort和一個DstPort,分別代表了輸入端口和輸出端口,然后BufferProperties用於表示兩個端口之間的buffer配置。
7. Port
作為Node的輸入輸出的端口,在XML文件中,標簽用來定義一個輸入端口,標簽用來定義輸出端口,每一個Node都可以根據需要使用一個或者多個輸入輸出端口,使用OutputPort以及InputPort結構體來進行在代碼中定義。
Port
PortId: 該端口的Id: 該端口的名稱
NodeName: 該端口從屬的Node名稱
NodeId: 該端口從屬的Node的Id
NodeInstance: 該端口從屬的Node的實例名稱
NodeInstanceId: 該端口從屬的Node的實例的Id
三、組件結構關系
通過之前的介紹,我們對於幾個基本組件有了一個比較清晰地認識,但是任何一個框架體系並不是僅靠組件胡亂堆砌而成的,相反,它們都必須基於各自的定位,按照各自所獨有的行為模式,同時按照約定俗稱的一系列規則組合起來,共同完成整個框架某一特定的功能。所以這里不得不產生一個疑問,在該框架中它們到底是如何組織起來的呢?它們之間的關系又是如何的呢?接下來我們以下圖入手開始進行分析:
image
由上圖可以看到,幾者是通過包含關系組合起來的,Usecase 包含Feature,而Feature包含了Session,Session又維護了內部的Pipeline的流轉,而每一條pipeline中又通過Link將所有Node都連接了起來,接下我們就這幾種關系詳細講解下:
首先,一個Usecase代表了某個特定的圖像采集場景,比如人像場景,后置拍照場景等等,在初始化的時候通過根據上層傳入的一些具體信息來進行創建,這個過程中,一方面實例化了特定的Usecase,這個實例是用來管理整個場景的所有資源,同時也負責了其中的業務處理邏輯,另一方面,獲取了定義在XML中的特定Usecase,獲取了用於實現某些特定功能的pipeline。
其次,在Usecase中,Feature是一個可選項,如果當前用戶選擇了HDR模式或者需要在Zoom下進行拍照等特殊功能的話,在Usecase創建過程中,便會根據需要創建一個或者多個Feature,一般一個Feature對應着一個特定的功能,如果場景中並不需要任何特定的功能,則也完全可以不使用也不創建任何Feature。
然后,每一個Usecase或者Feature都可以包含一個或者多個Session,每一個Session都是直接管理並負責了內部的Pipeline的數據流轉,其中每一次的Request都是Usecase或者Featuret通過Session下發到內部的Pipeline進行處理,數據處理完成之后也是通過Session的方法將結果給到CHI中,之后是直接給到上層還是將數據封裝下再次下發到另一個Session中進行后處理,這都交由CHI來決定。
其中,Session和Pipeline是一對多的關系,通常一個Session只包含了一條Pipeline,用於某個特定圖像處理功能的實現,但是也不絕對,比如FeatureMFNR中包含的Session就包括了三條pipeline,又比如后置人像預覽,也是用一個Session包含了兩條分別用於主副雙攝預覽的Pipeline,主要是要看當前功能需要的pipeline數量以及它們之間是否存在一定關聯。
同時,根據上面關於Pipeline的定義,它內部包含了一定數量的Node,並且實現的功能越復雜,所包含的Node也就越多,同時Node之間的連接也就越錯綜復雜,比如后置人像預覽虛化效果的實現就是將拿到的主副雙攝的圖像通過RTBOfflinePreview這一條Pipeline將兩幀圖像合成一幀具有虛化效果的圖像,從而完成了虛化功能。
最后Pipeline中的Node的連接方式是通過XML文件中的Link來進行描述的,每一個Link定義了一個輸入端和輸出端分別對應着不同Node上面的輸入輸出端口,通過這種方式就將其中的一個Node的輸出端與另外一個Node的輸入端,一個一個串聯起來,等到圖像數據從Pipeline的起始端開始輸入的時候,便可以按照這種定義好的軌跡在一個一個Node之間進行流轉,而在流轉的過程中每經過一個Node都會在內部對數據進行處理,這樣等到數據從起始端一直流轉到最后一個Node的輸出端的時候,數據就經過了很多次處理,這些處理效果最后疊加在一起便是該Pipeline所要實現的功能,比如降噪、虛化等等。
四、關鍵流程詳解
1. Camera Provider 啟動初始化
當系統啟動的時候,Camera Provider主程序會被運行,在整個程序初始化的過程中會通過獲取到的camera_module_t調用其get_number_of_camera接口獲取底層支持的camera數量,由於是第一次獲取,所以在CamX-CHI中會伴隨着很多初始化動作,具體操作見下圖:
在這里插入圖片描述
主要流程如下:
通過HAL3Module::GetInstance()靜態方法實例化了HAL3Module對象,在其構造方法里面通過HwEnvironment::GetInstance()靜態方法又實例化了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模組信息,並且將獲取的信息暫存起來,等待后續階段使用,總得來說在HwEnvironment初始化的過程中,通過探測方法獲取了所有底層的硬件驅動模塊,並將其信息存儲下來供后續階段使用。
之后通過調用HwEnvironment對象中的ProbeChiCompoents方法在/vendor/lib64/camera/components路徑下找尋各個Node生成的So庫,並獲取Node提供的標准對外接口,這些Node不但包括CHI部分用戶自定義的模塊,還包括了CamX部分實現的硬件模塊,並最后都將其都存入ExternalComponentInfo對象中,等待后續階段使用。
另外在初始化階段還有一個比較重要的操作就是CamX 與CHI是通過互相dlopen對方的So庫,獲取了對方的入口方法,最后通過彼此的入口方法獲取了對方操作方法集合,之后再通過這些操作方法與對方進行通訊,其主要流程見下圖:
image
從上圖不難看出,在HAL3Module構造方法中會去通過dlopen方法加載com.qti.chi.override.so庫,並通過dlsym映射出CHI部分的入口方法chi_hal_override_entry,並調用該方法將HAL3Module對像中的成員變量m_ChiAppCallbacks(CHIAppCallbacks)傳入CHI中,其中包含了很多函數指針,這些函數指針分別對應着CHI部分的操作方法集中的方法,一旦進入到CHI中,就會將CHI本地的操作方法集合中的函數地址依次賦值給m_ChiAppCallbacks,這樣CamX后續就可以通過這個成員變量調用到CHI中方法,從而保持了與CHI的通訊。
同樣地,CHI中的ExtensionModule在初始化的時候,其構造方法中也會通過調用dlopen方法加載camera.qcom.so庫,並將其入口方法ChiEntry通過dlsym映射出來,之后調用該方法,將g_chiContextOps(ChiContextOps,該結構體中定義了很多指針函數)作為參數傳入CamX中,一旦進入CamX中,便會將本地的操作方法地址依次賦值給g_chiContextOps中的每一個函數指針,這樣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中會繼續調用獲取到的camera3_device_t的initialize方法進行初始化動作。接下來我們便來詳細分析下CamX-CHI對於open以及initialize的具體實現流程:
a) open
該方法是camera_module_t的標准方法,主要用來獲取camera3_device_t設備結構體的,CamX-CHI對其進行了實現,open方法中完成的工作主要有以下幾個:
將當前camera id傳入CHI中進行remap操作,當然這個remap操作邏輯完全是根據CHI中用戶需求來的,用戶可以根據自己的需要在CHI中加入自定義remap邏輯。
實例化HALDevice對象,其構造函數中調用Initialize方法,該方法會填充CamX中自定義的Camera3Device結構體。
將m_HALCallbacks.process_capture_result指向了本地方法ProcessCaptureResult以及m_HALCallbacks.notify_result指向了本地方法Notify(之后會在配置數據流的過程中,將m_HALCallbacks注冊到CHI中, 一旦當CHI數據處理完成之后,便會通過這兩個回調方法將數據或者事件回傳給CamX)。
最后將HALDevice 中的Camera3Device成員變量作為返回值給到Provider中的CameraCaptureSession中。
Camera3Device 其實重定義了camera3_device_t,其中HwDevice對應於camera3_device_t中的hw_device_t,Camera3DeviceOps對應於camera3_device_ops_t,而在HALDevice的初始化過程中,會將CamX實現的HAL3接口的結構體g_camera3DeviceOps賦值給Camera3DeviceOps中。
b) initialize
該方法在調用open后緊接着被調用,主要用於將上層的回調接口傳入HAL中,一旦有數據或者事件產生,CamX便會通過這些回調接口將數據或者事件上傳至調用者,其內部的實現較為簡單。
initialize方法中有兩個參數,分別是之前通過open方法獲取的camera3_device_t結構體和實現了camera3_callback_ops_t的CameraDevice,很顯然camera3_device_t結構體並不是重點,所以該方法的主要工作是將camera3_callback_ops_t與CamX關聯上,一旦數據准備完成便通過這里camera3_callback_ops_t中回調方法將數據回傳到Camera Provider中的CameraDevice中,基本流程可以總結為以下幾點:
實例化了一個Camera3CbOpsRedirect對象並將其加入了g_HAL3Entry.m_cbOpsList隊列中,這樣方便之后需要的時候能夠順利拿到該對象。
將本地的process_capture_result以及notify方法地址分別賦值給Camera3CbOpsRedirect.cbOps中的process_capture_result以及notify函數指針。
將上層傳入的回調方法結構體指針pCamera3CbOpsAPI賦值給Camera3CbOpsRedirect.pCbOpsAPI,並將Camera3CbOpsRedirect.cbOps賦值給pCamera3CbOpsAPI,通過JumpTableHal3的initialize方法將pCamera3CbOpsAPI傳給HALDevice中的m_pCamera3CbOps成員變量,這樣HALDevice中的m_pCamera3CbOps就指向了CamX中本地方法process_capture_result以及notify。
經過這樣的一番操作之后,一旦CHI有數據傳入便會首先進入到本地方法ProcessCaptureResult,然后在該方法中獲取到HALDevice的成員變量m_pCamera3CbOps,進而調用m_pCamera3CbOps中的process_capture_result方法,即camxhal3entry.cpp中定義的process_capture_result方法,然后這個方法中會去調用JumpTableHAL3.process_capture_result方法,該方法最終會去調用Camera3CbOpsRedirect.pCbOpsAPI中的process_capture_result方法,這樣就調到從Provider傳入的回調方法,將數據順利給到了CameraCaptureSession中。
3. 配置相機設備數據流
在打開相機應用過程中,App在獲取並打開相機設備之后,會調用CameraDevice.createCaptureSession來獲取CameraDeviceSession,並且通過Camera api v2標准接口,通知Camera Service,調用其CameraDeviceClient.endConfigure方法,在該方法內部又會去通過HIDL接口ICameraDeviceSession::configureStreams_3_4通知Provider開始處理此次配置需求,在Provider內部,會去通過在調用open流程中獲取的camera3_device_t結構體的configure_streams方法來將數據流的配置傳入CamX-CHI中,之后由CamX-CHI完成對數據流的配置工作,接下來我們來詳細分析下CamX-CHI對於該標准HAL3接口 configure_streams的具體實現:
配置數據流是整個CamX-CHI流程比較重要的一環,其中主要包括兩個階段:
選擇UsecaseId
根據選擇的UsecaseId創建Usecase
接下來我們就這兩個階段分別進行詳細介紹:
① 選擇UsecaseId
不同的UsecaseId分別對應的不同的應用場景,該階段是通過調用UsecaseSelector::GetMatchingUsecase()方法來實現的,該函數中通過傳入的operation_mode、num_streams配置數據流數量以及當前使用的Sensor個數來選擇相應的UsecaseId,比如當numPhysicalCameras值大於1同時配置的數據流數量num_streams大於1時選擇的就是UsecaseId::MultiCamera,表示當前采用的是雙攝場景。
② 創建Usecase
根據之前選擇的UsecaseId,通過UsecaseFactory來創建相應的Usecase,
其中Class Usecase是所有Usecase的基類,其中定義並實現了一些通用接口,CameraUsecaseBase繼承於Usecase,並擴展了部分功能。AdvancedCameraUsecase又繼承於CameraUsecaseBase,作為主要負責大部分場景的Usecase實現類,另外對於多攝場景,現提供了繼承於AdvancedCameraUsecase的UsecaseMultiCamera來負責實現。
除了雙攝場景,其它大部分場景使用的都是AdvancedCameraUsecase類來管理各項資源的,接下來我們重點梳理下AdvancedCameraUsecase::Create()方法。
在AdvancedCameraUsecase::Create方法中做了很多初始化操作,其中包括了以下幾個階段:
獲取XML文件中Usecase配置信息
創建Feature
保存數據流,重建Usecase的配置信息
調用父類CameraUsecaseBase的initialize方法,進行一些常規初始化工作
接下來我們就這幾個階段逐一進行分析:
1. 獲取XML文件中Usecase配置信息
這一部分主要通過調用CameraUsecaseBase::GetXMLUsecaseByName方法進行實現。
該方法的主要操作是從PerNumTargetUsecases數組中找到匹配到給定的usecaseName的Usecase,並作為返回值返回給調用者,其中這里我們以"UsecaseZSL“為例進行分析,PerNumTargetUsecases的定義是在g_pipeline.h中,該文件是在編譯過程中通過usecaseconverter.pl腳本將定義在個平台目錄下的common_usecase.xml中的內容轉換生成g_pipeline.h。
2.創建Feature
如果當前場景選取了Feature,則調用FeatureSetup來完成創建工作。
該方法主要是通過諸如operation_mode、camera數量以及UsecaseId等信息來決定需要選擇哪些Feature,具體邏輯比較清晰,一旦決定需要使用哪一個Feature之后,便調用相應的Feature的Create()方法進行初始化操作。
3.保存數據流,重建Usecase的配置信息
從Camera Service 傳入的數據流,需要將其存儲下來,供后續使用,同時高通針對Usecase也加入了Override機制,根據需要可以選擇性地擴展Usecase,這兩個步驟的實現主要是通過SelectUsecaseConfig方法來實現。
其中主要是調用以下兩個方法來實現的:
ConfigureStream:該方法將從上層配置的數據流指針存入AdvancedCameraUsecase中,其中包括了用於預覽的m_pPreviewStream以及用於拍照的m_pSnapshotStream。
BuildUsecase:這個方法用來重新在原有的Usecase上面加入了Feature中所需要的pipeline,並創建了一個新的Usecase,並將其存入AdvancedCameraUsecase中的m_pChiUsecase成員變量中,緊接着通過SetPipelineToSessionMapping方法將pipeline與Session進行關聯。
4.調用父類CameraUsecaseBase的initialize方法,進行一些常規初始化工作
該方法中的操作主要有以下三個:
設置Session回調
創建Pipeline
創建Session
設置Session回調
該方法有兩個參數,第二個是缺省的,第一個是ChiCallBacks,該參數是作為創建的每一條Session的回調方法,當Session中的pipeline全部跑完之后,會回調該方法將數據投遞到CHI中。
創建Pipeline
根據之前獲取的pipeline信息開始創建每一條pipeline,通過調用CreatePipeline()方法實現。
創建Session
創建Session,通過CreateSession()方法實現,此時會將AdvancedCameraUsecase端的回調函數注冊到Session中,一旦Session中數據處理完成,便會調用回調將數據回傳給AdvancedCameraUsecase。
綜上,整個configure_stream過程,基本可以概括為以下幾點:
根據operation_mode、camera 個數以及stream的配置信息選取了對應的UsecaseId
根據所選取的UsecaseId,使用UsecaseFactory簡單工廠類創建了用於管理整個場景下所有資源的AdvancedCameraUsecase對象。
創建AdvancedCameraUsecase對象是通過調用其Create()方法完成,該方法中獲取了common_usecase.xml定義的關於Usecase的配置信息,之后又根據需要創建了Feature並選取了Feature所需的pipeline,並通過Override機制將Feature中所需要的Pipeline加入重建后的Usecase中。
最后通過調用CameraUsecaseBaese的initialize方法依次創建了各個pipeline以及Session,並且將AdvancedCameraUsecase的成員方法注冊到Session,用於Session將數據返回給Usecase中
4. 處理拍照請求
當用戶打開相機應用進行預覽或者點擊一次拍照操作的時候,便觸發了一次拍照請求,該動作首先通過CameraDeviceSession的capture或者setRepeatingRequest方法將請求通過Camera api v2接口下發到Camera Service中,然后在Camera Service內部將此次請求發送到CameraDevice::RequestThread線程中進行處理,一旦進入到該線程之后,便會最終通過HIDL接口ICameraCaptureSession:processCaptureRequest_3_4將請求發送至Provider中,之后當Provider收到請求之后,會調用camera3_device_t結構體的process_capture_request開始了HAL針對此次Request的處理,而該處理是由CamX-CHI來負責實現,現在我們就來看下CamX-CHI是如何實現該方法的:
首先CamX中會將此次request轉發到HALDevice中,再通過HALDevice對象調用之前初始化的時候獲取的CHI部分的回調接口m_ChiAppCallbacks.chi_override_process_request方法(chi_override_process_request方法的定義位於chxextensioninterface.cpp中)將request發送到CHI部分。
在chi_override_process_request方法中會去獲取ExtensionModule對象,並將request發送到ExtensionModule對象中,該對象中存儲了之前創建的Usecase對象,然后經過層層調用,最終會調用AdvancedCameraUsecase的ExecuteCaptureRequest方法,該方法負責處理此次Request,具體流程如下:
在AdvancedCameraUsecase的ExecuteCaptureRequest中會有兩個主要的分支來分別處理:
如果當前並沒有任何Feature需要實現,此時便會走默認流程,根據上面的流程圖所示,這里會調用CameraUsecaseBase::ExecuteCaptureRequest方法,在該方法中,首先會將request取出,重新封裝成CHICAPTUREREQUEST,然后調用CheckAndActivatePipeline方法喚醒pipeline,這一操作到最后會調到Session的StreamOn方法,在喚醒了pipeline之后,繼續往下執行,再將封裝后的Request發送到CamX中,最終調用到相應的Session::ProcessCaptureRequest方法,此時Request就進入到了Session內部進行流轉了。
如果當前場景需要實現某個Feature,則直接調用Feature的ExecuteProcessRequest方法將此次request送入Feature中處理,最后依然會調用到Session::StreamOn以及Session::ProcessCaptureRequest方法來分別完成喚醒pipeline以及下發request的到Session的操作。
該流程最終都會調用到兩個比較關鍵的方法Session::StreamOn以及Session::ProcessCaptureRequest,接下來針對這兩個方法重點介紹下:
Session::StreamOn
從方法名稱基本可以知道該方法主要用於開始硬件的數據輸出,具體點兒就是進行配置Sensor寄存器,讓其開始出圖,並且將當前的Session的狀態告知每一Node,讓它們在自己內部也做好處理數據的准備,所以之后的相關Request的流轉都是以該方法為前提進行的,所以該方法重要性可見一斑,其操作流程見下圖:
Session的StreamOn方法中主要做了如下兩個工作:
調用FinalizeDeferPipeline()方法,如果當前pipeline並未初始化,則會調用pipeline的FinalizePipeline方法,這里方法里面會去針對每一個從屬於當前pipeline的Node依次做FinalizeInitialization、CreateBufferManager、NotifyPipelineCreated以及PrepareNodeStreamOn操作,FinalizeInitialization用於完成Node的初始化動作,NotifyPipelineCreated用於通知Node當前Pipeline的狀態,此時Node內部可以根據自身的需要作相應的操作,PrepareNodeStreamOn方法的主要是完成Sensor以及IFE等Node的控制硬件模塊出圖前的配置,其中包括了曝光的參數的設置,CreateBufferManagers方法涉及到CamX-CHI中的一個非常重要的Buffer管理機制,用於Node的ImageBufferManager的創建,而該類用於管理Node中的output port的buffer申請/流轉/釋放等操作。
調用Pipeline的StreamOn方法,里面會進一步通知CSL部分開啟數據流,並且調用每一個Node的OnNodeStreamOn方法,該方法會去調用ImageBufferManager的Activate(),該方法里面會去真正分配用於裝載圖像數據的buffer,之后會去調用CHI部分實現的用戶自定義的Nod的pOnStreamOn方法,用戶可以在該方法中做一些自定義的操作。
Session::ProcessCaptureRequest
針對每一次的Request的流轉,都是以該方法為入口開始的,具體流程見下圖:
在這里插入圖片描述
上述流程可以總結為以下幾個步驟:
通過調用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數據,並更新至DRQ中,當該Node處理完成之后,會將處於m_deferredNodes中的已無依賴關系的Node移到m_readyNodes中,並再次調用DispatchReadyNodes方法從m_readyNodes取出Node進行處理。
與此過程中,當Node的數據處理完成之后會通過CSLFenceCallback通知到Pipeline,此時Pipeline會判斷當前Node的Output port 是否是Sink Port(輸出到CHI),如果不是,則會更新依賴項到DRQ中,並且將不存在依賴項的Node移到m_readyNodes隊列中,然后調用DispatchReadyNdoes繼續進入到DRQ中流轉,如果是Sink Port,則表示此Node是整個Pipeline的最末端,調用sinkPortFenceSignaled將數據給到Session中,最后通過調用Session中的NotifyResult將結果發送到CHI中。
DeferredRequestQueue
上述流程里面中涉及到DeferredRequestQueue這個概念,這里簡單介紹下:
DeferredRequestQueue繼承於IPropertyPoolObserver,實現了OnPropertyUpdate/OnMetadataUpdate/OnPropertyFailure/OnMetadataFailure接口,這幾個接口用於接收Meta Data以及Property的更新,另外,DRQ主要包含了以下幾個主要方法:
Create()
該方法用於創建DRQ,其中創建了用於存儲依賴信息的m_pDependencyMap,並將自己注冊到MetadataPool中,一旦有meta data或者property更新便會通過類中實現的幾個接口通知到DRQ。DispatchReadyNodes()
該方法主要用於將處於m_readyNodes隊列的Node取出,將其投遞到m_hDeferredWorker線程中進行處理。AddDeferredNode()
該方法主要用於添加依賴項到m_pDependencyMap中。FenceSignaledCallback()
當Node內部針對某次request處理完成之后,會通過一系列回調通知到DRQ,而其調用的方法便是該方法,在該方法中,會首先調用UpdateDependency更新依賴項,然后調用DispatchReadyNodes觸發開始對處於ready狀態的Node開始進行處理OnPropertyUpdate()
該方法是定義於IPropertyPoolObserver接口,DRQ實現了它,主要用於接收Property更新的通知,並在內部調用UpdateDependency更新依賴項。OnMetadataUpdate()
該方法是定義於IPropertyPoolObserver接口,DRQ實現了它,主要用於接收Meta data更新的通知,並在內部調用UpdateDependency更新依賴項。UpdateDependency()
該方法用於更新Node的依賴項信息,並且將沒有依賴的Node從m_deferredNodes隊列中移到m_readyNodes,這樣該Node就可以在之后的某次DispatchReadyNodes調用之后投入運行。DeferredWorkerWrapper()
該方法是m_hDeferredWorker線程的處理函數,主要用於處理需要下發request的Node,同時再次更新依賴項,最后會再次調用DispatchReadyNodes開始處理。
其中需要注意的是,Pipeline首次針對每一個Node通過調用AddDeferredNode方法加入到DRQ中,此時所有的Node都會加入到m_readyNodes中,然后通過調用dispatchReadyNodes方法,觸發DRQ開始進行整個內部處理流程,基本流程可以參見下圖,接下來就以該圖進行深入梳理下:
在這里插入圖片描述
當調用了DRQ的dispatchReadyNodes方法后,會從m_readyNodes鏈表里面依次取出Dependency,將其投遞到DeferredWorkerWrapper線程中,在該線程會從Dependency取出Node調用其ProcessRequest方法開始在Node內部處理本次request,處理完成之后如果當前Node依然存在依賴項,則調用AddDeferredNode方法將Node再次加入到m_deferredNodes鏈表中,並且加入新的依賴項,存入m_pDependencyMap hash表中。
在Node處理request的過程中,會持續更新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繼續進行處理。
5. 上傳拍照結果
在用戶開啟了相機應用,相機框架收到某次Request請求之后會開始對其進行處理,一旦有圖像數據產生便會通過層層回調最終返回到應用層進行顯示,這里我們針對CamX-CHI部分對於拍照結果的上傳流程進行一個簡單的梳理:
每一個Request對應了三個Result,分別是partial metadata、metadata以及image data,對於每一個Result,上傳過程可以大致分為以下兩個階段:
Session內部完成圖像數據的處理,將結果發送至Usecase中
Usecase接收到來自Session的數據,並將其上傳至Provider
首先來看下Session內部完成圖像數據的處理后是如何將結果發送至Usecase的:
在這里插入圖片描述
在整個requets流轉的過程中,一旦Node中有Partial Meta Data產生,便會調用Node的ProcessPartialMetadataDone方法去通知從屬的Pipeline,其內部又調用了pipeline的NotifyNodePartialMetadataDone方法。每次調用Pipeline的NotifyNodePartialMetadataDone方法都會去將pPerRequestInfo→numNodesPartialMetadataDone加一並且判斷當前值是否等於pipeline中的Node數量,一旦相等,便說明當前所有的Node都完成了partial meta data的更新動作,此時,便會調用ProcessPartialMetadataRequestIdDone方法,里面會去取出partial meta data,並且重新封裝成ResultsData結構體,將其作為參數通過Session的NotifyResult方法傳入Session中,之后在Session中經過層層調用最終會調用到內部成員變量m_chiCallBacks的ChiProcessPartialCaptureResult方法,該方法正是創建Session的時候,傳入Session中的Usecase的方法(AdvancedCameraUsecase::ProcessDriverPartialCaptureResultCb),通過該方法就將meta data返回到了CHI中。
同樣地,Meta data的邏輯和Partial Meta Data很相似,每個Node在處理request的過程中,會調用ProcessMetadataDone方法將數據發送到Pipeline中,一旦所有的Node的meta data否發送完成了,pipeline會調用NotifyNodeMetadataDone方法,將最終的結果發送至Session中,最后經過層層調用,會調用Session 中成員變量m_chiCallBacks的ChiProcessCaptureResult方法,將結果發送到CHI中Usecase中。
圖像數據的流轉和前兩個meta data的流轉有點兒差異,一旦Node內部圖像數據處理完成后便會調用其ProcessFenceCallback方法,在該方法中會去檢查當前輸出是否是SInk Buffer,如果是則會調用Pipeline的SinkPortFenceSignaled方法將數據發送到Pipeline中,在該方法中Pipeline又會將數據發送至Session中,最后經過層層調用,會調用Session 中成員變量m_chiCallBacks的ChiProcessCaptureResult方法,將結果發送到CHI中Usecase中。
接下來我們來看下一旦Usecase接收到Session的數據,是如何發送至Provider的:
我們以常用的AdvancedCameraUsecase為例進行代碼的梳理:
在這里插入圖片描述
如上圖所示,整個result的流轉邏輯還是比較清晰的,CamX通過回調方法將結果回傳給CHI中,而在CHI中,首先判斷是否需要發送到具體的Feature的, 如果需要,則調用相應Feature的ProcessDriverPartialCaptureResult或者ProcessResult方法將結果發送到具體的Feature中,一旦處理完成,便會調用調用CameraUsecaseBase的ProcessAndReturnPartialMetadataFinishedResults以及ProcessAndReturnFinishedResults方法將結果發送到Usecase中,如果當前不需要發送到Feature進行處理,就在AdvancedCameraUsecase中調用CameraUsecaseBase的SessionCbPartialCaptureResult以及SessionCbCaptureResult方法,然后通過Usecase::ReturnFrameResult方法將結果發送到ExtensionModule中,之后調用ExtensionModule中存儲的CamX中的回調函數process_capture_result將結果發送到CamX中的HALDevice中,之后HALDevice又通過之前存儲的上層傳入的回調方法,將結果最終發送到CameraDeviceSession中。
通過以上的梳理,可以發現,整個CamX-CHI框架設計的很不錯,目錄結構清晰明確,框架簡單高效,流程控制邏輯分明,比如針對某一圖像請求,整個流程經過Usecase、Feature、Session、Pipeline並且給到具體的Node中進行處理,最終輸出結果。另外,相比較之前的QCamera & Mm-Camera框架的針對某個算法的擴展需要在整個流程代碼中嵌入自定義的修改做法而言,CamX-CHI通過將自定義實現的放入CHI中,提高了其擴展性,降低了開發門檻,使得平台廠商在並不是很熟悉CamX框架的情況下也可以通過小規模的修改成功添加新功能。但是人無完人,框架也是一樣,該框架異步化處理太多,加大了定位問題以及解決問題的難度,給開發者帶來了不小的壓力。另外,框架對於內存的要求較高,所以在一些低端機型尤其是低內存機型上,整個框架的運行效率可能會受到一定的限制,進而導致相機效率低於預期
原文鏈接:https://blog.csdn.net/u012596975/article/details/107138576
相關文章友情推薦
1. Android開發干貨分享
至此,本篇已結束。轉載網絡的文章,小編覺得很優秀,歡迎點擊閱讀原文,支持原創作者,如有侵權,懇請聯系小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!
點個在看,方便您使用時快速查看!