feat(audiod): A3+DSD — ①24/32bit高清byte-exact(偏好S32) ②独立AUDIO_FMT_DOP DoP DSD透传+失败报错关流(拒BT) ③音量收编v2双组写PCM+DSD_VOLUME平方锥度audiod权威 ④/api/audio/monitor监控+前端音频页监控卡;宿主100/0,0warning;SDK meow_ui_audio ABI变大M6.34需重编meow_ui.a+重链四App

This commit is contained in:
AskaEth committed 2026-09-04 00:56:57 +08:00
1 parent 2dc4cad454
commit effce6d0db
6 files changed
+527 -50

No files matched your search

+14 -5
View File
@@ -139,7 +139,8 @@ static int fmt_bps(int fmt)
case AUDIO_FMT_S16: return 2; case AUDIO_FMT_S16: return 2;
case AUDIO_FMT_S24: case AUDIO_FMT_S24:
case AUDIO_FMT_S32: case AUDIO_FMT_S32:
case AUDIO_FMT_FLOAT: return 4; case AUDIO_FMT_FLOAT:
case AUDIO_FMT_DOP: return 4;
default: return 2; default: return 2;
} }
} }
@@ -150,6 +151,7 @@ static const char *fmt_name(int fmt)
case AUDIO_FMT_S24: return "S24"; case AUDIO_FMT_S24: return "S24";
case AUDIO_FMT_S32: return "S32"; case AUDIO_FMT_S32: return "S32";
case AUDIO_FMT_FLOAT: return "FLOAT"; case AUDIO_FMT_FLOAT: return "FLOAT";
case AUDIO_FMT_DOP: return "DoP";
default: return "?"; default: return "?";
} }
} }
@@ -318,14 +320,14 @@ static size_t gen_sine(uint8_t *buf, size_t bufcap, int grant_fmt, int grant_ch,
return done; return done;
} }
static int cmd_tone(int freq, int ms, int rate) static int cmd_tone(int freq, int ms, int rate, int fmt)
{ {
m_audio_open_t o; m_audio_open_t o;
memset(&o, 0, sizeof o); memset(&o, 0, sizeof o);
snprintf(o.app_id, sizeof o.app_id, "audio-cli"); snprintf(o.app_id, sizeof o.app_id, "audio-cli");
o.rate = (uint32_t)rate; o.rate = (uint32_t)rate;
o.ch = 2; o.ch = 2;
o.fmt = AUDIO_FMT_S16; o.fmt = (uint16_t)fmt;
uint8_t rbuf[512]; uint16_t rt; uint8_t rbuf[512]; uint16_t rt;
int n = req_resp(MSG_AUDIO_OPEN, &o, sizeof o, &rt, rbuf, sizeof rbuf); int n = req_resp(MSG_AUDIO_OPEN, &o, sizeof o, &rt, rbuf, sizeof rbuf);
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; } if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; }
@@ -386,7 +388,7 @@ static int cmd_tone(int freq, int ms, int rate)
static void usage(const char *argv0) static void usage(const char *argv0)
{ {
fprintf(stderr, "usage: %s status | tone [freq] [ms] [rate] | route <0|2|3> | vol [0-100] | output <dac|bt> | monitor [秒]\n" fprintf(stderr, "usage: %s status | tone [freq] [ms] [rate] [S16|S24|S32] | route <0|2|3> | vol [0-100] | output <dac|bt> | monitor [秒]\n"
" output: 切换输出目标(dac=本地 hw:0,0 / bt=蓝牙 A2DP source;bt 时音量=AVRCP 侧控)\n" " output: 切换输出目标(dac=本地 hw:0,0 / bt=蓝牙 A2DP source;bt 时音量=AVRCP 侧控)\n"
" monitor: 终端实时电平表(200ms 刷新+文本电平条;缺省 10s,0=持续到 Ctrl-C)\n" " monitor: 终端实时电平表(200ms 刷新+文本电平条;缺省 10s,0=持续到 Ctrl-C)\n"
" env: MEOW_AUDIO_SOCK (socket 缺省 %s)\n", argv0, AUD_SOCK_DEFAULT); " env: MEOW_AUDIO_SOCK (socket 缺省 %s)\n", argv0, AUD_SOCK_DEFAULT);
@@ -432,9 +434,16 @@ int main(int argc, char **argv)
int freq = (argc >= 3) ? atoi(argv[2]) : 440; int freq = (argc >= 3) ? atoi(argv[2]) : 440;
int ms = (argc >= 4) ? atoi(argv[3]) : 500; int ms = (argc >= 4) ? atoi(argv[3]) : 500;
int rate = (argc >= 5) ? atoi(argv[4]) : 44100; int rate = (argc >= 5) ? atoi(argv[4]) : 44100;
int fmt = AUDIO_FMT_S16;
if (argc >= 6) {
if (!strcmp(argv[5], "S16")) fmt = AUDIO_FMT_S16;
else if (!strcmp(argv[5], "S24")) fmt = AUDIO_FMT_S24;
else if (!strcmp(argv[5], "S32")) fmt = AUDIO_FMT_S32;
else { fprintf(stderr, "tone: fmt S16|S24|S32(DoP 由 App 侧生成,CLI 不产 DoP)\n"); return 2; }
}
if (freq <= 0 || ms <= 0 || rate <= 0) { fprintf(stderr, "tone: bad params\n"); return 2; } if (freq <= 0 || ms <= 0 || rate <= 0) { fprintf(stderr, "tone: bad params\n"); return 2; }
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; } if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; }
return cmd_tone(freq, ms, rate); return cmd_tone(freq, ms, rate, fmt);
} else if (!strcmp(cmd, "monitor")) { } else if (!strcmp(cmd, "monitor")) {
int secs = (argc >= 3) ? atoi(argv[2]) : 10; int secs = (argc >= 3) ? atoi(argv[2]) : 10;
if (secs < 0) secs = 10; if (secs < 0) secs = 10;
+142 -29
View File
@@ -169,10 +169,11 @@ static int g_ring_wait_ms = RING_WAIT_DEFAULT;
static volatile unsigned g_hb_io = 0; static volatile unsigned g_hb_io = 0;
static volatile unsigned g_hb_play = 0; static volatile unsigned g_hb_play = 0;
/* 测试钩子(仅 env 显式开启;MEOW_AUDIO_TEST_WRITE_DELAY_MS / _WEDGE_AFTER_BYTES) */ /* 测试钩子(仅 env 显式开启;MEOW_AUDIO_TEST_WRITE_DELAY_MS / _WEDGE_AFTER_BYTES / _AMIXER_TRACE) */
static int g_test_write_delay_ms = 0; static int g_test_write_delay_ms = 0;
static long long g_test_wedge_after = -1; /* dev_write 累计字节≥此值后模拟设备 wedge(睡到 g_stop) */ static long long g_test_wedge_after = -1; /* dev_write 累计字节≥此值后模拟设备 wedge(睡到 g_stop) */
static uint64_t g_dev_bytes = 0; static uint64_t g_dev_bytes = 0;
static char g_amixer_trace_path[256] = ""; /* MEOW_AUDIO_AMIXER_TRACE:记录每次 amixer cset */
/* ring(SPSC:IO 线程写,播放线程读;g_mu + g_cond 保护) */ /* ring(SPSC:IO 线程写,播放线程读;g_mu + g_cond 保护) */
static uint8_t *g_ring = NULL; static uint8_t *g_ring = NULL;
@@ -183,6 +184,7 @@ static size_t g_rpos = 0, g_wpos = 0;
static int g_dev_fd = -1; static int g_dev_fd = -1;
static int g_dev_open = 0; static int g_dev_open = 0;
static uint32_t g_dev_rate = 0, g_dev_ch = 0, g_dev_fmt = 0; static uint32_t g_dev_rate = 0, g_dev_ch = 0, g_dev_fmt = 0;
static int g_dev_dsd = 0; /* 当前打开设备为 DoP DSD(决定 DOP_EN 硬件时序;g_mu 保护) */
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 时用) ----
@@ -323,7 +325,8 @@ static int fmt_bps(int fmt)
case AUDIO_FMT_S16: return 2; case AUDIO_FMT_S16: return 2;
case AUDIO_FMT_S24: case AUDIO_FMT_S24:
case AUDIO_FMT_S32: case AUDIO_FMT_S32:
case AUDIO_FMT_FLOAT: return 4; case AUDIO_FMT_FLOAT:
case AUDIO_FMT_DOP: return 4; /* DOP=32-bit DoP 容器(16bit DSD + 8bit 标记 + 8bit 0),与 S32 同宽 */
default: return 2; default: return 2;
} }
} }
@@ -334,6 +337,7 @@ static const char *fmt_name(int fmt)
case AUDIO_FMT_S24: return "S24"; case AUDIO_FMT_S24: return "S24";
case AUDIO_FMT_S32: return "S32"; case AUDIO_FMT_S32: return "S32";
case AUDIO_FMT_FLOAT: return "FLOAT"; case AUDIO_FMT_FLOAT: return "FLOAT";
case AUDIO_FMT_DOP: return "DoP";
default: return "?"; default: return "?";
} }
} }
@@ -345,12 +349,17 @@ static const char *target_name(void)
return (g_target == AUDIO_OUT_BT) ? "bt" : "dac"; return (g_target == AUDIO_OUT_BT) ? "bt" : "dac";
} }
/* 授权给客户端的硬件最优格式。BT(A2DP source)=S16(codec 基座,pcm.bluealsa 前置 plug 层自动转 /* 授权给客户端的硬件最优格式。native_fmt=客户端请求格式。BT(A2DP source)=S16(codec 基座,
* codec 协商率);DAC=hw 探测最优(FILE/NULL 宿主=S16)。 */ * pcm.bluealsa 前置 plug 层自动转 codec 协商率);DAC=hw 探测最优(S32 升位/S16 直通);
static int target_grant_fmt(void) * FILE/NULL 宿主 sink:S24/S32 按请求**byte-exact 透传**(高清实测保真),S16 缺省,DOP 恒透传。 */
static int target_grant_fmt(int native_fmt)
{ {
if (native_fmt == AUDIO_FMT_DOP) return AUDIO_FMT_DOP; /* DoP 恒透传(audiod 只做管道) */
if (g_target == AUDIO_OUT_BT) return AUDIO_FMT_S16; if (g_target == AUDIO_OUT_BT) return AUDIO_FMT_S16;
return (g_sink == SINK_ALSA) ? g_hw_fmt : AUDIO_FMT_S16; if (g_sink == SINK_ALSA) return g_hw_fmt; /* DAC 探测最优(S32 偏好) */
if (native_fmt == AUDIO_FMT_S24 || native_fmt == AUDIO_FMT_S32)
return native_fmt; /* FILE/NULL:高保真记录 */
return AUDIO_FMT_S16;
} }
/* 音量控制归属(QUERY volctl=):dac=反转控制;avrcp=蓝牙 AVRCP 侧控。 */ /* 音量控制归属(QUERY volctl=):dac=反转控制;avrcp=蓝牙 AVRCP 侧控。 */
@@ -570,6 +579,17 @@ static int amixer_set(int numid, int val, int card)
g_amixer, (card ? " -c 0" : ""), numid, val); g_amixer, (card ? " -c 0" : ""), numid, val);
char *o = popen_read_tmo(1500, cmd); char *o = popen_read_tmo(1500, cmd);
if (!o) return -1; if (!o) return -1;
/* 测试钩子(仅 env MEOW_AUDIO_AMIXER_TRACE=<path>):把每次 amixer cset 追加到文件,
* 供宿主断言「音量双组写」PCM+DSD_VOLUME 是否成对下发(真机无 amixer 时不写/不报错)。 */
if (g_amixer_trace_path[0]) {
int tfd = open(g_amixer_trace_path, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (tfd >= 0) {
char tb[64];
int tn = snprintf(tb, sizeof tb, "cset numid=%d %d\n", numid, val);
if (tn > 0) { if (write(tfd, tb, (size_t)tn) < 0) { } }
close(tfd);
}
}
/* popen_read_tmo 用的是静态缓冲,这里不 free(与 meow_route.c 同权衡) */ /* popen_read_tmo 用的是静态缓冲,这里不 free(与 meow_route.c 同权衡) */
return 0; return 0;
} }
@@ -592,6 +612,15 @@ static int amixer_get(int numid, int card)
return -1; return -1;
} }
/* ---- DSD(DoP)相关常量 + 状态 ----
* ⚠️ 真机核实项(AUDIOD_A3_DSD_PLAN §五,未做):DSD_VOLUME_A/B 寄存器地址/control、DoP 标记字节
* (0x05/0xFA vs 0xA5/0x5A)。下述 numid 为设计定案占位:DOP_EN=numid 10(plan §三「cset numid=10 1」),
* DSD_VOLUME 单 control 占位 numid 11(真实为 A/B 两路或 write_reg_val,待 DS1039 核实)。
* 未核实前 DSD_VOLUME 写为 best-effort(amixer 出错仅日志,绝不掩盖;正确地址真机定后再上)。 */
#define DOP_EN_NUMID 10
#define DSD_VOL_NUMID 11
static int g_dsd_active = 0; /* 当前授权流为 DoP DSD(g_mu 保护;决定 DOP_EN=1 与 DSD 音量作用域) */
static void dac_vol_set(int pct) static void dac_vol_set(int pct)
{ {
int t = 100 - pct, dac; int t = 100 - pct, dac;
@@ -599,9 +628,12 @@ static void dac_vol_set(int pct)
else dac = t * t * 192 / 10000; /* 感知平方锥度:dB 衰减=(1-pct)^2×96dB */ else dac = t * t * 192 / 10000; /* 感知平方锥度:dB 衰减=(1-pct)^2×96dB */
if (dac > 255) dac = 255; if (dac > 255) dac = 255;
if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */ if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */
/* 音量收编 v2:**泛化双组写**——同刻写 PCM(numid=1/2) 与 DSD_VOLUME,保证 DSD↔PCM 切换
* 音量连续 + 防爆音闭环(决策①,勿只写 PCM)。锥度沿用平方(决策②)。 */
amixer_set(1, dac, 1); amixer_set(1, dac, 1);
amixer_set(2, dac, 1); amixer_set(2, dac, 1);
logmsg("DAC vol -> %d%% (L/R=%d)", pct, dac); amixer_set(DSD_VOL_NUMID, dac, 1);
logmsg("DAC/DSD vol -> %d%% (PCM L/R=%d, DSD=%d)", pct, dac, dac);
} }
static void dac_vol_mute(void) static void dac_vol_mute(void)
@@ -610,6 +642,30 @@ static void dac_vol_mute(void)
* 旧版写 0 在闪退/重配瞬间造成满幅炸音(真机事故 2026-09-01)。真静音写 255。 */ * 旧版写 0 在闪退/重配瞬间造成满幅炸音(真机事故 2026-09-01)。真静音写 255。 */
amixer_set(1, 255, 1); amixer_set(1, 255, 1);
amixer_set(2, 255, 1); amixer_set(2, 255, 1);
amixer_set(DSD_VOL_NUMID, 255, 1); /* DSD 同步真静音(防爆音闭环:DSD 也 255) */
}
/* ---- DSD(DoP)管道:audiod 只做 DOP_EN 时序 + DSD 音量 + 透传;不引 dsd2pcm(许可未核实,避风险)。
* 流级判定 vs 设备级判定分拆:
* - g_dsd_active:当前授权流为 DoP DSD(dispatch 授 DOP 时置位,session_clean 复位)——供 QUERY/monitor。
* - g_dev_dsd:当前**打开的设备**为 DSD(reconfigure/dev_open 后置位)——决定 DOP_EN 硬件时序。
* 进入 DSD(plan §三时序)由播放线程 reconfigure 执行:mute(PCM+DSD) → DOP_EN=1 → hw_params(容器率)
* → dac_vol_set(DSD_VOLUME) → unmute。本函数只做「流级可入性」判定:BT 拒(A2DP 不能承载 DoP),
* 非 ALSA(FILE/NULL)=纯透传管道允许。失败回退=报错关流(案 A:DSD 打不开/不支持→报错并关闭,绝不静默退化)。 */
static int dsd_enter(void)
{
if (g_target == AUDIO_OUT_BT) {
logmsg("dsd: enter rejected (BT/A2DP cannot carry DoP) -> close stream");
return -1;
}
if (g_sink != SINK_ALSA) {
logmsg("dsd: enter ok (sink=%d passthrough, no DOP_EN)", g_sink);
g_dsd_active = 1;
return 0;
}
g_dsd_active = 1; /* 流级置位;DOP_EN 硬件时序由播放线程 reconfigure 执行 */
logmsg("dsd: stream enters DoP DSD (DOP_EN sequenced on device reconfigure)");
return 0;
} }
static const char *port_name(int port) static const char *port_name(int port)
@@ -1165,31 +1221,43 @@ static int alsa_fmt_to_snd(int fmt)
case AUDIO_FMT_S16: return SNDRV_PCM_FORMAT_S16_LE; case AUDIO_FMT_S16: return SNDRV_PCM_FORMAT_S16_LE;
case AUDIO_FMT_S24: return SNDRV_PCM_FORMAT_S24_LE; case AUDIO_FMT_S24: return SNDRV_PCM_FORMAT_S24_LE;
case AUDIO_FMT_S32: return SNDRV_PCM_FORMAT_S32_LE; case AUDIO_FMT_S32: return SNDRV_PCM_FORMAT_S32_LE;
case AUDIO_FMT_DOP: return SNDRV_PCM_FORMAT_S32_LE; /* DoP 走 32-bit 容器(16bit DSD + 标记 + 0) */
case AUDIO_FMT_FLOAT: return SNDRV_PCM_FORMAT_FLOAT_LE; case AUDIO_FMT_FLOAT: return SNDRV_PCM_FORMAT_FLOAT_LE;
default: return SNDRV_PCM_FORMAT_S16_LE; default: return SNDRV_PCM_FORMAT_S16_LE;
} }
} }
/* 探测硬件最优格式(设备空闲时调用;缓存 g_hw_fmt)。返回 0=确定,-1=无法探测。 */ /* 探测硬件最优格式(设备空闲时调用;缓存 g_hw_fmt)。返回 0=确定,-1=无法探测。
* A3(24/32bit 高清流):**偏好 S32**——CS43198 为 32bit DAC,S32 可顺带把 16bit 内容做无损升位、
* 24/32bit 直通不降位(plan §一「S16 直通/S32 升位」)。按高清率优先探测 S32(192k→176.4k→44.1k),
* 全失败再退 S16。旧实现只在 44.1k 试 S16 并**立即返回**(永远拿不到 S32,高清降位)。
* ⚠️ 探测只 open+hw_params(非 start),与 FPGA 武装无关;真机此路径仍需 AUDIO 实测复核。 */
static int hw_probe(void) static int hw_probe(void)
{ {
if (g_sink != SINK_ALSA) { g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; return 0; } if (g_sink != SINK_ALSA) { g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; return 0; }
if (g_hw_probed) return 0; if (g_hw_probed) return 0;
#ifdef AUDIOD_HAVE_ALSA #ifdef AUDIOD_HAVE_ALSA
static const unsigned rates[3] = { 192000u, 176400u, 44100u };
for (int r = 0; r < 3 && !g_hw_probed; r++) {
int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK);
if (fd >= 0) { if (fd >= 0) {
if (alsa_hw_params(fd, 44100, 2, SNDRV_PCM_FORMAT_S16_LE) == 0) { g_hw_fmt = AUDIO_FMT_S16; close(fd); g_hw_probed = 1; return 0; } if (alsa_hw_params(fd, rates[r], 2, SNDRV_PCM_FORMAT_S32_LE) == 0)
{ g_hw_fmt = AUDIO_FMT_S32; g_hw_probed = 1; }
close(fd); close(fd);
} }
fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); }
for (int r = 0; r < 3 && !g_hw_probed; r++) {
int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK);
if (fd >= 0) { if (fd >= 0) {
if (alsa_hw_params(fd, 44100, 2, SNDRV_PCM_FORMAT_S32_LE) == 0) { g_hw_fmt = AUDIO_FMT_S32; close(fd); g_hw_probed = 1; return 0; } if (alsa_hw_params(fd, rates[r], 2, SNDRV_PCM_FORMAT_S16_LE) == 0)
{ g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; }
close(fd); close(fd);
} }
}
#endif #endif
g_hw_fmt = AUDIO_FMT_S16; if (!g_hw_probed) { g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; }
g_hw_probed = 1; logmsg("hw probe: optimal fmt=%s", fmt_name(g_hw_fmt));
return -1; return 0;
} }
/* ---- FILE/NULL sink ---- */ /* ---- FILE/NULL sink ---- */
@@ -1360,26 +1428,50 @@ static void dev_close(void)
g_dev_open = 0; g_dev_open = 0;
} }
/* 播放线程:处理一次重配(静音 → 关旧 → 开新 → 复原)。调用方已持 g_mu。 */ /* 播放线程:处理一次重配(静音 → 关旧 → 开新 → 复原)。调用方已持 g_mu。
* DSD(audiod 只透传):DoP 时序「mute(PCM+DSD) → DOP_EN=1 → hw_params(容器率) → DSD_VOLUME → unmute」。
* DOP_EN 必须早于 dev_open 的 hw_params(plan §三),离开 DSD 时 DOP_EN=0 须早于关设备。 */
static void reconfigure_locked(void) static void reconfigure_locked(void)
{ {
uint32_t rate = g_st.rate, ch = g_st.ch, fmt = g_st.fmt; uint32_t rate = g_st.rate, ch = g_st.ch, fmt = g_st.fmt;
const char *tgt = (g_target == AUDIO_OUT_BT) ? "bt" : "dac"; const char *tgt = (g_target == AUDIO_OUT_BT) ? "bt" : "dac";
logmsg("reconfigure device: %u/%u/%s target=%s", (unsigned)rate, (unsigned)ch, fmt_name((int)fmt), tgt); logmsg("reconfigure device: %u/%u/%s target=%s", (unsigned)rate, (unsigned)ch, fmt_name((int)fmt), tgt);
int saved_vol = g_vol_pct; int saved_vol = g_vol_pct;
/* 防爆音仅 DAC(重配前静音 + 复原);BT 独立目标,跳过 DAC 音量(音量由 AVRCP 侧控)。 */ int new_dsd = (fmt == AUDIO_FMT_DOP);
if (g_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA) dac_vol_mute(); int do_dsd_seq = (g_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA); /* DOP_EN 仅 DAC 物理路径 */
if (do_dsd_seq) {
if (new_dsd) {
dac_vol_mute(); /* ① mute(PCM+DSD 双组) */
/* ② DOP_EN 写后校验:**DSD 打不开/不支持→报错关流(案 A,不静默退化)**——DOP_EN 未确认
* 为 1 则丢弃本流(active=0→播放线程不再喂 DAC),客户端经 STATE active=0 感知并重开/换目标。 */
if (amixer_set(DOP_EN_NUMID, 1, 1) < 0 || amixer_get(DOP_EN_NUMID, 1) != 1) {
logmsg("dsd: DOP_EN (numid=%d) not confirmed -> close stream (no silent degrade)", DOP_EN_NUMID);
dac_vol_mute();
amixer_set(DOP_EN_NUMID, 0, 1); /* 确保离开 DOP(兼容旧 DSD 设备残留) */
session_end_locked();
g_dev_dsd = 0;
g_reconfig_pending = 0;
return;
}
logmsg("dsd: DOP_EN -> 1 (DoP container)");
} else if (g_dev_dsd) {
logmsg("dsd: DOP_EN -> 0 (leave DoP)"); /* 离开 DSD:DOP_EN=0 后关设备 */
amixer_set(DOP_EN_NUMID, 0, 1);
}
}
dev_close(); dev_close();
g_dev_fd = dev_open(); g_dev_fd = dev_open();
if (g_dev_fd >= 0 || (g_sink == SINK_NULL && g_dev_fd == -2)) { if (g_dev_fd >= 0 || (g_sink == SINK_NULL && g_dev_fd == -2)) {
g_dev_open = 1; g_dev_open = 1;
g_dev_rate = rate; g_dev_ch = ch; g_dev_fmt = fmt; g_dev_rate = rate; g_dev_ch = ch; g_dev_fmt = fmt;
if (g_dev_fd == -2) g_dev_fd = -1; /* NULL sink:无真实 fd */ if (g_dev_fd == -2) g_dev_fd = -1; /* NULL sink:无真实 fd */
if (g_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA) dac_vol_set(saved_vol); /* 复原 */ g_dev_dsd = (do_dsd_seq && new_dsd);
logmsg("device open ok: %u/%u/%s sink=%d target=%s", (unsigned)rate, (unsigned)ch, if (g_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA) dac_vol_set(saved_vol); /* 复原(含 DSD_VOLUME) */
fmt_name((int)fmt), g_sink, tgt); logmsg("device open ok: %u/%u/%s sink=%d target=%s%s", (unsigned)rate, (unsigned)ch,
fmt_name((int)fmt), g_sink, tgt, g_dev_dsd ? " dsd" : "");
} else { } else {
logmsg("device open FAILED !!"); logmsg("device open FAILED !!");
g_dev_dsd = 0;
} }
g_reconfig_pending = 0; g_reconfig_pending = 0;
} }
@@ -1408,6 +1500,11 @@ static void *playback_thread(void *arg)
if ((draining && av < (size_t)fs) || paused) { if ((draining && av < (size_t)fs) || paused) {
/* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */ /* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */
if (draining && g_dev_open) dev_close(); if (draining && g_dev_open) dev_close();
if (draining && g_dev_dsd && g_sink == SINK_ALSA && g_target == AUDIO_OUT_DAC) {
logmsg("dsd: DOP_EN -> 0 (stream drained)");
amixer_set(DOP_EN_NUMID, 0, 1);
g_dev_dsd = 0;
}
if (draining) session_clean_locked(); /* 流已死且排空完成:会话统计彻底归零 */ if (draining) session_clean_locked(); /* 流已死且排空完成:会话统计彻底归零 */
cond_wait_tmo(COND_WAIT_MS); cond_wait_tmo(COND_WAIT_MS);
continue; continue;
@@ -1490,6 +1587,8 @@ static void session_clean_locked(void)
g_st.rate = 0; g_st.rate = 0;
g_st.ch = 0; g_st.ch = 0;
g_st.fmt = 0; g_st.fmt = 0;
g_dsd_active = 0; /* 流级 DSD 位归零(DOP_EN 设备位由 drain/reconfigure 时序负责) */
g_dev_dsd = 0;
g_written_frames = 0; g_written_frames = 0;
g_played_frames = 0; g_played_frames = 0;
g_underrun = 0; g_underrun = 0;
@@ -1511,10 +1610,20 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
if (len < sizeof(m_audio_open_t)) { m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_INVAL; send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); return; } if (len < sizeof(m_audio_open_t)) { m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_INVAL; send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); return; }
m_audio_open_t o; m_audio_open_t o;
memcpy(&o, pl, sizeof o); memcpy(&o, pl, sizeof o);
if (o.ch > 2 || o.fmt < AUDIO_FMT_S16 || o.fmt > AUDIO_FMT_FLOAT || o.fmt == AUDIO_FMT_FLOAT || o.rate == 0) { /* 合法 fmt:S16/S24/S32/DOP;FLOAT 拒(v1 未支持)。DOP 须带 AUDIO_FLAG_DSD(DoP 独立格式值)。 */
int fmt_inval = (o.fmt != AUDIO_FMT_S16 && o.fmt != AUDIO_FMT_S24 &&
o.fmt != AUDIO_FMT_S32 && o.fmt != AUDIO_FMT_DOP);
if (o.ch > 2 || o.ch < 1 || fmt_inval || (o.fmt == AUDIO_FMT_DOP && !(o.flags & AUDIO_FLAG_DSD)) || o.rate == 0) {
m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_INVAL; m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_INVAL;
send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a);
logmsg("open rejected: ch=%u fmt=%d rate=%u", (unsigned)o.ch, o.fmt, (unsigned)o.rate); logmsg("open rejected: ch=%u fmt=%d rate=%u flags=0x%x", (unsigned)o.ch, o.fmt, (unsigned)o.rate, (unsigned)o.flags);
return;
}
/* DSD 可入性判定(BT 拒 / FILE-NULL 透传允许);失败=报错关流(案 A,绝不静默退化) */
if (o.fmt == AUDIO_FMT_DOP && dsd_enter() != 0) {
m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_NODEV;
send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a);
logmsg("open rejected: DoP DSD not supported (target=%s sink=%d)", target_name(), g_sink);
return; return;
} }
/* 输出目标=BT:不依赖 DAC hw 探测(BT 走 pcm.bluealsa,与 hw:0,0/FGA numid=9 解耦)—— /* 输出目标=BT:不依赖 DAC hw 探测(BT 走 pcm.bluealsa,与 hw:0,0/FGA numid=9 解耦)——
@@ -1528,7 +1637,7 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
return; return;
} }
} }
int grant_fmt = target_grant_fmt(); /* DAC=hw 最优;BT=S16(plug 层转 codec 协商率) */ int grant_fmt = target_grant_fmt(o.fmt); /* DAC=hw 最优;BT=S16(plug 层转 codec 协商率);FILE/NULL=保真透传 */
uint32_t sid = g_next_sid++; uint32_t sid = g_next_sid++;
/* 抢占:已有当前流 → 旧客户端收 PREEMPTED */ /* 抢占:已有当前流 → 旧客户端收 PREEMPTED */
if (g_st.active) { if (g_st.active) {
@@ -1561,9 +1670,9 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
g_drop_frames = 0; /* 每流计数(txdrop 为全局客户端级,不随流清) */ g_drop_frames = 0; /* 每流计数(txdrop 为全局客户端级,不随流清) */
g_stall = 0; g_stall = 0;
g_dev_bytes = 0; /* 测试钩子计数随新流归零 */ g_dev_bytes = 0; /* 测试钩子计数随新流归零 */
/* 电平表复位 + 窗口目标(~100ms) */ /* 电平表复位 + 窗口目标(~100ms;DoP DSD 无 PCM 意义,窗口=0 即跳过——meter_feed early-return) */
g_m_win = g_st.rate / 10; g_m_win = (grant_fmt == AUDIO_FMT_DOP) ? 0 : (g_st.rate / 10);
if (!g_m_win) g_m_win = 1; if (!g_m_win && grant_fmt != AUDIO_FMT_DOP) g_m_win = 1;
g_m_sumsq = 0; g_m_peak = 0; g_m_frames = 0; g_m_sumsq = 0; g_m_peak = 0; g_m_frames = 0;
g_lvl_rms_db10 = g_lvl_peak_db10 = METER_FLOOR_DB10; g_lvl_rms_db10 = g_lvl_peak_db10 = METER_FLOOR_DB10;
g_lvl_seq++; g_lvl_seq++;
@@ -1626,7 +1735,7 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
int rb = g_route_cur; int rb = g_route_cur;
int vol = g_vol_pct; int vol = g_vol_pct;
snprintf(txt, sizeof txt, snprintf(txt, sizeof txt,
"active=%d app=%s sid=%u rate=%u ch=%u fmt=%s " "active=%d app=%s sid=%u rate=%u ch=%u fmt=%s dsd=%d "
"written=%llu played=%llu underrun=%u dropped=%llu stall=%u txdrop=%u paused=%d " "written=%llu played=%llu underrun=%u dropped=%llu stall=%u txdrop=%u paused=%d "
"route_mode=%s port=%d vol=%d volctl=%s rms=%d peak=%d lvlseq=%u " "route_mode=%s port=%d vol=%d volctl=%s rms=%d peak=%d lvlseq=%u "
"target=%s codec=%s\n", "target=%s codec=%s\n",
@@ -1634,6 +1743,7 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
g_st.active ? g_st.app_id : "-", g_st.active ? g_st.app_id : "-",
(unsigned)g_st.sid, (unsigned)g_st.rate, (unsigned)g_st.ch, (unsigned)g_st.sid, (unsigned)g_st.rate, (unsigned)g_st.ch,
g_st.active ? fmt_name((int)g_st.fmt) : "-", g_st.active ? fmt_name((int)g_st.fmt) : "-",
g_dsd_active ? 1 : 0,
(unsigned long long)g_written_frames, (unsigned long long)g_written_frames,
(unsigned long long)g_played_frames, (unsigned long long)g_played_frames,
(unsigned)g_underrun, (unsigned)g_underrun,
@@ -1934,10 +2044,12 @@ static void mon_tick(void)
int port = (rmanual != -1) ? rmanual : rcur; int port = (rmanual != -1) ? rmanual : rcur;
char line[512]; char line[512];
int n = snprintf(line, sizeof line, int n = snprintf(line, sizeof line,
"%s app=%s sid=%u %uHz/%uch/%s rms=%sdBFS peak=%sdBFS " "%s app=%s sid=%u %uHz/%uch/%s dsd=%d "
"rms=%sdBFS peak=%sdBFS "
"written=%llu played=%llu underrun=%u stall=%u dropped=%llu txdrop=%u " "written=%llu played=%llu underrun=%u stall=%u dropped=%llu txdrop=%u "
"route=%d(%s) fpga=%s vol=%d%% hwrate=%lluf/s%s\n", "route=%d(%s) fpga=%s vol=%d%% hwrate=%lluf/s%s\n",
tbuf, app, (unsigned)sid, (unsigned)rate, ch, fmt_name((int)fmt), tbuf, app, (unsigned)sid, (unsigned)rate, ch, fmt_name((int)fmt),
(fmt == AUDIO_FMT_DOP ? 1 : 0),
rmsb, pkb, rmsb, pkb,
(unsigned long long)wr, (unsigned long long)pl, (unsigned long long)wr, (unsigned long long)pl,
ur, st, (unsigned long long)dr, tx, ur, st, (unsigned long long)dr, tx,
@@ -1970,7 +2082,7 @@ int main(int argc, char **argv)
" MEOW_AUDIO_SETTINGS (settings.conf 路径, 缺省 %s; 宿主测覆盖)\n" " MEOW_AUDIO_SETTINGS (settings.conf 路径, 缺省 %s; 宿主测覆盖)\n"
" MEOW_AUDIO_TARGET (0=dac 1=bt, 覆盖 settings audio.output_bt)\n" " MEOW_AUDIO_TARGET (0=dac 1=bt, 覆盖 settings audio.output_bt)\n"
" MEOW_AUDIO_RING_WAIT_MS (ring 满写等待上限, 缺省 5000)\n" " MEOW_AUDIO_RING_WAIT_MS (ring 满写等待上限, 缺省 5000)\n"
" MEOW_AUDIO_TEST_WRITE_DELAY_MS / MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES (故障注入, 仅测试)\n", " MEOW_AUDIO_TEST_WRITE_DELAY_MS / MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES / MEOW_AUDIO_AMIXER_TRACE (故障注入/amixer 轨迹, 仅测试)\n",
SETTINGS_CONF); SETTINGS_CONF);
return 0; return 0;
} }
@@ -1982,6 +2094,7 @@ int main(int argc, char **argv)
if ((e = getenv("MEOW_AUDIO_RING_WAIT_MS")) && atoi(e) > 0) g_ring_wait_ms = atoi(e); if ((e = getenv("MEOW_AUDIO_RING_WAIT_MS")) && atoi(e) > 0) g_ring_wait_ms = atoi(e);
if ((e = getenv("MEOW_AUDIO_TEST_WRITE_DELAY_MS")) && atoi(e) > 0) g_test_write_delay_ms = atoi(e); if ((e = getenv("MEOW_AUDIO_TEST_WRITE_DELAY_MS")) && atoi(e) > 0) g_test_write_delay_ms = atoi(e);
if ((e = getenv("MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES")) && atoll(e) > 0) g_test_wedge_after = atoll(e); if ((e = getenv("MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES")) && atoll(e) > 0) g_test_wedge_after = atoll(e);
if ((e = getenv("MEOW_AUDIO_AMIXER_TRACE")) && e[0]) snprintf(g_amixer_trace_path, sizeof g_amixer_trace_path, "%s", e);
if ((e = getenv("MEOW_AUDIO_SETTINGS")) && e[0]) snprintf(g_settings_path, sizeof g_settings_path, "%s", e); if ((e = getenv("MEOW_AUDIO_SETTINGS")) && e[0]) snprintf(g_settings_path, sizeof g_settings_path, "%s", e);
const char *sink = getenv("MEOW_AUDIO_SINK"); const char *sink = getenv("MEOW_AUDIO_SINK");
if (sink && sink[0]) { if (sink && sink[0]) {
+305 -3
View File
@@ -27,6 +27,7 @@
#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 <sys/stat.h>
#define TSOCK "/tmp/meow_audio_test.sock" #define TSOCK "/tmp/meow_audio_test.sock"
#define TWAV "/tmp/meow_audio_test.wav" #define TWAV "/tmp/meow_audio_test.wav"
@@ -169,12 +170,29 @@ static int start_audiod_settings(const char *sink, const char *settings_path)
return pid; return pid;
} }
/* 直接客户端 open */ /* 带 amixer 追踪的启动(A3/DSD:断言「音量双组写」PCM+DSD_VOLUME 成对下发)。settings_path 可空。 */
static int do_open(struct cli *c, int rate, int ch, int fmt, m_audio_open_ack_t *ack) static int start_audiod_trace(const char *sink, const char *settings_path, const char *trace_path)
{
pid_t pid = fork();
if (pid == 0) {
setenv("MEOW_AUDIO_SINK", sink, 1);
setenv("MEOW_AUDIO_OUT", TWAV, 1);
setenv("MEOW_AUDIO_SOCK", TSOCK, 1);
setenv("MEOW_AUDIO_FORCE", "1", 1);
if (settings_path) setenv("MEOW_AUDIO_SETTINGS", settings_path, 1);
if (trace_path) setenv("MEOW_AUDIO_AMIXER_TRACE", trace_path, 1);
execl("./meow_audiod_host", "meow_audiod_host", (char *)0);
_exit(127);
}
return pid;
}
/* 直接客户端 open(带 flags:AUDIO_FLAG_DSD 用于 DoP) */
static int do_open_f(struct cli *c, int rate, int ch, int fmt, uint32_t flags, m_audio_open_ack_t *ack)
{ {
m_audio_open_t o; memset(&o, 0, sizeof o); m_audio_open_t o; memset(&o, 0, sizeof o);
snprintf(o.app_id, sizeof o.app_id, "test"); snprintf(o.app_id, sizeof o.app_id, "test");
o.rate = (uint32_t)rate; o.ch = (uint16_t)ch; o.fmt = (uint16_t)fmt; o.flags = 0; o.rate = (uint32_t)rate; o.ch = (uint16_t)ch; o.fmt = (uint16_t)fmt; o.flags = flags;
if (sf(c->fd, MSG_AUDIO_OPEN, 1, &o, sizeof o)) return -1; if (sf(c->fd, MSG_AUDIO_OPEN, 1, &o, sizeof o)) return -1;
uint8_t rbuf[256]; uint16_t rt; uint8_t rbuf[256]; uint16_t rt;
if (rf(c->fd, &rt, rbuf, sizeof rbuf) < 0) return -1; if (rf(c->fd, &rt, rbuf, sizeof rbuf) < 0) return -1;
@@ -182,6 +200,11 @@ static int do_open(struct cli *c, int rate, int ch, int fmt, m_audio_open_ack_t
memcpy(ack, rbuf, sizeof *ack); memcpy(ack, rbuf, sizeof *ack);
return 0; return 0;
} }
/* 直接客户端 open */
static int do_open(struct cli *c, int rate, int ch, int fmt, m_audio_open_ack_t *ack)
{
return do_open_f(c, rate, ch, fmt, 0, ack);
}
static int do_write(struct cli *c, const void *pcm, uint32_t len) static int do_write(struct cli *c, const void *pcm, uint32_t len)
{ {
@@ -266,6 +289,45 @@ static int verify_wav(const char *path, int rate_expect, int ch_expect, int bits
return 0; return 0;
} }
/* 字节保真校验(A3 高清 / DoP 透传):WAV 头 rate/ch/bits 精确匹配 + data 段与 payload 逐字节一致。
* 返回 0=合法且逐字节等同;-1=文件/头非法;-2=data_len 或内容不匹配。 */
static int wav_byte_exact(const char *path, int rate_expect, int ch_expect, int bits_expect,
const uint8_t *payload, size_t plen)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
unsigned char h[44];
if (fread(h, 1, 44, f) != 44) { fclose(f); return -1; }
if (memcmp(h, "RIFF", 4) || memcmp(h + 8, "WAVE", 4) || memcmp(h + 12, "fmt ", 4)) { fclose(f); return -1; }
uint16_t ch, bits; uint32_t rate, data_len, byterate;
memcpy(&ch, h + 22, 2); memcpy(&rate, h + 24, 4); memcpy(&byterate, h + 28, 4); memcpy(&bits, h + 34, 2);
memcpy(&data_len, h + 40, 4);
if (ch != (uint16_t)ch_expect || rate != (uint32_t)rate_expect || bits != (uint16_t)bits_expect) { fclose(f); return -1; }
if (byterate != rate * ch * bits / 8) { fclose(f); return -1; }
if (data_len != (uint32_t)plen) { fclose(f); return -2; }
uint8_t *body = malloc(plen ? plen : 1);
if (!body) { fclose(f); return -1; }
size_t rd = fread(body, 1, plen, f);
fclose(f);
int ok = (rd == plen) && (memcmp(body, payload, plen) == 0);
free(body);
return ok ? 0 : -2;
}
/* 等 WAV 头出现(设备已 dev_open 写入 44B 头):**STOP 前调用**——修复 OPEN→write→STOP 过快时
* 播放线程尚未来得及 reconfigure(设备未打开,STOP 的 session_end 清 reconfig → 空 WAV)的竞态。
* 返回 0=头已出现;-1=超时。 */
static int wait_wav_open(const char *path, int tmo_ms)
{
for (int i = 0; i < tmo_ms / 50; i++) {
struct stat st;
if (stat(path, &st) < 0) { usleep(50000); continue; }
if (st.st_size >= 44) return 0;
usleep(50000);
}
return -1;
}
int main(void) int main(void)
{ {
unlink(TSOCK); unlink(TWAV); unlink(TSOCK); unlink(TWAV);
@@ -778,6 +840,246 @@ int main(void)
close(b11.fd); close(b11.fd);
stop_audiod(); stop_audiod();
/* ---- T12:24/32bit 高清流 byte-exact(A3 高清实测;FILE sink 按请求保真,S32 升位不降位)。
* ① S32@192000:open 应 grant S32(FILE/NULL sink 按 native 高保真透传);写已知 32-bit 交织帧,
* 验证 WAV 为 32-bit 且 data 段与 payload 逐字节相等(无降位/无重排)。
* ② S24@88200(32-in-32):grant S24,WAV 32-bit 逐字节等同。鉴真机 DAC 高清率由 hw_probe 决定。 */
printf("[T12 24/32bit hi-res byte-exact]\n");
unlink(TSOCK); unlink(TWAV);
g_audiod_pid = start_audiod(); /* file sink */
usleep(400000);
{
struct cli h12;
check(cli_connect(&h12) == 0, "connect H12");
m_audio_open_ack_t ack12;
check(do_open_f(&h12, 192000, 2, AUDIO_FMT_S32, 0, &ack12) == 0 && ack12.err == MEOW_OK,
"open S32@192000");
check(ack12.grant_fmt == AUDIO_FMT_S32, "grant S32 for S32 request (FILE byte-exact)");
check(ack12.grant_rate == 192000, "grant_rate=192000 (no SRC)");
uint8_t pl[4096];
size_t fs12 = 2 * 4; /* stereo S32 */
size_t nframes = sizeof pl / fs12;
uint8_t *op = pl;
for (size_t i = 0; i < nframes; i++) {
uint32_t l = (uint32_t)(((i * 7u) & 0xFFFFu) << 16);
uint32_t r = (uint32_t)((0x10000u - ((i * 7u) & 0xFFFFu)) << 16);
memcpy(op, &l, 4); op += 4; memcpy(op, &r, 4); op += 4;
}
do_write(&h12, pl, (uint32_t)sizeof pl);
char q12[1024];
do_query(&h12, q12, sizeof q12); /* 活动态快照 + 给播放线程时间重配设备 */
check(strstr(q12, "fmt=S32") != NULL, "query fmt=S32 (hi-res format exposed)");
wait_wav_open(TWAV, 3000); /* 等设备开(WAV 头已在),再 STOP */
do_ctrl(&h12, MSG_AUDIO_STOP, ack12.stream_id);
for (int i = 0; i < 300; i++) { /* ≤30s 等排空 + WAV 头回填 */
usleep(100000);
if (wav_byte_exact(TWAV, 192000, 2, 32, pl, sizeof pl) == 0) break;
}
check(wav_byte_exact(TWAV, 192000, 2, 32, pl, sizeof pl) == 0,
"S32@192000 WAV byte-exact (32-bit, no downshift)");
/* ② S24@88200(32-in-32) */
unlink(TWAV);
struct cli h12b;
check(cli_connect(&h12b) == 0, "connect H12b");
m_audio_open_ack_t ack12b;
check(do_open_f(&h12b, 88200, 2, AUDIO_FMT_S24, 0, &ack12b) == 0 && ack12b.err == MEOW_OK,
"open S24@88200");
check(ack12b.grant_fmt == AUDIO_FMT_S24, "grant S24 for S24 request (32-in-32)");
uint8_t pl2[4096];
size_t fs24 = 2 * 4;
size_t n2 = sizeof pl2 / fs24;
uint8_t *op2 = pl2;
for (size_t i = 0; i < n2; i++) {
uint32_t l = (uint32_t)((i * 11u) << 8); /* 24-bit 区在 [31:8] */
uint32_t r = (uint32_t)(0x1000000u - ((i * 11u) << 8));
memcpy(op2, &l, 4); op2 += 4; memcpy(op2, &r, 4); op2 += 4;
}
do_write(&h12b, pl2, (uint32_t)sizeof pl2);
char q12b[1024];
do_query(&h12b, q12b, sizeof q12b); /* 活动态快照:fmt 未清 */
check(strstr(q12b, "fmt=S24") != NULL, "query fmt=S24 (hi-res format exposed)");
wait_wav_open(TWAV, 3000); /* 等设备开(WAV 头已在),再 STOP */
do_ctrl(&h12b, MSG_AUDIO_STOP, ack12b.stream_id);
for (int i = 0; i < 300; i++) {
usleep(100000);
if (wav_byte_exact(TWAV, 88200, 2, 32, pl2, sizeof pl2) == 0) break;
}
check(wav_byte_exact(TWAV, 88200, 2, 32, pl2, sizeof pl2) == 0,
"S24@88200 WAV byte-exact (32-in-32)");
close(h12.fd); close(h12b.fd);
}
stop_audiod();
/* ---- T13:DoP DSD 透传(A3+DSD:fmt=DOP+flags=DSD,audiod 只透传;不做 dsd2pcm)。
* 写已知 DoP 帧(32-bit:bits[31:16]=16bit DSD,bits[15:8]=标记 0xFA(L)/0x05(R)),
* 验证 grant=DOP、WAV 32-bit 逐字节等同(透传保真)、query fmt=DoP dsd=1。 */
printf("[T13 DoP DSD passthrough]\n");
unlink(TSOCK); unlink(TWAV);
g_audiod_pid = start_audiod();
usleep(400000);
{
struct cli d13;
check(cli_connect(&d13) == 0, "connect D13");
m_audio_open_ack_t ack13;
check(do_open_f(&d13, 176400, 2, AUDIO_FMT_DOP, AUDIO_FLAG_DSD, &ack13) == 0 && ack13.err == MEOW_OK,
"open DoP@176400 (DSD64)");
check(ack13.grant_fmt == AUDIO_FMT_DOP, "grant=DoP (DOP passthrough)");
check(ack13.grant_rate == 176400, "grant_rate=176400 (container rate, no SRC)");
/* DoP 帧:16bit DSD 数据(左/右不同)+ 标记字节 */
uint8_t dop[4096];
size_t fsd = 2 * 4;
size_t nd = sizeof dop / fsd;
uint8_t *od = dop;
for (size_t i = 0; i < nd; i++) {
uint32_t wl = ((uint32_t)(i & 0xFFFFu) << 16) | (0xFAu << 8); /* 标记 0xFA=左 */
uint32_t wr = ((uint32_t)(~i & 0xFFFFu) << 16) | (0x05u << 8); /* 标记 0x05=右 */
memcpy(od, &wl, 4); od += 4; memcpy(od, &wr, 4); od += 4;
}
do_write(&d13, dop, (uint32_t)sizeof dop);
char q13[1024];
do_query(&d13, q13, sizeof q13); /* 活动态快照:fmt=DoP/dsd=1 */
check(strstr(q13, "fmt=DoP") != NULL, "query fmt=DoP");
check(strstr(q13, "dsd=1") != NULL, "query dsd=1 (DoP stream active)");
wait_wav_open(TWAV, 3000); /* 等设备开(WAV 头已在),再 STOP */
do_ctrl(&d13, MSG_AUDIO_STOP, ack13.stream_id);
for (int i = 0; i < 300; i++) {
usleep(100000);
if (wav_byte_exact(TWAV, 176400, 2, 32, dop, sizeof dop) == 0) break;
}
check(wav_byte_exact(TWAV, 176400, 2, 32, dop, sizeof dop) == 0,
"DoP WAV byte-exact (32-bit container, marker preserved)");
/* 无 DSD 标志的 DOP 请求 → 拒(INVAL):DoP 必须带 AUDIO_FLAG_DSD */
struct cli d13b;
check(cli_connect(&d13b) == 0, "connect D13b");
m_audio_open_ack_t ack13b;
check(do_open_f(&d13b, 176400, 2, AUDIO_FMT_DOP, 0, &ack13b) == 0 && ack13b.err == MEOW_ERR_INVAL,
"DoP without AUDIO_FLAG_DSD rejected (INVAL)");
close(d13.fd); close(d13b.fd);
}
stop_audiod();
/* ---- T14:DSD 失败回退 = 报错关流(案 A;不引 dsd2pcm、不静默退化)。
* output_bt=1(BT 目标)下 DoP open → MEOW_ERR_NODEV(BT/A2DP 不能承载 DoP);
* 此后普通 PCM(S16)open 仍正常(BT 目标不变),证明失败只关该 DSD 流、不殃及目标。 */
printf("[T14 DSD fallback (BT) = error/close]\n");
unlink(TSOCK); unlink(TWAV);
const char *tconf14 = "/tmp/meow_test_settings14.conf";
FILE *tf14 = fopen(tconf14, "w");
if (tf14) { fprintf(tf14, "# meow-settings 1\naudio.volume = 60\naudio.output_bt = 1\n"); fclose(tf14); }
g_audiod_pid = start_audiod_settings("file", tconf14);
usleep(400000);
{
struct cli d14;
check(cli_connect(&d14) == 0, "connect D14");
m_audio_open_ack_t ack14;
check(do_open_f(&d14, 176400, 2, AUDIO_FMT_DOP, AUDIO_FLAG_DSD, &ack14) == 0 &&
ack14.err == MEOW_ERR_NODEV, "DoP on BT target rejected (NODEV)");
/* 失败后 state 归干净(无流基线);同目标普通 PCM 正常开 */
struct cli d14b;
check(cli_connect(&d14b) == 0, "connect D14b");
m_audio_open_ack_t ack14b;
check(do_open(&d14b, 44100, 2, AUDIO_FMT_S16, &ack14b) == 0 && ack14b.err == MEOW_OK,
"PCM open still ok on BT after DSD reject");
do_ctrl(&d14b, MSG_AUDIO_STOP, ack14b.stream_id);
close(d14.fd); close(d14b.fd);
}
stop_audiod();
/* ---- T15:音量收编 v2 双组写 + 权威(A3:audiod 唯一音量权威;泛化双组写 PCM+DSD_VOLUME;
* 维持平方锥度;audio.volume 由 audiod 读写权威)。amixer 轨迹断言:一次 vol set 同发 numid=1/2(DSD)。
* 全部经 MSG_AUDIO_VOLUME —— 权威路径;host FILE sink 无真实 amixer(popen 失败仍记 trace)。 */
printf("[T15 volume v2 dual-group write + authority]\n");
unlink(TSOCK); unlink(TWAV);
const char *tconf15 = "/tmp/meow_test_settings15.conf";
FILE *tf15 = fopen(tconf15, "w");
if (tf15) { fprintf(tf15, "# meow-settings 1\naudio.volume = 60\n"); fclose(tf15); }
const char *trace15 = "/tmp/meow_amixer_trace15.txt";
unlink(trace15);
g_audiod_pid = start_audiod_trace("file", tconf15, trace15);
usleep(400000);
{
struct cli v15;
check(cli_connect(&v15) == 0, "connect V15");
m_audio_open_ack_t ack15;
check(do_open(&v15, 44100, 2, AUDIO_FMT_S16, &ack15) == 0 && ack15.err == MEOW_OK, "open V15");
/* 权威写音量(非 BT→DAC):设置 40 */
{
int32_t pct = 40;
if (sf(v15.fd, MSG_AUDIO_VOLUME, 15, &pct, sizeof pct) == 0) {
uint8_t ab[64]; uint16_t at; rf(v15.fd, &at, ab, sizeof ab);
}
}
usleep(150000);
char q15[1024];
check(do_query(&v15, q15, sizeof q15) == 0 && strstr(q15, "vol=40"),
"authority vol set -> g_vol_pct=40 (audiod authoritative)");
/* 持久化 audio.volume(audiod 读写权威,与 launcher/watchdog 同源同键) */
{
FILE *chk = fopen(tconf15, "r"); int has40 = 0; char l[128];
if (chk) { while (fgets(l, sizeof l, chk)) if (strstr(l, "audio.volume = 40")) has40 = 1; fclose(chk); }
check(has40, "audio.volume persisted = 40 (audiod authority)");
}
/* 双组写断言:trace 含 numid=1、numid=2、且 DSD_VOLUME(11) 同值(40→锥度≈40×40×192/10000≈30) */
{
char trb[4096]; int trl = 0;
int fd = open(trace15, O_RDONLY);
if (fd >= 0) { trl = (int)read(fd, trb, sizeof trb - 1); close(fd); if (trl > 0) trb[trl] = 0; }
check(trl > 0 && strstr(trb, "cset numid=1 ") && strstr(trb, "cset numid=2 ") &&
strstr(trb, "cset numid=11 "), "dual-group write: PCM numid=1/2 + DSD_VOLUME numid=11");
/* 平方锥度:pct=40 → dac = (100-40)^2*192/10000 = 69.12 → 69(±1 容差) */
int dac40 = (100 - 40) * (100 - 40) * 192 / 10000;
/* 从 trace 找 numid=2 的值,应与 dac40 一致 */
const char *p2 = strstr(trb, "cset numid=2 ");
int got2 = p2 ? atoi(p2 + strlen("cset numid=2 ")) : -1;
check(got2 == dac40, "square taper kept (numid=2 == (100-40)^2*192/10000)");
}
do_ctrl(&v15, MSG_AUDIO_STOP, ack15.stream_id);
close(v15.fd);
}
stop_audiod();
/* ---- T16:监控日志记录高清/DoP 实际率 + 格式(A3:monitor 记录实际率;DSD 标记)。
* 生命周期同 T8:流活跃期写 /tmp/audio_monitor_<bootts>.log,行含 %uHz/%uch/DoP 与 hwrate/vol。 */
printf("[T16 monitor logs hi-res/DoP rate+fmt]\n");
unlink(TSOCK); unlink(TWAV);
g_audiod_pid = start_audiod();
usleep(400000);
{
struct cli m16;
check(cli_connect(&m16) == 0, "connect M16");
/* 先清空 monitor 文件(复用同一 bootts 名;先前测试 T8-T15 已写入大量旧行),
* 保证下面的检查只针对本 DoP 流新写行(append 模式将在文件尾追加)。 */
char bootts16[64] = "";
{ int fd = open("/tmp/meow_bootts", O_RDONLY); if (fd >= 0) { ssize_t r = read(fd, bootts16, 63); close(fd); if (r > 0) bootts16[r] = 0; } }
char mp16[160]; snprintf(mp16, sizeof mp16, "/tmp/audio_monitor_%s.log", bootts16);
unlink(mp16);
m_audio_open_ack_t ack16;
check(do_open_f(&m16, 176400, 2, AUDIO_FMT_DOP, AUDIO_FLAG_DSD, &ack16) == 0 && ack16.err == MEOW_OK,
"open DoP@176400 for monitor");
uint8_t dop16[4096];
size_t fs16 = 2 * 4;
size_t nd16 = sizeof dop16 / fs16;
uint8_t *od16 = dop16;
for (size_t i = 0; i < nd16; i++) {
uint32_t wl = ((uint32_t)(i & 0xFFFFu) << 16) | (0xFAu << 8);
uint32_t wr = ((uint32_t)(~i & 0xFFFFu) << 16) | (0x05u << 8);
memcpy(od16, &wl, 4); od16 += 4; memcpy(od16, &wr, 4); od16 += 4;
}
for (int i = 0; i < 12; i++) do_write(&m16, dop16, (uint32_t)sizeof dop16);
char mb16[4096]; int ml16 = 0;
for (int i = 0; i < 30; i++) {
usleep(200000); /* 等 ≥2 个 1s 节拍写 monitor 行 */
int fd = open(mp16, O_RDONLY);
if (fd >= 0) { ml16 = (int)read(fd, mb16, sizeof mb16 - 1); close(fd); if (ml16 > 0) mb16[ml16] = 0; }
if (ml16 > 0 && strstr(mb16, "176400Hz/2ch/DoP") && strstr(mb16, "dsd=1")) break;
}
check(ml16 > 0 && strstr(mb16, "176400Hz/2ch/DoP") && strstr(mb16, "dsd=1"),
"monitor log records container rate 176400 + fmt DoP + dsd=1");
do_ctrl(&m16, MSG_AUDIO_STOP, ack16.stream_id);
close(m16.fd);
}
stop_audiod();
printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail); printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail);
return g_fail ? 1 : 0; return g_fail ? 1 : 0;
} }
+18 -9
View File
@@ -485,24 +485,32 @@ typedef struct m_picker_result {
/* ================= audiod 独占音频服务(AUDIOD_DESIGN.md) = */ /* ================= audiod 独占音频服务(AUDIOD_DESIGN.md) = */
/* PCM 格式枚举(客户端原生 fmt 与 grant_fmt 共用):1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE。 /* PCM 格式枚举(客户端原生 fmt 与 grant_fmt 共用):1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE
* v1 实装 S16/S32(S16 直通 + S32 升位);S24 按 32-in-32 处理;FLOAT v1 拒绝。 */ * 5=AUDIO_FMT_DOP(DoP DSD 透明容器格式)。v1 实装 S16/S32(S16 直通 + S32 升位);S24 按 32-in-32
* 处理;FLOAT v1 拒绝。AUDIO_FMT_DOP 为 **独立格式值**(A3+DSD 定案:避免 S24 名义(3B)/实际(4B)歧义),
* 仅在 MSG_AUDIO_OPEN.flags 带 AUDIO_FLAG_DSD 时合法;audiod 只做 DoP 容器透传(不引 dsd2pcm)。 */
enum { enum {
AUDIO_FMT_S16 = 1, AUDIO_FMT_S16 = 1,
AUDIO_FMT_S24 = 2, AUDIO_FMT_S24 = 2,
AUDIO_FMT_S32 = 3, AUDIO_FMT_S32 = 3,
AUDIO_FMT_FLOAT = 4 AUDIO_FMT_FLOAT = 4,
AUDIO_FMT_DOP = 5 /* DoP DSD(32-bit 容器:bits[31:16]=16bit DSD 数据,bits[15:8]=标记 0xFA/0x05,bits[7:0]=0),
容器率 = DSD 位率/16(DSD64→176400 / DSD128→352800 / DSD256→705600) */
}; };
/* MSG_AUDIO_OPEN.flags 位(预留曾恒 0;A3+DSD 起使用) */
#define AUDIO_FLAG_DSD 0x00000001u /* fmt==AUDIO_FMT_DOP:本流为 DoP DSD(App 侧已解码),audiod 只透传 */
/* MSG_AUDIO_OPEN ↑:请求开流(App 原生声源参数)。grant_*(ACK)回 audiod 实际授权给客户端的 /* MSG_AUDIO_OPEN ↑:请求开流(App 原生声源参数)。grant_*(ACK)回 audiod 实际授权给客户端的
* 产出格式(率/声道/位深)——客户端必须按 grant 格式产出 PCM(SDK meow_ui_audio 负责归一: * 产出格式(率/声道/位深)——客户端必须按 grant 格式产出 PCM(SDK meow_ui_audio 负责归一:
* mono→stereo 复制 / S16↔S32 定值升降位)。ch>2 或 FLOAT → ERR_INVAL。 */ * mono→stereo 复制 / S16↔S32 定值升降位)。ch>2 或 FLOAT → ERR_INVAL。fmt==AUDIO_FMT_DOP
* 须带 AUDIO_FLAG_DSD(否则 ERR_INVAL);DSD 经 DoP 透传,audiod 不引 dsd2pcm。 */
typedef struct m_audio_open { typedef struct m_audio_open {
char app_id[MEOW_APP_ID_MAX]; /* 32 */ char app_id[MEOW_APP_ID_MAX]; /* 32 */
uint32_t rate; /* 原生采样率(应用源,如 44100/48000) */ uint32_t rate; /* 原生采样率(应用源,如 44100/48000;DSD 容器率) */
uint16_t ch; /* 原生声道数(1=mono 2=stereo) */ uint16_t ch; /* 原生声道数(1=mono 2=stereo) */
uint16_t fmt; /* 原生格式(AUDIO_FMT_*) */ uint16_t fmt; /* 原生格式(AUDIO_FMT_*;DSD 用 AUDIO_FMT_DOP) */
uint32_t flags; /* 预留(0) */ uint32_t flags; /* AUDIO_FLAG_DSD 等 */
} m_audio_open_t; /* 32+4+2+2+4 = 44 B */ } m_audio_open_t; /* 32+4+2+2+4 = 44 B */
/* MSG_AUDIO_OPEN_ACK ↓:授权结果。err=MEOW_OK 才有效(stream_id>0 且 grant_* 为实际授权值); /* MSG_AUDIO_OPEN_ACK ↓:授权结果。err=MEOW_OK 才有效(stream_id>0 且 grant_* 为实际授权值);
@@ -533,8 +541,9 @@ typedef struct m_audio_route {
} m_audio_route_t; /* 4 B */ } m_audio_route_t; /* 4 B */
/* MSG_AUDIO_VOLUME ↑:音量查询/设置。pct<0(-1)=查询当前音量;0-100=设置(写 DAC numid=1/2 /* MSG_AUDIO_VOLUME ↑:音量查询/设置。pct<0(-1)=查询当前音量;0-100=设置(写 DAC numid=1/2
* + 持久化 settings.conf `audio.volume`,与 watchdog/launcher 同源同键)。BT 输出目标时 * + **DSD_VOLUME(音量收编 v2 泛化双组写,保证 DSD↔PCM 切换连续/防爆音)** + 持久化 settings.conf
* **不下发 DAC 音量反转**(音量由 AVRCP/耳机侧控,见 BLUETOOTH_FULL_OVERHAUL_PLAN §五)。 */ * `audio.volume`,与 watchdog/launcher 同源同键)。BT 输出目标时**不下发 DAC 音量反转**
* (音量由 AVRCP/耳机侧控,见 BLUETOOTH_FULL_OVERHAUL_PLAN §五)。 */
typedef struct m_audio_volume { typedef struct m_audio_volume {
int32_t pct; /* -1=查询;0-100=设置 */ int32_t pct; /* -1=查询;0-100=设置 */
} m_audio_volume_t; /* 4 B */ } m_audio_volume_t; /* 4 B */
+9
View File
@@ -38,6 +38,7 @@ typedef struct meow_audio_state {
int paused; /* 当前流是否暂停 */ int paused; /* 当前流是否暂停 */
int route_port; /* 当前路由口(-1 未知) */ int route_port; /* 当前路由口(-1 未知) */
int vol; /* 当前音量 0-100 */ int vol; /* 当前音量 0-100 */
int dsd; /* 当前流是否为 DoP DSD(fmt==AUDIO_FMT_DOP 且 audiod 已授权) */
} meow_audio_state; } meow_audio_state;
/* 被抢占回调:by_app=抢占者 app_id(非空)。ctx 由 on_preempt 注册时传入。 */ /* 被抢占回调:by_app=抢占者 app_id(非空)。ctx 由 on_preempt 注册时传入。 */
@@ -48,6 +49,10 @@ typedef void (*meow_audio_preempt_fn)(const char *by_app, void *ctx);
* MEOW_ERR_NODEV=无可用 PCM 设备;MEOW_ERR_* 常量本身即负值)。open 后客户端须按内部记录的 * MEOW_ERR_NODEV=无可用 PCM 设备;MEOW_ERR_* 常量本身即负值)。open 后客户端须按内部记录的
* grant 格式产出 PCM。 */ * grant 格式产出 PCM。 */
int meow_audio_open(int rate, int ch, int fmt); int meow_audio_open(int rate, int ch, int fmt);
/* 开流(带 flags)。flags=AUDIO_FLAG_DSD 且 fmt=AUDIO_FMT_DOP 时为 DoP DSD 流(A3+DSD 定案):
* App 侧须把 DSD 解码封装为 DoP(每样本 32-bit 容器:16bit DSD 数据 + 标记 + 0),audiod 只透传
* (不引 dsd2pcm、不做 DOP_EN 之外的任何处理)。DSD 打不开/不支持 → open 返回负错误码(报错关流)。 */
int meow_audio_open_ex(int rate, int ch, int fmt, uint32_t flags);
/* 写 PCM。pcm=native fmt/ch 样本(open 传的参数),frames=帧数;内部归一成 grant 并分批发送。 /* 写 PCM。pcm=native fmt/ch 样本(open 传的参数),frames=帧数;内部归一成 grant 并分批发送。
* 返回写入帧数;<0=流已死(被抢占/audiod 断开),App 应重新 open。阻塞(socket 背压)。 */ * 返回写入帧数;<0=流已死(被抢占/audiod 断开),App 应重新 open。阻塞(socket 背压)。 */
int meow_audio_write(const void *pcm, int frames); int meow_audio_write(const void *pcm, int frames);
@@ -57,6 +62,10 @@ int meow_audio_pause(int sid, int paused);
int meow_audio_close(int sid); int meow_audio_close(int sid);
/* 查询 audiod 快照(填充 out)。返回 0 成功;<0 失败。 */ /* 查询 audiod 快照(填充 out)。返回 0 成功;<0 失败。 */
int meow_audio_query(meow_audio_state *out); int meow_audio_query(meow_audio_state *out);
/* 设置音量 0-100(audiod 唯一权威:写 DAC numid=1/2 + DSD_VOLUME 双组保切换连续 + 持久化
* settings.conf audio.volume,与 launcher/watchdog 同源同键)。BT 输出目标时音量由 AVRCP 侧控,
* audiod 不下发 DAC 反转。返回 0 成功;<0 失败(audiod 不在)。 */
int meow_audio_set_volume(int pct);
/* 注册被抢占回调(可选;内部读线程分发)。重复调用幂等。 */ /* 注册被抢占回调(可选;内部读线程分发)。重复调用幂等。 */
void meow_audio_on_preempt(meow_audio_preempt_fn cb, void *ctx); void meow_audio_on_preempt(meow_audio_preempt_fn cb, void *ctx);
+39 -4
View File
@@ -229,7 +229,8 @@ static int fmt_bps(int fmt)
case AUDIO_FMT_S16: return 2; case AUDIO_FMT_S16: return 2;
case AUDIO_FMT_S24: case AUDIO_FMT_S24:
case AUDIO_FMT_S32: case AUDIO_FMT_S32:
case AUDIO_FMT_FLOAT: return 4; case AUDIO_FMT_FLOAT:
case AUDIO_FMT_DOP: return 4; /* DOP=32-bit DoP 容器(16bit DSD+标记+0),同 S32 宽 */
default: return 2; default: return 2;
} }
} }
@@ -262,7 +263,7 @@ static int send_write_chunk(const uint8_t *p, uint32_t len)
/* ---- 公开 API ---- */ /* ---- 公开 API ---- */
int meow_audio_open(int rate, int ch, int fmt) int meow_audio_open_ex(int rate, int ch, int fmt, uint32_t flags)
{ {
/* env 检查:调试直开 */ /* env 检查:调试直开 */
const char *de = getenv("MEOW_AUDIO_DIRECT"); const char *de = getenv("MEOW_AUDIO_DIRECT");
@@ -270,7 +271,10 @@ int meow_audio_open(int rate, int ch, int fmt)
pthread_mutex_lock(&g_mu); pthread_mutex_lock(&g_mu);
if (g_has_stream) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_BUSY; } if (g_has_stream) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_BUSY; }
if (ch > 2 || ch < 1 || rate <= 0 || fmt < AUDIO_FMT_S16 || fmt > AUDIO_FMT_FLOAT || fmt == AUDIO_FMT_FLOAT) { int dsd = (fmt == AUDIO_FMT_DOP) && (flags & AUDIO_FLAG_DSD);
int fmt_ok = (fmt == AUDIO_FMT_S16 || fmt == AUDIO_FMT_S24 ||
fmt == AUDIO_FMT_S32 || fmt == AUDIO_FMT_DOP);
if (ch > 2 || ch < 1 || rate <= 0 || !fmt_ok || (fmt == AUDIO_FMT_DOP && !dsd)) {
pthread_mutex_unlock(&g_mu); pthread_mutex_unlock(&g_mu);
return MEOW_ERR_INVAL; return MEOW_ERR_INVAL;
} }
@@ -297,7 +301,7 @@ int meow_audio_open(int rate, int ch, int fmt)
m_audio_open_t o; m_audio_open_t o;
memset(&o, 0, sizeof o); memset(&o, 0, sizeof o);
snprintf(o.app_id, sizeof o.app_id, "%s", getenv("MEOW_LOG_APP") ? getenv("MEOW_LOG_APP") : "app"); snprintf(o.app_id, sizeof o.app_id, "%s", getenv("MEOW_LOG_APP") ? getenv("MEOW_LOG_APP") : "app");
o.rate = (uint32_t)rate; o.ch = (uint16_t)ch; o.fmt = (uint16_t)fmt; o.rate = (uint32_t)rate; o.ch = (uint16_t)ch; o.fmt = (uint16_t)fmt; o.flags = flags;
uint32_t req = (g_pending == 0xFFFFFFFF) ? 1 : (g_pending + 1); /* 单调递增(单个挂起) */ uint32_t req = (g_pending == 0xFFFFFFFF) ? 1 : (g_pending + 1); /* 单调递增(单个挂起) */
g_pending = req; g_pending = req;
g_resp_len = 0; g_resp_len = 0;
@@ -322,6 +326,35 @@ int meow_audio_open(int rate, int ch, int fmt)
return (int)g_sid; return (int)g_sid;
} }
int meow_audio_open(int rate, int ch, int fmt)
{
return meow_audio_open_ex(rate, ch, fmt, 0);
}
int meow_audio_set_volume(int pct)
{
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
pthread_mutex_lock(&g_mu);
int fd = conn_locked();
if (fd < 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_NOENT; }
m_audio_volume_t v; v.pct = (int32_t)pct;
uint32_t req = (g_pending == 0xFFFFFFFF) ? 1 : (g_pending + 1);
g_pending = req;
if (send_frame(g_fd, MSG_AUDIO_VOLUME, req, &v, sizeof v) != 0) {
mark_dead_locked(); pthread_mutex_unlock(&g_mu); return MEOW_ERR_IO;
}
/* 等 ACK(3s 超时;audiod 停摆时透明失败标记流死) */
struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3;
int rc = 0;
while (g_pending == req) {
if (pthread_cond_timedwait(&g_cond, &g_mu, &ts) != 0) { rc = MEOW_ERR_TIMEOUT; break; }
}
if (g_resp_type != MSG_ACK) rc = (rc == 0) ? MEOW_ERR_IO : rc;
pthread_mutex_unlock(&g_mu);
return rc;
}
int meow_audio_write(const void *pcm, int frames) int meow_audio_write(const void *pcm, int frames)
{ {
if (!pcm || frames <= 0) return 0; if (!pcm || frames <= 0) return 0;
@@ -429,8 +462,10 @@ static int state_text_parse(const char *t, meow_audio_state *o)
if (strncmp(p + 4, "S16", 3) == 0) f = AUDIO_FMT_S16; if (strncmp(p + 4, "S16", 3) == 0) f = AUDIO_FMT_S16;
else if (strncmp(p + 4, "S24", 3) == 0) f = AUDIO_FMT_S24; else if (strncmp(p + 4, "S24", 3) == 0) f = AUDIO_FMT_S24;
else if (strncmp(p + 4, "S32", 3) == 0) f = AUDIO_FMT_S32; else if (strncmp(p + 4, "S32", 3) == 0) f = AUDIO_FMT_S32;
else if (strncmp(p + 4, "DoP", 3) == 0) f = AUDIO_FMT_DOP;
o->fmt = (uint16_t)f; o->fmt = (uint16_t)f;
} }
if ((p = strstr(t, "dsd="))) o->dsd = atoi(p + 4);
if ((p = strstr(t, "written="))) o->written_frames = (uint64_t)strtoull(p + 8, NULL, 10); if ((p = strstr(t, "written="))) o->written_frames = (uint64_t)strtoull(p + 8, NULL, 10);
if ((p = strstr(t, "played="))) o->played_frames = (uint64_t)strtoull(p + 7, NULL, 10); if ((p = strstr(t, "played="))) o->played_frames = (uint64_t)strtoull(p + 7, NULL, 10);
if ((p = strstr(t, "underrun="))) o->underrun = (uint32_t)atoi(p + 9); if ((p = strstr(t, "underrun="))) o->underrun = (uint32_t)atoi(p + 9);