适用场景:你已经在 Jetson Nano / Jetson Orin Nano 上接好了免驱 USB 麦克风和喇叭,希望通过语音和本地部署的大模型交互。
本文最终实现链路:麦克风语音输入 → Vosk 中文语音识别 → Ollama 本地大模型 qwen2.5:0.5b → edge-tts 晓晓语音合成 → USB 喇叭播放。

一、项目效果
最终运行后,你可以直接对麦克风说话,程序会自动识别你的中文语音,把识别结果发送给 Jetson 上的 Ollama 本地大模型 qwen2.5:0.5b,然后用 edge-tts 的 zh-CN-XiaoxiaoNeural 中文女声读出来。
完整流程如下:
text
USB 麦克风
↓
Python + sounddevice 采集音频
↓
Vosk 中文语音识别
↓
Ollama API 调用 qwen2.5:0.5b
↓
edge-tts 生成中文语音 mp3
↓
mpg123 指定 USB 声卡播放
↓
USB 喇叭输出
二、为什么这样设计
本文使用的核心组件如下:
| 模块 | 作用 | 选择原因 |
|---|---|---|
| Ollama | 本地运行大模型 | 已经部署好 qwen2.5:0.5b,直接通过 HTTP API 调用 |
| Vosk | 离线中文语音识别 | 轻量、可离线运行,适合 Jetson 这类边缘设备 |
| edge-tts | 中文语音合成 | 中文音色自然,zh-CN-XiaoxiaoNeural 效果比 espeak-ng 好很多 |
| ALSA / amixer / aplay | 声卡控制与播放 | Linux 下直接控制 USB 声卡 |
| Miniforge / Conda | Python 环境隔离 | 避免影响系统 Python 和其他程序 |
Ollama 的 /api/generate 接口可以传入 model 和 prompt,并通过 "stream": false 返回一次性完整结果;Vosk 官方 Python 示例使用 sounddevice.RawInputStream 采集音频,并通过 KaldiRecognizer 实时识别;edge-tts 支持 --voice、--rate、--write-media 等命令行参数;amixer 可以通过 -c 指定声卡并用 scontrols/scontents/sset 查看和设置声卡控制项。
三、硬件和软件准备
1. 硬件
本文使用:
text
Jetson Nano / Jetson Orin Nano
USB 免驱麦克风 + 喇叭声卡板
网络连接
已安装 Ollama
已拉取 qwen2.5:0.5b
你的 USB 音频设备在系统里可能显示为类似:
text
card 2: UACDemoV10 [UACDemoV1.0], device 0: USB Audio [USB Audio]
这里的重点是:
text
card 2
device 0
后面播放设备就会写成:
text
plughw:2,0
如果你的设备不是 card 2,要根据自己的实际输出修改。
四、新建独立项目文件夹
为了不影响其他程序,所有项目文件都放在一个新建目录里:
bash
mkdir -p ~/jetson_voice_ollama
cd ~/jetson_voice_ollama
最终目录结构大致如下:
text
~/jetson_voice_ollama/
├── models/
│ └── vosk-model-small-cn-0.22/
├── voice_ollama.py
└── run.sh
五、创建 Miniforge / Conda 环境
如果你已经安装好了 Miniforge,它的使用方式和 Anaconda 基本一致。
创建环境:
bash
conda create -n jetson_voice_ollama python=3.9 -y
激活环境:
bash
conda activate jetson_voice_ollama
如果 conda activate 不生效,可以先执行:
bash
source ~/miniforge3/bin/activate
conda init bash
source ~/.bashrc
然后重新激活:
bash
conda activate jetson_voice_ollama
六、安装系统依赖
这些是系统层面的音频工具,建议通过 apt 安装:
bash
sudo apt update
sudo apt install -y alsa-utils portaudio19-dev espeak-ng unzip wget mpg123
说明:
text
alsa-utils 提供 arecord、aplay、amixer、alsamixer
portaudio19-dev 给 Python sounddevice 使用
espeak-ng 作为 edge-tts 失败时的兜底 TTS
wget/unzip 下载和解压 Vosk 模型
mpg123 播放 edge-tts 生成的 mp3
七、安装 Python 依赖
在 Conda 环境里执行:
bash
cd ~/jetson_voice_ollama
conda activate jetson_voice_ollama
pip install vosk sounddevice requests edge-tts
八、下载 Vosk 中文模型
创建模型目录:
bash
cd ~/jetson_voice_ollama
mkdir -p models
cd models
下载中文小模型:
bash
wget https://alphacephei.com/vosk/models/vosk-model-small-cn-0.22.zip
unzip vosk-model-small-cn-0.22.zip
回到项目目录:
bash
cd ~/jetson_voice_ollama
确认目录存在:
bash
ls models
应该能看到:
text
vosk-model-small-cn-0.22
九、确认 Ollama 和大模型可用
确认 Ollama 已经安装并且模型存在:
bash
ollama list
如果还没有模型,拉取:
bash
ollama pull qwen2.5:0.5b
测试模型:
bash
ollama run qwen2.5:0.5b
也可以测试 API:
bash
curl http://localhost:11434/api/generate \
-d '{
"model": "qwen2.5:0.5b",
"prompt": "你好,用一句话介绍你自己",
"stream": false
}'
能返回中文内容,就说明 Ollama 这部分正常。
十、确认 USB 声卡编号
查看播放设备:
bash
aplay -l
你可能会看到类似:
text
card 2: UACDemoV10 [UACDemoV1.0], device 0: USB Audio [USB Audio]
这说明 USB 声卡是:
text
card 2, device 0
对应播放设备:
text
plughw:2,0
查看录音设备:
bash
arecord -l
如果麦克风和喇叭在同一个 USB 声卡上,通常也会看到 card 2。
十一、调节 USB 声卡音量
不要盲目执行:
bash
amixer sset Master 90%
amixer sset Speaker 90%
很多 USB 声卡没有 Master 或 Speaker 控制项,会报:
text
Unable to find simple control 'Master',0
Unable to find simple control 'Speaker',0
正确做法是先看当前 USB 声卡有哪些控制项。假设你的 USB 声卡是 card 2:
bash
amixer -c 2 scontrols
如果输出类似:
text
Simple mixer control 'PCM',0
Simple mixer control 'Mic',0
Simple mixer control 'Auto Gain Control',0
说明播放音量控制项叫 PCM。
查看详细内容:
bash
amixer -c 2 scontents
如果你看到:
text
Simple mixer control 'PCM',0
Front Left: Playback 44 [30%] ...
Front Right: Playback 44 [30%] ...
说明当前音量只有 30%,声音小是正常的。
把音量调到 95%:
bash
amixer -c 2 sset PCM 95% unmute
确认:
bash
amixer -c 2 sget PCM
保存音量:
bash
sudo alsactl store
麦克风音量可以设置成 85%:
bash
amixer -c 2 sset Mic 85% cap
十二、测试 USB 喇叭播放
用系统测试音:
bash
speaker-test -D plughw:2,0 -t wav -c 2
能响就说明 USB 喇叭输出正常。
十三、测试 edge-tts 中文声音
安装完成后,先测试你喜欢的音色:
bash
edge-tts --voice zh-CN-XiaoxiaoNeural --rate +15% --text "你好,这是晓晓的声音。" --write-media /tmp/xiaoxiao.mp3 && mpg123 -q -a plughw:2,0 /tmp/xiaoxiao.mp3
如果能正常播放,并且声音、语速都合适,后面代码就使用这一套:
text
voice: zh-CN-XiaoxiaoNeural
rate: +15%
player: mpg123 -q -a plughw:2,0
如果想查看 edge-tts 支持哪些声音:
bash
edge-tts --list-voices
十四、完整 Python 代码
创建主程序:
bash
cd ~/jetson_voice_ollama
nano voice_ollama.py
把下面完整代码复制进去。
python
import json
import queue
import re
import subprocess
import requests
import sounddevice as sd
from vosk import Model, KaldiRecognizer
# =========================
# 基本配置
# =========================
VOSK_MODEL_PATH = "models/vosk-model-small-cn-0.22"
OLLAMA_URL = "http://localhost:11434/api/generate"
OLLAMA_MODEL = "qwen2.5:0.5b"
SAMPLE_RATE = 16000
BLOCK_SIZE = 8000
# 麦克风输入设备:
# 先用 None。如果识别不到,再运行:
# python -c "import sounddevice as sd; print(sd.query_devices())"
# 找到 USB 麦克风编号后改成对应数字,例如 INPUT_DEVICE = 2
INPUT_DEVICE = None
# =========================
# USB 声卡配置
# =========================
# 根据 aplay -l 的实际输出修改。
# 如果你的 USB 声卡是:
# card 2: UACDemoV10 [UACDemoV1.0], device 0: USB Audio
# 那么这里就是 card 2,对应播放设备 plughw:2,0。
ALSA_CARD = "2"
ALSA_PLAY_DEVICE = "plughw:2,0"
# 程序启动时自动设置 USB 声卡音量
AUTO_SET_USB_VOLUME = True
USB_PCM_VOLUME = "95%"
# =========================
# TTS 配置:优先使用 edge-tts
# =========================
TTS_ENGINE = "edge"
# 已测试合适的中文女声和语速
EDGE_VOICE = "zh-CN-XiaoxiaoNeural"
EDGE_RATE = "+15%"
EDGE_VOLUME = "+0%"
EDGE_OUTPUT_MP3 = "/tmp/jetson_voice_edge.mp3"
# edge-tts 失败时的兜底 espeak-ng 配置
TTS_VOICE = "zh"
TTS_VOLUME = "200"
TTS_SPEED = "210"
TTS_PITCH = "45"
TTS_GAP = "0"
audio_queue = queue.Queue()
def run_cmd(cmd, silent=True):
try:
if silent:
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
else:
subprocess.run(cmd)
except Exception as e:
print("命令执行失败:", cmd, e)
def setup_usb_audio_volume():
"""
根据 amixer 输出,card 2 里通常有 PCM、Mic、Auto Gain Control。
这里自动把 PCM 播放音量调到 95%,并确保未静音。
"""
if not AUTO_SET_USB_VOLUME:
return
run_cmd(["amixer", "-c", ALSA_CARD, "sset", "PCM", USB_PCM_VOLUME, "unmute"])
run_cmd(["amixer", "-c", ALSA_CARD, "sset", "Mic", "85%", "cap"])
def audio_callback(indata, frames, time, status):
if status:
print("音频状态:", status)
audio_queue.put(bytes(indata))
def clean_text_for_tts(text):
"""
清理大模型输出,避免 TTS 把 Markdown、代码符号读出来。
"""
if not text:
return ""
text = text.strip()
# 删除代码块
text = re.sub(r"```.*?```", "", text, flags=re.S)
# 删除常见 Markdown / 代码符号
for ch in ["**", "*", "`", "#", "_", ">", "|", "[", "]", "{", "}"]:
text = text.replace(ch, "")
text = text.replace("---", "")
text = text.replace("--", "")
# 压缩空白
text = re.sub(r"\s+", " ", text)
# 控制朗读长度,避免一次说太久
max_len = 160
if len(text) > max_len:
text = text[:max_len] + "。"
return text.strip()
def ask_ollama(user_text):
payload = {
"model": OLLAMA_MODEL,
"prompt": (
"你是运行在 Jetson Nano 上的本地语音助手。"
"你正在通过喇叭说话,所以回答必须简短、自然、口语化。"
"不要使用 Markdown,不要列项目符号,不要输出代码。"
"每次回答控制在三句话以内。\n\n"
f"用户:{user_text}\n助手:"
),
"stream": False,
"options": {
"temperature": 0.6,
"num_predict": 90
}
}
try:
response = requests.post(OLLAMA_URL, json=payload, timeout=120)
response.raise_for_status()
data = response.json()
answer = data.get("response", "").strip()
return clean_text_for_tts(answer)
except Exception as e:
print("调用 Ollama 失败:", e)
return "我现在无法连接本地大模型,请检查 Ollama 是否正在运行。"
def speak_with_edge_tts(text):
"""
使用 edge-tts 生成 mp3,然后用 mpg123 指定 USB 声卡播放。
等价命令:
edge-tts --voice zh-CN-XiaoxiaoNeural --rate +15% --text "..." --write-media /tmp/edge.mp3
mpg123 -q -a plughw:2,0 /tmp/edge.mp3
"""
subprocess.run(
[
"edge-tts",
"--voice", EDGE_VOICE,
"--rate", EDGE_RATE,
"--volume", EDGE_VOLUME,
"--text", text,
"--write-media", EDGE_OUTPUT_MP3
],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True
)
subprocess.run(
["mpg123", "-q", "-a", ALSA_PLAY_DEVICE, EDGE_OUTPUT_MP3],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=True
)
def speak_with_espeak(text):
"""
兜底方案:edge-tts 失败时使用 espeak-ng。
"""
espeak = subprocess.Popen(
[
"espeak-ng",
"-v", TTS_VOICE,
"-a", TTS_VOLUME,
"-s", TTS_SPEED,
"-p", TTS_PITCH,
"-g", TTS_GAP,
"--stdout",
text
],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL
)
aplay = subprocess.Popen(
["aplay", "-D", ALSA_PLAY_DEVICE, "-q"],
stdin=espeak.stdout,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)
if espeak.stdout:
espeak.stdout.close()
aplay.communicate()
espeak.wait()
def speak(text):
text = clean_text_for_tts(text)
if not text:
return
if TTS_ENGINE == "edge":
try:
speak_with_edge_tts(text)
return
except Exception as e:
print("edge-tts 播放失败,回退到 espeak-ng:", e)
try:
speak_with_espeak(text)
except Exception as e:
print("语音播放失败:", e)
def main():
setup_usb_audio_volume()
print("正在加载中文语音识别模型...")
model = Model(VOSK_MODEL_PATH)
recognizer = KaldiRecognizer(model, SAMPLE_RATE)
print("语音助手已启动。")
print("当前音频配置:")
print(f" USB card = {ALSA_CARD}")
print(f" play device = {ALSA_PLAY_DEVICE}")
print(f" PCM volume = {USB_PCM_VOLUME}")
print("当前 TTS 配置:")
print(f" engine = {TTS_ENGINE}")
print(f" voice = {EDGE_VOICE}")
print(f" rate = {EDGE_RATE}")
print("说话后停顿一下,系统会识别并调用 Ollama。")
print("说"退出"或按 Ctrl+C 可结束程序。")
speak("语音助手已启动。")
with sd.RawInputStream(
samplerate=SAMPLE_RATE,
blocksize=BLOCK_SIZE,
device=INPUT_DEVICE,
dtype="int16",
channels=1,
callback=audio_callback
):
while True:
data = audio_queue.get()
if recognizer.AcceptWaveform(data):
result = json.loads(recognizer.Result())
user_text = result.get("text", "").strip().replace(" ", "")
if not user_text:
continue
print("你说:", user_text)
if user_text in ["退出", "结束", "停止", "关闭"]:
answer = "好的,再见。"
print("助手:", answer)
speak(answer)
break
answer = ask_ollama(user_text)
print("助手:", answer)
speak(answer)
if __name__ == "__main__":
main()
十五、创建启动脚本
创建 run.sh:
bash
cd ~/jetson_voice_ollama
nano run.sh
写入:
bash
#!/bin/bash
cd ~/jetson_voice_ollama
source ~/miniforge3/bin/activate
conda activate jetson_voice_ollama
python voice_ollama.py
赋予执行权限:
bash
chmod +x run.sh
以后直接运行:
bash
~/jetson_voice_ollama/run.sh
十六、运行前完整检查清单
1. 检查 Conda 环境
bash
conda activate jetson_voice_ollama
python --version
2. 检查 Python 包
bash
python - <<'PY'
import vosk
import sounddevice
import requests
print("Python 依赖正常")
PY
3. 检查 edge-tts
bash
edge-tts --voice zh-CN-XiaoxiaoNeural --rate +15% --text "你好,这是晓晓的声音。" --write-media /tmp/xiaoxiao.mp3 && mpg123 -q -a plughw:2,0 /tmp/xiaoxiao.mp3
4. 检查 USB 喇叭
bash
speaker-test -D plughw:2,0 -t wav -c 2
5. 检查 Ollama
bash
curl http://localhost:11434/api/generate \
-d '{
"model": "qwen2.5:0.5b",
"prompt": "你好,请简单回复一句话",
"stream": false
}'
十七、启动程序
bash
~/jetson_voice_ollama/run.sh
启动后会输出类似:
text
正在加载中文语音识别模型...
语音助手已启动。
当前音频配置:
USB card = 2
play device = plughw:2,0
PCM volume = 95%
当前 TTS 配置:
engine = edge
voice = zh-CN-XiaoxiaoNeural
rate = +15%
说话后停顿一下,系统会识别并调用 Ollama。
说"退出"或按 Ctrl+C 可结束程序。
此时对麦克风说话,停顿一下,程序会识别并回答。
十八、常见问题和解决办法
问题 1:amixer sset Master 90% 报错
报错:
text
Unable to find simple control 'Master',0
原因:你的 USB 声卡没有叫 Master 的控制项。
解决:
bash
amixer -c 2 scontrols
amixer -c 2 scontents
找到实际控制项,比如 PCM,然后设置:
bash
amixer -c 2 sset PCM 95% unmute
问题 2:声音很小
先确认 USB 声卡 PCM 音量:
bash
amixer -c 2 sget PCM
如果只有 30%,调大:
bash
amixer -c 2 sset PCM 95% unmute
sudo alsactl store
问题 3:edge-tts 能生成文件,但没有声音
先确认播放命令:
bash
mpg123 -q -a plughw:2,0 /tmp/xiaoxiao.mp3
如果不响,确认你的声卡是不是 plughw:2,0:
bash
aplay -l
如果是 card 1,就要改成:
text
plughw:1,0
同时修改代码:
python
ALSA_CARD = "1"
ALSA_PLAY_DEVICE = "plughw:1,0"
问题 4:Vosk 识别不到麦克风
查看 Python 能看到哪些音频设备:
bash
cd ~/jetson_voice_ollama
conda activate jetson_voice_ollama
python -c "import sounddevice as sd; print(sd.query_devices())"
找到 USB 麦克风的编号,比如是 2,就修改代码:
python
INPUT_DEVICE = 2
如果不确定,先保持:
python
INPUT_DEVICE = None
问题 5:Ollama 调用失败
先检查 Ollama 服务:
bash
ollama list
再检查模型是否存在:
bash
ollama run qwen2.5:0.5b
API 测试:
bash
curl http://localhost:11434/api/generate \
-d '{
"model": "qwen2.5:0.5b",
"prompt": "你好",
"stream": false
}'
如果 API 不通,说明 Ollama 服务没有正常运行。
问题 6:edge-tts 有延迟
edge-tts 是在线 TTS,需要网络。网络慢时,生成语音会有等待。
可以接受的话继续使用。
如果希望完全离线,可以换 Piper TTS;但实际测试中,edge-tts 的中文音色更自然。
问题 7:大模型回答太长
代码里已经限制:
python
"num_predict": 90
以及:
python
max_len = 160
如果还是太长,可以改小:
python
"num_predict": 60
max_len = 100
十九、关键参数说明
1. USB 声卡参数
python
ALSA_CARD = "2"
ALSA_PLAY_DEVICE = "plughw:2,0"
USB_PCM_VOLUME = "95%"
如果你的 USB 声卡不是 card 2,需要按 aplay -l 修改。
2. edge-tts 参数
python
EDGE_VOICE = "zh-CN-XiaoxiaoNeural"
EDGE_RATE = "+15%"
EDGE_VOLUME = "+0%"
如果想快一点:
python
EDGE_RATE = "+25%"
如果想慢一点:
python
EDGE_RATE = "+5%"
如果想换声音,可以先查看声音列表:
bash
edge-tts --list-voices
3. Ollama 模型参数
python
OLLAMA_MODEL = "qwen2.5:0.5b"
如果你换了别的模型,比如:
bash
ollama pull qwen2.5:1.5b
就把代码改成:
python
OLLAMA_MODEL = "qwen2.5:1.5b"
二十、最终命令汇总
从零开始的核心命令如下:
bash
mkdir -p ~/jetson_voice_ollama
cd ~/jetson_voice_ollama
conda create -n jetson_voice_ollama python=3.9 -y
conda activate jetson_voice_ollama
sudo apt update
sudo apt install -y alsa-utils portaudio19-dev espeak-ng unzip wget mpg123
pip install vosk sounddevice requests edge-tts
mkdir -p models
cd models
wget https://alphacephei.com/vosk/models/vosk-model-small-cn-0.22.zip
unzip vosk-model-small-cn-0.22.zip
cd ..
aplay -l
arecord -l
amixer -c 2 scontrols
amixer -c 2 scontents
amixer -c 2 sset PCM 95% unmute
amixer -c 2 sset Mic 85% cap
sudo alsactl store
edge-tts --voice zh-CN-XiaoxiaoNeural --rate +15% --text "你好,这是晓晓的声音。" --write-media /tmp/xiaoxiao.mp3 && mpg123 -q -a plughw:2,0 /tmp/xiaoxiao.mp3
ollama pull qwen2.5:0.5b
ollama run qwen2.5:0.5b
然后创建 voice_ollama.py 和 run.sh,运行:
bash
~/jetson_voice_ollama/run.sh
二十一、参考资料
-
Ollama API 文档:
/api/generate支持传入模型和 prompt,并支持通过stream:false返回完整响应。 -
Vosk Python 麦克风示例:使用
sounddevice.RawInputStream采集音频,并通过KaldiRecognizer实时识别。https://github.com/alphacep/vosk-api/blob/master/python/example/test_microphone.py
-
edge-tts 项目文档:支持
--voice、--list-voices、--write-media等命令行参数。 -
amixer 手册:支持通过
-c指定声卡,使用scontrols、scontents、sset查看和设置 ALSA 声卡控制项。
二十二、总结
本文实现了一个可以在 Jetson Nano / Jetson Orin Nano 上运行的中文语音助手:
text
USB 麦克风输入
→ Vosk 中文离线识别
→ Ollama 本地 qwen2.5:0.5b 大模型
→ edge-tts 晓晓中文语音
→ USB 喇叭输出
关键点有三个:
- 音频设备一定要先定位清楚 :通过
aplay -l找到 USB 声卡编号,比如card 2,播放设备就是plughw:2,0。 - 音量不要乱调 Master :很多 USB 声卡没有
Master,实际控制项可能叫PCM,需要通过amixer -c 2 scontrols查看。 - TTS 建议用 edge-tts :
espeak-ng虽然离线轻量,但中文声音比较机械;zh-CN-XiaoxiaoNeural +15%的效果更适合语音助手。
这个项目后续可以继续扩展,比如加入唤醒词、对话历史、多轮上下文、离线 TTS、开机自启动等功能。