我使用以下代码创建了一个音频片段(javax.sound.sampled.Clip
):
public Clip getClip() throws Exception {
AudioInputStream in = AudioSystem.getAudioInputStream(getClass().getResource("test.wav"));
Clip clip = AudioSystem.getClip();
clip.open(in);
return clip;
}
...
this.clip = getClip();
我反复触发the clip播放:
public void play() {
clip.stop();
clip.flush();
clip.setFramePosition(0);
clip.start();
}
我正在测试此问题,方法是使用JFrame
并在每次按键时调用play()
(full test class here)。大多数情况下,无论按键的速度有多快,每次按键都会播放声音。但有时候,当按键快速连续按下时,声音会跳过其中一个按键,而根本不会发挥作用。这是在游戏中实现的,因此一致的声音播放非常重要。
研究这个问题把我带到了this question,建议每次播放结束时关闭该行,如下:
clip.addLineListener(e -> {
if (e.getType() == LineEvent.Type.STOP) e.getLine().close();
});
但是在第一次之后,这就完全停止了回放。
我尝试过的其他事情:
play()
中.stop()
,.flush()
和.drain()
的各种组合。java.applet.AudioClip
代替Clip
我已经测试了几台个人电脑,这个问题在高端电脑上似乎不那么突出,或者至少不那么明显。如果是这种情况,是否可以采取任何措施来改善低端系统的播放一致性?如果实现是这里的问题,那么实现它的正确方法是什么?
答案 0 :(得分:1)
Clip
可能无法提供您所需的粒度级别。我不认为Clip
在这方面有任何特殊要求。如果缓冲区大小太大,SourceDataLine
将会阻止,因此Clip
可能会发生类似的情况,但Clip
不允许您指定内部缓冲区大小
如果是我,我会使用Clip
编写自己的SourceDataLine
对象,这样我就可以指定一个特定的缓冲区大小,可能是以大致相同的帧速率写入的作为游戏或其中的一部分。
int bytesPerSecond = (int) fmt.getSampleRate()
* fmt.getFrameSize();
int targetGameFPS = 30;
int bufferSize = bytesPerSecond / targetGameFPS;
请注意,使用太小的缓冲区可能会导致在较慢的计算机上点击或撕裂等工件。
然后你确保也用这个缓冲区大小调用sourceDataLine.open(audioFmt, bufferSize)
。
缺点是您需要使用后台线程和同步自己编写start
和stop
控件。 (这非常难,但它确实意味着它不是一个简单的解决方案。)
我无法肯定地说这会解决问题,但这可能是我接下来会尝试的。 (编写自己的音频播放器也具有长期灵活性更高的优势。Clip
没有一个非常令人印象深刻的功能集开始。)
答案 1 :(得分:1)
你写的代码对我来说很好。我不确定您是否需要flush
方法,但当我对其进行评论时,它对性能没有帮助。
如果我理解Radiodef的理论,那么为Clip
执行内部缓冲区的时间可能大于击键之间的时间,如果它们非常靠近的话。 Clips
和SourceDataLines
都因为不允许在缓冲区边界之外进行更改而臭名昭着。 (例如,如果尝试执行音量淡化,可能会非常烦人。)
是指定Clip
的缓冲区大小的方法。 API为here
open(AudioFormat format, byte[] data, int offset, int bufferSize)
在您的情况下,指定低缓冲区大小可能非常有用,但是丢失的风险会增加,尤其是在有其他声音播放的情况下。说实话,我还没有尝试过这种加载和播放Clips
的方式。请注意,您必须将PCM数据放在字节数组中才能使用此方法。我在StackOverflow上看到过如何做到这一点的问题。
另一种解决方案是使用击键捕获方法加载数组(可能包括时间戳),并使用另一个进程从该数组执行音频播放。然后你可以修改播放之间所需的最短时间,以确保每个笔画都有相关的游戏。
我想提供的替代方法是使用AudioCue。我知道你这是一个学习项目,并且可能不愿意在此基础上使用预先编写的课程。但在这种情况下,源可用,因此您可以自由地检查和编辑/修改代码。涉及三个文件,主类,以及用于实现监听器的接口和辅助类。
AudioCue
具有Radiodef推荐的功能。该类将媒体数据加载到数组中并通过SourceDataLine
播放。当您open
AudioCue
时,有一种方法可以选择指定缓冲区大小。 AudioCue
可以像Clip
一样播放,可以停止,重置和启动单个实例,但它还支持并发播放并具有一些附加功能(例如,实时音量,平移,播放速度推子等以帧为单位进行响应,而不仅仅是在缓冲区边界。)
如果没有其他内容,您可以查看代码并查看如何实现Clip
的示例 - 例如,如Radiodef所建议的SourceDataLine
。