Files
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

183 lines
7.4 KiB
C
Raw Permalink 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.
/* 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;
}