/* vol_gain.c — 纯整数增益表 + 缩放/斜坡(宿主与目标机共用)。见 vol_gain.h 的定性说明。 */ #include "vol_gain.h" #include /* 每衰减步 0.5dB 的振幅系数:10^(-0.5/20) = 10^(-0.025) = 0.9440608762859234。 * Q31 = round(0.9440608762859234 × 2^31) = 2027355295(离线算出并钉死;运行期不碰浮点)。 */ #define VOL_GAIN_STEP_Q31 2027355295u static int32_t s_gain_q15[256]; /* 下标=衰减寄存器值;只读(init 之后) */ static int s_gain_ready = 0; /* 衰减步 → 增益的整数迭代:acc 以 Q31(15 位整数 + 16 位小数)表示 Q15 增益, * 每步 acc = acc × STEP >> 31。255 步累计相对误差 ~2^-23,实测与 pow() 相差 ≤1 LSB。 */ void vol_gain_init(void) { if (s_gain_ready) return; uint64_t acc = (uint64_t)VOL_GAIN_UNITY << 16; for (int reg = 0; reg < 256; reg++) { int32_t g = (int32_t)((acc + 32768u) >> 16); if (g > VOL_GAIN_UNITY) g = VOL_GAIN_UNITY; s_gain_q15[reg] = g; acc = (acc * VOL_GAIN_STEP_Q31 + (1u << 30)) >> 31; } s_gain_ready = 1; } /* dac_vol_set() 的锥度原样搬来(平方锥度;255=-127.5dB)。**单一出处**:dac_vol_set 与 * BT 增益表都取这个值,谁都不许自己再算一遍,否则 DAC/BT 曲线会各走各的。 */ int vol_gain_dac_att_reg(int pct) { if (pct <= 0) return 255; /* 真静音 */ if (pct > 100) pct = 100; int reg = (100 - pct) * (100 - pct) * 192 / 10000; if (reg > 255) reg = 255; if (reg < 0) reg = 0; return reg; } int vol_gain_q15(int pct) { if (!s_gain_ready) vol_gain_init(); /* 未初始化就查=防御(正常由 main 先 init) */ if (pct <= 0) return 0; /* vol=0 必须真静音,不给「很小声」留余地 */ return s_gain_q15[vol_gain_dac_att_reg(pct)]; } int16_t vol_gain_scale_s16(int16_t in, int g_q15) { int32_t v = (int32_t)in * g_q15; /* |in|≤32768 × g≤32768 → |v|≤2^30,int32 不溢出 */ v = (v + (1 << 14)) >> 15; /* 四舍五入回 S16 量程(单位增益时恒等) */ if (v > 32767) return 32767; if (v < -32768) return -32768; return (int16_t)v; } void vol_gain_ramp_snap(vol_gain_ramp_t *r, int pct) { r->cur_q15 = r->tgt_q15 = vol_gain_q15(pct); r->step_q15 = 0; r->bump = 0; r->bump_left = 0; r->left = 0; } void vol_gain_ramp_set(vol_gain_ramp_t *r, int pct, int ramp_samples) { int32_t tgt = vol_gain_q15(pct); if (tgt == r->tgt_q15) return; /* 目标没变:不重置进行中的斜坡 */ r->tgt_q15 = tgt; int32_t d = tgt - r->cur_q15; if (ramp_samples <= 0 || d == 0) { r->cur_q15 = tgt; r->step_q15 = 0; r->bump = 0; r->bump_left = 0; r->left = 0; return; } /* 线性斜坡 = 固定步进 + 余额分摊(不除法的 Bresenham): * n 个样本走完 d,step=d/n 截断,余数 rem 按 ±1 分摊到前 |rem| 个样本。 * 好处:**最后一步不会突然补差**(旧版 step 截断后末尾要一次跳 500+ LSB = 爆音), * 全程每样本增益变化 ≤ |step|+1,且终点精确等于目标(无过冲)。 */ int32_t ad = (d > 0) ? d : -d; int32_t n = ramp_samples; if (n > ad) n = ad; /* 增益差比斜坡短:缩短斜坡(步进仍至少 1 LSB) */ int32_t step = d / n; int32_t rem = d - step * n; /* |rem| < n,与 d 同号 */ r->left = n; r->step_q15 = step; r->bump = (rem > 0) ? 1 : -1; r->bump_left = (rem > 0) ? rem : -rem; } void vol_gain_ramp_apply(vol_gain_ramp_t *r, uint8_t *buf, size_t nbytes) { size_t ns = nbytes / 2; if (!buf || !ns) return; if (r->left <= 0) { /* 稳态快路:单位增益→原样返回(vol=100 逐字节不变);静音→memset 全 0(vol=0 真静音) */ if (r->cur_q15 == VOL_GAIN_UNITY) return; if (r->cur_q15 == 0) { memset(buf, 0, ns * 2); return; } } for (size_t i = 0; i < ns; i++) { int32_t g = r->cur_q15; if (r->left > 0) { r->cur_q15 += r->step_q15; if (r->bump_left > 0) { r->cur_q15 += r->bump; r->bump_left--; } if (--r->left == 0) r->cur_q15 = r->tgt_q15; /* 兜齐(理论上已精确相等) */ g = r->cur_q15; } int16_t s; memcpy(&s, buf + i * 2, 2); /* 字节存取:不假设缓冲对齐、不违反严格别名 */ s = vol_gain_scale_s16(s, g); memcpy(buf + i * 2, &s, 2); } }