fix(ncm §20.3): 降级链账户/fee感知 auto_av —— 修「SQ 被跳过掉标准」(297)

根因:ncm_api_song_url_auto 无账户感知,依赖调用方按会员钳顶;若账户 VIP 态未就绪/
错钳到 EXHIGH,降级链从 EXHIGH 起步,LOSSLESS(无损 SQ) 被跳过,直接掉「标准」。
修法:新增 ncm_api_song_url_auto_av[_f](§20.3 权威形态):fee∈{1,4} 且非 CVIP →
NCM_ERR_VIP 零请求;按 account_vip 自钳顶(NONE→EXHIGH/CVIP→HIRES/SVIP→SURROUND);
只试账户可访问档(ncm_api_level_gate<=vip);落在最近可用档。原 auto/auto_f 行为零回归。
逐级尝试重构为共享 level_attempt(type/url 接受+静默降档采信)。
测试(bench/degrade_sim.c):fee门禁5例 + auto_av矩阵 vip3×desired9×avail9=243例全PASS
+ 真机复现(CVIP+Hi-Res+有SQ无HiRes→落无损SQ flac);原90例零回归。
验证:test_ncm_core_host+qemu-mipsel 全PASS;build_core/build_ncm -Wall -Wextra 0 warning。
新 ncm md5 1064a1d686390cc159f18296ee2980ee(未部署,留主会话)。
遗留:player_manager.c 仍调 auto(不传vip/fee),应改请 auto_av(account_vip()+fee);
真机若仍掉标准需另查旧缓存 song_<id>_hires.mp3 复用或旧二进制。
This commit is contained in:
AskaEth committed 2026-09-02 02:41:09 +08:00
1 parent 55f0dfe91d
commit 33414de7c4
3 files changed
+175 -26

No files matched your search

+85
View File
@@ -66,6 +66,34 @@ static int total_calls(void)
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;
@@ -160,6 +188,63 @@ int main(void)
"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;
}