跳转至

唤醒、识别、对话与播报

本页面向排查语音链路、接入新业务和维护 Ali flavor 的工程师,描述当前 Ali + Headless 路径。安装准备见快速开始,跨进程展示见 Omni 接入

当前实现与历史描述

Ali 不是只有占位代码。AliASRAdapter 仍是返回空流的兼容适配器,但实际实时识别通过 AliDialogueRepositoryImpl 内的 MultiModalDialog 完成;TTS 使用 SDK 产生的音频,再由语音助手本地播放器播放。

同样,工厂注释中的“Stub 实现”不能代表整条 Ali 链路。判断能力时应沿实际依赖注入与 DialogueRepository 调用路径追踪。

组件 当前职责
KWSCoordinator 常驻唤醒/命令词监听与分发
DialogueCoordinator 初始化、启动和结束会话,处理外部播报请求
DialogueStateManager 事件订阅、会话与录音衔接、TTS 播放调用
AliDialogueRepositoryImpl SDK 会话、音频输入、回调处理、后端路由与文本播报
AliCallbackAdapter / AliEventMapper 将 SDK 回调转换成统一 ChatEvent
A2aBridge 最终 ASR 的后端落域请求与业务结果转换
DialogueEventBus 将事件分发给展示、控制与播放链路
GrpcEventBroadcaster 向 Omni 推送文字与状态
AppCtrlDispatcher 按业务结果协调应用启动与待执行命令

一次完整对话

sequenceDiagram
    participant U as 用户
    participant K as KWS / Service
    participant D as 对话协调与录音
    participant A as Ali SDK
    participant B as A2aBridge
    participant P as 本地 TTS 播放器
    participant O as Omni Launcher
    U->>K: 唤醒词
    K->>K: 校验登录状态
    K->>D: 启动对话会话
    D->>A: 建立 SDK 会话
    D->>A: 连续上送 PCM
    A-->>D: ASR partial / final
    D-->>O: 用户文本与 THINKING
    D->>A: 中断当前轮自动回复
    D->>B: 最终 ASR / 上下文
    B-->>D: 业务结果 / 命令 / 播报文本
    D-->>O: 业务文字 / 动作卡片 / AppCtrl
    D->>A: 请求文本播报
    A-->>P: TTS 开始 / PCM / 流结束
    P-->>D: 本地播放完成
    D->>A: sendResponseEnded
    D-->>O: SDK 可聆听后进入 LISTENING

图中表示主路径,不代表每一轮都会产生 TTS 或应用控制。车控、媒体、本地支付代理等按业务结果分别执行,详见业务能力

1. 唤醒与会话启动

VoiceAssistantService 以麦克风前台服务承载常驻逻辑。在创建阶段绑定 KWS、ASR 协调器,启动业务分发和 gRPC 广播,并初始化对话协调器。

KWS 分发前检查登录态:未登录时发出登录引导并消费唤醒词,后续对话不会照常启动。排查“已检测到唤醒但没有会话”时应先确认这个前置条件。

当前 KWS 引擎由 KWS_ENGINE_PROVIDER 决定。Gradle 默认值为 CANGHAI,Ali 工厂也支持 ALI_NATIVE;构建属性可覆盖默认值。Ali 对话配置中的云端 KWS 默认关闭,因此不要把云端 KWS 开关与本地唤醒引擎混为一谈。

唤醒触发会话时先建立会话,再播放唤醒应答。对已有会话再次发出唤醒词会走重启路径,避免旧的“已启动”标记吞掉新一轮唤醒。TTS 播放期间 KWS 保持监听。

核心启动顺序是:

  1. 收集对话事件,确保新回调有消费者。
  2. 调用 startDialogueUseCase() 建立后端会话。
  3. 启动录音并上送 PCM。

不能颠倒第二步与第三步:仓库会忽略非活跃会话收到的音频,先录音可能丢掉用户第一段话。

Headless 的含义

HEADLESS_MODE 默认是 trueOverlayWindowCoordinator.showWindow() 在该模式只标记逻辑会话,不调用悬浮窗 addView;关闭时清除标记。

Headless 仍保留录音、业务处理、TTS 和事件消费,不意味着“只开 gRPC”或“无本地声音”。默认展示依赖 Omni,调试时不要以语音助手没有悬浮窗判定启动失败。

2. 录音与 Ali 配置

当前 AliDataModule 配置如下;这是本仓实现值,不是所有 Ali SDK 项目的通用要求。

参数 当前值/行为
链路模式 WEBSOCKET
对话模式 DUPLEX
上行类型 AudioOnly
上行格式/采样率 pcm / 16000 Hz
下行格式/采样率 pcm / 24000 Hz
中间文本 intermediateText="transcript"
对话超时默认值 30000 毫秒
云端 KWS enabled=false
ASR 断句相关配置 semanticPunctuationEnabledmaxSentenceSilencemultiThresholdModeEnabled 当前为 null,沿用服务端默认值

配置对象支持 RTC 等模式,但当前注入路径选定 WebSocket。URL、鉴权信息、工作空间和应用标识由项目配置注入,公开文档不提供可用凭据。

录音数据经过有界队列交给发送协程,队列容量当前为 100 个音频块。发送队列关闭或满时增加丢帧计数并记录告警;队列满意味着发送侧落后,不能仅靠忽略日志处理。

使用 DialogueInput.AudioChunk 上送录音,使用 RefAudioChunk 上送参考音频,TextMessage 承载文本请求。未启动会话时输入会被日志提示并忽略,因此应等待会话建立,不依靠发送调用返回证明输入已被云端接受。

3. SDK 回调到识别结果

SDK 状态回调和识别内容回调有不同含义:

