Files
MeowOS-For-R3P2/patch_v2/ncm_app/app/logic/lyric.c
T
AskaEth 3d70a3dd2c feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘)
- OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复
- ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640
- 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫)
- 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
2026-09-03 12:16:01 +08:00

238 lines
8.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* logic/lyric.c — LRC 时间轴解析 + 当前行定位。
*
* 输入:ncm_api_lyric 返回的 lrc(原文)与 tlrc(翻译)。两者均为标准 [mm:ss.xx] 行。
* 解析:把所有带时间戳的行按时间升序排序,存 timestamp(ms)/text;翻译按相同时间戳合并到
* 对应原文行(存 tl)。无匹配的翻译行忽略。流式、有界:NCM_LYRIC_LINES 行,超限截断。
* 纯 C 无 UI,仅依赖标准库。
*/
#include "../ncm_app_internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 歌词数据由 lyric_worker(player_manager.c,后台线程)写(lyric_parse/lyric_clear)而 UI 线程
* 与 IPC 线程读(lyric_count/lyric_line_at/lyric_line_text/lyric_line_tl)。三期第一批(REPORT §14.3)
* 补一把锁消除**数据竞争**(早前为良性竞态 §10.9 FM4;本节要求禁止数据竞争)。锁粒度=函数级,
* 写侧在 lyric_parse 整段持锁、读侧每次访问持锁,无嵌套(清空用 nolock 版,避免二次取锁自锁死)。 */
#define LRC_MAX_TEXT 192
static long s_t[NCM_LYRIC_LINES];
static char s_txt[NCM_LYRIC_LINES][LRC_MAX_TEXT];
static char s_tl[NCM_LYRIC_LINES][LRC_MAX_TEXT];
static int s_n;
static pthread_mutex_t g_lyric_mu = PTHREAD_MUTEX_INITIALIZER;
static void lyric_clear_nolock(void)
{
s_n = 0;
memset(s_t, 0, sizeof(s_t));
memset(s_txt, 0, sizeof(s_txt));
memset(s_tl, 0, sizeof(s_tl));
}
/* 复制一行歌词正文 n 字节到 dst,把「字面反斜杠 n/r」序列(服务器用 \n 把多行文本塞进一行,
* 字面 \n 渲染成豆腐块/凌乱)替换为空格;顺带把 \r\n / \n\r 折叠为单空格。有界 ≤max。 */
static void copy_lrc_text(char *dst, const char *src, size_t n, size_t max)
{
size_t oi = 0, si = 0;
while (si < n && oi < max - 1) {
if (src[si] == '\\' && si + 1 < n && (src[si + 1] == 'n' || src[si + 1] == 'r')) {
dst[oi++] = ' ';
si += 2;
/* \r\n / \n\r 成对 → 只留一个空格(跳过紧随的互补转义,防双空格) */
if (si + 1 < n && src[si] == '\\' &&
((src[si + 1] == 'r' && src[si - 1] == 'n') ||
(src[si + 1] == 'n' && src[si - 1] == 'r')))
si += 2;
} else {
dst[oi++] = src[si++];
}
}
dst[oi] = 0;
}
/* 解析单个 [mm:ss.ff] 时间标签为毫秒。返回 1=合法时间标签(*out_ms、*next=']' 之后);
* 0=非时间标签(*next=']' 之后;NULL=无闭合 ']',调用方按残缺处理)。
* 小数位自适应:1 位=0.1s(×100)、2 位=0.01s(×10,标准 LRC 厘秒)、3 位=0.001s(×1,
* 网易云 LRC 毫秒)——此前固定 ×10 把网易云 3 位毫秒当 2 位厘秒解析,时间戳放大 ~10 倍,
* 歌词行按绝对时间激活滞后 ~4~10s(REPORT「英文歌歌词滚动慢」)。 */
static int parse_lrc_tag(const char *p, long *out_ms, const char **next)
{
const char *q = p + 1; /* 跳过 '[' */
int m = 0, md = 0, s = 0, sd = 0;
while (*q >= '0' && *q <= '9') { m = m * 10 + (*q - '0'); md++; q++; }
if (md == 0 || *q != ':') goto notime;
q++;
while (*q >= '0' && *q <= '9') { s = s * 10 + (*q - '0'); sd++; q++; }
if (sd == 0) goto notime;
long frac = 0; int fd = 0; long scale = 10;
if (*q == '.') {
q++;
while (*q >= '0' && *q <= '9') { frac = frac * 10 + (*q - '0'); fd++; q++; }
if (fd == 1) scale = 100;
else if (fd == 2) scale = 10;
else if (fd >= 3) { scale = 1; while (fd > 3) { frac /= 10; fd--; } } /* >3 位截断到 ms */
else scale = 0; /* 孤立 '.' 无数字:忽略小数 */
}
if (*q != ']') goto notime;
*out_ms = (long)m * 60000L + (long)s * 1000L + frac * scale;
*next = q + 1;
return 1;
notime:
{
const char *r = strchr(p, ']');
*next = r ? r + 1 : NULL;
return 0;
}
}
static void lrc_parse(const char *lrc, int is_trans)
{
if (!lrc) return;
const char *p = lrc;
while (*p) {
/* 找行首 '[' */
while (*p && *p != '[') p++;
if (!*p) break;
/* 解析一个或多个时间标签 */
int have = 0;
long first_ms = 0;
while (*p == '[') {
long ms;
const char *after;
if (parse_lrc_tag(p, &ms, &after)) {
if (!have) { first_ms = ms; have = 1; }
/* 跳到本标签结尾 */
p = after;
} else {
/* 非时间标签([ti:..] 等)——跳过到行尾 */
if (!after) return; /* 无闭合 ']':残缺文件 */
p = after;
/* 若行内还有时间标签继续解析(极少),否则整行的正文以第一个文本为准 */
break;
}
}
if (have) {
/* 正文 = 到行尾(去 \r\n) */
const char *e = p;
while (*e && *e != '\n' && *e != '\r') e++;
size_t len = (size_t)(e - p);
if (len > LRC_MAX_TEXT - 1) len = LRC_MAX_TEXT - 1;
if (is_trans) {
/* 翻译行:按 first_ms 匹配到已有原文行 */
for (int i = 0; i < s_n; i++) {
if (s_t[i] == first_ms) {
copy_lrc_text(s_tl[i], p, len, LRC_MAX_TEXT);
break;
}
}
} else {
if (s_n < NCM_LYRIC_LINES) {
s_t[s_n] = first_ms;
copy_lrc_text(s_txt[s_n], p, len, LRC_MAX_TEXT);
s_tl[s_n][0] = 0;
s_n++;
}
}
}
while (*p && *p != '\n') p++; /* 跳到下一行 */
if (*p == '\n') p++;
}
}
static int cmp_lines(const void *a, const void *b)
{
const int *ia = (const int *)a, *ib = (const int *)b;
if (s_t[*ia] < s_t[*ib]) return -1;
if (s_t[*ia] > s_t[*ib]) return 1;
return 0;
}
int lyric_parse(const char *lrc, const char *tlrc)
{
pthread_mutex_lock(&g_lyric_mu);
lyric_clear_nolock();
int result = 0;
lrc_parse(lrc, 0);
lrc_parse(tlrc, 1);
/* 按时间排序(用索引数组)。临时数组放**堆**上:`char [600][192]`×2 + `long[600]`
* 若放栈会占 ~227KB,超过任何线程的 128KB 栈(ncm_thread_create NCM_THREAD_STACK)
* → lyric_worker 起播解析时栈溢出 SIGSEGV(真机 sig=11,RA 落在 lyric_parse)。
* 堆上按 s_n 计(≤600 行),峰值 ~230KB 瞬态、用后即 free。 */
if (s_n > 0) {
int *idx = (int *)malloc(sizeof(int) * (size_t)s_n);
long *ttmp = (long *)malloc(sizeof(long) * (size_t)s_n);
char (*txt)[LRC_MAX_TEXT] =
(char (*)[LRC_MAX_TEXT])malloc((size_t)LRC_MAX_TEXT * (size_t)s_n);
char (*tl)[LRC_MAX_TEXT] =
(char (*)[LRC_MAX_TEXT])malloc((size_t)LRC_MAX_TEXT * (size_t)s_n);
if (idx && ttmp && txt && tl) {
for (int i = 0; i < s_n; i++) idx[i] = i;
qsort(idx, (size_t)s_n, sizeof(int), cmp_lines);
memcpy(ttmp, s_t, sizeof(long) * (size_t)s_n);
memcpy(txt, s_txt, (size_t)LRC_MAX_TEXT * (size_t)s_n);
memcpy(tl, s_tl, (size_t)LRC_MAX_TEXT * (size_t)s_n);
for (int i = 0; i < s_n; i++) {
s_t[i] = ttmp[idx[i]];
memcpy(s_txt[i], txt[idx[i]], LRC_MAX_TEXT);
memcpy(s_tl[i], tl[idx[i]], LRC_MAX_TEXT);
}
}
free(idx); free(ttmp); free(txt); free(tl); /* 任一 malloc 失败:保留未排序(不崩) */
}
result = s_n;
pthread_mutex_unlock(&g_lyric_mu);
return result;
}
int lyric_line_at(int64_t ms)
{
pthread_mutex_lock(&g_lyric_mu);
int line = 0;
for (int i = 0; i < s_n; i++) {
if (s_t[i] <= ms) line = i;
else break;
}
pthread_mutex_unlock(&g_lyric_mu);
return line;
}
const char *lyric_line_text(int i)
{
pthread_mutex_lock(&g_lyric_mu);
const char *p = (i < 0 || i >= s_n) ? "" : s_txt[i];
pthread_mutex_unlock(&g_lyric_mu);
return p;
}
const char *lyric_line_tl(int i)
{
pthread_mutex_lock(&g_lyric_mu);
const char *p = (i < 0 || i >= s_n) ? "" : s_tl[i];
pthread_mutex_unlock(&g_lyric_mu);
return p;
}
/* 第 i 行时间戳(ms)。越界返回 -1。供播放页「点行跳转」(§22:seek 到该行时间)。 */
long lyric_line_time(int i)
{
pthread_mutex_lock(&g_lyric_mu);
long t = (i < 0 || i >= s_n) ? -1 : s_t[i];
pthread_mutex_unlock(&g_lyric_mu);
return t;
}
int lyric_count(void)
{
pthread_mutex_lock(&g_lyric_mu);
int n = s_n;
pthread_mutex_unlock(&g_lyric_mu);
return n;
}
void lyric_clear(void)
{
pthread_mutex_lock(&g_lyric_mu);
lyric_clear_nolock();
pthread_mutex_unlock(&g_lyric_mu);
}