fix(audiod): M50 r2 蓝牙打开时序 — MAC 改从 hcitool con 取(list-pcms 易失)+ 短重试 + 行内匹配修正
真机(2026-11-11 18:2x/18:3x,MOONDROP Pudding 33:DE:2E:21:AD:30)暴露两个时序/解析问题:
① **list-pcms 易失,不能当 MAC 主来源**:bluealsa 的 a2dpsrc PCM 对象只在 A2DP profile
配置好后的秒级窗口内存在(无人开 PCM 时 1~3s 就消失)。r1 用它取 MAC → 探针一错过窗口
就 btdev=- / bt=fail 回落。改为 **主来源 `hcitool con` 的 ACL 行**(内核 ACL 是连接权威,
开销一个 popen),拿到 MAC 直接 snd_pcm_open("bluealsa:DEV=<mac>,...") 让 bluealsa
在 open 时自己创建 PCM;list-pcms 降级为兜底来源。
② **ACL 刚建立时 profile 可能还没配置好** → open 会瞬时 ENODEV。改 3 次 × 300ms 短重试
(上限 600ms,不占播放线程太久);失败后才记为 fail#n 进退避(仍只在开流/重配边界)。
③ **端点路径要 D-Bus 全串**(`bluealsa-cli volume|info` 要它,不是 MAC):新增
bt_dev_endpoint_path();PCM 撑住后再 list-pcms 取路径(此时稳定)→ bt_refresh_endpoint()。
④ **命令换行 bug**:旧 vol_full 的 token 扫描不认 '\n',真机把端点路径连换行一起塞进
`bluealsa-cli volume <path>\n 127`,第二行成了独立命令 → 音量设置实际没下发。改为
bt_dev_endpoint_path() 严格按空白/\r/\n 截断。
⑤ **行内匹配 bug**:is_src_line() 对整串 strstr 会借到下一行的 a2dpsrc —— hfphf/source
行被误判成 A2DP 源端点(count_src 少算/endpoint_path 取错行)。改 line_is_src() 只在
本行内判定。
⑥ STATE 增 `btacodec=`:读回 `bluealsa-cli info` 的 Selected codec。真机 MOONDROP Pudding
只有 SBC/AAC,settings 请求 LDAC 时 bluealsa 接受参数但实际选 AAC —— 面板必须看到
实际值(btcodec= 仍是请求值,btacodec= 是实际值)。
⑦ AVRCP 满量程 127(bluealsa-cli volume 取值范围 [0,127],旧值 100 只到 79%)。
宿主:test_bt_dev 66/66(新增 hcitool con 真机原文 / 端点路径 / info 原文解析用例),
test_audiod_host 109/0,交叉编译 0 warning(67944B,md5 0ec3b9a6…)。
真机:r1 二进制 18:26 那次已实测 BT 出声(bt=ok / played=55296 / bluealsa Running:true /
Delay 17.2ms / Volume 127),18:33 起耳机自动关机,r2 的 hcitool-con 主路径未能在真机复验
(耳机不可达),详见报告。
This commit is contained in:
1 parent
33bef36d16
commit
6e7c0d00c0
4 files changed
+321
-57
No files matched your search
@@ -1,8 +1,9 @@
|
||||
/* test_bt_dev.c — 蓝牙端点选择 / PCM 名构造 / 回落退避 宿主验证(纯逻辑,bt_dev.c)。
|
||||
*
|
||||
* 输入全部取自**真机实测格式**(bluealsa v4.1.1,设备 /usr/bin/bluealsa 的
|
||||
* `dev_%.2X_%.2X_%.2X_%.2X_%.2X_%.2X` + src/ba-transport.c 的 `<dev>/a2dpsrc/source`):
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source
|
||||
* 输入全部取自**真机实测原文**(bluealsa v4.1.1;设备 /usr/bin/bluealsa 的
|
||||
* `dev_%.2X_..` + src/ba-transport.c 的 `<dev>/<profile>/<source|sink>`):
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink ← 实机(本机 a2dp-source)
|
||||
* (同一格式下 /a2dpsrc/source 只是 PCM 模式不同,解析只认 dev_XX_.. 段)
|
||||
*
|
||||
* 覆盖矩阵:
|
||||
* D1 bt_dev_detect_mac:真实 D-Bus 路径 → MAC;多端点/多设备取第一个 a2dpsrc;
|
||||
@@ -12,6 +13,9 @@
|
||||
* auto/空/unchanged/垃圾 → unchanged。
|
||||
* D4 bt_dev_pcm_name:内联 DEV/PROFILE/CODEC;坏 MAC/小缓冲 → -1。
|
||||
* D5 bt_dev_backoff_ms / should_try:2s→60s 封顶 + 门控(防重试风暴)。
|
||||
* D6 bt_dev_endpoint_path:取整条 D-Bus 路径给 bluealsa-cli volume|info(真机行含 /sink)。
|
||||
* D7 bt_dev_info_codec:真机 `bluealsa-cli info` 原文 → Selected codec(不能误吃 Available 行)。
|
||||
* D8 bt_dev_acl_mac:真机 `hcitool con` 原文 → ACL 对端 MAC(MAC 主来源;list-pcms 只在秒级窗口存在)。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_audiod 下):
|
||||
* cc -Wall -Wextra -o test_bt_dev test_bt_dev.c bt_dev.c
|
||||
@@ -99,6 +103,11 @@ int main(void)
|
||||
check(bt_dev_count_src(
|
||||
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source\n") == 1,
|
||||
"D2c one a2dpsrc -> 1");
|
||||
/* 行内判定回归:hfphf 行在 a2dpsrc 行之前,不能被算成源端点(整串 strstr 会误判) */
|
||||
check(bt_dev_count_src(
|
||||
"/org/bluealsa/hci0/dev_E0_42_6D_79_C5_98/hfphf/source\n"
|
||||
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink\n") == 1,
|
||||
"D2e hfphf line before a2dpsrc -> count 1 (per-line match, not whole-buffer)");
|
||||
check(bt_dev_count_src(
|
||||
"/org/bluealsa/hci0/dev_AA_BB_CC_DD_EE_FF/a2dpsrc/source\n"
|
||||
"/org/bluealsa/hci0/dev_11_22_33_44_55_66/a2dpsrc/source\n") == 2,
|
||||
@@ -159,6 +168,95 @@ int main(void)
|
||||
check(bt_dev_pcm_name("33:DE:2E:21:AD:30", "LDAC", n, 16) == -1, "D4g tiny buffer -> -1");
|
||||
}
|
||||
|
||||
/* ==== D6 端点完整路径(volume/info 要 D-Bus 路径,不是 MAC) ==== */
|
||||
{
|
||||
char path[128] = "";
|
||||
int rc = bt_dev_endpoint_path(
|
||||
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink\n", path, sizeof path);
|
||||
check(rc == 0 && strcmp(path, "/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink") == 0,
|
||||
"D6a real /a2dpsrc/sink line -> full path (trailing newline trimmed)");
|
||||
}
|
||||
{
|
||||
char path[128] = "";
|
||||
int rc = bt_dev_endpoint_path(
|
||||
"/org/bluealsa/hci0/dev_E0_42_6D_79_C5_98/hfphf/source\n"
|
||||
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink\r\n", path, sizeof path);
|
||||
check(rc == 0 && strcmp(path, "/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink") == 0,
|
||||
"D6b skips hfphf, strips CRLF (no newline leaks into popen cmd)");
|
||||
}
|
||||
{
|
||||
char path[128] = "";
|
||||
check(bt_dev_endpoint_path("/org/bluealsa/hci0/dev_11_22_33_44_55_66/a2dpsnk/sink\n",
|
||||
path, sizeof path) == -1, "D6c no a2dpsrc -> -1");
|
||||
check(bt_dev_endpoint_path("", path, sizeof path) == -1, "D6d empty -> -1");
|
||||
check(bt_dev_endpoint_path(NULL, path, sizeof path) == -1, "D6e NULL -> -1");
|
||||
}
|
||||
|
||||
/* ==== D7 实际协商 codec(真机 info 原文:MOONDROP Pudding 只有 SBC/AAC,请求 LDAC 实际选 AAC) ==== */
|
||||
{
|
||||
const char *info =
|
||||
"Device: /org/bluez/hci0/dev_33_DE_2E_21_AD_30\n"
|
||||
"Sequence: 0\n"
|
||||
"Transport: A2DP-source\n"
|
||||
"Mode: sink\n"
|
||||
"Running: true\n"
|
||||
"Format: S16_LE\n"
|
||||
"Channels: 2\n"
|
||||
"Sampling: 48000 Hz\n"
|
||||
"Available codecs: SBC AAC\n"
|
||||
"Selected codec: AAC\n"
|
||||
"Delay: 17.2 ms\n"
|
||||
"SoftVolume: false\n"
|
||||
"Volume: L: 127 R: 127\n"
|
||||
"Muted: L: false R: false\n";
|
||||
char codec[16] = "";
|
||||
check(bt_dev_info_codec(info, codec, sizeof codec) == 0 && strcmp(codec, "AAC") == 0,
|
||||
"D7a real info -> Selected codec=AAC (not the Available line)");
|
||||
}
|
||||
{
|
||||
const char *info = "Available codecs: SBC AAC\nSelected codec: SBC\r\n";
|
||||
char codec[16] = "";
|
||||
check(bt_dev_info_codec(info, codec, sizeof codec) == 0 && strcmp(codec, "SBC") == 0,
|
||||
"D7b CRLF trimmed");
|
||||
}
|
||||
{
|
||||
char codec[16] = "";
|
||||
check(bt_dev_info_codec("Running: false\n", codec, sizeof codec) == -1,
|
||||
"D7c no Selected line -> -1");
|
||||
check(bt_dev_info_codec(NULL, codec, sizeof codec) == -1, "D7d NULL -> -1");
|
||||
}
|
||||
|
||||
/* ==== D8 hcitool con → ACL MAC(真机原文;MAC 主来源) ==== */
|
||||
{
|
||||
const char *con =
|
||||
"Connections:\n"
|
||||
"\t< ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER AUTH ENCRYPT \n";
|
||||
char mac[32] = "";
|
||||
check(bt_dev_acl_mac(con, mac, sizeof mac) == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0,
|
||||
"D8a real hcitool con (state 1 AUTH ENCRYPT) -> ACL MAC");
|
||||
}
|
||||
{
|
||||
const char *con =
|
||||
"Connections:\n"
|
||||
"\t< ACL 33:DE:2E:21:AD:30 handle 0 state 5 lm MASTER \n";
|
||||
char mac[32] = "";
|
||||
check(bt_dev_acl_mac(con, mac, sizeof mac) == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0,
|
||||
"D8b half-open ACL (state 5) still gives MAC (kernel is authority)");
|
||||
}
|
||||
{
|
||||
const char *con = "Connections:\n";
|
||||
char mac[32] = "";
|
||||
check(bt_dev_acl_mac(con, mac, sizeof mac) == -1 && mac[0] == 0, "D8c no ACL -> -1");
|
||||
check(bt_dev_acl_mac(NULL, mac, sizeof mac) == -1, "D8d NULL -> -1");
|
||||
}
|
||||
{
|
||||
/* 小写输入归一为大写(bluealsa DEV 参数按大写比较) */
|
||||
const char *con = "\t< ACL aa:bb:cc:dd:ee:ff handle 3 state 1 lm SLAVE \n";
|
||||
char mac[32] = "";
|
||||
check(bt_dev_acl_mac(con, mac, sizeof mac) == 0 && strcmp(mac, "AA:BB:CC:DD:EE:FF") == 0,
|
||||
"D8e lowercase -> uppercase canonical");
|
||||
}
|
||||
|
||||
/* ==== D5 回落退避(防重试风暴:M10 教训) ==== */
|
||||
check(bt_dev_backoff_ms(0) == 0, "D5a fails=0 -> 0ms (immediate)");
|
||||
check(bt_dev_backoff_ms(1) == 2000, "D5b fails=1 -> 2s");
|
||||
|
||||
Reference in new issue
Block a user