LibGDX / Android:播放音效会导致游戏卡顿-在高端设备上,Sound.play()需要4毫秒。

4
每当我在安卓设备上的LibGDX游戏中播放音效时,游戏就会出现卡顿。 我已经在三个三星设备上尝试过这个游戏: - 在Galaxy S7 Edge(2016年,Android 8)和Galaxy Tab S 10.5(2014年,Android 6.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秒钟的静音,与所有上述格式和采样率的组合。 - 有人说在后台循环播放一个静音的声音剪辑可以解决问题,但我无论如何都会在游戏中不断循环另一个声音(汽车引擎),而这似乎没有任何影响。 我还看到建议使用Music类而不是Sound,但对于Box2D的碰撞声效来说不太适用,因为无法调整音调。
我唯一找到但尚未尝试的解决方法是在不同的线程上播放声音。我没有尝试过,因为我对多线程不熟悉,并且没有找到足够全面的指南来告诉我如何在LibGDX中正确地执行它。我还假设该方法可能会对任何必须在主线程中由某个动作者暂停、停止或调整播放的声音造成问题。此外,根据https://github.com/libgdx/libgdx/wiki/Threading,“您绝不能在与图形或音频有关的任何内容上执行多线程操作”。
因此,在我开始熟悉这个主题(多线程)之前,我只想再问一次:真的没有其他解决方案吗?一个今年的高端Android设备竟然无法比4毫秒更快地启动小型WAV声音,这让人感到不舒服。Play商店里有很多带有工作音效和平滑游戏体验的游戏,他们真的都在使用多线程吗?

4
您可能会发现这个合并的 PR 很有趣,它已经在最新的 libGDX 1.9.12 发布之前被合并。您可以更换音频后端以使用一个异步后端,这应该可以改善此问题。https://github.com/libgdx/libgdx/pull/6243 - Tenfour04
2
是的,那非常有趣!由于没有Sound ID支持,无法直接使用它,但是通过将其用作模板,我能够在Android设备上创建自己的解决方案,在单独的HandlerThread上播放和修改所有声音。现在游戏在所有测试设备上都运行得非常顺畅,至少到目前为止,我还没有遇到任何因声音在单独线程上播放而引起的问题。谢谢! - Jaggie
2个回答

3
我没有完整的答案,但我会在这里分享一些想法。
根据我的个人经验,像启动声音播放这样的声音操作对于 Android 上的典型渲染线程来说耗时过长。我尝试了一些不同的方法(例如AudioTrackSoundPool等),据我所记得,在每种情况下都得到了类似的结果。
将音频放在不同的线程中似乎是最实际的解决方案。如果您不熟悉多线程,我理解您的犹豫,并且我认为在使用第三方库时保持谨慎是正确的。然而,对于简单的任务,Android 提供了一些相当简单的工具(例如HandlerThreadHandler),可以利用它们。
至于 LibGDX 文档中说不要在任何与音频相关的事物上执行多线程操作,对我来说并不清楚它是否意味着不要在除渲染线程以外的线程上做任何与音频有关的事情,还是只是表示所有音频都必须在单个线程上运行,但该线程不必是渲染线程。如果是后者,则将音频置于单独的线程可能是一个选项。
我快速查看了 LibGDX 的源代码。我需要花更多的时间来更好地理解正在发生的事情,但我看到了对AudioTrackSoundPool的使用,并且我非常确定我已经在这两个方面遇到了这个问题。
但是,我也看到了一些异步声音功能的迹象。有一些类名称中带有“异步”字样,并使用专用的处理程序线程。我不知道是否有文档支持此功能(我无法立即找到文档),但它似乎存在于源代码中。注释说有一些限制,但我目前还不清楚它们是什么。
至于渲染线程和音频线程之间的通信,它会增加一些复杂性,但您应该可以使用处理程序或其他类似的工具轻松地完成它。实际上,这就是我查看的 LibGDX 代码所做的-它创建了一个HandlerThread并使用一个Handler(自然而然)来将消息发送到它。这仍然可能很困难,特别是在使用第三方库且无法控制所有音频操作发生位置时。例如,LibGDX 可能总是在特定线程(例如渲染线程)上设置音频对象,这意味着如果您使用另一个线程,则将在不同于创建它们的线程上使用对象。我不认为这会成为问题,但这取决于技术(例如,ExoPlayer 的文档说实例只能从单个线程使用)。
在我的代码中,我自己处理了所有的音频,因此可以控制它并将所有东西放在同一个线程中。这可能在LibGDX中很困难或不可能完成,但“异步”音频类的存在可能表明在不同的线程上播放音频是安全的。(也许你可以利用那些类,假设它们是API的一部分)。

1
非常感谢您提供如此有用的答案!我成功地使用了HandlerThread来播放和修改声音,就像我在上面的另一条评论中所描述的那样。目前看来,在单独的线程上播放声音似乎是唯一可行的解决方案,而且实现起来并不困难。 - Jaggie

1
如果有其他人也遇到了这个问题,你需要在AndroidLauncher中进行重写。
@Override
public AndroidAudio createAudio(Context context, AndroidApplicationConfiguration config) {
    return new AsynchronousAndroidAudio(context, config);
}

在发布游戏之前,一定要确保您没有任何SoundId操作(例如some_sound.Stop(sound_id)),因为这些操作在使用AsynchronousAndroidAudio时不起作用,会导致游戏崩溃。请检查并确保无误。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接