fix(audiod): M50 r3 评审 P2 三条 — fd 卫生 / 端点路径按 MAC 精确匹配 / 多 ACL 逐个试开
评审 r1(p2-3items/merge,reviewed 26c6077)三条:
① P2-1 dev_close() 在「BT 写路径判死 → 回落」这条路上会 close(0) 误关 stdin:
播放线程处理 g_bt_fail_req 时**提前把 g_bt_dev 清 0**,下拍 dev_close() 就落到
close(g_dev_fd) 分支,而 BT 打开成功时 dev_open() 返回 0(g_dev_fd==0)。
- 去掉那行提前清零(让 dev_close 走 BT 分支自己清),并同时置 g_dev_fd = -1 做第二道保险;
- 把关闭决策抽成纯函数 bt_dev_close_kind(bt_dev, fd)(0=NONE/1=BT/2=FD):**fd 0/1/2
一律不当设备 fd 关**,宿主单测 D10 覆盖(含 bt_dev=0+fd=0 这条回归用例);
- 补不变式「设备 fd 恒 >2」:dev_open 的 DAC/WAV open 过 fd_high()(0..2 时 F_DUPFD 升位,
dup 失败则关掉并报打开失败——绝不留 stdio 区间里没人认领的设备 fd);
- dev_close 注释改成诚实版:不再声称「已消除」,写明「BT 分支内自行清理;调用方不得
提前清 g_bt_dev」。
② P2-2 g_bt_pcm 只写一次 → 换耳机 A→B 后 volume/info 打在 A 的端点上:
- bt_refresh_endpoint() 每次先清空再探测,并用新 bt_dev_pick_endpoint() **按 g_bt_mac
精确挑端点**(找不到就留空,宁可跳过 volume/info 也不串台);
- bt_vol_full() 删除「拿 list-pcms 第一条端点凑数」的兜底(同样会串台);
- 单测 D9:两条端点里按 MAC 分别取 A/B、未知 MAC 返回 -1。
③ P2-3 bt_dev_acl_mac() 只取第一条 ACL,多设备(手机/手柄并存,M49 正在测 BT 控制面)
会挑错设备 → 耳机连着却回落 DAC:
- 改名/重写为 bt_dev_acl_macs():收集**全部** ACL MAC(去重、上限由调用方给);
- bt_find_mac() → bt_collect_candidates():①list-pcms 里已有端点的设备优先(真 A2DP)
②其后全部 ACL 设备;候选按可信度排序、去重;
- bt_try_open() 改为对候选**轮流试开**,3 次尝试、300ms 间隔(1 候选=A,A,A 与 r2 语义
相同;2 候选=A,B,A;3 候选=A,B,C),仍受退避门控;**删除裸 "bluealsa" 兜底**
(那会落到「最近连接」的错设备,review §P2-3 明确指出);
- 单测 D8c/D8d:两条 ACL 全取到且保序、三条含重复时去重并受上限裁剪。
顺带清死代码(评审「其余观察 2」):删除 bt_dev_count_src()、bt_dev_endpoint_path()
(生产路径已无调用者,前者早已只剩单测)。
宿主:test_bt_dev 71/71(新增 D8c/D8d/D9/D10 共 10 条),test_audiod_host 109/0,
交叉编译 0 warning(68604B,md5 9f58b2197ed91d24951918d41a78cf29,NEEDED 三条、
interpreter /lib/ld.so.1、GLIBC≤2.17),test_time32_abi.sh --builds 里
`build meow_audio src=yes macro=yes` 保持。真机项(耳机)未动:本轮不连蓝牙、不扫设备。
This commit is contained in:
1 parent
6e7c0d00c0
commit
6e67c74fe6
4 files changed
+222
-168
No files matched your search
@@ -8,14 +8,16 @@
|
||||
* 覆盖矩阵:
|
||||
* D1 bt_dev_detect_mac:真实 D-Bus 路径 → MAC;多端点/多设备取第一个 a2dpsrc;
|
||||
* hfphf/a2dpsnk 不算;简化 `a2dpsrc/<MAC>` 兼容;空/无源端点/NULL/坏 dev_ 段 → -1。
|
||||
* D2 bt_dev_count_src:0/1/2 个源端点。
|
||||
|
||||
* D3 bt_dev_codec_token:settings bt.codec → bluealsa canonical(4.1.1 别名表 strcasecmp);
|
||||
* 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 只在秒级窗口存在)。
|
||||
* D8 bt_dev_acl_macs:真机 `hcitool con` 原文 → **全部** ACL 对端 MAC(多设备一等公民:
|
||||
* 只取第一条会在手机/手柄同时连着时挑错设备,导致有耳机却回落 DAC)。
|
||||
* D9 bt_dev_pick_endpoint:按 MAC 精确挑端点(换耳机 A→B 后不能拿 A 的路径发 volume/info)。
|
||||
* D10 bt_dev_close_kind:关闭决策(BT 分支 / 真 fd / **0-2 一律不动**=护 stdin,P2-1 回归)。
|
||||
*
|
||||
* 编译(在 patch_v2/meow_audiod 下):
|
||||
* cc -Wall -Wextra -o test_bt_dev test_bt_dev.c bt_dev.c
|
||||
@@ -95,24 +97,6 @@ int main(void)
|
||||
check(rc == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0, "D1i trailing token tolerated");
|
||||
}
|
||||
|
||||
/* ==== D2 源端点计数(有无 A2DP 端点的快速判定) ==== */
|
||||
check(bt_dev_count_src("") == 0, "D2a empty -> 0");
|
||||
check(bt_dev_count_src(
|
||||
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsnk/sink\n") == 0,
|
||||
"D2b only sink -> 0");
|
||||
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,
|
||||
"D2d two a2dpsrc -> 2");
|
||||
|
||||
/* ==== D3 settings bt.codec → bluealsa canonical token ==== */
|
||||
{
|
||||
struct { const char *in; const char *want; const char *name; } t[] = {
|
||||
@@ -168,30 +152,6 @@ 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 =
|
||||
@@ -226,36 +186,85 @@ int main(void)
|
||||
check(bt_dev_info_codec(NULL, codec, sizeof codec) == -1, "D7d NULL -> -1");
|
||||
}
|
||||
|
||||
/* ==== D8 hcitool con → ACL MAC(真机原文;MAC 主来源) ==== */
|
||||
/* ==== 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");
|
||||
char m[4][18];
|
||||
check(bt_dev_acl_macs(con, m, 4) == 1 && strcmp(m[0], "33:DE:2E:21:AD:30") == 0,
|
||||
"D8a real hcitool con (state 1 AUTH ENCRYPT) -> 1 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,
|
||||
char m[4][18];
|
||||
check(bt_dev_acl_macs(con, m, 4) == 1 && strcmp(m[0], "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");
|
||||
/* 多设备:第二副耳机 + 手机同时连着 —— 两条都要拿到,且顺序=输出顺序 */
|
||||
const char *con =
|
||||
"Connections:\n"
|
||||
"\t< ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER AUTH ENCRYPT \n"
|
||||
"\t< ACL 5C:FB:7C:11:22:33 handle 5 state 1 lm SLAVE \n";
|
||||
char m[4][18];
|
||||
int n = bt_dev_acl_macs(con, m, 4);
|
||||
check(n == 2 && strcmp(m[0], "33:DE:2E:21:AD:30") == 0 && strcmp(m[1], "5C:FB:7C:11:22:33") == 0,
|
||||
"D8c two ACLs -> both MACs in order (not just the first one)");
|
||||
}
|
||||
{
|
||||
/* 小写输入归一为大写(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");
|
||||
/* 三设备 + 上限裁剪 + 去重(同一设备多 profile 行重复出现) */
|
||||
const char *con =
|
||||
"Connections:\n"
|
||||
"\t< ACL AA:BB:CC:DD:EE:01 handle 1 state 1 lm MASTER \n"
|
||||
"\t< ACL AA:BB:CC:DD:EE:01 handle 2 state 1 lm SLAVE \n"
|
||||
"\t< ACL AA:BB:CC:DD:EE:02 handle 3 state 1 lm MASTER \n"
|
||||
"\t< ACL AA:BB:CC:DD:EE:03 handle 4 state 1 lm MASTER \n";
|
||||
char m[3][18];
|
||||
int n = bt_dev_acl_macs(con, m, 3);
|
||||
check(n == 3 && strcmp(m[0], "AA:BB:CC:DD:EE:01") == 0 &&
|
||||
strcmp(m[1], "AA:BB:CC:DD:EE:02") == 0 && strcmp(m[2], "AA:BB:CC:DD:EE:03") == 0,
|
||||
"D8d dedup + cap at max (3 distinct)");
|
||||
}
|
||||
{
|
||||
char m[4][18];
|
||||
check(bt_dev_acl_macs("Connections:\n", m, 4) == 0, "D8e no ACL -> 0");
|
||||
check(bt_dev_acl_macs(NULL, m, 4) == 0, "D8f NULL -> 0");
|
||||
check(bt_dev_acl_macs("\t< ACL aa:bb:cc:dd:ee:ff handle 3 state 1 lm SLAVE \n", m, 4) == 1 &&
|
||||
strcmp(m[0], "AA:BB:CC:DD:EE:FF") == 0, "D8g lowercase -> uppercase canonical");
|
||||
}
|
||||
|
||||
/* ==== D9 按 MAC 精确挑端点(换耳机 A→B 不串台) ==== */
|
||||
{
|
||||
const char *pcms =
|
||||
"/org/bluealsa/hci0/dev_AA_BB_CC_DD_EE_01/a2dpsrc/sink\n"
|
||||
"/org/bluealsa/hci0/dev_AA_BB_CC_DD_EE_02/a2dpsrc/sink\n";
|
||||
char path[128] = "";
|
||||
check(bt_dev_pick_endpoint(pcms, "AA:BB:CC:DD:EE:02", path, sizeof path) == 0 &&
|
||||
strcmp(path, "/org/bluealsa/hci0/dev_AA_BB_CC_DD_EE_02/a2dpsrc/sink") == 0,
|
||||
"D9a picks B's path when MAC=B (not the first endpoint)");
|
||||
check(bt_dev_pick_endpoint(pcms, "AA:BB:CC:DD:EE:01", path, sizeof path) == 0 &&
|
||||
strcmp(path, "/org/bluealsa/hci0/dev_AA_BB_CC_DD_EE_01/a2dpsrc/sink") == 0,
|
||||
"D9b picks A's path when MAC=A");
|
||||
check(bt_dev_pick_endpoint(pcms, "AA:BB:CC:DD:EE:03", path, sizeof path) == -1 &&
|
||||
path[0] == 0, "D9c unknown MAC -> -1 (never borrow another device's path)");
|
||||
check(bt_dev_pick_endpoint("/org/bluealsa/hci0/dev_AA_BB_CC_DD_EE_01/hfphf/source\n",
|
||||
"AA:BB:CC:DD:EE:01", path, sizeof path) == -1,
|
||||
"D9d non-a2dpsrc endpoint -> -1");
|
||||
check(bt_dev_pick_endpoint(NULL, "AA:BB:CC:DD:EE:01", path, sizeof path) == -1, "D9e NULL -> -1");
|
||||
}
|
||||
|
||||
/* ==== D10 关闭决策(P2-1 回归:0/1/2 永不当设备 fd 关) ==== */
|
||||
check(bt_dev_close_kind(1, 0) == 1, "D10a bt_dev=1 + fd=0 -> close BT only (never fd 0)");
|
||||
check(bt_dev_close_kind(1, 5) == 1, "D10b bt_dev=1 + fd=5 -> close BT only");
|
||||
check(bt_dev_close_kind(0, 0) == 0, "D10c bt_dev=0 + fd=0 -> NONE (stdin guard, P2-1)");
|
||||
check(bt_dev_close_kind(0, 1) == 0, "D10d bt_dev=0 + fd=1 -> NONE (stdout guard)");
|
||||
check(bt_dev_close_kind(0, 2) == 0, "D10e bt_dev=0 + fd=2 -> NONE (stderr guard)");
|
||||
check(bt_dev_close_kind(0, 3) == 2, "D10f bt_dev=0 + fd=3 -> close FD");
|
||||
check(bt_dev_close_kind(0, -1) == 0, "D10g bt_dev=0 + fd=-1 -> NONE");
|
||||
check(bt_dev_close_kind(0, -2) == 0, "D10h bt_dev=0 + fd=-2 (NULL sink) -> NONE");
|
||||
|
||||
/* ==== D5 回落退避(防重试风暴:M10 教训) ==== */
|
||||
check(bt_dev_backoff_ms(0) == 0, "D5a fails=0 -> 0ms (immediate)");
|
||||
|
||||
Reference in new issue
Block a user