009、Camera 开源生态:AOSPQualcommMediaTek
009、Camera 开源生态AOSPQualcommMediaTek那次调试记录我到现在还记得。一台骁龙平台的设备客户反馈“切换前后摄偶尔黑屏”抓log看到一行关键报错ConfigureStreams() returns UNKNOWN_ERROR。顺着AOSP的CameraService代码往下追发现这个错误来自Camera HAL层的ICameraDevice::configureStreams但具体是哪一层的实现是AOSP自带的那套还是高通vendor里那套都不是。那是你代码里真正跑的HAL实现可能来自Qualcomm的CamX也可能来自MediaTek的mtkcam唯独不是AOSP那套“空壳”。这就是所有做Camera HAL的人最应该明白的第一件事AOSP只是给你画好了边界定义了接口而真正填满这些接口的是高通、联发科、或者你自己写的代码。如果只盯着AOSP源码看很多问题永远解释不通。AOSP那层皮先聊聊AOSP这个“框架侧”。从Android 8.0开始Camera HAL被强制要求走Treble架构所以HAL接口变成了HIDL后来Android 13又推AIDL版本。AOSP负责的业务模块主要有三个frameworks/av/services/cameraCameraService处理客户端请求管理provider和设备生命周期。hardware/interfaces/cameraHIDL/AIDL的定义这里是真正的接口契约。分为provider,device,common,metadata几类。frameworks/av/camera给上层App用的客户端库比如CameraDevice等。AOSP自己也有一个默认的Camera HAL实现放在hardware/libhardware/modules/camera但那基本是个“假HAL”能打开设备能出个绿屏画面仅供模拟器或者基础验证用。真正在手机上跑的都是厂商基于这套接口实现的。调试那个黑屏问题时我最初怀疑是AOSP的CameraDeviceClient::configureStreams里没处理好stream切换后来单步走到HAL层才发现问题出在高通的CHICamera Hardware Interface的Usecase里。因为客户的应用同时开了预览流和少量元数据流但高通默认的Usecase里没有匹配这个组合的pipeline于是返回UNKNOWN_ERROR。这已经跟AOSP没什么关系了。所以说AOSP只是“What”而“How”完全由vendor决定。看懂AOSP你只是有了一张地图离找到宝藏还远着呢。高通CamX与CHI的丛林现在几乎所有中高端安卓机非高通即联发科。高通在Camera这块的演进路径是老的mm-cameraAndroid 8之前到现在的CamXCamera eXtensions加CHI。你的代码里看到camera.qcom.so实际上它就是整个CamX的入口。CamX负责底层硬件抽象、sensor控制、ISP pipe调度而CHI是一套安装在CamX之上的“算法/应用层”插件框架。CHI里最核心的概念是“Usecase”和“Pipeline”。你可以把Usecase理解为一个“拍照片”或“录视频”的场景配置。它定义了你要发多少路stream每路stream的格式、分辨率、方向以及这些stream怎么映射到底层的物理或虚拟Pipeline上。高通的文档和代码模板里会给你几个标准的Usecase比如“Preview”、“Snapshot”、“Video”。但你实际产品往往要定制比如“预览在高通dsp上做人脸检测”你就得自己写一个Usecase去组合不同stream。我踩过一个典型的坑在CHI的chi_metadata.h里自定义了一个vendor tag想传一些私有数据给算法节点。但忘了在chi_customize.cpp里把对应的vendor tag注册到gChiVendorTagOps里。结果算法节点拿到的tag返回nullptr但log里又没有任何直接的报错最后对比了高通的sample code才定位到。这里建议你凡是动了vendor tag第一件事去查ChxVendorTags.h和chi_ocr.h里的注册表别想当然。高通的开源性相对较好CHI的代码里有很多chitest、chi_sqnode这种示例节点但问题在于“太多可选路径”。比如你配置pipeline时可以用旧版的sensorpath也可以走新版的camera3path。同一条请求可能经过CamX::Node好几个不同的节点每个节点都可能丢buffer或者改metadata。如果遇到性能问题优先查是不是有某个节点在等待另一个节点的输出导致整个request卡死。我见过一次黑屏竟然是CPP节点色彩处理的buffer queue设得太小塞满之后整个ISP不会再收新请求。那个log怎么抓都像是“sensor idle”最后连了debugger看到queue depth恒为N才破案。联发科mtkcam的围墙花园再看MediaTek。MTK的Camera HAL不像高通那样有CHI这种“半开放”的插件框架它更偏“整体方案”。代码路径通常在vendor/mediatek/proprietary/hardware/mtkcam下面核心模块叫mtkcam内部有HalBase,Pipeline,IPipe,IFeaturePipe等一大堆以“I”开头的类。第一次进这个代码库的人多半会晕。因为它的命名风格面向“内部流水线”而不是直接对应AOSP的API。比如AOSP要求HAL3接口实现的processCaptureRequest()在MTK里会经过RequestThread分发到FeaturePipe或者StreamingPipe。你看到processRequest函数体可能有两千多行里面按请求类型切出去有持续预览、多帧降噪、HDR、人像背景虚化等等。每个功能模块都像是独立的“图”节点和数据路径非常多。MTK的调试工具有时候比高通更不方便。因为很多底层配置是通过tuning文件比如htp、isp参数控制的这些参数在运行时会被加载到sensor驱动里。如果你改一个曝光时间感觉没生效极有可能是没有正确设置对应的HTPflow。我印象深刻的一次客户说暗光下预览花屏log里没有异常但拿imx355_mipi_raw的sensor dump发现Bayer数据的gain被某一块寄存器锁成了最大。后来在MTK的hal/inc/camera_custom.h里看到一个宏控制“low light gain limit”默认值是关的但我们那份codebase里被某次merge改成了开导致gain溢出。这事让我明白了MTK的项目一定要用官方发布的release branch别自己merge大版本否则分分钟给你“隐藏惊喜”。MTK相比高通最大的缺点是“开放度”。你想加一个自研的3A算法得走它的custom流程比高通要繁琐。但它的稳定性和功耗控制做得相当好因为整条链路都是自己调过的。如果你做的是量产机、追求出货速度MTK其实更省心。前提是别轻易碰它内部的pipeline结构。三方握手的真相回到AOSP、高通、MTK三者的关系。你可以这么理解AOSP是“立法机构”它制定了接口规范和调用的时序高通和MTK是“执法机构”它们把规范落实到硬件上。但这两个“执法机构”的风格完全不一样。高通仗着CHI的灵活性让你能在它的框架里塞进各种私有的节点、算法、vendor tag。好处是定制能力强坏处是你要学的东西非常多。而且高通的HAL代码往往通过编译条件混入了很多官方debug和测试代码。如果你没做裁剪性能上会吃亏。比如chitest如果没被去掉它内部的命令解析线程会一直开着多耗几十毫安的电。这里踩过坑后来在Android.mk里把CHI_USE_TEST关掉才正常。MediaTek则更像一个“黑盒”。它把大部分行为都封装在mtkcam库里你能修改的部分主要是custom和hal/inc下的配置。好处是行为可预期只要不跳出它设定的框架基本不会出大乱子。坏处是如果你碰到了它框架没覆盖到的需求你可能得写很多“hack”代码去绕过或者直接用它的“extension pipe”这个学习成本不低。有一点两个平台是同步的都在努力适配AOSP最新的AIDL HAL。Android 13之后AOSP把camera.provider和camera.device从HIDL逐步迁移到AIDL。你会发现高通的CamX里有aidl目录MTK也有mtkcam/aidl。这样做是为了减少进程间通信开销但带来的问题是你的旧代码如果是基于HIDL写的必须重新适配。我见过一个项目升级Android 13后HAL还在用HIDL的configureStreams结果系统服务已经用AIDL接口跨进程调用了于是直接“Transaction failed”。那个排查太痛苦了最后在device.mk里同时把HIDL和AIDL的实现都加上才过渡过去。另外不要忽略“provider”这一层。AOSP的设计里一个Camera provider可以管理多个device一个设备可以有多个provider。高通和MTK通常都只实现一个主provider但有些方案比如外接USB摄像头会用到multiple providers。如果你在做车载或者外接多摄方案这一块要特别注意。getCameraIdList()返回的顺序会影响上层UI显示曾经有客户因为顺序不对硬说“前后摄颠倒了”。这个问题不在HAL里而是vendor的provider实现里需要调整camera id的枚举顺序。关于调试工具和心态经验这种东西纸上得来终觉浅。真到了调高通时建议你学会用camx的trace和perfetto抓HAL层耗时调MTK时学会用mtkTuning工具和dmabuf查看buffer生命周期。不要一上来就用printf打log那会刷爆缓冲区。更不要一上来就改AOSP源码因为AOSP源码是共享的你改了之后高通和MTK的升级包会直接覆盖。你要改的是vendor层或者通过vendor tag扩展。还有一个常见的误区很多人以为“Camera HAL”就是hardware/libhardware/modules/camera里的那一堆.c文件。那只是Nougat以前的老黄历了。现在Camera HAL是一整套服务它运行在独立的进程里通常是vendor.qti.hardware.camera.provider2.x-service或者android.hardware.camera.provider-service通过HIDL/AIDL绑定到Framework。如果你在调试时发现HAL进程崩了不要只抓logcat还要抓systrace、tombstone有时候问题出在BN (Binder Node)不会告诉你崩在哪一行。回到开头那个黑屏问题最后怎么解决的我在高通的chitest里写了一个打印pipeline状态的小工具发现当前stream配置没有匹配到任何一个Usecase。然后我们在cameraHALconfig里把那个元数据流的stream-type改成IMPLEMENTATION_DEFINED并允许它跟预览流共享pipeline才成功解决了。事后想想如果一开始就懂高通CHI的Usecase概念可能半小时就能定位不至于折腾两天。所以我的建议是在任何一个新平台上做Camera HAL第一天就该花时间去读vendor自己的代码模板和使用说明而不是急着看AOSP官方文档。AOSP的文档只告诉你怎么“调用”vendor的文档才告诉你怎么“写”。最好把高通的CamXDocs、MTK的MediaTek_Camera_FAQ都打印出来放在手边。别嫌厚你每翻一次都会找到救命的东西。最后再啰嗦一句“HAL”这三个字母不是你写的C类名而是你与硬件之间的契约。尊重它它也尊重你。