#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 * 跳过门控。 * * 防卡死护栏(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 是 * 重索引数组(见 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) #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 丢弃 */ /* 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 唤醒;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; 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; 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; 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) ---- */ /* 单发即丢:客户端 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) { 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; } /* cond 有界等待(g_cond 为 CLOCK_MONOTONIC;调用方已持 g_mu)。 */ static void cond_wait_tmo(int ms) { 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 大小/位深 ---- */ 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)。**有界等待**: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 0; /* 流失效/代际变化:非当前授权、被抢占、被停止 → 丢弃 */ if (!g_st.active || g_st.sid != sid) return 0; size_t freeb = g_ring_cap - ring_bytes(); if (freeb >= n) break; 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; 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 n; } /* 重建 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 → 优雅失败) ---- */ /* 读子进程 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]; 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]); 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; } 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"; case 4: return "i2s/spdif digital"; case 5: return "none(disable)"; default: return "idle"; } } /* 应用路由(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) { 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 优先;都不插不动;写失败不重试风暴。 * manual:调用方快照的 g_route_manual(本函数不持 g_mu、不读全局)。 */ static void route_evaluate(int manual, int *last_hb, int *last_bal, int *last_target) { 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; 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 | 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 | 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); } #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 void dev_close(void); /* 前向:dev_write 的 wedge 判死路径要用 */ 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 /* 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)); 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) { /* 测试钩子(仅 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 拒/无卡/wedge 后被判死):丢弃 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; } /* 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; stalls = 0; 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 末尾/缓冲尾被截断)。 * 所有等待为 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) { 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; size_t av = ring_bytes(); if ((draining && av < (size_t)fs) || paused) { /* 排空完毕(或暂停):若设备仍开且已排空 → 关;否则等待 */ if (draining && g_dev_open) dev_close(); cond_wait_tmo(COND_WAIT_MS); continue; } if (draining || av >= (size_t)fs) { have = 2; break; } /* 有数据可读(或排空态) */ 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; } 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; g_drop_frames = 0; /* 每流计数(txdrop 为全局客户端级,不随流清) */ g_stall = 0; g_dev_bytes = 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) { 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; } 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); } /* 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; 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]; /* 路由口用缓存值 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 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 : "-", (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, (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); 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, 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; 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; /* 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]; 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); /* M2.91 挂死根治(AUDIO_HANG_INVESTIGATION.md):hbc3000 FPGA 未烧录+enable 时 * 启动 PCM 会在第二次 hbc3000_start 死锁整机。写 Output Port Switch 是烧录触发器 * (驱动 put:set_spdif_mode→program_ram→enabled)。开机端口恒 0=未烧,故启动时 * 无条件写一次:4.4 在→3,3.5 在→2,都没→4(官方播放器拔插孔后的选择,保 FPGA 已烧)。 * 端口已是目标值(audiod 重启场景,驱动同值短路不写)→ 先写 5(disable) 强制重烧。 */ { int hb = read_switch("/sys/class/switch/headset/state"); int bal = read_switch("/sys/class/switch/balance/state"); int target = (bal == 1) ? 3 : (hb == 1) ? 2 : 4; int cur = amixer_get(9, 1); if (cur == target) { logmsg("route: port already %d, force re-arm via 5", target); apply_route(5); } apply_route(target); last_hb = hb; last_bal = bal; last_target = (hb == 0 && bal == 0) ? -1 : target; logmsg("route: boot arm done (port %d %s, FPGA programmed)", target, port_name(target)); } while (!g_stop) { pthread_mutex_lock(&g_mu); 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 }; nanosleep(&ts, NULL); } } return NULL; } 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; } 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" " MEOW_AUDIO_RING_WAIT_MS (ring 满写等待上限, 缺省 5000)\n" " MEOW_AUDIO_TEST_WRITE_DELAY_MS / MEOW_AUDIO_TEST_WEDGE_AFTER_BYTES (故障注入, 仅测试)\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; 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; 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 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); pthread_join(tp, NULL); pthread_join(tr, NULL); unlink(g_sock); if (g_logfd >= 0) close(g_logfd); return 0; }