Files
MeowOS-For-R3P2/patch_v2/ncm_app/bench/vol_sync_sim.c
T
AskaEth 7c7d891570 feat: M6.44+M6.45 收编 — 真机 0.8.45 现场保全(reader章节/key_router按键路由/WiFi连接修复/ncm bench+IME修复)
脏工作区现场收编:devdocs/local/m6_44_45_dirty_tracked.patch(46 个已跟踪文本文件,
+3090/-585, 已对 M6.43 基线干净适配) + m6_44_45_untracked_src.tar.gz 中 patch_v2/ 下
13 个新增源文件, 合成单个提交, 内容与容器工作树(R3ProII/patch_v2)逐字节一致。

- reader 章节: 新增 core/rdchapter.{h,c} — TXT 章节规范解析器, 扫描 512KB 码位对齐段文件
  逐段流式识别标题行(行首缩进≤4/显示宽≤40/行内标点≤1 三重约束防误判), 输出标题+全书字符
  偏移+段号/段内偏移+阿拉伯章号(中文数字归一), 首章前内容归"卷首"; book/reader/rdenc/
  rdlayout/settings 接入章节跳转与进度锚点, 附 chapter_smoke/turn_repro/build_turn_repro.sh
- key_router 按键路由: 新增 meow_launcherd/key_router.{h,c} — launcher-UI 页物理键统一路由
  (P1 仅音量键), 把读取边沿+防抖+长按 held+ramp 推进从 5 个会话收敛为进程内单开;
  各页每圈 key_router_pump() 取防抖后边沿, 动作(调音量/光标导航)仍由调用页决定,
  保留 run_menu 的 meow_mode 分支; App 前台 volume_page 武装时边沿抑制;
  main_menu/now_playing/notif_center/settings_page/subpage 接入, 附 test_key_router_vol/test_np_keys
- WiFi 连接修复: meow_netmgrd connect 有界等待 wpa_state=COMPLETED(普通 10s/隐藏网络 15s,
  env 可调)修设置页连接无响应; list_networks/scan_results 的 \xNN 转义还原始字节, 修中文 SSID
  永不命中致新建同名 open 记录顶影; launcher netmgr_ipc 配读超时; 附 test_wifi_connect(桩
  wpa_cli 驱动 static wifi_connect_ex), test_netmgrd.sh 补用例; wifi_page 同步
- ncm bench: 新增 bench/lyric_sim.c、bench/plpage_sim.c, vol_sync_sim 与 ncm core/app 同步
- SDK IME 修复: shift 大小写、K_ENTER 只提交不关、阻塞 IME 循环喂 on_tick 不被遮挡、
  IME 打开 ~200ms TAP 冷却窗吞 stray-TAP; 附 sdk/test_ime_fix.c
- 同期: meow_audiod 音量异步 ACK 线程(代际防 fd/槽复用竞态)、watchdog 音量收编(首选 audiod
  权威, settings.conf 覆盖路径)、panel 文件搜索(F1-④)与蓝牙只读详情(F1-③)、各类 build 脚本同步

范围红线: third_party/legado-E-main 原开发者未跟踪, 本次不入库(保留 devdocs/local tar 参考);
二进制不入库(后续重编); devdocs/ 不入库。59 文件全部在 patch_v2/ 下, 无 third_party/devdocs。
验证: git apply --check 干净; 59 文件 md5 与容器 patch_v2 逐一比对 59/59 一致(含
key_router.c/rdchapter.c/meow_ui_ime.c/test_wifi_connect.c 抽查); 可执行位保守
(check_lgpl_release.sh 100755, build_turn_repro.sh 100644 与容器一致)。
2026-11-04 20:02:28 +08:00

