#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) * meow_audio output 输出目标(P2 蓝牙:bt=A2DP source,音量走 AVRCP 不下发 DAC 反转) * meow_audio monitor [秒] 终端实时电平表(rms/peak dBFS + 文本电平条,200ms 刷新) * * 传输: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 #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; } /* 连接 audiod:connect 非阻塞 + poll 2s 有界(对端 backlog 满/wedge 时绝不永久阻塞); * 建连后 SO_SNDTIMEO 2s(tone 长写在 audiod 停摆时报错退出而非挂死)。 */ static int connect_sock(void) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) return -1; int fl = fcntl(fd, F_GETFL, 0); if (fl >= 0) fcntl(fd, F_SETFL, fl | O_NONBLOCK); 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); int r = connect(fd, (struct sockaddr *)&a, sizeof a); if (r < 0 && errno == EINPROGRESS) { struct pollfd p; p.fd = fd; p.events = POLLOUT; p.revents = 0; if (poll(&p, 1, 2000) > 0) { int err = 0; socklen_t el = sizeof err; if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &el) == 0 && err != 0) { errno = err; r = -1; } } else { r = -1; } } if (r < 0) { close(fd); return -1; } if (fl >= 0) fcntl(fd, F_SETFL, fl); struct timeval tv; tv.tv_sec = 2; tv.tv_usec = 0; setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); 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: case AUDIO_FMT_DOP: 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"; case AUDIO_FMT_DOP: return "DoP"; 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; } /* ---- monitor:终端实时电平表(200ms 刷新,文本电平条;数据源=MSG_AUDIO_STATE 的 rms/peak 键) ---- */ static volatile sig_atomic_t g_mon_stop = 0; static void on_mon_sig(int sig) { (void)sig; g_mon_stop = 1; } /* 从 STATE 文本取整型字段(key 含 '=',如 "rms=";缺省 def) */ static int txt_get_int(const char *t, const char *key, int def) { const char *p = strstr(t, key); return p ? atoi(p + strlen(key)) : def; } static void txt_get_str(const char *t, const char *key, char *out, size_t n) { const char *p = strstr(t, key); if (!p) { snprintf(out, n, "-"); return; } p += strlen(key); size_t k = 0; while (p[k] && p[k] != ' ' && p[k] != '\n' && p[k] != '\t' && k < n - 1) { out[k] = p[k]; k++; } out[k] = 0; } /* deci-dB → "-13.1"(地板显 "-inf") */ static void fmt_db10(char *out, size_t n, int v) { if (v <= -990) { snprintf(out, n, " -inf"); return; } int neg = v < 0; int a = neg ? -v : v; snprintf(out, n, "%s%3d.%d", neg ? "-" : " ", a / 10, a % 10); } /* 文本电平条:-60..0 dBFS → 0..30 格 */ static void print_bar(int db10) { int cells = 0; if (db10 > -600) cells = (db10 + 600) * 30 / 600; if (cells > 30) cells = 30; putchar('['); for (int i = 0; i < 30; i++) putchar(i < cells ? '#' : '-'); putchar(']'); } static int cmd_monitor(int secs) { if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s) — 音频服务不可用\n", g_sock); return 1; } signal(SIGINT, on_mon_sig); signal(SIGTERM, on_mon_sig); printf("monitor: 5Hz 刷新,%s(Ctrl-C 退出)\n", secs > 0 ? "定时自动停" : "持续监视"); struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t t0 = (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; uint8_t rbuf[MEOW_IPC_MAX]; uint16_t rt; while (!g_mon_stop) { int n = req_resp(MSG_AUDIO_QUERY, NULL, 0, &rt, rbuf, sizeof rbuf); if (n < 0 || rt != MSG_AUDIO_STATE) { fprintf(stderr, "\nmeow_audio: query failed (audiod died?)\n"); return 1; } rbuf[n < (int)sizeof rbuf - 1 ? n : (int)sizeof rbuf - 1] = 0; const char *txt = (const char *)rbuf; int active = txt_get_int(txt, "active=", 0); int rms = txt_get_int(txt, "rms=", -990); int pk = txt_get_int(txt, "peak=", -990); int vol = txt_get_int(txt, "vol=", -1); int port = txt_get_int(txt, "port=", -1); int rate = txt_get_int(txt, "rate=", 0); char app[40]; txt_get_str(txt, "app=", app, sizeof app); char rmsb[16], pkb[16]; fmt_db10(rmsb, sizeof rmsb, rms); fmt_db10(pkb, sizeof pkb, pk); if (active) { printf("\rLIVE rms=%sdBFS peak=%sdBFS ", rmsb, pkb); print_bar(pk); printf(" %s %dHz port=%d vol=%d%% ", app, rate, port, vol); } else { printf("\rIDLE 无活跃流(等待播放…) "); } fflush(stdout); if (secs > 0) { clock_gettime(CLOCK_MONOTONIC, &ts); uint64_t now = (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000; if (now - t0 >= (uint64_t)secs * 1000) break; } usleep(200000); /* 200ms 刷新 */ } putchar('\n'); 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; } /* P2 蓝牙:切换输出目标(0=DAC 1=BT)。audiod 侧重配 PCM;BT 时音量走 AVRCP,不下发 DAC 反转。 */ static int cmd_output(int target) { m_audio_output_t o; o.target = (uint32_t)target; uint8_t rbuf[256]; uint16_t rt; int n = req_resp(MSG_AUDIO_OUTPUT, &o, sizeof o, &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("output %s\n", a.status == AUDIO_OUT_BT ? "bt" : "dac"); 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, int fmt) { 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 = (uint16_t)fmt; 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] [S16|S24|S32] | route <0|2|3> | vol [0-100] | output | monitor [秒]\n" " output: 切换输出目标(dac=本地 hw:0,0 / bt=蓝牙 A2DP source;bt 时音量=AVRCP 侧控)\n" " monitor: 终端实时电平表(200ms 刷新+文本电平条;缺省 10s,0=持续到 Ctrl-C)\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, "output")) { if (argc < 3) { fprintf(stderr, "output: \n"); return 2; } int t = (strcmp(argv[2], "bt") == 0) ? AUDIO_OUT_BT : (strcmp(argv[2], "dac") == 0) ? AUDIO_OUT_DAC : -1; if (t < 0) { fprintf(stderr, "output: dac|bt\n"); return 2; } if (g_fd < 0) { fprintf(stderr, "meow_audio: no audiod (%s)\n", g_sock); return 1; } int rc = cmd_output(t); 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; int fmt = AUDIO_FMT_S16; if (argc >= 6) { if (!strcmp(argv[5], "S16")) fmt = AUDIO_FMT_S16; else if (!strcmp(argv[5], "S24")) fmt = AUDIO_FMT_S24; else if (!strcmp(argv[5], "S32")) fmt = AUDIO_FMT_S32; else { fprintf(stderr, "tone: fmt S16|S24|S32(DoP 由 App 侧生成,CLI 不产 DoP)\n"); return 2; } } 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, fmt); } else if (!strcmp(cmd, "monitor")) { int secs = (argc >= 3) ? atoi(argv[2]) : 10; if (secs < 0) secs = 10; return cmd_monitor(secs); } usage(argv[0]); return 2; }