ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

低资源设备AI语音交互:常驻与按需的模块划分实战

低资源设备AI语音交互:常驻与按需的模块划分实战

讲个具体的场景:一块国产低端语音模组,主控是单核 400MHz 的 Cortex-M 级别芯片,内部 RAM 只有 512KB,Flash 8MB,没有独立 NPU。就这么个配置,硬是把“离线唤醒 + 云端大模型语音问答”这种 AI 语音交互跑起来了。很多人第一反应是“这么小的设备能跑 AI?”,其实真正的问题不是“能不能跑”,而是另一件事:低资源设备上的 AI 语音交互,到底哪些环节必须一直开着,哪些环节可以关了再启动。

我最早做这类项目时也犯过糊涂,想着语音交互就是一个流水线,从麦克风到扬声器全流程都很重要,那就全都在线跑。结果内存直接爆掉,功耗也压不下去,50mA 的电池供电设备两小时就没电。后来踩了一轮坑才明白:低资源设备上的 AI 语音交互,本质是“常驻的轻量监听”和“按需的重型计算”之间的配合。这篇就把链路拆开讲清楚,顺便给出我自己实际用的模块状态表、内存预算和排查思路,给正在做低成本音箱、故事机、智能家居面板、离线语音模组的同学一个参考。

1. 语音交互链路拆解:从唤醒到出声,谁停谁转

1.1 完整链路不止“听到再说”,其实有 8 个环节

很多教程喜欢把语音交互简化成“麦克风输入 -> 识别 -> 回答 -> 播放”,听起来简单。但真正落地到低资源设备,至少要拆成 8 个环节:

  1. 麦克风采集:把声压变成数字信号,通常 16kHz/16bit 单声道,也可能 8 通道麦克风阵列。
  2. 前端信号处理:回声消除(AEC)、自动增益(AGC)、降噪(NS)、声源定位。这一步不做,后面识别率会很难看。
  3. 语音活动检测(VAD):判断“有没有人说话”,而不是“说了什么”。
  4. 唤醒词检测:识别特定词,比如“小智小智”,这是低资源设备最常见的本地 AI 入口。
  5. 语音识别(ASR):把语音转成文本,可以是本地命令词识别,也可以上传云端做大词表识别。
  6. 语义理解/任务处理:传统叫 NLU,现在很多设备直接接 LLM API,理解用户问什么,决定怎么回答。
  7. 语音合成(TTS):把文本转成语音。云端返回音频,或本地跑 TTS 模型。
  8. 音频播放与反馈:DA 转换、功放、扬声器,有些还要做打断和音量调节。

这 8 个环节并不是所有都要常驻内存。低资源设备跑 AI 语音交互,第一步就是给这 8 个环节分等级:谁 7x24 小时在岗,谁只有在用户唤醒后才能被加载。

1.2 决定“一直开”的还是那三堵墙:内存、功耗、时延

为什么不能把全链路都常驻?先说内存。一个实时 VAD 小模型大概几十 KB,一个唤醒词模型几百 KB,但一个离线中文 ASR 模型动辄 5MB 到 20MB,一个本地 TTS 声学模型也要几 MB。低资源设备 RAM 总共才 512KB,全加载的结果就是启动即崩溃。

再说功耗。常驻模块需要持续供电、持续读时钟、持续处理音频帧。唤醒词检测常驻可以做到很低功耗,但把 ASR 常驻就是灾难,因为 ASR 每一帧都要算概率,CPU 占用率上去后,电池设备根本顶不住。我做过的某颗音频 DSP 芯片,常驻唤醒方案能做到平均 20mW 左右,但如果不小心让 ASR 也一直跑,功耗直接跳到 500mW 以上。

最后是时延。语音交互讲究“唤醒后 1 秒内能出声”,如果所有模型常驻,启动确实快,但资源不够;如果全按需加载,Flash 读取和模型初始化又要浪费 300ms。所以低资源设备的真实设计思路是:保证一条最低功耗的常驻监听通道,同时保留一条最快的按需加载路径。

2. “必须一直开着”的环节:低功耗常驻监听链路

2.1 麦克风采集和前端信号处理为什么不能关

