2011-08-08 58 views
7

我想根據用戶在Java中的操作來生成聲音。即使我將SourceDataLine中的緩衝區大小設置爲儘可能最小的值(1幀),我仍然有約1秒的延遲。如何在沒有延遲的情況下使用SourceDataLine流式傳輸聲音

因爲代碼段是勝過千言萬語(?或者是它的照片),這裏是代碼:

import javax.sound.sampled.AudioFormat; 
import javax.sound.sampled.AudioSystem; 
import javax.sound.sampled.DataLine; 
import javax.sound.sampled.SourceDataLine; 
import javax.swing.JFrame; 
import javax.swing.JSlider; 
import javax.swing.event.ChangeEvent; 
import javax.swing.event.ChangeListener; 

public class SoundTest { 

    private static int sliderValue = 500; 

    public static void main(String[] args) throws Exception { 
     final JFrame frame = new JFrame(); 
     final JSlider slider = new JSlider(500, 1000); 
     frame.add(slider); 
     slider.addChangeListener(new ChangeListener() { 
      @Override 
      public void stateChanged(ChangeEvent e) { 
       sliderValue = slider.getValue(); 
      } 
     }); 
     frame.pack(); 
     frame.setVisible(true); 

     final AudioFormat audioFormat = new AudioFormat(44100, 8, 1, true, true); 
     final DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat, 1); 
     final SourceDataLine soundLine = (SourceDataLine) AudioSystem.getLine(info); 
     soundLine.open(audioFormat); 
     soundLine.start(); 
     byte counter = 0; 
     final byte[] buffer = new byte[1]; 
     byte sign = 1; 
     while (frame.isVisible()) { 
      if (counter > audioFormat.getFrameRate()/sliderValue) { 
       sign = (byte) -sign; 
       counter = 0; 
      } 
      buffer[0] = (byte) (sign * 30); 
      soundLine.write(buffer, 0, 1); 
      counter++; 
     } 
    } 
} 

嘗試移動滑塊,一邊聽聲音。是否有可能,還是我必須創建內存緩衝區並將它們包裝在Clip實例中?

回答

14

修復方法是在open(AudioFormat,int)方法中指定緩衝區大小。實時音頻延遲10ms-100ms是可以接受的。非常低的延遲時間不適用於所有計算機系統,100毫秒或更長時間可能會讓用戶感到煩惱。一個好的折衷是,例如50毫秒。對於您的音頻格式,8位,44100Hz的單聲道,良好的緩衝區大小爲2200字節,幾乎是50ms。

另請注意,不同的操作系統在Java中具有不同的音頻功能。在Windows和Linux上,您可以使用相當小的緩衝區大小,但OS X使用延遲明顯較大的舊實施。作爲一條經驗法則,我總是將一個完整的緩衝區大小寫入SourceDataLine中,並且將數據逐字節寫入SourceDataLine的效率非常低(緩衝區大小在open()方法中設置,而不是在write()中設置) 。

設立的SourceDataLine後,使用此代碼:

final int bufferSize = 2200; // in Bytes 
soundLine.open(audioFormat, bufferSize); 
soundLine.start(); 
byte counter = 0; 
final byte[] buffer = new byte[bufferSize]; 
byte sign = 1; 
while (frame.isVisible()) { 
    int threshold = audioFormat.getFrameRate()/sliderValue; 
    for (int i = 0; i < bufferSize; i++) { 
     if (counter > threshold) { 
      sign = (byte) -sign; 
      counter = 0; 
     } 
     buffer[i] = (byte) (sign * 30); 
     counter++; 
    } 
    // the next call is blocking until the entire buffer is 
    // sent to the SourceDataLine 
    soundLine.write(buffer, 0, bufferSize); 
} 
+0

謝謝。我被_new DataLine.Info(SourceDataLine.class,audioFormat,1)_中的_bufferSize_參數蒙上了一層陰影。當然,我不打算使用這樣一個小緩衝區。這只是爲了表明我的問題。 – andi

+0

@弗洛裏安謝謝你的這個例子。如果'int n = soundLine.write(buffer,0,bufferSize);'n在第一次寫入後返回0值,這意味着什麼? – user390525

+0

@ user390525,根據SourceDataLine.write()的規範,如果發生錯誤(格式錯誤的參數),或者SourceDataLine停止,刷新或關閉,它只能返回小於指定的緩衝區大小。 如果您100%確定這些條件都不適用,則該SourceDataLine的Java實現中可能存在一個錯誤。 – Florian

相關問題