游戏引擎学习第139天

决定做一个音频混音器

我们正在进行一个完整的游戏开发,完全从零开始,不使用任何游戏引擎或第三方库,而是亲手实现所有底层技术。目前,我们正处于一个决策点,思考接下来的开发方向。在上一次的开发过程中,我们讨论了是先完成资源加载系统,还是先开发音频混合器。经过考虑,我们决定优先开发音频混合器。

做出这个决定的原因在于,我们尚不确定是否需要对WAV音频文件进行分块加载。如果先实现音频系统,就能更清楚地了解其需求,从而决定是否在资源管理系统中加入音频流式加载功能,以及如何实现这一功能。

通常来说,音效文件体积较小,可以一次性加载并播放,因此无需特殊处理。然而,对于较长的音乐文件,例如三到四分钟的背景音乐,我们可能不希望将整个音频数据一次性全部载入内存,而是按需加载。例如,我们可以每次仅加载十秒的音频数据,播放完毕后再加载下一段,从而减少内存占用。这种方法称为音频流式加载。

不过,在现代计算机上,是否真的有必要进行这样的流式加载仍然值得商榷。通过简单的计算可以看出,如果采样率是48,000Hz,16位立体声,每秒钟的音频数据大小大约是192KB。一首三分钟的音乐大约需要32MB内存。如果我们留出64MB的内存作为音频缓冲区,在当前硬件条件下,这点内存占用完全可以接受。因此,可能根本没有必要特意实现流式加载。

我们来详细拆解这个计算过程:

1. 计算每秒钟的音频数据大小

已知的音频参数:

  • 采样率(Sample Rate):48,000 Hz(即每秒采样 48,000 次)
  • 采样位深(Bit Depth):16 位(即每个采样点占 16 位 = 2 字节)
  • 声道数(Channels):2(立体声,每个采样点有两个值)

计算每秒数据量:
每秒采样点数 × 每个采样点的字节数 × 声道数 \text{每秒采样点数} \times \text{每个采样点的字节数} \times \text{声道数} 每秒采样点数×每个采样点的字节数×声道数
= 48 , 000 × 2 × 2 = 48,000 \times 2 \times 2 =48,000×2×2
= 192 , 000 字节 = 192 KB = 192,000 \text{ 字节} = 192 \text{ KB} =192,000 字节=192 KB

所以 每秒钟的音频数据大小是 192 KB


2. 计算三分钟音频的大小

三分钟 = 3 × 60 = 180 秒
180 × 192 KB = 34 , 560 KB 180 \times 192\text{KB} = 34,560 \text{KB} 180×192KB=34,560KB
= 33.75 MB ≈ 32 MB(近似取整) = 33.75 \text{MB} \approx 32 \text{MB}(近似取整) =33.75MB≈32MB(近似取整)

所以 三分钟的音频大约需要 32MB 内存

如果需要更精确的计算,就保持 33.75MB,但如果是大致估算,32MB 是一个可以接受的近似值。

尽管如此,从技术探索和教育的角度来看,研究音频流式加载仍然可能是有意义的。因此,我们决定先开发音频系统,然后再回过头完成资源加载系统,之后再完善渲染系统。这种方式类似"俄罗斯套娃"式的逐层推进,最终可以完成整个游戏引擎的核心部分。在完成这些工作后,我们可能还会做一些调试相关的开发,但整体架构就基本完成了。

接下来,我们会详细讨论音频系统的基础知识,包括音频的基本原理、数据结构、低级实现方式等。许多开发者可能只熟悉在游戏引擎(如Unity)中调用现成的API播放声音,而不了解音频在底层是如何工作的。因此,我们会从基础讲起,深入探讨音频系统的实现。

黑板:声音和音乐

物理特性可以参考:https://physics.info/sound/

我们将讨论声音,同时也会涉及音乐,因为在现代技术条件下,音乐基本上就是一段较长的声音。如今,由于计算机硬件性能的提升,我们不再需要依赖 MIDI 这种方式来生成音乐,而是可以直接存储和播放完整的音频录音文件,这在游戏开发中非常方便。

声音的基本概念

那么,当我们谈论"声音"时,我们究竟在讨论什么?声音在现实世界中的传播机制是怎样的?

我们能够听到声音,是因为空气中的分子受到振动,并以波的形式传播。这些振动传递到我们的耳朵后,被耳蜗等器官转换成神经信号,从而形成我们对声音的感知。在物理学上,声音是一种 纵波,它以压力波的形式在空气或其他介质(如水或固体)中传播。

在计算机领域,我们通过 数字音频 来模拟和存储这种波动。数字音频的基本原理是使用 采样 (Sampling)的方法,将连续变化的声音信号转换为一系列离散的数值。采样率(Sample Rate)决定了每秒钟采集多少个点,而 位深(Bit Depth) 决定了每个点能存储多少信息。例如,CD 质量的音频通常采用 44.1kHz(即每秒 44,100 个样本)和 16 位深度(即每个样本 2 字节)。

在现代游戏开发中,我们不再受早期硬件限制,可以直接存储和播放高质量的音频文件。因此,声音处理的重点更多在于混音、音效管理以及优化播放性能,而不再需要依赖早期的 MIDI 方式进行合成。接下来,我们将进一步探讨声音的具体组成以及如何在计算机中进行处理。

"这个世界充满了分子"

世界充满了各种分子,空气中弥漫着这些分子,而物体如墙壁等则由更密集的分子组成。声音的本质是这些分子的振动和传播。在正常情况下,空气中的分子是无序运动的,但当外界施加某种力,使这些分子产生规律性的运动,例如风推动空气中的分子,它们便开始协同运动,形成振动。这种振动会在分子之间不断传播,使声音在空间中扩散。

声音的传播方式

