ncm 三期三批: 歌曲对象瘦身(1324→608B)+滑动窗口(NCM_SONG_WINDOW=256, 取消1000截断支撑任意长度歌单)+播放快照解耦 — agent-251

- core/ncm_api.h: ncm_song_t 瘦身 id24/name128/art96/al96/pic256/dt int32 (1324B→608B)
- appstate.c 滑动窗口: song_ids 全量 trackIds 索引(16384) + songs[256] 窗口感知
  (song_valid 位图/window_base/滑窗 memmove 保留重叠), ensure_range 批网格(100)对齐
  预取, songmeta.c 盘缓存 <cache_dir>/meta/<id>.bin (回滚/二次进入 0 网络请求)
- playlist.c 触底分页退役→视口 ensure(±24 行), 占位行可直点(播放线程 fetch 物化),
  footer 改视口区间/总数
- 播放兼容: g_play.play_song 快照(mu), run_song 起播物化后全链路(下载/歌词/IPC META/
  播放页 UI)读快照——窗口滑出当前播放曲不崩; 边界 song_count→track_ids_count
- 验证(0 真实请求): pagination_sim 全 PASS(5191 无截断/52 批); window_sim 6000 首
  全程下滚 750 步视口全物化/song_detail 恰 60 次/回滚+0 请求/播放 fetch 不滑窗;
  core 宿主+qemu 全 PASS; mipsel -Wall -Wextra 0 warning, bss 2052068→1080900(-47%)
- 未部署真机(设备音频线排查中, 部署留主会话)
This commit is contained in:
MeowOS committed 2026-09-01 04:03:01 +08:00
1 parent d8898afa09
commit 997f89f879
18 files changed
+987 -209

No files matched your search

