feat(audiod): DAC 实时监控 — 数字域电平表(100ms窗口整数RMS/peak,deci-dBFS)+1s监控日志(TF audio_monitor_<bootts>.log,流停即停)+CLI monitor 电平条;STATE 补 rms/peak/lvlseq 键(ABI兼容)。宿主47/47,qemu+真机0.7.91 tone验证(1kHz 0.3幅:rms=-13.9 peak=-10.8dBFS,hwrate~44.1k健康)。audiod ab193a2b meow_audio 891ab264

This commit is contained in:
MeowOS committed 2026-09-01 14:25:51 +08:00
1 parent 70aec677ce
commit 9a4ea10c64
9 files changed
+377 -5

No files matched your search

+187 -2
View File
@@ -171,6 +171,25 @@ static int g_dev_open = 0;
static uint32_t g_dev_rate = 0, g_dev_ch = 0, g_dev_fmt = 0;
static int g_reconfig_pending = 0;
/* ---- 数字域电平表(DAC 实时监控;CS43198 无模拟电平回读,播放线程看 PCM 样本) ----
* 播放线程对从 ring 取出的每块 PCM 做纯整数累加(归一到 16bit 域:S24/S32 >>16),
* ~100ms 窗口(rate/10 帧)到点才结算一次 RMS/peak 并发布快照——每帧仅几次整数加乘,
* 不拖慢播放线程;无流时无数据读出即零开销。不用浮点(MIPS 软浮点昂贵)。 */
#define METER_FLOOR_DB10 (-990) /* 静音地板 -99.0 dBFS */
static uint64_t g_m_sumsq = 0; /* 窗口内样本平方和(16bit 域) */
static uint32_t g_m_peak = 0; /* 窗口峰值(0..32768) */
static uint32_t g_m_frames = 0; /* 窗口已计帧 */
static uint32_t g_m_win = 0; /* 窗口目标帧数(rate/10;0=未配置/无流) */
/* 发布快照(g_mu 保护;deci-dB,METER_FLOOR_DB10=静音) */
static int g_lvl_rms_db10 = METER_FLOOR_DB10;
static int g_lvl_peak_db10 = METER_FLOOR_DB10;
static uint32_t g_lvl_seq = 0; /* 快照代际(每次结算/开流复位递增) */
/* ---- 全面监控日志(主线程 1s 节拍写;流活跃才写,流停止即停写,无流零开销) ---- */
static int g_monfd = -1;
static uint64_t g_mon_prev_played = 0;
static int64_t g_mon_prev_ms = 0;
/* ---- 日志 ---- */
static void logmsg(const char *fmt, ...)
{
@@ -299,6 +318,73 @@ static const char *fmt_name(int fmt)
}
static int frame_size(void) { return g_st.ch * fmt_bps(g_st.fmt); }
/* 整数开平方(uint64→uint32,逐位法;替代软浮点 sqrt) */
static uint32_t isqrt64(uint64_t x)
{
uint64_t r = 0, bit = 1ULL << 62;
while (bit > x) bit >>= 2;
while (bit) {
if (x >= r + bit) { x -= r + bit; r = (r >> 1) + bit; }
else r >>= 1;
bit >>= 2;
}
return (uint32_t)r;
}
/* 线性幅度(16bit 域 0..32768)→ deci-dBFS(×10 整数,相对满幅 32768)。
* log2 = 整数位 + octave 内线性插值;最大误差 ~0.5dB,电平表/日志足够。 */
static int db10_of_lin(uint32_t v)
{
if (v == 0) return METER_FLOOR_DB10;
if (v > 32768) v = 32768;
int b = 0;
uint32_t t = v;
while (t >>= 1) b++; /* b = floor(log2(v)) ∈ 0..15 */
uint32_t frac = v - (1u << b);
/* 20*log10(2)*10 = 60.206 */
int64_t db10 = (int64_t)60206 * (b - 15) / 1000;
db10 += (int64_t)60206 * (int64_t)frac / ((int64_t)1000 << b);
if (db10 < METER_FLOOR_DB10) db10 = METER_FLOOR_DB10;
return (int)db10;
}
/* 窗口结算 + 发布快照(调用方已持 g_mu) */
static void meter_window_end(void)
{
uint64_t samples = (uint64_t)g_m_frames * (g_st.ch ? g_st.ch : 2);
uint32_t rms = samples ? isqrt64(g_m_sumsq / samples) : 0;
g_lvl_rms_db10 = db10_of_lin(rms);
g_lvl_peak_db10 = db10_of_lin(g_m_peak);
g_lvl_seq++;
g_m_sumsq = 0; g_m_peak = 0; g_m_frames = 0;
}
/* 喂一块 PCM 进电平表(调用方已持 g_mu,播放线程 ring_read 后紧跟调用)。
* 逐样本整数累加;**逐帧判窗口边界**(帧循环里一次加+比较,开销可忽略)——
* 若按块末尾才结算,块越过窗口边界的部分会混进旧窗口(宿主流失真正弦+静音
* 混合窗口实测 rms 偏差 7dB+)。fmt 为 grant 格式;S24/S32 归一 >>16。 */
static void meter_feed(const uint8_t *buf, size_t n)
{
if (!g_m_win) return;
int bps = fmt_bps(g_st.fmt);
int ch = g_st.ch ? (int)g_st.ch : 2;
size_t fs = (size_t)ch * (size_t)bps;
if (fs == 0) return;
size_t frames = n / fs;
for (size_t i = 0; i < frames; i++) {
const uint8_t *p = buf + i * fs;
for (int c = 0; c < ch; c++, p += bps) {
int32_t v;
if (bps == 2) { int16_t s; memcpy(&s, p, 2); v = s; }
else { int32_t s; memcpy(&s, p, 4); v = s >> 16; }
uint32_t a = (v < 0) ? (uint32_t)(-(int64_t)v) : (uint32_t)v;
g_m_sumsq += (uint64_t)a * a;
if (a > g_m_peak) g_m_peak = a;
}
if (++g_m_frames >= g_m_win) meter_window_end();
}
}
static size_t ring_bytes(void) /* 已缓冲字节 */
{
return (g_wpos >= g_rpos) ? (g_wpos - g_rpos) : (g_ring_cap - g_rpos + g_wpos);
@@ -936,6 +1022,7 @@ static void *playback_thread(void *arg)
size_t fs = (size_t)frame_size();
if (fs > 0) want = (want / fs) * fs;
size_t n = ring_read_locked(buf, want);
if (n) meter_feed(buf, n); /* 电平表:整数累加,窗口到点才结算 */
pthread_mutex_unlock(&g_mu);
if (n == 0) continue;
dev_write(buf, n);
@@ -1044,6 +1131,12 @@ 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;
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++;
/* 重建 ring + 标记重配 */
ring_rebuild();
_audio_mark_reconfig_needed();
@@ -1105,7 +1198,7 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
snprintf(txt, sizeof txt,
"active=%d app=%s sid=%u rate=%u ch=%u fmt=%s "
"written=%llu played=%llu underrun=%u dropped=%llu stall=%u txdrop=%u paused=%d "
"route_mode=%s port=%d vol=%d\n",
"route_mode=%s port=%d vol=%d rms=%d peak=%d lvlseq=%u\n",
g_st.active ? 1 : 0,
g_st.active ? g_st.app_id : "-",
(unsigned)g_st.sid, (unsigned)g_st.rate, (unsigned)g_st.ch,
@@ -1117,7 +1210,8 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
(unsigned)g_stall, (unsigned)g_txdrop, g_paused,
g_route_manual == -1 ? "auto" : "manual",
(g_route_manual != -1 ? g_route_manual : rb),
vol);
vol,
g_lvl_rms_db10, g_lvl_peak_db10, (unsigned)g_lvl_seq);
send_frame(c->fd, MSG_AUDIO_STATE, req_id, txt, (uint32_t)strlen(txt) + 1);
break;
}
@@ -1310,6 +1404,95 @@ static void *route_thread(void *arg)
return NULL;
}
/* ---- 全面监控日志(§8 扩展:流活跃时每 1s 一行;主线程节拍驱动,不碰播放线程) ---- */
/* deci-dB → "-13.1" 文本(静音地板显 "-inf") */
static void fmt_db10(char *out, size_t n, int v)
{
if (v <= METER_FLOOR_DB10) { snprintf(out, n, "-inf"); return; }
int neg = v < 0;
int a = neg ? -v : v;
snprintf(out, n, "%s%d.%d", neg ? "-" : "", a / 10, a % 10);
}
static void mon_close(void)
{
if (g_monfd >= 0) { close(g_monfd); g_monfd = -1; }
}
/* 主线程 1s 节拍调用。流活跃 → 懒打开 TF 卡 MeowOS/logs/audio_monitor_<bootts>.log
* (无卡降级 /tmp)并写一行(时间戳+app+参数+rms/peak dBFS+帧计数+underrun/stall/
* dropped/txdrop+路由口+FPGA 状态+音量%+hw 消费速率帧/s);流停止即停写关文件。
* 快照先取后写:文件 IO 不持 g_mu。 */
static void mon_tick(void)
{
pthread_mutex_lock(&g_mu);
int active = g_st.active;
char app[MEOW_APP_ID_MAX]; snprintf(app, sizeof app, "%s", g_st.app_id);
uint32_t sid = g_st.sid, rate = g_st.rate;
unsigned ch = g_st.ch, fmt = g_st.fmt;
uint64_t wr = g_written_frames, pl = g_played_frames, dr = g_drop_frames;
unsigned ur = g_underrun, st = g_stall, tx = g_txdrop;
int paused = g_paused, vol = g_vol_pct, rmanual = g_route_manual, rcur = g_route_cur;
int rms = g_lvl_rms_db10, pk = g_lvl_peak_db10;
pthread_mutex_unlock(&g_mu);
if (!active) { /* 流停止即停写 */
if (g_monfd >= 0) {
mon_close();
logmsg("monitor log closed (stream ended)");
}
g_mon_prev_ms = 0;
return;
}
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
int64_t now_ms = (int64_t)tp.tv_sec * 1000 + tp.tv_nsec / 1000000;
if (g_monfd < 0) { /* 懒打开(流开始才建文件) */
char ts[64];
get_bootts(ts, sizeof ts);
char path[368];
if (access(LOG_DIR_CARD, W_OK) == 0)
snprintf(path, sizeof path, "%s/audio_monitor_%s.log", LOG_DIR_CARD, ts);
else
snprintf(path, sizeof path, "/tmp/audio_monitor_%s.log", ts);
g_monfd = open(path, O_WRONLY | O_CREAT | O_APPEND, 0644);
if (g_monfd < 0) return; /* 打不开就静默降级(下拍再试) */
logmsg("monitor log -> %s", path);
const char *hdr = "# meow_audiod monitor: time | app sid rate/ch/fmt | rms/peak dBFS | written played underrun stall dropped txdrop | route fpga vol hwrate\n";
if (write(g_monfd, hdr, strlen(hdr)) < 0) { }
g_mon_prev_played = pl;
g_mon_prev_ms = now_ms;
}
int64_t dt = now_ms - g_mon_prev_ms;
uint64_t hwrate = (dt > 0 && pl >= g_mon_prev_played)
? (pl - g_mon_prev_played) * 1000 / (uint64_t)dt : 0;
g_mon_prev_played = pl;
g_mon_prev_ms = now_ms;
struct tm tm;
time_t now = time(NULL);
localtime_r(&now, &tm);
char tbuf[32];
strftime(tbuf, sizeof tbuf, "%Y-%m-%d %H:%M:%S", &tm);
char rmsb[16], pkb[16];
fmt_db10(rmsb, sizeof rmsb, rms);
fmt_db10(pkb, sizeof pkb, pk);
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 "
"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),
rmsb, pkb,
(unsigned long long)wr, (unsigned long long)pl,
ur, st, (unsigned long long)dr, tx,
port, port_name(port),
rcur >= 0 ? "armed" : "unknown",
vol, (unsigned long long)hwrate,
paused ? " paused" : "");
if (n > 0 && write(g_monfd, line, (size_t)n) < 0) { }
}
static void on_sig(int sig)
{
(void)sig;
@@ -1406,8 +1589,10 @@ int main(int argc, char **argv)
logmsg("WATCHDOG: playback thread silent %ds (active=%d written=%llu played=%llu, device wedged?)",
play_silent, act, (unsigned long long)wr, (unsigned long long)plf);
}
mon_tick(); /* 流活跃才写监控日志(无流零开销) */
}
logmsg("meow_audiod shutdown");
mon_close();
pthread_join(ti, NULL);
pthread_join(tp, NULL);
pthread_join(tr, NULL);