Files
MeowOS-SDK/sdk/meow_ui_audio.c
T
AskaEth c0396e40ce sync(SDK): 增量同步主仓 M6.37–M6.39 至公开 SDK 仓
sdk/:
- 同步 meow_ui_widget/input/gfx/lazygrid/audio/media/img 等源码与头
  (SDK 默认列表/容器控件默认 MD3 — meow_list_set_container 可回退;
   全局 App 内底部上滑回桌面 — meow_ui_input_is_home_swipe 系统保留手势;
   LazyGrid/LazyColumn/LazyRow MD3 化;叠加让位自抑改 app_id 判定;
   DSD 音频格式等)
- 新增独立模块源码 meow_img_color / meow_lyric / meow_lyric_view 及对应宿主 test_*.c
- 保留公开仓自包含适配:vendored meow_font.c/meow_font.h/meow_notif.h 不动;
  build_sdk.sh 自包含版本不动;meow_ui_gfx.c 仅改 meow_font.h include 路径;
  meow_ui_internal.h 去除 FreeType 内部声明
- meow_ipc.h 同步到主仓 launcher 版本(含 AUDIO_FMT_DOP/媒体歌词/蓝牙输出协议)
- 不含 FreeType 矢量字 meow_ft_*(需 freetype vendor,公开仓以位图 meow_font
  自包含);不含 mipsel 二进制 / .o / .a / 固件 / 密钥 / devdocs

examples/hello/:
- 新增 ui/demo/sdk2/sdk2.c;build_hello.sh 纳入该文件(保持自包含)

docs / README / .gitignore:
- docs/MEOW_APP_SDK.md 补 §12.5(默认 MD3 容器 + 底部上滑回桌面 + 两个新 API)
- README 能力清单补「默认 MD3 容器」与「底部上滑回桌面」
- .gitignore 补新增宿主测试二进制

验证:sdk/meow_ui.a 与 examples/hello/hello 交叉编译均 0 warning;工作区无生成物。
2026-09-04 18:05:36 +08:00

576 lines
23 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:
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;
}