541 lines
22 KiB
C
541 lines
22 KiB
C
/* 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 播放线程永久压进内核——真机长流卡死的同款路径);
|
||
* 建连后 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;
|
||
}
|