fix(ncm §32): 内存瘦身修 OOM 被杀 —— 封面降采样(8.28MB→0.78MB) + 瞬态 rbuf 缩减 + 线程栈显式
真机 dmesg 实锤:OOM Kill ncm (score 460, anon-rss 26.9MB, 设备 56MB)。 画像:封面全分辨率 RGBA 8.28MB(1448x1448 原生解码驻留) 是最大可控块。 瘦身(按收益排序): 1. sdk meow_ui_img 新增纯增量 meow_img_downscale + cover.c cover_cap(≤480px) → 封面 8.28MB→0.78MB,-7.65MB(mem_sim 实测 PASS)。 2. ncm_api.c 瞬态响应 rbuf:song_url→64KB、lyric/song_detail/cloudsearch/user_playlist→256KB (detail/track_all 保 1MB)。 3. ncm_app_internal.h 新增 ncm_thread_create(显式 128KB 栈),替换全部 14 处 pthread_create。 4. 滑动窗口/meta 复核零回退(window_sim:608B/首,6000 首 280KB 恒定)。 验证:mem_sim 封面收益 PASS + host home/player 渲染 OK + stream_sim/player_flow_sim/ window_sim/search_sim/cache_evict_sim 全 PASS + test_ncm_core host+mipsel(qemu) PASS + build_ncm.sh -Wall -Wextra 0 warning。真机 scp 播放在跑确认(缓存曲,0 播放请求)。 新 ncm md5 44fa16fe(text 1015150/bss 1362500)。正式部署留主会话。
This commit is contained in:
1 parent
202caaf66b
commit
68ce46a5d8
19 files changed
+368
-19
No files matched your search
@@ -2724,3 +2724,85 @@ de4c5e1939c7571b90f75a80f48236c0 build_ncm_host.sh # SDK_SRC 补 meow_
|
||||
§20.3 档位链语义本身的验证属主会话(297 队列项)。
|
||||
- **bss +~95KB**:`s_pls[NCM_PLAYLIST_MAX]`(120×808 ≈ 95KB)staging,与既存 `s_build[16384]`
|
||||
(128KB) 同口径;设备 RAM 56MB 可承受(总 bss 1.36MB)。
|
||||
|
||||
## 32. 内存瘦身:真机 OOM 被杀修复 —— 封面降采样 + 瞬态缓冲缩减(2026-09-02,agent-308)
|
||||
|
||||
> 范围:`patch_v2/ncm_app/` + `patch_v2/sdk/meow_ui_img.c/.h`(新增一个纯增量 downscale API)。
|
||||
> 风控:真机验证用 autoplay 播**已缓存**曲目(0 播放请求);HOME 歌单列表走盘缓存
|
||||
> (`playlists.bin`,§26),首启一次网络刷新(少量真实请求,任务允许)。
|
||||
|
||||
### 32.1 动机(真机 dmesg 实锤)
|
||||
|
||||
2026-09-02 02:56 设备内核 OOM:`Out of memory: Kill process 1151 (ncm) score 460`,
|
||||
ncm `total-vm 97MB / anon-rss 26.9MB`,设备 56MB 总内存(系统+服务占完可用 ~20MB)。
|
||||
launcher `app_launcher.c:1647` 已有 OOM 护栏(`MemAvailable<24MB` → 大字库降 common),
|
||||
说明系统侧早已见过该类压力。本批把 ncm 的匿名 RSS 拆清算清,并把能砍的砍到常态 ≤16MB。
|
||||
|
||||
### 32.2 内存画像(拆到每块,宿主 mem_sim + 设备实测 + 源码审计)
|
||||
|
||||
| 块 | 实测/审计 | 说明 |
|
||||
|---|---|---|
|
||||
| **封面 RGBA(大头)** | 1448×1448 → **8.28MB**(mem_sim 实测 RssAnon 增量 8484KB) | `meow_img_load_file` 用 stbi 按**原生分辨率**解码为 RGBA 并驻留(`g_cover_img`)。网易云全尺寸封面即此规模。 |
|
||||
| 字库(fonts=common 档) | plane **2.44MB**(`default/regular/font.png` 1280×3996 depth4 灰度)+glyphs ~130KB | app.json `fonts:"common"`,launcher 注入 `MEOW_FONT_TIER=0`。plane 懒触达(绘制 glyph 才换页)。GBK 档约 17MB(launcher 注释),common 远小。 |
|
||||
| bss 静态数组 | **1.36MB** | `song_ids[16384]`128KB + `songs[256]`152KB + `search_songs[256]`156KB + `playlists[120]` + `qr_mods` + … |
|
||||
| back_buf(SDK) | **0.69MB** | 480×720×2(16bpp,mmap fb 之外仍 heap 分配一份 `g_back_buf`)。 |
|
||||
| 线程栈 | musl 静态默认 **128KB/线程** × ~5-8 线程 ≈ 0.6-1MB | 非 glibc 8MB/thread;实测调用链栈深 ≤~30KB。 |
|
||||
| mbedtls(每连接瞬态) | in/out 各 16KB(默认)+ CA bundle 解析 ~440KB | 每 `http_run` 加载 182KB CA + 解析 + 32KB ssl。播放+封面并发 ~2-3 连接。 |
|
||||
| API 响应 rbuf(瞬态) | 旧每 `api_post` 固定 **1MB** | song_url/lyric/song_detail/cloudsearch 响应实际 ≤100KB,1MB 严重超额;并发几 × 1MB 是可砍峰值。 |
|
||||
|
||||
**结论**:真正的大头是**封面全分辨率 RGBA(8.28MB)**,其次瞬态 1MB rbuf。字库(common 2.44MB)
|
||||
与 bss(1.36MB)不可由 ncm 轻易再砍(字库为系统字体档,bss 已是 §17 瘦身后)。
|
||||
|
||||
### 32.3 瘦身(按收益排序)
|
||||
|
||||
1. **封面降采样(-7.65MB)**:`patch_v2/sdk/meow_ui_img.c` 新增纯增量 `meow_img_downscale(img,maxdim)`
|
||||
(最近邻等比缩放到最长边 ≤maxdim,复用既有 `scale_buf`);声明进 `include/meow_ui_image.h`。
|
||||
`cover.c` 新增 `cover_cap()`(`NCM_COVER_DECODE_MAX=480`)在缓存命中/下载解码后统一降采样,
|
||||
释放原大图驻留小图。播放页黑胶方图 + 歌词页模糊铺底仅需 ≤480px,全尺寸属浪费。
|
||||
2. **瞬态 API rbuf 缩减**:`ncm_api.c` 新增 `RESP_SMALL(64KB)/RESP_MED(256KB)`——
|
||||
song_url→64KB、lyric/song_detail/cloudsearch/user_playlist→256KB;仅
|
||||
`playlist/detail`、`track/all`(全量 trackIds ≤16384)保留 1MB。
|
||||
3. **线程栈显式**:`ncm_app_internal.h` 新增 `ncm_thread_create()`(`pthread_attr_setstacksize` 128KB),
|
||||
替换全部 14 处 `pthread_create`。musl 默认即 128KB(本设备 0 收益),显式声明防未来
|
||||
libc/平台默认变大(如宿主 glibc 8MB/thread),并满足「按实际用量调小」要求。
|
||||
4. **滑动窗口/meta 复核**:`window_sim` 重跑确认零回退——`ncm_song_t`=608B(旧 1324B)、
|
||||
窗口=155904B(256 首)、trackIds 索引=131072B(16384 首);6000 首场景旧 1292KB→新 280KB(恒定)。
|
||||
|
||||
### 32.4 验证记录
|
||||
|
||||
| 项 | 方式 | 结果 |
|
||||
|---|---|---|
|
||||
| 封面收益 | **mem_sim**(新 bench,链 host SDK):init 基线 → 载全尺寸封面 → downscale 480 | **PASS**:全尺寸 RssAnon +8484KB → 降采样后 -7836KB(8.28MB→0.78MB),尺寸 480×480 |
|
||||
| 宿主渲染回归 | build_ncm_host_mem.sh + NCM_PAGE=home/player MEOW_UI_DUMP | rc=0、两页渲染正常(SDK img 改动无回归) |
|
||||
| 播放链路回归 | stream_sim 全矩阵 + resume / player_flow_sim P1-P12 | **ALL PASS** |
|
||||
| 大歌单/搜索/缓存 | window_sim / search_sim / cache_evict_sim | **ALL PASS**(含 6000 首滑动窗口内存断言、搜索分页、LRU 防淘汰+7 天回收) |
|
||||
| core 回归 | build_core.sh → test_ncm_core_host + qemu test_ncm_core_mipsel | **PASS**(host + mipsel+qemu) |
|
||||
| mipsel 构建 | build_ncm.sh(自有代码 -Wall -Wextra) | **0 warning**(set -e 下任何告警即失败) |
|
||||
| 真机播放 | autoplay 播已缓存曲(新 ncm 已上卡) | 音频开流/播放正常(log `play: start … audiod open ok`);常驻 ~6MB(HOME 不触发封面,封面收益由 mem_sim 证明) |
|
||||
|
||||
**真机峰值说明(诚实)**:裸启 ncm(不经 launcher)在拖入会话后偶收 GTP 复位产生的幽灵左滑 → `exit:swipe`
|
||||
退出,无法做长时驻留采样;封面在设备上的运行时 RSS 收益以宿主 mem_sim 的逐字节实测为准
|
||||
(封面解码路径 `meow_img_load_file` 宿主/设备同源,差异仅在字库背板)。
|
||||
|
||||
### 32.5 md5(本批产物)
|
||||
|
||||
```
|
||||
44fa16feba8e597d7dbe9535983f2629 ncm # mipsel 静态 App(text 1015150 / bss 1362500;未部署留主会话,仅真机播放在跑确认)
|
||||
34be096f196aa24b755629bd43a543f7 ncm_host # 宿主离屏二进制(重链)
|
||||
a9842284ecf9529ac1c39a9119561a20 mem_sim # 宿主内存测量 harness
|
||||
(REPORT §32;sdk/meow_ui_img.c + include/meow_ui_image.h 变更见 git)
|
||||
```
|
||||
|
||||
### 32.6 诚实出入 / 遗留
|
||||
|
||||
- **未部署**:新 ncm 已 scp 到设备 `/usr/data/mnt/sd_0/MeowAPPs/dev.player.ncm/ncm` 做真机播放在跑确认,
|
||||
正式部署/固件打包留主会话统一。
|
||||
- **字库(common 2.44MB)本批未动**:属系统字体档位,由 launcher 按 `MemAvailable` 护栏决定;
|
||||
ncm 无法(也不应)自行缩减。若需更低,只能走「GBK/Full」降档之外的字体裁剪(另立批)。
|
||||
- **mbedtls in/out(16KB/16KB)未缩**:改 `MBEDTLS_SSL_OUT_CONTENT_LEN` 需重编 mbedtls,收益 ~12KB/连接,
|
||||
低于风险,留主会话。
|
||||
- **封面 downscale 只作用于封面**:SDK `meow_img_downscale` 为纯增量 API,未改动其它 App 的图片路径
|
||||
(reader/hello/files 不引入)。若后续发现其它 App 大图驻留,可复用同一 API。
|
||||
- **真机长期峰值**:幽灵左滑导致无法持续采样,设备端「≤16MB」是「基线 ~6MB + 封面 0.78MB + 字库
|
||||
2.44MB」的推演上限(约 8-9MB),低于 16MB 目标;正式部署后建议主会话做一次带 launcher 的
|
||||
前台三场景采样复核。
|
||||
@@ -722,7 +722,7 @@ void autoplay_maybe(void)
|
||||
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);
|
||||
if (ncm_thread_create(&t, autoplay_worker, sel) == 0) pthread_detach(t);
|
||||
else free(sel);
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,19 @@ static void save_cover_raw(const char *path, size_t len)
|
||||
|
||||
#define COVER_RETRY_MAX 8 /* 连续失败放弃次数(UI 重进页/切歌会重新提交目标) */
|
||||
|
||||
/* 内存瘦身:封面解码后按显示上限降采样驻留。网易云全尺寸封面(如 1448×1448)解码成
|
||||
* RGBA = 8MB,而应用显示只需 ≤480px(播放页黑胶方图+歌词页模糊铺底均 ≤ 屏幕 480 宽)——
|
||||
* 缩到 480×480 = 0.9MB,省 ~7MB anon(设备 RAM 仅 56MB,OOM 真机被杀,最高优先级)。
|
||||
* 若已 ≤上限(小缩略图)则原样保留。返回本函数处理后的图(可能替换/释放旧图)。 */
|
||||
#define NCM_COVER_DECODE_MAX 480
|
||||
static meow_img *cover_cap(meow_img *im)
|
||||
{
|
||||
if (!im) return NULL;
|
||||
meow_img *small = meow_img_downscale(im, NCM_COVER_DECODE_MAX);
|
||||
if (small) { meow_img_free(im); return small; } /* 已缩放:释放原始大图,换小图驻留 */
|
||||
return im; /* 本就够小:保留原图 */
|
||||
}
|
||||
|
||||
static void *cover_worker(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@@ -168,6 +181,8 @@ static void *cover_worker(void *arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* 内存瘦身:全尺寸封面解码后降采样到 ≤480px 驻留(省 ~7MB anon,OOM 修复)。 */
|
||||
if (img) img = cover_cap(img);
|
||||
if (!img) {
|
||||
/* 失败:指数退避重试,连续 COVER_RETRY_MAX 次放弃(不无声死等;
|
||||
* 目标保留期间 UI 每帧重请求由 cover_request 去重挡住)。 */
|
||||
@@ -218,7 +233,7 @@ void cover_request(const char *url)
|
||||
pthread_mutex_unlock(&g_cm);
|
||||
if (!g_thr_on) {
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, cover_worker, NULL) == 0)
|
||||
if (ncm_thread_create(&g_thr, cover_worker, NULL) == 0)
|
||||
pthread_detach(g_thr);
|
||||
else
|
||||
g_thr_on = 0;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <pthread.h>
|
||||
#include <limits.h>
|
||||
#include "meow_ui.h"
|
||||
#include "meow_ui_view.h"
|
||||
#include "ncm.h" /* 核心库伞头(ctx/api/login/cache/...) */
|
||||
@@ -81,6 +82,21 @@
|
||||
/* active_playlist 哨兵:搜索歌单详情(复用 playlist_open 链路,读 g_st.search_pl_open) */
|
||||
#define NCM_PL_SEARCH (-2)
|
||||
|
||||
/* 线程创建:显式小栈(内存瘦身,OOM 修复)。本 App 为 musl 静态二进制,pthread 默认栈
|
||||
* 即 128KB;这里显式声明 128KB(与默认一致、零成本)并防未来 libc/平台默认变化(如
|
||||
* glibc 8MB/thread 的宿主构建)。实测网络/解码调用链栈深 ≤~30KB,128KB 充足。
|
||||
* 返回 pthread_create 结果。 */
|
||||
#define NCM_THREAD_STACK (128 * 1024)
|
||||
static inline int ncm_thread_create(pthread_t *t, void *(*fn)(void *), void *arg)
|
||||
{
|
||||
pthread_attr_t a;
|
||||
pthread_attr_init(&a);
|
||||
pthread_attr_setstacksize(&a, (size_t)NCM_THREAD_STACK);
|
||||
int r = pthread_create(t, &a, fn, arg);
|
||||
pthread_attr_destroy(&a);
|
||||
return r;
|
||||
}
|
||||
|
||||
/* ---- 播放/媒体会话状态(play 线程 ↔ UI/IPC 共享,mu 保护)---- */
|
||||
enum {
|
||||
NCM_PC_STOPPED = 0,
|
||||
|
||||
@@ -205,7 +205,7 @@ int ipc_media_start(void)
|
||||
{
|
||||
if (g_thr_on) return 0;
|
||||
g_quit = 0;
|
||||
if (pthread_create(&g_thr, NULL, ipc_thread, NULL) != 0) return -1;
|
||||
if (ncm_thread_create(&g_thr, ipc_thread, NULL) != 0) return -1;
|
||||
pthread_detach(g_thr);
|
||||
g_thr_on = 1;
|
||||
return 0;
|
||||
|
||||
@@ -369,7 +369,7 @@ static int song_download_attempt(const ncm_song_t *s, int idx, int seq, const ch
|
||||
size_t cap = (ext[0] == 'f') ? (1024u << 20) : (64u << 20);
|
||||
ss->maxb = (u.size > 0 && (uint64_t)u.size < cap) ? (size_t)u.size : cap;
|
||||
ncm_strlcpy(ss->url, u.url, sizeof ss->url);
|
||||
if (pthread_create(&ss->dl_thr, NULL, dl_thread, ss) != 0) {
|
||||
if (ncm_thread_create(&ss->dl_thr, dl_thread, ss) != 0) {
|
||||
ncm_cache_abort(ss->w); ss->w = NULL;
|
||||
stream_src_close(ss->src); ss->src = NULL;
|
||||
return NCM_ERR_IO;
|
||||
@@ -793,7 +793,7 @@ static void lyric_ensure_worker(void)
|
||||
pthread_mutex_lock(&g_lyrmu);
|
||||
if (!g_lyr_thr_on) {
|
||||
g_lyr_thr_on = 1;
|
||||
if (pthread_create(&g_lyr_thr, NULL, lyric_worker, NULL) == 0) pthread_detach(g_lyr_thr);
|
||||
if (ncm_thread_create(&g_lyr_thr, lyric_worker, NULL) == 0) pthread_detach(g_lyr_thr);
|
||||
else g_lyr_thr_on = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&g_lyrmu);
|
||||
@@ -825,7 +825,7 @@ int player_manager_start(void)
|
||||
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;
|
||||
if (ncm_thread_create(&g_thr, player_thread, NULL) != 0) return -1;
|
||||
g_thr_on = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -233,7 +233,7 @@ void home_reload(void)
|
||||
g_home_loading = 1;
|
||||
}
|
||||
g_home_thr_on = 1;
|
||||
if (pthread_create(&g_home_thr, NULL, home_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_home_thr, home_worker, NULL) != 0) {
|
||||
g_home_thr_on = 0; g_home_loading = 0; g_load_err = NCM_ERR_IO;
|
||||
}
|
||||
home_apply();
|
||||
|
||||
@@ -176,7 +176,7 @@ int login_start(void)
|
||||
g_polling = 1;
|
||||
g_done = 0;
|
||||
g_err = 0;
|
||||
if (pthread_create(&g_thread, NULL, login_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_thread, login_worker, NULL) != 0) {
|
||||
g_polling = 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ static void pl_maybe_ensure(void)
|
||||
g_ens_first = 0;
|
||||
g_plloading = 1;
|
||||
g_plthr_on = 1;
|
||||
if (pthread_create(&g_plthr, NULL, playlist_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_plthr, playlist_worker, NULL) != 0) {
|
||||
g_plthr_on = 0; g_plloading = 0; ncm_snack("加载失败");
|
||||
}
|
||||
}
|
||||
@@ -340,7 +340,7 @@ void playlist_refresh(void)
|
||||
g_pl_stale = 0;
|
||||
g_plloading = 1;
|
||||
g_plthr_on = 1;
|
||||
if (pthread_create(&g_plthr, NULL, playlist_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_plthr, playlist_worker, NULL) != 0) {
|
||||
g_plthr_on = 0; g_plloading = 0; g_load_err = NCM_ERR_IO;
|
||||
}
|
||||
pl_apply(1);
|
||||
|
||||
@@ -211,7 +211,7 @@ static void sr_maybe_load_more(void)
|
||||
g_req_tab = g_tab; g_req_offset = n; g_req_replace = 0;
|
||||
/* 单飞:无 worker 才发(search_worker 尾部解锁) */
|
||||
g_thr_on = 1; g_loading = 1; g_more_err = 0;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_thr, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0; g_more_err = 1;
|
||||
}
|
||||
}
|
||||
@@ -270,7 +270,7 @@ void search_submit(const char *kw)
|
||||
g_st.search_err = 0;
|
||||
g_loading = 1; g_more_err = 0;
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_thr, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0; g_st.search_err = NCM_ERR_IO;
|
||||
}
|
||||
sr_apply();
|
||||
@@ -290,7 +290,7 @@ static void sr_set_tab(int t)
|
||||
g_req_tab = g_tab; g_req_offset = 0; g_req_replace = 1;
|
||||
g_loading = 1; g_more_err = 0;
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_thr, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0;
|
||||
}
|
||||
}
|
||||
@@ -354,7 +354,7 @@ int search_on_tap(int x, int y)
|
||||
g_req_tab = g_tab; g_req_offset = 0; g_req_replace = 1;
|
||||
g_loading = 1;
|
||||
g_thr_on = 1;
|
||||
if (pthread_create(&g_thr, NULL, search_worker, NULL) != 0) {
|
||||
if (ncm_thread_create(&g_thr, search_worker, NULL) != 0) {
|
||||
g_thr_on = 0; g_loading = 0; g_st.search_err = NCM_ERR_IO;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/* bench/mem_sim.c — 宿主内存测量 harness(OOM 瘦身验证)。
|
||||
*
|
||||
* 目的:把 ncm 相关块的匿名 RSS(RssAnon)拆开,验证封面降采样(meow_img_downscale)收益。
|
||||
* 依赖:仅 host SDK 对象(meow_ui_init 装字库 + meow_img 解码),无需 App/core 对象。
|
||||
* 用法:MEM_SIM=1 ./ncm_host
|
||||
*
|
||||
* 场景(各阶段 RssAnon,KB):
|
||||
* [0] meow_ui_init 后基线(含字库 plane + back_buf + 启动堆)。
|
||||
* [1] meow_img_load_file(全尺寸封面 1448x1448) —— 全尺寸 RGBA ≈ 8MB 驻留。
|
||||
* [2] meow_img_downscale(480) + free 原图 —— RGBA 降到 ≤480x480 ≈ 0.9MB。
|
||||
* 断言:RssAnon[1]-RssAnon[0] > 2MB(大封面确实驻留),RssAnon[2] < RssAnon[1]-4MB
|
||||
* (降采样省 >4MB,可验证)。
|
||||
*/
|
||||
#include "../app/ncm_app_internal.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
static long rss_anon_kb(void)
|
||||
{
|
||||
FILE *f = fopen("/proc/self/status", "r");
|
||||
if (!f) return -1;
|
||||
char line[256];
|
||||
long v = -1;
|
||||
while (fgets(line, sizeof line, f))
|
||||
if (strncmp(line, "RssAnon:", 8) == 0) { v = atol(line + 8); break; }
|
||||
fclose(f);
|
||||
return v;
|
||||
}
|
||||
|
||||
static int mem_sim_run(void)
|
||||
{
|
||||
meow_ui_opts o = { APP_W, APP_H };
|
||||
if (meow_ui_init(&o) != 0) { printf("mem: meow_ui_init FAILED\n"); return 1; }
|
||||
|
||||
long r0 = rss_anon_kb();
|
||||
printf("mem[0] baseline (font plane + back_buf + startup): RssAnon=%ldKB\n", r0);
|
||||
if (r0 < 0) { printf("mem: cannot read RssAnon\n"); return 1; }
|
||||
|
||||
const char *cover = "/tmp/ncmfont/cover_current.jpg";
|
||||
if (access(cover, F_OK) != 0) {
|
||||
printf("mem: warning: %s not found; skip cover scenario\n", cover);
|
||||
} else {
|
||||
meow_img *f0 = meow_img_load_file(cover);
|
||||
if (!f0) { printf("mem: cover load FAILED\n"); return 1; }
|
||||
long r1 = rss_anon_kb();
|
||||
printf("mem[1] cover full-res %dx%d loaded: RssAnon=%ldKB (delta=%ldKB)\n",
|
||||
meow_img_w(f0), meow_img_h(f0), r1, r1 - r0);
|
||||
|
||||
meow_img *small = meow_img_downscale(f0, 480);
|
||||
if (small) meow_img_free(f0);
|
||||
long r2 = rss_anon_kb();
|
||||
int w = small ? meow_img_w(small) : (f0 ? meow_img_w(f0) : 0);
|
||||
int h = small ? meow_img_h(small) : (f0 ? meow_img_h(f0) : 0);
|
||||
printf("mem[2] cover downscaled to %dx%d: RssAnon=%ldKB (delta_from_full=%ldKB)\n",
|
||||
w, h, r2, r2 - r1);
|
||||
|
||||
int ok = (r1 - r0 > 2048) && (r1 - r2 > 4096) && (small != NULL) &&
|
||||
(w <= 480 && h <= 480);
|
||||
printf("mem: cover-downscale %s (full_delta=%ldKB, saved=%ldKB, size=%dx%d)\n",
|
||||
ok ? "PASS" : "FAIL", r1 - r0, r1 - r2, w, h);
|
||||
if (small) meow_img_free(small);
|
||||
}
|
||||
|
||||
meow_ui_shutdown();
|
||||
printf("mem: done\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
if (getenv("MEM_SIM") && getenv("MEM_SIM")[0] == '1') return mem_sim_run();
|
||||
/* 未设 MEM_SIM:宿主页面测试走 test_ncm_app.c 的 driver(本文件被链时只占位)。 */
|
||||
printf("mem_sim: set MEM_SIM=1 to run memory scenarios\n");
|
||||
return 0;
|
||||
}
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
# build_ncm_host_mem.sh — 宿主编译 ncm 的真实页面/逻辑(离屏 480x720)+ 内存测量 harness。
|
||||
#
|
||||
# 与 build_ncm_host.sh 的差异:
|
||||
# * 复用既有 out/host/sdk_*.o(SDL 层既有对象,避免 SDK meow_ui_gfx.c 当前工作区
|
||||
# 的注释语法缺陷导致全量 SDK 重编失败),仅重编 meow_ui_img.c(新增 meow_img_downscale)。
|
||||
# * 用途:验证封面降采样(内存瘦身)在宿主按预期工作;供 mem_sim 报告各场景 RSS。
|
||||
# 运行:MEM_SIM=1 ./ncm_host(内置内存自测)或 NCM_PAGE=home MEOW_UI_DUMP=/tmp/home.ppm ./ncm_host
|
||||
cd "$(dirname "$0")"
|
||||
ROOT="$HOME/Project/R3ProII"
|
||||
OUT="$PWD/out"
|
||||
mkdir -p "$OUT/host"
|
||||
CC_HOST=${CC_HOST:-gcc}
|
||||
|
||||
SDK_DIR="$ROOT/patch_v2/sdk"
|
||||
MBED="$OUT/mbedtls/host"
|
||||
|
||||
INC="-I. -I../sdk -I../meow_launcherd -Icore \
|
||||
-I$SDK_DIR -I$ROOT/patch_v2/meow_launcherd \
|
||||
-Ivendor/mbedtls/include -Ivendor/jsmn -Ivendor/qrcodegen/c -Ivendor/minimp3 -Ivendor/dr_flac"
|
||||
|
||||
echo "==> recompile host meow_ui_img (adds meow_img_downscale)"
|
||||
$CC_HOST -O2 -c -DMEOW_UI_HOST_TEST -I"$SDK_DIR" -I"$ROOT/patch_v2/meow_launcherd" \
|
||||
"$SDK_DIR/meow_ui_img.c" -o "$OUT/host/sdk_meow_ui_img.o" || exit 1
|
||||
|
||||
echo "==> host app sources (exclude app.c: test provides main + g_st)"
|
||||
APP_SRC="app/ui/login.c app/ui/home.c app/ui/playlist.c app/ui/player.c \
|
||||
app/ui/settings.c app/ui/search.c app/ui/ncm_colors.c app/ui/chrome.c \
|
||||
app/logic/appstate.c app/logic/lyric.c app/logic/songmeta.c \
|
||||
app/logic/cover.c app/logic/account.c app/play/player_manager.c app/play/audio_out.c \
|
||||
app/play/ipc_media.c app/play/decoder.c app/play/stream_src.c app/play/minimp3_impl.c"
|
||||
for f in $APP_SRC; do
|
||||
b=$(basename "$f" .c)
|
||||
$CC_HOST -O2 -c -DMEOW_UI_HOST_TEST -DJSMN_HEADER $INC "$f" -o "$OUT/host/$b.o" || { echo "app $f FAILED"; exit 1; }
|
||||
done
|
||||
# dr_flac 实现 TU
|
||||
$CC_HOST -O2 -c -Ivendor/dr_flac app/play/dr_flac_impl.c -o "$OUT/host/dr_flac_impl.o" || exit 1
|
||||
$CC_HOST -O2 -c -DMEOW_UI_HOST_TEST -DJSMN_HEADER $INC app/test/test_ncm_app.c -o "$OUT/host/app_test.o" || exit 1
|
||||
|
||||
echo "==> link ncm_host"
|
||||
$CC_HOST -O2 -o ncm_host \
|
||||
"$OUT/host"/*.o \
|
||||
out/core-host/*.o \
|
||||
-L"$MBED" -lmbedtls -lmbedx509 -lmbedcrypto \
|
||||
-lz -lpthread -lm
|
||||
ls -la ncm_host
|
||||
echo "run: NCM_PAGE=home MEOW_UI_DUMP=/tmp/home.ppm ./ncm_host (or MEM_SIM=1 ./ncm_host)"
|
||||
Executable
+79
@@ -0,0 +1,79 @@
|
||||
#!/bin/bash
|
||||
# bench/... 各 sim 的宿主编译+运行(命令取自各 sim 文件头注释;agent 308 内存瘦身回归用)。
|
||||
# 在 ncm_app 目录下运行。
|
||||
set -e
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
CC=gcc
|
||||
INC="-DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd"
|
||||
VINC="-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include -Ivendor/mbedtls/include"
|
||||
# 部分 sim 的 include 路径写法用 vendor/(window/search),有的用 ../vendor/(bench 内部相对),
|
||||
# 统一用绝对相对路径(项目根 pub)。这里按各 sim 头部原样拼接。
|
||||
|
||||
run_sim () { echo "===== $1 ====="; ./$1; echo "[$1 exit=$?]"; }
|
||||
|
||||
echo "==> build stream_sim"
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c bench/stream_sim.c -o /tmp/ss.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c app/play/stream_src.c -o /tmp/ss_src.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c app/play/decoder.c -o /tmp/ss_dec.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c app/play/audio_out.c -o /tmp/ss_ao.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c core/ncm_cache.c -o /tmp/ss_cache.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c core/ncm_common.c -o /tmp/ss_common.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c core/ncm_http.c -o /tmp/ss_http.o
|
||||
gcc -O2 -Wall -Wextra -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
|
||||
-Ivendor/jsmn -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include \
|
||||
-c core/ncm_json.c -o /tmp/ss_json.o
|
||||
gcc -O2 -c -Ivendor/minimp3 app/play/minimp3_impl.c -o /tmp/ss_mp3.o
|
||||
gcc -O2 -c -Ivendor/dr_flac app/play/dr_flac_impl.c -o /tmp/ss_flac.o
|
||||
gcc -O2 -o stream_sim /tmp/ss.o /tmp/ss_src.o /tmp/ss_dec.o /tmp/ss_ao.o /tmp/ss_cache.o /tmp/ss_mp3.o /tmp/ss_flac.o -lpthread -lm
|
||||
run_sim stream_sim
|
||||
|
||||
echo "==> build player_flow_sim"
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd -Icore -Ivendor/jsmn -Ivendor/mbedtls/include \
|
||||
-c bench/player_flow_sim.c -o /tmp/pf.o
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||
-Icore -Ivendor/jsmn -Ivendor/mbedtls/include -Ivendor/minimp3 -Ivendor/dr_flac -Ivendor/mbedtls/include -c app/play/player_manager.c -o /tmp/pf_pm.o
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||
-Icore -c app/play/stream_src.c -o /tmp/pf_ss.o
|
||||
gcc -O2 -Wl,--gc-sections -o player_flow_sim /tmp/pf.o /tmp/pf_pm.o /tmp/pf_ss.o -lpthread -lm
|
||||
run_sim player_flow_sim
|
||||
|
||||
echo "==> build window_sim"
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd -Icore -Ivendor/jsmn -Ivendor/mbedtls/include \
|
||||
-c bench/window_sim.c -o /tmp/ws.o
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||
-Icore -Ivendor/jsmn -Ivendor/mbedtls/include -c app/logic/appstate.c -o /tmp/ws_app.o
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||
-Icore -Ivendor/jsmn -Ivendor/mbedtls/include -c app/logic/songmeta.c -o /tmp/ws_meta.o
|
||||
gcc -O2 -Wl,--gc-sections -o window_sim /tmp/ws.o /tmp/ws_app.o /tmp/ws_meta.o -lpthread
|
||||
run_sim window_sim
|
||||
|
||||
echo "==> build search_sim"
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd -Icore -Ivendor/jsmn \
|
||||
-c bench/search_sim.c -o /tmp/sh.o
|
||||
gcc -O2 -ffunction-sections -DJSMN_HEADER -I. -I../sdk -I../meow_launcherd \
|
||||
-Icore -Ivendor/jsmn -c app/logic/appstate.c -o /tmp/sh_app.o
|
||||
gcc -O2 -Wl,--gc-sections -o search_sim /tmp/sh.o /tmp/sh_app.o -lpthread
|
||||
run_sim search_sim
|
||||
|
||||
echo "==> build cache_evict_sim"
|
||||
gcc -O2 -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include -c bench/cache_evict_sim.c -o /tmp/ce.o
|
||||
gcc -O2 -DJSMN_HEADER -I. -Icore -Ivendor/mbedtls/include -c core/ncm_cache.c -o /tmp/ce_cache.o
|
||||
gcc -O2 -o cache_evict_sim /tmp/ce.o /tmp/ce_cache.o -lpthread
|
||||
run_sim cache_evict_sim
|
||||
|
||||
echo "===== all sims done ====="
|
||||
@@ -9,6 +9,12 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 响应缓冲上界(内存瘦身,OOM 修复):多数接口响应远小于 1MB,按需给尺寸避免每次调用
|
||||
* 都 malloc 1MB 堆峰值(播放中 song_url/lyric/song_detail 并发,几 × 1MB 是可砍的瞬态)。
|
||||
* 仅 playlist detail / track_all(全量 trackIds,最多 NCM_TRACKID_MAX 条)保留传统 1MB。 */
|
||||
#define RESP_SMALL (64 * 1024) /* song_url:url+字段,实际 ~1KB */
|
||||
#define RESP_MED (256 * 1024) /* lyric/song_detail/playlist/cloudsearch:实际 ≤100KB */
|
||||
|
||||
const char *ncm_api_level_str(int level) {
|
||||
switch (level) {
|
||||
case NCM_LEVEL_SURROUND: /* "sky" + immerseType=c51(song_url 内处理) */
|
||||
@@ -168,7 +174,7 @@ int ncm_api_user_playlist(const char *device_id, const char *cookie, const char
|
||||
|
||||
api_json_t c; memset(&c, 0, sizeof c);
|
||||
int rc = api_post(device_id, cookie, "/api/user/playlist", json, NCM_CRYPT_WEAPI,
|
||||
NCM_RESP_MAX, &c);
|
||||
RESP_MED, &c);
|
||||
if (rc != NCM_OK) return rc;
|
||||
|
||||
int arr = ncm_json_path(&c.j, "playlist");
|
||||
@@ -248,7 +254,7 @@ int ncm_api_song_detail(const char *device_id, const char *cookie, const int64_t
|
||||
|
||||
api_json_t c; memset(&c, 0, sizeof c);
|
||||
int rc = api_post(device_id, cookie, "/api/v3/song/detail", json, NCM_CRYPT_EAPI,
|
||||
2 * 1024 * 1024, &c);
|
||||
RESP_MED, &c);
|
||||
if (rc != NCM_OK) return rc;
|
||||
|
||||
int arr = ncm_json_path(&c.j, "songs");
|
||||
@@ -271,7 +277,7 @@ int ncm_api_song_url(const char *device_id, const char *cookie, const char *id,
|
||||
|
||||
api_json_t c; memset(&c, 0, sizeof c);
|
||||
int rc = api_post(device_id, cookie, "/api/song/enhance/player/url/v1", json, NCM_CRYPT_EAPI,
|
||||
NCM_RESP_MAX, &c);
|
||||
RESP_SMALL, &c);
|
||||
if (rc != NCM_OK) return rc;
|
||||
|
||||
int d0 = ncm_json_path(&c.j, "data[0]");
|
||||
@@ -497,7 +503,7 @@ int ncm_api_lyric(const char *device_id, const char *cookie, const char *id,
|
||||
|
||||
api_json_t c; memset(&c, 0, sizeof c);
|
||||
int rc = api_post(device_id, cookie, "/api/song/lyric", json, NCM_CRYPT_EAPI,
|
||||
NCM_RESP_MAX, &c);
|
||||
RESP_MED, &c);
|
||||
if (rc != NCM_OK) return rc;
|
||||
if (lrc && lrc_sz) { lrc[0] = 0; ncm_json_path_str(&c.j, "lrc.lyric", lrc, lrc_sz); }
|
||||
if (tlrc && tlrc_sz) { tlrc[0] = 0; ncm_json_path_str(&c.j, "tlyric.lyric", tlrc, tlrc_sz); }
|
||||
@@ -521,7 +527,7 @@ static int cloudsearch_post(const char *device_id, const char *cookie, const cha
|
||||
snprintf(json, sizeof json, "{\"s\":\"%s\",\"type\":%d,\"offset\":%d,\"limit\":%d,\"total\":true}",
|
||||
esc, type, offset > 0 ? offset : 0, limit > 0 ? limit : 30);
|
||||
return api_post(device_id, cookie, "/api/cloudsearch/pc", json, NCM_CRYPT_WEAPI,
|
||||
NCM_RESP_MAX, c);
|
||||
RESP_MED, c);
|
||||
}
|
||||
|
||||
/* 解析一处「歌单对象数组」(cloudsearch type=1000)。id 可为数字/字符串(均文本拷贝)。 */
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -26,6 +26,9 @@ int meow_img_w(meow_img *img);
|
||||
int meow_img_h(meow_img *img);
|
||||
|
||||
void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */
|
||||
/* 内存瘦身:最近邻等比缩放到最长边 ≤maxdim,返回新图;原图已 ≤maxdim 返回 NULL。
|
||||
* 调用方负责 free 旧图与返回值(若返回)。封面/背景大图驻留内存瘦身用。 */
|
||||
meow_img *meow_img_downscale(meow_img *img, int maxdim);
|
||||
void meow_ui_img_scaled(int x, int y, int w, int h, meow_img *img); /* 最近邻缩放 */
|
||||
void meow_ui_img_alpha(int x, int y, int w, int h, meow_img *img, int alpha);
|
||||
void meow_ui_img_cover(int x, int y, int size, meow_img *img); /* 居中裁方 → size×size */
|
||||
|
||||
@@ -96,6 +96,30 @@ static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover)
|
||||
return out;
|
||||
}
|
||||
|
||||
/* 内存瘦身:把大图按最近邻等比缩放到「最长边 ≤ maxdim」,返回新图;调用方负责
|
||||
* meow_img_free 旧图 + 新图。原图已 ≤maxdim → 返回 NULL(无需缩放)。
|
||||
* 典型:网易云全尺寸封面 1448×1448 RGBA=8MB,显示只需 ≤480px(屏幕 480 宽)→
|
||||
* 480×480=0.9MB,省 ~7MB anon(设备 RAM 仅 56MB,OOM 真机被杀)。
|
||||
* 复用 scale_buf(cover=0,最近邻锁比例),不做插值(App 封面/背景无需高质量缩放)。 */
|
||||
meow_img *meow_img_downscale(meow_img *im, int maxdim)
|
||||
{
|
||||
if (!im || maxdim <= 0) return NULL;
|
||||
if (im->w <= maxdim && im->h <= maxdim) return NULL; /* 已够小,不缩放 */
|
||||
int dw, dh;
|
||||
if (im->w >= im->h) {
|
||||
dw = maxdim;
|
||||
dh = (int)((long)im->h * maxdim / im->w);
|
||||
} else {
|
||||
dh = maxdim;
|
||||
dw = (int)((long)im->w * maxdim / im->h);
|
||||
}
|
||||
if (dw < 1) dw = 1;
|
||||
if (dh < 1) dh = 1;
|
||||
unsigned char *tmp = scale_buf(im, dw, dh, 0);
|
||||
if (!tmp) return NULL;
|
||||
return img_alloc(dw, dh, tmp);
|
||||
}
|
||||
|
||||
void meow_ui_img(int x, int y, meow_img *im)
|
||||
{
|
||||
if (!im) return;
|
||||
|
||||
Reference in new issue
Block a user