Nahimic 出厂音效续篇:变体判定与空间效果

接着上一篇:用 SMBIOS 的 OEM 串判定扬声器变体,排查 390 个参数的去向,复核空间效果与串扰抵消为什么在笔记本上不成立。

上一篇《把 Nahimic 出厂音效搬到 Linux》结尾留了三件事:扬声器变体 A/B 没有判定、461 个参数里有 390 个不知道取值、空间效果没有落地。 所以就有了后续。

先决条件

需要 本次环境
硬件 MECHREVO 蛟龙,Conexant SN6140(`VEN_14F1 DEV_1F87`,SUBSYS `1D053010`)
系统 CachyOS,PipeWire 1.6.9
效果器 EasyEffects 8.3.0(Flatpak)
原厂数据 Windows 分区里的 A-Volute/Nahimic 驱动包(180 个明文 XML)
工具 Python 3、NumPy、`cabextract`、`objdump`

上一篇的环境是 PipeWire 1.6.8 与 EasyEffects 8.2.9。8.3.0 在 2026-09-29 发布,这一轮关于插件的结论都以 8.3.0 为准。

范围限制:只看笔记本内置扬声器。耳机、外置声卡、蓝牙设备都不在范围内。

一、变体由 SMBIOS 的 OEM 串决定

`Devices/` 里属于本机 SUBSYS 的扬声器档案有 8 个,分成两套曲线。上一篇停在「主板型号 `X6xR55xK` 对不上任何一个文件名,不能按文件数量猜」这一步。

判据就在设备档案末尾的 `<Data>` 块里:

```xml
<HWID>       <Value>SUBSYS_1D053010</Value> </HWID>
<FormFactor> <Value>Speakers</Value>        </FormFactor>
<ID>         <Value>{753ed818-…}</Value>    </ID>
<SMBIOS>     <Value>1D053010K5500103</Value></SMBIOS>
```

配置器要拿本机的一个字符串去比 `<SMBIOS>`。这个字符串的来源写在 `NahimicPnPAPO4ConfiguratorDaemonModule.dll` 里:

```text
SELECT OEMStringArray FROM Win32_ComputerSystem
```

`Win32_ComputerSystem.OEMStringArray` 就是 SMBIOS 的 Type 11(OEM Strings)。

把那张表读出来

Linux 这边三条常规入口都不通:

入口 结果
`/sys/firmware/dmi/tables/DMI`、`entries/11-0/raw` 权限 `-r-------- root`
`/sys/class/dmi/id/*` 17 个字段里没有 OEM 串
Windows 侧 `HKLM\SYSTEM\…\mssmbios\Data` Windows 11 已不再缓存这张表

所以做一次只读的特权读取就够:

```bash
pkexec /usr/bin/cat /sys/firmware/dmi/tables/DMI > data/smbios/DMI.bin
python3 tools/read_smbios_oem.py data/smbios/DMI.bin
# → Type 11 OEM Strings (20 条):
#     [16] '1D053010K5500103'   <-- 形如 SUBSYS+型号键(候选选择键)
#   ✅ 命中: 1D053010K5500103
#      档案: 1D053010_1D053010K5500103_Speakers.nsx
```

完整脚本:read_smbios_oem.py 149 行 / 5.2 KB | 直接下载

判据是三段拼接:`SUBSYS_1D053010` 取自 HWID,`K5500103` 是 OEM 键,两者与档案里的 `<SMBIOS>` 必须逐字符相同。本机命中即变体 B(`X6DR57TK`),不需要扫频,也不需要听感 A/B。

8 个候选其实只有 2 套曲线

把每个档案的 `<ID>`(GUID)与 `<SMBIOS>` 归一化之后算 md5:

档案 `<SMBIOS>` 归一化后 md5
`…K5500101` – `…K5500107` 各不相同 七个文件完全相同 `c2b5d235…`
`…K5500100` `1D053010K5500100` `7cf30fdc…`

同一套调音挂在 7 个 OEM 键下面,不是 7 次独立采样。

