
讲个具体的场景一块国产低端语音模组主控是单核 400MHz 的 Cortex-M 级别芯片内部 RAM 只有 512KBFlash 8MB没有独立 NPU。就这么个配置硬是把“离线唤醒 云端大模型语音问答”这种 AI 语音交互跑起来了。很多人第一反应是“这么小的设备能跑 AI”其实真正的问题不是“能不能跑”而是另一件事低资源设备上的 AI 语音交互到底哪些环节必须一直开着哪些环节可以关了再启动。我最早做这类项目时也犯过糊涂想着语音交互就是一个流水线从麦克风到扬声器全流程都很重要那就全都在线跑。结果内存直接爆掉功耗也压不下去50mA 的电池供电设备两小时就没电。后来踩了一轮坑才明白低资源设备上的 AI 语音交互本质是“常驻的轻量监听”和“按需的重型计算”之间的配合。这篇就把链路拆开讲清楚顺便给出我自己实际用的模块状态表、内存预算和排查思路给正在做低成本音箱、故事机、智能家居面板、离线语音模组的同学一个参考。1. 语音交互链路拆解从唤醒到出声谁停谁转1.1 完整链路不止“听到再说”其实有 8 个环节很多教程喜欢把语音交互简化成“麦克风输入 - 识别 - 回答 - 播放”听起来简单。但真正落地到低资源设备至少要拆成 8 个环节麦克风采集把声压变成数字信号通常 16kHz/16bit 单声道也可能 8 通道麦克风阵列。前端信号处理回声消除AEC、自动增益AGC、降噪NS、声源定位。这一步不做后面识别率会很难看。语音活动检测VAD判断“有没有人说话”而不是“说了什么”。唤醒词检测识别特定词比如“小智小智”这是低资源设备最常见的本地 AI 入口。语音识别ASR把语音转成文本可以是本地命令词识别也可以上传云端做大词表识别。语义理解/任务处理传统叫 NLU现在很多设备直接接 LLM API理解用户问什么决定怎么回答。语音合成TTS把文本转成语音。云端返回音频或本地跑 TTS 模型。音频播放与反馈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 - PROCESSINGASR 出了完整文本立刻释放 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按需与唤醒词模型分时复用一块 SRAMTTS 推理池192KB按需与 ASR 推理池内存复用网络协议栈缓冲48KB按需只在 PROCESSING 状态使用临时文本/JSON16KB按需LLM 回复解析你可能已经发现了唤醒词模型 256KB ASR 推理池 256KB 系统 96KB 音频缓冲 64KB 早就超过 512KB 了。关键在于ASR 推理池和唤醒词模型所在的内存区域是物理重叠的。唤醒词模型常驻在 Flash不占 RAMASR 模型加载进 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网络往返 600msTTS 首包 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 模型常驻在内存里只为了省那几十毫秒启动时间就忍不住想跟他聊聊电池续航。最后再分享一个小技巧所有状态切换点都要打日志尤其是“模块释放”和“内存分配”。低资源设备的内存问题往往不是瞬间爆的而是慢慢泄漏。我用一个简单的循环日志记录每次状态切换时的空闲堆大小跑一天后看趋势比用调试器断点高效得多。把这个习惯养成了你手里的低资源设备就能既聪明又省电。