Files
MeowOS-For-R3P2/patch_v2/meow_audiod/vol_gain.h
T
AskaEth 2c0ec2ba15 test(audiod): r1 P2-1/P2-2 — 补 rapid-toggle 回归用例 + BT 音量语义文档(实现零改动)
评审 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 完全一致,
证明二进制未变)。
2026-11-12 08:07:37 +08:00

75 lines
4.6 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.
/* 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