#define _GNU_SOURCE /* test_ui_audio_host.c — meow_ui_audio SDK 客户端宿主测试。 * * 验证: * - meow_audio_open/write/query/pause/resume/close 全链(走 audiod FILE sink) * - on_preempt 回调:第二客户端(raw)抢先 → 本 SDK 客户端收到 PREEMPTED,write 返回负值 * * 启动被测试 audiod(宿主二进制,FILE sink)。 * 编译:cc -I../meow_launcherd -I. -Os -Wall -Wextra test_ui_audio_host.c meow_ui_audio.c -lpthread -o test_ui_audio_host */ #include "meow_ui.h" #include "meow_ipc.h" #include #include #include #include #include #include #include #include #include #include #define TSOCK "/tmp/meow_audio_ui.sock" #define TWAV "/tmp/meow_audio_ui.wav" static int g_pass = 0, g_fail = 0; static void check(int c, const char *n) { if (c) { printf(" PASS %s\n", n); g_pass++; } else { printf(" FAIL %s\n", n); g_fail++; } } static int g_preempted = 0; static char g_by[64]; static void preempt_cb(const char *by_app, void *ctx) { (void)ctx; g_preempted = 1; snprintf(g_by, sizeof g_by, "%s", by_app ? by_app : "?"); } /* 小工具:raw 客户端连接 */ static int raw_connect(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", TSOCK); if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } return fd; } static int w_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 r_all(int fd, void *p, size_t n) { uint8_t *b = p; size_t o = 0; while (o < n) { ssize_t r = read(fd, b + o, n - o); if (r <= 0) return -1; o += (size_t)r; } return 0; } static int sf(int fd, uint16_t type, uint32_t req, const void *pl, uint32_t len) { uint8_t hdr[MEOW_IPC_HDR_LEN]; uint32_t magic = MEOW_IPC_MAGIC; uint16_t ver = MEOW_IPC_VERSION; memcpy(hdr, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &type, 2); memcpy(hdr + 8, &req, 4); memcpy(hdr + 12, &len, 4); if (w_all(fd, hdr, sizeof hdr)) return -1; if (len && pl && w_all(fd, pl, len)) return -1; return 0; } static int rf(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap) { uint8_t hdr[MEOW_IPC_HDR_LEN]; if (r_all(fd, hdr, sizeof hdr)) return -1; uint32_t magic, len; uint16_t ver; memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(type, hdr + 6, 2); memcpy(&len, hdr + 12, 4); if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) return -1; if (len > rcap) len = rcap; if (len && r_all(fd, rbuf, len)) return -1; return (int)len; } static int raw_open(int fd, const char *app, int rate) { m_audio_open_t o; memset(&o, 0, sizeof o); snprintf(o.app_id, sizeof o.app_id, "%s", app); o.rate = (uint32_t)rate; o.ch = 2; o.fmt = AUDIO_FMT_S16; if (sf(fd, MSG_AUDIO_OPEN, 9, &o, sizeof o)) return -1; uint8_t rbuf[64]; uint16_t rt; if (rf(fd, &rt, rbuf, sizeof rbuf) < 0 || rt != MSG_AUDIO_OPEN_ACK) return -1; m_audio_open_ack_t a; memcpy(&a, rbuf, sizeof a); return (a.err == MEOW_OK) ? (int)a.stream_id : -1; } static int g_audiod_pid = -1; static void stop_audiod(void) { if (g_audiod_pid > 0) { kill(g_audiod_pid, SIGTERM); waitpid(g_audiod_pid, NULL, 0); g_audiod_pid = -1; } } int main(void) { unlink(TSOCK); unlink(TWAV); setenv("MEOW_AUDIO_SOCK", TSOCK, 1); /* SDK 客户端连接测试 audiod 的 socket */ g_audiod_pid = fork(); if (g_audiod_pid == 0) { setenv("MEOW_AUDIO_SINK", "file", 1); setenv("MEOW_AUDIO_OUT", TWAV, 1); setenv("MEOW_AUDIO_SOCK", TSOCK, 1); setenv("MEOW_AUDIO_FORCE", "1", 1); execl("../meow_audiod/meow_audiod_host", "meow_audiod_host", (char *)0); /* SDK 测试运行于 sdk/ 目录 */ _exit(127); } usleep(300000); printf("=== meow_ui_audio SDK host test ===\n"); /* 注册回调(读线程懒启动) */ meow_audio_on_preempt(preempt_cb, NULL); /* 无 audiod 场景:断开后 open 应返回 -ENOENT(先测一次) */ int sid = meow_audio_open(44100, 2, AUDIO_FMT_S16); check(sid > 0, "open (audiod present)"); /* 写正弦(stereo:L/R 同值交织) */ static int16_t pcm[4096]; for (int i = 0; i < 2048; i++) { int16_t s = (int16_t)(sin(2.0 * M_PI * 440 * i / 44100) * 0.3 * 32767); pcm[i * 2] = s; pcm[i * 2 + 1] = s; } int wr = meow_audio_write(pcm, 2048); check(wr == 2048, "write 2048 frames"); meow_audio_state st; check(meow_audio_query(&st) == 0 && st.active == 1, "query active"); printf(" state: active=%d app=%s rate=%u written=%llu\n", st.active, st.app_id, (unsigned)st.rate, (unsigned long long)st.written_frames); check(meow_audio_pause(sid, 1) == 0, "pause"); meow_audio_query(&st); check(st.paused == 1, "query paused"); check(meow_audio_pause(sid, 0) == 0, "resume"); /* 第二客户端(raw)抢先 → 本 SDK 收 PREEMPTED,write 返回负值 */ int rfd = raw_connect(); check(rfd >= 0, "raw connect"); int rsid = raw_open(rfd, "raw-app", 96000); check(rsid > 0, "raw open (preempt)"); usleep(200000); check(g_preempted == 1, "on_preempt fired"); printf(" preempted by %s\n", g_by); check(meow_audio_write(pcm, 2048) < 0, "write after preempt -> <0 (dead)"); check(meow_audio_query(&st) == 0, "query still works"); /* close 旧 sid(已死,应无副作用) */ check(meow_audio_close(sid) == 0, "close dead sid"); check(meow_audio_pause(sid, 1) < 0 || meow_audio_pause(sid, 1) == -MEOW_ERR_NOENT, "pause dead sid -> error"); close(rfd); stop_audiod(); printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail); return g_fail ? 1 : 0; }