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:
1 parent
4040b41649
commit
e37c622597
6 files changed
+678
-26
No files matched your search
@@ -60,6 +60,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include "meow_ipc.h"
|
||||
#include "bt_dev.h" /* M50:端点探测 / PCM 名构造 / 回落退避(纯逻辑,不改任何配置文件) */
|
||||
#include "vol_gain.h" /* M55:BT 写路径软件增益(与 DAC 同一锥度,全整数) */
|
||||
|
||||
#ifdef __linux__
|
||||
#include <sound/asound.h>
|
||||
@@ -118,7 +119,7 @@ static char g_out_wav[256] = DEFAULT_OUT_WAV;
|
||||
static const char *g_amixer = "/usr/bin/amixer"; /* 环境可覆盖 */
|
||||
static int g_route_manual = -1; /* -1=auto;2=3.5;3=4.4 */
|
||||
static int g_route_cur = -1; /* 最近已知路由;-1=未知 */
|
||||
static int g_vol_pct = VOL_DEFAULT; /* 当前音量 0-100(缓存:DAC 音量;BT 时 volctl=avrcp) */
|
||||
static int g_vol_pct = VOL_DEFAULT; /* 当前音量 0-100(BT 时 = 写路径软件增益的输入,见 vol_apply) */
|
||||
static int g_force = 0; /* MEOW_AUDIO_FORCE=1 跳过启动门控(宿主/qemu 测试) */
|
||||
|
||||
/* ---- 缓冲状态(+日志) ---- */
|
||||
@@ -228,6 +229,12 @@ static char g_bt_pcm[128] = ""; /* 端点 D-Bus 路径(volume/in
|
||||
static int g_bt_fail_req = 0; /* 写路径判死:请求播放线程下一拍重配(回落 DAC) */
|
||||
static int g_bt_dev = 0; /* 当前「设备」是 BT PCM(dev_close 分流;g_mu 保护) */
|
||||
|
||||
/* ---- BT 软件增益(M55):AVRCP 钉在 127(耳机功放满量程),音量在写路径逐样本做 ----
|
||||
* g_bt_gain 是**播放线程私有**状态(dev_write/dev_open 都只在该线程跑),不持锁、不用原子;
|
||||
* 控制线程只经 g_vol_pct(单个对齐 int)传目标值,音频线程每块读一次 → 最坏晚一块生效。
|
||||
* 见 vol_gain.h:曲线与 dac_vol_set 同源(0.5dB/步、平方锥度),100%=逐字节不变,0%=全 0。 */
|
||||
static vol_gain_ramp_t g_bt_gain;
|
||||
|
||||
/* ---- 数字域电平表(DAC 实时监控;CS43198 无模拟电平回读,播放线程看 PCM 样本) ----
|
||||
* 播放线程对从 ring 取出的每块 PCM 做纯整数累加(归一到 16bit 域:S24/S32 >>16),
|
||||
* ~100ms 窗口(rate/10 帧)到点才结算一次 RMS/peak 并发布快照——每帧仅几次整数加乘,
|
||||
@@ -397,8 +404,10 @@ static int target_grant_fmt(int native_fmt)
|
||||
return AUDIO_FMT_S16;
|
||||
}
|
||||
|
||||
/* 音量控制归属(QUERY volctl=):dac=反转控制;avrcp=蓝牙 AVRCP 侧控。
|
||||
* BT 回落 DAC(bt=fail)时实际由 DAC 控,故报 dac——面板据此显示「蓝牙(失败)」。 */
|
||||
/* 音量控制归属(QUERY volctl=):dac=反转控制;avrcp=蓝牙路径(AVRCP 侧 + 写路径软件增益)。
|
||||
* BT 回落 DAC(bt=fail)时实际由 DAC 控,故报 dac——面板据此显示「蓝牙(失败)」。
|
||||
* ⚠️ M55 起 BT 的实际音量是 dev_write 里的软件增益(AVRCP 只负责把耳机功放钉在满量程),
|
||||
* 但 `volctl=avrcp` 作为面板/launcher 已在用的既有取值**保持不变**(改值属跨组件契约变更)。 */
|
||||
static const char *volctl_name(void)
|
||||
{
|
||||
if (g_target != AUDIO_OUT_BT) return "dac";
|
||||
@@ -660,11 +669,9 @@ static int g_dsd_active = 0; /* 当前授权流为 DoP DSD(g_mu 保护;决
|
||||
|
||||
static void dac_vol_set(int pct)
|
||||
{
|
||||
int t = 100 - pct, dac;
|
||||
if (pct <= 0) dac = 255; /* 真静音(DS1039:255=-127.5dB) */
|
||||
else dac = t * t * 192 / 10000; /* 感知平方锥度:dB 衰减=(1-pct)^2×96dB */
|
||||
if (dac > 255) dac = 255;
|
||||
if (dac < 0) dac = 0; /* CS43198=衰减量(0最响255静音)+平方锥度(听感线性) */
|
||||
/* 锥度**只有一处实现**(vol_gain_dac_att_reg,M55 起 BT 软件增益也用同一张表):
|
||||
* CS43198 numid=1/2 = 衰减量(0 最响、255=-127.5dB、0.5dB/步)+ 平方锥度(听感线性)。 */
|
||||
int dac = vol_gain_dac_att_reg(pct);
|
||||
/* 音量收编 v2:**泛化双组写**——同刻写 PCM(numid=1/2) 与 DSD_VOLUME,保证 DSD↔PCM 切换
|
||||
* 音量连续 + 防爆音闭环(决策①,勿只写 PCM)。锥度沿用平方(决策②)。 */
|
||||
amixer_set(1, dac, 1);
|
||||
@@ -919,7 +926,8 @@ static int settings_get_volume(void)
|
||||
* 把播放器解码输出透传过去。BT 是**独立目标**:
|
||||
* - 不开 hw:0,0(不 period=1024×4、不 FPGA numid=9 武装、不 DAC 音量反转);
|
||||
* - 保留音频流透传 + 格式归一(grant_fmt=S16,plug 层转 codec 协商率);
|
||||
* - 音量走 AVRCP/耳机侧(音量隔离:绝不下发 DAC numid=1/2 反转,见 vol_apply)。
|
||||
* - 音量:AVRCP 钉在 127(耳机功放满量程),实际音量在**写路径做软件增益**(M55)——
|
||||
* 仍绝不下发 DAC numid=1/2 反转(见 vol_apply / dev_write)。
|
||||
* 链接说明(2026-09-03 真机定案):`pcm.bluealsa` 的 bluealsa 是**独立 pcm 插件**,libasound
|
||||
* 在运行时以 dlopen 加载 /usr/lib/alsa-lib/libasound_module_pcm_bluealsa.so(glibc,依赖
|
||||
* libdbus-1.so.3)——故 audiod 必须以**动态 glibc** 形态链接设备现有 libasound.so.2
|
||||
@@ -1226,9 +1234,11 @@ static void bt_read_used_codec(void)
|
||||
}
|
||||
}
|
||||
|
||||
/* 音量应用(含 BT 隔离):BT(真实走 A2DP)→ 只通知 AVRCP(best-effort),绝不动 DAC numid=1/2、
|
||||
* 不持久化 audio.volume(那是 DAC 音量键);DAC 及「BT 回落 DAC」→ 原感知锥度反转 + 持久化
|
||||
* (回落时声音实际从 DAC 出,音量必须由 DAC 控,否则面板拖动无感)。
|
||||
/* 音量应用(含 BT 隔离):DAC 及「BT 回落 DAC」→ 感知锥度反转 + 持久化(回落时声音实际从
|
||||
* DAC 出,音量必须由 DAC 控,否则面板拖动无感)。
|
||||
* BT(真实走 A2DP)→ **不写 DAC numid=1/2**(M50 T11 隔离断言),音量改由写路径的软件增益
|
||||
* 生效(M55,见 vol_gain.h)——但 g_vol_pct 与持久化**必须更新**:旧版在这里 logmsg+return,
|
||||
* 连 g_vol_pct 都不动,于是 BT 音量永远停在满量程、重启又跳回去(真机「音量 10 还是很响」)。
|
||||
* target 为调用方在持锁下快照的 g_target;本函数不持 g_mu 执行(g_vol_pct 为 plain int
|
||||
* 松散写,同 route_thread 的 g_route_cur 语义,MIPS32 int 写天然原子)。 */
|
||||
static void vol_apply(int pct, int target)
|
||||
@@ -1236,7 +1246,13 @@ static void vol_apply(int pct, int target)
|
||||
if (pct < 0) pct = 0;
|
||||
if (pct > 100) pct = 100;
|
||||
if (target == AUDIO_OUT_BT && g_bt_state != BT_ST_FAIL) {
|
||||
logmsg("vol -> %d%% (BT/AVRCP, DAC numid=1/2 NOT written)", pct);
|
||||
/* 只写 g_vol_pct(音频线程每块读一次 → 起 512 样本斜坡,不「叮」)+ 持久化。
|
||||
* AVRCP 仍保持 bt_vol_full() 设的 127(耳机功放满量程,增益全在数字域)。 */
|
||||
g_vol_pct = pct;
|
||||
settings_set_volume(pct);
|
||||
int reg = vol_gain_dac_att_reg(pct); /* 与 DAC 同一个衰减步数(0.5dB/步) */
|
||||
logmsg("vol -> %d%% (BT software gain q15=%d/32768, -%d.%ddB, AVRCP stays 127, DAC numid=1/2 NOT written)",
|
||||
pct, vol_gain_q15(pct), (reg * 5) / 10, (reg * 5) % 10);
|
||||
return;
|
||||
}
|
||||
g_vol_pct = pct;
|
||||
@@ -1421,6 +1437,10 @@ static int fd_high(int fd)
|
||||
|
||||
static int dev_open(void)
|
||||
{
|
||||
/* M55:开流边界把 BT 软件增益**直接对齐**到当前音量(新流不做淡入——否则每首歌开头
|
||||
* 会有 11ms 的渐强)。判据与 vol_apply 的 BT 分支一致:请求 BT 且未回落 DAC。 */
|
||||
if (g_target == AUDIO_OUT_BT && g_bt_state != BT_ST_FAIL)
|
||||
vol_gain_ramp_snap(&g_bt_gain, g_vol_pct);
|
||||
/* 输出目标=BT 且走真机 ALSA:打开 libasound 的 bluealsa PCM(内联 DEV/CODEC,不开 hw:0,0)。
|
||||
* 宿主即 FILE/NULL sink 时不走此支——因此宿主可测「目标选择/音量隔离/格式归一」逻辑。
|
||||
* **失败必回落 DAC**(M50 硬验收:没有 A2DP 端点时整机不许静音;同时区间由 bt_note_fail
|
||||
@@ -1480,6 +1500,20 @@ static void dev_write(uint8_t *buf, size_t n)
|
||||
}
|
||||
g_dev_bytes += n;
|
||||
}
|
||||
/* M55 蓝牙软件增益:**BT 路径唯一真正控制音量的地方**(AVRCP 钉 127 = 耳机功放满量程)。
|
||||
* 判据与 vol_apply 的 BT 分支一致(target=BT 且未回落 DAC)——回落时声音由 DAC 出,
|
||||
* 音量必须交给 DAC numid=1/2,数字域不得再衰减一次(否则会是「两套音量相乘」)。
|
||||
* 缓冲此时恒为 S16:target_grant_fmt() 对 BT 目标一律授 S16(ALSA/FILE/NULL 三种 sink 同理)。
|
||||
* 每块只读一次 g_vol_pct(控制线程单点松散写);斜坡状态只在本线程。 */
|
||||
if (g_target == AUDIO_OUT_BT && g_bt_state != BT_ST_FAIL) {
|
||||
if (g_dev_fmt == AUDIO_FMT_S16) {
|
||||
vol_gain_ramp_set(&g_bt_gain, g_vol_pct, VOL_GAIN_RAMP_SAMPLES);
|
||||
vol_gain_ramp_apply(&g_bt_gain, buf, n);
|
||||
} else {
|
||||
static int s_lg = 0;
|
||||
if (!s_lg) { s_lg = 1; logmsg("bt gain: skip (device fmt=%s, expected S16)", fmt_name((int)g_dev_fmt)); }
|
||||
}
|
||||
}
|
||||
if (bt_effective_bt()) { bt_write(buf, n); return; }
|
||||
if (g_sink == SINK_NULL) { g_played_frames += n / (size_t)frame_size(); return; }
|
||||
if (g_sink == SINK_ALSA) {
|
||||
@@ -2114,7 +2148,8 @@ static void *io_thread(void *arg)
|
||||
/* ---- 音量执行线程(事项三):异步落 amixer + 持久化,不在 IO 线程持锁 popen ----
|
||||
* 等待 g_vol_cond 取末值(连续请求合并=去抖),放锁后执行 dac_vol_set(3×popen,有界)
|
||||
* + settings_set_volume(文件 IO),完成后只置 ACK 标记,由 IO 线程统一发送(防 fd 复用竞态)。
|
||||
* BT 目标只记日志不动 DAC/不持久化(AVRCP 隔离,g_vol_pct 保持不变)。 */
|
||||
* BT 目标:不写 DAC、不 popen,只更新 g_vol_pct(音频线程据此走软件增益)+ 持久化
|
||||
* (M55 起持久化必须做——否则重启后音量跳回去,见 vol_apply)。 */
|
||||
static void *vol_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
@@ -2373,6 +2408,8 @@ int main(int argc, char **argv)
|
||||
}
|
||||
if (g_sink == SINK_ALSA && g_target == AUDIO_OUT_DAC) hw_probe();
|
||||
g_vol_pct = settings_get_volume();
|
||||
vol_gain_init(); /* M55:BT 增益表开机算一次(全整数,无浮点) */
|
||||
vol_gain_ramp_snap(&g_bt_gain, g_vol_pct); /* 首个流直接以当前音量起(不淡入) */
|
||||
logmsg("volume init = %d%% target=%s codec=%s volctl=%s",
|
||||
g_vol_pct, target_name(), g_bt_codec, volctl_name());
|
||||
|
||||
|
||||
Reference in new issue
Block a user