Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/play/audio_out.c
T
MeowOS e0136213fe ncm A2: 音频输出迁移 meow_audiod(SINK_AUDIOD 默认后端)— 修无声根因
- audio_out.c: 新增 SINK_AUDIOD 默认(SDK meow_audio_* 连 audiod);NCM_AUDIO_SINK=file/null/alsa
  与 MEOW_AUDIO_DIRECT=1 保留调试后端;ALSA 保底修两大坑(hw_params 全放行初始化 + mask 清零
  再单点)——旧 memset0 interval=「只能取0」→ HW_PARAMS 恒 EINVAL 即播放无声根因
- 新增 audio_out_pause:PAUSED 沿向 audiod 发 pause(停时钟不丢流);resume 流死自动重开
- player_manager: 注册 meow_audio_on_preempt → g_play 命令通道暂停;播放事件全量 meow_log
  (open 参数/grant_*/write 错误节流/切歌/暂停/seek/done)
- appstate/app: /tmp/ncm_autoplay 真机联调 hook(仅播已缓存曲目,绝不发 song_url)
- bench/audiod_chain_sim.c: 真实 audiod_host 假 socket 全链离线回归 14 项全 PASS
- 验证: host 0 warning / WAV 回归 / qemu core PASS / mipsel 0 warning;真机 OPEN→grant 已通,
  持续写帧被 M2.90 设备 PCM 播放内核硬挂死阻断(REPORT §16.3,4/4 复现,留主会话)
- ncm md5 db5b78ad9e8f40e6306484efe2a08eb3(已热更设备)
2026-09-01 01:17:21 +08:00

297 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) return -1;
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, 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 (direct debug)", rate, ch);
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)。 */
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 独占服务)。v1 SDK 声源格式 S16(minimp3 输出即 S16_LE)。 */
if (bits != 16) {
meow_log(MEOW_LOG_ERROR, "audio: audiod open: bits=%d unsupported (v1 S16 only)", bits);
return -1;
}
int sid = meow_audio_open(sample_rate, channels, AUDIO_FMT_S16);
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 short *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;
}
/* 恢复失败 = 流已死 → 重开 */
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, 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;
}