#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; }