feat(ncm): 新写入的缓存文件嵌入歌曲元数据(FLAC/MP3,commit 前流式重写,失败降级裸流)
用户需求:保留元数据分离的同时把歌曲元数据嵌进缓存文件(否则外部播放器读不到任何信息)。
- 接入点 player_manager:下载完整(dl_rc==OK)→ stream_src_finish(读端先自然收尾)→
ncm_tag_embed_file(.tmp, 实际扩展名, {name/art_name/album_name}) → dispose join 之后才
commit。tag 重写是同目录 .tag.tmp + rename 换 inode,解码端全程持旧 fd,播放/seek/
.part 续传字节语义零改动。
- 容器按实际 magic 分支(ncm_tag.c);峰值内存 64KB;失败只记 WARN、commit 裸流,
播放主链路不受影响(用户红线)。
- 元数据分离体系不动:<cache_dir>/meta/<id>.bin 旁路照旧写读;LRU 只 stat 文件,
无 size/etag 索引需回填(重写后 stat 自然一致)。
- test_ncm_core 新增 test_cache_tag:writer 写裸流 → 嵌入 ID3v2.3 / VORBIS_COMMENT →
读回 → ncm_cache_scan(hires 名 + 320k mp3 体 → 实际档 EXHIGH,名称来自文件内 tag)
→ ncm_cache_remove → ncm_cache_clear。
- REPORT §38:设计/内存估算/验证/诚实遗留(本机缺 vendor 与交叉工具链,未跑整链与真机)。
This commit is contained in:
1 parent
f392ee5c48
commit
39465ea1ef
3 files changed
+234
-1
No files matched your search
@@ -168,6 +168,143 @@ static void test_cache(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- M5:缓存文件内元数据嵌入 + 已缓存列表扫描 ----------
|
||||
* 样本全手工构造(不引第三方库):FLAC = "fLaC" + STREAMINFO(34B) + 假帧;MP3 = 帧同步 + 假帧。
|
||||
* 断言链路:writer 写裸流 → ncm_tag_embed_file 嵌 tag → ncm_tag_read_file 读回 →
|
||||
* ncm_cache_scan 列出(名称来自文件内 tag、档位=实际档)→ ncm_cache_remove/clear。 */
|
||||
static void make_flac_bytes(unsigned char *buf, size_t *len, int bps)
|
||||
{
|
||||
size_t o = 0;
|
||||
memset(buf, 0, 34 + 12 + 512);
|
||||
memcpy(buf + o, "fLaC", 4); o += 4;
|
||||
buf[o++] = 0x80; buf[o++] = 0x00; buf[o++] = 0x00; buf[o++] = 34; /* STREAMINFO, last */
|
||||
unsigned char *si = buf + o;
|
||||
si[0] = 0x10; si[1] = 0x00; si[2] = 0x10; si[3] = 0x00;
|
||||
si[10] = 0x0A; si[11] = 0xC4; /* 44100 */
|
||||
si[12] = (unsigned char)(0x40 | 0x02 | (((bps - 1) >> 4) & 0x01)); /* 2ch + bps 高位 */
|
||||
si[13] = (unsigned char)(((bps - 1) & 0x0F) << 4);
|
||||
si[16] = 0xAC; si[17] = 0x44; /* total samples */
|
||||
o += 34;
|
||||
for (int i = 0; i < 512; i++) buf[o++] = (unsigned char)(0xF8 ^ i);
|
||||
*len = o;
|
||||
}
|
||||
|
||||
static void test_cache_tag(void) {
|
||||
printf("== cache tag embed + scan (M5) ==\n");
|
||||
ncm_cache_t c;
|
||||
char dir[] = "/tmp/ncm_tag_cache";
|
||||
{
|
||||
char cmd[160];
|
||||
snprintf(cmd, sizeof cmd, "rm -rf %s; mkdir -p %s", dir, dir);
|
||||
if (system(cmd) != 0) { /* best-effort */ }
|
||||
}
|
||||
ncm_cache_init(&c, dir);
|
||||
|
||||
/* ① MP3 条目:文件名带请求档 hires、实际是 320kbps mp3(真机 song_<id>_hires.mp3 形态) */
|
||||
{
|
||||
unsigned char mp3[600];
|
||||
memset(mp3, 0x11, sizeof mp3);
|
||||
mp3[0] = 0xFF; mp3[1] = 0xFB; mp3[2] = 0xE0; mp3[3] = 0x00; /* 320kbps / 44.1kHz */
|
||||
ncm_cache_writer_t *w;
|
||||
CHECKRC(ncm_cache_open(&c, "171650_hires", "mp3", &w), "open cache entry (hires name/mp3 body)");
|
||||
CHECKRC(ncm_cache_write(w, mp3, sizeof mp3), "write raw mp3");
|
||||
CHECKRC(ncm_cache_commit(w), "commit");
|
||||
|
||||
char path[NCM_CACHE_DIR_MAX + 64];
|
||||
CHECKRC(ncm_cache_lookup(&c, "171650_hires", "mp3", path, sizeof path), "lookup entry");
|
||||
ncm_tag_meta_t m, got;
|
||||
memset(&m, 0, sizeof m);
|
||||
snprintf(m.title, sizeof m.title, "晴天");
|
||||
snprintf(m.artist, sizeof m.artist, "周杰伦");
|
||||
snprintf(m.album, sizeof m.album, "叶惠美");
|
||||
CHECKRC(ncm_tag_embed_file(path, "mp3", &m), "embed ID3v2.3");
|
||||
memset(&got, 0, sizeof got);
|
||||
CHECKRC(ncm_tag_read_file(path, &got), "read back embedded tag");
|
||||
CHECK(strcmp(got.title, "晴天") == 0 && strcmp(got.artist, "周杰伦") == 0 &&
|
||||
strcmp(got.album, "叶惠美") == 0,
|
||||
"tag roundtrip (title=%s artist=%s)", got.title, got.artist);
|
||||
struct stat st;
|
||||
CHECK(stat(path, &st) == 0 && st.st_size > (off_t)sizeof mp3,
|
||||
"file grew by tag bytes (%ld > %zu)", (long)st.st_size, sizeof mp3);
|
||||
ncm_audio_info_t ai;
|
||||
CHECK(ncm_audio_probe_file(path, &ai) == NCM_OK &&
|
||||
strcmp(ai.container, "mp3") == 0 && ai.bitrate_kbps == 320,
|
||||
"probe actual container after tagging (%s %dkbps)", ai.container, ai.bitrate_kbps);
|
||||
}
|
||||
|
||||
/* ② FLAC 条目:24bit/44.1k → 实际档应判 Hi-Res(请求档写 hires 亦一致) */
|
||||
{
|
||||
unsigned char fl[700];
|
||||
size_t flen = 0;
|
||||
make_flac_bytes(fl, &flen, 24);
|
||||
ncm_cache_writer_t *w;
|
||||
CHECKRC(ncm_cache_open(&c, "2001_hires", "flac", &w), "open flac cache entry");
|
||||
CHECKRC(ncm_cache_write(w, fl, flen), "write raw flac");
|
||||
CHECKRC(ncm_cache_commit(w), "commit flac");
|
||||
char path[NCM_CACHE_DIR_MAX + 64];
|
||||
CHECKRC(ncm_cache_lookup(&c, "2001_hires", "flac", path, sizeof path), "lookup flac entry");
|
||||
ncm_tag_meta_t m, got;
|
||||
memset(&m, 0, sizeof m);
|
||||
snprintf(m.title, sizeof m.title, "夜曲");
|
||||
snprintf(m.artist, sizeof m.artist, "周杰伦");
|
||||
CHECKRC(ncm_tag_embed_file(path, "flac", &m), "embed VORBIS_COMMENT");
|
||||
memset(&got, 0, sizeof got);
|
||||
CHECKRC(ncm_tag_read_file(path, &got), "read back flac tag");
|
||||
CHECK(strcmp(got.title, "夜曲") == 0, "flac tag roundtrip (title=%s)", got.title);
|
||||
}
|
||||
|
||||
/* ③ 扫描:两条;名称来自文件内 tag;档位=实际档(mp3 320k → EXHIGH,不显示请求档 hires) */
|
||||
{
|
||||
ncm_cache_item_t items[8];
|
||||
int n = ncm_cache_scan(&c, items, 8);
|
||||
CHECK(n == 2, "scan lists 2 cached entries (got %d)", n);
|
||||
int seen_mp3 = 0, seen_flac = 0;
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (strcmp(items[i].id, "171650") == 0) {
|
||||
seen_mp3 = 1;
|
||||
CHECK(strcmp(items[i].level, "hires") == 0, "requested level kept in name (%s)",
|
||||
items[i].level);
|
||||
CHECK(items[i].actual_level == NCM_LEVEL_EXHIGH,
|
||||
"actual level = EXHIGH for 320k mp3 (got %d)", items[i].actual_level);
|
||||
CHECK(strcmp(items[i].name, "晴天") == 0 &&
|
||||
strcmp(items[i].artist, "周杰伦") == 0,
|
||||
"scan name/artist from embedded tag (%s/%s)", items[i].name, items[i].artist);
|
||||
CHECK(strcmp(items[i].ext, "mp3") == 0 && items[i].size > 0, "scan ext/size");
|
||||
} else if (strcmp(items[i].id, "2001") == 0) {
|
||||
seen_flac = 1;
|
||||
CHECK(items[i].actual_level >= NCM_LEVEL_HIRES,
|
||||
"actual level = Hi-Res+ for 24bit flac (got %d)", items[i].actual_level);
|
||||
CHECK(strcmp(items[i].name, "夜曲") == 0, "flac scan name (%s)", items[i].name);
|
||||
}
|
||||
}
|
||||
CHECK(seen_mp3 && seen_flac, "both entries present (mp3=%d flac=%d)", seen_mp3, seen_flac);
|
||||
}
|
||||
|
||||
/* ④ 单曲删除 + LRU 淘汰(带 tag 文件照常计入池)+ 清空 */
|
||||
{
|
||||
char path[NCM_CACHE_DIR_MAX + 64];
|
||||
CHECKRC(ncm_cache_remove(&c, "171650", "hires", "mp3"), "remove single entry");
|
||||
CHECK(ncm_cache_lookup(&c, "171650_hires", "mp3", path, sizeof path) == NCM_ERR_NOENT,
|
||||
"removed entry gone");
|
||||
ncm_cache_item_t items[8];
|
||||
CHECK(ncm_cache_scan(&c, items, 8) == 1, "scan after remove -> 1");
|
||||
/* 兼容性:嵌入 tag 的文件照常计入 LRU 池、按 mtime 被淘汰(旧无 tag 条目同理) */
|
||||
CHECK(ncm_cache_total_bytes(&c) > 0, "tagged entry counted in LRU pool");
|
||||
c.limit_bytes = 1; /* 任何条目都超限 → 必淘汰 */
|
||||
CHECKRC(ncm_cache_evict(&c), "evict with tagged entry");
|
||||
CHECK(ncm_cache_scan(&c, items, 8) == 0, "tagged entry evicted by LRU");
|
||||
ncm_cache_set_limit_gb(&c, 10);
|
||||
{
|
||||
ncm_cache_writer_t *w2;
|
||||
ncm_cache_open(&c, "3001_exhigh", "mp3", &w2);
|
||||
ncm_cache_write(w2, (const unsigned char *)"X", 1);
|
||||
ncm_cache_commit(w2);
|
||||
}
|
||||
CHECKRC(ncm_cache_clear(&c), "clear cache pool");
|
||||
CHECK(ncm_cache_scan(&c, items, 8) == 0, "scan after clear -> 0");
|
||||
}
|
||||
}
|
||||
|
||||
static void test_qr(void) {
|
||||
printf("== qrcodegen ==\n");
|
||||
unsigned char mods[177 * 177];
|
||||
@@ -541,6 +678,7 @@ int main(int argc, char **argv) {
|
||||
test_crypto_vectors();
|
||||
test_store_cookie();
|
||||
test_cache();
|
||||
test_cache_tag();
|
||||
test_qr();
|
||||
test_cookie_merge();
|
||||
test_eapi_musicu_hdr();
|
||||
|
||||
Reference in new issue
Block a user