简介:高通相机Camx架构的全套源码,为Android平台上的相机软件开发与图像处理研究提供了完整参考。资源面向相机驱动工程师、图像算法工程师和基于骁龙平台的设备制造商,覆盖核心框架、图像处理节点(如IFE节点、IPE节点、BPS节点)、传感器控制以及硬件抽象层交互逻辑,可帮助理解自动曝光、自动对焦、白平衡、HDR、降噪等算法是如何在真实工程中落地的。压缩包内共763个文件,以358个头文件与321个C++源文件为主体,辅以构建脚本、说明文档以及少量XML与动态库文件,整体体积仅1.68MB,结构清晰,便于按模块查找和二次开发。该资源已有529人学习下载,适合希望深入高通相机管线源码的开发者。通过研读其中的传感器节点、图像处理节点等关键模块,可以掌握从硬件控制到完整图像处理链路的实现思路,为定制相机功能、优化成像效果提供直接参考。
1. 高通Camera Camx是什么:从源码仓库到成像流水线
做高通平台的相机bring up,绕不开Camx这套东西。新平台回来第一步往往就是打开log,看到一行"camx is not supported"或者"no camera are attached",然后开始翻vendor/qcom/proprietary/camx下的代码。Camx全称Camera eXtension,是高通从SDM845之后主推的相机HAL架构,取代了老旧的MM-Camera。它分两层:底层Camx HAL负责和ISP、传感器打交道,上层CHI-CDK(Camera HAL Interface - Camera Driver Kit)做算法集成和feature定制。源码仓库通常跟着BSP一起发,CAF上也能同步到。这套代码决定了你最终能否把一颗sensor点亮、把美颜水印叠上去、把出帧延时压下来。这篇文章就按我实际调过的路径,从架构、同步、编译一直讲到踩坑和验证,给准备在这套源码上动手的人一条靠谱的路线。
2. Camx架构拆解:从HAL到ISP的调用链与关键模块
2.1 CHI/Camx分层:为什么高通把相机HAL拆成两层
先看整体分层。Camx不是单一半身,而是"Camx HAL + CHI-CDK"两个仓库协同工作。Camx是核心HAL实现,包含V4L2对接、ISP pipeline管理、node节点调度、request队列等;CHI-CDK是上层可配的扩展层,包含feature graph、usecase、extension模块,以及外置算法厂商(高通ais、虹软、商汤等)的override接口。
这种拆分的目的只有一个:把"稳定不变的硬件抽象"和"天天变的算法策略"分开。Camx层尽量保持稳定,改动多在CHI-CDK层完成。你拿到一套camx仓库全套源码,实际上会看到camx目录和chi-cdk目录并存,编译产物也是一个hal模块和一个chi-cdk模块。工程上最常见的做法是:sensor特性、AE/AWB参数、降噪算法、美颜策略全部放CHI-CDK侧,而Camx侧只处理通用流程,比如request submission、pipeline切换、stream on/off。
这种分层带来的直接好处是,多项目共用同一份Camx核心,只替换CHI-CDK配置。但也埋了坑:你在CHI-CDK里加一个override函数,Camx侧如果没有对应接口,一样编不过;反过来,Camx层动了node的连接关系,CHI-CDK的graph定义就得同步改。
2.2 Camx核心数据结构:Pipeline、Node、Request
手上如果有一份camx源码,优先看这几个文件:camxhal3.cpp、camxdevice.cpp、camxpipeline.cpp、camxnode.cpp、camxrequest.cpp。它们对应了HAL3标准接口、设备管理、pipeline生命周期、node调度和request流转。
一个典型的Camx pipeline由一串node组成,每个node承担一个功能切片。常见node包括IFEBayer(RAW域处理)、IPELite(降噪/色彩)、IPE(输出域处理)、JPEG node、Sensor node、Stats node等。它们通过node link连成拓扑,类似一个有向图。运行时,一次拍照请求会生成一个request,request里会携带3A结果、buffer句柄、tuning mode等信息,然后Camx把request分发到pipeline的各个node去执行。
关键要理解"preview/picture pipeline分离"和"batching调度"这两个底层机制。Camx默认有batching参数,把一个request批次内的多个帧合并提交给ISP,减少CPU和硬件的交互次数;同时preview和capture分开走不同的pipeline,避免高分辨率拍照阻塞低分辨率预览的出帧。
2.3 从Open Camera到出帧:一次拍照请求走的路
实际调试时最常问的问题是"相机打不开,到底卡在哪一环节"。我一般用这条调用链去定位:
- CameraService调用HAL3接口openCamera,正常会走到CamxDevice的CreateDevice。
- CamxDevice创建各种session,每个session对应一组stream和pipeline。
- 上层下发processCaptureRequest,CamxDevice把request转成CamxRequest,塞进对应Session的queue。
- Sensor node开始读取曝光参数,IFEBayer node和stats node采集数据,3A算法更新,最后IPE node输出YUV/JPEG,填回hal3 buffer。
如果中间任何一环的拓扑配置和实际硬件能力不匹配,比如sensor的raw格式配错、node link少了条边、tuning mode名对不上,request就会卡死在camx内部,上层表现为"打开相机超时"或者"第一帧黑屏"。这时你打开camx log,能看到"Failed to submit request"或者"Node X not ready"。
3. camx仓库全套源码怎么落到本地:repo同步与编译环境
3.1 获取源码:从CAF同步camx与chi-cdk
拿到高通平台BSP后,camx源码一般会在vendor/qcom/proprietary/camx和vendor/qcom/proprietary/chi-cdk两个目录。如果是通过CAF同步,常见初始化命令是:
repo init -u https://git.codelinaro.org/clo/la/platform/manifest.git -b release -m LA.QSSI.12.0.rx.xml repo sync -c -j16说明:这里-b release重点是匹配你的平台release tag,可以从BSP release notes里查到具体分支,比如kalama、pineapple对应的分支不同。-c只同步当前分支,能大幅减少下载量;-j16是并行任务数,按机器CPU核数调。
如果只需要camx相关源码,可以只同步camx和chi-cdk两个git项目。拿到后第一件事不是编译,而是检查目录完整性:camx源码通常包含core、hwl、oem、utils、tools等子目录,chi-cdk则包含chi、override、topology等目录。中间缺任何一块,编译都会报找不到头文件。
3.2 编译camx模块:使用Soong构建单模块
Camx从Android 11之后基本用Soong(Android.bp)构建,不再走旧款Android.mk。所以编译前要确认你使用的Android版本和编译环境。最小验证编译命令:
source build/envsetup.sh lunch <device>-userdebug cd vendor/qcom/proprietary/camx mm -j$(nproc) 2>&1 | tee camx_build.log说明:mm会在当前目录编译该模块及其依赖,如果没有单独初始化过camx的product变量,可能要加ALLOW_MISSING_DEPENDENCIES=true。编译日志最重要的一行是最后的"Install: out/target/product/ /vendor/lib64/hw/camera.qcom.so",看到这条才算成功。
如果编译报依赖缺失,先检查你的BSP是否带完整的MM-Camera和ais相关仓库。Camx经常依赖高通的amera_conf、scve、ais等预编译库,这些仓库不在camx目录里。同步时最好把vendor/qcom/proprietary下整个common主题都拉下来,否则会反复报找不到头文件。
3.3 把camx放到设备上:手动push与权限校验
编译产物出来后,最常见的验证方式是把新HAL push到设备vendor分区。注意fastboot和adb两个方向都可能存在分区校验,特别是Android 12以上有动态分区和avb校验。常规路径:
adb root adb remount adb push camera.qcom.so /vendor/lib64/hw/ adb push camera.qcom.so /vendor/lib/hw/ adb push libchi-cdk.so /vendor/lib64/ adb sync adb reboot说明:camx的hal库名通常是camera.qcom.so,但如果你的平台有camera.kalama.so之类的依赖,也要一起同步。push后先检查权限是否变成rw-r--r--,如果push时提示"Read-only file system",说明没有remount成功,或者adb root之后仍然被avb锁住,需要刷入userdebug版本的vbmeta,或者用adb disable-verity。
还有一类玄学问题是:push之后相机能打开,但一拍照就闪退,多半是vendor/bin下面的hwservicemanager缓存了旧的HAL。解决办法是重启hwservicemanager或直接reboot,不要只kill camera provider进程。
4. camx源码实战:改一个小feature并验证
4.1 最小改动:在CHI-CDK增加一个tuning override
拿到源码后,做一个最小的改动可以验证你改的是不是"活代码"。我常用的做法是在CHI-CDK的override里加一个自定义tuning模式,比如给夜景模式强制改一下降噪强度。先找到chi-cdk/override/chiOverridesettings.cpp,在某个usecase的PopulateChiMetadata函数里加一段:
if (pRequest->pUsecaseRequest->usecaseType == ChiUsecase::Night) { ChiMetadata *pMetadata = pRequest->pUsecaseRequest->pChiMetadata; pMetadata->SetMetadata(ANDROID_NOISE_REDUCTION_STRENGTH, 5); }说明:ChiMetadata::SetMetadata可以在request的metadata里写一个自定义值,供后面camx的tuning模块读取。这里关键在于理解override的调用时机:它是在Camx把request下发到node之前执行,所以改动生效于整个pipeline前。ANDROID_NOISE_REDUCTION_STRENGTH是实际存在的metadata tag,可以替换成厂商自定义tag。
编译chi-cdk模块后,同样mm单独出chi-cdk的so,push到vendor/lib64,通常能立即通过日志验证新增log有没有打印。这是判断源码版本和当前设备是否匹配的快速手段——很多工程机刷的bin和手里的源码根本对不上,通过加log再对比logcat,最直接。
4.2 用camx debug日志定位问题
Camx的日志体系是运行时可配的。最常用的日志控制方式是通过camxoverridesettings.txt,放/vendor/etc或者/data/vendor/camera。比如:
LogConfig=2 LogTag=CamxNode LogLevel=3说明:LogTag可以指定你想要跟踪的模块,CamxNode会打印所有node的状态切换;LogLevel=3对应verbose级别,一般定位启动超时先开到3。改完重启camera provider(adb shell pkill camera-provider)再复测。
你本地编译camx时,还可以打开CAMX_LOG_LEVEL编译宏,在Android.bp里加一句:
cflags: ["-DCAMX_LOG_LEVEL=3"],这样编出来的camx会强制开启verbose日志,不用依赖配置文件。缺点是日志量巨大,连拍时每秒几百条,要通过logcat的tag过滤成camx*再抓取。
4.3 使用camx自带的dump工具导出错误现场
遇到crash或者死机画面,camx仓库里的几个工具很关键:camxhal3dump、dump pipeline topology和节点自带的DumpState。其中最简单的是在camxnode.cpp里找到DumpState函数,手动加一段代码把当前frame的buffer信息导出:
if (IsFrameError(pRequest)) { DumpDebugInfo(m_pNodeName, pRequest->GetFrameNumber()); }说明:实际操作中会发现,单靠logcat很难看出死锁现场,因为camx内部线程多,而且request queue的依赖关系在log里是散的。我一般通过kill -3触发camx的dump,把整个pipeline的node状态和buffer引用打印到logcat,再结合addr2line反解crash地址。比如:
adb shell kill -3 $(pidof camera-provider) adb logcat -d | grep -A100 "CamxPipeline::DumpState" > pipelinedump.txt这时图上会画出node间的link关系,以及每个node当前缓存了几帧。如果某个node一直处于WaitForDependency状态,瓶颈就在那个node的上游。
5. camx仓库踩坑:编译、联调、稳定性三板斧
5.1 编译报错找不到"camx/inc/camxdefs.h"
现象:同步camx源码后直接用mm编译,报错fatal error: camx/inc/camxdefs.h: No such file or directory。
原因:camx的头文件路径依赖全局的include路径,这个路径通常由vendor/qcom/proprietary/common/Android.bp注入。只同步camx和chi-cdk会缺common模块,导致include路径不完整。
解决:回到repo manifest,把vendor/qcom/proprietary/common和vendor/qcom/proprietary/ais一起同步,重新mm。如果不想全量同步,也可以手动在camx的Android.bp里加一条include_dirs: ["vendor/qcom/proprietary/camx/inc"],但不推荐,因为后续还会依赖更多公共路径。
5.2 相机打开闪退,logcat报"camx: request failed: no camera are attached"
现象:设备启动正常,但打开相机App瞬间黑屏闪退,logcat里能看到No Camera are attached。
原因:这个报错不是"没插摄像头",而是Camx没有识别到任何sensor。常见原因有三种:sensor驱动没加载、camxsensor配置里的传感器名不匹配、以及sensor device tree的i2c addr不对。
解决:先确认adb shell ls /dev/video*有没有对应的video节点;再用adb shell cat /sys/bus/i2c/devices/*/name查sensor名字,去chi-cdk/topology/sensor目录找同名配置XML。如果XML里sensor名和驱动名对不上,把XML里sensorName改成驱动输出,或者反向改驱动的name字段。这类问题在多平台共用一个camx源码树时尤其容易发生,因为不同项目会通过编译宏或overlay配置选sensor。
5.3 预览一帧后卡死,camx log停在"Streamon: waiting for sensor pipe"
现象:预览出第一帧后不再刷新,logcat里CamxSensorPipeline反复打印waiting for sensor done。
原因:这是sensor的stream on序列没有完成。最常见的是sensor standby/gpio控制时序不对,或者sensor的s_stream_on callback里sleep时间超过camx预期。也有一种情况是你在batching配置里设置的最大批次数太小,导致sensor帧率跟不上设定值。
解决:先打开sensor node的verbose日志,重点看sensor的stream on/off返回码。如果返回码是-110,说明I2C通信超时,检查sensor供电和reset gpio时序。如果返回码是0但还卡,把/vendor/etc/camera/camxoverridesettings.txt里BatchingCount调回0试试,0表示由硬件自动决定批次数,能规避一批sensor驱动对burst mode支持不完整的问题。
5.4 拍照crash在"CamxNode::ReleaseBuffer"附近
现象:正常预览,一拍照片就crash,backtrace最后帧停在CamxNode::ReleaseBuffer或者CamxBufferManager::ReleaseBuffer。
原因:这种问题绝大多数和buffer的producer/consumer生命周期不同步有关。在HAL3下,camera provider的buffer queue由CamxBufferManager管理,拍照模式会同时存在多个stream(preview同时开),如果某个node在request完成前提前release了buffer,另一个node再访问就会use-after-free。
解决:优先查是不是你在override层自己动了buffer句柄。很多人在CHI-CDK里往metadata里塞了中间buffer的handle,但没有增加buffer引用计数,导致Camx把buf释放后,你的override在后续frame里还在引用。正确做法是引用chiBufferHandle前先调CamxBufferManager::Acquire增加refcount,用完再release。其次可以把CamxOverridesettings.txt里DisableBufferSharing=1打开,粗暴但能快速确认是不是share buffer导致。
5.5 改了camx源码后push进去,行为没有任何变化
现象:在camx或chi-cdk里加上log,编译push后log不出现,行为还是老样子。
原因:大概率是你改的模块没有被真正加载,或者代码改动藏在某个条件宏里没被编进去。常见三个坑:第一,camera HAL有多个variant,可能加载的是camera.kalama.so而你在camera.qcom.so的Android.bp里改,push错库;第二,camx有precompiled版本,设备上跑的是/vendor/lib64/libcamx.so而不是你在源码里改的那个hal模块;第三,manifest里选用的fwk版本和你改的usecase代码不在同一条编译路径。
解决:先确认设备加载了哪个so:adb shell ls -l /vendor/lib64/hw/ | grep camera,再对比你push的文件hash;然后用adb shell strings <so> | grep 你加的log关键字,确认目标库里确实包含了你的改动。如果strings查不到,说明编译时这个文件没被重新编,检查mm的产物路径和$OUT环境是否对应。
6. 把camx用起来:调试入口、性能分析与常用命令
到了能改能编能跑这一步,真正生产环境里还缺一套调试习惯。我最后分享几个能直接复用的入口。
6.1 用属性开关快速切换camx行为
不重编译就能调一些行为,用adb shell setprop配合camx在运行时读取的override配置。比如:
adb shell setprop persist.vendor.camera.profiling 1 adb shell setprop persist.vendor.camera.3a.debug 1 adb shell setprop persist.vendor.camera.sensor.debug 1这些属性在Camx初始化时读取,会改动日志级别和3A调试输出。进阶的是camxoverridesettings.txt放在/data/vendor/camera下,修改后只需重启camera provider,不需要重启整机,这对线上稳定性测试很有用。
6.2 性能分析:量出帧延时和出帧率
做camera稳定性问题分析,最终都要落在帧率和延时两个数字上。Camx里最直接的方式是看log中每帧的ts时间戳:
adb logcat -d | grep "CamxRequest::ProcessRequest" | grep -o "frameNum=[0-9]*.*"配合perfetto的camera trace,可以精确看到sensor、IFE、IPE每段的耗时。我自己的习惯是保存一版camx_profiling.log,连续预览5分钟,再统计出帧间隔是否稳定在33ms/16ms附近。如果间隔抖动大,优先去看tuning里的AEC收敛速度和你GMSL/FPD-Link转接板上的帧同步,这两个位置是最常出问题的。
最后一句话送给准备动camx仓库源码的朋友:先别急着改功能,把日志能力和模块加载路径摸清楚,这比多看十篇架构文章更能帮你避开莫名其妙的黑匣子。希望帮到你。
本文还有配套的精品资源,点击获取