以前用另一个操作系统时,我用一个程序实时处理正在播放的音频,让声音更好听:它让声音显得更远、听感更轻盈,同时保留相关信息。我一直想在 Linux 上有一个同样简单易用的类似程序,于是决定自己写一个。请戴耳机听,示例片段取自 Bacao Rhythm & Steel Band 的 P.I.M.P.(0:00 / 0:10);处理后的版本听起来离耳朵更远,不再那么贴耳,听一阵音乐后也不会觉得发闷。
正文
之前用另一个操作系统时,我用一个实时处理播放音频的程序,让声音好听很多。它让声音显得离耳朵更远,听起来轻一点,同时保留了该有的信息。我一直想要一个在 Linux 上同样简单易用的类似程序,于是决定自己写一个。
试听片段来自 Bacao Rhythm & Steel Band 的《P.I.M.P.》,建议戴耳机听。
第一次尝试
因为目标平台是 Linux,我的底层框架是 PipeWire。我之前没有音频经验,于是把 LLM 当作讨论对象,一边试验标准音频滤波器和心理声学方法,一边和它交流。
我后来了解到 HRTF(头部相关传输函数),它描述来自某个方向的声音如何到达耳朵,具体取决于方向和听者的头部几何形状。我先试了 MIT KEMAR 数据库,觉得很难听,因为它是在消声室里录的,没有房间回声,而这里正需要房间回声这种心理声学效果。结果显得廉价,而且更贴耳朵。后来我找到 SADIE II 数据库,它附带了房间响应,听起来就接近我记忆中的效果了。
但这条路很快碰到上限。LLM 开始谈论“感知包围感”“后期场去相关”“频谱扩散”“扩散空间能量”这类东西。我能听出效果还有问题,但这些描述既没有指向我能理解的东西,也没法拿来改进。
于是项目搁置了一段时间,没有明确方向。
再次尝试
很久以后,我换了思路:不去猜那个程序内部具体用了什么参数、技术和算法,而是研究滤波器本身的行为。
事后看这很自然,但我并没有那个程序的副本,只能在朋友的笔记本上做实验,所以没法随时改自己的实现再和参考对比。
我开始读 Signalsmith 和一些音频工程师的博客,先建立足够的直觉,知道该测量什么;很快我意识到,并不需要一直开着那个程序去抓它的脉冲响应和其他可测特征。我在 Linux 上生成一个音频文件,借朋友的笔记本用一段时间,把这个文件送进程序播放并录下输出。
测试信号的组装
问题是:该文件里应该放什么,才能一次录下足以区分这个程序的细节,而不必反复重录特征或滤波器。
第一个有用的东西是脉冲响应测量。如果把一个只含一个非零采样的文件送进滤波器,录音中出现在它之后的一切都应该是滤波器加进去的。衰减持续多久、衰减形状如何、伴随的相位延迟是什么,都会体现在输出里。把脉冲放在右声道,就能提取两个输出声道对右声道声音的响应,左声道同理。
如果单个脉冲的响应是某个信号,那么位于某处的脉冲响应就是同一信号经移位和缩放后的版本;把所有响应相加,就得到叠加输入的输出。这当然要求滤波器是线性时不变(LTI)滤波器。
更大声的脉冲应该产生同样形状、只是按比例更大的响应;两个一起播放的输入,输出应该等于各自单独输出之和;同一个输入晚一点播放,输出也只是晚一点出现。因为是立体声,完整关系由四个响应组成:左输入到左输出、左输入到右输出、右输入到左输出、右输入到右输出。
由于我是把一个文件拿去用每一种模式录音,我在里面加了不同幅度的脉冲,用来检查是否存在随电平变化的处理;还把同一个脉冲同时放到两个声道,以及右声道反相的情况。前一种输出应该等于各自单独响应之和,后一种应该等于相减,用来验证上面的条件。脉冲之间留了足够静音,让上一个响应结束再进入下一个。
测试脉冲分三种电平,分别走左声道、右声道、双声道,以及右声道反相。
读资料时我注意到,测量类论文往往用正弦扫频而不是脉冲。脉冲虽然包含所有频率,但只有一个采样点,每个频率上的能量都很小;用一段长扫频,响应会更容易从录音噪声里分辨出来。于是我加了一段缩短的对数扫频。
对大部分频率范围这是可行的,但如果某个频率的激励接近零,用录音噪声除以它,会得到一个看起来厉害、却和那个程序没什么关系的滤波器。可以用正则化版本避免:在输入能量足够的地方,结果近似等于直接相除;在其他地方,正则项会限制结果。复数结果还保留相位,而相位是必需的,因为送入一个声道的声音可能会延迟后出现在另一个声道。
扫频提供了和脉冲不同的另一种测量,但播放时间长得多,期间程序有可能改变行为。于是我又加了不同频率和幅度的稳态音,以及从轻到响再回轻的较长音,用来观察增益是否会变、变化需要多久。
我加了稳态音,缩短后走双声道;还有一段在两级之间交替的 1000 Hz 音;再加一个多频音,检查有没有新频率冒出来——那是一个八频多音;又把同样的多音重复三遍,检查时不变性。我还留了一段噪声,按低频、中频、高频顺序覆盖,用来事后测试提取出的滤波器能否重现它的录音输出。
恢复滤波器
录音和播放都是手动开始的,所以录音先要对齐。我在开头附近放了等间隔的咔嗒声,最初想用原始咔嗒声和录音做相关来定位;但因为滤波器会把每个咔嗒声在时间上展开,我改为在一个窗口内对样本平方求和,估算它周围的能量。
开头是八个同步用咔嗒声。
据此我找到偏移量,并对两个声道使用同一个偏移量,以保留它们之间的延迟。
输出
在主环绕设置下,缩放后的脉冲几乎完全一致;组合输入的输出等于各自单独响应之和;多音也几乎没有产生新频率。为了比较重建输出和录音,我用提取出的滤波器产生输出,再和录音输出比较。由于原本完全相同的重复段已经有轻微差异,我把模型的误差和这个差异做比较。
降低音量的模式会随幅度改变增益,而且恢复需要时间;另一个空间模式在重复之间也会变化,所以两者都不能用固定的卷积滤波器重现。
把一个脉冲分别送进每个输入声道,并录下两个输出声道,我得到了四个滤波器。声音也会出现在另一声道,音量更小、略有延迟,后面拖着长尾。这些合在一起,正是我之前想手工做出来的“头外”效果。
为了决定保留多长的响应,我用噪声段试了越来越长的滤波器,最后保留了完整响应,因为它对噪声的预测比短版本更准。从扫频提取出的响应,在输入能量足够的范围内也和它一致。
把四个响应载入 Saq,就得到了我之前一直想手工复现的定位感。
你可以在这里试听结果:选择本地音频文件,或在 Firefox 以外的浏览器里捕获标签页,然后点击方形环绕图标开关效果。
评论
多数人质疑文章演示的空间音频后处理听感糟糕,认为它只是用激进处理掩盖廉价小单元缺陷、制造出令人生厌的“音效”;少数人承认其技术实现有含金量,并指出专业场景中类似商用效果音质可靠。分歧主轴在于这种虚拟环绕处理究竟提升了真实感,还是牺牲了中性音质。
认为这种夸张后处理听起来糟糕、掩盖廉价单元缺陷
- 评论者承认做出效果需要才华,但成品听感很差,被嘲讽为夸张的“音效呕吐”;如今电视、蓝牙音箱和耳机普遍用激进音频处理来弥补超廉价微小单元的物理限制,导致中性音质设备越来越难找。还有人对比 Nothing Ear 耳机的空间音频,认为那个更真实,因此不信服文章方案;亦有人直言声音难听,但承认文章技术层面有价值。(认可度:较多人) > 这种处理让音乐听起来像是被胶带绑在1950年代小学礼堂喇叭上的花园水管里放出来的。(@clzerv)
认为专业商用空间音频音质可以很可靠
- 虽然演示被吐槽,但有开发者指出,自己十二年前做的商用空间音频效果曾用于乔治·卢卡斯参与电影《红色机尾》的混录母带,音质没问题;相关音频工程师两度获格莱美提名,好莱坞也认可这类技术。(认可度:少数人) > 做出来的插件曾用于《红色机尾》的混录母带,所以音质应该没问题。(@fxtentacle)
有价值的补充
- 有评论把效果类比为一类编曲软件里的卷积混响器:用脉冲响应把某个空间或吉他音箱的特征套到音频流上,可加载不同寻常空间录制的混响;作者还希望文章能给出处理前后对比,或至少能开关效果,并顺带分享了一段 2000 年代用仿真头麦克风录制的经典“虚拟理发店”双耳音频,用来展示沉浸式错觉。(@andai)
- 发烧友社区的空间音频实践更偏向把杜比全景声音乐(5.1 环绕、更高动态范围)下混成立体声,例如在视窗系统的苹果音乐设置里直接下混,或用开源项目 PEQDB;也有人尝试用头部相关传输函数做空间音频,但评论认为它用于音乐并不好。(@coldblues)
- 有人建议作者参照吉他音箱建模里的神经建模采集思路,去捕获音箱或空间特征来做模型。(@haddr)
- 前述开发者还补充,内置空间音频的硬件耳机方案能让电视以为输出的是杜比全景声、由耳机端再做虚拟环绕,但硬件利润率偏低,尽管已有可工作原型,投资人仍不感兴趣。(@fxtentacle)