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:
MeowOS committed 2026-08-31 22:40:08 +08:00
1 parent 94874107b6
commit ade0a57353
24 files changed
+2795 -11

No files matched your search

+40
View File
@@ -0,0 +1,40 @@
#!/bin/bash
# build_audiod.sh — meow_audiod 交叉编译(mipsel 静态)+ 宿主多目标可选。
#
# 目标:R3 Pro II / MeowOS(mipsel-linux-muslsf,静态)。audiod 复用 meow_ipc.h(共享协议头,
# -I../meow_launcherd)。audiod 为 svcd 托管的独立守护;meow_audio 为用户侧 CLI。
#
# 用法:
# ./build_audiod.sh # 交叉编译 → meow_audiod(mipsel 静态)
# ./build_audiod.sh cli # 交叉编译 meow_audio CLI → meow_audio(mipsel 静态)
# ./build_audiod.sh host # 宿主编译 meow_audiod_host + meow_audio_host(集成测试)
set -e
cd "$(dirname "$0")"
ROOT="$HOME/Project/R3ProII"
CC="$ROOT/.tools/zig-mipsel-cc"
INC="-I$ROOT/patch_v2/meow_launcherd"
if [ "$1" = "host" ]; then
HOST_CC="${CC_HOST:-cc}"
"$HOST_CC" $INC -Os -Wall -Wextra meow_audiod.c -o meow_audiod_host -lpthread
"$HOST_CC" $INC -Os -Wall -Wextra meow_audio.c -o meow_audio_host -lm
echo "== host builds =="
ls -la meow_audiod_host meow_audio_host
file meow_audiod_host meow_audio_host
exit 0
fi
if [ "$1" = "cli" ]; then
"$CC" $INC -Os -Wall -Wextra -static meow_audio.c -o meow_audio -lm
echo "== meow_audio (mipsel static CLI) =="
ls -la meow_audio
md5sum meow_audio
file meow_audio
exit 0
fi
"$CC" $INC -Os -Wall -Wextra -static meow_audiod.c -o meow_audiod -lpthread
echo "== meow_audiod (mipsel static) =="
ls -la meow_audiod
md5sum meow_audiod
file meow_audiod
BIN
View File
Binary file not shown.
+309
View File
@@ -0,0 +1,309 @@
#define _GNU_SOURCE
/* meow_audio.c — meow_audiod 用户侧 CLI(root 免鉴权,ssh 直呼)。
*
* 命令:
* meow_audio status 当前流/参数/帧数/underrun/路由/音量
* meow_audio tone [freq] [ms] [rate] 正弦测试音(默认 440Hz 500ms 44100;排查无声不须开 App)
* meow_audio route <0|2|3> 手动切路由(0=auto 2=3.5 3=4.4;调试/面板)
* meow_audio vol [0-100] 设置音量(无参=查询;v1 透传写 DAC numid=1/2 + settings.conf)
*
* 传输:AF_UNIX SOCK_STREAM 到 audiod socket(env MEOW_AUDIO_SOCK / --sock 覆盖,宿主/qemu)。
* 帧格式复用 meow_ipc.h 16B 头 + m_audio_* 负载。无 audiod → 连接失败/超时 → 打印友好错误并退出。
*
* 编译(mipsel 静态):build_audiod.sh cli。宿主编译:build_audiod.sh host。
*/
#include "meow_ipc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <poll.h>
#include <math.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#define AUD_SOCK_DEFAULT "/usr/data/meow_audio.sock"
static char g_sock[108] = AUD_SOCK_DEFAULT;
static int g_fd = -1;
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 connect_sock(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", g_sock);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
return fd;
}
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;
}
/* 读一帧头部+负载;返回负载长(≥0),-1=断/坏帧/超时。 */
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;
}
/* 请求-响应:发一帧、读响应(带 3s 超时防挂)。返回响应负载长;-1=失败。 */
static int req_resp(uint16_t type, const void *pl, uint32_t len,
uint16_t *otype, uint8_t *obuf, uint32_t ocap)
{
if (send_frame(g_fd, type, 1, pl, len) != 0) return -1;
struct pollfd p;
p.fd = g_fd; p.events = POLLIN; p.revents = 0;
int r = poll(&p, 1, 3000);
if (r <= 0 || !(p.revents & POLLIN)) return -1;
return read_frame(g_fd, otype, obuf, ocap);
}
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 const char *fmt_name(int fmt)
{
switch (fmt) {
case AUDIO_FMT_S16: return "S16";
case AUDIO_FMT_S24: return "S24";
case AUDIO_FMT_S32: return "S32";
case AUDIO_FMT_FLOAT: return "FLOAT";
default: return "?";
}
}
static int cmd_status(void)
{
uint8_t rbuf[MEOW_IPC_MAX];
uint16_t rt;
int n = req_resp(MSG_AUDIO_QUERY, NULL, 0, &rt, rbuf, sizeof rbuf);
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s), status unavailable\n", g_sock); return 1; }
if (rt != MSG_AUDIO_STATE) { fprintf(stderr, "meow_audio: unexpected reply type 0x%04x\n", rt); return 1; }
rbuf[n < (int)sizeof rbuf - 1 ? n : (int)sizeof rbuf - 1] = 0;
fputs((const char *)rbuf, stdout);
return 0;
}
static int cmd_route(int port)
{
m_audio_route_t r; r.port = (uint32_t)port;
uint8_t rbuf[256]; uint16_t rt;
int n = req_resp(MSG_AUDIO_ROUTE, &r, sizeof r, &rt, rbuf, sizeof rbuf);
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
printf("route %d -> %s\n", port, (port == 0) ? "auto" : (port == 3 ? "4.4" : "3.5"));
return 0;
}
static int cmd_vol(int pct)
{
m_audio_volume_t v; v.pct = (int32_t)pct;
uint8_t rbuf[256]; uint16_t rt;
int n = req_resp(MSG_AUDIO_VOLUME, &v, sizeof v, &rt, rbuf, sizeof rbuf);
if (n < 0 || rt != MSG_ACK) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
m_ack_t a; memcpy(&a, rbuf, sizeof a);
printf("vol %u%%%s\n", (unsigned)a.status, (pct >= 0 ? " (set)" : " (query)"));
return 0;
}
/* 正弦生成:填 nframes 帧(grant 交织格式)到 buf(上限 bufcap,帧对齐)。
* start 为起始帧号;返回实际写入字节数。 */
static size_t gen_sine(uint8_t *buf, size_t bufcap, int grant_fmt, int grant_ch,
double freq, int rate, size_t start, size_t nframes, double amp)
{
int bps = fmt_bps(grant_fmt);
size_t fs = (size_t)grant_ch * (size_t)bps;
if (fs == 0) fs = 1;
size_t n = (bufcap / fs) * fs;
if (n / fs > nframes) n = nframes * fs;
size_t done = 0;
for (size_t i = 0; i < n / fs; i++) {
double ph = 2.0 * M_PI * freq * (double)(start + i) / (double)rate;
double s = sin(ph) * amp;
long v = (long)(s * 32767.0);
if (v > 32767) v = 32767;
if (v < -32768) v = -32768;
long vs = v; /* S16 值 */
if (grant_fmt == AUDIO_FMT_S32 || grant_fmt == AUDIO_FMT_S24) vs = v << 16;
for (int c = 0; c < grant_ch; c++) {
if (grant_fmt == AUDIO_FMT_S16) {
int16_t xx = (int16_t)v;
memcpy(buf + done, &xx, 2); done += 2;
} else {
int32_t xx = (int32_t)(vs);
memcpy(buf + done, &xx, 4); done += 4;
}
}
}
return done;
}
static int cmd_tone(int freq, int ms, int rate)
{
m_audio_open_t o;
memset(&o, 0, sizeof o);
snprintf(o.app_id, sizeof o.app_id, "audio-cli");
o.rate = (uint32_t)rate;
o.ch = 2;
o.fmt = AUDIO_FMT_S16;
uint8_t rbuf[512]; uint16_t rt;
int n = req_resp(MSG_AUDIO_OPEN, &o, sizeof o, &rt, rbuf, sizeof rbuf);
if (n < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; }
if (rt != MSG_AUDIO_OPEN_ACK) { fprintf(stderr, "meow_audio: unexpected reply 0x%04x\n", rt); return 1; }
m_audio_open_ack_t a; memcpy(&a, rbuf, sizeof a);
if (a.err != MEOW_OK) {
fprintf(stderr, "meow_audio: open failed (err=%d): %s\n", a.err,
a.err == MEOW_ERR_NODEV ? "no usable PCM device" :
a.err == MEOW_ERR_INVAL ? "invalid params" : "error");
return 1;
}
printf("tone: sid=%u grant=%uHz/%uch/%s\n", (unsigned)a.stream_id,
(unsigned)a.grant_rate, (unsigned)a.grant_ch, fmt_name((int)a.grant_fmt));
double amp = 0.3;
size_t total_frames = (size_t)((uint64_t)ms * (uint64_t)rate / 1000);
uint8_t pcm[MEOW_IPC_MAX];
size_t fs = (size_t)a.grant_ch * (size_t)fmt_bps((int)a.grant_fmt);
if (fs == 0) fs = 1;
size_t sent_frames = 0;
while (sent_frames < total_frames) {
size_t want = total_frames - sent_frames;
if (want > (size_t)(sizeof pcm / fs)) want = sizeof pcm / fs;
size_t by = gen_sine(pcm, sizeof pcm, (int)a.grant_fmt, (int)a.grant_ch,
(double)freq, rate, sent_frames, want, amp);
if (by == 0) break;
if (send_frame(g_fd, MSG_AUDIO_WRITE, 0, pcm, (uint32_t)by) != 0) {
fprintf(stderr, "meow_audio: write failed (audiod died?)\n");
return 1;
}
sent_frames += by / fs;
}
/* 停止 + 等待排空(WAV 完整落盘 / 真实 DAC 播放完) */
m_audio_stream_t s; s.stream_id = a.stream_id;
req_resp(MSG_AUDIO_STOP, &s, sizeof s, &rt, rbuf, sizeof rbuf);
uint64_t deadline_ms = (uint64_t)ms + 3000;
uint64_t t0 = 0;
{
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
t0 = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
for (;;) {
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
uint64_t now = (uint64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
if (now - t0 > deadline_ms) break;
req_resp(MSG_AUDIO_QUERY, NULL, 0, &rt, rbuf, sizeof rbuf);
if (rt == MSG_AUDIO_STATE) {
if (strstr((const char *)rbuf, "active=0")) break;
}
usleep(100000); /* 100ms 轮询 */
}
printf("tone done: sent %zu frames\n", sent_frames);
close(g_fd);
g_fd = -1;
return 0;
}
static void usage(const char *argv0)
{
fprintf(stderr, "usage: %s status | tone [freq] [ms] [rate] | route <0|2|3> | vol [0-100]\n"
" env: MEOW_AUDIO_SOCK (socket 缺省 %s)\n", argv0, AUD_SOCK_DEFAULT);
}
int main(int argc, char **argv)
{
const char *e = getenv("MEOW_AUDIO_SOCK");
if (e && e[0]) snprintf(g_sock, sizeof g_sock, "%s", e);
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--sock") && i + 1 < argc) { snprintf(g_sock, sizeof g_sock, "%s", argv[++i]); }
else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { usage(argv[0]); return 0; }
}
if (argc < 2) { usage(argv[0]); return 2; }
g_fd = connect_sock();
if (g_fd < 0 && strcmp(argv[1], "status") != 0 && strcmp(argv[1], "vol") != 0
&& strcmp(argv[1], "route") != 0 && strcmp(argv[1], "tone") != 0) {
/* 未知命令用 status 兜底说明 */
}
/* 连接失败:status/vol/route/tone 各自报错;未知命令直接 usage */
const char *cmd = argv[1];
if (!strcmp(cmd, "status")) {
return cmd_status();
} else if (!strcmp(cmd, "route")) {
if (argc < 3) { usage(argv[0]); return 2; }
int port = atoi(argv[2]);
if (port != 0 && port != 2 && port != 3) { fprintf(stderr, "route: port 0/2/3\n"); return 2; }
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
int rc = cmd_route(port); close(g_fd); return rc;
} else if (!strcmp(cmd, "vol")) {
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; }
int pct = (argc >= 3) ? atoi(argv[2]) : -1;
if (pct > 100) pct = 100;
int rc = cmd_vol(pct); close(g_fd); return rc;
} else if (!strcmp(cmd, "tone")) {
int freq = (argc >= 3) ? atoi(argv[2]) : 440;
int ms = (argc >= 4) ? atoi(argv[3]) : 500;
int rate = (argc >= 5) ? atoi(argv[4]) : 44100;
if (freq <= 0 || ms <= 0 || rate <= 0) { fprintf(stderr, "tone: bad params\n"); return 2; }
if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; }
return cmd_tone(freq, ms, rate);
}
usage(argv[0]);
return 2;
}
Binary file not shown.
BIN
View File
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
BIN
View File
Binary file not shown.
+75
View File
@@ -0,0 +1,75 @@
#define _GNU_SOURCE
/* probe_alsa.c — 探测 R3 Pro II CS43198 PCM 设备的 hw_params 约束(找可用的 access/format/rate)。
* mipsel 交叉编译后推到设备运行,输出 kernel 支持哪些组合,诊断「hw_params ioctl 失败」。 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sound/asound.h>
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 const char *fmt_name(unsigned f)
{
#define N(x) case x: return #x
switch (f) {
N(SNDRV_PCM_FORMAT_S8); N(SNDRV_PCM_FORMAT_U8); N(SNDRV_PCM_FORMAT_S16_LE);
N(SNDRV_PCM_FORMAT_S16_BE); N(SNDRV_PCM_FORMAT_U16_LE); N(SNDRV_PCM_FORMAT_U16_BE);
N(SNDRV_PCM_FORMAT_S24_LE); N(SNDRV_PCM_FORMAT_S24_BE); N(SNDRV_PCM_FORMAT_S32_LE);
N(SNDRV_PCM_FORMAT_S32_BE); N(SNDRV_PCM_FORMAT_FLOAT_LE); N(SNDRV_PCM_FORMAT_FLOAT_BE);
N(SNDRV_PCM_FORMAT_S24_3LE); N(SNDRV_PCM_FORMAT_S24_3BE); N(SNDRV_PCM_FORMAT_U24_LE);
N(SNDRV_PCM_FORMAT_S20_3LE); N(SNDRV_PCM_FORMAT_S20_3BE);
default: return "?";
}
#undef N
}
int main(void)
{
setvbuf(stdout, NULL, _IONBF, 0); /* 每行立即刷出,防 kill 丢输出 */
const char *dev = "/dev/snd/pcmC0D0p";
unsigned access[] = { SNDRV_PCM_ACCESS_MMAP_INTERLEAVED, SNDRV_PCM_ACCESS_RW_INTERLEAVED,
SNDRV_PCM_ACCESS_MMAP_NONINTERLEAVED, SNDRV_PCM_ACCESS_RW_NONINTERLEAVED, ~0u };
unsigned fmts[] = { SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S32_LE,
SNDRV_PCM_FORMAT_S24_3LE, ~0u };
unsigned rates[] = { 44100, 48000, 96000, 88200, ~0u };
for (unsigned ai = 0; access[ai] != ~0u; ai++) {
for (unsigned fi = 0; fmts[fi] != ~0u; fi++) {
printf("== access=%u fmt=%s ==\n", access[ai], fmt_name(fmts[fi]));
struct snd_pcm_hw_params p;
for (unsigned ri = 0; rates[ri] != ~0u; ri++) {
int fd = open(dev, O_RDWR | O_NONBLOCK);
if (fd < 0) { printf(" open: %s\n", strerror(errno)); break; }
/* 「全放行」初始化:memset 0 的 interval =「只能取 0」→ refine 必空 EINVAL
*(2026-08-31 真机全灭的根因;libasound snd_pcm_hw_params_any 语义) */
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; }
/* mask 先清零再单点:全 1 会被 choose 取最低 bit(access→MMAP_INTERLEAVED) */
struct snd_mask *am = &p.masks[SNDRV_PCM_HW_PARAM_ACCESS - SNDRV_PCM_HW_PARAM_FIRST_MASK];
memset(am, 0, sizeof *am); pmask(am, access[ai]);
struct snd_mask *fm = &p.masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK];
memset(fm, 0, sizeof *fm); pmask(fm, fmts[fi]);
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], rates[ri]);
p.rmask = ~0u; p.info = 0; p.msbits = 0; p.rate_num = 0; p.rate_den = 0; p.flags = 0;
int rc = ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &p);
printf(" @%u: %s\n", rates[ri], rc ? strerror(errno) : "OK");
close(fd);
}
}
}
return 0;
}
@@ -0,0 +1,14 @@
# meow_audiod 板载服务配置(svcd 托管;由 S89meow_svcd 首启预置到 /usr/data/meow/services.d/)
#
# trigger=manual:audiod 不在开机自动拉起 —— 由 launcher 在确认 MeowOS 模式后 `meow_svcd start
# audiod`(幂等)。audiod 自身还有启动门控(/tmp/meow_boot_mode != meow → exit(0)),双重保证
# HibyOS 模式下它物理不存在、绝不碰声卡。
# restart=on-fail:运行中崩溃 → svcd 重拉(关流状态由客户端 socket EOF 回收兜底),不级联。
exec=/usr/bin/meow_audiod
args=
enabled=1
restart=on-fail
backoff_ms=2000
max_restart=5
trigger=manual
cap=
Binary file not shown.
+310
View File
@@ -0,0 +1,310 @@
#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;
}
+25
View File
@@ -1684,6 +1684,30 @@ static void usage(void)
* M2.89 回归修复用,见 main() 内「开机选单期不抢屏」。 */
#define MEOW_BOOT_MODE_PATH "/tmp/meow_boot_mode"
/* 拉起独占音频服务(AUDIOD_DESIGN.md §1):launcher 确认 MeowOS 模式后 `meow_svcd start audiod`。
* 幂等(svcd cmd_start 在 pid>0 时 no-op)、best-effort(audiod 未装/失败静默)、
* 不阻塞主循环(double-fork + 仅 waitpid 立即退出的中间子进程,避免僵尸 + 不占用 fb)。
* 双重门控:audiod 自身读 /tmp/meow_boot_mode,非 meow 即 exit(0)——HibyOS 下物理不存在。 */
static void meow_audiod_kick(void)
{
if (access("/usr/bin/meow_audiod", X_OK) != 0) return; /* 固件未装 audiod → 静默 */
pid_t pid = fork();
if (pid < 0) return;
if (pid == 0) {
pid_t pid2 = fork(); /* double-fork:隔离执行者,防僵尸 */
if (pid2 != 0) _exit(0); /* 中间子进程立即退出,由父进程 waitpid 回收 */
{
/* 执行者:重定向 stdio 到 /dev/null,exec meow_svcd start audiod */
int devnull = open("/dev/null", O_RDWR);
if (devnull >= 0) { dup2(devnull, 1); dup2(devnull, 2); if (devnull > 2) close(devnull); }
execl("/usr/bin/meow_svcd", "meow_svcd", "start", "audiod", (char *)0);
_exit(127);
}
}
waitpid(pid, NULL, 0); /* 等中间子进程退出(即刻返回,不实质阻塞) */
logmsg("audiod: meow_svcd start audiod requested");
}
int main(int argc, char **argv)
{
long sim_hotkey = 0, sim_tap = 0, sim_exit = 0, dumpkeys = 0;
@@ -1931,6 +1955,7 @@ int main(int argc, char **argv)
/* M2.2 拔营:启动即尝试挂 TF 卡 + 联网(非阻塞);失败由 run_menu 后台周期重试。 */
ensure_card_mounted();
ensure_wifi();
meow_audiod_kick(); /* 确认 MeowOS 模式 → 拉起独占音频服务(幂等、best-effort、不阻塞主循环) */
struct app apps[MAX_APPS];
int n = scan_apps(apps);
int state = (n < 0) ? (n == -1 ? 1 : 2) : (n == 0 ? 2 : 0);
+69
View File
@@ -78,6 +78,20 @@ enum {
* 语义:amsd 为「启动/终止」决策者 → launcher(执行器)执行。payload 见下。 */
MSG_AMS_START = 0x00A0, /* → 请求 launcher 拉起某 App(pkg + 可选 activity) */
MSG_AMS_KILL = 0x00A1, /* → 请求 launcher 终止某 App(SIGTERM 语义) */
/* ---- audiod 独占音频服务(devdocs/plan/AUDIOD_DESIGN.md §2):0x00C0-0x00CA ----
* 语义:App 只经 SDK 客户端 API(meow_ui_audio.c)喂 PCM,audiod 独占 hw:0,0。
* 独占+抢占(后开赢);软混音已否决(MIPS32 软 SRC 伤音质,留混音器接口位)。 */
MSG_AUDIO_OPEN = 0x00C0, /* ↑ 开流(m_audio_open;client 原生 rate/ch/fmt/flags) */
MSG_AUDIO_OPEN_ACK = 0x00C1, /* ↓ 授权(stream_id + grant_rate/ch/fmt + err;grant=客户端应产出的格式) */
MSG_AUDIO_WRITE = 0x00C2, /* ↑ PCM 字节流(按 grant 格式交织;单帧 ≤4096B,多帧连续发,无回执) */
MSG_AUDIO_PAUSE = 0x00C3, /* ↑ 暂停(u32 stream_id) */
MSG_AUDIO_RESUME = 0x00C4, /* ↑ 恢复(u32 stream_id) */
MSG_AUDIO_STOP = 0x00C5, /* ↑ 停止并关流(u32 stream_id) */
MSG_AUDIO_PREEMPTED= 0x00C6, /* ↓ 被抢占通知(m_audio_preempted;旧流被后开流赶走,App 收 UI 提示) */
MSG_AUDIO_QUERY = 0x00C7, /* ↑ 状态快照查询(空负载) */
MSG_AUDIO_STATE = 0x00C8, /* ↓ 状态快照文本(NUL 结尾;当前流/参数/帧数/underrun/路由/音量) */
MSG_AUDIO_ROUTE = 0x00C9, /* ↑ 手动路由(u32 port:0=auto 2=3.5 3=4.4;调试/面板用) */
MSG_AUDIO_VOLUME = 0x00CA, /* ↑ 音量(i32 pct:-1=查询 0-100=设置;v1 透传写 DAC numid=1/2) */
MSG_SYS = 0x00F0 /* ↓ 系统事件(守护关闭/服务死亡/播放器状态) */
};
@@ -369,4 +383,59 @@ typedef struct m_picker_result {
char value[256]; /* 返回项(路径/文本);空=cancel */
} m_picker_result_t; /* 320 B */
/* ================= audiod 独占音频服务(AUDIOD_DESIGN.md) = */
/* PCM 格式枚举(客户端原生 fmt 与 grant_fmt 共用):1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE。
* v1 实装 S16/S32(S16 直通 + S32 升位);S24 按 32-in-32 处理;FLOAT v1 拒绝。 */
enum {
AUDIO_FMT_S16 = 1,
AUDIO_FMT_S24 = 2,
AUDIO_FMT_S32 = 3,
AUDIO_FMT_FLOAT = 4
};
/* MSG_AUDIO_OPEN ↑:请求开流(App 原生声源参数)。grant_*(ACK)回 audiod 实际授权给客户端的
* 产出格式(率/声道/位深)——客户端必须按 grant 格式产出 PCM(SDK meow_ui_audio 负责归一:
* mono→stereo 复制 / S16↔S32 定值升降位)。ch>2 或 FLOAT → ERR_INVAL。 */
typedef struct m_audio_open {
char app_id[MEOW_APP_ID_MAX]; /* 32 */
uint32_t rate; /* 原生采样率(应用源,如 44100/48000) */
uint16_t ch; /* 原生声道数(1=mono 2=stereo) */
uint16_t fmt; /* 原生格式(AUDIO_FMT_*) */
uint32_t flags; /* 预留(0) */
} m_audio_open_t; /* 32+4+2+2+4 = 44 B */
/* MSG_AUDIO_OPEN_ACK ↓:授权结果。err=MEOW_OK 才有效(stream_id>0 且 grant_* 为实际授权值);
* err<0 时 stream_id=0、grant_* 视为无效(客户端 open 返回负错误码)。 */
typedef struct m_audio_open_ack {
uint32_t stream_id; /* 授权流 id(>0) */
uint32_t grant_rate; /* 客户端应产出的采样率(=客户端 rate,无 SRC) */
uint16_t grant_ch; /* 客户端应产出的声道数(DAC 双声道 → 通常 2) */
uint16_t grant_fmt; /* 客户端应产出的格式(AUDIO_FMT_*,=硬件最优) */
int32_t err; /* MEOW_OK / MEOW_ERR_INVAL / MEOW_ERR_NODEV / MEOW_ERR_BUSY 等 */
} m_audio_open_ack_t; /* 4+4+2+2+4 = 16 B */
/* MSG_AUDIO_PAUSE/RESUME/STOP ↑:流控制(按 stream_id 定位)。 */
typedef struct m_audio_stream {
uint32_t stream_id;
} m_audio_stream_t; /* 4 B */
/* MSG_AUDIO_PREEMPTED ↓:被抢占通知(后开流赢;旧流收此消息,App 应暂停 UI/提示)。
* 旧流仍可读但 PCM 被丢弃(draining),直到客户端 CLOSE/断开。 */
typedef struct m_audio_preempted {
uint32_t stream_id; /* 被抢占的流 id */
char by_app[MEOW_APP_ID_MAX]; /* 32:抢占者 app_id */
} m_audio_preempted_t; /* 4+32 = 36 B */
/* MSG_AUDIO_ROUTE ↑:手动切换输出路由(调试/面板用)。port:0=auto(跟随插孔) 2=3.5 3=4.4。 */
typedef struct m_audio_route {
uint32_t port; /* 0=auto 2=3.5 3=4.4 */
} m_audio_route_t; /* 4 B */
/* MSG_AUDIO_VOLUME ↑:音量查询/设置。pct<0(-1)=查询当前音量;0-100=设置(写 DAC numid=1/2
* + 持久化 settings.conf `audio.volume`,与 watchdog/launcher 同源同键)。 */
typedef struct m_audio_volume {
int32_t pct; /* -1=查询;0-100=设置 */
} m_audio_volume_t; /* 4 B */
#endif /* MEOW_IPC_H */
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
# meow_route(已退役,归档)
本目录的独立 `meow_route` 服务已于 2026-08-31(agent-244)**并入 meow_audiod**
(`patch_v2/meow_audiod/meow_audiod.c` 的路由线程,见 `devdocs/plan/AUDIOD_DESIGN.md` §5)。
- `meow_route.c` 的轮询逻辑(读 `/sys/class/switch/headset|balance/state` → `amixer cset numid=9`,
4.4 优先,0/5 危险勿写)原样移植进 audiod 路由线程;`meow_audio route` CLI 提供手动切换。
- **退役原因**:路由是音频的一部分,少一个进程;audiod 在=路由在。
- **不再入固件**:`fw_content.conf` 已移除 `meow_route` 条目;`S89meow_svcd` 预置 conf 改为 `audiod.conf`。
本目录仅作源码归档(保留 `meow_route.c` + `build_route.sh` + 原可执行文件,勿再使用/部署)。
+16 -7
View File
@@ -110,21 +110,30 @@ EOF
chmod 644 /usr/data/meow/services.d/meow_crash.conf
echo -n "provisioned crash.conf "
fi
if [ -f /usr/data/meow/services.d/meow_route.conf ]; then
echo -n "route.conf exists "
if [ -f /usr/data/meow/services.d/audiod.conf ]; then
echo -n "audiod.conf exists "
else
cat > /usr/data/meow/services.d/meow_route.conf <<'EOF2'
exec=/usr/bin/meow_route
cat > /usr/data/meow/services.d/audiod.conf <<'EOF2'
exec=/usr/bin/meow_audiod
args=
enabled=1
restart=on-fail
backoff_ms=2000
max_restart=5
trigger=boot
trigger=manual
cap=
EOF2
chmod 644 /usr/data/meow/services.d/meow_route.conf
echo -n "provisioned route.conf "
chmod 644 /usr/data/meow/services.d/audiod.conf
echo -n "provisioned audiod.conf "
fi
# meow_route 退役(并入 meow_audiod,AUDIOD_DESIGN.md §5):清理旧固件遗留的
# meow_route.conf(trigger=boot 会拉起已被固件移除的 /usr/bin/meow_route → 重启循环)。
if [ -f /usr/data/meow/services.d/meow_route.conf ]; then
if [ -x /usr/bin/meow_svcd ]; then
/usr/bin/meow_svcd stop meow_route 2>/dev/null
fi
rm -f /usr/data/meow/services.d/meow_route.conf
echo -n "removed stale meow_route.conf "
fi
if [ -f /usr/data/meow/services.d/meow_amsd.conf ]; then
echo -n "amsd.conf exists "
Binary file not shown.
+1 -1
View File
@@ -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 =="
+46
View File
@@ -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
View File
@@ -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;
}
BIN
View File
Binary file not shown.
+158
View File
@@ -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;
}
+4 -3
View File
@@ -28,7 +28,7 @@
# ---- 用户态/服务守护二进制(usr/bin,全部 chmod 755 静态 MIPS)----
patch_v2/bootmenu/meow_bootmenu|/usr/bin/meow_bootmenu|4441822d87e76bfce2ce584604ac3830|引导选单 M2.85(sysui让位 agent-216)
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|3c1bf9c04a3b0d06d2ecf8e7056f3837|主屏 launcher M2.89(开机选单期不抢屏回归修复)
patch_v2/meow_launcherd/meow_launcherd|/usr/bin/meow_launcherd|a2f932a1534ac2bb035d39a4d1bcca46|主屏 launcher(+拉起 audiod:MeowOS 模式确认后 meow_svcd start audiod agent-244)
patch_v2/meow_watchdog/meow_watchdog|/usr/bin/meow_watchdog|6373d8e5cac62a34ee468a344b267761|应用看门狗 M2.89(SIGUSR1 仅裸沉浸 App,框架 App 不误杀 agent-236)
patch_v2/meow_svcd/meow_svcd|/usr/bin/meow_svcd|605646c4cc93f2baf29071ea6bc870ea|服务管理器 M2.77(+崩溃上报 meow_crash agent-187)
mips-repo/bins/meow_mdns/bin/meow_mdns|/usr/bin/meow_mdns|d6b0151bda26768f19952dae1342610d|mDNS 应答器 M2.78(启动竞态+换IP重检 agent-194)
@@ -37,12 +37,13 @@ patch_v2/meowpkg/nyai|/usr/bin/nyai|2858c3bd8f57fb723df82cb97b488f31|nyai M2.89
patch_v2/meow_svc_power/meow_svc_power|/usr/bin/meow_svc_power|306df962d319f16169b14fb808c6a738|电源专项服务 2B(面板精确绑定)
patch_v2/meow_verify/meow_verify|/usr/bin/meow_verify|6c2e8ed24aebe5caf41e7aed391312d5|II期验签 2B(多公钥+v2签名块 agent-221)
patch_v2/meow_crash/meow_crash|/usr/bin/meow_crash|62781084445e82351bcadbdadbe4c7fc|系统级崩溃捕获 (agent-187)
patch_v2/meow_route/meow_route|/usr/bin/meow_route|fa2b80be5efebefffa032f08c6aae25c|音频输出路由自动跟随插孔 (agent-220)
patch_v2/meow_svc_perf/meow_svc_perf|/usr/bin/meow_svc_perf|7ea196de9d59ab8cbd1e26edb7762772|性能专项服务 M2.75(net_json 补 network 闭合括号,修 /api/system JSON 解析失败)
patch_v2/meow_audiod/meow_audiod|/usr/bin/meow_audiod||独占音频守护 A1(AUDIOD_DESIGN.md;独占开卡/格式归一/抢占/路由并入;md5 打包时统一填 agent-244)
patch_v2/meow_audiod/meow_audio|/usr/bin/meow_audio||audiod 用户侧 CLI(status/tone/route/vol;md5 打包时统一填 agent-244)
# ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)----
patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移)
patch_v2/meow_svcd/S89meow_svcd|/etc/init.d/S89meow_svcd|d293dd517953a948a0b35766d0a6ff25|svcd 自启 M2.88(+预置 meow_route.conf)
patch_v2/meow_svcd/S89meow_svcd|/etc/init.d/S89meow_svcd|77da8236453f763d9f09a02e2736b798|svcd 自启(meow_route.conf → audiod.conf + 清理旧 route conf,预置独占音频服务 agent-244/246)
patch_v2/meow_amsd/meow_amsd|/usr/bin/meow_amsd|c6c2eda2d63ac73c4b6fac29008e0c9b|活动管理器 M2.89(陈旧revents丢连接/收发超时/自动重连 agent-238)
patch_v2/meow_amsd/am|/usr/bin/am|d687d8bc87df102e625e6482b0423430|amsd 用户侧CLI(+resolve/picker)
patch_v2/bootmenu/S90meow_bootmenu|/etc/init.d/S90meow_bootmenu|e0ace111ce5ee446a1e3c6605ac1e9c9|bootmenu 自启 M2.85(注释同步)