feat: M6.10–M6.19 批量收编 + M6.19 推卡验收
- M7 launcher 重写 5 期(虚拟化宫格/meow_theme 单一权威/横幅动画/自定义 PIN 键盘) - OOM 通用化(cachegov 上收 SDK)、自动切歌根治、meowpanel jv_free 泄漏修复、Hello 滚动卡顿修复 - ncm 歌词 UI / 媒体通知封面 / 封面分辨率 640 - 5 bug 修复:ncm 后台化 UI 叠层+点击穿透、再进 ncm 桌面叠层、播放页新旧 UI 叠层抢刷脏区(meow_ui_gfx 恢复边沿整帧重画)、PIN 亮屏亮度跳(keys.c meow_panel_on 按 settings_brightness_clamped 恢复)、通知中心封面显歌单封面(cover.c save_cover_raw 加 is_song + 命中块守卫) - 四 App 统一重链新 meow_ui.a 51caee4a;M6.19=0.8.19 固件 2d2331f2 推卡自刷验收全绿
This commit is contained in:
1 parent
c2ee7626a4
commit
3d70a3dd2c
176 files changed
+15507
-597
No files matched your search
@@ -22,13 +22,15 @@
|
||||
* (无声排查的永久资产,勿省)。
|
||||
*/
|
||||
#include "../ncm_app_internal.h"
|
||||
#include "meow_ipc.h" /* AUDIO_FMT_S16 */
|
||||
#include "meow_ipc.h" /* AUDIO_FMT_S16 / MSG_AUDIO_VOLUME / MEOW_IPC_HDR_LEN */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sound/asound.h>
|
||||
@@ -143,6 +145,111 @@ static int file_open(int rate, int ch, int bits)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ============================================================ */
|
||||
/* 任务1:开播前把「系统当前音量」同步下发到 audiod */
|
||||
/* ============================================================ */
|
||||
/* 背景(用户实测 2026-09-02):未开始播放时音量不是系统设定值;按一下音量键才跳到设定值。
|
||||
* 根因= ncm 启动并打开 audiod/DAC 时,没有把系统当前音量下发到 audiod(meow_audiod 只在
|
||||
* MSG_AUDIO_VOLUME 收到时才写 DAC,流开/重配上不主动应用 settings 音量),导致 DAC 停在
|
||||
* 默认/上次残留音量;用户按音量键时 launcher/watchdog 那条音量链才更新并下发到 audiod。
|
||||
* 修法= SINK_AUDIOD open 成功后读 settings.conf 的 audio.volume,经 MSG_AUDIO_VOLUME 下发
|
||||
* (audiod 写 DAC numid=1/2 + 持久化 settings,同源同键)。语意:CS43198 寄存器=衰减量
|
||||
* (0 最响/255 静音),audiod dac_vol_set 负责反转与真静音(写 255),本处只传 pct。
|
||||
* 宿主验证:fake audiod socket 收帧断言 pct==系统设定(bench/vol_sync_sim.c)。 */
|
||||
|
||||
#define NCM_SETTINGS_CONF "/usr/data/meow/settings.conf" /* 与 audiod SETTINGS_CONF 同源 */
|
||||
#define NCM_AUDIO_SOCK_DEF "/usr/data/meow_audio.sock" /* 与 audiod / SDK 客户端同缺省 */
|
||||
#define NCM_VOL_DEFAULT 60 /* 与 audiod VOL_DEFAULT/SDK 缺省一致 */
|
||||
#define NCM_VOL_MAX 100
|
||||
|
||||
static const char *settings_conf_path(void)
|
||||
{
|
||||
const char *e = getenv("MEOW_SETTINGS_FILE"); /* 宿主单测指向临时文件;设备缺省系统 conf */
|
||||
return (e && e[0]) ? e : NCM_SETTINGS_CONF;
|
||||
}
|
||||
|
||||
/* 读 settings.conf 的 audio.volume(扁平 "key = value";兼容无空格/多空格)。
|
||||
* 与 launcher meow_settings / SDK meow_ui_vol_read 同款解析。缺省 60;夹到 [0,100]。 */
|
||||
static int settings_read_volume(void)
|
||||
{
|
||||
static const char key[] = "audio.volume";
|
||||
const int kl = (int)sizeof key - 1;
|
||||
FILE *f = fopen(settings_conf_path(), "r");
|
||||
int vol = NCM_VOL_DEFAULT;
|
||||
if (!f) return vol;
|
||||
char line[160];
|
||||
while (fgets(line, sizeof line, f)) {
|
||||
char *eq = strchr(line, '=');
|
||||
if (!eq) continue; /* 非 key=value 行跳过 */
|
||||
char *k = line;
|
||||
while (*k == ' ' || *k == '\t') k++; /* key 左去空白 */
|
||||
int n = (int)(eq - k);
|
||||
while (n > 0 && (k[n - 1] == ' ' || k[n - 1] == '\t')) n--; /* key 右去空白 */
|
||||
if (n != kl || strncmp(k, key, (size_t)kl) != 0) continue; /* 精确匹配 */
|
||||
char *v = eq + 1;
|
||||
while (*v == ' ' || *v == '\t') v++; /* value 左去空白 */
|
||||
int x = atoi(v);
|
||||
vol = (x < 0) ? 0 : (x > NCM_VOL_MAX ? NCM_VOL_MAX : x);
|
||||
break;
|
||||
}
|
||||
fclose(f);
|
||||
return vol;
|
||||
}
|
||||
|
||||
/* 把 pct(0-100) 下发到 audiod。独立连接(SDK 客户端连接私有不可复用);MSG_AUDIO_VOLUME
|
||||
* 不要求开流,audiod 侧写 DAC+持久化后 ACK(meow_audiod.c MSG_AUDIO_VOLUME)。失败仅日志,
|
||||
* 不影响播放本身(音量是次要正确性,非播放成败)。
|
||||
*
|
||||
* §2026-09-02 连接风暴根因:audiod_sync_volume 每次 audio_out_open 都新建一条 AF_UNIX
|
||||
* 短连接(socket→connect→write→close)。边下边播/切歌/seek 反复 open/close 时,audiod 端表现为
|
||||
* 「同一 fd=N 被反复 accept」(真机 1489 次 "io: client connected (fd=4)")。音量值实际只在
|
||||
* 用户按音量键时变化,且 audiod 收到已持久化(settings_set_volume),无需每次 open 重发——
|
||||
* 修法:每进程首次 open 同步一次;此后仅当 settings 音量**变化**才重发(同一连接数坍缩为
|
||||
* O(1),风暴消除)。失败不记 status(下次 open 仍会尝试,幂等)。 */
|
||||
static void audiod_sync_volume(int pct)
|
||||
{
|
||||
const char *sp = getenv("MEOW_AUDIO_SOCK");
|
||||
if (!sp || !sp[0]) sp = NCM_AUDIO_SOCK_DEF;
|
||||
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (fd < 0) return;
|
||||
struct sockaddr_un a;
|
||||
memset(&a, 0, sizeof a);
|
||||
a.sun_family = AF_UNIX;
|
||||
strncpy(a.sun_path, sp, sizeof a.sun_path - 1);
|
||||
if (connect(fd, (struct sockaddr *)&a, sizeof a) < 0) { close(fd); return; }
|
||||
|
||||
uint8_t frame[MEOW_IPC_HDR_LEN + sizeof(int32_t)];
|
||||
uint32_t magic = MEOW_IPC_MAGIC, len = sizeof(int32_t), req = 1;
|
||||
uint16_t ver = MEOW_IPC_VERSION, type = MSG_AUDIO_VOLUME;
|
||||
memcpy(frame + 0, &magic, 4);
|
||||
memcpy(frame + 4, &ver, 2);
|
||||
memcpy(frame + 6, &type, 2);
|
||||
memcpy(frame + 8, &req, 4);
|
||||
memcpy(frame + 12, &len, 4);
|
||||
int32_t v32 = (int32_t)pct;
|
||||
memcpy(frame + 16, &v32, sizeof v32);
|
||||
if (write(fd, frame, sizeof frame) != (ssize_t)sizeof frame)
|
||||
meow_log(MEOW_LOG_WARN, "audio: sync volume write failed pct=%d", pct);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
/* 每进程同步一次 + 值变才重发:杜绝每次 audio_out_open 新建短连接(fd=N accept 风暴根因)。
|
||||
* g_vol_synced=1 即已同步过;g_vol_last=上次下发的 pct,二者相同则零连接直接返回。 */
|
||||
static int g_vol_synced = 0;
|
||||
static int g_vol_last = -1;
|
||||
|
||||
int audio_out_sync_volume(void)
|
||||
{
|
||||
int pct = settings_read_volume();
|
||||
if (g_vol_synced && pct == g_vol_last) return 0; /* 已同步且未变:零新建连接 */
|
||||
g_vol_synced = 1;
|
||||
g_vol_last = pct;
|
||||
audiod_sync_volume(pct);
|
||||
meow_log(MEOW_LOG_INFO, "audio: sync volume -> audiod pct=%d (settings %s)",
|
||||
pct, settings_conf_path());
|
||||
return 0;
|
||||
}
|
||||
|
||||
int audio_out_open(int sample_rate, int channels, int bits)
|
||||
{
|
||||
g_rate = sample_rate; g_ch = channels; g_bits = bits;
|
||||
@@ -202,6 +309,9 @@ int audio_out_open(int sample_rate, int channels, int bits)
|
||||
else
|
||||
meow_log(MEOW_LOG_INFO, "audio: audiod open ok sid=%d req=%dHz/%dch/%dbit",
|
||||
sid, sample_rate, channels, bits);
|
||||
/* 任务1:开播/打开音频会话时把系统当前音量同步下发到 audiod(DAC 停在默认/上次残留音量的根治)。
|
||||
* 幂等(同值重写无害);失败不阻断播放。 */
|
||||
audio_out_sync_volume();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
static pthread_t g_thr;
|
||||
static int g_thr_on;
|
||||
static int g_quit;
|
||||
static volatile int g_req_focus_player = 0; /* 任务3:launcher 请求「回到播放页」(UI 线程消费) */
|
||||
|
||||
static const char *sock_path(void)
|
||||
{
|
||||
@@ -151,6 +152,11 @@ static void apply_playctl(const m_playctl_t *c)
|
||||
* (停止/缓冲)时 pause() 内部按态过滤,天然 no-op 安全。 */
|
||||
player_manager_pause(player_manager_state() == NCM_PC_PAUSED ? 0 : 1);
|
||||
break;
|
||||
case NCM_CTL_FOCUS:
|
||||
/* 任务3:通知中心媒体卡片点「卡片体」→ launcher 发本 cmd(需要 launcher 接线,见任务书):
|
||||
* 请求 App 回到播放页。只置标志(跨线程),UI 线程 app_on_tick 消费后导航(page nav 非线程安全)。 */
|
||||
g_req_focus_player = 1;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -201,6 +207,78 @@ static void *ipc_thread(void *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* 任务2:home 根页左滑「回桌面后台」——不再进程退出,改发 MSG_NAV{EXIT} 给 launcher。
|
||||
* launcher M4 据 app.json background_audio + 媒体会话 PLAYING(meow_media_app_playing)判
|
||||
* 保活(进程+音频流保留转后台,UI 走 §10.5 SDK present 自抑,非 SIGSTOP);否则才真终止。
|
||||
* 本函数**不**置 g_app_quit、**不**停线程——进程保留,媒体会话继续上报 PLAYING(任务2 ③)。
|
||||
* 独立连接发一帧(launcher ipc_dispatch MSG_NAV{EXIT} → meow_nav_request_exit,app_launcher.c:571)。
|
||||
* 返回 0=发出 / -1=无 socket 或写失败。 */
|
||||
int ipc_media_nav_exit(void)
|
||||
{
|
||||
int fd = connect_path(sock_path());
|
||||
if (fd < 0) {
|
||||
meow_log(MEOW_LOG_WARN, "ipc: nav_exit no launcher socket");
|
||||
return -1;
|
||||
}
|
||||
m_nav_t n;
|
||||
memset(&n, 0, sizeof n);
|
||||
ncm_strlcpy(n.app_id, "dev.player.ncm", sizeof n.app_id);
|
||||
n.nav_type = MEOW_NAV_EXIT;
|
||||
n.depth = 0;
|
||||
int rc = send_frame(fd, MSG_NAV, 9, &n, sizeof n);
|
||||
close(fd);
|
||||
meow_log(MEOW_LOG_INFO, "ipc: nav_exit sent rc=%d (request launcher background)", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 任务4 keep_awake:ACT_* 值 → launcher app.json awake/route 键用的活动名。
|
||||
* 命中的只有这些(ncm_app_internal.h ACT_* 枚举);未知/空栈(0) 返回 NULL(不起作用)。 */
|
||||
static const char *ncm_act_name(int act)
|
||||
{
|
||||
switch (act) {
|
||||
case ACT_LOGIN: return "login";
|
||||
case ACT_HOME: return "home";
|
||||
case ACT_PLAYLIST: return "playlist";
|
||||
case ACT_PLAYER: return "player";
|
||||
case ACT_SETTINGS: return "settings";
|
||||
case ACT_SEARCH: return "search";
|
||||
default: return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* 任务4 keep_awake:把当前顶层 activity 上报 launcher(MSG_ACT_FOCUS,独立连接发一帧,无需 HELLO)。
|
||||
* launcher util.c ipc_dispatch MSG_ACT_FOCUS → meow_fg_act_set(app,act);fg_keep_awake() 依
|
||||
* app.json awake.<act>=1 判定该 activity 是否常亮(超时不自动息屏)。无 socket/launcher 不跑 → 优雅跳过。
|
||||
* 返回 0=发出 / -1=无 socket 或写失败。 */
|
||||
int ipc_act_report(int act)
|
||||
{
|
||||
const char *name = ncm_act_name(act);
|
||||
if (!name) return -1;
|
||||
int fd = connect_path(sock_path());
|
||||
if (fd < 0) {
|
||||
meow_log(MEOW_LOG_WARN, "ipc: act_focus(%s) no launcher socket", name);
|
||||
return -1;
|
||||
}
|
||||
m_act_focus_t f;
|
||||
memset(&f, 0, sizeof f);
|
||||
ncm_strlcpy(f.app_id, "dev.player.ncm", sizeof f.app_id);
|
||||
ncm_strlcpy(f.activity, name, sizeof f.activity);
|
||||
int rc = send_frame(fd, MSG_ACT_FOCUS, 10, &f, sizeof f);
|
||||
close(fd);
|
||||
meow_log(MEOW_LOG_INFO, "ipc: act_focus act=%s rc=%d", name, rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* 任务3:UI 线程消费「回到播放页」标志。返回 1=有未消费的聚焦请求(消费一次)。 */
|
||||
int ipc_media_focus_consume(void)
|
||||
{
|
||||
if (g_req_focus_player) {
|
||||
g_req_focus_player = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ipc_media_start(void)
|
||||
{
|
||||
if (g_thr_on) return 0;
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/* play/mem_watch.c — 边播边监控**取证**实现(接口见 mem_watch.h;agent 32bit 大文件 OOM 取证)。
|
||||
*
|
||||
* 归属演进(2026-09-02 定案,ARCHITECTURE_PRINCIPLES ③混合):**降载决策上收通用层**——
|
||||
* A(DONTNEED 治理)+ C(/proc/meminfo Cached 监控)分别归 SDK meow_cachegov / meow_svc_perf;
|
||||
* 本模块退化为「RssAnon 取证」:低频读 /proc/self/status(优先 RssAnon,缺失回退 VmRSS),
|
||||
* 记录匿名内存峰值,**仅供日志**。不再驱动下载/预取降载(那由 meow_cachegov_download_paused
|
||||
* / prefetch_paused 查询)。**绝不打断解码/播放**。
|
||||
*
|
||||
* 阈值滞后(仅用于取证 level 标注):
|
||||
* kb >= CRITICAL → level=2;kb >= HIGH 且当前<2 → level=1;kb <= LOW → level=0;其它保持。
|
||||
* 本模块自含:只依赖 pthread + meow_log(SDK 日志),不依赖 core/app 其它 TU。
|
||||
*/
|
||||
#include "mem_watch.h"
|
||||
#include "../sdk/include/meow_ui_log.h"
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* 默认阈值(设备缺省;宿主单测用 env/force 桩覆盖) */
|
||||
#define MEM_WATCH_PERIOD_MS 500
|
||||
#define MEM_WATCH_HIGH_KB (30 * 1024)
|
||||
#define MEM_WATCH_LOW_KB (20 * 1024)
|
||||
#define MEM_WATCH_CRITICAL_KB (40 * 1024)
|
||||
|
||||
struct mw_cfg {
|
||||
int period_ms;
|
||||
long high_kb;
|
||||
long low_kb;
|
||||
long critical_kb;
|
||||
};
|
||||
|
||||
static struct mw_cfg g_cfg = {
|
||||
.period_ms = MEM_WATCH_PERIOD_MS,
|
||||
.high_kb = MEM_WATCH_HIGH_KB,
|
||||
.low_kb = MEM_WATCH_LOW_KB,
|
||||
.critical_kb = MEM_WATCH_CRITICAL_KB,
|
||||
};
|
||||
|
||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_t g_thr;
|
||||
static int g_thr_on;
|
||||
static volatile int g_quit;
|
||||
static long g_rss_anon_kb; /* 最近一次采样(KB;-1=不可读) */
|
||||
static long g_peak_song_kb; /* 本曲匿名峰值(song_begin 清零) */
|
||||
static int g_level; /* NCM_MEM_LVL_*(仅取证日志) */
|
||||
static long g_forced_kb = -1; /* 宿主桩 ≥0 时替代真读 */
|
||||
|
||||
/* env 覆盖阈值:非法值回落默认。 */
|
||||
static int env_int(const char *k, int def)
|
||||
{
|
||||
const char *e = getenv(k);
|
||||
if (!e || !e[0]) return def;
|
||||
int v = atoi(e);
|
||||
return v > 0 ? v : def;
|
||||
}
|
||||
static long env_long(const char *k, long def)
|
||||
{
|
||||
const char *e = getenv(k);
|
||||
if (!e || !e[0]) return def;
|
||||
long v = atol(e);
|
||||
return v > 0 ? v : def;
|
||||
}
|
||||
|
||||
/* 读匿名 RSS(KB)。优先 RssAnon;缺则回退 VmRSS;都无 → -1。 */
|
||||
static long read_rss_kb(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
long forced = g_forced_kb;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
if (forced >= 0) return forced;
|
||||
|
||||
FILE *fp = fopen("/proc/self/status", "r");
|
||||
if (!fp) return -1;
|
||||
char line[256];
|
||||
long anon = -1, rss = -1;
|
||||
while (fgets(line, sizeof line, fp))
|
||||
if (!strncmp(line, "RssAnon:", 8)) anon = atol(line + 8);
|
||||
else if (rss < 0 && !strncmp(line, "VmRSS:", 6)) rss = atol(line + 6);
|
||||
fclose(fp);
|
||||
return anon >= 0 ? anon : rss;
|
||||
}
|
||||
|
||||
static const char *level_str(int lvl)
|
||||
{
|
||||
switch (lvl) {
|
||||
case NCM_MEM_LVL_HIGH: return "high";
|
||||
case NCM_MEM_LVL_CRITICAL: return "critical";
|
||||
default: return "normal";
|
||||
}
|
||||
}
|
||||
|
||||
static void *watch_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (!g_quit) {
|
||||
long kb = read_rss_kb();
|
||||
int lvl;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
g_rss_anon_kb = kb;
|
||||
if (kb > g_peak_song_kb) g_peak_song_kb = kb;
|
||||
long hi = g_cfg.high_kb, lo = g_cfg.low_kb, cr = g_cfg.critical_kb;
|
||||
/* 滞后三档(仅取证 level 标注) */
|
||||
if (kb >= cr) lvl = NCM_MEM_LVL_CRITICAL;
|
||||
else if (kb >= hi) lvl = (g_level == NCM_MEM_LVL_CRITICAL) ? NCM_MEM_LVL_CRITICAL : NCM_MEM_LVL_HIGH;
|
||||
else if (kb <= lo) lvl = NCM_MEM_LVL_NORMAL;
|
||||
else lvl = g_level; /* 滞后带:保持 */
|
||||
if (lvl != g_level) {
|
||||
meow_log(MEOW_LOG_INFO,
|
||||
"[mem] rss_anon=%ldKB peak=%ldKB level=%s (H=%ldK/L=%ldK/C=%ldK)",
|
||||
kb, g_peak_song_kb, level_str(lvl), hi, lo, cr);
|
||||
}
|
||||
g_level = lvl;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
usleep((useconds_t)g_cfg.period_ms * 1000u);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mem_watch_start(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (g_thr_on) { pthread_mutex_unlock(&g_mu); return 0; }
|
||||
/* 开关(默认开;NCM_MEM_WATCH_ENABLED=0/off 关闭监控,行为退化为旧版零侵入) */
|
||||
const char *en = getenv("NCM_MEM_WATCH_ENABLED");
|
||||
if (en && (!en[0] || !strcmp(en, "0") || !strcmp(en, "off") || !strcmp(en, "false"))) {
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
meow_log(MEOW_LOG_INFO, "[mem] watch disabled (NCM_MEM_WATCH_ENABLED=%s)", en);
|
||||
return 0;
|
||||
}
|
||||
/* 阈值 env 覆盖(宿主单测) */
|
||||
g_cfg.period_ms = env_int("NCM_MEM_WATCH_PERIOD_MS", MEM_WATCH_PERIOD_MS);
|
||||
g_cfg.high_kb = env_long("NCM_MEM_WATCH_HIGH_KB", MEM_WATCH_HIGH_KB);
|
||||
g_cfg.low_kb = env_long("NCM_MEM_WATCH_LOW_KB", MEM_WATCH_LOW_KB);
|
||||
g_cfg.critical_kb = env_long("NCM_MEM_WATCH_CRITICAL_KB", MEM_WATCH_CRITICAL_KB);
|
||||
g_quit = 0;
|
||||
pthread_attr_t a;
|
||||
pthread_attr_init(&a);
|
||||
pthread_attr_setstacksize(&a, (size_t)(128 * 1024)); /* 同 ncm_thread_create 128KB(内存瘦身,OOM 修复) */
|
||||
int rc = pthread_create(&g_thr, &a, watch_thread, NULL);
|
||||
pthread_attr_destroy(&a);
|
||||
if (rc != 0) { pthread_mutex_unlock(&g_mu); return -1; }
|
||||
g_thr_on = 1;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
meow_log(MEOW_LOG_INFO,
|
||||
"[mem] watch started period=%dms H=%ldK L=%ldK C=%ldK (RssAnon 优先)",
|
||||
g_cfg.period_ms, g_cfg.high_kb, g_cfg.low_kb, g_cfg.critical_kb);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mem_watch_stop(void)
|
||||
{
|
||||
g_quit = 1;
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (g_thr_on) { g_thr_on = 0; pthread_detach(g_thr); }
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
}
|
||||
|
||||
int mem_watch_level(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
int v = g_level;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return v;
|
||||
}
|
||||
|
||||
long mem_watch_rss_anon_kb(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
long v = g_rss_anon_kb;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
return v;
|
||||
}
|
||||
|
||||
void mem_watch_song_begin(void)
|
||||
{
|
||||
long kb = read_rss_kb(); /* 起播即采样当前内存,本曲峰值从当前起算 */
|
||||
pthread_mutex_lock(&g_mu);
|
||||
g_peak_song_kb = kb;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
meow_log(MEOW_LOG_INFO, "[mem] song begin rss_anon=%ldKB", kb);
|
||||
}
|
||||
|
||||
/* 播完/切歌释放钩子之后调用:落本曲峰值 + 重置计数。 */
|
||||
void mem_watch_song_end(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
long peak = g_peak_song_kb;
|
||||
g_peak_song_kb = g_rss_anon_kb;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
meow_log(MEOW_LOG_INFO, "[mem] song end peak_anon=%ldKB (released)", peak);
|
||||
}
|
||||
|
||||
void mem_watch_set_forced_kb(long kb)
|
||||
{
|
||||
pthread_mutex_lock(&g_mu);
|
||||
g_forced_kb = kb;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/* play/mem_watch.h — 边播边监控**取证**(agent 32bit 大文件 OOM 取证;OOM_PAGECACHE_GOVERNANCE)。
|
||||
*
|
||||
* 归属演进(2026-09-02 定案,ARCHITECTURE_PRINCIPLES ③混合):**降载决策上收通用层**——
|
||||
* A(DONTNEED 治理)+ C(/proc/meminfo Cached 监控)分别归 SDK meow_cachegov / 系统服务
|
||||
* meow_svc_perf;本模块退化为「RssAnon 取样取证」:一个轻量线程(ncm_thread_create 同款
|
||||
* 128KB 栈)低频(默认 500ms)读 /proc/self/status 的 RssAnon(缺失回退 VmRSS),记录匿名
|
||||
* 内存峰值(RssAnon 贴近「应用自有不可回收内存」,是 OOM kill 高危口径),**仅供日志/取证**,
|
||||
* 不再驱动下载/预取降载(那由 meow_cachegov_download_paused/prefetch_paused 查询)。
|
||||
*
|
||||
* 调度器结合释放钩子:mem_watch_song_begin/end 在 run_song 的起播/收尾(song_src_dispose +
|
||||
* ncm_dec_close + audio_out_close,即「彻底释放」钩子)处调用,重置本曲峰值计数并落日志
|
||||
* (峰值 RSS 即 RssAnon/RssFile 分离取证锚点;Cached/Dirty 侧取证归 svc_perf/meow_cachegov)。
|
||||
*
|
||||
* 阈值/开关可通过环境变量覆盖(宿主单测确定性驱动):NCM_MEM_WATCH_ENABLED /
|
||||
* NCM_MEM_WATCH_PERIOD_MS / NCM_MEM_WATCH_HIGH_KB / NCM_MEM_WATCH_LOW_KB / NCM_MEM_WATCH_CRITICAL_KB。
|
||||
* 宿主测试桩:mem_watch_set_forced_kb() 注入读值(>=0 时监控线程不再读真 proc,仅测取证逻辑)。
|
||||
*/
|
||||
#ifndef NCM_MEM_WATCH_H
|
||||
#define NCM_MEM_WATCH_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 内存压力档(仅取证日志用;不再驱动降载) */
|
||||
enum {
|
||||
NCM_MEM_LVL_NORMAL = 0, /* < HIGH */
|
||||
NCM_MEM_LVL_HIGH = 1, /* ≥ HIGH */
|
||||
NCM_MEM_LVL_CRITICAL = 2, /* ≥ CRITICAL */
|
||||
};
|
||||
|
||||
/* 启动监控线程(幂等;ncm_thread_create 128KB 栈)。返回 0=ok / -1=线程建失败。 */
|
||||
int mem_watch_start(void);
|
||||
/* 停止(置退出标志;线程 detach,不 join——同 ipc_media 模式)。 */
|
||||
void mem_watch_stop(void);
|
||||
|
||||
/* 当前压力档(0/1/2;仅取证日志)。 */
|
||||
int mem_watch_level(void);
|
||||
/* 最近一次采样的匿名 RSS(KB);-1=不可读。 */
|
||||
long mem_watch_rss_anon_kb(void);
|
||||
|
||||
/* ---- 调度器:与播放释放钩子联动 ---- */
|
||||
/* 起播一首曲时调用:本曲峰值计数清零,开始记录本曲内存画像。 */
|
||||
void mem_watch_song_begin(void);
|
||||
/* 播完/切歌、即「彻底释放」钩子(decoder 关+下载线程 join+源关+.part 收尾)执行后调用:
|
||||
* 落本曲峰值日志 + 重置计数,给下一首让出统计(内存本身已由释放钩子归还)。 */
|
||||
void mem_watch_song_end(void);
|
||||
|
||||
/* ---- 宿主测试桩(设备永不调用;≥0 时监控线程用注入值替代真 /proc 读)---- */
|
||||
void mem_watch_set_forced_kb(long kb);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* NCM_MEM_WATCH_H */
|
||||
@@ -32,6 +32,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include "decoder.h"
|
||||
#include "stream_src.h"
|
||||
#include "mem_watch.h"
|
||||
|
||||
static pthread_t g_thr;
|
||||
static int g_thr_on;
|
||||
@@ -43,6 +44,10 @@ static char g_path[512]; /* 本地音频路径(缓存,mp3/flac
|
||||
static int64_t g_file_bytes;
|
||||
static int g_level; /* 音质档(来自设置) */
|
||||
static int g_cache_gb;
|
||||
/* 任务3(§re-entry):pending_replay=1 表示「切音质/缓存后点了正在播的同曲」——此时重播须换档
|
||||
* (P12 语义,seq++),不得被「同曲不重播」护栏吞掉。set_quality 置位;player_manager_play 的
|
||||
* 同曲护栏据它分流:pending=1 走重播(换档),pending=0 只聚焦不重播(点正在播的曲)。 */
|
||||
static int g_pending_replay;
|
||||
|
||||
/* 被抢占回调(SDK 读线程分发;audiod 后开流赢):走 g_play 命令通道暂停(内部持锁),
|
||||
* 不直接动锁外状态。恢复时 audio_out_pause(0) 会检测流死并自动重开(见 audio_out.c)。 */
|
||||
@@ -90,12 +95,25 @@ void player_manager_set_quality(int level, int cache_gb)
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
g_play.err = 0;
|
||||
ncm_cache_set_limit_gb(&g_st.ctx.cache, cache_gb);
|
||||
g_pending_replay = 1; /* 任务3:改档后点正在播的同曲 → 强制换档重播(§25 P12) */
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
}
|
||||
|
||||
void player_manager_play(int song_idx)
|
||||
{
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
/* 任务3:点「正在播的曲」(同 idx 且 PLAYING/PAUSED,非切档重播)→ 只聚焦不重播:
|
||||
* 不 seq++、不置 BUFFER——旧曲继续出声/停原状态,UI 由调用方 nav 到播放页。
|
||||
* 注意:set_quality 已置 g_pending_replay=1(切音质后重播同曲须换档,P12),此护栏放行。
|
||||
* 非 PLAYING/PAUSED(STOPPED/另一曲)不受影响,照旧起播。 */
|
||||
if (song_idx == g_play.cur_song && !g_pending_replay &&
|
||||
(g_play.state == NCM_PC_PLAYING || g_play.state == NCM_PC_PAUSED)) {
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
meow_log(MEOW_LOG_INFO, "play: re-enter current idx=%d state=%d -> no restart",
|
||||
song_idx, g_play.state);
|
||||
return;
|
||||
}
|
||||
g_pending_replay = 0; /* 消费掉(新播放意图落地,后续同曲 tap 恢复「只聚焦」) */
|
||||
g_play.cur_song = song_idx;
|
||||
g_play.cmd = 0;
|
||||
g_play.state = NCM_PC_BUFFER;
|
||||
@@ -108,6 +126,7 @@ void player_manager_play(int song_idx)
|
||||
g_play.underruns = 0; /* §21:边下边播统计随新播放意图清零 */
|
||||
g_play.underrun_ms = 0;
|
||||
g_play.stalled = 0;
|
||||
g_play.refaults = 0; /* §2.3:refault 取证随新播放意图清零 */
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
}
|
||||
|
||||
@@ -135,6 +154,7 @@ void player_manager_next(int dir)
|
||||
int cnt = appstate_queue_count(); /* §23:按队列来源(窗口全量 或 搜索条数) */
|
||||
if (c >= cnt) c = cnt - 1;
|
||||
if (c != g_play.cur_song) {
|
||||
g_pending_replay = 0; /* 新播放意图落地:清「切档重播」待办 */
|
||||
g_play.cur_song = c;
|
||||
g_play.cmd = 0;
|
||||
g_play.state = NCM_PC_BUFFER;
|
||||
@@ -147,6 +167,7 @@ void player_manager_next(int dir)
|
||||
g_play.underruns = 0;
|
||||
g_play.underrun_ms = 0;
|
||||
g_play.stalled = 0;
|
||||
g_play.refaults = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
}
|
||||
@@ -154,6 +175,7 @@ void player_manager_next(int dir)
|
||||
void player_manager_stop(void)
|
||||
{
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
g_pending_replay = 0; /* 停止:清「切档重播」待办 */
|
||||
g_play.cur_song = -1;
|
||||
g_play.state = NCM_PC_STOPPED;
|
||||
g_play.pos_ms = 0;
|
||||
@@ -189,8 +211,19 @@ typedef struct {
|
||||
int *st_ptr; /* 指向 dl_thread 的 status 局部变量(同线程,无竞态) */
|
||||
int need_fresh; /* 416/其它:服务器拒绝续传 → 放 .part、交调用方整首重下 */
|
||||
uint64_t skip; /* 200 忽略 Range:body 前 skip 字节与 .part 前缀重复,丢弃 */
|
||||
/* page-cache 治理(A:meow_cachegov)登记路径:播放实际读的文件(流式=.tmp/.part,命中=final)。 */
|
||||
char gov_path[512];
|
||||
/* 曲终 L0 用:commit 后的正式路径(流式 commit rename 后 = final;命中=gov_path 同)。 */
|
||||
char gov_final_path[512];
|
||||
} song_src_t;
|
||||
|
||||
/* page-cache 治理(A):SDK meow_cachegov 的 read_pos 回调——返回当前解码逻辑读指针
|
||||
* (stream_src 内部维护,经 stream_src_read_pos 读取;回调上下文=stream_src*)。 */
|
||||
static uint64_t gov_read_pos(void *ctx)
|
||||
{
|
||||
return stream_src_read_pos((stream_src_t *)ctx);
|
||||
}
|
||||
|
||||
/* http 体回调(下载线程上下文;ncm_http 每次投递 ≤4KB):write(2) 直写落盘 +
|
||||
* 锁内推进 written + broadcast 唤醒读端 + 进度反馈。
|
||||
* 阶段3 续传(§5.2):首 body 字节按 response status 切换写姿态——
|
||||
@@ -201,6 +234,11 @@ static int stream_dl_cb(void *ctx, const unsigned char *data, size_t len)
|
||||
{
|
||||
song_src_t *ss = (song_src_t *)ctx;
|
||||
if (stream_src_aborted(ss->src)) return 1; /* 切歌/停止:中止 http */
|
||||
/* page-cache 治理(§2.3/§3):下载 cb 查 meow_cachegov_download_paused()(A 回退硬暂停
|
||||
* OR svc_perf C Cached 降载)→ 暂停下载(限速),优先解码播放;不打断播放线程
|
||||
* (读端追上下载走既有 underrun/停滞联动)。每 50ms 查一次中止,切歌可立即脱身。 */
|
||||
while (meow_cachegov_download_paused() && !stream_src_aborted(ss->src))
|
||||
usleep(50 * 1000);
|
||||
if (ss->first_body) {
|
||||
ss->first_body = 0;
|
||||
int st = ss->st_ptr ? *ss->st_ptr : 0;
|
||||
@@ -295,6 +333,15 @@ static void song_src_dispose(song_src_t *ss)
|
||||
if (ss->src) { stream_src_close(ss->src); ss->src = NULL; }
|
||||
}
|
||||
|
||||
/* page-cache 治理(A)曲终清理:注销 L1 登记条目 + 下载完整(commit 成功)则对正式文件做
|
||||
* L0 整文件 DONTNEED(仅曲终安全窗口;未 commit/中断保留 .part 不丢)。 */
|
||||
static void song_gov_cleanup(song_src_t *ss)
|
||||
{
|
||||
if (ss->gov_path[0]) meow_cachegov_unregister(ss->gov_path);
|
||||
if (ss->dl_rc == NCM_OK && ss->gov_final_path[0])
|
||||
meow_cachegov_drop_finished(ss->gov_final_path);
|
||||
}
|
||||
|
||||
/* 缓存键(用户定案 NCM_CACHE_DESIGN §2.5):歌曲ID + 音质档位 → "song_<id>_<level>.<ext>"。
|
||||
* 同歌同档命中不重复下载;换档=新条目,旧档留给 LRU 自然淘汰。
|
||||
* §20:键后缀用 ncm_api_level_key(sky 与 sky_c51 可区分)。 */
|
||||
@@ -587,6 +634,7 @@ static void run_song(int idx)
|
||||
g_play.play_song = s;
|
||||
g_play.play_song_valid = 1;
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
mem_watch_song_begin(); /* 边播边监控:本曲峰值从当前起算(下载/解码/播放全程记录) */
|
||||
|
||||
song_src_t ss;
|
||||
int64_t file_bytes = 0;
|
||||
@@ -602,11 +650,22 @@ static void run_song(int idx)
|
||||
}
|
||||
if (!still_intent(idx, my_seq)) { /* 预缓冲完成但已切歌/同曲重播:勿开解码/勿动状态 */
|
||||
song_src_dispose(&ss);
|
||||
song_gov_cleanup(&ss); /* 未登记(register 在其后)时为 no-op,统一清理 */
|
||||
mem_watch_song_end(); /* 释放钩子:join 下载线程+关源+缓冲 free+.part 收尾 */
|
||||
return;
|
||||
}
|
||||
ncm_strlcpy(g_path, path, sizeof g_path);
|
||||
g_file_bytes = file_bytes;
|
||||
|
||||
/* page-cache 治理(A):登记本曲缓存文件(播放实际读的路径)+ read_pos 回调,供 L1 丢已读
|
||||
* 前缀 / refault 回退。cache 命中(ss.w==NULL)path=正式文件;流式 path=.tmp/.part
|
||||
* (cur file 即正被 pread 的文件,DONTNEED 只作用于它;正式路径待 commit 后 L0 用)。 */
|
||||
ncm_strlcpy(ss.gov_path, path, sizeof ss.gov_path);
|
||||
if (ss.w) ncm_strlcpy(ss.gov_final_path, ncm_cache_writer_final_path(ss.w), sizeof ss.gov_final_path);
|
||||
else ncm_strlcpy(ss.gov_final_path, path, sizeof ss.gov_final_path);
|
||||
meow_cachegov_register(ss.gov_path, (uint64_t)(file_bytes > 0 ? file_bytes : 0),
|
||||
gov_read_pos, ss.src);
|
||||
|
||||
int64_t dur = s.duration_ms > 0 ? s.duration_ms : 0;
|
||||
|
||||
/* 解码分发(§19/§21):stream_src 上 magic 探测 fLaC→dr_flac / 其它→minimp3(open_cb
|
||||
@@ -616,6 +675,8 @@ static void run_song(int idx)
|
||||
int64_t total_frames = 0;
|
||||
if (ncm_dec_open_src(&dec, ss.src, &rate, &ch, &bits, &total_frames) != 0) {
|
||||
song_src_dispose(&ss);
|
||||
song_gov_cleanup(&ss);
|
||||
mem_watch_song_end(); /* 释放钩子 */
|
||||
if (!still_intent(idx, my_seq)) return; /* open 期间(flac 元数据等待)已切歌/重播 */
|
||||
set_err(NCM_ERR_IO, "decode open failed"); return;
|
||||
}
|
||||
@@ -623,7 +684,8 @@ static void run_song(int idx)
|
||||
|
||||
/* 音频输出:按实际流参数开(flac ≤16bit→S16,>16bit→S32;audiod 侧格式归一+原生率跟随) */
|
||||
if (audio_out_open(rate, ch, bits) != 0) {
|
||||
ncm_dec_close(dec); song_src_dispose(&ss);
|
||||
ncm_dec_close(dec); song_src_dispose(&ss); song_gov_cleanup(&ss);
|
||||
mem_watch_song_end(); /* 释放钩子 */
|
||||
set_err(NCM_ERR_IO, "audio open failed"); return;
|
||||
}
|
||||
|
||||
@@ -645,6 +707,7 @@ static void run_song(int idx)
|
||||
int paused_edge = 0; /* 暂停沿:进暂停已向 audiod 发 pause */
|
||||
int stalled_edge = 0; /* §21 停滞沿:已对 audiod pause(1),待数据恢复 pause(0) */
|
||||
int underruns_seen = 0;
|
||||
int refaults_seen = 0; /* §2.3:本曲 DONTNEED 丢页重读 TF 计数(取证) */
|
||||
|
||||
for (;;) {
|
||||
if (g_app_quit) break;
|
||||
@@ -751,6 +814,18 @@ static void run_song(int idx)
|
||||
idx, u, (unsigned long long)wm);
|
||||
}
|
||||
}
|
||||
/* page-cache 治理(A):refault 取证落 g_play + 节拍执行 L1 丢弃/refault 回退 */
|
||||
{
|
||||
int rf = stream_src_refaults(ss.src);
|
||||
if (rf != refaults_seen) {
|
||||
refaults_seen = rf;
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
g_play.refaults = rf;
|
||||
pthread_mutex_unlock(&g_play.mu);
|
||||
meow_log(MEOW_LOG_INFO, "play: refault idx=%d count=%d", idx, rf);
|
||||
}
|
||||
meow_cachegov_tick();
|
||||
}
|
||||
/* 更新进度(~200ms 节流)*/
|
||||
int64_t pos = rate > 0 ? frames_done * 1000 / rate : 0;
|
||||
pthread_mutex_lock(&g_play.mu);
|
||||
@@ -763,6 +838,9 @@ static void run_song(int idx)
|
||||
ncm_dec_close(dec);
|
||||
/* 下载完成由播放线程 commit(rename) / 中断 abort(阶段 2 语义,§21) */
|
||||
song_src_dispose(&ss);
|
||||
song_gov_cleanup(&ss); /* page-cache 治理(A)曲终:注销 L1 条目 + commit 成功则 L0 整文件 DONTNEED */
|
||||
mem_watch_song_end(); /* 调度器释放钩子:上一首缓冲完全清空(decoder 关 + 下载线程
|
||||
* join + 源关 + 缓冲 free + .part 收尾)——给下一首让内存 */
|
||||
/* 若应继续播下一首且非因命令打断,由外层处理(外层见 cur==idx 且需自然结束) */
|
||||
}
|
||||
|
||||
@@ -835,7 +913,9 @@ static void *lyric_worker(void *arg)
|
||||
}
|
||||
if (!id[0]) continue;
|
||||
if (strcmp(id, last_id) == 0) continue; /* 同一曲已拉/在拉 */
|
||||
|
||||
if (meow_cachegov_prefetch_paused()) continue; /* page-cache 治理(§2.3/§3):暂停歌词预取
|
||||
* (A 回退硬暂停 OR svc_perf C Cached 降载),
|
||||
* 不置 last_id → 降载恢复后下轮再拉。 */
|
||||
ncm_strlcpy(last_id, id, sizeof last_id);
|
||||
lyric_clear();
|
||||
pthread_mutex_lock(&g_lyrmu);
|
||||
@@ -898,6 +978,7 @@ int player_manager_start(void)
|
||||
g_cache_gb = g_st.cache_limit_gb;
|
||||
pthread_mutex_init(&g_play.mu, NULL);
|
||||
meow_audio_on_preempt(on_audio_preempt, NULL); /* 被抢占 → 暂停(SDK 读线程分发) */
|
||||
mem_watch_start(); /* 边播边监控(防 OOM):起监控线程读 RSS,驱下载降载 */
|
||||
if (ncm_thread_create(&g_thr, player_thread, NULL) != 0) return -1;
|
||||
g_thr_on = 1;
|
||||
return 0;
|
||||
@@ -908,6 +989,7 @@ void player_manager_stop_thread(void)
|
||||
g_want_run = 0;
|
||||
g_app_quit = 1;
|
||||
audio_out_close(); /* 打断阻塞写 */
|
||||
mem_watch_stop(); /* 停监控线程 */
|
||||
if (g_thr_on) {
|
||||
pthread_join(g_thr, NULL);
|
||||
g_thr_on = 0;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#define REBUFFER_AFTER_MS 500 /* 单次调用累计等待超此值后启用再缓冲迟滞 */
|
||||
#define REBUFFER_MIN (64 * 1024) /* 迟滞放行门槛(≈1.6s@320kbps) */
|
||||
#define STALL_MS 1000 /* 单次调用等待超此值 → 置 stalled(播放线程 pause 联动) */
|
||||
#define STREAM_SRC_REFAULT_MS 50 /* pread 慢于此阈且数据已在盘上 → 计一次 refault(§2.3) */
|
||||
|
||||
struct stream_src {
|
||||
int fd; /* 只读 fd(pread;写端另有自己的写 fd) */
|
||||
@@ -36,8 +37,16 @@ struct stream_src {
|
||||
uint64_t wait_ms_total;
|
||||
int underruns;
|
||||
int stalled;
|
||||
int refaults; /* 数据在盘上但 pread 慢(>50ms)= DONTNEED 丢页重读 TF(§2.3) */
|
||||
};
|
||||
|
||||
/* SDK meow_cachegov 提供的 refault 上报(strong 时覆盖弱符号;未链 SDK 时为空指针,调用需判空)。 */
|
||||
#if defined(__GNUC__)
|
||||
extern void meow_cachegov_note_refault(void) __attribute__((weak));
|
||||
#else
|
||||
extern void meow_cachegov_note_refault(void);
|
||||
#endif
|
||||
|
||||
static int64_t mono_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
@@ -182,9 +191,18 @@ static size_t src_read(void *buf, size_t size, void *ud)
|
||||
if (avail > 0 && !gated) {
|
||||
size_t n = (size - done) < (size_t)avail ? (size - done) : (size_t)avail;
|
||||
uint64_t pos = s->read_pos;
|
||||
int had_data = (pos < s->written); /* 数据已在盘上(放锁前采样;勿在放锁后读 written) */
|
||||
pthread_mutex_unlock(&s->mu); /* pread 期间放锁,不阻塞写端 broadcast */
|
||||
int64_t t0 = mono_ms();
|
||||
ssize_t r = pread(s->fd, (char *)buf + done, n, (off_t)pos);
|
||||
int64_t pread_ms = mono_ms() - t0;
|
||||
pthread_mutex_lock(&s->mu);
|
||||
if (r > 0 && had_data && pread_ms > STREAM_SRC_REFAULT_MS) {
|
||||
/* 数据在盘上但 pread 慢 = DONTNEED 丢页后重读 TF(underrun 确认是 refault,
|
||||
* 非网络等待)。上报 SDK meow_cachegov(弱符号,未链时为空指针需判空)。 */
|
||||
s->refaults++;
|
||||
if (meow_cachegov_note_refault) meow_cachegov_note_refault();
|
||||
}
|
||||
if (r <= 0) { /* 盘 IO 错:按中断收尾(真 EOF 语义) */
|
||||
if (!s->failed) s->failed = -3; /* NCM_ERR_IO(本层不依赖 core 头) */
|
||||
s->final = s->written ? s->written : 1;
|
||||
@@ -290,6 +308,24 @@ uint64_t stream_src_written(stream_src_t *s)
|
||||
return w;
|
||||
}
|
||||
|
||||
int stream_src_refaults(stream_src_t *s)
|
||||
{
|
||||
if (!s) return 0;
|
||||
pthread_mutex_lock(&s->mu);
|
||||
int r = s->refaults;
|
||||
pthread_mutex_unlock(&s->mu);
|
||||
return r;
|
||||
}
|
||||
|
||||
uint64_t stream_src_read_pos(stream_src_t *s)
|
||||
{
|
||||
if (!s) return 0;
|
||||
pthread_mutex_lock(&s->mu);
|
||||
uint64_t p = s->read_pos;
|
||||
pthread_mutex_unlock(&s->mu);
|
||||
return p;
|
||||
}
|
||||
|
||||
uint64_t stream_src_final(stream_src_t *s)
|
||||
{
|
||||
if (!s) return 1;
|
||||
|
||||
@@ -72,6 +72,15 @@ void stream_src_stats(stream_src_t *s, int *underruns, uint64_t *wait_ms);
|
||||
* 取走即清零。 */
|
||||
int stream_src_take_stalled(stream_src_t *s);
|
||||
|
||||
/* ---- page-cache 治理(OOM_PAGECACHE_GOVERNANCE §2.3:underrun/refault 检测)----
|
||||
* refault = pread 处「数据已在盘上(written>read_pos)但读出耗时 >50ms」——DONTNEED 丢页后
|
||||
* 重读 TF 的信号,区别于网络 underrun(avail==0 等待)。SDK meow_cachegov 据此做 A 回退。
|
||||
* 播放线程读 refaults 落 g_play 取证;回调由 SDK(meow_cachegov_note_refault)提供,
|
||||
* 未链 SDK 时弱符号为空(host sim 兼容)。 */
|
||||
int stream_src_refaults(stream_src_t *s);
|
||||
/* 当前解码逻辑读指针(stream_src_seek_cb 改写)。 */
|
||||
uint64_t stream_src_read_pos(stream_src_t *s);
|
||||
|
||||
uint64_t stream_src_written(stream_src_t *s);
|
||||
uint64_t stream_src_final(stream_src_t *s); /* 0=下载中;>0=最终大小(完成=全长,中断=截断点) */
|
||||
int stream_src_failed(stream_src_t *s); /* 0=无错误;<0=下载/IO 错误码 */
|
||||
|
||||
Reference in new issue
Block a user