Tuku reader: 真实解码峰值模型 + 3MB 硬阈值 + 解码取消 + 3 槽(M22 止损版)
真机(R3 Pro II 480×720 / MemTotal 56872kB):空闲 RSS 6044kB → 翻页 11400kB → 峰值 15600kB(MemFree 仅 1404kB)→ SIGKILL。M20 审计坐实根因是「单页解码瞬时峰值」, 且旧模型把 stb 当缩解库,对 JPEG 低估 3.5–8 倍(实测 baseline 2000×3000 +10.1MB、 渐进 +23.5MB、1200×1600 PNG +12.9MB)。本次按实测重定预算: - 真实峰值模型 decode_peak_bytes():JPEG 分量面(+渐进 short 系数面)、PNG 源缓冲 + RGBA 转换副本、BMP 直写目标,再叠精修/落框两段一次性重采样;上限常态 3MB (MR_DECODE_PEAK_CAP),只有 MemFree 真富余时才放宽到 min(12MB, MemFree-4MB)。 - 超预算时的降级:JPEG 逐步加粗 stbi 缩解档位(meow_img_load_file_step), PNG/BMP 无解码期缩放 → 占位;结果精确重采样回显示框(meow_img_resample), 命中页仍是单次 blit(M17 热路径纪律不破)。 - 占位可重试(g_page_skip 与永久失败 g_page_fail 分离):焦点页冷却 1.5s 自动重试、 中间区点按立即重试、离开再回来即重试;坏图仍是永久占位。 - 解码前内存门控:MemAvailable < 6MB 不解码(读不到就放行,不猜)。 - 解码请求取消:worker 出队后、mr_book_open 前二次判 gen/窗口;拖动/fling 期间只解落点页, 预取邻居仅当 MemAvailable ≥ 12MB。 - 先释放再解码:queue_page 按落地字节 slots_reserve() 先淘汰不可见旧图;可见页不淘汰。 - 槽位 4→3、常驻上限 9MB→3MB、结果队列 4→2。 - 可观测性:`reader decode page=N src=WxH peak=XkB memfree=YkB memavail=ZkB ok|skip=… step=S ms=M` 与进出阅读器 rss/hwm/memfree/memavail。 page_source: 头部探测新增 codec/progressive/channels/planes(mr_probe_info), 只带最小 SOF 的合成头仍能取尺寸(旧契约不退化)。 宿主证据:新增 test_reader_membudget(--wrap malloc 计真实峰值): PNG 600×960 model 4001kB vs 实测 3943kB;JPEG 1000×1500 step2 model 3749/实测 3711; 连翻 100 页 peak_transient 0kB(先释放再解码让缓冲复用)、驻留 ≤3MB、解码 100 次(取消生效)、 3MB 阈值下 1.5MP JPEG 可解、4MP JPEG 需 MemFree 放行(否则占位)、渐进式模型更高于 baseline。 run_tests.sh 全绿(含 zip 106 / cbz 325)、run_webtoon_tests.sh 全绿、SDK 与 tuku 交叉构建零警告。
This commit is contained in:
1 parent
773ab9ff92
commit
40751c09bd
10 files changed
+1257
-166
No files matched your search
+121
-27
@@ -192,7 +192,7 @@ static unsigned probe_be32(const unsigned char *p)
|
||||
return ((unsigned)p[0] << 24) | ((unsigned)p[1] << 16) | ((unsigned)p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
static int probe_png(probe_src *s, int *w, int *h)
|
||||
static int probe_png(probe_src *s, mr_img_info *in)
|
||||
{
|
||||
unsigned char hdr[24];
|
||||
if (!ps_read(s, hdr, sizeof hdr)) return 0;
|
||||
@@ -200,12 +200,33 @@ static int probe_png(probe_src *s, int *w, int *h)
|
||||
if (memcmp(hdr, sig, 8) != 0 || memcmp(hdr + 12, "IHDR", 4) != 0) return 0;
|
||||
unsigned ww = probe_be32(hdr + 16), hh = probe_be32(hdr + 20);
|
||||
if (ww == 0 || hh == 0) return 0;
|
||||
*w = (int)ww;
|
||||
*h = (int)hh;
|
||||
/* IHDR 的位深/颜色类型(紧跟在宽高后)。截断头(stb 也读不下去)按 RGB 8bit 记。 */
|
||||
int depth = 8, ctype = 2;
|
||||
unsigned char ex[2];
|
||||
if (ps_read(s, ex, sizeof ex)) { depth = ex[0]; ctype = ex[1]; }
|
||||
int ch;
|
||||
switch (ctype) {
|
||||
case 0: ch = 1; break; /* 灰度 */
|
||||
case 2: ch = 3; break; /* RGB */
|
||||
case 3: ch = 3; break; /* 调色板:stb 展开成 RGB(A) */
|
||||
case 4: ch = 2; break; /* 灰度+alpha */
|
||||
case 6: ch = 4; break; /* RGBA */
|
||||
default: ch = 3; break;
|
||||
}
|
||||
if (ww > 0x7FFFFFu || hh > 0x7FFFFFu) return 0; /* 防 int 溢出 */
|
||||
int bpc = (depth == 16) ? 2 : 1;
|
||||
in->w = (int)ww;
|
||||
in->h = (int)hh;
|
||||
in->codec = MR_IMG_CODEC_PNG;
|
||||
in->channels = ch;
|
||||
in->progressive = 0;
|
||||
/* ch==4:stb 直接解到目标 RGBA 缓冲(就是最终输出),中间面记 0。
|
||||
* 其余:stb 先解出 ch 通道源缓冲,再 convert_format 复制一张 RGBA。 */
|
||||
in->planes = (ch == 4) ? 0 : (long long)ww * hh * ch * bpc;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int probe_jpeg(probe_src *s, int *w, int *h)
|
||||
static int probe_jpeg(probe_src *s, mr_img_info *in)
|
||||
{
|
||||
unsigned char b[2];
|
||||
if (!ps_read(s, b, 2) || b[0] != 0xFF || b[1] != 0xD8) return 0;
|
||||
@@ -224,20 +245,61 @@ static int probe_jpeg(probe_src *s, int *w, int *h)
|
||||
if (len < 2) return 0;
|
||||
int is_sof = (m >= 0xC0 && m <= 0xCF) && m != 0xC4 && m != 0xC8 && m != 0xCC;
|
||||
if (is_sof) {
|
||||
/* 5 字节就够拿宽高(精度 + 高 + 宽);分量数与采样因子能读就读。
|
||||
* 只带最小 SOF 的合成头(宿主测试夹具)也必须能取到尺寸——尺寸探测是
|
||||
* M4 起的硬契约,不能因为预算模型要更多信息就退化。 */
|
||||
unsigned char so[5];
|
||||
if (len < 7 || !ps_read(s, so, 5)) return 0;
|
||||
if (len < 7 || !ps_read(s, so, sizeof so)) return 0;
|
||||
int hh = ((int)so[1] << 8) | so[2];
|
||||
int ww = ((int)so[3] << 8) | so[4];
|
||||
if (ww <= 0 || hh <= 0) return 0;
|
||||
*w = ww;
|
||||
*h = hh;
|
||||
int prog = (m == 0xC2 || m == 0xC6 || m == 0xCA || m == 0xCE);
|
||||
in->w = ww;
|
||||
in->h = hh;
|
||||
in->codec = MR_IMG_CODEC_JPEG;
|
||||
in->progressive = prog;
|
||||
in->channels = 3;
|
||||
/* 未知采样因子时按 4:4:4 估(3·W·H,偏保守):宁可多算峰值,不要低估。 */
|
||||
in->planes = (long long)ww * hh * 3;
|
||||
unsigned char nc = 0;
|
||||
if (ps_read(s, &nc, 1) && nc >= 1 && nc <= 4 && len >= 8 + 3 * nc) {
|
||||
int hv[4] = { 0, 0, 0, 0 };
|
||||
int hmax = 1, vmax = 1, ok = 1;
|
||||
for (int i = 0; i < nc && ok; i++) {
|
||||
unsigned char comp[3]; /* id / 采样因子 hv / 量化表 */
|
||||
if (!ps_read(s, comp, sizeof comp)) { ok = 0; break; }
|
||||
hv[i] = comp[1];
|
||||
int ch = hv[i] >> 4, cv = hv[i] & 15;
|
||||
if (ch < 1 || ch > 4 || cv < 1 || cv > 4) { ok = 0; break; }
|
||||
if (ch > hmax) hmax = ch;
|
||||
if (cv > vmax) vmax = cv;
|
||||
}
|
||||
for (int i = 0; ok && i < nc; i++) {
|
||||
if (hmax % (hv[i] >> 4) != 0 || vmax % (hv[i] & 15) != 0) ok = 0;
|
||||
}
|
||||
if (ok) {
|
||||
/* 分量面字节 = 与 stb 完全同式的 w2×h2(MCU 对齐 + 采样因子);
|
||||
* 渐进式每个分量还多一张 w2×h2×2 的 short 系数面(M20 实测 +23.5MB 的来源)。 */
|
||||
long long mcu_x = (ww + 8 * hmax - 1) / (8 * hmax);
|
||||
long long mcu_y = (hh + 8 * vmax - 1) / (8 * vmax);
|
||||
long long planes = 0;
|
||||
for (int i = 0; i < nc; i++) {
|
||||
long long w2 = mcu_x * (hv[i] >> 4) * 8;
|
||||
long long h2 = mcu_y * (hv[i] & 15) * 8;
|
||||
planes += w2 * h2;
|
||||
if (prog) planes += w2 * h2 * 2;
|
||||
}
|
||||
in->channels = nc;
|
||||
in->planes = planes;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
if (!ps_skip(s, len - 2)) return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static int probe_bmp(probe_src *s, int *w, int *h)
|
||||
static int probe_bmp(probe_src *s, mr_img_info *in)
|
||||
{
|
||||
unsigned char hdr[26];
|
||||
if (!ps_read(s, hdr, sizeof hdr)) return 0;
|
||||
@@ -248,49 +310,81 @@ static int probe_bmp(probe_src *s, int *w, int *h)
|
||||
((unsigned)hdr[24] << 16) | ((unsigned)hdr[25] << 24));
|
||||
if (hh < 0) hh = -hh; /* 自底向上 BMP */
|
||||
if (ww <= 0 || hh <= 0) return 0;
|
||||
*w = ww;
|
||||
*h = hh;
|
||||
unsigned char ex[4]; /* biPlanes(2) / biBitCount(2) */
|
||||
int bpp = 24;
|
||||
if (ps_read(s, ex, sizeof ex)) bpp = (int)((unsigned)ex[2] | ((unsigned)ex[3] << 8));
|
||||
in->w = ww;
|
||||
in->h = hh;
|
||||
in->codec = MR_IMG_CODEC_BMP;
|
||||
in->channels = bpp >= 8 ? bpp / 8 : 4; /* stb 一律解到 RGBA 目标 */
|
||||
in->progressive = 0;
|
||||
in->planes = 0; /* stb 直接写 RGBA 目标,不另开源缓冲 */
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int probe_any(probe_src *s, int *w, int *h)
|
||||
static int probe_any(probe_src *s, mr_img_info *in)
|
||||
{
|
||||
memset(in, 0, sizeof *in);
|
||||
for (int pass = 0; pass < 3; pass++) {
|
||||
s->pos = 0;
|
||||
if (s->f) rewind(s->f);
|
||||
int ok = (pass == 0) ? probe_png(s, w, h)
|
||||
: (pass == 1) ? probe_jpeg(s, w, h)
|
||||
: probe_bmp(s, w, h);
|
||||
int ok = (pass == 0) ? probe_png(s, in)
|
||||
: (pass == 1) ? probe_jpeg(s, in)
|
||||
: probe_bmp(s, in);
|
||||
if (ok) return 1;
|
||||
memset(in, 0, sizeof *in);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mr_probe_size_mem(const void *buf, size_t len, int *w, int *h)
|
||||
int mr_probe_info_mem(const void *buf, size_t len, mr_img_info *info)
|
||||
{
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
if (!buf || len < 8 || !w || !h) return 0;
|
||||
if (info) memset(info, 0, sizeof *info);
|
||||
if (!buf || len < 8 || !info) return 0;
|
||||
probe_src s = { (const unsigned char *)buf, len, 0, NULL };
|
||||
if (!probe_any(&s, w, h) || *w <= 0 || *h <= 0) {
|
||||
*w = *h = 0;
|
||||
if (!probe_any(&s, info) || info->w <= 0 || info->h <= 0) {
|
||||
memset(info, 0, sizeof *info);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mr_probe_size(const char *path, int *w, int *h)
|
||||
int mr_probe_info(const char *path, mr_img_info *info)
|
||||
{
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
if (!path || !path[0] || !w || !h) return 0;
|
||||
if (info) memset(info, 0, sizeof *info);
|
||||
if (!path || !path[0] || !info) return 0;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
probe_src s = { NULL, 0, 0, f };
|
||||
int ok = probe_any(&s, w, h);
|
||||
int ok = probe_any(&s, info);
|
||||
fclose(f);
|
||||
if (ok && (*w <= 0 || *h <= 0)) { *w = *h = 0; return 0; }
|
||||
return ok;
|
||||
if (!ok || info->w <= 0 || info->h <= 0) {
|
||||
memset(info, 0, sizeof *info);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mr_probe_size_mem(const void *buf, size_t len, int *w, int *h)
|
||||
{
|
||||
mr_img_info in;
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
if (!mr_probe_info_mem(buf, len, &in)) return 0;
|
||||
if (w) *w = in.w;
|
||||
if (h) *h = in.h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int mr_probe_size(const char *path, int *w, int *h)
|
||||
{
|
||||
mr_img_info in;
|
||||
if (w) *w = 0;
|
||||
if (h) *h = 0;
|
||||
if (!mr_probe_info(path, &in)) return 0;
|
||||
if (w) *w = in.w;
|
||||
if (h) *h = in.h;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
|
||||
@@ -88,6 +88,30 @@ int mr_probe_size(const char *path, int *w, int *h);
|
||||
/* 同上,但从内存缓冲探测(档案页解压后不需要落盘再读一遍)。 */
|
||||
int mr_probe_size_mem(const void *buf, size_t len, int *w, int *h);
|
||||
|
||||
/* ---- 追加(M22):解码内存预算需要的头部信息(仍然零像素内存) ----
|
||||
* 解析器按**内容**嗅探(PNG → JPEG → BMP),不信任扩展名:一个 .jpg 后缀的 PNG 也能
|
||||
* 得到正确结论。planes 是 stb 解码时**除最终 RGBA 输出缓冲以外**的中间面字节数:
|
||||
* - JPEG:各分量面 w2*h2(按 MCU 对齐 + 采样因子,与 stb 同式),渐进式再加 short 系数面;
|
||||
* - PNG :源像素缓冲 ch*W*H(16bit 再 ×2,ch==4 时就是输出缓冲本身,故记 0);
|
||||
* - BMP :0(stb 对 24/32bpp 直接写 RGBA 目标,不另开源缓冲)。
|
||||
* 调用方(阅读器)再叠加「抽取输出 + 精修/落框临时图」得到真实峰值。
|
||||
* 1=已知(info 已填满),0=未知/损坏(info 清零且 codec=MR_IMG_CODEC_UNKNOWN)。 */
|
||||
#define MR_IMG_CODEC_UNKNOWN 0
|
||||
#define MR_IMG_CODEC_JPEG 1
|
||||
#define MR_IMG_CODEC_PNG 2
|
||||
#define MR_IMG_CODEC_BMP 3
|
||||
|
||||
typedef struct mr_img_info {
|
||||
int w, h;
|
||||
int codec; /* MR_IMG_CODEC_* */
|
||||
int progressive; /* 1=渐进式 JPEG(SOF2/SOF6/SOF10/SOF14) */
|
||||
int channels; /* 源通道数(JPEG 分量数 / PNG 颜色类型 / BMP 位深/8) */
|
||||
long long planes; /* 见上:stb 中间面字节(不含最终 RGBA 输出) */
|
||||
} mr_img_info;
|
||||
|
||||
int mr_probe_info(const char *path, mr_img_info *info);
|
||||
int mr_probe_info_mem(const void *buf, size_t len, mr_img_info *info);
|
||||
|
||||
/* ============================================================ */
|
||||
/* 档案册(M11):.cbz / .zip 当一本书 */
|
||||
/* ------------------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user