fix(bt): 蓝牙P2 mipsel libasound链接 — 改glibc-dynamic(设备实为glibc2.22硬浮点,pcm.bluealsa=独立glibc插件需dlopen,musl-static死路)直链设备libasound.so.2,BT层脱离dlopen(内联9个libasound原型);qemu装载动态库验证+宿主67pass/0warning;audiod改glibc-dynamic(57KB)M6.30后需重验DAC出声

This commit is contained in:
AskaEth committed 2026-09-03 21:18:19 +08:00
1 parent 71b2dafecc
commit 4ecddb5ea3
2 files changed
+74 -76

No files matched your search

+38 -13
View File
@@ -1,29 +1,41 @@
#!/bin/bash #!/bin/bash
# build_audiod.sh — meow_audiod 交叉编译(mipsel 静态)+ 宿主多目标可选。 # build_audiod.sh — meow_audiod 交叉编译(mipsel glibc-dynamic)+ 宿主多目标可选。
# #
# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。audiod 复用 meow_ipc.h(共享协议头, # 目标:R3 Pro II / MeowOS(MIPS32 o32,真实 ABI)。audiod 复用 meow_ipc.h(共享协议头,
# -I../meow_launcherd)。audiod 为 svcd 托管的独立守护;meow_audio 为用户侧 CLI。 # -I../meow_launcherd)。audiod 为 svcd 托管的独立守护;meow_audio 为用户侧 CLI。
# #
# 【P2 蓝牙链接定案 2026-09-03】设备为 glibc-2.22(ld-2.22.so / libc-2.22.so,非 musl),且
# libasound 会在运行时 dlopen 单独的 bluealsa pcm 插件(/usr/lib/alsa-lib/libasound_module_pcm_bluealsa.so)
# ——故 audiod 必须以**动态 glibc** 形态链接设备现有 libasound.so.2:
# - 用 zig --target=mipsel-linux-gnueabihf.2.22(硬浮点 o32,glibc-2.22 版本引用,设备 libs 齐);
# libasound 原型在 meow_audiod.c 内联声明(不依赖 asoundlib.h)。
# - 删 -static:musl-static 无 dlopen(实测 "Dynamic loading not supported",无法加载 bluealsa 插件);
# musl-dynamic 需设备不存在的 musl ld.so。libasound 链接库来自 libbt/(设备 libasound.so.2 副本)。
# - meow_audio CLI 无 libasound 依赖,保持 musl-static(zig-mipsel-cc)。
#
# 用法: # 用法:
# ./build_audiod.sh # 交叉编译 → meow_audiod(mipsel 静态) # ./build_audiod.sh # 交叉编译 → meow_audiod(mipsel glibc-dynamic,BT 直链 libasound)
# ./build_audiod.sh cli # 交叉编译 meow_audio CLI → meow_audio(mipsel 静态) # ./build_audiod.sh cli # 交叉编译 meow_audio CLI → meow_audio(mipsel musl-static)
# ./build_audiod.sh host # 宿主编译 meow_audiod_host + meow_audio_host(集成测试) # ./build_audiod.sh host # 宿主编译 *_host 并跑 test_audiod_host 回归(FILE sink)
set -e set -e
cd "$(dirname "$0")" cd "$(dirname "$0")"
ROOT="$HOME/Project/R3ProII" ROOT="$HOME/Project/R3ProII"
CC="$ROOT/.tools/zig-mipsel-cc" CC="$ROOT/.tools/zig-mipsel-cc"
ZIG="$ROOT/.tools/zig-env/bin/zig"
INC="-I$ROOT/patch_v2/meow_launcherd" INC="-I$ROOT/patch_v2/meow_launcherd"
if [ "$1" = "host" ]; then if [ "$1" = "host" ]; then
HOST_CC="${CC_HOST:-cc}" HOST_CC="${CC_HOST:-cc}"
# -ldl:BT 输出目标用 dlopen 运行时解析 libasound(pcm.bluealsa 是 alsa-lib 插件,非内核节点; # BT 目标直链宿主 libasound.so.2(无 asoundlib.h,原型内联于 meow_audiod.c;-l: 精确到版本,
# 主机/交叉工具链无 asoundlib.h/libasound,故不静态链接)。mipsel 静态 musl 不支持 dlopen,见 # 不依赖 unversioned dev 链接)。删 -ldl:BT 不再用 dlopen。
# meow_audiod.c bt 段注释(真机 mipsel 需主会话提供 musl-libasound 或改动态,本任务以源码+宿主为准)。 "$HOST_CC" $INC -Os -Wall -Wextra meow_audiod.c -o meow_audiod_host -lpthread -l:libasound.so.2
"$HOST_CC" $INC -Os -Wall -Wextra meow_audiod.c -o meow_audiod_host -lpthread -ldl
"$HOST_CC" $INC -Os -Wall -Wextra meow_audio.c -o meow_audio_host -lm "$HOST_CC" $INC -Os -Wall -Wextra meow_audio.c -o meow_audio_host -lm
"$HOST_CC" $INC -Os -Wall -Wextra test_audiod_host.c -o test_audiod_host -lm
echo "== host builds ==" echo "== host builds =="
ls -la meow_audiod_host meow_audio_host ls -la meow_audiod_host meow_audio_host test_audiod_host
file meow_audiod_host meow_audio_host file meow_audiod_host meow_audio_host test_audiod_host
echo "== host regression (test_audiod_host) =="
./test_audiod_host || { echo "!! host regression FAILED"; exit 1; }
exit 0 exit 0
fi fi
@@ -36,8 +48,21 @@ if [ "$1" = "cli" ]; then
exit 0 exit 0
fi fi
"$CC" $INC -Os -Wall -Wextra -static meow_audiod.c -o meow_audiod -lpthread # meow_audiod:mipsel glibc-dynamic,BT 直链 libasound(-Llibbt -lasound)。-lpthread 供播放线程。
echo "== meow_audiod (mipsel static) ==" # 链接源 libbt/libasound.so.2.0.0(设备固件 libasound.so.2 副本);缺失时从固件镜像自拷贝(idempotent)。
LIBBT="libbt"
ASOUND_SRC="$ROOT/hiby_os_crack/r3proii/squashfs-root-example/usr/lib/libasound.so.2.0.0"
mkdir -p "$LIBBT"
if [ ! -f "$LIBBT/libasound.so.2.0.0" ]; then cp "$ASOUND_SRC" "$LIBBT/libasound.so.2.0.0"; fi
ln -sf libasound.so.2.0.0 "$LIBBT/libasound.so"
ln -sf libasound.so.2.0.0 "$LIBBT/libasound.so.2"
"$ZIG" cc --target=mipsel-linux-gnueabihf.2.22 $INC -Os -Wall -Wextra meow_audiod.c -o meow_audiod \
-lpthread -Llibbt -lasound
echo "== meow_audiod (mipsel glibc-dynamic, BT links libasound.so.2) =="
ls -la meow_audiod ls -la meow_audiod
md5sum meow_audiod md5sum meow_audiod
file meow_audiod file meow_audiod
echo "-- dynamic NEEDED --"
readelf -d meow_audiod 2>/dev/null | grep -iE "needed" || true
echo "-- glibc version refs (max must be <= 2.22) --"
readelf --version-info meow_audiod 2>/dev/null | grep -oE "GLIBC_2\.[0-9]+" | sort -uV | tail -3 || true
+36 -63
View File
@@ -57,7 +57,6 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <dlfcn.h>
#include "meow_ipc.h" #include "meow_ipc.h"
#ifdef __linux__ #ifdef __linux__
@@ -78,7 +77,6 @@
/* ---- 蓝牙 A2DP source(P2 输出目标 OUT_BT) ---- */ /* ---- 蓝牙 A2DP source(P2 输出目标 OUT_BT) ---- */
#define BT_PCM_DEFAULT "pcm.bluealsa" /* alsa.conf `pcm.bluealsa`(plug→bluealsa);aplay -D bluealsa 等价 */ #define BT_PCM_DEFAULT "pcm.bluealsa" /* alsa.conf `pcm.bluealsa`(plug→bluealsa);aplay -D bluealsa 等价 */
#define BT_PCM_FALLBACK "bluealsa" /* 部分配置只定义别名节点,防打开失败再试一次 */ #define BT_PCM_FALLBACK "bluealsa" /* 部分配置只定义别名节点,防打开失败再试一次 */
#define BT_SO "libasound.so.2" /* 运行时 dlopen(不静态链接:设备为 musl 静态勿 dlopen,见 main 注释) */
#define BT_CODEC_AUTO "auto" #define BT_CODEC_AUTO "auto"
#define BT_VOL_FULL_PCM "/usr/bin/bluealsa-cli" /* 一键设 AVRCP 100%(best-effort,不存在则跳过) */ #define BT_VOL_FULL_PCM "/usr/bin/bluealsa-cli" /* 一键设 AVRCP 100%(best-effort,不存在则跳过) */
@@ -187,12 +185,10 @@ static uint32_t g_dev_rate = 0, g_dev_ch = 0, g_dev_fmt = 0;
static int g_reconfig_pending = 0; static int g_reconfig_pending = 0;
/* ---- 蓝牙 A2DP source 运行时状态(g_mu 保护;仅 g_target==OUT_BT && g_sink==SINK_ALSA 时用) ---- /* ---- 蓝牙 A2DP source 运行时状态(g_mu 保护;仅 g_target==OUT_BT && g_sink==SINK_ALSA 时用) ----
* 直接写 libasound 插件 PCM(pcm.bluealsa). 不静态链接 libasound:主机/工具链均无 asoundlib, * 直接调用 libasound 插件 PCM(pcm.bluealsa)。audiod 以动态 glibc 链接设备 libasound.so.2
* 用 dlopen/dlsym 运行时解析(设备 libasound.so.2 在 /usr/lib;见 dl 注释)。 */ * (见 bt_* 段注释),此处仅存 snd_pcm_t 不透明句柄。 */
static void *g_bt_hdl = NULL; /* snd_pcm_t *(libasound 不透明句柄) */ static void *g_bt_hdl = NULL; /* snd_pcm_t *(libasound 不透明句柄) */
static int g_bt_open = 0; /* BT PCM 已打开 */ static int g_bt_open = 0; /* BT PCM 已打开 */
static void *g_bt_so = NULL; /* dlopen 句柄 */
static int g_bt_loaded = 0; /* dlopen 尝试过(失败不再重试风暴) */
static int g_bt_vol_full_done = 0; /* 已对 AVRCP 置 100%(幂等,一次即可) */ static int g_bt_vol_full_done = 0; /* 已对 AVRCP 置 100%(幂等,一次即可) */
/* ---- 数字域电平表(DAC 实时监控;CS43198 无模拟电平回读,播放线程看 PCM 样本) ---- /* ---- 数字域电平表(DAC 实时监控;CS43198 无模拟电平回读,播放线程看 PCM 样本) ----
@@ -824,61 +820,39 @@ static int settings_get_volume(void)
/* ============================================================================ /* ============================================================================
* 蓝牙 A2DP source(输出目标 OUT_BT,PLAN §三/§五) * 蓝牙 A2DP source(输出目标 OUT_BT,PLAN §三/§五)
* ---------------------------------------------------------------------------- * ----------------------------------------------------------------------------
* audiod 用 libasound **运行时 dlopen** 直连 `pcm.bluealsa`(plug→bluealsa,播放向,A2DP), * audiod **直接调用 libasound** 连 `pcm.bluealsa`(plug→bluealsa,播放向,A2DP),
* 把播放器解码输出透传过去。BT 是**独立目标**: * 把播放器解码输出透传过去。BT 是**独立目标**:
* - 不开 hw:0,0(不 period=1024×4、不 FPGA numid=9 武装、不 DAC 音量反转); * - 不开 hw:0,0(不 period=1024×4、不 FPGA numid=9 武装、不 DAC 音量反转);
* - 保留音频流透传 + 格式归一(grant_fmt=S16,plug 层转 codec 协商率); * - 保留音频流透传 + 格式归一(grant_fmt=S16,plug 层转 codec 协商率);
* - 音量走 AVRCP/耳机侧(音量隔离:绝不下发 DAC numid=1/2 反转,见 vol_apply)。 * - 音量走 AVRCP/耳机侧(音量隔离:绝不下发 DAC numid=1/2 反转,见 vol_apply)。
* dlopen 说明:主机/交叉工具链均无 asoundlib.h/libasound,静态链接不可行;且设备 musl-静态 * 链接说明(2026-09-03 真机定案):`pcm.bluealsa` 的 bluealsa 是**独立 pcm 插件**,libasound
* 二进制本身不支持 dlopen("Dynamic loading not supported")——因此 mipsel 真机若要生效, * 在运行时以 dlopen 加载 /usr/lib/alsa-lib/libasound_module_pcm_bluealsa.so(glibc,依赖
* 需主会话提供 musl-mipsel libasound 并用 `-lasound` 直接链接(或改动态 musl-PIE)。本源码以 * libdbus-1.so.3)——故 audiod 必须以**动态 glibc** 形态链接设备现有 libasound.so.2
* dlopen 路径编译到宿主(0 warning),真机 mipsel 链接此点列为本任务风险清单。禁止旁路:不跑 * (build_audiod.sh:zig --target=mipsel-linux-gnueabihf.2.22,删 -static)。musl-static 不可行
* bluealsa-aplay/bluealsa-cli open(sink/D-Bus 或新建 daemon),audiod 内换目标 PCM 是唯一最简。 * (实测 "Dynamic loading not supported",无 dlopen 无法加载插件);musl-dynamic 亦不可行(设备
* 无 musl ld.so)。libasound 原型在此内联声明(避免依赖 asoundlib.h;工具链不提供)。
* 禁止旁路:不跑 bluealsa-aplay/bluealsa-cli open(D-Bus/新建 daemon),audiod 内换目标 PCM 唯一最简。
* ========================================================================== */ * ========================================================================== */
#define SND_PCM_STREAM_PLAYBACK 0 #define SND_PCM_STREAM_PLAYBACK 0
#define SND_PCM_ACCESS_RW_INTERLEAVED 3 #define SND_PCM_ACCESS_RW_INTERLEAVED 3
#define SND_PCM_FORMAT_S16_LE 2 #define SND_PCM_FORMAT_S16_LE 2
#define SND_PCM_NONBLOCK 1 #define SND_PCM_NONBLOCK 1
static int (*g_fn_open)(void **, const char *, int, int); /* libasound 相关函数原型(内联声明,避免依赖 asoundlib.h;snd_pcm_t 不透明 → void*) */
static int (*g_fn_set_params)(void *, int, int, unsigned, unsigned, int, unsigned); int snd_pcm_open(void **pcm, const char *name, int stream, int mode);
static int (*g_fn_nonblock)(void *, int); int snd_pcm_set_params(void *pcm, int format, int access, unsigned channels,
static long (*g_fn_writei)(void *, const void *, unsigned long); unsigned rate, int soft_resample, unsigned latency_us);
static int (*g_fn_wait)(void *, int); int snd_pcm_nonblock(void *pcm, int nonblock);
static int (*g_fn_recover)(void *, int, int); long snd_pcm_writei(void *pcm, const void *buffer, unsigned long size);
static int (*g_fn_drain)(void *); int snd_pcm_wait(void *pcm, int timeout);
static int (*g_fn_close)(void *); int snd_pcm_recover(void *pcm, int err, int silent);
static const char *(*g_fn_strerror)(int); int snd_pcm_drain(void *pcm);
/* dlsym→函数指针:用 memcpy 转(POSIX 可移植惯例;直接 `(void*)&fn` 或 union 会触发 int snd_pcm_close(void *pcm);
* -Wstrict-aliasing / 函数指针类型告警,memcpy 无此问题)。 */ const char *snd_strerror(int errnum);
#define BT_LOAD_SYM(field, name) do { \
void *p_ = dlsym(g_bt_so, name); \
if (!p_) { logmsg("bt: dlsym %s failed", name); dlclose(g_bt_so); g_bt_so = NULL; return -1; } \
memcpy(&g_##field, &p_, sizeof g_##field); \
} while (0)
static void bt_close(void); /* 前向:bt_write 判死路径要用 */ static void bt_close(void); /* 前向:bt_write 判死路径要用 */
static void bt_vol_full(void); /* 前向:bt_open 成功时促成 AVRCP 100% */ static void bt_vol_full(void); /* 前向:bt_open 成功时促成 AVRCP 100% */
static int bt_load(void)
{
if (g_bt_so) return 0;
if (g_bt_loaded) return -1; /* 已试过且失败:不再重试(防 dlopen 风暴) */
g_bt_loaded = 1;
g_bt_so = dlopen(BT_SO, RTLD_NOW);
if (!g_bt_so) { logmsg("bt: dlopen %s failed: %s", BT_SO, dlerror()); return -1; }
BT_LOAD_SYM(fn_open, "snd_pcm_open");
BT_LOAD_SYM(fn_set_params,"snd_pcm_set_params");
BT_LOAD_SYM(fn_nonblock, "snd_pcm_nonblock");
BT_LOAD_SYM(fn_writei, "snd_pcm_writei");
BT_LOAD_SYM(fn_wait, "snd_pcm_wait");
BT_LOAD_SYM(fn_recover, "snd_pcm_recover");
BT_LOAD_SYM(fn_drain, "snd_pcm_drain");
BT_LOAD_SYM(fn_close, "snd_pcm_close");
BT_LOAD_SYM(fn_strerror, "snd_strerror");
logmsg("bt: libasound loaded via dlopen");
return 0;
}
/* bt.codec 生效:bluealsa 的 codec 是 config-time 参数(/usr/data/alsa.conf `pcm.bluealsa` 块的 /* bt.codec 生效:bluealsa 的 codec 是 config-time 参数(/usr/data/alsa.conf `pcm.bluealsa` 块的
* `codec "X"`)。audiod 在 BT 打开前做一次**幂等且外科式**的 token 替换:只把 bluealsa 块内的 * `codec "X"`)。audiod 在 BT 打开前做一次**幂等且外科式**的 token 替换:只把 bluealsa 块内的
* `codec "..."` 改成 `codec "<g_bt_codec>"`(其余字符不动)。仅真机且文件可写时执行; * `codec "..."` 改成 `codec "<g_bt_codec>"`(其余字符不动)。仅真机且文件可写时执行;
@@ -936,30 +910,29 @@ static void bt_apply_codec_conf(void)
free(out); free(out);
} }
/* BT 打开:dlopen libasound → snd_pcm_open(pcm.bluealsa/bluealsa, PLAYBACK, NONBLOCK) → /* BT 打开:libasound → snd_pcm_open(pcm.bluealsa/bluealsa, PLAYBACK, NONBLOCK) →
* set_params(S16_LE, RW, ch, rate, soft_resample, 100ms)。返回 0=ok。失败置 g_bt_open=0。 */ * set_params(S16_LE, RW, ch, rate, soft_resample, 100ms)。返回 0=ok。失败置 g_bt_open=0。 */
static int bt_open(void) static int bt_open(void)
{ {
if (bt_load() != 0) return -1;
void *pcm = NULL; void *pcm = NULL;
const char *names[2] = { BT_PCM_DEFAULT, BT_PCM_FALLBACK }; const char *names[2] = { BT_PCM_DEFAULT, BT_PCM_FALLBACK };
int r = -1; int r = -1;
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
r = g_fn_open(&pcm, names[i], SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); r = snd_pcm_open(&pcm, names[i], SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
if (r == 0) { logmsg("bt: opened %s", names[i]); break; } if (r == 0) { logmsg("bt: opened %s", names[i]); break; }
logmsg("bt: snd_pcm_open %s: %s", names[i], g_fn_strerror ? g_fn_strerror(r) : "?"); logmsg("bt: snd_pcm_open %s: %s", names[i], snd_strerror(r));
pcm = NULL; pcm = NULL;
} }
if (r != 0) return -1; if (r != 0) return -1;
g_bt_hdl = pcm; g_bt_hdl = pcm;
g_fn_nonblock(pcm, 1); snd_pcm_nonblock(pcm, 1);
unsigned rate = g_st.rate ? g_st.rate : 44100; unsigned rate = g_st.rate ? g_st.rate : 44100;
unsigned ch = g_st.ch ? g_st.ch : 2; unsigned ch = g_st.ch ? g_st.ch : 2;
r = g_fn_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED, r = snd_pcm_set_params(pcm, SND_PCM_FORMAT_S16_LE, SND_PCM_ACCESS_RW_INTERLEAVED,
ch, rate, 1, 100000); ch, rate, 1, 100000);
if (r < 0) { if (r < 0) {
logmsg("bt: set_params %u/%u/S16: %s", rate, ch, g_fn_strerror ? g_fn_strerror(r) : "?"); logmsg("bt: set_params %u/%u/S16: %s", rate, ch, snd_strerror(r));
g_fn_close(pcm); g_bt_hdl = NULL; return -1; snd_pcm_close(pcm); g_bt_hdl = NULL; return -1;
} }
g_bt_open = 1; g_bt_open = 1;
logmsg("bt: PCM ready rate=%u ch=%u fmt=S16 codec=%s", rate, ch, g_bt_codec); logmsg("bt: PCM ready rate=%u ch=%u fmt=S16 codec=%s", rate, ch, g_bt_codec);
@@ -985,12 +958,12 @@ static void bt_write(uint8_t *buf, size_t n)
const uint8_t *off = buf; const uint8_t *off = buf;
int stalls = 0; int stalls = 0;
while (left > 0 && !g_stop) { while (left > 0 && !g_stop) {
long w = g_fn_writei(g_bt_hdl, off, left); long w = snd_pcm_writei(g_bt_hdl, off, left);
if (w > 0) { off += (size_t)w * fs; left -= (unsigned long)w; stalls = 0; continue; } if (w > 0) { off += (size_t)w * fs; left -= (unsigned long)w; stalls = 0; continue; }
if (w < 0) { if (w < 0) {
int err = (int)(-w); int err = (int)(-w);
if (err == EAGAIN || err == EWOULDBLOCK) { if (err == EAGAIN || err == EWOULDBLOCK) {
int pr = g_fn_wait(g_bt_hdl, ALSA_POLL_MS); int pr = snd_pcm_wait(g_bt_hdl, ALSA_POLL_MS);
if (pr == 0) { /* 无空间超时 → 停摆计数(镜像 DAC stall) */ if (pr == 0) { /* 无空间超时 → 停摆计数(镜像 DAC stall) */
g_stall++; stalls++; g_stall++; stalls++;
if (stalls >= ALSA_STALL_MAX) { if (stalls >= ALSA_STALL_MAX) {
@@ -1006,10 +979,10 @@ static void bt_write(uint8_t *buf, size_t n)
if (err == EPIPE || err == ESTRPIPE) { /* xrun:recover 后继续 */ if (err == EPIPE || err == ESTRPIPE) { /* xrun:recover 后继续 */
g_underrun++; g_underrun++;
logmsg("bt xrun #%u -> recover", (unsigned)g_underrun); logmsg("bt xrun #%u -> recover", (unsigned)g_underrun);
g_fn_recover(g_bt_hdl, err, 1); snd_pcm_recover(g_bt_hdl, err, 1);
continue; continue;
} }
logmsg("bt writei err: %s", g_fn_strerror ? g_fn_strerror(err) : "?"); logmsg("bt writei err: %s", snd_strerror(err));
break; break;
} }
if (w == 0) break; if (w == 0) break;
@@ -1021,8 +994,8 @@ static void bt_write(uint8_t *buf, size_t n)
static void bt_close(void) static void bt_close(void)
{ {
if (g_bt_hdl) { if (g_bt_hdl) {
g_fn_drain(g_bt_hdl); snd_pcm_drain(g_bt_hdl);
g_fn_close(g_bt_hdl); snd_pcm_close(g_bt_hdl);
g_bt_hdl = NULL; g_bt_hdl = NULL;
} }
g_bt_open = 0; g_bt_open = 0;