HRTF 3D音效简明算法

尊重原创,请勿转载!  作者:图林根の烤肠,如有纰漏欢迎指出

写作日期:2015年12月28日

文章来源:www.mahong.me/archives/97

最近毕设的UI设计已经大体上完工,在圣诞之际开始了音频的HRTF合成,由于这方面的相关资料不是非常全面,导师在过完一年一度的旅游假期后外加生病一星期(俺就就当做是真生病了)反而又到了圣诞长假,年前连半个个人影也找不到。同学Oli说过一句挺搞笑的话,科学家们经常把本来很普通的事情故意说得晦涩难懂,以证明自己非常牛叉。虽然导师给指点了一下,然而在看了很多资料后我仍是一头雾水,仍然没搞懂如何合成具体的3D音效,然而在读完Mr. Li 论文的代码后对HRTF的音频合成有了大致的了解,突然发现原来那么高大上的技术居然简单的要死,特此总结下相关知识,以便后人不必痛苦的为找不到资料儿发愁。

PS:本文特此感谢下 Mr.Li 的鼎力技术支持,至于说 Mr. LI 是谁? 我也不知道,那可是法兰火车站附近的名人~(偷笑)

另外给学校做个广告,欢迎各位同学来德国就读 伊尔梅瑙工业大学 的媒体技术专业,虽然这个学校不出名,但是媒体技术专业在德国仍然是前三名。这个城市位于德国中部图林根州,风景秀礼,城市虽然很小,但是十分适合学习研究。如果你对音频领域感兴趣,这个方向的 IEEE Fellow, mp3之父 Brandenburg教授,正是当年带领团队开发出举世著名mp3格式的团队,学校里面还坐落着德国 弗劳恩霍夫应用研究促进协会 的数字多媒体研究院,正是全德国研究媒体技术的前沿专业机构。

闲话不扯了,正文开始


如果看到此文,表明您已经对HRTF (head-related transfer function)有一定的了解,随着这几年VR技术的不断发展,3D音频的应用范围也得到了进一步的扩展。通俗来讲,HRTF数据库对应的频域范围的信号,与之对应的则为HRIR (head-related impulse response), 这里主要说明的是时域范围,通过HRIR与音频信号进行卷积来实现3D音效。当然,实现3D音效的数据库不止HRIR,据导师说明,音效模拟的效果依次为: BRIR > HRIR > Stereo ,这里的BRIR指的是 binaural room impulse response), 相对HRIR来说BRIR具有更多的空间信息(资料待考证) 。目前互联网上容易寻找的HRTF数据库是CIPIC HRTF DATABASE,传送门点我,相关内容我会在下一篇文章中阐述。

首先来说下实现3D模拟的算法,明白之后看代码就会容易许多。

  • 1) 确定音频的播放位置

我们知道,通过一段音频与对应的空间位置(azimuth和 elevation 角度)的HRIR数据进行卷积即可产生一段位于该点的空间音频。首先来做的是输入的音频在空间上需要分割为多少个点。这里如图所示(稍微将就下,个人手画水平实在不敢恭维),在平面上(这里简化成2D空间,容易理解其算法),假如我们想要在 0°,60°,120°, 180°,300° 和 330° 这六个点合成信号。

koordinatesystem

  • 2) 分割音频为N份

每个点的音频时间可以均分或者不等分,均分的话容易计算,因为之前已经定义了6个点,所以这里把音频分为6等分。

每段音频的长度 = 输入音频的总长度 / 需要在空间上生成的位置个数。

假设音频长度为264600个采样点,采样率为44100的6秒片段,那么每段长度为 264600 ➗6 = 44100 ,则每段音频长1秒

  • 3) 每段信号进行卷积

接下来就需要对每一段的音频信号跟相对应空间位置的HRIR函数卷积。这里的算法为

片段1 = 音频段1 * HRIR_0°

片段2 = 音频段2 * HRIR_60°

片段3 = 音频段3 * HRIR_120°

片段4 = 音频段4 * HRIR_180°

