把 Nahimic 出厂音效搬到 Linux

从本机 Windows 驱动中提取 Nahimic 的设备校正与场景参数,把可复现部分落成 EasyEffects 预设。

后续 《Nahimic 出厂音效续篇:变体判定与空间效果》接着本文往下做,见文末。

每次从 Windows 回到 Linux 我都会有一种遗憾,因为 Linux 下没有厂商调教的音效配置,导致我的笔记本扬声器发出的声音宛若一头年轻的水牛(没有歧视水牛的意思),所以我决定研究研究 Nahimic 是怎样增强笔记本音效的。

首先,Nahimic 的音效参数不一定只能在 Windows 里使用。驱动包里有一部分明文配置,里面包含设备校正 FIR、场景参数和音圈保护数据。把它们找出来之后,就可以把其中一部分搬到 Linux 的 EasyEffects 里。

提示

如果这篇文章触犯了你的权益,请通过 `me@miprota.cc` 联系笔者删除

本文章没有解包相关的教程

本文章出现的脚本均可在 nahimic-factory-audio-tuning 中下载

先决条件

需要 本次环境
硬件 MECHREVO 蛟龙,Conexant SN6140
系统 CachyOS,PipeWire 1.6.8
效果器 EasyEffects 8.2.9(Flatpak)
原厂数据 Windows 分区里的 A-Volute/Nahimic 驱动包
工具 Python 3、`cabextract`、`objdump`、NumPy

下面的硬件匹配和听感只在这一组环境上验证过。换机型时,不能直接套用 FIR 或设备文件名。

一、先排除 EasyEffects 没生效

优先测试一下 EasyEffects 是否生效(虽然大概率不用测试):

```bash
pw-play --target=<硬件输出端的名称> test.wav
```

播放仍然落在 `easyeffects_sink`,说明音频确实经过了 EasyEffects。

二、瞎猜的调教

在没有原厂数据前,我先按听感和常见的小扬声器问题手工搓了一条曲线:100 Hz 高通,220/420 Hz 附近衰减,再压一点 3.2 kHz。听起来比完全没有处理好一些,但那种塑料感还在。

问题在于,我不知道这块机器的箱体共振到底在哪。没有测量,也没有原厂曲线,继续改钟形(Bell)滤波器的 Q 值只是让一条猜出来的曲线变得更像“有依据”。这时才转向一个更直接的问题:Windows 上的 Nahimic 既然能按机型工作,调音数据应该存在哪里?

三、找到本机数据

先在 Linux 侧读取 codec、modalias 和子系统 ID:

```bash
cat /proc/asound/card2/codec#0 | grep -E "Vendor Id|Subsystem Id"
cat /sys/bus/hdaudio/devices/*/uevent | grep MODALIAS
```

本机得到的关键值是 `VEN_14F1 DEV_1F87` 和 `SUBSYS 1D053010`。Windows 驱动的 INF 里必须出现同一个 `HDAUDIO\\FUNC_01&VEN_14F1&DEV_1F87&SUBSYS_1D053010`,否则后面的 XML 不是这块声卡的调音数据。

调音档案按机型分文件,解析得越认真,假如硬件 ID 没对上,最后越可能是在认真研究别人的机器。

四、从 DriverStore 找到 XML

Nahimic 的配置在驱动包里,不在 Program Files。先在 Windows 分区定位 A-Volute 的扩展驱动:

```bash
find /mnt/windows/Windows/System32/DriverStore/FileRepository \\
  -maxdepth 2 -type f -iname '*.inf' -print \\
  | grep -iE 'avolute|nahimic|cnxt'
```

命令命中了 A-Volute 的 APO4 引擎和 CNXT(Conexant)扩展。INF 给出安装目录、设备匹配和 CAB 文件名。解包后得到 180 个明文 XML,结构大致分成:

提示

注意,不能只打开一个 `Music.nsx`:全局配置管采集链和全局开关,场景配置给出四个场景的差异,设备配置才是这台机器的扬声器校正。设备文件里最重要的是 `kSet_DeviceOptimizationFilterFL/FR`,它们是 2048 抽头的 float32 FIR,可以直接重放,不需要再猜一组 EQ 参数。

把 base64 解码后,每个通道正好是 8192 字节,也就是 2048 个 float32。能量集中在最前面的几个样本,符合最小相位校正滤波器的形状。到这里,小作坊手搓 EQ 已经有了一个明确的替代物:卷积器(Convolver)可以直接重放这条 FIR。

