高通相机ConfigureStream配流过程分析

0d6884555d73ab3d3c398c2b71456d36.gif

和你一起终身学习,这里是程序员Android

经典好文推荐,通过阅读本文,您将收获以下知识点:

从CameraDeviceSession开始

这个调用是app通过createCaptureSession一路下来的(CameraDeviceClient–>Camera3Device–>CameraDeviceSession)

\hardware\interfaces\camera\device\3.4\default\CameraDeviceSession.cpp

542a314d8b9c92e4e42789b22ee898d4.jpeg
37a1d5f5c3ed0179a50d73967576ed8e.jpeg
image.png

从这里,

即将进入CamX-CHI

对一下结构体名字,没错

602ea45309d75e5f39bc91f867dc990a.jpeg
e77462022d9ed357c39bdb96cb80cad0.jpeg

又要到camxhal3entry中去找了

\vendor\qcom\proprietary\camx\src\core\hal\camxhal3entry.cpp

d7f154747c15e6906964c9f481e21514.jpeg

下面几步跳转和open类似,不再赘述

b3b68fee8b2cc5f727abd30ce9aa6291.jpeg

\vendor\qcom\proprietary\camx\src\core\hal\camxhal3.cpp

b291215334f2ff9086a05a96211fae12.jpeg
22f4500c16ea653c73b587a4ef13ea79.jpeg
a968c4455f7fdae73020b508a24edc59.jpeg

\vendor\qcom\proprietary\camx\src\core\hal\camxhaldevice.cpp

df5de7b06bbcb645c2ab8b54f0ea7e63.jpeg

7d2297e69047982fc9f43d0ab1a56498.jpeg

很直观,先获取chi的接口对象,再通过这个对象去到chi实现中

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxextensioninterface.cpp

40ac08c55a70fdbd86d1699dcbd5c588.jpeg

这个函数有六七百行,大略看一下:

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxextensionmodule.cpp

eaff80db8eec7a1584cacb29cb7e0f92.jpeg

这个g_chiContextOps应该是CamX提供给CHI的对象,其中函数指针的映射是在ExtensionModule的初始化中完成的过程就不细讲了,可以看这段解释:

“CHI中的ExtensionModule在初始化的时候,其构造方法中也会通过调用dlopen方法加载camera.qcom.so库,并将其入口方法ChiEntry通过dlsym映射出来,之后调用该方法,将g_chiContextOps(ChiContextOps,该结构体中定义了很多指针函数)作为参数传入CamX中,一旦进入CamX中,便会将本地的操作方法地址依次赋值给g_chiContextOps中的每一个函数指针,这样CHI之后就可以通过g_chiContextOps访问到CamX方法。”

ChiEntry方法中的函数指针定义在:

\vendor\qcom\proprietary\camx\src\core\chi\camxchi.cpp

bed56895eaef4bab47a04f97a74b3933.jpeg

接着往下看,进行了很多StreamConfig的设置

90c2377e49d26055a4b367d41f4ccef7.jpeg

第一个框,看一下做了什么:

f118c659610eb17bacd6b9ad8a1059ad.jpeg

把当前camera对应的HalOps映射到m_HALOps本地,pHalOps是更上层传入的

回到InitializeOverrideSession中,第二个框看上去就很重要了,涉及到了Usercase的选择,有了之前的印象,我们可以知道Usecase是camx针对不同的stream建立的对象,用来管理选择feature,并且创建 pipeline以及session。

