Files

576 lines
23 KiB
C
Raw Permalink 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:
case AUDIO_FMT_DOP: return 4; /* DOP=32-bit DoP 容器(16bit DSD+标记+0),同 S32 宽 */
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_ex(int rate, int ch, int fmt, uint32_t flags)
{
/* 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; }
int dsd = (fmt == AUDIO_FMT_DOP) && (flags & AUDIO_FLAG_DSD);
int fmt_ok = (fmt == AUDIO_FMT_S16 || fmt == AUDIO_FMT_S24 ||
fmt == AUDIO_FMT_S32 || fmt == AUDIO_FMT_DOP);
if (ch > 2 || ch < 1 || rate <= 0 || !fmt_ok || (fmt == AUDIO_FMT_DOP && !dsd)) {
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; o.flags = flags;
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_open(int rate, int ch, int fmt)
{
return meow_audio_open_ex(rate, ch, fmt, 0);
}
int meow_audio_set_volume(int pct)
{
if (pct < 0) pct = 0;
if (pct > 100) pct = 100;
pthread_mutex_lock(&g_mu);
int fd = conn_locked();
if (fd < 0) { pthread_mutex_unlock(&g_mu); return MEOW_ERR_NOENT; }
m_audio_volume_t v; v.pct = (int32_t)pct;
uint32_t req = (g_pending == 0xFFFFFFFF) ? 1 : (g_pending + 1);
g_pending = req;
if (send_frame(g_fd, MSG_AUDIO_VOLUME, req, &v, sizeof v) != 0) {
mark_dead_locked(); pthread_mutex_unlock(&g_mu); return MEOW_ERR_IO;
}
/* 等 ACK(3s 超时;audiod 停摆时透明失败标记流死) */
struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 3;
int rc = 0;
while (g_pending == req) {
if (pthread_cond_timedwait(&g_cond, &g_mu, &ts) != 0) { rc = MEOW_ERR_TIMEOUT; break; }
}
if (g_resp_type != MSG_ACK) rc = (rc == 0) ? MEOW_ERR_IO : rc;
pthread_mutex_unlock(&g_mu);
return rc;
}
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;
else if (strncmp(p + 4, "DoP", 3) == 0) f = AUDIO_FMT_DOP;
o->fmt = (uint16_t)f;
}
if ((p = strstr(t, "dsd="))) o->dsd = atoi(p + 4);
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;
}