频响还有一个很直观的线索:两个设备变体都在 800 Hz 附近留下约 -2.7 dB 的凹陷。我的猜测曲线里没有这个位置,却在 3.2 kHz 做了衰减。出厂数据第一次把“听起来合理”与“确实是原厂曲线”分开了。

五、XML 导致的望文生义

XML 里有 `kSet_BassBoostGainDB`、`kSet_VoiceBoostGainDB` 这样的名字和值,但参数名不等于算法名。要知道一个旋钮接的是 EQ、动态压缩还是谐波生成,必须再看 DLL。

`NahimicAPO4.dll` 没有可用的 A-Volute 符号表,RTTI 里也只有标准库和 ATL 的类型。可行的路线是找阶段名字符串,再扫描 x64 的 RIP-relative `48 8D` 引用,定位它们被注册和读取的位置。过程中还踩过一次地址坑:objdump 输出的节区 VMA 已经包含映像基址,不能再额外加一遍 `0x180000000`。

这条路线能确认算法形态,但不能凭构造函数默认值宣称“所有运行时参数都找到了”。真实值仍然由 XML 经 `ApplyUserSettings` 写入。

低音增强(BassBoost):从低架到钟形滤波,再到谐波增强

我在这里连续犯了两次错误。先把 `BassBoostFrequencyHz=250` 当成 EQ 的中心频率,做成了 +6 dB 的低架;又看到别的机型里有 `BassBoostBandWidthOctave=0.707`,推断它一定是 Q 约 2 的钟形滤波器。第二版的数学曲线能对上部分数字,却仍然解释不了“低音增强”为什么要凭空补出小扬声器放不出的基频。

第三次证据来自 DLL:类名是 `Ape::SBP::VirtualBassBoost`,内部还能看到 `ippsTanh_32f_A24`。它对低频做软饱和、生成奇次谐波,再把谐波混回去;静态 EQ 不会凭空产生新的频率成分。于是 EasyEffects 侧的对应物改成低音增强器(Bass Enhancer),且必须放在卷积器之前,让它先看到未经 FIR 切削的原始低频。

清晰度(Clarity)不等于人声增强(VoiceBoost)

早期还把清晰度当成了人声增强。反汇编读到的清晰度默认值是 200 / 8000 / 6 / 6,而 XML 里的人声增强频带是 500–4000 Hz,参数对不上。DLL 里也没有一个可以直接叫作 `voiceboost_stage` 的静态引用,所以这个等同关系撤回:人声增强的真实实现还没确认,预设不拿静态 EQ 硬凑。

六、从证据得到可复现的链路

现在才有足够依据列出哪些环节可以搬:

原厂环节 原厂实现 EasyEffects 处理 状态
设备优化滤波器 DeviceOptimizationFilter,2048 抽头 FIR 卷积器 已实现
低音增强 `VirtualBassBoost`,tanh 谐波生成 低音增强器 已实现
高音增强 TrebleBoost,`trebleboost_stage` 8 kHz 高架 EQ 已实现
人声增强 VoiceBoost,动态处理,算法未确认 不建模 跳过
DRC 动态增益/压缩 不使用自动增益 跳过
衰减器 Attenuator,频段音圈保护 无直接对应 跳过
限幅器 Limiter,原厂限幅阶段 保留插件但旁路 旁路

七、生成预设

完整工具在源码仓库中:

完整脚本:build_factory_preset.py 156 行 / 5.7 KB | 直接下载

完整脚本:decode_nahimic.py 126 行 / 4.6 KB | 直接下载

生成脚本默认从仓库根目录的 `data/Devices` 读原厂 XML,而仓库里不带 `data/`,解包出来的数据要自己放进去。路径可以用 `NAHIMIC_DEVICES`、`EASYEFFECTS_DATA` 覆盖,但只有部分脚本读这两个变量:其余脚本把 EasyEffects 数据目录写死在 Flatpak 路径下,换环境得改脚本。校正 FIR 必须关闭 `autogain`,否则 EasyEffects 会重新归一化掉原厂刻意保留的增益结构。

EasyEffects 的另一个易错点是扩展名:卷积器找的是 `.irs`,而脉冲响应的内容是 32 位 PCM WAV,照容器格式写成 `.wav` 就找不到了。`kernel-name` 同样不能带扩展名;两件事混在一起,结果就是对话框报错或卷积器悄悄进入直通。

