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
@@ -10,14 +10,72 @@
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
|
||||
/* 是否 a2dpsrc / a2dp_source 端点行(bluealsa 4.1.1 list-pcms 的端点头) */
|
||||
static int is_src_line(const char *p)
|
||||
/* 是否 a2dpsrc / a2dp_source 端点行(bluealsa 4.1.1 list-pcms 的端点头)。
|
||||
* ⚠️ 必须**行内**判定:直接对整串 strstr 会让「本行」借到「下一行」里的 a2dpsrc
|
||||
* (真机 hfphf/source 行会被误判成 A2DP 源端点——M50 实测踩到)。 */
|
||||
static int line_is_src(const char *ln)
|
||||
{
|
||||
const char *nl = strchr(ln, '\n');
|
||||
size_t len = nl ? (size_t)(nl - ln) : strlen(ln);
|
||||
char line[256];
|
||||
if (len >= sizeof line) len = sizeof line - 1;
|
||||
memcpy(line, ln, len);
|
||||
line[len] = 0;
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
return strstr(p, "a2dpsrc") != NULL || strstr(p, "a2dp_source") != NULL;
|
||||
}
|
||||
|
||||
/* 从 `hcitool con` 输出取第一条 ACL 连接的对端 BD_ADDR。
|
||||
* 内核 ACL 是「设备是否真的连着」的权威(M45/M49 结论);bluealsa 的 list-pcms 只在
|
||||
* profile 配置好后的**秒级窗口**里存在,不能当 MAC 的唯一来源(M50 真机实测)。
|
||||
* 行形如:`\t< ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER AUTH ENCRYPT`
|
||||
* 返回 0=找到(17 字符大写冒号分隔),-1=无 ACL 连接/参数非法。 */
|
||||
int bt_dev_acl_mac(const char *hcitool_con, char *mac, size_t macsz)
|
||||
{
|
||||
if (!hcitool_con || !mac || macsz < 18) return -1;
|
||||
mac[0] = 0;
|
||||
const char *ln = hcitool_con;
|
||||
while (ln && *ln) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
const char *le = nl ? nl : (ln + strlen(ln));
|
||||
size_t linelen = (size_t)(le - ln);
|
||||
char line[256];
|
||||
if (linelen >= sizeof line) linelen = sizeof line - 1;
|
||||
memcpy(line, ln, linelen); line[linelen] = 0;
|
||||
if (strstr(line, "ACL")) {
|
||||
/* 行内找合法的 MAC token(6 组 2 位十六进制,冒号分隔) */
|
||||
for (const char *p = line; *p; p++) {
|
||||
int ok = 1;
|
||||
for (int i = 0; i < 17 && ok; i++) {
|
||||
if ((i % 3) == 2) { if (p[i] != ':') ok = 0; }
|
||||
else {
|
||||
char c = p[i];
|
||||
if (!((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'))) ok = 0;
|
||||
}
|
||||
}
|
||||
/* 必须是完整 token:前一个字符不是十六进制/冒号,后一个不是 */
|
||||
if (ok && (p == line || !(p[-1] == ':' || (p[-1] >= '0' && p[-1] <= '9') ||
|
||||
(p[-1] >= 'A' && p[-1] <= 'F') || (p[-1] >= 'a' && p[-1] <= 'f')))
|
||||
&& !(p[17] == ':' || (p[17] >= '0' && p[17] <= '9') ||
|
||||
(p[17] >= 'A' && p[17] <= 'F') || (p[17] >= 'a' && p[17] <= 'f'))) {
|
||||
for (int i = 0; i < 17; i++) {
|
||||
char c = p[i];
|
||||
mac[i] = (c >= 'a' && c <= 'f') ? (char)(c - 'a' + 'A') : c;
|
||||
}
|
||||
mac[17] = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nl) break;
|
||||
ln = nl + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 从 D-Bus 对象路径取 MAC。bluealsa 4.1.1 的 PCM 路径是
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink
|
||||
* (dev_%.2X_%.2X_%.2X_%.2X_%.2X_%.2X,见 src/ba-transport.c / src/shared/dbus-client.c)。
|
||||
* 返回 0=解析出 17 字符 MAC(大写、冒号分隔)。 */
|
||||
static int parse_dev_seg(const char *line, char *mac, size_t macsz)
|
||||
@@ -74,10 +132,10 @@ static int parse_tail_mac(const char *hit, char *mac, size_t macsz)
|
||||
|
||||
/* 解析 bluealsa-cli list-pcms 输出,取第一个 a2dpsrc/a2dp_source 端点的设备 MAC。
|
||||
* 真实输出是 D-Bus 对象路径(每行一个):
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/sink
|
||||
* MAC 取自 `dev_XX_XX_XX_XX_XX_XX` 段(下划线 → 冒号),另兼容简化形式 `a2dpsrc/AA:BB:...`。
|
||||
* 逐个**有界行**匹配(strstr 必须在行内,避免跨换行误把后一行当本行端点——如行 1
|
||||
* "hfphf/sink" 与行 3 "a2dpsrc/source" 的经典误判)。 */
|
||||
* "hfphf/source" 与行 2 "a2dpsrc/sink" 的经典误判)。 */
|
||||
int bt_dev_detect_mac(const char *listpcms, char *mac, size_t macsz)
|
||||
{
|
||||
if (!listpcms || !mac || macsz < 2) return -1;
|
||||
@@ -92,7 +150,7 @@ int bt_dev_detect_mac(const char *listpcms, char *mac, size_t macsz)
|
||||
memcpy(line, ln, linelen); line[linelen] = 0;
|
||||
const char *p = line;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (is_src_line(p)) {
|
||||
if (line_is_src(line)) {
|
||||
if (parse_dev_seg(p, mac, macsz) == 0) return 0;
|
||||
const char *hit = strstr(p, "a2dpsrc");
|
||||
if (!hit) hit = strstr(p, "a2dp_source");
|
||||
@@ -112,15 +170,65 @@ int bt_dev_count_src(const char *listpcms)
|
||||
const char *ln = listpcms;
|
||||
while (ln && *ln) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
const char *p = ln;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (is_src_line(p)) n++;
|
||||
if (line_is_src(ln)) n++;
|
||||
if (!nl) break;
|
||||
ln = nl + 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/* 取第一个 a2dpsrc/a2dp_source 端点的**完整 D-Bus 路径**(`bluealsa-cli volume|info` 要的就是它,
|
||||
* 不是 MAC)。行内按空白截断(含 \r,防 CRLF 把换行带进命令);越界/无端点 → -1。 */
|
||||
int bt_dev_endpoint_path(const char *listpcms, char *out, size_t outsz)
|
||||
{
|
||||
if (!listpcms || !out || outsz < 2) return -1;
|
||||
out[0] = 0;
|
||||
const char *ln = listpcms;
|
||||
while (ln && *ln) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
const char *p = ln;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (line_is_src(ln)) {
|
||||
size_t n = 0;
|
||||
while (p[n] && p[n] != ' ' && p[n] != '\t' && p[n] != '\n' && p[n] != '\r' && n + 1 < outsz) {
|
||||
out[n] = p[n];
|
||||
n++;
|
||||
}
|
||||
out[n] = 0;
|
||||
return n ? 0 : -1;
|
||||
}
|
||||
if (!nl) break;
|
||||
ln = nl + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 从 `bluealsa-cli info <path>` 输出取实际协商到的 codec("Selected codec: AAC" → "AAC")。
|
||||
* 只认行首 "Selected codec:"("Available codecs: SBC AAC" 那行不能误匹配)。 */
|
||||
int bt_dev_info_codec(const char *info_out, char *out, size_t outsz)
|
||||
{
|
||||
if (!info_out || !out || outsz < 2) return -1;
|
||||
out[0] = 0;
|
||||
const char *ln = info_out;
|
||||
while (ln && *ln) {
|
||||
const char *nl = strchr(ln, '\n');
|
||||
const char *p = ln;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
if (strncmp(p, "Selected codec:", 15) == 0) {
|
||||
p += 15;
|
||||
while (*p == ' ' || *p == '\t') p++;
|
||||
size_t n = 0;
|
||||
while (p[n] && p[n] != '\n' && p[n] != '\r' && n + 1 < outsz) { out[n] = p[n]; n++; }
|
||||
while (n > 0 && (out[n-1] == ' ' || out[n-1] == '\t')) n--;
|
||||
out[n] = 0;
|
||||
return n ? 0 : -1;
|
||||
}
|
||||
if (!nl) break;
|
||||
ln = nl + 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* bluealsa 4.1.1 src/shared/a2dp-codecs.c 的 canonical 名与别名(strcasecmp 匹配)。
|
||||
* 只列本机可达的音频 codec;auto/unchanged/未知 → unchanged(不干预协商)。 */
|
||||
static const struct {
|
||||
|
||||
Reference in new issue
Block a user