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

+123
View File
@@ -1170,3 +1170,126 @@ ed2f71c52563e812a04fb8a7cc05148d app/test/test_ncm_app.c # settings mock 初
| 设置页行数 | 4 | 5 | 新增「清理缓存」行并在「缓存上限」接「自定义」数字输入 |
| 音质 sheet 档数 | 3(Hi-Res/Higher/Standard) | 5(+Lossless/Exhigh) | 对齐 core `NCM_LEVEL_*` 全档 + 降级链起点 |
| `ncm_cache_clear` | — | 新增 | 删除 `song_*` 全部(mp3/封面/.tmp),供「清理缓存」与测试 |
---
## 16. A2:音频输出迁移 meow_audiod(SINK_AUDIOD 默认后端)+ 无声根因修复(2026-09-01,agent-248)
> 范围:仅 `patch_v2/ncm_app/`(含 build 脚本、bench、REPORT 本文件)。未动 `sdk/`、`meow_audiod/`、
> `meow_launcherd/`、`vendor/`。风控纪律:真实 NCM 请求 = autoplay 验证的 1 detail + 1 song_detail
> (Docu 单首歌单,共 2 次 metadata,**0 次 song_url 下载**,曲目走已缓存 mp3)。
### 16.1 迁移内容
- **`app/play/audio_out.c` 新增 SINK_AUDIOD 后端并设为默认**(AUDIOD_DESIGN.md §7 落地):
- 后端选择(env 驱动):缺省 → AUDIOD(SDK `meow_audio_*` 连 `/usr/data/meow_audio.sock`);
`NCM_AUDIO_SINK=file/null/alsa` → FILE(WAV 回归)/NULL(解码 RT)/ALSA 直开;
`MEOW_AUDIO_DIRECT=1` → ALSA 直开(调试保底,非正式能力)。audiod 不在 → open 返回 -1
(不隐式回退直开),日志记 `audiod open failed rc=...`。
- **ALSA 保底路径修复两大坑**(与 audiod/SDK 同款修法,注释标明勿回退):① hw_params 改
「全放行」初始化(mask 全 1 + interval [0,UINT_MAX])——旧 memset 0 的 interval 等价
「只能取 0」→ 内核 refine 必空 → HW_PARAMS 恒 EINVAL,这正是**网易云「播放无声」的根因**
(真机 probe 实证);② access/format/subformat mask 先清零再单点(留全 1 会被 choose 成
MMAP_INTERLEAVED → write() EINVAL)。
- **被抢占处理**(`player_manager.c`):`player_manager_start` 注册 `meow_audio_on_preempt`
→ 回调记 WARN 日志并经 `player_manager_pause(1)`(g_play 命令通道,持锁)暂停播放+更新 UI 态,
不直接动锁外状态。
- **暂停/恢复语义对齐**:`run_song` 进 NCM_PC_PAUSED 沿 → `audio_out_pause(1)` 向 audiod 发
MSG_AUDIO_PAUSE(停 PCM 时钟不丢流);出暂停沿 → `audio_out_pause(0)` 恢复;**恢复时若流已死**
(被抢占/断链,SDK 返回负值)→ 自动 close+reopen 重建输出流(解码位置不变)。
- **播放路径全量日志**(meow_log,无声排查永久资产):open 结果/请求参数/audiod 授权
grant_*(open 后 `meow_audio_query` 落 rate/ch/fmt/underrun)、write 错误(首次+每 100 次节流)、
pause/resume/reopen/close(含累计写帧)、play start/done/switch/seek/stopped 事件。
- **真机联调 autoplay hook**(`appstate.c` + `app.c`,非正式能力):`/tmp/ncm_autoplay` 触发文件
存在且 home 歌单已载 → 后台线程找「已缓存」曲目自动播放(内容=数字则歌单下标起扫 ≤3 个、
=文本则歌单名子串单点匹配如 "Docu")。**绝不发 song_url 下载**——无缓存命中只记日志退出。
用途:无用户在场时验证「ncm→SDK→audiod→DAC」全链。
- `build_ncm_host.sh`:SDK_SRC 补 `meow_ui_audio`(audio_out.c 新增 meow_audio_* 引用,宿主链接需要)。
### 16.2 验证记录(分层)
| 层 | 项 | 结果 |
|---|---|---|
| 宿主 | `build_ncm_host.sh` | **自有代码 0 warning**(仅 SDK meow_ui_vol.c 既有 system() 告警) |
| 宿主 | WAV 回归:`NCM_AUDIO_SINK=file NCM_AUDIO_OUT=... test_decode MIPSTest.mp3` | **PASS**:8.21s/48k/立体声,WAV 头合法,字节数 44+393984×4 精确一致 |
| 宿主 | 离屏渲染 home/playlist/player/settings/search | 全 rc=0 无回归 |
| 宿主 | **`bench/audiod_chain_sim.c`(新增)**:起真实 `meow_audiod_host`(FILE sink 假 socket)驱动真实 audio_out.c+SDK 客户端 | **14 项全 PASS**:OPEN(grant=44100/2ch/S16)→WRITE 51200 帧→QUERY(active=1 written>0)→PAUSE(paused=1)→RESUME→**抢占**(第二客户端裸发 OPEN→on_preempt 回调触发→旧流 write 失败)→**流死自动重开**(reopen 可写)→CLOSE(active=0)→audiod WAV 落盘非空 |
| qemu | `qemu-mipsel ./out/test_ncm_core_mipsel` | **PASS**(core 未动,回归) |
| mipsel | `build_ncm.sh` | **0 warning**;ncm md5 `db5b78ad9e8f40e6306484efe2a08eb3` |
| 真机 | 设备已刷 M2.90(0.7.85),/usr/bin/meow_audiod 在板运行 | ✓ |
| 真机 | ncm 热更(.new+mv),`am start` 不可用(amsd DECLARE 转发未联调,「pkg not registered」)→ 手动 nohup 起 | ✓ 启动/home 歌单 120 正常 |
| 真机 | autoplay hook:name 'Docu' → playlist[1](1 tracks)→ song[0] id=2048962657 缓存命中 → play | ✓ 日志链路正确 |
| 真机 | **audiod OPEN→grant 链**:`meow_audio tone` 拿到 `sid=1 grant=44100Hz/2ch/S16` | ✓ IPC/协商链设备上正常 |
| 真机 | **持续写帧/underrun 验收**:**被阻断**(见 §16.3 重大发现) | ✗ 未达成 |
### 16.3 重大发现:M2.90 设备 PCM 播放启动 → 内核硬挂死 → 看门狗重启(4/4 复现)
**现象**:设备(0.7.85,stock kernel 4.4.94+ #15 2026-01-16,未变)上一进入 PCM 播放启动序列,
数秒内整机硬挂死并重启。复现 4/4:`meow_audio tone` ×3(含一次无 ncm 的纯 audiod 路径)+
ncm 经 SDK 链播放 ×1。kmsg 流抓取到最后内核日志(死亡点,两次抓取一致):
```
[ 412.104797] hbc3000_set_samplerate codec_type=4 samplerate=44100 spdif=0 ...
[ 412.104819] hbc3000_stop
[ 412.104865] hbc3000_start
[ 412.168307] cs43198_dai_prepare pcm
[ 412.371919] hbc3000_start
<<< 日志到此中断,无任何 panic/oops 文本 → 硬挂死(疑似时钟/电源域死锁),随后重启 >>>
```
**归因排查记录**:
- **固件打包无误**:设备 /usr/bin/meow_audiod md5 `e07e9144…` == 仓库构建 == fw_content.conf 条目。
- **ncm 无关**:对照组 ncm.bak(A2 前二进制 19d99ce4)同设备前台 idle **31 分钟稳定**(boot G,
期间经历 suspend/resume);本会话早段(23:05-23:45)观察到的 4 次「idle ncm 在跑数分钟后重启」
实为**用户 M2.90 刷机循环**(卡根 r3proii.upt 22:46 上卡,多次升级重启)同窗口巧合,对照组证伪。
- **suspend 相关性未证伪也未证实**:设备每次 boot 后 ~40s 自动息屏进 `System suspend`(dmesg 实证),
而 4 次致命播放全部发生在 resume 之后;A1 收尾(agent-246)同内核 tone 实测 written=played、
underrun=0 **通过**——据信在未 suspend 的新鲜 boot 窗口。未 suspend 的播放验证需要
「重启后 40s 内立刻 tone」,本会话无法在不主动重启的前提下获得该窗口(留给用户/主会话)。
**影响与建议(留主会话/用户决策)**:
- A2 真机验收的「持续写帧+underrun=0」被此问题阻断,非 ncm 代码问题(SDK 链 OPEN→grant 已实证:
tone 拿到 `sid=1 grant=44100Hz/2ch/S16`;ncm 日志链到「缓存命中 → play」)。
- 建议:① 新鲜 boot 40s 内(未 suspend)测 `meow_audio tone` 验证 suspend 相关性;
② 若为真,权宜=禁用自动息屏(`display.screen_off_timeout`=0)或 resume 后重置 codec 电源/时钟;
③ 根治需看 hbc3000/cs43198 驱动 resume 路径(stock 内核源码不在本仓库);挂死点恒在
`cs43198_dai_prepare` 后第二次 `hbc3000_start`,无任何内核日志 = 硬挂(时钟/i2c/电源域死锁特征)。
- 本会话为定位该问题触发过多次设备重启(均自动恢复;未碰 recovery_all/未 killall hiby_player/
未主动重启设备)。
### 16.4 md5(本批改动文件 + 产物)
```
db5b78ad9e8f40e6306484efe2a08eb3 ncm # mipsel 静态 App(新;已部署设备)
```
(源文件 md5 以 git 为准;改动清单见 §16.5)
### 16.5 出入清单(只改 `patch_v2/ncm_app/`)
```
修改:
app/play/audio_out.c # SINK_AUDIOD 默认后端 + ALSA 保底两坑修复 + audio_out_pause + 全量日志
app/play/player_manager.c # on_audio_preempt 注册/回调 + 暂停沿 audiod pause/resume + 播放事件日志
app/logic/appstate.c # autoplay 真机联调 hook(/tmp/ncm_autoplay,缓存命中才播,绝不下载)
app/app.c # app_on_tick 挂 autoplay_maybe
app/ncm_app_internal.h # audio_out_pause/autoplay_maybe 声明 + audio_out 注释更新
build_ncm_host.sh # SDK_SRC 补 meow_ui_audio
ncm / ncm_host # 重编产物
新增:
bench/audiod_chain_sim.c # audiod 全链离线回归(真实 audiod_host 假 socket,14 项)
REPORT.md # 本批记录(§16)
(未动 sdk/、meow_audiod/、meow_launcherd/、vendor/;未动设备系统/恢复区)
```
### 16.6 诚实出入 / 已知限制
- **真机验收未闭环**:§16.3 内核硬挂死阻断「持续写帧 + underrun=0 + meow_audio status 在播」三项。
已实证到 OPEN→grant(CLI tone)与 ncm→autoplay→缓存命中→play 日志链;SDK 客户端全链
(含抢占/重开)由宿主 sim 对真实 audiod_host 14 项覆盖。
- **autoplay hook 默认 0 起扫前 3 个歌单首页**:若首个歌单是超大歌单(5191 首 detail ~35s 解析)
会慢/重;文本名单点匹配(如 Docu)是推荐用法。hook 为调试资产保留,不影响正常 UI 路径。
- **`am start dev.player.ncm` 报 pkg not registered**:amsd 二期 launcher→amsd DECLARE 转发未联调
(WORK_STATE 已记残余),本次手动 nohup 起 ncm(fb 与 launcher 并存,测试场景可接受)。
- 设备日志中 ipc_media META 每秒一对(state=0 空态也发)为既有行为,本批未动。
- **未碰** /data/recovery_all、未 killall hiby_player、未主动重启设备(§16.3 的多次重启均为
被测路径触发的设备自重启)。
+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;
+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;
}
+2 -1
View File
@@ -19,7 +19,8 @@ INC="-I. -I../sdk -I../meow_launcherd -Icore \
echo "==> host SDK objects"
SDK_SRC="meow_ui_gfx meow_ui_sys meow_ui_theme meow_ui_vol meow_ui_log meow_ui_widget \
meow_ui_input meow_ui_activity meow_ui_dialog meow_ui_ime meow_ui_draw meow_ui_view \
meow_ui_img meow_ui_config meow_ui_nav meow_ui_media meow_notify_ipc meow_pinyin"
meow_ui_img meow_ui_config meow_ui_nav meow_ui_media meow_notify_ipc meow_pinyin \
meow_ui_audio"
for f in $SDK_SRC; do
$CC_HOST -O2 -c -DMEOW_UI_HOST_TEST -I"$SDK_DIR" -I"$ROOT/patch_v2/meow_launcherd" \
"$SDK_DIR/$f.c" -o "$OUT/host/sdk_$f.o" || { echo "sdk $f FAILED"; exit 1; }
Binary file not shown.
Binary file not shown.