215 lines
9.9 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/vol_sync_sim.c — 任务1:开播前「系统当前音量 → audiod」下发宿主验证。
*
* 场景(0 真实请求/0 真实 audiod):fake audiod socket(AF_UNIX SOCK_STREAM)+ 临时 settings.conf
* (audio.volume = <V>,读改写单键同源同键)。调 audio_out_sync_volume(),断言 fake audiod 收到
* 一条 MSG_AUDIO_VOLUME 帧,且 pct == settings 的 audio.volume。这验证「打开音频会话时把系统
* 音量同步下发到 audiod」的根因修复(未播放时 DAC 停在默认/残留音量 → 动音量键才追设定值)。
*
* 另验证:音量语义只传 pct(0-100)——反转/真静音(255) 由 audiod dac_vol_set 负责,本层不碰。
*
* 编译(宿主,ncm_app 目录下):
* gcc -O1 -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
* -c bench/vol_sync_sim.c -o /tmp/vs.o
* gcc -O1 -DJSMN_HEADER -I. -Icore -I../sdk -I../meow_launcherd \
* -c app/play/audio_out.c -o /tmp/vs_ao.o
* gcc -O1 -o vol_sync_sim /tmp/vs.o /tmp/vs_ao.o -lpthread
* 运行:./vol_sync_sim (0 网络;PASS/FAIL 退出码)
*/
#include "../app/ncm_app_internal.h"
#include "meow_ipc.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/un.h>
ncm_app_state_t g_st;
ncm_play_run_t g_play;
volatile int g_app_quit = 0;
volatile int g_net_ready = 1;
int g_host_mock = 0;
size_t ncm_strlcpy(char *dst, const char *src, size_t sz)
{
size_t n = 0;
if (sz) { while (src[n] && n < sz - 1) { dst[n] = src[n]; n++; } dst[n] = 0; }
while (src[n]) n++;
return n;
}
void meow_log(meow_log_level lvl, const char *fmt, ...) { (void)lvl; (void)fmt; }
/* audio_out.c 引用但本 sim 不经 SDK 调用的符号:只链不跑就满足链接。
* 会话建立→音量同步用例(§切歌音量骤增回归)需要 audio_out_open 真正「开新流/分配新 sid」,
* 故 meow_audio_open 桩改为返回递增 sid(模拟 audiod 单调分配),供 §切歌 会话切换复现。 */
static int g_sim_sid = 0;
int meow_audio_open(int rate, int ch, int fmt) { (void)rate;(void)ch;(void)fmt; return ++g_sim_sid; }
int meow_audio_write(const void *pcm, int frames) { (void)pcm; return frames; }
int meow_audio_pause(int sid, int paused) { (void)sid;(void)paused; return 0; }
int meow_audio_close(int sid) { (void)sid; return 0; }
int meow_audio_query(meow_audio_state *out) { (void)out; return -1; }
/* ---- fake audiod ---- */
static const char *g_sock = "/tmp/vol_sync_audiod.sock";
static uint16_t g_got_type = 0;
static int32_t g_got_pct = -1;
static int g_got = 0;
static volatile int g_nconn = 0; /* 收到多少条连接 */
static volatile int g_nframe = 0; /* 收到多少条 VOLUME 帧 */
static volatile int g_stop = 0;
/* fake audiod:接受多条连接(上限 32,超过即停——失败才触发,正常不该超),每条只读一帧
* VOLUME 然后等对端 EOF(ncm 侧 close)。统计连接数与帧数,供「重复 sync 不新建连接」断言。 */
static void *server(void *arg)
{
(void)arg;
unlink(g_sock);
int lfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (lfd < 0) return NULL;
struct sockaddr_un a;
memset(&a, 0, sizeof a);
a.sun_family = AF_UNIX;
strncpy(a.sun_path, g_sock, sizeof a.sun_path - 1);
if (bind(lfd, (struct sockaddr *)&a, sizeof a) != 0) { close(lfd); return NULL; }
listen(lfd, 16);
int lfl = fcntl(lfd, F_GETFL, 0);
if (lfl >= 0) fcntl(lfd, F_SETFL, lfl | O_NONBLOCK); /* 非阻塞:g_stop 能及时退出 */
while (!g_stop && g_nconn < 32) {
int fd = accept(lfd, NULL, NULL);
if (fd < 0) { usleep(20000); continue; } /* EAGAIN:无连接,短睡重试 */
g_nconn++;
/* 读满一帧(20B)或 EOF:stream socket read 可分段返回,须循环凑满再判。 */
uint8_t frame[MEOW_IPC_HDR_LEN + sizeof(int32_t)];
ssize_t n = 0;
while (n < (ssize_t)sizeof frame) {
ssize_t r = read(fd, frame + n, sizeof frame - (size_t)n);
if (r <= 0) break; /* EOF / 错 */
n += r;
}
if (n >= (ssize_t)(MEOW_IPC_HDR_LEN + sizeof(int32_t))) {
uint32_t magic, len;
uint16_t ver, type;
memcpy(&magic, frame, 4); memcpy(&ver, frame + 4, 2); memcpy(&type, frame + 6, 2);
memcpy(&len, frame + 12, 4);
if (magic == MEOW_IPC_MAGIC && ver == MEOW_IPC_VERSION && type == MSG_AUDIO_VOLUME) {
g_got_type = type;
memcpy(&g_got_pct, frame + 16, sizeof g_got_pct);
g_got = 1;
g_nframe++;
}
}
close(fd);
}
close(lfd);
return NULL;
}
int main(void)
{
int fail = 0;
/* 临时 settings.conf:audio.volume = 24(读改写单键同源键),另带 max 干扰键 */
const char *cfg = "/tmp/vol_sync_settings.conf";
FILE *f = fopen(cfg, "w");
if (!f) { printf("FAIL cannot write settings\n"); return 1; }
fprintf(f, "# meow-settings 1\naudio.volume = 24\naudio.volume_max = 50\ndisplay.brightness = 80\n");
fclose(f);
setenv("MEOW_SETTINGS_FILE", cfg, 1);
setenv("MEOW_AUDIO_SOCK", g_sock, 1);
pthread_t th;
if (pthread_create(&th, NULL, server, NULL) != 0) { printf("FAIL server thread\n"); return 1; }
usleep(50000); /* 等 fake audiod listen */
/* 第一次 sync(首次 open):应下发一帧 VOLUME,pct==settings */
audio_out_sync_volume();
usleep(50000); /* 等服务线程 accept+读帧(主/服务线程异步) */
if (!g_got) { printf("FAIL fake audiod received no frame\n"); fail = 1; }
else if (g_got_type != MSG_AUDIO_VOLUME) {
printf("FAIL expected MSG_AUDIO_VOLUME (0x%04x) got 0x%04x\n", MSG_AUDIO_VOLUME, g_got_type);
fail = 1;
} else if (g_got_pct != 24) {
printf("FAIL expected pct=24 (settings audio.volume) got %d\n", (int)g_got_pct);
fail = 1;
} else {
printf("PASS audiod sync volume: type=0x%04x pct=%d == settings.audio.volume\n",
g_got_type, (int)g_got_pct);
}
/* 连接风暴回归(§2026-09-02 fd=N accept 根因):音量未变时反复 sync 不得新建连接。
* 模拟边下边播/切歌期频繁 audio_out_open → audio_out_sync_volume(同值,不重发)。 */
for (int i = 0; i < 50; i++) audio_out_sync_volume(); /* 同值 50 次:应零新增连接 */
usleep(50000);
if (g_nconn != 1) {
printf("FAIL unchanged-volume re-sync created %d connections (expect 1)\n", (int)g_nconn);
fail = 1;
} else {
printf("PASS unchanged-volume re-sync: 50 calls -> 1 connection (storm eliminated)\n");
}
/* 值变才重发:settings 改成 50 → 下一次 sync 应新建第 2 条连接 */
FILE *g = fopen(cfg, "w");
if (!g) { printf("FAIL cannot rewrite settings\n"); fail = 1; }
else {
fprintf(g, "# meow-settings 1\naudio.volume = 50\naudio.volume_max = 50\ndisplay.brightness = 80\n");
fclose(g);
}
audio_out_sync_volume();
usleep(50000);
if (g_nconn != 2 || g_got_pct != 50) {
printf("FAIL changed-volume re-sync: conns=%d (expect 2) last_pct=%d (expect 50)\n",
(int)g_nconn, (int)g_got_pct);
fail = 1;
} else {
printf("PASS changed-volume re-sync: settings=50 -> 2nd connection pct=50\n");
}
/* §切歌音量骤增回归:会话建立→音量同步。切歌 = 新 audio_out_open = 新 audiod 流(新 sid),
* 必须**每会话重发一次**当前音量——此前 g_vol_synced 进程级「首同步一次」,新会话被跳过,
* DAC 停在会话建立后的默认响度(切歌骤增,按音量键才回落)。用真实 audio_out_open/close
* (meow_audio_open 桩返回递增 sid)断言:新会话必 +1 连接(重发音量),同会话内重复 sync 不新增。 */
{
int base = g_nconn; /* 上面值变重发测试后基线 = 2 条连接 */
/* 会话 1(首开):应重发 1 条 VOLUME,pct==当前 settings(50) */
if (audio_out_open(44100, 2, 16) != 0) { printf("FAIL session1 audio_out_open\n"); fail = 1; }
usleep(50000);
if (g_nconn != base + 1) {
printf("FAIL session1 establish: conns=%d (expect %d) — 新会话未重发音量\n", (int)g_nconn, base + 1);
fail = 1;
} else if (g_got_pct != 50) {
printf("FAIL session1 establish: pct=%d (expect 50)\n", (int)g_got_pct); fail = 1;
} else {
printf("PASS session1 establish -> 1 volume sync (pct=50)\n");
}
/* 同会话内反复 sync(同值):不得新建连接(防 fd 风暴) */
for (int i = 0; i < 20; i++) audio_out_sync_volume();
usleep(50000);
if (g_nconn != base + 1) {
printf("FAIL same-session repeat sync: conns=%d (expect %d)\n", (int)g_nconn, base + 1);
fail = 1;
} else {
printf("PASS same-session repeat sync -> no new connection (storm-free)\n");
}
audio_out_close();
/* 会话 2(切歌):再开 → 必须再重发 1 条(新 sid),pct==50 */
if (audio_out_open(44100, 2, 16) != 0) { printf("FAIL session2 audio_out_open\n"); fail = 1; }
usleep(50000);
if (g_nconn != base + 2) {
printf("FAIL session2 (song switch): conns=%d (expect %d) — 切歌未重发音量\n", (int)g_nconn, base + 2);
fail = 1;
} else if (g_got_pct != 50) {
printf("FAIL session2 switch: pct=%d (expect 50)\n", (int)g_got_pct); fail = 1;
} else {
printf("PASS session2 (song switch) -> new volume sync (pct=50)\n");
}
}
g_stop = 1;
pthread_join(th, NULL);
unlink(g_sock);
unlink(cfg);
if (fail) { printf("vol_sync_sim FAIL\n"); return 1; }
printf("vol_sync_sim PASS\n");
return 0;
}