diff --git a/patch_v2/meow_audiod/meow_audio b/patch_v2/meow_audiod/meow_audio index e809d35..790948f 100755 Binary files a/patch_v2/meow_audiod/meow_audio 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 index 1029153..53f509d 100644 --- a/patch_v2/meow_audiod/meow_audio.c +++ b/patch_v2/meow_audiod/meow_audio.c @@ -22,6 +22,8 @@ #include #include #include +#include +#include #include #include @@ -54,15 +56,35 @@ static int read_all(int fd, uint8_t *p, size_t n) 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); - if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + 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; } diff --git a/patch_v2/meow_audiod/meow_audio_host b/patch_v2/meow_audiod/meow_audio_host index f246e3f..5cafc0f 100755 Binary files a/patch_v2/meow_audiod/meow_audio_host 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 index b64d658..ce77121 100755 Binary files a/patch_v2/meow_audiod/meow_audiod 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 index 20da178..4858f3b 100644 --- a/patch_v2/meow_audiod/meow_audiod.c +++ b/patch_v2/meow_audiod/meow_audiod.c @@ -20,6 +20,17 @@ * (HibyOS 模式下 audiod 物理不存在,绝不碰声卡)。测试/宿主用 env MEOW_AUDIO_FORCE=1 * 跳过门控。 * + * 防卡死护栏(2026-09-01,真机长流卡死教训——驱动 wedge 时绝不允许级联冻死全守护): + * - ring 写**有界等待**(MEOW_AUDIO_RING_WAIT_MS,缺省 5000ms):超时丢弃该 PCM 块并计数 + * (dropped=),IO 线程永不永久睡眠 → accept/读循环永生; + * - 所有 cond 等待均为 timedwait(1s 周期自愈,防丢失唤醒); + * - popen(amixer) 收割有界:WNOHANG + SIGKILL + 仍不死则放弃收割(宁可泄漏不卡线程); + * - ALSA 写改 O_NONBLOCK + poll(POLLOUT,1s):连续 3 次停摆判定设备 wedge → 关卡进入 + * 丢弃模式(stall=),下一路 OPEN 重开重试; + * - server→client 下行帧单发 write,写不全即整帧丢弃(txdrop=),绝不因客户端不读堵 IO 循环; + * - 主线程 watchdog:IO/播放线程心跳 10s 无活动即告警日志; + * - io 线程 poll 出错只记日志不退出(listener 不丢)。 + * * 音频路径双形态: * - 真机/ALSA:env MEOW_AUDIO_SINK 缺省自动检测 /dev/snd/pcmC0D0p ∈ 存在 → open ALSA UAPI * (非 mmap:HW_PARAMS + PREPARE + write)。musl 的 sound/asound.h masks/intervals 是 @@ -70,6 +81,11 @@ #define VOL_DEFAULT 60 /* settings.conf audio.volume 缺省 */ #define RING_MIN 4096 #define RING_MAX (1u << 20) +#define RING_WAIT_DEFAULT 5000 /* ring 满时 IO 线程等待上限 ms(超时丢弃,防背压变死锁) */ +#define COND_WAIT_MS 1000 /* 播放线程 cond 周期醒(自愈丢失唤醒 + 响应 g_stop) */ +#define ALSA_POLL_MS 1000 /* ALSA 可写 poll 超时;连续 ALSA_STALL_MAX 次判定 wedge */ +#define ALSA_STALL_MAX 3 +#define WD_SILENT_SEC 10 /* watchdog:线程心跳静默上限(> RING_WAIT 余量) */ /* 客户端流状态 */ enum { CLS_OPEN = 0, CLS_PAUSED = 1, CLS_DRAINING = 2 }; /* DRAINING=被抢占,PCM 丢弃 */ @@ -94,12 +110,12 @@ static int g_logfd = -1; static volatile sig_atomic_t g_stop = 0; -/* ---- 共享态(g_mu 保护 + g_cond 唤醒) ---- */ +/* ---- 共享态(g_mu 保护 + g_cond 唤醒;g_cond 用 CLOCK_MONOTONIC,main 中初始化) ---- */ /* 前向声明(供 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; +static pthread_cond_t g_cond; struct client { int used; @@ -128,6 +144,21 @@ 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; +static uint64_t g_drop_frames = 0; /* ring 满超时丢弃的帧(背压护栏,QUERY dropped=) */ +static uint32_t g_stall = 0; /* ALSA 可写 poll 超时次数(设备停摆;QUERY stall=) */ +static uint32_t g_txdrop = 0; /* server→client 下行帧丢弃(客户端不读;QUERY txdrop=) */ + +/* ring 写等待上限(env MEOW_AUDIO_RING_WAIT_MS 覆盖;测试用小值) */ +static int g_ring_wait_ms = RING_WAIT_DEFAULT; + +/* 线程心跳(watchdog 用;宽松计数,不加锁) */ +static volatile unsigned g_hb_io = 0; +static volatile unsigned g_hb_play = 0; + +/* 测试钩子(仅 env 显式开启;MEOW_AUDIO_TEST_WRITE_DELAY_MS / _WEDGE_AFTER_BYTES) */ +static int g_test_write_delay_ms = 0; +static long long g_test_wedge_after = -1; /* dev_write 累计字节≥此值后模拟设备 wedge(睡到 g_stop) */ +static uint64_t g_dev_bytes = 0; /* ring(SPSC:IO 线程写,播放线程读;g_mu + g_cond 保护) */ static uint8_t *g_ring = NULL; @@ -206,31 +237,43 @@ static int boot_mode_ok(void) } /* ---- 帧发送(仅 IO 线程用:OPEN_ACK/STATE/PREEMPTED/ACK) ---- */ -static int write_all(int fd, const uint8_t *p, size_t n) +/* 单发即丢:客户端 fd 是 O_NONBLOCK;拼装整帧一次 write(),写不全(对端不读/缓冲满) + * 就整帧丢弃并计数——绝不为慢客户端堵 IO 循环,也绝不写出半个帧让对端协议错位。 + * 丢 OPEN_ACK/ACK 的后果 = 客户端 3s 超时判流死重连(可接受);丢 PREEMPTED 由 App 侧 + * 播放停摆自检兜底。 */ +static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) { - 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; + if (len > MEOW_IPC_MAX) return -1; + uint8_t frame[MEOW_IPC_HDR_LEN + MEOW_IPC_MAX]; + uint32_t magic = MEOW_IPC_MAGIC; + uint16_t ver = MEOW_IPC_VERSION; + memcpy(frame + 0, &magic, 4); + memcpy(frame + 4, &ver, 2); + memcpy(frame + 6, &type, 2); + memcpy(frame + 8, &req_id, 4); + memcpy(frame + 12, &len, 4); + if (len && payload) memcpy(frame + MEOW_IPC_HDR_LEN, payload, len); + size_t total = MEOW_IPC_HDR_LEN + len; + ssize_t w = write(fd, frame, total); + if (w != (ssize_t)total) { + g_txdrop++; + if ((g_txdrop & 63u) == 1u) + logmsg("tx drop: fd=%d type=0x%04x len=%u wrote=%zd (total x%u, client not reading?)", + fd, (unsigned)type, (unsigned)len, w, (unsigned)g_txdrop); + return -1; } return 0; } -static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) +/* cond 有界等待(g_cond 为 CLOCK_MONOTONIC;调用方已持 g_mu)。 */ +static void cond_wait_tmo(int ms) { - 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; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + ts.tv_sec += ms / 1000; + ts.tv_nsec += (long)(ms % 1000) * 1000000L; + if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; } + pthread_cond_timedwait(&g_cond, &g_mu, &ts); } /* ---- frame 大小/位深 ---- */ @@ -279,22 +322,40 @@ static size_t ring_read_locked(uint8_t *dst, size_t want) return n; } -/* IO 线程写整块(调用方已持 g_mu,但可能阻塞等待空间——返回后释放由调用方负责)。 - * 返回 0=写入;-1=流已失效(被抢占/停止,丢弃)。 */ -static int ring_write_locked(const uint8_t *src, size_t want) +/* IO 线程写整块(调用方已持 g_mu)。**有界等待**:ring 满最多等 g_ring_wait_ms, + * 超时丢弃该块(返回 0)——背压绝不能退化成 IO 线程永久睡眠(真机长流卡死根因: + * 播放线程堵死 → ring 满 → IO cond_wait 永不醒 → accept 停摆 → 全链冻结)。 + * sid 为写入方开流时的代际:等待期间被抢占(g_st.sid 变化)立即丢弃(返回 0), + * 防止旧流 PCM 注入新流 ring(ring_rebuild 后错位)。 + * 返回实际写入字节数;0=被丢弃(超时/流死/被抢占)。 */ +static size_t ring_write_locked(const uint8_t *src, size_t want, uint32_t sid) { size_t fs = (size_t)frame_size(); if (fs == 0) fs = 1; size_t n = (want / fs) * fs; if (n == 0) return 0; + if (!g_ring || g_ring_cap < n) return 0; /* ring 未建/异常小:直接丢 */ + struct timespec deadline; + clock_gettime(CLOCK_MONOTONIC, &deadline); + deadline.tv_sec += g_ring_wait_ms / 1000; + deadline.tv_nsec += (long)(g_ring_wait_ms % 1000) * 1000000L; + if (deadline.tv_nsec >= 1000000000L) { deadline.tv_sec++; deadline.tv_nsec -= 1000000000L; } for (;;) { - if (g_stop) return -1; - /* 流失效:非当前授权/被抢占/暂停(PAUSE 不新增,但已缓冲保留) */ - if (!g_st.active) - return -1; + if (g_stop) return 0; + /* 流失效/代际变化:非当前授权、被抢占、被停止 → 丢弃 */ + if (!g_st.active || g_st.sid != sid) + return 0; size_t freeb = g_ring_cap - ring_bytes(); if (freeb >= n) break; - pthread_cond_wait(&g_cond, &g_mu); + int wr = pthread_cond_timedwait(&g_cond, &g_mu, &deadline); + if (wr == ETIMEDOUT) { + g_drop_frames += n / fs; + static unsigned s_droplog = 0; + if ((s_droplog++ & 63u) == 0) + logmsg("ring full >%dms: dropped %zu bytes (total dropped=%llu frames, playback stuck?)", + g_ring_wait_ms, n, (unsigned long long)g_drop_frames); + return 0; + } } size_t wpos = g_wpos; size_t first = g_ring_cap - wpos; @@ -303,7 +364,7 @@ static int ring_write_locked(const uint8_t *src, size_t want) 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; + return n; } /* 重建 ring(调用方已持 g_mu;按当前授权流参数算 ~200ms 容量;重建=清空旧流缓冲) */ @@ -327,6 +388,10 @@ static void ring_rebuild(void) } /* ---- amixer 封装(popen 限时;无 amixer → 优雅失败) ---- */ +/* 读子进程 stdout(非阻塞+poll 限时)。收割**必须有界**:驱动 wedge 时 amixer 可能 + * 挂在内核 ioctl 里,无条件 waitpid 会把调用线程(路由线程持 g_mu / IO 线程)一起拖死 + * ——真机长流卡死的级联路径之一。策略:先 WNOHANG 等 300ms;不死 → SIGKILL 再等 300ms; + * 仍不死(D 态)→ 放弃收割(泄漏一个子进程,换线程不卡死;已记日志)。 */ static char *popen_read_tmo(int tmo_ms, const char *cmd) { int p[2]; @@ -350,7 +415,23 @@ static char *popen_read_tmo(int tmo_ms, const char *cmd) off += (size_t)rd; } close(p[0]); - waitpid(pid, NULL, 0); + int reaped = 0; + for (int i = 0; i < 30; i++) { /* 300ms 优雅退出窗口 */ + if (waitpid(pid, NULL, WNOHANG) == pid) { reaped = 1; break; } + struct timespec ts = { 0, 10 * 1000000L }; + nanosleep(&ts, NULL); + } + if (!reaped) { + logmsg("popen: child %ld did not exit in 300ms, SIGKILL (%s)", (long)pid, cmd); + kill(pid, SIGKILL); + for (int i = 0; i < 30; i++) { + if (waitpid(pid, NULL, WNOHANG) == pid) { reaped = 1; break; } + struct timespec ts = { 0, 10 * 1000000L }; + nanosleep(&ts, NULL); + } + if (!reaped) + logmsg("popen: child %ld unkillable (D-state, driver wedged?) — abandon reap", (long)pid); + } buf[off] = 0; return buf; } @@ -411,7 +492,9 @@ static const char *port_name(int port) } } -/* 应用路由(numid=9)。port 0=idle;写后 cget 校验,失败记日志不重试风暴。 */ +/* 应用路由(numid=9)。port 0=idle;写后 cget 校验,失败记日志不重试风暴。 + * g_route_cur 为 plain int 松散写(路由线程不持 g_mu 调本函数——amixer popen 虽已有界, + * 仍不该在持锁状态执行外部命令;int 写对 MIPS32 天然原子,竞争最坏是 QUERY 读到上一拍值)。 */ static void apply_route(int port) { if (amixer_set(9, port, 1) < 0) { @@ -437,10 +520,11 @@ static int read_switch(const char *path) return atoi(buf); } -/* 路由评估(移植自 meow_route.c):4.4 优先;都不插不动;写失败不重试风暴。 */ -static void route_evaluate(int *last_hb, int *last_bal, int *last_target) +/* 路由评估(移植自 meow_route.c):4.4 优先;都不插不动;写失败不重试风暴。 + * manual:调用方快照的 g_route_manual(本函数不持 g_mu、不读全局)。 */ +static void route_evaluate(int manual, int *last_hb, int *last_bal, int *last_target) { - if (g_route_manual != -1) return; /* 手动模式:不自动切 */ + if (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; @@ -614,12 +698,12 @@ 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); + int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); 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); + fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); 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); @@ -652,6 +736,7 @@ static void wav_header(int fd, int rate, int ch, int bits, uint32_t data_len) } /* ---- 播放线程设备管理(dev_open/close/write 皆在播放线程调用) ---- */ +static void dev_close(void); /* 前向:dev_write 的 wedge 判死路径要用 */ static int dev_open(void) { @@ -659,7 +744,9 @@ static int dev_open(void) int bits = fmt_bps((int)fmt) * 8; if (g_sink == SINK_ALSA) { #ifdef AUDIOD_HAVE_ALSA - int fd = open(DEV_PCM, O_RDWR); + /* O_NONBLOCK:写路径走 poll(POLLOUT)+限时(dev_write),write 本身永不睡眠—— + * 驱动 wedge 时绝不能把播放线程永久压进内核(真机长流卡死根因防线)。 */ + int fd = open(DEV_PCM, O_RDWR | O_NONBLOCK); 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)); @@ -683,11 +770,29 @@ static int dev_open(void) static void dev_write(uint8_t *buf, size_t n) { + /* 测试钩子(仅 env 开启):写延迟(模拟慢设备/实时 pacing)与 wedge(模拟驱动卡死—— + * 睡到 g_stop,复现真机「播放线程永不返回」故障注入,验证 IO 线程/ring 不死锁)。 */ + if (g_test_write_delay_ms > 0) { + struct timespec ts = { g_test_write_delay_ms / 1000, (long)(g_test_write_delay_ms % 1000) * 1000000L }; + nanosleep(&ts, NULL); + } + if (g_test_wedge_after >= 0) { + if (g_dev_bytes >= (uint64_t)g_test_wedge_after) { + static int s_wedged = 0; + if (!s_wedged) { s_wedged = 1; logmsg("TEST: device wedged (simulated after %llu bytes)", (unsigned long long)g_dev_bytes); } + while (!g_stop) { + struct timespec ts = { 0, 100 * 1000000L }; + nanosleep(&ts, NULL); + } + return; + } + g_dev_bytes += 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 堵死 + 背压卡死客户端; + /* 设备开失败(hw_params 拒/无卡/wedge 后被判死):丢弃 PCM 防 ring 堵死 + 背压卡死客户端; * 日志限频(每 256 次一记),避免 write(-1) 错误风暴(真机踩过)。 */ static unsigned s_drop = 0; if ((s_drop++ % 256) == 0) @@ -695,10 +800,33 @@ static void dev_write(uint8_t *buf, size_t n) g_played_frames += n / (size_t)frame_size(); return; } + /* O_NONBLOCK + poll(POLLOUT) 模式:写前等可写(上限 ALSA_POLL_MS)。 + * 连续 ALSA_STALL_MAX 次超时 = 设备停摆(驱动 wedge 特征)→ PREPARE 自救仍无效 + * 则关卡进入丢弃模式(g_dev_open=0),下一路 OPEN 重开重试——播放线程绝不永久 + * 堵在内核 write 里(真机长流卡死的级联源头)。 */ + int stalls = 0; size_t off = 0; while (off < n) { + if (g_stop) break; + struct pollfd pf; + pf.fd = g_dev_fd; pf.events = POLLOUT; pf.revents = 0; + int pr = poll(&pf, 1, ALSA_POLL_MS); + if (pr == 0) { + g_stall++; + stalls++; + logmsg("alsa stall #%u (poll %dms no space, xrun-ish) -> PREPARE", + (unsigned)g_stall, ALSA_POLL_MS); + ioctl(g_dev_fd, SNDRV_PCM_IOCTL_PREPARE, 0); + if (stalls >= ALSA_STALL_MAX) { + logmsg("alsa WEDGED: %d consecutive stalls, close device + drop PCM until next OPEN", stalls); + dev_close(); + return; + } + continue; + } + if (pr < 0) { if (errno == EINTR) continue; logmsg("alsa poll err: %s", strerror(errno)); break; } ssize_t w = write(g_dev_fd, buf + off, n - off); - if (w > 0) { off += (size_t)w; continue; } + if (w > 0) { off += (size_t)w; stalls = 0; continue; } if (w < 0) { if (errno == EINTR || errno == EAGAIN) continue; if (errno == EPIPE) { /* xrun:重备 + 恢复(设计 §2) */ @@ -765,18 +893,21 @@ static void reconfigure_locked(void) /* 播放线程主体。 * 语义:只有当前授权流(g_st.active)喂 DAC;暂停时不读 ring(保留已缓冲); - * 停止/EOF 时不丢尾——先排空 ring 再关设备(否则 tone 末尾/缓冲尾被截断)。 */ + * 停止/EOF 时不丢尾——先排空 ring 再关设备(否则 tone 末尾/缓冲尾被截断)。 + * 所有等待为 COND_WAIT_MS 周期 timedwait:丢失唤醒最多迟 1s 自愈,SIGTERM 也能及时退。 */ static void *playback_thread(void *arg) { (void)arg; static uint8_t buf[READ_CHUNK]; for (;;) { + g_hb_play++; int have = 0; pthread_mutex_lock(&g_mu); for (;;) { + g_hb_play++; if (g_stop) { have = -1; break; } int fs = frame_size(); - if (fs <= 0) { pthread_cond_wait(&g_cond, &g_mu); continue; } /* 无流 param → 等 */ + if (fs <= 0) { cond_wait_tmo(COND_WAIT_MS); continue; } /* 无流 param → 等 */ if (g_reconfig_pending) { have = 1; break; } /* 重配 */ int draining = !g_st.active; int paused = g_st.active && g_paused; @@ -784,11 +915,11 @@ static void *playback_thread(void *arg) if ((draining && av < (size_t)fs) || paused) { /* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */ if (draining && g_dev_open) dev_close(); - pthread_cond_wait(&g_cond, &g_mu); + cond_wait_tmo(COND_WAIT_MS); continue; } if (draining || av >= (size_t)fs) { have = 2; break; } /* 有数据可读(或排空态) */ - pthread_cond_wait(&g_cond, &g_mu); + cond_wait_tmo(COND_WAIT_MS); } if (have == -1) { pthread_mutex_unlock(&g_mu); break; } if (have == 1) { reconfigure_locked(); pthread_mutex_unlock(&g_mu); continue; } @@ -902,6 +1033,9 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin g_paused = 0; g_written_frames = g_played_frames = 0; g_underrun = 0; + g_drop_frames = 0; /* 每流计数(txdrop 为全局客户端级,不随流清) */ + g_stall = 0; + g_dev_bytes = 0; /* 测试钩子计数随新流归零 */ /* 重建 ring + 标记重配 */ ring_rebuild(); _audio_mark_reconfig_needed(); @@ -919,12 +1053,10 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin 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; - } + size_t wn = ring_write_locked(pl, len, g_st.sid); + int fs = frame_size(); + if (wn && fs > 0) + g_written_frames += wn / (uint32_t)fs; } break; } @@ -936,6 +1068,10 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin 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); } + /* PAUSE/RESUME 必须唤醒播放线程(它可能正 timedwait 在 paused 分支; + * 旧代码 RESUME 不 signal,恢复后要等下一块 WRITE 才醒) */ + if (type != MSG_AUDIO_STOP) + pthread_cond_signal(&g_cond); else /* STOP */ { logmsg("STOP sid=%u (app=%s)", (unsigned)sid, g_st.app_id); g_st.active = 0; @@ -954,11 +1090,13 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin } case MSG_AUDIO_QUERY: { char txt[1024]; - int rb = amixer_get(9, 1); + /* 路由口用缓存值 g_route_cur(路由线程维护),不在 IO 热路径 popen amixer—— + * 驱动 wedge 时 amixer 虽有界但仍会拖慢 IO 循环 1.5s/次。 */ + int rb = g_route_cur; 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 " + "written=%llu played=%llu underrun=%u dropped=%llu stall=%u txdrop=%u paused=%d " "route_mode=%s port=%d vol=%d\n", g_st.active ? 1 : 0, g_st.active ? g_st.app_id : "-", @@ -966,7 +1104,9 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin 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, + (unsigned)g_underrun, + (unsigned long long)g_drop_frames, + (unsigned)g_stall, (unsigned)g_txdrop, g_paused, g_route_manual == -1 ? "auto" : "manual", (g_route_manual != -1 ? g_route_manual : rb), vol); @@ -1066,10 +1206,11 @@ static void *io_thread(void *arg) return NULL; } chmod(g_sock, 0666); - if (listen(lfd, 4) != 0) { logmsg("io: listen: %s", strerror(errno)); close(lfd); return NULL; } + if (listen(lfd, 8) != 0) { logmsg("io: listen: %s", strerror(errno)); close(lfd); return NULL; } logmsg("io thread listening on %s", g_sock); for (;;) { + g_hb_io++; if (g_stop) break; struct pollfd pfds[CLI_MAX + 1]; int n = 1; @@ -1080,7 +1221,16 @@ static void *io_thread(void *arg) n++; } int pr = poll(pfds, (nfds_t)n, 200); - if (pr < 0) { if (errno == EINTR) continue; break; } + if (pr < 0) { + if (errno == EINTR) continue; + /* poll 出错绝不退出线程(listener 一丢就是全守护失联,CLI ECONNREFUSED); + * 记日志(限频)+ 短睡后继续。 */ + static unsigned s_pollerr = 0; + if ((s_pollerr++ & 15u) == 0) logmsg("io: poll err: %s (x%u, keep going)", strerror(errno), s_pollerr); + struct timespec ts = { 0, 100 * 1000000L }; + nanosleep(&ts, NULL); + continue; + } /* pfds 是本轮新数组(revents 本轮清零),直接以 revents 为准(238 教训:每圈重建, * 杜绝陈旧 revents。勿在此清零)。 */ struct pollfd snaps[CLI_MAX + 1]; @@ -1140,8 +1290,9 @@ static void *route_thread(void *arg) } while (!g_stop) { pthread_mutex_lock(&g_mu); - route_evaluate(&last_hb, &last_bal, &last_target); + int manual = g_route_manual; /* 快照后放锁:amixer popen 不持 g_mu(防 wedge 级联) */ pthread_mutex_unlock(&g_mu); + route_evaluate(manual, &last_hb, &last_bal, &last_target); int naps = 5; /* 1000ms / 200ms 分段,即时响应 SIGTERM */ for (int k = 0; k < naps && !g_stop; k++) { struct timespec ts = { 0, 200 * 1000000L }; @@ -1154,8 +1305,10 @@ static void *route_thread(void *arg) static void on_sig(int sig) { (void)sig; + /* 只置标志(async-signal-safe):所有等待均为有界 timedwait(播放 1s / IO poll 200ms / + * 路由 200ms 分段 / ring 写 ≤g_ring_wait_ms),线程自行看到 g_stop 退出—— + * 不在信号处理器里调 pthread_cond_signal(非 async-signal-safe)。 */ g_stop = 1; - pthread_cond_signal(&g_cond); } int main(int argc, char **argv) @@ -1166,7 +1319,10 @@ int main(int argc, char **argv) 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"); + 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" + " MEOW_AUDIO_RING_WAIT_MS (ring 满写等待上限, 缺省 5000)\n" + " MEOW_AUDIO_TEST_WRITE_DELAY_MS / MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES (故障注入, 仅测试)\n"); return 0; } } @@ -1174,6 +1330,9 @@ int main(int argc, char **argv) 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; + if ((e = getenv("MEOW_AUDIO_RING_WAIT_MS")) && atoi(e) > 0) g_ring_wait_ms = atoi(e); + if ((e = getenv("MEOW_AUDIO_TEST_WRITE_DELAY_MS")) && atoi(e) > 0) g_test_write_delay_ms = atoi(e); + if ((e = getenv("MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES")) && atoll(e) > 0) g_test_wedge_after = atoll(e); const char *sink = getenv("MEOW_AUDIO_SINK"); if (sink && sink[0]) { if (!strcmp(sink, "file")) g_sink = SINK_FILE; @@ -1196,19 +1355,49 @@ int main(int argc, char **argv) } log_open(); - logmsg("meow_audiod start: sock=%s sink=%d wav=%s force=%d", g_sock, g_sink, g_out_wav, g_force); + logmsg("meow_audiod start: sock=%s sink=%d wav=%s force=%d ring_wait=%dms%s%s", + g_sock, g_sink, g_out_wav, g_force, g_ring_wait_ms, + g_test_write_delay_ms > 0 ? " TEST-DELAY" : "", + g_test_wedge_after >= 0 ? " TEST-WEDGE" : ""); if (g_sink == SINK_ALSA) hw_probe(); g_vol_pct = settings_get_volume(); logmsg("volume init = %d%%", g_vol_pct); + /* g_cond 用 CLOCK_MONOTONIC(所有 timedwait 的基准;防系统时间跳变影响护栏超时) */ + { + pthread_condattr_t ca; + pthread_condattr_init(&ca); + pthread_condattr_setclock(&ca, CLOCK_MONOTONIC); + pthread_cond_init(&g_cond, &ca); + pthread_condattr_destroy(&ca); + } + 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); + /* 主线程 = watchdog:心跳静默 >WD_SILENT_SEC 秒即告警(只告警不强杀—— + * 对卡在内核的线程任何外部干预都不安全;护栏保证其它线程/连接继续服务)。 */ + unsigned last_io = 0, last_play = 0; + int io_silent = 0, play_silent = 0; while (!g_stop) { struct timespec ts = { 1, 0 }; nanosleep(&ts, NULL); + if (g_stop) break; + unsigned io = g_hb_io, pl = g_hb_play; + io_silent = (io == last_io) ? io_silent + 1 : 0; last_io = io; + play_silent = (pl == last_play) ? play_silent + 1 : 0; last_play = pl; + if (io_silent == WD_SILENT_SEC || (io_silent > WD_SILENT_SEC && io_silent % 30 == 0)) + logmsg("WATCHDOG: io thread silent %ds (accept/read loop stuck?)", io_silent); + if (play_silent == WD_SILENT_SEC || (play_silent > WD_SILENT_SEC && play_silent % 30 == 0)) { + pthread_mutex_lock(&g_mu); + int act = g_st.active; + uint64_t wr = g_written_frames, plf = g_played_frames; + pthread_mutex_unlock(&g_mu); + logmsg("WATCHDOG: playback thread silent %ds (active=%d written=%llu played=%llu, device wedged?)", + play_silent, act, (unsigned long long)wr, (unsigned long long)plf); + } } logmsg("meow_audiod shutdown"); pthread_join(ti, NULL); diff --git a/patch_v2/meow_audiod/meow_audiod_host b/patch_v2/meow_audiod/meow_audiod_host index d1e5517..47164c8 100755 Binary files a/patch_v2/meow_audiod/meow_audiod_host and b/patch_v2/meow_audiod/meow_audiod_host differ diff --git a/patch_v2/meow_audiod/test_audiod_host b/patch_v2/meow_audiod/test_audiod_host index b89bf2d..3975099 100755 Binary files a/patch_v2/meow_audiod/test_audiod_host 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 index 15a6508..1263e7f 100644 --- a/patch_v2/meow_audiod/test_audiod_host.c +++ b/patch_v2/meow_audiod/test_audiod_host.c @@ -18,10 +18,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -38,17 +40,36 @@ static void check(int cond, const char *name) else { printf(" FAIL %s\n", name); g_fail++; } } -/* ---- 原始 IPC 客户端 ---- */ +/* ---- 原始 IPC 客户端(全有界:connect 非阻塞 2s + SO_SNDTIMEO 2s——回归卡死时 + * 测试 FAIL 而不是跟着挂死) ---- */ 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; + int fl = fcntl(c->fd, F_GETFL, 0); + if (fl >= 0) fcntl(c->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", TSOCK); - if (connect(c->fd, (struct sockaddr *)&a, sizeof a) < 0) { close(c->fd); return -1; } + int r = connect(c->fd, (struct sockaddr *)&a, sizeof a); + if (r < 0 && errno == EINPROGRESS) { + struct pollfd p; + p.fd = c->fd; p.events = POLLOUT; p.revents = 0; + if (poll(&p, 1, 2000) > 0) { + int err = 0; + socklen_t el = sizeof err; + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, &err, &el) == 0 && err != 0) { errno = err; r = -1; } + } else { + r = -1; + } + } + if (r < 0) { close(c->fd); return -1; } + if (fl >= 0) fcntl(c->fd, F_SETFL, fl); + struct timeval tv; + tv.tv_sec = 2; tv.tv_usec = 0; + setsockopt(c->fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); return 0; } static int w_all(int fd, const void *p, size_t n) @@ -96,20 +117,42 @@ 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) +/* 带超时的停止:SIGTERM 后最多等 tmo_ms;不退 → SIGKILL 并返回 0(卡死回归显形)。 */ +static int stop_audiod_tmo(int tmo_ms) +{ + if (g_audiod_pid <= 0) return 1; + kill(g_audiod_pid, SIGTERM); + int ok = 0; + for (int i = 0; i < tmo_ms / 50; i++) { + if (waitpid(g_audiod_pid, NULL, WNOHANG) == g_audiod_pid) { ok = 1; break; } + usleep(50000); + } + if (!ok) { kill(g_audiod_pid, SIGKILL); waitpid(g_audiod_pid, NULL, 0); } + g_audiod_pid = -1; + return ok; +} + +static int start_audiod_ex(const char *sink, const char *wedge_after, const char *ring_wait_ms) { pid_t pid = fork(); if (pid == 0) { - setenv("MEOW_AUDIO_SINK", "file", 1); + setenv("MEOW_AUDIO_SINK", sink, 1); setenv("MEOW_AUDIO_OUT", TWAV, 1); setenv("MEOW_AUDIO_SOCK", TSOCK, 1); setenv("MEOW_AUDIO_FORCE", "1", 1); + if (wedge_after) setenv("MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES", wedge_after, 1); + if (ring_wait_ms) setenv("MEOW_AUDIO_RING_WAIT_MS", ring_wait_ms, 1); execl("./meow_audiod_host", "meow_audiod_host", (char *)0); _exit(127); } return pid; } +static int start_audiod(void) +{ + return start_audiod_ex("file", NULL, NULL); +} + /* 直接客户端 open */ static int do_open(struct cli *c, int rate, int ch, int fmt, m_audio_open_ack_t *ack) { @@ -289,6 +332,65 @@ int main(void) check(strstr(q, "active=0") != NULL, "EOF recycle -> inactive"); close(c3q.fd); + /* ---- T5:长流回归(3 分钟等效 PCM 持续灌;FILE sink 全速排空 → 加速时钟等效)。 + * 回归目标:真机「长流卡死」——喂流期间 listener/IO 循环必须持续可服务, + * 帧数零丢失、零 underrun、零丢弃,STOP 后排空落盘完整。 ---- */ + printf("[T5 long stream (3min equivalent)]\n"); + struct cli l5; + check(cli_connect(&l5) == 0, "connect L5"); + m_audio_open_ack_t ackL5; + check(do_open(&l5, 44100, 2, AUDIO_FMT_S16, &ackL5) == 0 && ackL5.err == MEOW_OK, "open L5"); + uint32_t fs5 = (uint32_t)ackL5.grant_ch * (ackL5.grant_fmt == AUDIO_FMT_S16 ? 2u : 4u); + if (fs5 == 0) fs5 = 4; + uint64_t total_frames = 44100ULL * 180; /* 3 分钟等效流 */ + uint64_t total_bytes = total_frames * fs5; + uint8_t chunk[4096]; + memset(chunk, 0x5A, sizeof chunk); /* 内容无关:测完整性/不卡死 */ + size_t per = (sizeof chunk / fs5) * fs5; + uint64_t sent = 0; + int werr = 0, probe_n = 0, probe_ok = 0; + while (sent < total_bytes) { + uint64_t w = total_bytes - sent; + if (w > per) w = per; + if (do_write(&l5, chunk, (uint32_t)w) != 0) { werr = 1; break; } + sent += w; + if (sent % (512 * per) == 0) { /* 每 2MB 探一次 listener 活性 */ + struct cli s5; + s5.fd = -1; + probe_n++; + if (cli_connect(&s5) == 0) { + char q5[1024]; + if (do_query(&s5, q5, sizeof q5) == 0 && strstr(q5, "active=1")) probe_ok++; + close(s5.fd); + } + } + } + check(!werr && sent == total_bytes, "long feed completed (io never stalled)"); + printf(" fed %llu bytes (%llu frames), mid-feed status probes %d/%d alive\n", + (unsigned long long)sent, (unsigned long long)(sent / fs5), probe_ok, probe_n); + check(probe_n >= 10 && probe_ok == probe_n, "listener/io alive during long feed"); + do_ctrl(&l5, MSG_AUDIO_STOP, ackL5.stream_id); + char expect[64]; + snprintf(expect, sizeof expect, "played=%llu", (unsigned long long)total_frames); + int drained = 0; + char q5[1024]; + for (int i = 0; i < 300; i++) { /* 最多 30s 等排空 */ + if (do_query(&l5, q5, sizeof q5) == 0 && strstr(q5, expect)) { drained = 1; break; } + usleep(100000); + } + check(drained, "long stream fully played (played == fed frames)"); + if (drained) + check(strstr(q5, "underrun=0") != NULL && strstr(q5, "dropped=0") != NULL, + "no underrun/drop on long stream"); + usleep(300000); /* 等 dev_close 回填 WAV 头 */ + close(l5.fd); + { + FILE *f = fopen(TWAV, "rb"); + long sz = -1; + if (f) { fseek(f, 0, SEEK_END); sz = ftell(f); fclose(f); } + check(sz == (long)(44 + total_bytes), "long WAV size == 44 + fed bytes"); + } + /* ---- T4:tone 写 WAV(由 CLI 走完整 IPC 链路;mipsel/host CLI 共用) ---- */ printf("[T4 tone -> WAV]\n"); unlink(TWAV); @@ -305,6 +407,78 @@ int main(void) { FILE *f = fopen(TWAV, "rb"); if (f) { fseek(f, 0, SEEK_END); printf("%ld bytes\n", ftell(f)); fclose(f); } } stop_audiod(); + + /* ---- T6:设备 wedge 生存(故障注入复现真机长流卡死场景)。 + * MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES=128KB → 播放线程永久堵在 dev_write(睡到 g_stop)。 + * 旧代码:ring 满 → IO 线程 ring_write cond_wait 永不醒 → accept 停摆 → 全链冻结。 + * 护栏后:ring 写有界(RING_WAIT_MS=50ms)超时丢弃,IO/accept/preempt 全部存活。 ---- */ + printf("[T6 device wedge survival]\n"); + unlink(TSOCK); + g_audiod_pid = start_audiod_ex("null", "131072", "50"); + usleep(400000); + struct cli w6; + check(cli_connect(&w6) == 0, "connect W6"); + m_audio_open_ack_t ackW6; + check(do_open(&w6, 44100, 2, AUDIO_FMT_S16, &ackW6) == 0 && ackW6.err == MEOW_OK, "open W6"); + int werr6 = 0; + uint64_t sent6 = 0; + for (int i = 0; i < 100; i++) { /* 400KB:128KB 后 wedge,其余全靠丢弃 */ + if (do_write(&w6, chunk, (uint32_t)per) != 0) { werr6 = 1; break; } + sent6 += per; + } + check(!werr6 && sent6 == 100 * (uint64_t)per, "feed continues across wedge (no deadlock)"); + struct cli s6; + s6.fd = -1; + char q6[1024]; + int io_alive = 0; + if (cli_connect(&s6) == 0) { + if (do_query(&s6, q6, sizeof q6) == 0 && strstr(q6, "route_mode=")) io_alive = 1; + close(s6.fd); + } + check(io_alive, "io loop alive after wedge (connect+query served)"); + check(io_alive && strstr(q6, "dropped=") != NULL && atoi(strstr(q6, "dropped=") + 8) > 0, + "drop policy engaged (dropped>0)"); + struct cli p6; + p6.fd = -1; + int pre6 = 0; + if (cli_connect(&p6) == 0) { + m_audio_open_ack_t ak6; + if (do_open(&p6, 48000, 2, AUDIO_FMT_S16, &ak6) == 0 && ak6.err == MEOW_OK) pre6 = 1; + close(p6.fd); + } + check(pre6, "preempt OPEN granted while wedged"); + close(w6.fd); + check(stop_audiod_tmo(5000) != 0, "wedged audiod exits on SIGTERM within 5s"); + + /* ---- T7:慢/不读客户端下行丢弃(server→client 写绝不堵 IO 循环)。 + * Z 狂发 QUERY 从不读响应 → server 下行缓冲灌满 → send_frame 单发即丢(txdrop++)。 + * 护栏断言:server 不卡死、新连接照常服务、txdrop 有计数。 ---- */ + printf("[T7 slow-client tx drop]\n"); + unlink(TSOCK); + g_audiod_pid = start_audiod_ex("null", NULL, NULL); + usleep(400000); + struct cli z7; + check(cli_connect(&z7) == 0, "connect Z7"); + int serr7 = 0, sent7 = 0; + for (int i = 0; i < 2000; i++) { + if (sf(z7.fd, MSG_AUDIO_QUERY, 9, NULL, 0) != 0) { serr7 = 1; break; } + sent7++; + } + check(!serr7 && sent7 == 2000, "query spam sent (client never reads)"); + struct cli f7; + f7.fd = -1; + char q7[1024]; + int alive7 = 0; + if (cli_connect(&f7) == 0) { + if (do_query(&f7, q7, sizeof q7) == 0 && strstr(q7, "route_mode=")) alive7 = 1; + close(f7.fd); + } + check(alive7, "server responsive after tx drops"); + check(alive7 && strstr(q7, "txdrop=") != NULL && atoi(strstr(q7, "txdrop=") + 7) > 0, + "txdrop counted"); + close(z7.fd); + stop_audiod(); + printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail); return g_fail ? 1 : 0; } diff --git a/patch_v2/sdk/meow_ui_audio.c b/patch_v2/sdk/meow_ui_audio.c index e16afb8..eb7a008 100644 --- a/patch_v2/sdk/meow_ui_audio.c +++ b/patch_v2/sdk/meow_ui_audio.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -73,15 +74,39 @@ static const char *sock_path(void) return (e && e[0]) ? e : AUD_SOCK_DEFAULT; } +/* 连接 audiod。**全部有界**:connect 非阻塞 + poll 2s(对端 listen backlog 满/wedge 时 + * 阻塞 connect 会把 App 播放线程永久压进内核——真机长流卡死时 ncm 卡死的同款路径); + * 建连后 SO_SNDTIMEO/RCVTIMEO 2s:audiod 停摆时 write 超时返回错误 → 标记流死, + * App 走「重新 open」恢复路径,绝不永久阻塞。 */ +#define AUD_CONN_TMO_MS 2000 static int connect_path(const char *path) { 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; strncpy(a.sun_path, path, sizeof a.sun_path - 1); - if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; } + 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, AUD_CONN_TMO_MS) > 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); /* 回阻塞模式;读写超时由 SO_*TIMEO 兜底 */ + struct timeval tv; + tv.tv_sec = AUD_CONN_TMO_MS / 1000; tv.tv_usec = 0; + setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof tv); + setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof tv); return fd; } diff --git a/scripts/build/fw_content.conf b/scripts/build/fw_content.conf index fb9360b..38f0db1 100644 --- a/scripts/build/fw_content.conf +++ b/scripts/build/fw_content.conf @@ -38,8 +38,8 @@ patch_v2/meow_svc_power/meow_svc_power|/usr/bin/meow_svc_power|39ae3aa255695a97f 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_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|a6e4c2950b74a28748d7b31a9a680b31|独占音频守护 M2.91(+开机武装 hbc3000 FPGA 根治 PCM 启动死锁) -patch_v2/meow_audiod/meow_audio|/usr/bin/meow_audio|5cc3c35459409e93672448c99543dc93|audiod 用户侧 CLI(status/tone/route/vol;md5 打包时统一填 agent-244) +patch_v2/meow_audiod/meow_audiod|/usr/bin/meow_audiod|86a8b6793d516c90bbd12c2997b30d8f|独占音频守护 M2.91(+开机武装 hbc3000 FPGA 根治 PCM 启动死锁;+长流防卡死护栏 §12:ring有界写/ALSA poll自愈/popen有界收割/tx丢弃/watchdog) +patch_v2/meow_audiod/meow_audio|/usr/bin/meow_audio|c23b96490842f54324b855fd9c2467a5|audiod 用户侧 CLI(status/tone/route/vol;connect/write 全有界 2s 超时) # ---- 开机 init 脚本(etc/init.d,数字决定启动顺序,按文件名自然序)---- patch_v2/meow_sysui/S70meow_sysui|/etc/init.d/S70meow_sysui|e435affcb56499e5229728c6ae2524b9|sysui 自启 M2.85(launcher.conf enabled=1+迁移)