Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/audio_out.c
T
MeowOS 70aec677ce ncm: FLAC 解码落地(dr_flac 0.13.4 单头入库) + 缓存键=歌曲ID+音质档位
- 解码分发 decoder.c:magic(fLaC→dr_flac / 其它→minimp3);flac ≤16bit→S16、
  >16bit→S32 满幅送 audiod(AUDIO_FMT_S32),rate/ch/bits 按实际流,零 SRC 零裁剪
- song_url_auto 接受 flac(type 权威/url 扩展名兜底),降级语义不变;flac 下载限幅 1GB
- 缓存键 song_<id>_<level>.<ext>:同歌同档命中不重下,换档新条目旧档留 LRU;
  ext 前导点正名(修双点名),lookup 回退历史双点名+旧无档键温和兼容
- audio_out S32 通路(AUDIOD/ALSA/WAV)+ 流死重开按 bits 还原格式
- 验证(0 真实请求):44.1k/16 与 96k/24 自制样本宿主+qemu 帧数/时长精确、
  逐样本零误差;mp3 回归逐帧一致;player_flow_sim P1-P6(新 P6 档位键/flac ext)、
  cache_evict/cover/pagination/window/audiod_chain sim 全 PASS;mipsel 0 warning
- REPORT §19 / VENDOR_NOTES dr_flac 条目;未部署真机(留主会话)
2026-09-01 14:14:38 +08:00

303 lines
13 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.
/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#ifdef __linux__
#include <sound/asound.h>
#include <sys/ioctl.h>
#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;
}