feat(audiod): BT 输出音量软件增益(曲线与 DAC 同源)+ vol_apply BT 分支真生效

真机现象:音量设 10 仍然满响度——vol_apply() 的 BT 分支 logmsg + return,
g_vol_pct 不更新、AVRCP 被 bt_vol_full() 钉在 127、写路径无任何软件缩放,
于是 BT 永远在耳机自己的满音量。

- 新增 vol_gain.{c,h}(纯整数,无 libm/软浮点):增益表由 dac_vol_set 的同一个
  衰减寄存器函数 vol_gain_dac_att_reg() 推出(0.5dB/步、平方锥度),
  Q31 迭代乘法开机算一次;vol=100 逐字节不变,vol=0 全 0(memset 快路)。
- dev_write(BT 路径)逐样本乘 + 32 位中间量 + 饱和;增益恒 <=1 故不可能削波。
- 改音量走 512 样本线性斜坡(Bresenham 余额分摊,末步不补差),防爆音。
- vol_apply() BT 分支改为更新 g_vol_pct + 持久化;DAC numid=1/2/11 仍不写
  (M50 T11 隔离断言不变)。
- dac_vol_set() 改用 vol_gain_dac_att_reg(),DAC/BT 两条路锥度不可能漂移。
- 测试:test_vol_gain(曲线/缩放/斜坡纯函数断言)+ T11 改为断言
  「BT 音量不写 DAC 寄存器」+ 新增 T18(wav 捕获音量阶梯 RMS/峰值)
  / T19(活改音量不爆音、不丢帧)。
This commit is contained in:
AskaEth committed 2026-11-12 08:07:37 +08:00
1 parent 4040b41649
commit e37c622597
6 files changed
+678 -26

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
/* vol_gain.h — 蓝牙(A2DP)写路径的软件音量增益(M55)。
*
* 为什么需要:BT 输出时 AVRCP 被钉在 127(耳机功放满量程),`vol_apply()` 旧版在 BT 分支
* 直接 return——音量在整条 BT 路径上被跳过,`meow_audio vol <n>` 完全无效(真机现象:
* 音量 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 <stdint.h>
#include <stddef.h>
#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