Files
MeowOS-SDK/meow_ui_audio.c
T
MeowOS 7d0426f07b fix(audiod): 长流卡死根治 — 防卡死护栏全量落地(AUDIOD_DESIGN §12)
根因(级联死锁): 内核驱动 wedge(hbc3000/cs43198 时钟域族, write/PREPARE/amixer ioctl
卡死在内核) → 播放线程永不返回 → ring 填满 → IO 线程在 ring_write 无超时 cond_wait
永久睡眠 → accept 停摆(backlog 满后 CLI ECONNREFUSED) → ncm 堵 socket 写 → 全链冻结。
同效路径: popen_read_tmo 无界 waitpid 随挂死的 amixer 子进程拖死持锁线程。

修法:
- ring 写有界等待(MEOW_AUDIO_RING_WAIT_MS 缺省 5s)超时丢弃+计数(dropped=), IO 永生;
  等待期 sid 代际守卫(抢占中等待的旧流 PCM 不再注入新流 ring)
- 全部 cond 等待 timedwait 化(播放 1s 周期自愈, g_cond 改 CLOCK_MONOTONIC);
  信号处理器只置 g_stop(不再调非 async-signal-safe 的 cond_signal)
- popen 收割有界: WNOHANG 300ms → SIGKILL 300ms → D 态放弃收割(泄漏换永生);
  路由线程 amixer 调用不持 g_mu; QUERY 改报 g_route_cur 缓存(IO 热路径不再 popen)
- ALSA 改 O_NONBLOCK + poll(POLLOUT,1s): 连续 3 次停摆判 wedge → PREPARE 自救无效即
  关卡进丢弃模式(stall=), 下一路 OPEN 重开重试
- server→client 下行帧单发 write 写不全即整帧丢弃(txdrop=), 杜绝半帧错位/慢客户端堵 IO
- 主线程 watchdog: IO/播放心跳 10s 静默告警; io 线程 poll 出错不退出(listener 不丢);
  backlog 4→8; RESUME/PAUSE 补 cond 唤醒
- SDK meow_ui_audio.c + CLI: connect 非阻塞+poll 2s, SO_SNDTIMEO/RCVTIMEO 2s
  (audiod 真死时 App 报错重开, 不再永久阻塞)

验证: 宿主 38/38 PASS(旧 19 回归 + 新增 T5 长流 3min 等效 31.7MB 持续灌 played==written
underrun/dropped=0 喂流中 listener 探活 15/15; T6 wedge 故障注入生存 — 旧代码同款注入
挂死(90s timeout 强制 kill), 新代码 IO/accept/preempt/SIGTERM 全活; T7 慢客户端 tx 丢弃);
qemu-mipsel 静态二进制 status/tone 全通(written=played=13230 underrun=0);
-Wall -Wextra 0 warning(host+mipsel+SDK 双端编译)。
meow_audiod md5 86a8b6793d516c90bbd12c2997b30d8f / meow_audio c23b96490842f54324b855fd9c2467a5
(fw_content.conf 已更)。暂不部署真机(主会话统一); meow_ui.a/App 重链留主会话。
2026-09-01 10:32:15 +08:00

541 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <pthread.h>
#include <time.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
#ifdef __linux__
#include <sound/asound.h>
#include <sys/ioctl.h>
#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;
}
/* 连接 audiod。**全部有界**:connect 非阻塞 + poll 2s(对端 listen backlog 满/wedge 时
* 阻塞 connect 会把 App 播放线程永久压进内核——真机长流卡死时 ncm 卡死的同款路径);
* 建连后 SO_SNDTIMEO/RCVTIMEO 2s:audiod 停摆时 write 超时返回错误 → 标记流死,
* App 走「重新 open」恢复路径,绝不永久阻塞。 */
#define AUD_CONN_TMO_MS 2000
static int connect_path(const char *path)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
int fl = fcntl(fd, F_GETFL, 0);
if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK);
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, path, sizeof a.sun_path - 1);
int r = connect(fd, (struct sockaddr *)&a, sizeof a);
if (r < 0 && errno == EINPROGRESS) {
struct pollfd p;
p.fd = fd; p.events = POLLOUT; p.revents = 0;
if (poll(&p, 1, AUD_CONN_TMO_MS) > 0) {
int err = 0;
socklen_t el = sizeof err;
if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &el) == 0 && err != 0) { errno = err; r = -1; }
} else {
r = -1; /* 超时/错误 */
}
}
if (r < 0) { close(fd); return -1; }
if (fl >= 0) fcntl(fd, F_SETFL, fl); /* 回阻塞模式;读写超时由 SO_*TIMEO 兜底 */
struct timeval tv;
tv.tv_sec = AUD_CONN_TMO_MS / 1000; tv.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv);
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv);
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;
}