可以将声音的传播类比为水面的波纹。当我们在水面投入一个石子时,会看到波纹从中心向外扩散。尽管水面最终会恢复平静,但波纹会向四周传播,并遇到障碍物时发生反射,形成新的波纹。这些波纹相互作用,可能会产生干涉,形成复杂的波形。

声音的传播方式与此类似,但不同于光。光的传播方式是直线传播,遇到物体后会按照一定的反射、折射规律继续沿直线传播,并且能够较完整地保留信息。而声音的传播依赖于介质中的分子振动,振动可以沿多个方向扩散,因此声音不会像光那样严格沿直线传播,而是以三维波动的形式传播。这意味着声音可以绕过障碍物,进入房间的角落,即使没有直接的传播路径,也可以通过多次反射或折射进入不同的空间。

例如,假设有一扇门,一部分声音会直接通过门口进入房间,类似于光的传播方式。然而,即使门被关闭,声音仍然可以通过空气中的分子振动扩散,并绕过门框进入房间。此外,硬质表面对声音的反射较强,而柔软的材料(如地毯或厚布)则会吸收更多的振动,从而减少反射。这些特性决定了不同环境下声音的传播和混响效果。

声音的理解与应用

虽然不需要深入研究声音的物理特性,但理解声音传播的基本原理对于音频处理、游戏开发等领域仍然至关重要。例如,在游戏音频设计中,我们需要考虑声音如何在游戏世界中传播、反射和衰减,以提供更加真实的听觉体验。因此,尽管不需要像研究声学的科学家那样掌握所有细节,了解基本的传播原理仍然是十分有用的。

黑板:我们是如何听到声音的

也可以看这个三分钟的小视频
https://www.bilibili.com/video/BV1vh411R7Mz

声音的感知与生理机制

在我们的应用场景中,需要关注的核心问题是如何重现声音在世界中的传播现象。声音本质上是振动,这些振动通过空气或其他介质传递,最终到达人耳。而人耳的构造使得我们能够感知并解析这些振动,将其转换为有意义的声音信号。

1. 声音进入耳朵的过程

当外界的振动传播到耳朵时,首先会经过耳廓的收集,并进入耳道。这些振动到达 鼓膜 后,会使鼓膜发生相应的振动。鼓膜的振动会进一步传递到 听小骨(包括锤骨、砧骨和镫骨),这一过程对声音进行了一定的物理放大和过滤。

随后,振动被传递到 内耳耳蜗 之中。耳蜗是一个充满液体的螺旋状结构,其中存在大量的 毛细胞(听觉感受器细胞)。这些毛细胞的排列方式使得它们能够感知不同频率的振动。

2. 毛细胞如何感知声音

当声音振动进入耳蜗后,耳蜗内部的液体随之波动,使得不同位置的毛细胞受到不同程度的刺激并开始振动。这些毛细胞的运动会触发电信号,并通过 听神经 传递到大脑。

  • 频率感知:不同频率的声音会在耳蜗的不同位置引起最大共振。例如,高频声音在耳蜗的基部被检测到,而低频声音在耳蜗的顶端被检测到。
  • 音量感知:更强的声音会导致毛细胞振动幅度更大,进而产生更强的神经信号,使大脑感知到更高的音量。
3. 视觉与听觉的对比

这一过程类似于眼睛中的感光细胞(视杆细胞和视锥细胞)如何检测光线并将信息传输到大脑。不同之处在于,眼睛感知的是光子,而耳朵感知的是振动。但本质上,二者都是将物理信号(光或声波)转换为神经信号,以供大脑处理和理解。

综上,我们需要关注的核心问题是 如何在计算机中模拟这一物理现象,以便在数字系统中实现逼真的声音回放。

黑板:频率

声音的感知主要依赖于振动的频率和振幅。频率决定了声音的音调,高频振动产生高音,低频振动产生低音。可以将这一过程比作水中的涟漪,石头落入水中时,产生的波浪有峰和谷,测量这些波浪的间隔即为频率。人耳能够感知的频率范围通常在20Hz到22kHz之间,超过或低于这个范围的声音可能无法听到,但低频声音仍可能通过身体的振动感知,例如超低音的震动。

振动进入耳朵后,通过耳膜传递到内耳,内耳中的毛细胞根据振动的不同频率产生不同的电信号,并将其传输至大脑,从而形成对声音的理解。类似于眼睛中的视锥细胞和视杆细胞负责感知光,耳朵中的毛细胞负责解析声音的频率和振幅。

不同生物的听觉范围不同,例如狗可以听到更高频率的声音,因此能察觉人类听不到的狗哨声。人耳的听觉能力也会因年龄和个体差异有所变化。

此外,声音信号的采样率与人类的听觉能力相关。现代音频通常采用44.1kHz或48kHz的采样率,这意味着每秒钟记录44,100或48,000个数据点,以尽可能精准地再现声音。这远高于视觉的更新速率,例如VR设备通常以90Hz的刷新率呈现图像,而音频处理远超这个数值。

相比视觉,听觉的信息处理速度更快,但分辨率和定位能力较弱。人类在声音的空间定位方面不如某些动物,例如蝙蝠利用回声定位来精确感知周围环境,而人类主要依靠双耳的时间和强度差异来判断声源方向。

由于音频数据的采样频率高于视觉数据,如果要以相同的速率处理图像,例如48,000帧每秒,计算量将极其庞大,远超当前计算机的能力。因此,音频数据处理的要求与视觉数据处理的方式截然不同。

黑板:振幅

声音的两个重要属性是频率和振幅。频率决定音调(pitch),振幅决定音量(volume)。

频率与音调

