树莓派离线语音助手搭建:基于Sherpa-onnx的完整指南
2026/9/24 4:59:38 网站建设 项目流程

家里网络一抽风,语音助手就变成了一个只会转圈的摆设,这种体验你应该不陌生。我上次就是在路由器重启的那几分钟里,对着音箱喊了好几遍“关灯”,最后只能自己站起来去按开关。也就是从那时候开始,我决定把语音助手彻底搬到树莓派上,完全离线运行。这个方案的核心,就是标题里提到的Sherpa-onnx这个开源工具包。

这篇文章我按“为什么选它、怎么搭环境、怎么跑通代码、怎么串成完整流程、实际会遇到哪些坑”这个顺序来写,全程基于树莓派 4B + Ubuntu 22.04 Server 实测。无论你是第一次接触嵌入式语音,还是已经折腾过几个树莓派项目,照着文中的步骤操作,基本都能把一套“能听、能说、不依赖外网”的离线语音助手跑起来。我会把关键代码、模型选型、调试经验全部放出来,能省的时间我一定帮你省。

1. 为什么我决定在树莓派上折腾一个完全离线的语音助手

先说说动机。市面上大多数语音助手,语音识别、语义理解、语音合成都在云端完成。本地录一句话,传到服务器,服务器算完再传回来,链路长不说,还牵扯三个问题。

1.1 联网语音助手的三个硬伤

第一个是延迟。即使网络状况很好,一次完整的“语音→识别→响应→合成”也要一两秒,如果中间夹着智能家居平台的云转发,延迟会更高。你在客厅说“关灯”,灯可能要两三秒后才动,体验非常割裂。

第二个是隐私。房间里的每一句话都会被传到远端服务器,不管服务商怎么承诺“脱敏处理”,从本地设备的角度看,这就是把家庭环境的声音数据完全交给第三方。对很多场景来说,这是不可接受的。

第三个是稳定性,也是让我彻底放弃联网方案的直接原因。断网、路由器重启、云服务商接口波动,都会让本来简单的语音指令变成废操作。一旦家里网络出现问题,语音助手就是一块砖,这种“能力建立在网络永远在线”的前提上,本身就是脆弱的。

1.2 为什么以前不行,现在行了

过去在树莓派上跑语音识别不是不行,而是效果和成本很难平衡。早期的开源识别模型体积大、推理慢,4B 那块 ARM CPU 跑起来经常是“识别完,人已经走了”。而现在情况完全不同了。

一方面,端侧模型被压缩得越来越小,量化技术成熟后,模型体积和推理速度都大幅优化。另一方面,ONNX Runtime 对 ARM 平台做了不少底层优化,同样一个模型,在树莓派上跑的速度比几年前快很多。再加上社区里已经有成体系的中文预训练模型可以直接下载,不再需要自己采集数据训练。所以在 2025 年的今天,“树莓派 + 离线语音”已经不是玩具级玩法,而是真正能日常使用的方案。

2. 选型对比:为什么是 Sherpa-onnx 而不是其他方案

确定离线方向之后,我在选型上花了一些时间。市面上的本地语音方案并不少,但能同时满足“中文识别效果好、支持离线合成、ARM 上跑得动、部署不折腾”这四点的,确实不多。

2.1 主流离线语音方案横评

我实际试用过下面这几种方案,先给个直观对比。

方案离线识别离线合成中文效果ARM 性能上手成本
Vosk支持不支持中规中矩流畅
whisper.cpp支持不支持不错偏慢
PaddleSpeech支持支持依赖较重
Sherpa-onnx支持支持流畅

Vosk 的识别能力不错,部署也简单,但它不自带语音合成模块,你要单独再去接一个 TTS,模块之间的协作难度就上来了。whisper.cpp 强在识别质量,尤其是复杂环境下,但它本质是离线转写工具,不是为实时对话场景设计的,推理延迟偏高,树莓派上跑实时对话比较吃力,也更不适合做唤醒词。

