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

+182
View File
@@ -0,0 +1,182 @@
/* bench/audiod_chain_sim.c — ncm 音频输出迁移(A2,SINK_AUDIOD)离线全链验证(0 网络)。
*
* 起**真实** meow_audiod_host(FILE sink,假 socket=/tmp/ncm_audiod_sim.sock)作服务端,
* 驱动**真实** app/play/audio_out.c(SINK_AUDIOD 默认后端)+ SDK 客户端 meow_ui_audio.c:
* ① audio_out_open(44100,2,16) → audiod OPEN/OPEN_ACK(grant 授权)
* ② 连续 WRITE(正弦帧)→ meow_audio_query 见 active=1 / written>0
* ③ audio_out_pause(1)/(0) → QUERY paused=1/0(停 PCM 时钟不丢流)
* ④ 第二客户端裸发 OPEN 抢占 → on_preempt 回调触发 + 旧流 write 失败
* ⑤ audio_out_pause(0) 对流死自动重开 → 恢复可写
* ⑥ audio_out_close → QUERY active=0(流回收);audiod 侧 WAV 落盘非空
*
* 编译(宿主;需先 ../meow_audiod/build_audiod.sh host 产出 meow_audiod_host):
* cc -O2 -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
* -Ivendor/mbedtls/include -Ivendor/jsmn -Ivendor/qrcodegen/c -Ivendor/minimp3 \
* bench/audiod_chain_sim.c app/play/audio_out.c \
* ../sdk/meow_ui_audio.c ../sdk/meow_ui_log.c -o audiod_chain_sim -lpthread -lm
* 运行:./audiod_chain_sim (PASS/FAIL 计数,全过 exit 0)
*/
#include "../app/ncm_app_internal.h" /* audio_out_* + meow_ui.h */
#include "meow_ipc.h" /* 抢占者裸帧:MSG_AUDIO_OPEN/m_audio_open_t */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/wait.h>
/* ---- audio_out.c / meow_ui_log.c 依赖的外部符号(本 sim 提供最小实现) ---- */
volatile int g_app_quit = 0;
uint64_t meow_ui_time_ms(void)
{
struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000 + (uint64_t)ts.tv_nsec / 1000000;
}
#define SIM_SOCK "/tmp/ncm_audiod_sim.sock"
#define SIM_WAV "/tmp/ncm_audiod_sim.wav"
static int g_pass = 0, g_fail = 0;
static void check(int cond, const char *name)
{
if (cond) { printf(" PASS %s\n", name); g_pass++; }
else { printf(" FAIL %s\n", name); g_fail++; }
}
/* 抢占回调标志(SDK 读线程置位) */
static volatile int g_preempted = 0;
static void sim_preempt_cb(const char *by_app, void *ctx)
{
(void)ctx;
printf(" [cb] preempted by %s\n", by_app ? by_app : "?");
g_preempted = 1;
}
/* ---- 起 audiod(FILE sink 假声卡) ---- */
static pid_t g_srv = -1;
static int start_audiod(void)
{
unlink(SIM_SOCK); unlink(SIM_WAV);
pid_t p = fork();
if (p == 0) {
setenv("MEOW_AUDIO_SOCK", SIM_SOCK, 1);
setenv("MEOW_AUDIO_OUT", SIM_WAV, 1);
setenv("MEOW_AUDIO_SINK", "file", 1);
setenv("MEOW_AUDIO_FORCE", "1", 1);
setenv("MEOW_AUDIO_AMIXER", "/bin/true", 1);
int devnull = open("/dev/null", O_WRONLY);
if (devnull >= 0) { dup2(devnull, 1); dup2(devnull, 2); }
execl("../meow_audiod/meow_audiod_host", "meow_audiod_host", (char *)NULL);
_exit(127);
}
if (p < 0) return -1;
g_srv = p;
for (int i = 0; i < 50; i++) { /* 等 socket 出现(≤5s) */
if (access(SIM_SOCK, F_OK) == 0) return 0;
usleep(100 * 1000);
}
return -1;
}
/* ---- 抢占者:裸 IPC 客户端发 OPEN ---- */
static int wr_all(int fd, const void *p, size_t n)
{
const uint8_t *b = p; size_t o = 0;
while (o < n) { ssize_t r = write(fd, b + o, n - o); if (r < 0) { if (errno == EINTR) continue; return -1; } o += (size_t)r; }
return 0;
}
static int intruder_open(void)
{
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd < 0) return -1;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
snprintf(a.sun_path, sizeof a.sun_path, "%s", SIM_SOCK);
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return -1; }
m_audio_open_t o;
memset(&o, 0, sizeof o);
snprintf(o.app_id, sizeof o.app_id, "intruder");
o.rate = 44100; o.ch = 2; o.fmt = AUDIO_FMT_S16;
uint8_t hdr[16];
uint32_t magic = MEOW_IPC_MAGIC, len = sizeof o, req = 99;
uint16_t ver = MEOW_IPC_VERSION, type = MSG_AUDIO_OPEN;
memcpy(hdr + 0, &magic, 4); memcpy(hdr + 4, &ver, 2); memcpy(hdr + 6, &type, 2);
memcpy(hdr + 8, &req, 4); memcpy(hdr + 12, &len, 4);
if (wr_all(fd, hdr, 16) != 0 || wr_all(fd, &o, sizeof o) != 0) { close(fd); return -1; }
return fd; /* 保持连接(断开会 EOF 回收 intruder 流) */
}
int main(void)
{
setenv("MEOW_AUDIO_SOCK", SIM_SOCK, 1); /* SDK 客户端连假 socket */
setenv("MEOW_LOG_APP", "dev.player.ncm", 1); /* audiod 状态里的 app_id */
unsetenv("NCM_AUDIO_SINK"); /* 确保走默认 SINK_AUDIOD */
unsetenv("MEOW_AUDIO_DIRECT");
if (start_audiod() != 0) { printf("FAIL: audiod_host start\n"); return 1; }
printf("audiod up (sock=%s)\n", SIM_SOCK);
/* ① OPEN */
meow_audio_on_preempt(sim_preempt_cb, NULL);
check(audio_out_open(44100, 2, 16) == 0, "open: SINK_AUDIOD default ok");
/* ② WRITE 连续帧(~1s 正弦)+ QUERY 见活跃流 */
static short pcm[2048 * 2];
int wr_ok = 1;
for (int c = 0; c < 25 && wr_ok; c++) {
for (int i = 0; i < 2048; i++) {
double t = (double)(c * 2048 + i) / 44100.0;
short v = (short)(sin(2.0 * 3.14159265 * 440.0 * t) * 12000);
pcm[i * 2] = v; pcm[i * 2 + 1] = v;
}
if (audio_out_write(pcm, 2048) != 2048) wr_ok = 0;
usleep(10 * 1000);
}
check(wr_ok, "write: 25 chunks x 2048 frames ok");
meow_audio_state st;
check(meow_audio_query(&st) == 0 && st.active == 1 && st.written_frames > 0,
"query: active=1 written>0");
printf(" [state] app=%s sid=%u rate=%u ch=%u fmt=%u written=%llu played=%llu underrun=%u paused=%d\n",
st.app_id, st.stream_id, st.rate, st.ch, st.fmt,
(unsigned long long)st.written_frames, (unsigned long long)st.played_frames,
st.underrun, st.paused);
/* ③ PAUSE / RESUME(停时钟不丢流) */
check(audio_out_pause(1) == 0, "pause(1) ok");
usleep(100 * 1000);
check(meow_audio_query(&st) == 0 && st.paused == 1, "query: paused=1");
check(audio_out_pause(0) == 0, "resume(0) ok");
usleep(100 * 1000);
check(meow_audio_query(&st) == 0 && st.paused == 0, "query: paused=0");
/* ④ 抢占:intruder OPEN → 回调触发 + 本流写失败 */
int ifd = intruder_open();
check(ifd >= 0, "intruder: open sent");
for (int i = 0; i < 30 && !g_preempted; i++) usleep(100 * 1000);
check(g_preempted == 1, "preempt: on_preempt callback fired");
check(audio_out_write(pcm, 128) < 0, "preempt: old stream write fails");
/* ⑤ 流死后 resume(0) 自动重开(抢回流)→ 恢复可写 */
check(audio_out_pause(0) == 0, "resume-after-preempt: reopen ok");
check(audio_out_write(pcm, 2048) == 2048, "reopened stream writable");
if (ifd >= 0) close(ifd);
/* ⑥ CLOSE → 流回收 */
audio_out_close();
usleep(200 * 1000);
check(meow_audio_query(&st) == 0 && st.active == 0, "close: query active=0");
struct stat sb;
check(stat(SIM_WAV, &sb) == 0 && sb.st_size > 44, "audiod WAV non-empty");
if (g_srv > 0) { kill(g_srv, SIGTERM); waitpid(g_srv, NULL, 0); }
printf("== audiod_chain_sim: pass=%d fail=%d ==\n", g_pass, g_fail);
return g_fail ? 1 : 0;
}