ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MTK Camera7启动流程全解析:从上层调用到底层Sensor出帧

MTK Camera7启动流程全解析:从上层调用到底层Sensor出帧 MTK Camera7这个关键词玩Android相机底层的工程师应该都不陌生。不管你是刚接手MTK平台项目还是从高通切过来不久第一次面对camsys、imgsensor、isp7.0、pipeline这一堆日志和代码时大概率都会有点发懵。这篇东西不是官方文档的翻译是我在真实项目里把整条启动链路从上层调用到底层寄存器捋顺之后的笔记想把“相机从点击图标到出帧系统里到底发生了什么”这件事讲清楚尤其适合正在调试MT6833、MT6768、MT6853这类平台相机问题的兄弟们参考。MTK的Camera7架构和早年的Camera3/4差别非常大它不再是一个简单的HAL包而是一套完整的框架从用户态HAL到内核V4L2驱动再到Sensor模组本身的power-on时序每一层都有关键节点。启动流程就是一条从业务命令变成硬件动作的传导链任何一环出了问题表现都是黑屏、闪退、卡预览。所以理清这条链路的每个环节比遇到bug再翻代码要高效得多。1. 先把整体链路放在脑子里1.1 Camera7到底是什么先纠正一个容易混淆的点。Camera7不是指Android 7也不是相机硬件版本7它是MTK推出的新一代相机HAL架构代号。从Android 10开始MTK在多个平台上把相机软件栈统一到了Camera7比如MT6768、MT6853、MT6833、MT6983这些平台走的都是这套框架。它的核心特点是把相机的整个处理过程抽象成一条Pipeline每个处理环节是一个NodeNode之间通过Buffer和Event交互底层再通过V4L2节点和内核态交互。这套架构里你会经常看到这几个词Client、Server、Pipeline、Node、Stream、Request。打开相机App后App通过CameraManager拿到一个Client这个Client向HAL发请求HAL把请求翻译成Pipeline的一次执行Pipeline里的每个Node分别干自己的事比如P1Node去收RAW图P2Node把RAW变成YUV最后把YUV buffer交给上层显示。理解了这个基本模型后面看代码就有抓手了。说句实在话MTK这套东西比高通的CHI架构要“重”一些节点多层次深初次上手会觉得绕。但好处也很明显每个环节的职责非常清晰出了性能问题或者图像问题定位起来反而比高通那边靠一堆override来得直接。1.2 一条帧请求从Launcher到屏幕的完整路径我习惯把一个正常的拍照启动过程分成四个阶段应用发起、HAL建链、驱动出帧、数据回显。下面这张表是我在项目里自己整理的基本能对应到log和代码里的关键调用点。阶段关键模块核心动作应用层Camera2 API / CameraServiceApp调用openCamera申请预览流HAL层CameraProvider / MTK CameraHALconfigureStreams构建Pipeline算法层3A / Tuning / ISP Node加载tuning参数初始化AWB/AF/AEC内核驱动camsys / imgsensor driverpower-on sensor配置MIPI出RAW帧实际启动时从App点击相机图标到第一帧预览显示通常要经过几百毫秒。其中大头基本耗在HAL建Pipeline和Sensor Power-on上。如果你们项目里有个“点击图标后黑屏3秒”的bug重点就是查这两个地方。整个链路可以打个比方App是食堂的客人Camera HAL是前台点单员Pipeline是后厨流水线Sensor是食材供应商。客人说“来一份拍黄瓜”点单员把需求写进单子Request后厨各岗位按单子开工Node处理供应商供货Sensor出RAW最后成品端上桌buffer回显。哪个环节慢了或者堵了都会导致上菜慢或者直接上不了菜。2. 从设备树到Sensor上电硬件侧的启动细节2.1 设备树里Sensor是怎么挂上去的MTK平台每个物理CameraSensor都会在设备树里有一个独立节点路径一般在arch/arm64/boot/dts/mediatek/下的imgsensor或kd_camera_hw相关dtsi里。节点里最重要的几项是I2C地址、电源引脚、Reset引脚、MCLK频率、sensor型号ID。pio { camera_pins_cam0_rst_0: cam0rst0 { pins_cmd_dat { pinmux PINMUX_GPIO86__FUNC_GPIO86; slew-rate 1; output-low; }; }; camera_pins_cam0_rst_1: cam0rst1 { pins_cmd_dat { pinmux PINMUX_GPIO86__FUNC_GPIO86; slew-rate 1; output-high; }; }; }; i2c3 { camera_sensor0: sensor10 { compatible mediatek,camera_sensor; reg 0x10; pinctrl-names default, cam0_rst0, cam0_rst1; pinctrl-0 camera_pins_default; pinctrl-1 camera_pins_cam0_rst_0; pinctrl-2 camera_pins_cam0_rst_1; }; };这段配置看着简单但实际项目里90%的sensor探测失败都跟它有关。最常见的坑是I2C地址和sensor datasheet对不上、电源GPIO配错、Reset脚高低电平逻辑反了。我之前遇到一个项目硬件上把AVDD和DVDD接反了但设备树里还按老模组配sensor在开机时能读到ID就是不出图后来量电压才发现电源都不对这种问题光看软件log很难定位。另一个容易忽略的是MCLK频率配置。MTK平台MCLK一般有13MHz和24MHz两档Sensor的datasheet里会写明需要多少M。设备树里如果配错sensor能probe成功但输出图像会有水波纹或者噪点跟驱动关系不大纯粹是clock不对。2.2 内核态驱动加载与V4L2子设备设备树配置好之后内核在启动时会注册对应的platform device然后调用kd_imgsensor驱动里的probe函数。这个驱动的核心工作就三件事解析设备树、读取sensor ID、注册V4L2 subdev。MTK的imgsensor框架是独立于标准V4L2的它先通过imgsensor_probe扫一遍有哪些sensor在线然后把每个sensor封装成一个MTK_CAM_DEV最后注册成/dev/videoN节点。这里有坑video节点号和sensor逻辑id不一定对应是动态分配的。调试时不要拿着/dev/video0就以为是主摄需要通过media-ctl -p或读/sys/class/video4linux/video0/name确认。驱动加载阶段的日志关键字一般是kd_imgsensor、imgsensor_probe、sensor_id。正常情况会看到[ 12.345678] kd_imgsensor_probe: sensor0 detect ok, id0x40, type0如果sensor没有上电或者I2C读不到ID这里就会输出类似read sensor id fail的log然后这个节点就不挂了。注意这里的ID检测只是帮sensor在系统里挂名真正的通信要等到HAL层open camera、调用streamOn之后才开始。2.3 Sensor Power-on时序与MCLK设备树只解决了“硬件上有没有sensor”的问题真正让sensor亮起来是在每次open camera时做的power-on序列。MTK的imgsensor驱动里每个sensor都有一个power_on回调里面按顺序做这么几件事拉高AVDD/DVDD/DOVDD电源、拉高Reset脚、启动MCLK、等稳定、I2C初始化寄存器。static int sensor_power_on(struct mtk_sensor_power_info *power_info) { // 1. 各电源域上电 mt_set_gpio_mode(GPIO_AVDD_EN, GPIO_MODE_00); mt_set_gpio_out(GPIO_AVDD_EN, GPIO_OUT_ONE); // 2. reset低电平释放 mt_set_gpio_out(GPIO_RESET, GPIO_OUT_ZERO); usleep_range(1000, 2000); // 3. MCLK开启 mtk_mclk_enable(MCLK_13M); // 4. reset高电平进入工作状态 mt_set_gpio_out(GPIO_RESET, GPIO_OUT_ONE); usleep_range(5000, 10000); // 5. 写sensor初始化寄存器 sensor_write_reg_list(init_setting); return 0; }每个步骤之间的延迟非常关键。比如reset拉低后至少要等几个ms再拉高否则sensor内部逻辑没复位完后面I2C读寄存器就会返回错误或者读到默认值。如果某些sensor数据手册里要求MCLK必须在reset拉高之后才启动而你按通用模板先开了时钟再复位可能就会出现“偶尔能出图换一颗sensor就黑屏”的诡异现象。我调试的时候习惯在power_on和s_stream里临时加printk带时间戳用dmesg对比实际时序和datasheet要求的差值。这样能快速定位是不是时序问题比拿着示波器去量快得多尤其是没有硬件支持的时候。3. HAL层的启动流程从CameraService到Pipeline3.1 CameraProvider加载与Camera open应用层调用CameraManager.openCamera(cameraId, callback, handler)之后经过CameraService最终会调用到HAL层的HalCameraFactory和CameraDeviceManager。在MTK的Camera7 HAL里每个cameraId对应一个MtkCameraDevice这个对象在第一次open时创建负责后续所有与该摄像头相关的会话。整个过程可以通过logcat中的Cam3HAL、Cam3Provider等tag来跟踪。正常情况下能看到类似Cam3HAL: [open] cameraId0, apiMode1 Cam3HAL: [configureStreams] streamCnt3, opMode1 Cam3HAL: [createPipeline] pipeId0x1001, nodeCnt8到这里HAL已经知道上层要的是什么规格的预览流、拍照流、分析流了。后面的动作是根据这些stream的格式和分辨率决定要建一条什么样的Pipeline以及Pipeline里包含哪些Node。顺带提一下“按键进入拍照”这个常见需求。很多项目会做音量键拍照之类的外设按键功能这个路径和Launcher里点击相机App图标不一样。按键事件从系统按键服务进来后CameraService会向HAL发送triggerAction或者直接拉起相机进程。调试这个需求的难点在于按键事件到达的时机可能比HAL初始化更早如果HAL还没创建MtkCameraDevice按键事件会被丢掉。项目里遇到过这个问题最后是在CameraService层加了按键事件的缓存等相机进程起来之后再派发。3.2 configureStreams与Pipeline结构解析configureStreams是HAL启动流程里最重要的一个方法。上层App在预览前会把需要的stream列表发给HAL比如预览1280x720、拍照4032x3024、分析640x480。HAL拿到这个列表后会做两件事检查传感器是否支持这些尺寸确定节点拓扑并创建对应的Node。这里用到了MTK很核心的一个设计一个session可以包含多条pipeline每条pipeline由一个RootNode和若干子节点组成。预览和拍照如果分辨率不同可能不会走同一条pipelineMTK会按需要实时创建新的pipeline。这也就解释了一个常见现象预览一切正常一按拍照就黑一下或者卡顿这是新pipeline在切换时开销大导致的优化方向通常是做pipeline复用或预创建。普通单摄预览场景Pipeline里典型的节点排列是这样的P1Node从sensor获取RAW图管曝光、增益的硬件配置NR3DNode做降噪如果是单帧RAW退化为2D降噪P2ANode做demosaic、颜色校正、插值缩放输出YUVP2CRZNode专门做裁剪和缩放把图变成目标分辨率FDNode人脸检测可选JpegNode拍照时会挂上负责编码成JPEG每个Node拿到上一步的buffer处理完再交给下一个Node一个Node处理完一帧用不了几毫秒整个pipeline一帧的正常耗时在20~40ms之间包含sensor曝光。如果超过这个量级就要检查是某个Node的算法耗时超标还是buffer在节点间传递时被拖住了。3.3 3A与Tuning数据的初始化顺序3AAE、AF、AWB是相机出图质量的核心也是MTK Camera7启动流程里比较隐秘的一部分。在Pipeline创建的同时MTK会为每个打开的设备创建一个3AController它会从sensorlist和tuning数据里读取当前sensor对应的参数表。MTK平台里3A相关的参数主要放在vendor目录下tuning解析RAW图用的ISP参数比如shading、CCM、gammaaaa3A算法的一些配置开关AE收敛速度、AF搜索范围dbg调试参数可以动态开关log和高通平台对比这一点差异非常大。高通的3A参数基本都在camx和chi-cdk里通过override机制修改MTK则是集中式的参数文件直接改对应平台的tuning文件就能调整效果。很多从高通转过来的工程师一开始会不太习惯找不到参数在哪其实只要你打开vendor/mediatek/proprietary/packages/apps/MtkCamera/或者vendor/mediatek/proprietary/custom/mtXXXX/hal/imgsensor/按照sensor型号搜索就行。从调试角度来看3A初始化慢、AE不收敛这类问题一大半不是算法本身的问题而是初始化时tuning参数没有正确加载。判断方法很简单打开logcat搜3A相关tag如果看到类似[3A] use initial setting或者[3A] force sync说明3A认为当前参数不可信重新走了一遍收敛首帧亮度就会明显偏暗或偏亮。4. Pipeline运行时帧请求的流转与Buffer管理4.1 Node的职责与常见排列启动流程走完Pipeline里的各个Node就进入待命状态。每一帧请求从上层下来之后会从一个Node传到另一个Node每个节点都会完成一次buffer的读入和写出。我把最常见的预览链路列了一下方便对照节点名输入输出主要职责P1NodeRaw buffer分配RAWSensor曝光、增益、出RAWNR3DNodeRAWRAW(降噪后)多帧降噪/单帧降噪P2ANodeRAWYUVDemosaic、颜色校正、插值到目标尺寸P2CRZNodeYUVYUV裁剪缩放、Format转换JpegNodeYUVJPEG编码成jpg预监场景里最耗时的往往是P2ANode因为它干了图像处理的大头。插值、缩放、色彩空间转换都在这里做算法本身复杂度又高。如果你发现预览帧率上不去用perfetto抓一帧各个node的耗时基本都能看到P2ANode占了一多半。优化手段一般是降低P2A的处理分辨率先降到一个中间尺寸再由P2CRZ缩到目标尺寸以时间换空间。4.2 Buffer如何从CameraService到ISPCamera7的Buffer管理也是新手比较容易懵的地方。每一帧处理的过程中你会看到很多buffer fd在流转它们到底是谁分配、谁释放、谁消费理清楚不容易。简单说三类bufferStream bufferApp和HAL共享的比如预览用的YUV buffer来自GrallocInternal bufferHAL内部各个Node之间流转的RAW或YUV buffer来自ION/DMA-BUF heapSensor bufferP1Node从Sensor收RAW数据时用的buffer也是来自ION启动和预览时HAL会先向BufferPoolManager申请一批内部buffer做备用这就是为什么你在/d/ion里能看到一堆camera标签的大块内存。如果某个Node没有可用buffer就会陷入阻塞等待整个Pipeline的帧率立刻塌陷。所以“帧率掉到10fps”这类问题很多时候不是ISP性能不够是某个buffer pool的深度配少了。项目里我踩过一个大坑硬件没改代码也没改只是把预览分辨率从720p换成了1080p预览就变得卡顿且时不时黑屏。查下来发现是某个内部buffer的长度还是按720p一个帧的size去申请1080p的RAW帧放不下导致DMA-BUF出现截断图像花掉Node间buffer传递超时。4.3 版本差异不同平台的启动差异和热点场景MTK的Camera7虽然框架统一但在不同芯片平台上的表现细节还是不少差异的。MT6768这类中端平台的ISP算力有限P2ANode的处理压力更大到MT6983这类旗舰平台多了一个独立的硬件加速器来做前期图像处理P1Node的负担会小很多Raw域的额外处理也更多。另外你们看到的MTK相机插值、Camera7这些热词实际上都和P2A节点的处理逻辑相关。很多项目的变焦或高像素模式就是靠P2A在RAW域做插值和超高分辨率重建不是让Sensor真正输出那么大的物理分辨率。这样理解之后再去看那些“1亿像素模式”的实现逻辑就有头绪了。还有一类常见定制是“外接USB UVC摄像头”。这个走的是标准的V4L2 UVC驱动和Camera7的关系不大但如果你们做了融合比如把UVC摄像头作为一个虚拟的CameraId暴露给App那么就是在HAL层做了一层转换不是MTK默认行为这块不建议乱改投入产出不成正比。5. 常见启动故障与排查实录5.1 第一步log从哪看调试启动问题最关键的资源就是log。MTK平台启动相关的日志分散在好几个地方logcatHAL层、CameraService、App层的日志主要看Cam3HAL、Cam3Provider、CameraService这些tagdmesg内核态的日志主要是kd_imgsensor、camsys、mtk_md等tagmtklogMTK整套log工具会同时抓取mobile log、kernel log、network log等信息不足时还可以手动开底层log# 抓全部camera相关log adb shell logcat -s Cam3HAL:V Cam3Provider:V CameraService:V # 看内核态sensor和camsys的log adb shell dmesg | grep -E kd_imgsensor|camsys|sensor_id # 检查video节点与实体对应关系 adb shell ls -l /dev/video* adb shell cat /sys/class/video4linux/video0/name这套组合拳能解决90%的问题先看HAL有没有正常建Pipeline再看内核有没有出RAW帧。哪个环节没有日志问题就定位到哪个环节。5.2 常见错误码与含义错误码含义常见场景-19 (ENODEV)设备不存在Sensor probe失败或cameraId映射错误-22 (EINVAL)参数无效Stream配置不支持或format不匹配-110 (ETIMEDOUT)操作超时等待RAW帧超时多半是sensor没出数据-16 (EBUSY)资源占用摄像头被占用或buffer pool繁忙-11 (EAGAIN)资源暂时不可用预览buffer queue空frame rate受限举个例子开机后默认相机打开慢不止还会偶发黑屏抓log看到-110 Timeout基本上问题就锁定在底层没有出RAW帧。下一步就该查Sensor的power-on时序、MCLK是否正常再拿示波器量sensor的MIPI lane有没有数据。整个链条就顺下来了。5.3 三个我印象最深的实战案例案例一开机后第一次启动Camera总是黑屏第二次就能正常预览。一开始是往power-on时序和pipeline配置方向查的查了一圈没结论。后来回看dmesg发现每次第一次open camera之前kd_imgsensor的probe函数都没有被调用过MCLK也没有打开。再往下查是系统的电源管理策略把camera的clock gating打开得太早导致第一次power-on时clock不稳定sensor没有完成初始化。最后在HAL层openCamera之前无条件先做一次clock的操作问题解决。教训是黑屏不见得是软件流程问题很可能是电源clock时序和硬件初始化互相打架。案例二换了新一批sensor模组后打开相机提示“相机尚未连接”。一开始以为是新模组ID不对让硬件去查ID也对。后来对比新旧模组的datasheet发现新模组的MCLK要求是24MHz旧的是13MHz而设备树里仍然配的是13M。sensor能够probe成功是因为它的内部ID检测不依赖MCLK但真正跑起来之后时钟不够就会出错。改一个频率配置问题就消失了。所以换模组的时候一定把sensor型号、时钟、电源都核对一遍不要偷懒。案例三多摄项目前置摄像头有时打开慢有时直接失败。这个项目的多摄设备映射表是手工维护的后摄模组偶尔探测不到导致ID映射整个乱掉前摄被映射到了后摄的HAL设备上初始化当然失败。解决办法是在sensor_init之后做一个校验保证每个设备ID对应的sensor在线状态是可预期的同时把映射表的公共部分移到动态配置里而不是写死。这种问题项目初期看不出摄像头增多之后就是大隐患。6. 想加速启动这几个方向值得动手6.1 会话复用与预创建Camera启动“慢”一直是MTK平台的痛点但很多慢是可以优化的不要一上来就抱怨芯片性能。最常见的手段就是会话复用。默认情况下每次open camera都会新建MtkCameraDevice和pipeline即使你打开的是同一个摄像头。这个创建过程非常耗时因为要加载3A算法、解析tuning参数、注册buffer pool。如果上层应用频繁在前后摄间切换这个开销会被无限放大。MTK在Camera7架构里其实预留了会话复用的接口可以做到摄像头切换时不销毁整个会话只切换sensor配置。但默认实现为了兼容性很多时候还是会走完整流程。如果你对动框架有信心可以尝试把configureStreams里的pipeline复用逻辑打通效果立竿见影。6.2 用关键log时间戳定位启动瓶颈我在优化启动速度时喜欢用一段固定的log标注时间点adb logcat -v time -s Cam3HAL:I | grep openCamera\|configureStreams\|createPipeline\|processCaptureRequest\|firstFrame手机上操作一遍“启动相机”然后统计每个阶段的时间差。正常来说从openCamera到createPipeline结束应该控制在200ms以内从creatPipeline到第一帧request提交控制在50ms以内从request到第一帧预览看当时曝光情况一般100ms上下。如果某个阶段远超这个数那就是优化重点。这个方法和“看整条链路的瓶颈”是两回事启动优化是看阶段耗时运行优化是看每帧耗时不要混淆。6.3 调试工具和命令速查除了常规的logcat、dmesgMTK还有一些工程上常用的调试入口# 查看当前camera pipeline状态部分平台支持 adb shell cat /d/mtk_cam/pipeline # 查看camera的buffer使用情况 adb shell cat /d/ion/heaps/ion_camera # 强制使用test pattern不需要sensor也能出图 # 这个需要HAL层支持读取vendor属性 adb shell setprop vendor.mtk.camera.testpattern 1另外adb shell dmesg | grep camsys是查底层大问题的神兵利器。很多你以为的“HAL bug”最后都是底层驱动没有正确响应。如果把dmesg里camsys、imgsensor、mtk_md这几类log养成习惯排查问题的速度能快一倍。最后再分享一个小技巧如果你们项目里在调试“点击相机图标卡顿”这类问题不要只盯HAL层先打开一个纯黑色的预览画面看帧率看第一帧出来要多久。这个测试能帮你区分卡是出现在Pipeline处理上还是出在底层Sensor曝光和ISP链路至少能少走一半弯路。MTK Camera7虽然前期学习成本不低但整个框架的清晰度还是值得花时间吃透的。后续有机会我还可以再单独讲讲AE收敛和AF调试这两块都是实际项目里最磨人的部分。
返回列表