频率指的是振动的快慢,也就是声波振动的速度。频率越高,声音的音调越高;频率越低,音调越低。例如,缓慢振动的声波会产生低沉的声音,而高速振动的声波会产生尖锐的高音。单位赫兹(Hz)表示每秒振动的次数,比如 22kHz(22000Hz)意味着每秒钟振动 22000 次。人类的听力范围一般在 20Hz 到 22kHz 之间,低于这个范围的声音可能无法听见,但可以通过身体感受到震动,比如超低音的冲击。而高于 22kHz 的超声波,人耳无法感知,但某些动物(如狗)能够听到。

振幅与音量

振幅指的是波的峰值高度,振幅越大,声音的音量越大。可以用水波的比喻来理解:如果在水中投入一块小石子,会形成小波纹,而投入一块大石头,波纹会更大,说明振幅更高。同理,声波的振幅越高,声音就越响亮。

声音的再现------扬声器的作用

声音在物理世界中的传播是靠振动完成的,因此要再现声音,就必须能重现这些振动。扬声器的核心原理就是利用电信号驱动其内部的振膜,使其前后振动,从而产生与原始声音相似的空气振动。当电信号的振幅增大时,扬声器的振动幅度也增大,声音就更响亮;当电信号的频率升高时,扬声器振动得更快,声音的音调就会更高。通过持续改变信号的振幅和频率,可以再现各种声音。

立体声与声音定位

人类有两个耳朵,因此能够通过双耳的声音输入来判断声音的大致方向。声音定位的关键在于耳朵接收声音的方式不同,比如来自左侧的声音会先到达左耳,然后才到达右耳,并且右耳接收到的声音可能会稍微减弱或有不同的频率特征。基于这些细微差别,大脑可以估算出声音的来源位置。然而,前后方向的声音定位较难,因为双耳接收的时间差和音量差不明显。人们主要依靠声音的特性变化(如是否被耳朵或头部阻挡)来判断前后位置。

多声道系统的作用

为了增强声音的方向感,音响系统通常使用多个扬声器。例如,立体声(Stereo)系统通过左右两个扬声器来模拟不同方向的声音来源。如果希望声音听起来像是从左前方传来,可以在左扬声器中播放 70% 的声音信号,而右扬声器播放 30% 的信号,这样听觉上就会产生声音偏左的错觉。虽然不完全精确,但能提供一定的空间感。

在传统音响系统中,声音是通过空气传播的,会受到环境反射、吸收等因素影响,导致声音的方向感不够精准。而耳机由于直接传递声音到耳朵,因此能够提供更纯净的声音输入,使得声音定位更加精准。这种方式在 3D 音效和虚拟现实(VR)中尤为重要。

3D 音频与空间音效

为了更真实地再现三维空间中的声音,人们研究了头部相关传输函数(HRTF, Head-Related Transfer Function)。HRTF 通过测量不同方向的声音到达耳朵时的变化情况,建立数学模型,然后在音频播放时对声音进行相应的处理,使得听众在耳机中也能感受到声音来自特定方向。例如,在 VR 体验中,如果一个声音是从右后方传来的,系统会调整左耳和右耳接收到的声音强度、延迟、频率衰减等因素,使得大脑误以为声音真的是从右后方传来的。这种技术可以极大增强沉浸感,使虚拟环境更加真实。

VR 中的声音处理

在 VR 游戏或应用中,声音不仅仅是背景元素,它还能极大地增强沉浸感。例如,如果一个物体在玩家背后掉落,但声音听起来仍像是从前方传来,就会影响体验的真实感。通过 3D 音频处理,可以让声音正确地反映物体的位置,使得用户更加沉浸其中。因此,VR 开发者越来越重视音频技术,未来可能会看到更加精确和复杂的空间音效处理方法。

总结

声音的基本要素包括频率(决定音调)和振幅(决定音量)。扬声器通过振动空气来再现声音,而立体声系统利用多扬声器播放不同强度的声音,使人产生方向感。在 VR 和 3D 音频中,通过 HRTF 等技术,可以更精准地模拟声音的方向和距离,从而增强沉浸感。虽然在普通游戏中,精确的 3D 音效可能不是必要的,但在 VR 领域,它可能会成为影响沉浸体验的重要因素,未来的发展值得期待。

黑板:从软件方面来做这个过程

声音的数字处理与输出

在了解了声音的基本原理之后,接下来讨论如何在软件层面处理和输出声音。从软件角度来看,声音的播放最终归结为向**扬声器(或耳机)**发送一组指令,让它们按照特定的方式振动,以再现所需的声音。

多通道音频输出

在播放声音时,我们通常会有多个输出通道,例如:

  • 立体声(Stereo):两个通道(左声道和右声道)。
  • 环绕声(5.1 声道):五个扬声器 + 一个低音扬声器(Subwoofer)。
  • 耳机音频:两个通道,分别对应左右耳。

无论使用的是普通扬声器、耳机,还是更复杂的多通道音响系统,所有音频输出的核心逻辑都是:
我们需要向多个通道提供声音数据,让每个扬声器按照特定的方式振动,以再现声音。

波形数据的编码与采样率

要向扬声器发送振动指令,我们需要对声音波形(waveform)进行数字编码。
在数字音频处理中,我们通过
采样(sampling)的方式,将声音的连续波形
转换成离散数据

例如,假设音频的采样率(sample rate)48,000Hz,意味着每秒钟 记录 48,000 次波形的瞬时状态,每个瞬间的状态都会被编码为一个数值。这些数值用于指示扬声器振膜在该时刻应该处于的位置。

扬声器振动的数值表示

可以想象扬声器振膜的中间位置 是零点(即静止状态),向前推是正值,向后拉是负值。

16-bit 音频编码中,每个采样点的值通常在 -32,768 到 32,767 之间:

  • 32,767:最大正振幅(扬声器振膜向前推动的最远位置)。
  • 0:振膜处于静止状态(中性位置)。
  • -32,768:最大负振幅(扬声器振膜向后拉动的最远位置)。

