15、语音控制集成:Google Assistant集成、自定义语音指令、TTS播报
语音控制,说白了就是让车载系统能「听懂人话」。这年头开车时用手戳屏幕,既不安全也不优雅。我做过好几个车载项目,语音交互这块踩过的坑,比屏幕上的指纹还多。今天咱们就把 Google Assistant 集成、自定义指令、TTS 播报这三件事彻底聊透。
15.1 Google Assistant 集成:让车机拥有「原生大脑」
Google Assistant 是现成的语音交互方案。你想想看,用户在家跟 Google Home 说「今天天气怎么样」,上车后跟车机说同样的话,体验就无缝了。我个人习惯是优先用官方提供的 Assistant SDK,而不是自己从头搭语音识别。
15.1.1 集成前的准备工作
集成 Google Assistant 需要满足几个硬性条件:
- Android 5.0 以上系统(车机一般没问题)
- Google Play Services 已安装
- 设备通过 Google 认证(这个在车机上比较敏感,后面会讲)
- 网络连接(语音识别需要云端处理)
15.1.2 使用 Assistant SDK 实现基础交互
Google 提供了 Assistant SDK 的 Android 客户端库。核心流程是:录音 → 发送音频 → 接收响应 → 播报结果。下面是一个简化版的集成示例:
// 初始化 Assistant 客户端
AssistantGrpc.AssistantStub assistantStub = AssistantGrpc.newStub(channel);
// 创建音频输入流(从麦克风读取)
AudioRecord recorder = new AudioRecord(
MediaRecorder.AudioSource.MIC,
SAMPLE_RATE,
AudioFormat.CHANNEL_IN_MONO,
AudioFormat.ENCODING_PCM_16BIT,
BUFFER_SIZE
);
// 发送语音请求
AssistRequest request = AssistRequest.newBuilder()
.setAudioIn(audioBytes)
.build();
// 接收响应
StreamObserver<AssistResponse> responseObserver = new StreamObserver<AssistResponse>() {
@Override
public void onNext(AssistResponse response) {
// 处理 Assistant 的回复
if (response.getDialogStateOut().getSupplementalDisplayText() != null) {
String text = response.getDialogStateOut().getSupplementalDisplayText();
// 显示到 UI 上
runOnUiThread(() -> textView.setText(text));
}
}
@Override
public void onError(Throwable t) {
Log.e("Assistant", "出错了: " + t.getMessage());
}
@Override
public void onCompleted() {
Log.d("Assistant", "对话结束");
}
};
// 开始双向流式通信
assistantStub.assist(responseObserver);
嗯,这里要注意:双向流式通信意味着你一边录音一边发送,Assistant 一边处理一边返回结果。延迟控制很关键,我遇到过因为缓冲区设置太大,导致用户说完话 3 秒后才响应的尴尬情况。
15.1.3 车机场景的特殊处理
Google Assistant 在车机上有个大问题:驾驶安全。你不能让 Assistant 在开车时显示复杂的 UI 或者播放视频。Google 提供了 Car App 的专用 API,可以限制某些操作。
| 场景 | 允许的操作 | 禁止的操作 |
|---|---|---|
| 车辆行驶中 | 语音播报、简单回复 | 显示长文本、播放视频 |
| 车辆静止时 | 完整交互 | 无特殊限制 |
15.2 自定义语音指令:让车机听懂「行话」
Google Assistant 能听懂「打开空调」,但听不懂「把温度调到 23 度,风量小一点」这种复合指令。这时候就需要自定义语音指令了。说白了,就是自己定义一套「关键词 → 动作」的映射规则。
15.2.1 使用 Android 的 SpeechRecognizer
Android 自带的 SpeechRecognizer 可以离线识别,虽然准确率不如云端,但胜在速度快、不依赖网络。我一般在车机上做「热词唤醒」时用它。
// 创建语音识别器
SpeechRecognizer recognizer = SpeechRecognizer.createSpeechRecognizer(context);
// 设置识别监听
recognizer.setRecognitionListener(new RecognitionListener() {
@Override
public void onResults(Bundle results) {
ArrayList<String> matches = results
.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
if (matches != null && matches.size() > 0) {
String command = matches.get(0);
handleCommand(command);
}
}
@Override
public void onError(int error) {
Log.e("Voice", "识别错误: " + error);
}
});
// 启动识别
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, "zh-CN");
recognizer.startListening(intent);
15.2.2 指令解析与匹配
识别出文字后,怎么匹配到具体动作?我试过正则表达式、关键词匹配、甚至简单的 NLP 模型。对于车载场景,关键词 + 槽位提取 是最实用的。
举个例子:用户说「导航到天安门」
- 关键词:导航
- 槽位:目的地 = 天安门
代码实现可以这样:
public class CommandParser {
private static final Map<String, CommandAction> COMMANDS = new HashMap<>();
static {
COMMANDS.put("导航", new NavigateAction());
COMMANDS.put("打电话", new CallAction());
COMMANDS.put("播放", new PlayMusicAction());
COMMANDS.put("空调", new ClimateAction());
}
public CommandResult parse(String text) {
for (String keyword : COMMANDS.keySet()) {
if (text.contains(keyword)) {
// 提取槽位信息
String slot = extractSlot(text, keyword);
return new CommandResult(COMMANDS.get(keyword), slot);
}
}
return null; // 未匹配
}
private String extractSlot(String text, String keyword) {
// 简单提取:关键词后面的内容作为槽位
int index = text.indexOf(keyword) + keyword.length();
return text.substring(index).trim();
}
}
15.2.3 复合指令的处理
用户可能会说「打开空调,温度调到 23 度,风量调到 3 档」。这种复合指令需要拆解成多个子指令。我的做法是:
- 先用分隔符(比如「,」「然后」「并且」)把句子拆成多个子句
- 每个子句单独匹配关键词和槽位
- 按顺序执行(或者并行执行,看场景)
public List<CommandResult> parseCompound(String text) {
List<CommandResult> results = new ArrayList<>();
// 用常见分隔符拆分
String[] parts = text.split("[,,、然后并且]");
for (String part : parts) {
CommandResult result = parse(part.trim());
if (result != null) {
results.add(result);
}
}
return results;
}
15.3 TTS 播报:让车机「开口说话」
语音交互不只是听,还要说。TTS(Text To Speech)就是把文字转成语音播报出来。Android 自带的 TextToSpeech 引擎已经够用,但车机场景有一些特殊要求。
15.3.1 使用 Android TextToSpeech
基础用法很简单:
TextToSpeech tts = new TextToSpeech(context, status -> {
if (status == TextToSpeech.SUCCESS) {
tts.setLanguage(Locale.CHINESE);
tts.setSpeechRate(0.9f); // 语速稍慢,适合驾驶场景
tts.setPitch(1.0f);
}
});
// 播报文本
tts.speak("前方 500 米有测速摄像头", TextToSpeech.QUEUE_FLUSH, null, "speed_camera");
// 播报完成后释放资源
tts.setOnUtteranceProgressListener(new UtteranceProgressListener() {
@Override
public void onDone(String utteranceId) {
// 播报完成
if ("speed_camera".equals(utteranceId)) {
// 可以执行后续操作
}
}
});
15.3.2 车机 TTS 的优化技巧
我在项目中总结了几条 TTS 的优化经验:
- 语速控制: 驾驶场景下语速建议 0.8-0.9 倍,太快了用户听不清
- 音量自适应: 根据车速自动调整 TTS 音量(车速越快,音量越大)
- 打断策略: 如果用户正在听音乐,TTS 播报时应该降低音乐音量(闪避效果)
- 缓存机制: 常用播报内容(比如「导航开始」「已为您规划路线」)可以预生成音频文件,避免每次实时合成
15.3.3 自定义 TTS 引擎
如果觉得 Android 自带的 TTS 声音太机械,可以接入第三方 TTS 引擎(比如科大讯飞、百度)。这些引擎支持更自然的语音、多音色选择,甚至情感表达。
// 以讯飞 TTS 为例
SpeechSynthesizer synthesizer = SpeechSynthesizer.createSynthesizer(context, null);
// 设置参数
synthesizer.setParameter(SpeechConstant.VOICE_NAME, "xiaoyan"); // 选择音色
synthesizer.setParameter(SpeechConstant.SPEED, "50"); // 语速
synthesizer.setParameter(SpeechConstant.VOLUME, "80"); // 音量
// 开始合成
synthesizer.startSpeaking("前方路口请右转", new SynthesizerListener() {
@Override
public void onCompleted(int error) {
if (error == 0) {
// 合成成功
}
}
});
15.4 知识体系总览
下面这张图把本章的核心逻辑串起来了。从语音输入到指令解析,再到动作执行和语音反馈,形成一个完整的闭环。
从图上你能看到,整个语音交互是一个闭环:用户说话 → 识别 → 解析 → 执行 → 播报反馈。每个环节都有优化空间,也都有坑。我当年第一次做车载语音时,忽略了「播报反馈」这一步,结果用户说了指令后车机没任何反应,用户以为没识别到,又重复说了一遍... 嗯,从那以后我特别重视反馈的及时性。
- Google Assistant 适合通用场景,自定义指令适合车机专属功能
- 复合指令需要拆解,槽位提取要精准
- TTS 播报要考虑驾驶安全,语速、音量、打断策略都要调优
- 整个交互闭环要流畅,延迟控制在 1 秒以内