fix(ncm): META 改「变化+低频心跳」,不再每 100ms 刷 launcher(M10)

根因:ipc_media 主循环 `if (now - last_meta >= 1000)` 同样依赖 meow_ui_time_ms()
的坏 64 位值(REPORT §42)→ 条件恒真 → 每个 IPC 轮次都发 MSG_META。
launcher 日志 meow_20261105_122541.log:单次启动 21016 条
`ipc: META app=dev.player.ncm state=0 pos=0/0 -> ACK`,间隔 ~100ms,且全是
无变化的静止帧;launcher 每帧写 2 行日志+归并会话,SD 写放大到整机卡死。

修复:
- 新增 app/play/meta_gate.{h,c}(纯逻辑):状态/时长/标题/歌手/专辑/封面/歌词
  标志变化 → 立即报;播放中位置偏移 ≥5s 重锚(launcher 自己按 last_active 外推);
  心跳播放中 5s / 非播放 30s;硬下限 250ms(任何路径 ≤4 帧/秒);时钟回退不下溢;
  字符串按发送侧截断长度比较(超长标题不得恒判「变了」)。
- ipc_media.send_meta 改为「组快照 → 闸判定 → 才发」,删除 1s 定时器;
  时间改 ncm_monotonic_ms();首帧仍必发(注册媒体源);发送走 DEBUG 日志。
This commit is contained in:
AskaEth committed 2026-11-05 13:00:28 +08:00
1 parent 35a636ef42
commit 7f4e18b0ed
3 files changed
+175 -13

No files matched your search

+35 -13
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -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;
+74
View File
@@ -0,0 +1,74 @@
/* play/meta_gate.c — 见 meta_gate.h 头注释(M10 媒体会话 META 上报闸)。 */
#include "meta_gate.h"
#include <string.h>
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;
}
+66
View File
@@ -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 <stdint.h>
/* 与 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 */