/* bench/audiod_chain_sim.c — ncm 音频输出迁移(A2,SINK_AUDIOD)离线全链验证(0 网络)。 * * 起**真实** meow_audiod_host(FILE sink,假 socket=/tmp/ncm_audiod_sim.sock)作服务端, * 驱动**真实** app/play/audio_out.c(SINK_AUDIOD 默认后端)+ SDK 客户端 meow_ui_audio.c: * ① audio_out_open(44100,2,16) → audiod OPEN/OPEN_ACK(grant 授权) * ② 连续 WRITE(正弦帧)→ meow_audio_query 见 active=1 / written>0 * ③ audio_out_pause(1)/(0) → QUERY paused=1/0(停 PCM 时钟不丢流) * ④ 第二客户端裸发 OPEN 抢占 → on_preempt 回调触发 + 旧流 write 失败 * ⑤ audio_out_pause(0) 对流死自动重开 → 恢复可写 * ⑥ audio_out_close → QUERY active=0(流回收);audiod 侧 WAV 落盘非空 * * 编译(宿主;需先 ../meow_audiod/build_audiod.sh host 产出 meow_audiod_host): * cc -O2 -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \ * -Ivendor/mbedtls/include -Ivendor/jsmn -Ivendor/qrcodegen/c -Ivendor/minimp3 \ * bench/audiod_chain_sim.c app/play/audio_out.c \ * ../sdk/meow_ui_audio.c ../sdk/meow_ui_log.c -o audiod_chain_sim -lpthread -lm * 运行:./audiod_chain_sim (PASS/FAIL 计数,全过 exit 0) */ #include "../app/ncm_app_internal.h" /* audio_out_* + meow_ui.h */ #include "meow_ipc.h" /* 抢占者裸帧:MSG_AUDIO_OPEN/m_audio_open_t */ #include #include #include #include #include #include #include #include #include #include #include #include /* ---- audio_out.c / meow_ui_log.c 依赖的外部符号(本 sim 提供最小实现) ---- */ volatile int g_app_quit = 0; uint64_t meow_ui_time_ms(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; } #define SIM_SOCK "/tmp/ncm_audiod_sim.sock" #define SIM_WAV "/tmp/ncm_audiod_sim.wav" static int g_pass = 0, g_fail = 0; static void check(int cond, const char *name) { if (cond) { printf(" PASS %s\n", name); g_pass++; } else { printf(" FAIL %s\n", name); g_fail++; } } /* 抢占回调标志(SDK 读线程置位) */ static volatile int g_preempted = 0; static void sim_preempt_cb(const char *by_app, void *ctx) { (void)ctx; printf(" [cb] preempted by %s\n", by_app ? by_app : "?"); g_preempted = 1; } /* ---- 起 audiod(FILE sink 假声卡) ---- */ static pid_t g_srv = -1; static int start_audiod(void) { unlink(SIM_SOCK); unlink(SIM_WAV); pid_t p = fork(); if (p == 0) { setenv("MEOW_AUDIO_SOCK", SIM_SOCK, 1); setenv("MEOW_AUDIO_OUT", SIM_WAV, 1); setenv("MEOW_AUDIO_SINK", "file", 1); setenv("MEOW_AUDIO_FORCE", "1", 1); setenv("MEOW_AUDIO_AMIXER", "/bin/true", 1); int devnull = open("/dev/null", O_WRONLY); if (devnull >= 0) { dup2(devnull, 1); dup2(devnull, 2); } execl("../meow_audiod/meow_audiod_host", "meow_audiod_host", (char *)NULL); _exit(127); } if (p < 0) return -1; g_srv = p; for (int i = 0; i < 50; i++) { /* 等 socket 出现(≤5s) */ if (access(SIM_SOCK, F_OK) == 0) return 0; usleep(100 * 1000); } return -1; } /* ---- 抢占者:裸 IPC 客户端发 OPEN ---- */ static int wr_all(int fd, const void *p, size_t n) { const uint8_t *b = p; size_t o = 0; while (o < n) { ssize_t r = write(fd, b + o, n - o); if (r < 0) { if (errno == EINTR) continue; return -1; } o += (size_t)r; } return 0; } static int intruder_open(void) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; snprintf(a.sun_path, sizeof a.sun_path, "%s", SIM_SOCK); if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } m_audio_open_t o; memset(&o, 0, sizeof o); snprintf(o.app_id, sizeof o.app_id, "intruder"); o.rate = 44100; o.ch = 2; o.fmt = AUDIO_FMT_S16; uint8_t hdr[16]; uint32_t magic = MEOW_IPC_MAGIC, len = sizeof o, req = 99; uint16_t ver = MEOW_IPC_VERSION, type = MSG_AUDIO_OPEN; memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &type, 2); memcpy(hdr + 8, &req, 4); memcpy(hdr + 12, &len, 4); if (wr_all(fd, hdr, 16) != 0 || wr_all(fd, &o, sizeof o) != 0) { close(fd); return -1; } return fd; /* 保持连接(断开会 EOF 回收 intruder 流) */ } int main(void) { setenv("MEOW_AUDIO_SOCK", SIM_SOCK, 1); /* SDK 客户端连假 socket */ setenv("MEOW_LOG_APP", "dev.player.ncm", 1); /* audiod 状态里的 app_id */ unsetenv("NCM_AUDIO_SINK"); /* 确保走默认 SINK_AUDIOD */ unsetenv("MEOW_AUDIO_DIRECT"); if (start_audiod() != 0) { printf("FAIL: audiod_host start\n"); return 1; } printf("audiod up (sock=%s)\n", SIM_SOCK); /* ① OPEN */ meow_audio_on_preempt(sim_preempt_cb, NULL); check(audio_out_open(44100, 2, 16) == 0, "open: SINK_AUDIOD default ok"); /* ② WRITE 连续帧(~1s 正弦)+ QUERY 见活跃流 */ static short pcm[2048 * 2]; int wr_ok = 1; for (int c = 0; c < 25 && wr_ok; c++) { for (int i = 0; i < 2048; i++) { double t = (double)(c * 2048 + i) / 44100.0; short v = (short)(sin(2.0 * 3.14159265 * 440.0 * t) * 12000); pcm[i * 2] = v; pcm[i * 2 + 1] = v; } if (audio_out_write(pcm, 2048) != 2048) wr_ok = 0; usleep(10 * 1000); } check(wr_ok, "write: 25 chunks x 2048 frames ok"); meow_audio_state st; check(meow_audio_query(&st) == 0 && st.active == 1 && st.written_frames > 0, "query: active=1 written>0"); printf(" [state] app=%s sid=%u rate=%u ch=%u fmt=%u written=%llu played=%llu underrun=%u paused=%d\n", st.app_id, st.stream_id, st.rate, st.ch, st.fmt, (unsigned long long)st.written_frames, (unsigned long long)st.played_frames, st.underrun, st.paused); /* ③ PAUSE / RESUME(停时钟不丢流) */ check(audio_out_pause(1) == 0, "pause(1) ok"); usleep(100 * 1000); check(meow_audio_query(&st) == 0 && st.paused == 1, "query: paused=1"); check(audio_out_pause(0) == 0, "resume(0) ok"); usleep(100 * 1000); check(meow_audio_query(&st) == 0 && st.paused == 0, "query: paused=0"); /* ④ 抢占:intruder OPEN → 回调触发 + 本流写失败 */ int ifd = intruder_open(); check(ifd >= 0, "intruder: open sent"); for (int i = 0; i < 30 && !g_preempted; i++) usleep(100 * 1000); check(g_preempted == 1, "preempt: on_preempt callback fired"); check(audio_out_write(pcm, 128) < 0, "preempt: old stream write fails"); /* ⑤ 流死后 resume(0) 自动重开(抢回流)→ 恢复可写 */ check(audio_out_pause(0) == 0, "resume-after-preempt: reopen ok"); check(audio_out_write(pcm, 2048) == 2048, "reopened stream writable"); if (ifd >= 0) close(ifd); /* ⑥ CLOSE → 流回收 */ audio_out_close(); usleep(200 * 1000); check(meow_audio_query(&st) == 0 && st.active == 0, "close: query active=0"); struct stat sb; check(stat(SIM_WAV, &sb) == 0 && sb.st_size > 44, "audiod WAV non-empty"); if (g_srv > 0) { kill(g_srv, SIGTERM); waitpid(g_srv, NULL, 0); } printf("== audiod_chain_sim: pass=%d fail=%d ==\n", g_pass, g_fail); return g_fail ? 1 : 0; }