fix(ncm §30): 真机四连症状——JSON unescape + 封面流式 + 歌单豆腐/分隔符 tofu + 首屏诊断日志

症状/根因/修法(0 真实 NCM 业务请求;宿主+qemu 全绿):
- ③ LRC 挤一行/时间戳未解析:ncm_json_get_str_at 不做 JSON 转义还原,
  lrc.lyric 的 \n 转义原样进串 → lrc_parse 无真实换行整段一行。修:ncm_json.c 加
  json_unescape(RFC8259 全转义 + \uXXXX/代理对),get_str_at 返回前还原。
- ⑤ 封面全 failed→give up:cover.c 静态 g_buf 288KB < 封面 1.34MB → NCM_ERR_FULL。
  设备 RAM 仅 56MB。修:cover.c 改流式写文件(ncm_http_download cb→ncm_cache_write +
  meow_img_load_file 临时文件),NCM_COVER_MAX 8MB 改下载上限(非缓冲,bss 未涨)。
- ① 162 首歌单所有条目「三个豆腐」+「加载中」:占位符 ···(U+00B7)在 default 字体缺
  该字形 → 3 豆腐(font.idx glyph 扫实证 U+00B7 MISSING)。修:占位 ···→...、全 App
  · 分隔符→/;playlist_worker 首屏打 first-screen 诊断日志。
- ② 返回首页歌单列表 1 秒方框:字体缺 U+00B7/特殊字形(◢◤/Kannada)tofu;瞬态疑 glyph
  atlas 冷缓存(SDK 内部未深挖)。修:·→/。
验证:test_ncm_core(+test_json_unescape, host+qemu) / listvirt44 / dispatch / box42 /
scroll17 / anim32 / widgets / overlay_suppress / sdk_input_keys / theme / vol / ime_fix /
pinyin / notif_gesture / notif_open / sdk_fixes / sb_collapse / ui_audio_host / notify_ipc /
notify_integration + playlist_race_sim / window_sim / search_sim / cache_evict_sim(1-9) /
player_flow_sim(P1-12) / stream_sim(16+2resume) 全 PASS;mipsel -Wall -Wextra 0 warning。
新 md5:ncm d91cd906、ncm_host b731654a、test_ncm_core_host 7a38194c。

(症状④ VOL_DOWN×3 杀 App 的 launcher 源修复 app_launcher.c 门控 !a->framework 已随
 并发 commit 54a6e82 存入 HEAD;launcher 二进制 03dfcf76 留主会话/固件打包。)
注:build_ncm_host.sh 补 SDK 292/295 的 anim/box/scroll 源(宿主链接)。
This commit is contained in:
AskaEth committed 2026-09-02 02:25:37 +08:00
1 parent 54a6e828b7
commit 3397a702aa
15 files changed
+384 -88

No files matched your search

