我最初在树莓派上折腾Snowboy,是想给一个老旧的USB麦克风找个正经用途。树莓派装Snowboy,说到底就是在本地跑一个“离线唤醒词检测引擎”,让树莓派像智能音箱一样,听到特定词才响应,而不是连续录音上传到云端。这个项目对语音助手DIY、智能家居控制、甚至给学生做课程设计都很有参考价值。我们一步步把环境、依赖、编译、模型、调试全部走通,顺带聊聊那些网上教程不会明说的坑。
这个文章适合有一定Linux基础、想在树莓派上做语音交互的同学。不需要你懂深度学习的细节,按照步骤走就能把“唤醒”这件事真正跑起来,后面再看代码才不会觉得虚。
1. 为什么都2024年了,我还要在树莓派上折腾Snowboy
1.1 Snowboy到底是什么
Snowboy是由Kitt.AI团队推出的唤醒词引擎,当年被很多智能硬件项目采用,后来团队被百度收购后,仓库就停留在1.3.0版本不再更新。它做的事情非常聚焦:在本地不断监听麦克风,用模型判断当前声音里有没有出现你指定好的唤醒词,比如“snowboy”“hey jarvis”“smart mirror”。
注意“本地”和“离线”这两个词是关键。它不像现在很多智能音箱那样把音频传到云端做语音识别,而是所有声学特征提取和分类推理都在树莓派上实时完成。这意味着哪怕你的树莓派断网了,唤醒功能照样能用,而且不存在隐私泄露的问题。对做智能家居的人来说,“隐私”往往是选择本地方案最直接的动机。
Snowboy的内部原理可以简单理解为:先用音频预处理从原始波形里提取出类似MFCC的声学特征,然后把这些特征送给一个训练好的深度卷积神经网络做二分类判断,输出的是“这个声音片段是唤醒词”的概率。整个模型很小,计算量也不大,所以树莓派这类性能并不强的板子也能实时跑,占用CPU一般只有百分之十几,比单纯跑一个本地STT要轻得多。
1.2 为什么选树莓派而不是PC开发板
树莓派跑Snowboy有三个明显的优势:第一,树莓派生态是语音交互项目的“标准实验室”,GPIO可以方便地接LED灯、继电器、红外发射头,唤醒之后马上联动硬件,扩展性极强;第二,树莓派系统带完整的ALSA音频栈,配合便宜的USB麦克风就能干活,入门成本低;第三,树莓派4B以及更新的5代性能已经足够,在跑Snowboy的同时还能很轻松地接一个TTS引擎和简单的意图识别逻辑。
如果你用的是树莓派5,需要注意一个问题。Snowboy官方提供的预编译库只覆盖了armv7和armv6的32位环境,树莓派5默认跑的是64位系统,直接拿官方编译产物基本跑不起来。我的建议是,如果手上是树莓派5,要么刷一个32位的Raspberry Pi OS,要么老老实实走源码编译路线。树莓派4B到是不用纠结,32位系统下装起来最顺手。
有人可能会问:Snowboy已经停止维护了,为什么不用新的唤醒词引擎?我承认新方案有很多,Porcupine、openWakeWord都不错,但Snowboy在大量开源项目里留下了完整示例和成熟的接入模式,尤其是围绕小智语音这类本地对话助手的讨论中,依然能看到Snowboy的身影。对学习唤醒词原理的人来说,它代码量小、逻辑清晰,拆开来看比看一个大框架舒服得多。
2. 装之前先把家底盘清楚:系统、依赖和音频设备
2.1 镜像和Python版本选择
安装Snowboy第一步不是敲pip命令,而是先把系统环境定下来。我从实际测试得到的结论是:Raspberry Pi OS 2023年之前基于Bullseye的32位版本配Python 3.7/3.9,安装Snowboy的阻力最小。新版Raspberry Pi OS基于Bookworm,默认Python 3.11,SDK里SWIG生成的旧式Python绑定编译时经常报“unknown type name”这类错误,所以新手我不想让你一上来就和编译器搏斗。
我建议你先确认系统位数:
uname -a输出如果是aarch64,说明你跑的是64位内核;如果是armv7l,说明是32位。如果你用的是树莓派4B且不是必须用64位,建议直接刷32位系统,后面编译Snowboy会省很多事情。树莓派5想稳定使用,可以试64位系统加源码编译,但一定要做好移植代码的准备。
接着检查Python版本:
python3 --version如果版本高于3.9还编译不过,可以尝试把SWIG生成的代码做小量修改,或者使用我后面提到的替代方案。不要被版本劝退,先按这个基线走。
2.2 安装基础依赖和编译工具
Snowboy的Python绑定依赖SWIG来生成C++和Python之间的桥接代码,另外还需要Atlas数学库用来跑矩阵运算。把这些一起装上:
sudo apt update sudo apt install -y git swig sox libatlas-base-dev python3-dev portaudio19-dev python3-pyaudio这里我唠叨两句每个包的作用:
- swig:把C++接口翻译成Python模块的工具,Snowboy正式支持Python2,后来社区做了Python3适配,但依然需要SWIG重新生成。
- libatlas-base-dev:BLAS数学库,Snowboy推理阶段大量使用矩阵运算,依赖这个库。
- portaudio19-dev和python3-pyaudio:给Snowboy的示例代码提供录音输入。Snowboy核心本身可以直接从ALSA读取音频,但官方示例decoder模块用的是PyAudio,所以少不了它。
装完后顺手测试一下麦克风。很多人跳过这一步,结果后面跑起来才发现根本没有音频输入,这个坑非常典型。先用:
arecord -l查看系统识别到的录音设备。如果你用的是USB麦克风,应该能看到一个card编号,比如card 1。然后用下面的命令录一段5秒的音频:
arecord -D plughw:1,0 -f S16_LE -r 16000 -c 1 test.wav这里的数字要和上面看到的card编号一致。录音结束后播放确认有声音:
aplay test.wav能听到自己的声音,说明音频链路是通的。Snowboy默认采样率是16000Hz,单声道16bit,所以录音参数直接用这个规格最稳。
2.3 别忽略默认声卡配置
树莓派默认的声卡不一定是USB麦克风,很多系统会把HDMI音频或者板载3.5mm接口排在前面,导致Snowboy打开设备失败。你可以在用户目录创建一个 .asoundrc 文件,把USB麦克风设为默认输入设备。
先用arecord -l找到USB麦克风的card编号,我这里是card 1,然后:
nano ~/.asoundrc写入:
pcm.!default { type hw card 1 device 0 } ctl.!default { type hw card 1 device 0 }保存之后,再用:
arecord -D default -f S16_LE -r 16000 -c 1 test2.wav如果这样能录到音,说明默认设备已经生效了。这个配置对后面的Snowboy极其重要,因为它默认会打开名为“default”的录音设备,如果不对应到实际麦克风,就会一直报ALSA错误。
3. 两条主流安装路线:pip快速装与源码编译
3.1 pip安装为什么容易翻车
在树莓派上安装Snowboy,最简单的方式当然是:
pip install snowboy如果你的树莓派系统是旧的32位Raspberry Pi OS、Python版本在3.7以下,这条命令也许能直接成功。因为PyPI上有编译好的armv7包。但实际环境很少这么顺,Python版本稍微新一点,pip就会尝试从源码编译,然后被SWIG生成代码里的兼容问题卡住。
我自己踩过的典型报错是:
error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1后面跟着一大堆C++语法错误。这种情况基本是源码包和Python3.9以上的头文件不兼容导致的。所以我不建议你把这作为首选路线,除非你只是想快速验证,并且已经准备好了回退方案。
3.2 源码编译的正确打开方式
源码编译虽然听起来复杂,但其实是可控的。先从GitHub拉取Snowboy仓库:
git clone https://github.com/Kitt-AI/snowboy.git cd snowboy仓库里有现成的Python3 SWIG绑定目录,我们只要进入这个目录做编译:
cd swig/Python3 make如果系统里的Python版本比较老,这一步通常能顺利跑完。编译完成后,目录里会生成一个snowboydetect.so文件,这就是Python可以直接import的扩展模块。不过仅仅有.so还不够,还需要同目录下的snowboydetect.py和snowboydecoder.py两个文件,前者是C++接口的封装,后者是官方提供的调用脚手架,里面已经写好了麦克风采集和回调逻辑。
为了让项目目录干净一点,我通常是建一个自己的项目文件夹,把这三个文件复制过去,再把模型文件也统一放在那里。比如:
mkdir ~/wake-word cd ~/wake-word cp ~/snowboy/swig/Python3/snowboydetect.py . cp ~/snowboy/swig/Python3/snowboydetect.so . cp ~/snowboy/swig/Python3/snowboydecoder.py . cp -r ~/snowboy/resources .3.3 编译过不去的排查思路
这里的源码编译,最需要留神的就是Makefile里的Python版本路径。默认的Makefile一般是针对特定Python版本写的,如果你的Python是3.9但你机器上是3.7,需要打开Makefile看一下这几行:
PYTHON := /usr/bin/python3 PYTHON_CONFIG := /usr/bin/python3-config确认python3-config这个命令存在,并且输出路径和你的Python版本匹配:
which python3-config ls -l $(which python3-config)如果系统没装python3-dev,那么python3-config可能不会返回正确结果,编译器会因为找不到Python.h而失败。这也是为什么我在前面一定要你先把python3-dev装好。
编译完成后,导入验证是最重要的:
python3 -c "import snowboydetect"如果没有任何输出,说明模块已经能正常导入了。碰到报错缺少某个共享库,一般用:
ldd snowboydetect.so看一下哪个依赖找不到,再通过 apt 安装对应的库,比如libatlas-base-dev就是很常见的缺失项。
4. 让唤醒词真正跑起来:模型选择、调用与调试
4.1 模型文件哪里找
Snowboy的模型文件后缀是.pmdl或者.umdl。官方GitHub仓库的resources文件夹里其实已经带了一批,例如:
- snowboy.umdl:官方通用唤醒词,唤醒词就是“snowboy”
- jarvis.pmdl:唤醒词“jarvis”
- smart_mirror.pmdl:唤醒词“smart mirror”
你不需要联网,在仓库里直接就能找到这些文件。把这些模型文件放到项目目录后,就可以开始写调用脚本了。
如果想让唤醒词变成自己的名字或者自定义的英文单词,比较标准的方式是去Kitt.AI官网训练模型。但Snowboy项目已经停止维护,官网的模型训练服务有时候能访问、有时候很慢,甚至可能提示不可用。如果你刚好需要自定义中文唤醒词,比如“小智”“小树”,Snowboy的官方训练方式对中文支持也非常有限。中文唤醒词建议还是不要死磕Snowboy,后面我提到的新方案会更合适。
4.2 最小可运行的唤醒脚本
现在我们写一个最简脚本,让树莓派在听到唤醒词后打印一句话并退出。在项目目录下新建awake.py:
import snowboydecoder def on_detected(): print("唤醒成功,检测到唤醒词") # 这里可以扩展后续操作,比如开灯、放提示音、开始录音 exit(0) detector = snowboydecoder.HotwordDetector( "resources/snowboy.umdl", sensitivity=0.5, audio_gain=1.0 ) print("开始监听,请说唤醒词 snowboy……") detector.start(detected_callback=on_detected, sleep_time=0.03)运行:
python3 awake.py对着麦克风说出“snowboy”,如果一切正常,终端会立刻输出“唤醒成功”。这里有几个参数说明:
- sensitivity:灵敏度,范围0到1,数值越低越难误触,但也会变迟钝;数值越高越敏感,但可能把环境噪声当成唤醒词。我一开始调到0.7,发现风扇声音都能触发,后来降到0.45才好很多。
- audio_gain:输入音频增益,如果你的麦克风离人远,或者录音声音轻,可以适当提高,1.0一般够用。
- sleep_time:检测循环的睡眠间隔,越小CPU占用越高,但响应越及时,0.03在树莓派4B上体验很流畅。
detector.start()是一个阻塞函数,会一直循环监听,想要程序在后台跑,可以让它和主逻辑放在同一进程,也可以单独开线程。
4.3 解决ALSA设备抢不到的问题
跑脚本时最常见的报错是:
ALSA lib pcm.c:8526:(snd_pcm_open) Unknown PCM cards.pcm.rear ALSA lib pcm.c:8526:(snd_pcm_open) Unknown PCM cards.pcm.center_lfe ALSA lib pcm.c:8526:(snd_pcm_open) Unknown PCM cards.pcm.side Cannot open microphone不用慌,这些ALSA库的报错信息看着吓人,其实是它在枚举周边设备时遇到不存在的“rear”“side”等声卡,打印的警告不一定致命。真正致命的是最后一行Cannot open microphone。
绝大多数时候,这个错误就是默认录音设备不对。按我前面说的,写好~/.asoundrc并且用arecord -D default测一遍,基本能解决。
还有一种情况是,你另外的程序先占用了麦克风,比如已经在运行某个录音脚本。ALSA设备默认不支持多进程同时打开,必须先关掉占用源。排查方法:
fuser -v /dev/snd/* sudo kill -9 <PID>这个命令可以看到具体是哪个进程占用了声卡文件,杀干净再跑Snowboy。
4.4 灵敏度调的三个小技巧
调灵敏度不是玄学,是可以靠测试量化的。你可以在脚本里加一个计数器,连续触发多少次算一次有效命中,用不同sensitivity跑几轮,统计误触发率和漏报率。
我的经验是:
- 环境噪声大,比如树莓派旁边有风扇,sensitivity往0.3到0.4靠
- 麦克风离嘴很近,低于50厘米,sensitivity可以顶到0.6以上
- USB麦克风质量一般时,audio_gain加太高会引入更多底噪,还不如调灵敏度
如果你发现“说三遍唤醒词只成功两次”,大概率不是灵敏度问题,而是模型本身对男女声、语速、口音的泛化差异。官方自带模型对英文唤醒词覆盖还不错,但对中文或者带口音的英文确实不友好。
5. 唤醒之后怎么办:对接语音助手与TTS
5.1 搭建一个完整的语音交互流程
唤醒词检测从来不是终点,它只是一个“门卫”。在真实项目里,门卫值班的目的是让后面的录音识别和智能问答逻辑在“被需要”时才运行,既省电又省心。一个典型的树莓派本地语音助手流程是这样的:
唤醒词检测 → 发出“叮”提示音 → 录音3到5秒 → 语音识别(STT) → 意图解析/调用回复 → 语音合成(TTS)播放 → 回到唤醒监听
这个流程在我自己搭的小智风格语音助手项目里跑得很顺。Snowboy在这个链条里只占第一阶段,但它的稳定程度决定了后面所有环节的质量。如果唤醒经常失灵,用户会对着机器喊很久,体验非常糟糕。
5.2 用一个回调函数串起整条链路
我这里给出一段比较完整的示例代码,它把唤醒后的动作做得更丰满一点,包括播放提示音、自动录音、然后保存音频文件,方便你后续接STT服务:
import snowboydecoder import subprocess import time def play_tone(): subprocess.Popen(["aplay", "ding.wav"]) def record_audio(): # 录制3秒音频,命名带时间戳 filename = "command_" + str(int(time.time())) + ".wav" subprocess.run([ "arecord", "-D", "default", "-f", "S16_LE", "-r", "16000", "-c", "1", "-d", "3", filename ]) print("录音已保存:", filename) # 这里可以接入faster-whisper或其他STT引擎 def on_detected(): print("唤醒成功,开始提示音并录音……") play_tone() record_audio() def main(): detector = snowboydecoder.HotwordDetector( "resources/smart_mirror.pmdl", sensitivity=0.4, audio_gain=1.0 ) detector.start(detected_callback=on_detected, sleep_time=0.03) if __name__ == "__main__": main()注意一个细节:我在on_detected里直接调用record_audio,而record_audio是同步阻塞的。这会阻塞Snowboy的检测循环,但对你录音是合理的。如果你需要在执行任务的同时继续监听唤醒词,就必须把任务扔到线程里处理,否则第二次唤醒词将不会被识别。
就树莓派4B来说,CPU跑Snowboy大概占10%到20%,录音时占得更多。如果你还要同时跑TTS和STT,建议对CPU做一个大致预算。我遇到过同时开唤醒检测、TTS和浏览器,树莓派直接卡死的情况,后来用htop排查发现唤醒检测占CPU并不高,倒是那些“一次性任务”累加起来拖垮了系统。
5.3 和TTS怎么配合比较自然
语音助手没有语音回复就没有温度。树莓派上最简单的TTS是用espeak:
sudo apt install espeak -y espeak -v en-us "hello"中文效果很机械化,但胜在离线、快、不占资源。如果你能接受联网TTS,比如调用一些在线API,声音质量会好很多,但要注意网络延迟可能让对话变得卡顿。我在实际项目中测试过,唤醒后TTS在2秒内返回语音的体验才算合格,离线方案在树莓派本地是更有优势的。
SnoWBoy本身不关心你用什么TTS,它只要在回调函数里完成触发就行。所以你可以自由组合,不用担心绑定关系。我的建议是:先把提示音和录音链路调通,再慢慢去换更好的TTS,这样问题定位起来方便。
6 常见问题与排查技巧实录
6.1 高频问题速查表
为了让后面的人少走弯路,我把常见问题和排查方法整理成一张速查表,你在实际操作中遇到类似情况可以直接对照:
| 现象 | 可能原因 | 解决办法 |
|---|---|---|
| 运行时报Cannot open microphone | 默认录音设备不是USB麦克风 | 配置 ~/.asoundrc,用 arecord -D default 验证 |
| 编译时找不到Python.h | 缺少python3-dev | sudo apt install python3-dev |
| 导入snowboydetect报错缺少.so依赖 | libatlas缺失 | sudo apt install libatlas-base-dev |
| 唤醒词不响应 | 麦克风增益太低 | 调大audio_gain到1.5到2.0 |
| 误触发频繁 | 灵敏度太高或环境噪声 | 降低sensitivity到0.3到0.4 |
| 运行一段时间后设备被占用 | 其他进程占用了声卡 | fuser -v /dev/snd/* 找到进程并结束 |
| Python3.10以上编译失败 | SWIG旧代码不兼容新Python | 换Python3.7/3.9系统,或迁移到新唤醒引擎 |
这个表格是个起点,不是终点。你最终还是要结合日志判断,因为同样的“Cannot open microphone”可能由三种完全不同的原因导致。
6.2 我差点被“No module named snowboy”坑到底
我最初在树莓派上装Snowboy,最崩溃的报错是:
ModuleNotFoundError: No module named 'snowboy'这是因为pip包名和import名并不完全对应,而且在不同平台上有不同的分发方式。用pip安装之后,snowboy包里的模块名是snowboydetect、snowboydecoder,不是snowboy。所以正确写法是:
import snowboydecoder而不是:
import snowboy很多人照着旧教程,第一行就写import snowboy,教程没问题,但Snowboy的目录结构和模块名在不同版本里确实有改动。建议你在导入前先看看到底有没有snowboydecoder.py和snowboydetect.so,再决定import什么。
6.3 设备占用的一个隐蔽细节
还有一个容易被忽略的坑是,树莓派系统自带的“蓝牙音频”和“HDMI音频”服务会在后台偷偷打开声卡,导致你的程序监听时抢不到设备。如果你把~/.asoundrc配置好依然报Cannot open microphone,可以试试临时停掉蓝牙音频服务:
sudo systemctl stop bluetooth在树莓派上,蓝牙服务并不是每次都会干扰,但如果你同时接USB麦克风和蓝牙设备,冲突概率很大。我后来干脆给模板里加了一条启动脚本,手动决定哪些音频服务开机不启动,彻底解决设备占用问题。
7 Snowboy维护停滞之后,我为什么还会用它
7.1 老代码的价值比想象中高
Snowboy确实是一个“停止维护”的项目,这是事实。但我依然喜欢在树莓派上给它配环境,倒不是因为它技术前沿,而是因为它的代码量小,边界清楚,适合作为理解“唤醒词引擎”的入门样本。打开它的源码,你能直接看到音频预处理、特征提取、模型推理的整体流程,不像一些大而全的语音助手框架藏着太多抽象层。
如果你只是想让产品跑起来,不值得和旧依赖较劲。但如果你和我一样,愿意花一个下午把SWIG、ALSA、共享库这些底层东西全走一遍,收获是实打实的。树莓派本来就是拿来玩和折腾的,有过一次把老项目救活的经验,以后再碰其他停止维护的开源项目会淡定了很多。
7.2 新项目我会这样选
如果是新项目,尤其是要上产品、要长期维护,我更推荐用还在更新的唤醒词方案。Porcupine支持树莓派和多种语言,有免费档位,配置更友好;openWakeWord也更现代。但它们的模型训练平台、许可证和路径都不同,需要额外学习成本。
我的建议是两条腿走路:学习原理和做课程设计,用Snowboy没问题,相关资料多、坑也都被前人踩过了;如果想做个真正能稳定24小时在线的家居语音助手,优先考虑仍在维护、对树莓派有官方支持的引擎,否则未来系统一升级,维护成本会一直找上你。
7.3 关于树莓派5的一点个人判断
很多朋友问树莓派5能不能顺利安装Snowboy,说实话,树莓派5在性能上完全够用,但它是64位ARM架构,官方编译好的armv7库基本用不了,从源码编译又要面对Python 3.11+的兼容性问题。如果你非得在树莓派5上跑Snowboy,我建议先降级到32位系统再试,但这样又浪费了树莓派5的性能优势。
我自己折腾下来,比较务实的选择是:树莓派4B跑Snowboy做唤醒,树莓派5跑现代的openWakeWord或Porcupine做同一件事。这样既保留了老方案的学习价值,又能用上新硬件的能力。等你有精力做二次开发,还会慢慢体会到在不同架构上适配语音开源项目的乐趣。
说实话,树莓派安装Snowboy这件事,技术上并不算复杂,难点全在环境和细节。你只要把音频设备配置好、依赖装齐、源码编译通过,后面的调用逻辑基本就是一马平川。我在实际调试中最大的体会是:不要在“准备工作”上偷懒,麦克风测试、asoundrc配置、Python版本确认,每一步都做扎实,比编译时反复报错再回头找原因要快得多。最后再分享一个小技巧:把编译产生的.so文件和模型文件单独备份,下次换一张SD卡或者迁移到另一块树莓派上,可以省掉重新编译的半小时,直接拷贝复用,这一点在折腾树莓派的老朋友之间很实用,希望你能用上。