diff --git a/patch_v2/ncm_app/app/play/ipc_media.c b/patch_v2/ncm_app/app/play/ipc_media.c index 82e6c59..c82c333 100644 --- a/patch_v2/ncm_app/app/play/ipc_media.c +++ b/patch_v2/ncm_app/app/play/ipc_media.c @@ -1,12 +1,14 @@ /* play/ipc_media.c — 媒体会话 IPC 最小客户端(SDK 尚无媒体封装,App 内用 meow_ipc.h 帧格式直连)。 * * 连接:AF_UNIX SOCK_STREAM 到 MEOW_IPC_SOCK(缺省 /usr/data/meow_ipc.sock)。HELLO(app_id, - * MF_PLAYBACK) 注册为媒体源;之后**同一连接**周期发 MSG_META(~1s,进度随播放推进)并阻塞等 - * launcher 下发 MSG_PLAYCTL(播放/暂停/上下曲/seek/toggle)。launcher 经该连接 fd 回发(meow_media.c - * send_playctl 用最近活跃会话的 fd)。无 socket/launcher 不跑 → 优雅跳过(打日志不崩)。 + * MF_PLAYBACK) 注册为媒体源;之后**同一连接**上报 MSG_META 并阻塞等 launcher 下发 MSG_PLAYCTL + * (播放/暂停/上下曲/seek/toggle)。launcher 经该连接 fd 回发(meow_media.c send_playctl 用最近 + * 活跃会话的 fd)。META 节奏见 meta_gate.h(变化 + 低频心跳,不再是 1s 定时;M10 修洪泛)。 + * 无 socket/launcher 不跑 → 优雅跳过(打日志不崩)。 * 线程模型:独立线程(main 起了 meow_ui_run 阻塞;播放线程归 player_manager,本线程只做 IPC 收发)。 */ #include "../ncm_app_internal.h" +#include "meta_gate.h" #include #include #include @@ -23,6 +25,12 @@ static int g_thr_on; static int g_quit; static volatile int g_req_focus_player = 0; /* 任务3:launcher 请求「回到播放页」(UI 线程消费) */ +/* M10:META 上报闸(变化 + 低频心跳;时间用 core 单调钟)。 + * 旧实现 `if (now - last_meta >= 1000)` 依赖 meow_ui_time_ms(),真机该值高 32 位是垃圾 + * (REPORT §42)→ 条件恒真 → 每个 IPC 轮次都发 META(真机 launcher 日志 ~100ms 一帧、 + * 单次启动 21016 帧),launcher 每帧写日志+归并会话,SD 写放大到整机卡死。 */ +static ncm_meta_gate g_meta_gate; + static const char *sock_path(void) { const char *e = getenv("MEOW_IPC_SOCK"); @@ -92,8 +100,10 @@ static int read_frame(int fd, uint16_t *type, uint8_t *rbuf, uint32_t rcap) return (int)len; } -/* 组并发送一条 META。 */ -static void send_meta(int fd, uint32_t req_id) +/* 组并发送一条 META。返回 1=已发 / 0=闸判定不必发 / -1=写失败。 + * 是否发由 meta_gate 决定:状态/元数据变化立即发;播放中位置偏移 ≥5s 重锚;心跳 5s/30s; + * 硬下限 250ms(任何路径都不快于 4 帧/秒)。now_ms 必须是单调毫秒(ncm_monotonic_ms)。 */ +static int send_meta(int fd, uint32_t req_id, int64_t now_ms) { m_meta_t mm; memset(&mm, 0, sizeof mm); @@ -135,7 +145,21 @@ static void send_meta(int fd, uint32_t req_id) mm.flags |= MF_ART | MF_COVER; } if (lyric_count() > 0) mm.flags |= MF_LYRIC; - send_frame(fd, MSG_META, req_id, &mm, sizeof mm); + + ncm_meta_snap s; + s.state = (int)mm.state; + s.pos_ms = (int)mm.pos_ms; + s.dur_ms = (int)mm.dur_ms; + s.flags = (int)mm.flags; + s.title = mm.title; + s.artist = mm.artist; + s.album = mm.album; + s.artwork = mm.artwork; + if (!ncm_meta_gate_should_send(&g_meta_gate, &s, now_ms)) return 0; + + if (send_frame(fd, MSG_META, req_id, &mm, sizeof mm) != 0) return -1; + meow_log(MEOW_LOG_DEBUG, "ipc: meta sent state=%u pos=%u/%u", mm.state, mm.pos_ms, mm.dur_ms); + return 1; } static void apply_playctl(const m_playctl_t *c) @@ -177,9 +201,8 @@ static void *ipc_thread(void *arg) uint16_t htype; if (read_frame(fd, &htype, hb, sizeof hb) < 0) { close(fd); return NULL; } - send_meta(fd, 2); /* 首条注册媒体源(state=0 未播也登记,start 播后自动成源) */ + send_meta(fd, 2, ncm_monotonic_ms()); /* 首条注册媒体源(state=0 未播也登记,start 播后自动成源) */ - uint64_t last_meta = 0; while (!g_quit && !g_app_quit) { struct pollfd p; p.fd = fd; p.events = POLLIN; @@ -197,11 +220,9 @@ static void *ipc_thread(void *arg) } else if (pr < 0 && errno != EINTR) { break; } - uint64_t now = meow_ui_time_ms(); - if (now - last_meta >= 1000) { - send_meta(fd, (uint32_t)(now & 0x7FFFFFFF)); - last_meta = now; - } + /* M10:不再定时无脑发;send_meta 内部按 meta_gate 判「变化 / 位置偏移 / 低频心跳」。 */ + int64_t now_ms = ncm_monotonic_ms(); + send_meta(fd, (uint32_t)(now_ms & 0x7FFFFFFF), now_ms); } close(fd); return NULL; @@ -284,6 +305,7 @@ int ipc_media_start(void) { if (g_thr_on) return 0; g_quit = 0; + ncm_meta_gate_init(&g_meta_gate); /* M10:首帧必发(注册媒体源),之后按变化/心跳 */ if (ncm_thread_create(&g_thr, ipc_thread, NULL) != 0) return -1; pthread_detach(g_thr); g_thr_on = 1; diff --git a/patch_v2/ncm_app/app/play/meta_gate.c b/patch_v2/ncm_app/app/play/meta_gate.c new file mode 100644 index 0000000..82efb90 --- /dev/null +++ b/patch_v2/ncm_app/app/play/meta_gate.c @@ -0,0 +1,74 @@ +/* play/meta_gate.c — 见 meta_gate.h 头注释(M10 媒体会话 META 上报闸)。 */ +#include "meta_gate.h" +#include + +static void gate_cp(char *dst, size_t cap, const char *src) +{ + size_t i = 0; + if (!dst || cap == 0) return; + if (src) { + for (; src[i] && i + 1 < cap; i++) dst[i] = src[i]; + } + dst[i] = 0; +} + +void ncm_meta_gate_init(ncm_meta_gate *g) +{ + if (g) memset(g, 0, sizeof *g); +} + +static void gate_record(ncm_meta_gate *g, const ncm_meta_snap *s, int64_t now_ms) +{ + g->inited = 1; + g->state = s->state; + g->dur_ms = s->dur_ms; + g->flags = s->flags; + g->last_pos_ms = s->pos_ms; + g->last_send_ms = now_ms; + gate_cp(g->title, sizeof g->title, s->title); + gate_cp(g->artist, sizeof g->artist, s->artist); + gate_cp(g->album, sizeof g->album, s->album); + gate_cp(g->artwork, sizeof g->artwork, s->artwork); +} + +/* 只比较「会被发送的那部分」:发送侧同样按 cap 截断,超长串不得因截断而恒判「变了」。 */ +static int str_diff(const char *stored, size_t cap, const char *now) +{ + size_t i = 0; + if (!now) now = ""; + for (; i + 1 < cap && stored[i] && stored[i] == now[i]; i++) { } + if (i + 1 >= cap) return 0; /* 前 cap-1 字符一致且已到截断长度 → 相同 */ + return stored[i] != now[i]; +} + +int ncm_meta_gate_should_send(ncm_meta_gate *g, const ncm_meta_snap *s, int64_t now_ms) +{ + if (!g || !s) return 0; + if (!g->inited) { gate_record(g, s, now_ms); return 1; } /* 首帧:注册媒体源 */ + + int64_t gap = now_ms - g->last_send_ms; + if (gap < 0) { /* 时钟回退/异常:重定基准,差值按 0 处理(防下溢成「恒该发」) */ + g->last_send_ms = now_ms; + gap = 0; + } + + int changed = (s->state != g->state) || + (s->dur_ms != g->dur_ms) || + (s->flags != g->flags) || + str_diff(g->title, sizeof g->title, s->title) || + str_diff(g->artist, sizeof g->artist, s->artist) || + str_diff(g->album, sizeof g->album, s->album) || + str_diff(g->artwork, sizeof g->artwork, s->artwork); + + int64_t dpos = (int64_t)s->pos_ms - (int64_t)g->last_pos_ms; + if (dpos < 0) dpos = -dpos; /* seek 前后跳都算偏移 */ + + int playing = (s->state == NCM_META_ST_PLAYING || s->state == NCM_META_ST_BUFFER); + int64_t hb = playing ? NCM_META_HB_PLAY_MS : NCM_META_HB_IDLE_MS; + + if (!changed && dpos < NCM_META_POS_RESEND_MS && gap < hb) return 0; + if (gap < NCM_META_MIN_GAP_MS) return 0; /* 硬下限:变化挂起,下一轮再发(不快于 4 帧/秒) */ + + gate_record(g, s, now_ms); + return 1; +} diff --git a/patch_v2/ncm_app/app/play/meta_gate.h b/patch_v2/ncm_app/app/play/meta_gate.h new file mode 100644 index 0000000..c1512d1 --- /dev/null +++ b/patch_v2/ncm_app/app/play/meta_gate.h @@ -0,0 +1,66 @@ +/* play/meta_gate.h — 媒体会话 META 上报闸(M10:META 洪泛修复点;纯逻辑,宿主可驱动)。 + * + * 背景(真机证据):launcher meow_20261105_122541.log 里 21016 条 `ipc: META app=dev.player.ncm + * state=0 pos=0/0 rc=0 -> ACK`,间隔 ~100ms(每次 launcher 收帧都要写日志+归并会话)。 + * 旧 ipc_media 的节奏是 `if (now - last_meta >= 1000)`,同样依赖 meow_ui_time_ms() 的 64 位差值; + * 真机该值高 32 位是垃圾(REPORT §42)→ 条件几乎恒真 → 每个 IPC 轮次都发 META, + * 1s 心跳退化成 10Hz 洪泛,把 launcher 的 SD 写/会话归并拖满,整机卡死。 + * + * 修复后的上报策略(不再是定时器,而是「变化 + 低频心跳」): + * - 状态/时长/标题/歌手/专辑/封面/歌词标志变化 → 立即上报(用户操作要即时可见); + * - 播放中位置相对上次上报偏移 ≥ 5s(含 seek 前后跳)→ 上报,给 launcher 重锚进度 + * (launcher meow_media_display_pos 会按 last_active 自行外推,5s 足够); + * - 心跳:播放中 5s、非播放 30s(保活会话/防 launcher 会话超时清理); + * - 硬下限 250ms:任何路径(含状态抖动)都不快于 4 帧/秒。 + * 时间基准由调用方给单调钟(ipc_media 传 core 的 ncm_monotonic_ms())。 + */ +#ifndef NCM_META_GATE_H +#define NCM_META_GATE_H + +#include + +/* 与 meow_ipc.h 的 m_meta_t.state 同口径(MF_PLAYBACK 会话状态)。 */ +enum { + NCM_META_ST_STOPPED = 0, + NCM_META_ST_PLAYING = 1, + NCM_META_ST_PAUSED = 2, + NCM_META_ST_BUFFER = 3, +}; + +/* 一次上报快照(字符串由调用方持有,本模块只拷贝比较,不接管所有权)。 */ +typedef struct { + int state; /* NCM_META_ST_* */ + int pos_ms; + int dur_ms; + int flags; /* MF_ART/MF_COVER/MF_LYRIC 位 */ + const char *title; + const char *artist; + const char *album; + const char *artwork; +} ncm_meta_snap; + +typedef struct { + int inited; + int state; + int dur_ms; + int flags; + int last_pos_ms; /* 上次上报时的播放位置(判进度偏移/seek) */ + int64_t last_send_ms; /* 上次上报的单调毫秒 */ + char title[128]; + char artist[128]; + char album[128]; + char artwork[192]; +} ncm_meta_gate; + +#define NCM_META_POS_RESEND_MS 5000 /* 播放中位置偏移(ms)触发重报 */ +#define NCM_META_HB_PLAY_MS 5000 /* 播放中心跳 */ +#define NCM_META_HB_IDLE_MS 30000 /* 非播放心跳 */ +#define NCM_META_MIN_GAP_MS 250 /* 硬下限:任何情况下 ≤ 4 帧/秒 */ + +void ncm_meta_gate_init(ncm_meta_gate *g); + +/* 返回 1=现在应当发送(内部已把本次快照记为「已上报」);0=不必发。 + * now_ms=单调毫秒(建议 ncm_monotonic_ms())。未初始化时首调必发(注册媒体源)。 */ +int ncm_meta_gate_should_send(ncm_meta_gate *g, const ncm_meta_snap *s, int64_t now_ms); + +#endif /* NCM_META_GATE_H */