/* test_media_lyric.c — 宿主单测:批3 live 歌词数据源框架(meow_media 会话携带 lrc/tlrc → meow_lyric 解析 → 随播放位置命中当前行)。 * * 链路:App 上报歌词(分片 m_lyric → meow_media_lyric)→ 会话累积 lrc/tlrc(有界、seq 代际) * → launcher 读取(meow_media_lyric_valid/lrc/tlrc)→ meow_lyric_parse → meow_lyric_line_at(meow_media_display_pos)。 * * 覆盖矩阵: * L1 App 上报 lrc/tlrc → 会话携带歌词(valid/lrc/tlrc 正确) * L2 launcher 读取 → meow_lyric_parse 成功(count/行文本/翻译合并正确) * L3 播放位置推进(display_pos 随 PLAYING 单调增)→ line_at 命中当前行(pos 增大 → 行号不降) * L4 分片重组:>MEOW_LYRIC_CHUNK 的 lrc 拆多帧上报,会话按 off 累积成完整串 * L5 seq 代际切换(换歌)→ 清旧词;同 seq 重报(幂等)不破坏 * L6 会话不存在 → meow_media_lyric 返回 -1;翻译无匹配自动忽略 * L7 多 App 独立:另一源上报自己的歌词,互不串 * * 编译(宿主):meow_media.c(纯 libc)+ meow_lyric.c(纯 libc+pthread)。 * gcc -Wall -Wextra -pthread -o test_media_lyric test_media_lyric.c meow_media.c ../sdk/meow_lyric.c -I. -I../sdk * ./test_media_lyric */ #include #include #include #include #include #include #include "meow_media.h" #include "meow_ipc.h" #include "meow_lyric.h" static int g_fails = 0; #define CHECK(cond, msg) do { \ if (cond) printf("PASS: %s\n", msg); \ else { printf("FAIL: %s (%s:%d)\n", msg, __FILE__, __LINE__); g_fails++; } } while (0) static size_t test_send(int fd, uint16_t type, uint32_t req_id, const void *payload, uint32_t len) { /* 用不到(本链路无下行),占位满足 meow_media_ops.send 签名。 */ (void)fd; (void)type; (void)req_id; (void)payload; (void)len; return 0; } static void fill_meta(m_meta_t *m, const char *app, uint32_t state, uint32_t pos, uint32_t dur) { memset(m, 0, sizeof *m); snprintf(m->app_id, sizeof m->app_id, "%s", app); m->state = state; m->pos_ms = pos; m->dur_ms = dur; snprintf(m->title, sizeof m->title, "Song"); snprintf(m->artist, sizeof m->artist, "Artist"); } /* 模拟 ncm 的 send_lyric_kind:把 text 按 ≤MEOW_LYRIC_CHUNK 分片,逐片调 meow_media_lyric。 */ static void report_lyric_chunked(const char *app, uint32_t seq, uint16_t kind, const char *text, size_t cmax) { if (!text) text = ""; size_t total = strlen(text); if (total > cmax) total = cmax; size_t off = 0; do { size_t n = total - off; if (n > MEOW_LYRIC_CHUNK) n = MEOW_LYRIC_CHUNK; m_lyric_t ly; memset(&ly, 0, sizeof ly); snprintf(ly.app_id, sizeof ly.app_id, "%s", app); ly.seq = seq; ly.kind = kind; ly.total = (uint16_t)total; ly.off = (uint16_t)off; ly.len = (uint16_t)n; if (n) memcpy(ly.data, text + off, n); ly.data[n] = 0; int rc = meow_media_lyric(&ly); if (rc != 0) { printf(" (report chunk off=%zu rc=%d)\n", off, rc); } off += n; } while (off < total); } int main(void) { meow_media_ops ops; ops.send = test_send; ops.log = NULL; meow_media_init(&ops); const char *lrc = "[00:01.00]Hello\n[00:02.00]World\n[00:03.00]Bye\n"; const char *tlrc = "[00:01.00]你好\n[00:03.00]再见\n[99:99.99]ignore me\n"; /* ---- L1+L4 大片(跨分片)---- */ char big[4096]; int bi = 0; bi += snprintf(big + bi, sizeof big - bi, "[00:00.10]p0\n"); for (int i = 1; i <= 40; i++) /* 约 40 行 → >MEOW_LYRIC_CHUNK(980B) */ bi += snprintf(big + bi, sizeof big - bi, "[00:%02d.%02d]line%d\n", i / 60, i % 60, i); /* 注册会话(META 先行,否则 meow_media_lyric 丢弃) */ m_meta_t m; fill_meta(&m, "dev.player.ncm", MEOW_MEDIA_PLAYING, 1000, 120000); CHECK(meow_media_meta(&m, -1) == 0, "META registers session"); /* 上报歌词:seq=1(新代际),lrc 与 tlrc 各分片 */ report_lyric_chunked("dev.player.ncm", 1, 0, lrc, MEOW_MEDIA_LRC_MAX); report_lyric_chunked("dev.player.ncm", 1, 1, tlrc, MEOW_MEDIA_TLRC_MAX); const meow_media_session_t *s = meow_media_current(); CHECK(s && !strcmp(s->app_id, "dev.player.ncm"), "current() == dev.player.ncm"); CHECK(meow_media_lyric_valid(s) == 1, "L1: lyric_valid == 1"); CHECK(strcmp(meow_media_lyric_lrc(s), lrc) == 0, "L1: lrc matches"); CHECK(strcmp(meow_media_lyric_tlrc(s), tlrc) == 0, "L1: tlrc matches"); /* ---- L2 launcher 读取 → 解析 ---- */ int n = meow_lyric_parse(meow_media_lyric_lrc(s), meow_media_lyric_tlrc(s)); CHECK(n == 3, "L2: meow_lyric_parse count == 3"); CHECK(meow_lyric_count() == 3, "L2: count() == 3"); CHECK(strcmp(meow_lyric_line_text(0), "Hello") == 0, "L2: line0 text Hello"); CHECK(strcmp(meow_lyric_line_tl(0), "你好") == 0, "L2: line0 tl 你好 (2000ms 合并)"); CHECK(strcmp(meow_lyric_line_text(2), "Bye") == 0, "L2: line2 text Bye"); CHECK(strcmp(meow_lyric_line_tl(2), "再见") == 0, "L2: line2 tl 再见"); CHECK(meow_lyric_count() == 3, "L2: 无匹配翻译行被忽略(仍 3 行)"); /* ---- L3 播放位置推进 → 命中当前行 ---- */ uint32_t pA = meow_media_display_pos(s); int lineA = meow_lyric_line_at(pA); CHECK(lineA == 0, "L3: display_pos ≈ 1000ms → line 0 (Hello)"); usleep(600 * 1000); /* 播放中:display_pos 随墙钟推进 */ uint32_t pB = meow_media_display_pos(s); int lineB = meow_lyric_line_at(pB); CHECK(pB >= pA, "L3: display_pos advances (playing)"); CHECK(lineB >= lineA, "L3: line_at pos 增大 → 行号不降(随播放命中当前行)"); /* 再推进到 ≥3000ms 应到末行 */ usleep(1500 * 1000); uint32_t pC = meow_media_display_pos(s); CHECK(pC >= 3000u || lineB >= 2, "L3: pos 推进后到位(或已末行)"); CHECK(meow_lyric_line_at(pC) >= meow_lyric_line_at(pB), "L3: 行号单调不降"); /* ---- L4 大片分片重组(big lrc)---- */ meow_media_clear(NULL); fill_meta(&m, "dev.player.ncm", MEOW_MEDIA_PLAYING, 0, 120000); meow_media_meta(&m, -1); report_lyric_chunked("dev.player.ncm", 2, 0, big, MEOW_MEDIA_LRC_MAX); s = meow_media_current(); CHECK(s && meow_media_lyric_valid(s) == 1, "L4: big lyric_valid == 1"); int bn = meow_lyric_parse(meow_media_lyric_lrc(s), NULL); CHECK(bn > 40, "L4: 大片跨分片重组后解析行数 > 40"); CHECK(meow_lyric_line_at(0) == 0 && meow_lyric_line_at(10) == 0, "L4: 低 pos 命中第0行"); CHECK(meow_lyric_line_at(meow_lyric_line_time(bn - 1)) == bn - 1, "L4: 末行命中"); /* ---- L5 seq 代际切换(换歌)清旧词 ---- */ report_lyric_chunked("dev.player.ncm", 3, 0, "[00:05.00]New song line\n", MEOW_MEDIA_LRC_MAX); s = meow_media_current(); CHECK(meow_media_lyric_valid(s) == 1, "L5: 新代际仍 valid"); CHECK(strcmp(meow_media_lyric_lrc(s), "[00:05.00]New song line\n") == 0, "L5: 新词覆盖旧词"); CHECK(meow_media_lyric_tlrc(s)[0] == 0, "L5: 新代际清空旧翻译"); /* seq 不变重报:幂等(不清) */ report_lyric_chunked("dev.player.ncm", 3, 1, "[00:05.00]新歌\n", MEOW_MEDIA_TLRC_MAX); s = meow_media_current(); CHECK(strcmp(meow_media_lyric_tlrc(s), "[00:05.00]新歌\n") == 0, "L5: 同 seq 补翻译不清"); /* ---- L6 会话不存在 → -1;空 app_id → -1 ---- */ m_lyric_t empty; memset(&empty, 0, sizeof empty); snprintf(empty.app_id, sizeof empty.app_id, "no.session.app"); empty.seq = 9; empty.kind = 0; empty.total = 0; empty.off = 0; empty.len = 0; CHECK(meow_media_lyric(&empty) == -1, "L6: 未注册会话 → -1"); memset(&empty, 0, sizeof empty); CHECK(meow_media_lyric(&empty) == -1, "L6: 空 app_id → -1"); /* ---- L7 多 App 独立 ---- */ fill_meta(&m, "com.player2", MEOW_MEDIA_PLAYING, 0, 180000); meow_media_meta(&m, -1); meow_media_clear("dev.player.ncm"); /* 只清 ncm */ report_lyric_chunked("com.player2", 1, 0, "[00:01.00]Second app\n", MEOW_MEDIA_LRC_MAX); s = meow_media_current(); CHECK(s && !strcmp(s->app_id, "com.player2"), "L7: current == com.player2"); CHECK(meow_media_lyric_valid(s) == 1, "L7: player2 歌词有效"); CHECK(strcmp(meow_media_lyric_lrc(s), "[00:01.00]Second app\n") == 0, "L7: player2 歌词独立"); /* ---- 空歌词(无词歌曲):上报空串 → valid=0 ---- */ report_lyric_chunked("com.player2", 2, 0, "", MEOW_MEDIA_LRC_MAX); report_lyric_chunked("com.player2", 2, 1, "", MEOW_MEDIA_TLRC_MAX); s = meow_media_current(); CHECK(meow_media_lyric_valid(s) == 0, "空词上报 → lyric_valid == 0"); printf("\n%s (%d fails)\n", g_fails ? "FAILED" : "ALL PASS", g_fails); return g_fails ? 1 : 0; }