八、四个场景和低频余量

音乐、电影、游戏、通信四个场景的差别主要在低音、高音、人声、空间效果和压缩器开关。当前场景脚本保留原厂场景参数,但不伪造人声增强;低频恢复使用 20–400 Hz 的多频段压缩,回收 FIR 带来的中频余量。

完整脚本:build_nahimic_music.py 184 行 / 6.9 KB | 直接下载

完整脚本:add_bass_compressor.py 172 行 / 5.8 KB | 直接下载

完整脚本:build_scenario_presets.py 289 行 / 11.5 KB | 直接下载

完整脚本:build_official_eq_presets.py 153 行 / 5.0 KB | 直接下载

完整脚本:build_capture_presets.py 143 行 / 6.2 KB | 直接下载

提示

生成的 EasyEffects 预设里,链路顺序不能调换:低音增强器要在卷积器和 EQ 之前,否则谐波发生器拿到的低频已经被 FIR 切掉。出厂 FIR 在 80–400 Hz 有明显增益,整条链如果不处理余量,主观音量会被迫压低;因此加入只作用于 20–400 Hz 的多频段压缩器(Multiband Compressor),把低频峰值压下来,给中频和高频留出空间。限幅器保留在链里但旁路,防削顶由固定增益预算承担。

最终的音乐链路因此变成:

```text
低音增强器(虚拟低音增强的近似)
  → 卷积器(出厂 2048 抽头 FIR)
  → 高架 EQ(高音增强)
  → 多频段压缩器(仅 20–400 Hz)
  → 限幅器(旁路)
```

九、校验、失败和未决问题

预设生成后先跑校验:

完整脚本:validate_presets.py 183 行 / 8.1 KB | 直接下载

完整脚本:validate_complete.py 93 行 / 3.4 KB | 直接下载

其余工具分别用于 FIR 更新、HRTF 解码、SOFA/IRS 转换、反汇编和测量。它们按调查阶段分组:

完整脚本:update_irs.py 175 行 / 6.4 KB | 直接下载

完整脚本:decode_hrtf.py 178 行 / 6.1 KB | 直接下载

完整脚本:hrir_to_irs.py 164 行 / 6.0 KB | 直接下载

完整脚本:hrir_to_sofa.py 277 行 / 11.6 KB | 直接下载

完整脚本:build_headphone_presets.py 93 行 / 3.1 KB | 直接下载

完整脚本:build_complete_preset.py 278 行 / 11.1 KB | 直接下载

完整脚本:remove_limiter.py 162 行 / 5.3 KB | 直接下载

完整脚本:measure.py 161 行 / 5.3 KB | 直接下载

完整脚本:find_xrefs.py 110 行 / 3.4 KB | 直接下载

完整脚本:disasm_window.py 96 行 / 2.7 KB | 直接下载

HRTF 的 EasyEffects 路线已经撤销:单个立体声卷积核不能完成真正的多声道到双耳渲染,实测结果也不合格。扬声器 FIR 的 A/B 变体同样尚未根据本机主板精确判定,不能只凭文件数量猜测。

预设校验解决的是 JSON、插件键和值、FIR 文件名等机器可检查的问题;它不能替你判断 A/B 变体是否属于当前主板,也不能把 HRTF 的单方向卷积变成真正的多声道环绕。

文章里的脚本只保证代码与源码仓库同步;EasyEffects 插件版本、设备变体和实际听感仍需在使用者自己的环境中重新确认。

十、省流

我知道你不想看前面的长篇大论,只想看怎么执行步骤和结果,所以这里给出省流步骤:

  1. 克隆仓库
```bash
git clone https://github.com/LaT-SKY/nahimic-factory-audio-tuning --depth 1
```

仓库里只有 `tools/`,原厂数据要自己解包后放进去。

  1. 在 Windows 分区定位驱动包,解包后取出 `Global.nsx`、`AudioProfiles/`、`Devices/`、`MicProfile/`:
```bash
find /mnt/windows/Windows/System32/DriverStore/FileRepository \\
  -maxdepth 2 -type f -iname '*.inf' -print \\
  | grep -iE 'avolute|nahimic|cnxt'
```
  1. 确认硬件身份:

三处必须一致。

  1. 设置数据路径,再按需要覆盖路径(当然,你的路径可能与我不一致):
