/* play/audio_out.c — 音频输出抽象:默认 SINK_AUDIOD(meow_audiod 独占服务,经 SDK meow_audio_*), * 调试保底 SINK_ALSADEV 直开 hw:0,0,另有 FILE/NULL sink。 * * 后端选择(audio_out_open,env 驱动): * - 缺省 → SINK_AUDIOD(SDK 客户端连 /usr/data/meow_audio.sock;audiod 独占声卡、 * 格式归一/采样率跟随/抢占全在服务端,AUDIOD_DESIGN.md §7)。 * - NCM_AUDIO_SINK=file/null/alsa → FILE(WAV,NCM_AUDIO_OUT 指定路径,缺省 /tmp/ncm_out.wav)/ * NULL(丢弃,仅测解码 RT)/ ALSA 直开。 * - MEOW_AUDIO_DIRECT=1 → ALSA 直开(调试保底,非正式能力,勿依赖)。 * audiod 不在 → open 返回 -1(不隐式回退直开,AUDIOD_DESIGN.md §7)。 * * ALSA 直开两大坑(真机定位,agent-246,与 audiod/SDK 同款修法,勿回退): * ① hw_params 须「全放行」初始化(params_any 语义:mask 全 1、interval [0,UINT_MAX]) * ——memset 0 的 interval 等价「只能取 0」→ 内核 refine 必空 → HW_PARAMS 恒 EINVAL; * ② access/format/subformat mask 须先清零再单点(pmask 只 OR)——留全 1 会被 choose 成 * MMAP_INTERLEAVED → write() 恒 EINVAL。 * 注意 musl 的 sound/asound.h 中 masks/intervals 是**重索引**数组: * masks[k] ↔ hw_param (FIRST_MASK + k);intervals[k] ↔ hw_param (FIRST_INTERVAL + k)。 * * 线程模型:仅由 player_manager 播放线程调用(单写者);audio_out_close 可被 UI 线程调用打断阻塞写。 * 日志:open 结果/参数、audiod 授权 grant_*、write 错误、pause/resume/close 全写 meow_log * (无声排查的永久资产,勿省)。 */ #include "../ncm_app_internal.h" #include "meow_ipc.h" /* AUDIO_FMT_S16 */ #include #include #include #include #include #include #ifdef __linux__ #include #include #define NCM_HAVE_ALSA 1 #endif enum { SINK_AUDIOD = 0, SINK_ALSADEV = 1, SINK_FILE = 2, SINK_NULL = 3 }; static int g_sink = -1; static int g_fd = -1; /* alsa or file fd */ static int g_sid = 0; /* audiod stream id(>0 有效) */ static int g_rate, g_ch, g_bits; static const char *g_path = NULL; static uint64_t g_wr_frames = 0; /* 本次 open 累计写帧(日志) */ static int g_wr_errs = 0; /* 写错误计数(节流日志) */ #define NCM_DEFAULT_OUT "/tmp/ncm_out.wav" void audio_out_set_target(int ms) { (void)ms; } int audio_out_buffered_ms(void) { return 0; } #ifdef NCM_HAVE_ALSA /* ALSA 非 mmap 直装:开设备 → hw_params(access=rw_interleaved, S16_LE, rate, ch)→ write。 */ 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; } static int alsa_open(int rate, int ch, int bits) { if (bits != 16 && bits != 32) return -1; /* §19:S16 + S32(flac >16bit 满幅) */ int fd = open("/dev/snd/pcmC0D0p", O_RDWR); if (fd < 0) { meow_log(MEOW_LOG_ERROR, "audio: alsa open /dev/snd/pcmC0D0p failed errno=%d", errno); return -1; } struct snd_pcm_hw_params p; /* 坑①:「全放行」初始化(snd_pcm_hw_params_any 语义):mask 全 1、interval [0,UINT_MAX]。 * memset 0 的 interval 会被内核当成「只能取 0」约束 → refine 必空 → HW_PARAMS 恒 EINVAL。 */ 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; } p.flags = 0; /* 坑②:mask 先清零再单点(pmask 只 OR):全 1 的 access 会被内核 choose 成 * MMAP_INTERLEAVED → write() 恒 EINVAL(本机 RW_INTERLEAVED 可用,须显式约束)。 */ 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, bits == 32 ? SNDRV_PCM_FORMAT_S32_LE : SNDRV_PCM_FORMAT_S16_LE); 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], (unsigned)ch); pivset(&p.intervals[SNDRV_PCM_HW_PARAM_RATE - SNDRV_PCM_HW_PARAM_FIRST_INTERVAL], (unsigned)rate); p.rmask = ~0u; p.info = 0; p.msbits = 0; p.rate_num = 0; p.rate_den = 0; if (ioctl(fd, SNDRV_PCM_IOCTL_HW_PARAMS, &p) != 0) { meow_log(MEOW_LOG_ERROR, "audio: alsa HW_PARAMS failed errno=%d rate=%d ch=%d", errno, rate, ch); close(fd); return -1; } ioctl(fd, SNDRV_PCM_IOCTL_PREPARE, 0); meow_log(MEOW_LOG_INFO, "audio: alsa open ok rate=%d ch=%d bits=%d (direct debug)", rate, ch, bits); return fd; } #endif /* 写 WAV 头(44 字节标准 PCM 布局:RIFF/WAVE/fmt(16)/data)。 * fmt(16) = format(2) ch(2) sample_rate(4) byte_rate(4) block_align(2) bits(2)。 * §19:bits 16/32 均合法(32=flac >16bit 的 S32 满幅样本)。 */ static void file_write_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); /* PCM */ s16 = (uint16_t)ch; memcpy(h+22, &s16, 2); le = (uint32_t)rate; memcpy(h+24, &le, 4); /* sample rate */ le = (uint32_t)(rate * ch * bits / 8); memcpy(h+28, &le, 4); /* byte rate */ s16 = (uint16_t)(ch * bits / 8); memcpy(h+32, &s16, 2); /* block align */ s16 = (uint16_t)bits; memcpy(h+34, &s16, 2); /* bits/sample */ memcpy(h+36, "data", 4); le = data_len; memcpy(h+40, &le, 4); if (write(fd, h, 44) != 44) { /* 忽略 */ } } static int file_open(int rate, int ch, int bits) { int fd = open(g_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { /* 无路可写 → null */ meow_log(MEOW_LOG_WARN, "audio: file open %s failed errno=%d -> null sink", g_path, errno); g_sink = SINK_NULL; g_fd = -1; return 0; } file_write_header(fd, rate, ch, bits, 0); g_sink = SINK_FILE; g_fd = fd; meow_log(MEOW_LOG_INFO, "audio: file sink %s rate=%d ch=%d bits=%d", g_path, rate, ch, bits); return 0; } int audio_out_open(int sample_rate, int channels, int bits) { g_rate = sample_rate; g_ch = channels; g_bits = bits; g_wr_frames = 0; g_wr_errs = 0; const char *env = getenv("NCM_AUDIO_SINK"); g_path = getenv("NCM_AUDIO_OUT"); if (!g_path || !g_path[0]) g_path = NCM_DEFAULT_OUT; /* 显式 null(测解码 RT) */ if (env && strcmp(env, "null") == 0) { g_sink = SINK_NULL; g_fd = -1; meow_log(MEOW_LOG_INFO, "audio: null sink rate=%d ch=%d", sample_rate, channels); return 0; } /* 显式 file(宿主/回归) */ if (env && strcmp(env, "file") == 0) return file_open(sample_rate, channels, bits); /* 调试保底:ALSA 直开(仅显式指定;修法见文件头两大坑注释) */ const char *de = getenv("MEOW_AUDIO_DIRECT"); int want_alsa = (env && strcmp(env, "alsa") == 0) || (de && de[0] == '1'); if (want_alsa) { #ifdef NCM_HAVE_ALSA int fd = alsa_open(sample_rate, channels, bits); if (fd >= 0) { g_sink = SINK_ALSADEV; g_fd = fd; return 0; } meow_log(MEOW_LOG_ERROR, "audio: alsa direct open failed"); return -1; #else meow_log(MEOW_LOG_ERROR, "audio: alsa sink requested but no ALSA on this build"); return -1; #endif } /* 默认:SINK_AUDIOD(meow_audiod 独占服务)。§19:bits 16→S16 / 32→S32 * (flac >16bit 由 dr_flac 出 S32 满幅;格式归一/DAC 原生率跟随在 audiod 侧)。 */ int fmt; if (bits == 16) fmt = AUDIO_FMT_S16; else if (bits == 32) fmt = AUDIO_FMT_S32; else { meow_log(MEOW_LOG_ERROR, "audio: audiod open: bits=%d unsupported (16/32)", bits); return -1; } int sid = meow_audio_open(sample_rate, channels, fmt); if (sid < 0) { meow_log(MEOW_LOG_ERROR, "audio: audiod open failed rc=%d rate=%d ch=%d (audiod 不在?)", sid, sample_rate, channels); return -1; } g_sink = SINK_AUDIOD; g_sid = sid; /* 授权参数(grant_*)落日志:无声排查关键资产 */ meow_audio_state st; if (meow_audio_query(&st) == 0) meow_log(MEOW_LOG_INFO, "audio: audiod open ok sid=%d req=%dHz/%dch/%dbit grant=%uHz/%uch/fmt=%u underrun=%u", sid, sample_rate, channels, bits, st.rate, st.ch, st.fmt, st.underrun); else meow_log(MEOW_LOG_INFO, "audio: audiod open ok sid=%d req=%dHz/%dch/%dbit", sid, sample_rate, channels, bits); return 0; } int audio_out_write(const void *samples, int frames) { if (g_app_quit) return -1; if (g_sink == SINK_NULL || g_sink < 0) return frames; if (g_sink == SINK_AUDIOD) { int w = meow_audio_write(samples, frames); if (w != frames) { g_wr_errs++; if (g_wr_errs == 1 || (g_wr_errs % 100) == 0) meow_log(MEOW_LOG_ERROR, "audio: audiod write failed rc=%d frames=%d (errs=%d)", w, frames, g_wr_errs); return -1; } g_wr_frames += (uint64_t)frames; return frames; } size_t n = (size_t)frames * (size_t)g_ch * (size_t)(g_bits / 8); if (g_sink == SINK_ALSADEV) { /* 阻塞尽力写满 - EINTR/EAGAIN 重试;错误返回 */ const unsigned char *p = (const unsigned char *)samples; size_t off = 0; while (off < n) { ssize_t w = write(g_fd, p + off, n - off); if (w > 0) { off += (size_t)w; continue; } if (w < 0 && (errno == EINTR || errno == EAGAIN)) continue; meow_log(MEOW_LOG_ERROR, "audio: alsa write failed errno=%d", errno); return -1; } g_wr_frames += (uint64_t)frames; return frames; } if (g_sink == SINK_FILE) { if (write(g_fd, samples, n) != (ssize_t)n) return -1; g_wr_frames += (uint64_t)frames; return frames; } return frames; } /* 暂停(1)/恢复(0) audiod 流(停 PCM 时钟但不丢流)。仅 AUDIOD 后端有效,其余后端恒 0。 * 恢复时若流已死(被抢占/audiod 断链)→ 自动重开(解码位置不变,仅重建输出流)。 */ int audio_out_pause(int paused) { if (g_sink != SINK_AUDIOD || g_sid <= 0) return 0; int rc = meow_audio_pause(g_sid, paused); if (rc >= 0) { meow_log(MEOW_LOG_INFO, "audio: audiod %s sid=%d ok", paused ? "pause" : "resume", g_sid); return 0; } if (paused) { /* 暂停失败(流多半已死):记日志即可,恢复时会走重开 */ meow_log(MEOW_LOG_WARN, "audio: audiod pause sid=%d rc=%d (stream dead?)", g_sid, rc); return 0; } /* 恢复失败 = 流已死 → 重开(格式按本次 open 的 bits 还原:16→S16 / 32→S32) */ meow_log(MEOW_LOG_WARN, "audio: audiod resume sid=%d rc=%d -> reopen", g_sid, rc); meow_audio_close(g_sid); g_sid = 0; int sid = meow_audio_open(g_rate, g_ch, g_bits == 32 ? AUDIO_FMT_S32 : AUDIO_FMT_S16); if (sid < 0) { meow_log(MEOW_LOG_ERROR, "audio: audiod reopen failed rc=%d rate=%d ch=%d", sid, g_rate, g_ch); return -1; } g_sid = sid; meow_log(MEOW_LOG_INFO, "audio: audiod reopen ok sid=%d", sid); return 0; } void audio_out_close(void) { if (g_sink == SINK_AUDIOD) { if (g_sid > 0) { meow_audio_close(g_sid); meow_log(MEOW_LOG_INFO, "audio: audiod close sid=%d written_frames=%llu wr_errs=%d", g_sid, (unsigned long long)g_wr_frames, g_wr_errs); g_sid = 0; } g_sink = -1; return; } if (g_sink == SINK_FILE && g_fd >= 0) { /* 回填:offset 4 = RIFF 总长-36;offset 40 = data 长度 */ off_t end = lseek(g_fd, 0, SEEK_END); if (end > 44) { uint32_t data_len = (uint32_t)(end - 44); lseek(g_fd, 4, SEEK_SET); uint32_t le = 36 + data_len; if (write(g_fd, &le, 4) != 4) { } lseek(g_fd, 40, SEEK_SET); if (write(g_fd, &data_len, 4) != 4) { } } } if (g_fd >= 0) close(g_fd); g_fd = -1; g_sink = -1; }