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:
1 parent
35a636ef42
commit
7f4e18b0ed
3 files changed
+175
-13
No files matched your search
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
Reference in new issue
Block a user