这些数值会被发送到数字音频转换器(DAC, Digital-to-Analog Converter),然后转换成模拟信号,驱动扬声器的振膜运动,从而再现声音。

立体声音频数据的存储

在立体声(stereo)系统中,我们有两个扬声器 (左声道和右声道),因此需要为每个时间点存储两个波形数值:

  • 左声道样本值(Left Sample)
  • 右声道样本值(Right Sample)

示例如下(采样率 48kHz,16-bit 立体声):

采样时间点 左声道样本值 右声道样本值
0 12000 -8000
1 14000 -7500
2 16000 -7000
3 18000 -6500
... ... ...

这样,每秒钟都会有 48,000 组这样的数值对,被发送到声卡进行播放。

声音混合(Mixing)

在游戏或音频软件中,往往有多个声音需要同时播放。例如:

  • 游戏角色的脚步声
  • 背景音乐
  • 敌人的攻击音效
  • 环境音(风声、雨声等)

这些声音可能来自不同的音轨(track) ,但最终都要混合成一个最终的音频信号,然后输出到扬声器。

混音的核心原理很简单:

只需要把所有的声音样本值加在一起!

例如,如果有两个音轨:

  1. 音轨 A(脚步声)
    • 采样点 1: 5000
    • 采样点 2: 6000
  2. 音轨 B(背景音乐)
    • 采样点 1: 8000
    • 采样点 2: 7000

那么混合后的结果就是:

  • 采样点 1: 5000 + 8000 = 13000
  • 采样点 2: 6000 + 7000 = 13000

这样就得到了最终的混合波形,可以直接发送到扬声器播放。

混音时的音量溢出问题

在混音时,如果多个声音的振幅相加后超过了允许的最大范围 (16-bit 的 ±32,767),就会发生削波(clipping) ,导致声音失真。因此,通常会对混音后的数据进行归一化(normalization)动态范围调整(dynamic range compression),以防止溢出。

总结
  1. 数字音频的基本流程

    • 通过采样获取波形的离散数据(例如 48,000 次/秒)。
    • 每个采样点存储 16-bit 数值,表示扬声器振膜的位置。
    • 立体声需要存储左声道右声道的波形数据。
  2. 声音输出

    • 计算机向声卡传输音频数据,声卡通过 DAC 将数字信号转换为模拟信号,并驱动扬声器振动。
    • 扬声器根据输入信号的变化,产生对应的空气震动,从而形成声音。
  3. 混音(Mixing)

    • 同时播放多个音效时,只需要对每个采样点的数值求和,即可实现音频混合。
    • 需要防止音量溢出,避免削波导致声音失真。

这一系列过程构成了计算机音频播放的基础,无论是普通游戏、电影音效,还是复杂的 3D 音频技术,都是建立在这些基本原理之上的。

黑板:通过将声音相加来混合声音

声音的叠加与混合

在理解声音如何通过扬声器播放时,我们可以得出一个关键结论:声音的输出最终归结为一组数值,每个扬声器都有一组独立的数值来控制它的振动。我们通过这些数值来告诉扬声器振膜在每个时刻应该如何移动(振动)。这种数值流通常是通过采样率来表示,比如每秒 48,000 次采样,记录扬声器振膜的位置信息。

声音的叠加与冲突

当我们考虑多个声音的同时播放时,首先要理解的是,所有声音的物理表现最终都是通过空气分子振动 来传播的。空气中的分子会因声音波的传播而产生振动,并且这些振动会按特定频率和力度变化。关键在于,声音是一种波动现象,而这些波动是不能直接在同一个空间点上同时存在的

假设我们在某个空间点上想要产生两个声音,这两个声音的波动必须叠加 在一起。这是因为,从物理上讲,我们无法在同一个点上发送两种完全不同的振动。举个例子,如果我们在同一个位置播放两个声音,一个是低频声音,一个是高频声音,我们不能将这两种波形分别"发送"出去。实际上,扬声器的振动只能呈现出两者的叠加波形

如何混合多个声音

为了实现这一点,当我们需要同时播放多个声音时,最简单的方式就是将每个声音的波形数值相加。举个例子,假设我们有两个声音(每个声音都是一组振动波形),我们就可以将它们在每个时间点上的数值进行叠加,从而得到一个新的合成波形。

例如:

  • 如果第一个声音在某个时刻的振动数值是 +2000,
  • 第二个声音在同一时刻的振动数值是 +3000,

那么这两个声音的叠加结果就是 +5000。

这个过程是逐样本地进行的,即我们对每一个时间点的样本进行加法操作。这样,扬声器的振膜就会根据合成后的波形振动,产生一个包含了多个声音的复合音。

声音的干涉效应

需要注意的是,当两个声音非常接近时,它们的波形叠加可能会产生干涉现象 ,这种干涉可以是建设性干涉破坏性干涉

  • 建设性干涉:当两个波的振幅同向时,它们会相互增强,导致声音更响。
  • 破坏性干涉:当两个波的振幅相反时,它们会相互抵消,导致声音变弱,甚至消失。

实际上,声音的干涉效应在现实世界中是非常常见的,尤其是当两个声音非常接近时,这种效应会影响我们感知到的声音。例如,噪声取消技术正是利用了这一原理,通过生成与噪音波形相反的波形来消除噪音。

音频混音器的实现

在游戏或音频处理中,我们常常需要将多个声音混合成一个最终的音频信号。通过上述叠加的原理,我们可以简单地实现这一功能:

  • 每个声音都可以表示为一系列振动波形,这些波形是由麦克风记录的实际振动信号,经过编辑和处理后成为最终的声音。
  • 若我们有多个声音需要同时播放(例如,背景音乐、环境音效和角色的声音等),我们只需要将这些声音在每个时间点的振动数值进行加法操作。