PaddleSpeech 的中文效果确实好,模型很强大,但依赖的是 PaddlePaddle 全家桶,装到 ARM Linux 上光是环境依赖就够折腾一下午。如果你只是想在树莓派上做一个足够好用的助手,这个成本不划算。

2.2 Sherpa-onnx 能做什么

Sherpa-onnx 是 k2-fsa 社区开源的一套端侧语音处理工具集,和底层语音识别框架配合,所有推理都通过 ONNX Runtime 跑在本地 CPU 上。它覆盖的能力包括:

  • 离线/流式语音识别:支持 Zipformer、Paraformer、Whisper 等主流模型
  • 语音合成:支持 VITS 等端侧 TTS 模型
  • 关键词唤醒:可以自定义“你好小智”“小爱同学”这类唤醒词
  • 语音活动检测:自动判断人是否开始/结束说话
  • 说话人识别:区分不同说话人,做声纹验证

最打动我的点不是某一个功能,而是它每个功能都提供了统一的 Python API、C++ API,而且官方仓库里有一大堆可以直接跑的示例脚本。模型下载之后,几行代码就能把一个模块拉起来,这对个人项目来说太重要了。

2.3 树莓派 4B 的算力能不能扛住

我用的设备是树莓派 4B,4GB 内存版本,CPU 是四核 Cortex-A72,主频 1.5GHz。这套配置在今天的电脑面前不值一提,但跑流式 Zipformer 识别模型是够用的。我实测下来,识别实时率大致在 0.3 到 0.5 之间,也就是说,处理 1 秒音频只需要 0.3 到 0.5 秒,人在正常语速下说话,识别基本能跟上。

语音合成方面,VITS 这种端侧模型本身就很小,合成一句“你好,我是你的离线语音助手”大概在 1 秒内完成,属于完全可以接受的范围。如果你用的是树莓派 5,性能更强,延迟还会更低。

3. 环境准备:从空白系统到依赖就绪

标题里写了“完整配置流程”,那这一章我就按我实际操作的顺序,把环境这一部分写清楚。建议你手里已经有烧录好系统的树莓派,如果没有,看这一章也能从头搭起来。

3.1 系统选型与软件源配置

我选择的是树莓派 4B + Ubuntu 22.04 Server 64 位。为什么不用官方 Raspberry Pi OS?没有特殊原因,Ubuntu 22.04 的包管理、Python 环境对我来说更熟悉,而且长期支持周期到 2027 年,省心。树莓派 5 跑同样这套流程也一样,只是系统镜像换成对应的 Ubuntu 版本即可。

烧录系统用 Raspberry Pi Imager 就行,把 Ubuntu Server 镜像写到一张 32GB 以上 SD 卡。烧完之后开机,默认账户是ubuntu,密码是ubuntu,首次登录会让你改密码。这里建议在改密码前先开启 SSH,这样后面所有操作都可以通过电脑远程连接完成。

开机后第一步是更新软件源。国内网络环境下,Ubuntu 默认源速度容易让人崩溃,所以系统装好第一件事就是换源。Ubuntu 22.04 Server 的软件源配置文件在/etc/apt/sources.list.d/ubuntu.sources,编辑这个文件,把http://archive.ubuntu.com/ubuntu/替换成你本地的 Ubuntu 镜像源地址即可。这里不具体推荐某一个源,你根据自己的网络环境选一个可用的就行。

换完源之后执行:

sudo apt update && sudo apt upgrade -y sudo apt install -y python3-pip python3-venv git

这里有个习惯建议:不要用系统全局 Python 装任何 Python 包,后面项目依赖容易冲突。我在家目录下建了一个虚拟环境,所有语音相关的包都装在里面:

cd ~ python3 -m venv voice-env source voice-env/bin/activate

后续所有安装和使用操作,都先source ~/voice-env/bin/activate再执行。