+145
View File
@@ -2488,3 +2488,148 @@ b7a2bfaec3c3d7bd29a151072cd40f32 bench/cache_evict_sim.c # 新 case9:.part
- **歌单列表缓存无版本化清理**:`playlists.bin` 靠 ver/magic 判 miss;数组改版/账号切换不主动失效(账号掉线重登前 `appstate_logout` 未清 playlists.bin——本次 `logout` 未联动删缓存,**下一账号可能看到上一账号列表直到首次网络成功覆写**;留主会话)。
- **`.part` 7 天回收只在 LRU 扫描(evict/total_bytes)时触发**:无网/不触发 evict 时不会主动清;属「惰性删除」(任务要求)。`.tmp`(在播写穿)mtime 持续更新,安全。
- **未动 sdk/、meow_launcherd/、vendor/**;部署链路同 §13.5 留主会话。commit `cd216fa`(git 记录)。
## 30. 真机四连症状排查修复:歌单豆腐/首页方框/LRC 一行/音量连按杀 App(2026-09-02,REPORT §30)
> 真机(10.78.193.50,设备 `dev.player.ncm`)四连症状,均在 SDK 292/293/295 重链(ncm `b30b0aed`)
> 后出现。本批先取证后定位,逐症状给根因/修法/测试。风控纪律:**0 次真实 NCM 业务请求**
> (curl 打网易云 CDN 封面仅 `GET` 静态图片做网络侧诊断,不算 NCM 接口请求;全部修复验证在
> 宿主 mock + 离线 sim)。
### 30.1 症状①:162 首歌单打开后所有条目歌名显示「三个豆腐」+「加载中」(小歌单 72 快且正常)
**根因(两个叠加)**:
- **显示层(豆腐的真相)**:行占位符 `pl_src2` 的 `out->title = "···"`(U+00B7 中点 ×3)。设备默认
字体 `MeowOS/fonts/default/regular/font.idx` **没有 U+00B7 字形**(`meow_font.c` 缺码位画空心方块
tofu)→ `···` 渲染成**三个豆腐块**。副标题正是占位行的「加载中」。
- 证据:把我取回的 `font.idx` 用 `load_idx` 同款解析(MFNI + 每面 glyph 记录 16B)扫全 glyph:
`cp U+002E('.') present / U+00B7('·') MISSING / U+2022('•') MISSING / U+2026('…') present /
U+4E2D(中) present / U+25E2('◢') MISSING`。字体缺 U+00B7 与 ◢◤/Kannada(ಡ) 等字形。
- **物化层(为何一直占位)**:占位行 = `appstate_song_copy(idx)!=0`(滑窗未覆盖/未物化)。首屏
`appstate_load_playlist_songs_ctx(...,0,NCM_SONG_BATCH=100)` 应物化 0-99;若该批 song_detail
网络/解析失败(非致命,§26 fill-and-continue 只记 `g_ens_err` 不置 `g_load_err`)→ 窗口空、
track_ids_count=162 → **所有行占位**。162>100 走 2 批(首屏 1 批 100 + 二批 62),92/200/5194 已由
§26 修过但 162 的真实 song_detail 批在真机失败原因待下一会话诊断日志(**本批给 playlist_worker
首屏打 INFO 日志:idx/plid/trackIds/materialized/rc**,见下)。
**修法**:
- `app/ui/playlist.c` 占位 title `"···"`→`"..."`(ASCII 三点)——豆腐消除;
- 全 App 把 U+00B7 分隔符 ` · ` 换成 ASCII ` / `(`playlist/search/home/player/settings/login`),
因为字体缺 U+00B7,所有 ` · `(歌单副标题、组头「我创建的 · N 个」、播放页 meta、设置副标)
都会渲染成豆腐,一并消除。
- `app/ui/playlist.c` `playlist_worker` 首屏完成打 `playlist: first-screen idx=.. plid=.. trackIds=..
materialized=.. rc=..` 日志,供下一会话定位 162 首屏是否真失败。
### 30.2 症状②:从该歌单返回首页,歌单列表约 1 秒全是方框(之后恢复)
**根因(部分定位)**:同上字体缺字形。首页组头「我创建的 · N 个」用 ` · `(U+00B7)→ 豆腐;
「我收藏的」页签里大量歌单名含特殊字形(`A神◢◤永存`、`(ಡωಡ)`、`【】『』` 等,字体缺 ◢◤/Kannada)
→ 对应行豆腐。「全是方框约 1 秒后恢复」的**瞬态**属性更符合「页面重进时字体 glyph atlas 冷缓存重栅格
(渲染大量陌生 CJK 歌名后 LRU 淘汰,重进首页需重栅格)」——SDK 内部行为,需设备逐帧观测才能钉死,
本批未再深挖(避免大改 SDK)。
**修法**:` · `→` / `(本批已整体替换);特殊字形歌名属字体覆盖缺口,建议后续给 default 字库补
U+2022/U+25E2 等(不在本批范围)。
### 30.3 症状③:LRC 歌词全部挤成一行纯文本,时间戳也没解析(有词歌曲全这样;272 歌词页曾正常)
**根因**:`core/ncm_json.c` 的 `ncm_json_get_str_at` **只 memcpy 原文、不做 JSON 转义还原**。
`/api/song/lyric` 的 `lrc.lyric` 把整段 LRC 的换行写成 JSON 转义 `\n`;jsmn 只分词不还原 →
`lrc` 字符串里是**字面 `\`+`n`(无真实换行)** → `lrc_parse`(lyric.c)扫不到行尾 `\n` → 整段歌词
取成第一行的一个超长正文,只解析出 1 个时间戳 → 「挤成一行纯文本、时间戳没解析」。先前每个带
JSON 转义字段(`\n`/`\/`/`\uXXXX`)都受影响,只因歌词行数多最显眼。
**修法**:`ncm_json.c` 新增 `static json_unescape()`(RFC 8259 §7:`\" \\ \/ \b \f \n \r \t` + `\uXXXX`
UTF-16→UTF-8,含 `\uD800..DBFF+\uDC00..DFFF` 代理对合成),`ncm_json_get_str_at` 原文→还原写入。
输出恒 ≤ 输入长度(`\uXXXX` 6B→≤4B),out_sz 内必然放得下。`\n`→真实换行后 LRC 逐行切分正常。
(`lyric.c` 的 `copy_lrc_text` 对服务端真·字面 `\n` 的空格化兜底保留不动,双保险。)
### 30.4 症状④:状态栏音量刷新仅 1Hz;连按音量键按多了前台 App 被 SIGKILL(闪退)
**根因(崩溃,实证)**:`patch_v2/meow_launcherd/app_launcher.c` 的「应用内强杀」快捷键
**VOL_DOWN ×3**(`app_seq_hits>=3 && seq_gap_ms=600`)→ `vol_restore` + `kill(SIGTERM)` + 500ms +
`kill(SIGKILL)`。用户**连按音量下键**(正常调音量)3 下即误触发 → SIGKILL。
- 证据:设备 launcher 日志 `meow_20260902_012940.log` 第 1022 行 `1061363 kill seq: VOL_DOWN x3 ->
SIGTERM app`;amsd 日志 `1788284842 amsd EXIT dev.player.ncm sig=9 dur=255353 abnormal=1`
(对应 pid 2169,正与本会话 ncm 日志末尾 `resumed` 后被杀吻合)。非 SIGSEGV、dmesg 无 OOM——
是被 VOL_DOWN×3 快捷键 SIGTERM→SIGKILL。
- 设计缘起:设备前置键 GPIO 不通(§18.12)→ 原 `PREVIOUS+VOL_UP` 退出组合退役,改用 VOL_DOWN×3。
但框架 App(`framework=1`,如 ncm)有 SDK 左缘横滑返回退出(`app.c` 根页 `meow_ui_request_exit`),
VOL_DOWN×3 与之冲突。
**修法**:`app_launcher.c` VOL_DOWN×3 门控到 `!a->framework`(裸 App 保留;框架 App 走左滑返回退出,
连按音量键只调音量)。框架 App 挂死兜底走 `am kill <pkg>`。其余半「根因」:音量 1Hz 刷新是 launcher
状态栏对 settings.conf 的 1s 节流轮询(262 Bug2 定案),音量本体现在秒切(SDK 即时写 settings.conf+
CS43198 DAC),仅图标刷新滞后 1s——属 pacing 取舍,未改(改需 firmware + 设计决断)。
### 30.5 症状⑤(顺带):封面大量 `fetch failed → give up`(封面加载不出)
**根因**:`cover.c` 旧 `static g_buf[NCM_COVER_MAX=288KB]` 作为下载缓冲;网易云封面全尺寸实测
**1.34MB**(`GET` 返回 `200 size=1342045`)> 288KB → `ncm_http` body 超 cap 回 `NCM_ERR_FULL`
→ `decode_cover` 返回 NULL → 退避重试 ×8 放弃。且设备 RAM 仅 **56MB**,不能靠加静态 bss 解决。
**修法**:`cover.c` 改**流式写文件**——`ncm_http_download` 的写回调把 body 直接写进 ncm_cache
写者临时文件(`ncm_cache_write` 是 `write(2)` 直写无缓冲),`meow_img_load_file(临时文件)` 解码,
commit 落盘;`save_cover_raw` 改为临时文件→`cover_current.jpg` 文件复制。NCM_COVER_MAX 改语义为
「下载字节上限 8MB」(非缓冲),静态 bss 归零(设备 RAM 有限)。
### 30.6 验证记录(0 次真实 NCM 业务请求)
| 项 | 方式 | 结果 |
|---|---|---|
| ①/③ unescape | `test_ncm_core.c` 新 `test_json_unescape`(合成 `\n`+`\/`+`\u00e9` JSON) | 宿主 + qemu-mipsel **PASS**:`lrc \n unescaped -> 3 行`、`URL \/ unescaped`、`URL no literal backslash` |
| ①②③ 界面 | `build_ncm_host.sh` + `MEOW_UI_DUMP`(mock,0 网络) | 宿主 ncm_host exit=0 dump 正常 |
| ① 歌单链路 | `playlist_race_sim`(72/92/200 竞态防串/批边界/盘缓存) | **PASS** |
| ① 滑窗物化 | `window_sim`(6000 首滚动 0 占位破窗 + 盘缓存回环) | **PASS** |
| ① 搜索 | `search_sim` | **PASS** |
| ① 缓存 | `cache_evict_sim`(LRU/防淘汰/7 天回收 case1-9) | **PASS** |
| ① 播放链 | `player_flow_sim`(P1-P12 含同 idx 重播换档) | **PASS** |
| ① 边下边播 | `stream_sim`(16 用例 + 2 resume,PCM 金标准) | **ALL PASS** |
| SDK 292/293/295 | `test_meow_ui_listvirt` 44 / `dispatch` / `box` 42 / `scroll` 17 / `anim` 32 / `widgets` / `overlay_suppress` / `sdk_input_keys` / `theme` / `vol` / `ime_fix` / `pinyin` / `notif_gesture` / `notif_open` / `sdk_fixes` / `sb_collapse` / `ui_audio_host` / `notify_ipc` / `notify_integration` | 全 **PASS** |
| launcher | `test_vol_ramp_jack` 15 / `test_ams_start` / `test_notif_tf` / `test_settings_brightness` / `test_media` / `test_lock_screen` / `test_notif_panel` | 全 **PASS** |
| mipsel 构建 | `build_ncm.sh` 自有代码 `-Wall -Wextra -Os` | **0 warning**;`text 991942 / data 1624 / bss 1216260`(bss 从 §26 的 1.5MB 回到 1.2MB——封面去静态缓冲后未涨) |
| core mipsel | `build_core.sh` + qemu-mipsel `test_ncm_core_mipsel` | PASS(含新 unescape) |
### 30.7 md5(本批产物,未部署,留主会话)
```
d91cd906162d58dcc05a59913cca1c93 ncm # 新(未部署)
03dfcf7664cc73a93637dc8816a606e1 meow_launcherd # 新(/usr/bin 只读,需固件或 /usr/data 热副本)
b731654a06d5ca5746edea8393f8668f ncm_host # 宿主调试二进制(重链)
7a38194c990c36fcbc003897168a9020 test/test_ncm_core_host # 宿主 core 测试(重编,含 unescape)
```
改动文件(本批,仅这些):
```
修改:
core/ncm_json.c # json_unescape + ncm_json_get_str_at 还原转义(§30.3)
app/ncm_app_internal.h # NCM_COVER_MAX 288KB→8MB 语义改下载上限(非缓冲,§30.5)
app/logic/cover.c # 封面流式写文件(ncm_http_download cb→ncm_cache_write),§30.5
app/ui/playlist.c # 占位 "···"→"..." + 副标题 " · "→" / " + 首屏诊断日志(§30.1)
app/ui/home.c # 组头 " · "→" / "(§30.2)
app/ui/search.c # 副标题 " · "→" / "
app/ui/settings.c # 设置副标/页脚 " · "→" / "
app/ui/login.c # 登录页 " · "→" / "
app/ui/player.c # 播放 meta " · "→" / "
test/test_ncm_core.c # 新 test_json_unescape(§30.3)
build_ncm_host.sh # SDK_SRC 补 meow_ui_anim/box/scroll(宿主链接 292/295 新源)
../meow_launcherd/app_launcher.c # VOL_DOWN×3 门控 !a->framework(§30.4)
新建:
(无新文件)
```
### 30.8 诚实出入 / 遗留
- **真机未部署未验**(留主会话统一部署 ncm `d91cd906`、launcher `03dfcf76`)。真机验收点:
①162 首歌单点开条目显示歌名(不再三个豆腐/加载中),首页歌单列表无方框;②歌词按行滚动、点行跳
转、时间戳高亮;③连按音量键只调音量不闪退;④封面正常显示;⑤大歌单首屏日志 `playlist:
first-screen ... materialized=N rc=0`(N>0,无失败)。
- **症状①物化层未再钉死**:162 首屏 song_detail 批在真机失败的确切 rc 待新日志(`playlist:
first-screen` 行)复现读取;若 rc 为网络码需要后续加固超时/重试(本批未改,保持 §26 语义)。
- **症状②「全方框 1s 恢复」**:高度疑为字体 glyph atlas 冷缓存重栅格(SDK 内部),本批未深挖;
已消除 ` · ` tofu,特殊字形(◢◤/Kannada)建议补 default 字库。
- **音量 1Hz 刷新**:launcher 状态栏 1s 节流(262 定案)——音量本体秒切、仅图标滞后;改需 firmware
+ pacing 设计决断,本批未动。
- **`parse_url` http→443 潜性缺陷**:`http://` URL 无显式端口时 `url.port` 恒 443(TLS),对
music.126.net CDN(443 上提供 TLS)恰好可用;语义错误但未阻断,未改(改动有回归风险)。
- **未动 sdk/、vendor/**;`ncm_host`/`test_ncm_core_host` 为宿主调试产物随批重编。