环境:Quectel Pi M2(RK3576,6 TOPS NPU)/ Debian 13 (trixie) aarch64
目标:为「完全离线语音助手」打地基 —— 先把录音和蓝牙放音这两条音频链路跑通
前言
在做端侧离线语音助手(ASR → LLM → TTS)时,第一步不是上模型,而是把音频输入/输出这条最基础、也最容易踩坑的链路验证清楚:
- 板载麦克风能不能录到清晰的人声?
- 蓝牙音箱能不能稳定连接并播放?
本文记录在一台 Quectel Pi M2 开发板上,从零完成「板载麦克风录音」到「蓝牙音箱 A2DP 播放」的完整过程,包含完整可复制命令、一键测试脚本,以及几个我实际踩到的坑(尤其是 BLE 广播地址 ≠ 经典蓝牙地址 导致搜不到音箱的问题)。
一、测试环境
| 项目 | 说明 |
|---|---|
| 板卡 | Quectel Pi M2(瑞芯微 RK3576,6 TOPS NPU) |
| 系统 | Debian 13 (trixie),aarch64,内核 6.1.118-rt36 (PREEMPT_RT) |
| 音频服务 | PipeWire 1.4.2 + WirePlumber 0.5.8(注意不是 PulseAudio,但 pactl/paplay 兼容可用) |
| 相关包 | libspa-0.2-bluetooth、pipewire-pulse、libldacbt-* |
| 板载 Codec | ES8389(ALSA card 0,名称 rockchipes8389) |
| 蓝牙音箱 | 小爱音箱(经典蓝牙地址 44:DF:65:2F:52:28) |
| 工具 | arecord / aplay / paplay / pactl / ffmpeg / bluetoothctl |
提示:PipeWire 环境下请不要直接
systemctl去操作 PulseAudio,卡可正常走 PipeWire,用pactl/wpctl管理即可。
二、板载麦克风录音测试
2.1 确认声卡与采集设备
arecord -l
输出示例:
**** List of CAPTURE Hardware Devices ****
card 0: rockchipes8389 [rockchipes8389], device 0: ES8389 HiFi es8389-hifi-0 [ES8389 HiFi es8389-hifi-0]
Subdevices: 1/1
Subdevice #0: subdevice #0
说明板载 Codec 是 ES8389,位于 card 0 / device 0。
2.2 查看 PipeWire 输入源
pactl list short sources
pactl get-default-source
通常默认源为:
alsa_input.platform-es8389-sound.analog-stereo
2.3 检查采集增益(重要)
麦克风太小声/爆音,多半是 ADC 增益没调好:
amixer -c 0 scontrols
amixer -c 0 | grep -iE 'capture|mic|adc|gain' -A2
如果增益偏低,可拉满测试:
# 把常见采集控件都设为 100% 并取消静音
for c in Capture MIC Mic ADC "Capture Volume" "ADC Gain"; do
amixer -c 0 sset "$c" 100% unmute 2>/dev/null
done
本机实测:ADCL/ADCR = 191(约 75%),ADC MUX = AMIC。
做 ASR 时建议把增益降到 50%~60% 并关闭 ALC(自动增益),避免环境噪声被放大。
2.4 录音 + 音量分析
录 12 秒、16 kHz、单声道:
arecord -D default -f S16_LE -r 16000 -c 1 -d 12 /userdata/rkllm/mic_test.wav
用 ffmpeg 的 volumedetect 判断是否真的录到了声音:
ffmpeg -i /userdata/rkllm/mic_test.wav -af volumedetect -f null - 2>&1 \
| grep -E "mean_volume|max_volume|Duration"
本机实测结果:
Duration: 00:00:12.00
mean_volume: -32.3 dB
max_volume: -0.0 dB
mean_volume只要不是-91 dB(近乎静音)就说明录到了信号;max_volume接近0 dB说明峰值没削顶、增益合适。
三、蓝牙音箱连接与播放
3.1 坑一:scan on 搜不到音箱?
现象:bluetoothctl 里 scan on,只看到一堆 BLE 广播,死活搜不到音箱。
原因:小爱音箱等设备会同时暴露 BLE 广播和经典蓝牙(BR/EDR)服务,而 A2DP 音频走的是经典蓝牙。默认 scan on 常常只抓到 BLE,导致经典侧设备没被 inquiry 出来。
3.2 坑二:BLE 地址 ≠ 经典地址
小米系设备的约定:BLE 广播地址 = 经典蓝牙地址 + 1。
本机实测:
| 类型 | 地址 |
|---|---|
| BLE 广播(手机上看到的) | 44:DF:65:2F:52:29 |
| 经典蓝牙(音频必须用这个) | 44:DF:65:2F:52:28 |
如果按 BLE 地址去连音频,会连不上或拿不到 A2DP sink。
3.3 正确姿势:指定经典蓝牙扫描
bluetoothctl
[bluetoothctl]> power on
[bluetoothctl]> agent on
[bluetoothctl]> default-agent
[bluetoothctl]> scan bredr # 关键:只扫经典蓝牙
此时即可看到:
[NEW] Device 44:DF:65:2F:52:28 小爱音箱-2419
3.4 配对、信任、连接
[bluetoothctl]> pair 44:DF:65:2F:52:28
[bluetoothctl]> trust 44:DF:65:2F:52:28
[bluetoothctl]> connect 44:DF:65:2F:52:28
坑三:首次配对可能弹出 passkey 确认框。若
pair直接报AuthenticationFailed,直接connect往往能自动完成配对。
需要全自动脚本时,把 agent 设为NoInputNoOutput:
bluetoothctl --agent=NoInputNoOutput
3.5 确认 A2DP sink 与默认输出
连接成功后,PipeWire/BlueZ 会自动创建一个 A2DP 输出设备:
pactl list short sinks
输出中会出现(本机):
bluez_output.44_DF_65_2F_52_28.1
把它设为默认输出:
pactl set-default-sink bluez_output.44_DF_65_2F_52_28.1
3.6 播放测试
mp3 建议先转 wav(PipeWire 下 paplay 对 mp3 支持不如 wav 稳):
ffmpeg -i piano.mp3 /tmp/test_bt.wav
paplay --device=bluez_output.44_DF_65_2F_52_28.1 /tmp/test_bt.wav
echo "EXIT=$?"
返回 EXIT=0 且音箱出声即成功。也可以直接回放刚录的麦克风文件,形成「录进去 → 放出来」闭环。
四、一键脚本:录音 + 回放(推荐)
把下面内容保存为 /userdata/rkllm/test_record_play.sh,先打印设备状态,再录音、分析音量、自动挑蓝牙 sink 回放,最后给出排查提示。
#!/usr/bin/env bash
# 录音 + 回放 手动测试脚本 (Quectel Pi M2 / RK3576)
# 用法: bash /userdata/rkllm/test_record_play.sh [录音秒数,默认8]
set -u
DUR="${1:-8}"
TS="$(date +%Y%m%d_%H%M%S)"
WAV="/userdata/rkllm/rec_${TS}.wav"
BT_MAC="44:DF:65:2F:52:28"
echo "==================== 1) 设备状态 ===================="
echo "[蓝牙]"
bluetoothctl info "$BT_MAC" 2>/dev/null | grep -E "Name:|Connected:|Audio Sink|Trusted:" \
|| echo " 未找到蓝牙设备信息(可能断连)"
echo "[所有 sink]"
pactl list short sinks 2>/dev/null || echo " pactl 不可用"
echo "[默认 sink] $(pactl get-default-sink 2>/dev/null)"
echo "[默认 source] $(pactl get-default-source 2>/dev/null)"
echo
echo "==================== 2) 录音 ${DUR}s ===================="
arecord -D default -f S16_LE -r 16000 -c 1 -d "$DUR" "$WAV"
RC=$?
echo "arecord 退出码 = $RC"
ls -l "$WAV" 2>/dev/null
if [ "$RC" -ne 0 ]; then echo "录音失败,退出。"; exit 1; fi
echo
echo "==================== 3) 音量分析 ===================="
ffmpeg -hide_banner -i "$WAV" -af volumedetect -f null - 2>&1 \
| grep -E "mean_volume|max_volume|Duration" || true
echo
echo "==================== 4) 回放到蓝牙音箱 ===================="
BT_SINK="$(pactl list short sinks 2>/dev/null | awk '/bluez_output/{print $2; exit}')"
if [ -z "$BT_SINK" ]; then
echo "未发现蓝牙 sink -> 尝试重连音箱..."
bluetoothctl connect "$BT_MAC"
sleep 1
BT_SINK="$(pactl list short sinks 2>/dev/null | awk '/bluez_output/{print $2; exit}')"
fi
if [ -n "$BT_SINK" ]; then
echo "目标 sink: $BT_SINK"
pactl set-default-sink "$BT_SINK"
echo "[音量] $(pactl get-sink-volume "$BT_SINK" 2>/dev/null | head -1)"
echo "[静音] $(pactl get-sink-mute "$BT_SINK" 2>/dev/null)"
pactl set-sink-mute "$BT_SINK" 0 2>/dev/null
paplay --device="$BT_SINK" "$WAV"
echo "蓝牙回放退出码 = $?"
else
echo "仍无蓝牙 sink,改用默认输出:"
paplay "$WAV"; echo "默认输出回放退出码 = $?"
fi
echo
echo "提示:还听不到就依次查 -> 音箱开机? 在蓝牙范围内? Connected: yes? 音箱自身音量?"
echo "录音文件: $WAV"
运行:
chmod +x /userdata/rkllm/test_record_play.sh
bash /userdata/rkllm/test_record_play.sh 8
五、常见问题(FAQ)
Q1:录音文件存在,但基本都是静音?
- 用
volumedetect看mean_volume是否接近-91 dB; - 检查采集增益是否过低/被静音(见 2.3);
- 确认 ADC MUX 选的是 AMIC(板载模拟麦)而不是 Line-in。
Q2:pactl 提示连不上?
PipeWire 没起来或 pipewire-pulse 未运行:
systemctl --user status pipewire pipewire-pulse wireplumber
Q3:蓝牙连上了但没声音?
- 确认
bluetoothctl info <MAC>里Audio Sink存在、Connected: yes; - 确认默认 sink 是
bluez_output.*; - 检查 sink 音量/静音:
pactl get-sink-volume <sink>、pactl get-sink-mute <sink>; - 音箱自身音量、是否已切到蓝牙输入。
Q4:mp3 播不出声?
先 ffmpeg 转 wav 再 paplay,在 PipeWire 环境下最稳。
Q5:scan on 卡在 InProgress?
把已有 discovery 关掉或直接换终端重连,再 scan bredr。
六、总结
- 录音链路:ES8389(card 0)→ PipeWire 源 →
arecord采集 →ffmpeg volumedetect验证电平,一条龙搞定。 - 蓝牙链路:核心坑是 BLE 广播地址与经典蓝牙地址不同(小米系 = 经典 +1),务必用
scan bredr扫经典侧、用经典地址配对,才能拿到 A2DP sink。 - 验证闭环:录一段 → 回放到蓝牙音箱,能把「输入 → 输出」整条音频通路一次性验证,为后续接入 ASR / TTS 扫清障碍。
- 一键脚本:
test_record_play.sh自带状态打印、音量分析、sink 自动选择与重连,方便反复回归测试。
下一步就可以在这条稳定音频链路上,接入本地 ASR 与 TTS,往「完全离线语音助手」推进了。
本文为 Quectel Pi M2 离线语音助手系列实践第一篇:音频链路验证。
