很多做智能硬件的人拿到AI小智这样的开源语音助手项目,第一反应都是先找块开发板,把镜像烧进去,然后喊一嗓子“小智小智”,看它能不能答应。能答应只是起点,真正让人头疼的是它背后的语音服务架构:音频从麦克风到服务器转一圈再回来,链路里任何一环慢了、断了或贵了,整个产品就立不住。这篇文章就把我在AI小智语音助手项目里从服务端到嵌入式端做的一整套实践记录一遍,重点聊四件事:端侧和云端怎么分工、语音链路每一步怎么做、设备端音频通路有哪些坑,以及从硬件BOM到云端调用费怎么一步步把钱省下来。如果你准备做智能音箱、陪伴机器人或者其他语音交互设备,这些内容可以直接当参考资料用。
1. 先想清楚:端侧和云端怎么分工,别把整条语音链路都丢给服务器
1.1 第一版方案为什么走不通
第一版的时候,我图省事,让设备只负责录一段音频,然后整段POST到一个ASR接口,识别完成后调用大模型,再拿TTS音频播放。流程看着简单,实际用起来问题很多。最明显的是延迟:一段3秒的语音,录音结束后上传、识别、等大模型生成、再合成语音,整个往返下来5秒起步,用户说完话之后面对一片沉默,体验非常差。同时,所有音频都传到云端,唤醒词误触发也会被当成有效请求,每个月的ASR费用很不好看。
后来我把唤醒词、VAD断句、本地播放这些放到板子上,服务器只负责真正需要识别和理解的那部分语音。这样做之后,唤醒响应做到了毫秒级,因为唤醒本身就是本地特征匹配,不涉及网络;上传给云端的数据量也明显减小,只有确认要下发的语音才会上传,成本立刻降下来。
1.2 最终定下来的端云分工
我把AI小智的整套能力拆成了一层“设备基础能力”和一层“云端认知能力”。设备基础能力包括:音频采集、唤醒词检测、语音活动检测、本地简单指令执行、音频播放、打断检测。云端认知能力包括:语音识别ASR、意图理解和多轮对话、文本转语音TTS、技能服务。
分工明确之后,架构就简单了:设备通过一个常驻的WebSocket连接和云端网关保持通信,音频以Opus编码分片推送,云端返回中间识别结果和最终对话结果。举个例子,如果用户说“小智小智,把灯打开”,唤醒词在本地命中,接下来“把灯打开”这半句才会被送往云端ASR。云端识别出文本后,如果意图是本地控制,LLM层就直接返回一个本地指令,设备端执行开关灯,整个过程不需要服务器合成大段语音。
1.3 为什么“端侧唤醒+云端识别”是性价比最高的组合
有的方案是全部离线,端侧跑一个完整的语音识别加对话模型,这样做隐私和成本都很诱人,但嵌入式的算力和存储撑不住效果好的模型。反过来全云端也不现实,唤醒词如果也放云端,设备必须持续上传音频流,流量和费用会失控,而且断网时设备连基本唤醒都做不了。
所以最终选择“端侧唤醒+云端识别”的混合方式,背后逻辑很简单:把实时性要求高、逻辑固定的任务留在端侧,把吃算力、需要持续更新的认知任务放到云端。这套分工也决定了我后面所有成本优化工作的边界——先保证架构是健康的,再去考虑每一分钱怎么省。
2. 语音链路一步步拆:从一句“小智小智”到音箱发声,中间发生了什么
2.1 唤醒词和VAD:端侧的两道闸门
唤醒词看似简单,实际很讲究。我第一次用的开源唤醒引擎,默认模型在安静环境下识别率还行,一旦开着电视、放着音乐,误唤醒率就高得离谱。后来我专门采集了在设备旁边播放音乐、人声、厨房噪声的音频,用这些负面样本重新训练了一个小小的唤醒模型,才把误唤醒次数从一晚上十几次压到几乎为零。训练唤醒词模型的过程不难,关键是收集场景数据,别只在办公桌边测试。
唤醒命中之后,设备进入“听指令”状态。这时候需要VAD判断用户什么时候说完了。VAD的实现可以用轻量模型,也可以直接用WebRTC的VAD模块,我最初选了后者,因为它几乎不占CPU。前提是音频必须是16kHz、16bit、单声道,否则VAD阈值不适用。具体做法是:唤醒后开始缓存音频分片,每100毫秒做一次VAD判断,连续500毫秒检测到静音就认为一句话结束,把缓存区里的音频统一封包发送给ASR。
2.2 流式ASR:首字结果越快,用户体验越稳
如果等用户说完再整段上传,识别延迟会累加到对话总时长里。我改成流式识别后,设备一边采集一边把Opus音频分片推到云端,ASR在解码器内部持续返回“中间识别结果”,比如用户说“今天天气怎么样”,用户在说到“今天天”的时候,服务器已经能返回一部分文字。这样最终的对话响应总延迟能压缩到1.5秒以内。
流式ASR接入有几个细节要注意。第一,音频分片不能随便切,一般每200ms到300ms一包比较合理,太小会导致网络包过多,太大又增加缓冲延迟。第二,要带上序列号和时间戳,偶发网络重传时,服务器可以按顺序拼回原始音频。第三,设备发送完最后一个分片后,必须发送一个结束标记,ASR才知道这句话结束了。否则云端会一直等下一包,卡到超时。这些逻辑不复杂,但是缺一个都会在真实网络下暴露问题。
2.3 LLM对话层的会话管理:长对话不被无限token吃掉
LLM接入本身不复杂,复杂的是会话管理。小智这类语音助手天然是多轮对话,比如用户先问“今天深圳天气怎么样”,再问“那明天呢”,如果第二句不带上下文,模型根本不知道“那”指什么。所以服务端需要为每台设备维护一个session,保存最近几轮对话。
但记忆越多越贵,提示词里的历史token越长,每次请求的耗时和费用都上升。我的做法是:保留最近6轮完整对话,超过6轮就生成一段摘要塞进system prompt里作为“动态背景知识”。实测下来,大多数家用场景这个窗口足够,偶尔出现多轮复杂追问丢上下文,用户也不会太在意。另外,LLM的system prompt里明确写了“你是AI小智,回答控制在50字以内”,这既是压缩token成本,也是让TTS播报更自然。
2.4 TTS也要分层:不是所有回复都值得跑一次云端合成
早期版本所有播报都走云端TTS,后来发现很多回复内容重复度极高,比如“已打开”“好的”“晚安”。同一句话,每次都在云上合成一遍,既花时间又费钱。我在服务端加了一个TTS缓存:按文本计算MD5,命中缓存直接返回存储的音频文件,不命中才去调用TTS服务。简单的固定回复命中率能达到60%以上,TTS资源开销一下子降了一半多。
同时,我把TTS分成了两级。一级是超短反馈,比如“好的”“嗯”,这类直接由设备本地合成,或者干脆用预置音频文件播放,不占服务器资源。二级是长文本回复,才走云端TTS。这里推荐优先考虑自建开源的TTS推理服务,效果和稳定性在自己掌控范围内,云TTS虽然方便,但后面会遇到限流和费用问题。
3. 嵌入式端的现实问题:音频通路、ALSA配置和回声消除,全是体验分水岭
3.1 选型方向:先用树莓派验证,再用低成本Linux SoC做产品
AI小智的原型阶段,我用树莓派4B跑,音频codec芯片是板上自带的3.5mm音频口加一个USB麦克风。树莓派资源充足,整个程序写起来很随意,语音服务的代码几乎没做性能优化。但当我们准备做小批量设备时,成本完全扛不住。一块树莓派4B的采购价已经超过很多语音音箱的零售价,所以最后我们把平台换成了全志芯片的低成本Linux核心板,内存从4GB砍到512MB,CPU主频也减半。
换平台之后,第一件事就是测音频链路的CPU占用。同样的SpeexDSP回声消除逻辑,在树莓派上只占10%的CPU,换到低成本SoC上直接飙到35%,如果再加上TTS播放和网络传输,系统频繁出现卡顿。这个教训让我意识到,嵌入式语音项目选型时,“能不能跑”和“跑得稳不稳”是两码事,必须提前用目标硬件跑一遍完整的音频调用链。
3.2 ALSA配置:16kHz、16bit、单声道,这是VAD和ASR的基本盘
很多语音识别服务要求音频是16kHz采样率,但不少板载codec默认工作在48kHz。如果直接用hw设备采集,拿到的音频就是48kHz,送到ASR之前必须做重采样。我踩过最典型的坑,是在应用层手动对每一帧PCM做采样率转换,CPU占用高不说,重采样后还有爆音。
后来我改用ALSA的plug插件,在采集和播放设备上直接配置采样率转换,应用层拿到的就是标准的16kHz音频。一个简单的配置如下:
pcm.mic { type plug slave { pcm "hw:0,0" rate 16000 format S16_LE channels 1 } } pcm.spk { type plug slave { pcm "hw:0,1" rate 48000 format S16_LE channels 2 } }这里mic采集强制16k单声道,播放仍保持48k立体声。注意不同板子的hw编号不一样,我的经验是先用arecord -l和aplay -l确认声卡编号,再写进配置文件,而不是盲抄网上现成的asound.conf。改完配置后,用arecord -D mic -f S16_LE -r 16000 -c 1 test.wav录一段音频,确认波形正常,再进行下一步。
3.3 回声消除和打断:音箱自己说话时,不能把TTS的声音当成用户指令
语音助手的巨大难点是回声。设备正在播放“好的,马上为你打开”时,用户接着说“不,关掉”——麦克风采到的音频里同时有扬声器的声音和人声,如果回声没消除,ASR很可能把“好的马上为你打开”也识别成用户指令,导致奇怪的多轮对话。
软件回声消除方案我用过SpeexDSP,也试过WebRTC的AEC模块,两者在树莓派上效果都不错,但在低端Linux板上CPU开销明显。后来我换成了带硬件AEC的音频前端,把回声消除从应用层挪到codec里完成,CPU占用才真正降下来。如果你不想改硬件,软件AEC也能用,但务必在安静房间、音乐播放、大音量TTS播报等场景下压测,别只在通话测试环境里验证。
打断检测同样重要。设备在播放TTS时,如果用户喊唤醒词,系统应当立刻停止播放并重新进入听指令状态。这里我用了两层检测:先在播放线程里检测唤醒词,命中后立即停播;同时在音频采集侧检测附近的瞬时能量,如果能量超过阈值且持续一段时间,就提前停止TTS等待唤醒结果。不要小看这个细节,很多语音助手产品“叫不停”的毛病就出在这。
3.4 大音量下的削波、爆音和气流噪声
最后一个不起眼但影响很大的问题:扬声器音量开大后,麦克风容易削波,采到的波形顶部被削平,ASR识别率会明显下降。解决方案除了回声消除,还要在音频输入通路里加一个自动增益控制,或者把麦克风灵敏度调低一点。很多开发板默认的mic增益设得过高,插上耳机或者外接音箱后问题立刻暴露。建议在量产前做一组测试:分别在20%、50%、80%音量播放TTS,录制麦克风输入,检查波形的峰峰值和削波比例,再决定是否需要调整。
4. 成本优化:别一味砍配置,先把每句话的真实开销算清楚
4.1 云端成本的拆解:ASR、LLM、TTS各占多少
做成本优化之前,我先给每台设备每天的语音请求算了一笔账。假设一台设备一天有20次有效语音交互,每次语音指令的有效音频大概3秒,模型回复文本大约20个字。按我当时拿到的云上报价,ASR按小时计费折算下来,每秒成本约0.0003元,3秒音频一次就是0.001元上下,一个月大约0.6元;LLM按token计费,每次完整链路消耗约1000 token,按市场常见价格大约一次0.002元,一个月约1.2元;TTS按字符计费,一次回复约0.0002元,一个月0.12元。三项加起来,每台设备每月云端成本大约2元。
如果有一万台设备,一个月就是两万,这还不包括服务器、带宽和GPU折旧。看清楚这个数字之后,我的结论很明确:继续压价格不如压调用量,因为单次调用成本已经是几分钱量级,但每天调用次数有一百倍优化空间。
4.2 压调用量的四个方向
第一个方向是本地直接处理固定指令。“打开灯”“关空调”“定个十分钟的闹钟”这类指令,完全可以在端侧做关键词匹配并直接执行,不走ASR和LLM。我在端侧内置了一个很小的本地命令表,系统只把不匹配的语句上传云端,实测大概能挡住20%的固定请求。
第二个方向是意图分层。LLM不是所有请求都值得用大模型,我先用规则和关键词判断用户意图,只有复杂对话才走LLM,简单查询走轻量化的百科接口或者本地数据库。这个方案上线后LLM调用次数减少了30%左右,而且响应速度更快。
第三个方向是流式ASR的VAD门控。前面说过,唤醒后如果检测不到有效人声,就不要把音频送到云端。我在端侧加了一道有声音量门槛,音频能量低于阈值一律不上传,能滤掉一部分误触发和模糊语音。
第四个方向是TTS缓存和预置音频。固定话术、天气播报模板、系统提示音,全部在服务端或端侧预生成并缓存。TTS的实际调用量可以降到原来的三分之一。
4.3 硬件BOM怎么优化:不是选最便宜的芯片,而是选刚好够用的芯片
硬件成本上,我见过很多团队一上来就选高算力芯片,怕后面功能不够用。AI小智的完整链路里,真正的大算力需求都在云端,端侧只做唤醒、VAD、录音和播放,所以对SoC的AI算力没有硬性要求。我们最终选择了64位ARM Cortex-A系列芯片加512MB内存的Linux核心板,成本比树莓派低很多,同时还能跑完整的语音采集程序。
我整理过一个对比表格,方便原型阶段做选型判断:
| 平台 | 参考成本 | 能跑完整链路? | 适合阶段 |
|---|---|---|---|
| 树莓派4B | 300元以上 | 能,资源余量很大 | 原型验证、算法调试 |
| 全志H3/H616核心板 | 80-120元 | 能,需要做音频和CPU优化 | 小批量量产 |
| ESP32-S3模组 | 30-50元 | 只能跑唤醒和本地命令,不能跑完整LLM链路 | 低端离线场景 |
产品批量出货后,核心板省下来的钱,比在云端省下来的更直接。但也要算清楚隐形成本:低成本SoC的散热、Flash容量、驱动稳定性,都需要额外的开发验证时间。
4.4 带宽和存储也是成本:音频编码、OTA包体积
很多人只盯着ASR和LLM费用,忽略设备和服务器之间的带宽费用。如果音频不压缩直接传PCM,16kHz16bit单声道一秒就是32KB,一天下来流量可观。我改成Opus编码后,同样音频码率压到24kbps左右,流量只有原来的十分之一。设备端到云端的音频走Opus,云端返回的TTS也按需压缩成mp3或opus再下发,传输时间和带宽都降下来。
OTA镜像体积也要控制。我们的设备根文件系统裁到200MB以内,真正传的更新包通常只有30-60MB。用增量包更新时,设备只下载差异部分,而不是整包覆盖,这样长期维护很多台设备时,流量成本会差一个数量级。
5. 服务端落地:小智AI服务器镜像、设备批量管理与监控
5.1 服务端容器化:一套Docker Compose跑起所有服务
AI小智的服务端如果手工部署,环境不一致会导致非常多的隐性问题。我最终把服务全部容器化,用Docker Compose编排。主要服务包括:接入网关、ASR识别服务、LLM代理服务、TTS合成服务、会话状态服务。每个服务都有独立的镜像,版本打上tag推送到私有镜像仓库,服务器上一条docker compose pull命令就能完成升级。
容器编排看起来简单,但有些细节要提前设计好。比如内存限制,每个容器都设置mem_limit,防止某个ASR服务内存泄漏把整台服务器拖垮。比如日志轮转,容器日志默认会无限增长,我在compose文件里配上max-size和max-file,限定单个日志文件大小和数量。这些配置在单机开发时无所谓,设备量起来之后就是救命的。
services: gateway: image: registry.local/aixiaozhi/gateway:1.4.2 mem_limit: 512m logging: driver: json-file options: max-size: "10m" max-file: "3" ports: - "8080:8080"5.2 设备端更新:A/B分区和签名校验,不能把回滚赌在运气上
嵌入式设备升级最大的风险是升级到一半断电,设备变成砖头。我用的是A/B双分区方案:设备上有两个系统分区,当前运行在A区,下载新版镜像写入B区,写入完成后修改启动标志,让系统从B区启动。如果B区启动失败,引导程序会自动回滚到A区。整个过程对用户无感,重启一次就能完成升级。
OTA包的校验也做得比较严格。镜像文件用签名工具签名,设备端在写入前先验证签名和哈希,防止镜像在下载过程中被破坏或者被替换。曾经有一次我图省事跳过签名校验,结果一台设备在弱网下下载了截断的镜像,启动后反复崩溃,最后才排查出来是校验环节缺失。从那以后,签名校验被列为不可省略的步骤。
5.3 设备可观测性:没有监控,批量发货就是盲目赌博
当设备数量从几台增长到几百台,任何偶发问题都会被放大。我在设备端加了一个轻量上报模块,每隔30秒上报心跳、当前状态、内存占用、音频队列长度,以及最近一次语音请求的耗时和结果码。云端把这些指标收下来,建了一个简单的看板,重点盯四个数:唤醒成功率、ASR首字耗时、LLM首token耗时、TTS缓存命中率。
有一次上线新版本后,我发现ASR首字耗时突然从400ms涨到1.2秒,排查发现是接入网关的连接数超过了预期,导致部分请求排队。如果不看监控,用户只会觉得音箱变笨了,很难定位到是哪一层出的问题。监控数据不一定要上重度的大数据平台,用一个轻量的时序数据库加简单的告警规则就够用,关键是先把链路指标埋好。
整套架构跑通之后,我最想给后来人提个醒:别一上来就纠结模型选哪个,先把端云分工和成本模型画清楚。AI小智这类项目的坑,多半不在AI本身,而在链路延迟、硬件成本和批量运维。先把这三件事的底摸清楚,再去做功能,会顺手很多。