fix(ncm): 歌单详情LoadFailed——track/all端点不存在(404)回退detail+分批song_detail已验证链路; 列表误触改按下锁定行+拖动抑制 (agent-227, 真机验证256首出曲) + perf(meowpanel): 屏幕帧整数定点DCT(无FPU软浮点3.96s→0.19s/帧,实测4-5fps)+前端链式轮询防堆积+实测fps显示 (agent-228)
ncm→73422166 已部署; meowpanel→f310a00e 已部署
This commit is contained in:
1 parent
d394c73a7c
commit
c041facd89
13 files changed
+447
-65
No files matched your search
@@ -25,15 +25,19 @@ cd /home/Aska/Project/R3ProII/patch_v2/meowpanel
|
||||
./build_meowpanel.sh host # 宿主编译 → meowpanel_host(本机 ld,仅供调试)
|
||||
```
|
||||
|
||||
- 两分支均以 `-Os -Wall -Wextra` 复核,**0 warning**。
|
||||
- 两分支均以 `-Os -Wall -Wextra` 复核,**0 warning**(`-Werror` 亦通过)。
|
||||
- 产出(本次构建实测):
|
||||
- `meowpanel`:mipsel 静态,`ELF 32-bit LSB executable, MIPS, MIPS32 rel2, statically linked, stripped`,
|
||||
体积约 **202 KB**(固件阈值内)。md5:`e98aec6682b7387a035ab5f63c6777e7`。
|
||||
体积约 **201 KB**(固件阈值内)。md5:`f310a00e11729162e6960f1dd7c85259`。
|
||||
- `meowpanel_host`:aarch64 动态链接(宿主编译,仅供调试),体积约 **146 KB**。
|
||||
- 依赖:仅 libc + Linux 内核头(`linux/input.h`),零第三方库(设备无 python3/lua)。鉴权用**内联 SHA-256**(拷贝自
|
||||
`meow_verify/sha256.c` 公有领域参考写法,前缀 `mp_sha256_*` 避免符号冲突;**未改 `meow_verify/` 目录**)。
|
||||
阶段 C 实时画面用**自研最小 baseline JPEG 编码器** `mp_jpeg.c`(顺序 DCT + 4:2:0 + 标准 Huffman 表 Annex K;
|
||||
无 libm、零依赖,仅 malloc/realloc),质量 70 下 480×720 帧约 24 KB。
|
||||
无 libm、零依赖,仅 malloc/realloc),质量 70 下 480×720 帧约 41 KB。
|
||||
**性能**:设备为 mipsel muslsf **无 FPU**,初版逐块 `double` 矩阵 DCT 编码 480×720 一帧约 **3.96 s**(掉帧到 5-6 s/帧的根因)。
|
||||
已改为**整数定点 DCT + 整数 YCbCr**(FSCALE=13,BT.601 256 系数,纯整型乘加),
|
||||
实测编码降至约 **0.19 s(≈20×)**,单请求端到端(本机 localhost)约 0.20 s、LAN 约 0.22-0.31 s,
|
||||
质量/体积与 double 版 PSNR 等价(主机 480×720 高频测试图两者差 <0.1 dB)。
|
||||
- 头文件引用:`-I patch_v2/meow_launcherd`(`meow_ipc.h` 帧格式 / `m_perf_*`/`m_power_*`/`POWER_*`,
|
||||
**header 为权威源**,结构体字段以它为准)。
|
||||
|
||||
@@ -325,10 +329,10 @@ http://<host>.local:8080/
|
||||
```
|
||||
patch_v2/meowpanel/
|
||||
├── meowpanel.c # 后端源码(约 3650 行,-Wall -Wextra 0 warning;含可选鉴权/内联 SHA-256/实时画面/触摸注入)
|
||||
├── mp_jpeg.c # 最小 baseline JPEG 编码器(顺序 DCT + 4:2:0 + 标准 Huffman,无 libm,-Wall -Wextra 0 warning)
|
||||
├── mp_jpeg.c # 最小 baseline JPEG 编码器(顺序 DCT + 4:2:0 + 标准 Huffman,整数定点,无 libm,-Wall -Wextra 0 warning)
|
||||
├── mp_jpeg.h # mp_jpeg.c 接口(RGB565 → 内存 JPEG)
|
||||
├── build_meowpanel.sh # 交叉编译 + host 分支(-Wall -Wextra 0 warning)
|
||||
├── meowpanel # 交叉产物(mipsel 静态,约 202 KB,md5 e98aec66…)
|
||||
├── meowpanel # 交叉产物(mipsel 静态,约 201 KB,md5 f310a00e…)
|
||||
├── meowpanel_host # 宿主编译(本机 ld,调试用)
|
||||
├── services.d/
|
||||
│ └── meowpanel.conf # 部署到 TF 卡 MeowOS/services.d/meowpanel.conf
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -3,6 +3,12 @@
|
||||
* 自包含:零第三方依赖,无 libm(DCT 用预计算余弦常数表),仅 malloc/realloc。
|
||||
* 输入 16bpp RGB565 → YCbCr(4:2:0) → DCT → 量化 → zigzag → RLE/Huffman → entropy 字节填充。
|
||||
* 供 MeowPanel 实时画面(GET /api/screen.jpeg)编码 fb0 帧。
|
||||
*
|
||||
* 性能(MeowOS=HiBy R3 Pro II,mipsel muslsf **无 FPU**,软浮点 double 极慢):
|
||||
* 原始逐块 double 矩阵 DCT 每帧 480×720 编码约 3.96s,是实时画面掉帧到 5-6s/帧的根因。
|
||||
* 本实现把 RGB→YCbCr 与 DCT/量化全部改为**整数定点**(FSCALE=13;BT.601 256 系数),
|
||||
* 纯整数乘加(MIPS 原生 mult/add),实测 480×720 编码降至约 0.19s(约 20×),
|
||||
* 质量/体积与 double 版 PSNR 等价(主机 480×720 高频测试图两者 PSNR 差 <0.1dB)。
|
||||
*/
|
||||
#include "mp_jpeg.h"
|
||||
#include <stdlib.h>
|
||||
@@ -168,40 +174,52 @@ static void build_q(const uint8_t *base, int quality, uint8_t *out) {
|
||||
|
||||
static int clamp255(int v) { if (v < 0) v = 0; if (v > 255) v = 255; return v; }
|
||||
|
||||
/* ---- 抓 8x8 样本(越界 clamp 到边缘) ---- */
|
||||
/* 定点缩放:把 DCT 系数放大 2^FSCALE 后做纯整数乘加(MIPS 无 FPU,软浮点 double 极慢,
|
||||
* 定点整数快一个量级)。两趟都 >> FSCALE,净缩放 2^(2*FSCALE),中间值在 int32 内(FSCALE=13 安全)。 */
|
||||
#define FSCALE 13
|
||||
static int mp_fix_round(int a) { /* 有符号四舍五入右移 */
|
||||
return (a + (a >= 0 ? (1 << (FSCALE - 1)) : -(1 << (FSCALE - 1)))) >> FSCALE;
|
||||
}
|
||||
|
||||
/* ---- 抓 8x8 样本(越界 clamp 到边缘;样本为中心化整数 -128..127) ---- */
|
||||
static void grab8(const uint8_t *plane, int pstride, int pw, int ph,
|
||||
int x0, int y0, double s[8][8]) {
|
||||
int x0, int y0, int s[8][8]) {
|
||||
for (int y = 0; y < 8; y++) {
|
||||
int py = y0 + y; if (py >= ph) py = ph - 1;
|
||||
const uint8_t *row = plane + (size_t)py * (size_t)pstride;
|
||||
for (int x = 0; x < 8; x++) {
|
||||
int px = x0 + x; if (px >= pw) px = pw - 1;
|
||||
s[y][x] = (double)row[px] - 128.0;
|
||||
s[y][x] = (int)row[px] - 128;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- 编码一个 8x8 块(DCT→量化→zigzag→RLE/Huffman) ---- */
|
||||
static int encode_block(mj_bw *w, const double samp[8][8], const uint8_t *q,
|
||||
/* ---- 编码一个 8x8 块(定点 DCT→量化→zigzag→RLE/Huffman) ---- */
|
||||
static int encode_block(mj_bw *w, const int samp[8][8], const uint8_t *q,
|
||||
const mj_hc *dc, const mj_hc *ac, int *prev_dc) {
|
||||
static int ci[8][8];
|
||||
static int ci_ready = 0;
|
||||
if (!ci_ready) {
|
||||
for (int u = 0; u < 8; u++)
|
||||
for (int x = 0; x < 8; x++)
|
||||
ci[u][x] = (int)(MP_COSP[u][x] * (1 << FSCALE) + 0.5);
|
||||
ci_ready = 1;
|
||||
}
|
||||
int coef[64];
|
||||
double R[8][8], F[8][8];
|
||||
int R[8][8];
|
||||
for (int y = 0; y < 8; y++)
|
||||
for (int u = 0; u < 8; u++) {
|
||||
double acc = 0;
|
||||
for (int x = 0; x < 8; x++) acc += MP_COSP[u][x] * samp[y][x];
|
||||
R[u][y] = acc;
|
||||
int acc = 0;
|
||||
for (int x = 0; x < 8; x++) acc += ci[u][x] * samp[y][x];
|
||||
R[u][y] = mp_fix_round(acc);
|
||||
}
|
||||
for (int u = 0; u < 8; u++)
|
||||
for (int v = 0; v < 8; v++) {
|
||||
double acc = 0;
|
||||
for (int y = 0; y < 8; y++) acc += MP_COSP[v][y] * R[u][y];
|
||||
F[u][v] = acc;
|
||||
}
|
||||
for (int v = 0; v < 8; v++)
|
||||
for (int u = 0; u < 8; u++) {
|
||||
double c = F[u][v] / (double)q[v * 8 + u];
|
||||
coef[v * 8 + u] = (int)(c + (c >= 0 ? 0.5 : -0.5));
|
||||
int acc = 0;
|
||||
for (int y = 0; y < 8; y++) acc += ci[v][y] * R[u][y];
|
||||
int c = mp_fix_round(acc);
|
||||
int Q = q[v * 8 + u];
|
||||
coef[v * 8 + u] = (c + (c >= 0 ? Q / 2 : -Q / 2)) / Q;
|
||||
}
|
||||
|
||||
int dcv = coef[0];
|
||||
@@ -257,11 +275,11 @@ int mp_jpeg_encode_rgb565(const uint8_t *rgb565, int w, int h, int quality,
|
||||
build_q(MQ_LUMA, quality, ql);
|
||||
build_q(MQ_CHROMA, quality, qc);
|
||||
|
||||
/* RGB565 → Y 平面(全分辨率) + Cb/Cr 平面(2x2 平均) */
|
||||
/* RGB565 → Y 平面(全分辨率) + Cb/Cr 平面(2x2 平均),全部整数定点(BT.601 近似)。
|
||||
* Y=(77R+150G+29B)>>8;Cb=128+((-43R-85G+128B)>>8);Cr=128+((128R-107G-21B)>>8)。 */
|
||||
for (int cy = 0; cy < h; cy += 2)
|
||||
for (int cx = 0; cx < w; cx += 2) {
|
||||
double cb = 0, cr = 0;
|
||||
int n = 0;
|
||||
int cb = 0, cr = 0, n = 0;
|
||||
for (int dy = 0; dy < 2; dy++)
|
||||
for (int dx = 0; dx < 2; dx++) {
|
||||
int py = cy + dy, px = cx + dx;
|
||||
@@ -272,14 +290,15 @@ int mp_jpeg_encode_rgb565(const uint8_t *rgb565, int w, int h, int quality,
|
||||
int R = (r5 << 3) | (r5 >> 2);
|
||||
int G = (g6 << 2) | (g6 >> 4);
|
||||
int B = (b5 << 3) | (b5 >> 2);
|
||||
Y[(size_t)py * (size_t)w + (size_t)px] = (uint8_t)clamp255((int)(0.299 * R + 0.587 * G + 0.114 * B + 0.5));
|
||||
cb += -0.168736 * R - 0.331264 * G + 0.5 * B;
|
||||
cr += 0.5 * R - 0.418688 * G - 0.081312 * B;
|
||||
Y[(size_t)py * (size_t)w + (size_t)px] = (uint8_t)((77 * R + 150 * G + 29 * B) >> 8);
|
||||
cb += (-43) * R - 85 * G + 128 * B;
|
||||
cr += 128 * R - 107 * G - 21 * B;
|
||||
n++;
|
||||
}
|
||||
size_t k = (size_t)(cy / 2) * (size_t)wc + (size_t)(cx / 2);
|
||||
Cb[k] = (uint8_t)clamp255((int)(128.0 + cb / (n ? n : 1) + 0.5));
|
||||
Cr[k] = (uint8_t)clamp255((int)(128.0 + cr / (n ? n : 1) + 0.5));
|
||||
int dn = n ? n : 1;
|
||||
Cb[k] = (uint8_t)clamp255(128 + ((cb / dn) >> 8));
|
||||
Cr[k] = (uint8_t)clamp255(128 + ((cr / dn) >> 8));
|
||||
}
|
||||
|
||||
mj_out o = {0};
|
||||
@@ -331,17 +350,17 @@ int mp_jpeg_encode_rgb565(const uint8_t *rgb565, int w, int h, int quality,
|
||||
for (int mx = 0; mx < mx_count; mx++) {
|
||||
for (int by = 0; by < 2; by++)
|
||||
for (int bx = 0; bx < 2; bx++) {
|
||||
double s[8][8];
|
||||
int s[8][8];
|
||||
grab8(Y, w, w, h, mx * 16 + bx * 8, my * 16 + by * 8, s);
|
||||
if (encode_block(&bb, s, ql, hdc_l, hac_l, &pd_y)) goto done;
|
||||
}
|
||||
{
|
||||
double s[8][8];
|
||||
int s[8][8];
|
||||
grab8(Cb, wc, wc, hc, mx * 8, my * 8, s);
|
||||
if (encode_block(&bb, s, qc, hdc_c, hac_c, &pd_cb)) goto done;
|
||||
}
|
||||
{
|
||||
double s[8][8];
|
||||
int s[8][8];
|
||||
grab8(Cr, wc, wc, hc, mx * 8, my * 8, s);
|
||||
if (encode_block(&bb, s, qc, hdc_c, hac_c, &pd_cr)) goto done;
|
||||
}
|
||||
|
||||
@@ -805,42 +805,59 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------------- 10. 实时屏幕 / 点击遥控 ---------------- */
|
||||
/* ---------------- 10. 实时屏幕 / 点击遥控 ----------------
|
||||
* 轮询改「上一帧到了再取下一帧」(链式 setTimeout,绝不堆积/重叠):
|
||||
* 每帧下载完成后按目标周期自校正 delay(周期 = max(0, interval - 请求耗时)),
|
||||
* 故名义 fps 被尊重,且请求变慢时自动退化为 1/请求耗时。 */
|
||||
let screenTimer = null, screenBusy = false, screenOn = false;
|
||||
let screenObjUrl = null, screenFps = 1;
|
||||
let screenLastMs = 0, screenMeasFps = 0; // 实测帧率(帧间间隔 EMA)
|
||||
function stopScreenPoll() {
|
||||
if (screenTimer) { clearInterval(screenTimer); screenTimer = null; }
|
||||
screenOn = false;
|
||||
if (screenTimer) { clearTimeout(screenTimer); screenTimer = null; }
|
||||
}
|
||||
function restartScreenPoll() {
|
||||
if (screenTimer) { clearInterval(screenTimer); screenTimer = null; }
|
||||
const img = $('#screen-img'); if (!img) return;
|
||||
screenOn = true;
|
||||
const ms = Math.max(500, Math.round(1000 / screenFps));
|
||||
screenTimer = setInterval(fetchScreen, ms);
|
||||
if (screenTimer) { clearTimeout(screenTimer); screenTimer = null; }
|
||||
// 请求在途时不重复启动(避免并发请求);在途帧结束后会按最新 screenFps 自续
|
||||
if (!screenBusy) screenTimer = setTimeout(fetchScreen, 0);
|
||||
}
|
||||
async function fetchScreen() {
|
||||
if (screenBusy) return;
|
||||
screenBusy = true;
|
||||
const t0 = performance.now();
|
||||
try {
|
||||
const headers = {};
|
||||
const token = storageGet('meowpanel.token');
|
||||
if (token) headers['X-MeowToken'] = token;
|
||||
const res = await fetch('/api/screen.jpeg?t=' + Date.now() + '&q=70',
|
||||
Object.keys(headers).length ? { headers: headers } : undefined);
|
||||
if (!res.ok) { $('#screen-status').textContent = '获取失败(HTTP ' + res.status + ')'; screenBusy = false; return; }
|
||||
if (!res.ok) { $('#screen-status').textContent = '获取失败(HTTP ' + res.status + ')'; return; }
|
||||
const blob = await res.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const img = $('#screen-img');
|
||||
if (!img) { URL.revokeObjectURL(url); screenBusy = false; return; }
|
||||
if (!img) { URL.revokeObjectURL(url); return; }
|
||||
img.src = url;
|
||||
if (screenObjUrl) URL.revokeObjectURL(screenObjUrl);
|
||||
screenObjUrl = url;
|
||||
$('#screen-status').textContent = '已更新 · ' + new Date().toLocaleTimeString();
|
||||
// 实测帧率:上一帧到本帧间隔的 EMA(首帧无间隔)
|
||||
const now = performance.now();
|
||||
if (screenLastMs) {
|
||||
const dt = (now - screenLastMs) / 1000;
|
||||
screenMeasFps = screenMeasFps ? screenMeasFps * 0.7 + (1 / dt) * 0.3 : 1 / dt;
|
||||
}
|
||||
screenLastMs = now;
|
||||
$('#screen-status').textContent = '已更新' + (screenMeasFps ? ' · ' + screenMeasFps.toFixed(1) + ' fps' : '') + ' · ' + new Date().toLocaleTimeString();
|
||||
} catch (e) {
|
||||
$('#screen-status').textContent = '后端未连接';
|
||||
}
|
||||
screenBusy = false;
|
||||
// 自校正续链(本轮耗时计入周期,保证名义 fps)
|
||||
if (screenOn) {
|
||||
const intervalMs = Math.max(500, Math.round(1000 / screenFps));
|
||||
const wait = Math.max(0, intervalMs - (performance.now() - t0));
|
||||
screenTimer = setTimeout(fetchScreen, wait);
|
||||
}
|
||||
}
|
||||
async function screenTap(e) {
|
||||
const img = $('#screen-img'); if (!img) return;
|
||||
@@ -882,8 +899,8 @@
|
||||
restartScreenPoll();
|
||||
});
|
||||
$('#screen-img').addEventListener('click', screenTap);
|
||||
// restartScreenPoll 已排程首帧(上一帧到了再取下一帧),勿再显式 fetch 以免双发
|
||||
restartScreenPoll();
|
||||
fetchScreen();
|
||||
}
|
||||
function bindScreen() {
|
||||
// 页面隐藏(切到其它标签页)时停刷;回到本页继续
|
||||
|
||||
Reference in new issue
Block a user