做 Camera 的老司机都清楚一个道理:预览虚、曝光乱、对焦飘,最后多半能在 Sensor 这一层找到蛛丝马迹。Android Q 的 MTK Camera Hal3 Search Sensor,就是这条请求链路的最前端。之前我跟着调一个模拟项目里的前摄点亮任务,发现不少同学把精力全放在 3A tuning 和 stream 参数上,结果 Sensor 列表为空还在那里怼 pipeline,纯属白忙。所以这一篇想把 Search Sensor 讲透:它发生在哪个阶段、底层到底在搜什么、搜不到怎么定位,以及我自己踩过的几个真实存在的坑。
说明一下,以下分析以 Android Q 分支的 MTK Camera HAL3 代码组织方式为例,不同版本、不同项目在文件命名和调用层级上会有差异,但设计思路是通用的。
1. 为什么 Camera 请求链路要把 Sensor 搜索放在最前面
1.1 Camera 打开瞬间,系统在等一个“答案”
用 Camera 应用打开预览,看起来只是一次 startPreview,但底层是 CameraService 通过 HIDL 接口找到 CameraProvider,再让 HAL 打开对应 cameraId。这个过程中,HAL 必须向 Framework 保证一件事:这颗 Sensor 存在,并且它的能力参数是完整可用的。只有在拿到这个保证之后,Framework 才会继续下发 configureStreams、processCaptureRequest 这些后续动作。
你可以把 Search Sensor 理解为一次“点名签到”。HAL3 架构里的 pipeline、node、stream 都建立在 Sensor 这个物理实体之上。如果点名失败,后面所有环节都无法构造。很多现场日志里看似是 HAL 打不开,或者 getCameraIdList 返回空,根因其实都埋在更早的 Sensor 搜索阶段。所以排查 Camera 问题,第一件事不是翻上层代码,而是先在日志里确认 Search Sensor 到底成功了没有。
1.2 Search Sensor 不是“找摄像头”,而是建立物理器件和软件配置的契约
有人会误解“搜索 Sensor”就是把设备列表遍历一遍,看看系统里插了几个摄像头。实际不是。Search Sensor 要做的是把三样东西对齐:内核驱动里真实探测到的物理 Sensor、HAL 侧静态配置表里登记的 Sensor 信息、以及最终要返回给 Framework 的 Camera 静态 metadata。
物理 Sensor 不是凭空出现的,它靠 I2C 挂在主板上,有上电时序,有厂商 ID,有分辨率,有色温、镜头朝向等物理属性。软件眼里这些都不是自动知道的,必须通过一次真实的硬件交互把 chip ID 读回来。读完 ID 再去静态配置表里反查,才能确定这颗 Sensor 是哪一颗、该怎么建 pipeline、该把哪些能力写进 characteristics。这正是 Search Sensor 存在的意义:用硬件探针去校验软件配置,而不是单纯相信配置文件。
2. Search Sensor 在 MTK HAL3 代码中的调用时机和典型走向
2.1 从 HAL open 到 Sensor 节点的调用链
在 Android Q 的 MTK HAL3 实现里,整体架构已经从早期 legacy 相机 HAL 切到了 HIDL CameraProvider 那套体系。CameraProvider 加载 HAL3 的 .so 之后,调用路径大致是这样的:
CameraProvider(HIDL 服务) -> hal3_module->open() -> CamDeviceManager::openDevice() -> CamDevice 构造 -> PipelineModel / PipelineModelCallback 创建 -> 各个 Node 初始化,包括 SensorNode -> SensorHal 接口调用 -> Search Sensor这里最关键的转折在 PipelineModel 创建之后。PipelineModel 编译 camera pipeline 时,需要先把 Sensor 节点的输入输出确认下来,否则后续的 ISP 节点、P1 节点、raw 流全都无从谈起。所以在 MTK 代码里,SensorNode 第一次初始化时通常就会触发 SensorHal 的 Search Sensor。
需要注意,Android Q 时代已经有多摄、逻辑摄像头、物理摄像头这些概念,Search Sensor 往往不是只做一次,而是要对每个物理 Sensor 都执行一遍。表现为日志里可能连续出现多次 sensor index、chip ID 相关信息,最后汇总成一份 Sensor 能力列表。
2.2 为什么每次 open 都要重新搜索,而不是缓存一次
有人会问:出厂时 Sensor 又不会变,为什么每次打开相机都要重新搜索一遍,直接用一次保存下来的结果不行吗?
从平台稳定性角度讲,重新搜索是更稳妥的做法。Sensor 驱动在 probe 阶段可能依赖上电时序,上一轮的 power off 是否彻底,会直接影响下一轮 power on 之后读芯片 ID 的结果。如果 HAL 长期缓存上一次的结果,一旦发生异常下电、系统休眠唤醒、摄像头模组方向改动,就会出现缓存数据和实际硬件不一致,给问题排查添乱。
MTK 代码里虽然会有静态信息缓存,比如 sensor list 的配置、SensorStaticInfo 的部分参数,但 pipeline 真正建立之前,仍会对 Sensor 做一次 ID 确认。你可以把这次确认类比成每次开机都要重新握手,而不是相信历史连接状态的 TCP Keepalive。
3. Sensor 枚举的核心机制:设备节点、驱动 ID 与静态信息表的三方对齐
3.1 内核侧:Sensor 是 v4l2 subdev,不是普通 I2C 设备
在 Android 的相机架构里,内核 sensor 驱动通常以 V4L2 subdev 形式注册,用户空间看到的可能是/dev/videoX或者/dev/v4l-subdevX节点。HAL 通过 open 节点、调用VIDIOC_SUBDEV_G_FMT这类 ioctl,和内核驱动打交道。
Search Sensor 要拿到最可靠的信息,不是看文件名字,也不是猜节点号,而是直接读 Sensor 寄存器里的 chip ID。这个动作在驱动层通过 I2C 完成,HAL 层再通过自定义 ioctl 或者能力查询接口拿到返回值。换句话说,Search Sensor 是一个真实发生的硬件访问过程:先给 Sensor 上电,再拉 MCLK,然后往某个 I2C 地址写读寄存器指令,最后把返回的 ID 和预期值比较。
3.2 一个可以反复对照的 Search Sensor 伪代码流程
MTK 不同分支的具体实现差异很大,但核心逻辑殊途同归。下面这段伪代码可以帮你理解搜索的大致节奏:
// 伪代码:展示核心搜索思路,具体实现因分支而异 int searchSensor() { for (int i = 0; i < sensor_table.size(); i++) { SensorInfo& info = sensor_table[i]; // 配置文件里可以单独关闭某个 Sensor 的搜索 if (!info.enable) { continue; } // 三步走:上电 -> 读ID -> 下电 sensorHal->powerOn(info); // 供AVDD/DOVDD/DVDD,拉MCLK,释放Reset int chipId = sensorHal->readChipId(info); // I2C读寄存器 sensorHal->powerOff(info); // 必须下电,避免影响下一颗Sensor if (chipId == info.chipId) { matchedSensorIndex = i; return OK; } } return -ENODEV; }这段伪代码里最容易被忽视的就是最后那个powerOff。有些人调试时为了省事,把下电步骤注释掉,结果第二颗 Sensor 读取的时候返回了一个莫名其妙的 ID。多摄模组共用电源轨时尤其明显,一颗 Sensor 还上着电,另一颗去读 ID,回读的数据可能来自没被搜索的那一颗。所以搜索循环里的上下电配对,不是可有可无的规范,而是保证结果正确的先决条件。
3.3 静态信息表:“通缉名单”由谁维护
HAL 侧不可能无限遍历所有 Sensor,它搜索的依据是一张静态信息表。在 MTK 平台里,这类配置通常来自sensorlist、cam_cal或者 vendor 目录下的 sensor 配置模块。面向项目集成时,这张表就是“通缉名单”,里面登记了系统支持哪些 Sensor、每颗 Sensor 的 I2C 地址是多少、芯片 ID 是多少、用哪路电源、分辨率多大。
典型字段如下:
| 字段 | 作用 | 常见错误 |
|---|---|---|
| sensor_id | Sensor 在 HAL 里的逻辑编号 | 与驱动侧编号不一致 |
| sensor_name | 模组厂商型号,如某型号前摄 | 型号写错导致匹配失败 |
| i2c_bus / i2c_addr | Sensor 挂在哪条 I2C 总线、设备地址 | 地址与原理图不符 |
| chip_id / chip_id_mask | 读取后的 ID 和掩码比对值 | 掩码写错导致误判 |
| avdd / dovdd / dvdd | 电源域配置 | 用错 regulator 导致上电失败 |
| mclk | 主时钟频率 | 频率不对导致 PLL 不稳 |
| orientation / facing | 画面朝向和前后摄属性 | 前摄写成后摄,旋转不对 |
这张表不归内核驱动管,而是平台集成者根据模组规格书维护的。新项目点 Sensor,第一步就是往表里加一条记录,否则 HAL 根本不会去探测这颗 Sensor。很多“新 Sensor 搜不到”的反馈,最后查下来只是这行配置没有加。
3.4 搜索结果的两个分支:找到与找不到
如果循环结束都没有匹配到预期的 chip ID,Search Sensor 返回失败。此时 CamDevice 的构造流程会中断,HAL 向上返回 open 失败,或者 getCameraIdList 返回的列表里干脆没有这颗摄像头。Framework 层看到的可能是openCamera fail、camera id not available,误导很多人去查上层 service,其实根源在 Sensor 搜索这一步。
如果搜索成功,HAL 就拿到了一组 Sensor 身份信息,包括 sensor_idx、chip ID、名称等。接下来它会结合静态配置表,生成一颗 Sensor 的能力集,再交给上层使用。这个“能力集”本质上是 Camera characteristics 的基础素材。
4. 搜索结果如何变成 HAL3 的“户口本”:Metadata 映射与多摄归类
4.1 从 Sensor 结果到 Camera Characteristics 的字段映射
Search Sensor 本身不负责生成所有 metadata,它只是把 Sensor 的第一手身份信息拿到手,后续再和 HAL 的其他模块一起拼出完整的静态能力。你可以理解成 Search Sensor 负责“验明正身”,而能力组装负责“填写简历”。
常见映射关系如下:
| Search Sensor 结果字段 | Android Camera Metadata Key |
|---|---|
| sensor_name | ANDROID_SENSOR_INFO_NAME |
| active array width / height | ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE |
| orientation | ANDROID_SENSOR_ORIENTATION |
| facing | ANDROID_LENS_FACING |
| 分辨率、帧率能力 | ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES |
| raw 格式支持 | ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS |
所以如果 Search Sensor 失败,不只影响打开相机,而是整个静态 metadata 都无法正确生成。表现为部分第三方 App 直接拿不到相机,系统相机也可能闪退。
4.2 Android Q 多摄逻辑:物理 Sensor 要先搜出来再归并
Android Q 一个比较大的变化是逻辑摄像头和物理摄像头的关系。逻辑 cameraId 可以对应多个物理 cameraId,比如后置广角加长焦在 Framework 看来是一个逻辑摄像头,实际由两颗独立 Sensor 组成。
这种情况下的 Search Sensor 流程,往往先逐颗物理 Sensor 做搜索,拿到所有物理 sensor 的 ID 和静态信息,再根据平台的 topology 配置,把几个物理 Sensor 归并成一个逻辑 cameraId。如果这个归并表配置错误,也会出现“物理 Sensor 能搜到,但逻辑摄像头打不开”的诡异现象。
实战中遇到双摄或三摄项目时,不要一看到 Search Sensor 失败就只盯 Sensor 驱动,还要确认多摄拓扑表里物理 ID 的映射顺序是不是和硬件排布一致。顺序错乱造成的失败,日志里几乎看不出 Sensor 本身的问题,很容易被误判为驱动 bug。
5. 一次 Search Sensor 失败的完整排查链路:从 log 到 root cause
5.1 现象与第一条日志
假设场景:某项目升级系统后,前摄完全黑屏,后摄正常。logcat 里能看到类似这样的关键字:
[CamDeviceManager] openDevice 0 fail, searchSensor not found [CameraService] connectDevice error: -EINVAL这种日志已经把问题指向了 Search Sensor。接下来要做的不是去改 App 的 GL 渲染,也不是去查 surface buffer,而是沿着 Sensor 搜索链路一层层往下压。
5.2 内核层、HAL层、配置表三步定位
我通常按三层来过滤日志:
| 检查层 | 常用命令/日志 | 重点看什么 |
|---|---|---|
| 内核驱动层 | dmesg | grep -iE "imgsensor|i2c|mclk" | sensor driver probe 是否成功、I2C 传输是否报错 |
| HAL 搜索层 | logcat | grep -iE "searchSensor|sensorHal" | 搜索到哪个 sensor index、power on/off 是否成对出现 |
| 静态配置层 | 查看 vendor 下的 sensorlist / cam_cal | chip_id、I2C 地址、电源域是否和实际硬件一致 |
排查时先确认内核 probe 是否成功。如果 dmesg 里连 imgsensor probe 成功的记录都没有,说明问题在更底层的 device tree 或者电源配置,HAL 再怎么写都是白搭。如果 probe 成功但 HAL 搜索失败,重点对比 sensorlist 里的 chip ID 和驱动实际读到的 chip ID。
5.3 两个典型 root cause
我遇到过两次比较有代表性的失败,可以给各位参考。
第一次是 I2C 地址不匹配。项目的硬件改版把前摄模组换成了另一家供应商,Sensor 的 I2C 地址从 0x20 改成了 0x10,但 sensorlist 里还是旧地址。Search Sensor 读出来的 ID 自然是错的,比对不过,搜索失败。这种问题看日志只会看到“chip ID mismatch”,不看硬件原理图很难定位。
第二次是 Sensor 电源配置问题。某颗 Sensor 的 AVDD 和 DOVDD 电源域在 device tree 里挂反了,导致上电顺序不对,冷启动时搜索偶发失败,但热启动又正常。这种“偶发搜不到”最折磨人,因为整机重启十次可能有九次正常,只有一次黑屏。后来通过不断抓 dmesg,对比读 ID 阶段的时序,才发现是电源域配置问题。
5.4 下手改代码之前的前置检查清单
排查 Search Sensor 失败,我建议先按下面这个清单过一遍,再考虑改代码:
- 确认 CameraProvider 服务确实重启过,而不是只 force-stop 了相机 App。HAL 侧缓存没清掉,有时会掩盖真实结果。
- 确认内核 device tree 中 Sensor 节点 probe 成功,相关 regulator 和 GPIO 都被正确申请。
- 确认 sensorlist 里新增的 Sensor 记录与模组规格书一致,尤其是 chip ID 和掩码。
- 确认多摄拓扑表里物理 Sensor 顺序和硬件排布一致。
- 确认平台加载的 dtbo/board revision 与实际硬件版本一致,不同版本板子的 I2C 地址可能不同。
这些前置检查看起来很基础,但绝大多数搜不到的问题都出在这里,而不是在 HAL 的算法逻辑里。
6. Search Sensor 不能只靠软件:上电时序、I2C 地址和节点映射是隐性依赖
6.1 上电时序:Sensor 也有自己的起床流程
Sensor 从断电到能读 ID,并不是一蹴而就。典型流程是先拉 MCLK,再依次给 AVDD、DOVDD、DVDD 上电,然后拉高 Reset,等待一段时间稳定,最后才能通过 I2C 读寄存器。
这段时序里任何一个 delay 不够,都可能导致 chip ID 读取失败或读到错误值。尤其是 Reset 释放后的稳定时间,很多工程师为了赶进度会拍脑袋设置一个比较短的值,结果 Read ID 偶发失败。排查时可以用一个简单方法验证:把 Reset 释放后的等待时间临时加长到规格书建议值的两倍,再看搜索是否变稳定。如果变稳定了,基本就是时序余量不足。
6.2 共电源双摄下电不彻底,会串出错误的 chip_id
双摄模组共用一路主电源非常常见。Search Sensor 在遍历时,如果上一颗 Sensor 没有彻底下电,下一颗 Sensor 上电后总线状态可能没达到理想状态,读回的 ID 不一定来自目标 Sensor。
这也是为什么很多规范的代码会在搜索循环里强制执行powerOff,甚至会在每次搜索前先做一次全局下电复位。Android Q 的多摄场景越来越多,这条要求已经不是“建议”,而是必需品。自己写搜索逻辑时,千万别图快省略这步。
6.3 /dev/videoN 的节点号不是永远可信
V4L2 的/dev/videoN节点编号由内核驱动 probe 顺序决定,不是一个稳定不变的物理地址。今天启动顺序是 video0 对应后摄,明天某个驱动 probe 快了一点,video0 就可能对应前摄,或者干脆多占一个 video 节点。
Search Sensor 如果硬编码节点号,很容易被这种动态变化坑到。更稳的方案是通过 media controller 查询实体名称,或者遍历/dev/v4l-subdevX后用实体名字匹配目标 Sensor。日志里看到 “open /dev/video2 fail” 时,不要急着判断是硬件问题,先确认这个节点到底对应哪颗传感器。
6.4 搜索顺序为什么不能随意调整
部分 Sensor 驱动的s_power回调依赖一个隐式顺序:先 A 后 B 能正常 probe,反过来就会因为电源轨竞争失败。HAL 的搜索顺序如果被随意调整,读 ID 阶段的成功率和稳定性都会受影响。
所以这类代码看起来只是简单循环,实际排列顺序往往经过平台验证。调试时如果发现顺序和你想象的不同,先保留原状,优先怀疑其他环节。真要调顺序,必须把每一条上下电日志都打开,确认没有任何一个节点出现电源冲突。
7. 写在最后:我给自己定的三条 Sensor 调试纪律
7.1 先谈 Search,再谈 Tuning
Sensor 搜不到,后面 3A、ISP、raw 全部是空转。现在我把 Search Sensor 的日志提前到了刚开始调试时的第一道检查项,任何相机问题都先确认这一步过了再往下走。这一步省掉的时间,远比来回翻上层日志要多。
7.2 把搜索路径上的每一步都留下日志
我会在每次 powerOn、readChipId、powerOff 前后各打一条日志,记录 sensor index、chip ID 和操作结果。刚开始觉得冗余,后来发现排查硬件时序问题全靠这些点。某次偶发失败,就是把日志时间戳和 dmesg 时间戳对照后,才发现 Reset 释放后稳定时间不足。
7.3 偶发搜不到先怀疑时序与电源,而不是急着改代码
遇到“十次有九次正常、一次黑屏”,先别在 HAL 里加 sleep。优先抓 dmesg,对比成功和失败两次流程的差异。很多时候是电源纹波、Reset 保持时间、I2C 上拉电阻这些问题。软件可以临时加长时序做对照实验,但真正的修法是回到硬件或者 device tree 配置里解决。
Search Sensor 虽然只是 Camera HAL3 里的一段早期逻辑,但它把硬件、内核、配置表、HIDL 服务串在了一起。如果你能把这段逻辑彻底吃透,后面再遇到 Camera 打不开、黑屏、枚举异常,定位速度会快上不止一个量级。