简介:这份资源面向Android车机与车载影像方向的开发者,聚焦高通平台上的EVS(Exterior View System)实现,覆盖EVS1.0与EVS2.0两代方案,帮助读者理解车载相机在HAL层的适配方式与启动控制逻辑。包内共124个文件,以46个h头文件、43个cpp源文件为核心,配合8个mk编译脚本、5个rc启动配置及若干xml、json、png与license、notice文件,压缩包约366KB,结构紧凑,便于直接编译验证。内容涉及EVS枚举、状态控制、GL渲染与环视拼接等模块,可据此掌握后视、环视、行车记录等场景下的影像处理流程,并了解通过rc文件调整开机自启或分阶段启动的方法。目前已有1651人学习下载,适合希望深入车载相机HAL与Android车机影像系统的中高级开发者参考。
1. 车机影像系统 EVS:从开机到倒车影像的 2 秒硬仗
冷启动挂倒挡,屏幕要等两三秒才出画面,这种体验在车机上属于致命伤。Android 车载影像系统 EVS(Exterior View System)就是专门解决这个问题的底层框架,它绕开常规应用层,直接对接相机 HAL 和显示管线,把倒车、环视、盲区监测的实时画面在系统启动早期就推上屏。这套源码包围绕高通平台展开,覆盖从 EVS HAL 到 CarService 的完整链路,适合做车机系统定制的 Android 系统工程师、车载相机模块开发者,以及需要把倒车影像响应压进 2 秒内的项目团队。它不教你写 App,而是拆开系统层怎么把摄像头数据从驱动一路送到 SurfaceFlinger,中间经过哪些进程、哪些配置、哪些权限。如果你正在调 EVS 延迟、改环视拼接、或者被 SELinux 卡住,这份东西能省掉大量翻源码的时间。
2. EVS 架构拆解:从 HAL 到 CarService 的数据链路
2.1 为什么 EVS 不走普通 Camera2 那条路
普通 Android 相机应用走的是 CameraService → Camera HAL → App 的路径,中间有应用进程调度、权限检查、Surface 分配等环节,冷启动时延很难压下来。EVS 的设计目标是在系统启动早期就能出图,所以它把链路缩短成:EVS HAL 直接对接 V4L2 或 MIPI CSI 驱动,通过 HIDL/AIDL 接口把 buffer 交给 EvsService,再由 EvsService 通过 CarService 暴露给显示模块。关键区别在于 EVS 的 Enumerator 和 Camera 是独立于 CameraService 的,它有自己的 HAL 进程,启动时机更早,通常在 init 阶段就通过 rc 文件拉起。
这套源码包里,EVS HAL 部分主要看hardware/interfaces/automotive/evs和packages/services/Car/cpp/evs两个目录。前者定义接口,后者是参考实现。高通平台会在 vendor 分区放自己的 EVS HAL 实现,通常叫vendor.qti.hardware.evs@1.0-service之类。你要改的是 vendor 那层,AOSP 这层主要用来理解接口语义。
2.2 数据从 MIPI CSI 到 Surface 的完整路径
以倒车影像为例,数据流大致是:摄像头 sensor → MIPI CSI → V4L2 video 节点 → EVS HAL 的EvsCamera实现 →EvsDisplay实现 → SurfaceFlinger 合成上屏。中间 EVS HAL 会做格式转换(比如 YUYV 转 NV12)、裁剪、缩放,这些操作在高通平台上通常由 ADSP 或 GPU 辅助完成,但源码里能看到的是 CPU 侧的 buffer 管理逻辑。
关键结构体是EvsCamera和EvsDisplay,前者负责getFrame和returnFrame,后者负责getDisplayBuffer和returnDisplayBuffer。源码包里有一个EvsV4lCamera.cpp和EvsGlDisplay.cpp的参考实现,前者用 V4L2 的VIDIOC_DQBUF取帧,后者用 EGL 做渲染。你要关注的是 buffer 数量、超时时间、以及returnFrame的时机——如果 frame 没及时归还,后续取帧会阻塞,倒车画面就会卡顿。
2.3 编译与部署:把 EVS 服务塞进系统镜像
源码包里的 EVS 模块通常以 so 和可执行文件形式存在,编译方式取决于你是整编 AOSP 还是单独编 vendor 模块。整编时在device/qcom/xxx下加PRODUCT_PACKAGES += evs_app和vendor.qti.hardware.evs@1.0-service,然后make -j出镜像。单独编 vendor 模块用mmm packages/services/Car/cpp/evs或高通提供的make evs目标。
部署时注意 rc 文件,EVS 服务需要在init.rc里配service evs_hal /vendor/bin/hw/vendor.qti.hardware.evs@1.0-service,并设置class hal和user system。如果服务起不来,先看dmesg | grep evs和logcat -b all | grep -i evs,常见问题是 SELinux 拒绝或 V4L2 节点权限不对。
# 编译 EVS 参考实现 source build/envsetup.sh lunch aosp_xxx-userdebug mmm packages/services/Car/cpp/evs # 推送到设备并重启服务 adb root && adb remount adb push out/target/product/xxx/vendor/bin/evs_app /vendor/bin/ adb shell setprop ctl.restart evs_hal上面命令里mmm是编译单个模块,lunch选目标平台,adb remount需要先adb disable-verity。推完重启服务后,用adb shell dumpsys evs看状态,正常会列出 camera 和 display 的 id、分辨率、格式。如果 dumpsys 没输出,说明服务没起来,回去查 rc 和 SELinux。
3. 倒车影像延迟优化:从 3 秒压到 1.5 秒的实操
3.1 延迟到底花在哪:分段打点方法
倒车影像延迟不是单一环节的问题,得拆开看。常见做法是在 EVS HAL 的getFrame入口、returnFrame出口、以及 SurfaceFlinger 的onFrameAvailable三处打时间戳,用clock_gettime(CLOCK_MONOTONIC)记录。源码包里有一个EvsTiming.cpp的示例,把每帧的耗时写到/data/vendor/evs/timing.log,然后拉出来算 P50 和 P95。
分段后你会发现,延迟大头通常在三个地方:一是 V4L2 取帧的DQBUF阻塞,二是格式转换的 CPU 拷贝,三是 SurfaceFlinger 合成排队。如果DQBUF耗时超过 30ms,说明 buffer 数量不够或驱动有问题;如果格式转换超过 20ms,考虑用 GPU 或 ADSP 做;如果合成排队超过 50ms,检查 display 的 vsync 和 layer 优先级。
3.2 参数调优:buffer 数量、格式与超时
EVS HAL 里几个关键参数直接影响延迟。EvsV4lCamera的构造函数里会设置mNumBuffers,默认可能是 4,倒车场景建议加到 6 到 8,但别超过驱动限制。格式方面,如果 sensor 出 YUYV,而 display 要 NV12,中间转换很耗时,能配成 sensor 直接出 NV12 最好。超时参数mFrameTimeout默认 500ms,倒车时可以降到 200ms,让阻塞早点返回。
// EvsV4lCamera.cpp 关键参数调整 mNumBuffers = 8; // 增加 buffer 数量,减少 DQBUF 阻塞 mFormat = HAL_PIXEL_FORMAT_YCbCr_420_888; // 尽量匹配 sensor 输出 mFrameTimeout = 200; // 单位 ms,倒车场景缩短超时上面代码里mNumBuffers不是越大越好,超过驱动支持的 buffer 数会申请失败;mFormat要跟 V4L2 的fmt.fmt.pix.pixelformat对齐;mFrameTimeout太短会导致丢帧,太长会让倒车画面卡住。改完重新编译 HAL,推送到设备,再用打点脚本看 P95 有没有降下来。
3.3 显示侧优化:Surface 优先级与合成策略
显示侧能做的优化包括:把 EVS 的 Surface 设为HWC2::Layer的最高优先级,在 SurfaceFlinger 里给 EVS layer 单独开一个 display 或者用 overlay 直出。源码包里有一个EvsDisplay.cpp的修改示例,把setLayerPriority调到PRIORITY_HIGH,并在CarService里把倒车影像的 layer 加到DisplayManager的 top 位置。
另一个技巧是关闭不必要的合成,如果 EVS 画面是全屏的,直接让 HWC 走 overlay 通道,别经过 GPU 合成。这需要在hardware/qcom/display里改 HWC 的配置,把 EVS layer 标记为HWC2::Composition::Device。改完用adb shell dumpsys SurfaceFlinger看 layer 的 composition type,如果是Device就说明走 overlay 了。
4. 环视拼接与多路相机同步:四个摄像头的对齐坑
4.1 多路 EVS Camera 的枚举与配置
环视需要四路摄像头,EVS HAL 的EvsEnumerator要能枚举出四个EvsCamera,每个对应一个 V4L2 节点。源码包里EvsEnumerator.cpp的getCameraList会扫描/dev/video*,但高通平台上摄像头节点可能是/dev/video0到/dev/video3,也可能在/dev/v4l-subdev*下。你要在EvsV4lCamera的构造函数里根据cameraId映射到正确的节点。
配置时注意每路的分辨率和格式要一致,否则拼接时要做额外缩放。常见做法是四路都配 1280x720 NV12,帧率 30fps。如果某路 sensor 只支持 YUYV,要么换 sensor 配置,要么在 HAL 里做转换。枚举失败时看logcat | grep EvsEnumerator,通常会打印每个节点的VIDIOC_QUERYCAP结果。
4.2 时间戳同步:用 SOF 还是用系统时钟
多路拼接最怕时间不同步,画面会撕裂。EVS HAL 里每帧都带timestamp,来源可以是 V4L2 的v4l2_buffer.timestamp,也可以是CLOCK_MONOTONIC。如果四路都用系统时钟,但取帧时刻不同,拼接时就要做时间对齐。源码包里有一个EvsSync.cpp的示例,用环形 buffer 缓存每路最近 5 帧,然后按 timestamp 找最接近的四帧一起送拼接。
另一种做法是用 sensor 的 SOF(Start of Frame)信号做硬件同步,这需要 sensor 驱动支持外部触发。高通平台上可以在cam_sensor驱动里配master/slave模式,让四路 sensor 同时曝光。源码包里没有直接给驱动代码,但EvsV4lCamera里留了setSyncMode的接口,你可以根据平台实现。
4.3 拼接与畸变校正:GPU 还是 DSP
拼接和畸变校正计算量大,CPU 做肯定来不及。常见做法是用 GPU shader 做,源码包里EvsGlDisplay.cpp有一个简单的拼接 shader 示例,把四路纹理映射到一个立方体或碗状模型上。如果平台有 ADSP 或 Hexagon,也可以把校正表放到 DSP 里做,但调试更麻烦。
// 简化版拼接 shader 片段 vec2 uv = texCoord; vec4 front = texture2D(frontCam, uv); vec4 rear = texture2D(rearCam, uv); vec4 left = texture2D(leftCam, uv); vec4 right = texture2D(rightCam, uv); gl_FragColor = blend(front, rear, left, right, uv);上面 shader 只是示意,实际要做畸变查表和权重融合。blend函数根据 uv 位置决定四路权重,边缘区域要平滑过渡。如果拼接后有黑边或错位,先检查每路的标定参数是否写对,再检查纹理坐标有没有翻转。
5. 避坑与排查:EVS 调试中常见的五个翻车现场
5.1 服务起不来,dumpsys 没输出
现象:adb shell dumpsys evs返回空,logcat里看到evs_hal进程反复重启。原因通常是 SELinux 拒绝或 V4L2 节点权限不对。解决:先adb shell dmesg | grep avc看有没有denied记录,如果有,用audit2allow生成策略补丁;再adb shell ls -l /dev/video*确认节点存在且权限是crw-rw---- system camera。如果节点不存在,检查 kernel 的 dts 里 CSI 和 sensor 有没有配好。
5.2 画面卡顿或丢帧
现象:倒车影像能动但一顿一顿,打点日志里DQBUF耗时忽高忽低。原因可能是 buffer 数量不够,或者returnFrame没及时调用。解决:把mNumBuffers从 4 加到 8,检查EvsV4lCamera::returnFrame是否在getFrame之后尽快执行。如果还卡,看v4l2-ctl --stream-mmap --stream-count=100的耗时,确认驱动侧有没有问题。
5.3 颜色不对或画面偏绿
现象:画面能出但颜色明显异常,比如偏绿或偏紫。原因通常是格式不匹配,比如 sensor 出 YUYV 但 HAL 按 NV12 解析。解决:用v4l2-ctl --list-formats-ext看 sensor 支持的格式,然后在EvsV4lCamera里把mFormat改成对应值。如果格式对但颜色还不对,检查EvsGlDisplay的 shader 里 YUV 转 RGB 的矩阵是不是用错了。
5.4 多路拼接错位
现象:环视画面四路接缝处有明显错位或重影。原因可能是标定参数不对,或者时间戳没对齐。解决:先确认每路的畸变校正表是不是从同一套标定流程出来的,再检查EvsSync的环形 buffer 深度够不够。如果错位只在动态场景出现,多半是时间同步问题,考虑上硬件 SOF 同步。
5.5 开机后第一次倒车特别慢
现象:冷启动后第一次挂倒挡要等 3 秒以上,之后正常。原因通常是 EVS 服务启动晚,或者第一次取帧要等 sensor 出流。解决:把 EVS 服务的 rc 文件里class改成core或hal并加oneshot,让它更早启动;在EvsV4lCamera的open里提前VIDIOC_STREAMON,别等到getFrame才开流。如果还慢,看init.rc里有没有依赖其他服务,把依赖去掉。
6. 进阶:用 EVS 做盲区监测与录制回放
6.1 盲区监测的帧差法实现
EVS 不止能出倒车影像,还能做盲区监测。思路是在EvsV4lCamera的getFrame回调里加一个帧差检测,把当前帧和上一帧做差分,如果某个区域变化超过阈值就触发报警。源码包里有一个EvsMotionDetect.cpp的示例,用 OpenCV 的absdiff和threshold,但车机上不一定有 OpenCV,可以用 NEON 指令手写。
// 简化帧差检测 void detectMotion(const uint8_t* cur, const uint8_t* prev, int width, int height) { int diffCount = 0; for (int i = 0; i < width * height; i++) { if (abs(cur[i] - prev[i]) > 30) diffCount++; } if (diffCount > width * height * 0.05) { // 触发报警 notifyMotion(); } }上面代码里阈值 30 和 5% 是经验值,实际要根据光照和场景调。notifyMotion可以往 CarService 发信号,让上层弹提示或响警报。注意别在 HAL 里做太重,否则影响出图延迟。
6.2 录制回放:把 EVS 帧存成视频
调试时经常需要把 EVS 画面录下来回放。可以在EvsV4lCamera的returnFrame之前把 buffer 写到一个文件,格式用 raw NV12,然后用 ffmpeg 转成 mp4。源码包里有一个EvsRecorder.cpp的示例,用fwrite写文件,注意加锁避免多线程写乱。
# 把 raw NV12 转成 mp4 ffmpeg -f rawvideo -pix_fmt nv12 -s 1280x720 -r 30 -i evs_dump.nv12 -c:v libx264 -preset ultrafast output.mp4上面命令里-pix_fmt nv12要跟 dump 的格式一致,-r 30是帧率,-preset ultrafast是为了快。如果回放发现画面跳帧,检查 dump 时有没有丢帧,或者写文件太慢导致阻塞。
6.3 验证方法:用 dumpsys 和 systrace 交叉确认
改完 EVS 后怎么确认优化生效?我一般走三步:先adb shell dumpsys evs看 camera 和 display 的状态,确认服务正常;再adb shell dumpsys SurfaceFlinger --latency看 EVS layer 的合成延迟;最后用systrace抓 10 秒,看EVS和SurfaceFlinger的耗时分布。如果 systrace 里DQBUF还是很长,说明 HAL 侧没改到位;如果onFrameAvailable到onCompose之间很长,说明显示侧还有优化空间。
从那以后我每次改 EVS 参数,都强制走一遍 dumpsys + systrace + 打点日志,不然根本不知道改的是不是有效。希望帮到你。
本文还有配套的精品资源,点击获取