Files
AskaEth 44a790dc1d fix(ncm §33): 流媒专项批六项——流式SIGSEGV防御日志/档位保真(无损级必须flac+显示档按实际)/封面4xx不重试/收藏显隐/缓存命名迁移/topbar位图图标
- §2① ncm_api.c level_attempt: 无损级(hires/lossless及以上)命中必须 type=flac,返回 mp3 视为该档不可用续降
  (修「hires 档静默给 mp3 被当命中」)
- §2② player_manager.c eff_level_for_resp: 显示档按响应实际格式/码率——mp3 绝不显示 Hi-Res(≤128k→标准, 否则极高),
  flac 采信链 eff; br 未知钳 ≤EXHIGH
- §1 流式 SIGSEGV(《十面埋伏》sig=11) 审计+UBSAN 探针未复现 → 落地防御日志(dl start/done/interrupt + play done fin/exp/fail/underrun)
- §3 cover.c: download_cover 回填 rc/status; 确定性 4xx(404等)不重试立即跳下一张, 仅瞬时错误退避(rc=%d status=%d 日志)
- §4 playlist.c: pl_show_fav 我创建(is_collected==0)隐藏收藏钮, 我收藏保留, 搜索歌单保守显示(评论槽并入无空洞)
- §5 ncm_cache.c 双点→单点迁移 + player_manager song_local_file 旧键→当前档位键(不重下)
- §6 home.c: topbar 动作 ⚙↻⌕ 迁移 sb_icons 位图(meow_topappbar_set_item_icon, 修 4k 字库豆腐)
- 测试: test_ncm_core 新 test_grade_flac_gate + test_cache 双点迁移断言; degrade_sim/player_flow_sim 桩 flac 化
- 验证: stream/player_flow/window/search/cache_evict/degrade sim 全 PASS + test_ncm_core host+mipsel(qemu) + mem_sim + build_ncm.sh 0 warning
- 新 ncm md5 cb818786 (text 1017110/bss 1362500), 未部署留主会话; 0 真实 NCM 业务请求
2026-09-02 04:20:46 +08:00

