- 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
311 lines
12 KiB
C
311 lines
12 KiB
C
#define _GNU_SOURCE
|
||
|
||
/* test_audiod_host.c — meow_audiod 宿主集成测试(FILE sink,无 /dev/snd 环境)。
|
||
*
|
||
* 验证 AUDIOD_DESIGN.md 里程碑 A1:
|
||
* - 全 IPC 链路:OPEN→WRITE→QUERY→PAUSE/RESUME→STOP→EOF 回收
|
||
* - 两个客户端抢占:旧流收 MSG_AUDIO_PREEMPTED(PCM 丢弃)
|
||
* - tone 生成 WAV 可核验正弦(440Hz,帧数/过零率粗检)
|
||
*
|
||
* 启动被测试 audiod(宿主二进制,env MEOW_AUDIO_SINK=file / MEOW_AUDIO_OUT=<wav> /
|
||
* MEOW_AUDIO_SOCK=<sock> / MEOW_AUDIO_FORCE=1 跳过门控)。
|
||
*
|
||
* 编译(宿主):cc -I../meow_launcherd -Os -Wall -Wextra test_audiod_host.c -o test_audiod_host
|
||
*/
|
||
#include "meow_ipc.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
#include <math.h>
|
||
#include <poll.h>
|
||
#include <signal.h>
|
||
#include <time.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
#include <sys/wait.h>
|
||
|
||
#define TSOCK "/tmp/meow_audio_test.sock"
|
||
#define TWAV "/tmp/meow_audio_test.wav"
|
||
|
||
static int g_audiod_pid = -1;
|
||
static int g_pass = 0, g_fail = 0;
|
||
|
||
static void check(int cond, const char *name)
|
||
{
|
||
if (cond) { printf(" PASS %s\n", name); g_pass++; }
|
||
else { printf(" FAIL %s\n", name); g_fail++; }
|
||
}
|
||
|
||
/* ---- 原始 IPC 客户端 ---- */
|
||
struct cli { int fd; };
|
||
static int cli_connect(struct cli *c)
|
||
{
|
||
c->fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (c->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(c->fd, (struct sockaddr *)&a, sizeof a) < 0) { close(c->fd); return -1; }
|
||
return 0;
|
||
}
|
||
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;
|
||
}
|
||
/* 带超时读一帧(poll) */
|
||
static int rf_tmo(int fd, int tmo_ms, uint16_t *type, uint8_t *rbuf, uint32_t rcap)
|
||
{
|
||
struct pollfd p; p.fd = fd; p.events = POLLIN; p.revents = 0;
|
||
if (poll(&p, 1, tmo_ms) <= 0) return -2;
|
||
return rf(fd, type, rbuf, rcap);
|
||
}
|
||
|
||
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; }
|
||
}
|
||
|
||
static int start_audiod(void)
|
||
{
|
||
pid_t pid = fork();
|
||
if (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_host", "meow_audiod_host", (char *)0);
|
||
_exit(127);
|
||
}
|
||
return pid;
|
||
}
|
||
|
||
/* 直接客户端 open */
|
||
static int do_open(struct cli *c, int rate, int ch, int fmt, m_audio_open_ack_t *ack)
|
||
{
|
||
m_audio_open_t o; memset(&o, 0, sizeof o);
|
||
snprintf(o.app_id, sizeof o.app_id, "test");
|
||
o.rate = (uint32_t)rate; o.ch = (uint16_t)ch; o.fmt = (uint16_t)fmt; o.flags = 0;
|
||
if (sf(c->fd, MSG_AUDIO_OPEN, 1, &o, sizeof o)) return -1;
|
||
uint8_t rbuf[256]; uint16_t rt;
|
||
if (rf(c->fd, &rt, rbuf, sizeof rbuf) < 0) return -1;
|
||
if (rt != MSG_AUDIO_OPEN_ACK) return -2;
|
||
memcpy(ack, rbuf, sizeof *ack);
|
||
return 0;
|
||
}
|
||
|
||
static int do_write(struct cli *c, const void *pcm, uint32_t len)
|
||
{
|
||
return sf(c->fd, MSG_AUDIO_WRITE, 0, pcm, len);
|
||
}
|
||
static int do_ctrl(struct cli *c, uint16_t type, uint32_t sid)
|
||
{
|
||
m_audio_stream_t s; s.stream_id = sid;
|
||
if (sf(c->fd, type, 2, &s, sizeof s)) return -1;
|
||
uint8_t rbuf[64]; uint16_t rt;
|
||
if (rf(c->fd, &rt, rbuf, sizeof rbuf) < 0) return -1;
|
||
return (rt == MSG_ACK) ? 0 : -2;
|
||
}
|
||
static int do_query(struct cli *c, char *out, size_t n)
|
||
{
|
||
if (sf(c->fd, MSG_AUDIO_QUERY, 3, NULL, 0)) return -1;
|
||
uint8_t rbuf[MEOW_IPC_MAX]; uint16_t rt;
|
||
int r = rf(c->fd, &rt, rbuf, sizeof rbuf);
|
||
if (r < 0 || rt != MSG_AUDIO_STATE) return -1;
|
||
size_t m = ((size_t)r < n - 1) ? (size_t)r : (n - 1);
|
||
memcpy(out, rbuf, m);
|
||
out[m] = 0;
|
||
return 0;
|
||
}
|
||
|
||
/* 生成正弦 PCM(S16 or S32,stereo 交织),返回字节数 */
|
||
static size_t gen_sine(uint8_t *buf, size_t cap, int fmt, int frames, double freq, int rate)
|
||
{
|
||
int bps = (fmt == AUDIO_FMT_S16) ? 2 : 4;
|
||
size_t fs = 2; /* 双声道 */
|
||
size_t n = (frames > (int)(cap / (fs * bps))) ? (cap / (fs * bps)) : (size_t)frames;
|
||
size_t o = 0;
|
||
for (size_t i = 0; i < n; i++) {
|
||
double ph = 2.0 * M_PI * freq * (double)i / (double)rate;
|
||
int16_t s = (int16_t)(sin(ph) * 0.3 * 32767);
|
||
for (int c = 0; c < 2; c++) {
|
||
int32_t v = (fmt == AUDIO_FMT_S16) ? (int32_t)s : ((int32_t)s << 16);
|
||
if (fmt == AUDIO_FMT_S16) { memcpy(buf + o, &s, 2); o += 2; }
|
||
else { memcpy(buf + o, &v, 4); o += 4; }
|
||
}
|
||
}
|
||
return o;
|
||
}
|
||
|
||
/* ---- WAV 校验:合法 + 近似正弦(逐帧过零/幅度粗检) ---- */
|
||
static int verify_wav(const char *path, int rate_expect, int ch_expect, int bits_expect)
|
||
{
|
||
FILE *f = fopen(path, "rb");
|
||
if (!f) return -1;
|
||
unsigned char h[44];
|
||
if (fread(h, 1, 44, f) != 44) { fclose(f); return -1; }
|
||
if (memcmp(h, "RIFF", 4) || memcmp(h + 8, "WAVE", 4) || memcmp(h + 12, "fmt ", 4)) { fclose(f); return -1; }
|
||
uint16_t ch, bits; uint32_t rate, byterate;
|
||
memcpy(&ch, h + 22, 2); memcpy(&rate, h + 24, 4); memcpy(&byterate, h + 28, 4); memcpy(&bits, h + 34, 2);
|
||
if (ch != ch_expect || rate != (uint32_t)rate_expect || bits != bits_expect) { fclose(f); return -1; }
|
||
if (byterate != rate * ch * bits / 8) { fclose(f); return -1; }
|
||
uint32_t data_len; memcpy(&data_len, h + 40, 4);
|
||
if (data_len < 44) { fclose(f); return -1; }
|
||
/* 读「左声道」样本(跳右声道)统计过零比例:正弦 freq@rate 单声道期望过零比例 ≈ 2*freq/rate。
|
||
* 仅对「近似正弦」做粗检(40% 容差),确认不是静音/噪声/满幅方波。 */
|
||
int bytes_samp = bits / 8;
|
||
int frame_bytes = ch * bytes_samp;
|
||
uint8_t framebuf[8];
|
||
long zero_cross = 0, total = 0, prev_neg = 0;
|
||
for (long i = 0; i < (long)(data_len / frame_bytes); i++) {
|
||
if (fread(framebuf, 1, (size_t)frame_bytes, f) != (size_t)frame_bytes) break;
|
||
int32_t val = 0;
|
||
if (bytes_samp == 2) { int16_t v; memcpy(&v, framebuf, 2); val = v; }
|
||
else { int32_t v; memcpy(&v, framebuf, 4); val = v >> 16; }
|
||
if (val != 0) {
|
||
int neg = val < 0;
|
||
if (total > 0 && neg != prev_neg) zero_cross++;
|
||
prev_neg = neg;
|
||
}
|
||
total++;
|
||
}
|
||
fclose(f);
|
||
if (total < 100) return -3;
|
||
double obs = (double)zero_cross / (double)total;
|
||
double exp = 2.0 * 440.0 / (double)rate_expect;
|
||
if (obs < exp * 0.6 || obs > exp * 1.4) return -2;
|
||
return 0;
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
unlink(TSOCK); unlink(TWAV);
|
||
g_audiod_pid = start_audiod();
|
||
if (g_audiod_pid < 0) { printf("cannot fork audiod\n"); return 1; }
|
||
usleep(300000);
|
||
printf("=== meow_audiod host test (FILE sink) ===\n");
|
||
|
||
/* ---- T1:全链路 OPEN→WRITE→QUERY→PAUSE/RESUME→STOP ---- */
|
||
printf("[T1 full chain]\n");
|
||
struct cli a;
|
||
check(cli_connect(&a) == 0, "connect A");
|
||
m_audio_open_ack_t ack;
|
||
check(do_open(&a, 44100, 2, AUDIO_FMT_S16, &ack) == 0 && ack.err == MEOW_OK && ack.stream_id > 0, "open A");
|
||
printf(" grant=%u/%u/%s sid=%u\n", (unsigned)ack.grant_rate, (unsigned)ack.grant_ch, (ack.grant_fmt == AUDIO_FMT_S16 ? "S16" : "S32"), (unsigned)ack.stream_id);
|
||
uint8_t pcm[4096];
|
||
size_t wb = gen_sine(pcm, sizeof pcm, (int)(ack.grant_fmt ? ack.grant_fmt : AUDIO_FMT_S16), 100, 440, 44100);
|
||
check(wb > 0 && do_write(&a, pcm, (uint32_t)wb) == 0, "write A");
|
||
usleep(150000);
|
||
char q[1024];
|
||
check(do_query(&a, q, sizeof q) == 0 && strstr(q, "active=1"), "query active");
|
||
printf(" query: %s\n", q);
|
||
check(do_ctrl(&a, MSG_AUDIO_PAUSE, ack.stream_id) == 0, "pause A");
|
||
usleep(50000);
|
||
do_query(&a, q, sizeof q);
|
||
check(strstr(q, "paused=1") != NULL, "query paused");
|
||
check(do_ctrl(&a, MSG_AUDIO_RESUME, ack.stream_id) == 0, "resume A");
|
||
usleep(50000);
|
||
check(do_ctrl(&a, MSG_AUDIO_STOP, ack.stream_id) == 0, "stop A");
|
||
usleep(100000);
|
||
do_query(&a, q, sizeof q);
|
||
check(strstr(q, "active=0") != NULL, "stop -> inactive");
|
||
close(a.fd);
|
||
|
||
/* ---- T2:两个客户端抢占(旧收 PREEMPTED,PCM 丢弃) ---- */
|
||
printf("[T2 preemption]\n");
|
||
struct cli a2, b2;
|
||
check(cli_connect(&a2) == 0, "connect A2");
|
||
m_audio_open_ack_t ackA2;
|
||
check(do_open(&a2, 48000, 2, AUDIO_FMT_S16, &ackA2) == 0 && ackA2.err == MEOW_OK, "open A2");
|
||
size_t wb2 = gen_sine(pcm, sizeof pcm, (int)(ackA2.grant_fmt ? ackA2.grant_fmt : AUDIO_FMT_S16), 100, 440, 48000);
|
||
do_write(&a2, pcm, (uint32_t)wb2);
|
||
check(cli_connect(&b2) == 0, "connect B2");
|
||
m_audio_open_ack_t ackB2;
|
||
check(do_open(&b2, 96000, 2, AUDIO_FMT_S16, &ackB2) == 0 && ackB2.err == MEOW_OK, "open B2 (preempt)");
|
||
/* A2 应收到 PREEMPTED */
|
||
uint8_t rbuf[128]; uint16_t rt;
|
||
int npr = rf_tmo(a2.fd, 1000, &rt, rbuf, sizeof rbuf);
|
||
check(npr >= (int)sizeof(m_audio_preempted_t) && rt == MSG_AUDIO_PREEMPTED, "A2 got PREEMPTED");
|
||
if (npr >= (int)sizeof(m_audio_preempted_t)) {
|
||
m_audio_preempted_t pm; memcpy(&pm, rbuf, sizeof pm);
|
||
printf(" preempted by %s (stream %u)\n", pm.by_app, (unsigned)pm.stream_id);
|
||
}
|
||
/* A2 写被丢弃;B2 写生效 */
|
||
do_write(&a2, pcm, (uint32_t)wb2); /* 应丢弃(A2 已 draining) */
|
||
usleep(100000);
|
||
do_query(&a2, q, sizeof q);
|
||
printf(" after preempt, A2 query: %s\n", q);
|
||
do_write(&b2, pcm, (uint32_t)wb2);
|
||
usleep(100000);
|
||
do_query(&b2, q, sizeof q);
|
||
check(strstr(q, "active=1") && strstr(q, "rate=96000"), "B2 active after preempt");
|
||
do_ctrl(&b2, MSG_AUDIO_STOP, ackB2.stream_id);
|
||
do_ctrl(&a2, MSG_AUDIO_STOP, ackA2.stream_id);
|
||
close(a2.fd); close(b2.fd);
|
||
|
||
/* ---- T3:EOF 回收(客户端直接断链) ---- */
|
||
printf("[T3 EOF recycle]\n");
|
||
struct cli c3;
|
||
check(cli_connect(&c3) == 0, "connect C3");
|
||
m_audio_open_ack_t ackC3;
|
||
check(do_open(&c3, 44100, 2, AUDIO_FMT_S16, &ackC3) == 0, "open C3");
|
||
/* 不 STOP,直接断链(close fd)→ audiod poll 读到 EOF → 回收 */
|
||
do_write(&c3, pcm, (uint32_t)gen_sine(pcm, sizeof pcm, AUDIO_FMT_S16, 100, 440, 44100));
|
||
close(c3.fd);
|
||
usleep(200000);
|
||
struct cli c3q; check(cli_connect(&c3q) == 0, "connect C3q");
|
||
do_query(&c3q, q, sizeof q);
|
||
printf(" after EOF: %s\n", q);
|
||
check(strstr(q, "active=0") != NULL, "EOF recycle -> inactive");
|
||
close(c3q.fd);
|
||
|
||
/* ---- T4:tone 写 WAV(由 CLI 走完整 IPC 链路;mipsel/host CLI 共用) ---- */
|
||
printf("[T4 tone -> WAV]\n");
|
||
unlink(TWAV);
|
||
pid_t tone_pid = fork();
|
||
if (tone_pid == 0) {
|
||
setenv("MEOW_AUDIO_SOCK", TSOCK, 1);
|
||
execl("./meow_audio_host", "meow_audio_host", "tone", "440", "500", "44100", (char *)0);
|
||
_exit(127);
|
||
}
|
||
waitpid(tone_pid, NULL, 0);
|
||
usleep(300000);
|
||
check(verify_wav(TWAV, 44100, 2, 16) == 0, "WAV valid sine");
|
||
printf(" wav size: ");
|
||
{ FILE *f = fopen(TWAV, "rb"); if (f) { fseek(f, 0, SEEK_END); printf("%ld bytes\n", ftell(f)); fclose(f); } }
|
||
|
||
stop_audiod();
|
||
printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail);
|
||
return g_fail ? 1 : 0;
|
||
}
|