做了大半年AI口语陪练机,从最初的手工搭板子到现在的量产方案,中间踩过的坑比想象中多得多。这玩意儿看着就是个会说话的盒子,但真正把硬件、固件、音频链路和云端大模型串起来的时候,才发现每个环节都有不少门道。今天就把这套方案从零到一的完整思路拆开揉碎,包括那些文档里不会写的细节,给准备入坑或正在调板子的朋友做个参考。
1. 硬件方案选型:为什么我没有直接用现成开发板
市面上现成的AI语音开发板其实不少,像ESP32-S3、瑞芯微RV1126这类带NPU的板子,拿来跑个离线唤醒和简单的语音识别都没问题。但口语陪练这个场景有个特殊之处——它需要的是“对话式”交互,不是“命令式”交互。孩子说一句英文,设备要能录音、降噪、上传、等待大模型回复、再播放出来,这个链路对算力、内存、音频前端的要求完全不同。
我最终选的主控是全志V853,理由有三条:
- 内置64MB DDR3,跑轻量级Linux系统不紧张,给音频缓冲和网络协议栈留了足够空间。
- 自带HIFI5音频DSP核,可以在不占用主CPU的情况下做回声消除和降噪,这个对实时对话体验太重要了。
- 价格在量产BOM里能控制在合理区间,不像高端应用处理器那样成本失控。
麦克风阵列用了一颗双麦线性阵列,两颗MEMS麦克风间距大概3.5cm,这样既能做波束成形,又能兼顾小体积的ID设计。扬声器选的是3W左右的钕磁喇叭,音腔容积控制在8ml左右——这个参数是通过仿真和实测折中出来的,腔体再大低频会好一点,但整机厚度就压不住了。
这里有个容易被忽视的坑:麦克风开孔位置和喇叭开孔必须在结构上做物理隔离,中间要加密封泡棉挡墙。否则喇叭的声波直接通过外壳内部传导到麦克风,回声路径会变得极其复杂,后面DSP怎么调都压不干净,我们第一版样机就吃过这个亏。
电源部分用的是单节18650电池加升压方案,系统电压3.8V升到5V后,再分别给数字和模拟部分供电。模拟音频供电一定要单独用LDO隔离,千万不要直接怼到开关电源的输出上,否则底噪会大到孩子以为设备坏了。这块我在调试时用频谱仪看过,直接怼供电底噪能到-60dBFS,换成独立LDO之后能压到-80dBFS以下,差距非常明显。
2. 音频链路搭建:从MEMS麦克风到云端的一整条信号链
2.1 采集端的增益分配与抗混叠设计
麦克风出来的信号非常微弱,大概只有几个毫伏,必须先经过前置放大器才能给到ADC。我们用的是V853内置的3路ADC通道,采样率最高能到48kHz,24bit分辨率,理论上够用。
但这里有个关键参数——模拟增益分配。我在实际调试中总结的经验是:前级放大倍数控制在20dB左右,剩下的增益尽量交给数字域处理。原因是模拟放大倍数太高,会把电源纹波和PCB耦合噪声一起放大,而且一旦过载削波,后面任何算法都救不回来。数字增益虽然没有噪声问题,但要注意处理完截位别丢有效位。
ADC采样率我们锁定在16kHz,这个频率对语音交互是黄金标准,大模型的音频接口也基本都认这个采样率。之前试过48kHz采集,上传前再降采样到16k,理论上多一道处理就多一重风险,而且人耳对清辅音和摩擦音的感知在16k范围内已经足够,没必要给系统增加无谓负担。
抗混叠滤波器我直接在ADC内部开启了,截止频率设在7.2kHz左右。这个值比奈奎斯特频率稍低一点,能有效滤掉带外噪声,同时不会切掉语音信号的频带边缘。
2.2 回声消除与噪声抑制的工程实现
这是整个项目里我投入时间最多、也最想跟大家分享经验的部分。
回采参考信号必须走专门的参考通道,从扬声器功率放大器后端取,这个参考信号直接送给AEC模块。务必使用I2S回采,不要用ADC再采集一遍喇叭声音,后者会引入额外的相位延迟,导致AEC收敛不住。我们早期试过用模拟差分线直接采集功放输出,效果惨不忍睹,后来改成I2S数字回采就一切正常了。
AEC滤波器长度设置为1024点,在16kHz采样率下等效于64ms的延迟覆盖范围。这个长度基本涵盖了DSP处理缓冲、网络上传缓冲和功放输出延迟的总和。实际调测时发现,当滤波器长度不足时,回声尾巴会有明显的残留,听起来就像电话里的“嗡嗡”声。
噪声抑制(NS)部分用的是自研的谱减法加维纳滤波混合方案。双麦数据经过波束成形后,主麦指向使用者嘴巴方向,辅麦采集环境噪声。两者做自适应滤波相减,能在不伤语音的前提下压掉大概15dB的稳态噪声。实测在55dB左右的咖啡馆环境,语音唤醒率从单麦的82%提升到了94%,这个提升幅度非常可观。
2.3 播放链路的延迟控制
播放链路的延迟是整个对话体验的隐形杀手。我们最终把系统端到端延迟控制在120ms以内,其中音频采集处理占30ms,网络上传15ms,大模型首字响应开始流式返回后,剩下的主要由网络和TTS决定。
播放缓冲我设成了50ms,这个值既能抗抖动,又不会觉得拖沓。如果缓冲设太大(比如200ms),虽然更稳,但人耳对超过150ms的延迟就会明显感觉“迟钝”;设太小,Wi-Fi一抖就断音,体验更差。50ms是我们反复测试后的折中点,实测在普通家用路由器下,断音率低于0.3%。
3. 固件架构与模块划分
固件这块我用的是RTOS加轻量级组件方案,没有直接上完整版Linux,主要考虑是启动速度——从按下电源键到进入待唤醒状态,我们目标是控制在1.5秒以内。Linux光内核启动就得占掉小半秒,RTOS在场景里占尽优势,配套的驱动和内存管理也更可控。
固件模块按功能拆成了四个独立任务,通过消息队列通信,谁都不阻塞谁:
| 模块 | 职责 | 优先级 | 周期/触发 |
|---|---|---|---|
| 音频采集 | 从I2S读取PCM数据,填入环形缓冲 | 高 | 每10ms |
| 音频处理 | AEC/NS/AGC/降采样 | 中 | 每20ms |
| 网络传输 | WebSocket上行/下行数据 | 中 | 事件触发 |
| 播放控制 | 接收下行音频,写入I2S输出 | 高 | 每10ms |
音频采集和播放控制任务优先级最高,这里不能妥协,因为一旦被低优先级任务卡住,音频流就会出现明显卡顿。网络传输模块反而是可以偶尔延迟一下的,因为播放缓冲已经留了余量,网络抖动20~30ms不会影响听感。
主循环里放的是一个状态机,负责管理整个设备的生命周期:
- IDLE状态:只有唤醒词检测任务在跑,ADC保持开启,其他模块休眠,整机功耗在待机时只有32mA。
- LISTEN状态:检测到唤醒后进入,开始采集完整语音帧。此时会自动播放一声“滴”提示音,同时在状态寄存器里置位,防止重复唤醒。
- PROCESSING状态:等待大模型返回结果期间,麦克风继续监听,但不再响应唤醒词,只保留一个打断按键的中断响应。
- PLAYING状态:正在播放回复音频,此时如果有新的唤醒词,会立即停止播放并切回LISTEN,实现“随时打断”。
状态机的切换逻辑看起来容易,实际调试中发现最棘手的是打断时机处理。如果孩子正在跟AI对话的过程中,又说了新的唤醒词,必须保证音频采集通道干净、播放通道立刻关闭,而且不会把正在播放的内容串到后面的录音里。我们在固件里专门做了一个“播放立即静音”的硬件控制位,同时把播放缓冲区清零,才把这个逻辑彻底理顺。
3.1 启动流程与关键时序
启动时序这块我贴一段简化后的代码思路,方便大家理解整个流程:
void system_boot(void) { // 1. 初始化时钟和电源域,这个必须在最前面 system_clock_init(); power_domain_enable(PD_AUDIO); power_domain_enable(PD_WIFI); // 2. 初始化音频子系统 audio_subsys_init(); i2s_config(I2S_MODE_MASTER, SAMPLE_RATE_16K, BIT_DEPTH_24); codec_power_on(); // 3. 加载固件到DSP核,启动AEC算法 dsp_firmware_load("aec_ns_alg.bin"); dsp_start(); // 4. 启动Wi-Fi,这里可以和音频初始化并行 wifi_init(); wifi_connect(SSID, PASSWORD); // 5. 建立WebSocket连接 ws_connect("wss://api.xxx.com/v1/chat"); // 6. 打开麦克风采集通道 mic_open(); // 7. 进入待唤醒状态 set_system_state(STATE_IDLE); }实际开发中启动流程里最花时间的是第3步——DSP固件加载。V853的DSP核需要用专用的加载器把算法镜像搬进去,第一次搞的时候经常加载失败后主核和DSP核状态不同步,表现出来就是AEC不工作,回声吵得吓人。后面加了一个握手机制,DSP加载完会往共享内存写一个“就绪”标志,主核轮询到了才继续往下走,这个问题才算根治。
3.2 Wi-Fi连接策略与断线重连机制
口语陪练机对网络的依赖度很高,但家里Wi-Fi环境又千奇百怪,所以连接策略需要仔细打磨。我们做了以下处理:
- 连接优先用5GHz频段:5G频段干扰少、带宽高,但穿墙能力弱。如果设备离路由器远,会自动回落到2.4GHz。
- 完成Wi-Fi配置后,立即做云端连通性测试:向服务器发一个PING包,测RTT,RTT超过300ms就认为“弱网”,提示用户优化网络环境。
- 断线重连用指数退避算法:第一次重连等1秒,第二次等2秒,到最大间隔30秒封顶。测试发现,直线式固定间隔重连很容易在路由器重启的窗口期反复撞车,退避策略明显更稳。
- 保存最近的连接成功配置:设备重启后直接加载最近一次成功参数,不重新扫描,可以省掉2~3秒的启动时间。
有一件事必须在固件里做兜底——看门狗。只要系统状态机卡在某个状态超过一定时间,就自动重启,重启后自动回到上次正常工作的配置。这个在量产前一定得加,真实用户环境千奇百怪,靠测试是测不全的。
4. AI大模型对接:流式传输与上下文管理的实战经验
固件这块跑通了,接下来就是对接到真正的灵魂——大模型。这个环节虽然不涉及硬件,但很多交互细节直接决定产品体验,必须从固件层面就做好配合。
4.1 为什么选流式WebSocket而不是HTTP请求
最开始我们用的是传统HTTP POST,一次性上传音频、等待识别结果、再等大模型生成完整回复、最后播放。实测下来这个链路在弱网环境下,端到端响应时间动不动就到了3~4秒,孩子说完一句话要等那么久,口语练习的连贯性完全被打破了。
后来改成WebSocket长连接 + 流式返回,响应时间大幅改善:
- 音频采集过程中就能实时发送分帧数据,不用等用户说完再整体上传。
- 大模型生成回复时,首批内容几百毫秒就开始下发,我们边接收边播放,形成“打字机”式的效果。
- WebSocket连接建立后一直保持,省去了每次请求的握手开销。
做了个简单对比:
| 方案 | 端到端首包延迟 | 整体回复播放体验 |
|---|---|---|
| HTTP POST一次性请求 | 1.5~2.5秒 | 等待时间长,无反馈 |
| WebSocket流式 | 200ms左右首批 | 连续播放,自然度高 |
从协议设计上,上行数据用JSON包格式,里面包含一个音频帧的二进制块;下行数据同样用JSON封帧头、二进制封音频数据。这样的好处是协议简单清晰,方便扩展。
4.2 VAD(语音活动检测)的端侧判断逻辑
VAD是决定整个交互体验的隐形开关。如果太灵敏,会把环境噪声当成人声,白白上传一堆静音帧;如果太迟钝,容易把话尾截断,孩子最后那个辅音直接被吃掉,导致识别出错。
我最终在固件里实现了一套混合VAD策略:
- 短时能量检测:计算每20ms音频帧的RMS能量,超过阈值就标记为候选语音帧。
- 过零率检测:辅助判断是语音还是瞬态噪声,语音的过零率相对稳定,而门铃、敲击这类噪声过零率会异常偏高。
- DSP侧的语音存在概率:从AEC/NS模块拿一个0~1的概率值,这个值结合了谱特征和先验模型,比单纯能量判断靠谱得多。
- 静音超时判定:连续语音帧出现后,如果静音超过800ms,就自动判断为“一句话说完了”,主动封帧并上传。
这套逻辑跑下来,基本能模拟真人对话的停顿感和节奏,不会出现“孩子想中间喘口气”却被误判为说话结束的情况。
4.3 打断与重定向:让对话更像真人
口语陪练的核心场景里,孩子经常说一半改主意了,或者发现自己说错了想重新说。如果固件不具备打断能力和重定向逻辑,体验会非常生硬。
实现方式是:在PLAYING状态下,麦克风依然处于监听状态。一旦VAD检测到新的语音帧出现,而当前正处于播放过程中,系统立即触发“打断”:
- 播放通道强制静音并清空缓冲区。
- 把之前已经发送的请求标记为“可放弃”。
- 把当前正在采集的新音频追加到新会话中。
这里有一个重要的工程细节:打断时上行请求的Session ID要保持不变,但要在消息里增加一个“interrupt_prev”标志,让云端知道当前对话需要重新生成,而不是顺着上一轮继续。不然会出现AI还在回答上一个问题、孩子又问了新问题,两边内容串在一起的情况,我们在测试中踩过好几次。
4.4 音频格式的细节坑
大模型接口接收的音频一般是16kHz、16bit、单声道PCM或经过编码的Opus格式。我们上行直接发PCM裸流,简单直接,但流量消耗偏大。后来切换成了Opus编码,在保持音质的前提下压缩到原来的五分之一,对弱网场景帮助非常大。
下行音频从云端返回的是24kHz采样率的MP3编码,因为TTS合成出来的声音高频成分丰富,16kHz采样率会明显损失明亮度。播放前,固件先把MP3解码成PCM,再做24kHz→16kHz降采样。这里要注意的是,降采样前必须加低通滤波器,否则会产生频谱混叠杂音,听起来像“金属声”,很容易被用户误判为硬件问题。
4.5 多语言混合与发音评测的云端协作
口语陪练跟普通对话机器人最大的不同,是它需要对孩子的发音进行评价。这就意味着上行音频不能只传一次,而是在对话过程中,还要额外传一个“评测版本”的音频流,交给云端的发音评测引擎做音素级打分。
我们在协议里扩展了一个channel_id字段:
- channel_id=0:对话识别通道,走ASR和第二轮对话模型。
- channel_id=1:发音评测通道,走音素比对和流利度评分模型。
云端返回时,评测通道的延迟要求可以放宽到1秒内返回即可,因为测评反馈可以在对话结束后的停顿期展示,不会打断对话节奏。
5. 固件OTA升级策略:为后续算法迭代留好后路
任何硬件产品都不可能一版固件定终身,尤其是AI产品,模型的更新迭代往往以周为单位。OTA升级模块看似不起眼,但设计不好会直接导致用户设备变砖,这块值得单独聊。
5.1 差分升级与全量升级的取舍
固件体积大概在8MB左右,如果每次都全量下载,耗时长且浪费用户流量。我们升级包采用了差分升级方案,生成的diff包大小在1.5~2MB之间,大幅降低下载时间和失败概率。
差分升级有一定风险——如果用户在升级过程中断电,容易出现固件固件损坏。我们的做法是双分区方案:
- A区运行当前固件。
- B区存放新固件,整个下载并在B区写入完成后,设置标志位,一次性切换启动分区。
- 切换后如果新固件运行异常(看门狗连续复位3次),自动回滚到A区,确保不会变砖。
5.2 升级时机选择
口语陪练机的使用场景是孩子主动练习,高频率对话期主要集中在晚上。我们升级策略是:
- 优先在设备空闲且电量高于30%时推送升级包下载。
- 下载完成后不立刻切换,等到凌晨2:00~4:00自动静默升级。
- 如果连续3天没有等到空闲窗口,就弹提示引导用户手动升级。
这块在测试中确实发现在弱网环境下升级包下载容易中断,所以下载模块加了断点续传能力,服务器端按块存储校验值,客户端记录已接收块位置,再次下载时跳过已完成部分。
5.3 升级包签名与安全校验
智能设备联上云,安全一定不能轻视。固件升级包必须做数字签名校验,升级前先验签,防止被恶意篡改。我们用的是RSA2048签名,验签在BootROM阶段完成,即使用户拆机短路引导引脚也绕不开这道防线。
设备端存储了公钥,私钥只放在服务器端。做过一次泄密演练,发现私钥一旦被拿走,攻击者就可以伪装成设备接入云端,所以平时私钥保管和权限控制必须做严格的分离,不能开发者一个人说了算,要有个互相监督的机制。
6. 实测数据与调试工具推荐
项目收尾阶段,我们做了为期两周的入户测试,样品覆盖了6个家庭,孩子年龄在5~12岁之间。最终关键数据如下:
| 指标 | 测试结果 | 备注 |
|---|---|---|
| 首次唤醒成功率 | 96.7% | 环境噪声低于55dB场景 |
| 对话端到端延迟 | 950ms左右 | 本地Wi-Fi + 云端GPU推理 |
| 断流率 | 0.3% | 常规家庭路由器环境 |
| 整机续航 | 4小时连续对话,8天待机 | 3000mAh电池 |
| TTS自然度MOS分 | 4.3 / 5.0 | 第三方主观评测 |
调试过程中最有用的三样工具,缺一不可:
- 逻辑分析仪:抓I2S时序和媒体数据非常方便。有一次怀疑ADC输出异常,用它直接抓PCM数据跟正常波形做对比,很快就定位到是MCLK时钟没配够,导致位时钟抖动。
- 优盘挂载抓日志:固件里预留一个日志模块,把关键路径打点,输出到串口的同时也可以写进TF卡/优盘。入户测试时直接让用户插个U盘,跑一晚上把日志丢给我们分析,效率极高。
- 网络抓包工具(Tcpdump或Wireshark):排查Wi-Fi丢包和WebSocket断线时的重连细节,必须得靠它。
7. 那些固件之外的连带坑
最后聊几个不属于固件本身、但开发过程中必然会碰到的事。
**声学结构验证一定要提前做。**我们第一版结构件因为ID设计太激进,麦克风开孔朝天,加上音腔结构跟主板靠得太近,实测啸叫和共振问题严重。后来改了结构,加了硅胶减震和密封泡棉,效果立竿见影。声学这块不是固件算法能兜底的,到了后期靠算法补救只会越搞越复杂。
**云端接口的容错性要提前设计。**实际使用中,孩子可能会突然拔掉Wi-Fi插头,或者家长直接把路由器关了。设备端对网络异常要有完善的提示机制,不能让人感觉“AI哑巴了”。我们做了一套分级提示:轻度抖动时语音提示“网络不太稳哦”,中度异常时播放一段欢快音效并进入重连模式,重度异常时红灯闪烁提示检查Wi-Fi。
**家长端的联动功能别忽略。**陪练机不只是给孩子用的,家长得能看到孩子的练习报告和发音进步曲线。所以固件上云时,设备状态数据(每次对话时长、打扰次数、唤醒成功率)要一起上报,这些数据在家长App里就是最直观的“进步怎么看”的依据。
项目做到这个阶段,最大的体会是:AI口语陪练机本质上不只是一个“带喇叭的录音机”,它是一个音频链路、实时通信和嵌入式状态机的精密组合体。任何一个环节掉链子,最终呈现的都是一个“不太聪明的盒子”。硬件方案选型要克制,固件逻辑要闭环,音频链路要较真,云端对接要灵活,这几条主线理顺了,产品的基本盘就稳了。之后再叠加模型迭代和场景创新,就是锦上添花的事了。