2017-07-19 54 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樣。