玩家语音作为游戏输入 2026:端侧 ASR 工程化
把玩家嘴里的声音变成游戏里的动作,难点从来不在 ASR 模型本身,而在采集层选错音频源、VAD 切错句子、唤醒词漏掉指令、端到端延迟吃掉手感这四件事上——本文给出一套端侧语音输入链路的工程化拆解:从平台麦克风 API 的真实差异,到 16kHz 单声道流的端点检测策略,到模型体积与延迟的双预算分配,再到热词偏置、误触发治理和无障碍延伸,最后给出一份可以照着做上线门禁的度量清单。
约 47 分钟阅读13,837 字5 次阅读博主

把玩家嘴里的声音变成游戏里的动作,难点从来不在 ASR 模型本身,而在采集层选错音频源、VAD 切错句子、唤醒词漏掉指令、端到端延迟吃掉手感这四件事上——本文给出一套端侧语音输入链路的工程化拆解:从平台麦克风 API 的真实差异,到 16kHz 单声道流的端点检测策略,到模型体积与延迟的双预算分配,再到热词偏置、误触发治理和无障碍延伸,最后给出一份可以照着做上线门禁的度量清单。

一句话摘要:把玩家嘴里的声音变成游戏里的动作,难点从来不在 ASR 模型本身,而在采集层选错音频源、VAD 切错句子、唤醒词漏掉指令、端到端延迟吃掉手感这四件事上——本文给出一套端侧语音输入链路的工程化拆解:从平台麦克风 API 的真实差异,到 16kHz 单声道流的端点检测策略,到模型体积与延迟的双预算分配,再到热词偏置、误触发治理和无障碍延伸,最后给出一份可以照着做上线门禁的度量清单。
键鼠和手柄的输入是离散、确定、无歧义的:一次按下就是一个事件,事件带时间戳和坐标。语音输入完全不同,它是连续、有噪、语义模糊的一段模拟信号。想把"往左走两步"这句话变成游戏里的位移,中间要跨过采集、端点检测、识别、意图解析、执行五道关卡,每一道都会引入延迟和错误率。
更麻烦的是,游戏语音输入的使用环境比通用语音助手恶劣得多。手机游戏里玩家可能在地铁、咖啡馆、construction 现场外放,背景有 BGM、有环境噪声、有队友在喊;PC 上玩家可能开着语音房和四个队友同时说话,麦克风同时采集了游戏外放的声音——这叫回声,是最容易让语音指令系统彻底失效的单一因素。
所以做一个语音输入系统,工程决策的重心不在"选哪个 ASR 模型",而在下面这四件事上:
VOICE_RECOGNITION 和 VOICE_COMMUNICATION 走的是完全不同的 DSP 链路,选错了不是掉点的问题,是整个特征分布都错。本文就按这四件事的顺序来组织。先说明选题边界:本文只讨论游戏场景下把玩家语音转成游戏内动作这一件事,不写 AIGC 音频生成(TTS、音乐生成、音效合成),不写直播解说的语音管线,也不写内容审核里的语音识别分支——那些场景虽然同样用 ASR,但方向是反的:审核是把语音变成文本送进风控,直播是把语音变成文本送进解说生成,只有游戏输入是把语音变成玩家意图并直接驱动玩法状态。
Android 的 MediaRecorder.AudioSource 定义了一组采集源。官方文档把它们逐一列了出来,几个关键的:
MIC:普通麦克风音频源。VOICE_RECOGNITION:文档原文是 "Microphone audio source tuned for voice recognition"——为语音识别调优的麦克风源。VOICE_COMMUNICATION:文档原文是 "Microphone audio source tuned for voice communications such as VoIP"——为语音通信调优。UNPROCESSED:文档原文是 "Microphone audio source tuned for unprocessed (raw) sound if available, behaves like DEFAULT otherwise"——注意这个 "if available",不支持的设备上会静默退化成 DEFAULT。VOICE_CALL:语音通话的上下行合一,文档明确写了 "Capturing from VOICE_CALL source requires the Manifest.permission.CAPTURE_AUDIO_OUTPUT permission"——这是一个额外的危险权限。VOICE_PERFORMANCE:为实时处理和回放准备的源,文档举例是卡拉 OK。CAMCORDER:为录像调优,方向与相机一致。工程结论一:做 ASR 就用 VOICE_RECOGNITION,不要用 MIC。MIC 是给"录一段用户语音"用的通用路径,链路里可能带着为播放场景设计的增益控制和降噪优化;VOICE_RECOGNITION 的处理目标就是"尽可能忠实地把语音特征送出来"。这两条路在物理麦克风灵敏度、AGC 行为、频响加权上都可能不同,换源等于换了一组特征分布,你的模型精度会莫名其妙地掉一截。
工程结论二:UNPROCESSED 听起来很美,但它的可用性由设备决定,且不保证。如果你的后续处理链自己做 AEC(回声消除)和降噪,用 UNPROCESSED 拿原始数据是最干净的;但如果你没打算自己做前端处理,用一个可能悄悄退化成 DEFAULT 的源是纯粹的赌博。
工程结论三:VOICE_CALL 需要的 CAPTURE_AUDIO_OUTPUT 是敏感权限,不要为了"效果好"去申请它。
AudioRecord.getMinBufferSize(int sampleRateInHz, int channelConfig, int audioFormat) 的官方说明有两句非常关键的话:
Returns the minimum buffer size required for the successful creation of an AudioRecord object, in byte units.
Using values smaller than getMinBufferSize() will result in an initialization failure.
也就是说,返回值是字节数不是采样点数,而且低于它会直接初始化失败。这里有个很容易踩的单位陷阱:如果你按"采样率 × 通道数 × 2 字节"去估算一个 200ms 的缓冲,那得到的是字节数;而如果你以为要传采样点数,那传给 getMinBufferSize 的三个参数和最后乘出来的结果会对不上。缓冲区还必须大于你单次读取的量,否则读取会阻塞在 read() 里,把整个游戏主线程或音频线程卡住。
AudioRecord 实现了一个 MicrophoneDirection 接口,定义了 MIC_DIRECTION_TOWARDS_USER(朝用户方向)、MIC_DIRECTION_AWAY_FROM_USER(背用户方向)、MIC_DIRECTION_EXTERNAL(设备外接麦克风)、MIC_DIRECTION_UNSPECIFIED(不做方向性处理)。这对折叠屏和平板尤其有意义:同一个物理设备的两半可能朝向不同,用错方向会让玩家说出来的话被设备另一侧的麦克风主导。
Apple 文档对 AVAudioSession 的定义只有一句话,但这句话就是全部的要点:
An object that communicates to the system how you intend to use audio in your app.
它是一个意图声明对象。游戏同时要播 BGM、要播语音、要录麦克风,还可能要在语音聊天和游戏语音指令之间切换,这些意图之间的组合决定了系统给不给你的路由、会不会被别的 App 抢占、以及切后台时怎么收尾。这件事必须在架构阶段就画出来,不能等到接入 ASR 时再补。
Unity 的 Microphone 类把采集抽象成一个录音会话:devices 静态属性给出所有可用麦克风的名字列表,Start / End 控制会话,GetDeviceCaps 返回某个设备的频率能力,GetPosition 返回当前录音位置(以采样计),IsRecording 查询状态。文档特别提醒:Unity Web 上 Microphone 类需要用户授权,得先调 Application.RequestUserAuthorization。
Godot 的 AudioEffectCapture 走的是完全不同的思路——它挂在音频总线(Audio Bus)上,把总线上的音频采样取出来。关键细节有三个:
get_buffer(frames) 返回 PackedVector2Array,即交错的双通道 32 位浮点 PCM。get_frames_available() 返回当前可读样本数。get_pushed_frames() 返回因为缓冲区满而被丢弃的样本数——这是最重要的一个,它是溢出检测器。Godot 这套设计有一个直接后果:你拿到的是双通道 32 位浮点数据,而绝大多数 ASR 模型要的是单声道 16kHz 16 位 PCM。中间要自己做降采样和降混。而且因为它是总线侧的捕获,游戏自己的外放声音也会被一起捕获——回声问题在 Godot 这条路上是默认存在的,不是可选项。
Silero VAD 是目前被广泛使用的轻量级 VAD,官方 README 给的性能描述是:
One audio chunk (30+ ms) takes less than 1ms to be processed on a single CPU thread.
以及:
JIT model is around two megabytes in size.
30ms 一块、单线程不到 1ms、JIT 模型约 2MB——这三个数字合起来说明 VAD 在移动端几乎不构成预算压力。很多人设计语音链路时,第一反应是"要不要上 VAD",答案是要,而且几乎免费。
还有一条常被忽略的增益:Silero README 提到 "Under certain conditions ONNX may even run up to 4-5x faster",也就是说换用 ONNX Runtime 而不是原生 JIT,在某些条件下还能再快 4-5 倍。
VAD 在语音输入链路里承担三个职责,缺一不可:
职责一:切出语音段。ASR 模型按固定窗口吃音频,如果把静音和呼吸声一起喂进去,模型的输出会包含大量幻觉(在没有语音的地方生成文字)。幻觉在游戏里的破坏力比在转写软件里大得多——因为我们会把识别结果直接送进指令解析器。
职责二:提供端点(endpointing)。玩家说完最后一个字之后,系统要判断"说完了"才能提交识别。这个判断错了,代价是双向的:判早了会把句子截断,判晚了会让交互显得迟钝。
职责三:把唤醒词检测和全量识别解耦。唤醒词引擎自己带 VAD,全量识别也带 VAD,但两者的 VAD 参数可以不同——唤醒词要极低延迟(宁可误触发),全量识别要高准确(宁可晚一点)。用同一套 VAD 参数服务两者,必然有一头不舒服。
按误触发代价从高到低,常见的策略是:
| 策略 | 机制 | 延迟 | 误触发 | 适合场景 |
|---|---|---|---|---|
| 按住说话(push-to-talk) | 按键期间持续采集 | 最低(松开即知) | 最低 | 竞技射击、语音社交 |
| 短静音断句 | 采集持续进行,VAD 判定静音超过 N 毫秒则提交 | 中 | 低 | 移动端语音指令、语音导航 |
| 流式增量提交 | 边说边出字,每个 chunk 都更新指令预览 | 最低但可能抖动 | 中 | 实时字幕、长句输入 |
一个务实的结论是:不要一上来就追求全自动。绝大多数商业语音指令玩法是从"按住说话"起步的,因为它把回声消除、误触发、断句三个难题一次性绕开了。当产品真的需要免手操作时,再逐步引入唤醒词。
当游戏正在外放 BGM 或语音时,麦克风采到的是"人声 + 游戏外放"的混合信号。ASR 会同时听到两路声音,识别结果可能把游戏角色的台词当成玩家指令——这在叙事游戏里能直接触发剧情分支。
工程上有两条路:
路线 A:拿干净的前端。用带 AEC 的音频源(Android 上的 VOICE_COMMUNICATION 就是为 VoIP 场景调优的,内置 AEC 和回声抑制),或者用 UNPROCESSED 拿原始数据后自己做 AEC。前者简单,后者灵活但要自己维护 AEC 实现。
路线 B:拿参考信号。如果你的引擎架构里,游戏的输出音频是可知的(Godot 的总线捕获模型天然满足),那么把输出音频作为参考信号送进 AEC 是最准的做法。
无论哪条路,验证方法都是一样的:戴耳机播放游戏 BGM,麦克风不接声,测试指令识别。如果这时候能稳定识别出你并没有说过的话,那就是 AEC 没起作用。
真实的采集链路会经历一连串格式变换:Android 的 AudioRecord 可以直接按 16kHz 单声道 16 位开;Unity 的 Microphone 录成 AudioClip;Godot 给的是双通道 32 位浮点。必须在进入模型前把格式收敛到唯一一种,否则每条采集路径都会训出不同的行为。
统一目标建议是:16kHz / 单声道 / 16 位有符号 PCM。这是 sherpa-onnx 官方文档里 FeatureExtractorConfig 的默认值(sampling_rate = 16000,feature_dim = 80,low_freq = 20,high_freq = -400),也是绝大多数 ASR 模型的训练规格——用这个规格意味着你可以直接用现成的预训练模型,而不用重训。
端侧语音识别目前基本分成两派:
通用自回归解码派,代表是 Whisper 及其 C/C++ 移植 whisper.cpp。官方 README 给出的内存占用表非常直观:
| Model | Disk | Mem |
|---|---|---|
| tiny | 75 MiB | ~273 MB |
| base | 142 MiB | ~388 MB |
| small | 466 MiB | ~852 MB |
| medium | 1.5 GiB | ~2.1 GB |
| large | 2.9 GiB | ~3.9 GB |
这张表直接给出了移动端的选型边界:tiny 和 base 是唯一现实的选项,small 开始就已经不适合中低端机,large 更是与移动端无关。而且 whisper.cpp 官方 README 里 Limitations 一节写的是 "Inference only"——它只做推理,训练要回 Python 侧。
这条路线在 Apple Silicon 上的表现有官方说明:
On Apple Silicon devices, the Encoder inference can be executed on the Apple Neural Engine (ANE) via Core ML. This can result in significant speed-up - more than x3 faster compared with CPU-only execution.
而在 Metal 上,"the inference runs fully on the GPU"。
专用流式 transducer 派,代表是 sherpa-onnx 支持的 Paraformer、Zipformer 系列。这类模型为流式设计,天然有更小的体积和更低的延迟。sherpa-onnx 官方文档列出的 sherpa-onnx-streaming-paraformer-bilingual-zh-en 目录里实际文件大小是:
encoder.onnx 607M / encoder.int8.onnx 158Mdecoder.onnx 218M / decoder.int8.onnx 68Mint8 量化后合计约 226M,比 whisper.cpp 的 base(388M 内存)小一截,而且流式模型可以边说边出。
看上面的文件大小:encoder.onnx 607M → encoder.int8.onnx 158M,是3.8 倍的体积压缩。代价通常体现在两个地方:词表/语言模型的部分精度损失,以及对某些音素(尤其是中文的细粒度音素)的区分度下降。
工程上的取舍建议:
sherpa-onnx 官方文档的功能清单对做游戏语音输入非常对口,逐项看:
它支持的平台覆盖 Android、HarmonyOS、iOS、Flutter、Tauri Desktop Apps、WebAssembly、WebSocket,API 覆盖 C/C++/Python/C#/Dart/Go/Java/JavaScript/Kotlin/Swift/Rust。这对小游戏尤其重要:微信小游戏跑在 Web 环境里,WebAssembly 后端意味着 ASR 可以直接跑在小游戏里而不必绕服务端。
如果游戏本身有语音聊天功能(组队开黑、直播连麦),那么语音链路已经存在了,ASR 可以作为它的下游消费者而不是重新采集一遍。这条路径省掉了麦克风采集、回声消除这一整段最麻烦的工程量。
代价是延迟变长:语音房的数据已经走了一圈网络再到 ASR 侧。sherpa-onnx 生态里的 livekit/agents 正是做实时语音 agent 的框架(Apache-2.0,GitHub 上 14948 星,最近推送 2026-10-27),它的存在本身就是这条路线的验证。
决策建议:如果游戏有语音房,语音指令优先复用语音房流;如果游戏没有语音房且指令是核心玩法,就自己做本地采集,牺牲一点延迟换掉整条网络链路。
如果 ASR 全程常驻,那么玩家不说话的时候,麦克风采集、唤醒词检测、VAD、特征提取这几步全在耗电——哪怕最后一步 ASR 被跳过了,前面的开销也是在的。唤醒词引擎是一道独立的、极轻量的闸门:只在它触发之后才启动 ASR。
两个可选方案:
sherpa-onnx 自己也有 Keyword spotting 模块,下一节会看到它的参数细节。
sherpa-onnx 的 KWS 文档给了 sherpa-onnx-kws-zipformer-zh-en-3M-2025-12-20(中英双语)的完整配置和输出格式。文件大小非常小:
encoder-epoch-13-avg-2-chunk-8-left-64.int8.onnx 4.4Mencoder-epoch-13-avg-2-chunk-16-left-64.int8.onnx 4.4Mdecoder-epoch-13-avg-2-chunk-16-left-64.onnx 743Kjoiner-epoch-13-avg-2-chunk-8-left-64.onnx 331Ktokens.txt 1.9K4.4MB 的编码器就能做中英双语关键词检测——和前面动辄几百 MB 的 ASR 模型相比,这个量级让唤醒词成为可以在任何手机上常驻的东西。
文档里还有一条极其重要的 latency 说明:
The models with chunk-16 in their names are exported for chunk_size=16, while those with chunk-8 in their names are for chunk_size=8. The chunk_size=8 model has a latency of 160ms, while the chunk_size=16 model has a latency of 320ms, please choose the right models according to your needs (lower latency usually means lower accuracy).
这是整篇文章最值得记住的一个数字对:chunk 大小是延迟和精度的直接旋钮,8 对 160ms,16 对 320ms,一倍的延迟换精度。这正是唤醒词要"宁可误触发"的技术根据。
另一个细节:keywords_score = 1、keywords_threshold = 0.25,这两个参数直接决定了触发灵敏度。而关键词本身来自一个纯文本文件(keywords.txt),意味着改指令词表不需要重新训练模型——这是热词机制相对唤醒词模型最大的运维优势。
这两者的区别值得单独说清楚,因为工程实践中经常被混为一谈:
| 唤醒词 / 关键词检测 | 热词偏置(Contextual Biasing) | |
|---|---|---|
| 作用位置 | 模型之前,决定要不要启动 ASR | ASR 解码过程内部,把先验概率偏向指定词 |
| 召回目标 | 极小的词表(1~几个) | 较大的开放词表(几十~几百) |
| 误触发代价 | 唤醒后 ASR 空转,浪费电和算力 | 直接把指令解析错了 |
| 典型用途 | "小助手小助手" | 角色名、技能名、地图名 |
| 可否动态更新 | 通常要重训或换模型 | 改配置文件即可 |
sherpa-onnx 官方文档在功能清单里同时列了 Hotwords (Contextual biasing) 和 Keyword spotting,还额外提供了一项 "拼音词组匹配替换" 功能——这对中国玩家尤其有用,因为它允许用拼音输入汉字词组,对手机端输入法的迁移场景是直接可用的。
从工程角度看,指令词表的每一个设计决定都会直接改变识别准确率:
一次语音指令的端到端延迟由五段构成:
[ 采集窗口 ] [ VAD 判定 ] [ ASR 首字 ] [ 后处理 ] [ 指令执行 ]
↑ ↑ ↑ ↑ ↑
缓冲填满 静音判定 模型解码 标点/意图 状态变更
关键点在于:第一段和第二段是串行等待,而第三段可以用流式解码与前两段重叠。
采集窗口:这是被最多人忽略的一段。VAD 要确认"有人在说话"至少需要几十毫秒的音频,判断"说完了"还需要一段静音尾巴。一个常见的经验值是:说话结束后的静音尾长设 500-800ms,能在断句准确率和响应速度之间取得较好平衡。调到 200ms 会频繁截断,调到 1500ms 会让每个指令都显得迟钝。
VAD 判定:Silero 单块 30ms 音频的处理时间不到 1ms,所以这一段的计算成本可忽略,但等待成本不可忽略——它包含在采集窗口里。
ASR 首字:这是唯一真正需要优化的一段。
stream 工具每半秒采样一次音频并持续转写。也就是说这个官方示例的天然延迟粒度就是 500ms 级别——对游戏指令来说偏粗,需要更细的分块策略。后处理:标点恢复、意图解析、置信度门限。这一段应该尽可能短。很多团队在这里犯的错是引入一个 LLM 来解析指令——如果你的指令是封闭集合,规则匹配或一个小的意图分类模型就够了,LLM 的首 token 延迟会把整个预算吃光。
指令执行:这一段应该是零成本的,因为它只是改游戏状态。前提是状态变更在主线程的下一帧完成,不要在音频线程里直接改场景。
不要用"用户说完到我觉得有反应"来估。应该埋点记录四个时间戳:最后一个采样点写入缓冲的时刻、ASR 返回首字的时刻、意图解析完成的时刻、指令实际生效的时刻。然后看 P50 / P90 / P99,尤其要看 P99——语音链路的尾延迟往往是被某个偶发路径拉高的(比如误判为静音导致重新开始、或者模型文件被系统换出内存后首次加载)。
一个必须提前处理的坑:模型首次加载时间。中低端机上加载一个 158MB 的 ONNX 模型可能需要数秒。必须在游戏启动流程里预加载并预热(跑一遍空推理把内存页建立好),绝不能等玩家第一次说话时才加载。
如果指令词表是封闭的(几十条),那么意图解析的正确做法是归一化 + 精确匹配 + 编辑距离兜底:
原始识别文本
→ 全角转半角 / 去标点 / 去语气词
→ 拼音纠错(针对中文同音错字)
→ 精确匹配词表
→ 未命中则计算与词表的编辑距离,低于阈值则接受并记录
→ 仍未命中则丢弃(而不是"猜一个最接近的")
最后一条尤其重要:宁可丢掉一次指令,也不要猜错。猜错一次,玩家按下的技能键位和实际触发的效果对不上,信任就崩了。
几乎所有 ASR 引擎都会给出置信度或 token 级别的概率。把它用起来:
这个三段式在语音输入系统里是必需品,因为语音的误触发代价远高于触屏——玩家没法"没按到",因为按错了就是按错了。
把语音指令接到游戏状态时,最容易出问题的地方是语义歧义。"使用技能"——用哪个技能?在有目标选择的状态下,语音应该说"治疗"而不是"用技能"。所以语音指令的解析必须读当前的游戏状态,而不是做一个无状态的文本到枚举的映射。
这也意味着:语音指令不是可以后加的功能。如果你在写玩法系统的时候没有预留"可被外部事件驱动的动作接口",后期接语音就得去改玩法代码。
# 伪代码:把"按住说话"到"动作生效"的链路串起来
class VoiceCommandPipeline:
def __init__(self, asr, vad, matcher, min_conf=0.55):
self.asr, self.vad, self.matcher = asr, vad, matcher
self.min_conf = min_conf
self.tail_silence_ms = 600 # 断句静音尾长,按 §6.2 调
self.ring = RingBuffer(pcm16_mono_16k)
def on_audio(self, frame): # frame: 16k mono int16
self.ring.push(frame)
self.vad.push(frame)
if self.vad.speech_started() and not self.streaming:
self.streaming = True
self.asr.begin_stream()
if self.streaming:
self.asr.push(self.ring.pop_new())
if self.vad.silence_for(self.tail_silence_ms):
text, conf = self.asr.finalize()
self._dispatch(text, conf)
def _dispatch(self, text, conf):
norm = normalize(text) # 全角/标点/语气词
cmd = self.matcher.match(norm) # 精确 -> 编辑距离
if cmd is None or conf < self.min_conf:
self.ui.hint("没听清") # 宁可丢掉,不要猜错
return
self.game.execute(cmd, source="voice") # 主线程下一帧生效
注意 self.ring 这个结构:它是为什么需要 AudioEffectCapture 类"溢出检测"的原因。Godot 的 get_pushed_frames() 就是告诉你有多少样本被丢弃了——如果这个数在涨,说明你的消费速度跟不上生产速度,你正在丢音频,而丢音频在 ASR 看来就是"句子断了一下",会直接变成识别错误。
前文的全站词频扫描里,"无障碍""视障""听障""字幕""手语"这类词在全站文章中命中为 0。这说明这是一个被低估的方向,而不是一个不值得写的方向。
对使用屏幕阅读器的视障玩家来说,语音指令几乎是一种必需品而不是加分项。如果你的游戏有"无障碍模式"的设计,语音指令应该是其中优先级最高的一环之一。
工程上的额外考虑:
如果游戏有语音聊天,语音指令系统在语音房里会遇到一个结构性的矛盾:玩家在跟队友说话时,指令系统会不会把队友的话也当成指令?
处理办法有三种,按成本排序:
把这三条叠加起来是一个相当实用的组合:词表匹配挡掉无关内容,置信度挡掉模糊内容,声纹挡掉"队友恰好说了个指令词"的情况。
语音输入的引入会带来一个新的合规面:你在采集什么、存不存、传不传。前文反复强调过要本地化推理(端侧 ASR 的一个直接好处就是原始音频不出设备),这不仅是隐私考量,也是成本和延迟考量——如果音频要上云做识别,那么每个玩家每次说话都是一次网络往返和一笔 API 费用。
此外,Android 的 SpeechRecognizer 提供了 createOnDeviceSpeechRecognizer(Context)(文档标注 Added in API level 31),也定义了 ERROR_INSUFFICIENT_PERMISSIONS 这类错误码。这类系统级 API 可以作为轻量场景的补充,但它的可用性依赖设备与系统版本,不能作为主链路——它的行为控制和错误处理粒度都不够做商业化玩法。
语音指令系统必须有一套自己的观测指标,不能只看"玩家用了没有"。建议至少这六项:
| 指标 | 定义 | 为什么重要 |
|---|---|---|
| 唤醒率 | 唤醒词触发次数 / 实际说话次数 | 唤醒词模型的灵敏度是否合适 |
| 误唤醒率 | 无人说话时触发次数 / 在线时长 | 直接决定玩家会不会关掉这个功能 |
| 指令准确率 | 正确执行数 / 识别出的指令数 | 端到端真实效果,不是 WER |
| 端到端延迟 P50/P90/P99 | 最后采样点到指令生效 | 决定"手感",尤其看 P99 |
| 首次响应延迟 | 首次说话到反馈 | 包含模型加载、首次预热 |
| 拒识率 | 因置信度不足被丢弃的比例 | 置信度门限是否设得太严 |
通用 benchmark 对游戏指令几乎没有参考价值——你需要一个自己的、覆盖真实噪声环境的评测集:
有了这个集合,量化选型、门限调参、模型替换就都是可回归的工程操作,而不是靠手感。
一个可以照抄的最小门禁清单:
get_pushed_frames() 之类)在压力测试下不增长。把前面散落的坑汇总成六条,这是本文最想留下的东西:
MIC 而不是 VOICE_RECOGNITION,或者在 Android 上用了 VOICE_CALL 招来敏感权限。语音输入在游戏里的天花板,取决于两件事:声学前端的质量,和指令解析的确定性。前者有大量成熟方案——Silero VAD 的 30ms 单块不到 1ms、sherpa-onnx 的 4.4MB 中英双语关键词编码器、whisper.cpp 的 Core ML ANE 三倍以上加速——都已经是可以直接依赖的工程件。后者只有一条路:把能枚举的都枚举掉,把不能确定的都拒掉。
从"按住说话"起步,把回声消除、端点检测、误触发治理这三件事做扎实,再考虑唤醒词和开放语义。这条路径的每一步都有可测量的验收标准,也都有明确的失败判据——这正是它值得写下来的原因。
一句话摘要:把玩家声音变成游戏动作的关键不在 ASR 模型选型,而在采集层选对音频源、VAD 切对句子、唤醒词不漏指令、端到端延迟守住 300ms 这四件事——本文给出一套端侧语音输入链路的工程化拆解与上线门禁。
Conversation
0 条