第二章:系统架构总览:端侧与云侧架构、模块划分、数据流与信号流设计
好,咱们正式开始搭建语音交互系统。这一章,我带你从高空俯瞰整个系统长什么样。
很多人一上来就扎进代码细节,结果越写越乱。我个人的习惯是——先画一张架构图。哪怕画在餐巾纸上都行。你得知道你的系统分几块,数据怎么流,信号怎么传。
说白了,车载语音交互系统就两大块:端侧(车机本地)和云侧(云端服务器)。这两者配合,才能实现流畅的语音体验。
2.1 端侧架构:车机本地的大脑
端侧,就是车机上的那块芯片和跑在上面的软件。为什么要有端侧?你想想看,如果每次说“打开空调”都要等网络往返,那体验得多糟糕。我在项目中遇到过,网络信号不好的地下车库,语音直接瘫痪。所以,端侧必须能处理一部分任务。
端侧架构通常分为以下几个模块:
- 音频采集模块:负责从麦克风阵列获取原始音频。嗯,这里要注意,不是简单录音,还要做回声消除、降噪、波束成形。我见过不少项目,麦克风选型没做好,导致远场唤醒率极低。
- 唤醒词引擎:持续监听音频流,检测预设的唤醒词(比如“你好,小驰”)。这个模块必须低功耗、低延迟。我曾经踩过一个坑——唤醒词模型太大,导致车机待机时CPU占用过高,电瓶亏电。
- 语音识别(ASR)前端:将唤醒后的语音片段进行特征提取,准备发送给云端做完整识别。端侧也会做一部分轻量级识别,比如本地命令词。
- 本地技能引擎:处理一些不需要联网的技能,比如“打开车窗”、“调高音量”。这些指令必须秒级响应。
- 音频播放模块:负责合成语音(TTS)的播放,以及系统提示音的管理。
核心原则:端侧负责“快”和“稳”,云侧负责“强”和“全”。
2.2 云侧架构:云端的大脑
云侧,说白了就是后端服务集群。它处理端侧搞不定的复杂任务。比如,你想问“附近有什么好吃的川菜馆”,这需要联网搜索,端侧做不了。
云侧架构的模块划分,我习惯这样分:
- 接入层:负责接收端侧发来的音频流或文本。通常用WebSocket或HTTP/2协议。这里要做好负载均衡和鉴权。我记得有一次,某个车型的并发量突然暴增,接入层没做限流,直接把后端打挂了。
- 语音识别(ASR)引擎:将音频流转换成文本。这是云侧的核心能力之一。它支持多语种、多方言、自定义热词。比如,你车机里有个“导航到望京SOHO”,这个“望京SOHO”就得加到热词表里。
- 自然语言理解(NLU)引擎:理解文本的意图。比如,“我有点冷” -> 意图是“调节空调温度”,槽位是“温度调高”。NLU的准确率直接决定了用户体验。
- 对话管理(DM)引擎:管理多轮对话的状态。比如,你说“导航到天安门”,系统问“走高速还是普通路”,你回答“高速”。DM要记住刚才的上下文。
- 技能执行层:调用具体的服务。比如,导航技能调用地图API,音乐技能调用音乐库API。
- 语音合成(TTS)引擎:将文本转换成自然流畅的语音。现在流行用神经网络TTS,声音更自然。
个人经验:云侧架构一定要设计成无状态的。这样方便水平扩展。我见过一个项目,把用户会话状态存在本地内存里,结果一扩容,用户全掉线了。
2.3 模块划分:一张表说清楚
为了方便你理解,我把端侧和云侧的核心模块整理成了一张表。你设计系统时,可以直接参考这个划分。
| 层级 | 模块名称 | 核心职责 | 部署位置 |
|---|---|---|---|
| 端侧 | 音频采集 | 麦克风阵列管理、AEC、降噪 | 车机DSP/CPU |
| 端侧 | 唤醒引擎 | 关键词检测、低功耗监听 | 车机NPU/CPU |
| 端侧 | 本地ASR | 本地命令词识别 | 车机CPU |
| 端侧 | 本地技能 | 车辆控制、本地设置 | 车机MCU/CPU |
| 云侧 | 接入层 | 连接管理、鉴权、限流 | 云端服务器 |
| 云侧 | ASR引擎 | 大词汇量连续语音识别 | 云端GPU/CPU |
| 云侧 | NLU引擎 | 意图识别、槽位填充 | 云端CPU |
| 云侧 | DM引擎 | 多轮对话、状态跟踪 | 云端CPU |
| 云侧 | TTS引擎 | 文本转语音 | 云端GPU/CPU |
2.4 数据流设计:语音是怎么变成动作的
数据流,就是语音信号从麦克风到最终执行动作的完整路径。我把它分成几个关键步骤:
- 音频采集:麦克风采集到模拟信号,经过ADC转换成数字信号。采样率通常是16kHz,16bit。
- 前端处理:做回声消除、降噪、波束成形。这一步很关键,直接影响识别率。我见过一个项目,没做回声消除,车机自己播放音乐时,语音识别全是音乐声。
- 唤醒检测:端侧唤醒引擎持续分析音频流。一旦检测到唤醒词,就启动后续流程。
- 音频上传:端侧将唤醒后的音频片段(通常是VAD检测到的语音段)压缩后上传到云端。常用编码格式有Opus、Speex。
- 云端识别:云端ASR引擎将音频转成文本。这里会用到语言模型和声学模型。
- 语义理解:NLU引擎分析文本,提取意图和槽位。比如,“导航到人民广场” -> 意图:导航,槽位:目的地=人民广场。
- 技能执行:DM引擎根据意图调用对应的技能。比如,导航技能调用地图SDK,计算路线。
- 结果返回:云端将执行结果(比如导航路线)和TTS文本返回给端侧。
- 端侧执行:端侧播放TTS语音,并执行具体操作(比如显示导航界面)。
避坑指南:我曾经遇到过一个性能问题——端侧上传音频时,没有做静音检测(VAD),结果把一大段静音也上传了。这不仅浪费带宽,还导致云端ASR识别出大量空文本。所以,一定要在端侧做好VAD。
2.5 信号流设计:谁在跟谁说话
信号流,关注的是模块之间的通信协议和接口。说白了,就是各个模块之间怎么“打招呼”。
我把它分为两类:
- 端侧内部信号流:通常通过进程间通信(IPC)或共享内存。比如,音频采集模块把处理后的音频数据通过共享内存传给唤醒引擎。这样做延迟最低。
- 端侧与云侧信号流:通常通过WebSocket或HTTP/2。WebSocket适合双向实时通信,比如音频流上传和TTS流式返回。HTTP/2适合请求-响应模式,比如查询天气。
这里我给出一个典型的信号流设计示例:
// 端侧 -> 云侧:音频流上传
// 使用WebSocket,二进制帧格式
{
"type": "audio_data",
"session_id": "abc123",
"sequence": 1,
"payload": "<base64编码的音频数据>"
}
// 云侧 -> 端侧:识别结果
{
"type": "asr_result",
"session_id": "abc123",
"text": "导航到人民广场",
"is_final": true
}
// 云侧 -> 端侧:技能执行结果
{
"type": "skill_result",
"session_id": "abc123",
"skill": "navigation",
"data": {
"route": "<路线数据>",
"tts_text": "已为您规划路线,全程10公里"
}
}
嗯,这里要注意一点:信号流设计一定要考虑超时和重试。网络是不稳定的。我建议端侧设置一个3秒的超时,如果云端没响应,就提示用户“网络开小差了”。
2.6 架构设计的几个关键原则
最后,我总结几条我在项目中反复验证过的原则:
- 端侧优先:能本地处理的,绝不走云端。这能保证基本体验。
- 异步解耦:端侧和云侧之间用消息队列解耦。比如,端侧上传音频后,不用等云端处理完,可以继续做其他事。
- 降级设计:云端挂了,端侧要能独立工作。至少本地命令词要能用。我见过一个项目,云端一挂,整个语音系统都瘫痪了,连“打开空调”都做不了。
- 可观测性:每个模块都要有日志和监控。出了问题,能快速定位。我曾经靠一条日志定位到一个NLU模型加载失败的问题。
好了,这一章的内容就到这里。你理解了端侧和云侧的划分,掌握了数据流和信号流的设计思路,接下来就可以深入到每个模块的细节了。下一章,我们聊聊音频采集和前处理——这可是语音系统的“耳朵”,搞不好,后面全白搭。