Files
MeowOS-For-R3P2/patch_v2/meow_launcherd/audio.c
T

285 lines
13 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.
/* audio.c — MeowOS launcher 拆分模块(LAUNCHER_SPLIT_PLAN 拆分)。*/
/* 本文件由拆分工具从 meow_launcherd.c 按职责搬移;逻辑与原文 0-diff(仅 static 可见性按跨模块需求调整)。*/
#include "meow_launcher_internal.h"
static int amixer_vol(int numid)
{
char cmd[96];
snprintf(cmd, sizeof cmd, "amixer -c 0 cget numid=%d 2>/dev/null", numid);
FILE *p = popen(cmd, "r");
if (!p) return -1;
char buf[128];
int v = -1;
while (fgets(buf, sizeof buf, p)) {
/* ": values=N" line — the other "values=1" on the type line is the
* element's value COUNT, not the actual volume */
char *s = strstr(buf, ": values=");
if (s) { v = atoi(s + 9); break; }
}
pclose(p);
return v;
}
static void amixer_set(int numid, int v)
{
if (v < 0) return;
char cmd[96];
snprintf(cmd, sizeof cmd, "amixer -c 0 cset numid=%d %d >/dev/null 2>&1",
numid, v);
system(cmd);
}
struct vol_snap vol_snapshot(void)
{
struct vol_snap s = { amixer_vol(1), amixer_vol(2) };
return s;
}
void vol_restore(struct vol_snap s)
{
logmsg("volume restore -> L=%d R=%d", s.l, s.r);
amixer_set(1, s.l);
amixer_set(2, s.r);
}
int settings_volume = MEOW_VOL_DEFAULT; /* 当前音量 0-100(= 写入 DAC 的映射值) */
int settings_volume_max = MEOW_VOL_MAX_DEFAULT; /* 最大音量上限 clamp */
int settings_volume_step = MEOW_VOL_STEP_DEFAULT; /* 跳步 */
/* M2.17/M2.16:把 meow_settings 当前值同步到本模块的缓存全局。读取方
* (DAC/状态栏/息屏/刷新率调度)统一从缓存取,避免散读 backend。 */
void settings_sync_cache_from_backend(void)
{
int v;
if (meow_settings_get_int("audio.volume", &v) == 0) settings_volume = v;
if (meow_settings_get_int("audio.volume_max", &v) == 0) settings_volume_max = v;
if (meow_settings_get_int("audio.volume_step", &v) == 0) settings_volume_step = v;
if (meow_settings_get_enum("display.refresh_rate", &v) == 0) /* names "60,30,15" */
refresh_rate_cfg = (v == 1) ? 30 : (v == 2) ? 15 : 60;
if (meow_settings_get_bool("display.refresh_always_max", &v) == 0) refresh_always_max = v;
if (meow_settings_get_int("display.screen_off_timeout", &v) == 0) screen_off_timeout = v;
if (meow_settings_get_enum("display.screen_off_mode", &v) == 0) screen_off_mode = v;
if (meow_settings_get_int("power.longpress_menu_ms", &v) == 0) power_longpress_menu_ms = v;
if (meow_settings_get_int("power.forceoff_ms", &v) == 0) power_forceoff_ms = v;
}
/* meow_settings 变更回调:set_*(如音量键)后同步缓存全局,保证既有引用
* settings_volume 等变量的代码与 backend 单点一致(SYSTEM_SETTINGS §3)。 */
void settings_sync_cache_listener(const char *key)
{
if (!strcmp(key, "audio.volume")) { int v; if (meow_settings_get_int("audio.volume", &v) == 0) settings_volume = v; }
else if (!strcmp(key, "audio.volume_max")) { int v; if (meow_settings_get_int("audio.volume_max", &v) == 0) settings_volume_max = v; }
else if (!strcmp(key, "audio.volume_step")) { int v; if (meow_settings_get_int("audio.volume_step", &v) == 0) settings_volume_step = v; }
else if (!strcmp(key, "display.refresh_rate")) { int v; if (meow_settings_get_enum("display.refresh_rate", &v) == 0) refresh_rate_cfg = (v == 1) ? 30 : (v == 2) ? 15 : 60; }
else if (!strcmp(key, "display.refresh_always_max")) { int v; if (meow_settings_get_bool("display.refresh_always_max", &v) == 0) refresh_always_max = v; }
else if (!strcmp(key, "display.screen_off_timeout")) { int v; if (meow_settings_get_int("display.screen_off_timeout", &v) == 0) screen_off_timeout = v; }
else if (!strcmp(key, "display.screen_off_mode")) { int v; if (meow_settings_get_enum("display.screen_off_mode", &v) == 0) screen_off_mode = v; }
else if (!strcmp(key, "power.longpress_menu_ms")) { int v; if (meow_settings_get_int("power.longpress_menu_ms", &v) == 0) power_longpress_menu_ms = v; }
else if (!strcmp(key, "power.forceoff_ms")) { int v; if (meow_settings_get_int("power.forceoff_ms", &v) == 0) power_forceoff_ms = v; }
}
/* 启动加载(M2.11/M2.24):统一由 meow_settings 后端读 settings.conf;
* 此处仅注册监听(一次性)+ 把 backend 当前值同步到缓存全局。重载(应用期
* watchdog 可能改音量)等价于 meow_settings_init() + 手动同步。 */
int settings_cache_registered = 0; /* M2.24:监听只注册一次 */
/* ---- MeowOS 真实 DAC 音量(CS43198,ANALYSIS §18.39)----
* HibyOS 播放器经 ioctl 直写 /dev/snd/controlC0 的 "Left/Right Playback
* Volume"(numid 1/2,0-255)→ codec 驱动 cs43198_vol_*_put → I2C 直写 CS43198
* 寄存器(0x9000-0x9002/0x7000-0x7001,双片 L/R)。我们复刻同路径(amixer
* cset = SNDRV_CTL_IOCTL_ELEM_WRITE),0-100 → 0-255 映射直写两声道。
* 读回不可用(驱动 get 为 dummy 恒 0;/dev/i2c-3 被驱动占用),故系统音量以
* settings_volume(写入 DAC 的映射值)为权威跟踪值。 */
void meow_dac_vol_write(int pct)
{
int dac = 255 - pct * 255 / 100; /* CS43198 寄存器=衰减量(0=最响 255=静音),用户 pct 越大越响 → 反转映射 */
if (dac < 0) dac = 0;
if (dac > 255) dac = 255;
char cmd[160];
snprintf(cmd, sizeof cmd,
"amixer -c 0 cset numid=1 %d >/dev/null 2>&1; amixer -c 0 cset numid=2 %d >/dev/null 2>&1",
dac, dac);
system(cmd);
logmsg("DAC vol -> L/R=%d (pct %d)", dac, pct);
}
static unsigned long long vol_dac_last_ms = 0;
void meow_dac_vol_write_throttled(void)
{
unsigned long long now = now_ms();
if (now - vol_dac_last_ms < VOL_DRAG_DAC_MS) return; /* 节流窗口内跳过,等抬手统一写 */
vol_dac_last_ms = now;
meow_dac_vol_write(settings_volume);
}
/* 抬手/点击提交连 save 时调用:无条件直写一次真实 DAC(绕过节流,保证最终音量落 DAC/落盘)。 */
void meow_dac_vol_flush(void)
{
vol_dac_last_ms = now_ms();
meow_dac_vol_write(settings_volume);
}
/* clamp 集中:0..MEOW_VOL_MAX,再 clamp 到上限 settings_volume_max。 */
int vol_clamp(int v)
{
if (v < 0) v = 0;
if (v > MEOW_VOL_MAX) v = MEOW_VOL_MAX;
if (v > settings_volume_max) v = settings_volume_max;
return v;
}
int settings_volume_get(void) { return settings_volume; }
int vol_ramp_dir = 0; /* +1 上 / -1 下 / 0 停 */
static int vol_ramp_target = -1; /* 目标值;-1=无 */
static unsigned long long vol_ramp_next_at = 0;
static unsigned long long vol_ramp_press_at = 0; /* M2.18 Bug 4:本方向键按下时刻(长按判据) */
/* ---- 插孔消抖(2026-09-01,真机日志实证)----
* 4.4/3.5 耳机插拔的电气瞬态在音量键 ADC(jz_adc_aux,event2)上产生 VOL_UP 幽灵按住
* (EVIOCGKEY 轮询到持续 down),vol_ramp 一路爬到上限:日志 225391「volume UP <- ramp」
* → 227213「ramp end -> 50」,紧接着 audiod 才报「route: jack changed -> 4.4 balance」。
* 修法:/sys/class/switch/{headset,balance}/state 任一变化后 JACK_DEBOUNCE_MS 内忽略音量键
* 边沿(vol_ramp_begin 不起、vol_ramp_tick 在爬也中止)。插拔耳机时没人按音量,误伤可忽略。
* 检测走 1s 节流读缓存——绝不在 10ms 主循环热路径上每圈读文件。 */
/* 宿主测试可 -D 覆盖(同 meow_settings.c MEOW_SETTINGS_FILE 惯例) */
#ifndef JACK_HS_STATE
#define JACK_HS_STATE "/sys/class/switch/headset/state"
#endif
#ifndef JACK_BAL_STATE
#define JACK_BAL_STATE "/sys/class/switch/balance/state"
#endif
static int jack_combo_cached = -1; /* hs*2+bal;-1=未初始化/节点缺失 */
static unsigned long long jack_poll_at = 0; /* 上次节流读时刻 */
static unsigned long long jack_changed_at = 0; /* 最近一次检测到插孔变化的时刻(0=无) */
/* 读两个插孔开关节点合成 hs*2+bal;两节点都不可读返回 -1(不更新缓存,不误判变化)。 */
static int jack_combo_read(void)
{
int hs = read_file_int(JACK_HS_STATE);
int bal = read_file_int(JACK_BAL_STATE);
if (hs < 0 && bal < 0) return -1;
return (hs > 0 ? 2 : 0) | (bal > 0 ? 1 : 0);
}
/* 返回 1=插孔变化后消抖窗口内(音量键边沿应忽略/在爬 ramp 应中止)。
* 自节流:每 JACK_POLL_MS 才真正读一次 sysfs。 */
static int jack_debounce_active(void)
{
unsigned long long now = now_ms();
if (now - jack_poll_at >= JACK_POLL_MS) {
jack_poll_at = now;
int c = jack_combo_read();
if (c >= 0) {
if (jack_combo_cached >= 0 && c != jack_combo_cached) {
jack_changed_at = now;
logmsg("jack state changed (%d -> %d), vol-key debounce %dms",
jack_combo_cached, c, JACK_DEBOUNCE_MS);
}
jack_combo_cached = c;
}
}
return jack_changed_at && (now - jack_changed_at < (unsigned long long)JACK_DEBOUNCE_MS);
}
static void vol_ramp_begin(int delta)
{
if (jack_debounce_active()) { /* 插孔瞬态期:忽略音量键边沿(幽灵按下) */
logmsg("volume adj %+d ignored (jack debounce)", delta);
return;
}
int target = vol_clamp(settings_volume + delta);
if (target == settings_volume) return;
vol_ramp_dir = (target > settings_volume) ? 1 : -1;
vol_ramp_target = target;
vol_ramp_press_at = now_ms(); /* M2.18 Bug 4:记录该方向键按下时刻,供 CLICK_MS 长按判定 */
vol_ramp_next_at = 0; /* 立即迈第一步,给即时反馈 */
}
/* 每主循环 tick 调;key_held=该方向按键是否仍按住(长按续爬)。返回 1=仍在渐变。
* M2.18 Bug 4 语义:单击(<CLICK_MS=400ms) 到 target 即停(只跳 step 格);长按(≥400ms) 才
* 续段连续爬。普通按压力 50-150ms 远大于 VOL_RAMP_MS(15ms),旧逻辑在到 target 后只要
* key_held=1 就续段 → 单击被当成长按,连爬 6~7 格(「点一下跳好几格」)。CLICK_MS 此前只
* 被 kd_tick 状态机使用,vol_ramp 路径从未接上。 */
int vol_ramp_tick(int key_held)
{
if (vol_ramp_dir == 0) return 0;
unsigned long long now = now_ms();
if (now < vol_ramp_next_at) return 1;
vol_ramp_next_at = now + VOL_RAMP_MS;
/* 插孔消抖:插拔瞬态的幽灵按住正在驱动 ramp(真机实证 24%→上限 50)——
* 一旦检测到插孔变化(1s 节流轮询),立即中止在爬的 ramp。 */
if (jack_debounce_active()) {
vol_ramp_dir = 0; vol_ramp_target = -1;
meow_settings_save();
logmsg("volume ramp aborted (jack debounce) -> %d", settings_volume);
return 0;
}
/* 兜底:ramp 总时长上限 VOL_RAMP_MAX_MS——防任何「持续按住」假象(ADC 噪声卡 key_held=1)
* 一次打满/打到底。正常长按 0→上限(通常 ≤2.7s)不受影响;到顶想再调需松手重按。 */
if (now - vol_ramp_press_at >= (unsigned long long)VOL_RAMP_MAX_MS) {
vol_ramp_dir = 0; vol_ramp_target = -1;
meow_settings_save();
logmsg("volume ramp force-stop (%dms cap) -> %d", VOL_RAMP_MAX_MS, settings_volume);
return 0;
}
/* 先看是否已到「当前目标值」(settings_volume 等于 target,而非候选 v)——
* 因为一旦在 target 再取 v=settings_volume+dir 便越过 target,导致 hold_wait 失效继续爬。 */
if (settings_volume == vol_ramp_target) {
if (!key_held) { /* 松开 → 一段完成,结束(单击到 target 即停,只跳 step 格) */
vol_ramp_dir = 0; vol_ramp_target = -1;
meow_settings_save(); return 0;
}
if (now - vol_ramp_press_at < CLICK_MS) {
/* hold_wait:已到 target 但未到长按阈值 → 停在 target 不爬(保持待命不清 dir,
* 等长按激活或松开);若直接续段,长按第一次到 target 就断了。 */
return 1;
}
/* 长按激活:续段(在 target 基础上再 +step) */
vol_ramp_target = vol_clamp(vol_ramp_target + vol_ramp_dir * settings_volume_step);
if (vol_ramp_target == settings_volume) { /* 续段到上限无进展 → 结束 */
vol_ramp_dir = 0; vol_ramp_target = -1;
meow_settings_save(); return 0;
}
}
int v = vol_clamp(settings_volume + vol_ramp_dir);
if (v == settings_volume) { /* 已达上下限 */
vol_ramp_dir = 0; vol_ramp_target = -1;
meow_settings_save(); return 0;
}
meow_settings_set_int("audio.volume", v); /* 更新 backend 内存 + 同步缓存(不逐 1% 落盘) */
meow_dac_vol_write(settings_volume); /* 每 1% 写一次真实 DAC(settings_volume 已同步) */
return 1;
}
/* 步进调节(±settings_volume_step)→ 起线性渐变(1% 爬,见上)。 */
void settings_volume_adj(int delta) { vol_ramp_begin(delta); }