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:
1 parent
ca042aa79c
commit
33bef36d16
6 files changed
+605
-339
No files matched your search
+165
-100
@@ -1,18 +1,83 @@
|
||||
/* bt_dev.c — 蓝牙 A2DP 设备自识别(纯逻辑实现,见 bt_dev.h 契约)。
|
||||
/* bt_dev.c — 蓝牙 A2DP 端点选择 + PCM 名构造 + 回落退避(纯逻辑实现,见 bt_dev.h 契约)。
|
||||
*
|
||||
* 纯 libc(string / stdlib),无任何 meow_* 调用、无 popen、无文件 I/O —— meow_audiod 负责
|
||||
* 读取 /usr/data/alsa.conf、执行 `bluealsa-cli list-pcms` 及写回。宿主单测零链路重。
|
||||
* 执行 `bluealsa-cli list-pcms`、调用 libasound 及维护运行时状态。
|
||||
*/
|
||||
#include "bt_dev.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <strings.h>
|
||||
|
||||
/* 是否 a2dpsrc / a2dp_source 端点行(bluealsa 4.1.1 list-pcms 的端点头) */
|
||||
static int is_src_line(const char *p)
|
||||
{
|
||||
return strstr(p, "a2dpsrc") != NULL || strstr(p, "a2dp_source") != NULL;
|
||||
}
|
||||
|
||||
/* 从 D-Bus 对象路径取 MAC。bluealsa 4.1.1 的 PCM 路径是
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source
|
||||
* (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)
|
||||
{
|
||||
if (!line || !mac || macsz < 18) return -1;
|
||||
const char *d = strstr(line, "dev_");
|
||||
if (!d) return -1;
|
||||
const char *p = d + 4;
|
||||
char out[18];
|
||||
size_t o = 0;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
unsigned v = 0;
|
||||
int digits = 0;
|
||||
for (; digits < 2; digits++) {
|
||||
char c = *p;
|
||||
int h = -1;
|
||||
if (c >= '0' && c <= '9') h = c - '0';
|
||||
else if (c >= 'A' && c <= 'F') h = c - 'A' + 10;
|
||||
else if (c >= 'a' && c <= 'f') h = c - 'a' + 10;
|
||||
if (h < 0) break;
|
||||
v = v * 16u + (unsigned)h;
|
||||
p++;
|
||||
}
|
||||
if (digits == 0) return -1;
|
||||
o += (size_t)snprintf(out + o, sizeof out - o, (i == 0) ? "%02X" : ":%02X", v);
|
||||
if (i < 5) {
|
||||
if (*p != '_') return -1;
|
||||
p++;
|
||||
}
|
||||
}
|
||||
if (o != 17) return -1;
|
||||
memcpy(mac, out, 18);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 兜底:端点 token 的最后一段直接是 MAC(老式/简化输出 `a2dpsrc/AA:BB:...`)。 */
|
||||
static int parse_tail_mac(const char *hit, char *mac, size_t macsz)
|
||||
{
|
||||
const char *te = hit;
|
||||
while (*te && *te != ' ' && *te != '\t' && *te != '\n') te++;
|
||||
const char *slash = NULL;
|
||||
for (const char *q = hit; q < te; q++) if (*q == '/') slash = q;
|
||||
if (!slash || (slash + 1) >= te) return -1;
|
||||
const char *start = slash + 1;
|
||||
size_t len = (size_t)(te - start);
|
||||
if (len != 17 || len >= macsz) return -1;
|
||||
int colons = 0;
|
||||
for (size_t i = 0; i < len; i++) if (start[i] == ':') colons++;
|
||||
if (colons != 5) return -1;
|
||||
memcpy(mac, start, len);
|
||||
mac[len] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 解析 bluealsa-cli list-pcms 输出,取第一个 a2dpsrc/a2dp_source 端点的设备 MAC。
|
||||
* 端点形如 `a2dpsrc/AA:BB:CC:DD:EE:FF`(MAC 在端点 token 内最后一个 '/' 之后到空白前)。
|
||||
* 真实输出是 D-Bus 对象路径(每行一个):
|
||||
* /org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc/source
|
||||
* MAC 取自 `dev_XX_XX_XX_XX_XX_XX` 段(下划线 → 冒号),另兼容简化形式 `a2dpsrc/AA:BB:...`。
|
||||
* 逐个**有界行**匹配(strstr 必须在行内,避免跨换行误把后一行当本行端点——如行 1
|
||||
* "profile/aptx" 与行 3 "a2dp_source/..." 的经典误判)。 */
|
||||
* "hfphf/sink" 与行 3 "a2dpsrc/source" 的经典误判)。 */
|
||||
int bt_dev_detect_mac(const char *listpcms, char *mac, size_t macsz)
|
||||
{
|
||||
if (!listpcms || !mac || macsz < 2) return -1;
|
||||
@@ -27,112 +92,112 @@ 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++;
|
||||
const char *hit = strstr(p, "a2dpsrc");
|
||||
if (!hit) hit = strstr(p, "a2dp_source");
|
||||
if (hit) {
|
||||
const char *te = hit;
|
||||
while (*te && *te != ' ' && *te != '\t' && *te != '\n') te++;
|
||||
const char *slash = NULL;
|
||||
for (const char *q = hit; q < te; q++) if (*q == '/') slash = q;
|
||||
if (slash && (slash + 1) < te) {
|
||||
const char *start = slash + 1;
|
||||
size_t len = (size_t)(te - start);
|
||||
if (len > 0 && len < macsz) {
|
||||
memcpy(mac, start, len);
|
||||
mac[len] = 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (is_src_line(p)) {
|
||||
if (parse_dev_seg(p, mac, macsz) == 0) return 0;
|
||||
const char *hit = strstr(p, "a2dpsrc");
|
||||
if (!hit) hit = strstr(p, "a2dp_source");
|
||||
if (hit && parse_tail_mac(hit, mac, macsz) == 0) return 0;
|
||||
}
|
||||
if (!nl) break;
|
||||
ln = nl + 1;
|
||||
}
|
||||
mac[0] = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 在单行内把 anchor("DEV=" / "device")后的 MAC 值替换为 mac,保留引号风格。
|
||||
* 行未含 anchor → 返回 1(out=原样);含 anchor → 写回新行并返回 0。 */
|
||||
static int line_replace_mac(char *out, size_t outsz, const char *line,
|
||||
const char *anchor, const char *mac)
|
||||
int bt_dev_count_src(const char *listpcms)
|
||||
{
|
||||
const char *at = strstr(line, anchor);
|
||||
if (!at) { snprintf(out, outsz, "%s", line); return 1; }
|
||||
const char *vp = at + strlen(anchor);
|
||||
while (*vp == ' ' || *vp == '\t') vp++;
|
||||
if (*vp == '"') vp++; /* 引号风格:值可带双引号 */
|
||||
const char *ve = vp;
|
||||
while (*ve && *ve != ' ' && *ve != '\t' && *ve != '\n' &&
|
||||
*ve != '"' && *ve != '{' && *ve != '}') ve++;
|
||||
size_t prefix = (size_t)(vp - line); /* 前段(含开引号,若带) */
|
||||
size_t tail = (size_t)(ve - line); /* 值终点(不含闭引号,若带) */
|
||||
/* 前段 + mac + 尾段(尾段保留闭引号与其后内容) */
|
||||
snprintf(out, outsz, "%.*s%s%s", (int)prefix, line, mac, line + tail);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 幂等改写 alsa.conf:只把 `pcm.bluealsa` 块内 `DEV=` 与 `device` 后的 MAC 换成 mac。
|
||||
* 逐行处理;无 pcm.bluealsa / 无 DEV= / device token → -1;两端已一致 → 1;改了 → 0。 */
|
||||
int bt_dev_rewrite(const char *in, size_t in_len, const char *mac,
|
||||
char **out, size_t *out_len)
|
||||
{
|
||||
if (!in || !mac || !mac[0] || !out || !out_len || in_len == 0) return -1;
|
||||
if (!strstr(in, "pcm.bluealsa") ||
|
||||
(!strstr(in, "DEV=") && !strstr(in, "device"))) return -1;
|
||||
|
||||
size_t cap = in_len + 128;
|
||||
char *res = malloc(cap ? cap : 1);
|
||||
if (!res) return -1;
|
||||
size_t o = 0, changed = 0;
|
||||
const char *p = in, *end = in + in_len;
|
||||
|
||||
while (p < end) {
|
||||
const char *nl = memchr(p, '\n', (size_t)(end - p));
|
||||
const char *le = nl ? nl : end;
|
||||
size_t linelen = (size_t)(le - p);
|
||||
char line[512], newline[512];
|
||||
if (linelen >= sizeof line) linelen = sizeof line - 1;
|
||||
memcpy(line, p, linelen); line[linelen] = 0;
|
||||
|
||||
const char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++;
|
||||
int anchored = 0;
|
||||
if (strstr(k, "DEV=")) {
|
||||
line_replace_mac(newline, sizeof newline, line, "DEV=", mac);
|
||||
anchored = 1;
|
||||
} else if (strncmp(k, "device", 6) == 0 &&
|
||||
(k[6] == ' ' || k[6] == '\t' || k[6] == 0)) {
|
||||
line_replace_mac(newline, sizeof newline, line, "device", mac);
|
||||
anchored = 1;
|
||||
}
|
||||
if (anchored) {
|
||||
if (strcmp(line, newline) != 0) changed = 1;
|
||||
size_t n = strlen(newline);
|
||||
if (o + n + 2 > cap) {
|
||||
cap = o + n + 256;
|
||||
char *t = realloc(res, cap);
|
||||
if (!t) { free(res); return -1; }
|
||||
res = t;
|
||||
}
|
||||
memcpy(res + o, newline, n); o += n;
|
||||
if (nl) res[o++] = '\n';
|
||||
} else {
|
||||
size_t n = linelen;
|
||||
if (o + n + 2 > cap) {
|
||||
cap = o + n + 256;
|
||||
char *t = realloc(res, cap);
|
||||
if (!t) { free(res); return -1; }
|
||||
res = t;
|
||||
}
|
||||
memcpy(res + o, p, n); o += n;
|
||||
if (nl) res[o++] = '\n';
|
||||
}
|
||||
if (!listpcms) return 0;
|
||||
int n = 0;
|
||||
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 (!nl) break;
|
||||
p = nl + 1;
|
||||
ln = nl + 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
if (!changed) { free(res); return 1; }
|
||||
res[o] = 0;
|
||||
*out = res;
|
||||
*out_len = o;
|
||||
/* bluealsa 4.1.1 src/shared/a2dp-codecs.c 的 canonical 名与别名(strcasecmp 匹配)。
|
||||
* 只列本机可达的音频 codec;auto/unchanged/未知 → unchanged(不干预协商)。 */
|
||||
static const struct {
|
||||
const char *canonical;
|
||||
const char *aliases[4];
|
||||
} codecs[] = {
|
||||
{ "SBC", { "SBC", NULL } },
|
||||
{ "MP3", { "MP3", "MPEG12", "MPEG", NULL } },
|
||||
{ "AAC", { "AAC", "MPEG24", NULL } },
|
||||
{ "ATRAC", { "ATRAC", NULL } },
|
||||
{ "aptX", { "aptX", "apt-X", NULL } },
|
||||
{ "aptX-AD", { "aptX-AD", "apt-X-AD", NULL } },
|
||||
{ "aptX-HD", { "aptX-HD", "apt-X-HD", NULL } },
|
||||
{ "aptX-LL", { "aptX-LL", "apt-X-LL", NULL } },
|
||||
{ "aptX-TWS", { "aptX-TWS", "apt-X-TWS", NULL } },
|
||||
{ "FastStream", { "FastStream", "FS", NULL } },
|
||||
{ "LC3plus", { "LC3plus", NULL } },
|
||||
{ "LDAC", { "LDAC", NULL } },
|
||||
{ "LHDC", { "LHDC", NULL } },
|
||||
};
|
||||
|
||||
const char *bt_dev_codec_token(const char *setting, char *out, size_t outsz)
|
||||
{
|
||||
const char *auto_tok = "unchanged";
|
||||
if (!out || outsz < 2) return auto_tok;
|
||||
if (!setting || !setting[0]) { snprintf(out, outsz, "%s", auto_tok); return out; }
|
||||
if (!strcasecmp(setting, "auto") || !strcasecmp(setting, "unchanged")) {
|
||||
snprintf(out, outsz, "%s", auto_tok);
|
||||
return out;
|
||||
}
|
||||
for (size_t i = 0; i < sizeof codecs / sizeof codecs[0]; i++)
|
||||
for (size_t n = 0; n < 4 && codecs[i].aliases[n]; n++)
|
||||
if (!strcasecmp(codecs[i].aliases[n], setting)) {
|
||||
snprintf(out, outsz, "%s", codecs[i].canonical);
|
||||
return out;
|
||||
}
|
||||
snprintf(out, outsz, "%s", auto_tok); /* 未知值:不干预协商(不把垃圾塞进 PCM 名) */
|
||||
return out;
|
||||
}
|
||||
|
||||
/* MAC 合法性:6 组两位十六进制,'/' 或 ':' 分隔都不接受(只认冒号,bluealsa 约定)。 */
|
||||
static int mac_valid(const char *mac)
|
||||
{
|
||||
if (!mac || strlen(mac) != 17) return 0;
|
||||
for (int i = 0; i < 17; i++) {
|
||||
if ((i % 3) == 2) { if (mac[i] != ':') return 0; }
|
||||
else {
|
||||
char c = mac[i];
|
||||
int hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
|
||||
if (!hex) return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int bt_dev_pcm_name(const char *mac, const char *codec_token, char *out, size_t outsz)
|
||||
{
|
||||
if (!out || outsz < 32 || !mac_valid(mac)) return -1;
|
||||
const char *tok = (codec_token && codec_token[0]) ? codec_token : "unchanged";
|
||||
int n = snprintf(out, outsz, "bluealsa:DEV=%s,PROFILE=a2dp,CODEC=%s", mac, tok);
|
||||
if (n < 0 || (size_t)n >= outsz) { out[0] = 0; return -1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned bt_dev_backoff_ms(unsigned fails)
|
||||
{
|
||||
if (fails == 0) return 0;
|
||||
unsigned shift = fails - 1;
|
||||
if (shift > 5) shift = 5;
|
||||
unsigned long d = 2000UL << shift; /* 2s,4s,8s,16s,32s,64s */
|
||||
if (d > 60000UL) d = 60000UL;
|
||||
return (unsigned)d;
|
||||
}
|
||||
|
||||
int bt_dev_should_try(unsigned fails, long long since_ms)
|
||||
{
|
||||
if (fails == 0) return 1;
|
||||
if (since_ms < 0) return 1;
|
||||
return since_ms >= (long long)bt_dev_backoff_ms(fails) ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user