Files
MeowOS-For-R3P2/patch_v2/meow_audiod/meow_audiod.c
T

1221 lines
46 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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
* 跳过门控。
*
* 音频路径双形态:
* - 真机/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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <time.h>
#include <poll.h>
#include <pthread.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>
#include "meow_ipc.h"
#ifdef __linux__
#include <sound/asound.h>
#include <sys/ioctl.h>
#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)
/* 客户端流状态 */
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 唤醒) ---- */
/* 前向声明(供 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;
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;
/* 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) ---- */
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 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;
}
/* ---- 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,但可能阻塞等待空间——返回后释放由调用方负责)。
* 返回 0=写入;-1=流已失效(被抢占/停止,丢弃)。 */
static int ring_write_locked(const uint8_t *src, size_t want)
{
size_t fs = (size_t)frame_size();
if (fs == 0) fs = 1;
size_t n = (want / fs) * fs;
if (n == 0) return 0;
for (;;) {
if (g_stop) return -1;
/* 流失效:非当前授权/被抢占/暂停(PAUSE 不新增,但已缓冲保留) */
if (!g_st.active)
return -1;
size_t freeb = g_ring_cap - ring_bytes();
if (freeb >= n) break;
pthread_cond_wait(&g_cond, &g_mu);
}
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 0;
}
/* 重建 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 → 优雅失败) ---- */
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]);
waitpid(pid, NULL, 0);
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 校验,失败记日志不重试风暴。 */
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 优先;都不插不动;写失败不重试风暴。 */
static void route_evaluate(int *last_hb, int *last_bal, int *last_target)
{
if (g_route_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);
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);
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 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
int fd = open(DEV_PCM, O_RDWR);
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)
{
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 堵死 + 背压卡死客户端;
* 日志限频(每 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;
}
size_t off = 0;
while (off < n) {
ssize_t w = write(g_dev_fd, buf + off, n - off);
if (w > 0) { off += (size_t)w; 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 末尾/缓冲尾被截断)。 */
static void *playback_thread(void *arg)
{
(void)arg;
static uint8_t buf[READ_CHUNK];
for (;;) {
int have = 0;
pthread_mutex_lock(&g_mu);
for (;;) {
if (g_stop) { have = -1; break; }
int fs = frame_size();
if (fs <= 0) { pthread_cond_wait(&g_cond, &g_mu); 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();
pthread_cond_wait(&g_cond, &g_mu);
continue;
}
if (draining || av >= (size_t)fs) { have = 2; break; } /* 有数据可读(或排空态) */
pthread_cond_wait(&g_cond, &g_mu);
}
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;
/* 重建 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) {
if (g_ring) {
ring_write_locked(pl, len);
int fs = frame_size();
if (fs > 0)
g_written_frames += len / (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); }
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];
int rb = amixer_get(9, 1);
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 "
"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, 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, 4) != 0) { logmsg("io: listen: %s", strerror(errno)); close(lfd); return NULL; }
logmsg("io thread listening on %s", g_sock);
for (;;) {
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; break; }
/* 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);
route_evaluate(&last_hb, &last_bal, &last_target);
pthread_mutex_unlock(&g_mu);
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;
g_stop = 1;
pthread_cond_signal(&g_cond);
}
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 <path>] [--out <wav>]\n env: MEOW_AUDIO_SOCK/MEOW_AUDIO_OUT/MEOW_AUDIO_SINK(alsa|file|null)/MEOW_AUDIO_AMIXER/MEOW_AUDIO_FORCE=1\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;
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", g_sock, g_sink, g_out_wav, g_force);
if (g_sink == SINK_ALSA) hw_probe();
g_vol_pct = settings_get_volume();
logmsg("volume init = %d%%", g_vol_pct);
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);
while (!g_stop) {
struct timespec ts = { 1, 0 };
nanosleep(&ts, NULL);
}
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;
}