refactor(sdk/lyric)+misc: ①G5断言改成EXIT(播放页任意右滑回桌面,宿主全过) ②meow_img_pixels正式访问器上收(替代局部补全结构体,const化w/h,清3处警告) ③RECOVERY_PROMPT §五铁律补重链四App ④MEOW_APP_SDK.md §4.1取色/§5.9音频/§9后台字段/§16歌词组件;0warning;meow_ui.a变M6.34需重编+四App重链
This commit is contained in:
1 parent
45c633868f
commit
3c5612f7c9
6 files changed
+168
-33
No files matched your search
+133
-5
@@ -163,9 +163,12 @@ meow_img *meow_img_load_file(const char *path); /* 内部读文件 + stb 解
|
|||||||
meow_img *meow_img_load_mem(const void *buf, int len);
|
meow_img *meow_img_load_mem(const void *buf, int len);
|
||||||
void meow_img_free(meow_img *img);
|
void meow_img_free(meow_img *img);
|
||||||
|
|
||||||
/* 查询 */
|
/* 查询(const 正确) */
|
||||||
int meow_img_w(meow_img *img);
|
int meow_img_w(const meow_img *img);
|
||||||
int meow_img_h(meow_img *img);
|
int meow_img_h(const meow_img *img);
|
||||||
|
/* 访问器:取 RGBA 像素指针(4 通道 straight alpha;供取色/直读像素)。
|
||||||
|
* meow_img 对外 opaque,本函数为其唯一像素读入口;NULL 返回 NULL。 */
|
||||||
|
const unsigned char *meow_img_pixels(const meow_img *img);
|
||||||
|
|
||||||
/* 绘制:整图 / 缩放 / 带 alpha */
|
/* 绘制:整图 / 缩放 / 带 alpha */
|
||||||
void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */
|
void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */
|
||||||
@@ -177,6 +180,33 @@ void meow_ui_img_cover(int x, int y, int size, meow_img *img); /* 居
|
|||||||
```
|
```
|
||||||
|
|
||||||
> **裁剪/缩放**:软渲用最近邻(快),不做高质量插值(省 CPU)。封面用 `meow_ui_img_cover` 居中裁方 + 缩放到目标尺寸即可。
|
> **裁剪/缩放**:软渲用最近邻(快),不做高质量插值(省 CPU)。封面用 `meow_ui_img_cover` 居中裁方 + 缩放到目标尺寸即可。
|
||||||
|
>
|
||||||
|
> **内存瘦身**:封面/背景大图驻留会耗尽 anon(设备 RAM 仅 56MB);`meow_img_downscale(im, maxdim)` 最近邻等比缩放到最长边 ≤maxdim(原图已够小返回 NULL),调用方负责 free 旧/新图。
|
||||||
|
|
||||||
|
### 4.1 封面取色(`meow_img_color_*`,批5)
|
||||||
|
|
||||||
|
独立公共头 `meow_img_color.h`(仿 `meow_lyric_view.h` 模式,**不随 `meow_ui.h` 伞头发布**;App/launcher 显式 `#include "meow_img_color.h"` 并单链 `meow_img_color.c`——**不并入 `meow_ui.a`**,避免并发重编/四 App 重链)。从解码后的 RGBA 像素或 `meow_img` 句柄提取「主色 + 次色」,供播放页/歌词页动态主题(背景渐变/文字 accent)。
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* 从 RGBA 像素缓冲取主色+次色(纯算法;样本副采样)。返回 0=成功;-1=失败。 */
|
||||||
|
int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h,
|
||||||
|
uint32_t *main, uint32_t *second);
|
||||||
|
/* 从 meow_img 句柄取主色+次色(经 meow_img_pixels 读像素)。返回 0=成功;-1=失败。 */
|
||||||
|
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second);
|
||||||
|
/* 由主/次色派生播放页 + 歌词页封面主题(纯函数;th 提供默认回退色)。 */
|
||||||
|
typedef struct meow_img_color_theme {
|
||||||
|
uint32_t bg_top, bg_bottom; /* 播放页渐变顶/底(主/次各压暗 55%) */
|
||||||
|
uint32_t fg, fg_dim; /* 背景上可读文字色 + 次要文字色 */
|
||||||
|
uint32_t accent, accent_fg; /* 强调色(控件/进度)+ 其上可读图标色 */
|
||||||
|
meow_lyric_view_colors lyr; /* 歌词页 set_colors 覆盖(scrim=主色压暗 α176) */
|
||||||
|
} meow_img_color_theme;
|
||||||
|
void meow_img_color_derive(uint32_t main, uint32_t second, const meow_theme *th,
|
||||||
|
meow_img_color_theme *out);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **取色语义**:输入 RGBA(stb_image 输出,4 通道 straight alpha),最多遍历 ~3.6k 采样(内部自适应副采样步进,等效缩略图成本,大图/缩略图成本恒定);色域分桶 **4×4×4=64**(RGB 各 4 段),跳过近黑(luma<24)/近白(luma>244)/透明(alpha<64),按「像素数 + 饱和度」加权(偏鲜艳);**主色**=加权最大桶中心;**次色**=与主色 RGB 色距最大的显著桶(≥主桶 1/7≈14%)中心;无显著桶 → 次色=主色缩暗 2/3(渐变互补)。返回 -1=参数非法/有效采样不足(调用方回退全局 `meow_theme`)。
|
||||||
|
- **派生 `meow_img_color_theme`**:`bg_top/bg_bottom`(主/次各压暗 55% 防刺眼 + 保文字可读)、`fg`(主/次平均亮度选黑/白)、`fg_dim`(向背景靠拢的次要文字)、`accent=次色`、`accent_fg`(accent 上可读图标色)、`lyr`(歌词页 `meow_lyric_view_set_colors` 覆盖:scrim=主色压暗(α176)、cur=fg,gray 留 0 由 lyric_view 以 cur 自动递减)。
|
||||||
|
- **经 `meow_img_pixels` 读像素**(本 TU 已上收 agent-402 遗留的「局部补全结构体」):`meow_img_duocolor` 不再局部补全 opaque 布局,改用 §4 正式访问器。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -933,15 +963,20 @@ MeowOS 的声卡由**独占音频守护 `meow_audiod`** 持有(`hw:0,0`;M2.9
|
|||||||
|
|
||||||
```c
|
```c
|
||||||
int meow_audio_open(int rate, int ch, int fmt); /* 返回 stream_id(>0);<0=错误(-EBUSY 被占且被抢等) */
|
int meow_audio_open(int rate, int ch, int fmt); /* 返回 stream_id(>0);<0=错误(-EBUSY 被占且被抢等) */
|
||||||
|
int meow_audio_open_ex(int rate, int ch, int fmt, uint32_t flags); /* 带 flags(A3+DSD:flags=AUDIO_FLAG_DSD 且 fmt=AUDIO_FMT_DOP → DoP DSD 流) */
|
||||||
int meow_audio_write(const void *pcm, int frames);/* 阻塞(socket 背压);返回写入帧数, <0=流已死 */
|
int meow_audio_write(const void *pcm, int frames);/* 阻塞(socket 背压);返回写入帧数, <0=流已死 */
|
||||||
int meow_audio_pause(int sid, int paused); /* 1=暂停 0=恢复 */
|
int meow_audio_pause(int sid, int paused); /* 1=暂停 0=恢复 */
|
||||||
int meow_audio_close(int sid); /* 关流 */
|
int meow_audio_close(int sid); /* 关流 */
|
||||||
int meow_audio_query(meow_audio_state *out); /* 当前流/路由/音量/underrun 快照 */
|
int meow_audio_query(meow_audio_state *out); /* 当前流/路由/音量/underrun 快照 */
|
||||||
|
int meow_audio_set_volume(int pct); /* audiod 唯一音量权威:写 DAC + DSD_VOLUME 双组 + 持久化 */
|
||||||
/* 被抢占回调(可选):收到 MSG_AUDIO_PREEMPTED 时调(SDK 内部读线程分发) */
|
/* 被抢占回调(可选):收到 MSG_AUDIO_PREEMPTED 时调(SDK 内部读线程分发) */
|
||||||
void meow_audio_on_preempt(void (*cb)(const char *by_app, void *ctx), void *ctx);
|
void meow_audio_on_preempt(void (*cb)(const char *by_app, void *ctx), void *ctx);
|
||||||
```
|
```
|
||||||
|
|
||||||
- **fmt 枚举**:1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE(v1 实装 S16/S24/S32);服务端做**格式归一 + DAC 原生率跟随**(无 SRC,44.1-192k 全覆盖)。
|
- **fmt 枚举**:1=S16_LE 2=S24_LE 3=S32_LE 4=FLOAT_LE 5=AUDIO_FMT_DOP(v1 实装 S16/S24/S32;FLOAT v1 拒绝);服务端做**格式归一 + DAC 原生率跟随**(无 SRC,44.1-192k 全覆盖)。
|
||||||
|
- **`meow_audio_open_ex` / DoP DSD(A3+DSD 定案,勿回退)**:比 `meow_audio_open` 多 `flags`。`flags=AUDIO_FLAG_DSD(0x1)` 且 `fmt=AUDIO_FMT_DOP(=5)` → **DoP DSD 流**:App 侧把 DSD 解码封装为 DoP(每样本 32-bit 容器:bits[31:16]=16bit DSD 数据、bits[15:8]=标记 0xFA/0x05、bits[7:0]=0;独立格式值避免 S24 名义(3B)/实际(4B)歧义);audiod **只透传**(不引 dsd2pcm、不做 DOP_EN 之外处理);DSD 打不开/不支持 → open 返回负值、报错关流(**不降级**,勿静默回退 PCM 伪装)。
|
||||||
|
- **`meow_audio_state.dsd`(追加字段,ABI 兼容)**:当前流是否为 DoP DSD(`fmt==AUDIO_FMT_DOP` 且 audiod 已授权)。`meow_audio_state` 其它字段不变,此键为追加、旧结构体兼容。
|
||||||
|
- **`meow_audio_set_volume`(音量收编 v2,audiod 唯一权威)**:写 DAC numid=1/2 + DSD_VOLUME **双组**(保切换连续)+ 持久化 `settings.conf audio.volume`(与 launcher/watchdog 同源同键)。BT 输出目标时音量由 AVRCP 侧控,audiod 不下发 DAC 反转。返回 0 成功;<0=audiod 不在。
|
||||||
- **独占+抢占**:后开流赢,旧流收 `MSG_AUDIO_PREEMPTED`(App 收到自行暂停 UI);**socket EOF=回收**(客户端崩溃/被 SIGKILL 不泄漏)。流绑定 socket 连接而非 App 前台状态(后台播放见 AUDIOD_DESIGN §3)。
|
- **独占+抢占**:后开流赢,旧流收 `MSG_AUDIO_PREEMPTED`(App 收到自行暂停 UI);**socket EOF=回收**(客户端崩溃/被 SIGKILL 不泄漏)。流绑定 socket 连接而非 App 前台状态(后台播放见 AUDIOD_DESIGN §3)。
|
||||||
- **懒连接 + 断线重连**:open 时连 `/usr/data/meow_audio.sock`;write 遇 EPIPE → 标记流死,App 需重新 open。**不做隐式直开回退**:audiod 不在 → open 返回 -ENOENT;调试可 `MEOW_AUDIO_DIRECT=1` 允许 App 自带直开路径(仅开发用,非正式能力)。
|
- **懒连接 + 断线重连**:open 时连 `/usr/data/meow_audio.sock`;write 遇 EPIPE → 标记流死,App 需重新 open。**不做隐式直开回退**:audiod 不在 → open 返回 -ENOENT;调试可 `MEOW_AUDIO_DIRECT=1` 允许 App 自带直开路径(仅开发用,非正式能力)。
|
||||||
- **调试 CLI**(root 免鉴权,ssh 直呼):`meow_audio status` / `meow_audio tone [freq] [ms] [rate]`(正弦测试音,排查无声不用开 App)/ `route <0|2|3>` / `vol [0-100]` / **`monitor [秒]`**(261,终端实时电平表:200ms 刷新 rms/peak dBFS + 文本电平条,缺省 10s,0=持续)。
|
- **调试 CLI**(root 免鉴权,ssh 直呼):`meow_audio status` / `meow_audio tone [freq] [ms] [rate]`(正弦测试音,排查无声不用开 App)/ `route <0|2|3>` / `vol [0-100]` / **`monitor [秒]`**(261,终端实时电平表:200ms 刷新 rms/peak dBFS + 文本电平条,缺省 10s,0=持续)。
|
||||||
@@ -1326,11 +1361,20 @@ patch_v2/meow_launcherd/meow_font.a # 复用(静态链接)
|
|||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"framework": 1, // 1=走 meow_ui(框架承载);缺省/0=裸自绘(旧)
|
"framework": 1, // 1=走 meow_ui(框架承载);缺省/0=裸自绘(旧)
|
||||||
"immersive": 0, // 1=沉浸模式(隐藏虚拟返回键,左缘滑动返回)
|
"immersive": 0, // 1=沉浸模式(隐藏虚拟返回键,左缘滑动返回)
|
||||||
"icon": "apps/os.meow.hello/icon.png" // 可选:列表图标(供图片层)
|
"icon": "apps/os.meow.hello/icon.png", // 可选:列表图标(供图片层)
|
||||||
|
"background": 0, // M4:可注册无头后台服务(MSG_REGISTER 权限位0)
|
||||||
|
"background_audio": 1, // M4:播放中退出转后台保活(媒体会话活跃=保活,见 §10.5)
|
||||||
|
"background_keepalive": 0 // 批①:进入后台即保活(不依赖媒体会话)=通用常驻
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
上例 `<pkg>`=`os.meow.hello`(= `apps/<pkg>/` 目录名 = `package` 字段值);exec 即 `<挂载点>/MeowAPPs/<pkg>/<binary>`。
|
上例 `<pkg>`=`os.meow.hello`(= `apps/<pkg>/` 目录名 = `package` 字段值);exec 即 `<挂载点>/MeowAPPs/<pkg>/<binary>`。
|
||||||
|
|
||||||
|
> **app 后台常驻语义(M4 + 批①,launcher `meow_bgplay.c` 判定,勿与 `background`(无头服务注册)混用)**:
|
||||||
|
> - `background_audio=true`(M4):媒体会话**活跃**(PLAYING/PAUSED/BUFFER)即保活;暂停也驻留(补 M4 仅 PLAYING 缺口,根治「声明后台播放的 App 一暂停就被杀」)。ncm 已声明。
|
||||||
|
> - `background_keepalive=true`(批①):**进入后台即保活**,不依赖媒体会话=**通用常驻**(声音/无媒体会话的 App 亦可常驻)。是 `background_audio` 的超集(`meow_bg_should_keep`:keepalive=1 恒驻留;否则 bg_audio && media_resident)。
|
||||||
|
> - `background=true`(M4):仅注册**无头后台服务**(不同义于保活;`service.<svc>` 声明服务策略)。
|
||||||
|
> - 解析器注意 3 键前缀关系(`background` 是 `background_audio` 前缀、`background_keepalive` 又是 `background` 前缀):键匹配独立(`json_str` ident 前缀隔离),命中顺序靠 `strstr` 先行匹配无副作用——`"background_keepalive":true` 行即使先被 `"background"` 分支命中,`json_str(buf,"background")` 读出的也是 `_keepalive…`≠`true`,自然留 0,无害。**枚举/语义见 `devdocs/plan/APP_BACKGROUND_KEEPALIVE_P1.md`**。
|
||||||
|
|
||||||
> **app 字体来源(M2.x 已落地)**:app 若**自带字库**(`load_handle`,见 §5.7.3,**未实现**)则用它;否则 SDK `meow_ui_gfx.c load_font()` **优先读 launcher 注入的环境变量** `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(=系统 `font.file` 当前生效字体,绝对路径)/`MEOW_FONT_APP`(family),无 env 则回退卡默认 `MeowOS/fonts/default/regular/font.png`(M2.6 字重结构)→ 固件。因此**app 未自带字库时跟随系统 `font.file`**,不再硬编码 `MeowAPPs/fonts`。
|
> **app 字体来源(M2.x 已落地)**:app 若**自带字库**(`load_handle`,见 §5.7.3,**未实现**)则用它;否则 SDK `meow_ui_gfx.c load_font()` **优先读 launcher 注入的环境变量** `MEOW_FONT_PNG`/`MEOW_FONT_IDX`(=系统 `font.file` 当前生效字体,绝对路径)/`MEOW_FONT_APP`(family),无 env 则回退卡默认 `MeowOS/fonts/default/regular/font.png`(M2.6 字重结构)→ 固件。因此**app 未自带字库时跟随系统 `font.file`**,不再硬编码 `MeowAPPs/fonts`。
|
||||||
>
|
>
|
||||||
> **app 多字体(M2.5x,`display.multi_font`)**:launcher 注入 `MEOW_MULTI_FONT=0/1` 与 6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 的 `meow_gfx_load_multifont()` 读 env → 调 `meow_font_config_multifont()`(同 png 去重、≤4 套上限、缺类回退全局)构建**逐字符分类路由**:`meow_font_drawtext/width_text/line_height` 按 `meow_font_class_of(cp)`(`MEOW_FCLS_CJK/_CJK_SYM/_NUM/_LATIN/_LATIN_SYM/_OTHER`)用对应字库取字形与度量;`meow_font_has_glyph(cp)` 可预判缺字。`display.multi_font=0`/无 env → 单字体(现状,零额外开销)。底层接口见 meow_font.h:`meow_font_load_slot`/`meow_font_unload_slot`/`meow_font_enable_multifont`/`meow_font_set_class_slot`/`meow_font_config_multifont`;`MEOW_MAX_SLOTS=5`。
|
> **app 多字体(M2.5x,`display.multi_font`)**:launcher 注入 `MEOW_MULTI_FONT=0/1` 与 6 类 `MEOW_FONT_CLS_<类>_PNG/IDX`(命中则覆盖全局;类值为空=跟随全局不注入)。SDK `meow_ui_gfx` 的 `meow_gfx_load_multifont()` 读 env → 调 `meow_font_config_multifont()`(同 png 去重、≤4 套上限、缺类回退全局)构建**逐字符分类路由**:`meow_font_drawtext/width_text/line_height` 按 `meow_font_class_of(cp)`(`MEOW_FCLS_CJK/_CJK_SYM/_NUM/_LATIN/_LATIN_SYM/_OTHER`)用对应字库取字形与度量;`meow_font_has_glyph(cp)` 可预判缺字。`display.multi_font=0`/无 env → 单字体(现状,零额外开销)。底层接口见 meow_font.h:`meow_font_load_slot`/`meow_font_unload_slot`/`meow_font_enable_multifont`/`meow_font_set_class_slot`/`meow_font_config_multifont`;`MEOW_MAX_SLOTS=5`。
|
||||||
@@ -1746,5 +1790,89 @@ int meow_cachegov_backoff_level(void); /* 0=正常 1=keep_hot 放
|
|||||||
- [D6](M7.0a)新增 `meow_iconbtn_create`(圆形/位图图标按钮,§5.8.6)+ `meow_theme_export`/`meow_theme_inject`(§8.1.5 色板导出/注入);`meow_view_dispatch_event` 接入 `meow_ui_run` 主循环(§6.4 之上,TAP/KEY/SWIPE/LONG_PRESS 先经 view 路由、命中即消费、未命中回落 `on_*`);`meow_view` 的 enter/leave 递归 children 已实现 + `meow_view_add_child` 在父进入后挂子立即进入;`meow_view_sub_push/pop` 标注为废弃(设计定案 b 不做页内子导航栈)。
|
- [D6](M7.0a)新增 `meow_iconbtn_create`(圆形/位图图标按钮,§5.8.6)+ `meow_theme_export`/`meow_theme_inject`(§8.1.5 色板导出/注入);`meow_view_dispatch_event` 接入 `meow_ui_run` 主循环(§6.4 之上,TAP/KEY/SWIPE/LONG_PRESS 先经 view 路由、命中即消费、未命中回落 `on_*`);`meow_view` 的 enter/leave 递归 children 已实现 + `meow_view_add_child` 在父进入后挂子立即进入;`meow_view_sub_push/pop` 标注为废弃(设计定案 b 不做页内子导航栈)。
|
||||||
- `meow_ui.h` 为对外权威签名;本文件 §3-§8 作为设计蓝本。
|
- `meow_ui.h` 为对外权威签名;本文件 §3-§8 作为设计蓝本。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. 歌词组件(`meow_lyric` / `meow_lyric_view` / `meow_media_lyric`,批1-3)
|
||||||
|
|
||||||
|
三件套把网易云 ncm 的歌词能力**通用化上收**,供 launcher Now Playing 与 ncm 等任意 App 复用;launcher 与 ncm 共用同一份实现,避免两端漂移。
|
||||||
|
|
||||||
|
### 16.1 `meow_lyric`(批1:LRC 时间轴解析 / 查询 / 折行)
|
||||||
|
|
||||||
|
独立公共头 `meow_lyric.h`(**不随 `meow_ui.h` 伞头发布**;App 显式 `#include "meow_lyric.h"`)。纯 libc + pthread、**不依赖其它 SDK 对象**(不含 meow_log,便于 non-meow_ui.a 使用方独立链入);宿主 gcc 可直接编译单测。
|
||||||
|
|
||||||
|
```c
|
||||||
|
void meow_lyric_clear(void); /* 清空全部数据 */
|
||||||
|
int meow_lyric_parse(const char *lrc, const char *tlrc); /* 解析原文+翻译,按时间升序排序,返回行数 */
|
||||||
|
|
||||||
|
int meow_lyric_count(void);
|
||||||
|
int meow_lyric_line_at(int64_t ms); /* last timestamp<=ms;首戳前=0;末戳后=n-1 */
|
||||||
|
const char *meow_lyric_line_text(int i); /* 第 i 行原文;越界 "" */
|
||||||
|
const char *meow_lyric_line_tl(int i); /* 第 i 行翻译;越界/无翻译 "" */
|
||||||
|
int64_t meow_lyric_line_time(int i); /* 第 i 行时间戳(ms);越界 -1(供「点行跳转」seek) */
|
||||||
|
void meow_lyric_set_max_lines(int max); /* 行上限 [1,MEOW_LYRIC_LINES_MAX],下次 parse 生效 */
|
||||||
|
|
||||||
|
int meow_lyric_wrap(const char *txt, int size, int maxw,
|
||||||
|
char seg[][MEOW_LYRIC_SEG_SZ], int max_rows,
|
||||||
|
meow_lyric_width_fn width, void *ctx); /* 折行 ≤max_rows 段(宽测经回调) */
|
||||||
|
```
|
||||||
|
|
||||||
|
- **容量/静态存储**:行数硬上限 `MEOW_LYRIC_LINES_MAX`(600);单行文本(原文/翻译)上限 `MEOW_LYRIC_TEXT_MAX`(192) 字节;折行单段 `MEOW_LYRIC_SEG_SZ`(256)。
|
||||||
|
- **输入**:标准 LRC 行 `[mm:ss.xx]正文`;lrc=原文、tlrc=翻译,翻译按相同 timestamp 合并到对应原文行(存 tl),无匹配忽略。服务器字面序列:网易云把多行文本用字面 `\n`/`\r` 塞进一行(渲染豆腐/凌乱)→ 按空格替换(`\r\n`/`\n\r` 折单空格,语义同 ncm copy_lrc_text)。
|
||||||
|
- **时间戳小数位自适应**:1 位=0.1s(×100)、2 位=0.01s(×10,标准 LRC 厘秒)、3 位=0.001s(×1,网易云 LRC 毫秒)、>3 位截断到毫秒——避免把网易云 3 位毫秒当 2 位厘秒解析导致时间放大。
|
||||||
|
- **线程安全(单写多读,同 ncm lyric.c 交错安全)**:数据后台线程写(parse/clear/set_max_lines)、UI 线程读(count/line_at/line_text/line_tl/line_time);全部函数内部持同一 pthread 互斥、函数级粒度、无嵌套(清空/解析用 nolock 私有版防自锁死)。**读返回指针指向静态存储,在下次 parse/clear 前有效**(调用方不得跨重建持有)。
|
||||||
|
|
||||||
|
### 16.2 `meow_lyric_view`(批2:歌词页渲染控件)
|
||||||
|
|
||||||
|
独立公共头 `meow_lyric_view.h`(仿 meow_ui_view.h 模式,不随 `meow_ui.h` 伞头发布)。把 ncm 歌词页布局上收成 SDK 控件:活动行纯白(居中偏上锚定)、上下行按行距逐级灰阶、翻译行紧跟原文不叠行(块高含翻译行)、超宽折行(中西文兼容)、上方保底 ≥above_min 行已过、总视觉行数 ≤max_rows、点行 seek 命中与绘制一致。
|
||||||
|
|
||||||
|
```c
|
||||||
|
meow_lyric_view *meow_lyric_view_create(int x,int y,int w,int h,meow_lyric_width_fn width,void *width_ctx);
|
||||||
|
void meow_lyric_view_free(meow_lyric_view *lv);
|
||||||
|
meow_widget *meow_lyric_view_widget(const meow_lyric_view *lv); /* 底层 W_CUSTOM 控件 */
|
||||||
|
|
||||||
|
void meow_lyric_view_set_pos(meow_lyric_view *lv, int64_t pos_ms); /* 播放器位置 */
|
||||||
|
void meow_lyric_view_set_colors(meow_lyric_view *lv, const meow_lyric_view_colors *c); /* NULL=回主题默认 */
|
||||||
|
void meow_lyric_view_set_layout(meow_lyric_view *lv, const meow_lyric_view_layout *ls); /* NULL=回内建默认 */
|
||||||
|
void meow_lyric_view_set_empty(meow_lyric_view *lv, const char *txt); /* 无歌词占位;NULL=默认 */
|
||||||
|
void meow_lyric_view_set_on_seek(meow_lyric_view *lv, meow_lyric_seek_fn cb, void *ctx); /* 点行 seek */
|
||||||
|
|
||||||
|
uint32_t meow_lyric_view_cur_color(const meow_lyric_view *lv); /* 主题默认或覆盖的活动行色 */
|
||||||
|
uint32_t meow_lyric_view_line_color(const meow_lyric_view *lv, int dist); /* 距活动行 dist(0..5) 行色 */
|
||||||
|
int meow_lyric_view_hit_line(meow_lyric_view *lv, int x, int y); /* 命中行下标;未命中 -1 */
|
||||||
|
int meow_lyric_view_visible(meow_lyric_view *lv, int64_t pos_ms, meow_lyric_view_vis *out, int cap);
|
||||||
|
```
|
||||||
|
|
||||||
|
- **数据来源**:绑定批1 `meow_lyric`(全局单例,线程安全)+ 播放位置(`set_pos`);宽度测量用回调注入(`meow_lyric_width_fn`,包 meow_ui_text_width;宿主测试传自测函数)。App 可只用查询接口(`visible`/`hit_line`)在自绘流程里复用布局逻辑。
|
||||||
|
- **主题**:SDK 全局 `meow_theme` 为默认(明暗背景 scrim、活动行 onSurface=dark 纯白、灰阶递减);`meow_lyric_view_colors` 为批5 封面取色覆盖入口(**字段 0=用主题默认**):`scrim`(黑叠加封面之上)、`cur`(活动行色)、`gray[6]`(距活动行 0..5 灰阶;gray[0]=cur,字段 0=自动递减)、`scrim_alpha`(<0=默认176,0=不画 scrim)。
|
||||||
|
- **布局参数**(`meow_lyric_view_layout`,NULL=内建默认):`pad`(24)/`sub_lh`(44)/`cur_sz`(28)/`oth_sz`(24)/`tl_sz`(20)/`max_rows`(9,≤`MEOW_LYRIC_VIEW_MAX_ROWS`=9)/`above_min`(2)/`above_off`(50 偏上)。
|
||||||
|
- **可见条目**(`meow_lyric_view_vis`,布局/命中/绘制共用同一算法,保证「点行 seek 命中与绘制一致」):`ent`=歌词行下标、`rw`=原文折行段数、`bh`=块高(子行数)=rw+当前行翻译占 1、`sz`=字号、`y`=块顶 y(绝对屏幕坐标)。
|
||||||
|
|
||||||
|
### 16.3 `meow_media_lyric_*`(批3:媒体会话 live 歌词源)
|
||||||
|
|
||||||
|
`meow_media.h`(launcher 侧**媒体会话注册表**,launcher/ncm 共用)。App 声明 MF_PLAYBACK + 上报 `MSG_META` → 成为媒体源;最近活跃者=当前源;通知中心媒体卡片 / Now Playing / 物理媒体键 / 控制器统一消费。
|
||||||
|
|
||||||
|
```c
|
||||||
|
int meow_media_meta(const m_meta_t *m, int fd); /* 按 app_id 归并会话;-1=会话满/参数非法 */
|
||||||
|
const meow_media_session_t *meow_media_current(void); /* 最近活跃源;无返回 NULL */
|
||||||
|
int meow_media_has_source(void); /* 当前源存在且 state!=STOPPED */
|
||||||
|
int meow_media_app_playing(const char *app_id); /* 叠加媒体保活(WORK_STATE §10.5):state==PLAYING 且匹配 */
|
||||||
|
int meow_media_app_resident(const char *app_id); /* 批①常驻判定:会话存在且 state!=STOPPED */
|
||||||
|
int meow_media_playctl(uint32_t cmd, int64_t seek_ms); /* 向当前源下发 PLAYCTL */
|
||||||
|
int meow_media_playctl_to(const char *app_id, uint32_t cmd, int64_t seek_ms);
|
||||||
|
void meow_media_clear(const char *app_id); /* 清某 app 会话;空/NULL=清全部 */
|
||||||
|
void meow_media_tick(void); /* ~1s:播放/缓冲超时清槽回空态 */
|
||||||
|
uint32_t meow_media_display_pos(const meow_media_session_t *s);/* 播放中按墙钟推进(clamp);暂停/缓冲原样 */
|
||||||
|
|
||||||
|
/* 歌词(批3 · live lyric 数据源) */
|
||||||
|
int meow_media_lyric(const m_lyric_t *l); /* 上报当前曲歌词分片;-1=会话不存在/参数非法 */
|
||||||
|
int meow_media_lyric_valid(const meow_media_session_t *s); /* 会话携带有效歌词(lrc 非空) */
|
||||||
|
const char *meow_media_lyric_lrc(const meow_media_session_t *s); /* 原文;无则 "" */
|
||||||
|
const char *meow_media_lyric_tlrc(const meow_media_session_t *s); /* 翻译;无则 "" */
|
||||||
|
```
|
||||||
|
|
||||||
|
- **会话**:一个 App 一个槽(`MEOW_MEDIA_MAX`=4,app_id 归并防占满);当前源=最近活跃(`last_active_ms`/`active_seq` 最大)。源死亡判定:播放中(1)/缓冲(3) 超 `MEOW_MEDIA_STALE_MS`(30000) 无 META → 清槽回空态(App 死/服务断);暂停(2)/停止(0) 不超时。`state`:STOPPED=0 / PLAYING=1 / PAUSED=2 / BUFFER=3。
|
||||||
|
- **live 歌词(批3)**:App 经 `MSG_META_LYRIC` 分片上报当前曲歌词 → 按 app_id 归并会话内有序缓冲(`lyric_lrc`/`lyric_tlrc`,超 `MEOW_MEDIA_LRC_MAX`/`TLRC_MAX` 截断);`lyric_seq`=歌曲代际(新歌/新词自增,launcher 见新 seq 清旧词)。**取数链**:桌面歌词页 `meow_media_lyric_lrc/tlrc` 取文本 → 批1 `meow_lyric_parse` 解析 → `meow_lyric_line_at(meow_media_display_pos(s))` → 批2 `meow_lyric_view` 实时滚动。
|
||||||
|
- **依赖注入**:发送/日志经 `meow_media_ops`(launcher 用 `ipc_send`/`logmsg`;宿主单测用 socket write/NULL),可独立单测(`test_media.c`)。发送 `MSG_META_LYRIC` 前 App 须先报 `MSG_META` 注册会话,否则歌词被丢弃(防 fd=0 槽)。
|
||||||
|
|
||||||
---
|
---
|
||||||
*文档基于 2026-08-29 现有渲染基建(M2.16 无闪烁地基 + meow_font + blend565)编写;API 名会随 B1 实现微调,实现时以 `meow_ui.h` 为准。*
|
*文档基于 2026-08-29 现有渲染基建(M2.16 无闪烁地基 + meow_font + blend565)编写;API 名会随 B1 实现微调,实现时以 `meow_ui.h` 为准。*
|
||||||
@@ -287,9 +287,9 @@ int main(void)
|
|||||||
/* 播放页:左缘右滑 → 回桌面(保留 M6.26) */
|
/* 播放页:左缘右滑 → 回桌面(保留 M6.26) */
|
||||||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 8, 100, 80, 5) == NP_GEST_EXIT,
|
CHECK(np_gesture_decide(NP_PAGE_PLAY, 8, 100, 80, 5) == NP_GEST_EXIT,
|
||||||
"play: edge right-swipe -> exit");
|
"play: edge right-swipe -> exit");
|
||||||
/* 播放页:非左缘右滑 → 播放页无上一页,忽略(不误切/不误退) */
|
/* 播放页:任意右滑 → 返回桌面(用户定案「播放页任意右滑回桌面」,含非左缘;np_gesture.h 恒 EXIT) */
|
||||||
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 100, 80, 5) == NP_GEST_NONE,
|
CHECK(np_gesture_decide(NP_PAGE_PLAY, 200, 100, 80, 5) == NP_GEST_EXIT,
|
||||||
"play: non-edge right-swipe -> none");
|
"play: non-edge right-swipe -> EXIT");
|
||||||
/* 歌词页:左→右滑 → 回播放页(不是回桌面) */
|
/* 歌词页:左→右滑 → 回播放页(不是回桌面) */
|
||||||
CHECK(np_gesture_decide(NP_PAGE_LYRIC, 200, 100, 60, 5) == NP_GEST_PREV,
|
CHECK(np_gesture_decide(NP_PAGE_LYRIC, 200, 100, 60, 5) == NP_GEST_PREV,
|
||||||
"lyric: right-swipe -> play");
|
"lyric: right-swipe -> play");
|
||||||
|
|||||||
@@ -22,8 +22,11 @@ meow_img *meow_img_load_file(const char *path); /* 内部读文件 + stb 解
|
|||||||
meow_img *meow_img_load_mem(const void *buf, int len);
|
meow_img *meow_img_load_mem(const void *buf, int len);
|
||||||
void meow_img_free(meow_img *img);
|
void meow_img_free(meow_img *img);
|
||||||
|
|
||||||
int meow_img_w(meow_img *img);
|
int meow_img_w(const meow_img *img);
|
||||||
int meow_img_h(meow_img *img);
|
int meow_img_h(const meow_img *img);
|
||||||
|
/* 访问器:取 RGBA 像素指针(4 通道 straight alpha;供取色/直读像素)。meow_img 对外 opaque,
|
||||||
|
* 本函数为其唯一像素读入口;NULL 返回 NULL。 */
|
||||||
|
const unsigned char *meow_img_pixels(const meow_img *img);
|
||||||
|
|
||||||
void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */
|
void meow_ui_img(int x, int y, meow_img *img); /* 原始尺寸 */
|
||||||
/* 内存瘦身:最近邻等比缩放到最长边 ≤maxdim,返回新图;原图已 ≤maxdim 返回 NULL。
|
/* 内存瘦身:最近邻等比缩放到最长边 ≤maxdim,返回新图;原图已 ≤maxdim 返回 NULL。
|
||||||
|
|||||||
@@ -1,21 +1,16 @@
|
|||||||
/*
|
/*
|
||||||
* meow_img_color.c — MeowOS SDK 封面双色渐变取色实现(见 meow_img_color.h 契约)。
|
* meow_img_color.c — MeowOS SDK 封面双色渐变取色实现(见 meow_img_color.h 契约)。
|
||||||
*
|
*
|
||||||
* 独立源码,不并入 meow_ui.a(铁律:避免并发重编/四 App 重链)。纯函数,无任何 meow_* 调用,
|
* 独立源码,不并入 meow_ui.a(铁律:避免并发重编/四 App 重链)。取色算法纯函数(无 GPU/浮点/堆)。
|
||||||
* 宿主单测零链接重(仅需本 .c + meow_img_color.h 及其 include 链)。
|
* 唯一 meow_* 依赖:仅 meow_img_pixels(SDK 正式访问器,meow_ui_img.c §4),并经 meow_img_w/h 取宽高
|
||||||
*
|
* ——三者读 meow_img 的唯一入口,本 TU 不再局部补全其结构体(agent-402 遗留点已上收)。App/宿主
|
||||||
* 布局耦合说明:meow_img 为 opaque(对外仅前向声明,结构体私有于 meow_ui_img.c)。为在不动
|
* 链入 meow_img_color.c 时需能解析 meow_img_pixels/w/h(宿主测试已链 meow_ui_img.c,实际 App 恒链
|
||||||
* meow_ui.a 二进制的前提下直读封面像素(批5 取色),本 TU 按 meow_ui_img.c 自 A1 起稳定的
|
* meow_ui.a)。
|
||||||
* `struct meow_img { int w, h; unsigned char *rgba; };` 未布局局部补全。两 TU 各持同一定义,
|
|
||||||
* 链接后字段偏移一致(本项目内 ABI 稳定)。主会话下次重建 SDK 时可上收为 meow_ui_img.c 的
|
|
||||||
* 正式访问器,届时本补全即删除。
|
|
||||||
*/
|
*/
|
||||||
#include "meow_img_color.h"
|
#include "meow_img_color.h"
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
struct meow_img { int w, h; unsigned char *rgba; }; /* 见上:局部补全(与 meow_ui_img.c 一致) */
|
|
||||||
|
|
||||||
#define MIC_BUCKETS 4 /* 每通道色阶数 ⇒ 4×4×4=64 桶 */
|
#define MIC_BUCKETS 4 /* 每通道色阶数 ⇒ 4×4×4=64 桶 */
|
||||||
#define MIC_NBUCKET (MIC_BUCKETS*MIC_BUCKETS*MIC_BUCKETS)
|
#define MIC_NBUCKET (MIC_BUCKETS*MIC_BUCKETS*MIC_BUCKETS)
|
||||||
#define MIC_ALPHA_MIN 64 /* 跳过近透明(alpha<64) */
|
#define MIC_ALPHA_MIN 64 /* 跳过近透明(alpha<64) */
|
||||||
@@ -120,8 +115,10 @@ int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h,
|
|||||||
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second)
|
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second)
|
||||||
{
|
{
|
||||||
if (!im) return -1;
|
if (!im) return -1;
|
||||||
/* 局部补全 struct 视图直读像素(布局与 meow_ui_img.c 一致,见顶部说明)。 */
|
/* 经 SDK 正式访问器读像素(meow_img opaque;meow_img_pixels 为唯一读入口)。 */
|
||||||
return meow_img_duocolor_rgba(im->rgba, im->w, im->h, main, second);
|
const unsigned char *rgba = meow_img_pixels(im);
|
||||||
|
if (!rgba) return -1;
|
||||||
|
return meow_img_duocolor_rgba(rgba, meow_img_w(im), meow_img_h(im), main, second);
|
||||||
}
|
}
|
||||||
|
|
||||||
void meow_img_color_derive(uint32_t main, uint32_t second, const meow_theme *th,
|
void meow_img_color_derive(uint32_t main, uint32_t second, const meow_theme *th,
|
||||||
|
|||||||
@@ -9,11 +9,11 @@
|
|||||||
* `#include "meow_img_color.h"` 并在构建时单链 meow_img_color.c(宿主单测同法)。
|
* `#include "meow_img_color.h"` 并在构建时单链 meow_img_color.c(宿主单测同法)。
|
||||||
*
|
*
|
||||||
* 约束(0 warning / 最小侵入):
|
* 约束(0 warning / 最小侵入):
|
||||||
* - 不修改 meow_ui.a 任何成员(meow_img 保持 opaque 对外)。为直读封面像素,本 TU 按
|
* - meow_img 对外保持 opaque;像素直读经 SDK 正式访问器 `meow_img_pixels`(meow_ui_img.c §4,
|
||||||
* meow_ui_img.c 自 A1 起稳定的 `struct meow_img { int w,h; unsigned char *rgba; }` 布局
|
* 已上收「局部补全结构体」遗留),宽高经 `meow_img_w`/`meow_img_h`。本 TU 不再局部补全布局。
|
||||||
* 局部补全(见 .c 顶部说明)。主会话下次重建 SDK 时可上收为 meow_ui_img.c 的正式访问器。
|
* - 取色算法纯函数:meow_img_duocolor_rgba / meow_img_color_derive 纯 C、无 meow_* 依赖;
|
||||||
* - 纯函数:meow_img_duocolor_rgba / meow_img_duocolor / meow_img_color_derive 不调用任何
|
* meow_img_duocolor(句柄版)仅经上述三访问器读,不触碰 meow_ui.a 内部实现。宿主单测链
|
||||||
* meow_* 符号,宿主单测可零链接重(仅需本头 + 本 .c)。
|
* meow_img_color.c + meow_ui_img.c 即可。
|
||||||
*
|
*
|
||||||
* 色彩语义:
|
* 色彩语义:
|
||||||
* - 输入 RGBA(stb_image 输出,4 通道 straight alpha),w×h,最多遍历 ~3.6k 采样(内部自适
|
* - 输入 RGBA(stb_image 输出,4 通道 straight alpha),w×h,最多遍历 ~3.6k 采样(内部自适
|
||||||
@@ -37,7 +37,7 @@ extern "C" {
|
|||||||
int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h,
|
int meow_img_duocolor_rgba(const uint8_t *rgba, int w, int h,
|
||||||
uint32_t *main, uint32_t *second);
|
uint32_t *main, uint32_t *second);
|
||||||
|
|
||||||
/* 从 meow_img 句柄取主色+次色(内部直读 rgba 缓冲,见 .c 布局说明)。返回 0=成功;-1=失败。 */
|
/* 从 meow_img 句柄取主色+次色(经 meow_img_pixels 读像素,见 .c)。返回 0=成功;-1=失败。 */
|
||||||
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second);
|
int meow_img_duocolor(const meow_img *im, uint32_t *main, uint32_t *second);
|
||||||
|
|
||||||
/* 由主/次色派生播放页 + 歌词页封面主题(纯函数,可宿主单测)。
|
/* 由主/次色派生播放页 + 歌词页封面主题(纯函数,可宿主单测)。
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ void meow_img_free(meow_img *im)
|
|||||||
free(im);
|
free(im);
|
||||||
}
|
}
|
||||||
|
|
||||||
int meow_img_w(meow_img *im) { return im ? im->w : 0; }
|
int meow_img_w(const meow_img *im) { return im ? im->w : 0; }
|
||||||
int meow_img_h(meow_img *im) { return im ? im->h : 0; }
|
int meow_img_h(const meow_img *im) { return im ? im->h : 0; }
|
||||||
|
|
||||||
|
/* 访问器:取 RGBA 像素指针(meow_img 对外 opaque,本函数为唯一像素读入口;供取色/直读像素)。
|
||||||
|
* NULL 返回 NULL。 */
|
||||||
|
const unsigned char *meow_img_pixels(const meow_img *im) { return im ? im->rgba : NULL; }
|
||||||
|
|
||||||
/* 最近邻采样缩放(cover=1 居中裁方),返回 dw*dh*4 临时 RGBA 缓冲 */
|
/* 最近邻采样缩放(cover=1 居中裁方),返回 dw*dh*4 临时 RGBA 缓冲 */
|
||||||
static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover)
|
static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover)
|
||||||
@@ -70,13 +74,16 @@ static unsigned char *scale_buf(const meow_img *im, int dw, int dh, int cover)
|
|||||||
s = sx < sy ? sx : sy; /* 源像素/目标像素(铺满 → 取小) */
|
s = sx < sy ? sx : sy; /* 源像素/目标像素(铺满 → 取小) */
|
||||||
float oxs = ((float)im->w - (float)dw * s) * 0.5f; /* 居中裁剪起点(源像素,≥0) */
|
float oxs = ((float)im->w - (float)dw * s) * 0.5f; /* 居中裁剪起点(源像素,≥0) */
|
||||||
float oys = ((float)im->h - (float)dh * s) * 0.5f;
|
float oys = ((float)im->h - (float)dh * s) * 0.5f;
|
||||||
if (oxs < 0) oxs = 0; if (oys < 0) oys = 0;
|
if (oxs < 0) oxs = 0;
|
||||||
|
if (oys < 0) oys = 0;
|
||||||
for (int dy = 0; dy < dh; dy++) {
|
for (int dy = 0; dy < dh; dy++) {
|
||||||
int syi = (int)((float)dy * s + oys);
|
int syi = (int)((float)dy * s + oys);
|
||||||
if (syi < 0) syi = 0; if (syi >= im->h) syi = im->h - 1;
|
if (syi < 0) syi = 0;
|
||||||
|
if (syi >= im->h) syi = im->h - 1;
|
||||||
for (int dx = 0; dx < dw; dx++) {
|
for (int dx = 0; dx < dw; dx++) {
|
||||||
int sxi = (int)((float)dx * s + oxs);
|
int sxi = (int)((float)dx * s + oxs);
|
||||||
if (sxi < 0) sxi = 0; if (sxi >= im->w) sxi = im->w - 1;
|
if (sxi < 0) sxi = 0;
|
||||||
|
if (sxi >= im->w) sxi = im->w - 1;
|
||||||
memcpy(out + ((size_t)dy * dw + dx) * 4,
|
memcpy(out + ((size_t)dy * dw + dx) * 4,
|
||||||
im->rgba + ((size_t)syi * im->w + sxi) * 4, 4);
|
im->rgba + ((size_t)syi * im->w + sxi) * 4, 4);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user