总结

  1. 声音波形的叠加

    在物理上,我们不能同时从同一个位置发出两个完全独立的声音波,而是通过将它们的波形叠加在一起,产生一个合成波形,这样扬声器就会发出同时包含多个声音的复合音。

  2. 音频混音器的实现

    在软件中,音频混音器的功能就是将多个音轨的振动波形数值逐样本地相加。每个音轨都提供一组波形,通过加法操作合并这些波形,得到最终的音频输出。

  3. 干涉效应

    多个声音的叠加可能会产生干涉效应,如建设性干涉和破坏性干涉,这会影响我们听到的声音。实际的声音输出会受到这些干涉效应的影响。

  4. 简化的音频混合

    因为声音混音只是将多个声音样本进行加法,所以音频混合的过程实际上非常简单,核心操作就是"加法"。

https://www.geogebra.org/classic/bxwscqex

混音效果

黑板:可选地增加复杂性

当我们谈论如何实现音频输出时,核心概念并不复杂。基本上,我们只需要输出一组数字流,每个扬声器都有一个流,通常每秒有48,000个采样(假设使用48 kHz的采样率)。每个采样值都代表扬声器的振动模式,我们通过编码这些振动信息来控制扬声器的行为。虽然在实际硬件中可能有一些细节复杂化,但从软件的角度来看,这个过程相对简单。

在处理多个声音时,我们的目标是将它们混合在一起。简单来说,混音的核心就是将所有声音的采样值逐个相加,最终输出一个新的音频信号。这听起来很直接,就像将多个波形叠加起来,最终生成一个复合的波形。对于大多数开发者来说,这种方法其实是非常容易实现的。

然而,在处理音频时,还是有一些复杂性需要考虑,尤其是**削波(Clipping)插值(Interpolation)**两个问题。尽管它们可能看起来让问题变得复杂,但实际上,这些问题并不是必须处理的,你可以选择不去解决它们,通常也能得到合适的结果。

**削波(Clipping)**是指当两个或更多的音频信号叠加在一起时,它们的振幅可能超过了音频编码的最大范围,导致声音信号被裁剪,从而产生失真。虽然这种情况可能会让音频听起来有些不自然,但实际上,在大多数场景下,音频信号的叠加并不会产生明显的削波失真。

**插值(Interpolation)**是指当处理音频数据时,可能需要在已有的采样点之间插入额外的样本,特别是在需要调整音频播放速度或采样率时。虽然插值在某些特定场景下非常有用,但在简单的音频处理任务中,通常不需要过多考虑。

总结来说,这两个问题的处理并不是强制性的,很多情况下,即使不解决它们,音频的表现依然可以接受。如果你有兴趣深入了解这些复杂的音频处理技术,可以去研究**动态压缩器(Compressor)增益控制(Gain Control)**等技术,它们帮助在音频中避免削波,同时保证音频的平稳和清晰。不过,对于大部分开发者来说,通常可以选择不去处理这些复杂问题,音频处理依然能够满足需求。

黑板:削波

在处理音频时,我们使用了16位的音频采样格式,这意味着每个音频采样的范围是从负3kHz到正3kHz。这里的振幅范围实际上决定了音频的响度。最响的声音是那些振幅达到编码范围极限的声音,通常这些声音的波形会在最大范围内振荡,也就是从负3kHz到正3kHz之间波动。声音的振幅越大,它的音量就越高,但这种振幅的变化并不会对音频本身的含义产生任何影响。实际上,这只意味着扬声器能够输出的最大声音,在特定音量设置下,它就是声波的最大强度。

在混合多个声音信号时,我们通过将各个声音的采样值相加来合成一个新的信号。假设所有的声音采样都在这个编码的最大范围内,那么它们就会被编码成最大精度。举个例子,我们可能会选择将所有声音的最高峰限制在编码范围的上限,像是±32k至±2k之间,确保每个声音的峰值都不会超过这个范围。如果希望声音播放得更柔和,可以通过衰减来调整它们的音量。

然而,当多个声音的峰值叠加时,就可能出现一个问题,即所谓的"削波(Clipping)"。如果其中一个声音的峰值与另一个声音的峰值重合,结果可能会导致一个超出扬声器能够输出的范围的峰值,这样的声音就会产生失真。失真通常表现为音频信号的波形被压缩,无法完全还原原始的声音,这就是所谓的"削波"。大家可能都曾听过或者在使用音响放大器时遇到过这种现象,特别是在吉他音响中,某些情况下,实际上我们希望达到这种削波效果来得到特定的音质。

但事实上,削波并不是每次都会成为问题。在许多情况下,音频信号的叠加并不会引起严重的削波失真。令人惊讶的是,很多情况下即使不特别处理削波问题,也不会导致明显的音质问题。即使出现了削波,也常常是无害的,特别是当声音信号并不达到音频设备的极限时。

不过,如果希望更精确地控制声音的输出,可以使用压缩器(Compressor)来处理音频信号。压缩器有两种常见的类型:软压缩和硬压缩。它们的工作原理是对声音信号进行动态范围的压缩,从而避免峰值超过最大输出范围。这意味着即使声音的强度非常大,压缩器也能将其限制在一个可接受的范围内,避免失真。具体来说,压缩器会对信号的波形进行映射,使得高峰值的声音不会超出设定的上限,并且低强度的声音依然能够得到合适的输出。

虽然压缩器的实现原理相对简单,但由于其并不是我的专长领域,因此并不会详细讲解这些技术。如果你对压缩器感兴趣,可以深入了解动态增益和压缩算法,学习如何平衡音频的整体强度,确保不会出现削波现象。压缩器的实现并不特别复杂,但需要一定的音频处理经验。如果你有兴趣,可以去阅读相关资料,深入研究这些技术。