3.2 安装 Sherpa-onnx 及音频依赖

激活虚拟环境之后,安装过程非常简单:

pip install sherpa-onnx sounddevice soundfile numpy

需要说明的是,sherpa-onnx 这个 pip 包在 ARM 64 位平台上有预编译好的 Python wheel,所以不需要你手动编译。如果你用的是树莓派 4B 之前的 32 位系统,那可能需要从源码编译,过程会痛苦一些,所以这也是我坚持让你装 64 位系统的原因。

sounddevice用来读写麦克风音频,soundfile用来保存和读取 WAV 文件,numpy是音频数组处理的基础库。这几个加起来就够了,不需要额外安装复杂的音频引擎。

3.3 麦克风与音箱:最容易翻车的环节

软件装完只是第一步,真正卡住很多人的是音频设备。语音助手要“能听”又要“能说”,所以麦克风和音箱都要准备。我在这块踩了不少坑,下面直接给你可操作的检查方法。

先插入 USB 麦克风(我用的是几十块的普通 USB 麦克风),然后用命令查看系统是否识别:

arecord -l aplay -l

arecord -l列出录音设备,aplay -l列出播放设备。如果能看到card 1: ...这样的输出,说明设备已经被系统认出来了。接着把默认输入输出设备指到 USB 声卡上,我在/home/ubuntu/.asoundrc里写了这样的配置:

defaults.pcm.card 1 defaults.pcm.device 0 defaults.ctl.card 1

其中card 1是 USB 声卡的编号,以你arecord -l输出为准。写完后用speaker-test测试输出,再用arecord -d 5 test.wav录一段,aplay test.wav回放验证。如果能清楚地听到自己的声音,说明外设链路已经通了。

这里插一句经验:树莓派板载的 3.5mm 音频孔音质一般,而且和 USB 声卡同时存在时容易选错默认设备。我建议直接用 USB 声卡解决输入输出,省去很多麻烦。

4. 模型下载与 ASR / TTS 代码跑通

环境就绪之后,核心工作就是下载模型、写代码、跑通识别和合成的闭环。Sherpa-onnx 不提供训练好的模型,它需要在官方模型仓库下载预训练模型文件。这一节我会给出具体的模型选择建议和实际可用的代码。

4.1 模型怎么选:识别模型与合成模型

语音识别模型我选的是sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20。这是流式识别模型,支持中英双语,识别中文日常对话表现不错,而且专门为流式场景优化,延迟低。模型包里有encoder.onnxdecoder.onnxjoiner.onnxtokens.txt这几个文件。

语音合成模型我选的是vits-zh-hf-fanchen,这是一个标准中文女声模型,音色自然,模型文件也就几十 MB,在树莓派上跑无压力。如果你的场景需要中英混说的效果,官方模型列表里还有vits-melo-tts-zh_en这类模型,可以根据需要换。

模型文件从官方 GitHub Release 页面下载后,我建议统一放到/home/ubuntu/sherpa-models/目录下,每个模型用一个独立子目录管理,避免后面代码里路径混乱。

4.2 调用流式语音识别

模型放好之后,识别代码核心部分长这样:

import sherpa_onnx import sounddevice as sd import numpy as np SAMPLE_RATE = 16000 BLOCK_SIZE = 1600 # 100ms 的音频块 recognizer = sherpa_onnx.OnlineRecognizer.from_transducer( tokens="sherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/tokens.txt", encoder="sherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/encoder.onnx", decoder="sherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/decoder.onnx", joiner="sherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/joiner.onnx", num_threads=2, sample_rate=SAMPLE_RATE, feature_dim=80, enable_endpoint_detection=True, rule1_min_trailing_silence=2.4, rule2_min_trailing_silence=1.2, rule3_min_utterance_length=300, ) stream = recognizer.create_stream() last_text = "" def audio_callback(indata, frames, time, status): global stream, last_text samples = indata.reshape(-1) stream.accept_waveform(SAMPLE_RATE, samples) recognizer.decode_stream(stream) text = recognizer.get_result(stream) if text != last_text and len(text) > 0: last_text = text print(text, end="\r", flush=True) if stream.is_endpoint: print("\n完整识别结果:", text) stream = recognizer.create_stream() last_text = "" with sd.InputStream( samplerate=SAMPLE_RATE, blocksize=BLOCK_SIZE, channels=1, dtype="int16", callback=audio_callback, ): print("开始说话...") sd.sleep(30000)

