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(已热更设备)
This commit is contained in:
MeowOS committed 2026-09-01 01:17:21 +08:00
1 parent 9bbc3a1467
commit e0136213fe
10 files changed
+598 -41

No files matched your search

+1
View File
@@ -84,6 +84,7 @@ static void app_on_swipe_progress(int x_start, int dx)
static void app_on_tick(void)
{
uint64_t now = meow_ui_time_ms();
autoplay_maybe(); /* 真机联调 hook:/tmp/ncm_autoplay 存在则自动播已缓存曲目(一次性) */
if (login_is_active()) { login_tick(now); return; }
if (player_is_active()) { player_tick(now); return; }
if (search_is_active()) { search_tick(now); return; }
+87
View File
@@ -3,6 +3,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define NCM_DEFAULT_DATA "/usr/data/mnt/sd_0/MeowData/dev.player.ncm/data"
@@ -205,3 +206,89 @@ int appstate_current_playing(void)
if (g_st.playing_song < 0 || g_st.playing_song >= g_st.song_count) return -1;
return g_st.playing_song;
}
/* ---- 真机联调 autoplay hook(A2 音频迁移验收用,非正式能力)----
* 触发文件 /tmp/ncm_autoplay 存在且 home 歌单已载 → 后台线程找「已缓存」曲目并播放:
* - 内容为纯数字 → 从该歌单下标起扫最多 AUTOPLAY_SCAN_PL 个歌单首页;
* - 内容为其它文本 → 按歌单名子串匹配**单个**歌单(如 "Docu";避开超大歌单的
* 全量 trackIds 解析,真机联调省时省内存);
* - 空文件 → 从 0 起扫。
* 风控纪律:**绝不发 song_url 下载**——无缓存命中只记日志退出;metadata 请求 = 每歌单
* 1 detail + 1 song_detail(≤ 2×AUTOPLAY_SCAN_PL 次)。 */
#define AUTOPLAY_TRIGGER "/tmp/ncm_autoplay"
#define AUTOPLAY_SCAN_PL 3
/* 播放单个歌单首页中第一首已缓存曲目。命中播放返回 1,否则 0。 */
static int autoplay_try_playlist(int pl)
{
int rc = appstate_load_playlist_songs_page(pl, 0, NCM_SONG_BATCH);
if (rc != NCM_OK) {
meow_log(MEOW_LOG_WARN, "autoplay: playlist[%d] load rc=%d", pl, rc);
return 0;
}
char cpath[600];
for (int i = 0; i < g_st.song_count; i++) {
if (ncm_cache_lookup(&g_st.ctx.cache, g_st.songs[i].id, ".mp3",
cpath, sizeof cpath) == NCM_OK) {
g_st.active_playlist = pl;
meow_log(MEOW_LOG_INFO, "autoplay: playlist[%d] song[%d] id=%s cached -> play",
pl, i, g_st.songs[i].id);
player_manager_play(i);
return 1;
}
}
meow_log(MEOW_LOG_INFO, "autoplay: playlist[%d] first page %d songs, no cached track",
pl, g_st.song_count);
return 0;
}
static void *autoplay_worker(void *arg)
{
const char *sel = (const char *)arg; /* NULL=从 0 起扫;数字=起始下标;其它=歌单名子串 */
if (sel && sel[0] && !(sel[0] >= '0' && sel[0] <= '9')) {
for (int pl = 0; pl < g_st.playlist_count && !g_app_quit; pl++) {
if (strstr(g_st.playlists[pl].name, sel)) {
meow_log(MEOW_LOG_INFO, "autoplay: name '%s' -> playlist[%d] '%s' (%d tracks)",
sel, pl, g_st.playlists[pl].name, g_st.playlists[pl].track_count);
if (autoplay_try_playlist(pl)) { free((void *)sel); return NULL; }
break;
}
}
meow_log(MEOW_LOG_WARN, "autoplay: no playlist name match / no cached track ('%s')", sel);
free((void *)sel);
return NULL;
}
int start = (sel && sel[0]) ? atoi(sel) : 0;
free((void *)sel);
if (start < 0) start = 0;
int end = start + AUTOPLAY_SCAN_PL;
if (end > g_st.playlist_count) end = g_st.playlist_count;
for (int pl = start; pl < end && !g_app_quit; pl++)
if (autoplay_try_playlist(pl)) return NULL;
meow_log(MEOW_LOG_WARN, "autoplay: no cached track found (no download, risk control)");
return NULL;
}
void autoplay_maybe(void)
{
static int s_fired = 0;
if (s_fired || g_app_quit) return;
if (access(AUTOPLAY_TRIGGER, F_OK) != 0) return;
if (!g_st.logged_in || g_st.playlist_count <= 0) return; /* 等 home 歌单载完 */
s_fired = 1;
char buf[64] = "";
FILE *f = fopen(AUTOPLAY_TRIGGER, "r"); /* 一次性:读完即删 */
if (f) {
if (fgets(buf, sizeof buf, f)) {
size_t n = strlen(buf);
while (n && (buf[n - 1] == '\n' || buf[n - 1] == '\r' || buf[n - 1] == ' ')) buf[--n] = 0;
}
fclose(f);
}
unlink(AUTOPLAY_TRIGGER);
meow_log(MEOW_LOG_INFO, "autoplay: triggered, sel='%s'", buf);
char *sel = strdup(buf);
pthread_t t;
if (pthread_create(&t, NULL, autoplay_worker, sel) == 0) pthread_detach(t);
else free(sel);
}
+5 -2
View File
@@ -5,7 +5,7 @@
* - app/ui/<page>.c 每页一个 view 文件(login/home/playlist/player/settings)
* - app/ui/common/chrome.c 共享 chrome(topbar / 内容区几何 / 空态 / 提示)
* - app/logic/ appstate(全局 ctx/歌单/歌曲/播放态)+ lyric(LRC 解析) + cover(封面缓存)
* - app/play/ player_manager(播放线程) + audio_out(ALSA/文件输出) + ipc_media(媒体会话 IPC)
* - app/play/ player_manager(播放线程) + audio_out(audiod/ALSA/文件输出) + ipc_media(媒体会话 IPC)
*
* 铁律(APPS.md §3.4.1 / WORK_STATE.md):物理键禁映射——on_key 不处理任何键语义;
* 返回=左缘左滑(on_swipe LEFT)/返回钮/meow_ui_request_exit;左缘返回:播放页→歌单→主页→退出。
@@ -267,6 +267,7 @@ int appstate_load_playlist_songs_page(int idx, int offset, int limit);
void appstate_apply_playlist_cover(int idx);
void appstate_set_playing_song(int idx, const char *cover_url);
int appstate_current_playing(void); /* 返回当前播放歌曲下标(UI) */
void autoplay_maybe(void); /* 真机联调 hook(/tmp/ncm_autoplay 触发,自动播已缓存曲目,一次性) */
/* lyric */
int lyric_parse(const char *lrc, const char *tlrc); /* 填充 g_lyric_* */
@@ -286,9 +287,11 @@ const char *cover_current_path(void); /* 封面原始文件路径(供 la
void cover_cache_reset(void);
/* ---- play(app/play/)---- */
/* audio_out */
/* audio_out(后端:默认 SINK_AUDIOD=meow_audiod 独占服务;env NCM_AUDIO_SINK=file/null/alsa
* 或 MEOW_AUDIO_DIRECT=1 选调试后端,见 audio_out.c 文件头) */
int audio_out_open(int sample_rate, int channels, int bits); /* 0=成功 */
int audio_out_write(const short *samples, int frames); /* <0=错误/结束 */
int audio_out_pause(int paused); /* 1=暂停(停 PCM 时钟不丢流) 0=恢复(流死自动重开);非 AUDIOD 后端恒 0 */
void audio_out_close(void);
void audio_out_set_target(int ms); /* 预留:缓冲目标 */
int audio_out_buffered_ms(void);
+164 -34
View File
@@ -1,13 +1,28 @@
/* play/audio_out.c — 音频输出抽象:ALSA 直开 hw:0,0(44.1k/16bit/立体声)或 FILE/NULL sink。
/* 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)。
*
* 设备(mipsel musl 无 libasound):用 ALSA 内核 UAPI(sound/asound.h)直开 /dev/snd/pcmC0D0p,
* 非 mmap RW 模式下 ioctl 设 hw_params + write() 写交织帧。布局/时序真机待验证(本层只保证跨编译 +
* 宿主回退正确)。
* 宿主/qemu 验证:无 /dev/snd/pcmC0D0p → 回退 FILE sink(写 WAV,env NCM_AUDIO_OUT 指定路径,
* 缺省 /tmp/ncm_out.wav)或 NULL(env NCM_AUDIO_SINK=null,丢弃,仅测解码 RT 用)。
* 线程模型:仅由 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>
@@ -21,11 +36,14 @@
#define NCM_HAVE_ALSA 1
#endif
enum { SINK_ALSADEV = 0, SINK_FILE = 1, SINK_NULL = 2 };
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"
@@ -33,10 +51,7 @@ 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, 2ch, s16)→ write。
* 注意:musl 的 sound/asound.h 中 masks/intervals 是**重索引**数组:
* masks[k] ↔ hw_param (FIRST_MASK + k); intervals[k] ↔ hw_param (FIRST_INTERVAL + k)。
* 故访问时须减 FIRST_MASK/FIRST_INTERVAL,否则越界/错位。 */
/* 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)
{
@@ -49,27 +64,42 @@ 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) return -1;
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;
pmask(&p.masks[SNDRV_PCM_HW_PARAM_ACCESS - SNDRV_PCM_HW_PARAM_FIRST_MASK],
SNDRV_PCM_ACCESS_RW_INTERLEAVED);
pmask(&p.masks[SNDRV_PCM_HW_PARAM_FORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK],
SNDRV_PCM_FORMAT_S16_LE);
pmask(&p.masks[SNDRV_PCM_HW_PARAM_SUBFORMAT - SNDRV_PCM_HW_PARAM_FIRST_MASK],
SNDRV_PCM_SUBFORMAT_STD);
/* 坑②: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) { close(fd); return -1; }
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
@@ -96,29 +126,76 @@ static void file_write_header(int fd, int rate, int ch, int bits, uint32_t data_
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; return 0; }
/* 显式 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(仅未显式指定 file;宿主无设备自动回退) */
if (!(env && strcmp(env, "file") == 0)) {
/* 调试保底: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
}
/* FILE sink */
int fd = open(g_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) { g_sink = SINK_NULL; g_fd = -1; return 0; } /* 无路可写 → null */
file_write_header(fd, sample_rate, channels, bits, 0);
g_sink = SINK_FILE;
g_fd = fd;
/* 默认: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;
}
@@ -126,6 +203,18 @@ 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 重试;错误返回 */
@@ -135,19 +224,60 @@ int audio_out_write(const short *samples, int frames)
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);
+34 -4
View File
@@ -26,6 +26,15 @@ static int64_t g_file_bytes;
static int g_level; /* 音质档(来自设置) */
static int g_cache_gb;
/* 被抢占回调(SDK 读线程分发;audiod 后开流赢):走 g_play 命令通道暂停(内部持锁),
* 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */
static void on_audio_preempt(const char *by_app, void *ctx)
{
(void)ctx;
meow_log(MEOW_LOG_WARN, "play: preempted by [%s] -> pause", (by_app && by_app[0]) ? by_app : "?");
player_manager_pause(1);
}
int player_manager_state(void) { int s; pthread_mutex_lock(&g_play.mu); s = g_play.state; pthread_mutex_unlock(&g_play.mu); return s; }
int player_manager_playing_song(void) { int s; pthread_mutex_lock(&g_play.mu); s = g_play.cur_song; pthread_mutex_unlock(&g_play.mu); return s; }
int64_t player_manager_pos_ms(void) { int64_t p; pthread_mutex_lock(&g_play.mu); p = g_play.pos_ms; pthread_mutex_unlock(&g_play.mu); return p; }
@@ -196,10 +205,12 @@ static void run_song(int idx)
g_play.dur_ms = dur;
g_play.err = 0;
pthread_mutex_unlock(&g_play.mu);
meow_log(MEOW_LOG_INFO, "play: start idx=%d id=%s dur=%lldms", idx, s->id, (long long)dur);
mp3d_sample_t pcm[PCM_CHUNK];
int64_t frames_done = 0; /* 已输出帧(每通道) */
int need_seek_ms = -1;
int paused_edge = 0; /* 暂停沿:进暂停已向 audiod 发 pause */
for (;;) {
if (g_app_quit) break;
@@ -212,15 +223,30 @@ static void run_song(int idx)
int st = g_play.state;
pthread_mutex_unlock(&g_play.mu);
if (cur != idx) break; /* 切歌 → 回外层 */
if (st == NCM_PC_STOPPED) break;
if (cur != idx) { meow_log(MEOW_LOG_INFO, "play: switch idx=%d -> %d", idx, cur); break; } /* 切歌 → 回外层 */
if (st == NCM_PC_STOPPED) { meow_log(MEOW_LOG_INFO, "play: stopped idx=%d", idx); break; }
if (cmd == NCM_CTL_SEEK && seek_ms >= 0) { need_seek_ms = (int)seek_ms; }
if (st == NCM_PC_PAUSED) { usleep(40 * 1000); continue; } /* 冻结 */
if (st == NCM_PC_PAUSED) {
if (!paused_edge) { /* 进暂停:停 PCM 时钟但不丢流(audiod MSG_AUDIO_PAUSE) */
paused_edge = 1;
meow_log(MEOW_LOG_INFO, "play: paused idx=%d pos=%lldms", idx,
(long long)(frames_done * 1000 / 44100));
audio_out_pause(1);
}
usleep(40 * 1000);
continue; /* 冻结 */
}
if (paused_edge) { /* 出暂停:恢复 audiod 流(流死则自动重开) */
paused_edge = 0;
meow_log(MEOW_LOG_INFO, "play: resumed idx=%d", idx);
if (audio_out_pause(0) != 0) { set_err(NCM_ERR_IO, "audio resume failed"); break; }
}
if (need_seek_ms >= 0) {
/* 重开并 seek:关闭后重开(字节 offset 近似) */
meow_log(MEOW_LOG_INFO, "play: seek idx=%d -> %dms", idx, need_seek_ms);
audio_out_close();
mp3dec_ex_close(&dec);
memset(&dec, 0, sizeof dec);
@@ -235,7 +261,10 @@ static void run_song(int idx)
}
size_t got = mp3dec_ex_read(&dec, pcm, PCM_CHUNK);
if (got == 0) break; /* 播完 */
if (got == 0) { /* 播完 */
meow_log(MEOW_LOG_INFO, "play: done idx=%d frames=%lld", idx, (long long)frames_done);
break;
}
int frames = (int)(got / 2);
if (audio_out_write(pcm, frames) != frames) break;
frames_done += frames;
@@ -377,6 +406,7 @@ int player_manager_start(void)
g_level = g_st.level;
g_cache_gb = g_st.cache_limit_gb;
pthread_mutex_init(&g_play.mu, NULL);
meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */
if (pthread_create(&g_thr, NULL, player_thread, NULL) != 0) return -1;
g_thr_on = 1;
return 0;