【小白也能行】树莓派智能蓝牙音箱项目实践2.0
【小白也能行】树莓派智能蓝牙音箱项目实践2.0
📋 USB麦克风硬件接入
1. USB 麦克风硬件集成
| 步骤 | 内容 | 结果 |
|---|---|---|
| 设备识别 | lsusb发现Texas Instruments PCM2902 Audio Codec | ✅ |
| 录音设备确认 | arecord -l确认为 card 2, device 0 | ✅ |
| PyAudio 设备定位 | 设备索引为 3,max inputs: 1 | ✅ |
2. 解决录音兼容性问题
| 问题 | 原因 | 解决方案 |
|---|---|---|
Invalid number of channels | 设备索引写错(用了 ALSA 的 card 号 2,而非 PyAudio 的索引 3) | 改为RECORD_DEVICE_INDEX = 3 |
Invalid sample rate | PCM2902 芯片不支持 16000Hz,只支持 44100/48000Hz | 用 44100Hz 录音,再ffmpeg重采样到 16000Hz |
input_device_name参数报错 | PyAudio 不支持该参数 | 舍弃 plughw 字符串方案,改用重采样方案 |
问题一过程回放:
Cannot connect to server socket err=No suchfileor directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init notdonefor-1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init notdonefor-1, skipping unlock 📋 可用录音设备:[3]USB PnP Sound Device: Audio(hw:2,0)(max inputs:1)[8]pulse(max inputs:32)[13]default(max inputs:32)Expression'parameters->channelCount <= maxChans'failedin'src/hostapi/alsa/pa_linux_alsa.c', line:1514Expression'ValidateParameters( inputParameters, hostApi, StreamDirection_In )'failedin'src/hostapi/alsa/pa_linux_alsa.c', line:2818❌ 无法打开录音设备2:[Errno -9998]Invalid number of channels 请根据上面列表,修改 RECORD_DEVICE_INDEX 为正确的编号关键错误信息:
📋 可用录音设备:
[3] USB PnP Sound Device: Audio (hw:2,0) (max inputs: 1)
修复方案:
# ❌ 原来的RECORD_DEVICE_INDEX=2# ✅ 改成RECORD_DEVICE_INDEX=3# 确认22行声道数CHANNELS=1# max inputs: 1,必须是 1问题二过程回放:
Cannot connect to server socket err=No suchfileor directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init notdonefor-1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init notdonefor-1, skipping unlock 📋 可用录音设备:[3]USB PnP Sound Device: Audio(hw:2,0)(max inputs:1)[8]pulse(max inputs:32)[13]default(max inputs:32)Expression'paInvalidSampleRate'failedin'src/hostapi/alsa/pa_linux_alsa.c', line:2048Expression'PaAlsaStreamComponent_InitialConfigure( &self->capture, inParams, self->primeBuffers, hwParamsCapture, &realSr )'failedin'src/hostapi/alsa/pa_linux_alsa.c', line:2718Expression'PaAlsaStream_Configure( stream, inputParameters, outputParameters, sampleRate, framesPerBuffer, &inputLatency, &outputLatency, &hostBufferSizeMode )'failedin'src/hostapi/alsa/pa_linux_alsa.c', line:2842❌ 无法打开录音设备3:[Errno -9997]Invalid sample rate 请根据上面列表,修改 RECORD_DEVICE_INDEX 为正确的编号关键错误信息:
❌ 无法打开录音设备 3: [Errno -9997] Invalid sample rate 请根据上面列表,修改 RECORD_DEVICE_INDEX 为正确的编号
这说明PCM2902 这款 USB 声卡不支持 16000Hz 采样率。PCM2902 是标准的 48kHz 芯片,只支持 48000Hz 和 44100Hz。
修复方案:
# 不用改采样率(改了会让 ASR 和录音代码耦合),直接用 ALSA 的 plughw 插件,它自动把任意采样率转成硬件支持的格式。# 1、删除了 input_device_index=3# 2、改用 input_device_name="plughw:2,0"(plughw 自动处理采样率转换,16000 → 48000)# 3、input_device_index 设为 Nonedefrecord_audio(output_file):""" 录音并保存为 WAV。 使用 plughw 自动适配硬件采样率。 """pa=pyaudio.PyAudio()# 打印可用录音设备print("📋 可用录音设备:")foriinrange(pa.get_device_count()):dev=pa.get_device_info_by_index(i)ifdev['maxInputChannels']>0:print(f" [{i}]{dev['name']}(max inputs:{dev['maxInputChannels']})")# 用 plughw 打开 —— 自动处理采样率转换device_name="plughw:2,0"try:stream=pa.open(format=pyaudio.paInt16,channels=CHANNELS,rate=SAMPLE_RATE,input=True,input_device_index=None,# 不指定索引input_device_name=device_name,# 用 plughw 设备名frames_per_buffer=1024)exceptExceptionase:print(f"❌ 无法打开录音设备{device_name}:{e}")pa.terminate()returnFalseprint(f"🎤 开始录音... (最长{MAX_RECORD_SECONDS}秒,检测到静音自动结束)")frames=[]has_speech=Falsesilence_frames=0frames_per_second=SAMPLE_RATE/1024foriinrange(int(frames_per_second*MAX_RECORD_SECONDS)):try:data=stream.read(1024,exception_on_overflow=False)exceptException:continueframes.append(data)# 简单音量检测max_val=max(abs(int.from_bytes(data[j:j+2],'little',signed=True))forjinrange(0,len(data),2))ifmax_val>SILENCE_THRESHOLD:has_speech=Truesilence_frames=0else:silence_frames+=1ifhas_speechandsilence_frames>frames_per_second*SILENCE_SECONDS:print("🔇 检测到静音,结束录音")breakstream.stop_stream()stream.close()pa.terminate()duration=len(frames)/frames_per_secondprint(f"✅ 录音完成,时长约{duration:.1f}秒")# 保存 WAVwf=wave.open(output_file,'wb')wf.setnchannels(CHANNELS)wf.setsampwidth(pa.get_sample_size(pyaudio.paInt16))wf.setframerate(SAMPLE_RATE)wf.writeframes(b''.join(frames))wf.close()returnTrue问题三过程回放:
# 报错信息Cannot connect to server socket err=No suchfileor directory Cannot connect to server request channel jack server is not running or cannot be started JackShmReadWritePtr::~JackShmReadWritePtr - Init notdonefor-1, skipping unlock JackShmReadWritePtr::~JackShmReadWritePtr - Init notdonefor-1, skipping unlock 📋 可用录音设备:[3]USB PnP Sound Device: Audio(hw:2,0)(max inputs:1)[8]pulse(max inputs:32)[13]default(max inputs:32)❌ 无法打开录音设备 plughw:2,0: PyAudio.Stream.__init__()got an unexpected keyword argument'input_device_name'关键错误信息:
❌ 无法打开录音设备 plughw:2,0: PyAudio.Stream.init() got an unexpected keyword argument ‘input_device_name’
原因:PCM2902 硬件只支持 48000Hz 和 44100Hz。我们用 44100Hz 录音,然后调用
ffmpeg重采样成 ASR 模型需要的 16000Hz。
修复方案:
# 修改USB麦克风相关设置# 录音参数RECORD_DEVICE_INDEX=3# USB 声卡在 PyAudio 中的设备索引SAMPLE_RATE=44100# PCM2902 硬件支持的采样率(不用 16000)ASR_SAMPLE_RATE=16000# ASR 模型需要的采样率CHANNELS=1MAX_RECORD_SECONDS=10SILENCE_THRESHOLD=500SILENCE_SECONDS=1.5# 添加ffmpeg重采样defrecord_audio(output_file):""" 录音并保存为 ASR 需要的 16kHz WAV。 硬件用 44100Hz 录音,然后 ffmpeg 重采样到 16000Hz。 """importtempfile pa=pyaudio.PyAudio()print("📋 可用录音设备:")foriinrange(pa.get_device_count()):dev=pa.get_device_info_by_index(i)ifdev['maxInputChannels']>0:print(f" [{i}]{dev['name']}(max inputs:{dev['maxInputChannels']})")try:stream=pa.open(format=pyaudio.paInt16,channels=CHANNELS,rate=SAMPLE_RATE,# 44100,硬件原生支持input=True,input_device_index=RECORD_DEVICE_INDEX,frames_per_buffer=1024)exceptExceptionase:print(f"❌ 无法打开录音设备{RECORD_DEVICE_INDEX}:{e}")pa.terminate()returnFalseprint(f"🎤 开始录音 ({SAMPLE_RATE}Hz)... (最长{MAX_RECORD_SECONDS}秒,检测到静音自动结束)")frames=[]has_speech=Falsesilence_frames=0frames_per_second=SAMPLE_RATE/1024foriinrange(int(frames_per_second*MAX_RECORD_SECONDS)):try:data=stream.read(1024,exception_on_overflow=False)exceptException:continueframes.append(data)# 简单音量检测max_val=max(abs(int.from_bytes(data[j:j+2],'little',signed=True))forjinrange(0,len(data),2))ifmax_val>SILENCE_THRESHOLD:has_speech=Truesilence_frames=0else:silence_frames+=1ifhas_speechandsilence_frames>frames_per_second*SILENCE_SECONDS:print("🔇 检测到静音,结束录音")breakstream.stop_stream()stream.close()pa.terminate()duration=len(frames)/frames_per_secondprint(f"✅ 录音完成,时长约{duration:.1f}秒")# 先保存为临时文件(44100Hz),再用 ffmpeg 重采样到 16000Hzraw_file=tempfile.mktemp(suffix='.wav')wf=wave.open(raw_file,'wb')wf.setnchannels(CHANNELS)wf.setsampwidth(pa.get_sample_size(pyaudio.paInt16))wf.setframerate(SAMPLE_RATE)wf.writeframes(b''.join(frames))wf.close()# ffmpeg 重采样print("🔄 重采样到 16kHz...")ret=subprocess.run(['ffmpeg','-y','-loglevel','quiet','-i',raw_file,'-ar',str(ASR_SAMPLE_RATE),'-ac','1',output_file],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL)os.unlink(raw_file)# 删除临时文件ifret.returncode!=0:print("❌ 重采样失败")returnFalseprint(f"✅ 已保存{SAMPLE_RATE}Hz →{ASR_SAMPLE_RATE}Hz")returnTrue至此硬件主要的问题就解决完了!另外附带一个‘欢迎词’的优化过程
问题四过程回放:
# 问题现象:欢迎语有明显的卡顿和内容丢失# 原因分析:通常是 TTS 音频还没完全生成就开始播放,或者是蓝牙音频缓冲区初始化不及时导致的。# 修复方案:通过预加载延迟来解决欢迎词卡顿和丢失内容的现象# 主循环里播放欢迎语这段添加修改# 启动问候# 原代码逻辑print("🔊 播放欢迎语...")try:play_audio(tts("你好,我是小硅,你的语音助手已上线。"))exceptExceptionase:print(f"❌ 欢迎语失败:{e}")# 修改后代码逻辑# 启动问候print("🔊 准备欢迎语...")try:audio_data=tts("你好,我是小硅,你的语音助手已上线。")# 等待音频数据完全就绪importtime time.sleep(0.5)print("🔊 播放中...")play_audio(audio_data)exceptExceptionase:print(f"❌ 欢迎语失败:{e}")关键改动:
- 先把 TTS 结果存到变量
audio_data- 加
time.sleep(0.5)让蓝牙音频缓冲区就绪- 再调用
play_audio
3. 完整语音链路跑通
🎤 USB麦克风 (44100Hz) → 🔄 ffmpeg重采样 (16000Hz) → 📝 硅基流动 ASR → 🤖 硅基流动 LLM → 🔊 硅基流动 TTS → 🎵 蓝牙音箱全链路验证通过 ✅
4. 体验优化
| 问题 | 调整 |
|---|---|
| 音量太低 | pactl set-sink-volume @DEFAULT_SINK@ +10% |
| 语速太快 | speed参数从1.0改为0.8 |
| 欢迎语卡顿/丢失 | TTS 生成后加time.sleep(0.5)预加载 |
📊 当前项目状态
| 模块 | 状态 | 备注 |
|---|---|---|
| 蓝牙音箱播放 | ✅ | A2DP 模式,音量可调 |
| USB 麦克风录音 | ✅ | 44100Hz → ffmpeg 重采样 16000Hz |
| 静音检测自动结束 | ✅ | 1.5 秒静音触发停止 |
| ASR 语音识别 | ✅ | 硅基流动 SenseVoiceSmall |
| LLM 对话 | ✅ | Qwen2.5-7B-Instruct,多轮上下文 |
| TTS 语音合成 | ✅ | CosyVoice2-0.5B,opus 格式 |
| 欢迎语播放 | ✅ | 预加载延迟修复卡顿 |
| 语速调节 | ✅ | speed=0.8 |
已交付文件
| 文件 | 用途 |
|---|---|
keyboard_assistant.py | 键盘版(调试用) |
voice_assistant.py | 完整语音版(日常使用) |
test_keyboard.py | 系统能力测试脚本 |
🔮 后续优化方向和规划
🔴 高优先级(核心体验提升)
| 序号 | 优化项 | 说明 | 预计耗时 |
|---|---|---|---|
| 1 | 开机自启 | systemd 服务,上电自动运行语音助手 | 20 分钟 |
| 2 | 唤醒词 | Picovoice Porcupine,"小硅小硅"免按回车直接唤醒 | 30 分钟 |
| 3 | 异常重连 | 蓝牙断开自动回连、API 超时重试、网络恢复后自愈 | 30 分钟 |
| 4 | 对话提示音 | 唤醒成功/开始录音/处理完成,用短促提示音替代看屏幕 | 15 分钟 |
🟡 中优先级(功能增强)
| 序号 | 优化项 | 说明 | 预计耗时 |
|---|---|---|---|
| 5 | 状态指示灯 | USB 声卡或 GPIO LED 显示:待机(暗)/唤醒(蓝)/处理(绿闪烁)/播放(绿)/错误(红) | 30 分钟 |
| 6 | 打断功能 | 播放回复时按回车或说唤醒词打断,立刻进入下一轮录音 | 20 分钟 |
| 7 | 对话历史管理 | 限制上下文长度,避免 token 爆炸;支持"忘记之前说的"重置对话 | 15 分钟 |
| 8 | 连续对话模式 | 回复播放完后自动进入录音,无需每次都按回车 | 20 分钟 |
🟢 低优先级(锦上添花)
| 序号 | 优化项 | 说明 | 预计耗时 |
|---|---|---|---|
| 9 | 自定义唤醒词训练 | 用 Picovoice 在线训练"小硅小硅",替代内置词 | 10 分钟 |
| 10 | TTS 音色优选 | 测试全部 8 种 CosyVoice2 音色,选定最适合的 | 15 分钟 |
| 11 | 音量自适应 | 根据环境噪音自动调节 TTS 音量 | 30 分钟 |
| 12 | 外壳与按键 | 3D 打印外壳 + 物理按键触发录音/静音 | 视硬件而定 |
| 13 | 日志记录 | 对话记录保存到文件,方便回溯 | 15 分钟 |
| 14 | OTA 升级 | 远程更新代码和模型配置 | 1 小时+ |
📌 下次继续的起点
等你再次开始时,建议按这个顺序推进:
1. 开机自启(最实用,插电即用) 2. 唤醒词(体验质的飞跃,真正解放双手) 3. 提示音 + 状态灯(脱离屏幕,纯语音交互) 4. 对话历史管理(稳定运行的基础)📊 项目进度
核心功能 ██████████████████████████████████████████████████ 100% ✅ 语音交互链路 ██████████████████████████████████████████████████ 100% ✅ 体验优化 ████████████████████████░░░░░░░░░░░░░░░░░░░░░░░░ 60% ⏳ 系统集成 ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 5% ⏳ ──────────────────────────────────────────────────── 总体进度 ████████████████████████████████░░░░░░░░░░░░░░░░ 66%📋 分项明细
| 大类 | 子项 | 状态 |
|---|---|---|
| 核心功能 | 蓝牙音箱播放 | ✅ |
| USB 麦克风录音 | ✅ | |
| ASR 语音识别 | ✅ | |
| LLM 对话生成 | ✅ | |
| TTS 语音合成 | ✅ | |
| 完整链路跑通 | ✅ | |
| 语音交互链路 | 多轮对话上下文 | ✅ |
| 静音检测自动结束 | ✅ | |
| 键盘版(调试用) | ✅ | |
| 语音版(日常用) | ✅ | |
| 体验优化 | 音量调节 | ✅ |
| 语速调节 | ✅ | |
| 欢迎语卡顿修复 | ✅ | |
| 唤醒词 | ⏳ | |
| 提示音 | ⏳ | |
| 系统集成 | 开机自启 | ⏳ |
| 异常重连 | ⏳ | |
| 状态指示灯 | ⏳ | |
| 外壳/按键 | ⏳ |
一句话总结:能用了,但还不够"无感"。核心链路 100% 打通,差的都是"让它更好用"的活儿。下次从唤醒词和开机自启下手,体验会有质的飞跃 🚀