+161
View File
@@ -1293,3 +1293,164 @@ db5b78ad9e8f40e6306484efe2a08eb3 ncm # mipsel 静态 App
- 设备日志中 ipc_media META 每秒一对(state=0 空态也发)为既有行为,本批未动。
- **未碰** /data/recovery_all、未 killall hiby_player、未主动重启设备(§16.3 的多次重启均为
被测路径触发的设备自重启)。
---
## 17. 三期第三批:歌曲对象瘦身 + 滑动窗口(任意长度歌单,取消 1000 截断)+ 播放快照(2026-09-01)
> 范围:仅 `patch_v2/ncm_app/`(含 `core/` 结构体瘦身)。未动 `sdk/`、`meow_launcherd/`、
> `meow_audiod/`、`vendor/`。风控纪律:本批真实 NCM 请求 **0 次**(全部离线 stub + /tmp 盘缓存)。
> **本批未部署真机**(设备音频线挂死排查中,ncm 二进制不动卡;部署留主会话)。
### 17.1 动机
§14 的歌单分页(detail 全量 trackIds 驱动 song_detail 分批 100、触底加载)仍受
`NCM_SONG_MAX=1000` 内存硬上限约束(每首 `ncm_song_t` ≈1324B → 1000 首 ≈1.32MB bss),
用户「我喜欢」6000+ 首只能看到前 1000。本批把「全量物化」改为**滑动窗口**:内存只保留
视口附近的歌曲对象(256 首),滚动出窗释放、入窗按需拉取,支撑任意长度歌单。
### 17.2 歌曲对象瘦身(core/ncm_api.h,1324B → 608B,-54%)
| 字段 | 旧 | 新 | 依据 |
|---|---|---|---|
| `id` | 32 | 24 | 数字文本,int64 最大 19 位 + NUL |
| `name` | 256 | 128 | 列表/播放页展示均按宽再截断(`ncm_text_fit`/listview) |
| `art_name` | 256 | 96 | 首个歌手名 |
| `album_name` | 256 | 96 | 同上 |
| `al_pic` | 512 | 256 | picUrl 实际 <200 字符;`obj_str` 截断安全 |
| `duration_ms` | `long` | `int32_t` | <2^31ms≈24 天;MIPS32 的 long 本就 32 位(零行为变化) |
解析侧(`parse_songs_array`)全部走 `sizeof 字段` 拷贝,无需改动;core 测试
(`test_ncm_core.c` 断言 id/name/ar/al/dt/fee/al_pic)宿主 + qemu-mipsel 全 PASS。
### 17.3 滑动窗口(appstate.c + playlist.c)
- **内存模型**(`ncm_app_internal.h`):
- `song_ids[NCM_TRACKID_MAX=16384]` = detail 取回的全量 trackIds 索引(int64,128KB bss,
不再 1000 截断;16384×~13B ≈ 213KB JSON,远在 2MB 响应限幅内);
- `songs[NCM_SONG_WINDOW=256]` + `song_valid[256]` 位图 = 物化窗口(256×608B≈152KB bss),
`songs[i] ↔ 全局下标 window_base+i`;
- 列表 count = `track_ids_count`(全量),未物化行画占位「··· / 加载中」。
- **API**(`app/logic/appstate.c`,全锁 `g_song_mu`;网络/盘 IO 在锁外,锁内仅内存搬移):
- `appstate_song_ensure_range(lo,hi)`:窗口未覆盖先滑窗(重叠区 `songs+valid` 一起
memmove 保留,出窗槽位失效),缺失槽先读盘缓存,miss 攒批 song_detail 补载并落盘;
- `appstate_song_copy(gidx,&out)` / `appstate_range_loaded(lo,hi)`:锁内读窗(UI 行回调);
- `appstate_song_fetch(gidx,&out)`:任意下标物化(窗→盘→song_detail 单曲),**不滑窗**
(播放线程专用,避免与浏览视口抢窗口造成乒乓);
- `appstate_adopt_songs(n)`:收编外部已填的 `songs[0..n)`(搜索结果)为窗口 base=0 播放上下文;
- 兼容入口 `appstate_load_playlist_songs_page(idx,offset,limit)`:offset=0 重建索引+物化
首段;offset>0 = ensure [offset,offset+limit)。
- **批网格对齐预取**:补载区间对齐到 `NCM_SONG_BATCH=100` 的全局网格([a0,b0) ≤100),
逐行滚动不产生小请求放大——6000 首全程下滚 song_detail 调用恰 = 60 次(6000/100)。
- **盘缓存(app/logic/songmeta.c,新)**:拉过的 song_detail 结果逐首序列化到
`<cache_dir>/meta/<id>.bin`(头 magic/ver/size + 结构体镜像;ver/size 失配判 miss)。
刻意不进 ncm_cache LRU 池:meta 每首 ≈600B(一万首 ≈6MB),不被清音频缓存误清。
效果:二次进入歌单 / 回滚全程 **0 网络请求**(离线可浏览已拉过的部分)。
- **UI(playlist.c)**:`pl_maybe_load_more`(触底追加)退役 → `pl_maybe_ensure`(视口 ±24 行
有未物化槽 → 后台 worker ensure);footer 改「视口区间 / 总数」(如 `5965-5988 / 6000 首`);
首屏失败整页空态 + 点按重试不变;补载失败 snack + 占位行保留(滚动/点按再试)。
### 17.4 播放兼容(窗口滑出当前播放曲不许崩)
- **播放快照**:`g_play.play_song`(mu 保护)——`run_song` 起播时物化目标曲
(`song_copy` 窗内命中 / `song_fetch` 窗外)并整体拷入快照;之后下载(id)、时长、
歌词 worker(id)、IPC META(title/artist/album)、播放页 UI(歌名/歌手/封面)**全读快照**,
与窗口完全解耦。窗口滑出当前播放曲 → 播放/歌词/媒体会话照常。
- 上下首/自然顺延边界:`g_st.song_count`(窗口内有效数)→ `g_st.track_ids_count`(全量索引),
cur_song 语义 = 全局下标不变。
- **占位行可直接点歌**:`player_play_song(row)` 不要求行已物化——封面回落歌单封面,
元数据由播放线程 fetch 后快照接管;UI tick 按快照 id 变化纠正封面/歌名(快照滞后 settle)。
- 搜索上下文:`search_worker` 结果经 `adopt_songs` 收编(song_ids 同步 strtoll),
播放/上下一首对结果集生效(同旧语义)。
### 17.5 内存对比(6000 首歌单场景)
| 项 | 改前(§14) | 改后(§17) |
|---|---|---|
| `ncm_song_t` | 1324B | 608B(-54%) |
| 歌曲对象数组 | 1000×1324B ≈ 1292KB(**且 1000 截断,6000 首只能看前 1000**) | 256×608B ≈ 152KB(任意长度恒定) |
| trackIds 索引 | 1000×8B = 8KB(截断) | 16384×8B = 128KB(全量) |
| 有效位图 | — | 256B |
| **合计(歌曲数据)** | **≈1300KB(有截断)** | **≈280KB(无截断)** |
| ncm bss 总量(`size`) | 2052068 | **1080900**(-972KB,-47%) |
6000 首全量物化的假想成本 6000×1324B≈7.9MB(14MB 预算下不可行)→ 滑窗后与歌单长度无关。
### 17.6 验证记录(0 真实 NCM 请求)
| 项 | 方式 | 结果 |
|---|---|---|
| 窗口基本语义 | `bench/pagination_sim.c`(已更新到新语义,离线 stub 驱动真实 appstate) | **全 PASS**:130 首整装入窗(1 detail + 2 song_detail);5191 首无截断(索引全量 5191、52 批网格、末首可达、出窗槽 copy 失败、越界无操作);空歌单 0 |
| 6000 首滚动全程 | `bench/window_sim.c`(新;真实 appstate + 真实 songmeta 盘缓存 /tmp) | **全 PASS**:首屏 1+1 请求;下滚 750 步(步进 8 行)每步视口 12 行全物化、song_detail 恰 60 次(无放大)、有效槽恒 ≤256;**回滚全程 +0 请求**(盘缓存全命中,6000 meta 文件落盘);窗口滑出播放曲后 `song_fetch` 不滑窗不崩、盘命中免请求、尾部/越界钳制正确 |
| core 回归 | `build_core.sh` + `test_ncm_core_host` + `qemu-mipsel out/test_ncm_core_mipsel` | 宿主 + qemu 全 PASS(结构体瘦身不影响解析断言) |
| mipsel 构建 | `bash build_ncm.sh`(自有代码 `-Wall -Wextra`) | **0 warning**;`text 865604 / data 1480 / bss 1080900` |
| 宿主离屏渲染 | `build_ncm_host.sh` + `MEOW_UI_DUMP`(mock,0 网络) | home/playlist/player/lands/settings/search/login 渲染正常:歌单 6 行 + footer `1-6 6`(视口区间格式)、黑胶 + 快照歌名/歌手 + 歌词高亮;自有代码 0 warning(仅 SDK `meow_ui_vol.c` 既有告警) |
### 17.7 md5(本批改动文件 + 产物)
```
b32e8a80c12de4dac2e4ad52d26ae55c ncm # mipsel 静态 App(新;未部署)
783d3ddf9bfc1905f732f688c54c5447 ncm_host # 宿主离屏调试二进制
321039c8743af6ccd934c63a3a161e84 app/ncm_app_internal.h
bde871b33298cf25187054650560754a core/ncm_api.h
3c4122e72cc701aa5d0d95862b0201d1 app/logic/appstate.c
dd8fbb70ba5e0978fa75501135c473a3 app/logic/songmeta.c
cb44e2385108ba6cdad242aecf635b64 app/ui/playlist.c
533dafebce47bd60355a1efc7719db9c app/ui/player.c
a5447ac5e6f2ba83405a7a3c19c88dc9 app/play/player_manager.c
74eded2b9a220aba9a50832ff9c6914c app/play/ipc_media.c
d2c2505eede96ddb1387209681894b83 app/ui/search.c
564630846354d3e202de303435e82c59 app/test/test_ncm_app.c
00a1445f83e98a068c4219593bb7d6d8 bench/pagination_sim.c
b2b6869990c8ef3906c100f18c7194e8 bench/window_sim.c
3159c53adf59967cacdad50a58946858 build_ncm.sh
430211ee6895c26c9871da22e9aede76 build_ncm_host.sh
```
### 17.8 出入清单(只改 `patch_v2/ncm_app/`)
```
修改:
core/ncm_api.h # ncm_song_t 瘦身 1324B→608B(id24/name128/art96/al96/pic256/dt int32)
app/ncm_app_internal.h # 删 NCM_SONG_MAX;+NCM_TRACKID_MAX 16384 / NCM_SONG_WINDOW 256 /
# song_valid[]/window_base;g_play.play_song 快照;窗口 API + songmeta 声明
app/logic/appstate.c # 滑动窗口(g_song_mu/滑窗 memmove/批网格预取/盘缓存补载)+
# song_copy/range_loaded/song_fetch/adopt_songs + 兼容入口改窗口语义 +
# autoplay 走 song_copy + logout 清窗口 + strncpy→ncm_strlcpy 消告警
app/ui/playlist.c # 触底分页退役 → 视口 ensure(pl_maybe_ensure)+ 占位行 + footer 视口区间
app/ui/player.c # 元数据读播放快照(cur_song_meta)+ 快照 id 变化纠正封面/歌名 +
# 占位行可点(播放线程物化)
app/play/player_manager.c # run_song 起播快照 + song_local_file 按 id + 边界 track_ids_count +
# 歌词 worker 读快照 + player_manager_current_song 访问器
app/play/ipc_media.c # META title/artist/album 读播放快照
app/ui/search.c # 结果经 appstate_adopt_songs 收编为窗口播放上下文
app/test/test_ncm_app.c # mock 补窗口字段(song_valid/track_ids_count)+ 播放快照
bench/pagination_sim.c # 更新到窗口语义(5191 无截断/出窗 copy 失败/网格批数)
build_ncm.sh / build_ncm_host.sh # +app/logic/songmeta.c
ncm / ncm_host # 重编产物
新增:
app/logic/songmeta.c # 歌曲元数据盘缓存(<cache_dir>/meta/<id>.bin,magic/ver/size 头)
bench/window_sim.c # 6000 首滚动全程离线验证(0 请求,盘缓存回滚 0 新增请求)
REPORT.md # 本批记录(§17)
(未动 sdk/、meow_launcherd/、meow_audiod/、vendor/;未部署真机;未动设备系统/恢复区)
```
### 17.9 诚实出入 / 已知限制
- **真机未部署/未验**(风控 + 设备音频线排查中;部署留主会话)。真机验收点:① 6000+ 歌单
全程下滚/回滚无占位残影、footer 视口区间随滚动更新;② 占位行直点能播(fetch 物化);
③ 播放中猛滑列表把当前曲滑出窗 → 播放/歌词/媒体卡片不断;④ 二次进入歌单离线可浏览
已拉过的部分(meta 盘缓存);⑤ 歌单 >16384 首时 trackIds 索引截断(footer 总数仍对,
尾部不可达——16384 是 128KB bss 的取舍,当前用户最大歌单 6000+ 有足够余量)。
- **单写者假设**:ensure/page 仅由 playlist worker 驱动(`g_plthr_on` 互斥),播放线程
`song_fetch` 不写窗口;搜索 worker 的 cloudsearch 直接写 `songs[]` 前半再 `adopt_songs`
收编(与旧代码同款的单写者时序,未加新锁)。
- **快照滞后一瞬**:切歌瞬间(next/prev/占位行直点)播放页歌名/封面仍是旧曲快照,
play 线程物化后(通常 <100ms,窗外需一次盘读/网络)由 tick 按快照 id 纠正——一帧级旧
内容,与 §14.7 歌词锁残余同级接受。
- **切歌单时播放上下文**:打开新歌单会重建索引/清窗口(同 §14 既有行为),正在播的旧歌单
曲目靠快照续播(比旧模型更安全:旧模型 memset 后 id/dur 全丢);但 cur_song 下标语义
随新歌单索引变化(既有竞态,未扩大)。
- **meta 盘缓存无版本化清理**:结构体再改版靠 ver/size 判 miss 自动重建;无 TTL
(歌曲改名/换封面不主动失效——与 ncm_cache 同级的「缓存可再生」取舍)。
- **host 构建有 SDK `meow_ui_vol.c`「system 返回值忽略」告警**(SDK 源,非本 App;mipsel musl 无)。