diff --git a/build_sdk.sh b/build_sdk.sh index 3c6090b..8c67ac6 100755 --- a/build_sdk.sh +++ b/build_sdk.sh @@ -10,7 +10,7 @@ AR="${AR:-/usr/bin/ar}" # 系统 GNU ar 即可归档 mipsel ELF 对象(架 rm -f meow_ui.a *.o # -I../meow_launcherd:meow_notify_ipc.c 复用共享协议头 meow_ipc.h(同一份、杜绝两端漂移) -OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media stb_image" +OBJS="meow_ui_gfx meow_ui_log meow_ui_notify meow_notify_ipc meow_ui_img meow_ui_widget meow_ui_input meow_ui_theme meow_ui_ime meow_pinyin meow_ui_sys meow_ui_vol meow_ui_activity meow_ui_global meow_ui_dialog meow_ui_config meow_ui_draw meow_ui_view meow_ui_nav meow_ui_media meow_ui_audio stb_image" CFLAGS="-Os -static -I$ROOT/patch_v2/meow_launcherd" for f in $OBJS; do echo "== cc $f ==" diff --git a/meow_ui.h b/meow_ui.h index a7acd98..62a5f61 100644 --- a/meow_ui.h +++ b/meow_ui.h @@ -870,6 +870,52 @@ int meow_font_set_tier(int tier); /* 当前字体档(初始 = MEOW_FONT_TIER_COMMON;meow_ui_init 时读 MEOW_FONT_TIER env 对齐)。 */ int meow_font_tier(void); +/* ============================================================ */ +/* §11 独占音频客户端(devdocs/plan/AUDIOD_DESIGN.md §7;meow_ui_audio.c) */ +/* ============================================================ */ +/* 说明:App 只经本 API 喂 PCM 给 meow_audiod(独占 hw:0,0 守护,svcd 托管)。audiod 不在一律 + * 失败(open 返回 MEOW_ERR_NOENT,负值),不做隐式直开回退;调试可用 env MEOW_AUDIO_DIRECT=1 走 + * 自带直接 ALSA 直开路径(非正式能力,勿依赖)。 + * 生命周期:open 取 stream_id>0 → write 喂 PCM(阻塞,socket 背压)→ pause/resume → close 释放。 + * 被抢占(后开流赢):SDK 内部读线程收 MSG_AUDIO_PREEMPTED → 调 on_preempt 回调 + 标写死 + * (后续 write 返回负值,App 应重新 open)。 */ + +/* 当前流快照(meow_audio_query 填充)。 */ +typedef struct meow_audio_state { + int active; /* 1=audiod 有活跃流(不一定是本 App 的) */ + char app_id[32]; /* 活跃流 app_id(无流为 "-") */ + uint32_t stream_id; /* 活跃流 id */ + uint32_t rate; /* 活跃流采样率 */ + uint16_t ch; /* 活跃流声道数 */ + uint16_t fmt; /* 活跃流格式(AUDIO_FMT_*) */ + uint64_t written_frames; /* 客户端写入 ring 的帧 */ + uint64_t played_frames; /* 播放线程写到设备的帧 */ + uint32_t underrun; /* xrun 计数 */ + int paused; /* 当前流是否暂停 */ + int route_port; /* 当前路由口(-1 未知) */ + int vol; /* 当前音量 0-100 */ +} meow_audio_state; + +/* 被抢占回调:by_app=抢占者 app_id(非空)。ctx 由 on_preempt 注册时传入。 */ +typedef void (*meow_audio_preempt_fn)(const char *by_app, void *ctx); + +/* 开流。rate/ch/fmt=App 声源原生参数(fmt 值见 meow_ipc.h AUDIO_FMT_*)。 + * 返回 stream_id(>0);负错误码(MEOW_ERR_NOENT=audiod 不在;MEOW_ERR_INVAL=参数非法; + * MEOW_ERR_NODEV=无可用 PCM 设备;MEOW_ERR_* 常量本身即负值)。open 后客户端须按内部记录的 + * grant 格式产出 PCM。 */ +int meow_audio_open(int rate, int ch, int fmt); +/* 写 PCM。pcm=native fmt/ch 样本(open 传的参数),frames=帧数;内部归一成 grant 并分批发送。 + * 返回写入帧数;<0=流已死(被抢占/audiod 断开),App 应重新 open。阻塞(socket 背压)。 */ +int meow_audio_write(const void *pcm, int frames); +/* 暂停(1)/恢复(0)当前流。返回 0 成功;<0 失败。 */ +int meow_audio_pause(int sid, int paused); +/* 关流(STOP + 释放)。返回 0;连接保留供下次 open。 */ +int meow_audio_close(int sid); +/* 查询 audiod 快照(填充 out)。返回 0 成功;<0 失败。 */ +int meow_audio_query(meow_audio_state *out); +/* 注册被抢占回调(可选;内部读线程分发)。重复调用幂等。 */ +void meow_audio_on_preempt(meow_audio_preempt_fn cb, void *ctx); + #ifdef __cplusplus } #endif diff --git a/meow_ui_audio.c b/meow_ui_audio.c new file mode 100644 index 0000000..e16afb8 --- /dev/null +++ b/meow_ui_audio.c @@ -0,0 +1,515 @@ +/* meow_ui_audio.c — App 侧独占音频客户端(SDK 公共件;见 meow_ui.h §11)。 + * + * 定位:App 只经本 API 喂 PCM 给 meow_audiod(独占 hw:0,0 守护)。audiod 不在一律失败 + * (open 返回 MEOW_ERR_NOENT 等负错误码),不做隐式直开回退;调试可 env MEOW_AUDIO_DIRECT=1 走自带 + * 直接 ALSA 直开(非正式能力)。 + * + * 传输:AF_UNIX SOCK_STREAM 到 audiod socket(缺省 /usr/data/meow_audio.sock;env + * MEOW_AUDIO_SOCK 覆盖,宿主/qemu)。帧格式复用 meow_ipc.h 16B 头 + m_audio_* 负载(同一份)。 + * + * 线程模型(懒连 + 内部读线程): + * - 首次 open/query 时 connect → 懒启动读线程(poll 读 audiod 下发帧)。 + * - 读线程为唯一读方:PREEMPTED → 调 on_preempt 回调 + 标写死;OPEN_ACK/STATE/ACK(匹配 + * 当前挂起请求)→ 拷贝到 g_resp、唤醒调用方(条件变量)。 + * - 写路径(write/pause/resume/close)由 App 线程在另一方向写同一 fd(全双工,互不干扰)。 + * + * 格式归一:open 返回前记录 grant_rate/grant_ch/grant_fmt;meow_audio_write 把 native(open 参数) + * 样本逐帧转成 grant 格式(mono→stereo 复制 / S16↔S32 定值升降位),分批 ≤4096B 发 MSG_AUDIO_WRITE。 + * 无 SRC:grant_rate == native rate(audiod 把 DAC 原生率设为流率)。 + */ +#include "meow_ui.h" +#include "meow_ipc.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#include +#define UI_AUDIO_HAVE_ALSA 1 +#endif + +#define AUD_SOCK_DEFAULT "/usr/data/meow_audio.sock" + +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER; +static int g_fd = -1; +static pthread_t g_thr; +static int g_thr_on = 0; +static volatile int g_quit = 0; + +/* 当前流状态 */ +static int g_has_stream = 0; +static uint32_t g_sid = 0; +static int g_dead = 0; /* 收到 PREEMPTED 后置 1:write 返回负值 */ +static int g_own_rate = 0, g_own_ch = 0, g_own_fmt = 0; /* native(open 参数) */ +static int g_grant_rate = 0, g_grant_ch = 0, g_grant_fmt = 0; + +static meow_audio_preempt_fn g_preempt_cb = NULL; +static void *g_preempt_ctx = NULL; + +/* 挂起请求(读线程分发;一次只有一个) */ +static uint32_t g_pending = 0; /* req_id,0=无挂起 */ +static uint16_t g_resp_type = 0; +static uint8_t g_resp[MEOW_IPC_MAX]; +static uint32_t g_resp_len = 0; + +/* 调试直开(env MEOW_AUDIO_DIRECT=1) */ +static int g_direct = 0; +static int g_dir_fd = -1; + +static const char *sock_path(void) +{ + const char *e = getenv("MEOW_AUDIO_SOCK"); + return (e && e[0]) ? e : AUD_SOCK_DEFAULT; +} + +static int connect_path(const char *path) +{ + 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; + strncpy(a.sun_path, path, sizeof a.sun_path - 1); + if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + return fd; +} + +static int write_all(int fd, const uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t w = write(fd, p + off, n - off); + if (w < 0) { if (errno == EINTR) continue; return -1; } + if (w == 0) return -1; + off += (size_t)w; + } + return 0; +} +static int read_all(int fd, uint8_t *p, size_t n) +{ + size_t off = 0; + while (off < n) { + ssize_t r = read(fd, p + off, n - off); + if (r < 0) { if (errno == EINTR) continue; return -1; } + if (r == 0) return -1; + off += (size_t)r; + } + return 0; +} +static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + uint32_t magic = MEOW_IPC_MAGIC; + uint16_t ver = MEOW_IPC_VERSION; + memcpy(hdr + 0, &magic, 4); + memcpy(hdr + 4, &ver, 2); + memcpy(hdr + 6, &type, 2); + memcpy(hdr + 8, &req_id, 4); + memcpy(hdr + 12, &len, 4); + if (write_all(fd, hdr, sizeof hdr) != 0) return -1; + if (len && payload && write_all(fd, payload, len) != 0) return -1; + return 0; +} +static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap) +{ + uint8_t hdr[MEOW_IPC_HDR_LEN]; + if (read_all(fd, hdr, sizeof hdr) != 0) 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 > 0 && read_all(fd, rbuf, len) != 0) return -1; + return (int)len; +} + +static void *audio_thread_main(void *arg); + +/* (调用方已持 mu)标记断线并关闭。 */ +static void mark_dead_locked(void) +{ + if (g_fd >= 0) { close(g_fd); g_fd = -1; } + g_dead = 1; +} + +/* (调用方已持 mu)懒连接 + 懒起读线程。成功 0;audiod 不在 -1。 */ +static int conn_locked(void) +{ + if (g_fd >= 0) return g_fd; + if (g_direct) return -2; /* 调试直开:不连 socket */ + const char *sp = sock_path(); + int fd = connect_path(sp); + if (fd < 0) return -1; + int one = 1; + setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + g_fd = fd; + if (!g_thr_on) { g_quit = 0; g_thr_on = 1; pthread_create(&g_thr, NULL, audio_thread_main, NULL); pthread_detach(g_thr); } + return fd; +} + +/* ---- 调试直开(MEOW_AUDIO_DIRECT=1,仅开发) ---- */ +#ifdef UI_AUDIO_HAVE_ALSA +static void pmask(struct snd_mask *m, unsigned bit) { m->bits[bit / 32] |= 1u << (bit % 32); } +static void pivset(struct snd_interval *iv, unsigned val) +{ + iv->min = iv->max = val; iv->integer = 1; iv->openmin = iv->openmax = 0; iv->empty = 0; +} +static int direct_alsa_open(int rate) +{ + int fd = open("/dev/snd/pcmC0D0p", O_RDWR); + if (fd < 0) return -1; + struct snd_pcm_hw_params p; + /* 「全放行」初始化(snd_pcm_hw_params_any 语义):memset 0 的 interval 会被内核当成 + * 「只能取 0」约束 → refine 必空 → HW_PARAMS 恒 EINVAL(audiod 同款坑,勿回退)。 */ + memset(&p, 0, sizeof p); + unsigned nm = SNDRV_PCM_HW_PARAM_LAST_MASK - SNDRV_PCM_HW_PARAM_FIRST_MASK + 1; + for (unsigned i = 0; i < nm; i++) + for (unsigned b = 0; b < sizeof p.masks[i].bits / sizeof p.masks[i].bits[0]; b++) + p.masks[i].bits[b] = ~0u; + unsigned ni = SNDRV_PCM_HW_PARAM_LAST_INTERVAL - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL + 1; + for (unsigned i = 0; i < ni; i++) { p.intervals[i].min = 0; p.intervals[i].max = ~0u; } + p.flags = 0; + /* mask 先清零再单点(pmask 只 OR):全 1 的 access 会被内核 choose 成 MMAP_INTERLEAVED + * → write() EINVAL(audiod 同款第二坑,勿回退)。 */ + struct snd_mask *am = &p.masks[SNDRV_PCM_HW_PARAM_ACCESS - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + memset(am, 0, sizeof *am); pmask(am, SNDRV_PCM_ACCESS_RW_INTERLEAVED); + struct snd_mask *fm = &p.masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + memset(fm, 0, sizeof *fm); pmask(fm, SNDRV_PCM_FORMAT_S16_LE); + struct snd_mask *sm = &p.masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK]; + memset(sm, 0, sizeof *sm); pmask(sm, SNDRV_PCM_SUBFORMAT_STD); + pivset(&p.intervals[SNDRV_PCM_HW_PARAM_CHANNELS - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL], 2); + pivset(&p.intervals[SNDRV_PCM_HW_PARAM_RATE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL], (unsigned)rate); + p.rmask = ~0u; p.info = 0; p.msbits = 0; p.rate_num = 0; p.rate_den = 0; + if (ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &p) != 0) { close(fd); return -1; } + ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0); + return fd; +} +#endif + +/* ---- 格式转换 ---- */ +static int fmt_bps(int fmt) +{ + switch (fmt) { + case AUDIO_FMT_S16: return 2; + case AUDIO_FMT_S24: + case AUDIO_FMT_S32: + case AUDIO_FMT_FLOAT: return 4; + default: return 2; + } +} +static int32_t sample_from_native(const uint8_t *p, int fmt) +{ + if (fmt == AUDIO_FMT_S16) { int16_t v; memcpy(&v, p, 2); return v; } + int32_t v; memcpy(&v, p, 4); return v; /* S32/S24(32-in-32)/FLOAT 按 32 位采样 */ +} +static void sample_to_grant(uint8_t *p, int fmt, int32_t v) +{ + if (fmt == AUDIO_FMT_S16) { int16_t x = (int16_t)v; memcpy(p, &x, 2); return; } + memcpy(p, &v, 4); +} +static int32_t convert_sample(int32_t v, int nfmt, int gfmt) +{ + int nb = (nfmt == AUDIO_FMT_S16) ? 16 : 32; + int gb = (gfmt == AUDIO_FMT_S16) ? 16 : 32; + if (gb > nb) return v << (gb - nb); + if (gb < nb) return v >> (nb - gb); + return v; +} + +/* 发一批 WRITE(≤MEOW_IPC_MAX,帧对齐;调用方已持 mu) */ +static int send_write_chunk(const uint8_t *p, uint32_t len) +{ + if (g_fd < 0) { g_dead = 1; return -1; } + if (send_frame(g_fd, MSG_AUDIO_WRITE, 0, p, len) != 0) { mark_dead_locked(); return -1; } + return 0; +} + +/* ---- 公开 API ---- */ + +int meow_audio_open(int rate, int ch, int fmt) +{ + /* env 检查:调试直开 */ + const char *de = getenv("MEOW_AUDIO_DIRECT"); + g_direct = (de && de[0] == '1') ? 1 : 0; + + pthread_mutex_lock(&g_mu); + if (g_has_stream) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_BUSY; } + if (ch > 2 || ch < 1 || rate <= 0 || fmt < AUDIO_FMT_S16 || fmt > AUDIO_FMT_FLOAT || fmt == AUDIO_FMT_FLOAT) { + pthread_mutex_unlock(&g_mu); + return MEOW_ERR_INVAL; + } + g_own_rate = rate; g_own_ch = ch; g_own_fmt = fmt; + + if (g_direct) { +#ifdef UI_AUDIO_HAVE_ALSA + int fd = direct_alsa_open(rate); + if (fd < 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_NODEV; } + g_dir_fd = fd; + g_grant_rate = rate; g_grant_ch = 2; g_grant_fmt = AUDIO_FMT_S16; + g_sid = 1; g_has_stream = 1; g_dead = 0; + pthread_mutex_unlock(&g_mu); + return 1; +#else + pthread_mutex_unlock(&g_mu); + return MEOW_ERR_NODEV; +#endif + } + + int fd = conn_locked(); + if (fd < 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_NOENT; } /* audiod 不在 */ + + m_audio_open_t o; + memset(&o, 0, sizeof o); + snprintf(o.app_id, sizeof o.app_id, "%s", getenv("MEOW_LOG_APP") ? getenv("MEOW_LOG_APP") : "app"); + o.rate = (uint32_t)rate; o.ch = (uint16_t)ch; o.fmt = (uint16_t)fmt; + uint32_t req = (g_pending == 0xFFFFFFFF) ? 1 : (g_pending + 1); /* 单调递增(单个挂起) */ + g_pending = req; + g_resp_len = 0; + if (send_frame(g_fd, MSG_AUDIO_OPEN, req, &o, sizeof o) != 0) { mark_dead_locked(); pthread_mutex_unlock(&g_mu); return MEOW_ERR_IO; } + /* 等响应(3s 超时) */ + struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; + while (g_pending == req) { + int r = pthread_cond_timedwait(&g_cond, &g_mu, &ts); + if (r != 0) { mark_dead_locked(); pthread_mutex_unlock(&g_mu); return MEOW_ERR_TIMEOUT; } + } + if (g_resp_type != MSG_AUDIO_OPEN_ACK || g_resp_len < sizeof(m_audio_open_ack_t)) { + pthread_mutex_unlock(&g_mu); + return MEOW_ERR_IO; + } + m_audio_open_ack_t a; + memcpy(&a, g_resp, sizeof a); + if (a.err != MEOW_OK) { pthread_mutex_unlock(&g_mu); return (int)a.err; } + g_sid = a.stream_id; + g_grant_rate = (int)a.grant_rate; g_grant_ch = (int)a.grant_ch; g_grant_fmt = (int)a.grant_fmt; + g_has_stream = 1; g_dead = 0; + pthread_mutex_unlock(&g_mu); + return (int)g_sid; +} + +int meow_audio_write(const void *pcm, int frames) +{ + if (!pcm || frames <= 0) return 0; + pthread_mutex_lock(&g_mu); + if (g_direct) { + size_t nb = (size_t)frames * (size_t)g_grant_ch * (size_t)fmt_bps(g_grant_fmt); + const uint8_t *p = (const uint8_t *)pcm; + size_t off = 0; + while (off < nb) { + ssize_t w = write(g_dir_fd, p + off, nb - off); + if (w < 0) { if (errno == EINTR) continue; pthread_mutex_unlock(&g_mu); return -1; } + off += (size_t)w; + } + pthread_mutex_unlock(&g_mu); + return frames; + } + if (!g_has_stream || g_dead || g_fd < 0) { pthread_mutex_unlock(&g_mu); return -1; } + /* 归一:native → grant */ + int ownfs = g_own_ch * fmt_bps(g_own_fmt); + int gfs = g_grant_ch * fmt_bps(g_grant_fmt); + static uint8_t out[MEOW_IPC_MAX]; + size_t ofs = 0; + int done_frames = 0; + const uint8_t *np = (const uint8_t *)pcm; + for (int i = 0; i < frames; i++) { + /* 取 native 帧样本 */ + int32_t s[2]; + for (int c = 0; c < g_own_ch && c < 2; c++) + s[c] = sample_from_native(np + (size_t)i * ownfs + (size_t)c * fmt_bps(g_own_fmt), g_own_fmt); + /* 整帧才能进 payload(每帧不得跨两个 WRITE 分片) */ + if (ofs + (size_t)gfs > sizeof out) { + if (send_write_chunk(out, (uint32_t)ofs) != 0) { pthread_mutex_unlock(&g_mu); return -1; } + ofs = 0; + } + /* 生成 grant 帧:own_ch==1 → 复制;own_ch==2 → 直通 */ + for (int c = 0; c < g_grant_ch && c < 2; c++) { + int32_t v = (g_own_ch == 1) ? s[0] : s[c]; + v = convert_sample(v, g_own_fmt, g_grant_fmt); + sample_to_grant(out + ofs, g_grant_fmt, v); + ofs += (size_t)fmt_bps(g_grant_fmt); + } + done_frames++; + } + if (ofs && send_write_chunk(out, (uint32_t)ofs) != 0) { pthread_mutex_unlock(&g_mu); return -1; } + pthread_mutex_unlock(&g_mu); + return done_frames; +} + +int meow_audio_pause(int sid, int paused) +{ + pthread_mutex_lock(&g_mu); + if (g_direct) { pthread_mutex_unlock(&g_mu); return 0; } + if (!g_has_stream || g_sid != (uint32_t)sid) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_NOENT; } + m_audio_stream_t s; s.stream_id = (uint32_t)sid; + uint32_t req = g_pending + 1; g_pending = req; + if (send_frame(g_fd, paused ? MSG_AUDIO_PAUSE : MSG_AUDIO_RESUME, req, &s, sizeof s) != 0) { + mark_dead_locked(); pthread_mutex_unlock(&g_mu); return MEOW_ERR_IO; + } + struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; + while (g_pending == req) { if (pthread_cond_timedwait(&g_cond, &g_mu, &ts) != 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_TIMEOUT; } } + pthread_mutex_unlock(&g_mu); + return 0; +} + +int meow_audio_close(int sid) +{ + pthread_mutex_lock(&g_mu); + if (g_direct) { g_has_stream = 0; if (g_dir_fd >= 0) close(g_dir_fd); g_dir_fd = -1; pthread_mutex_unlock(&g_mu); return 0; } + if (g_has_stream && g_sid == (uint32_t)sid) { + m_audio_stream_t s; s.stream_id = (uint32_t)sid; + uint32_t req = g_pending + 1; g_pending = req; + if (send_frame(g_fd, MSG_AUDIO_STOP, req, &s, sizeof s) == 0) { + struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; + while (g_pending == req) { if (pthread_cond_timedwait(&g_cond, &g_mu, &ts) != 0) break; } + } + g_has_stream = 0; + g_sid = 0; + g_dead = 0; + } + pthread_mutex_unlock(&g_mu); + return 0; +} + +/* 从 audiod STATE 文本解析关键字段 */ +static int state_text_parse(const char *t, meow_audio_state *o) +{ + memset(o, 0, sizeof *o); + o->route_port = -1; + const char *p; + if ((p = strstr(t, "active="))) o->active = atoi(p + 7); + if ((p = strstr(t, "app="))) { + /* app_id 只取到下一个空白(状态行是空格分隔 k=v,整体拷贝会吞掉后续字段) */ + p += 4; + size_t k = 0; + while (p[k] && p[k] != ' ' && p[k] != '\n' && p[k] != '\t' && k < sizeof o->app_id - 1) { + o->app_id[k] = p[k]; k++; + } + o->app_id[k] = 0; + } + if ((p = strstr(t, "sid="))) o->stream_id = (uint32_t)atoi(p + 4); + if ((p = strstr(t, "rate="))) o->rate = (uint32_t)atoi(p + 5); + if ((p = strstr(t, "ch="))) o->ch = (uint16_t)atoi(p + 3); + if ((p = strstr(t, "fmt="))) { + int f = 0; + if (strncmp(p + 4, "S16", 3) == 0) f = AUDIO_FMT_S16; + else if (strncmp(p + 4, "S24", 3) == 0) f = AUDIO_FMT_S24; + else if (strncmp(p + 4, "S32", 3) == 0) f = AUDIO_FMT_S32; + o->fmt = (uint16_t)f; + } + if ((p = strstr(t, "written="))) o->written_frames = (uint64_t)strtoull(p + 8, NULL, 10); + if ((p = strstr(t, "played="))) o->played_frames = (uint64_t)strtoull(p + 7, NULL, 10); + if ((p = strstr(t, "underrun="))) o->underrun = (uint32_t)atoi(p + 9); + if ((p = strstr(t, "paused="))) o->paused = atoi(p + 7); + if ((p = strstr(t, "port="))) o->route_port = atoi(p + 5); + if ((p = strstr(t, "vol="))) o->vol = atoi(p + 4); + if (o->app_id[0] == '\0') snprintf(o->app_id, sizeof o->app_id, "-"); + return 0; +} + +int meow_audio_query(meow_audio_state *out) +{ + if (!out) return MEOW_ERR_INVAL; + pthread_mutex_lock(&g_mu); + if (g_direct) { + memset(out, 0, sizeof *out); + out->active = g_has_stream; + out->stream_id = g_sid; + out->rate = (uint32_t)g_grant_rate; + out->ch = (uint16_t)g_grant_ch; + out->fmt = (uint16_t)g_grant_fmt; + out->route_port = -1; + pthread_mutex_unlock(&g_mu); + return 0; + } + int fd = conn_locked(); + if (fd < 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_NOENT; } + uint32_t req = g_pending + 1; g_pending = req; + if (send_frame(g_fd, MSG_AUDIO_QUERY, req, NULL, 0) != 0) { mark_dead_locked(); pthread_mutex_unlock(&g_mu); return MEOW_ERR_IO; } + struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3; + while (g_pending == req) { if (pthread_cond_timedwait(&g_cond, &g_mu, &ts) != 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_TIMEOUT; } } + if (g_resp_type != MSG_AUDIO_STATE) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_IO; } + g_resp[g_resp_len < MEOW_IPC_MAX - 1 ? g_resp_len : MEOW_IPC_MAX - 1] = 0; + state_text_parse((const char *)g_resp, out); + pthread_mutex_unlock(&g_mu); + return 0; +} + +void meow_audio_on_preempt(meow_audio_preempt_fn cb, void *ctx) +{ + pthread_mutex_lock(&g_mu); + g_preempt_cb = cb; + g_preempt_ctx = ctx; + int need = !g_thr_on && !g_direct; + if (need) { g_quit = 0; g_thr_on = 1; } + pthread_mutex_unlock(&g_mu); + if (need) { pthread_create(&g_thr, NULL, audio_thread_main, NULL); pthread_detach(g_thr); } +} + +/* 读线程主体:poll 读 audiod 下发帧 → 分发 PREEMPTED / 挂起响应。 + * **不**自行 connect——连接由调用方(open/query 经 conn_locked)建立并启动本线程; + * 这里只等 g_fd 就绪后 poll(避免与调用方 conn_locked 竞争出新 socket)。 */ +static void *audio_thread_main(void *arg) +{ + (void)arg; + while (!g_quit) { + pthread_mutex_lock(&g_mu); + int fd = g_fd; + pthread_mutex_unlock(&g_mu); + if (fd < 0) { usleep(300000); continue; } + + struct pollfd p; + p.fd = fd; p.events = POLLIN; p.revents = 0; + int pr = poll(&p, 1, 200); + if (pr < 0 && errno != EINTR) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (pr > 0 && (p.revents & (POLLHUP | POLLERR | POLLNVAL))) { + pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); + continue; + } + if (pr > 0 && (p.revents & POLLIN)) { + uint8_t rbuf[MEOW_IPC_MAX]; + uint16_t type; + int n = read_frame(fd, &type, rbuf, sizeof rbuf); + if (n < 0) { pthread_mutex_lock(&g_mu); mark_dead_locked(); pthread_mutex_unlock(&g_mu); continue; } + if (type == MSG_AUDIO_PREEMPTED && n >= (int)sizeof(m_audio_preempted_t)) { + m_audio_preempted_t pm; + memcpy(&pm, rbuf, sizeof pm); + meow_audio_preempt_fn cb; + void *ctx; + pthread_mutex_lock(&g_mu); + cb = g_preempt_cb; ctx = g_preempt_ctx; + if (pm.stream_id == g_sid) { g_has_stream = 0; g_dead = 1; g_sid = 0; } + pthread_mutex_unlock(&g_mu); + if (cb) cb(pm.by_app, ctx); + } else if (type == MSG_AUDIO_OPEN_ACK || type == MSG_ACK || type == MSG_AUDIO_STATE) { + pthread_mutex_lock(&g_mu); + if (g_pending) { + g_resp_type = type; + if ((uint32_t)n > MEOW_IPC_MAX) n = (int)MEOW_IPC_MAX; + memcpy(g_resp, rbuf, (size_t)n); + g_resp_len = (uint32_t)n; + g_pending = 0; + pthread_cond_signal(&g_cond); + } + pthread_mutex_unlock(&g_mu); + } + /* 其它类型丢弃 */ + } + } + pthread_mutex_lock(&g_mu); + mark_dead_locked(); + pthread_mutex_unlock(&g_mu); + return NULL; +} diff --git a/test_ui_audio_host b/test_ui_audio_host new file mode 100755 index 0000000..3890d78 Binary files /dev/null and b/test_ui_audio_host differ diff --git a/test_ui_audio_host.c b/test_ui_audio_host.c new file mode 100644 index 0000000..70a2789 --- /dev/null +++ b/test_ui_audio_host.c @@ -0,0 +1,158 @@ +#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; +}