输入 内部事件 用途
DIALOG_LISTENING Asr.SpeechStarted SDK 已准备聆听,不证明用户已经开口
识别中内容 Asr.TranscriptUpdate 更新用户识别文字
完成识别内容 Asr.TranscriptCompleted 触发最终文本处理
DIALOG_THINKING / DIALOG_RESPONDING Llm.MessageStart 对话输出阶段信号
DIALOG_IDLE Session.Stop SDK 会话空闲/停止信号

识别内容按会话、是否完成及文本去重。非空最终文本进入后端路由;如果最终文本只是 KWS 关键词,仓库会屏蔽落域并中断当前回复,避免把纯唤醒词当作业务问题。

与 Launcher 对接时,非空 final 会推动 ASSISTANT_STATE_THINKING。不要使用 DIALOG_LISTENING 推测真实 VAD 起点;更完整的事件字段见接口参考

4. 后端落域与业务执行

主路径收到最终 ASR 后调用 forwardFinalAsrToBackend()。它先中断 Ali 当前轮自动回复,再通过 A2aBridge.handleFinalAsr() 进行后端落域,避免同一输入同时产生两套业务结果。

suspend fun handleFinalAsr(
    asrText: String,
    contextId: String?,
    dialogId: String?,
): A2aBridgeResult

每次请求生成新的本地请求标记,取消前一个任务;返回后再次检查是否仍是当前请求,旧请求结果不会覆盖新一轮上下文。桥接结果可包含业务事件、播报文本、延迟命令以及新的上下文标识。

  • 业务事件进入事件总线,由展示和控制组件各自消费。
  • 非空播报文本通过 Ali 文本播报路径输出,当前轮已经中断过,不再为这次播报重复中断。
  • 桥接的延迟命令与 Business.Result.pendingCommands 的具体执行路径应分别追踪,不能仅以“收到了业务文字”证明外部应用已执行。
  • asrForwardEnabled=false 的调试路径会跳过后端路由,不会自动回退为另一套真实业务路由。

桥接请求失败会产生 ChatEvent.Error;gRPC 广播将错误转为通用用户提示,结束当前展示态。详细排查依据仍是本地对应轮次日志,而不是只看用户提示文本。

5. TTS 传输与真实播放完成

音频回调产生 Tts.AudioChunkDialogueStateManager 调用 TTS 用例播放。Ali 的 AliTTSAdapter 委托给 DefaultTTSPlayerAdapter,所以本地播放器是音频最终输出环节。

flowchart TD
    Start[Tts.StreamStarted] --> Speaking[进入 SPEAKING]
    Chunk[Tts.AudioChunk] --> Queue[本地音频缓冲与播放]
    End[Tts.StreamCompleted] --> Drain[表示上游流已结束]
    Queue --> Finished[Tts.PlayFinished]
    Finished --> Gate[检查播放完成与 SDK Listening]
    Listening[SDK Listening 回调] --> Gate
    Gate --> Ready[两项均满足后进入 LISTENING]

StreamCompleted 只说明服务器音频传完,播放缓冲可能仍有内容。仓库在 onPlaybackFinished() 中调用 SDK 的 sendResponseEnded(),再发出 PlayFinished

播报输出同时可作为参考音频交给 SDK。跟随聆听需要本地播放结束与 SDK Listening 两项同时满足;若只到达一项,Launcher 继续保持播报态。

当前 GrpcEventBroadcaster 不广播 TTS PCM,因此 Omni 不应重复播放音频。音频是否存在应检查本地播放与 SDK 回调,不应仅观察 gRPC 音频字段。

6. 中断、停止与调试输入

interrupt() 请求中断 SDK 当前回复;完整停止路径还需要停止本地 TTS、会话及录音。DialogueStateManager.stopDialogueSession() 负责将这些动作串联,单独停止 UI 不代表网络或录音已停止。

静默超时可能落后于真实语音活动。当前仓库使用单调时钟判断最近活动,过滤过期 SpeechTimeout;点单签约和仅文本播报阶段也有专门处理,不能把所有超时都等同于用户离开。

维护者还需要区分三种验证路径:

路径 会验证什么 不代表什么
真实唤醒 + 真实 ASR 设备录音、SDK 识别、业务与展示 仍需核验各业务外部应用结果
Mock 最终 ASR 文本事件、后端路由和后续展示 不证明麦克风与识别正常
Mock LLM 结果 事件解析、卡片和控制链路 不要求建立真实 Ali 会话

startDialogueSessionWithoutRecording() 用于仅文本播报,不启动录音。Mock LLM 管线可以只初始化事件观察;测试成功时必须注明输入路径,避免把注入结果当作真实语音端到端验收。

排障顺序与验收

现象 首先检查
唤醒无反应 KWS 引擎配置、初始化、录音权限和登录拦截
第一段话缺字 是否先建立会话再启动录音;是否出现会话非活跃/丢帧日志
有 ASR 无业务 纯 KWS 过滤、asrForwardEnabled、A2a 请求及过期结果过滤
有文字无声音 文本是否需要播报、SDK TTS 回调、本地播放器、设备音频路由
声音结束仍显示播报 PlayFinished 和 SDK Listening 是否都到达
会话提前关闭 当前轮活动时间、旧超时、会话中断事件和错误日志

单轮排查可沿 ASR_PARTIAL_FIRSTASR_FINALA2A_FORWARD_ENTRYA2A_BRIDGE_DONETTS_STREAM_STARTEDTTS_PLAY_FINISHED 关联时间线;无 TTS 的业务轮不会出现所有节点。

验收至少包含首次唤醒、连续两轮、播报中再次唤醒、空识别、后端失败、结束会话,以及 Headless 下 Omni 正常展示。不同板端的真实音频表现需要设备验证,静态代码核对不能替代实测。日志与音频诊断入口见调试与维护