ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

树莓派离线语音助手搭建:基于Sherpa-onnx的完整指南

树莓派离线语音助手搭建:基于Sherpa-onnx的完整指南 家里网络一抽风语音助手就变成了一个只会转圈的摆设这种体验你应该不陌生。我上次就是在路由器重启的那几分钟里对着音箱喊了好几遍“关灯”最后只能自己站起来去按开关。也就是从那时候开始我决定把语音助手彻底搬到树莓派上完全离线运行。这个方案的核心就是标题里提到的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 的算力能不能扛住我用的设备是树莓派 4B4GB 内存版本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 -larecord -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.onnx、decoder.onnx、joiner.onnx、tokens.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( tokenssherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/tokens.txt, encodersherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/encoder.onnx, decodersherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/decoder.onnx, joinersherpa-models/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20/joiner.onnx, num_threads2, sample_rateSAMPLE_RATE, feature_dim80, enable_endpoint_detectionTrue, rule1_min_trailing_silence2.4, rule2_min_trailing_silence1.2, rule3_min_utterance_length300, ) 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, flushTrue) if stream.is_endpoint: print(\n完整识别结果:, text) stream recognizer.create_stream() last_text with sd.InputStream( samplerateSAMPLE_RATE, blocksizeBLOCK_SIZE, channels1, dtypeint16, callbackaudio_callback, ): print(开始说话...) sd.sleep(30000)这段代码做的事情是不断从麦克风读取 100ms 的音频块喂给识别器的流式接口同时持续取回当前识别结果并输出。当检测到语音端点即用户停顿一段时间后会输出整句结果并重置流。几个参数的解释num_threads2是推理线程数树莓派 4B 是四核 CPU但我不建议设成 4因为系统本身还要分线程处理音频输入等任务设太高反而增加调度开销。enable_endpoint_detectionTrue开启端点检测这样用户说完话停顿 1-2 秒系统会自动认为一句话结束。4.3 调用离线语音合成识别跑通之后合成代码更简单import sherpa_onnx import sounddevice as sd config sherpa_onnx.OfflineTtsConfig( modelsherpa_onnx.OfflineTtsModelConfig( vitssherpa_onnx.OfflineTtsVitsModelConfig( modelsherpa-models/vits-zh-hf-fanchen/model.onnx, tokenssherpa-models/vits-zh-hf-fanchen/tokens.txt, lexiconsherpa-models/vits-zh-hf-fanchen/lexicon.txt, dict_dirsherpa-models/vits-zh-hf-fanchen/dict, ), ), ) tts sherpa_onnx.OfflineTts(config) def speak(text): audio tts.generate(text) sd.play(audio.samples, samplerateaudio.sample_rate) sd.wait()sherpa_onnx.OfflineTts会加载模型并生成音频。generate返回的音频数据是 float 数组speak函数直接通过sounddevice播放出来。如果你的模型包里没有lexicon.txt和dict目录就把那两行配置省略不影响整体功能。到这里识别和合成两个模块已经各自跑通了。接下来真正的工作是把它们串成一个可以对话的完整助手。5. 把识别和合成串成一个可用的离线语音助手有了 ASR 和 TTS你实际上已经具备了“听得懂”和“说得出”两个基础能力。但要让它们变成一个像样的语音助手还需要解决两个问题怎么知道用户在叫它以及怎么处理识别结果并给出回应。5.1 唤醒词检测让助手“随叫随到”如果你希望助手一直在后台监听但只对特定唤醒词有反应那需要用到 Sherpa-onnx 的关键词唤醒功能。官方提供的关键词唤醒模型一般是 Zipformer 结构目录里有encoder.onnx、decoder.onnx、joiner.onnx、tokens.txt和keywords.txt。使用方式也很直观import sherpa_onnx kws sherpa_onnx.KeywordSpotter.from_transducer( tokenssherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/tokens.txt, encodersherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/encoder.onnx, decodersherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/decoder.onnx, joinersherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/joiner.onnx, keywordssherpa-models/sherpa-onnx-kws-zipformer-wenetspeech-20250228/keywords.txt, num_threads2, )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_threads2。试过设成 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(device1, ...)绕开系统默认设置。7.3 识别结果乱码或频繁出错如果你的识别代码跑起来输出的是乱码或经常识别错误大概率是采样率或采样格式不对。Sherpa-onnx 的流式识别默认接受 16kHz 单声道 16bit PCM 音频。但很多 USB 麦克风默认采样率是 48kHz或者录制的是双声道这就会导致识别质量急剧下降。在sounddevice.InputStream中我强制设置了samplerate16000、channels1、dtypeint16让声卡按标准输入。如果麦克风硬件本身不支持 16kHz你可以先用arecord -f S16_LE -r 16000 -c 1 test.wav测试一下能否正常录制。7.4 模型加载时文件路径找不到模型的tokens.txt、encoder.onnx这些文件在官方模型包里是放在同一目录下的。我一开始图省事只写了文件名结果换个目录运行代码就报文件不存在。后来我把所有模型统一放在/home/ubuntu/sherpa-models/下代码里全部使用绝对路径再没出现过这个问题。7.5 唤醒词阈值调优关键词唤醒模型默认阈值可能不适合你的使用环境。如果经常没有喊唤醒词也误触发说明阈值太低如果凑近麦克风喊好几遍都没反应则阈值太高。具体怎么调整看你下载的模型包说明不同模型暴露的参数名可能不同但核心思路是一致的在稳定性和灵敏度之间找一个合适的平衡点。我个人把阈值调高了一点宁可偶尔喊两遍也别让它莫名其妙自己醒过来。一个在自己说话时突然接话的语音助手比一个反应稍慢的助手要烦人得多。8. 下一步还能往哪玩整套流程跑通之后你会发现树莓派上的语音助手已经从一个“能跑的 Demo”变成了一个“能用的基础设施”剩下的事情反而更有趣——怎么把它的能力嫁接到你的其他项目里。最基本的玩法是接 GPIO让语音直接控制硬件。我用树莓派控制了一个 LED 灯和一个小舵机“开灯”“关门”这种指令已经完全通过离线语音触发了响应速度比我预期的好。再进一步你可以把识别结果通过局域网协议发给其他设备或者接入智能家居控制中心的 API让全屋设备都支持本地语音控制。我自己最近在折腾的是离线意图识别用简单的关键词规则库和正则表达式把指令分类比如“查天气”“设闹钟”“播音乐”这几个意图。虽然做不到云端大模型那种开放式问答但对家庭场景来说规则匹配已经能覆盖 80% 的日常命令而且完全可控、完全离线、完全免费。现在的树莓派被我放在客厅角落连网线都没插只靠一个 5V 电源活着。每天回家说一声“我回来了”它就自动播报今天的日程安排。那种不依赖任何人、任何服务器的确定感是云端方案永远给不了的。希望这篇流水账式的记录能帮你把同样的确定感搬到自己的桌面上。
返回列表