设备要听到用户说话,麦克风采集就必然常驻。但常驻不是一句空话,实现上要考虑几件事。第一,音频要用 DMA 直接搬到内存,不能用 CPU 中断一颗颗采样点去搬,否则 CPU 根本没时间睡觉。我习惯在 MCU 上配置一个 64KB 的环形缓冲区,DMA 每 10ms 产生一次半满中断,CPU 醒来处理 10ms 数据后再次睡眠。这样平均占用的 CPU 时间可以做到 5% 以下。

第二,AEC、AGC、降噪这些前端算法也要常驻运行。很多人觉得降噪是“锦上添花”,实际上在低资源设备上,AEC 和降噪直接决定了唤醒词能不能在播放音乐时被听到。如果只听远端唤醒,AEC 只在系统播放声音时参与计算,平时可以旁路;AGC 则要保持麦克风增益稳定,防止用户距离忽远忽近导致唤醒率抖动。这块通常在专用音频 DSP 上跑,如果没有独立 DSP,就在主控低功耗模式下用有限脉冲响应(FIR)滤波器做轻量级降噪,不要上重型神经网络降噪,否则内存和算力都吃不消。

2.2 唤醒词引擎的常驻策略:VAD 在前面挡一道

唤醒词检测是所有 AI 语音交互里最典型的“必须一直开着”的环节。但细看,常驻的其实不是整个唤醒模型,而是一条分级的链路:

  • 第一级:极简能量 VAD,只算音频帧的短期能量和过零率,超过阈值的帧才往下送。
  • 第二级:轻量语音活动分类器,识别人声和噪声,挡住空调声、风扇声、电视声。
  • 第三级:唤醒词模型,只有前两级都通过才开始推理。

这样分级的价值在于:绝大多数时间音频里根本没有语音,前两级就可以过滤掉 95% 的无效数据,唤醒模型绝大多数时间都在“待命”,CPU 功耗就很低。我见过很多初版方案直接把唤醒模型放在最前面,每 10ms 的音频帧都过一遍模型推理,结果误唤醒没改善,功耗还翻倍。

唤醒模型本身也要选小。通常用几百 KB 参数的 CNN 或 DS-CNN 结构,输入 16kHz 的 40 维 MFCC,识别固定长度的唤醒词。比如“小智小智”这种四字词组,模型输入窗口 1 秒,每 200ms 滑动一次。参数量控制在 300KB 以内,单次推理时间不超过 30ms,才能在低主频设备上常驻运行。

2.3 常驻监听的具体实现:中断、缓存和低功耗模式

这一节直接给可参考的工程实现思路。主控采用“睡眠 + 短唤醒”循环:

