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 认证(这个在车机上比较敏感,后面会讲)
  • 网络连接(语音识别需要云端处理)
⚠️ 注意: 国内车机如果无法访问 Google 服务,需要走第三方方案(比如百度、讯飞)。我在海外项目里用 Google Assistant,国内项目用自研方案,两边都做过。

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,可以限制某些操作。

场景 允许的操作 禁止的操作
车辆行驶中 语音播报、简单回复 显示长文本、播放视频
车辆静止时 完整交互 无特殊限制
💡 我的经验: 在行驶状态下,把 Assistant 的回复强制转为 TTS 播报,同时 UI 上只显示 2-3 个关键词。这样既安全又实用。

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 档」。这种复合指令需要拆解成多个子指令。我的做法是:

  1. 先用分隔符(比如「,」「然后」「并且」)把句子拆成多个子句
  2. 每个子句单独匹配关键词和槽位
  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 播报时应该降低音乐音量(闪避效果)
  • 缓存机制: 常用播报内容(比如「导航开始」「已为您规划路线」)可以预生成音频文件,避免每次实时合成
💡 我的习惯: 在 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 SpeechRecognizer 指令解析与匹配 自定义指令处理 执行动作 TTS 播报反馈 交互闭环完成

从图上你能看到,整个语音交互是一个闭环:用户说话 → 识别 → 解析 → 执行 → 播报反馈。每个环节都有优化空间,也都有坑。我当年第一次做车载语音时,忽略了「播报反馈」这一步,结果用户说了指令后车机没任何反应,用户以为没识别到,又重复说了一遍... 嗯,从那以后我特别重视反馈的及时性。

核心要点回顾:
  • Google Assistant 适合通用场景,自定义指令适合车机专属功能
  • 复合指令需要拆解,槽位提取要精准
  • TTS 播报要考虑驾驶安全,语速、音量、打断策略都要调优
  • 整个交互闭环要流畅,延迟控制在 1 秒以内
公众号:蓝海资料掘金营,微信deep3321