feat(ncm §24): 流媒体客户端边下边播阶段3——.part断点续传(Range/206/200/416)+LRU防淘汰(.tmp/.part跳过)+失败重试1次退避
按 devdocs/plan/NCM_STREAM_PLAY.md §8 阶段3: - .part 断续传:中断留 .part,续传带 Range,206 追加 / 200 丢头段 / 416 删残留整首重下;URL 每次新拉 - LRU 防淘汰:evict/total_bytes 跳过 .tmp/.part(在播文件不被动过) - 失败重试:预缓冲前网络失败重试 1 次,退避 800ms,重试拿新签名 URL(风控:0 真实请求,未部署) - 顺手修:gb_to_bytes 返回 int 溢出致默认 10GB 上限失效 -> uint64_t - 验证:stream_sim(续传 PCM 金标准+全矩阵)/player_flow_sim(P9/P10/P11)/cache_evict_sim(case7/8)/test_ncm_core 宿主+qemu-mipsel 全 PASS;build_ncm.sh 0 warning;ncm_host 离屏正常 md5 ncm f8f316a1e6f3bdd8f6b5921fdbb3f3a2
This commit is contained in:
1 parent
fcff0b0ac1
commit
19e8fc93ed
14 files changed
+735
-93
No files matched your search
@@ -195,6 +195,96 @@ int main(void)
|
||||
CHECK(ncm_cache_total_bytes(&g_c) == 0, "total=%llu after clear (expect 0)",
|
||||
(unsigned long long)ncm_cache_total_bytes(&g_c));
|
||||
|
||||
/* ⑦ 阶段3:播放中的 .tmp / 中断残体 .part 不被 LRU 淘汰,且不计入池占用。
|
||||
* 「正在下载播放的文件」= .tmp(写穿缓存态);「上次中断的可续传残体」= .part。
|
||||
* 二者在 evict/total_bytes 中被跳过——防正在听的歌被缓存上限挤掉(§5.1)。 */
|
||||
printf("== case 7: evict skips .tmp/.part (playing file not evicted; stage3 §5.1) ==\n");
|
||||
cleanup();
|
||||
ncm_cache_set_limit_gb(&g_c, 1); /* 1GB(int 内,避免既有 gb_to_bytes int 溢出 bug 干扰本用例) */
|
||||
make_cached("a", ".mp3", mb(600), 1000); /* 600MB 最旧 */
|
||||
make_cached("b", ".mp3", mb(600), 1100); /* 600MB */
|
||||
/* 在途 .tmp(边下边播写穿缓存态):song_b.mp3.tmp */
|
||||
{
|
||||
char p[600];
|
||||
ncm_cache_path(&g_c, "b", ".mp3", p, sizeof p);
|
||||
strncat(p, ".tmp", sizeof p - strlen(p) - 1);
|
||||
int fd = open(p, O_WRONLY | O_CREAT, 0644);
|
||||
if (ftruncate(fd, (off_t)mb(300)) != 0) printf(" truncate tmp err\n");
|
||||
close(fd);
|
||||
}
|
||||
/* 中断残体 .part(可续传):song_d.mp3.part */
|
||||
{
|
||||
char p[600];
|
||||
ncm_cache_path(&g_c, "d", ".mp3", p, sizeof p);
|
||||
strncat(p, ".part", sizeof p - strlen(p) - 1);
|
||||
int fd = open(p, O_WRONLY | O_CREAT, 0644);
|
||||
if (ftruncate(fd, (off_t)mb(300)) != 0) printf(" truncate part err\n");
|
||||
close(fd);
|
||||
}
|
||||
CHECK(ncm_cache_total_bytes(&g_c) == mb(1200),
|
||||
"total excludes .tmp/.part (=%lluMB, expect 1200MB)",
|
||||
(unsigned long long)(ncm_cache_total_bytes(&g_c) / (1024*1024)));
|
||||
ncm_cache_evict(&g_c);
|
||||
CHECK(!cached_exists("a", ".mp3"), "oldest committed a evicted");
|
||||
CHECK(cached_exists("b", ".mp3"), "b kept (<=limit)");
|
||||
/* .tmp/.part 文件仍在(未被打包进 LRU 淘汰) */
|
||||
{
|
||||
char tp[600], pp[600];
|
||||
ncm_cache_path(&g_c, "b", ".mp3", tp, sizeof tp); strncat(tp, ".tmp", sizeof tp-strlen(tp)-1);
|
||||
ncm_cache_path(&g_c, "d", ".mp3", pp, sizeof pp); strncat(pp, ".part", sizeof pp-strlen(pp)-1);
|
||||
struct stat st;
|
||||
CHECK(stat(tp, &st) == 0, "playing .tmp survives evict");
|
||||
CHECK(stat(pp, &st) == 0, "resume .part survives evict");
|
||||
}
|
||||
CHECK(ncm_cache_total_bytes(&g_c) == mb(600),
|
||||
"total after evict excludes partials (=%lluMB, expect 600MB)",
|
||||
(unsigned long long)(ncm_cache_total_bytes(&g_c) / (1024*1024)));
|
||||
|
||||
/* ⑧ 阶段3:部分文件 API 快检——part_size 检测 / open_resume 追加 / abort_part 保留 /
|
||||
* abort 删除 的命名契约(供 player_manager 续传链路)。 */
|
||||
printf("== case 8: .part resume API contract (stage3 §5.2) ==\n");
|
||||
cleanup();
|
||||
{
|
||||
/* 用 ncm_cache_open_resume 打开上一步遗留 .part 无(已 cleanup)——先造一个真 .part */
|
||||
char p[600];
|
||||
ncm_cache_path(&g_c, "r", ".mp3", p, sizeof p);
|
||||
snprintf(p + strlen(p), sizeof p - strlen(p), ".part");
|
||||
int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) { (void)!write(fd, "ABCDEF", 6); close(fd); }
|
||||
uint64_t sz = 0;
|
||||
CHECK(ncm_cache_part_size(&g_c, "r", ".mp3", &sz) == NCM_OK && sz == 6,
|
||||
"part_size reports 6 (got %llu)", (unsigned long long)sz);
|
||||
ncm_cache_writer_t *w = NULL;
|
||||
CHECK(ncm_cache_open_resume(&g_c, "r", ".mp3", &w) == NCM_OK, "open_resume ok");
|
||||
CHECK(ncm_cache_write(w, (const unsigned char *)"GH", 2) == NCM_OK, "resume append write");
|
||||
CHECK(ncm_cache_commit(w) == NCM_OK, "resume commit (rename .part->final)");
|
||||
/* commit 后 .part 转正:lookup 命中且内容=ABCDEFGH,.part 消失 */
|
||||
char path[600];
|
||||
CHECK(ncm_cache_lookup(&g_c, "r", ".mp3", path, sizeof path) == NCM_OK, "resume commit lookup hit");
|
||||
FILE *f = fopen(path, "rb");
|
||||
char buf[16] = {0}; int n = 0;
|
||||
if (f) { n = (int)fread(buf, 1, sizeof buf - 1, f); fclose(f); }
|
||||
CHECK(n == 8 && memcmp(buf, "ABCDEFGH", 8) == 0, "committed bytes=%.8s (expect ABCDEFGH)", buf);
|
||||
CHECK(ncm_cache_part_size(&g_c, "r", ".mp3", &sz) == NCM_ERR_NOENT, ".part gone after commit");
|
||||
/* abort_part 保留残体 / abort 删除:造两个 .part 分别走两路 */
|
||||
ncm_cache_path(&g_c, "s", ".mp3", p, sizeof p);
|
||||
snprintf(p + strlen(p), sizeof p - strlen(p), ".part");
|
||||
fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) { (void)!write(fd, "1234", 4); close(fd); }
|
||||
CHECK(ncm_cache_open_resume(&g_c, "s", ".mp3", &w) == NCM_OK, "s open_resume");
|
||||
CHECK(ncm_cache_abort_part(w) == NCM_OK, "s abort_part");
|
||||
struct stat st2;
|
||||
CHECK(stat(p, &st2) == 0, "abort_part keeps .part (resumable)");
|
||||
CHECK(ncm_cache_open_resume(&g_c, "s", ".mp3", &w) == NCM_OK, "s reopen_resume");
|
||||
CHECK(ncm_cache_abort(w) == NCM_OK, "s abort (delete)");
|
||||
CHECK(stat(p, &st2) != 0, "abort deletes .part");
|
||||
/* part_remove 显式删除 */
|
||||
fd = open(p, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (fd >= 0) { (void)!write(fd, "x", 1); close(fd); }
|
||||
CHECK(ncm_cache_part_remove(&g_c, "s", ".mp3") == NCM_OK, "part_remove ok");
|
||||
CHECK(stat(p, &st2) != 0, "part_remove deleted .part");
|
||||
}
|
||||
|
||||
cleanup();
|
||||
printf("%s\n", g_fail ? "RESULT: FAIL" : "RESULT: PASS");
|
||||
return g_fail ? 1 : 0;
|
||||
|
||||
Reference in new issue
Block a user