黑板:调制和插值

在音频处理时,我们可以使用简单的混音方法来将多个音频信号合并。在最基本的混音方法中,我们只是简单地将多个音频缓冲区的内容相加,然后写入结果。这种混音方法非常简单,实际上就是将不同的声音波形相加并输出。如果不处理音频输出时的削波问题,这个过程几乎是最基本的实现。

然而,这种简单的混音方法虽然足够基本,但它的功能较为有限。特别是如果我们希望对游戏中的声音进行一些基本的控制,比如调整音量、平移(即左右声道的分配)以及变调等,简单的相加方法就显得不足够用了。

首先,我们可以对音频进行音量调整。音量的控制实际上是通过调节音频信号的振幅来实现的。如果我们希望将音频变得更安静,只需要将其乘以一个小于1的系数;如果想让音频更响亮,则可以乘以一个大于1的系数。音量调整的原理就是改变信号的振幅,具体地说,就是通过乘法来调节波形的高度,从而达到更强或更弱的效果。

平移(panning)和音量控制有相似之处。平移实际上是对左右声道的音量进行调整,从而让声音偏向左边或右边。可以通过分别调整左右声道的音量来实现不同的平移效果。

如果我们只需要静态地控制音量或平移,那就非常简单了,只需要通过乘法来实现即可。我们可以遍历每个音频样本,通过一个简单的循环将每个样本的值乘以指定的音量或平移系数,然后将其加到最终输出中。这种方式非常直观,代码实现起来也很简单。

然而,问题在于我们不能随意地在不连续的时刻改变音量或平移。比如,如果我们希望音量随着时间的推移逐渐增大,或者在播放过程中做一个渐变的音量调整,那么就需要更复杂的处理。这时候,音量值需要在一段时间内平滑地变化,而不是突然跳跃。这就需要进行插值处理。

举个例子,假设我们想要让一个音轨从静音逐渐变得响亮,直到最终达到最大音量。为了实现这一点,我们需要在一段时间内平滑地控制音量变化,而不是瞬间改变它。这意味着我们需要在每个采样周期中根据时间计算出合适的音量值,并通过插值的方式使音量变化更加平滑。

类似的,如果我们要调整平移效果,同样需要平滑过渡。这就是为什么处理音量和音效时,我们不能仅仅依赖静态的乘法操作,而需要进行时间上的插值和连续变化的处理。这使得问题变得更加复杂,因为我们不仅要处理音频的混合,还要确保在变化过程中音量或平移能够平滑过渡。

黑板:音高

在音频处理中,变调是一个比简单的音量控制和声道平移更为复杂的过程。音频的变调主要是通过调整声音的频率来实现,频率的变化决定了声音的高低。对于音频的变调,有两种主要的方式:一种是"保持长度的变调",另一种是"非保持长度的变调"。

首先,频率决定了音高。如果我们想让声音变得更高,我们可以通过加快声音的播放速度来实现。这意味着,如果一个音频样本的采样率是48,000赫兹(Hz),我们可以将其播放速度提高到2倍,即播放速度为96,000赫兹,这样声音的频率会增加,音高也会变得更高。这样做的缺点是,声音不仅会变得更高,还会变得更快,音频的时长会缩短。

这种简单的做法适用于那些音高变化不大、只需要微调的场景,例如改变钢琴音符的半音高。然而,如果我们希望进行更大幅度的变调,比如想让一段音频既保持原来的时长,又改变音高,这就是"保持长度的变调"了。保持长度的变调非常复杂,直到最近的几年里,才有技术可以较好地实现这一过程。以前,很多商业软件在进行这种变调时的效果并不好,尤其是离线处理时,往往会产生明显的失真。

在游戏音频处理中,通常不会需要进行这种大幅度的、保持长度的变调。大多数情况下,音频只需要做小幅度的音高调整,这种调整通常不影响音频的时长。而且,即使进行轻微的变调,使用非保持长度的变调方法也能取得良好的效果。这类变调方法会导致音频的时长发生变化,但对于小范围的变调,人耳通常无法察觉这种变化。

对于变调,我们可能需要做非常微小的调整,例如将音频从400赫兹调至600赫兹,或者更精细的调整,比如调整至1.02倍或者1.1倍等。这些细微的变调需要对音频的样本进行重新采样,但这些采样点并不总是正好出现在需要变调的地方。为了处理这种情况,可以使用线性插值技术,即通过假设这些采样点之间有一条直线来进行近似计算。这种方法虽然简单,但对于小幅度的变调效果来说,足够使用,并且几乎不会被听觉察觉到。

如果我们需要更强大的变调能力,像保持长度的变调或者大幅度的变调,就需要更多的信号处理技术,但对于大多数游戏音频的需求而言,线性插值足以完成任务。这种方法不仅高效且低成本,而且可以实现足够平滑的音高调整,因此在实际应用中非常常见,尤其是在对音频做小范围变调时。

黑板:为明天做准备

我们在音频处理的最后阶段,重点是创建一个混音器,它能够处理声音的输入、音量调整、音高变化以及它们在时间轴上的播放顺序。混音器需要具备以下功能:

  1. 处理多个声音输入:混音器需要能够接收不同的音频输入,并了解每个声音在时间轴上的位置。这是因为声音通常是随着时间播放的,可能会跨越多个帧,因此必须跟踪每个声音的播放进度,确保播放的是声音的正确部分。

  2. 音量控制:混音器必须能够根据需求调整音量。我们希望音量能够连续变化,可以根据需要进行逐渐的音量变化。这样,游戏中的声音效果可以根据场景需求动态调整,比如渐强或渐弱的背景音乐,或者音效的淡入淡出。

  3. 音高控制:混音器还需要具备音高变化的能力。尽管我们通常不需要对每个音频样本进行音高的连续变化,但在一些特定场景下,比如赛车游戏中的引擎声音,音高可能需要根据特定的物理或游戏机制进行变化。因此,混音器应允许对音频进行音高的调整,通常是针对整个音频的,而不是每个样本的音高。

  4. 不需要持续的音高变化:对于大多数情况,音高变化通常是针对每个音频文件,而不是每个音频样本。只有在特殊情况下,如引擎声音或某些需要动态音效的游戏中,才会需要持续变化音高。

