Files
MeowOS-For-R3P2/patch_v2/meow_audiod/bt_dev.c
T
AskaEth 33bef36d16 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 统一重编)。
2026-11-11 19:37:03 +08:00

204 lines
7.3 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.c — 蓝牙 A2DP 端点选择 + PCM 名构造 + 回落退避(纯逻辑实现,见 bt_dev.h 契约)。
*
* 纯 libc(string / stdlib),无任何 meow_* 调用、无 popen、无文件 I/O —— meow_audiod 负责
* 执行 `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。
* 真实输出是 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
* "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;
mac[0] = 0;
const char *ln = listpcms;
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;
const char *p = line;
while (*p == ' ' || *p == '\t') p++;
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;
}
int bt_dev_count_src(const char *listpcms)
{
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;
ln = nl + 1;
}
return n;
}
/* 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;
}