- 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(已热更设备)
183 lines
7.4 KiB
C
183 lines
7.4 KiB
C
/* 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;
|
||
}
|