评审 r1 的 `p2-2items`,两项都是「缺用例 / 缺一句文档」,不改任何已交付行为。 P2-1(连续拖滑块/ramp 中途反向缺回归): - test_vol_gain 新增 G4b:块长 100(< 512 斜坡)× 60 次 100↔10 反向,实测 59 次落在 斜坡中段;断言增益全程 ∈ [q15(10)=4, 32768] 且从不放大、块内逐样本只朝目标单调走、 相邻样本跳变 ≤64 LSB(硬跳变 ~16380)、停手后 cur=4/left=0/bump_left=0。 - test_audiod_host 新增 T20(daemon 侧等价集成用例):经 IPC 连发 60 次 vol, 每次只喂 100 帧(400B),断言 60 次全 ACK、underrun/dropped=0、 落盘 PCM 无硬跳变(max_delta 1020 = 正弦自身斜率)、尾部 RMS 1.49 比头部 6376.58 低 72dB(10% 真的生效)。 P2-2(文档):vol_gain.h 顶部新增「App/SDK 侧契约」段:BT 下 set_volume 同样生效 (走软件增益 + 持久化),AVRCP 钉 127 只为耳机功放满量程而非「音量交给耳机」, pct 只在 [0,100] 有意义(<=0 真静音、>100 按满量程截断),16bit 写路径的 量化下限说明;vol_gain_dac_att_reg 注释补 pct>100 行为。build_audiod.sh 头部补 M55 说明(必须一起编 vol_gain.c、旧 SDK 说法已过期)。 验证:test_audiod_host 134/134 PASS、test_vol_gain 36/36 PASS、test_bt_dev PASS; mipsel 交叉编译 0 warning,产物 md5 6cef4c54a1cb8e0c55f4ad5b6c4a323e(与 r1 完全一致, 证明二进制未变)。
75 lines
4.6 KiB
C
75 lines
4.6 KiB
C
/* 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。
|
||
*
|
||
* ---- App / SDK 侧契约(M55 定案;MEOW_APP_SDK.md 里「BT 输出目标时音量由 AVRCP 侧控」已过期)----
|
||
* - `meow_audio_set_volume(pct)` 在**两种输出目标下都生效**:DAC 目标写 CS43198 音量寄存器
|
||
* (numid=1/2 + DSD_VOLUME 双组),BT 目标写本模块的软件增益(写进 A2DP 的 PCM 样本)。
|
||
* 同一 pct 在两条路上是**同一条听感曲线**(vol_gain_dac_att_reg),不是「线性 vs 非线性」两套。
|
||
* - BT 下 AVRCP 被钉在 127(耳机功放满量程)——那是为了让耳机的模拟级不二次衰减,
|
||
* **不是**「音量交给耳机控」:用户的 vol 值由 audiod 的数字增益执行,`vol=0` 是真正的全 0 静音。
|
||
* - `pct` 只在 `[0,100]` 有意义:`<=0` 一律真静音,`>100` 一律按 100(满量程)处理。
|
||
* (`settings.conf` 里被手改成如 `audio.volume = 150` 时表现为满量程,不会超过 100% 的合法响度。)
|
||
* - 16bit 写路径的固有下限:约 <=-78dB 的档位(pct<=10)只剩 1 LSB 量级,听感上是静音;
|
||
* 实用区间约 60..100%(与 DAC 相同,因为曲线同源)。
|
||
*/
|
||
#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() 也调它**,保证两条路同一条锥度。
|
||
* pct<=0 → 255(真静音);pct>=100 → 0(0dB 满量程,**包括 pct>100**:越界按满量程截断,
|
||
* 不会给出 >100% 的响度)。 */
|
||
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
|