```bash
export EASYEFFECTS_DATA="$HOME/.var/app/com.github.wwmm.easyeffects/data/easyeffects"
export NAHIMIC_DEVICES="$PWD/data/Devices"
```

只有 `build_factory_preset.py`、`build_complete_preset.py` 认这两个变量;其余脚本把路径写死在 Flatpak 数据目录下,换环境要改脚本。反汇编那两个认 `NAHIMIC_DLL`,`decode_nahimic.py` 认 `NAHIMIC_ARCHIVE`。

  1. 生成出厂卷积核与基础预设,当然,你大概率可以跳过这个步骤:
```bash
python3 tools/build_factory_preset.py
```

它从现有的 `output/Music.json` 里取限幅器键模板,所以先确认那个文件在。脚本里的设备文件名是本机的,换机型要改 `VARIANTS`。

  1. 按顺序搭音乐链路,后一个脚本会在前一个的产物上加多频段压缩器:
```bash
python3 tools/build_nahimic_music.py
python3 tools/add_bass_compressor.py
```
  1. 四个场景与采集链:
```bash
python3 tools/build_scenario_presets.py
python3 tools/build_capture_presets.py
```

另外 `build_official_eq_presets.py` 处理的是 Nahimic UWP 应用里的 10 段图形 EQ。它不在驱动包里,而在 Windows 用户目录的 `A-Volute.Nahimic_*/LocalState/Settings/v1/EQPresets/`,要自己复制到仓库的 `data/ui-eq-presets/` 下;这套参数与出厂 XML 不是一回事。

  1. 校验,然后在 EasyEffects 里核对三件事:
```bash
python3 tools/validate_presets.py
python3 tools/validate_complete.py
```

卷积器的 `autogain` 已关、`kernel-name` 不带扩展名、顺序是 低音增强器 → 卷积器 → 高架 EQ → 多频段压缩器 → 限幅器(旁路)。

  1. 不要照抄的部分:人声增强、DRC、衰减器没有建模;HRTF 路线已撤销;扬声器 FIR 的 A/B 变体要按自己的主板判定。

  2. 你可以用 AI Agent 帮你完成上述步骤,你可以如下注入提示词:

```text
请帮我把 Windows 分区里的 Nahimic 出厂音效数据搬成 EasyEffects 预设。
按顺序做,每步先把命令和输出贴给我看,再进下一步;对不上就停下问我,不要猜。

0. 报告环境:发行版、PipeWire 版本、EasyEffects 版本与安装方式;
   当前目录没有仓库的话,先 git clone --depth 1 下面的仓库。
   https://github.com/LaT-SKY/nahimic-factory-audio-tuning
1. 读 /proc/asound/card*/codec#* 与 /sys/bus/hdaudio/devices/*/uevent,
   报出 Vendor Id、Subsystem Id 和 MODALIAS。
2. 在挂载好的 Windows 分区里找 A-Volute/Nahimic 的 INF
   (System32/DriverStore/FileRepository),读出里面的设备匹配串,
   确认 SUBSYS 与第 1 步一致;不一致就停,不要继续解包。
3. 解包 INF 指向的 CAB,把 Global.nsx、AudioProfiles/、Devices/、MicProfile/
   放进仓库的 data/ 目录,并告诉我实际路径。
4. 列出仓库里哪些脚本读环境变量、哪些把路径写死;写死的按我的实际路径改,
   改动逐条给我看。EasyEffects 若是 Flatpak,数据目录是
   ~/.var/app/com.github.wwmm.easyeffects/data/easyeffects
5. 依次跑 tools/build_factory_preset.py、build_nahimic_music.py、
   add_bass_compressor.py、build_scenario_presets.py、build_capture_presets.py。
   其中 build_factory_preset.py 要从已有的 output/Music.json 取限幅器键模板,
   缺这个文件就先告诉我。
6. 跑 tools/validate_presets.py 与 tools/validate_complete.py,把结果报给我。
7. 核对预设:卷积器的 autogain 已关、kernel-name 不带扩展名、顺序是
   低音增强器 → 卷积器 → 高架 EQ → 多频段压缩器 → 限幅器(旁路)。

底线:不要伪造参数,不要把没验证的算法写成结论。人声增强、DRC、衰减器没有建模,
不要自己补;HRTF 那条路已经撤销;扬声器 FIR 的 A/B 变体必须由我确认,
不能按文件数量猜。
```

引用本文的文章