顺带一提,档案头部的 `FT-NOTE` 注释不可信:`K5500101` 到 `K5500107` 的注释统统写 `SMBIOS-1D053010K5500103`,连 `0107` 也写 `0103`。只有 `<SMBIOS>` 字段算数。

二、390 个参数不在磁盘上

`NahimicAPO4API.dll` 里有 461 个 `kSet_*` 键。XML 只出现了其中 71 个(交集 67 个),剩下 394 个键在配置文件里从来没有出现过。

上一篇的猜测是:实际取值保存在运行态,`Settings/settings.dat` 是最像的地方。这一轮把候选逐一拆开看:

# 位置 实际内容
1 `Settings/settings.dat`(8192 B,注册表 hive) 只有 `CurrentMicProfile='Chat\0'` 与 `ToastShown='[]\0'`
2 `LocalState/AppSettings.json` 应用启动状态
3 `LocalState/EQPresets.json` 每个场景选用的预设名(本机全为 `Custom`)
4 `LocalState/Chat.nsx` / `Conference.nsx` 采集链的 6 个开关,属采集侧
5 `LocalState/Settings/v1/EQPresets/**` UI 那 10 段图形 EQ,已转成 EasyEffects 预设
6 `HKCU\Software\A-Volute\Nahimic\{701C0AEE-…}` 守护模块登记表:每个 `*DaemonModule.dll` 的路径与 GUID
7 `HKCU\Software\Nahimic\…\ShortcutEngine\{7943271B-…}` 一个快捷键码 `k000031a4 = 8`
8 `HKLM\SOFTWARE\…\MMDevices\Audio\Render\{端点}\FxProperties` 只有 APO 注册项
9 `HKLM\SYSTEM\…\Enum\SWD\…AVOLUTE_NH4APO&6&…` 设备枚举项,`[Device Parameters]` 的 `values=0 subkeys=0`

第 8、9 项本来最有希望:`FxProperties` 是第三方 APO 在 Windows 上持久化参数的常规位置,而那一项里只有一个 APO 注册和三个 mode CLSID。

结论是负的:这 390 个键不落盘,运行时参数由后台服务推送给 APO 的共享内存。想拿到它们,只剩静态提取 `NahimicAPO4.dll` 的内置默认值表一条路,所以我们又又延期了(详见第八节待办)。

这一轮顺带确认了两件事:当前生效的 APO 是 A-Volute/Nahimic(端点属性里 `{f363df17-…},11 = 'A-Volute.Nahimic'`),`Senary Audio` 只是 OEM 驱动的品牌名;另外 `Nahimic_Mirroring` 建了一个独立的镜像端点。

三、空间效果:只有开关,没有强度

出厂四个场景里,空间类效果的开关如下(来自同一份 XML):

参数 音乐 电影 游戏 通讯
`SpkVirtualSurroundState` 0 1 1 0
`StereoWideningState` 0 1 1 0
`UpmixState` 0 1 1 0
`ReverbState` 0 1 1 0
`ReverbGainDB` -10.0 -10.0 -10.0 -10.0

音乐场景不启用任何空间效果,只做设备校正加三段增益。电影与游戏则把四个开关全打开。

问题在于强度:461 个键里,`SpkVirtualSurround`、`StereoWidening`、`Upmix` 都只有 `State`,没有任何强度或角度参数。原厂只给了开关,给不出「开多大」。所以这边的做法是给一组分级试听预设,由耳朵选:

场景 预设
电影 加宽弱、加宽强、串扰、综合(4 个)
游戏 加宽弱、加宽强、串扰、综合(4 个)
音乐 加宽弱、加宽强

注意,原厂的音乐预设是没有加宽的,这里纯粹是我想试试效果所以加上去了

落地用的是两个插件:立体声扩展走 Calf 的 Stereo Tools(`stereo_tools`),串扰抵消走 EasyEffects 原生的 `crosstalk_canceller`。这两个插件的 JSON 键名是从上游 v8.3.0 的 kcfg 与源码里核对的,凭印象写键名会被静默忽略。

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

