feat(media): 批3歌词数据源框架 — meow_media_session扩实时歌词(lrc/tlrc/seq)+MSG_META_LYRIC上报分片+ncm上报+launcher读meow_media_lyric_* 接批1/2;宿主L1-L7全过/ncm热更27554dd8,launcher待固件收编

This commit is contained in:
AskaEth committed 2026-09-03 19:49:28 +08:00
1 parent 7ca3e5bd84
commit 2e5b814059
8 files changed
+363 -4

No files matched your search

+185
View File
@@ -0,0 +1,185 @@
/* 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
#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;
}