news 2026/10/27 5:16:20

010、Camera HAL 开发环境搭建与代码导读

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
010、Camera HAL 开发环境搭建与代码导读

010、Camera HAL 开发环境搭建与代码导读

刚接手一个项目,Sensor 是 OV5640,平台是某国产 SoC。明明照着 vendor 给的手册搭环境,编译完刷进去,adb shell ls /vendor/lib/hw/里能看到camera.vendor.so,但用 Camera2 打开直接黑屏,logcat 里一句 “DeviceNotConfigured” 反复刷屏。后来折腾了两天,发现问题是编译时没把 vendor 依赖的私有库libcam_common.so一起拷到系统镜像里,运行时dlopen失败,HAL 模块加载不出来。这个坑让我意识到,开发环境搭建和代码导读不能跳过。

环境搭建先搞定三件事:编译工具链、AOSP 源码树、vendor 的私有库

说真的,Camera HAL 开发最烦的不是写代码,是环境。你以为把source build/envsetup.sh跑一遍就万事大吉了?太年轻。HAL 最底层跟 Linux Kernel 的 V4L2 打交道,上层要对接 Android 的 camera2 框架,中间还夹着一堆 vendor 自己封装的基础库。你手上的 SoC 厂商通常会给一个完整的 SDK 包,里面有预编译好的交叉编译器、AOSP 对应版本的源码补丁、还有一堆名字怪异的.so。第一步不是急着去看 HAL 代码,而是把我们需要的编译环境拉通。

拿我这边举例,Android 版本是 12,SoC vendor 要求用他提供的 NDK r23 和专用的 GCC 工具链,不能用 AOSP 默认的 clang。这个必须在build/envsetup.sh之后强制改CC和CXX环境变量。怎么改?在device/vendor/xxx/BoardConfig.mk里加:

TARGET_CC := vendor_prebuilt_cc TARGET_CXX := vendor_prebuilt_cxx

别以为这行没用,踩过坑的人都知道,缺了这步,编出来的.so加载到真机上会直接SIGILL,因为 SoC 里有个自定义的乘加指令,发行版的 clang 不会生成。

还有编译命令。别用make systemimage这种整包编译,等你编完一次,半天过去了,然后告诉你某个头文件找不到。你会发现你改的 HAL 代码根本没触发重新编译。正确姿势是进入源码根目录,先lunch你的 product,然后m指定模块名,或者mm在模块路径下编译。我个人的习惯是:

source build/envsetup.sh lunch xxx-userdebug cd $ANDROID_BUILD_TOP/hardware/vendor/camera mma -j8

