蓝牙功放连接与音量异常排查记录
环境
笔记本运行 Ubuntu,桌面为 KDE Plasma Wayland,蓝牙栈为 BlueZ 加 PipeWire 1.6.2 加 WirePlumber,音频设备为一台 TVCTO 蓝牙功放,通过 A2DP 播放。
现象
三个问题同时存在。
第一,功放与电脑连接不稳定,表现为 Connection refused 111 与 Connection timed out 110 交替出现,重试若干次后可以连上。
第二,蓝牙日志中持续出现 Hands-Free Voice gateway 相关报错,A2DP 媒体流频繁断开重连。
第三,连接恢复后,系统音量调节异常。音量键只在 14 到 16 的小区间内变化,托盘滑条拖动后显示跳回 15,但实际音量已经改变。
排查过程
连接拒绝
排查入口是 BlueZ 守护进程日志:
journalctl -u bluetooth -f
拒连时段的关键日志:
9月 13 23:33:55 bluetoothd[6105]: src/profile.c:ext_connect() Hands-Free Voice gateway failed connect to 41:42:46:3C:D4:31: Connection refused (111)
9月 13 23:33:55 bluetoothd[6105]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to 41:42:46:3C:D4:31: Connection refused (111)
9月 13 23:37:55 bluetoothd[6105]: src/profile.c:record_cb() Unable to get Hands-Free Voice gateway SDP record: Operation already in progress
9月 13 23:38:20 bluetoothd[6105]: profiles/audio/avdtp.c:avdtp_connect_cb() connect to 41:42:46:3C:D4:31: Connection timed out (110)
9月 13 23:38:47 bluetoothd[6105]: src/profile.c:record_cb() Unable to get Hands-Free Voice gateway SDP record: Connection timed out
9月 13 23:39:52 bluetoothd[6105]: /org/bluez/hci1/dev_41_42_46_3C_D4_31/sep1/fd0: fd(40) ready
Connection refused 是 L2CAP 层主动拒绝,说明协议栈与设备均存活,是设备策略层面的拒连。紧随其后的 SDP 查询超时是同一原因派生的次生现象。设备端并未与其他主机建立真实连接,但固件维护的链路状态机认为旧连接仍然存活,形成僵尸连接,新连接进入时主动拒绝。
这类行为无法从主机侧根治。有效触发手段有两个。其一,按下功放配对键使其重新进入配对模式,固件会清理链路状态。其二,确保旧主机侧先执行显式断开:
bluetoothctl disconnect 41:42:46:3C:D4:31
断电重启是保底手段,原理相同但成本更高。日志中 23:38 到 23:39:52 约 90 秒后才自动恢复,说明该设备链路释放周期长,不宜依赖其自愈。
HFP 协商异常
日志中 ext_connect Hands-Free Voice gateway failed 表明 BlueZ 作为 Audio Gateway 角色持续尝试与设备建立 HFP 链路。功放放音乐只依赖 A2DP,HFP 与 HSP 属于多余协商面,协商失败会直接干扰 A2DP 会话稳定性。
BlueZ 不提供环境变量方式关闭 profile,正确做法是通过 --noplugin 禁用对应插件。修改 systemd 覆盖文件:
sudo mkdir -p /etc/systemd/system/bluetooth.service.d
sudo tee /etc/systemd/system/bluetooth.service.d/override.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/libexec/bluetooth/bluetoothd --noplugin=hfp_ag,hsp_ag
EOF
sudo systemctl daemon-reload
sudo systemctl restart bluetooth
注意三点。其一,ExecStart= 留空行用于清除原有启动命令,不可省略。其二,bluetoothd 路径以 systemctl cat bluetooth 实际输出为准,不要凭发行版惯性推断。旧版本 Ubuntu 与 Debian 位于 /usr/lib/bluetooth/bluetoothd,新版本 Ubuntu 的 bluez 打包已迁移到 /usr/libexec/bluetooth/bluetoothd。其三,验证覆盖是否挂载:
systemctl cat bluetooth
输出末尾应同时出现空 ExecStart= 与带参数的新 ExecStart。进一步验证 HFP 已禁用:
sudo sdptool records 41:42:46:3C:D4:31 | grep -i handsfree
无输出即生效。
音量显示异常
蓝牙音量控制走 AVRCP 协议的控制信道,与传输音频的 A2DP 媒体信道相互独立。绝对音量模式下,电脑将目标音量通过 SetAbsoluteVolume 命令下发设备,设备执行后通过 Changed 通知上报当前值。协议规定上报范围为 0 到 127,但该功放固件将其映射为 16 级粗刻度,并且上报值与实际执行值不一致,导致 GUI 显示锁死在 15,按键步进被设备刻度钳制。
处理方式为禁用绝对音量,让音量回归 PipeWire 内部的软件控制。先确认设备 sink 状态:
wpctl status
pactl list sinks short
修复前 TVCTO sink 的音量显示为 16 级刻度:
Sinks:
272. TVCTO [vol: 0.16]
新增 WirePlumber 规则:
sudo tee /etc/wireplumber/wireplumber.conf.d/51-bluez-no-absvol.conf <<'EOF'
monitor.bluez.rules = [
{
matches = [
{ device.name = "~bluez_card.*" }
]
actions = {
update-props = {
bluez5.absolute-volume = false
}
}
}
]
EOF
重启 WirePlumber 并断开重连设备:
systemctl --user restart wireplumber
bluetoothctl disconnect 41:42:46:3C:D4:31
sleep 3
bluetoothctl connect 41:42:46:3C:D4:31
断开后立即回连可能返回:
Failed to connect: org.bluez.Error.Failed br-connection-unknown
原因是控制器与设备均在清理旧链路状态,间隔数秒后重试即可。
验证标准为 wpctl status 中设备 sink 音量显示为浮点值:
Sinks:
135. TVCTO [vol: 0.32]
按键与托盘滑条在 0 到 100 区间连续可调,显示值与实际音量一致。音量事件可用 pactl subscribe 实时监听。
结论
三个问题的根源均在设备固件侧。主机侧的应对策略一致,即识别有缺陷的协议环节并逐个绕开。禁 HFP 砍掉多余协商面,关绝对音量绕开错误上报,连接异常则通过配对键触发设备自清。
配置变更存档三处:bluetooth.service 覆盖文件,WirePlumber 规则文件,以及操作习惯变更,即连接前先确认旧链路已释放。
工具备忘
| 工具 | 用途 |
|---|---|
journalctl -u bluetooth -f |
BlueZ 守护进程日志 |
journalctl --user -f -u pipewire -u wireplumber |
音频栈日志 |
sudo btmon |
HCI 层原始报文,断连归因看 reason code |
bluetoothctl |
设备连接管理 |
wpctl status |
sink 与音量状态 |
pactl list sinks short |
sink 列表与运行状态 |
pactl subscribe |
音量事件实时监听 |
sdptool records <MAC> |
设备 SDP 记录检查 |