256 lines
13 KiB
C
Raw Permalink 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.
/* bench/degrade_sim.c — song_url_auto 全档降级链离线矩阵(§20,0 真实请求)。
*
* 驱动真实 core/ncm_api.c 的 ncm_api_song_url_auto_f(注入假 try_fn,按「可用下限」
* 模型应答:level <= avail_from 才给 url)。覆盖:
* ① desired × avail_from 全矩阵(9 档 × 9 档 + 全不可用):命中档 = min(desired, avail_from),
* 请求数 = desired - 命中 + 1(逐级试,不多不少);
* ② desired 越界钳制(DOLBY=16 → SURROUND;负数 → STANDARD);
* ③ url 在但 type 未知(如 aac)→ 跳过继续降;
* ④ type 缺失时按 url 扩展名收 flac;
* ⑤ out_level 回填值与命中档一致(「下首恢复用户档」由调用方重传 desired 保证,
* 链内不记忆——每次调用独立从 desired 起步)。
*
* 编译(宿主,ncm_app 目录下,先跑过 build_core.sh 产出 out/core-host/ 全部对象):
* gcc -O2 -Wall -Wextra -DJSMN_HEADER -Icore -Ivendor/jsmn \
* bench/degrade_sim.c out/core-host/全部.o \
* -Lout/mbedtls/host -lmbedtls -lmbedx509 -lmbedcrypto -o degrade_sim -lpthread -lm
* 运行:./degrade_sim (PASS/FAIL 退出码)
*/
#include "ncm.h"
#include <stdio.h>
#include <string.h>
static int g_fail = 0;
#define CHECK(cond, fmt, ...) do { \
if (cond) printf(" PASS " fmt "\n", ##__VA_ARGS__); \
else { printf(" FAIL " fmt "\n", ##__VA_ARGS__); g_fail = 1; } \
} while (0)
/* ---- 假逐档取址:level <= g_avail_from 给 url(type 可配),记录每档请求次数 ---- */
static int g_avail_from; /* 可用下限;-1=全档不可用 */
static const char *g_type = "mp3"; /* 应答 type(""=缺省看扩展名) */
static const char *g_resp_level = NULL; /* 非 NULL:响应 data[0].level 固定串(服务端静默降档模拟) */
static int g_calls[NCM_LEVEL_SURROUND + 1];
static int fake_try(const char *dev, const char *ck, const char *id, int level,
ncm_play_url_t *out)
{
(void)dev; (void)ck; (void)id;
if (level >= 0 && level <= NCM_LEVEL_SURROUND) g_calls[level]++;
memset(out, 0, sizeof *out);
if (level > g_avail_from) return NCM_OK; /* 该档不可用:url 空 */
snprintf(out->url, sizeof out->url, "http://sim/%lld.%s", (long long)12345,
(level >= NCM_LEVEL_LOSSLESS) ? "flac" : "mp3");
/* §2① 档位保真:无损级及以上,服务端 encodeType=flac → 应答 type=flac(真实);g_type=""
* (缺失)时保留空串,交给 level_attempt 按 url 扩展名推断(测试该推断路径)。 */
if (level >= NCM_LEVEL_LOSSLESS && g_type[0])
snprintf(out->type, sizeof out->type, "flac");
else
snprintf(out->type, sizeof out->type, "%s", g_type);
if (g_resp_level) snprintf(out->level, sizeof out->level, "%s", g_resp_level);
out->br = 320000;
out->size = 9000000;
return NCM_OK;
}
static int g_bad_type_from = -1; /* >=0: 该档以上给 aac(未知格式应跳过) */
static int fake_try_badtype(const char *dev, const char *ck, const char *id, int level,
ncm_play_url_t *out)
{
int r = fake_try(dev, ck, id, level, out);
if (r == NCM_OK && out->url[0] && level >= g_bad_type_from)
snprintf(out->type, sizeof out->type, "aac");
return r;
}
static void reset_calls(void) { memset(g_calls, 0, sizeof g_calls); }
static int total_calls(void)
{
int n = 0;
for (int i = 0; i <= NCM_LEVEL_SURROUND; i++) n += g_calls[i];
return n;
}
/* ---- §20.3 账户/fee 感知降级链(auto_av_f)期望模型 ----
* cap(vip):NONE→EXHIGH / CVIP→HIRES / SVIP→SURROUND(与 account.c account_level_cap 同口径)。
* start = clamp(desired, STANDARD, cap);只试 gate(lv)<=vip 的档;落在 lv<=avail 的最近可用档。 */
static int cap_for_vip(int vip) {
switch (vip) {
case NCM_VIP_SVIP: return NCM_LEVEL_SURROUND;
case NCM_VIP_CVIP: return NCM_LEVEL_HIRES;
default: return NCM_LEVEL_EXHIGH;
}
}
static int expect_land(int vip, int desired, int avail) {
int cap = cap_for_vip(vip);
int start = desired > cap ? cap : desired;
if (start < NCM_LEVEL_STANDARD) start = NCM_LEVEL_STANDARD;
for (int lv = start; lv >= NCM_LEVEL_STANDARD; lv--)
if (ncm_api_level_gate(lv) <= vip && lv <= avail) return lv;
return -1;
}
static int expect_tried(int vip, int desired, int best) {
int cap = cap_for_vip(vip);
int start = desired > cap ? cap : desired;
if (start < NCM_LEVEL_STANDARD) start = NCM_LEVEL_STANDARD;
int n = 0;
for (int lv = start; lv >= NCM_LEVEL_STANDARD; lv--)
if (ncm_api_level_gate(lv) <= vip && (best < 0 || lv >= best)) n++;
return n;
}
int main(void)
{
ncm_play_url_t u;
int eff;
printf("== 1) desired × avail_from 全矩阵 ==\n");
int cases = 0, bad = 0;
for (int desired = NCM_LEVEL_STANDARD; desired <= NCM_LEVEL_SURROUND; desired++) {
for (int avail = NCM_LEVEL_STANDARD; avail <= NCM_LEVEL_SURROUND; avail++) {
reset_calls();
g_avail_from = avail; g_type = "mp3";
int rc = ncm_api_song_url_auto_f("dev", "ck", "1", desired, &u, &eff, fake_try);
int want = (avail < desired) ? avail : desired; /* 期望命中 = min */
int ok = (rc == NCM_OK) && (eff == want) && u.url[0] &&
(total_calls() == desired - want + 1);
/* 高档(>命中)各恰好 1 次,低档不请求 */
for (int lv = want + 1; lv <= desired; lv++) ok = ok && (g_calls[lv] == 1);
for (int lv = 0; lv < want; lv++) ok = ok && (g_calls[lv] == 0);
cases++;
if (!ok) {
bad++;
printf(" FAIL desired=%d(%s) avail=%d -> rc=%d eff=%d calls=%d (want %d/%d)\n",
desired, ncm_api_level_str(desired), avail, rc, eff, total_calls(),
want, desired - want + 1);
g_fail = 1;
}
}
/* 全不可用 → NOTFOUND,且恰好 desired+1 次请求 */
reset_calls();
g_avail_from = -1; g_type = "mp3";
int rc = ncm_api_song_url_auto_f("dev", "ck", "1", desired, &u, &eff, fake_try);
int ok = (rc == NCM_ERR_NOTFOUND) && (eff == -1) &&
(total_calls() == desired - NCM_LEVEL_STANDARD + 1);
cases++;
if (!ok) {
bad++;
printf(" FAIL desired=%d all-unavail -> rc=%d eff=%d calls=%d\n",
desired, rc, eff, total_calls());
g_fail = 1;
}
}
CHECK(bad == 0, "matrix %d cases all pass (desired 9 x avail 9 + 全不可用 9)", cases);
printf("== 2) desired 越界钳制 ==\n");
reset_calls();
g_avail_from = NCM_LEVEL_SURROUND; g_type = "mp3";
CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_DOLBY, &u, &eff, fake_try) == NCM_OK &&
eff == NCM_LEVEL_SURROUND && total_calls() == 1 && g_calls[NCM_LEVEL_SURROUND] == 1,
"desired=DOLBY(16) clamped to SURROUND (eff=%d calls=%d)", eff, total_calls());
reset_calls();
CHECK(ncm_api_song_url_auto_f("d", "c", "1", -7, &u, &eff, fake_try) == NCM_OK &&
eff == NCM_LEVEL_STANDARD && total_calls() == 1,
"desired=-7 clamped to STANDARD (eff=%d)", eff);
printf("== 3) 未知 type 跳过继续降 ==\n");
reset_calls();
g_avail_from = NCM_LEVEL_SURROUND; g_bad_type_from = NCM_LEVEL_HIRES;
/* hires 及以上 url 在但 type=aac → 跳过,落在 lossless */
CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_SKY, &u, &eff, fake_try_badtype)
== NCM_OK && eff == NCM_LEVEL_LOSSLESS,
"aac above hires -> land lossless (eff=%d)", eff);
printf("== 4) type 缺失按 url 扩展名收 flac ==\n");
reset_calls();
g_avail_from = NCM_LEVEL_LOSSLESS; g_type = "";
CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_LOSSLESS, &u, &eff, fake_try)
== NCM_OK && strcmp(u.type, "flac") == 0,
"type empty + .flac url -> type backfilled flac");
printf("== 5) out_level 每次独立(下首恢复用户档语义) ==\n");
reset_calls();
g_avail_from = NCM_LEVEL_STANDARD; g_type = "mp3";
ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_SURROUND, &u, &eff, fake_try);
int first_eff = eff;
reset_calls();
g_avail_from = NCM_LEVEL_SURROUND;
ncm_api_song_url_auto_f("d", "c", "2", NCM_LEVEL_SURROUND, &u, &eff, fake_try);
CHECK(first_eff == NCM_LEVEL_STANDARD && eff == NCM_LEVEL_SURROUND,
"prev degraded to standard, next song back at surround (eff %d -> %d)",
first_eff, eff);
printf("== 6) 响应 level 静默降档采信(data[0].level < 请求档) ==\n");
reset_calls();
g_avail_from = NCM_LEVEL_SURROUND; g_type = "flac"; g_resp_level = "exhigh";
CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_HIRES, &u, &eff, fake_try) == NCM_OK &&
eff == NCM_LEVEL_EXHIGH,
"req hires, resp level=exhigh -> eff=exhigh (got %d)", eff);
reset_calls();
g_resp_level = "sky"; /* sky 族有歧义:不采信,保守取请求档 */
CHECK(ncm_api_song_url_auto_f("d", "c", "1", NCM_LEVEL_SURROUND, &u, &eff, fake_try) == NCM_OK &&
eff == NCM_LEVEL_SURROUND,
"req surround, resp level=sky (歧义) -> eff=surround (got %d)", eff);
g_resp_level = NULL;
printf("== 7) auto_av_f fee 门禁(§20.4:fee∈{1,4} 且非 CVIP → 零请求 NCM_ERR_VIP)==\n");
reset_calls(); g_avail_from = NCM_LEVEL_SURROUND; g_type = "mp3";
CHECK(ncm_api_song_url_auto_av_f("d", "c", "1", NCM_VIP_NONE, 1, NCM_LEVEL_HIRES, &u, &eff, fake_try)
== NCM_ERR_VIP && total_calls() == 0,
"fee=1 + NONE -> NCM_ERR_VIP (calls=%d)", total_calls());
CHECK(ncm_api_song_url_auto_av_f("d", "c", "1", NCM_VIP_NONE, 4, NCM_LEVEL_HIRES, &u, &eff, fake_try)
== NCM_ERR_VIP && total_calls() == 0,
"fee=4 + NONE -> NCM_ERR_VIP (calls=%d)", total_calls());
CHECK(ncm_api_song_url_auto_av_f("d", "c", "1", NCM_VIP_CVIP, 1, NCM_LEVEL_HIRES, &u, &eff, fake_try)
== NCM_OK, "fee=1 + CVIP -> 放行");
CHECK(ncm_api_song_url_auto_av_f("d", "c", "1", NCM_VIP_NONE, 8, NCM_LEVEL_HIRES, &u, &eff, fake_try)
== NCM_OK, "fee=8 + NONE -> 放行(免费听低音质)");
CHECK(ncm_api_song_url_auto_av_f("d", "c", "1", NCM_VIP_NONE, 0, NCM_LEVEL_HIRES, &u, &eff, fake_try)
== NCM_OK, "fee=0 + NONE -> 放行");
printf("== 8) auto_av_f 全矩阵: account_vip 3 x desired 9 x avail 9(钳顶+门槛+最近可用档)==\n");
int av_bad = 0, av_cases = 0;
for (int vip = NCM_VIP_NONE; vip <= NCM_VIP_SVIP; vip++) {
for (int desired = NCM_LEVEL_STANDARD; desired <= NCM_LEVEL_SURROUND; desired++) {
for (int avail = NCM_LEVEL_STANDARD; avail <= NCM_LEVEL_SURROUND; avail++) {
reset_calls();
g_avail_from = avail; g_type = "mp3";
int rc = ncm_api_song_url_auto_av_f("dev", "ck", "1", vip, 0, desired, &u, &eff, fake_try);
int want = expect_land(vip, desired, avail);
int ok = (want >= 0 && rc == NCM_OK && eff == want && u.url[0] &&
total_calls() == expect_tried(vip, desired, want));
av_cases++;
if (!ok) {
av_bad++;
printf(" FAIL vip=%d desired=%d(%s) avail=%d -> rc=%d eff=%d calls=%d (want %d/%d)\n",
vip, desired, ncm_api_level_str(desired), avail, rc, eff, total_calls(),
want, expect_tried(vip, desired, want));
g_fail = 1;
}
}
}
}
CHECK(av_bad == 0, "auto_av matrix %d cases all pass (vip 3 x desired 9 x avail 9)", av_cases);
printf("== 9) 真机场景复现:CVIP + 设置Hi-Res + 歌曲「有SQ无Hi-Res」→ 落无损SQ ==\n");
reset_calls();
g_avail_from = NCM_LEVEL_LOSSLESS; /* 歌曲只提供到 SQ(及更低)→ 无 Hi-Res */
g_type = "flac"; /* SQ 以 flac 下发 */
int rc9 = ncm_api_song_url_auto_av_f("d", "c", "10", NCM_VIP_CVIP, 0, NCM_LEVEL_HIRES,
&u, &eff, fake_try);
CHECK(rc9 == NCM_OK && eff == NCM_LEVEL_LOSSLESS && strcmp(u.type, "flac") == 0,
"CVIP Hi-Res、有SQ无HiRes -> 落无损SQ (rc=%d eff=%d(%s) type=%s)",
rc9, eff, ncm_api_level_str(eff), u.type);
/* 对照:若账户VIP态未就绪(NONE),钳顶把Hi-Res压到极高,SQ被跳过(旧app clamp即此路径) */
reset_calls();
g_avail_from = NCM_LEVEL_LOSSLESS; g_type = "flac";
int rcn = ncm_api_song_url_auto_av_f("d", "c", "10", NCM_VIP_NONE, 0, NCM_LEVEL_HIRES,
&u, &eff, fake_try);
CHECK(rcn == NCM_OK && eff == NCM_LEVEL_EXHIGH,
"NONE+Hi-Res -> 钳到极高EXHIGH (rc=%d eff=%d) — 旧app clamp正是此路径致SQ被跳过",
rcn, eff);
printf(g_fail ? "== DEGRADE SIM: FAIL ==\n" : "== DEGRADE SIM: ALL PASS ==\n");
return g_fail;
}