这里mma背后会先编依赖,比如我们要用到的libcamera_metadata,它可能在system/media下。如果依赖没编,报错不是找不到函数,而是undefined reference tocreate_camera_metadata_from_blob。那是真头疼。所以第一次做环境,建议在源码根目录执行m libcamera_metadata libcutils libhardware -j8`,把基础库先编一遍。

代码导读,别从camera_hal.cpp开始读

很多新手拿到 HAL 程序,打开camera_hal.cpp看到一堆hw_module_methods_t就开始头晕。正确打开方式是先看入口点,也就是 HIDL/HIDL HAL 服务进程的 main 函数。现在 Android 8.0 之后,Camera HAL 被封装成一个独立的 vendor 进程,比如android.hardware.camera.provider@2.7-service.vendor。这个服务的main.cpp做了三件事:注册 HIDL 服务、启动线程池、等待客户端 binder 调过来。

从代码阅读路径上,我建议这样走第一遍:

  1. 先看manifest.xml或hal.makefile里声明的版本,明确是 v3.4 还是 v3.7,或者还是老式的 v1。
  2. 找到camera_module_t这个结构体,它就是 HIDL 层和传统 hardware lib 层的桥梁。
  3. 看open()函数,这里面会初始化你的 sensor 管线,并且给camera3_device_ops_t赋值。
  4. 再看configure_streams()、process_request()这三个函数,这基本就是 HAL 的全部业务逻辑了。

代码目录结构,命名可能有差异,但核心模块跑不了:

- include/ // 头文件,定义内部接口、数据结构、宏 - src/ // 源码 - hal/ // 上层的 camera3 device ops - pipeline/ // 底层 sensor 驱动封装,V4L2 ioctl 调用 - processing/ // ISP 相关算法封装 - metadata/ // 静态 metadata 和动态 metadata 的处理 - utils/ // 日志、锁、内存管理辅助 - test/ // 单元测试,编译成可执行文件单独跑

src/hal/下的文件名字都很有规律:CameraModule.cpp对应模块入口,CameraDevice.cpp对应单路 camera 的设备操作。注意这里的命名跟 AOSP 注释要统一,好多厂商直接砍掉注释,包一层 C++ 类,再包一层 C 的接口,你看着复杂,其实就一个套路:C 函数把参数强制转换成内部类的指针,然后调用成员函数。

日志系统,搞不清楚会浪费一整个下午

HAL 里不能用printf,因为 HAL 跑在 vendor 进程里,stdout 跑到哪儿去你根本看不见。要用ALOGE/ALOGD/ALOGV,这些宏会打印到 logd 的 kernel 缓冲区。调试时需要设置setprop vendor.camera.hal.log_level 2,然后重启 camera provider 服务,或者直接重启机器。抓 log 用:

adb logcat -s CAM_HAL:CAM_PIPE:CAM_META

如果厂商有专门的日志 tag,比如CamX或CamHAL3,那就用自己的。注意区分 vendor 进程的 log 和 framework 层的 log。HAL 层输出的 log 按 pid 过滤看不见,因为 HIDL 会隔层。我的经验是直接给硬编码一个 tag,比如#define TAG "CTAM_HAL",然后用:

ALOGE("%s: %s: failure, ret=%d", TAG, __func__, ret);

这样抓 log 时adb logcat | grep CTAM_HAL一行不漏。

还有一个坑,ALOGV默认是关闭的,因为需要定义LOG_NDEBUG为 0。在你的 Android.bp 里要加:

cppflags: ["-DLOG_TAG=\"CTAM_HAL\"", "-DLOG_NDEBUG=0"]

否则写ALOGV等于没写。我当年因为这个调试信息全部消失,以为代码没跑进去。

编译绕不开的几个坑,都给你列出来

第一,头文件路径。AOSP 源码树里面,有一个external/和vendor/下的 include 目录经常不被打进默认的 include path。比如你用到<linux/media.h>,系统里的版本太老,没有MEDIA_ENT_F_IO_V4L这个枚举。解决办法是直接在Android.bp里写:

local_include_dirs: ["kernel_includes/", "hardware/libhardware/include/"]

指定到 vendor 提供的内核头文件目录。别把整个 kernel 源码 include 进去,会有重复定义。

第二,编译宏。有的 SoC 支持三路 virtual channel 摄像头,需要打开ENABLE_VIRTUAL_CHANNEL。这个宏必须在BoardConfig.mk里定义:

TARGET_VENDOR_CAMERA_DEFINES := -DENABLE_VIRTUAL_CHANNEL

然后在模块的cflags里引用。如果你不知道有这个宏,代码里被#ifdef包裹的那段初始化逻辑永远不会编译,然后你看到的 sensor 配置总是错乱,颜色呈紫色。后来发现是没开启宏,导致 route 配置表选错。

第三,链接符号。HAL 依赖 vendor 的私有库,比如libcam_common.so,它不在 AOSP 源码里,是 vendor 编译好的。如果在链接时发现undefined reference toGetSensorRegTable,别慌,这是没让链接器找到这个库。在Android.bp` 里加:

shared_libs: [ "libcam_common", "libcutils", "libutils", "libhardware", "libcamera_metadata", ]

但要注意,libcam_common必须已经在/vendor/lib里存在。如果你编译时链接的是libcam_common,而系统镜像里没有这个文件,就会遇到我开头的那个问题。检查方法很简单,编译完看out/target/product/xxx/vendor/lib/libcam_common.so在不在。

代码导读的切入点:open 流程到底干了啥

不要急着看 process_request 那个大管线的实现。先跑通 open/close 单测,能让 HAL 成功 open 一个 camera 设备,你就成功了一半。以 Camera3 为例,打开流程是:

camera_module_t->open() -> CameraDevice::open()

CameraDevice::open里主要做:

  • 初始化底层 sensor:SensorV4L2::Open("/dev/video0")
  • 配置管道状态机:把mState设为INITIALIZED
  • 把当前动态 metadata 中的ANDROID_DEVICE_INFO_SUPPORTED_HARDWARE_LEVEL设为 LIMITED

这里的坑是,你必须等init()调用完之后才能访问 sensor。如果init()里有个wait_for_ready(10)超时,那 HAL 会卡在 open 阶段,然后camera_service那边会报camera device was externally scheduled to be opened。所以建议在 open 里只做资源初始化和状态机切换,不要做长时间的操作,比如 sensor 的 firmware 升级。

我刚接触 HAL 时,认为 open 就应该把 ISP 也配完,结果每次 open 都要等 2 秒,然后 framework 那里直接提示Failed to open camera。后来把 ISP 初始化移到第一个process_capture_request里用懒加载方式,才解决。切记 open 一定要快,快过 200 毫秒。

调试过程中为什么经常怀疑人生

因为 HAL 的崩溃不像应用层那样弹窗。一旦 HAL 进程死了,framework 会重启它,但你在 logcat 里只看到Client exited due to error -32。这几乎没用。正确方法是先关闭 SELinux,或者至少把 camera 相关的 te 策略改成 permissive。否则 HAL 打不开/dev/video0时会被 SELinux 挡,报avc: denied { read } for scontext=u:r:hal_camera。我现在通常开局就先切 permissive:

