根因(级联死锁): 内核驱动 wedge(hbc3000/cs43198 时钟域族, write/PREPARE/amixer ioctl 卡死在内核) → 播放线程永不返回 → ring 填满 → IO 线程在 ring_write 无超时 cond_wait 永久睡眠 → accept 停摆(backlog 满后 CLI ECONNREFUSED) → ncm 堵 socket 写 → 全链冻结。 同效路径: popen_read_tmo 无界 waitpid 随挂死的 amixer 子进程拖死持锁线程。 修法: - ring 写有界等待(MEOW_AUDIO_RING_WAIT_MS 缺省 5s)超时丢弃+计数(dropped=), IO 永生; 等待期 sid 代际守卫(抢占中等待的旧流 PCM 不再注入新流 ring) - 全部 cond 等待 timedwait 化(播放 1s 周期自愈, g_cond 改 CLOCK_MONOTONIC); 信号处理器只置 g_stop(不再调非 async-signal-safe 的 cond_signal) - popen 收割有界: WNOHANG 300ms → SIGKILL 300ms → D 态放弃收割(泄漏换永生); 路由线程 amixer 调用不持 g_mu; QUERY 改报 g_route_cur 缓存(IO 热路径不再 popen) - ALSA 改 O_NONBLOCK + poll(POLLOUT,1s): 连续 3 次停摆判 wedge → PREPARE 自救无效即 关卡进丢弃模式(stall=), 下一路 OPEN 重开重试 - server→client 下行帧单发 write 写不全即整帧丢弃(txdrop=), 杜绝半帧错位/慢客户端堵 IO - 主线程 watchdog: IO/播放心跳 10s 静默告警; io 线程 poll 出错不退出(listener 不丢); backlog 4→8; RESUME/PAUSE 补 cond 唤醒 - SDK meow_ui_audio.c + CLI: connect 非阻塞+poll 2s, SO_SNDTIMEO/RCVTIMEO 2s (audiod 真死时 App 报错重开, 不再永久阻塞) 验证: 宿主 38/38 PASS(旧 19 回归 + 新增 T5 长流 3min 等效 31.7MB 持续灌 played==written underrun/dropped=0 喂流中 listener 探活 15/15; T6 wedge 故障注入生存 — 旧代码同款注入 挂死(90s timeout 强制 kill), 新代码 IO/accept/preempt/SIGTERM 全活; T7 慢客户端 tx 丢弃); qemu-mipsel 静态二进制 status/tone 全通(written=played=13230 underrun=0); -Wall -Wextra 0 warning(host+mipsel+SDK 双端编译)。 meow_audiod md5 86a8b6793d516c90bbd12c2997b30d8f / meow_audio c23b96490842f54324b855fd9c2467a5 (fw_content.conf 已更)。暂不部署真机(主会话统一); meow_ui.a/App 重链留主会话。
332 lines
12 KiB
C
332 lines
12 KiB
C
#define _GNU_SOURCE
|
||
|
||
/* meow_audio.c — meow_audiod 用户侧 CLI(root 免鉴权,ssh 直呼)。
|
||
*
|
||
* 命令:
|
||
* meow_audio status 当前流/参数/帧数/underrun/路由/音量
|
||
* meow_audio tone [freq] [ms] [rate] 正弦测试音(默认 440Hz 500ms 44100;排查无声不须开 App)
|
||
* meow_audio route <0|2|3> 手动切路由(0=auto 2=3.5 3=4.4;调试/面板)
|
||
* meow_audio vol [0-100] 设置音量(无参=查询;v1 透传写 DAC numid=1/2 + settings.conf)
|
||
*
|
||
* 传输:AF_UNIX SOCK_STREAM 到 audiod socket(env MEOW_AUDIO_SOCK / --sock 覆盖,宿主/qemu)。
|
||
* 帧格式复用 meow_ipc.h 16B 头 + m_audio_* 负载。无 audiod → 连接失败/超时 → 打印友好错误并退出。
|
||
*
|
||
* 编译(mipsel 静态):build_audiod.sh cli。宿主编译:build_audiod.sh host。
|
||
*/
|
||
#include "meow_ipc.h"
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
#include <poll.h>
|
||
#include <math.h>
|
||
#include <time.h>
|
||
#include <fcntl.h>
|
||
#include <sys/time.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.h>
|
||
|
||
#define AUD_SOCK_DEFAULT "/usr/data/meow_audio.sock"
|
||
|
||
static char g_sock[108] = AUD_SOCK_DEFAULT;
|
||
static int g_fd = -1;
|
||
|
||
static int write_all(int fd, const uint8_t *p, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) {
|
||
ssize_t w = write(fd, p + off, n - off);
|
||
if (w < 0) { if (errno == EINTR) continue; return -1; }
|
||
if (w == 0) return -1;
|
||
off += (size_t)w;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int read_all(int fd, uint8_t *p, size_t n)
|
||
{
|
||
size_t off = 0;
|
||
while (off < n) {
|
||
ssize_t r = read(fd, p + off, n - off);
|
||
if (r < 0) { if (errno == EINTR) continue; return -1; }
|
||
if (r == 0) return -1;
|
||
off += (size_t)r;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* 连接 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: 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;
|
||
}
|