和你一起一生学习,这里是程序员 Android
本篇文章次要介绍 Android
开发中的局部知识点,通过浏览本篇文章,您将播种以下内容:
一、概览
二、根本组件概念
三、组件构造关系
四、要害流程详解
一、概览
回顾高通平台 Camera HAL 历史,之前高通采纳的是 QCamera & MM-Camera 架构,然而为了更精细化管制底层硬件(Sensor/ISP 等要害硬件),同时不便手机厂商自定义一些性能,当初提出了 CamX-CHI 架构,因为在 CamX-CHI 中齐全看不到之前老架构的影子,所以它齐全是一个全新的架构,它将一些高度对立的功能性接口抽离进去放到 CamX 中,将可定制化的局部放在 CHI 中供不同厂商进行批改,实现各自独有的特色性能,这样设计的益处不言而喻,那便是即使开发者对于 CamX 并不是很理解,然而仍然能够很不便的退出自定义的性能,从而升高了开发者在高通平台的开发门槛。
接下来咱们以最直观的目录构造动手对该架构做一个简略的意识,以下便是 CamX-CHI 根本目录构造:
该局部代码次要位于 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 作为基类存在,定义了一系列通用办法。
几个罕用的 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
三、组件构造关系
通过之前的介绍,咱们对于几个根本组件有了一个比拟清晰地意识,然而任何一个框架体系并不是仅靠组件胡乱堆砌而成的,相同,它们都必须基于各自的定位,依照各自所独有的行为模式,同时依照约定俗称的一系列规定组合起来,共同完成整个框架某一特定的性能。所以这里不得不产生一个疑难,在该框架中它们到底是如何组织起来的呢?它们之间的关系又是如何的呢?接下来咱们以下图动手开始进行剖析:
由上图能够看到,几者是通过蕴含关系组合起来的,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 库,获取了对方的入口办法,最初通过彼此的入口办法获取了对方操作方法汇合,之后再通过这些操作方法与对方进行通信,其次要流程见下图:
从上图不难看出,在 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/u012596…
情谊举荐:
Android 干货分享
至此,本篇已完结,如有不对的中央,欢迎您的倡议与斧正。同时期待您的关注,感谢您的浏览,谢谢!