为什么 JavaFX MediaPlayer 音频播放在 Windows 和 Mac 上完美运行,但在 Linux(popOS/Ubuntu)上出现问题?

Why is JavaFX MediaPlayer audio playback working perfectly on windows and mac, but having issues on Linux (popOS/Ubuntu)?

提问人:Daniel Hardeman 提问时间:10/28/2023 最后编辑:Daniel Hardeman 更新时间:11/1/2023 访问量:70

问:

我有一个 JavaFX 俄罗斯方块游戏,它使用 MediaPlayer 类来播放音乐和声音效果。具体来说,在处理所有音频的 SoundPlayer 类中,我在游戏启动时加载游戏的 .wav 文件,并迭代每个文件,为每个文件创建一个 Media 对象,然后为每个 Media 对象创建一个 MediaPlayer 对象。MediaPlayer(每个声音一个)存储在数组中,以便将来使用类的 playSound 方法进行播放,该方法接受一个整数参数,该参数指示与所需声音效果对应的 MediaPlayer 的索引。它在 Windows 和 Mac 上完美运行,但在我运行 popOS 的笔记本电脑上,虽然它在技术上可以工作,但它存在以下问题:

  • 当游戏启动时,一些声音会正常播放,但玩几次后,播放音量会变得非常安静,即使 MediaPlayer 的实际音量属性没有改变,您也几乎听不到它

  • 有些声音不会播放整个声音,而只能播放其中的一部分。

背景音乐和大多数声音都能正常工作,但对于某些人来说,会出现这些问题。 这是我在游戏触发时播放声音的代码:

public void playSound(int index)
{
     MediaPlayer mediaPlayer = mediaPlayerList[index];
     mediaPlayer.stop();
     mediaPlayer.seek(Duration.ZERO);
     mediaPlayer.play();
}

这在我的其他计算机上完美运行,但我只在我的 popOS 笔记本电脑上遇到这个问题。 有谁知道这是 Java.sound 工作方式的 Linux 问题,还是我的笔记本电脑硬件问题?我尝试配置java.sound.config文件,但无济于事,不知所措。

如上所述,我尝试查找 java.sound 配置文件的修复程序,但我似乎没有任何发现,所以我开始认为这可能是我的笔记本电脑的问题,但我想我会问看看是否有人知道我可能错过的任何事情。我目前是一名 CS 学生,这是我第一次尝试使用 Java 进行音频播放的项目,所以我对它在低层次上的工作方式相当无知。如果有人知道任何修复程序或潜在原因,将不胜感激。此外,我的音频驱动程序是最新的,我在这台笔记本电脑上尝试过的所有其他音频播放都工作正常。

最初,我使用 Java Sound API 的 Clip 类来播放声音效果,音频播放在 Clips 上可以正常工作,但我使用 Clips 的问题是,当声音被用户输入快速触发时,就像我的游戏中经常出现的情况一样,我发现音频经常跳过,这可能是由于我对多线程的了解有限, 所以我决定尝试 JavaFX 的 MediaPlayer,它的响应速度和优化性要高得多,而且正如我所说,在我的 Windows 机器和 Mac 上完美运行......它只在我的 Linux 笔记本电脑上遇到这个问题。如果有任何帮助,我正在使用 openjdk-17....我不知道这是否是 open-jdk 与 oracle jdk 的具体问题,或者类似性质的问题。

java linux ubuntu javafx javasound

评论

0赞 Daniel Hardeman 10/28/2023
感谢大家到目前为止的回复。我将研究尝试 AudioClip 并看看它是如何工作的。问题是,我没有收到任何错误,从技术上讲,声音是使用 MediaPlayer 工作的,只是无论出于何种原因,几个声音的音频播放要么没有播放完整的声音(音频中断),要么音量下降,即使 MediaPlayer 的实际音量属性没有降低。至于每次需要播放声音时创建一个新的 MediaPlayer,我尝试过,但由于我发现当用户快速输入时会导致延迟。
0赞 Daniel Hardeman 10/28/2023
我还将尝试不同的 JDK,看看是否有帮助,并将在我的 Windows 上的虚拟框中尝试不同的 Linux 发行版,看看这是否仅仅是我的特定笔记本电脑在硬件端的问题,不能很好地与 Linux 配合使用,我怀疑这有几个原因。谢谢。
1赞 Daniel Hardeman 10/29/2023
谢谢你的建议。我最终使用了 AudioClips 而不是 MediaPlayer,因为它们似乎更适合创建和对象一次,然后使用相同的实例进行重复播放,而不是创建和处置 MediaPlayer。现在一切都很好。

答:

4赞 Phil Freihofner 10/29/2023 #1

开源 AudioCue 库(可通过 Maven 获得)基本上是为支持多线程而编写的。它应该适合您,并且可以免费使用。Clip

评论中有很多好点(应该是用来对原始帖子提出问题和澄清,afaik),在我看来,这些观点很可能被列为“答案”。我在这里的回答将重复其中的一些观点。

