/* vol_gain.h — 蓝牙(A2DP)写路径的软件音量增益(M55)。 * * 为什么需要:BT 输出时 AVRCP 被钉在 127(耳机功放满量程),`vol_apply()` 旧版在 BT 分支 * 直接 return——音量在整条 BT 路径上被跳过,`meow_audio vol ` 完全无效(真机现象: * 音量 10 仍是满响度;`bluealsa-cli volume … → L: 127 R: 127`)。 * 而每次改音量 fork 一次 `bluealsa-cli volume` 约 25–30ms(单核 1GHz),不能放在滑块每一步; * 大量耳机还忽略 AVRCP absolute volume。→ 定案:AVRCP 保持 127,增益在写路径上做。 * * 曲线与 DAC **同源**:CS43198 numid=1/2 是反转衰减(0=0dB,255=-127.5dB,0.5dB/步), * dac_vol_set() 的锥度是「(1-pct)²×96dB」。这里不重写锥度,而是直接取 * vol_gain_dac_att_reg(pct)(与 dac_vol_set 同一个函数)→ 增益 = 10^(-0.5dB×reg/20), * 于是「同一百分比」在 DAC 与 BT 上得到同一条听感曲线(非线性、不重排)。 * * 全整数:增益表开机算一次(Q31 定点迭代乘法,不引 libm/软浮点),逐样本一次 32 位乘 + * 移位 + 饱和;100% 走「单位增益直接返回」快路(逐字节不变、零开销),0% 走 memset。 */ #ifndef MEOW_VOL_GAIN_H #define MEOW_VOL_GAIN_H #include #include #define VOL_GAIN_UNITY 32768 /* Q15 单位增益:逐样本 (in*32768+16384)>>15 ≡ in */ #define VOL_GAIN_RAMP_SAMPLES 512 /* 增益变更斜坡长度(44.1k ≈ 11.6ms;防「叮」一声爆音) */ /* 开机调用一次(幂等):生成 256 项增益表。之后查表全只读、无锁。 */ void vol_gain_init(void); /* pct(0..100) → DAC 衰减寄存器 0..255。**dac_vol_set() 也调它**,保证两条路同一条锥度。 */ int vol_gain_dac_att_reg(int pct); /* pct → 线性增益 Q15(32768=单位增益,0=真静音)。pct 越界自动 clamp。 */ int vol_gain_q15(int pct); /* 纯函数:单个 S16 样本按 Q15 增益缩放(32 位中间量 + 四舍五入 + 饱和)。 * 增益恒 ≤ 32768 → |in*g| ≤ 2^30 不溢出;单位增益时逐样本恒等。 */ int16_t vol_gain_scale_s16(int16_t in, int g_q15); /* ---- 斜坡状态(**音频线程私有**,调用方持有;不做任何加锁) ---- * 目标值由控制线程经单个对齐 int 松散写进来(MIPS32 int 写天然原子),音频线程每 * dev_write 读一次即可,最坏晚一帧生效。 */ typedef struct { int32_t cur_q15; /* 当前位置 */ int32_t tgt_q15; /* 目标增益 */ int32_t step_q15; /* 每样本基础步进 */ int32_t bump; /* 余额分摊方向(±1,0=无余额) */ int32_t bump_left; /* 还要分摊多少样本 */ int32_t left; /* 剩余斜坡样本数(0=已到位) */ } vol_gain_ramp_t; /* 立即跳到 pct 对应增益(开流/换设备/初始化用:新流不该淡入)。 */ void vol_gain_ramp_snap(vol_gain_ramp_t *r, int pct); /* 目标变为 pct 对应增益;与当前目标相同时不动(不打断进行中的斜坡)。 * ramp_samples<=0 → 立即生效。 */ void vol_gain_ramp_set(vol_gain_ramp_t *r, int pct, int ramp_samples); /* 就地缩放一段 S16_LE 交织 PCM(nbytes 字节;奇数尾字节原样保留)。 */ void vol_gain_ramp_apply(vol_gain_ramp_t *r, uint8_t *buf, size_t nbytes); #endif