第1章:日志系统入门:logcat使用技巧、HAL层日志添加方法、关键日志关键字解读
做Camera HAL开发,说白了就是跟日志打交道。你想想看,相机出问题了,黑屏、闪退、卡顿,你第一反应是什么?肯定是翻日志。日志系统就是我们调试的「眼睛」,看不透它,你就像在黑暗中摸索。
这一章,我带你把这双「眼睛」擦亮。我会从最基础的logcat讲起,再到HAL层怎么加日志,最后聊聊那些你必须要认识的关键关键字。嗯,都是实战中摸爬滚打出来的经验。
1.1 logcat使用技巧:别只会adb logcat
很多新手上来就是一句adb logcat,然后看着满屏滚动的信息发呆。我个人习惯是,先想清楚我要看什么,再动手过滤。
1.1.1 基本过滤:按TAG和优先级
Camera HAL的日志,TAG通常以CamX、CHI、CameraHAL开头。你可以这样过滤:
# 只看Camera HAL相关的日志
adb logcat -s CamX
# 同时看多个TAG
adb logcat -s CamX CHI CameraHAL
# 只看WARNING级别以上的
adb logcat -s CamX:* -v brief | grep -E "W|E|F"
我在项目中遇到过,有个同事死活找不到某个错误日志,后来发现是TAG拼写错了。嗯,这种低级错误其实挺常见的。
-v threadtime 可以显示线程ID和时间戳,对分析多线程问题特别有用。
1.1.2 高级过滤:按进程ID和关键字
有时候日志太多,TAG过滤还不够。你可以用进程ID(PID)来锁定目标:
# 先找到camera服务的PID
adb shell ps | grep camera
# 然后只看这个PID的日志
adb logcat --pid=1234
或者直接按关键字搜:
# 搜索包含"error"或"failed"的日志
adb logcat | grep -iE "error|failed|timeout"
为什么会这样?因为Camera HAL的日志量非常大,一秒钟可能刷出几百行。你不过滤,根本找不到重点。
1.1.3 日志保存与对比
我建议你把日志保存下来,方便对比分析:
# 保存到文件
adb logcat -d -f /data/local/tmp/cam_log.txt
# 或者重定向到本地
adb logcat -v threadtime > cam_log_$(date +%Y%m%d_%H%M%S).txt
有一次,我对比了正常和异常两台设备的日志,发现差异就在一个buffer handle的申请顺序上。这种问题,光靠肉眼盯着屏幕看,根本发现不了。
1.2 HAL层日志添加方法:让代码会说话
HAL层代码跑在C++环境里,日志工具是ALOG系列宏。你写日志的时候,要想着「后来的人能不能看懂」。
1.2.1 基础日志宏
常用的就这几个:
| 宏 | 级别 | 使用场景 |
|---|---|---|
ALOGE |
ERROR | 致命错误,比如打开设备失败 |
ALOGW |
WARNING | 异常情况,但还能继续运行 |
ALOGI |
INFO | 关键流程,比如初始化完成 |
ALOGD |
DEBUG | 调试信息,比如参数值 |
ALOGV |
VERBOSE | 啰嗦模式,比如每一帧的细节 |
举个例子,我在HAL的openCamera函数里会这样加日志:
#include <log/log.h>
#define TAG "CamX-HAL"
int openCamera(int cameraId) {
ALOGI("%s: Opening camera %d", __FUNCTION__, cameraId);
// 检查设备是否存在
if (!deviceExists(cameraId)) {
ALOGE("%s: Camera %d not found!", __FUNCTION__, cameraId);
return -ENODEV;
}
// 初始化成功
ALOGI("%s: Camera %d opened successfully", __FUNCTION__, cameraId);
return 0;
}
ALOGV和ALOGD,否则日志量太大,会影响性能。我见过有人把DEBUG日志带到量产版本,结果手机发烫严重。
1.2.2 添加关键参数的日志
我个人习惯,在每次调用底层接口前后,都打印关键参数。比如:
// 调用底层前
ALOGD("%s: Before configureStreams, input width=%d, height=%d",
__FUNCTION__, width, height);
// 调用底层
int ret = mChiApp->configureStreams(streamConfig);
// 调用底层后
ALOGD("%s: After configureStreams, ret=%d", __FUNCTION__, ret);
这样一旦出问题,你就能知道是「调用前参数不对」还是「调用后返回错误」。
1.2.3 条件日志:只在需要时打印
有些日志只在特定条件下才有意义。比如:
// 只在第一次调用时打印
static bool firstTime = true;
if (firstTime) {
ALOGI("%s: First frame received", __FUNCTION__);
firstTime = false;
}
// 只在错误时打印
if (frameNumber % 30 == 0) {
ALOGD("%s: Frame %d processed", __FUNCTION__, frameNumber);
}
嗯,这里要注意,不要在每个循环里都打印日志,否则你的logcat会变成「刷屏机器」。
1.3 关键日志关键字解读:一眼看穿问题
日志看多了,你就会发现,有些关键字一出现,基本就知道问题出在哪了。我整理了一份「必知关键字清单」:
1.3.1 错误类关键字
- FATAL:致命错误,进程即将崩溃。看到这个,基本就是空指针或者断言失败。
- DEAD:相机服务挂了。通常是HAL层崩溃导致。
- Timeout:超时。比如等待帧数据超时,或者等待某个锁超时。
- Buffer + null:缓冲区为空。常见于内存分配失败。
- Failed to + open:打开设备失败。可能是权限问题,或者设备被占用。
实战案例: 我曾经遇到一个黑屏问题,日志里反复出现 Buffer handle is null。一开始我以为是内存不够,后来发现是某个vendor模块没有正确传递buffer。加了一行日志打印buffer的地址,问题立刻水落石出。
1.3.2 流程类关键字
- initialize:初始化流程。看这里能知道HAL是否正常启动。
- configureStreams:配置数据流。这里出问题,通常会导致预览黑屏。
- processCaptureRequest:处理拍照请求。这里出问题,照片可能拍不出来。
- flush:刷新流程。常见于切换相机或关闭相机时。
你想想看,如果configureStreams返回了错误,但你没有打印返回值,那排查起来得多费劲?所以,关键流程的返回值一定要打出来。
1.3.3 性能类关键字
- FPS:帧率。低于目标值,说明性能有问题。
- Latency:延迟。拍照延迟过高,用户体验会很差。
- Drop + frame:丢帧。常见于ISP处理不过来。
- Overflow:溢出。可能是buffer队列满了。
我记得有一次,客户反馈相机预览卡顿。我一看日志,Drop frame 每秒钟出现十几次。顺着这个线索,发现是某个sensor的帧率配置错了。
1.4 实战:一个完整的日志分析案例
假设你遇到了「打开相机黑屏」的问题。你会怎么做?
我的步骤是这样的:
- 抓日志:
adb logcat -v threadtime -s CamX CHI > bugreport.txt - 搜关键字:先搜
FATAL、ERROR、Failed - 看流程:找到
openCamera和configureStreams的日志 - 对比正常设备:看看正常设备的日志里,这些流程是怎么走的
我曾经用这个方法,五分钟就定位到一个问题:某个vendor模块在configureStreams时,传了一个错误的buffer count。日志里清清楚楚地写着:
E CamX-HAL: configureStreams failed, buffer count = 0
嗯,看到这个,你还需要猜吗?直接找vendor的人改代码就行了。
好了,这一章就到这里。日志系统入门,说白了就是三件事:会过滤、会添加、会解读。下一章,我会带你深入HAL层的初始化流程,看看相机从「关」到「开」到底经历了什么。
公众号:蓝海资料掘金营,微信deep3321