1. 端侧语音方案选型的核心逻辑
1.1 为什么端侧语音突然成了刚需
做端侧语音这个方向差不多三年了,最早接触是在一个智能家居中控项目上,当时客户要求离线唤醒加离线指令识别,云端方案直接就被否了。从那之后我陆续在车载、穿戴、工业手持设备上落地过好几套端侧语音方案,踩的坑比写的代码还多。
端侧语音模型说白了就是把语音识别(ASR)、语音合成(TTS)、关键词检测(KWS)这些能力从云端搬到设备本地跑。它解决的核心问题有三个:第一是隐私,用户的语音数据不出设备,这在医疗、金融、政务场景里是硬性要求;第二是延迟,云端往返动辄几百毫秒,端侧可以做到几十毫秒级响应,交互体验完全不一样;第三是离线可用,没有网络或者网络不稳定的环境下,设备照样能工作。
适合看这篇内容的人大概分三类:一是正在做智能硬件产品、需要选语音方案的嵌入式或应用开发者;二是想在自己项目里集成离线语音能力的全栈工程师;三是对端侧AI感兴趣、想了解工程落地细节的技术爱好者。不管你之前有没有接触过sherpa-onnx或者FunASR,我都会从选型逻辑讲到实操细节,尽量让不同基础的读者都能拿到可用的东西。
1.2 Audio8四类方案到底在分什么
无界方舟Audio8这个平台把端侧语音方案分成了四类,这个分类逻辑其实很讲究,它不是按模型架构分的,而是按应用场景和能力组合来分的。我理解下来大概是这么四个维度:
- 纯KWS关键词检测方案:只做唤醒词和固定指令词的检测,模型极小,资源占用最低,适合Always-On场景
- KWS+ASR组合方案:先唤醒再识别,兼顾低功耗和通用识别能力,是目前最主流的端侧语音架构
- 流式ASR方案:持续识别,不需要唤醒词,适合会议记录、实时字幕这类场景
- ASR+TTS全双工方案:能听能说,适合语音助手、对话机器人这类交互场景
这四类方案对应的模型大小、内存占用、CPU消耗差异非常大。我见过太多项目一开始选型没想清楚,上来就怼全双工方案,结果跑在低端芯片上卡成幻灯片,最后返工重来。所以选型的第一步不是看哪个方案功能最全,而是看你的硬件资源预算和实际交互需求。
1.3 选型前必须搞清楚的三个硬指标
在动手之前,我建议先把这三个指标算清楚,它们直接决定你能选哪类方案:
第一,可用内存。端侧语音模型加载后常驻内存,KWS模型通常几MB到十几MB,ASR模型从几十MB到几百MB不等,TTS模型也差不多。你要算的是设备总内存减去系统占用和其他业务占用之后,还剩多少给语音模块。我一般会留至少30%的余量,因为推理过程中还有中间张量的内存开销。
第二,CPU算力。这个不能只看主频,要看实际推理时的实时率(RTF)。RTF小于1表示能实时处理,小于0.5才算比较从容。比如一个ASR模型在目标芯片上RTF是0.8,那基本没有余量做其他事了。我通常要求RTF控制在0.3以内才敢上产品。
第三,功耗预算。Always-On的KWS方案功耗要控制在毫瓦级,否则电池扛不住。流式ASR和TTS的功耗会高一个数量级,通常只在交互触发后短时间运行。
把这三个指标列出来,再对照四类方案的资源需求,基本就能筛掉一大半不合适的选项。下面我会逐类拆解每套方案的技术细节和实操要点。
2. 四类方案的技术拆解与适用边界
2.1 纯KWS方案:最低资源占用的Always-On选择
纯KWS方案的核心就是一个轻量级的关键词检测模型,它只负责判断音频流里有没有出现预设的唤醒词或指令词。这类模型通常是基于TDNN或者小型的Conformer结构,参数量可以压到几十KB到几MB。
sherpa-onnx里提供的KWS模型就是典型代表,它支持自定义关键词,你只需要提供关键词的音素序列或者文本,模型就能检测。我实测下来,一个典型的KWS模型在树莓派Zero这种级别的设备上,CPU占用不到5%,内存占用10MB左右,完全可以7x24小时运行。
这类方案的适用边界很明确:只需要固定指令词的场景。比如“打开灯光”“关闭空调”这种有限指令集的控制类应用。它的优势是资源占用极低、响应快、误唤醒可控;劣势是只能识别预设的词,没法做通用语音识别。
注意:KWS方案的误唤醒率是个关键指标,通常要求24小时内误唤醒不超过1次。调这个指标主要靠调整检测阈值和关键词的音素设计,后面实操部分会详细讲。
2.2 KWS+ASR组合方案:当前端侧语音的主流架构
这是目前端侧语音产品里用得最多的架构。逻辑很简单:KWS常驻低功耗运行,检测到唤醒词之后,唤醒ASR模块进行一段时间的语音识别,识别完成后ASR休眠,KWS继续监听。
这个架构的精髓在于分级唤醒,把高功耗的ASR模块的运行时间压到最短。我做过一个车载项目,KWS常驻功耗大概20mW,ASR唤醒后峰值功耗200mW,但每次交互ASR只运行3到5秒,平均功耗算下来非常低。
sherpa-onnx对这套架构支持得很好,它提供了KWS和ASR的独立API,你可以自己控制两者的切换逻辑。FunASR的Paraformer模型也可以用在ASR这一环,识别精度比sherpa-onnx自带的模型更高,但资源占用也更大,需要根据芯片能力权衡。
这套方案的适用场景最广:智能音箱、车载语音、智能家居中控、穿戴设备基本都能用。选型时主要看ASR模型的大小和识别精度要求,以及唤醒到识别的切换延迟能不能接受。
2.3 流式ASR方案:无需唤醒的持续识别
流式ASR方案不需要唤醒词,音频流持续送入模型,模型一边接收一边输出识别结果。这类方案适合会议记录、实时字幕、通话分析这类场景。
技术上的关键是流式推理,模型需要支持chunk级别的输入输出,而不是等整段音频结束再出结果。sherpa-onnx的流式ASR模型和FunASR的流式Paraformer都支持这个模式。我实测FunASR的流式模型在中文场景下识别效果不错,但模型体积偏大,适合算力相对充裕的设备。
这类方案的资源占用是持续性的,因为模型一直在跑。我一般建议在插电设备或者电池容量较大的设备上使用,纯电池供电的小设备要慎重。
2.4 ASR+TTS全双工方案:能听能说的完整交互
全双工方案就是ASR加TTS,设备既能听懂你说的话,也能用语音回复你。这是四类方案里资源占用最高的,但对交互体验的提升也最明显。
TTS这块,sherpa-onnx提供了多个轻量级TTS模型,包括VITS系列的模型,合成质量在端侧算是不错的。FunASR本身偏重ASR,TTS通常搭配其他方案使用。我一般会根据设备算力选择TTS模型的大小,算力紧张就用小模型,牺牲一点音质换流畅度。
全双工方案的难点在于ASR和TTS的协同调度,要处理好打断、回声消除、半双工切换这些细节。这块后面实操部分会展开讲。
2.5 四类方案横向对比与选型决策表
为了让大家更直观地做选择,我把四类方案的关键指标整理成了一张表:
| 方案类型 | 模型大小 | 内存占用 | CPU占用 | 功耗 | 适用场景 | 典型芯片 |
|---|---|---|---|---|---|---|
| 纯KWS | 几十KB-几MB | 5-15MB | 极低 | 毫瓦级 | 固定指令控制 | MCU、低端SoC |
| KWS+ASR | KWS几MB+ASR几十MB | 50-150MB | 中 | 低 | 智能音箱、车载 | 中端SoC |
| 流式ASR | 几十MB-几百MB | 100-300MB | 中高 | 中高 | 会议记录、字幕 | 中高端SoC |
| ASR+TTS | 几百MB | 200-500MB | 高 | 高 | 语音助手、对话 | 高端SoC |
选型决策的逻辑链是这样的:先看交互需求,如果只需要固定指令就选纯KWS;如果需要通用识别但可以接受唤醒词就选KWS+ASR;如果需要持续识别就选流式ASR;如果需要语音回复就选全双工。然后再用硬件资源去验证,如果资源不够就降级方案或者换芯片。
3. 基于sherpa-onnx的实操落地全流程
3.1 环境搭建与依赖安装
sherpa-onnx的安装方式有好几种,我一般根据目标平台选。在Linux和Android上,最省事的是直接用预编译的库;在嵌入式Linux上,可能需要自己交叉编译。
先说过一遍我在Ubuntu上的标准流程,这个流程在大多数Linux环境都通用:
# 安装Python包,用于快速验证模型效果 pip install sherpa-onnx # 如果需要C++开发,下载预编译库 wget https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.9.0/sherpa-onnx-v1.9.0-linux-x64-shared.tar.bz2 tar -xjf sherpa-onnx-v1.9.0-linux-x64-shared.tar.bz2Android平台的话,sherpa-onnx提供了AAR包,直接集成到项目里就行。Unity项目里用sherpa-onnx做KWS,需要把native库放到Plugins目录下,然后通过C#的DllImport调用。这块我踩过一个坑:Unity的Android构建默认会剥离一些native库,需要在Player Settings里把对应的ABI架构勾上,否则运行时会报找不到库。
提示:sherpa-onnx的版本更新比较快,API偶尔会有变动。建议锁定一个稳定版本,不要盲目追新。我目前生产环境用的是1.9.x系列,比较稳。
3.2 KWS模型的关键词配置与调优
KWS模型的关键词配置是实操里最需要花心思的地方。sherpa-onnx的KWS模型需要你提供关键词的音素序列,而不是直接给文本。它自带了一个文本转音素的工具,但中文和英文的处理方式不太一样。
中文关键词需要先转成拼音,再转成音素。比如“你好小舟”大概会转成类似n ǐ h ǎo x i ǎo zh ōu这样的音素序列。sherpa-onnx的文档里有详细的音素表,你需要按照它的格式来写。
关键词文件(keywords.txt)的格式大概是这样:
n ǐ h ǎo x i ǎo zh ōu :1.0 #你好小舟冒号后面的数字是** boosting score**,用来调整这个关键词的检测灵敏度。这个值调高,检测更灵敏但误唤醒也会增加;调低则相反。我一般从1.0开始试,根据实际误唤醒情况在0.5到3.0之间调整。
调优的经验是这样的:先在安静环境下测试,确保正常说能唤醒;然后在嘈杂环境下测试,看误唤醒率。如果误唤醒太多,先降boosting score,如果降了之后正常唤醒也受影响,那就要考虑优化关键词的音素设计,尽量选音节区分度高的词。
3.3 ASR模型的选型与推理参数设置
ASR这块,sherpa-onnx支持多种模型格式,包括Paraformer、Whisper、Zipformer等。中文场景我一般推荐Paraformer或者Zipformer的中文模型,识别效果和速度比较平衡。
模型选型主要看三个参数:模型大小、识别精度、推理速度。这三个是互相制约的,模型越大精度越高但速度越慢。我通常的做法是先选一个中等大小的模型,实测RTF和识别准确率,如果RTF太高就换小模型,如果精度不够就换大模型。
推理时的关键参数有这么几个:
- 采样率:sherpa-onnx的模型通常要求16kHz输入,如果你的音频是8kHz或者44.1kHz,需要先重采样
- 特征维度:一般是80维的fbank特征,模型训练时用的什么维度,推理时就要保持一致
- 解码方法:贪心解码速度快但精度略低,beam search精度高但速度慢,端侧一般用贪心解码
我实测下来,一个中等大小的Paraformer中文模型,在骁龙665级别的芯片上,RTF大概在0.2到0.3之间,完全能满足实时识别需求。
3.4 TTS模型的集成与音频输出处理
TTS这块,sherpa-onnx的VITS模型是端侧比较实用的选择。模型大小从几MB到几十MB不等,合成质量跟模型大小正相关。
集成TTS的流程大概是:文本输入 -> 文本前端处理(分词、注音)-> 模型推理 -> 音频输出。sherpa-onnx把前面几步都封装好了,你只需要调用API传入文本,它返回音频数据。
音频输出这块要注意采样率匹配。TTS模型输出的采样率可能是22050Hz或者44100Hz,而你的音频播放设备可能只支持特定采样率,需要做重采样。我一般会在TTS输出后加一个重采样模块,统一转成设备支持的采样率再播放。
注意:TTS的首次推理会比较慢,因为要加载模型和初始化。如果对首次响应时间有要求,可以在设备启动时就预加载TTS模型,或者做一个预热推理。
3.5 Unity中集成sherpa-onnx做KWS的完整流程
Unity项目里集成sherpa-onnx做KWS,这个需求我遇到过好几次,流程大致是这样的:
第一步,把sherpa-onnx的native库放到Assets/Plugins/Android目录下,注意要按ABI分目录放,比如arm64-v8a和armeabi-v7a各放一份。
第二步,写C#的封装层,通过DllImport调用native接口。核心接口包括创建KWS实例、送入音频数据、获取检测结果这几个。
[DllImport("sherpa-onnx-c-api")] private static extern IntPtr SherpaOnnxCreateKeywordSpotter(string config); [DllImport("sherpa-onnx-c-api")] private static extern int SherpaOnnxKeywordSpotterProcess(IntPtr spotter, float[] samples, int n); [DllImport("sherpa-onnx-c-api")] private static extern IntPtr SherpaOnnxKeywordSpotterGetResult(IntPtr spotter);第三步,在Unity里采集麦克风音频,转成float数组后送入KWS实例。Unity的Microphone类可以获取音频数据,但要注意采样率要跟模型要求的一致,通常是16kHz。
第四步,处理检测结果,触发对应的游戏逻辑或应用功能。
这套流程我在Android和Windows平台都跑通过,iOS平台稍微麻烦一点,需要额外处理麦克风权限和音频会话配置。
4. 性能优化与常见问题排查
4.1 模型量化与推理加速的实操手段
端侧部署最有效的优化手段就是模型量化。把FP32的模型量化成INT8,模型大小能缩小到原来的四分之一,推理速度也能提升不少,精度损失通常在可接受范围内。
sherpa-onnx支持量化模型的加载,你需要先用它的工具把原始模型转成量化模型。量化过程中要注意校准数据的选择,校准数据要能代表实际使用场景的音频分布,否则量化后的精度损失会比较大。
除了量化,还有几个加速手段:
- 线程数调整:sherpa-onnx支持设置推理线程数,一般设成CPU核心数的一半到全部,太多线程反而会因为调度开销导致速度下降
- 算子融合:部分平台支持算子融合优化,能减少内存访问开销
- 硬件加速:如果芯片有NPU或者DSP,可以尝试把部分算子卸载过去,但这需要额外的适配工作
我实测下来,INT8量化加上合理的线程数设置,能让RTF降低30%到50%,效果还是很明显的。
4.2 误唤醒与漏唤醒的平衡技巧
误唤醒和漏唤醒是KWS方案永恒的矛盾。误唤醒多了用户觉得烦,漏唤醒多了用户觉得不好用。平衡这两个指标,我总结了几个实操技巧:
第一,关键词设计要选音节区分度高的词。比如“小舟小舟”就比“你好你好”好,因为“舟”这个音在自然语音里出现频率低,不容易被误触发。
第二,boosting score要分场景调。安静环境可以调低一点减少误唤醒,嘈杂环境要调高一点保证唤醒率。如果设备有环境噪声检测能力,可以动态调整。
第三,加一个二次确认机制。检测到唤醒词后,不要立即触发,而是再等一小段音频做二次验证。这个能显著降低误唤醒,代价是增加一点延迟。
第四,收集实际误唤醒的音频做负样本。把误唤醒的音频收集起来,作为负样本重新训练或者微调模型,这是最根本的解决办法。
4.3 内存与CPU占用的监控方法
端侧部署一定要做资源监控,否则出了问题很难定位。我一般会在代码里加几个监控点:
- 模型加载后的内存占用
- 每次推理的耗时
- 推理时的CPU占用率
- 音频缓冲区的积压情况
Linux平台可以用/proc/self/status读内存信息,用clock_gettime测推理耗时。Android平台可以用Debug.MemoryInfo和SystemClock。
监控数据要打日志,最好能上报到后台做长期分析。我遇到过一个案例,设备运行几个小时后语音响应变慢,最后查出来是音频缓冲区没有及时释放导致内存泄漏,如果没有监控数据,这种问题很难发现。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 | 解决方法 |
|---|---|---|---|
| 模型加载失败 | 模型文件路径错误或格式不匹配 | 检查文件路径和模型版本 | 确认模型格式与sherpa-onnx版本匹配 |
| 识别结果为空 | 音频采样率不对或音频格式错误 | 检查输入音频的采样率和通道数 | 重采样到模型要求的采样率 |
| 误唤醒频繁 | boosting score过高或关键词设计不合理 | 查看误唤醒音频的特征 | 降低boosting score或更换关键词 |
| 推理速度慢 | 模型太大或线程数设置不合理 | 测RTF和CPU占用 | 量化模型或调整线程数 |
| 内存持续增长 | 资源未释放或缓冲区泄漏 | 监控内存变化趋势 | 检查推理后的资源释放逻辑 |
| TTS输出有杂音 | 采样率不匹配或音频格式问题 | 检查TTS输出和播放设备的采样率 | 统一采样率并做格式转换 |
| Unity中找不到native库 | ABI架构未勾选或库文件缺失 | 检查Player Settings和Plugins目录 | 勾选对应ABI并确认库文件存在 |
这张表里的问题都是我实际遇到过的,排查思路也是经过验证的。遇到问题先对照这张表过一遍,能解决大部分常见情况。
4.5 从开发到量产的经验教训
最后分享几个从开发到量产过程中踩过的坑,这些都是文档里不会写的:
第一,实验室效果不等于现场效果。我在实验室调好的KWS模型,到了现场因为背景噪声不同,误唤醒率直接翻倍。后来养成了习惯,一定要在实际部署环境里做至少一周的长时间测试。
第二,芯片的标称算力和实际可用算力差距很大。芯片手册上写的算力是理论峰值,实际因为内存带宽、调度开销等原因,能用到一半就不错了。选型时一定要留足余量。
第三,模型更新要考虑兼容性。产品卖出去之后如果要更新模型,新模型必须兼容旧版本的接口和配置,否则升级会出问题。我一般会在模型配置里加版本号,升级时做兼容性检查。
第四,功耗测试要覆盖所有场景。不能只测待机功耗,要测唤醒时的峰值功耗、连续识别时的平均功耗、TTS合成时的功耗。我见过一个产品待机功耗达标,但连续交互时功耗超标导致电池过热。
第五,一定要做长时间稳定性测试。至少跑72小时,观察内存、CPU、识别率的变化趋势。很多问题都是长时间运行后才暴露出来的。
这些经验说到底就是一句话:端侧语音方案的落地,技术选型只占三成,剩下七成都是工程细节和实际场景的打磨。选对方案只是开始,把方案在目标设备上跑稳、跑好,才是真正花时间的地方。