音频片段偶尔不播放

时间:2017-07-19 01:18:39

标签: java audio

我使用以下代码创建了一个音频片段(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();
});

但是在第一次之后,这就完全停止了回放。

我尝试过的其他事情:

我已经测试了几台个人电脑,这个问题在高端电脑上似乎不那么突出,或者至少不那么明显。如果是这种情况,是否可以采取任何措施来改善低端系统的播放一致性?如果实现是这里的问题,那么实现它的正确方法是什么?

2 个答案:

答案 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)

缺点是您需要使用后台线程和同步自己编写startstop控件。 (这非常难,但它确实意味着它不是一个简单的解决方案。)

我无法肯定地说这会解决问题,但这可能是我接下来会尝试的。 (编写自己的音频播放器也具有长期灵活性更高的优势。Clip没有一个非常令人印象深刻的功能集开始。)

答案 1 :(得分:1)

你写的代码对我来说很好。我不确定您是否需要flush方法,但当我对其进行评论时,它对性能没有帮助。

如果我理解Radiodef的理论,那么为Clip执行内部缓冲区的时间可能大于击键之间的时间,如果它们非常靠近的话。 ClipsSourceDataLines都因为不允许在缓冲区边界之外进行更改而臭名昭着。 (例如,如果尝试执行音量淡化,可能会非常烦人。)

指定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