LibGDX / Android:播放音效使游戏卡顿 - Sound.play() 在高端设备上需要 4 毫秒
LibGDX / Android: Playing sound effects makes the game stutter - Sound.play() takes 4 ms on a high-end device
每当我在 Android 设备上的 LibGDX 游戏中播放音效时,游戏就会卡顿。我已经在三台三星设备上玩过这款游戏:
- 在 Galaxy S7 Edge(2016,Android8)和 Galaxy Tab S 10.5(2014,Android6.0.1)上游戏仍然可以玩,但不是运行流畅每当播放多个音效时(循环音效不是问题)。
- 但是在 Galaxy S20 Ultra(2020,Android 10)上游戏无法玩:每次调用 Sound.play() 需要 2...4 毫秒并导致“ AUDIO_OUTPUT_FLAG_FAST 被服务器拒绝;frameCount 0 -> 54276" 错误。 此错误不会出现在其他设备上,但 Sound.play() 仍然需要 1...2 毫秒,其中当然是 16 毫秒帧的相当一部分。
所以我认为很清楚的是问题出在 Sound.play() 方法,而不是同时播放的声音数量(我限制为 8 个,但也尝试过 4 个),或者 Android 设备处理声音的速度太慢(在这种情况下,6 岁的 GT 应该不会胜过今年的高端 S20),或者音效文件会太大(我用于测试的文件最初是 3.8 kB WAV)。是的,我正在使用 AssetManager 提前加载声音。
我现在花了两天的时间进行研究,在不同的论坛上找到了大约 15-20 个主题,我认为这些主题是相同或相关的问题,并且尝试了所有建议的修复,但没有成功:
- 正在将音频格式从 WAV 更改为 OGG
- 不同的采样率:两种格式均为 44.1k、48k、96k(使用 96k 时,没有卡顿和错误,但也听不到声音)
- 在音效末尾添加 1 秒或 2 秒的静音(其本身长 41 毫秒),并使用上述格式和采样率的所有组合。
- 有人说“在后台”循环播放一段无声的声音片段可以解决问题,但无论如何我都有另一个声音(汽车引擎)在游戏中不断循环播放,但似乎没有效果。
我也看到建议使用 Music class 而不是 Sound,但是它不适合 Box2D 的碰撞音效,因为音高无法调整。
我发现但尚未尝试的唯一解决方法是在不同的线程上播放声音。我没有尝试过,因为我不熟悉多线程,并且无法在 LibGDX 中找到关于如何(正确)执行它的足够全面的指南。我还假设这种方法对于在主线程中的某些演员在播放期间可能必须暂停、停止或调整的任何声音都会有问题。此外,根据 https://github.com/libgdx/libgdx/wiki/Threading,“您永远不应对任何与图形或音频相关的内容执行多线程操作”。
因此,在我开始熟悉那个话题(多线程)之前,我只想再问一次:真的没有其他解决方案吗?今年的高端 Android 设备无法以超过 4 毫秒的速度开始播放小型 WAV 声音,这感觉不对。 Play商店里有很多游戏音效流畅,游戏流畅,真的都是多线程吗?
我没有完整的答案,但我会在这里分享一些想法。
我自己的轶事经验是,对于 Android 上的典型渲染线程来说,开始声音播放等声音操作往往过于耗时。我尝试了几种不同的方法(AudioTrack
、SoundPool
等),据我所知,在每种情况下都得到了相似的结果。
将音频放在不同的线程上似乎是最实用的解决方案。如果您不熟悉多线程,我理解您的犹豫,而且我认为您保持谨慎是正确的,尤其是在使用第三方库时。但是,对于简单的任务,Android 提供了一些相当简单的工具,例如 HandlerThread
和 Handler
,也许可以加以利用。
至于 LibGDX 文档说不要对任何与音频相关的内容执行多线程操作,我不清楚这是否意味着不要在渲染以外的线程上执行任何与音频相关的操作线程,或者如果它只是意味着将所有音频保存在一个线程上,但该线程不必是渲染线程。如果是后者,那么将音频放在单独的线程上可能是一种选择。
我快速浏览了 LibGDX 源代码。我必须花更多时间才能更好地了解发生了什么,但我看到 AudioTrack
和 SoundPool
都在使用,而且我很确定我已经 运行 解决了这个问题两者都有。
但是,我也看到了一些异步声音功能的迹象。有一些名称中带有 'asynchronous' 的 类 使用专用处理程序线程。我不知道此功能是否已记录(我无法立即找到文档)或以其他方式得到支持,但它似乎确实存在于源代码中。评论说有一些限制,但我不是很清楚它们是什么。
至于渲染线程和音频线程之间的通信,它会增加一些复杂性,但您应该能够使用处理程序或其他类似工具相当直接地完成它。事实上,这就是我看到的 LibGDX 代码所做的——它创建一个 HandlerThread
并使用 Handler
(自然地)到 post。这仍然很困难,尤其是在使用第三方库时,您无法控制所有音频操作的发生位置。例如,LibGDX 可能总是在特定线程(例如渲染线程)上设置音频对象,这意味着如果您使用另一个线程,您将在它们所在的线程之外的线程上使用这些对象被创造。我怀疑这会是一个问题,但这取决于技术。 (例如,ExoPlayer 的文档说实例只能在单个线程中使用。)
在我自己的代码中,我自己处理所有音频,所以我控制它并且可以将所有内容放在同一个线程上。使用 LibGDX 可能很难或不可能,但 'asynchronous' 音频 类 的存在可能暗示在不同线程上播放音频是安全的。 (也许你可以利用那些 类,假设它们是 API 的受支持部分。)
每当我在 Android 设备上的 LibGDX 游戏中播放音效时,游戏就会卡顿。我已经在三台三星设备上玩过这款游戏:
- 在 Galaxy S7 Edge(2016,Android8)和 Galaxy Tab S 10.5(2014,Android6.0.1)上游戏仍然可以玩,但不是运行流畅每当播放多个音效时(循环音效不是问题)。
- 但是在 Galaxy S20 Ultra(2020,Android 10)上游戏无法玩:每次调用 Sound.play() 需要 2...4 毫秒并导致“ AUDIO_OUTPUT_FLAG_FAST 被服务器拒绝;frameCount 0 -> 54276" 错误。 此错误不会出现在其他设备上,但 Sound.play() 仍然需要 1...2 毫秒,其中当然是 16 毫秒帧的相当一部分。
所以我认为很清楚的是问题出在 Sound.play() 方法,而不是同时播放的声音数量(我限制为 8 个,但也尝试过 4 个),或者 Android 设备处理声音的速度太慢(在这种情况下,6 岁的 GT 应该不会胜过今年的高端 S20),或者音效文件会太大(我用于测试的文件最初是 3.8 kB WAV)。是的,我正在使用 AssetManager 提前加载声音。
我现在花了两天的时间进行研究,在不同的论坛上找到了大约 15-20 个主题,我认为这些主题是相同或相关的问题,并且尝试了所有建议的修复,但没有成功:
- 正在将音频格式从 WAV 更改为 OGG
- 不同的采样率:两种格式均为 44.1k、48k、96k(使用 96k 时,没有卡顿和错误,但也听不到声音)
- 在音效末尾添加 1 秒或 2 秒的静音(其本身长 41 毫秒),并使用上述格式和采样率的所有组合。
- 有人说“在后台”循环播放一段无声的声音片段可以解决问题,但无论如何我都有另一个声音(汽车引擎)在游戏中不断循环播放,但似乎没有效果。
我也看到建议使用 Music class 而不是 Sound,但是它不适合 Box2D 的碰撞音效,因为音高无法调整。
我发现但尚未尝试的唯一解决方法是在不同的线程上播放声音。我没有尝试过,因为我不熟悉多线程,并且无法在 LibGDX 中找到关于如何(正确)执行它的足够全面的指南。我还假设这种方法对于在主线程中的某些演员在播放期间可能必须暂停、停止或调整的任何声音都会有问题。此外,根据 https://github.com/libgdx/libgdx/wiki/Threading,“您永远不应对任何与图形或音频相关的内容执行多线程操作”。
因此,在我开始熟悉那个话题(多线程)之前,我只想再问一次:真的没有其他解决方案吗?今年的高端 Android 设备无法以超过 4 毫秒的速度开始播放小型 WAV 声音,这感觉不对。 Play商店里有很多游戏音效流畅,游戏流畅,真的都是多线程吗?
我没有完整的答案,但我会在这里分享一些想法。
我自己的轶事经验是,对于 Android 上的典型渲染线程来说,开始声音播放等声音操作往往过于耗时。我尝试了几种不同的方法(AudioTrack
、SoundPool
等),据我所知,在每种情况下都得到了相似的结果。
将音频放在不同的线程上似乎是最实用的解决方案。如果您不熟悉多线程,我理解您的犹豫,而且我认为您保持谨慎是正确的,尤其是在使用第三方库时。但是,对于简单的任务,Android 提供了一些相当简单的工具,例如 HandlerThread
和 Handler
,也许可以加以利用。
至于 LibGDX 文档说不要对任何与音频相关的内容执行多线程操作,我不清楚这是否意味着不要在渲染以外的线程上执行任何与音频相关的操作线程,或者如果它只是意味着将所有音频保存在一个线程上,但该线程不必是渲染线程。如果是后者,那么将音频放在单独的线程上可能是一种选择。
我快速浏览了 LibGDX 源代码。我必须花更多时间才能更好地了解发生了什么,但我看到 AudioTrack
和 SoundPool
都在使用,而且我很确定我已经 运行 解决了这个问题两者都有。
但是,我也看到了一些异步声音功能的迹象。有一些名称中带有 'asynchronous' 的 类 使用专用处理程序线程。我不知道此功能是否已记录(我无法立即找到文档)或以其他方式得到支持,但它似乎确实存在于源代码中。评论说有一些限制,但我不是很清楚它们是什么。
至于渲染线程和音频线程之间的通信,它会增加一些复杂性,但您应该能够使用处理程序或其他类似工具相当直接地完成它。事实上,这就是我看到的 LibGDX 代码所做的——它创建一个 HandlerThread
并使用 Handler
(自然地)到 post。这仍然很困难,尤其是在使用第三方库时,您无法控制所有音频操作的发生位置。例如,LibGDX 可能总是在特定线程(例如渲染线程)上设置音频对象,这意味着如果您使用另一个线程,您将在它们所在的线程之外的线程上使用这些对象被创造。我怀疑这会是一个问题,但这取决于技术。 (例如,ExoPlayer 的文档说实例只能在单个线程中使用。)
在我自己的代码中,我自己处理所有音频,所以我控制它并且可以将所有内容放在同一个线程上。使用 LibGDX 可能很难或不可能,但 'asynchronous' 音频 类 的存在可能暗示在不同线程上播放音频是安全的。 (也许你可以利用那些 类,假设它们是 API 的受支持部分。)