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:
AskaEth committed 2026-09-02 03:43:42 +08:00
1 parent 202caaf66b
commit 68ce46a5d8
19 files changed
+368 -19

No files matched your search

+82
View File
@@ -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 的
前台三场景采样复核。
+1 -1
View File
@@ -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);
}
+16 -1
View File
@@ -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;
+16
View File
@@ -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,
+1 -1
View File
@@ -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;
+3 -3
View File
@@ -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;
}
+1 -1
View File
@@ -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();
+1 -1
View File
@@ -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;
}
+2 -2
View File
@@ -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);
+4 -4
View File
@@ -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;
}
}
+77
View File
@@ -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;
}
+47
View File
@@ -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)"
+79
View File
@@ -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 ====="
+11 -5
View File
@@ -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.