总结来说,混音器的目标是根据游戏需要,处理音频输入、音量调节、音高变化,并根据时间轴正确播放这些声音。这将是下一步的工作重点,确保混音器具备这些功能,能够根据游戏的需求进行有效的音频处理。

你说声道平移只是音量的一个函数,但它是否也可以将一个源声道的一部分混合到另一个输出声道中(对于立体声)?

在音频处理中,平移(Panning) 实际上是通过改变音量来控制声音在左右声道之间的分配。可以通过控制每个声道上声音的音量,来实现将声音放置在特定位置的效果。下面详细分析:

  1. 单声道音频

    • 单声道音频代表的是某个特定位置(比如麦克风)接收到的声音振动。当我们试图重现这种声音时,如果用两个扬声器播放这些声音,我们需要决定声音的位置。
    • 如果声音的来源更接近某个扬声器,我们就会把更多的声音信号送到这个扬声器;反之,如果声音更接近另一个扬声器,我们就会增加那个扬声器的音量。
    • 具体来说,对于单声道音频的每个声道,都会有一个相应的音量值来控制声音的强度。在左声道和右声道中,分别播放不同音量的相同声音,从而实现声音的定位。这里的操作本质上只是通过调整音量的比例,来控制声音在左右声道中的分布。
  2. 立体声音频

    • 立体声音频是通过两个麦克风录制的,通常两个麦克风的摆放位置不同,因此录制到的声音会有所差异。通过这种方式,声音可以在两个声道中以不同的方式呈现出来。
    • 立体声音频并不是直接输出左右声道两个不同的声音,而是将两个单独的声音样本(来自两个麦克风)放置在两个声道中。当我们播放立体声音频时,我们实际上是在播放两个单独的单声道样本,分别对应左右两个声道。
    • 对于游戏中的立体声处理,我们需要决定如何将这两个音频样本定位到左右声道,以模拟声音的空间位置。这两个样本的音量值会影响声音在每个声道中的表现。

总结来说,平移 操作的核心思想是在两个扬声器之间分配声音的音量,从而模拟声音的空间位置。无论是单声道还是立体声,最终的处理都是基于音量的控制。立体声并不是比单声道音频更复杂,只是它实际上是由两个单声道样本组成,播放时需要根据需要对它们进行平移处理,达到所需的空间定位效果。

在直播中的早期,你提到过声音和光的关系,并提到声音是体积的。但光不是也是体积的吗?

在音频和光线的讨论中,有关光线声音的比较,关键区别在于它们的传播方式和本质。

  1. 光线的特性

    • 光线是"一个东西",具体来说,它是光子。光子沿直线传播,并且在传播过程中,光子会以一定的波长进行振动,这种振动是我们感知为颜色的来源。
    • 光的传播不依赖于其他介质。在真空中,光可以自由地传播,不受阻碍。这就是为什么在太空中我们看到的星星是非常清晰和亮的,因为光在没有任何物质干扰的情况下直接传播。
    • 光的传播遵循直线原则,只有当光子碰到物体时,才会发生反射、折射等现象,从而改变光的传播方向。无论在什么情况下,光的传播可以被视为沿直线运动、碰撞、反射和传播。
  2. 声音的特性

    • 与光线不同,声音本身并不是一个物体,它是一种现象,描述的是物质粒子在介质中相互震动和传播的过程。声音的传播需要介质,比如空气、水等。
    • 声音通过物质粒子的振动传播,而这些粒子本身并不直接携带声音。它们通过相互碰撞和震动,将能量传递给周围的粒子,从而形成声音的波动。
    • 因为声音依赖物质粒子之间的相互作用,所以声音无法在真空中传播,这与光的传播方式完全不同。在太空等没有物质的环境中,声音是不存在的。
  3. 光与声音的"体积"概念

    • 是沿着直线传播的,即便是在某些情况下(如"体积光"效果),我们看到的光线穿过空气中的尘土、雾霾等颗粒后折射和散射,形成了"光束"或"光柱"的视觉效果。但这些视觉效果只是因为空气中的颗粒反射了光线,并不是光本身的体积效应。换句话说,光并不"占据空间",它始终遵循直线传播的规律。
    • 声音 则是一个真正的体积现象,它的传播依赖于介质中所有粒子的振动,这些粒子在空间中存在,并共同传递声音。因此,声音是体积性的,它需要空间中的物质来传播,并且它的传播是一个动态的、相互作用的过程。
  4. 光的"体积效应"和声音的区别

    • 尽管在一些视觉效果中,比如在游戏或电影中的"体积光"(volumetric lighting)效果,光似乎展现出"体积"的特征,实际上,这只是通过空气中的粒子(如灰尘、雾霾)反射光线所创造的假象。光仍然是沿直线传播,只是在某些条件下(如颗粒物的存在)产生了光束可视化的效果。
    • 声音的传播完全不同,声音的"体积"是其本质特征,它是由大量粒子的振动和碰撞构成的。声音并不依赖于反射或折射,而是通过振动的方式在空间中传播。
  5. 为什么光和声音在模拟中的处理不同

    • 在模拟过程中,光线的传播可以被直接视作沿直线运动,反射和折射可以通过数学模型准确模拟。而声音则完全不同,声音的传播需要模拟大量的粒子振动及其相互作用,模拟的复杂性较高。
    • 在游戏或渲染中,我们常常简化声音的传播过程,因为人类的听觉系统对声音的细节要求不高,而对于光的渲染,我们需要考虑更多的细节,如光线在空气中的反射、折射等现象。因此,光的模拟相对简单,可以通过线性方式处理。

