Quectel Pi M2 实战:板载麦克风录音 + 蓝牙音箱播放全流程测试

环境: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:录音文件存在,但基本都是静音?

  1. 用 volumedetect 看 mean_volume 是否接近 -91 dB;
  2. 检查采集增益是否过低/被静音(见 2.3);
  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。


六、总结

  1. 录音链路:ES8389(card 0)→ PipeWire 源 → arecord 采集 → ffmpeg volumedetect 验证电平,一条龙搞定。
  2. 蓝牙链路:核心坑是 BLE 广播地址与经典蓝牙地址不同(小米系 = 经典 +1),务必用 scan bredr 扫经典侧、用经典地址配对,才能拿到 A2DP sink。
  3. 验证闭环:录一段 → 回放到蓝牙音箱,能把「输入 → 输出」整条音频通路一次性验证,为后续接入 ASR / TTS 扫清障碍。
  4. 一键脚本:test_record_play.sh 自带状态打印、音量分析、sink 自动选择与重连,方便反复回归测试。

下一步就可以在这条稳定音频链路上,接入本地 ASR 与 TTS,往「完全离线语音助手」推进了。


本文为 Quectel Pi M2 离线语音助手系列实践第一篇:音频链路验证。