diff --git a/patch_v2/meow_audiod/build_audiod.sh b/patch_v2/meow_audiod/build_audiod.sh new file mode 100755 index 0000000..762ecfc --- /dev/null +++ b/patch_v2/meow_audiod/build_audiod.sh @@ -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 diff --git a/patch_v2/meow_audiod/meow_audio b/patch_v2/meow_audiod/meow_audio new file mode 100755 index 0000000..e809d35 Binary files /dev/null and b/patch_v2/meow_audiod/meow_audio differ diff --git a/patch_v2/meow_audiod/meow_audio.c b/patch_v2/meow_audiod/meow_audio.c new file mode 100644 index 0000000..1029153 --- /dev/null +++ b/patch_v2/meow_audiod/meow_audio.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/patch_v2/meow_audiod/meow_audio_host b/patch_v2/meow_audiod/meow_audio_host new file mode 100755 index 0000000..f246e3f Binary files /dev/null and b/patch_v2/meow_audiod/meow_audio_host differ diff --git a/patch_v2/meow_audiod/meow_audiod b/patch_v2/meow_audiod/meow_audiod new file mode 100755 index 0000000..1f1d049 Binary files /dev/null and b/patch_v2/meow_audiod/meow_audiod differ diff --git a/patch_v2/meow_audiod/meow_audiod.c b/patch_v2/meow_audiod/meow_audiod.c new file mode 100644 index 0000000..561e7d0 --- /dev/null +++ b/patch_v2/meow_audiod/meow_audiod.c @@ -0,0 +1,1203 @@ +#define _GNU_SOURCE + +/* meow_audiod.c — MeowOS 独占音频服务(devdocs/plan/AUDIOD_DESIGN.md)。 + * + * 定位:把**所有与声卡硬件的交互**收敛到本守护(svcd 托管,板载 /usr/bin/meow_audiod)。 + * App 只经 SDK 客户端 API(meow_ui_audio.c)喂 PCM;audiod 独占持有 hw:0,0。 + * + * 核心设计(用户定案,勿推翻): + * - 独占 + 抢占(后开流赢,旧流收 MSG_AUDIO_PREEMPTED 并被置 draining,PCM 丢弃); + * 软混音已否决(MIPS32 软 SRC 伤音质),留混音器接口位(v1 不实现)。 + * - 单进程双线程(+路由线程):IO 线程(accept/读帧/解析/ring 写)、播放线程 + * (独占 ALSA 直开,从 ring 取数写 DAC,xrun PREPARE 恢复)、路由线程 + * (轮询 headset/balance 插孔 switch 状态 → amixer cset numid=9)。 + * - 格式归一 = 协商:grant_rate/ch/fmt 回客户端应产出的格式(SDK 负责 mono→stereo + * 复制 / S16↔S32 定值升降位);audiod 只做「硬件最优」格式的字节管道。 + * - DAC 原生率跟随(无 SRC);切率 mute-重配-恢复。 + * - meow_route 已并入(§5):本进程在=路由在;独立 meow_route 服务退役。 + * + * 启动门控(红线):main() 先读 /tmp/meow_boot_mode;非 "meow" → 日志一行 → exit(0) + * (HibyOS 模式下 audiod 物理不存在,绝不碰声卡)。测试/宿主用 env MEOW_AUDIO_FORCE=1 + * 跳过门控。 + * + * 音频路径双形态: + * - 真机/ALSA:env MEOW_AUDIO_SINK 缺省自动检测 /dev/snd/pcmC0D0p ∈ 存在 → open ALSA UAPI + * (非 mmap:HW_PARAMS + PREPARE + write)。musl 的 sound/asound.h masks/intervals 是 + * 重索引数组(见 audio_out.c 注释),访问须减 FIRST_MASK/FIRST_INTERVAL。 + * - 无 ALSA(宿主/qemu):自动回退 FILE sink(写 WAV)或 NULL(env MEOW_AUDIO_SINK=null)。 + * + * 编译(mipsel 静态):build_audiod.sh。宿主编译:build_audiod.sh host。 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "meow_ipc.h" + +#ifdef __linux__ +#include +#include +#define AUDIOD_HAVE_ALSA 1 +#endif + +/* ---- 缺省路径 ---- */ +#define AUD_SOCK_DEFAULT "/usr/data/meow_audio.sock" +#define DEV_PCM "/dev/snd/pcmC0D0p" +#define LOG_DIR_CARD "/usr/data/mnt/sd_0/MeowOS/logs" +#define BOOT_MODE_PATH "/tmp/meow_boot_mode" +#define BOOTTS_CACHE "/tmp/meow_bootts" +#define SETTINGS_CONF "/usr/data/meow/settings.conf" +#define DEFAULT_OUT_WAV "/tmp/meow_audiod.wav" + +#define CLI_MAX 8 /* 同连客户端上限(防 fd 泄漏) */ +#define READ_BUF_MAX (MEOW_IPC_MAX + MEOW_IPC_HDR_LEN) +#define FRAME_READ_MAX (MEOW_IPC_MAX + MEOW_IPC_HDR_LEN) +#define READ_CHUNK 8192 /* 播放线程单次从 ring 取出的字节上限(帧对齐裁剪) */ +#define VOL_DEFAULT 60 /* settings.conf audio.volume 缺省 */ +#define RING_MIN 4096 +#define RING_MAX (1u << 20) + +/* 客户端流状态 */ +enum { CLS_OPEN = 0, CLS_PAUSED = 1, CLS_DRAINING = 2 }; /* DRAINING=被抢占,PCM 丢弃 */ + +/* Sink 枚举 */ +enum { SINK_ALSA = 0, SINK_FILE = 1, SINK_NULL = 2 }; +static int g_sink = SINK_ALSA; +static int g_hw_fmt = AUDIO_FMT_S16; /* 硬件最优格式(ALSA 探测;FILE/NULL=S16) */ +static int g_hw_probed = 0; + +static char g_sock[108] = AUD_SOCK_DEFAULT; +static char g_out_wav[256] = DEFAULT_OUT_WAV; +static const char *g_amixer = "/usr/bin/amixer"; /* 环境可覆盖 */ +static int g_route_manual = -1; /* -1=auto;2=3.5;3=4.4 */ +static int g_route_cur = -1; /* 最近已知路由;-1=未知 */ +static int g_vol_pct = VOL_DEFAULT; /* 当前音量 0-100(缓存) */ +static int g_force = 0; /* MEOW_AUDIO_FORCE=1 跳过启动门控(宿主/qemu 测试) */ + +/* ---- 缓冲状态(+日志) ---- */ +static char g_logfile[300] = ""; +static int g_logfd = -1; + +static volatile sig_atomic_t g_stop = 0; + +/* ---- 共享态(g_mu 保护 + g_cond 唤醒) ---- */ +/* 前向声明(供 cli_free/dispatch 使用;定义见下) */ +static void g_sid_reset(void); +static void _audio_mark_reconfig_needed(void); +static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t g_cond = PTHREAD_COND_INITIALIZER; + +struct client { + int used; + int fd; + char app_id[MEOW_APP_ID_MAX]; + uint32_t sid; + int state; /* CLS_* */ + uint8_t rbuf[READ_BUF_MAX]; + size_t rlen; +}; +static struct client g_cli[CLI_MAX]; + +/* 当前授权流(喂养 DAC 的唯一流) */ +struct stream_info { + int active; + uint32_t sid; + char app_id[MEOW_APP_ID_MAX]; + uint32_t rate; + uint16_t ch; /* grant ch(DAC 双声道 → 2) */ + uint16_t fmt; /* grant fmt */ +}; +static struct stream_info g_st; + +static int g_paused = 0; /* 当前流被暂停 */ +static uint32_t g_next_sid = 1; +static uint64_t g_written_frames = 0; /* 当前流:客户端写入 ring 的帧 */ +static uint64_t g_played_frames = 0; /* 当前流:播放线程写到设备的帧 */ +static uint32_t g_underrun = 0; + +/* ring(SPSC:IO 线程写,播放线程读;g_mu + g_cond 保护) */ +static uint8_t *g_ring = NULL; +static size_t g_ring_cap = 0; +static size_t g_rpos = 0, g_wpos = 0; + +/* 播放线程设备状态 */ +static int g_dev_fd = -1; +static int g_dev_open = 0; +static uint32_t g_dev_rate = 0, g_dev_ch = 0, g_dev_fmt = 0; +static int g_reconfig_pending = 0; + +/* ---- 日志 ---- */ +static void logmsg(const char *fmt, ...) +{ + if (g_logfd < 0) return; + char b[512]; + va_list ap; + va_start(ap, fmt); + int n = vsnprintf(b, sizeof b, fmt, ap); + va_end(ap); + if (n < 0) return; + if (n > (int)sizeof b - 1) n = (int)sizeof b - 1; + if (write(g_logfd, "[audiod] ", 9) < 0) { } + if (write(g_logfd, b, (size_t)n) < 0) { } + if (write(g_logfd, "\n", 1) < 0) { } + /* 同时到 stderr(supervisor/svcd 捕获 + 真机 SSH 直达) */ + fprintf(stderr, "[audiod] %s\n", b); +} + +/* ---- bootts 复用(与 launcher 共用 /tmp/meow_bootts 缓存,一次开机一份日志) ---- */ +static void get_bootts(char *out, size_t n) +{ + int fd = open(BOOTTS_CACHE, O_RDONLY); + if (fd >= 0) { + int r = read(fd, out, (int)n - 1); + close(fd); + if (r > 0) { out[r] = 0; return; } + } + time_t now = time(NULL), up = 0; + fd = open("/proc/uptime", O_RDONLY); + if (fd >= 0) { + char buf[64]; + int r = read(fd, buf, sizeof buf - 1); + close(fd); + if (r > 0) { buf[r] = 0; char *p = strchr(buf, ' '); if (p) *p = 0; up = (time_t)atof(buf); } + } + struct tm tm; + localtime_r(&now - up, &tm); + strftime(out, n, "meow_%Y%m%d_%H%M%S", &tm); + fd = open(BOOTTS_CACHE, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { if (write(fd, out, strlen(out)) < 0) { } close(fd); } +} + +static void log_open(void) +{ + char ts[64]; + get_bootts(ts, sizeof ts); + if (access(LOG_DIR_CARD, W_OK) == 0) + snprintf(g_logfile, sizeof g_logfile, "%s/%s.log", LOG_DIR_CARD, ts); + else + snprintf(g_logfile, sizeof g_logfile, "/tmp/%s.log", ts); + g_logfd = open(g_logfile, O_WRONLY | O_CREAT | O_APPEND, 0644); +} + +/* ---- 启动门控 ---- */ +static int boot_mode_ok(void) +{ + if (g_force) return 1; + FILE *f = fopen(BOOT_MODE_PATH, "r"); + if (!f) return 0; /* 文件缺失=非 meow(未知安全默认) */ + char buf[16] = ""; + if (fscanf(f, "%15s", buf) != 1) { fclose(f); return 0; } + fclose(f); + return strcmp(buf, "meow") == 0; +} + +/* ---- 帧发送(仅 IO 线程用:OPEN_ACK/STATE/PREEMPTED/ACK) ---- */ +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 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; +} + +/* ---- frame 大小/位深 ---- */ +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 frame_size(void) { return g_st.ch * fmt_bps(g_st.fmt); } + +static size_t ring_bytes(void) /* 已缓冲字节 */ +{ + return (g_wpos >= g_rpos) ? (g_wpos - g_rpos) : (g_ring_cap - g_rpos + g_wpos); +} + +/* 播放线程读整块(调用方已持 g_mu;返回实际移动字节,帧对齐裁剪) */ +static size_t ring_read_locked(uint8_t *dst, size_t want) +{ + size_t avail = ring_bytes(); + size_t fs = (size_t)frame_size(); + if (fs == 0) fs = 1; + if (avail > want) avail = want; + size_t n = (avail / fs) * fs; /* 帧对齐 */ + if (n == 0) return 0; + size_t first = g_ring_cap - g_rpos; + size_t take = n < first ? n : first; + memcpy(dst, g_ring + g_rpos, take); + if (take < n) memcpy(dst + take, g_ring, n - take); + g_rpos = (g_rpos + n) % g_ring_cap; + pthread_cond_signal(&g_cond); /* 腾出空间唤醒阻塞的 ring 写入方(IO 线程) */ + return n; +} + +/* IO 线程写整块(调用方已持 g_mu,但可能阻塞等待空间——返回后释放由调用方负责)。 + * 返回 0=写入;-1=流已失效(被抢占/停止,丢弃)。 */ +static int ring_write_locked(const uint8_t *src, size_t want) +{ + size_t fs = (size_t)frame_size(); + if (fs == 0) fs = 1; + size_t n = (want / fs) * fs; + if (n == 0) return 0; + for (;;) { + if (g_stop) return -1; + /* 流失效:非当前授权/被抢占/暂停(PAUSE 不新增,但已缓冲保留) */ + if (!g_st.active) + return -1; + size_t freeb = g_ring_cap - ring_bytes(); + if (freeb >= n) break; + pthread_cond_wait(&g_cond, &g_mu); + } + size_t wpos = g_wpos; + size_t first = g_ring_cap - wpos; + size_t take = n < first ? n : first; + memcpy(g_ring + wpos, src, take); + if (take < n) memcpy(g_ring, src + take, n - take); + g_wpos = (g_wpos + n) % g_ring_cap; + pthread_cond_signal(&g_cond); + return 0; +} + +/* 重建 ring(调用方已持 g_mu;按当前授权流参数算 ~200ms 容量;重建=清空旧流缓冲) */ +static void ring_rebuild(void) +{ + size_t fs = (size_t)frame_size(); + if (fs == 0) fs = 1; + size_t want = (size_t)((uint64_t)g_st.rate * fs / 5); /* 0.2s */ + if (want < RING_MIN) want = RING_MIN; + if (want > RING_MAX) want = RING_MAX; + size_t cap = (want / fs) * fs; + if (cap < fs) cap = fs; + free(g_ring); + g_ring = malloc(cap); + g_ring_cap = g_ring ? cap : 0; + if (!g_ring) g_ring_cap = 0; + g_rpos = g_wpos = 0; + logmsg("ring rebuilt: cap=%zu bytes (~%u ms @ %uHz %s x%u)", + g_ring_cap, (unsigned)(g_ring_cap ? g_ring_cap * 1000 / ((uint64_t)g_st.rate * fs) : 0), + (unsigned)g_st.rate, fmt_name(g_st.fmt), (unsigned)g_st.ch); +} + +/* ---- amixer 封装(popen 限时;无 amixer → 优雅失败) ---- */ +static char *popen_read_tmo(int tmo_ms, const char *cmd) +{ + int p[2]; + if (pipe(p) < 0) return NULL; + pid_t pid = fork(); + if (pid < 0) { close(p[0]); close(p[1]); return NULL; } + if (pid == 0) { dup2(p[1], 1); close(p[0]); close(p[1]); execl("/bin/sh", "sh", "-c", cmd, (char *)0); _exit(127); } + close(p[1]); + fcntl(p[0], F_SETFL, fcntl(p[0], F_GETFL, 0) | O_NONBLOCK); + static char buf[4096]; + size_t off = 0; + int remain = tmo_ms; + while (remain > 0 && off < sizeof buf - 1) { + struct pollfd pf = { p[0], POLLIN, 0 }; + int r = poll(&pf, 1, remain); + if (r < 0) { if (errno == EINTR) continue; break; } + if (r == 0) break; + ssize_t rd = read(p[0], buf + off, sizeof buf - 1 - off); + if (rd == 0) break; + if (rd < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) continue; break; } + off += (size_t)rd; + } + close(p[0]); + waitpid(pid, NULL, 0); + buf[off] = 0; + return buf; +} + +static int amixer_set(int numid, int val, int card) +{ + char cmd[160]; + snprintf(cmd, sizeof cmd, "%s%s cset numid=%d %d >/dev/null 2>&1", + g_amixer, (card ? " -c 0" : ""), numid, val); + char *o = popen_read_tmo(1500, cmd); + if (!o) return -1; + /* popen_read_tmo 用的是静态缓冲,这里不 free(与 meow_route.c 同权衡) */ + return 0; +} + +static int amixer_get(int numid, int card) +{ + char cmd[160]; + snprintf(cmd, sizeof cmd, "%s%s cget numid=%d 2>/dev/null", g_amixer, (card ? " -c 0" : ""), numid); + char *o = popen_read_tmo(1500, cmd); + if (!o) return -1; + const char *ln = o; + while (ln && *ln) { + const char *nl = strchr(ln, '\n'); + const char *p = ln; + while (*p == ' ' || *p == '\t') p++; + if (p[0] == ':' && strncmp(p, ": values=", 9) == 0) { int v = atoi(p + 9); return v; } + if (!nl) break; + ln = nl + 1; + } + return -1; +} + +static void dac_vol_set(int pct) +{ + int dac = pct * 255 / 100; + if (dac < 0) dac = 0; + if (dac > 255) dac = 255; + amixer_set(1, dac, 1); + amixer_set(2, dac, 1); + logmsg("DAC vol -> %d%% (L/R=%d)", pct, dac); +} + +static void dac_vol_mute(void) +{ + amixer_set(1, 0, 1); + amixer_set(2, 0, 1); +} + +static const char *port_name(int port) +{ + switch (port) { + case 2: return "3.5 headset"; + case 3: return "4.4 balance"; + default: return "idle"; + } +} + +/* 应用路由(numid=9)。port 0=idle;写后 cget 校验,失败记日志不重试风暴。 */ +static void apply_route(int port) +{ + if (amixer_set(9, port, 1) < 0) { + logmsg("route: amixer cset numid=9 %d exec failed", port); + return; + } + int rb = amixer_get(9, 1); + if (rb == port) logmsg("route: numid=9 -> %d (%s) ok", port, port_name(port)); + else logmsg("route: verify failed (wanted %d got %d)", port, rb); + g_route_cur = port; +} + +/* 读 switch 文件(0/1 插孔态,-1 读失败) */ +static int read_switch(const char *path) +{ + char buf[32]; + int fd = open(path, O_RDONLY); + if (fd < 0) return -1; + ssize_t n = read(fd, buf, sizeof buf - 1); + close(fd); + if (n <= 0) return -1; + buf[n] = 0; + return atoi(buf); +} + +/* 路由评估(移植自 meow_route.c):4.4 优先;都不插不动;写失败不重试风暴。 */ +static void route_evaluate(int *last_hb, int *last_bal, int *last_target) +{ + if (g_route_manual != -1) return; /* 手动模式:不自动切 */ + int hb = read_switch("/sys/class/switch/headset/state"); + int bal = read_switch("/sys/class/switch/balance/state"); + if (hb < 0 || bal < 0) return; + int prev_hb = *last_hb, prev_bal = *last_bal; + *last_hb = hb; *last_bal = bal; + int target; + if (bal == 1) target = 3; /* 4.4 优先 */ + else if (hb == 1) target = 2; + else target = 0; /* 都不插:保持上次(0=不动?) 用 -1 表示不动 */ + if (bal == 0 && hb == 0) { + if (prev_hb != hb || prev_bal != bal) + logmsg("route: no jack (hs=%d bal=%d), keep last port", hb, bal); + *last_target = -1; + return; + } + int changed = (prev_hb != hb || prev_bal != bal); + if (!changed && *last_target == target) return; + int cur = amixer_get(9, 1); + if (cur == target) { *last_target = target; if (changed) logmsg("route: %s already", port_name(target)); return; } + logmsg("route: jack changed -> %s (wanted %d, was %d)", port_name(target), target, cur); + apply_route(target); + *last_target = target; +} + +/* ---- settings.conf 写 audio.volume(同 launcher 同源同键) ---- */ +static void settings_set_volume(int pct) +{ + FILE *f = fopen(SETTINGS_CONF, "r"); + char *data = NULL; + size_t len = 0; + if (f) { + fseek(f, 0, SEEK_END); + long sz = ftell(f); + if (sz > 0 && sz < 64 * 1024) { + fseek(f, 0, SEEK_SET); + data = malloc((size_t)sz + 1); + if (data) { len = fread(data, 1, (size_t)sz, f); data[len] = 0; } + } + fclose(f); + } + char val[16]; + snprintf(val, sizeof val, "%d", pct); + char *out = malloc(len + 64); + if (!out) { free(data); return; } + out[0] = 0; + size_t o = 0; + int replaced = 0; + if (data) { + char *save = NULL; + char *ln = strtok_r(data, "\n", &save); + while (ln) { + char *k = ln; + while (*k == ' ' || *k == '\t') k++; + int m = strncmp(k, "audio.volume", 12); + if (m != 0) { + int n = snprintf(out + o, len + 64 - o, "%s\n", ln); + if (n > 0) o += (size_t)n; + } else { + int t = snprintf(out + o, len + 64 - o, "audio.volume = %s\n", val); + if (t > 0) o += (size_t)t; + replaced = 1; + } + ln = strtok_r(NULL, "\n", &save); + } + free(data); + } + if (!replaced) { + int n = snprintf(out + o, len + 64 - o, "audio.volume = %s\n", val); + if (n > 0) o += (size_t)n; + } + mkdir("/usr/data/meow", 0755); + int fd = open(SETTINGS_CONF, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd >= 0) { + /* 若原文件带 "# meow-settings 1" 头则保留——我们已被动保留了内容(含头)。 + * 无头(新文件)补一行头,符合 meow_settings 的解析预期。 */ + if (!replaced && o > 0) { + /* 检查首行是否已含头 */ + int has_hdr = (strncmp(out, "# meow-settings", 15) == 0); + if (!has_hdr) { + char tmp[64]; + int hn = snprintf(tmp, sizeof tmp, "# meow-settings 1\n"); + if (write(fd, tmp, (size_t)hn) < 0) { } + } + } + if (write(fd, out, o) < 0) { } + close(fd); + } + free(out); + logmsg("audio.volume persisted -> %d (%s)", pct, SETTINGS_CONF); +} + +static int settings_get_volume(void) +{ + FILE *f = fopen(SETTINGS_CONF, "r"); + if (!f) return g_vol_pct; + char line[128]; + int v = g_vol_pct; + while (fgets(line, sizeof line, f)) { + char *k = line; + while (*k == ' ' || *k == '\t') k++; + if (strncmp(k, "audio.volume", 12) == 0) { + char *eq = strchr(k, '='); + if (eq) { v = atoi(eq + 1); break; } + } + } + fclose(f); + return v; +} + +/* ---- ALSA UAPI ---- */ +#ifdef AUDIOD_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; +} +/* 初始化为「全放行」(libasound snd_pcm_hw_params_any 语义):所有 mask 全 1、所有 interval + * [0, UINT_MAX]。**必须**:memset 0 的 interval(min=max=0)等价于「只能取 0」,内核 refine + * 与设备真实约束求交必为空 → HW_PARAMS 恒 EINVAL(真机踩过,agent-244 probe_alsa 全灭定位)。 */ +static void alsa_params_any(struct snd_pcm_hw_params *p) +{ + 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; + } +} +static int alsa_hw_params(int fd, unsigned rate, unsigned ch, unsigned fmt) +{ + struct snd_pcm_hw_params p; + alsa_params_any(&p); + p.flags = 0; + /* mask 必须「先清零再单点」:params_any 已把 mask 置全 1,pmask 只 OR 不清位—— + * 若 access 留全 1,内核 choose 取最低 bit=MMAP_INTERLEAVED,runtime->access≠RW_INTERLEAVED + * → 之后 write() 恒 EINVAL(真机踩过第二坑;libasound 靠 plug 层自动转 mmap 掩盖了它)。 */ + 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, fmt); + 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], ch); + pivset(&p.intervals[SNDRV_PCM_HW_PARAM_RATE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL], rate); + p.rmask = ~0u; p.info = 0; p.msbits = 0; p.rate_num = 0; p.rate_den = 0; + return ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &p); +} +#endif + +static int alsa_fmt_to_snd(int fmt) +{ + switch (fmt) { + case AUDIO_FMT_S16: return SNDRV_PCM_FORMAT_S16_LE; + case AUDIO_FMT_S24: return SNDRV_PCM_FORMAT_S24_LE; + case AUDIO_FMT_S32: return SNDRV_PCM_FORMAT_S32_LE; + case AUDIO_FMT_FLOAT: return SNDRV_PCM_FORMAT_FLOAT_LE; + default: return SNDRV_PCM_FORMAT_S16_LE; + } +} + +/* 探测硬件最优格式(设备空闲时调用;缓存 g_hw_fmt)。返回 0=确定,-1=无法探测。 */ +static int hw_probe(void) +{ + if (g_sink != SINK_ALSA) { g_hw_fmt = AUDIO_FMT_S16; g_hw_probed = 1; return 0; } + if (g_hw_probed) return 0; +#ifdef AUDIOD_HAVE_ALSA + int fd = open(DEV_PCM, O_RDWR); + if (fd >= 0) { + if (alsa_hw_params(fd, 44100, 2, SNDRV_PCM_FORMAT_S16_LE) == 0) { g_hw_fmt = AUDIO_FMT_S16; close(fd); g_hw_probed = 1; return 0; } + close(fd); + } + fd = open(DEV_PCM, O_RDWR); + if (fd >= 0) { + if (alsa_hw_params(fd, 44100, 2, SNDRV_PCM_FORMAT_S32_LE) == 0) { g_hw_fmt = AUDIO_FMT_S32; close(fd); g_hw_probed = 1; return 0; } + close(fd); + } +#endif + g_hw_fmt = AUDIO_FMT_S16; + g_hw_probed = 1; + return -1; +} + +/* ---- FILE/NULL sink ---- */ +static void wav_header(int fd, int rate, int ch, int bits, uint32_t data_len) +{ + unsigned char h[44]; + memset(h, 0, sizeof h); + memcpy(h + 0, "RIFF", 4); + uint32_t le = 36 + data_len; memcpy(h + 4, &le, 4); + memcpy(h + 8, "WAVE", 4); + memcpy(h + 12, "fmt ", 4); + le = 16; memcpy(h + 16, &le, 4); + uint16_t s16 = 1; memcpy(h + 20, &s16, 2); + s16 = (uint16_t)ch; memcpy(h + 22, &s16, 2); + le = (uint32_t)rate; memcpy(h + 24, &le, 4); + le = (uint32_t)(rate * ch * bits / 8); memcpy(h + 28, &le, 4); + s16 = (uint16_t)(ch * bits / 8); memcpy(h + 32, &s16, 2); + s16 = (uint16_t)bits; memcpy(h + 34, &s16, 2); + memcpy(h + 36, "data", 4); + le = data_len; memcpy(h + 40, &le, 4); + if (write(fd, h, 44) != 44) { } +} + +/* ---- 播放线程设备管理(dev_open/close/write 皆在播放线程调用) ---- */ + +static int dev_open(void) +{ + uint32_t rate = g_st.rate, ch = g_st.ch, fmt = g_st.fmt; + int bits = fmt_bps((int)fmt) * 8; + if (g_sink == SINK_ALSA) { +#ifdef AUDIOD_HAVE_ALSA + int fd = open(DEV_PCM, O_RDWR); + if (fd < 0) { logmsg("dev open %s: %s", DEV_PCM, strerror(errno)); return -1; } + if (alsa_hw_params(fd, rate, ch, alsa_fmt_to_snd((int)fmt)) != 0) { + logmsg("dev hw_params %u/%u/%s failed", (unsigned)rate, (unsigned)ch, fmt_name((int)fmt)); + close(fd); + return -1; + } + ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0); + return fd; +#else + return -1; +#endif + } + if (g_sink == SINK_FILE) { + int fd = open(g_out_wav, O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd < 0) { logmsg("dev open wav %s: %s", g_out_wav, strerror(errno)); return -1; } + wav_header(fd, (int)rate, (int)ch, bits, 0); + return fd; + } + return -2; /* NULL sink */ +} + +static void dev_write(uint8_t *buf, size_t n) +{ + if (g_sink == SINK_NULL) { g_played_frames += n / (size_t)frame_size(); return; } + if (g_sink == SINK_ALSA) { +#ifdef AUDIOD_HAVE_ALSA + if (g_dev_fd < 0) { + /* 设备开失败(hw_params 拒/无卡):丢弃 PCM 防 ring 堵死 + 背压卡死客户端; + * 日志限频(每 256 次一记),避免 write(-1) 错误风暴(真机踩过)。 */ + static unsigned s_drop = 0; + if ((s_drop++ % 256) == 0) + logmsg("dev_write dropped %zu bytes (device not open, x%u)", n, s_drop); + g_played_frames += n / (size_t)frame_size(); + return; + } + size_t off = 0; + while (off < n) { + ssize_t w = write(g_dev_fd, buf + off, n - off); + if (w > 0) { off += (size_t)w; continue; } + if (w < 0) { + if (errno == EINTR || errno == EAGAIN) continue; + if (errno == EPIPE) { /* xrun:重备 + 恢复(设计 §2) */ + g_underrun++; + logmsg("xrun #%u -> PREPARE", (unsigned)g_underrun); + ioctl(g_dev_fd, SNDRV_PCM_IOCTL_PREPARE, 0); + continue; + } + logmsg("alsa write err: %s", strerror(errno)); + break; + } + break; + } + size_t done = (off / (size_t)frame_size()) * (size_t)frame_size(); + g_played_frames += done / (size_t)frame_size(); +#endif + return; + } + if (g_sink == SINK_FILE) { + if (write(g_dev_fd, buf, n) > 0) + g_played_frames += n / (size_t)frame_size(); + } +} + +static void dev_close(void) +{ + if (g_sink == SINK_FILE && g_dev_fd >= 0 && g_dev_fd != -2) { + off_t end = lseek(g_dev_fd, 0, SEEK_END); + if (end > 44) { + uint32_t data_len = (uint32_t)(end - 44); + lseek(g_dev_fd, 4, SEEK_SET); /* RIFF 总长(36 + data) */ + uint32_t le = 36 + data_len; if (write(g_dev_fd, &le, 4) != 4) { } + lseek(g_dev_fd, 40, SEEK_SET); /* data 块长(rate/ch/bits 头已由 wav_header 写入) */ + if (write(g_dev_fd, &data_len, 4) != 4) { } + } + close(g_dev_fd); + } else if (g_dev_fd >= 0 && g_dev_fd != -2) { + close(g_dev_fd); + } + g_dev_fd = -1; + g_dev_open = 0; +} + +/* 播放线程:处理一次重配(静音 → 关旧 → 开新 → 复原)。调用方已持 g_mu。 */ +static void reconfigure_locked(void) +{ + uint32_t rate = g_st.rate, ch = g_st.ch, fmt = g_st.fmt; + logmsg("reconfigure device: %u/%u/%s", (unsigned)rate, (unsigned)ch, fmt_name((int)fmt)); + int saved_vol = g_vol_pct; + if (g_sink == SINK_ALSA) dac_vol_mute(); /* 防爆音:重配前静音 */ + dev_close(); + g_dev_fd = dev_open(); + if (g_dev_fd >= 0 || (g_sink == SINK_NULL && g_dev_fd == -2)) { + g_dev_open = 1; + g_dev_rate = rate; g_dev_ch = ch; g_dev_fmt = fmt; + if (g_dev_fd == -2) g_dev_fd = -1; /* NULL sink:无真实 fd */ + if (g_sink == SINK_ALSA) dac_vol_set(saved_vol); /* 复原 */ + logmsg("device open ok: %u/%u/%s sink=%d", (unsigned)rate, (unsigned)ch, fmt_name((int)fmt), g_sink); + } else { + logmsg("device open FAILED !!"); + } + g_reconfig_pending = 0; +} + +/* 播放线程主体。 + * 语义:只有当前授权流(g_st.active)喂 DAC;暂停时不读 ring(保留已缓冲); + * 停止/EOF 时不丢尾——先排空 ring 再关设备(否则 tone 末尾/缓冲尾被截断)。 */ +static void *playback_thread(void *arg) +{ + (void)arg; + static uint8_t buf[READ_CHUNK]; + for (;;) { + int have = 0; + pthread_mutex_lock(&g_mu); + for (;;) { + if (g_stop) { have = -1; break; } + int fs = frame_size(); + if (fs <= 0) { pthread_cond_wait(&g_cond, &g_mu); continue; } /* 无流 param → 等 */ + if (g_reconfig_pending) { have = 1; break; } /* 重配 */ + int draining = !g_st.active; + int paused = g_st.active && g_paused; + size_t av = ring_bytes(); + if ((draining && av < (size_t)fs) || paused) { + /* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */ + if (draining && g_dev_open) dev_close(); + pthread_cond_wait(&g_cond, &g_mu); + continue; + } + if (draining || av >= (size_t)fs) { have = 2; break; } /* 有数据可读(或排空态) */ + pthread_cond_wait(&g_cond, &g_mu); + } + if (have == -1) { pthread_mutex_unlock(&g_mu); break; } + if (have == 1) { reconfigure_locked(); pthread_mutex_unlock(&g_mu); continue; } + if (have == 0) { if (g_dev_open) dev_close(); pthread_mutex_unlock(&g_mu); continue; } + size_t want = READ_CHUNK; + size_t fs = (size_t)frame_size(); + if (fs > 0) want = (want / fs) * fs; + size_t n = ring_read_locked(buf, want); + pthread_mutex_unlock(&g_mu); + if (n == 0) continue; + dev_write(buf, n); + } + pthread_mutex_lock(&g_mu); + if (g_dev_open) dev_close(); + pthread_mutex_unlock(&g_mu); + return NULL; +} + +/* ---- 客户端管理 ---- */ +static struct client *cli_by_fd(int fd) +{ + for (int i = 0; i < CLI_MAX; i++) + if (g_cli[i].used && g_cli[i].fd == fd) return &g_cli[i]; + return NULL; +} +static struct client *cli_alloc(int fd) +{ + struct client *freec = NULL; + for (int i = 0; i < CLI_MAX; i++) { + if (g_cli[i].used) continue; + freec = &g_cli[i]; + break; + } + if (!freec) return NULL; + memset(freec, 0, sizeof *freec); + freec->used = 1; + freec->fd = fd; + freec->state = CLS_OPEN; + freec->sid = 0; + return freec; +} +static void cli_free(struct client *c) +{ + if (!c || !c->used) return; + if (c->fd >= 0) close(c->fd); + c->used = 0; + c->fd = -1; + if (g_st.active && g_st.sid == c->sid) { + logmsg("client EOF: stream %u recycled (app=%s)", (unsigned)c->sid, g_st.app_id); + g_st.active = 0; + g_sid_reset(); + } +} +static void g_sid_reset(void) +{ + /* 停止/EOF 只标记流结束 + 唤醒播放线程排空 ring(**不清空 ring**:未播完的缓冲要写完 + * 才关设备,否则 tone/播放末尾被截断)。ring 的重置只发生在 OPEN 授予新流时(ring_rebuild)。 */ + g_paused = 0; + g_reconfig_pending = 0; + pthread_cond_signal(&g_cond); +} + +/* 处理一个客户端帧(调用方已持 g_mu) */ +static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uint8_t *pl, uint32_t len) +{ + switch (type) { + case MSG_AUDIO_OPEN: { + if (len < sizeof(m_audio_open_t)) { m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_INVAL; send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); return; } + m_audio_open_t o; + memcpy(&o, pl, sizeof o); + if (o.ch > 2 || o.fmt < AUDIO_FMT_S16 || o.fmt > AUDIO_FMT_FLOAT || o.fmt == AUDIO_FMT_FLOAT || o.rate == 0) { + m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_INVAL; + send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); + logmsg("open rejected: ch=%u fmt=%d rate=%u", (unsigned)o.ch, o.fmt, (unsigned)o.rate); + return; + } + hw_probe(); + if (g_sink == SINK_ALSA && !g_hw_probed) { + m_audio_open_ack_t a; memset(&a, 0, sizeof a); a.err = MEOW_ERR_NODEV; + send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); + logmsg("open rejected: no usable PCM device"); + return; + } + int grant_fmt = (g_sink == SINK_ALSA) ? g_hw_fmt : AUDIO_FMT_S16; + uint32_t sid = g_next_sid++; + /* 抢占:已有当前流 → 旧客户端收 PREEMPTED */ + if (g_st.active) { + struct client *old = NULL; + for (int i = 0; i < CLI_MAX; i++) + if (g_cli[i].used && g_cli[i].sid == g_st.sid) { old = &g_cli[i]; break; } + if (old && old != c) { + m_audio_preempted_t p; + memset(&p, 0, sizeof p); + p.stream_id = g_st.sid; + snprintf(p.by_app, sizeof p.by_app, "%s", o.app_id); + send_frame(old->fd, MSG_AUDIO_PREEMPTED, 0, &p, sizeof p); + old->state = CLS_DRAINING; + logmsg("PREEMPTED stream %u by %s", (unsigned)g_st.sid, o.app_id); + } + } + /* 授权新流 */ + c->sid = sid; + c->state = CLS_OPEN; + snprintf(c->app_id, sizeof c->app_id, "%s", o.app_id); + g_st.active = 1; + g_st.sid = sid; + snprintf(g_st.app_id, sizeof g_st.app_id, "%s", o.app_id); + g_st.rate = o.rate; + g_st.ch = 2; /* DAC 双声道:mCh→stereo 由 SDK 复制 */ + g_st.fmt = (uint16_t)grant_fmt; + g_paused = 0; + g_written_frames = g_played_frames = 0; + g_underrun = 0; + /* 重建 ring + 标记重配 */ + ring_rebuild(); + _audio_mark_reconfig_needed(); + m_audio_open_ack_t a; + memset(&a, 0, sizeof a); + a.stream_id = sid; + a.grant_rate = o.rate; + a.grant_ch = g_st.ch; + a.grant_fmt = g_st.fmt; + a.err = MEOW_OK; + send_frame(c->fd, MSG_AUDIO_OPEN_ACK, req_id, &a, sizeof a); + logmsg("OPEN grant: sid=%u app=%s rate=%u ch=%u fmt=%s", (unsigned)sid, g_st.app_id, (unsigned)o.rate, (unsigned)g_st.ch, fmt_name((int)g_st.fmt)); + break; + } + case MSG_AUDIO_WRITE: { + /* 仅当前授权流、非 draining、非 paused 才入 ring;否则丢弃 */ + if (g_st.active && c->sid == g_st.sid && c->state != CLS_DRAINING && !g_paused && c->state != CLS_PAUSED) { + if (g_ring) { + ring_write_locked(pl, len); + int fs = frame_size(); + if (fs > 0) + g_written_frames += len / (uint32_t)fs; + } + } + break; + } + case MSG_AUDIO_PAUSE: + case MSG_AUDIO_RESUME: + case MSG_AUDIO_STOP: { + uint32_t sid = 0; + if (len >= sizeof(uint32_t)) memcpy(&sid, pl, sizeof sid); + if (sid == g_st.sid && g_st.active) { + if (type == MSG_AUDIO_PAUSE) { g_paused = 1; c->state = CLS_PAUSED; logmsg("pause sid=%u", (unsigned)sid); } + else if (type == MSG_AUDIO_RESUME){ g_paused = 0; c->state = CLS_OPEN; logmsg("resume sid=%u", (unsigned)sid); } + else /* STOP */ { + logmsg("STOP sid=%u (app=%s)", (unsigned)sid, g_st.app_id); + g_st.active = 0; + g_st.sid = 0; + c->sid = 0; + c->state = CLS_OPEN; + g_sid_reset(); + } + } else if (type == MSG_AUDIO_STOP) { + /* 非当前/已 draining 流:仅清客户端绑定 */ + if (c->sid == sid) { c->sid = 0; } + } + m_ack_t a; a.code = MEOW_OK; a.status = 0; + send_frame(c->fd, MSG_ACK, req_id, &a, sizeof a); + break; + } + case MSG_AUDIO_QUERY: { + char txt[1024]; + int rb = amixer_get(9, 1); + int vol = g_vol_pct; + snprintf(txt, sizeof txt, + "active=%d app=%s sid=%u rate=%u ch=%u fmt=%s " + "written=%llu played=%llu underrun=%u paused=%d " + "route_mode=%s port=%d vol=%d\n", + g_st.active ? 1 : 0, + g_st.active ? g_st.app_id : "-", + (unsigned)g_st.sid, (unsigned)g_st.rate, (unsigned)g_st.ch, + g_st.active ? fmt_name((int)g_st.fmt) : "-", + (unsigned long long)g_written_frames, + (unsigned long long)g_played_frames, + (unsigned)g_underrun, g_paused, + g_route_manual == -1 ? "auto" : "manual", + (g_route_manual != -1 ? g_route_manual : rb), + vol); + send_frame(c->fd, MSG_AUDIO_STATE, req_id, txt, (uint32_t)strlen(txt) + 1); + break; + } + case MSG_AUDIO_ROUTE: { + uint32_t port = 0; + if (len >= sizeof(uint32_t)) memcpy(&port, pl, sizeof port); + if (port == 0) { g_route_manual = -1; logmsg("route manual -> auto"); } + else if (port == 2 || port == 3) { g_route_manual = (int)port; apply_route((int)port); } + else { logmsg("route: invalid port %u", (unsigned)port); } + m_ack_t a; a.code = MEOW_OK; a.status = 0; + send_frame(c->fd, MSG_ACK, req_id, &a, sizeof a); + break; + } + case MSG_AUDIO_VOLUME: { + int32_t pct = -1; + if (len >= sizeof(int32_t)) memcpy(&pct, pl, sizeof pct); + if (pct >= 0) { + if (pct > 100) pct = 100; + g_vol_pct = (int)pct; + dac_vol_set((int)pct); + settings_set_volume((int)pct); + } else { + g_vol_pct = settings_get_volume(); + } + m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_vol_pct; + send_frame(c->fd, MSG_ACK, req_id, &a, sizeof a); + break; + } + default: + break; + } +} + +/* 需要时标着重配:参数与当前设备不同才重开 */ +static void _audio_mark_reconfig_needed(void) +{ + if (!g_dev_open || g_dev_rate != g_st.rate || g_dev_ch != g_st.ch || g_dev_fmt != g_st.fmt) + g_reconfig_pending = 1; + pthread_cond_signal(&g_cond); +} + +/* 读客户端帧(调用方已持 g_mu)。读失败(EOF/错)→ 回收。 */ +static void cx_read_frames(struct client *c) +{ + ssize_t r = read(c->fd, c->rbuf + c->rlen, sizeof c->rbuf - c->rlen); + if (r <= 0) { + if (r < 0 && (errno == EAGAIN || errno == EWOULDBLOCK)) return; + cli_free(c); + return; + } + c->rlen += (size_t)r; + /* 处理所有完整帧 */ + while (c->rlen >= MEOW_IPC_HDR_LEN) { + uint32_t magic, len; + uint16_t ver, type; + memcpy(&magic, c->rbuf, 4); + memcpy(&ver, c->rbuf + 4, 2); + memcpy(&type, c->rbuf + 6, 2); + memcpy(&len, c->rbuf + 12, 4); + if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { + logmsg("bad frame from client (magic=%08x ver=%u) -> drop conn", (unsigned)magic, (unsigned)ver); + cli_free(c); + return; + } + if (len > MEOW_IPC_MAX) { + logmsg("frame too long (%u) -> drop conn", (unsigned)len); + cli_free(c); + return; + } + size_t total = MEOW_IPC_HDR_LEN + len; + if (c->rlen < total) break; + uint32_t req_id; + memcpy(&req_id, c->rbuf + 8, 4); + dispatch(c, type, req_id, c->rbuf + MEOW_IPC_HDR_LEN, len); + memmove(c->rbuf, c->rbuf + total, c->rlen - total); + c->rlen -= total; + } +} + +/* ---- IO 线程主体 ---- */ +static void *io_thread(void *arg) +{ + (void)arg; + int lfd = socket(AF_UNIX, SOCK_STREAM, 0); + if (lfd < 0) { logmsg("io: socket: %s", strerror(errno)); return NULL; } + 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); + unlink(g_sock); + if (bind(lfd, (struct sockaddr *)&a, sizeof a) != 0) { + logmsg("io: bind %s: %s", g_sock, strerror(errno)); + close(lfd); + return NULL; + } + chmod(g_sock, 0666); + if (listen(lfd, 4) != 0) { logmsg("io: listen: %s", strerror(errno)); close(lfd); return NULL; } + logmsg("io thread listening on %s", g_sock); + + for (;;) { + if (g_stop) break; + struct pollfd pfds[CLI_MAX + 1]; + int n = 1; + pfds[0].fd = lfd; pfds[0].events = POLLIN; pfds[0].revents = 0; + for (int i = 0; i < CLI_MAX; i++) { + if (!g_cli[i].used) continue; + pfds[n].fd = g_cli[i].fd; pfds[n].events = POLLIN; pfds[n].revents = 0; + n++; + } + int pr = poll(pfds, (nfds_t)n, 200); + if (pr < 0) { if (errno == EINTR) continue; break; } + /* pfds 是本轮新数组(revents 本轮清零),直接以 revents 为准(238 教训:每圈重建, + * 杜绝陈旧 revents。勿在此清零)。 */ + struct pollfd snaps[CLI_MAX + 1]; + memcpy(snaps, pfds, sizeof snaps); + pthread_mutex_lock(&g_mu); + (void)pr; + /* 处理 listen */ + if ((snaps[0].revents & POLLIN)) { + int cfd = accept(lfd, NULL, NULL); + if (cfd >= 0) { + fcntl(cfd, F_SETFL, fcntl(cfd, F_GETFL, 0) | O_NONBLOCK); + int one = 1; + setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof one); + struct client *c = cli_alloc(cfd); + if (!c) { close(cfd); logmsg("io: client limit reached, drop"); } + else logmsg("io: client connected (fd=%d)", cfd); + } + } + /* 处理客户读数(用本轮 revents 快照,避免连接被 cli_free 后误用) */ + for (int i = 1; i < n; i++) { + if (snaps[i].revents == 0) continue; + struct client *c = cli_by_fd(snaps[i].fd); + if (!c) continue; + if (snaps[i].revents & (POLLHUP | POLLERR | POLLNVAL)) { cli_free(c); continue; } + if (snaps[i].revents & POLLIN) cx_read_frames(c); + } + pthread_mutex_unlock(&g_mu); + /* 路由线程与播放线程独立;此处不再做 */ + } + pthread_mutex_lock(&g_mu); + for (int i = 0; i < CLI_MAX; i++) if (g_cli[i].used) cli_free(&g_cli[i]); + pthread_mutex_unlock(&g_mu); + close(lfd); + return NULL; +} + +/* ---- 路由线程主体 ---- */ +static void *route_thread(void *arg) +{ + (void)arg; + int last_hb = -1, last_bal = -1, last_target = -1; + logmsg("route thread init (numid=9, %s)", g_amixer); + while (!g_stop) { + pthread_mutex_lock(&g_mu); + route_evaluate(&last_hb, &last_bal, &last_target); + pthread_mutex_unlock(&g_mu); + int naps = 5; /* 1000ms / 200ms 分段,即时响应 SIGTERM */ + for (int k = 0; k < naps && !g_stop; k++) { + struct timespec ts = { 0, 200 * 1000000L }; + nanosleep(&ts, NULL); + } + } + return NULL; +} + +static void on_sig(int sig) +{ + (void)sig; + g_stop = 1; + pthread_cond_signal(&g_cond); +} + +int main(int argc, char **argv) +{ + const char *e; + /* 参数/env 覆盖 */ + 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], "--out") && i + 1 < argc) snprintf(g_out_wav, sizeof g_out_wav, "%s", argv[++i]); + else if (!strcmp(argv[i], "-h") || !strcmp(argv[i], "--help")) { + printf("meow_audiod [--sock ] [--out ]\n env: MEOW_AUDIO_SOCK/MEOW_AUDIO_OUT/MEOW_AUDIO_SINK(alsa|file|null)/MEOW_AUDIO_AMIXER/MEOW_AUDIO_FORCE=1\n"); + return 0; + } + } + if ((e = getenv("MEOW_AUDIO_SOCK")) && e[0]) snprintf(g_sock, sizeof g_sock, "%s", e); + if ((e = getenv("MEOW_AUDIO_OUT")) && e[0]) snprintf(g_out_wav, sizeof g_out_wav, "%s", e); + if ((e = getenv("MEOW_AUDIO_AMIXER")) && e[0]) g_amixer = e; + if ((e = getenv("MEOW_AUDIO_FORCE")) && e[0] == '1') g_force = 1; + const char *sink = getenv("MEOW_AUDIO_SINK"); + if (sink && sink[0]) { + if (!strcmp(sink, "file")) g_sink = SINK_FILE; + else if (!strcmp(sink, "null")) g_sink = SINK_NULL; + else if (!strcmp(sink, "alsa")) g_sink = SINK_ALSA; + } else { + /* 自动:/dev/snd/pcmC0D0p 存在 → ALSA;否则 FILE */ + if (access(DEV_PCM, F_OK) == 0) g_sink = SINK_ALSA; + else g_sink = SINK_FILE; + } + + signal(SIGPIPE, SIG_IGN); + signal(SIGTERM, on_sig); + signal(SIGINT, on_sig); + + /* --- 启动门控(红线) --- */ + if (!boot_mode_ok()) { + fprintf(stderr, "[audiod] not MeowOS mode (/tmp/meow_boot_mode != meow), exit\n"); + return 0; + } + + log_open(); + logmsg("meow_audiod start: sock=%s sink=%d wav=%s force=%d", g_sock, g_sink, g_out_wav, g_force); + if (g_sink == SINK_ALSA) hw_probe(); + g_vol_pct = settings_get_volume(); + logmsg("volume init = %d%%", g_vol_pct); + + pthread_t ti, tp, tr; + pthread_create(&ti, NULL, io_thread, NULL); + pthread_create(&tp, NULL, playback_thread, NULL); + pthread_create(&tr, NULL, route_thread, NULL); + + while (!g_stop) { + struct timespec ts = { 1, 0 }; + nanosleep(&ts, NULL); + } + logmsg("meow_audiod shutdown"); + pthread_join(ti, NULL); + pthread_join(tp, NULL); + pthread_join(tr, NULL); + unlink(g_sock); + if (g_logfd >= 0) close(g_logfd); + return 0; +} diff --git a/patch_v2/meow_audiod/meow_audiod_host b/patch_v2/meow_audiod/meow_audiod_host new file mode 100755 index 0000000..d1e5517 Binary files /dev/null and b/patch_v2/meow_audiod/meow_audiod_host differ diff --git a/patch_v2/meow_audiod/probe_alsa b/patch_v2/meow_audiod/probe_alsa new file mode 100755 index 0000000..e9b7158 Binary files /dev/null and b/patch_v2/meow_audiod/probe_alsa differ diff --git a/patch_v2/meow_audiod/probe_alsa.c b/patch_v2/meow_audiod/probe_alsa.c new file mode 100644 index 0000000..3d0ab05 --- /dev/null +++ b/patch_v2/meow_audiod/probe_alsa.c @@ -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 +#include +#include +#include +#include +#include +#include +#include + +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; +} diff --git a/patch_v2/meow_audiod/services.d/audiod.conf b/patch_v2/meow_audiod/services.d/audiod.conf new file mode 100644 index 0000000..0bd466e --- /dev/null +++ b/patch_v2/meow_audiod/services.d/audiod.conf @@ -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= diff --git a/patch_v2/meow_audiod/test_audiod_host b/patch_v2/meow_audiod/test_audiod_host new file mode 100755 index 0000000..b89bf2d Binary files /dev/null and b/patch_v2/meow_audiod/test_audiod_host differ diff --git a/patch_v2/meow_audiod/test_audiod_host.c b/patch_v2/meow_audiod/test_audiod_host.c new file mode 100644 index 0000000..15a6508 --- /dev/null +++ b/patch_v2/meow_audiod/test_audiod_host.c @@ -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= / + * MEOW_AUDIO_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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/patch_v2/meow_launcherd/main_menu.c b/patch_v2/meow_launcherd/main_menu.c index fba3a9b..b6551ef 100644 --- a/patch_v2/meow_launcherd/main_menu.c +++ b/patch_v2/meow_launcherd/main_menu.c @@ -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); diff --git a/patch_v2/meow_launcherd/meow_ipc.h b/patch_v2/meow_launcherd/meow_ipc.h index 8a8f693..7adbd46 100644 --- a/patch_v2/meow_launcherd/meow_ipc.h +++ b/patch_v2/meow_launcherd/meow_ipc.h @@ -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 */ diff --git a/patch_v2/meow_launcherd/meow_launcherd b/patch_v2/meow_launcherd/meow_launcherd index 8c9ce22..5c742a1 100755 Binary files a/patch_v2/meow_launcherd/meow_launcherd and b/patch_v2/meow_launcherd/meow_launcherd differ diff --git a/patch_v2/meow_route/README.md b/patch_v2/meow_route/README.md new file mode 100644 index 0000000..c635f7d --- /dev/null +++ b/patch_v2/meow_route/README.md @@ -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` + 原可执行文件,勿再使用/部署)。 diff --git a/patch_v2/meow_svcd/S89meow_svcd b/patch_v2/meow_svcd/S89meow_svcd index e194ba1..cc08504 100755 --- a/patch_v2/meow_svcd/S89meow_svcd +++ b/patch_v2/meow_svcd/S89meow_svcd @@ -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 " diff --git a/patch_v2/ncm_app/ncm b/patch_v2/ncm_app/ncm index 5267d60..3b3db7d 100755 Binary files a/patch_v2/ncm_app/ncm and b/patch_v2/ncm_app/ncm differ diff --git a/patch_v2/sdk/build_sdk.sh b/patch_v2/sdk/build_sdk.sh index 3c6090b..8c67ac6 100755 --- a/patch_v2/sdk/build_sdk.sh +++ b/patch_v2/sdk/build_sdk.sh @@ -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 ==" diff --git a/patch_v2/sdk/meow_ui.h b/patch_v2/sdk/meow_ui.h index a7acd98..62a5f61 100644 --- a/patch_v2/sdk/meow_ui.h +++ b/patch_v2/sdk/meow_ui.h @@ -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 diff --git a/patch_v2/sdk/meow_ui_audio.c b/patch_v2/sdk/meow_ui_audio.c new file mode 100644 index 0000000..e16afb8 --- /dev/null +++ b/patch_v2/sdk/meow_ui_audio.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __linux__ +#include +#include +#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; +} diff --git a/patch_v2/sdk/test_ui_audio_host b/patch_v2/sdk/test_ui_audio_host new file mode 100755 index 0000000..3890d78 Binary files /dev/null and b/patch_v2/sdk/test_ui_audio_host differ diff --git a/patch_v2/sdk/test_ui_audio_host.c b/patch_v2/sdk/test_ui_audio_host.c new file mode 100644 index 0000000..70a2789 --- /dev/null +++ b/patch_v2/sdk/test_ui_audio_host.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/scripts/build/fw_content.conf b/scripts/build/fw_content.conf index f928c52..7166384 100644 --- a/scripts/build/fw_content.conf +++ b/scripts/build/fw_content.conf @@ -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(注释同步)