while (1) { // 进入低功耗睡眠,等待 DMA 半满中断或定时器唤醒 __WFE(); if (audio_frame_ready) { vad_score = compute_energy_vad(audio_buf); if (vad_score > VAD_THRESHOLD) { speech_score = classify_voice(audio_buf); if (speech_score > SPEECH_THRESHOLD) { wakeword_score = run_wakeword_model(audio_buf); if (wakeword_score > WAKE_THRESHOLD) { enter_interactive_mode(); // 切换到交互状态,加载 ASR/TTS } } } } }

关键参数是我反复调过的:

  • VAD_THRESHOLD 不能设太低,否则屏幕无操作时周围一点响声就会唤醒;也不能太高,否则正常说话的第一帧就被砍掉。我一般取历史噪声底噪的 6dB 到 12dB 偏置。
  • 唤醒词阈值控制在“每 24 小时误唤醒不超过 3 次”的标准。调试时先录 2 小时背景噪声,再统计误唤醒次数。
  • DMA 缓冲块大小:10ms 最合适。太大导致唤醒响应变慢,太小则频繁中断,睡眠被切碎,功耗反而高。

这里还必须注意 Flash 和 SRAM 的低功耗保持。唤醒词模型放在 Flash 里,通过 XIP(原地执行)读取,避免启动时拷贝到 RAM。中间特征缓存尽量复用同一块 SRAM,不要让不同模块各占一块,否则 512KB 内存根本不够用。

3. “不用一直开着”的环节:按需加载与流式处理

3.1 ASR 和语义理解为什么要按需启动

一旦唤醒词命中,系统就从“监听态”切换到“交互态”,这时候才加载 ASR。常用的方案有两种:

第一种是本地离线 ASR,只识别固定命令词表,比如“开灯”“关灯”“播放音乐”“停止”,词表 50 到 200 个词。模型可以采用基于 CTC 或 attention 的小型端到端模型,或直接用传统的 DTW 模板匹配。优点是响应快、无网络依赖、隐私好;缺点是只能听懂命令词,无法处理开放对话。

第二种是云侧 ASR,设备把录音传上去,云端返回文本。这种方案对设备端算力要求低,但必须处理好网络延迟和断网兜底。实际产品里经常是“本地命令词 ASR 优先 + 云端大模型 ASR 兜底”:先让本地命令词表试一遍,如果置信度低,再走云端。

语义理解更没必要常驻。传统 NLU 模型和 LLM 服务只有在拿到 ASR 文本后才需要运行。低资源设备上我建议把 NLU/LLM 放在云端或边缘网关,设备通过一个非常轻量的 MQTT/HTTP 协议把文本传过去,再等结果。设备本地只保留最简单的“命令词 -> 意图”映射表,几十条规则,占不到 10KB。

按需启动的工程要点是“延迟换内存”。ASR 模型从一个压缩的 Flash 分区拷贝到 RAM,速度大概 2MB/s,一个 3MB 模型要 1.5 秒,这太慢了。所以不要用整包模型,用“头模型 + 流式解码”:预先加载声学模型的编码器,一边录音一边出中间结果;解码器在需要出文本时才申请内存。这样首包文本延迟可以压到 300ms 到 500ms,而内存占用只有全量加载的 60%。

3.2 TTS 和播放环节的按需策略

TTS 同样不需要一直开着。低资源设备最常见的做法是“预录音 + 拼接”和“端侧轻量 TTS”,两者差异很大。

预录音 + 拼接适合固定提示语的设备,比如“好的”“已为您打开灯光”“网络连接失败”。这些音频直接压缩成 MP3/ADPCM 存在 Flash 里,播放时按需读取。优点是不占内存、TTS 延迟几乎为 0、音质自然;缺点是没办法合成任意文本。对这个方案,我强烈建议做一个音频索引表:把所有提示语编号,播放时按序号读偏移量,而不是扫文件名。

端侧轻量 TTS 适合需要动态回复的设备,比如接入 LLM 后回答不固定。轻量 TTS 模型一般用声码器 + 音素前端,模型大小 3MB 到 8MB。播放前才加载,播完立刻释放内存。如果 RAM 不够,可以考虑“边解码边播放”,把 TTS 推理和音频播放做成双缓冲流水线,避免生成一整段音频到内存再播。

播放环节本身也要按需。功放和 DAC 的静态功耗很大,不播放时一定要关掉。低资源设备里的常见坑是:DAC 初始化后在低功耗模式仍然保持上电,导致待机功耗多出 10mA。我在实际项目中用 GPIO 控制功放使能引脚,预计播放前 50ms 才拉高,播放结束后延迟 200ms 再拉低,避免“啪”的爆音。

3.3 端侧与云边协同:怎么划分常驻和按需的边界

低资源设备的 AI 语音交互,很少是完全端侧,也很少是完全云端。更实用的结构是:

环节运行位置是否常驻原因
采集/前端/VAD设备端常驻必须持续监听,实时性要求最高
唤醒词设备端常驻隐私和断网兜底,功耗可控
本地命令词 ASR设备端按需快速响应,固定场景,不需要云
开放域 ASR云端/边缘按需模型大,设备内存放不下
NLU/LLM 对话云端/边缘按需计算密集,低资源设备无法本地跑
TTS设备端或云端按需本地适合短回复,云端适合长文本
播放设备端按需只在输出音频时开启

划分边界的原则很简单:凡是“一直在等”的,放本地且保持轻量;凡是“干重活”的,放云端或边缘,只在需要时临时调用。同时要加一条兜底逻辑:网络断了,设备至少要能用本地命令词完成离线控制。这也是为什么唤醒和本地命令词 ASR 一定要保留在设备端的原因。

4. 实操落地:模块状态机、内存预算和验证方法

4.1 用状态机管理“常驻 / 按需”,而不是散乱的开关

我在工程里把所有语音模块收敛到一个应用状态机里,避免各处代码各自为政。状态定义如下:

typedef enum { APP_STATE_IDLE, // 空闲监听,只有采集/VAD/唤醒常驻 APP_STATE_WAKEUP, // 唤醒命中,准备进入交互 APP_STATE_LISTENING, // 正在录音并做 ASR APP_STATE_PROCESSING, // 等待 NLU/LLM 返回文本 APP_STATE_RESPONDING, // 正在 TTS 或播放音频 APP_STATE_ERROR // 异常兜底 } app_state_t;

状态迁移的核心约束是:

  • IDLE -> WAKEUP:唤醒模型得分超过阈值,这时不开麦克风全链路,只记录唤醒后的音频起点。
  • WAKEUP -> LISTENING:加载 ASR 编码器,同时打开录音,开始流式识别。这里要小心重复,唤醒词本身可能被识别成内容,需要做静音/打断处理。
  • LISTENING -> PROCESSING:ASR 出了完整文本,立刻释放 ASR 编码器内存,然后发起网络请求。
  • PROCESSING -> RESPONDING:拿到回复文本,加载 TTS,播放音频。可以提前释放 NLU 相关的缓冲,只保留回复文本。
  • 任何状态在 5 秒无交互后回到 IDLE,并释放所有按需模块。

状态机的好处是让“必须一直开着”的边界非常明确:只有 IDLE 状态下需要的模块才常驻,其余模块在状态切换时加载和释放,内存就可以反复复用。

4.2 一份参考内存预算:512KB RAM 怎么分

下面是一个我已经跑通的 512KB RAM 预算表,供大家参考:

内存区块大小状态说明
系统/RTOS/栈96KB常驻内核、任务栈,心跳定时器
音频环形缓冲64KB常驻2 路 10ms 帧缓冲,支持 8 通道走时间轮转
前端处理上下文32KB常驻AEC/AGC/降噪参数和延迟线
VAD + 特征缓存24KB常驻能量计算和 40 维 MFCC 缓存
唤醒词模型256KB常驻量化 INT8 CNN 参数,XIP 只读
ASR 推理池256KB按需与唤醒词模型分时复用一块 SRAM
TTS 推理池192KB按需与 ASR 推理池内存复用
网络协议栈缓冲48KB按需只在 PROCESSING 状态使用
临时文本/JSON16KB按需LLM 回复解析

你可能已经发现了,唤醒词模型 256KB + ASR 推理池 256KB + 系统 96KB + 音频缓冲 64KB 早就超过 512KB 了。关键在于:ASR 推理池和唤醒词模型所在的内存区域是物理重叠的。唤醒词模型常驻在 Flash,不占 RAM;ASR 模型加载进 RAM 时,直接把原本给唤醒特征缓存和部分音频缓冲的区域借过来用,处理完再还回去。内存池管理要保证没有模块同时使用同一块区域,这靠状态机来保障。

对更小的设备,比如 RAM 只有 256KB,可以把唤醒模型压到 128KB,同时去掉 AEC 的延迟线重装载,改用简单的谱减法降噪。再小就只能换思路,比如把唤醒放一颗独立音频 DSP,主控只负责交互。

4.3 实测验证:功耗、唤醒率和首包延迟

模块划分不是写完代码就完了,一定要实测。我习惯用三组指标评估:

第一组是功耗。分别测 IDLE、LISTENING、PROCESSING、RESPONDING 四种状态的平均电流。IDLE 状态下整机电流如果超过 10mA,说明常驻链路太重,要检查 VAD 是不是还在处理全带宽音频、DMA 是不是没进低功耗模式。我测试时用 100mAh 电池做参考,IDLE 电流 8mA 意味着理论待机 12.5 小时,比较合理;如果想要更长,就得考虑一颗更低功耗的音频协处理器。

第二组是唤醒率。准备 50 条正常语速的唤醒词和 50 条噪声片段,跑一小时统计。误唤醒按 24 小时估算,不建议只看“唤醒率 95%”,因为如果误唤醒每小时 5 次,用户会直接把设备禁用。标准我上面提过:唤醒率 90% 以上,误唤醒 24 小时不超过 3 次。

第三组是端到端延迟。从用户说完“小智小智,现在几点”到设备出声,我实测一个云 LLM 方案的合理范围是 1.2 到 2.5 秒。其中唤醒 200ms,流式 ASR 出文本 400ms,网络往返 600ms,TTS 首包 300ms。如果总延迟大于 3 秒,用户就会觉得“卡”,优先排查网络耗时和 ASR 的端到端流式逻辑,而不是盲目压缩 TTS 音质。

5. 常见问题与排查技巧实录

5.1 误唤醒太频繁,半夜自己说话

这是低资源设备最常见的翻车点。误唤醒的产生有三个原因:VAD 阈值太低、唤醒模型本身对噪声过拟合、后端没做“二次确认”。排查时先分两层看:

  • 第一层,把唤醒模型输出打印出来,观察纯噪声下得分。如果噪声段得分经常超过 0.5,说明模型鲁棒性不行,需要重新混入更多噪声训练,或者干脆换更小的特征输入,减少对环境的过拟合。
  • 第二层,加“唤醒词后 300ms 内必须有有效语音”的确认逻辑。很多误唤醒是电视里的广告词恰好触发,后面没有继续说话,就可以直接忽略。

我在一次产品中,误唤醒从每小时 8 次降到每天 1 次,靠的就是在唤醒命中后不立即中断静音,而是等 300ms 的语音能量确认,确认不了就回到 IDLE。

5.2 唤醒成功后设备“反应迟钝”,首包语音特别慢

一开始我以为是模型加载慢,后来排查发现是唤醒后录音的起始点没处理好。唤醒引擎检测到“小智小智”,系统却要从“现在”才开始录用户的话,而用户通常紧接着就说指令,这中间就丢了几百毫秒。正确的做法是:唤醒引擎维护一个带时间戳的环形缓冲,唤醒命中后把前 300ms 到 500ms 的音频一起发给 ASR。这样首包文本延迟会明显下降。

另一个常见原因是 Flash 读取模型太慢。大量模型存储在 SPI Flash 上,按需加载时如果没开缓存预读,每次读一两个字节都会卡顿。解决方法是启用 Flash 的 memory-mapped 模式,或者一次性批量 DMA 读取整个模型头,再边推理边流式读取。

5.3 ASR 和 TTS 同时加载把 RAM 顶爆

很多初学者把“按需加载”理解成“用的时候才初始化”,但还是按同样大小申请内存,结果 IDLE 时没事,交互时 OOM。我的经验是 TTS 和 ASR 的推理内存必须做到可复用,靠状态机强制分时。比如 LISTENING 状态的 ASR 结束时,不能立刻申请 TTS 内存,应先释放 ASR 的全部中间张量,再进入 RESPONDING。这里有个小细节:ASR 的解码图和 TTS 的声码器缓存大小差不多,可以共用一块内存池,只要控制好生命周期就行。

如果实在分不开,就做“轻量回退”:TTS 内存不足时,先用预录音提示语“正在处理中”顶一下,等网络返回后播放。这个体验比死机好得多。

5.4 低资源设备上能跑大模型吗?不能,但可以“借力”

很多朋友问,本地跑不动 LLM,是不是就没资格叫“AI 语音交互”?不是。低资源设备上真正有价值的 AI 是“感知 + 入口”:唤醒、降噪、命令词识别这些 AI 能力放在本地,开放语义理解放在云端。设备小不小没关系,交互体验的核心是“随时随地能唤起”和“快速响应”。你甚至可以只在设备端放一个唤醒词模型,把后续所有 AI 语音处理都交给云 API,产品照样成立。

如果非要本地跑,建议走两条路:一是用极小的专用模型,比如 1-bit 量化或蒸馏后的唤醒模型;二是用“外围 MCU + 主 SoC + 云端”的三级结构,把不同计算量放在不同层级。无论哪条路,都绕不开“哪些环节必须一直开着”这个顶层设计。

回到最初的问题:先分清常驻和按需,AI 语音交互就不难了

我调这个系统最大的收获,不是某一个模型的精度,而是对资源分配的理解。低资源设备上的 AI 语音交互,本质上是在跟内存和功耗做博弈。常驻的环节一定要做到轻、稳、省电;按需的环节一定要做到加载快、复用彻底、释放干净。每次我看到有人把 ASR 模型常驻在内存里,只为了省那几十毫秒启动时间,就忍不住想跟他聊聊电池续航。

最后再分享一个小技巧:所有状态切换点都要打日志,尤其是“模块释放”和“内存分配”。低资源设备的内存问题往往不是瞬间爆的,而是慢慢泄漏。我用一个简单的循环日志记录每次状态切换时的空闲堆大小,跑一天后看趋势,比用调试器断点高效得多。把这个习惯养成了,你手里的低资源设备就能既聪明又省电。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进