简介:高通车机Android车载影像系统EVS工程源码,面向Android车机与车载相机应用开发者,解决车载环境多路影像采集、处理与显示的系统级实现问题。资源基于硬件抽象层(HAL)实现EVS1.0与EVS2.0两套影像方案,覆盖后视、环视、行车记录仪等典型场景,并兼顾ADAS辅助功能,适合从事车机底层驱动、Camera HAL及图像处理的工程师参考。压缩包共124个文件,以C++源码为主,含46个h头文件与43个cpp实现文件,辅以8个mk编译脚本、5个rc启动配置及少量XML/JSON描述文件,整体约366KB,结构紧凑、便于直接编译集成。目前已有1648人学习,从源码目录可看出资源完整呈现了EVS枚举、状态控制、GL渲染等核心模块实现。开发者可从中掌握HAL层对接与自定义相机服务的方法,通过调整rc文件控制EVS在开机或指定系统阶段自启,并借助图像增强、降噪、色彩校正等处理逻辑提升车载影像的可用性,是一份代码级、可落地的车机影像参考实现。
1. 车机影像不是普通摄像头预览的活
第一次把后视摄像头接到高通车机Android上,我犯过很低级的错:直接复用普通Camera预览链路。挂上R挡,画面两秒后才出来;切换视角必闪黑;行车记录再抢一路通道,整个系统被挤得抬不起头。车机影像需求——多路sensor并发、由车辆状态触发、端到端低延迟——不是普通预览那套服务能扛的。Android汽车系统为此定义了独立路线:EVS(Exterior View System,外部视图系统)。在高通平台做车机Android影像交付时,EVS几乎是既定选项,8系列、9系列座舱芯片方案都围绕它展开。下面把EVS从系统服务到HAL适配、从编译部署到参数调优的实际路径讲清楚,适合正在做车载影像集成,或正要接手EVS模块的工程师。
2. 先看清EVS在高通车机里的位置:职责、边界、延迟
2.1 为什么车机影像不能走普通Camera链路
这个命题背后是一串实际约束。普通Camera链路有CameraService、CameraProvider两个中心,App通过openCamera拿到设备,整个流程围绕“单路预览加拍照”设计。倒车影像是挂R挡才触发,环绕视图需要四路鱼眼同时出流,行车记录则是一路长时间高码率编码。把这三类需求都塞进普通Camera提供者里,会撞上几个硬限制:一是并发路数受provider限制,四路同时预览会让平台CPU和IPC负载明显偏高;二是普通链路为了兼容应用层做了太多转换,从CameraId协商到buffer回调再到消费端渲染,延迟天然不占优;三是车机影像和车辆状态强耦合,App层难以感知挂挡、转向灯这类车身信号。EVS把车外摄像头的控制权和显示通路从普通应用相机里剥出来,做成一套独立框架,统管车外视角摄像头。通俗点说:普通Camera是给“人拿着手机拍照”准备的,EVS是给“车按状态自动出画面”准备的。
高通平台为什么默认选EVS,还有一个现实原因:8系列、9系列座舱方案的底层摄像头适配,本身就以EVS HAL作为对外接口。厂商把ISP、GMSL解串器调通后,EVS能用自己的方式枚举sensor、请求buffer、输出帧。新平台想要快速点亮后视或环视,与其改普通Camera HAL去适配,不如直接在EVS框架下做适配,后者从驱动到服务的路径短了一大截。
2.2 高通EVS链路的四段结构与延迟分布
把一条EVS帧从传感器到屏幕完整走一遍,可以切成四段:采集段、搬运段、渲染段、显示段。采集段从sensor输出到ISP处理完,帧率由传感器配置决定,常见的倒车摄像头配置是1280x720@30fps,720p一帧YUV420大约1.3MB;搬运段是HAL把帧buffer交给EVS Manager再给应用,这段主要看buffer是走零拷贝还是每次memcpy;渲染段要用EGL把YUV贴到目标surface,这里受GPU负载影响;显示段由显示合成器决定出帧节奏。
哪段最容易变成瓶颈?按我调试过的平台经验,绝大多数延迟不是出现在像素搬运,而是出现在“事件到打开摄像头”的路上:挂R挡事件从车身总线到VHAL,再到EVS Manager,最后openCamera完成sensor初始化,中间串了好几个进程和线程调度。帧率不够、画面卡顿,才轮到渲染段与显示段去排查。把问题拆成“启动延迟”和“帧间隔抖动”两个维度来看,排查思路会清晰很多。启动延迟是事件到首帧的时间,目标做到500ms以内;帧间隔抖动是出帧稳定性的问题,30fps配置下相邻帧间隔应稳定在33ms左右,个别帧掉到50ms以上就说明有抢占或拷贝。
零拷贝在这套链路里很关键。常见的高通方案通过ION或DMA-BUF把物理内存句柄直接传给应用,EGL可以直接引用这个fd做纹理,避免YUV数据从驱动复制到用户态再复制到GPU显存。如果厂商HAL实现偷懒做了memcpy,720p@30fps下的搬运延迟就会从1ms以下直接跳到5ms以上,四路同开会放大到难以接受。所以接手一个平台时,第一件事应该确认EVS的buffer走的是handle透传还是内存拷贝。
| 链路分段 | 主要职责 | 常见瓶颈信号 | 优先排查对象 |
|---|---|---|---|
| 采集段 | 曝光、ISP处理、帧缓冲 | 帧间隔不均匀 | sensor配置、GMSL通道状态 |
| 搬运段 | HAL到Manager到App | 偶发丢帧、延迟抖动 | buffer个数、是否零拷贝 |
| 渲染段 | EGL贴图、多视角切换 | 黑屏、切换闪断 | EGL配置、输出窗口尺寸 |
| 显示段 | 合成器出帧节奏 | 画面撕裂、跟手性差 | 显示节点刷新率、vsync状态 |
2.3 一个容易被忽视的边界:EVS不负责的事
EVS只管出流和显示控制,它不负责图像质量调优。白平衡、曝光、gamma这些ISP参数仍然由摄像头驱动与sensor配置决定;也不负责镜头畸变矫正、四路拼接这类算法,环视拼接一般是在GPU或专用ISP核里做的。把这一点想清楚,联调时能少走很多弯路:画面颜色不对别去EVS Manager里翻参数,那是sensor和ISP的事;画面拼接不准也别指望EVS能调,那是算法引擎的事。
另一个边界是EVS与DMS、行车记录这类应用的共存关系。行车记录如果也要走EVS通道,意味着同一颗sensor可能被多个Consumer消费,这在高通方案里通常靠虚拟通道或副本来表示。实际操作中,我一般让行车记录走单独的编码通路,EVS只服务倒车和环视这类需要即时显示的视角,避免两套逻辑互相抢占buffer。EVS Manager本身很薄,它更像调度者,把物理设备抽象成CameraId,把buffer流转给上层,真正的逻辑密度在HAL实现和消费端应用两侧。
3. 把EVS跑起来:源码定位、编译、部署与台架演示
3.1 在源码树里定位EVS相关模块
AOSP车机代码里,EVS应用和服务默认放在packages/services/Car/evs下,HAL接口定义在hardware/interfaces/automotive/evs下。厂商SDK一般会把EVS的HAL实现放在vendor目录里,命名通常是厂商代号加evs。先在源码根目录做一次粗定位:
# 从源码根目录找出所有EVS相关目录 find . -type d -iname "*evs*" 2>/dev/null | head -40 # 确认HAL接口定义位置 find hardware/interfaces/automotive -maxdepth 2 -type d | grep -i evs # 查看应用与服务的构建配置 cat packages/services/Car/evs/Android.bp执行结果里通常能看到三块内容:HAL接口库、EvsManager服务模块、evs_app调试应用。Android.bp里的module_name是决定make目标名的关键,比如evs_app这个名字在make时就是m evs_app。不同平台版本里HAL接口有1.0、1.1、1.2的差异,接口更新主要是增加了超时控制、buffer描述和同步机制。上手时先确定平台用哪个版本,不要拿旧接口直接套新HAL。
3.2 编译EVS服务与调试应用
编译前先确认lunch目标,最好是userdebug版本,后面需要remount和日志权限:
# 初始化编译环境 source build/envsetup.sh # 切换目标平台(按厂商实际makefile填) lunch <厂商代号>_<平台>-userdebug # 编译EVS相关模块 m EvsManager evs_app -j$(nproc) # 编完先定位产物实际路径,不同平台可能放system或vendor目录 find $OUT -name "EvsManager" -o -name "evs_app" 2>/dev/nullEvsManager是系统服务进程,一般由init启动;evs_app是标准的EVS调试应用,自带预览窗口、截屏和录屏功能,做完驱动适配后,第一件事就是把它推进车机验证出图。注意:有些厂商会把evs_app改名成evs_demo或放进vendor目录,找不到时用find $OUT -name "*evs*"去看实际产物。编译报错最常见于接口库版本不一致,比如应用按1.1接口编译、HAL实现还是1.0的桩,这时logcat里会出现unknown transaction或接口解析失败报错,先把两边版本对齐。
3.3 部署到目标机并确认服务拉起
对userdebug版本,最省事的路径是adb remount后直接push:
adb root adb remount # 推送服务与调试应用,路径以实际产物为准 adb push $OUT/system/bin/EvsManager /system/bin/ adb push $OUT/system/bin/evs_app /system/bin/ # 摄像头描述文件如果变更,同步推到vendor分区 adb push $OUT/vendor/etc/evs_config.xml /vendor/etc/ # 重启后分两次确认:服务是否拉起、日志是否正常 adb reboot adb shell dumpsys -l | grep -i evs adb shell logcat -d -s EvsManager:I -s EVS:Idumpsys列表里能看到EvsManager,说明服务进程注册成功;logcat里如果出现camera open success和第一帧输出,说明摄像头链路已经通。没有输出时,先查SELinux审计日志,不要急着改代码。EVS服务起不来的场景,一半以上是SELinux拦截了设备节点访问权限,具体排查方法在第5章展开。
3.4 没有真车怎么演示倒车影像
台架阶段没有总线信号,R挡事件触发不了。常见做法是把VHAL换成mock模式,通过配置文件或调试工具直接写入车辆属性。思路是:EVS Manager监听VHAL的reverse gear状态变化,状态一旦置位就打开对应摄像头。手动模拟时,要么在VHAL的mock属性里把gear position写成reverse,要么借助HAL层自带的测试入口直接openCamera。如果EVS HAL提供了命令行测试程序,还可以直接指定CameraId拉起出流,绕过整个上层事件链:
# 示意:HAL层自测程序直接打开后视CameraId adb shell evs_hal_test --camera 0 --count 300没有这个程序也没关系,evs_app通常会带“打开指定摄像头”的调试按钮,在台架上点开即可验证渲染链路。先把链路跑通,R挡事件联调属于VHAL侧工作,两者解耦开来,排障范围能缩小很多。
4. 配置文件与参数调优:让EVS按需出流
4.1 CameraId映射与物理sensor绑定
EVS应用看到的CameraId,通常是HAL层把物理端口翻译后的逻辑编号。厂商普遍的做法是提供一个描述文件,把/dev/videoX节点、物理sensor名称、分辨率和帧率绑定在一起。我习惯在接手平台时先要一份这样的映射,否则等于在猜编号。示意格式如下:
<!-- 厂商EVS配置示意:逻辑ID到物理端口的映射 --> <camera-config> <camera id="0" sensor="rear_center"> <device>/dev/video0</device> <resolution width="1280" height="720"/> <fps>30</fps> </camera> <camera id="1" sensor="front_center"> <device>/dev/video1</device> <resolution width="1280" height="720"/> <fps>20</fps> </camera> </camera-config>不要把id顺序当作无关紧要的编号。上层默认会认为id 0是倒车视角,因为倒车是优先级最高的安全场景;id顺序同时决定枚举列表里的展示顺序。物理sensor名称要和GMSL解串器、ISP的通道命名保持一致,名称对不上时HAL打开设备会直接失败。分辨率建议不要超过显示目标分辨率,倒车影像输出到车载屏幕,1080p配置对720p屏幕没有额外收益,徒增带宽。
4.2 四个核心参数:分辨率、帧率、buffer数、超时
参数调优一上来不用贪多,把下面四个调对,八成问题都能解决:
| 参数 | 合理区间 | 作用与影响 |
|---|---|---|
| 分辨率 | 倒车720p或1080p;环视720p | 分辨率越高带宽和ISP负载越大,且受限于屏幕 |
| 帧率 | 倒车30fps;环视15-20fps | 太低会产生运动拖影,太高增加功耗和带宽 |
| Buffer个数 | 4-6个 | 太少会闪烁掉帧,太多会抬高端到端延迟 |
| 打开超时 | 1000-3000ms | 太短在弱光启动时会误报失败 |
buffer数目要单独解释一下。EVS链路是生产-消费模型,HAL每采一帧交给消费者,消费者处理完再归还。buffer太少,比如只有2个,生产者和消费者会互相等待,显示端表现为周期性卡顿;buffer太多,比如8个以上,队列积压会让倒车画面滞后于实际车身状态,倒车雷达都响了画面还没跟上。经验起点是5个,运行中看帧间隔抖动再增减。
打开超时的“弱光误报”也是常见问题。传感器在暗光下要拉长曝光做几帧收敛,如果设置的打开超时比收敛时间长,EVS会判定打开失败直接关流。遇到倒车影像偶发没画面的,先看是否都发生在傍晚,把超时调到3000ms再观察。
4.3 渲染配置与多视角切换的省事做法
EVS应用侧渲染一般直接用EGL stream,不走普通View和SurfaceFlinger的全链路合成。这样做是为了尽量减少中间环节,让帧buffer直接以纹理形式贴到输出表面。实际配置EGL时注意两点:颜色格式要跟HAL输出的buffer格式一致,常见是YUV420或NV12;窗口尺寸要和显示分辨率一致,宽度高度不一致时贴图会黑屏或变形。
多视角切换最大的坑是“切换时重新openCamera”。环视场景切视角时,如果应用先close再open,画面会先黑再出,黑屏时间随sensor初始化时间走,有时能到1秒。我一般会提前把所有视角的摄像头都打开、常挂在后台待命,档位或转向灯触发时只做显示内容切换,不重开设备。代价是多路sensor同时占带宽和ISP资源,要在配置阶段确认并发路数支持。若是平台并发能力紧张,就只预开“上一次视角加新视角”两个设备做热切换。
5. EVS排查:五条血泪坑与定位手法
5.1 挂R挡后画面三秒才上屏
现象:实车上挂倒挡,倒车影像要两三秒才出现,倒车雷达已经响了画面还没跟上。
原因:事件链路上任何一环阻塞都会把启动延迟拉大。最常见的是VHAL事件从总线到CarService再到EVS Manager,中间线程优先级被其他任务抢占;另一个常见原因是openCamera内部做了sensor全量重新配置,而不是走快速恢复流程。
解决:把事件与出流解耦定位。按经验把R挡事件到EVS的耗时、EVS打开摄像头的耗时分开打点。启动延迟要求500ms内,事件链路超过300ms就需要优化调度。另一个立竿见影的做法是常驻预打开倒车CameraId,挂挡只切换显示,启动延迟可以压到200ms内。
5.2 EVS应用黑屏,logcat没有任何Error
现象:应用起来了,界面正常,摄像头也显示打开成功,但屏幕就是黑的。
原因:EGL初始化失败但被应用层吞掉,最常见是输出窗口尺寸和HAL输出buffer尺寸不一致,或者颜色格式协商失败;还有平台GPU权限没给到应用进程,导致EGL上下文创建失败。
解决:先查EGL初始化返回值,再比对显示分辨率与buffer尺寸。用dumpsys SurfaceFlinger查显示规格,和EVS输出配置做对比。尺寸一致后仍黑屏,把EGL相关日志全开,看是GLContext还是Surface创建失败,后者通常要回到权限和SELinux层面查。
5.3 四路环视只出一路,其他三路超时
现象:环绕视图上只有一路有画面,其余三路一直转圈或黑块,logcat里报buffer no response。
原因:并发路数超过平台带宽或ISP通道上限;也可能是四路中有一路sensor打开失败,导致整个环视session被上层杀掉。
解决:逐路单独打开,确认每路都能出流;再两路同时开,逐步增加,定位是哪一路引入冲突。确认是平台并发不够,就把环视降到720p并在ISP端分配多通道带宽;确认是某一路驱动问题,检查它与其他路的GMSL配置是否占用了同一个分组地址。
5.4 图像偏绿且亮度异常,换个时间又正常
现象:傍晚后影像明显偏绿,路面和车身颜色都不对,白天偶尔也出现。
原因:AWB自动白平衡在弱光下漂移,AE长时间曝光导致帧率下降。这个问题属于sensor和ISP参数,不是EVS框架问题,但它会通过EVS显示暴露出来。
解决:在sensor配置里把AWB和AE锁到固定模式,或把白平衡范围收紧到车外场景的合理区间。测试时建议固定三组光照条件:白天、傍晚、地库,分别验证画面不偏色。这也是我在联调时踩过的坑:一开始在EVS Manager里翻参数翻了半天,其实问题根本不在EVS,方向完全错了。
5.5 服务起不来,logcat里什么也没有
现象:reboot之后dumpsys里没有EvsManager,logcat里也没有相关日志。
原因:SELinux把socket或设备节点访问拦了,日志落到了内核审计而不是app层logcat;也有init rc配置里服务依赖的设备节点没就绪导致起不来。
解决:先看事件日志有没有avc denied,这是最快路径:
# 查SELinux拦截记录,dmesg里通常有avc字样 adb shell dmesg | grep -i "avc: denied" # 或查看事件日志里的avc记录 adb logcat -b events -d | grep -i avc看到avc记录后,补te规则放行对应设备节点与socket,再重新编译bootimg或vendor分区验证。顺带说一句:不要在user版本上花时间排查这类问题,直接换userdebug,权限日志和remount能力能省一半排查时间。
6. 用三段时间戳量出EVS的真实延迟
6.1 三段时间戳定位延迟瓶颈
抛开玄学,EVS性能调优要的是可测量。我调试EVS时会在三条链路边界各打一个时间戳:事件到首帧、采集段到应用、渲染完成到显示。在驱动或HAL层加上打印,例如:
// HAL层示意:在帧到达回调中记录系统时钟 static void on_frame_ready(const struct evs_frame *f) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); ALOGI("EVS_FRAME id=%u ts=%ld.%06ld", f->id, ts.tv_sec, ts.tv_nsec / 1000); }配合应用侧打印每个关键动作的时间,就能算出:从收到R挡事件到第一帧进HAL的耗时、从第一帧进HAL到应用开始渲染的耗时。三段里哪段飘,哪段就是优化对象。帧间隔抖动时,把HAL侧每个frame_done时间点拉出来看,间隔稳定说明采集段没问题,问题在消费端。
我现在的习惯是,任何一次EVS联调都先把这三段时间戳打齐,再谈优化参数,而不是凭感觉调buffer数。这套方法对高通平台成立,换到其他芯片的EVS也成立。回到开头那个我自己翻过的车,现在接后视摄像头第一件事不是调sensor参数,而是先把事件到首帧的链路量一遍。希望帮到你。
本文还有配套的精品资源,点击获取