这段代码做的事情是:不断从麦克风读取 100ms 的音频块,喂给识别器的流式接口,同时持续取回当前识别结果并输出。当检测到语音端点(即用户停顿一段时间后)会输出整句结果并重置流。

几个参数的解释:num_threads=2是推理线程数,树莓派 4B 是四核 CPU,但我不建议设成 4,因为系统本身还要分线程处理音频输入等任务,设太高反而增加调度开销。enable_endpoint_detection=True开启端点检测,这样用户说完话停顿 1-2 秒,系统会自动认为一句话结束。

4.3 调用离线语音合成

识别跑通之后,合成代码更简单:

import sherpa_onnx import sounddevice as sd config = sherpa_onnx.OfflineTtsConfig( model=sherpa_onnx.OfflineTtsModelConfig( vits=sherpa_onnx.OfflineTtsVitsModelConfig( model="sherpa-models/vits-zh-hf-fanchen/model.onnx", tokens="sherpa-models/vits-zh-hf-fanchen/tokens.txt", lexicon="sherpa-models/vits-zh-hf-fanchen/lexicon.txt", dict_dir="sherpa-models/vits-zh-hf-fanchen/dict", ), ), ) tts = sherpa_onnx.OfflineTts(config) def speak(text): audio = tts.generate(text) sd.play(audio.samples, samplerate=audio.sample_rate) sd.wait()

sherpa_onnx.OfflineTts会加载模型并生成音频。generate返回的音频数据是 float 数组,speak函数直接通过sounddevice播放出来。如果你的模型包里没有lexicon.txtdict目录,就把那两行配置省略,不影响整体功能。

到这里,识别和合成两个模块已经各自跑通了。接下来真正的工作,是把它们串成一个可以对话的完整助手。

5. 把识别和合成串成一个可用的离线语音助手

有了 ASR 和 TTS,你实际上已经具备了“听得懂”和“说得出”两个基础能力。但要让它们变成一个像样的语音助手,还需要解决两个问题:怎么知道用户在叫它,以及怎么处理识别结果并给出回应。

5.1 唤醒词检测,让助手“随叫随到”

如果你希望助手一直在后台监听,但只对特定唤醒词有反应,那需要用到 Sherpa-onnx 的关键词唤醒功能。官方提供的关键词唤醒模型一般是 Zipformer 结构,目录里有encoder.onnxdecoder.onnxjoiner.onnxtokens.txtkeywords.txt

使用方式也很直观:

import sherpa_onnx kws = sherpa_onnx.KeywordSpotter.from_transducer( tokens="sherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/tokens.txt", encoder="sherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/encoder.onnx", decoder="sherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/decoder.onnx", joiner="sherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/joiner.onnx", keywords="sherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/keywords.txt", num_threads=2, )

keywords.txt里可以自定义唤醒词,例如:

你好小智 小智同学

然后实时从麦克风取音频,分块喂给输入流。一旦is_ready返回真,说明唤醒词被命中。

完整的唤醒检测代码和 ASR 类似,区别在于检测到唤醒后才开始真正的识别流程。我实际操作时,是在一个while True循环里先检测唤醒词,唤醒后进入录音识别状态,识别完成并给出回复后,再回到唤醒检测状态。这样语音助手的资源占用比较集中,不会一直双向跑模型导致内存吃紧。

5.2 助手的完整主流程

下面是一个简化的助手主循环,去掉了具体细节,但保留了核心逻辑框架:

while True: # 1. 等待唤醒 if not wait_for_keyword(): continue # 2. 播放提示音或说出提示语 speak("我在,请讲") # 3. 录音并识别用户指令 text = recognize_from_mic() print("识别结果:", text) # 4. 规则匹配,执行动作并回复 if "关灯" in text: turn_off_light() # 这里可以接 GPIO 或 Home Assistant reply = "灯已经关了" elif "开灯" in text: turn_on_light() reply = "灯已经开了" else: reply = "我不太明白你在说什么" speak(reply)

这个框架看着简单,但它把一个语音助手的完整链路串起来了:唤醒 → 提示 → 录音 → 识别 → 意图判断 → 执行 → 回复。就算你后面要接更复杂的自然语言理解,也只是替换第 4 步的规则匹配部分,整体流程不用大改。

这里有一个容易被忽略的细节:语音识别要区分“唤醒前”和“唤醒后”两个阶段。唤醒阶段只需要跑体积较小、延迟低的关键词模型;唤醒后进入录音识别阶段,再加载完整的流式 ASR 模型。两种模型同时挂载在内存里会让树莓派 4B 的 4GB 内存有些紧张,分开阶段加载更流畅。

6. “5分钟搞定”的真相:耗时拆解与优化建议

标题里写了“5分钟搞定”,我得诚实一点:这句话指的是“核心步骤 5 分钟”,不是“从零到完整助手 5 分钟”。为了让你对时间预期有准确判断,我把实际耗时拆开算一下。

6.1 全流程耗时拆解

环节耗时说明
安装 sherpa-onnx 及 Python 依赖约 1 分钟取决于 pip 网络速度
下载 ASR 模型网络决定模型约几十到几百 MB
下载 TTS 模型网络决定约几十 MB
跑通 ASR 示例代码2-3 分钟不涉及外设问题的话
跑通 TTS 示例代码1-2 分钟几乎不用调参
串成完整助手流程十几分钟主要花在唤醒和端点调参上

如果你已经把模型下好了,只是重新配置一台树莓派,那么“装依赖 + 跑通识别合成”确实 5 分钟内可以完成。但第一次折腾,建议预留一个晚上,省得卡在外设问题上焦虑。

6.2 几个立竿见影的性能优化

  • 优先使用 int8 量化模型:同一个 Zipformer 识别模型,官方同时提供 float32 和 int8 量化版本。在树莓派 4B 上,int8 版本推理速度提升明显,体积也小一大截,识别效果差距很小,能感知到但不影响使用。

  • 合理设置线程数:我在识别器上用了num_threads=2。试过设成 4,结果反而因为 CPU 资源竞争导致音频采集卡顿。树莓派 4B 保持 2 个推理线程是甜点值;树莓派 5 的话,可以试 3 或 4。

  • 用流式识别模型而不是离线识别模型:流式模型可以边说话边出结果,延迟感更低。离线模型要等整句话说完才开始识别,交互体验明显差一截。

  • 给树莓派加散热:跑 TTS 时 CPU 占用会拉高,一旦温度超过 80 度,CPU 会主动降频,识别和合成都会明显变慢。我加了小散热片 + 风扇之后,整机温度稳定在 60 度左右,识别延迟几乎没有波动。

7. 实测中那些文档里不会告诉你的坑

工具类项目,光看 README 永远学不会,真正消耗时间的往往是文档之外的问题。这一章我把我实际踩过的坑集中写出来,如果你在看文章的过程中遇到了类似问题,可以直接跳到对应段落找答案。

7.1 供电不足导致 USB 麦克风掉线

这个坑排第一,因为它最隐蔽。树莓派 4B 用 5V 3A 官方电源,理论上足够带动一个 USB 麦克风,但如果你把 USB 麦克风直接插在树莓派的 USB 口上,旁边再挂一个大功率外设,比如键盘接收器、移动硬盘,就可能导致瞬间电流不够,USB 麦克风直接掉线。表现是录音突然没有声音,arecord -l也看不到设备。