(更详细的介绍可以看https://blog.youkuaiyun.com/u012596975/article/details/107138576)

a1d118de747d671b58cfb4e6c7b1f6c5.jpeg

如此看来,接下来的m_pUsecaseSelector->GetMatchingUsecase这一步调用重要性可见一斑,看看具体做了什么:

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxusecaseutils.cpp

0a0deb0e27e0d58f05a78b4c8a85be22.jpeg

果然如同上述介绍所说,这里根据不同的使用场景,选择了不同的usecaseId,usecaseId结构的定义在:

\vendor\qcom\proprietary\chi-cdk\core\chiutils\chxdefs.h

b898c44e097b6ceb7ef49ab08bd92678.jpeg

最后把选择的usecaseId返回InitializeOverrideSession中,这里就要真正地创造usecase对象了:

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxextensionmodule.cpp

a03f72f67059dd7e8236b759b074123a.jpeg

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxusecaseutils.cpp

21dbc371ce9e674911dea5d27e087273.jpeg

可以看到,根据不同的usecaseId调用不同的Create方法向上返回usecase对象。并且通过这些case的名字来判断,预览应该走的是PreviewZSL的case,也就是创建了一个AdvancedCameraUsecase的对象,这个AdvancedCameraUsecase类也是使用最多的usecase。

至此,爽快直接,逻辑通顺,看来跟踪的流程是正确的

接着看AdvancedCameraUsecase::Create具体做了什么:

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

8a2954e0006395145f34730d0cc41a05.jpeg

4910741df12bca14559e789d76603e1d.jpeg

可以注意到,调用了一个从XML中获取usecase的方法GetXMLUsecaseByName,参数定义如下:

09e93180c08bbe719bdf8d2a73872afb.jpeg

这里很容易联想到第一篇中定义了pipeline、node的那个xml,进去看一下:

c612b2eb60fb11fe836d473a46c488b2.jpeg

这里并没有预想中打开特定xml之后读取的动作,看了一下高亮的这个变量应该是存储了所有的usecase,不幸的是里面涉及的数据结构比较复杂,这里先不深入了

回到Initialize:

9a3fec623de0c53b41e33cb11254363e.jpeg

又出现了关键词feature,跟进去看一下:

3dad976813016aa541d3a14d84f903fd.jpeg

1fee37fd1e8758d5d85305c7b4b2863c.jpeg

edd5af1850d1f2060266ff090641859a.jpeg

很长,但是可以理解。通过一系列设置,整了一个featureWrapper对象,最后把选好的feature设置到pStreamConfig里去

回到Initialize,看看用这个StreamConfig又干嘛了:

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

894802d3768a6ed6b35c536b7ee8541d.jpeg

可以看到,FeatureSetup中把pStreamConfig设置好之后,就丢进了SelectUsecaseConfig进行下一步处理

5e7b0f3aa2e14ec2911c88b83a1fbc67.jpeg

这下,出现了两个很惊人的函数调用,首先看ConfigureStream,没想到是在这里调用

94b3506cd44537a3d23b3a85e86ef0ef.jpeg

e67c6753740fb2748cf127e59727a48f.jpeg

根据不同的usecaseId,把pStreamConfig中带下来的stream们保存到不同的本地stream变量里

这里大概能看出预览和拍照应该分别使用的是m_pPreviewStream和m_pSnapshotStream

13c78dfba6c1566b064a9a1cb92f423a.jpeg

确认这两个stream保存下来之后,计算了一个纵横比保存下来,最后调用了ConfigFdStream

21aa956cc7ab69faa57b851c33ac7c59.jpeg
image.png

看名字以为和设备有关,看上去也只是把一些参数设置到m_pFdStream变量中去,看一下这个m_pFdStream的定义:

798ae06d11fabb0050b6ab174df77784.jpeg

看不出什么端倪,等等看之后的流程会不会用到吧

configStream算完事了,接下来回到SelectUsecaseConfig看BuildUsecase做了什么

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

1a4a2d7582ff5537ba4974eaa9bf6a7a.jpeg

4aba9557d560a923409b1312af7178d2.jpeg

主要填充了这两个Pipelines的数组

并且从6945行开始的两层循环可以看出来,每个物理摄像头可能对应多个feature,然后会把feature里的每个pipeline都保存下来

e8f92f741a586d0f3d1b55bc38f7af11.jpeg

接着,usercase相关的信息也会保存到m_pClonedUsecase中

这样,SelectUsecaseConfig完成了需要关注的事务,回到AdvancedCameraUsecase::Initialize中

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

b7dc782460b00e690ea8edc6293e4d75.jpeg

从SelectUsecaseConfig出来之后对m_pCallbacks做了一系列赋值操作,看看这个callback具体是callback什么,定义在

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.h

d68b6c73ce914186c807ce4bb6eb14d1.jpeg

可以看出,主要就是result的回调,那看来还挺重要的。

第一个框里可以看出,每一个pipeline都有其callback(也可以从初始化的时候,给m_pCallbacks分配空间时候看出,5194行,用pipeline的num来申请空间)

第二个框,调用了CameraUsecaseBase::Initialize,把刚映射完成的callback传进去

62bf73b522b22b6b978729b2fcd5ae90.jpeg

框里的注释可以看出来,session和pipeline是一对一的关系,数量是一样的

接着往下看:

9b4ee66027ed0872c816600f0d9f0f3e.jpeg

这里,终于正式开始创建pipeline,从循环次数和CreatePipeline接收的参数可以看出,这里也是把每个pipeline都创建出来,接着进入CreatePipeline中,注意一下第二个参数PipelineType::Default,值为5

9255f89d69815a498c14ee3612eff362.jpeg

b645aa3519b84db9c614fa2b6836a70c.jpeg

继续看Pipeline::Create

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxpipeline.cpp

38eb7aa44a60025f0f4c48b730b30ea3.jpeg

代码中能看到的Pipeline类有两个,chxpipeline,和camxpipeline。

虽然从参数来看可以知道这个Initialize调用到的是chxpipeline,但是camxpipeline是什么作用呢?它和chxpipeline有类似的接口设置,又有何用意?先把这个问题保留,对不上号的camxpipeline的参数是input/output两个data,留个印象:

df52449311c8664b0c7b80fbaa46bbf5.jpeg

接着先看chxpipeline中的Initialize

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxpipeline.cpp

2d19cc5407b3e28805c2d3c3bee1813f.jpeg

为pipeline中的一系列成员变量赋值,注意445行,这里应该是不进if的,记得刚才提到传入的type吗,是Default而不是OfflinePreview

接下来可以回到CameraUsecaseBase::CreatePipeline中继续往下看了

9061a09c098fff2e58bcb970c524b8c6.jpeg

可以看到对outputbuffer和inputbuffer中所有的sink和src进行了配置

sink和src又是什么呢,看它们和output/input的对应关系加上百度了一些相关知识,大概了解到sink一般对应一个模块的输出部分,src当然就是输入部分

那么推测这部分就是把pipeline中输入输出部分的buffer、node和port配置了一下

CreatePipeline中接着往下

ecd1dbc129807c69c5ab4204c35427dc.jpeg

配置好的pSinkTarget和pSrcTarget统一set到pPipelineData中,这些set方法没什么好说的,就是把传入的参数都保存到对应的成员变量里去。

再看看最后一个框中的调用:

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxpipeline.cpp

a533b0b46f61d07c28f05fa1f7d18da5.jpeg

用所有保存好的信息,创建pipelineCreateData对象,然后传到ExtensionModule中的CreatePipelineDescriptor

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxextensionmodule.cpp

1bf8b5e6e32ad409cebc6d2f46ca1f5a.jpeg

通过老朋友g_chiContextOps继续,这里就是向camx的调用了,不记得这个对象就看这里的函数映射关系

vendor\qcom\proprietary\camx\src\core\chi\camxchi.cpp

https://www.notion.so

继续跟着CreatePipelineDescriptor往下追踪:

\vendor\qcom\proprietary\camx\src\core\chi\camxchicontext.cpp

https://www.notion.so

主要关注这个函数中,对pPipelineDescriptor这个对象做了什么,毕竟是要返回上去的,上图只能看到一些常规的赋值,接着往下看:

9cdc574c24bcbc52ba27978ad6d153a3.jpeg

到这里,第一个框,大概知道根据pPipelineDescriptor中的信息,对pipelineCreateInputData和pipelineCreateOutputData做了一些设置。

而第二个框就有意思了,这两个参数唤起了沉睡的记忆,刚在initialize的时候遇到了两个pipeline类的问题,这里传入Pipeline::Create的是camxpipeline中的函数接收的input/output参数,也就可以回答上面提到的camxpipeline类是用来做什么的了。

解决了之前留下的问题,于是这里的Create调用到:

\vendor\qcom\proprietary\camx\src\core\camxpipeline.cpp

1dfe5a5961c15f4448d47a3afba3ee83.jpeg

这里就相当冗长了

6cdf6fd666dd288dc519b2398628bae3.jpeg

创建了一系列lock,后面也是很多从函数名看不出端倪的调用

追流程要抓关键,这个函数最终会返回result,那么很有理由相信result会作为关键调用的返回值被赋值,有了这个思想之后看看哪些地方修改了result的值

77f1eb431a3be24fc2145824579f9fd9.jpeg

果然,看到了这个想看到的函数,进入看看:

\vendor\qcom\proprietary\camx\src\core\camxpipeline.cpp

e48a3252dedd0c2a844e0e26dc18630a.jpeg

可以看到,node数量和node信息之类的值都是pCreateInputData->pPipelineDescriptor带下来的,也就是说pipeline中就已经决定了使用哪些node

同样,追着result往下看:

9f9173b50cc6b75f9651334a6dfab344.jpeg

\vendor\qcom\proprietary\camx\src\core\camxnode.cpp

0882fa15372e6d6311860dcf529beb43.jpeg
image.png

这里创建了一个pFactory对象,这个对象赋值的时候同时把HwEnviroment中的HwFactory单例创建出来了。

这部分流程看上去不太重要,但是可以说明白pFactory->CreateNode调用的位置,不想看可以跳过,这里大致列一下调用流程:

\vendor\qcom\proprietary\camx\src\core\camxhwenvironment.h

4e814c8e07a58489de1a3d7e8edecacf.jpeg

m_pHwFactory是怎么创造出来的呢:

\vendor\qcom\proprietary\camx\src\core\camxhwenvironment.cpp

182692e24200dc3363fdde8fab22ed86.jpeg

\vendor\qcom\proprietary\camx\src\core\camxhwenvironment.cpp

ae2b2e1445e1c26ce20b114b47207aa8.jpeg

要素察觉,看着又是一系列函数指针定向

\vendor\qcom\proprietary\camx\src\hwl\titan17x\camxtitan17xhwl.cpp

0662263787e95bf68d9f60fa9e50ed79.jpeg

\vendor\qcom\proprietary\camx\src\hwl\titan17x\camxtitan17xfactory.cpp

f9df633267970c9bf90752ad054aa63f.jpeg

回到Node::Create中

\vendor\qcom\proprietary\camx\src\core\camxnode.cpp

0084dba928abb84cc2856be336e95523.jpeg

通过上面支线解析可以知道,pFactory是HwFactory的子类Titan17xFactory的一个对象

所以CreateNode的调用位置就很好知道了:

\vendor\qcom\proprietary\camx\src\core\camxhwfactory.cpp \vendor\qcom\proprietary\camx\src\hwl\titan17x\camxtitan17xfactory.cpp

d86cf683875f30e400e61de5ed91b9cf.jpeg

终于看到了这些node最后被创造的地方,向上返回不同类型的Create创建返回的nodes

一直回到这里:

\vendor\qcom\proprietary\camx\src\core\camxnode.cpp

a3ec3a7683707370180835a027cbc2bf.jpeg

可以看到pNode还会调用一个Initialize函数,但是注意pNode创建的时候是由不同类型的Create创建返回的,所以这里不去关注这些node的初始化过程,接着向上返回

回到\vendor\qcom\proprietary\camx\src\core\camxpipeline.cpp:

b860d3ec425c8d5f797303b5f84dd89d.jpeg

看上去node创建出来之后,还对这个node的各种属性进行赋值

接着往下:

63f81f83841243f981c2e1d533f82b08.jpeg

如注释所说,创建node的连接关系,输入输出port、link等等

这样一直到最后,完成CreateNodes的工作,回到

\vendor\qcom\proprietary\camx\src\core\camxpipeline.cpp

Initialize中:

66ef77af94ea89d20ae1899e77e6dc4d.jpeg

接下来也没有什么值得说道的,Pipeline::Initialize结束,标记这个pipeline已经初始化后,向上返回

回到CreatePipelineDescriptor

\vendor\qcom\proprietary\camx\src\core\chi\camxchicontext.cpp

87c829567897c8275ea40ff4bd6dc0a4.jpeg

pPipelineDescriptor创建好了,继续向上返回到ChiCreatePipelineDescriptor

\vendor\qcom\proprietary\camx\src\core\chi\camxchi.cpp

aad13234d5447469996eb0394ac5a183.jpeg

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxextensionmodule.cpp

ecc12e02e73f6b5e79e2d6ab290f8253.jpeg

依然向上返回到CreateDescriptor中

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxpipeline.cpp

6de8a4b012367f3b0ab05a4d7c87489f.jpeg

创建出来的descriptor保存到m_hPipelineHandle

fea443f01f06a5664f0459d2f7ee0ec1.jpeg

再把信息保存到m_pipelineInfo.hPipelineDescriptor,继续向上返回到CreatePipeline中

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

272664df387706e11ce9ca0f9f88ea86.jpeg

CreatePipeline也就这样结束,看看Initialize中还做了什么

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

34062e69507555934f317d4fc27cd398.jpeg

除了pipeline,看样子还创造了session,omfg以为已经结束了

先不管这个session的创建过程,有机会再细看吧

这样看完了CameraUsecaseBase::Initialize的过程,调用这个Initialize的上层是AdvancedCameraUsecase::Initialize

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp

6fa9ab2294d8540df1308a801380e590.jpeg

把usecase创建完成的事post一下,主要是把feature

AdvancedCameraUsecase::Initialize中,重要的调用(

GetXMLUsecaseByName、FeatureSetup、SelectUsecaseConfig、CameraUsecaseBase::Initialize)执行完毕后,也继续向上返回

\vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxadvancedcamerausecase.cpp \vendor\qcom\proprietary\chi-cdk\core\chiusecase\chxusecaseutils.cpp

72a544e198f8f648ce204004017839b1.jpeg

继续一路返回到ExtensionModule::InitializeOverrideSession中:

\vendor\qcom\proprietary\chi-cdk\core\chiframework\chxextensionmodule.cpp

987b8da62bb105c8120561a7da59028c.jpeg

553af9dd224a00761d3d5e790d926754.jpeg

至此,一个logicalCamera对应的usecase就创建出来,可以看到创建好之后对这个对象的其他成员也都纷纷赋值或者新建,然后就继续向上返回

\vendor\qcom\proprietary\camx\src\core\hal\camxhaldevice.cpp

0bed07b1520116d842131e1953cb385b.jpeg

成功创建则向上返回true赋值状态

334773821c92ffce5d7496d1b94dcdf2.jpeg

向上:

\vendor\qcom\proprietary\camx\src\core\hal\camxhal3.cpp

b1a339c21c18f7ce1259513881d7b0fe.jpeg
image.png

confige完毕之后,把stream的信息打印了一滩,之后也是向上返回

d40042fb3bfd87bf9b492af614954f78.jpeg
image.png

之后就会一路回到cameraservice了

ConfigureStream整体流程参考:

6b1fc69dd807509b5ae272f02a0b7d86.jpeg
image.png

来源: 影像技术栈
文章作者: Abalone
文章链接: https://camerastacker.com/2022/092522604.html

至此,本篇已结束。转载网络的文章,小编觉得很优秀,欢迎点击阅读原文,支持原创作者,如有侵权,恳请联系小编删除,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

238881af5fb4932c6ee686667b95ce1d.jpeg

点个在看,为大佬点赞!

### 高通相机 Buffer 的定义与用途 #### 定义 在高通相机框架中,`buffer` 是指用于存储图像数据或其他媒体数据的内存区域。每个 `buffer` 可以被看作是一个指向特定 `stream` 的引用[^1]。具体来说: - **Input Buffer**: 这是指向输入的数据缓冲区,通常由应用程序提供给硬件模块处理。一个请求可能只包含一个 `input_buffer`。 - **Output Buffers (复数)**: 输出缓冲区则负责接收经过处理后的数据结果。这些缓冲区可以有多个,并且它们共同构成了一种队列机制。 #### 作用 1. **Stream 数据传递** 每个 `buffer` 被设计用来指向某个具体的 `stream`,而这个 `stream` 则具有明确的格式定义。这意味着不同的 `buffer` 可能共享同一个 `stream` 或者分别对应于独立的 `streams`。 2. **多 Buffer 协同工作** 在某些情况下,例如视频预览功能实现过程中,为了提高效率并减少延迟,往往需要利用一组轮使用的 `buffers` 来形成所谓的“队列”。这种做法能够确保即使在一个较长时间段内持续采集或显示画面时也能保持畅性。 3. **资源分管理** 关于如何有效地管理和分这些 buffers,在实际开发中有专门函数来进行操作,像下面展示的一个例子就是关于单 buffer 分的方法: ```cpp int QCameraMemory::allocOneBuffer(QCameraMemInfo &memInfo, unsigned int heap_id, size_t size, bool cached, bool secure_mode); ``` 此方法允许开发者指定所需 memory block 的大小以及其他属性参数以便创建合适的 buffer 实例供后续使用[^3]。 4. **HAL 层交互支持** 当涉及到更底层的操作如打开设备连接等情况下的初始化过程,则可以通过 HAL(Hardware Abstraction Layer) 接口完成相应置动作: ```cpp hardware/interfaces/camera/common/1.0/default/CameraModule.cpp int CameraModule::open(const char* id, struct hw_device_t** device) { res = filterOpenErrorCode(mModule->common.methods->open(&mModule->common, id, device)); return res; } ``` 上述代码片段展示了开启摄像头服务的核心逻辑之一——调用 open 函数建立必要的上下文环境准备阶段[^4]。 5. **应用场景适调整** 最后值得一提的是,在面对复杂光照条件变化等特殊需求场合下(比如夜间拍摄),整个 pipeline 中各个组件之间还需要紧密合才能达成最佳视觉体验目标。此时,来自上层应用层面发出指令最终会逐级传导至 sensor 级别从而触发相应的动态响应行为[^5]: > 相机 APP 通过光感发起场景切换... --- ### 总结 综上所述,高通相机中的 stream buffer 不仅承担着基础性的数据传输职责,而且还在性能优化以及跨层次协作方面发挥重要作用。理解其概念及其背后的工作原理对于从事相关技术研究或者产品开发人员而言至关重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值