片段5 = 音频段5 * HRIR_300°

片段6 = 音频段6 * HRIR_330°

  • 5) 合并每一段代码后合成最后的声音

输出 = 片段1 + 片段2 +片段3 +片段4 +片段5 +片段6

这里的加法在Matlab上面为数组的合并,并不是每一项的数学相加: 输出 = [片段1  片段2  片段3  片段4  片段5  片段6 ]


细心的读者会发现最后一步是步骤5而不是4,因为我们还需要做接下来的工作。

因为在实验之后会发现,如果只用以上4步的实际效果并不理想,在每一段的音频中总会听到断点,导致音频不连贯,具体原因还请其他明白的读者告知,所以我们的做法是在每一段的开头和结尾加上淡入淡出的效果,是每一段音频的连接处正常化。算法为:

  • 4-1) 生成窗函数:windows来实现淡入淡出效果,如图所示:

hann

这里用一个汉宁窗来实现淡入淡出,淡入取汉宁窗的前半段,淡出取后半段,这里的窗函数长度是自订的,窗函数长度 = 淡入长度 + 淡出长度。为了方便前后淡入淡出长度相等即可。及

窗函数长度  = 2 * 淡入长度 = 2 * 淡出长度 = 淡入长度 + 淡出长度

然后淡入效果实现为使用汉宁窗的前半部乘以需要淡入的音频片段即可,即可保证音频的平滑过渡。

淡入 = 音频的淡入部分 * 淡入窗函数(窗函数前半段)

淡出 = 音频的淡出部分 * 淡出窗函数(窗函数的后半段)

如果最后生成的音频仍有不连贯问题,可采用更改窗函数长度的方法反复试听,一般即可解决问题。

  • 4-2) 片段分解:

首先来看图片,因为图片理解起来比文字要具体不少:

我们需把每段的音频分为3部分:淡入  中间音频  淡出segment3

其中要注意的是:淡入淡出的时候第一段音频不需要淡入音频,而最后一段音频不需要淡出,可以当做特例来进行处理。

  • 4-3) 合并淡入与淡出

把每一段的音频合并起来,因为分段的音频结尾进行了淡出,为了保持连贯,需要在该段的结尾与下一段音频开头的淡出合并,才能实现连贯效果。注意,这里并不是把片段1结尾的长度相应加长了片段2淡入的长度!而是把片段1的结尾变成片段1的淡出与片段2淡入合并的结果,说得有点绕口,看下面图片即可明白:

segment_strich

最后实现的效果是:在片段的末尾加上了淡入和淡出合并的效果,在片段的开头也加上了相应的效果

接下来是文字的详细解释:

片段1’ =片段1 不需要淡入的部分 + 片段1 + (片段1 淡出与片段2 的淡入叠加)

片段2‘ =  片段2开头窗数据剔除掉 + 片段2(中间段)  + (片段2 淡出与片段3 的淡入叠加)

片段6’ = 片段6开头窗数据剔除掉 + 片段6 剩余部分

这里抛开第一段片段片段长度发生了改变,少去的部分即为淡入(淡出)的长度!因为第一段的末尾和第二段的开头是相同的,我们只需要其中的一个即可,如果舍去片段1的末尾,那么剩下的每一段都需要舍去开头。反之若舍去第二段片段的开头,则需要把剩余片段的开头一并舍掉即可。

举个实际的例子:

片段2原来长度 : 44100, 淡入淡出长度各为:256, 则推出 =>>片段2’ 长度仍为:44100-256 = 440744,中间的部分长度为 44100 – 256 – 256 = 43588

注意此时的 片段2’ 数值为原来片段2所 对应 的数值!片段2’ 的中间部分 = 片段2 [256+1: 43588] 及片段2的第256个值开始到43588的值

  • 4-4) 片段合并

最后一步就是把所有的片段合并即可,合并后的结果如图所示:

segment6

以上就是HRIR函数的具体算法,详细代码可以浏览我的 GitHub,我也会在 下一篇 文章中补充代码的说明