diff --git a/patch_v2/meow_audiod/meow_audio.c b/patch_v2/meow_audiod/meow_audio.c index 9461cb9..8fa40dc 100644 --- a/patch_v2/meow_audiod/meow_audio.c +++ b/patch_v2/meow_audiod/meow_audio.c @@ -139,7 +139,8 @@ static int fmt_bps(int fmt) case AUDIO_FMT_S16: return 2; case AUDIO_FMT_S24: case AUDIO_FMT_S32: - case AUDIO_FMT_FLOAT: return 4; + case AUDIO_FMT_FLOAT: + case AUDIO_FMT_DOP: return 4; default: return 2; } } @@ -150,6 +151,7 @@ static const char *fmt_name(int fmt) case AUDIO_FMT_S24: return "S24"; case AUDIO_FMT_S32: return "S32"; case AUDIO_FMT_FLOAT: return "FLOAT"; + case AUDIO_FMT_DOP: return "DoP"; 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; } -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; memset(&o, 0, sizeof o); snprintf(o.app_id, sizeof o.app_id, "audio-cli"); o.rate = (uint32_t)rate; o.ch = 2; - o.fmt = AUDIO_FMT_S16; + o.fmt = (uint16_t)fmt; uint8_t rbuf[512]; uint16_t rt; 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; } @@ -386,7 +388,7 @@ static int cmd_tone(int freq, int ms, int rate) static void usage(const char *argv0) { - fprintf(stderr, "usage: %s status | tone [freq] [ms] [rate] | route <0|2|3> | vol [0-100] | output | monitor [秒]\n" + fprintf(stderr, "usage: %s status | tone [freq] [ms] [rate] [S16|S24|S32] | route <0|2|3> | vol [0-100] | output | monitor [秒]\n" " output: 切换输出目标(dac=本地 hw:0,0 / bt=蓝牙 A2DP source;bt 时音量=AVRCP 侧控)\n" " monitor: 终端实时电平表(200ms 刷新+文本电平条;缺省 10s,0=持续到 Ctrl-C)\n" " 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 ms = (argc >= 4) ? atoi(argv[3]) : 500; 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 (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")) { int secs = (argc >= 3) ? atoi(argv[2]) : 10; if (secs < 0) secs = 10; diff --git a/patch_v2/meow_audiod/meow_audiod.c b/patch_v2/meow_audiod/meow_audiod.c index c587879..de00b07 100644 --- a/patch_v2/meow_audiod/meow_audiod.c +++ b/patch_v2/meow_audiod/meow_audiod.c @@ -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_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 long long g_test_wedge_after = -1; /* dev_write 累计字节≥此值后模拟设备 wedge(睡到 g_stop) */ 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 保护) */ 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_open = 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; /* ---- 蓝牙 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_S24: 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; } } @@ -334,6 +337,7 @@ static const char *fmt_name(int fmt) case AUDIO_FMT_S24: return "S24"; case AUDIO_FMT_S32: return "S32"; case AUDIO_FMT_FLOAT: return "FLOAT"; + case AUDIO_FMT_DOP: return "DoP"; default: return "?"; } } @@ -345,12 +349,17 @@ static const char *target_name(void) return (g_target == AUDIO_OUT_BT) ? "bt" : "dac"; } -/* 授权给客户端的硬件最优格式。BT(A2DP source)=S16(codec 基座,pcm.bluealsa 前置 plug 层自动转 - * codec 协商率);DAC=hw 探测最优(FILE/NULL 宿主=S16)。 */ -static int target_grant_fmt(void) +/* 授权给客户端的硬件最优格式。native_fmt=客户端请求格式。BT(A2DP source)=S16(codec 基座, + * pcm.bluealsa 前置 plug 层自动转 codec 协商率);DAC=hw 探测最优(S32 升位/S16 直通); + * 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; - 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 侧控。 */ @@ -570,6 +579,17 @@ static int amixer_set(int numid, int val, int card) g_amixer, (card ? " -c 0" : ""), numid, val); char *o = popen_read_tmo(1500, cmd); if (!o) return -1; + /* 测试钩子(仅 env MEOW_AUDIO_AMIXER_TRACE=):把每次 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 同权衡) */ return 0; } @@ -592,6 +612,15 @@ static int amixer_get(int numid, int card) 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) { 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 */ if (dac > 255) dac = 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(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) @@ -610,6 +642,30 @@ static void dac_vol_mute(void) * 旧版写 0 在闪退/重配瞬间造成满幅炸音(真机事故 2026-09-01)。真静音写 255。 */ amixer_set(1, 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) @@ -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_S24: return SNDRV_PCM_FORMAT_S24_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; 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) { if (g_sink != SINK_ALSA) { g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; return 0; } if (g_hw_probed) return 0; #ifdef AUDIOD_HAVE_ALSA - int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); - 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; } - close(fd); + 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); + if (fd >= 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); + } } - fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); - 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; } - close(fd); + for (int r = 0; r < 3 && !g_hw_probed; r++) { + int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); + if (fd >= 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); + } } #endif - g_hw_fmt = AUDIO_FMT_S16; - g_hw_probed = 1; - return -1; + if (!g_hw_probed) { g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; } + logmsg("hw probe: optimal fmt=%s", fmt_name(g_hw_fmt)); + return 0; } /* ---- FILE/NULL sink ---- */ @@ -1360,26 +1428,50 @@ static void dev_close(void) 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) { uint32_t rate = g_st.rate, ch = g_st.ch, fmt = g_st.fmt; 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); int saved_vol = g_vol_pct; - /* 防爆音仅 DAC(重配前静音 + 复原);BT 独立目标,跳过 DAC 音量(音量由 AVRCP 侧控)。 */ - if (g_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA) dac_vol_mute(); + int new_dsd = (fmt == AUDIO_FMT_DOP); + 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(); g_dev_fd = dev_open(); if (g_dev_fd >= 0 || (g_sink == SINK_NULL && g_dev_fd == -2)) { g_dev_open = 1; 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_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA) dac_vol_set(saved_vol); /* 复原 */ - logmsg("device open ok: %u/%u/%s sink=%d target=%s", (unsigned)rate, (unsigned)ch, - fmt_name((int)fmt), g_sink, tgt); + g_dev_dsd = (do_dsd_seq && new_dsd); + if (g_target == AUDIO_OUT_DAC && g_sink == SINK_ALSA) dac_vol_set(saved_vol); /* 复原(含 DSD_VOLUME) */ + 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 { logmsg("device open FAILED !!"); + g_dev_dsd = 0; } g_reconfig_pending = 0; } @@ -1408,6 +1500,11 @@ static void *playback_thread(void *arg) if ((draining && av < (size_t)fs) || paused) { /* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */ 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(); /* 流已死且排空完成:会话统计彻底归零 */ cond_wait_tmo(COND_WAIT_MS); continue; @@ -1490,6 +1587,8 @@ static void session_clean_locked(void) g_st.rate = 0; g_st.ch = 0; g_st.fmt = 0; + g_dsd_active = 0; /* 流级 DSD 位归零(DOP_EN 设备位由 drain/reconfigure 时序负责) */ + g_dev_dsd = 0; g_written_frames = 0; g_played_frames = 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; } m_audio_open_t 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; 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; } /* 输出目标=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; } } - 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++; /* 抢占:已有当前流 → 旧客户端收 PREEMPTED */ 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_stall = 0; g_dev_bytes = 0; /* 测试钩子计数随新流归零 */ - /* 电平表复位 + 窗口目标(~100ms) */ - g_m_win = g_st.rate / 10; - if (!g_m_win) g_m_win = 1; + /* 电平表复位 + 窗口目标(~100ms;DoP DSD 无 PCM 意义,窗口=0 即跳过——meter_feed early-return) */ + g_m_win = (grant_fmt == AUDIO_FMT_DOP) ? 0 : (g_st.rate / 10); + 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_lvl_rms_db10 = g_lvl_peak_db10 = METER_FLOOR_DB10; 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 vol = g_vol_pct; 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 " "route_mode=%s port=%d vol=%d volctl=%s rms=%d peak=%d lvlseq=%u " "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 : "-", (unsigned)g_st.sid, (unsigned)g_st.rate, (unsigned)g_st.ch, g_st.active ? fmt_name((int)g_st.fmt) : "-", + g_dsd_active ? 1 : 0, (unsigned long long)g_written_frames, (unsigned long long)g_played_frames, (unsigned)g_underrun, @@ -1934,10 +2044,12 @@ static void mon_tick(void) int port = (rmanual != -1) ? rmanual : rcur; char line[512]; 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 " "route=%d(%s) fpga=%s vol=%d%% hwrate=%lluf/s%s\n", tbuf, app, (unsigned)sid, (unsigned)rate, ch, fmt_name((int)fmt), + (fmt == AUDIO_FMT_DOP ? 1 : 0), rmsb, pkb, (unsigned long long)wr, (unsigned long long)pl, 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_TARGET (0=dac 1=bt, 覆盖 settings audio.output_bt)\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); 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_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_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); const char *sink = getenv("MEOW_AUDIO_SINK"); if (sink && sink[0]) { diff --git a/patch_v2/meow_audiod/test_audiod_host.c b/patch_v2/meow_audiod/test_audiod_host.c index aa101a9..9636357 100644 --- a/patch_v2/meow_audiod/test_audiod_host.c +++ b/patch_v2/meow_audiod/test_audiod_host.c @@ -27,6 +27,7 @@ #include #include #include +#include #define TSOCK "/tmp/meow_audio_test.sock" #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; } -/* 直接客户端 open */ -static int do_open(struct cli *c, int rate, int ch, int fmt, m_audio_open_ack_t *ack) +/* 带 amixer 追踪的启动(A3/DSD:断言「音量双组写」PCM+DSD_VOLUME 成对下发)。settings_path 可空。 */ +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); 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; uint8_t rbuf[256]; uint16_t rt; 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); 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) { @@ -266,6 +289,45 @@ static int verify_wav(const char *path, int rate_expect, int ch_expect, int bits 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) { unlink(TSOCK); unlink(TWAV); @@ -778,6 +840,246 @@ int main(void) close(b11.fd); 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_.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); return g_fail ? 1 : 0; -} +} \ No newline at end of file diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index 2a50eab..027160b 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -485,24 +485,32 @@ typedef struct m_picker_result { /* ================= audiod 独占音频服务(AUDIOD_DESIGN.md) = */ -/* 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 拒绝。 */ +/* PCM 格式枚举(客户端原生 fmt 与 grant_fmt 共用):1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE + * 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 { AUDIO_FMT_S16 = 1, AUDIO_FMT_S24 = 2, 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 实际授权给客户端的 * 产出格式(率/声道/位深)——客户端必须按 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 { 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 fmt; /* 原生格式(AUDIO_FMT_*) */ - uint32_t flags; /* 预留(0) */ + uint16_t fmt; /* 原生格式(AUDIO_FMT_*;DSD 用 AUDIO_FMT_DOP) */ + uint32_t flags; /* AUDIO_FLAG_DSD 等 */ } m_audio_open_t; /* 32+4+2+2+4 = 44 B */ /* 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 */ /* MSG_AUDIO_VOLUME ↑:音量查询/设置。pct<0(-1)=查询当前音量;0-100=设置(写 DAC numid=1/2 - * + 持久化 settings.conf `audio.volume`,与 watchdog/launcher 同源同键)。BT 输出目标时 - * **不下发 DAC 音量反转**(音量由 AVRCP/耳机侧控,见 BLUETOOTH_FULL_OVERHAUL_PLAN §五)。 */ + * + **DSD_VOLUME(音量收编 v2 泛化双组写,保证 DSD↔PCM 切换连续/防爆音)** + 持久化 settings.conf + * `audio.volume`,与 watchdog/launcher 同源同键)。BT 输出目标时**不下发 DAC 音量反转** + * (音量由 AVRCP/耳机侧控,见 BLUETOOTH_FULL_OVERHAUL_PLAN §五)。 */ typedef struct m_audio_volume { int32_t pct; /* -1=查询;0-100=设置 */ } m_audio_volume_t; /* 4 B */ diff --git a/patch_v2/sdk/include/meow_ui_audio.h b/patch_v2/sdk/include/meow_ui_audio.h index 15b7a07..dc0ee7c 100644 --- a/patch_v2/sdk/include/meow_ui_audio.h +++ b/patch_v2/sdk/include/meow_ui_audio.h @@ -38,6 +38,7 @@ typedef struct meow_audio_state { int paused; /* 当前流是否暂停 */ int route_port; /* 当前路由口(-1 未知) */ int vol; /* 当前音量 0-100 */ + int dsd; /* 当前流是否为 DoP DSD(fmt==AUDIO_FMT_DOP 且 audiod 已授权) */ } meow_audio_state; /* 被抢占回调: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 后客户端须按内部记录的 * grant 格式产出 PCM。 */ 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 并分批发送。 * 返回写入帧数;<0=流已死(被抢占/audiod 断开),App 应重新 open。阻塞(socket 背压)。 */ 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); /* 查询 audiod 快照(填充 out)。返回 0 成功;<0 失败。 */ 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); diff --git a/patch_v2/sdk/meow_ui_audio.c b/patch_v2/sdk/meow_ui_audio.c index eb7a008..03e24b0 100644 --- a/patch_v2/sdk/meow_ui_audio.c +++ b/patch_v2/sdk/meow_ui_audio.c @@ -229,7 +229,8 @@ static int fmt_bps(int fmt) case AUDIO_FMT_S16: return 2; case AUDIO_FMT_S24: 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; } } @@ -262,7 +263,7 @@ static int send_write_chunk(const uint8_t *p, uint32_t len) /* ---- 公开 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 检查:调试直开 */ 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); 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); return MEOW_ERR_INVAL; } @@ -297,7 +301,7 @@ int meow_audio_open(int rate, int ch, int fmt) m_audio_open_t o; memset(&o, 0, sizeof o); 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); /* 单调递增(单个挂起) */ g_pending = req; g_resp_len = 0; @@ -322,6 +326,35 @@ int meow_audio_open(int rate, int ch, int fmt) 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) { 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; 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, "DoP", 3) == 0) f = AUDIO_FMT_DOP; 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, "played="))) o->played_frames = (uint64_t)strtoull(p + 7, NULL, 10); if ((p = strstr(t, "underrun="))) o->underrun = (uint32_t)atoi(p + 9);