简介:这份资源面向Android车机与车载影像方向的开发者,聚焦高通平台EVS(Exterior View System)影像系统的HAL层实现,覆盖EVS1.0与EVS2.0两个版本,可用于后视、环视、行车记录及ADAS等车载相机场景的移植与二次开发。压缩包共124个文件,约366KB,以46个h头文件与43个cpp源文件为主体,另有8个mk编译脚本、5个rc启动配置、3个xml与2个json配置,以及license、notice、png等辅助文件,整体结构紧凑,便于直接编译与裁剪。内容涉及相机枚举、状态控制、配置管理、OpenGL渲染与顶层视图绘制等模块,读者可据此理解EVS在Android车机中的启动流程、HAL接口调用方式与图像处理链路,并参考rc文件调整开机自启或分阶段启动策略。目前已有1651人学习下载,适合具备一定Android系统与C++基础、希望深入车载影像底层实现的工程师参考。
1. 车机影像系统落地:从倒车影像到多路环视的完整工程链路
倒车影像黑屏、环视拼接错位、冷启动要等好几秒才出画面——这些在车机上被用户骂到爆的问题,根子往往不在摄像头硬件,而在 Android 车机侧的 EVS(Exterior View System)实现。这份「高通车机Android车载影像系统-EVS」资源,就是围绕高通车机平台把 EVS 从 HAL 到 App 的整条链路拆开的一份工程代码,适合正在做车载相机、倒车影像、360 环视的 Android 系统开发者和应用开发者。它解决的不是「怎么调个 Camera API」这种入门问题,而是车机场景下多路摄像头并发、开机快速出图、画面低延迟显示这些真正卡人的工程点。下面按「这项目能干什么 → 怎么跑起来 → 核心代码怎么改」的顺序拆一遍。
2. 架构与目录拆解:EVS 在 Android 车机里到底怎么串起来
2.1 从 Camera HAL 到 SurfaceFlinger 的数据流
车机上的 EVS 和手机上的 Camera 完全是两套东西。手机 Camera 走的是 CameraService → CameraProvider → App 这条标准链路,而 EVS 在高通平台上是一条相对独立的通路:摄像头数据由 EVS HAL 直接拿到,通过IEvsCamera接口把 buffer 交给上层,上层再决定是直接送显还是做拼接处理。
整条链路大致是这样:
Camera Sensor → ISP → EVS HAL (V4L2/高通 CAMX) → IEvsCamera (HIDL/AIDL) → EvsEnumerator 枚举设备 → EvsCamera 拿到帧 → 直接送 SurfaceFlinger 显示 → 或送 GPU 做拼接/畸变校正这里有个关键点:EVS 的帧不走 BufferQueue 那套标准流程,而是用IEvsCameraStream回调把BufferDesc直接递上来。这意味着延迟比标准 Camera 通路低不少,但也意味着你得自己管 buffer 的生命周期,doneWithFrame 忘了调,几帧之后就没 buffer 可用了,画面直接卡死。这是 EVS 开发里最常见的翻车点之一。
2.2 工程目录结构
这份资源的目录结构基本遵循 AOSP 里 EVS 的既有约定,同时针对高通平台做了适配。核心目录大致如下:
evs/ ├── app/ # 示例应用,含环视/倒车 UI │ ├── src/ # Java/Kotlin 层,负责 UI 和 Surface 管理 │ └── jni/ # 可选,拼接算法走 native ├── hal/ # EVS HAL 实现 │ ├── camera/ # IEvsCamera 实现,对接高通 CAMX │ └── enumerator/ # 设备枚举,管理多路摄像头 ├── manager/ # EvsManager 服务,系统级管理 ├── driver/ # 底层驱动对接层 └── config/ # 摄像头配置、标定参数hal/camera是最核心的部分,IEvsCamera的 open/startVideoStream/stopVideoStream 都在这里落地。config目录里放的是摄像头标定参数,环视拼接能不能拼得准,全靠这里的参数对不对。
2.3 多路摄像头的枚举与并发
车机通常有 4 路摄像头(前、后、左、右),EVS 通过IEvsEnumerator来枚举和管理。枚举的时候要注意,不是所有摄像头都能同时打开——高通平台上有带宽限制,4 路 1080p 同时跑可能就超了。常见做法是环视场景降到 720p 或者用较低帧率,倒车场景单独提一路到高分辨率。
// 枚举可用摄像头 IEvsEnumerator enumerator = EvsEnumerator.connect(); List<CameraDesc> cameras = enumerator.getCameraList(); for (CameraDesc desc : cameras) { // desc.id 是摄像头唯一标识,desc.videoFormat 是支持的格式 Log.d(TAG, "camera: " + desc.id + " format: " + desc.videoFormat); }getCameraList()返回的CameraDesc里,id是后续 open 用的关键,videoFormat决定了你能拿到的最大分辨率和像素格式。如果这里返回的格式和你预期不符,多半是 HAL 层的配置没对上,得回去查config里的摄像头能力声明。
3. 跑起来:编译、部署与开机出图验证
3.1 编译环境与依赖
这套东西不是 Android Studio 里点个 Run 就能跑的,它依赖 AOSP 编译环境和高通平台的 vendor 代码。基本要求是:
- 完整的 AOSP 源码树,且已经合入高通平台的 BSP
- 对应的 lunch target(车机通常是
aosp_car_xxx-userdebug) - EVS 相关的 SELinux 策略已经配好,否则 HAL 起不来
编译命令大致是:
source build/envsetup.sh lunch aosp_car_x86_64-userdebug # 换成你实际的 target make evs_app evs_hal -j$(nproc)编译产物里,evs_app是上层应用,evs_hal是 HAL 服务。两个都要推到设备上,HAL 通常放/vendor/bin/hw/,app 放/system/priv-app/。
3.2 部署与权限配置
推上去之后,最容易卡住的是权限。EVS 需要访问/dev/video*设备节点,还要有 SELinux 权限。常见做法是确认evs_hal的.rc文件里已经声明了对应的uevent和dev权限。
# 推 HAL 和 app adb push evs_hal /vendor/bin/hw/ adb push evs_app /system/priv-app/EvsApp/ adb shell chmod 644 /vendor/etc/init/evs_hal.rc # 重启相关服务 adb shell stop evs_hal adb shell start evs_hal adb logcat -s EvsHal EvsApp如果evs_hal起不来,先看dmesg里有没有 V4L2 设备注册失败的报错,再看logcat里 SELinux denial。这两个地方能覆盖八成启动失败。
3.3 验证画面是否正常出图
服务起来之后,验证分两步:先确认 HAL 能拿到帧,再确认 app 能把帧显示出来。
# 看 HAL 是否在持续出帧 adb logcat -s EvsHal | grep "frame" # 看 app 的 Surface 是否创建成功 adb shell dumpsys SurfaceFlinger | grep Evs如果 HAL 有帧但屏幕黑的,多半是 Surface 没绑对,或者送显的 layer 被别的 UI 盖住了。车机上经常有这种情况:倒车影像的 Surface 被 launcher 的 Surface 压在下面,得调 z-order。
提示:冷启动出图时间是车机 EVS 的硬指标,通常要求 2 秒内。如果超过这个数,先查 HAL 初始化耗时,再查 app 的 Surface 创建时机,别一上来就怀疑摄像头。
4. 核心代码拆解:帧回调、buffer 管理与送显
4.1 IEvsCameraStream 回调实现
EVS 拿帧的核心是实现IEvsCameraStream接口,在deliverFrame回调里处理每一帧。这个回调是同步的,处理慢了会阻塞后续帧,所以千万别在里面做耗时操作。
// 实现帧回调 class EvsStreamCallback implements IEvsCameraStream { private IEvsCamera mCamera; @Override public void deliverFrame(BufferDesc[] buffers, long timestamp) { for (BufferDesc buf : buffers) { // buf.bufferId 是 buffer 唯一标识 // buf.width/height/stride 描述帧布局 // 这里做送显或拼接处理 renderFrame(buf); // 关键:处理完必须归还 buffer mCamera.doneWithFrame(buf); } } }deliverFrame里的buffers是数组,一次可能给多帧。doneWithFrame必须调,而且要在处理完之后调,不能提前也不能漏。漏调几次之后 buffer 池耗尽,deliverFrame就不再被触发了,表现就是画面卡住不动。这是 EVS 开发里最经典的坑。
4.2 BufferDesc 与像素格式处理
BufferDesc里最关键的是format和stride。车机摄像头常见输出是 NV12 或 YUYV,送显之前可能要做格式转换。
// 根据像素格式决定处理路径 switch (buf.format) { case NV12: // 直接送显,SurfaceFlinger 支持 NV12 nativeRenderNV12(buf); break; case YUYV: // 需要转成 RGBA 再送显 nativeConvertYUYVtoRGBA(buf); break; default: Log.w(TAG, "unsupported format: " + buf.format); }stride是每行字节数,可能大于width * bytesPerPixel,处理的时候要按 stride 来算偏移,不能直接用 width。这个细节错了,画面会出现斜条纹或者错位。
4.3 环视拼接的 GPU 处理路径
如果是 360 环视,4 路摄像头的数据要拼成一张俯视图。常见做法是把 4 路帧传到 GPU,用 shader 做畸变校正和拼接。
// 把 4 路 buffer 传给 native 层做拼接 long[] bufferIds = new long[4]; for (int i = 0; i < 4; i++) { bufferIds[i] = cameraBuffers[i].bufferId; } // native 层用 EGLImage 把 buffer 转成纹理,再走 shader nativeStitchFrames(bufferIds, mOutputSurface);拼接的准确性依赖config里的标定参数,包括每个摄像头的内参、外参和畸变系数。参数不对,拼出来的图就是错位的。调试的时候可以先把 4 路原始画面分别显示出来,确认每路都正常,再开拼接。
5. 避坑与排查:EVS 开发里那些血泪经验
5.1 画面卡死不动
现象:画面显示几秒后卡住,logcat 里deliverFrame不再被调用。
原因:doneWithFrame漏调或者调晚了,buffer 池耗尽。
解决:检查所有分支路径,确保每个拿到的 buffer 都有对应的doneWithFrame。用 try-finally 包住处理逻辑,异常时也要归还。
5.2 冷启动出图慢
现象:开机后倒车影像要等 3-5 秒才出来。
原因:HAL 初始化慢,或者 app 的 Surface 创建晚于 HAL 出帧。
解决:把 HAL 的初始化提前到开机早期,app 侧用SurfaceControl提前创建好 Surface,别等用户挂倒挡才创建。
5.3 多路摄像头打开失败
现象:单独开一路正常,同时开 4 路就报错。
原因:带宽或内存不足,高通平台对并发摄像头有硬限制。
解决:降低分辨率或帧率,环视场景用 720p 通常够用。查dmesg里的 CAMX 报错确认具体限制。
5.4 画面颜色不对
现象:画面偏绿或偏紫。
原因:像素格式判断错了,NV12 当成 YUYV 处理,或者 UV 顺序反了。
解决:确认 HAL 输出的实际格式,用buf.format判断,别硬编码。NV12 和 NV21 的 UV 顺序是反的,搞错了颜色就偏。
5.5 SELinux 权限拒绝
现象:evs_hal起不来,logcat 里有 avc denied。
原因:SELinux 策略没配全,HAL 访问不了设备节点或共享内存。
解决:根据 avc denied 的日志补策略,或者临时用 permissive 模式验证是不是权限问题。
6. 二次开发:从能跑到好用的改造思路
把 EVS 跑起来只是第一步,真正要落地到量产车机,还得在几个地方做改造。
第一,出图速度优化。冷启动 2 秒是及格线,要做到 1 秒内,得把 HAL 初始化、Surface 创建、首帧送显这几个环节并行化。我一般会把 Surface 的创建提前到系统启动阶段,HAL 一有帧就能直接送,省掉等 Surface 的时间。
第二,多场景切换。倒车、环视、转向辅助这些场景对分辨率、帧率、拼接方式的要求不一样。常见做法是维护一套场景配置,切换的时候动态调整摄像头参数和送显路径,而不是所有场景用同一套配置硬扛。
第三,拼接质量调优。环视拼接的接缝处理是门玄学,标定参数准不准、shader 里的融合权重怎么设,直接决定用户看着舒不舒服。建议先把 4 路原始画面调正,再调拼接,别一上来就调拼接参数,那样出了问题根本不知道是哪层的锅。
第四,异常恢复。摄像头偶尔会掉线,HAL 得有重连机制。我一般会在deliverFrame里加超时检测,超过一定时间没帧就触发重连,而不是等用户发现黑屏了才处理。
改造完之后怎么验证?我的习惯是跑三个测试:冷启动出图时间、连续运行 2 小时看有没有卡死、4 路并发时的 CPU/GPU 占用。这三个过了,基本就能上车了。
从那以后我每次改 EVS 的 buffer 管理逻辑,都强制走一遍「拿帧-处理-归还」的完整路径检查,宁可多写几行日志,也不让 buffer 泄漏这种问题流到测试阶段。希望帮到你。
本文还有配套的精品资源,点击获取