fix(audiod): M50 蓝牙输出端到端 — 内联 DEV/CODEC 打开 + 失败必回落 DAC + MAC 解析修正

① ALSA 正确用法(核心):不再写 /usr/data/alsa.conf(HiBy 播放器数据文件、语法非法、
   libasound 从不加载,改它对解析零影响)。改为 snd_pcm_open("bluealsa:DEV=<mac>,
   PROFILE=a2dp,CODEC=<token>"),参数名严格取 /etc/alsa/conf.d/20-bluealsa.conf 的
   pcm.bluealsa @args[DEV PROFILE CODEC VOL SOFTVOL DELAY SRV](实测未知参数会被
   alsa-lib 拒绝);删掉 bt_apply_device_conf/bt_apply_codec_conf 两条改写分支。
② 硬 bug:bt_dev_detect_mac() 旧逻辑取端点 token「最后一个 '/' 之后」——真机
   bluealsa-cli list-pcms 输出是 D-Bus 路径 /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/
   a2dpsrc/source,旧代码会把 "source" 当 MAC(旧单测喂的是人造输入所以一直是绿的)。
   改按 dev_XX_XX_XX_XX_XX_XX 段解析,兼容老式 a2dpsrc/<MAC>。
③ codec:settings bt.codec 经 bt_dev_codec_token() 归一成 bluealsa canonical 名
   (v4.1.1 a2dp-codecs.c 别名表,strcasecmp);auto/未知 → unchanged(不干预协商)。
④ 失败必回落(硬验收):新增 BT_ST_IDLE/OK/FAIL 状态机 —— 无端点/未连接/set_params
   失败/写路径致命错(ENODEV 等)都回落 DAC 继续出声,绝不整机静音;回落前同步武装
   numid=9(hbc3000 未烧+enable 开 PCM 会楔死整机);STATE 增 bt=/btdev=/btcodec=
   三键供面板显示「输出=蓝牙(失败)」,volctl 回落时报 dac。
⑤ 防重试风暴:只在开流/重配边界重试,指数退避 2s→60s(bt_dev_should_try),写循环内
   绝不重试;用户显式切目标才清退避。
⑥ 音量:BT 真走 A2DP 时仍不下发 DAC 反转(回归确认),回落 DAC 时按 DAC 音量控;
   AVRCP 一键置最大从 100 改 127(bluealsa-cli volume 满量程 [0,127],旧值只到 79%)。
⑦ build_audiod.sh:ROOT/REPO 自动推导,ZIG/CC/LIBBT/ASOUND_SRC 可环境变量覆盖(旧
   hiby_os_crack 路径在多数 checkout 里不存在),宿主链接 -l:libasound.so.2 失败退
   -lasound;保留 M43 的 meow_audio time32 cli 目标。

宿主:test_bt_dev 51/51(真机 D-Bus 路径格式/别名表/退避),test_audiod_host 109/0,
0 warning。真机:新二进制 glibc-dynamic 上机正常,DAC 出声回归通过,无端点回落 DAC
出声(played/rms 实证),/usr/data/alsa.conf 全流程零写入;耳机出声一项待耳机上电。

不提交二进制、不改 fw_content.conf 钉值(按塔交付形态政策,由 M51 统一重编)。
This commit is contained in:
AskaEth committed 2026-11-11 19:37:03 +08:00
1 parent ca042aa79c
commit 33bef36d16
6 files changed
+605 -339

No files matched your search

+131 -66
View File
@@ -1,10 +1,17 @@
/* test_bt_dev.c — 蓝牙 DEV 自识别宿主验证(纯逻辑,bt_dev.c)。
/* test_bt_dev.c — 蓝牙端点选择 / PCM 名构造 / 回落退避 宿主验证(纯逻辑,bt_dev.c)。
*
* 覆盖矩阵(对齐任务B 宿主测试要求):
* D1 bt_dev_detect_mac:a2dpsrc/<MAC> 端点 → 提取 MAC(含 a2dp_source、多空行、多设备取第一个、
* 无 a2dpsrc / 空输出 / NULL → -1)。
* D2 bt_dev_rewrite:真实 alsa.conf 文本 → DEV=、device 两处 MAC 换成目标,保留 codec/其余 token;
* 同值 → 1(无需改动);无 pcm.bluealsa / 无 DEV= → -1;带引号风格保留。
* 输入全部取自**真机实测格式**(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
*
* 覆盖矩阵:
* 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 封顶 + 门控(防重试风暴)。
*
* 编译(在 patch_v2/meow_audiod 下):
* cc -Wall -Wextra -o test_bt_dev test_bt_dev.c bt_dev.c
@@ -26,91 +33,149 @@ int main(void)
{
setbuf(stdout, NULL);
/* ---- D1 设备列表 → 选中 a2dpsrc 连接设备 ---- */
/* ==== D1 设备列表 → 选中 a2dpsrc 连接设备(真实 D-Bus 路径格式) ==== */
{
char mac[32] = "";
int rc = bt_dev_detect_mac(
"a2dpsrc/E0:42:6D:79:C5:98\na2dpsnk/E0:42:6D:79:C5:98\n", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "E0:42:6D:79:C5:98") == 0, "D1a a2dpsrc/<MAC> -> extract MAC");
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source\n", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0,
"D1a real D-Bus path -> MAC (dev_ underscores -> colons)");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac(
"profile/aptx\n\na2dp_source/33:DE:2E:21:AD:30\n", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0, "D1b a2dp_source/<MAC> -> extract MAC");
"/org/bluealsa/hci0/dev_E0_42_6D_79_C5_98/hfphf/source\n"
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsnk/sink\n"
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source\n", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0,
"D1b hfphf/a2dpsnk skipped -> first a2dpsrc");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac(
"a2dpsrc/AA:BB:CC:DD:EE:FF 0\na2dpsrc/11:22:33:44:55:66 1\n", mac, sizeof mac);
"/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", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "AA:BB:CC:DD:EE:FF") == 0, "D1c multi-device -> first a2dpsrc");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac("\n", mac, sizeof mac);
check(rc == -1, "D1d empty output -> -1");
int rc = bt_dev_detect_mac("a2dpsrc/E0:42:6D:79:C5:98\n", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "E0:42:6D:79:C5:98") == 0, "D1d legacy a2dpsrc/<MAC> still parsed");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac("a2dpsnk/11:22:33:44:55:66\n", mac, sizeof mac);
check(rc == -1, "D1e only sink (no a2dpsrc) -> -1");
int rc = bt_dev_detect_mac("", mac, sizeof mac);
check(rc == -1, "D1e empty output -> -1");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac(
"/org/bluealsa/hci0/dev_11_22_33_44_55_66/a2dpsnk/sink\n", mac, sizeof mac);
check(rc == -1, "D1f only sink (no a2dpsrc) -> -1");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac(NULL, mac, sizeof mac);
check(rc == -1, "D1f NULL input -> -1");
check(rc == -1, "D1g NULL input -> -1");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac(
"/org/bluealsa/hci0/dev_ZZ_42_6D_79_C5_98/a2dpsrc/source\n", mac, sizeof mac);
check(rc == -1 && mac[0] == 0, "D1h malformed dev_ seg -> -1 (no garbage MAC)");
}
{
char mac[32] = "";
int rc = bt_dev_detect_mac(
"/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source 1\n", mac, sizeof mac);
check(rc == 0 && strcmp(mac, "33:DE:2E:21:AD:30") == 0, "D1i trailing token tolerated");
}
/* ---- D2 DEV 串入 / 保留其它 token ---- */
/* ==== 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");
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 ==== */
{
const char *conf =
"pcm.bluealsa:DEV=E0:42:6D:79:C5:98 {\n"
" type plug\n"
" slave {\n"
" pcm {\n"
" type bluealsa\n"
" device E0:42:6D:79:C5:98\n"
" profile \"a2dp\"\n"
" codec \"LDAC\"\n"
" ldac_eqmid \"LDAC_ABR\"\n"
" }\n"
" }\n"
"}\n";
char *out = NULL; size_t olen = 0;
int rc = bt_dev_rewrite(conf, strlen(conf), "33:DE:2E:21:AD:30", &out, &olen);
check(rc == 0 && out != NULL, "D2a rewrite returns changed");
check(out && strstr(out, ":DEV=33:DE:2E:21:AD:30 {"), "D2b DEV= updated");
check(out && strstr(out, "device 33:DE:2E:21:AD:30\n"), "D2c device updated");
check(out && strstr(out, "codec \"LDAC\"") && strstr(out, "ldac_eqmid \"LDAC_ABR\""),
"D2d codec/other tokens preserved");
/* 旧 MAC 不再出现 */
check(out && !strstr(out, "E0:42:6D:79:C5:98"), "D2e old MAC gone");
free(out);
}
{
const char *conf =
"pcm.bluealsa:DEV=33:DE:2E:21:AD:30 {\n"
" type plug\n slave {\n pcm {\n"
" type bluealsa\n device 33:DE:2E:21:AD:30\n"
" profile \"a2dp\"\n codec \"LDAC\"\n }\n }\n}\n";
char *out = NULL; size_t olen = 0;
int rc = bt_dev_rewrite(conf, strlen(conf), "33:DE:2E:21:AD:30", &out, &olen);
check(rc == 1 && out == NULL, "D2f same MAC -> unchanged (idempotent)");
}
{
const char *conf = "pcm.bluealsa {\n type bluealsa\n device \"E0:42:6D:79:C5:98\"\n}\n";
char *out = NULL; size_t olen = 0;
int rc = bt_dev_rewrite(conf, strlen(conf), "33:DE:2E:21:AD:30", &out, &olen);
check(rc == 0 && out && strstr(out, "device \"33:DE:2E:21:AD:30\""),
"D2g quoted device preserved (no DEV= token)");
free(out);
}
{
char *out = NULL; size_t olen = 0;
int rc = bt_dev_rewrite("pcm.hw {\n type hw\n}\n", 21, "33:DE:2E:21:AD:30", &out, &olen);
check(rc == -1 && out == NULL, "D2h no pcm.bluealsa -> -1");
struct { const char *in; const char *want; const char *name; } t[] = {
{ "LDAC", "LDAC", "D3a LDAC passthrough" },
{ "ldac", "LDAC", "D3b lowercase ldac -> canonical" },
{ "SBC", "SBC", "D3c SBC" },
{ "AAC", "AAC", "D3d AAC" },
{ "MPEG24", "AAC", "D3e MPEG24 alias -> AAC" },
{ "aptX", "aptX", "D3f aptX" },
{ "apt-X", "aptX", "D3g apt-X alias -> aptX" },
{ "aptx-hd", "aptX-HD", "D3h aptx-hd -> aptX-HD" },
{ "MPEG", "MP3", "D3i MPEG alias -> MP3" },
{ "MP3", "MP3", "D3j MP3" },
{ "FastStream", "FastStream", "D3k FastStream" },
{ "FS", "FastStream", "D3l FS alias -> FastStream" },
{ "auto", "unchanged", "D3m auto -> unchanged (do not touch negotiation)" },
{ "", "unchanged", "D3n empty -> unchanged" },
{ "unchanged", "unchanged", "D3o unchanged -> unchanged" },
{ "BOGUS", "unchanged", "D3p unknown -> unchanged (never inject junk)" },
};
for (size_t i = 0; i < sizeof t / sizeof t[0]; i++) {
char tok[16] = "";
const char *r = bt_dev_codec_token(t[i].in, tok, sizeof tok);
check(r == tok && strcmp(tok, t[i].want) == 0, t[i].name);
}
char tok[16] = "";
check(bt_dev_codec_token(NULL, tok, sizeof tok) == tok && !strcmp(tok, "unchanged"),
"D3q NULL setting -> unchanged");
}
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS\n", g_fail);
return g_fail;
/* ==== D4 PCM 名构造(20-bluealsa.conf @args 只认 DEV/PROFILE/CODEC 这套名字) ==== */
{
char n[96] = "";
int rc = bt_dev_pcm_name("33:DE:2E:21:AD:30", "LDAC", n, sizeof n);
check(rc == 0 && strcmp(n, "bluealsa:DEV=33:DE:2E:21:AD:30,PROFILE=a2dp,CODEC=LDAC") == 0,
"D4a name = bluealsa:DEV=<mac>,PROFILE=a2dp,CODEC=<token>");
}
{
char n[96] = "";
int rc = bt_dev_pcm_name("33:DE:2E:21:AD:30", "", n, sizeof n);
check(rc == 0 && strstr(n, "CODEC=unchanged") != NULL, "D4b empty token -> CODEC=unchanged");
}
{
char n[96] = "";
int rc = bt_dev_pcm_name("33:DE:2E:21:AD:30", NULL, n, sizeof n);
check(rc == 0 && strstr(n, "CODEC=unchanged") != NULL, "D4c NULL token -> CODEC=unchanged");
}
{
char n[96] = "";
check(bt_dev_pcm_name("33:DE:2E:21:AD", "LDAC", n, sizeof n) == -1, "D4d short MAC -> -1");
check(bt_dev_pcm_name("GG:DE:2E:21:AD:30", "LDAC", n, sizeof n) == -1, "D4e non-hex MAC -> -1");
check(bt_dev_pcm_name(NULL, "LDAC", n, sizeof n) == -1, "D4f NULL MAC -> -1");
check(bt_dev_pcm_name("33:DE:2E:21:AD:30", "LDAC", n, 16) == -1, "D4g tiny buffer -> -1");
}
/* ==== 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");
check(bt_dev_backoff_ms(2) == 4000, "D5c fails=2 -> 4s");
check(bt_dev_backoff_ms(3) == 8000, "D5d fails=3 -> 8s");
check(bt_dev_backoff_ms(4) == 16000, "D5e fails=4 -> 16s");
check(bt_dev_backoff_ms(5) == 32000, "D5f fails=5 -> 32s");
check(bt_dev_backoff_ms(6) == 60000, "D5g fails=6 -> 60s (capped)");
check(bt_dev_backoff_ms(99) == 60000, "D5h fails=99 -> 60s (capped)");
check(bt_dev_should_try(0, 0) == 1, "D5i never failed -> try now");
check(bt_dev_should_try(1, 1999) == 0, "D5j fail#1 @1999ms -> gated");
check(bt_dev_should_try(1, 2000) == 1, "D5k fail#1 @2000ms -> allowed");
check(bt_dev_should_try(3, 7999) == 0, "D5l fail#3 @7999ms -> gated");
check(bt_dev_should_try(3, 8000) == 1, "D5m fail#3 @8000ms -> allowed");
check(bt_dev_should_try(2, -1) == 1, "D5n clock anomaly (negative) -> allowed (no deadlock)");
printf(g_fail ? "\nRESULT: %d FAIL(S)\n" : "\nRESULT: ALL PASS (%d checks)\n",
g_fail ? g_fail : g_pass);
return g_fail ? 1 : 0;
}