ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

高通Camx相机调试实战:UMD/KMD日志与Dump图像定位技巧

高通Camx相机调试实战:UMD/KMD日志与Dump图像定位技巧 做高通Camera调试的工程师或多或少都被Camx这套架构折磨过。sensor上电正常但preview全灰AF来回拉风箱或者一按快门直接重启面对手里那一堆UMD/KMD Log和dump出来的图像想快速定位却不知道从哪下手。这篇文章不写纸面理论把我在Camx架构下开UMD/KMD Log、Dump图像、以及用离线脚本合成完整时间线的整套方法拿出来从原理到命令再到脚本一次性讲清楚。先说清楚一件事UMDUser Mode Driver指用户态的Camx HAL和CHI层KMDKernel Mode Driver指内核态从CSID、CSIPHY到Sensor、ISP的驱动。问题发生在用户态还是内核态排查路径完全不同Log开关、日志格式、时间戳体系也都不一样。Dump图像则是拿管线中间节点的buffer把RAW/YUV原样存下来用于确认数据在哪一环出了问题。这篇文章适合做高通平台Camera bringup、稳定性分析、或者想自己上手定位出图问题的工程师也适合刚接手Camx的新人作为一份可查的工具手册。我尽量把每一步都拆到位包括配置文件里的参数含义、命令行选项、以及为什么这样做的理由。很多细节是我在项目现场踩坑踩出来的文档里未必写这么直白。1. 高通Camx架构与Camera调试的核心思路任何调试技巧都离不开对架构的基本理解。Camx这套东西看上去体积庞大但掌握了它的分层结构调试时就能按图索骥快速把问题缩小到具体模块。1.1 Camx架构全景UMD、Core与KMD怎么协同高通的Camera软件栈从上到下大致分三层最上层是用户态的UMD也就是通常说的Camx HAL加上CHICamera Hardware Interface中间是高通自研的CSLCamera Service Layer负责用户态与内核态的通信再往下是KMD包含camera内核驱动比如sensor、eeprom、actuator、flash、CSID、CSIPHY、ISP等。UMD这一层实际上是两个部分的组合。CHI主要负责usecase层和feature逻辑比如拍照、录像、HDR、夜景这些功能怎么编排Camx core则负责更底层的pipeline和node调度比如buffer怎么流转、node之间怎么连接。很多人习惯把CHI和Camx混叫但调试时最好分清楚——feature行为不对先看CHIbuffer或者node执行异常重点查Camx core。KMD这层比较纯粹就是一个个内核驱动挂在v4l2 subdev框架上。sensor驱动负责上电、配寄存器、Stream onCSID和CSIPHY负责MIPI物理层的数据接收ISP驱动负责把RAW数据做处理。KMD的日志通过printk输出到dmesg和用户态的logcat是两套时间体系这也是后面需要脚本合成的原因。Camx里面经常打交道的两个配置文件分别是camxsettings.xml和camxoverridesettings.txt。前者是编译进系统的默认配置后者是运行时覆盖的文本文件通常放在/vendor/etc/camera/下面。想临时改参数时优先看后者因为它不用改代码、不用重编系统改完重启cameraserver就能生效非常适合现场调试。1.2 为什么“LogDump”是Camera调试的基本功很多刚接触Camera的工程师有个误区觉得出问题先翻loglog不够再想办法。但真正高效的调试方式是“Log定位流程、Dump定位数据”双管齐下。Log回答的是“代码走到哪了、有没有报错”Dump回答的是“图像数据在哪一步变了形、变成了什么样”。举个例子预览画面整体发绿。先看log确认sensor输出是否正常、ISP的AWB有没有生效、CHI的feature有没有跑挂。如果log正常那就要dump各node的输入输出。把sensor出来的RAW dump一份看RAW本身是不是偏绿再把后处理输出的YUV dump一份看颜色是不是在后端被转坏。两步一对比问题落在哪一层就非常清楚了。Dump图像还有一个大价值就是能把“偶现问题”变成“可回溯现场”。很多时候sensor丢帧或者ISP超时是偶发的log里只有一串超时错误码根本看不出图像内容。但如果有dump的buffer文件就能复现当时的图像状态判断是不是异常帧参与了处理这对稳定性分析和客诉问题非常有用。所以Log和Dump不是二选一而是两条腿走路。下面我就按这个思路把UMD/KMD Log怎么开、Dump怎么配、时间线怎么合成一个一个讲透。2. 手把手开启UMD/KMD Log命令、参数与原理Log开不对等于白抓。Camx的Log系统设计得比较灵活可以通过配置文件、系统属性、dynamic debug三种方式打开每一层还有自己的开关。你需要知道在什么场景用哪种方式而不是一上来就把所有log全量打开。2.1 UMD层日志配置Camx HAL Log的开关与级别开UMD日志最直接的方式是修改/vendor/etc/camera/camxoverridesettings.txt加入LogLevel3 MultiCameraLogLevel3LogLevel的取值范围和大部分Log体系一样0表示error1表示warning2表示info3表示verbose/debug。日常调试建议用2信息量适中如果问题出在很深的node内部再上3。开3的时候Camx的日志输出会非常夸张一张照片能打出几十MB的log抓log前一定预留存储空间并且logcat要用threadtime格式输出否则后面离线合成脚本完全没法用。如果你不想动系统文件也可以运行时设置属性。Camx支持通过persist.vendor.camera.logs来动态控制常见操作是adb root adb shell setprop persist.vendor.camera.logs 3 adb shell stop adb shell start执行stop/start会重启cameraserver新配置才会真正加载。有些平台可能还需要设置persist.vendor.camera.sensor.logs3sensor驱动层上报的详细日志才会打出来这个属性在sensor bringup阶段尤其有用。还有一个细节容易被忽略Camx的log tag比较多最常用的有CamX、Camx、CHIUSECASE、CHIOVERRIDE、CAM_REQ_MGR等。抓log时不加过滤直接把所有tag都抓到文件里抓完之后再用grep过滤也可以。但如果在现场想实时看log可以只过滤这几个tag输出会清爽很多adb logcat -v threadtime -s CamX:V Camx:V CHIUSECASE:V CHIOVERRIDE:V注意CamX和Camx的写法人很容易看混一个是大写的X一个是小写的x实际项目中两个tag共存过滤时最好都带上漏掉任何一个都可能错过关键信息。2.2 KMD层Log开启从dmesg到Dynamic DebugKMD日志的开法相对原始本质就是让内核打印更多Camera相关调试信息。最常用的是dynamic debug直接打开对应文件的调试输出adb root adb shell mkdir /sys/kernel/debug 2/dev/null adb shell mount -t debugfs none /sys/kernel/debug adb shell echo -n file msm_camera_isp*.c p /sys/kernel/debug/dynamic_debug/control这条命令会把msm_camera_isp相关c文件里的dev_dbg/pr_debug全部打开。实际项目里我常用的还有几条file msm_camera_sensor*.c p file msm_camera_csid*.c p file msm_camera_csiphy*.c p file cam_sensor*.c p如果通配符不好使就换成具体文件路径多试几次。注意打开dynamic debug后log量会急剧上升尤其是ISP的调试日志会带来明显的性能影响甚至导致preview掉帧。所以建议只打开你怀疑的那个模块不要一上来全开。除了dynamic debug也可以改内核defconfig把MSM_CAMERA相关debug选项打开。这种方式需要重新编译内核适合长期排查不适合现场快速定位。第三种是很多平台自带的camera debugfs节点通常在/sys/kernel/debug/camera/下面不同平台差异很大有的支持直接读寄存器、有的支持dump buffer。遇到问题时多ls一下目录有惊喜的概率很高。抓KMD日志一般用dmesgadb shell dmesg dmesg_kmd.txtdmesg默认显示的[ 123.456789]是内核uptime也就是开机到现在的秒数。如果问题发生在开机很久之后这个时间会很大。抓log的时候最好同时把uptime记录下来否则后期的离线脚本不好对时间轴。2.3 抓取完整Log的实操细节与时间基准问题Log开好之后抓取命令看起来简单但有几个细节必须提醒。logcat一定要加threadtime最好这样抓adb logcat -v threadtime -b all logcat_umd.txt adb shell dmesg dmesg_kmd.txt选-b all是为了同时拿到main/system/events三个buffer很多关键错误比如EventLog里的camera service异常只会出现在events里。dmesg最好不要用dmesg -c清除保留原始格式方便离线脚本按时间戳解析。抓完之后logcat和dmesg的时间基准不一样。logcat用的是墙钟时间比如08-15 10:20:30.123dmesg用的是内核uptime比如[ 123.456789]。两者相差一个开机时刻的墙钟偏移。这个偏移可以通过/proc/uptime和date %s算出来具体公式是内核运行秒数 当前墙钟秒数 - 开机墙钟秒数把每条dmesg日志的uptime换算成和logcat一致的相对运行秒数就能把两条日志按同一条时间轴排序。这个换算过程手工做很费劲所以我在第四部分专门写了一个Python脚本一步到位。3. Dump图像实操拿到能定位问题的RAW/YUVLog能告诉你流程走到哪、错误码是什么但图像本身出了问题光看Log是不够的。这时候就需要Dump图像把管线里关键节点的buffer原样保存下来。Dump能力Camx是原生支持的只是很多人不知道配置项在哪或者配了没生效。3.1 太抽象的“Dump图像”到底是什么所谓Dump图像简单说就是从Camx的pipeline中间把某个node输入或输出的buffer原样保存到文件里。Camx的pipeline里跑的是一个个node比如BPS、IPE、SWNR这些每个node吃进RAW或者YUV处理完输出新的buffer。如果最终出图是绿的可能是sensor原始RAW就绿也可能是ISP的AWB没生效还可能是后处理转格式时RGB通道搞乱了。通过在关键节点上dump数据拿到RAW或者YUV自己用工具看就能直接判断问题在哪一环。这个过程可以理解为在工厂流水线中间装了几个摄像头老板只说成品坏了但你不知道是原材料问题还是哪道工序出了问题。那就每道工序都留个影一对比问题环节立刻浮出水面。3.2 Camx UMD侧Dump配置常用方法在Camx UMD侧Dump图像最常用的方法还是改camxoverridesettings.txtDumpData1 DumpDataPath/data/vendor/camera/有些平台还需要配合DumpCount限定dump帧数避免一次全量dump把存储撑爆DumpCount1设置完记得重启cameraserver触发一次拍照或预览再去/data/vendor/camera/目录下面找文件。文件命名通常比较明确会包含pipeline名称、node名称和帧号。如果文件没生成先看目录权限和selinux很多case是写入权限不够直接chmod 777 /data/vendor/camera/有时候能让问题快速落地。如果你在做代码开发还可以用CamxDebugData或DebugData类主动触发dump。这类接口的好处是能指定dump哪个node、哪一帧、哪个端口适合回归测试。不过这些接口在不同版本之间差异较大不建议新人直接上先从配置文件入手最稳。关于RAW的格式Camx的dump文件有些直接保存的是芯片内部原始buffer也就是带padding、带meta的行数据直接解析会得到一堆花屏。这个时候还需要配合dump出来的meta信息或者用高通提供的rawviewer工具按正确的stride和bayer pattern去解析。常见bayer pattern有BGGR、RGGB、GRBG、GBRG如果能看到dump文件里的getLineOffsets字段对排错帮助非常大。3.3 从KMD侧抓取Sensor RAW/ISP中间数据的补充思路KMD层面的图像dump通常是在驱动里加临时逻辑或者在Titan ISP的debug单元里支持抓取ISP中间结果。这事不是每块板子都有现成接口需要看平台具体情况。如果KMD侧改了代码dump一定要记录输出文件里的stride和format信息。我自己踩过一个大坑有一次dump了RAW10结果直接用16bit解析所有颜色都是花的后来才发现是MIPI RAW10的packing方式没对齐。这也是为什么我一直强调在KMD侧dump图像一定先确认格式否则分析方向全错。另外提一句sensor把RAW通过MIPI传给CSIDCSID再输出到ISP。如果怀疑sensor本身输出异常可以从CSID的寄存器配置里读到当前的data type和word count跟预期值对比能快速判断MIPI链路是否正常。这不算严格意义的dump图像但在图像异常时是很有用的辅助手段。4. 离线Log合成脚本设计与实现一次典型的现场debug手上会有好几份材料logcat是用户态的UMD日志时间格式是08-15 10:20:30.123dmesg是内核日志时间格式是[ 123.456789]如果还开了trace那又是一个时间体系。手动翻页对时间眼睛很容易花所以写一个离线脚本把多份日志按同一条时间线合并是非常值得的投入。4.1 为什么不用现成工具而要自己写脚本高通的工具链里确实有一些辅助分析工具但大多数需要联网下载或者对应特定平台现场临时装并不方便。而且这些工具通常是黑盒想加自己的过滤条件、想按自己的方式展示都很困难。自己写脚本的好处是灵活性强可以随用随改还能沉淀成团队内部的调试工具。更重要的是现成工具解决不了“跨源时间线对齐”这个核心痛点。logcat、dmesg、还有trace文件各自的时间参考点不同工具不会帮你把这些来源揉成一条时间线。脚本虽然简单但它正好补上这个空缺能直接把CamX、CHIUSECASE的日志与KMD的日志按时间顺序排在一起定位效率提升几个量级。4.2 脚本核心原理时间戳统一换算脚本的核心原理其实就是第二节末尾提到的那条公式从/proc/uptime读到系统已经运行的秒数再用date %s拿到当前墙钟秒数两者一减就是开机时刻的墙钟秒数。dmesg每条日志的[ 123.456789]就是这个模块相对开机的运行秒数可以直接作为统一时间轴。logcat的threadtime时间戳需要先解析成墙钟秒数减去开机墙钟秒数得到相对运行秒数。两边都换算成相对秒数之后丢进同一个列表排序就完成合并了。这里有一个小坑需要提醒logcat的时间戳格式只有月-日没有年份。跨年调试时脚本可能会解析错误。稳妥的做法是在脚本里加一个--year参数或者抓log时手动记录一下当前日期。4.3 完整脚本代码与使用说明下面是我实际在用的合并脚本Python3直接运行依赖只有标准库不需要安装额外包。#!/usr/bin/env python3 # -*- coding: utf-8 -*- import re import sys import argparse import subprocess from datetime import datetime def get_boot_epoch(): 计算开机时刻的墙钟秒数 uptime_str subprocess.check_output( [awk, {print $1}, /proc/uptime] ).decode().strip() uptime float(uptime_str) now_epoch datetime.now().timestamp() return now_epoch - uptime, uptime def parse_logcat_time(ts_str): 解析 logcat 的 threadtime 时间戳: 08-15 10:20:30.123 m re.match( r(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{3}), ts_str ) if not m: return None month, day, hour, minute, second, ms m.groups() year datetime.now().year dt datetime( year, int(month), int(day), int(hour), int(minute), int(second), int(ms) * 1000 ) return dt.timestamp() def parse_dmesg_time(ts_str): 解析 dmesg 的内核时间戳: [ 123.456789] m re.match(r\[\s*(\d\.\d)\], ts_str) if not m: return None return float(m.group(1)) def main(): parser argparse.ArgumentParser( description合并 UMD Logcat 与 KMD dmesg 为统一时间线 ) parser.add_argument(--logcat, requiredTrue, helplogcat文件需带threadtime格式) parser.add_argument(--dmesg, requiredTrue, helpdmesg文件) parser.add_argument(--out, defaultmerged_log.txt, help合并输出文件) parser.add_argument(--year, typeint, defaultNone, helplogcat时间戳的年份默认取当前年份) args parser.parse_args() boot_epoch, _ get_boot_epoch() records [] with open(args.logcat, errorsignore) as f: for line in f: m re.match(r(\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}), line) if not m: continue ts parse_logcat_time(m.group(1)) if ts is None: continue rel ts - boot_epoch records.append((rel, UMD, line.rstrip())) with open(args.dmesg, errorsignore) as f: for line in f: m re.match(r\[\s*(\d\.\d)\], line) if not m: continue rel parse_dmesg_time(m.group(1)) if rel is None: continue records.append((rel, KMD, line.rstrip())) records.sort(keylambda x: x[0]) with open(args.out, w) as out: for rel, src, line in records: out.write(f[{rel:10.3f}] [{src}] {line}\n) print(fmerged {len(records)} lines - {args.out}) if __name__ __main__: main()使用方式如下python3 merge_log.py --logcat logcat_umd.txt --dmesg dmesg_kmd.txt --out merged.txt脚本会读取两条日志按时间排序后输出到merged.txt。每一行前面带统一的时间戳和来源标记UMD表示logcatKMD表示dmesg。4.4 脚本实际效果与扩展空间合并后的文件有两种典型用法。一是按时间轴顺序完整读一遍看事件发生的整体次序应用发起open camera后CHI在哪个时间点下发sensor配置KMD在哪个时间点完成stream-on又是哪个时间点出现error。这种全局视角对排查多模块交互问题特别有用。二是配合grep精确过滤。比如只看错误grep -E ERR|ERROR|FAILED merged.txt只看某条usecase链路grep -E CHIUSECASE|CAM_REQ_MGR merged.txt脚本的扩展空间也很大。我自己的版本里加了--filter参数可以按关键词筛出关心的tag还支持从CSV里导入trace时间点做三方时间轴对比。如果你有更细的需求比如统计相邻两次ERR之间的耗时、分析某帧从入队到出队的延迟都可以在这个基础上改成本很低。5. 常见问题与排查技巧实录配置写了不少实际动手的时候还会遇到各种奇怪现象。这里挑几个出现频率最高的问题给出我自己的排查思路和解决办法。5.1 开了Log但输出文件为空问题出在哪很多新人第一次开Log都会发现配置写得很顺利但logcat或者dmesg里干干净净。这种情况首查selinux。Camx的配置文件路径通常被file_contexts保护文件内容改了但avc权限没放开进程根本读不到这个文件。用adb shell dmesg | grep avc看到denied信息再决定要不要临时permissive或者同步修改file_contexts。其次是检查cameraserver有没有重启。Camx的很多配置是进程启动时一次性加载的只改了文件不重启等于没改。重启的方式前面讲过用stop/start组合或者直接杀进程让init拉起来adb shell pkill -f cameraserver再有就是属性名写错。Camx不同版本支持的prop名称有差异比如有的平台用persist.vendor.camera.logs有的平台改成了persist.vendor.camera.debug。配置不生效时先在源码里搜索一下实际的属性名确认没有拼写错误。5.2 Dump出来的图像花屏、全黑怎么快速定位花屏大概率是格式问题。RAW10/RAW12有MIPI packing在用户态dump出来的buffer如果按16bit去展开读出来的字节流会交错画面自然就是花的。遇到这种情况先确认dump文件的位深和packing方式再重新解析。全黑需要先检查曝光参数。如果用了极短曝光或者sensor没有正确出图dump出来全黑是很正常的不代表后处理有问题。然后看dump的是哪个node的输入如果是BPS输入但sensor没有输出那问题大概率在前级属于sensor配置或者MIPI链路异常。Bayer pattern不对会导致整体偏色而且是一种很有规律的马赛克状色彩。BGGR当成RGGB来解每个2x2块颜色都会交换画面会有明显的彩色网格。这时候调整一下解析工具的bayer order就行不用怀疑硬件坏了。5.3 时间戳对不齐合并不了怎么办前面强调过logcat默认用墙钟时间dmesg默认用内核uptime两个体系之间只差一个开机墙钟时刻换算本身不难。但有一个容易忽略的点时区。板子时区如果被改过date %s拿到的epoch本身不受时区影响但解析logcat字符串时如果datetime模块用了本地时区就会产生偏移。稳妥的做法是抓log时顺手记录一下date %s和/proc/uptime的对应关系脚本在解析时强制使用UTC或者固定时区。另外如果板子运行了很久dmesg里的uptime值很大排序时注意浮点精度统一用秒为单位的小数排序结果就不会乱。5.4 现场调试的几条避坑心得能复现的问题优先复现Log要一步步加不要一次开全量log。很多Camera问题有时序敏感性Log量过大会改变时序反而把问题掩盖掉。我见过不止一次全量Log打开后问题不再出现关掉Log后又复现结果浪费了半天时间在调Log量上。不要同时把Log和Dump全部打开。Dump的buffer文件很大存储压力高还可能在写文件的时候打断实时处理导致额外的问题。我的习惯是先抓Log分析确实需要图像证据后再开一轮Dump两者错开互不干扰。sensor bringup阶段建议先把i2c log打开。很多上电异常在Log里就是一条NACK一眼就能看到。如果没有i2c log光靠猜排错效率会非常低。dmesg在死机场景下会丢Log。如果机器直接hang住或者重启dmesg内容很可能不完整。这时候要么接串口抓kernel log要么在sysrq触发后去pstore里翻ramoops。依赖adb shell dmesg抓死机现场的log往往什么也抓不到。6. 写在最后的实际经验我自己的习惯是新板子到手先写一个alias一条命令把logcat、dmesg、uptime、date全部抓下来再开Camx的LogLevel3跑一轮必现的用例。分析时直接用脚本合并先看有没有ERR/ERROR再看关键usecase的打开、关闭流程最后有需要才谈Dump。这套流程帮我扛过了好几个项目的bringup和客诉问题。最后再分享一个小技巧合并脚本里的关键词过滤建议加上“CHIUSECASE”和“StreamOn”。这两个tag能把一次完整的Stream On/Off过程串起来很多异常在Stream On阶段就已经暴露了只是Log量太大没人注意到。把这两条链路抽出来看定位速度会快很多。
返回列表