Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/degrade_sim.c
T
AskaEth 33414de7c4 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 复用或旧二进制。
2026-09-02 02:41:09 +08:00

251 lines
13 KiB
C
Raw 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");
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;
}