2017-07-19 44 views
2

我已经创建了一个音频剪辑(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(); 
}); 

但是,这只是第一次后停止播放所有在一起。

,我已经尝试过其他的东西:的.stop()play().drain()

  • 各种组合,.flush()和。
  • 使用的java.applet.AudioClip代替Clip
  • 重新创建每个播放的剪辑对象(这每次只需导致延迟播放)

我测试过了几个电脑和这个问题似乎不那么在高端个人电脑上突出或至少不太引人注目。如果是这种情况,是否有任何事情可以改善低端系统的播放一致性?如果这里的实现是问题,那么实现这个的正确方法是什么?

回答

1

这可能是Clip只是不能提供您需要的粒度级别。我认为Clip在这方面没有什么特别的要求。如果缓冲区大小太大,SourceDataLine将会被阻塞,所以Clip可能会有类似的情况发生,但Clip不允许指定内部缓冲区大小。

如果是我,我会用SourceDataLine这样我就可以指定一个特定的缓冲区大小,可能是一个,在大致相同的帧速率的游戏或它的某些部分写入写我自己的Clip状物体。

int bytesPerSecond = (int) fmt.getSampleRate() 
         * fmt.getFrameSize(); 
int targetGameFPS = 30; 
int bufferSize  = bytesPerSecond/targetGameFPS; 

注意,使用的缓冲区是小可能会导致像点击或撕裂在速度较慢的电脑文物。

然后你一定要用这个缓冲区大小调用sourceDataLine.open(audioFmt, bufferSize)

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

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

+0

很好的答案。我在我的答案中添加了一些注释(有一个迂回的方式来指定一个剪辑的缓冲区大小),并想邀请你也检查出AudioCue。 –

1

你写的代码看起来好像没什么问题。我不确定你需要flush方法,但是当我评论它时,它没有帮助性能。

如果我理解Radiodef的理论,为Clip执行内部缓冲区的时间可能大于按键之间的时间间隔,如果它们非常接近。 ClipsSourceDataLines都因为除缓冲区边界外不允许更改而臭名昭着。 (例如,如果试图执行音量淡出,可能真的很烦人。)

There is一种指定Clip的缓冲区大小的方法。 API是here

open(AudioFormat format, byte[] data, int offset, int bufferSize) 

它可以在你的情况非常好工作,指定一个低缓冲区大小,虽然辍学的危险性会增加,尤其是如果有其他的声音播放。说实话,我还没有试验这种加载和播放Clips的方式。请注意,为了使用此方法,您必须将PCM数据存储在字节数组中。我在StackOverflow上看到了关于如何做到这一点的问题。

另一种解决方案是使用keystroke capture方法来加载一个数组(可能包含时间戳)并让另一个进程从该数组中执行音频播放。然后,您可以修改剧本之间所需的最短时间,以确保每个笔画都有关联的播放。

我想提供的替代方案是使用AudioCue。我知道你是在做这个学习项目,并且可能不情愿使用预先写好的课程。但是,在这种情况下,可以使用源代码,因此您可以自由地检查和编辑/修改代码。涉及三个文件,主类,以及用于实现侦听器的接口和辅助类。

AudioCue恰恰具有Radiodef推荐的功能。该类将媒体数据加载到数组中并通过SourceDataLine进行播放。当你openAudioCue,有一种方法,可以让你有选择地指定缓冲区大小。 AudioCue可以像Clip一样播放,具有停止,重置和启动单个实例的功能,但它也支持并发播放并具有一些附加功能(例如,实时音量,平移,以每帧为基础响应的播放速度推子,不只是在缓冲区边界)。

如果不出意外,你可以看看代码,看看和例子作为Radiodef曾建议如何通过SourceDataLine实现Clip样。