ARTICLE DETAIL

资讯详情

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

Android Camera Metadata 架构与实战:从框架到 HAL 的神经信号解析

Android Camera Metadata 架构与实战:从框架到 HAL 的神经信号解析 Camera 系列写到第五篇前面几篇一直在聊 buffer 链路、open 流程这些框架层面的事这次把 metadata 单独拎出来说清楚。如果你看过 logcat 里出现过 “prepare metadata”、“metadata not found”、“camera_metadata_get failed” 这类字样或者你在开发过程中发现预览黑屏、拍照卡住、3A 不工作但排查了半天都定位不到根因那问题大概率就出在 metadata 的构建、传递或者解析上。这篇文章我尽量把这个东西从架构讲到实战把我在 HAL 和 framework 两侧看到的问题一并拆开说。1. 先把它放在架构里看metadata 是 Camera 系统的“神经信号”1.1 开个门三类 metadata 分别解决什么问题很多刚开始接触相机开发的同学一听到 metadata 就先入为主把它理解成“一堆配置项”。这个理解方向没错但不够准确。在 Android Camera 体系里metadata 承载的远远不止“配置”它其实是 Camera 系统的神经信号贯穿了从 APP 下发拍摄意图、到 HAL 控制 sensor/ISP、再回到 APP 获取拍摄结果的全过程。在 Camera3 架构里metadata 通常分成三大类CameraCharacteristics静态元数据描述摄像头“天生是什么”。比如 sensor 方向、可用曝光时间范围、AF 支持哪些模式、像素大小等等。这部分在摄像头打开之前就可以读到并且整个生命周期内基本不变。CaptureRequest请求元数据描述 APP“想怎么拍”。比如曝光时间、ISO、AE/AF/AWB 模式、输出 crop 区域、3A 是否锁定等。每一帧拍摄APP 都要提交一个 requestHAL 严格按照这个 request 去控制硬件。CaptureResult结果元数据描述 HAL“实际拍成了什么样”。比如实际生效的曝光时间、sensor 时间戳、AF 状态、AE 状态、统计信息等。APP 通过 result 来了解这一帧到底发生了什么。这三类元数据在 Camera2 API 里对应CameraCharacteristics、CaptureRequest、CaptureResult三个 Java 类。很多上层同事在排查问题时习惯性只盯着ImageReader回调里的 Image 数据却忽略了 result 里携带的大量信息。我见过好多次HAL 已经通过 result 告诉 APP“AF 没锁定”但 APP 层代码还固执地认为当前处于对焦成功状态最后出来的照片全是糊的。1.2 从 Java 对象到二进制 blobmetadata 在框架里的三种存在形态metadata 在框架里有三种形态理解三种形态之间的转换是排查 metadata 问题的基础。最上层是 Java/Kotlin 看到的封装对象也就是android.hardware.camera2.CaptureRequest、CaptureResult这些类。它们内部持有的是一个CameraMetadataNative对象这个对象通过 JNI 和 native 层打通。第二层是 C 层的CameraMetadata类实现在frameworks/av/camera/CameraMetadata.cpp。它包装了底层纯粹的 C 结构体camera_metadata_t提供了一批更友好的增删改查接口。HAL 侧的 HAL3 接口里出现的const camera_metadata_t *settings就是这种二进制结构体。第三层是二进制序列化之后的样子。metadata 通过 Binder 从 APP 传到 framework再从 framework 传到 HAL靠的是把camera_metadata_t序列化成一段二进制 blob在 HIDL/AIDL 接口里传递。所以当你看到 binder 传输异常、或者 dump 出来的字节对不上时基本就得回到camera_metadata_t的内存布局去分析。这一段很多人会忽略但恰恰是各种“隔一层才能复现”的诡异问题的重灾区。比如 APP 设置了一个自定义 vendor tagFramework 层也能收到但到 HAL 层却发现 tag 不见了这时候十有八九是 vendor tag 在跨进程序列化时没有注册进 VendorTagDescriptor导致对端进程解析不了。1.3 先分清“哪个 metadata”几个同名术语别混在一起因为“metadata”这个词太通用我经常在群里看到有人把问题描述串了。比如数据库连接报错提示 “could not obtain connection to query metadata”Linux 下执行 yum 报出 “errors during downloading metadata for repo”以及某些 AI 推理框架提示 “model metadata not found, defaulting to fallback metadata”这些跟 Android Camera 的 metadata 完全不搭边。它们只是都借用了“元数据”这个通用概念表示“描述数据的数据”。你在搜索、讨论问题之前先确认自己面对的是 Camera 的camera_metadata_t是数据库的表结构元数据还是 Linux 软件源的仓库元数据。尤其是在社区提问时场景描述不清楚别人想帮你也无从下手。后面我讲的全部内容都限定在 Android camera 框架和 Camera3 HAL 的范畴内。2. 从 APP 按快门到 HAL 出帧metadata 完整流转链路2.1 CaptureRequest 从 Java 到 native 的一次旅行我把一次普通拍照请求的 metadata 流转拆开给你看。APP 层通常会这样构造请求CameraManager cameraManager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); CameraCharacteristics characteristics cameraManager.getCameraCharacteristics(cameraId); CameraDevice cameraDevice ...; CameraCaptureSession session ...; CaptureRequest.Builder builder cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE); builder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF); builder.set(CaptureRequest.SENSOR_EXPOSURE_TIME, 5000000L); // 5ms builder.set(CaptureRequest.SENSOR_SENSITIVITY, 200); builder.addTarget(imageReaderSurface); session.capture(builder.build(), callback, handler);这行代码走下去CaptureRequest里的字段会被收集到一个CameraMetadataNative对象里。到了 native 层它变成CameraMetadata再往下在CameraDeviceClient.submitRequest的流程中被打包最终通过 camera provider 的 HIDL/AIDL 接口递给 HAL 的processCaptureRequest。很多人不知道的是在processCaptureRequest里HAL 拿到的camera3_capture_request_t.settings指向的是一段连续内存。也就是说一个 request 的所有配置项全部被塞进同一个内存 buffer 里而不是靠几百个独立字段传递。这样做天然是为了性能因为每一帧的请求都要走一遍完整的序列化和反序列化如果按字段逐个传输Binder 事务会爆炸。这里有个容易踩的坑camera_metadata_t是带长度自描述的但跨进程传输时如果对方拿到的是一个截断的 blob解析时就会出现CAMERA_METADATA_INVALID_SIZE之类的错误。我曾在日志里看到过 HAL 进程报这种错误最终查到是 provider 在转发 metadata 时多包了一层封装长度字段和实际 buffer 不匹配。2.2 HAL 处理与返回process_capture_result 里的 result metadataHAL 收到 request 之后引擎会开始调度 sensor 和 ISP。等这一帧真正被硬件处理完HAL 会调用process_capture_result把这一帧的结果回传给 framework。camera3_capture_result_t里有一个result字段类型是const camera_metadata_t *。这里放的就是这一帧实际生效的参数和状态。注意“实际生效”这四个字非常重要。比如 APP 请求曝光时间是 10ms但 HAL 在 AE 自动模式下根本不理会这个请求值而是自己算出 15ms 来那么这一帧的 result 里就应该如实告诉 APP我的实际曝光时间是 15ms。而不是把你收到的 10ms 原样返回。framework 层的很多行为设计比如预览曝光条、人脸对焦框、3A 状态展示都依赖 result 里的真实值。如果你的 HAL 在 result 里随手填一些“理想值”而不是“实测值”上层就会出现各种“看起来没生效”的诡异现象。我在实际项目中就遇到过HAL 的 result 里一直返回ANDROID_CONTROL_AF_STATE 2即被动对焦已锁定但实际画面根本没对上焦。原因是 HAL 内部在某个分支里偷懒直接把 request 里的值复制回了 result而不是根据硬件状态去更新。结果 CTS 的对焦用例跑不过用户反馈拍照模糊最后在 HAL 代码里面对齐了状态机才解决。2.3 metadata 与图像 buffer 的协同为什么 buffer 管理会扯上 metadata很多做播放器、编解码器的同事会对“camera 多媒体 buffer 管理”这个说法很熟但大家容易忽略的是metadata 本身也是一种 buffer而且它和图像 buffer 的关系非常紧密。在 Camera3 的 pipeline 里每一帧的 output buffer也就是 YUV/RAW/JPEG 数据和这一帧的 metadata 是配套出现的。capture_result里既有output_buffers又有resultmetadata。上层可以通过captureResult.get(CaptureResult.SENSOR_TIMESTAMP)拿到时间戳然后跟Image.getTimestamp()对齐从而把 metadata 状态和图像帧关联起来。这块最容易出问题的点是 metadata buffer 的分配策略。底层 metadata 内存不是无限大的你需要预估 entry 数量和 data 总大小然后用calculate_camera_metadata_size计算需要的 buffer 大小。HAL 侧的 result 构建也是同样的道理如果某个分支往 result 里塞了大量 vendor tag或者统计信息超过预设容量就会出现 metadata 装不下的情况。我见过一个案例某厂商开启了多帧融合功能之后HAL 往 result 里填充的自定义统计信息暴增导致camera_metadata_t的 buffer 超限process_capture_result在 HAL 内部就提前返回了错误上层表现为拍照偶尔 ANR。这种问题如果不去看 metadata 大小和容量纯粹盯着图像 buffer 找是永远找不到根因的。3. 不知不觉就会出错的 Tag常见 metadata 配置与合法值3.1 Tag 的组织规律从 section 到 tag 编号android.*开头的 metadata 项在 native 层本质上是一个 32 位的整数 tag。这个整数的分布很规律高位用来表示 section比如 sensor、control、statistics、jpeg 等低位表示 section 内的索引。举个例子ANDROID_SENSOR_EXPOSURE_TIME在system/media/camera/include/system/camera_metadata_tags.h里是被定义好的常量。编译时这些常量会被转化成整数编码。我们平时开发不会直接跟原始整数值打交道但理解这个结构之后看到日志里出现的tag_idXXX就能快速对应到源码里的哪个定义。section 划分大致包括android.sensor、android.control、android.ae、android.af、android.awb、android.statistics、android.jpeg、android.tonemap、android.noiseReduction、android.edge、android.scaler、android.flash、android.lens、android.request等。每一类下面都有若干具体 tag。看 tag 的命名其实能发现很多规律以CONTROL_AE_*开头的都是和自动曝光相关以SENSOR_*开头的一般是 sensor 的曝光、增益、时间戳等信息。遇到不认识的 tag先查camera_metadata_tags.h和camera_metadata_tag_info.c不要凭感觉去猜类型猜错类型经常会把 uint8 的数组读成 int32得到一堆毫无意义的数字。3.2 最容易被滥用的几个控制类型AE、AF、AWB、tonemap控制类的 metadata 是最影响出图效果的也是各种“玄学问题”的重灾区。这里说几个高频字段。CONTROL_AE_MODE控制自动曝光模式。当你设置成CONTROL_AE_MODE_OFF时HAL 就不再自动调节曝光你必须同时给出SENSOR_EXPOSURE_TIME和SENSOR_SENSITIVITY。如果不给有些 HAL 实现会使用上一次请求的残留值有些则跳到默认值这会造成同一台机器在不同版本上表现不一致。CONTROL_AF_MODE和CONTROL_AF_TRIGGER经常被组合使用。尤其是做扫码类应用时大家喜欢把 AF 模式设置成CONTROL_AF_MODE_CONTINUOUS_PICTURE又需要手动触发对焦这时候要注意触发完一轮之后要TRIGGER_IDLE复位。很多第三方应用在这个状态机里做错导致对焦马达一直在反复拉风箱耗电还拍不清。TONEMAP_MODE如果被设置成TONEMAP_MODE_CONTRAST_CURVE你就必须同时把TONEMAP_CURVE_BLUE、TONEMAP_CURVE_GREEN、TONEMAP_CURVE_RED三个曲线的点都配置好。不少调试 HDR 的同事只配置了一个曲线最终出图颜色完全不对检查配置完才发现另外两个曲线其实没生效。我在项目里总结了一条经验任何控制类字段的改动不要只看当前帧效果的 log要去看 result 里回传的实际值。比如 AE 是否真的切到了 OFFsensor 时间戳是否真的用上了你设置的值。只有 request 和 result 能对上才能说明配置生效了。3.3 厂商扩展 tagvendor tag的正确打开方式标准 tag 覆盖了 Android 定义的所有通用能力但每家厂商都有自己的算法和硬件特性比如超级夜景、多帧降噪、特定 ISP 参数、自研 3A 策略等这些能力一般通过 vendor tag 暴露给上层。vendor tag 的 section 通常是com.xxx这种厂商命名空间。framework 在启动时会从 HAL 侧拿到 VendorTagList里面注册了所有 vendor tag 的名称、类型和 section。上层可以通过CameraCharacteristics去获取这些 tag 的 key也可以借助 Camera2 API 的扩展接口来读取。做 vendor tag 最容易出的问题有三个注册不完整HAL 侧定义了一堆 vendor tag但 provider 没有把它们的描述信息完整传给 framework导致上层解析时找不到对应 key或者类型对不上。类型不对比如你在 HAL 里把某个 vendor tag 定义成BYTE但 JNI 层却按INT32去读拿到的值自然是错的。生命周期问题有些 vendor tag 只在一段时间有效比如夜景模式只有在某个 scene mode 下才有意义如果上层没处理好在普通模式下设置这个 tag 可能被 HAL 默默忽略或者直接报错。在调试 vendor tag 时我建议先在 HAL 层写一个自测函数把 vendor tag 的原始值打印出来确认有效性再去查 framework 层是否透传正确这样能快速定位是“写入失败”还是“读取失败”。3.4 “model metadata not founddefaulting to fallback”这类 fallback 日志意味着什么很多时候你在 logcat 里会看到类似 “model metadata not found, defaulting to fallback metadata” 的日志。这里的 model 不一定是 AI 模型也可能指 HAL 内部加载的一组模板参数、标定参数或场景配置文件。当 HAL 期望某个 model 对应的 metadata 文件存在但实际找不到时通常会走 fallback 逻辑用一套内置的默认参数顶上去。这种机制本意是保证系统还能出图但后果是出图效果可能和产品调校差异巨大比如颜色偏淡、噪声明显、畸变校正失效。遇到这种日志你应该立刻去检查对应路径下的标定文件是否存在权限对不对该 model 对应的 metadata 版本和当前 HAL 固件版本是否匹配是不是升级后文件被清了或者路径被改了我遇到过一种情况某批整机在做产测的时候标定数据烧录环节超时导致 sensor 的标定文件没写全。开机后 HAL 找不到完整的标定 metadata只能 fallback用户拍照色彩一致性非常差。这类问题光靠改代码没法根治得回产线把标定数据重新烧录。4. 经典故障复盘“preparing metadata 卡住”到 metadata 相关稳定性问题4.1 复现一次“准备元数据卡住”“preparing metadata 卡住”这类现象在日志里的形态不是固定的。我遇到过的表现有这几种打开相机进入预览前UI 一直停在初始化转圈logcat 里反复打 “prepare metadata” 相关的行。camera provider 启动时卡住后续所有应用打不开相机。dumpsys media.camera能看到设备节点但openCamera没返回或者回调迟迟不来。有一次项目联调我在一台原型机上发现第一次打开相机很慢之后偶尔可以进预览但切到前摄再切回后摄直接黑屏。日志里没有 crash也没有明显的 error 级别异常只有一句反复出现的 metadata prepare 卡住。这种问题的本质通常是 HAL 在准备静态 metadataCameraCharacteristics时发生了阻塞。因为打开摄像头时 framework 需要先从 HAL 拿到能力参数才能向上层返回 characteristics如果 HAL 在构造 static metadata 时去读 sensor 寄存器和 eeprom 参数而这条 I2C 总线又被其他模块卡住了那么整体表现就是“preparing metadata 卡住”。4.2 用 dumpsys 和 logcat 逐层缩小范围排查这类问题我的顺序一般是这样第一步先看dumpsys media.camera。它会 dump 当前 CameraService 管理的 camera provider 状态能看到哪些摄像头已经注册、哪些还在初始化、当前是否有 request 在飞行中。如果 provider 显示为空或者只有部分摄像头说明问题出在更底层。第二步抓 HAL 侧的日志。重点看有没有 eeprom 读取失败、I2C 通信 timeout、sensor 驱动 probe 失败这类关键字。如果有说明卡在硬件访问上如果没有再去查 HAL 进程是不是在等某个锁。第三步看系统整体负载。top -H -p hal_pid看 HAL 进程里哪些线程在占用 CPU哪些线程长时间处于 D 状态或者 S 状态。卡住的线程在栈里一般能看到它阻塞在哪一次 ioctl 或 read 调用上。第四步如果前三步都没结论就要考虑是不是 framework 和 HAL 之间的接口返回值问题。比如 HAL 返回CAMERA3_STATUS_ERROR_METADATA_NOT_FOUND但 framework 对这种错误码的兼容处理没做好导致一直重试。4.3 metadata 大小估算与 buffer 溢出的坑还有一个坑不遇到一次很容易忽略metadata buffer 溢出。framework 和 HAL 交互时底层会用一块预设大小的内存来存放camera_metadata_t。构建 metadata 之前需要估算 entry 数量和 data 总大小如果实际填入的内容超过预估构建就会失败或者返回溢出错误。常见的溢出场景有HAL 针对 3A 统计信息开了很大的缓冲区往 result 里塞了大量统计图数据或者厂商在 vendor tag 里塞了 debug 用的图像特征点又或者某个字段的 string 格式被 JSON 化之后特别长。处理这类问题我推荐在构建 metadata 时做个保险给data_count多预留 20% 的空间然后每次构建完检查返回值确保不是CAMERA_METADATA_OVERFLOW。同时在 QA 环节做长稳压力测试专门验证长时间连续拍照下 metadata 内存会不会增长到溢出。4.4 由 VTS 暴露出来的 metadata 合规问题Metadata 的合规性最终是要提交到 VTS、CTS 去验证的。VTS 里关于 camera HAL3 的用例会覆盖静态 metadata 的完整性、request/result 的一致性、以及关键能力标记是否自相矛盾。举个例子VTS 会检查如果STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES里不包含OFF那么所有请求不允许关闭人脸检测。再比如如果CONTROL_AE_AVAILABLE_MODES里没有CONTROL_AE_MODE_OFFAPP 设置 AE OFF 就应该返回错误或者在结果里按无效参数处理。这类用例挂了往往不是一行代码的错误而是 HAL 实现里某个能力声明和实际行为不一致。我以前跑 VTS 时经常看到静态 metadata 里说支持 4K 60fps但实际上画面会掉帧说明 sensor 出图能力没那么强得调低声明。SENSOR_INFO_MAX_FRAME_DURATION和实际processCaptureRequest能承受的帧间隔不一致导致长时间跑会丢帧。跑 VTS 不是只为了过测它其实是一个很好的自我体检工具。VTS 报出的 metadata 问题很多是逻辑自相矛盾这类问题平常开发早就埋下了只是到了测试阶段才暴露。5. 日常开发里的 metadata 调试与自查建议5.1 拿到一个莫名其妙的结果先做这几步如果你遇到一个结果异常但又不知道它和 metadata 有没有关系我的经验是先做一次“最小复现 二分排查”第一步排除图像 buffer 问题。先不看画面只看 result 里的关键字段。比如SENSOR_TIMESTAMP是否连续同一 request 对应的 result 是否包含了你关心的所有 tag如果连关键 tag 都没有那问题就是 metadata 构建或传递的环节。第二步对比正常设备。同样的工程在正常机器上跑一遍dump 出 result 的完整内容和异常机器做 diff。很多问题不是某个字段错了而是某个字段缺失或者类型不对对比一看就能发现。第三步直接adb shell dumpsys media.camera抽框架层状态再进 HAL 打点。如果要定位到底是不是 HAL 解析错了最好的办法是在 HAL 的process_capture_result入口处把收到的 metadata 完整 dump 出来。5.2 一条实用的 ADB/代码自查清单我平时在项目里做 metadata 问题自查基本会走下面这些命令和代码路径adb shell dumpsys media.camera查看 CameraService 里摄像头和 request 的整体状态。adb shell dumpsys camera部分版本上也能看相关信息两者内容略有侧重。adb logcat -s CameraService CameraProviderV2 Camera3Device HAL跨层抓关键日志。native 层查某个 tag 是否存在的代码路径#include system/camera_metadata.h camera_metadata_entry_t entry; int ret camera_metadata_get(metadata, ANDROID_CONTROL_AF_MODE, entry); if (ret CAMERA_METADATA_OK) { ALOGI(AF mode %d, entry.data.u8[0]); } else { ALOGE(Failed to get AF mode, ret %d, ret); }Java 层读取静态能力CameraManager manager (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); CameraCharacteristics characteristics manager.getCameraCharacteristics(cameraId); int[] faceModes characteristics.get( CameraCharacteristics.STATISTICS_INFO_AVAILABLE_FACE_DETECT_MODES);需要提醒的是打印 tag 值的时候一定要注意类型。camera_metadata_get拿到的entry.data是一个联合体误用u8去读i64类型的数据读出来的值毫无意义反而会误导排查方向。5.3 几个我踩过的 metadata 怪坑最后分享几个我自己踩过、而且不太容易从文档里直接看到的怪坑。第一个result 里缺 tag。有些 HAL 实现为了节省拷贝只在 result 里放“有变化的字段”没有变化的字段就不放。这在框架规范里是允许的因为 framework 会做继承合并。但问题是如果你的上层逻辑用get(CaptureResult.XXX)得到 null 就说明这一帧没有携带这个字段而不是说这个值是 0。很多开发去做判空处理时经常漏掉这一点直接解引用出现 NPE。第二个vendor tag 的 key 名相同但定义在不同 section。两个不同厂商的 HAL 可能都用com.xxx.aaa定义了完全不同的含义。同一个设备如果有两套 provider比如外接 USB 摄像头和内置摄像头上层代码通过字符串去查找 vendor tag 时可能会拿到不是你想用的那个 tag。第三个metadata 的并发访问问题。CameraMetadata对象不是线程安全的。如果你在 HAL 的多个执行线程里同时对一个CameraMetadata做 add/update轻则数据错乱重则把camera_metadata_t的 header 写坏之后camera_metadata_get直接返回无效。处理这类并发建议要么给 metadata 加锁要么在每次回调时先复制一份再做修改。第四个时间戳的语义问题。SENSOR_TIMESTAMP在 result 里必须是 sensor 曝光开始的时间戳而不是 HAL 收到 request 的时间戳更不是系统当前时间。有些 HAL 开发为了省事直接拿了systemTime()填进去导致上层在做相册、录制同步时时间轴完全对不上。这类问题单看一帧数据很难发现连续打印几帧时间戳看差值是否符合实际帧间隔基本就能判断是不是时间戳语义错了。5.4 一些我现在还在坚持的习惯写到这里顺便说两个我现在还在坚持的习惯。第一个习惯是所有 HAL 侧的 metadata 修改不管多小都要在processCaptureRequest和processCaptureResult的入口出口把关键 tag 打点。不要觉得打点会影响性能其实关键 tag 就那么几个做成可开关的 log 就行。之前有两次线上问题都是靠这个出口日志快速定位到是 HAL 内部在中间环节把 tag 搞丢了。第二个习惯是做 metadata 的黄金备份。所谓“黄金备份”就是在设备处于刚校准完、一切正常的状态下把完整的CameraCharacteristics和每一类典型场景的CaptureResultdump 出来存档。之后一旦出现升级回归、校准丢失、效果偏差拿当前值和黄金备份做 diff很快就能看出差别在哪个字段。这个习惯救了我很多次强烈推荐。Camera metadata 这个主题其实还能往下挖很多比如 3A 状态机的默认值转换、scaler 模式对 crop region 的钳制、sensor 像素 binning 对 metadata 的影响这些都是实战里绕不开的部分。这个系列如果后面有需要我可以再单独展开。
返回列表