四、串扰抵消在笔记本扬声器上不成立(至少现在不成立)

试听串扰预设时,我总感觉听起来很奇怪,塑料味道相当大。

第一件事是确认卷积器有没有在工作。厂校 FIR 在 800 Hz 有一个箱体共振凹陷,串上串扰之后这个凹陷只变化 -0.12 dB:

```text
  FIR 单独            : -12.88 dB
  FIR × RACE(H_direct): -13.00 dB
  -> 凹陷被填回 -0.12 dB
```

凹陷完好,卷积器在正常工作,问题出在串扰本身。

RACE 是什么

EasyEffects 的串扰抵消是 RACE(Recursive Ambiophonic Crosstalk Elimination,作者 Antti S. Lankila)。它是一个递归结构:中置声道经过一个反馈梳状滤波器(见下方公式)之后再混回左右。:

是 6 个双二阶(biquad)级联:250 Hz 高通、3245 Hz 低通,加四个衰减峰(688 / 1066 / 2190 / 3792 Hz)。

延迟是量化过的:

313 µs 在 48 kHz 下等于 15 个采样,梳状间隔 `1/D` 是 3.2 kHz,第一个四分之一周期正好落在 800 Hz——厂校 FIR 挖凹陷的位置。

数值复核

先把解析模型(z 域)与逐样本时域仿真对着跑:两者的最大偏差 0.003 dB。这个自检有用,把模型里的符号写反时偏差立刻变成 2.1 dB。

然后看它对频响做了什么:

通路 结果
直达通路 全程 ±2 dB,峰 +3.11 dB @1449 Hz
交叉项(`phantom-center-only=true`) 250–3245 Hz 带内,1597 Hz 处 -8.98 dB
交叉项相位 与直达同相(800 Hz 处 87°)

同相的交叉项起不到抵消作用,它更像一份延迟 312 µs 的副本,加在原信号上形成梳状滤波——听感上的染色就来自这里。

完整脚本:analyze_crosstalk.py 347 行 / 14.3 KB | 直接下载

串扰抵消的四联频响图:RACE 反馈滤波器、直达通路(全程 ±2 dB)、注入对侧的交叉项,以及厂校 FIR 与串上 RACE 后的对比。800 Hz 的箱体共振凹陷只变化 0.12 dB

为什么在笔记本上不成立

RACE 的几何前提接近 KEMAR 那种头型:两只扬声器与听者构成固定夹角,双耳时间差是已知量。笔记本内置扬声器的张角只有约 ±10°,插件里的 `delay-us` 下限 200 µs 对应约 9.6 个采样,很可能已经高于这种间距下的真实双耳时间差。再加上预设用的 `phantom-center-only=true` 只处理中置声道,染色正好落在人声与主奏上。

结论:这 4 个串扰与综合预设在本机判定为不可用。这里没有做声学测量,机制解释属于「最合理的解释」,频响数值是实算的。

五、校验器漏掉的 37 个插件块

生成预设之后要跑校验。上一轮的 `validate_presets.py` 里,插件分派写成一串 `if/elif`,最后没有 `else`。结果是:不认识的插件直接跳过,一个检查都不做,还照样报 OK。

统计下来有 37 个插件块从来没被校验过:`limiter` 19 个、`bass_enhancer` 17 个、`deepfilternet` 1 个。这个数量已经超过当时预设里被检查的部分。

补法是给这三个插件写真实检查,并加两条硬规则:

```python
    # 未知插件**必须报错**,不能静默放行 ——
    # 否则新增插件时"全部通过"是假的(这正是本档案陷阱 1 的形态)。
    chk(base in HANDLED_PLUGINS,
        "%s: 插件 %r 没有校验规则 —— 请先补 HANDLED_PLUGINS 与对应检查" % (name, base))
```

