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 与容器一致)。
This commit is contained in:
1 parent
e87ccdf061
commit
7c7d891570
59 files changed
+5305
-590
No files matched your search
@@ -8,9 +8,10 @@
|
||||
* 核心设计(用户定案,勿推翻):
|
||||
* - 独占 + 抢占(后开流赢,旧流收 MSG_AUDIO_PREEMPTED 并被置 draining,PCM 丢弃);
|
||||
* 软混音已否决(MIPS32 软 SRC 伤音质),留混音器接口位(v1 不实现)。
|
||||
* - 单进程双线程(+路由线程):IO 线程(accept/读帧/解析/ring 写)、播放线程
|
||||
* - 单进程四线程:IO 线程(accept/读帧/解析/ring 写)、播放线程
|
||||
* (独占 ALSA 直开,从 ring 取数写 DAC,xrun PREPARE 恢复)、路由线程
|
||||
* (轮询 headset/balance 插孔 switch 状态 → amixer cset numid=9)。
|
||||
* (轮询 headset/balance 插孔 switch 状态 → amixer cset numid=9)、
|
||||
* 音量执行线程(MSG_AUDIO_VOLUME 异步落 amixer + 持久化,末值去抖,见事项三)。
|
||||
* - 格式归一 = 协商:grant_rate/ch/fmt 回客户端应产出的格式(SDK 负责 mono→stereo
|
||||
* 复制 / S16↔S32 定值升降位);audiod 只做「硬件最优」格式的字节管道。
|
||||
* - DAC 原生率跟随(无 SRC);切率 mute-重配-恢复。
|
||||
@@ -130,6 +131,7 @@ static void session_clean_locked(void);
|
||||
static void _audio_mark_reconfig_needed(void);
|
||||
static pthread_mutex_t g_mu = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t g_cond;
|
||||
static pthread_cond_t g_vol_cond; /* 音量异步执行线程等待(CLOCK_MONOTONIC,main 中初始化) */
|
||||
|
||||
struct client {
|
||||
int used;
|
||||
@@ -137,10 +139,23 @@ struct client {
|
||||
char app_id[MEOW_APP_ID_MAX];
|
||||
uint32_t sid;
|
||||
int state; /* CLS_* */
|
||||
uint32_t gen; /* 分配代际(音量异步 ACK 防 fd/槽复用竞态) */
|
||||
int vol_ack; /* 音量请求已执行,待 IO 线程回 ACK(vol 线程写/IO 线程清) */
|
||||
uint32_t vol_ack_req; /* 对应 req_id */
|
||||
uint32_t vol_ack_status; /* ACK status(应用后 g_vol_pct) */
|
||||
uint8_t rbuf[READ_BUF_MAX];
|
||||
size_t rlen;
|
||||
};
|
||||
static struct client g_cli[CLI_MAX];
|
||||
static uint32_t g_cli_gen = 0; /* 客户端分配代际计数(cli_alloc 递增) */
|
||||
|
||||
/* ---- 音量异步化(事项三):IO 线程只入队末值,专用 vol 线程落 amixer+持久化后
|
||||
* 经 IO 线程回 ACK。驱动 wedge 时 3×popen≈4.5s 不再卡持锁的 dispatch/IO 循环。 ---- */
|
||||
static int g_vol_pending = 0; /* 有待执行音量请求(g_mu 保护) */
|
||||
static int g_vol_pending_pct = 0; /* 末值(0-100;连续请求只保留最新=去抖) */
|
||||
static struct client *g_vol_pending_cli = NULL; /* 末次请求方(ACK 用;可能已回收,靠 gen 校验) */
|
||||
static uint32_t g_vol_pending_req = 0; /* 末次请求 req_id */
|
||||
static uint32_t g_vol_pending_gen = 0; /* 末次请求方 gen(防槽复用误 ACK) */
|
||||
|
||||
/* 当前授权流(喂养 DAC 的唯一流) */
|
||||
struct stream_info {
|
||||
@@ -308,15 +323,16 @@ static int send_frame(int fd, uint16_t type, uint32_t req_id, const void *payloa
|
||||
}
|
||||
|
||||
/* cond 有界等待(g_cond 为 CLOCK_MONOTONIC;调用方已持 g_mu)。 */
|
||||
static void cond_wait_tmo(int ms)
|
||||
static void cond_wait_tmo_cv(pthread_cond_t *cv, int ms)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
ts.tv_sec += ms / 1000;
|
||||
ts.tv_nsec += (long)(ms % 1000) * 1000000L;
|
||||
if (ts.tv_nsec >= 1000000000L) { ts.tv_sec++; ts.tv_nsec -= 1000000000L; }
|
||||
pthread_cond_timedwait(&g_cond, &g_mu, &ts);
|
||||
pthread_cond_timedwait(cv, &g_mu, &ts);
|
||||
}
|
||||
static void cond_wait_tmo(int ms) { cond_wait_tmo_cv(&g_cond, ms); }
|
||||
|
||||
/* ---- frame 大小/位深 ---- */
|
||||
static int fmt_bps(int fmt)
|
||||
@@ -1135,12 +1151,14 @@ static void bt_vol_full(void)
|
||||
}
|
||||
|
||||
/* 音量应用(含 BT 隔离):BT → 只通知 AVRCP(best-effort),绝不动 DAC numid=1/2、不持久化
|
||||
* audio.volume(那是 DAC 音量键);DAC → 原感知锥度反转 + 持久化。 */
|
||||
static void vol_apply(int pct)
|
||||
* audio.volume(那是 DAC 音量键);DAC → 原感知锥度反转 + 持久化。
|
||||
* target 为调用方在持锁下快照的 g_target;本函数不持 g_mu 执行(g_vol_pct 为 plain int
|
||||
* 松散写,同 route_thread 的 g_route_cur 语义,MIPS32 int 写天然原子)。 */
|
||||
static void vol_apply(int pct, int target)
|
||||
{
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
if (g_target == AUDIO_OUT_BT) {
|
||||
if (target == AUDIO_OUT_BT) {
|
||||
logmsg("vol -> %d%% (BT/AVRCP, DAC numid=1/2 NOT written)", pct);
|
||||
return;
|
||||
}
|
||||
@@ -1551,6 +1569,7 @@ static struct client *cli_alloc(int fd)
|
||||
freec->fd = fd;
|
||||
freec->state = CLS_OPEN;
|
||||
freec->sid = 0;
|
||||
freec->gen = ++g_cli_gen; /* 代际递增:音量异步 ACK 校验用,防槽复用误发 */
|
||||
return freec;
|
||||
}
|
||||
static void cli_free(struct client *c)
|
||||
@@ -1771,13 +1790,20 @@ static void dispatch(struct client *c, uint16_t type, uint32_t req_id, const uin
|
||||
int32_t pct = -1;
|
||||
if (len >= sizeof(int32_t)) memcpy(&pct, pl, sizeof pct);
|
||||
if (pct >= 0) {
|
||||
/* 音量应用含 BT 隔离:target=BT 时绝不下发 DAC 反转(vol_apply 内判断) */
|
||||
vol_apply((int)pct);
|
||||
/* 异步化(事项三):只入队末值,**不在持 g_mu 下 popen amixer**——驱动 wedge 时
|
||||
* 一次 set = 3×popen ≈ 4.5s,会把 OPEN/QUERY/喂流一起卡死。连续请求合并为末值
|
||||
* (去抖),由 vol 线程应用+持久化后经 IO 线程回 ACK。 */
|
||||
g_vol_pending = 1;
|
||||
g_vol_pending_pct = (pct > 100) ? 100 : (int)pct;
|
||||
g_vol_pending_cli = c;
|
||||
g_vol_pending_req = req_id;
|
||||
g_vol_pending_gen = c->gen;
|
||||
pthread_cond_signal(&g_vol_cond);
|
||||
} else {
|
||||
g_vol_pct = settings_get_volume();
|
||||
g_vol_pct = settings_get_volume(); /* 读当前:文件读非 popen,可同步 */
|
||||
m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_vol_pct;
|
||||
send_frame(c->fd, MSG_ACK, req_id, &a, sizeof a);
|
||||
}
|
||||
m_ack_t a; a.code = MEOW_OK; a.status = (uint32_t)g_vol_pct;
|
||||
send_frame(c->fd, MSG_ACK, req_id, &a, sizeof a);
|
||||
break;
|
||||
}
|
||||
case MSG_AUDIO_OUTPUT: {
|
||||
@@ -1911,6 +1937,14 @@ static void *io_thread(void *arg)
|
||||
if (snaps[i].revents & (POLLHUP | POLLERR | POLLNVAL)) { cli_free(c); continue; }
|
||||
if (snaps[i].revents & POLLIN) cx_read_frames(c);
|
||||
}
|
||||
/* 音量 ACK 回写(事项三):vol 线程执行完只置标记,由 IO 线程统一发送——客户端 fd
|
||||
* 生命周期归 IO 线程独占,跨线程直接 send_frame 有 fd 复用竞态。 */
|
||||
for (int i = 0; i < CLI_MAX; i++) {
|
||||
if (!g_cli[i].used || !g_cli[i].vol_ack) continue;
|
||||
m_ack_t va; va.code = MEOW_OK; va.status = g_cli[i].vol_ack_status;
|
||||
send_frame(g_cli[i].fd, MSG_ACK, g_cli[i].vol_ack_req, &va, sizeof va);
|
||||
g_cli[i].vol_ack = 0;
|
||||
}
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
/* 路由线程与播放线程独立;此处不再做 */
|
||||
}
|
||||
@@ -1921,6 +1955,40 @@ static void *io_thread(void *arg)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- 音量执行线程(事项三):异步落 amixer + 持久化,不在 IO 线程持锁 popen ----
|
||||
* 等待 g_vol_cond 取末值(连续请求合并=去抖),放锁后执行 dac_vol_set(3×popen,有界)
|
||||
* + settings_set_volume(文件 IO),完成后只置 ACK 标记,由 IO 线程统一发送(防 fd 复用竞态)。
|
||||
* BT 目标只记日志不动 DAC/不持久化(AVRCP 隔离,g_vol_pct 保持不变)。 */
|
||||
static void *vol_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
for (;;) {
|
||||
pthread_mutex_lock(&g_mu);
|
||||
while (!g_vol_pending && !g_stop)
|
||||
cond_wait_tmo_cv(&g_vol_cond, COND_WAIT_MS);
|
||||
if (g_stop) { pthread_mutex_unlock(&g_mu); break; }
|
||||
int pct = g_vol_pending_pct;
|
||||
int target = g_target; /* 持锁快照:vol_apply 不读全局 */
|
||||
struct client *c = g_vol_pending_cli;
|
||||
uint32_t req = g_vol_pending_req;
|
||||
uint32_t cgen = g_vol_pending_gen;
|
||||
g_vol_pending = 0;
|
||||
g_vol_pending_cli = NULL;
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
|
||||
vol_apply(pct, target);
|
||||
|
||||
pthread_mutex_lock(&g_mu);
|
||||
if (c && c->used && c->gen == cgen) { /* 槽复用校验:只 ACK 原请求方 */
|
||||
c->vol_ack = 1;
|
||||
c->vol_ack_req = req;
|
||||
c->vol_ack_status = (uint32_t)g_vol_pct;
|
||||
}
|
||||
pthread_mutex_unlock(&g_mu);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ---- 路由线程主体 ---- */
|
||||
static void *route_thread(void *arg)
|
||||
{
|
||||
@@ -2149,19 +2217,21 @@ int main(int argc, char **argv)
|
||||
logmsg("volume init = %d%% target=%s codec=%s volctl=%s",
|
||||
g_vol_pct, target_name(), g_bt_codec, volctl_name());
|
||||
|
||||
/* g_cond 用 CLOCK_MONOTONIC(所有 timedwait 的基准;防系统时间跳变影响护栏超时) */
|
||||
/* g_cond/g_vol_cond 用 CLOCK_MONOTONIC(所有 timedwait 的基准;防系统时间跳变影响护栏超时) */
|
||||
{
|
||||
pthread_condattr_t ca;
|
||||
pthread_condattr_init(&ca);
|
||||
pthread_condattr_setclock(&ca, CLOCK_MONOTONIC);
|
||||
pthread_cond_init(&g_cond, &ca);
|
||||
pthread_cond_init(&g_vol_cond, &ca);
|
||||
pthread_condattr_destroy(&ca);
|
||||
}
|
||||
|
||||
pthread_t ti, tp, tr;
|
||||
pthread_t ti, tp, tr, tv;
|
||||
pthread_create(&ti, NULL, io_thread, NULL);
|
||||
pthread_create(&tp, NULL, playback_thread, NULL);
|
||||
pthread_create(&tr, NULL, route_thread, NULL);
|
||||
pthread_create(&tv, NULL, vol_thread, NULL);
|
||||
|
||||
/* 主线程 = watchdog:心跳静默 >WD_SILENT_SEC 秒即告警(只告警不强杀——
|
||||
* 对卡在内核的线程任何外部干预都不安全;护栏保证其它线程/连接继续服务)。 */
|
||||
@@ -2191,6 +2261,7 @@ int main(int argc, char **argv)
|
||||
pthread_join(ti, NULL);
|
||||
pthread_join(tp, NULL);
|
||||
pthread_join(tr, NULL);
|
||||
pthread_join(tv, NULL);
|
||||
unlink(g_sock);
|
||||
if (g_logfd >= 0) close(g_logfd);
|
||||
return 0;
|
||||
|
||||
@@ -187,6 +187,24 @@ static int start_audiod_trace(const char *sink, const char *settings_path, const
|
||||
return pid;
|
||||
}
|
||||
|
||||
/* 带假 amixer 的启动(事项三异步化验证:假 amixer 每次 cset 睡 1s,模拟慢 popen)。
|
||||
* 用于断言「MSG_AUDIO_VOLUME 在 IO 线程只入队、不放锁 popen」——慢 amixer 期间 QUERY 仍即时。 */
|
||||
static int start_audiod_fake_amixer(const char *sink, const char *settings_path, const char *amixer_script)
|
||||
{
|
||||
pid_t pid = fork();
|
||||
if (pid == 0) {
|
||||
setenv("MEOW_AUDIO_SINK", sink, 1);
|
||||
setenv("MEOW_AUDIO_OUT", TWAV, 1);
|
||||
setenv("MEOW_AUDIO_SOCK", TSOCK, 1);
|
||||
setenv("MEOW_AUDIO_FORCE", "1", 1);
|
||||
if (settings_path) setenv("MEOW_AUDIO_SETTINGS", settings_path, 1);
|
||||
if (amixer_script) setenv("MEOW_AUDIO_AMIXER", amixer_script, 1);
|
||||
execl("./meow_audiod_host", "meow_audiod_host", (char *)0);
|
||||
_exit(127);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
/* 直接客户端 open(带 flags:AUDIO_FLAG_DSD 用于 DoP) */
|
||||
static int do_open_f(struct cli *c, int rate, int ch, int fmt, uint32_t flags, m_audio_open_ack_t *ack)
|
||||
{
|
||||
@@ -1080,6 +1098,62 @@ int main(void)
|
||||
}
|
||||
stop_audiod();
|
||||
|
||||
/* ---- T17:MSG_AUDIO_VOLUME 异步化(事项三)。假 amixer 每次 cset 睡 1s(一次 set=3×cset≈3s)。
|
||||
* 断言:慢 amixer 期间 QUERY 即时返回(dispatch 不放锁 popen);应用后回 ACK + 双组写 + 持久化。 */
|
||||
printf("[T17 volume async (dispatch not blocked by slow amixer)]\n");
|
||||
unlink(TSOCK); unlink(TWAV);
|
||||
const char *tconf17 = "/tmp/meow_test_settings17.conf";
|
||||
FILE *tf17 = fopen(tconf17, "w");
|
||||
if (tf17) { fprintf(tf17, "# meow-settings 1\naudio.volume = 60\n"); fclose(tf17); }
|
||||
const char *fake17 = "/tmp/meow_fake_amixer17.sh";
|
||||
const char *trace17 = "/tmp/meow_fake_amixer17_trace.txt";
|
||||
unlink(trace17);
|
||||
{
|
||||
FILE *fs = fopen(fake17, "w");
|
||||
if (fs) {
|
||||
fprintf(fs, "#!/bin/sh\necho \"cset $4 $5\" >> %s\nsleep 1\nexit 0\n", trace17);
|
||||
fclose(fs);
|
||||
chmod(fake17, 0755);
|
||||
}
|
||||
}
|
||||
g_audiod_pid = start_audiod_fake_amixer("file", tconf17, fake17);
|
||||
usleep(400000);
|
||||
{
|
||||
struct cli v17;
|
||||
check(cli_connect(&v17) == 0, "connect V17");
|
||||
int32_t pct = 40;
|
||||
check(sf(v17.fd, MSG_AUDIO_VOLUME, 17, &pct, sizeof pct) == 0, "send async volume 40");
|
||||
/* 慢 amixer 执行期间 QUERY 必须即时(<600ms),证明 IO 线程没被 popen 卡住 */
|
||||
struct timespec t0, t1;
|
||||
clock_gettime(CLOCK_MONOTONIC, &t0);
|
||||
char q17[1024];
|
||||
int qok = do_query(&v17, q17, sizeof q17);
|
||||
clock_gettime(CLOCK_MONOTONIC, &t1);
|
||||
long qms = (t1.tv_sec - t0.tv_sec) * 1000L + (t1.tv_nsec - t0.tv_nsec) / 1000000L;
|
||||
check(qok == 0 && qms < 600, "QUERY immediate during slow amixer (async dispatch)");
|
||||
if (qok == 0 && qms >= 600) printf(" (query took %ldms)\n", qms);
|
||||
/* 应用后回 ACK(末值 40) */
|
||||
uint8_t ab[64]; uint16_t at;
|
||||
int rlen = rf_tmo(v17.fd, 4000, &at, ab, sizeof ab);
|
||||
check(rlen >= 0 && at == MSG_ACK, "async volume ACK returned after apply");
|
||||
usleep(100000);
|
||||
{
|
||||
FILE *chk = fopen(tconf17, "r"); int has40 = 0; char l[128];
|
||||
if (chk) { while (fgets(l, sizeof l, chk)) if (strstr(l, "audio.volume = 40")) has40 = 1; fclose(chk); }
|
||||
check(has40, "async volume persisted = 40");
|
||||
}
|
||||
{
|
||||
char trb[2048]; int trl = 0;
|
||||
int fd = open(trace17, O_RDONLY);
|
||||
if (fd >= 0) { trl = (int)read(fd, trb, sizeof trb - 1); close(fd); if (trl > 0) trb[trl] = 0; }
|
||||
check(trl > 0 && strstr(trb, "numid=1 ") && strstr(trb, "numid=2 ") && strstr(trb, "numid=11 "),
|
||||
"async dual-group write (numid=1/2/11) via fake amixer");
|
||||
}
|
||||
close(v17.fd);
|
||||
}
|
||||
stop_audiod();
|
||||
unlink(fake17); unlink(trace17);
|
||||
|
||||
printf("=== SUMMARY: pass=%d fail=%d ===\n", g_pass, g_fail);
|
||||
return g_fail ? 1 : 0;
|
||||
}
|
||||
Reference in new issue
Block a user