七、音频模块

libGDX 提供了播放音效和声音片段的方法。 它还提供了对音频硬件的读写访问接口。所有音频设备的访问都是通过音频模块完成的:

Audio audio = Gdx.audio;

当应用程序暂停并恢复时,libGDX 的音频模块将会自动暂停并恢复所有音频播放。

播放 PCM 音频

音频模块可以为您提供直接访问音频硬件,以便向其写入 PCM 样本。音频硬件是通过 AudioDevice (source) 接口提取的。一下为创建一个新的 AudioDevice 实例:

AudioDevice device = Gdx.audio.newAudioDevice(44100, true);

这将创建一个采样频率为44.1 khz 并输出 mono 的新 AudioDevice。如果无法创建设备,则将抛出一个 GdxRuntimeException。

我们可以将16位有符号 PCM 或32位浮动 PCM 数据写入设备:

float[] floatPCM = ... generated from a sine for example ...
device.writeSamples(floatPCM, 0, floatPCM.length);

short[] shortPCM = ... generated from a decoder ...
device.writeSamples(shortPCM, 0, shortPCM.length);

如果使用立体声,左右通道样本通常交错(第一个浮点/短 -> 左,第二个浮点/短-> 右)

以毫秒为单位的延迟可以这样查询:

int latencyInSamples = device.getLatency();

这将返回样本中音频缓冲区的大小,从而为您提供关于延迟的良好指示器。返回值越大,音频到达收件人所需的时间就越长。请注意,几乎所有安卓手机的延迟都高得离谱。实时音频应用程序很难达到10-30毫秒的实用范围。通常你可以达到100毫秒的延迟,许多手机的延迟高达400毫秒。遗憾的是,这是一个与驱动程序/操作系统相关的问题,无法解决。

AudioDevice 是本地资源,不再使用时需要处理:

device.dispose();

JavaScript/WebGL 不支持直接 PCM 输出。

录制 PCM 音频

您可以通过 AudioRecorder 接口从 PC 或 Android 手机上的麦克风访问 PCM 数据:

AudioRecorder recorder = Gdx.audio.newAudioRecorder(22050, true);

这将创建一个在单声道模式下采样率为22.05千赫的 AudioRecorder。如果无法创建记录器,将抛出一个 GdxRuntimeException

样本可以读作16位有符号 PCM:

short[] shortPCM = new short[1024]; // 1024 samples
recorder.readSamples(shortPCM, 0, shortPCM.length);

立体声样本通常交错存储(第一个样本 > 左通道,第二个样本 > 右通道)。

AudioRecorder 是一种本地资源,如果不再使用需要处理:

recorder.dispose();

JavaScript/WebGL 不支持音频录制。

音效

音效是一些小的音频样本,通常不超过几秒钟,在特定的游戏事件中播放,比如角色跳跃或者开枪。

音效可以以各种格式存储,包括 MP3、 OGG 和 WAV。您应该使用哪种格式,取决于您的具体需要,因为每种格式都有自己的优点和缺点。例如,WAV 文件与其他格式相比相当大,OGG 文件在 RoboVM (iOS)和 Safari (GWT)上都不能工作,而且 mp3文件存在不适当的循环问题。

注意: 在 Android 上,Sound 实例的大小不能超过1mb (未压缩的原始 PCM 大小,而不是文件大小)。如果你有一个更大的文件,得使用 Music 代替。

音效由 Sound 接口表示。加载音效的代码如下:

Sound sound = Gdx.audio.newSound(Gdx.files.internal("data/mysound.mp3"));

这将从内部目录数据中加载一个名为“ mysound.mp3”的音频文件。一旦我们加载了声音,我们就可以播放它:

sound.play(1.0f);

这将播放音效一次,在全音量。单个 Sound 实例上的 play 方法可以连续调用多次,例如对于一个游戏中的一系列镜头,它将被相应地覆盖。可以使用更细粒度的控制。每次调用 Sound.play ()都返回一个 long,用于标识音效实例。使用这个句柄我们可以修改特定的回放实例:

long id = sound.play(1.0f); // play new sound and keep handle for further manipulation
sound.stop(id);             // stops the sound instance immediately
sound.setPitch(id, 2);      // increases the pitch to 2x the original pitch

id = sound.play(1.0f);      // plays the sound a second time, this is treated as a different instance
sound.setPan(id, -1, 1);    // sets the pan of the sound to the left side at full volume
sound.setLooping(id, true); // keeps the sound looping
sound.stop(id);             // stops the looping sound 

目前,这些方法在 JavaScript/WebGL 中的功能有限。从1.9.6开始,setPan ()只有在支持 flash 并启用 gwtapplicationconfiguration.preferveflash = true 的情况下才能工作

setPan ()方法不适用于立体声

一旦你不再需要一个 Sound,确保你处理掉它:

sound.dispose();

在释放声音后访问它将导致未定义的错误。

正如在一个 Audio 上所说的,Android 在音频方面有很多问题。其中之一是,等待合理的 ID 可能需要很长时间。Libgdx 的音乐默认是同步播放的。如果你同时播放很多声音,它会导致主循环被冻结很长时间。特别是这个问题在 Android 10上很明显。解决方案是让它们异步播放。但是,在需要 ID 的地方,它将导致无法使用 sounds 方法。

音乐

对于任何超过几秒钟的声音,最好是从磁盘流传出来,而不是将其完全加载到 RAM 中。libGDX 提供了一个音乐接口:

Music music = Gdx.audio.newMusic(Gdx.files.internal("data/mymusic.mp3"));

这将从内部目录数据中加载一个名为“ mymusic.MP3”的 mp3文件。

播放代码如下:

music.play();

当然,您可以设置 Music 实例的各种播放属性:

music.setVolume(0.5f);                 // sets the volume to half the maximum volume
music.setLooping(true);                // will repeat playback until music.stop() is called
music.stop();                          // stops the playback
music.pause();                         // pauses the playback
music.play();                          // resumes the playback
boolean isPlaying = music.isPlaying(); // obvious :)
boolean isLooping = music.isLooping(); // obvious as well :)
float position = music.getPosition();  // returns the playback position in seconds

Music 实例是重量级的,使用时得注意加载的数量。

如果不再需要 Music 实例,则需要将其释放,以释放资源。

music.dispose();

在安卓上的问题

安卓后端使用 SoundPool API 播放声音,MediaPlayer 播放音乐。这些 API 在某些情况下有一些限制和已知的问题:

  • 延迟不是很好,对于延迟敏感的应用程序,如节奏游戏,不推荐使用默认实现
  • 同时播放多个声音可能会导致某些设备的性能问题。这个问题的一个简单解决办法是使用默认的 Android 实现来替代实现 AsynchronousAndroidAudio,在 AndroidLauncher 上实现 createAudio () ,如下代码:
@Override
public AndroidAudio createAudio(Context context, AndroidApplicationConfiguration config) {
	return new AsynchronousAndroidAudio(context, config);
}

一般来说,Android 上的音频是有问题的,可能还有其他的场景或设备特殊的问题。

为了解决这些问题,Google 创建了 Oboe,这个 Oboe 可以用于 libGDX 项目,这要感谢 libGDX Oboe。