总结来说, 是沿直线传播的,其传播不依赖介质的存在,而声音则是一种体积性的现象,依赖介质中的粒子相互作用来传播。因此,在理解光和声音的传播时,光是"线性的",而声音是"体积性的"。

没有使用任何特殊的声音效果,比如混响吗?

关于是否加入特殊的声音效果,如混响(Reverb)等,目前没有计划加入这类效果。虽然可能在某个时刻会考虑加入,但目前看起来不会采用。这样的效果可以通过后期处理来加入,但我们倾向于避免过多的复杂音效,因为那样可能会走得很远,涉及到很多复杂的处理。总的来说,可能会保持较为简单的音效设计,而不会深入到那些复杂和吸引眼球的音效效果。

你是否会使用FFT进行声音分析或可视化?你有这方面的经验吗?

关于使用快速傅里叶变换(FFT)进行声音的可视化工作,暂时没有计划。主要原因是游戏中通常不需要关注声音的频率,频率可视化通常用于编辑工作,而在游戏中,了解声音的频率并不是必要的。虽然有使用过傅里叶变换,但并没有深入做过相关工作。理解傅里叶变换的原理,但并没有在实际项目中广泛应用。

游戏引擎是否需要均衡器?我猜测它实现起来会很简单。

实际上,在游戏引擎中实现均衡器并不是特别常见,也不是很简单。虽然看起来可能很简单,但实际上实现起来并不 trivial。均衡器工作在频率空间中,而要在频率空间中进行操作,通常需要使用快速傅里叶变换(FFT)。为了实现快速傅里叶变换,需要额外的代码,这涉及到窗口函数、混合等其他复杂的问题。

此外,快速傅里叶变换并不是音频处理的最佳选择。虽然它在某些方面看起来很有效,但它本质上并不是基于时间的东西,而是静态的频率分析。这会带来一些问题,需要用一些不太优雅的方法来解决。因此,对于均衡器的实现,实际上需要做很多额外的工作,涉及到频率的变换、频率调制等,还需要进行傅里叶变换和逆傅里叶变换来恢复声音。

比如从墙的另一边传来的音频,我们难道不需要至少一个低通滤波器吗?

在音频处理中,低通滤波器是用来处理例如墙壁另一侧的声音的一个常见工具。不过,大多数时候这些滤波器并不是特别重要,除非你非常注重音效的细节。如果想要为声音效果做得更好,当然可以使用低通滤波器,但它们的性价比并不高,因此是否添加这些效果并不是最关键的决定。

低通滤波器的实现其实相对简单,如果在游戏开发的后期,完成其他重要工作后,可能会考虑加入这些音效过滤器。但目前并不打算花太多时间在这些方面,因为实际上很多情况下不使用这些滤波器也不会影响游戏的音效质量。最优先的还是集中精力在其他更重要的开发工作上。

如果真有人对音效有强烈需求,可以选择深入到音效的细节部分,去研究和添加各种滤波器,但这并不是游戏开发的核心需求,因此不计划将其作为优先任务。

一些老旧的声卡能够进行串扰取消,试图用2个或4个扬声器而不是耳机来做3D声音,你有这方面的经验吗?

一些较旧的汽车音响系统曾尝试使用串音消除技术,通过两到四个扬声器来实现三维声音效果,而不是使用耳机。对于这种技术并没有实际的经验。

如何让声音更加真实地反射物体?(比如墙壁、汽车等)

要让声音更加真实地反射到物体上,如墙壁、汽车等,可以采取一些方法。首先,可以找到一些已经采样过的物质反射率数据,比如金属、石膏板等,了解不同表面如何反射声音。然后,可能需要通过发射测试射线来检测声音与物体的碰撞情况。可以通过发射一些新的声音源,在这些碰撞点产生声音的衰减版本,并进行处理,模拟反射的效果。然而,这种做法的实际应用和技术细节不是很清楚,因为对于声音处理的了解并不深入。

例如,像《盗贼:黑暗工程》这样的游戏曾经在声音的传播上做了一些尝试,尤其是在游戏中让玩家能够听到警卫的位置等声音效果,这些游戏在尝试实现更为真实的声音传播模型上做了一些探索。但现在不清楚这种技术的最新发展状态,也不清楚现在是否还有继续研究和应用这种技术。

相关推荐
肥肠可耐的西西公主2 分钟前
前端(AJAX)学习笔记(CLASS 4):进阶
前端·笔记·学习
熬夜苦读学习9 分钟前
库制作与原理
linux·数据库·后端
晨曦启明71131 分钟前
Linux云计算SRE-第十八周
linux·运维·云计算
云上艺旅34 分钟前
K8S学习之基础十五:k8s中Deployment扩容缩容
学习·docker·云原生·kubernetes·k8s
暴躁的小胡!!!1 小时前
Linux权限维持之vim python 扩展后门(五)
linux·运维·服务器·网络·安全
亭墨1 小时前
linux0.11内核源码修仙传第五章——内存初始化(主存与缓存)
linux·c语言·驱动开发·学习·缓存·系统架构
追寻光1 小时前
Linux 配置静态 IP
linux
凡人的AI工具箱1 小时前
PyTorch深度学习框架60天进阶学习计划第14天:循环神经网络进阶
人工智能·pytorch·python·深度学习·学习·ai编程
誓约酱2 小时前
(每日一题) 力扣 283 移动零
linux·c语言·数据结构·c++·算法·leetcode
快起床啊你2 小时前
【linux网络编程】浏览网页时客户端与服务器之间数据交互的完整过程
linux