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

+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;
}
}