做MTK平台Camera BSP这几年,被问得最多的问题就是:想学Camera BSP,资料从哪看起?老实说,网上的资料并不少,sensor驱动怎么移植、HAL接口怎么实现,都能搜到不少帖子和文档,但大多数是“点状”的,缺少一张把整个Android平台Camera链路串起来的地图。其实你真正需要先建立的是“架构认知”——先知道手机从按下快门到最终看到照片,中间到底经过哪些软件层次,MTK的BSP代码分别落在哪些位置,然后你才可能在出问题时一眼定位到该去翻哪块代码,而不是像无头苍蝇一样从camera app一路试到kernel。这篇我就以我自己在MTK平台上做Camera BSP的经验,把Android这一整套相机架构掰开揉碎讲清楚。
1. 为什么先学架构,而不是先啃代码
很多刚入行的朋友拿到代码后,总是习惯性地打开imgsensor.c或者CameraHal相关目录就开始一行行读。读了两天,发现自己记住了几个函数名,真遇到问题时依然不知道问题出在哪儿。这种低效学习的原因很简单:你对“上游是谁、下游是谁、谁在等谁的数据”完全没有概念。
1.1 手机拍出来的照片偏色,到底是哪一层的锅?
我举一个特别典型的例子:客户反馈预览画面偏色,屏幕里整个画面发紫或者发绿。这时候如果你只盯着某一个模块去查,会很崩溃。因为偏色可能来自好几个地方:
- 可能是sensor输出的raw数据本身就有问题,比如白平衡增益没生效;
- 可能是ISP里的AWB算法算出来的色温值不对,导致RGB增益配错了;
- 可能是tuning参数里某个颜色校正矩阵(CCM)配错了;
- 甚至是上层的hal层面在做format转换时,把R/B通道的字节顺序搞反了。
如果你不懂Android平台的Camera架构层次,你就不知道这些怀疑对象分别属于哪一层,更不知道它们之间是怎么交互的。你会陷入“到处看代码但不知道看哪段”的尴尬。学了架构之后,你能先判断:这个偏色现象是只在预览时出现,还是拍照出来的图片也偏色?是全场景偏色还是特定光源下偏色?根据这些特征,把问题缩小到sensor、ISP、算法、还是tuning上。
1.2 我在第一次接入新Sensor时犯过的错
我自己刚接触MTK平台时,也走过一段弯路。当时让我把一个新的sensor驱动加进项目,我觉得很兴奋,直接一头扎进imgsensor目录里去改寄存器、弄上电时序,折腾了大半天,驱动代码自认为改得没问题,可是编译完烧进去之后,adb shell dmesg里根本看不到sensor被识别。当时我很不理解,觉得驱动明明起来了,为什么没有反应?
后来前辈提醒我:你只改了内核驱动,但上层HAL怎么知道这个sensor存在?驱动和HAL之间还有一个匹配过程,你要确认你的sensor ID对应在HAL的枚举列表里,以及HAL通过什么方式去访问这颗sensor,比如是通过I2C地址还是电源配置。那一刻我才意识到,整个链路是一套协作的系统,不是你改完一个模块就能工作的。这个教训对我很深刻,也是我后来一直强调先学架构的原因。
1.3 架构学习要交付什么能力
学完架构,你至少应该能回答这几个问题:
- 一个Camera App发出的拍照请求,先到哪个进程,再到哪个进程?
- 哪一层是Android系统自带的,哪一层是MTK/厂商实现的?
- 修改一个sensor驱动,影响的是哪一层;改一个3A参数,影响的是哪一层;
- 当出现了某种现象时,你应该在哪个进程的日志里找线索。
有了这种“分层定位”能力,再看BSP代码,你就能知道每段代码放在这个链路里到底是干什么的。这时候再读代码,才会越读越通透,而不是越读越糊涂。
2. Android Camera这条流水线的真实路径
既然要学BSP,那首先就要搞明白Android里camera应用的一路“请求-填充”流水线。这条流水线跟真实世界的快递公司很像:用户下单,营业网点接单,调度中心分单,具体的快递员上门取件,然后送回转运中心分拣,最后到达目的地。
2.1 从点击快门到Sensor曝光,一共经历几层
在Android平台上,一次正常的拍照/预览流程大致经过下面这些角色:
- App层:使用Camera2 API或者CameraX的应用程序。它拿到一个
CameraManager,通过openCamera打开某个摄像头,然后创建CaptureRequest,里面有各种参数,比如曝光时间、ISO、对焦位置、输出的buffer等。 - Camera Service:它是Android系统里的相机服务,跑在
system_server进程里。App每次请求都要经过CameraService校验、分发,并维护整个Camera设备的状态机。 - Camera Provider服务:这是Android 8.0以后Treble架构下的产物,运行在
vendor进程空间里。它负责把CameraService的调用转给实际HAL实现。MTK平台上,你会看到类似android.hardware.camera.provider@2.6-service-mediatek的进程名。 - Camera HAL:核心是实现了
camera3_device_t接口的HAL库。它负责实现打开设备、配置数据流、处理CaptureRequest、填充结果回调等关键流程。 - ISP Pipeline:在HAL内部,请求会被拆解为两步:先把sensor输出的raw数据通过ISP处理,再做格式转换、缩放、裁剪等后处理,最后输出一张针对不同stream的YUV/JPEG图。
- Kernel驱动:包括sensor驱动(控制上电、I2C寄存器、曝光)、CCI或I2C控制器驱动、ISP驱动、DMA驱动等,负责真正驱动硬件完成曝光、读出、搬运数据。
App层请求是一个不断“向下传递”的过程:你在界面上点一下快门,这个意图变成不可变的CaptureRequest,一层层往下发,同时每一层还会往上层回传元数据和buffer。最终框架层把HAL返回的buffer合成成你看到的预览画面或照片。
2.2 别把Camera Service和Camera HAL混为一谈
初学的人最容易把“Camera Service”和“Camera HAL”当成一回事。实际上它们是两个完全不同的世界。
Camera Service属于AOSP系统框架,大部分代码你可以在framework/av/camera和frameworks/av/services/camera里看到。它遵循Android的通用接口协议,不关心你用的是哪家sensor。
而Camera HAL属于厂商实现,它必须遵循Google规定的android.hardware.camera接口。在Treble架构里,CameraService通过ICameraProvider接口调用vendor进程里的Camera Provider,然后Provider再加载厂商的Camera HAL。这个边界非常重要:Android系统为了解耦系统与硬件实现,强行立了一道“墙”,墙的一边是系统进程,另一边是vendor进程。你写BSP时,主要接触的是墙的另一边,但是你必须理解墙的这一边会怎么约束你。
这也是为什么你经常能在一个bug里同时看到logcat中CameraSerice的报错和vendor进程的crash日志。别只用一边日志就去定位问题,而要走完整条链路。
2.3 MTK BSP是“System”外挂在Linux内核上的一整套平台逻辑
提到MTK平台Camera BSP,总有人以为是某个单独的“驱动包”。其实它是一个从用户空间到内核空间的完整平台软件包。正常情况下,你会看到源码树的vendor分区里,MTK会把绝大多数相机私有内容放在vendor/mediatek/proprietary下,其中包括:
- HAL层:
hardware/mtkcam,比如mtkcam/main、mtkcam/hal等; - 3A算法和参数:
pipe、aaa、tuning等目录; - sensor适配层:
imgsensor目录; - 平台调试工具:
cameratool、factory等。
同时,在内核空间中,MTK也会布置一系列驱动,比如csi controller、ISP主驱动、sensor驱动等。这些和用户态HAL之间通常通过设备节点和私有ioctl交互。Android架构不会强制要求厂商用V4L2,很多情况下你看到的更像是“披着V4L2的外衣、内里是厂商自己的一套抽象”。所以,学MTK Camera BSP,第一步就是习惯他们这种自定义的结构。
3. 打开MTK Camera BSP源码后的第一眼
很多新手打开MTK的源码目录后直接懵了,因为光vendor/mediatek/proprietary里就有几千个文件,根本不知道从哪儿看起。这里我把我认为的“核心目录地图”分享出来,作为你的第一眼导航。
3.1 vendor目录里,哪些模块跟你有关
我用表格把通常会在MTK Camera BSP里出现的核心模块列出来,这样你可以先扫一眼这个表,再针对性去看代码:
| 模块路径/名称(以常见目录结构为例) | 主要职责 | 初学建议关注点 |
|---|---|---|
hardware/mtkcam/main | Camera HAL主入口,负责enum、open、close设备 | 从这里看HAL如何与Provider对接 |
hardware/mtkcam/imgsensor | 不同类型sensor的适配层 | 关注sensor如何被注册、如何枚举 |
hardware/mtkcam/isp | ISP pipeline处理节点 | 关注pipeline的node连接方式 |
hardware/mtkcam/aaa | 3A(AE、AF、AWB)算法框架 | 关注算法与tuning参数的交互入口 |
hardware/mtkcam/tuning | 各场景下的图像质量调参文件 | 关注一个参数文件如何被HAL加载 |
kernel侧对应目录 | sensor驱动、CCI/CSI/ISP驱动 | 关注sensor上电时序和csi通路 |
注意,MTK不同的代码分支和Android版本,目录名称会有些变化,但基本逻辑大同小异。你最好先把你手上那套源码的根目录翻一遍,确定这几个模块的准确路径,再根据这张表去理解。
3.2 kernel侧,别忽略“摄像头”不只一个驱动
很多人想到摄像头驱动就是imgsensor.c,但其实内核里“摄像头”是一组驱动的合称。至少要包含:
- sensor驱动:负责寄存器初始化、曝光和增益配置、输出尺寸/帧率切换、电源域管理。你可以把它理解为摄像头硬件本身的门卫。
- CSI/CAM接口驱动:负责从sensor取回MIPI/CIS数据,做lane分配、时钟设置,相当于一条高速公路的收费站。MTK平台里不同的接口驱动名称在不同版本里不一样,但你要知道它是从sensor到ISP之间的必经之路。
- ISP驱动:负责处理raw数据、统计信息收集,通常还会管理DMA、中断。ISP驱动通常和HAL的ISP pipeline一一对应。
- 电源、时钟和GPIO相关驱动:为摄像头提供工作环境,比如DVDD、AVDD、DOVDD这类电源轨,还有reset引脚、MCLK。
某个sensor不亮,你会盯着sensor驱动看半天;但如果和sensor之间的MIPI数据传不通,那就是CSI层的问题;如果读出来的raw数据在DMA搬运时丢失,那就是DMA/ISP驱动问题。很多人一上来就全堆在sensor驱动上,其实不对。
3.3 两层之间靠什么“约好”的
用户态HAL怎么和内核驱动对话?这里划个重点:它们之间通常会通过设备节点和ioctl来通信。你在HAL里调用open("/dev/video0")之类的手段,然后对文件描述符下发命令,内核驱动去处理。这个“命令协议”可能是V4L2标准的,也可能是厂商扩展的私有命令。
MTK平台通常不追求完全标准V4L2,更多是有一套私有抽象。你需要弄清楚:HAL中某个请求,对应内核侧哪个节点、哪一条命令。这往往也是最难啃的部分之一。建议先抓一次HAL调用,然后对比dmesg或者内核trace,观察命令是怎么从用户态一步步传到内核驱动的。这是打通“架构地图”非常有效的办法。
4. Camera HAL3在MTK上的具体角色
HAL3是整个Android Camera系统的“中间人”:上面要满足Framework的各种约束,下面要驱动具体硬件。MTK的HAL实现中有很多自定义的设计,让你第一眼看上去不太像Android标准的HAL。
4.1 开机时HAL在忙什么
刚开机时,Camera Provider进程会被拉起,它会去加载MTK的HAL模块,并调用HAL的get_number_of_cameras()和get_camera_info()来枚举当前设备上有哪些摄像头。
这个阶段MTK HAL要做的事情很多:读取sensor列表、探测每个sensor当前状态、构建静态能力metadata(比如支持哪些分辨率、哪些能力、曝光范围等)、把这些信息返回给Framework。你改了sensor驱动,但上层看不到这个摄像头,通常就是这里出了问题——sensor没有被枚举到,或者枚举失败。
有意思的是,MTK的sensor枚举并不是简单打开设备去读ID,它会有一段“sensor probe”逻辑,在这个逻辑里会模拟sensor上电、通过I2C读ID、然后断电。这套逻辑非常依赖你在kernel里写的sensor驱动是否都正确,常见问题往往是上电时序不对导致I2C读不到ID。
4.2 一次preview/拍照请求的流转
当Framework下发一个CaptureRequest后,MTK HAL会做一堆处理。大致流程可以理解成:
- 先把请求里要求的输出stream buffer分配好;
- 根据请求中的3A参数,生成一个“需求”;
- 把需求送到ISP Pipeline里,MTK内部通常会分几段处理,很多从其他平台转过来的朋友刚开始会不习惯,因为MTK内部会用“P1”、“P2”这种名称来代表不同处理阶段。P1一般负责raw域的预处理,P2负责后处理、格式转换、缩放等;
- sensor曝光一帧后,raw数据通过CSI进入内存,ISP对raw做处理;
- 后处理完成后,把数据填到多个输出buffer里(比如预览流和拍照流),同时生成结果metadata,通过回调返回给Framework。
如果你遇到“预览和拍照不同步”或者“某一帧拍出来全黑”这类问题,很大概率是request流转变坏——比如某项metadata没传对、某个stream没有被正确配置,或者在HAL内部某个processor节点上出现了buffer等待超时。
4.3 3A算法的位置和调试切入点
3A是AE/AF/AWB,是决定画质的重要模块。在Android架构里,3A并没有标准API,它完全是HAL内部的职责。MTK平台上,3A相关代码通常在mtkcam/aaa里,它和sensor驱动、ISP统计信息、tuning参数深度绑定:
- AE需要知道当前曝光增益、目标亮度、统计信息;
- AF要用到对焦马达的状态和sensor的pdaf数据;
- AWB要用到ISP输出的统计值,结合光源库来判断色温。
听感上,3A出问题时最典型的特征就是“预览忽明忽暗”、“怎么对焦都对不上”、“颜色突然漂移”。你要学会从metadata里看3A状态。用adb shell dumpsys media.camera能看到Framework侧的设备信息和最近一次的请求/结果,但更专业的是在HAL里打点或者打开3A log开关,看AE/AWB算出来的目标值。
4.4 metadata、Buffer和Stream:新手最容易翻车的地方
初学HAL时,最容易折在metadata和buffer这两块。
Metadata在Android Camera里就是一张巨大的键值表,Framework定义规范,HAL负责填值。你一旦漏填一个必须的key,可能导致系统服务直接报错。比如SENSOR_INFO_ACTIVE_ARRAY_SIZE没填对,你就会发现出图大小和裁剪中心对不上,甚至整片图像错位。
Buffer的问题更多出现在stream配置上。每个stream有固定的宽高、格式、用途,Framework会提出一批stream requirements,HAL的configure_streams必须返回“能不能支持”的结果。如果你不能支持,那就会出现“预览正常,一按拍照就crash”这种经典坑。实际的buffer流转还牵涉到生产者/消费者的角色,搞不清楚buffer所有权,你可能会在某个处理节点把已经被消费的buffer又写了一次,导致画面撕裂。
5. 把“问题”对应到“模块”的排查地图
架构学完之后,最大的价值不是“能画出图”,而是“能快速知道问题归谁”。我自己习惯在排查问题时先画一张地图,把现象可能涉及的模块和排查手段列出来。这里分享一个精简版本。
5.1 常见现象、对应模块与第一排查手段
| 现象 | 可能的模块 | 第一排查动作 |
|---|---|---|
| 打开相机黑屏/无预览 | 驱动到HAL的通路没打通 | 先看kernel侧sensor probe是否成功,再看HAL枚举; |
| 预览画面偏色 | sensor驱动/tuning/3A | 关掉AWB跑raw图,区分是否是sensor或ISP处理问题; |
| 拍照照片全黑,但预览正常 | stream/buffer配置 | 检查拍照流的buffer格式、大小以及HAL内部是否填了图; |
| 拍照/预览卡顿掉帧 | Ae曝光时间/ISP性能/驱动丢帧 | 看dmesg里的frame done中断是否连续,排除sensor配置问题; |
| 某一分辨率下模糊 | ISP缩放/tuning降噪参数 | 查看该分辨率下的tuning档位,确认是走哪条ISP管线; |
| 前摄/后摄调用crash | HAL与Framework的metadata不匹配 | 抓logcat,找CameraService报错的metadata key。 |
这张表当然不能覆盖所有问题,但能把80%的初学问题限制在几个模块里。拿到一个问题,先做“现象到层”的归类,再去深挖,效率会高很多。
5.2 我看到的一次现场排查:预览绿屏
有一次在实验室帮同事排查一个bug:新项目的后摄在某个预览分辨率下会出绿屏,但切换成拍照预览又正常。当时他没有多想,以为是sensor原timing的问题,一直在调驱动。我过去后问他:这个分辨率下HAL走的是按传感器原始比例裁剪,还是做了缩放?他一愣。
我们一起去查日志,发现这个分辨率是走HAL内部把sensor输出的raw直接处理成YUV后,再做任意缩放的。绿屏就出现在缩放节点之后的某个格式转换上。最后定位到是HAL的一个format转换表在这个分辨率下没匹配上,写成了一种未实现的格式,导致填充buffer时通道映射错乱。整个问题根本不在sensor驱动。这个例子告诉我们,同一现象在不同条件下会被不同层截获。如果不先弄清“这个分辨率属于哪个处理链路”,你大概率会把时间浪费在错误的方向上。
5.3 打日志的“三层搜寻法”
调试MTK Camera BSP,我建议你始终保持“三层日志”的意识:
- Kernel层:通过
dmesg查看sensor probe、reg read/write、ISP frame done、CSI error等关键节点; - HAL/vendor层:关注
logcat里mtkcam相关的打点,打开log开关后能看到request流向、processing node的执行序列、buffer的填放情况; - Framework层:着眼于
CameraService和CameraProvider之间的调用关系,主要看session、stream、request的状态迁徙。
具体操作上,你可以用下面这类命令快速过滤,比如:
adb shell dmesg | grep -iE "imgsensor|seninf|csi|isp|camsys" adb logcat -c adb logcat -v time | grep -iE "mtkcam|camera_service|camera provider|CamX"日志里你会看到各种tag,不要被数量吓到。关键是找一个“锚点事件”,比如frame done、stream configure、capture result,把链路串起来。这些你在日志里见得越多,大脑里的架构图就越清晰。
6. 从架构篇出发,下一步该怎么学
架构篇的目的不是让你背概念,而是让你知道接下来该把时间花在哪里。如果此刻你已经有了“分层地图”,下一步就是让知识点变成肌肉记忆。
6.1 先搭一套能随手复现问题的环境
学Camera BSP,光看书和代码是不够的,你需要一台能刷机的设备或者开发板,确保你能随时烧录、随时抓log。我见过不少人用仓库里的log文件来学,可一旦自己上手,很多操作还是不熟练。我的建议是,先把你手上的设备刷到和你源码匹配的状态,并记录下刷机流程,然后反复开关机、开关camera,确保自己能独立复现基础问题。
同时把调试工具链准备齐:adb、fastboot、串口(如果板子有),以及解开可能的log开关。这样碰到网络上的案例,你可以顺手在本机上验证,而不是纸上谈兵。
6.2 给自己定几个动手目标
架构学完后,不要急着通读代码,试着给自己安排几个可以验证“已理解”的小目标。我自己带人的时候一般会列这些任务:
- 目标1:把一个新sensor驱动加进工程里,让它能被上层枚举出来。这个目标考验你对sensor驱动、HAL枚举机制、编译系统三者的理解;
- 目标2:修改一个tuning参数,把预览的饱和度明显提高或者降低,再观察生成的jpg变化。这个目标能帮你理解tuning在HAL里的加载时机和作用范围;
- 目标3:在HAL日志中,找到一次拍照的完整request流转记录,能把
configureStreams、processCaptureRequest、processCaptureResult之间的对应关系标出来。这个目标会逼着你读API文档和打印日志; - 目标4:故意把一个metadata字段填错,比如把
SENSOR_INFO_ACTIVE_ARRAY_SIZE改小,看上层会不会崩,并从报错中去反推Framework对metadata的约束。
这四个目标覆盖了驱动、调参、API、系统约束四个维度,做完之后,你对架构的理解会比背十遍调用链都牢固。
6.3 最后说点心里话
关于MTK平台Camera BSP,很多人想找一条“捷径”,希望有一篇文章能让他一次就变成高手。我觉得真相是:架构是越用越清楚的。我干这行也有年头了,到现在每换一个项目,都会重新打开架构图,重新确认一些模块边界。每踩一次坑,对架构的某一条边就多一分理解。
这篇文章只是“Android平台架构篇”的引子。真正入了门之后,你还会碰到ISP pipeline的细节、sensor驱动各种时序配置、3A算法的调优经验、性能功耗的优化策略。建议你学完这一篇后,立刻去翻你手头那份工程里的目录结构,照着我在第三部分列的地图走一遍,把每个模块的入口文件标注下来。然后在一次切sensor的task里,把整个链路串起来体验一遍。用不了多久,你也能从看到一个log就能猜出问题出在哪一层的老手。