A1: meow_audiod 独占音频服务(agent-244 骨架 + agent-246 真机收尾)
- meow_audiod:启动门控(/tmp/meow_boot_mode 非 meow exit 0)、IO/播放/路由三线程、
ring ~200ms、ALSA UAPI 独占直开、切率 mute-重配-恢复、抢占(后开赢+PREEMPTED)、
socket EOF 回收、xrun PREPARE 恢复;meow_route 轮询逻辑并入路由线程
- meow_audio CLI:status/tone/route/vol;services.d/audiod.conf(trigger=manual,on-fail)
- SDK meow_ui_audio.c 编进 meow_ui.a:懒连接/读线程分发 PREEMPTED 回调/EPIPE 标死/
无 audiod 返回 MEOW_ERR_NOENT 不隐式直开(MEOW_AUDIO_DIRECT=1 仅调试)
- launcher:确认 MeowOS 模式后 meow_svcd start audiod(double-fork best-effort 不阻塞)
- meow_route 退役:S89 预置换 audiod.conf + 清理旧 meow_route.conf(防拉起已删二进制
重启循环);fw_content.conf 删 meow_route、加 meow_audiod+meow_audio(md5 打包时填)
- 真机定位并修复 ALSA UAPI 两大坑(即 ncm 播放无声根因):
① hw_params 须全放行初始化(mask 全 1/interval [0,UINT_MAX]),memset 0 interval
等价只能取 0 → refine 必空 → HW_PARAMS 恒 EINVAL
② access/format mask 先清零再单点(pmask 只 OR),全 1 被 choose 成 MMAP_INTERLEAVED
→ write() 恒 EINVAL
- 附带修复:dev_write fd<0 防日志风暴丢 PCM 限频日志;SDK state 解析 app_id 截空白;
meow_ui.h 文档负错误码表述修正
- 验证:宿主 FILE sink 全 IPC 链+抢占+tone WAV 20/20,SDK 客户端 13/13;
qemu-mipsel 同过;真机 tone 44.1k/96k 切率 written=played、underrun=0、日志完整
- meow_ui.a 53a3edd2 → ncm 重链 d2031829(已热更到卡);launcher a2f932a1
This commit is contained in:
1 parent
1664673238
commit
6b17a2ac7a
5 files changed
+720
-1
No files matched your search
+1
-1
@@ -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 =="
|
||||
|
||||
@@ -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
|
||||
|
||||
+515
@@ -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 <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/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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <math.h>
|
||||
#include <signal.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in new issue
Block a user