真机(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 主路径未能在真机复验
(耳机不可达),详见报告。
70 lines
4.0 KiB
C
70 lines
4.0 KiB
C
/* 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 配置后的秒级窗口内存在)。返回 0=找到,-1=无 ACL/参数非法。 */
|
||
int bt_dev_acl_mac(const char *hcitool_con, char *mac, size_t macsz);
|
||
|
||
/* 解析 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 输出里的 a2dpsrc/a2dp_source 端点数(0=无可用 A2DP 源端点)。
|
||
* 用于「先探测端点、再决定是否打开」——无端点时省掉一次注定失败的 open。 */
|
||
int bt_dev_count_src(const char *listpcms);
|
||
|
||
/* 取 list-pcms 里第一个 a2dpsrc/a2dp_source 端点的完整 D-Bus 路径
|
||
* (/org/bluealsa/hci0/dev_XX_../a2dpsrc/sink)——`bluealsa-cli volume|info` 要的是它,不是 MAC。
|
||
* 返回 0=写好(out NUL 结尾),-1=无端点/参数非法。outsz 建议 ≥96。 */
|
||
int bt_dev_endpoint_path(const char *listpcms, char *out, size_t outsz);
|
||
|
||
/* 从 `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 */
|