我的解决办法是给树莓派换一个带独立供电的 USB HUB,麦克风接在 HUB 上,HUB 自己供电。从那以后麦克风再没掉过线。如果你只是临时测试,建议至少插在紧挨着电源口的那个 USB 口上,电流会更稳一些。

7.2 默认音频设备漂移

树莓派上同时存在板载音频和 USB 声卡时,默认音频设备不一定是 USB 声卡。更气人的是,今天默认设备是对的,重启之后又变成了板载声卡,录音全录进了空气。

解决方法就是我前面提到的,在~/.asoundrc里显式指定defaults.pcm.card 1,把 USB 声卡锁定为默认。如果你在代码里用sounddevice遇到“Input overflow”或者“Invalid device”报错,也可以直接指定设备编号,比如sd.InputStream(device=1, ...),绕开系统默认设置。

7.3 识别结果乱码或频繁出错

如果你的识别代码跑起来,输出的是乱码或经常识别错误,大概率是采样率或采样格式不对。Sherpa-onnx 的流式识别默认接受 16kHz 单声道 16bit PCM 音频。但很多 USB 麦克风默认采样率是 48kHz,或者录制的是双声道,这就会导致识别质量急剧下降。

sounddevice.InputStream中,我强制设置了samplerate=16000channels=1dtype="int16",让声卡按标准输入。如果麦克风硬件本身不支持 16kHz,你可以先用arecord -f S16_LE -r 16000 -c 1 test.wav测试一下能否正常录制。

7.4 模型加载时文件路径找不到

模型的tokens.txtencoder.onnx这些文件,在官方模型包里是放在同一目录下的。我一开始图省事,只写了文件名,结果换个目录运行代码就报文件不存在。后来我把所有模型统一放在/home/ubuntu/sherpa-models/下,代码里全部使用绝对路径,再没出现过这个问题。

7.5 唤醒词阈值调优

关键词唤醒模型默认阈值可能不适合你的使用环境。如果经常没有喊唤醒词也误触发,说明阈值太低;如果凑近麦克风喊好几遍都没反应,则阈值太高。具体怎么调整,看你下载的模型包说明,不同模型暴露的参数名可能不同,但核心思路是一致的:在稳定性和灵敏度之间找一个合适的平衡点

我个人把阈值调高了一点,宁可偶尔喊两遍,也别让它莫名其妙自己醒过来。一个在自己说话时突然接话的语音助手,比一个反应稍慢的助手要烦人得多。

8. 下一步还能往哪玩

整套流程跑通之后,你会发现树莓派上的语音助手已经从一个“能跑的 Demo”变成了一个“能用的基础设施”,剩下的事情反而更有趣——怎么把它的能力嫁接到你的其他项目里。

最基本的玩法是接 GPIO,让语音直接控制硬件。我用树莓派控制了一个 LED 灯和一个小舵机,“开灯”“关门”这种指令已经完全通过离线语音触发了,响应速度比我预期的好。再进一步,你可以把识别结果通过局域网协议发给其他设备,或者接入智能家居控制中心的 API,让全屋设备都支持本地语音控制。

我自己最近在折腾的是离线意图识别,用简单的关键词规则库和正则表达式把指令分类,比如“查天气”“设闹钟”“播音乐”这几个意图。虽然做不到云端大模型那种开放式问答,但对家庭场景来说,规则匹配已经能覆盖 80% 的日常命令,而且完全可控、完全离线、完全免费。

现在的树莓派被我放在客厅角落,连网线都没插,只靠一个 5V 电源活着。每天回家说一声“我回来了”,它就自动播报今天的日程安排。那种不依赖任何人、任何服务器的确定感,是云端方案永远给不了的。希望这篇流水账式的记录,能帮你把同样的确定感搬到自己的桌面上。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询