diff --git a/patch_v2/meow_audiod/meow_audiod b/patch_v2/meow_audiod/meow_audiod index 3cb7d08..50b8db1 100755 Binary files a/patch_v2/meow_audiod/meow_audiod and b/patch_v2/meow_audiod/meow_audiod differ diff --git a/patch_v2/meow_audiod/meow_audiod.c b/patch_v2/meow_audiod/meow_audiod.c index 43dbc1b..493315d 100644 --- a/patch_v2/meow_audiod/meow_audiod.c +++ b/patch_v2/meow_audiod/meow_audiod.c @@ -111,8 +111,9 @@ static int g_logfd = -1; static volatile sig_atomic_t g_stop = 0; /* ---- 共享态(g_mu 保护 + g_cond 唤醒;g_cond 用 CLOCK_MONOTONIC,main 中初始化) ---- */ -/* 前向声明(供 cli_free/dispatch 使用;定义见下) */ -static void g_sid_reset(void); +/* 前向声明(供 cli_free/dispatch/播放线程使用;定义见下) */ +static void session_end_locked(void); +static void session_clean_locked(void); static void _audio_mark_reconfig_needed(void); static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t g_cond; @@ -556,7 +557,8 @@ static void dac_vol_set(int pct) int t = 100 - pct, dac; if (pct <= 0) dac = 255; /* 真静音(DS1039:255=-127.5dB) */ else dac = t * t * 192 / 10000; /* 感知平方锥度:dB 衰减=(1-pct)^2×96dB */ - if (dac > 255) dac = 255; if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */ + if (dac > 255) dac = 255; + if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */ amixer_set(1, dac, 1); amixer_set(2, dac, 1); logmsg("DAC vol -> %d%% (L/R=%d)", pct, dac); @@ -1010,6 +1012,7 @@ static void *playback_thread(void *arg) if ((draining && av < (size_t)fs) || paused) { /* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */ if (draining && g_dev_open) dev_close(); + if (draining) session_clean_locked(); /* 流已死且排空完成:会话统计彻底归零 */ cond_wait_tmo(COND_WAIT_MS); continue; } @@ -1065,18 +1068,44 @@ static void cli_free(struct client *c) c->fd = -1; if (g_st.active && g_st.sid == c->sid) { logmsg("client EOF: stream %u recycled (app=%s)", (unsigned)c->sid, g_st.app_id); - g_st.active = 0; - g_sid_reset(); + session_end_locked(); } } -static void g_sid_reset(void) +/* ---- 流结束/会话回收(客户端死亡或显式 STOP 后,STATE 须回到干净无流基线) ---- + * 两段式: + * - session_end_locked():流结束标记(停止/EOF 即时调用)。置 inactive + 清会话身份 + * (sid/app_id)+ 唤醒播放线程排空 ring。**不清空 ring、不清 rate/ch/fmt**:未播完的缓冲 + * 要写完(排空)才关设备,否则 tone/播放末尾被截断;排空期间 frame_size() 仍依赖 rate/ch/fmt。 + * ring 的重置只发生在 OPEN 授予新流时(ring_rebuild)。 + * - session_clean_locked():会话统计彻底清零(流已结束且 ring 排空完毕的迟后调用)—— + * STATE 归零:rate/ch/fmt=0、written=played=0、underrun/drop/stall=0、rms/peak=-inf、lvlseq=0。 + * 此刻 rate/ch/fmt 已不再被排空引用,可安全清零。txdrop 为全局客户端级(不随流清),不动。 */ +static void session_end_locked(void) { - /* 停止/EOF 只标记流结束 + 唤醒播放线程排空 ring(**不清空 ring**:未播完的缓冲要写完 - * 才关设备,否则 tone/播放末尾被截断)。ring 的重置只发生在 OPEN 授予新流时(ring_rebuild)。 */ + g_st.active = 0; + g_st.sid = 0; + g_st.app_id[0] = 0; g_paused = 0; g_reconfig_pending = 0; pthread_cond_signal(&g_cond); } +static void session_clean_locked(void) +{ + g_st.rate = 0; + g_st.ch = 0; + g_st.fmt = 0; + g_written_frames = 0; + g_played_frames = 0; + g_underrun = 0; + g_drop_frames = 0; + g_stall = 0; + g_m_sumsq = 0; + g_m_peak = 0; + g_m_frames = 0; + g_m_win = 0; + g_lvl_rms_db10 = g_lvl_peak_db10 = METER_FLOOR_DB10; + g_lvl_seq = 0; +} /* 处理一个客户端帧(调用方已持 g_mu) */ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uint8_t *pl, uint32_t len) @@ -1176,11 +1205,9 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin pthread_cond_signal(&g_cond); else /* STOP */ { logmsg("STOP sid=%u (app=%s)", (unsigned)sid, g_st.app_id); - g_st.active = 0; - g_st.sid = 0; c->sid = 0; c->state = CLS_OPEN; - g_sid_reset(); + session_end_locked(); } } else if (type == MSG_AUDIO_STOP) { /* 非当前/已 draining 流:仅清客户端绑定 */ diff --git a/patch_v2/meow_audiod/meow_audiod_host b/patch_v2/meow_audiod/meow_audiod_host index 1e037d8..f2b8f28 100755 Binary files a/patch_v2/meow_audiod/meow_audiod_host and b/patch_v2/meow_audiod/meow_audiod_host differ diff --git a/patch_v2/meow_audiod/test_audiod_host b/patch_v2/meow_audiod/test_audiod_host index 500b513..184a555 100755 Binary files a/patch_v2/meow_audiod/test_audiod_host and b/patch_v2/meow_audiod/test_audiod_host differ diff --git a/patch_v2/meow_audiod/test_audiod_host.c b/patch_v2/meow_audiod/test_audiod_host.c index 171da57..ad1373e 100644 --- a/patch_v2/meow_audiod/test_audiod_host.c +++ b/patch_v2/meow_audiod/test_audiod_host.c @@ -370,26 +370,25 @@ int main(void) (unsigned long long)sent, (unsigned long long)(sent / fs5), probe_ok, probe_n); check(probe_n >= 10 && probe_ok == probe_n, "listener/io alive during long feed"); do_ctrl(&l5, MSG_AUDIO_STOP, ackL5.stream_id); - char expect[64]; - snprintf(expect, sizeof expect, "played=%llu", (unsigned long long)total_frames); - int drained = 0; + /* 排空证明改为「WAV 写满 + STATE 归零」双判据:流结束后 played 已由 session_clean 清零 + * (会话回收契约),不再作排空判据;WAV size==44+fed 才是「帧无丢失」的 ground truth。 */ + long wavsz5 = -1; char q5[1024]; - for (int i = 0; i < 300; i++) { /* 最多 30s 等排空 */ - if (do_query(&l5, q5, sizeof q5) == 0 && strstr(q5, expect)) { drained = 1; break; } + int clean5 = 0; + for (int i = 0; i < 300; i++) { /* 最多 30s 等排空+回填 WAV 头 */ usleep(100000); - } - check(drained, "long stream fully played (played == fed frames)"); - if (drained) - check(strstr(q5, "underrun=0") != NULL && strstr(q5, "dropped=0") != NULL, - "no underrun/drop on long stream"); - usleep(300000); /* 等 dev_close 回填 WAV 头 */ - close(l5.fd); - { + do_query(&l5, q5, sizeof q5); FILE *f = fopen(TWAV, "rb"); - long sz = -1; - if (f) { fseek(f, 0, SEEK_END); sz = ftell(f); fclose(f); } - check(sz == (long)(44 + total_bytes), "long WAV size == 44 + fed bytes"); + if (f) { fseek(f, 0, SEEK_END); wavsz5 = ftell(f); fclose(f); } + if (wavsz5 == (long)(44 + total_bytes) && strstr(q5, "active=0") && strstr(q5, "played=0")) { + clean5 = 1; break; + } } + check(clean5, "long stream fully played (WAV==fed bytes, STATE clean)"); + check(strstr(q5, "underrun=0") != NULL && strstr(q5, "dropped=0") != NULL, + "no underrun/drop on long stream"); + check(wavsz5 == (long)(44 + total_bytes), "long WAV size == 44 + fed bytes"); + close(l5.fd); /* ---- T4:tone 写 WAV(由 CLI 走完整 IPC 链路;mipsel/host CLI 共用) ---- */ printf("[T4 tone -> WAV]\n"); @@ -572,6 +571,88 @@ int main(void) close(m8.fd); stop_audiod(); + /* ---- T9:孤儿会话回收(客户端崩溃/被杀 → 会话完整回收 → STATE 归零)。 + * 复现 ncm 被内核 OOM 杀(sig=9)场景:客户端子进程 OPEN + 写正弦(使 written/played/rms/peak/ + * lvlseq 非零、流活跃),随即 SIGKILL 子进程(内核自动关 socket fd)——audiod 应检测 EOF 并完整 + * 回收:active=0/sid=0/written=0/played=0/underrun=0/rms=peak=-inf/lvlseq=0,STATE 回干净无流基线。 ---- */ + printf("[T9 orphan reclaim (client SIGKILL)]\n"); + unlink(TSOCK); unlink(TWAV); + g_audiod_pid = start_audiod(); /* file sink */ + usleep(400000); + { + int p9[2]; + if (pipe(p9) == 0) { + pid_t kid = fork(); + if (kid == 0) { + /* 子进程 = 客户端:OPEN + 写足够正弦(≥2 窗口,使电平表结算)→ 通知父进程后挂死 */ + close(p9[0]); + setenv("MEOW_AUDIO_SOCK", TSOCK, 1); + struct cli c9; + if (cli_connect(&c9) != 0) _exit(2); + m_audio_open_ack_t ack9; + if (do_open(&c9, 44100, 2, AUDIO_FMT_S16, &ack9) != 0 || ack9.err != MEOW_OK) _exit(3); + uint8_t pc9[4096]; + for (int i = 0; i < 12; i++) { + size_t w9 = gen_sine(pc9, sizeof pc9, AUDIO_FMT_S16, 1000, 440, 44100); + if (do_write(&c9, pc9, (uint32_t)w9) != 0) _exit(4); + } + char ok = 1; + if (write(p9[1], &ok, 1) != 1) _exit(5); + for (;;) pause(); /* 等父进程 SIGKILL(模拟 OOM sig=9) */ + } + close(p9[1]); + char ok = 0; + int got = (int)read(p9[0], &ok, 1); /* 等子进程写完 */ + close(p9[0]); + check(got == 1 && ok == 1, "child client wrote audio (stream active)"); + /* pre-death:流活跃 + 电平表已污染(证明待回收的会话有非零统计) */ + int pre9 = 0; + char q9b[1024] = ""; + for (int i = 0; i < 20; i++) { /* 最多 2s 等电平窗口结算 */ + struct cli q9c; + if (cli_connect(&q9c) != 0) { usleep(100000); continue; } + if (do_query(&q9c, q9b, sizeof q9b) == 0) { + int rms9 = atoi(strstr(q9b, "rms=") + 4); + int lvl9 = atoi(strstr(q9b, "lvlseq=") + 7); + if (strstr(q9b, "active=1") && rms9 > -990 && lvl9 > 0) { pre9 = 1; close(q9c.fd); break; } + } + close(q9c.fd); + usleep(100000); + } + printf(" pre-crash: %s", q9b); + check(pre9, "stream active & meter populated before crash"); + /* 模拟崩溃:SIGKILL 子进程(内核关 socket fd → audiod poll 到 EOF → 回收) */ + kill(kid, SIGKILL); + waitpid(kid, NULL, 0); + /* post-death:轮询直到 STATE 完全归零(排空后 session_clean) */ + int clean9 = 0; + char q9a[1024] = ""; + for (int i = 0; i < 40; i++) { /* 最多 4s */ + usleep(100000); + struct cli q9c; + if (cli_connect(&q9c) != 0) continue; + if (do_query(&q9c, q9a, sizeof q9a) == 0) { + int rr = atoi(strstr(q9a, "rms=") + 4); + int pp = atoi(strstr(q9a, "peak=") + 5); + int lv = atoi(strstr(q9a, "lvlseq=") + 7); + if (strstr(q9a, "active=0") && strstr(q9a, "sid=0") && + strstr(q9a, "written=0") && strstr(q9a, "played=0") && + strstr(q9a, "underrun=0") && rr <= -990 && pp <= -990 && lv == 0) { + clean9 = 1; close(q9c.fd); break; + } + } + close(q9c.fd); + } + printf(" post-crash: %s", q9a); + check(clean9, "SIGKILL orphan -> STATE fully clean (sid/written/played/rms/peak/lvlseq zeroed)"); + check(strstr(q9a, "active=0") != NULL && strstr(q9a, "app=-") != NULL, + "orphan query shows no-stream baseline"); + } else { + check(0, "pipe for T9 child"); + } + } + stop_audiod(); + printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail); return g_fail ? 1 : 0; }