车载音频与显示联动编程:实现导航语音与中控屏幕信息的同步提示
导航“前方300米右转”播出来的同时,中控屏卡片、仪表提示条、HUD箭头也得同一时刻出来;语音被打断时屏幕卡片不能还赖着不消失。很多项目把音频和UI写成两条独立逻辑——“导航模块调TTS”和“UI自己刷页面”,结果就是声音先响、界面晚半秒,或者语音助手插话后屏幕还显示旧导航卡片。正确做法是导航只发事件,音频和显示都订阅同一事件源。
一、统一事件源:导航只产事件
导航规划模块别直接调TTS、也别直接改UI控件,只往事件总线发结构化事件:
typedef enum {
NAV_EVT_NONE,
NAV_EVT_TURN, // 转向提示
NAV_EVT_LANE, // 车道引导
NAV_EVT_REPLAN, // 路线重算
NAV_EVT_ARRIVE
} nav_evt_t;
typedef struct {
nav_evt_t type;
uint32_t evt_id; // 去重用,同ID只处理一次
uint64_t ts_ns; // 统一时间戳
uint8_t priority; // 0最高
char text[64]; // TTS文本
int16_t dist_m;
uint8_t dir; // 左/右/直
uint16_t icon_id;
} nav_event_t;
导航产生事件→总线分发→音频服务挑text播报、HMI服务挑icon/text画卡片。两边互不调用,只认evt_id和ts。
二、音频侧:焦点+Ducking
导航语音不是“暂停音乐播自己”,是临时抢占+压低媒体:
申请TRANSIENT_MAY_DUCK,音乐降音量不暂停
告警/来电优先级高于导航,导航让位
语音助手唤醒时,导航后续排队TTS必须拦截,别残音灌喇叭
播报完成释放焦点,媒体恢复
void audio_on_nav(nav_event_t*e){
if(audio_focus_busy(CALL)) return; // 通话中不打断
if(asr_listening()){ queue_defer(e); return; }
audio_request_focus(USAGE_NAVIGATION, TRANSIENT_MAY_DUCK);
media_duck(0.5f);
tts_speak(e->text);
}
离线TTS模块(UART TTS)也走这套:文本下发前先抢焦点,播完发停止。
三、显示侧:同事件同帧
HMI订阅同一个nav_event,按evt_id更新卡片:
void hmi_on_nav(nav_event_t*e){
if(e->evt_id == last_nav_id) return; // 去重
last_nav_id = e->evt_id;
show_nav_card(e->text, e->dist_m, e->dir, e->icon_id);
set_card_timeout(rule_timeout(e->type));
}
同步要点:
同时间戳提交:音频开始播的帧,HMI也在同帧commit,别各等各的定时器
多屏状态同源:中控、仪表、HUD都读同一份nav_state,不各存各的
VSync对齐:多屏用统一垂直同步,避免中控已翻页仪表还停上一步
卡片显示时长按事件类型定:转向提示随播报结束消失,限速/车道引导可常驻
四、打断回收:语音停卡片也停
最常见bug:TTS被语音助手打断,屏幕导航卡片还亮着。
规则放仲裁服务里统一管:
void nav_arbiter_on_interrupt(audio_src_t who){
if(who == VOICE_ASST || who == CALL){
cancel_pending_tts(); // 不再排新句
hmi_hide_nav_card(); // 卡片回收
media_duck_restore();
}
}
显示层不自己判断“语音是不是在播”,只听仲裁结果。这样音频策略和UI回收是同一份真相源。
五、去重与节流
导航GPS抖动会连发同内容事件,必须拦:
evt_id去重:同ID不重播
距离阈值:距离变化<20m不刷新卡片
文本归一:同句不同GPS点合并
队列只留最新:连续转向提示只保最后一条
if(strcmp(e->text,last_text)==0 && e->type==NAV_EVT_TURN)
return;
否则“右转”在300m/280m/260m连播三次,用户直接烦。
六、分层架构落法
导航引擎
└─ nav_event ─▶ 事件总线(SOME/IP/DDS/Binder)
├─▶ 音频仲裁服务 → TTS/Media/Ducking
└─▶ HMI状态服务 → 中控/仪表/HUD卡片
音频和显示都从总线拿事件,谁也不依赖谁;仲裁服务单独管优先级。
七、落坑清单
导航直接调TTS+直接改UI→音画不同步
语音打断只停声音不收卡片→屏显旧提示
同句GPS抖动重复播报→用户烦
媒体用暂停不用duck→音乐断一下很突兀
中控仪表各存nav状态→两屏不一致
多屏不用统一VSync→刷新错拍
告警音和导航同优先级→互相打架
卡片超时写死3秒→转向提示早灭
TTS回调里直接画UI→线程跨界卡顿
evt_id不记录→重连后重复弹窗
八、落地顺序
导航只产nav_event,不带音频/UI动作
事件总线分发,音频/HMI各自订阅
音频服务做焦点仲裁+Ducking+打断拦截
HMI按evt_id更新卡片,同帧commit
多屏走统一nav_state+VSync
打断事件统一回收卡片和TTS队列
evt_id去重+距离节流
告警/通话优先级高于导航,规则进仲裁服务
车载音画联动的本质是“一个事件、两条通道、同一仲裁”:导航不碰喇叭也不碰控件,音频走焦点管理保听清,显示走状态同源保多屏一致,打断逻辑放中间层统一收口。代码里最该避免的就是导航模块里顺手写tts_speak()又顺手set_label()——这两条一耦合,语音助手一插话屏幕就救不回来。把事件源收住,音频策略和HMI回收自然就能同拍。





