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
+533 -56

No files matched your search

+147 -34
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_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=<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 同权衡) */
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]) {