对于简短的提示,首选只加载一次并且可以直接从内存中播放多次的类:、、 ,以及需要从文件或其他形式的流式处理中读取的类:、。ClipAudioClipSourceDataLineMediaPlayer

好消息是它确实支持并发播放,这与流式播放方法不同。因此,您可以采用相同的方法并多次播放它,而无需先调用方法并重新定位其播放位置。每次迭代都应该一直进行到最后。AudioClipClipAudioClipstop

曾经有一段时间,一些 Linux 操作系统只允许在给定时刻播放单个声源,但那是很久以前的事了。IDK(如果这可能是您正在测试的特定操作系统配置的问题)。如果是这样,回到我在开头提到的库,它有一个类,用于将所有声音提示汇集到单个输出行中,这将是这种情况的解决方法。AudioCueAudioMixer

评论

2赞 Daniel Hardeman 10/29/2023
谢谢你的回答。我最终采纳了这里的建议和上面提到的其他人的建议,并最终使用了 AudioClips 而不是 MediaPlayers,它现在在 Linux 上运行良好。我很欣赏关于为什么会这样的完整解释。
1赞 jewelsea 10/31/2023
同意一些评论在答案中会更好,所以我把它们移到了那里
2赞 jewelsea 10/31/2023 #2

我把我和 Slaw 的笔记从评论移到了答案上。它们可能无法解决实际问题,相反,它们是解决 JavaFX 媒体播放某些问题的指南。

使用 AudioClip 进行短音频播放

javafx.scene.media.AudioClip 通常更适合于短音效。

请参阅 Phil 的回答中的进一步解释以及 javadoc 中的描述:AudioClip

AudioClip 表示可以以最小延迟播放的音频片段。剪辑的加载方式与 Media 对象类似,但具有不同的行为,例如,Media 无法自行播放。AudioClip 也可以立即使用。播放行为是即发即弃:一旦调用了其中一种播放方法,唯一可操作的控件就是 stop()。AudioClip 也可以同时播放多次。

要使用 Media 完成相同的任务,必须为并行播放的每个声音创建一个新的 MediaPlayer 对象。但是,媒体对象更适合长时间播放的声音。这主要是因为 AudioClip 在内存中存储了整个声音的原始、未压缩的音频数据,对于长音频剪辑来说,这些数据可能相当大。MediaPlayer 在内存中预卷的解压缩音频数据仅够播放一小段时间,因此对于长剪辑的内存效率要高得多,尤其是在压缩它们时。

如评论中所述:

我最终使用了 AudioClips 而不是 MediaPlayer,因为它们似乎更适合创建和对象一次,然后使用相同的实例进行重复播放,而不是创建和处置 MediaPlayer。现在一切都很好。

实现 MediaPlayers 的错误处理

我没有收到任何错误

媒体包 javadoc 中有错误处理代码。尝试一下,看看它是否报告了任何内容。

如果默认情况下处理,则不会报告大多数媒体错误,它可能只是静默失败。如果要彻底报告和处理它们,则需要实现媒体播放器包文档中完整而广泛的媒体异常逻辑。

当然,即使如此,如果问题确实出在代码中,或者系统没有准确检测问题并提供问题反馈,则可能仍然没有报告实际错误。

检查代码逻辑的正确性

无法播放完整声音(音频中断)

您的代码会停止播放器。然后从头开始播放。根据呼叫的时间,预计在完成播放之前切断现有声音。

如果不使用 java.sound API 和文档,请忽略它们

java.sound与 JavaFX 媒体无关,配置它不会帮助您使用 JavaFX 或 API 播放声音。MediaPlayerAudioClip

考虑异步操作

与 JavaFX 中的许多框架大多是单线程不同,当您查看 MediaPlayer 文档时,它会指出:

MediaPlayer 的操作本质上是异步的。

通常,您不需要太担心这一点,但它可能会导致您的平台上出现问题。如果玩家处于 UNKNOWN 状态,则此类方法将不起作用。seek

考虑创建新的 MediaPlayer,而不是重用它们

与其重复使用 MediaPlayer,不如在每次使用时创建一个新播放器,如果它们在重复使用后在您的平台上进入损坏状态,如您所述。但我想如果可能的话,请避免这种情况,因为每次创建新玩家时,可能需要时间和资源来为玩家做好准备。

正如提问者所指出的:

至于每次需要播放声音时创建一个新的 MediaPlayer,我尝试过,但由于我发现当用户快速输入时会导致延迟。

确保安装了正确的本机平台依赖项

JavaFX 媒体使用 GStreamer Lite 进行播放(媒体模块附带本机 gstreamer 库,您不需要安装它)。它可能对操作系统中的其他组件有一些依赖关系。例如,JavaFX 21 需要 GTK 3。但我不知道这些依赖关系可能是什么,如果不满足未知的依赖关系,你会怎么做:-)

也许可以尝试从平台的本机包类型(如 RPM 或 DEB)安装包含 Linux 平台的 JavaFX(如 Liberica Full JDK)的打包 JDK。如果正确打包,包管理器将确保任何本机依赖项都安装在平台上的正确版本上。我不知道这是否会对你产生任何影响,但可能是需要考虑的事情。