ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

高通CamX架构解析:从核心组件到一帧图像的完整数据流

高通CamX架构解析:从核心组件到一帧图像的完整数据流 做高通平台camera bringup这些年大部分人第一次接触CamX都有同一个感觉资料少、文档零散代码又绕。这很正常因为CamX本身就是一个大而全的相机软件框架从Android Camera HAL3往下一直到ISP驱动全都凑在这套体系里。如果连它的核心组件和数据流都没摸清后面不管是调bug、改feature还是拉性能都会处处碰壁。这篇主要聊两件事一是CamX的核心组件到底有哪些各自干什么活二是一帧图像从Sensor到最终预览/拍照/录像数据是怎么在组件之间流起来的。内容适合正在做高通camera bringup的工程师、打算从上层应用往底层驱动走的开发者以及所有被CamX源码绕晕的新手。1. 为什么会有CamX从老架构到新架构的必然1.1 mm-camera的痛点高通上一代相机软件框架是mm-camera很多老工程师都对它又爱又恨。爱的是结构简单想改哪里直接改恨的是扩展性差每加一个feature都要动一堆代码。最典型的问题就是Pipeline写得太死Sensor数据进来以后走哪条路、经过哪些模块基本是定死的想做多帧HDR、双摄虚化这类功能的代价特别高经常要绕开原有通路单独拉一条线。到了骁龙845以后平台对相机能力的要求明显变了多摄同时工作、AI场景识别、高分辨率raw dump、实时HDR这些都不是“在旧架构上打补丁”能解决的事。高通最终选择和Linux内核走类似的路子——推倒重来设计一套可扩展、可配置、模块化的框架。这就是CamX全称Camera eXtension framework。CamX的定位很清楚不再是一个“给某个芯片写死的相机驱动”而是一套能适配不同Sensor、不同ISP能力、不同算法组合的通用框架。通俗点说mm-camera像一条方向固定、无法挪动的传送带CamX像一间可以自由调整产线的工厂每条流水线Pipeline按需拼接每个工位Node按需插拔。1.2 CamX的设计目标与分层思想CamX设计上有几个非常明确的目标理解了这些目标后面看代码就不容易迷路。第一个目标是兼容Android Camera HAL3接口上层应用完全无需感知底层变化。Camera HAL3是Android定义的一套统一相机接口CamX从HAL3的openCamera、configureStreams、processCaptureRequest一路接进来保证上层framework和app的行为没有任何改动。第二个目标是支持多路流并发。现代手机动辄五摄六摄前后摄同时工作、多路raw流同时输出都是标配需求。CamX用Session和Pipeline的模型把多路流拆成多个独立但可共享资源的执行单元避免一路流的异常拖垮整个相机。第三个目标是灵活性优先于性能。性能当然重要但CamX先保证了“软件架构上所有功能都能被自由组合”性能优化放在后面通过buffer复用、低延迟链路设计和硬件加速去解决。这个取舍和很多嵌入式团队“先跑通再调优”的思路是一致的。从分层上看CamX大致可以分成四层最上层是Android HAL3接口层负责和CameraService通信完成标准HAL3语义转换中间是CHI层Camera Hardware Interface负责feature graph、拓展算法、vendor tag、调优配置这些灵活多变的东西再往下是CamX核心层负责Session、Request、Pipeline、Node、Buffer、Metadata这些主体逻辑最底下是CSLCamera Services Layer和KMD内核驱动负责把用户态请求翻译成硬件ISP、Sensor等设备能理解的命令。这四层各有各的任务但又是环环相扣的。下面我按从下往上、再回到数据通路的顺序把核心组件逐个拆开讲。2. 核心组件逐个拆解2.1 最底层CSL与KMD驱动CSL全称Camera Services Layer这一层很多资料里不太讲但它是CamX和内核驱动的唯一通道。CSL封装了所有对内核设备的访问包括打开设备、提交request、等待事件、映射buffer、读取sensor寄存器等等。用户态代码不会直接open /dev/video0而是通过CSL的接口统一完成。为什么要单独包一层CSL因为KMD内核驱动的功能在不同芯片上会有差异比如ISP硬件能力、统计信息格式、同步机制都可能不一样。CSL把这些差异统一封装成CamX核心层能理解的API核心层的代码不需要关心底下是骁龙8 Gen 1还是8 Gen 2。KMD侧的核心驱动有几个建议按重要程度记一下cam_req_mgr内核态request管理驱动负责接收用户态提交的capture request调度ISP硬件工作并在每帧完成后产生completion事件cam_isp驱动管理ISP硬件负责IFE/BPS等模块的配置和输出cam_sensor驱动管理sensor的供电、时钟、I2C通信、流控cam_sync驱动用于多sensor同步和多pipeline同步确保多摄帧对齐。我们调bug时最常用的log入口比如打开设备失败、request超时、ISP error很多都是先从CSL或KMD层报上来的。做底层开发的人一定要学会把这层log和用户态的log对应起来看。注意调试CSL和KMD问题时先确认内核设备节点权限和SElinux上下文很多奇怪的“打开失败”其实是权限问题而不是代码问题。2.2 中间层CHI与chi-cdkCHI层可能是CamX里最容易被误解的组件。很多人以为CHI就是某个代码目录其实CHI既是一组接口也是一套设计理念。CHI的核心思路是把“怎么做相机功能”这件事从CamX主体框架中剥离出来交给芯片厂商、OEM和算法厂商去定制而不用改动CamX本身。CHI的实体是chi-cdkCamera Hardware Interface - Camera Development Kit它在CamX之上封装了一个可编程的引擎支持用XML或代码方式定义feature graph。feature graph是CHI最精髓的东西它可以组合多个node让一帧数据按图上的路径流动比如常见的“多帧RAW输入→对齐降噪→HDR合成→YUV输出”就是一张典型的feature graph。CHI还承担了高通vendor tag的注册和管理。Android的vendor tag是厂商自定义metadata的扩展机制CHI把这些tag统一管理起来让上层通过标准HAL3接口就能读写厂商私有信息比如某个Sensor的曝光表、高通特有的降噪强度参数等。在启动流程里HAL3的入口会通过CHI override机制决定当前使用哪个usecase。默认是HAL3UseCase对应标准的Android camera功能如果接的是外挂Sensor比如UVC则可能走ChiUseCase。这种“override”的能力也是CHI存在的意义——让不同产品形态都可以复用同一套CamX核心。2.3 核心编排Session、Pipeline、Request、Node这几个概念是CamX的灵魂我尽量用大白话讲清楚。Session是最大粒度的执行单元一个Session对应一次相机会话。比如打开后置主摄的逻辑可以对应一个Session同时打开主摄和长焦做双摄可以用一个Session包含两条Pipeline也可以用两个Session各管一条。Session之间资源共享得少Session内部资源共享得多这是设计时的基本判断。实际代码里Session负责持有pipeline列表、管理pipeline间同步、维护全局资源。Pipeline是一条逻辑流水线由一组Node按拓扑连接组成。一条典型的preview pipeline可能是Sensor Node产生raw数据IFE Node做Bayer处理IPENode做YUV后处理最后输出到display buffer。Pipeline的拓扑在usecase配置时确定但也有不少动态修改的场景比如变焦时切换Sensor模式就需要更新Node参数而不是整个Pipeline重建。Node是流水线上最小的功能单元每个Node负责一种具体操作。Sensor Node管出图IFE Node管ISP前端JPEG Node管编码Stats Node管统计信息采集。Node之间通过端口连接数据从源端口流向sink端口。Node在执行时既可以调用硬件通过CSL也可以纯软件处理这取决于Node类型是external node还是kernel node。Request是整个系统运转的“指令单”。一次capture request对应Android HAL3里的一次processCaptureRequest调用里面携带了输出buffer、metadata、目标stream等所有信息。CamX内部会为每个request生成一个request id整个pipeline的所有node都以这个id为线索协同工作。我经常用快递仓库来打比方Session是仓库Pipeline是分拣线Node是每个分拣员Request就是贴在每个包裹上的面单。包裹沿着分拣线走每个分拣员看面单做事最终把包裹送到对应货架。2.4 Buffer与MetadataCamX里的“货物”和“说明书”Buffer就是图像数据本身Metadata是描述数据怎么处理的“说明书”。两者在CamX中都是贯穿始终的核心对象。Buffer管理上CamX做得比较精细。图像buffer有大有小有YUV、RAW、blob等不同格式分配来自ION/DMA-BUFCamX会做buffer的复用和生命周期管理。当一个buffer在多个Node之间流转时每个Node会先“取引用”完成后“放引用”引用计数归零才会真正释放或回收避免出现二次写导致的画面闪烁或内存损坏。Metadata则分两个层次Android标准的camera_metadata_t和CamX内部的Property。HAL3传入的capture request里带的metadata会被转换并分发到各个Node各个Node处理完以后再把结果比如3A计算出的曝光值写回metadata最终合并成completion结果返回给framework。3A相关的metadata非常多AE、AF、AWB、闪光灯状态、统计信息等调试时经常要在这里排查。提示怀疑3A或者tuning问题的时候先把HAL3 output metadata里面的android.control.aeState、android.statistics.lensShadingMap这些字段抓出来看能快速判断出问题出在算法侧还是硬件侧。3. 数据流设计一帧图像是怎么走完的3.1 Request-Response核心模型CamX的数据流本质上是围绕Request-Response模型转的。上层每次下发一个capture requestCamX内部会生成PerRequestInfo把request id、pipeline id、buffer信息、metadata信息全部绑定在一起然后驱动pipeline上的各个Node逐个执行。这个模型有什么好处最直接的好处就是可以乱序提交、乱序完成。比如app连拍时可能连续提交10个request硬件处理速度不一样有的后处理的先完成有的先处理的晚完成。CamX通过request id和completion slot把这些结果一一对应回上层保证Android framework拿到的每一帧结果都归属正确。数据流的起点是Sensor。Sensor按照预定的帧率输出RAW数据数据经过CSL进入IFE完成第一级ISP处理包括坏点校正、镜头阴影矫正、白平衡增益、去马赛克等。IFE输出的数据会分几路一路直接给Stats Node做3A统计一路在ISP内部缩放成不同尺寸分别给preview和video使用还有一路走raw dump给后续算法做多帧处理。3.2 从Sensor到最终输出的完整链路我用一次普通预览来做链路说明。app初始化以后HAL3进入configureStreams阶段CamX根据stream配置构建pipeline topology。这个阶段会决定使用哪一条链路比如只用主摄、用主摄加超广角还是带虚化的三摄链路都会影响node的选择。当第一个request下发时整个pipeline开始动作。Sensor Node先把sensor调到target mode出图后raw数据通过CSL送往IFE。IFE处理完正常情况下会输出几路不同尺寸的YUV一路进display buffer给预览一路送编码器给录像一路可能送给算法Node做降噪或者HDR。如果是拍照场景流程会有点不同。拍照通常要有更好的画质所以HAL3会要求raw流或者全尺寸YUV流。此时pipeline可能额外挂一个BPS或者ICP节点做多帧对齐和融合然后送到JPEG Node编码出图。拍照和预览的pipeline在CamX里通常是并行共存的两者共享Sensor但走不同的Node链路这样能保证从预览切到拍照时几乎无缝。3.3 多摄与时延同步的处理多摄是现在手机的标配也最能体现CamX数据流设计的功力。双摄甚至三摄同时工作时每个摄像头都有自己的Sensor Node但它们的raw数据经常需要合并计算比如做虚化、做广角和长焦的平滑切换。CamX应对多摄的核心机制有三个第一是Session级的pipeline并行每路sensor各跑各的pipeline互不阻塞第二是CSL的sync机制通过硬件或软件方式把多路sensor的帧起始时间对齐从源头保证帧同步第三是CHI的feature graph它可以把多路pipeline的输出汇聚到一个算法Node里面做融合再输出结果。我在实际项目里踩过一个典型的坑双摄预览时主摄和副摄的曝光时间不一致导致合成画面边缘闪烁。后来查下来发现问题出在两个Sensor Node的AE参数没有在同一个request周期内同步下发。CamX支持把多个sensor绑定到同一个sensor cluster里做统一控制但这需要在usecase级做配置默认不一定开着。3.4 调试利器如何观察数据流调试CamX数据流最重要的手段是抓log和dump buffer。log方面常用logcat tag有CamX、CamXHAL、CHIUSECASE、CHIOVERRIDE出现黑屏、崩溃等问题时先按tag过滤能迅速定位是框架层、HAL层还是CHI层出了问题。dump buffer方面CamX提供了很灵活的调试开关可以通过camxoverridesettings.txt开启raw dump和中间buffer dump。比如想确认IFE输出是否正常可以把IFE output buffer dump下来脱离Android显示链路直接在电脑上看图判断。顺便说一句camxoverridesettings.txt这个配置文件非常有用。它是CamX在启动时读取的调试配置文件可以覆盖很多默认行为比如强制开启debug log、指定sensor output size、关闭某些Node处理等。文件通常放在/vendor/etc/camera/下调试时改动它比改代码重新编译快得多。注意dump中间buffer会严重影响性能正常跑性能测试前一定要把dump开关全部关掉否则帧率数据完全没有参考价值。4. 实操中的经验与排查技巧4.1 常见启动与运行问题CamX上线以来我遇到过不少问题有些非常典型这里整理一张速查表供参考。现象可能原因排查方向打开相机黑屏无报错pipeline拓扑配置错误或CHI feature graph未匹配抓CamX和CHI log查看session创建是否成功Sensor无法出图I2C通信异常、电源时序、MCLK频率问题用CSL debug测sensor寄存器读写检查power rail时序预览卡顿帧率极低buffer不足或node间buffer拷贝过多抓CamXQOS log看每帧耗时检查buffer复用配置abort或crashcamera service重启KMD驱动panic或native层空指针抓tombstone和kernel log定位到具体node3A不收敛画面过曝或过暗tuning data异常或AE算法失联检查tuning加载log抓AE算法log看是否收到stats数据4.2 调试工具与日志分析CamX调试除了logcat还有几个很好用的工具链。第一是高通的Tuning Tool它配合tuning data可以实时调整3A参数和ISP参数比改配置文件再重启相机效率高一个量级。第二是perfetto或systraceCamX在关键路径上埋了不少trace point打开后能看到一帧从request到completion的完整时间线定位性能瓶颈很有用。分析log时我最常用的过滤命令是adb logcat -s CamX:C CamXHAL:C CHIUSECASE:C这个组合能过滤掉大部分噪音直接看到session、pipeline、request相关的关键流程。遇到流程卡住时我会再开详细logadb logcat -s CamX:D然后盯着“submit request”“node process done”“buffer done”这一系列关键词判断每一帧到底在哪一步断掉了。4.3 踩坑记录与避坑建议最后分享几个我自己踩过的坑每一个都是真金白银换来的。第一个坑是tuning data不生效。配置文件路径没错、加载log也显示成功但画质完全没有变化后来发现是高通新平台对tuning data的版本号和sensor mode匹配检查很严格一个mode_index对不上就静默跳过。排查这类问题一定要在log里搜tuning关键词确认用的bin文件被完全加载。第二个坑是camxoverridesettings.txt的权限和selinux上下文。文件放错目录、权限不对CamX读取时直接失败而且log还不明显。最稳妥的做法是参照系统原有配置文件的上下文去设置不要自己在软件里乱建目录。第三个坑是多摄时延。很多项目最初都觉得帧率够、画面清晰就完事但一到多摄景深场景就翻车因为两个sensor的出帧时刻没有对齐。CamX虽然提供同步机制但传感器本身的曝光时间差异需要单独校准主副摄的sensor mode配置、帧率配置必须严格匹配这才是同步的前提。第四个坑比较隐蔽是buffer格式不一致。上层配置的stream格式和pipeline里实际用的buffer格式对不上导致画面色彩异常或者绿屏。这种问题不报错只能靠dump buffer后用工具确认格式排查起来相对费劲。结尾CamX这套架构我个人的体会是它的复杂度确实高但设计上并不混乱核心就是“Session/Pipeline/Request/Node”这套模型加CHI的可编程能力。只要把核心组件和数据流这两条线拎清后面的调优和问题排查都会顺畅很多。最后再分享一个小技巧。新接手一个CamX项目时不要急着看代码先花半天时间把项目的usecase配置、pipeline拓扑xml、feature graph这一串从头读一遍再结合log把一条preview链路从Sensor到显示完整走一遍效果比闷头看源码好太多。这篇先讲到这后面有机会再继续聊CHI的feature graph、tuning和性能调优。
返回列表