- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
413 lines
19 KiB
C
413 lines
19 KiB
C
/* 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 / MSG_AUDIO_VOLUME / MEOW_IPC_HDR_LEN */
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <fcntl.h>
|
||
#include <unistd.h>
|
||
#include <errno.h>
|
||
#include <sys/socket.h>
|
||
#include <sys/un.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;
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* 任务1:开播前把「系统当前音量」同步下发到 audiod */
|
||
/* ============================================================ */
|
||
/* 背景(用户实测 2026-09-02):未开始播放时音量不是系统设定值;按一下音量键才跳到设定值。
|
||
* 根因= ncm 启动并打开 audiod/DAC 时,没有把系统当前音量下发到 audiod(meow_audiod 只在
|
||
* MSG_AUDIO_VOLUME 收到时才写 DAC,流开/重配上不主动应用 settings 音量),导致 DAC 停在
|
||
* 默认/上次残留音量;用户按音量键时 launcher/watchdog 那条音量链才更新并下发到 audiod。
|
||
* 修法= SINK_AUDIOD open 成功后读 settings.conf 的 audio.volume,经 MSG_AUDIO_VOLUME 下发
|
||
* (audiod 写 DAC numid=1/2 + 持久化 settings,同源同键)。语意:CS43198 寄存器=衰减量
|
||
* (0 最响/255 静音),audiod dac_vol_set 负责反转与真静音(写 255),本处只传 pct。
|
||
* 宿主验证:fake audiod socket 收帧断言 pct==系统设定(bench/vol_sync_sim.c)。 */
|
||
|
||
#define NCM_SETTINGS_CONF "/usr/data/meow/settings.conf" /* 与 audiod SETTINGS_CONF 同源 */
|
||
#define NCM_AUDIO_SOCK_DEF "/usr/data/meow_audio.sock" /* 与 audiod / SDK 客户端同缺省 */
|
||
#define NCM_VOL_DEFAULT 60 /* 与 audiod VOL_DEFAULT/SDK 缺省一致 */
|
||
#define NCM_VOL_MAX 100
|
||
|
||
static const char *settings_conf_path(void)
|
||
{
|
||
const char *e = getenv("MEOW_SETTINGS_FILE"); /* 宿主单测指向临时文件;设备缺省系统 conf */
|
||
return (e && e[0]) ? e : NCM_SETTINGS_CONF;
|
||
}
|
||
|
||
/* 读 settings.conf 的 audio.volume(扁平 "key = value";兼容无空格/多空格)。
|
||
* 与 launcher meow_settings / SDK meow_ui_vol_read 同款解析。缺省 60;夹到 [0,100]。 */
|
||
static int settings_read_volume(void)
|
||
{
|
||
static const char key[] = "audio.volume";
|
||
const int kl = (int)sizeof key - 1;
|
||
FILE *f = fopen(settings_conf_path(), "r");
|
||
int vol = NCM_VOL_DEFAULT;
|
||
if (!f) return vol;
|
||
char line[160];
|
||
while (fgets(line, sizeof line, f)) {
|
||
char *eq = strchr(line, '=');
|
||
if (!eq) continue; /* 非 key=value 行跳过 */
|
||
char *k = line;
|
||
while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */
|
||
int n = (int)(eq - k);
|
||
while (n > 0 && (k[n - 1] == ' ' || k[n - 1] == '\t')) n--; /* key 右去空白 */
|
||
if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* 精确匹配 */
|
||
char *v = eq + 1;
|
||
while (*v == ' ' || *v == '\t') v++; /* value 左去空白 */
|
||
int x = atoi(v);
|
||
vol = (x < 0) ? 0 : (x > NCM_VOL_MAX ? NCM_VOL_MAX : x);
|
||
break;
|
||
}
|
||
fclose(f);
|
||
return vol;
|
||
}
|
||
|
||
/* 把 pct(0-100) 下发到 audiod。独立连接(SDK 客户端连接私有不可复用);MSG_AUDIO_VOLUME
|
||
* 不要求开流,audiod 侧写 DAC+持久化后 ACK(meow_audiod.c MSG_AUDIO_VOLUME)。失败仅日志,
|
||
* 不影响播放本身(音量是次要正确性,非播放成败)。
|
||
*
|
||
* §2026-09-02 连接风暴根因:audiod_sync_volume 每次 audio_out_open 都新建一条 AF_UNIX
|
||
* 短连接(socket→connect→write→close)。边下边播/切歌/seek 反复 open/close 时,audiod 端表现为
|
||
* 「同一 fd=N 被反复 accept」(真机 1489 次 "io: client connected (fd=4)")。音量值实际只在
|
||
* 用户按音量键时变化,且 audiod 收到已持久化(settings_set_volume),无需每次 open 重发——
|
||
* 修法:每进程首次 open 同步一次;此后仅当 settings 音量**变化**才重发(同一连接数坍缩为
|
||
* O(1),风暴消除)。失败不记 status(下次 open 仍会尝试,幂等)。 */
|
||
static void audiod_sync_volume(int pct)
|
||
{
|
||
const char *sp = getenv("MEOW_AUDIO_SOCK");
|
||
if (!sp || !sp[0]) sp = NCM_AUDIO_SOCK_DEF;
|
||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||
if (fd < 0) return;
|
||
struct sockaddr_un a;
|
||
memset(&a, 0, sizeof a);
|
||
a.sun_family = AF_UNIX;
|
||
strncpy(a.sun_path, sp, sizeof a.sun_path - 1);
|
||
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return; }
|
||
|
||
uint8_t frame[MEOW_IPC_HDR_LEN + sizeof(int32_t)];
|
||
uint32_t magic = MEOW_IPC_MAGIC, len = sizeof(int32_t), req = 1;
|
||
uint16_t ver = MEOW_IPC_VERSION, type = MSG_AUDIO_VOLUME;
|
||
memcpy(frame + 0, &magic, 4);
|
||
memcpy(frame + 4, &ver, 2);
|
||
memcpy(frame + 6, &type, 2);
|
||
memcpy(frame + 8, &req, 4);
|
||
memcpy(frame + 12, &len, 4);
|
||
int32_t v32 = (int32_t)pct;
|
||
memcpy(frame + 16, &v32, sizeof v32);
|
||
if (write(fd, frame, sizeof frame) != (ssize_t)sizeof frame)
|
||
meow_log(MEOW_LOG_WARN, "audio: sync volume write failed pct=%d", pct);
|
||
close(fd);
|
||
}
|
||
|
||
/* 每进程同步一次 + 值变才重发:杜绝每次 audio_out_open 新建短连接(fd=N accept 风暴根因)。
|
||
* g_vol_synced=1 即已同步过;g_vol_last=上次下发的 pct,二者相同则零连接直接返回。 */
|
||
static int g_vol_synced = 0;
|
||
static int g_vol_last = -1;
|
||
|
||
int audio_out_sync_volume(void)
|
||
{
|
||
int pct = settings_read_volume();
|
||
if (g_vol_synced && pct == g_vol_last) return 0; /* 已同步且未变:零新建连接 */
|
||
g_vol_synced = 1;
|
||
g_vol_last = pct;
|
||
audiod_sync_volume(pct);
|
||
meow_log(MEOW_LOG_INFO, "audio: sync volume -> audiod pct=%d (settings %s)",
|
||
pct, settings_conf_path());
|
||
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);
|
||
/* 任务1:开播/打开音频会话时把系统当前音量同步下发到 audiod(DAC 停在默认/上次残留音量的根治)。
|
||
* 幂等(同值重写无害);失败不阻断播放。 */
|
||
audio_out_sync_volume();
|
||
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;
|
||
}
|