Files
MeowOS-For-R3P2/patch_v2/meow_audiod/bt_dev.h
T
AskaEth 6e67c74fe6 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` 保持。真机项(耳机)未动:本轮不连蓝牙、不扫设备。
2026-11-11 19:37:03 +08:00

72 lines
3.9 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* bt_dev.h — 蓝牙 A2DP 端点选择 + PCM 名构造 + 回落退避(纯逻辑,宿主可单测)。
*
* 背景(M50 真机定案):`/usr/data/alsa.conf` **不是** alsa 配置(是 HiBy 播放器的数据文件,
* libasound 从不加载),改它对实际解析零影响。真正生效的是 alsa-lib 自带的
* `/etc/alsa/conf.d/20-bluealsa.conf`:
*
* pcm.bluealsa {
* @args [ DEV PROFILE CODEC VOL SOFTVOL DELAY SRV ] ← 只有这 7 个名字
* ... type plug / slave.pcm { type bluealsa ... }
* }
*
* 其 `@args` 均可从 PCM 名内联传入(`snd_pcm_open("bluealsa:DEV=<mac>,PROFILE=a2dp,
* CODEC=<token>")`),未声明的参数会被 alsa-lib 拒绝(parse_args: Unknown parameter)——
* 所以本模块只生成这 7 个里的 3 个,且**不改任何文件**。
*
* 纯 libc(string/stdlib),无 meow_* / popen / 文件 I/O —— meow_audiod 负责探测与打开。
*/
#ifndef BT_DEV_H
#define BT_DEV_H
#include <stddef.h>
/* 从 `hcitool con` 输出收集**所有** ACL 连接的对端 BD_ADDR(按出现顺序去重;MAC 主来源:
* 内核 ACL 是连接权威,list-pcms 只在 profile 配置后的秒级窗口内存在)。
* 多设备是常态,调用方要**逐个试开**而不是只取第一条(M50 r1 评审 P2-3)。
* macs 为 [max][18];返回条数(0=无 ACL)。 */
int bt_dev_acl_macs(const char *hcitool_con, char (*macs)[18], int max);
/* 解析 bluealsa-cli list-pcms 输出,取第一个 a2dpsrc/a2dp_source 端点的设备 MAC
* (**兜底来源**:list-pcms 可见时用)。
* 真实行为 D-Bus 对象路径(bluealsa 4.1.1):
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink
* 返回 0=找到并写入 mac(17 字符、大写冒号分隔,NUL 结尾),-1=无 a2dpsrc 端点 / 参数非法。
* macsz ≥ 18。 */
int bt_dev_detect_mac(const char *listpcms, char *mac, size_t macsz);
/* 在 list-pcms 里按设备 MAC 精确挑端点路径(换耳机 A→B 后不能拿 A 的路径发 volume/info)。
* 返回 0=找到,-1=该 MAC 无端点。 */
int bt_dev_pick_endpoint(const char *listpcms, const char *mac, char *out, size_t outsz);
/* 关闭决策(纯函数):bt_dev=1 → 关 BT PCM;否则 fd>2 才关;0/1/2 一律不动(护 stdin)。
* 返回 0=NONE / 1=BT / 2=FD。 */
int bt_dev_close_kind(int bt_dev, int fd);
/* 从 `bluealsa-cli info` 输出取实际协商 codec:行首 "Selected codec: AAC" → "AAC"。
* 返回 0=找到,-1=无该行/参数非法。 */
int bt_dev_info_codec(const char *info_out, char *out, size_t outsz);
/* settings.conf `bt.codec` → bluealsa CODEC 参数 token。
* bluealsa 4.1.1 的 a2dp_codecs_codec_id_from_string() 用 strcasecmp 匹配 canonical 名与别名,
* 故大小写不敏感;本函数把它归一成 canonical 名(日志/STATE 用),未知值/auto/unchanged →
* "unchanged"(不改协商结果,等价 20-bluealsa.conf 的 defaults.bluealsa.codec)。
* 返回 out(调用方给的 buf)。outsz 建议 ≥16。 */
const char *bt_dev_codec_token(const char *setting, char *out, size_t outsz);
/* 组 snd_pcm_open 的 PCM 名:
* bluealsa:DEV=<mac>,PROFILE=a2dp,CODEC=<token>
* mac 非 6 组十六进制 → -1(不拼非法名字);token 为空 → 用 "unchanged"。
* 返回 0=写好(out NUL 结尾)。outsz 建议 ≥96。 */
int bt_dev_pcm_name(const char *mac, const char *codec_token, char *out, size_t outsz);
/* 回落退避:连续失败 fails 次后的重试间隔 ms(2s→4s→8s→16s→32s→60s 封顶;fails=0 → 0)。
* 防重试风暴(M10 教训):失败越多等越久,绝不每次 OPEN 都硬打一次 BT。 */
unsigned bt_dev_backoff_ms(unsigned fails);
/* 纯决策:此刻是否允许再尝试一次 BT。fails=0 → 允许;否则要求距上次失败 ≥ 退避间隔。
* since_ms<0(时钟异常)按「允许」处理,避免永久卡死。 */
int bt_dev_should_try(unsigned fails, long long since_ms);
#endif /* BT_DEV_H */