另一条是「键集完全一致」:每个插件块的键集合必须与 kcfg 枚举的集合逐键相同,多一个少一个都算错。以前只检查键值合法,键名写错、键漏掉都发现不了。

升级后立刻抓到两个真实缺陷:`-原厂-` 与 `-完整-` 两个预设的限幅器 `release = 100.0`,而这个键的合法上限是 20。两处限幅器都是旁路状态,所以对听感没有影响,但这类越界以前一个都报不出来。

新的校验器自己也做了五类注入测试(改枚举、删键、加键、改数值、换成不认识的插件),确认每类都能报错。整份脚本在源码仓库里:`tools/validate_presets.py`。

六、脉冲响应改名,以及 EasyEffects 的两个坑

卷积器只在 `kernel-name` 变化时才重新读 `.irs` 文件。如果原地覆盖同名文件,修正过的 FIR 不会生效,而且没有任何提示。所以脉冲响应改成带内容哈希的名字:

```text
Nahimic-Factory-X6DR57TK.irs  →  Nahimic-Factory-X6DR57TK-c03355d3.irs
```

改名过程中踩到两个坑:

改完之后 `irs/` 目录里只有一个文件,运行态、预设、磁盘三者一致。预设数量在这一轮的轨迹:

阶段 数量
双变体时期(输出) 38
删掉 11 个 A 变体预设 27
加上 8 个空间预设(电影/游戏) 33
加上 2 个音乐加宽 35

35 = 输出 34 个 + 输入 1 个(采集链的降噪预设),统计时间是 2026-10-06。

七、如何获取本文的工具?

代码见仓库:nahimic-factory-audio-tuning

脚本认这几个环境变量:

变量 用途 默认值
`EASYEFFECTS_DATA` EasyEffects 数据目录 `~/.var/app/com.github.wwmm.easyeffects/data/easyeffects`
`NAHIMIC_ARCHIVE` 档案根目录 脚本上两级目录
`NAHIMIC_DEVICES` 设备 `.nsx` 目录 `$NAHIMIC_ARCHIVE/data/Devices`
`NAHIMIC_DLL` `NahimicAPO4.dll` 路径 当前目录下的同名文件

八、还没做的

事项 状态
静态提取 `NahimicAPO4.dll` 的内置默认值表 未开始
`VirtualBassBoost` 的 tanh 驱动量、前置滤波、混合比 未开始
`Ape::SBP::Clarity` 的真身 未开始
4 个串扰/综合预设是否删除 待定
中频电平 受限:压缩后限制峰值的是 -4.06 dB 的高频段(高音增强 +4 dB 所致)

另外两条已知差异:`bass_enhancer` 是静态的,复现不了出厂那种随音量变化的低音补偿,所以小音量下低音比原厂少、大音量下比原厂多;`-响度` 变体的音量小得离谱(等响度插件多半把 `-12 dB` 当成了播放电平基准),在修好之前不建议使用。

九、省流

  1. 判定自己的变体(只读,需要一次 `pkexec`):
```bash
pkexec /usr/bin/cat /sys/firmware/dmi/tables/DMI > data/smbios/DMI.bin
NAHIMIC_DEVICES="$PWD/data/Devices" \
  python3 tools/read_smbios_oem.py data/smbios/DMI.bin
```

命中哪一个 `<SMBIOS>`,就用哪一个设备档案,不用扫频。

  1. 校验预设:
```bash
python3 tools/validate_presets.py
```
  1. 选空间效果:先试加宽,串扰那一档在笔记本上大概率不合适。

  2. 如果要让 AI Agent 重做一遍,把上一篇那段提示词的第 9 步换掉——变体现在可以判定,不必「由我确认」:

```text
9. 判定扬声器变体:用 UEFI 的 SMBIOS Type 11 OEM 串,与 data/Devices/*.nsx
   里的 <SMBIOS> 精确匹配(tools/read_smbios_oem.py),不要按文件数量猜,
   也不要靠听感或扫频。读 /sys/firmware/dmi/tables/DMI 需要一次只读的 pkexec。
```

本文引用的文章