adb root adb shell setenforce 0

调试完再恢复。千万别带着 permissive 交给测试,会很尴尬。

还有一个常用的招数,用 vendor 提供的 v4l2 测试工具,比如media-ctl或v4l2-ctl,先直接抓一帧 sensor 原始数据,确认 sensor 时序没问题,再回到 HAL 里去调试。HAL 也提供了一个-D参数可以 dump 出 YUV 帧到文件里,可以用adb pull拿出来用七牛/福昕看图?不,直接用 Python 脚本转换成 raw 文件。这些工具的作用是帮你把问题域切开:是 sensor 出图的问题,还是 HAL 软件的问题。

我的个人建议

写 HAL 一定要把环境搭建当成第一步重心,别急着写业务代码。我见过太多人,拿到源码就开始改 process_request,结果半天编不过,或者刷进去就崩。你还不如先把编译环境验证三遍:第一次用 mma 编 HAL,第二次用 mma 编依赖库,第三次整机刷机验证 open / close 是否正常。这三步跑通了,后面写代码才有底气。

代码导读上,强烈建议按照我上面给的顺序来,不要直接冲到最底层看 V4L2,那样很容易迷失在 ioctl 的海洋里。先明白这个 HAL 是干什么的,它返回给 framework 哪些能力,再从函数调用链去倒推实现。这样效率高很多。

最后一点,所有你加的调试打印不要用ALOGD,直接用ALOGE,即使不是错误。因为很多设备默认 log level 过滤掉了 verbose 和 debug,唯独 error 一定会打出来。等你调试完毕,再把这些错误级别的日志改成 debug 级别留给后续维护。这样能避免你写一大堆日志然后发现根本没输出。

对了,别忘记备份你调通的Android.bp和BoardConfig.mk,这些文件配置着环境最关键的变量。等后面升级 SDK 版本时,很多坑其实就藏在环境配置里,重新对一遍,能省一个星期。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/27 5:16:17

SAP-PP替代物料配置与生产订单缺料排查指南

简介&#xff1a;《SAP-PP-替代物料操作完全手册》面向SAP PP顾问、生产计划员及制造企业关键用户&#xff0c;聚焦生产订单中物料短缺、后继替代与按比例消耗等场景&#xff0c;帮助读者理清平行切换和非平行切换的配置思路与业务边界。压缩包共1个docx文件&#xff0c;约871K…

作者头像 李华
网站建设 2026/10/27 5:16:16

Rust实现零依赖Git提交校验工具gitru的技术解析

1. 项目概述&#xff1a;gitru 的诞生背景与核心价值在团队协作开发中&#xff0c;Git提交信息的规范性常常成为被忽视的一环。我曾参与过一个中型开源项目&#xff0c;某次因为某个成员提交的"fix bug"这样模糊的commit message&#xff0c;导致我们在排查一个线上问…

作者头像 李华
网站建设 2026/10/27 5:15:12

TCP三次握手与四次挥手:抓包排障与内核调优实战

做后端这些年&#xff0c;被问得最多的一类问题里&#xff0c;TCP三次握手和四次挥手绝对排得上前三。有意思的是&#xff0c;很多人背得滚瓜烂熟&#xff0c;一到真实场景就露馅&#xff1a;线上突然冒出一堆 TIME_WAIT 不知道怎么处理&#xff0c;看到 CLOSE_WAIT 上千个以为…

作者头像 李华
网站建设 2026/10/27 5:14:33

基于YOLOv12的蜜蜂识别检测系统技术解析

1. 项目概述&#xff1a;蜜蜂识别检测系统的技术架构与应用价值在农业监测和生态保护领域&#xff0c;蜜蜂作为重要的传粉昆虫&#xff0c;其种群监测一直是个技术难题。传统人工计数方法效率低下且准确性难以保证。这套基于YOLOv12的识别系统&#xff0c;通过计算机视觉技术实…

作者头像 李华
网站建设 2026/10/27 5:14:09

E2E测试稳定性实战:从环境隔离到CI集成的最佳实践

1. E2E测试为什么总是容易被低估&#xff0c;却又绕不开先聊点实际的。做后端接口测试时&#xff0c;单测跑得飞快&#xff0c;覆盖率数字也漂亮&#xff0c;但一到联调阶段&#xff0c;前后端口径不一致、登录态串了、某个页面白屏&#xff0c;问题一个接一个往外冒。这时候你…

作者头像 李华
网站建设 2026/10/27 5:13:33

不完全去重技术:平衡存储与数据完整性的实践指南

1. 不完全去重技术解析在数据处理领域&#xff0c;不完全去重&#xff08;Partial Deduplication&#xff09;是一种平衡存储效率与数据完整性的创新方法。与传统的完全去重不同&#xff0c;这种方法允许系统保留部分重复数据&#xff0c;在实际业务场景中往往能带来意想不到的…

作者头像 李华