我想根据用户在Java中的操作生成声音。即使我将SourceDataLine中的缓冲区大小设置为最小可能值(1帧),我仍然有大约1秒的延迟。
因为代码片段胜过千言万语(或者是图片?),这里是代码:
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实例中?
答案 0 :(得分:16)
修复是在open(AudioFormat,int)
方法中指定缓冲区大小。对于实时音频,延迟10ms-100ms是可以接受的。非常低的延迟会在所有计算机系统上无法正常工作,100毫秒或更长时间可能会让您的用户烦恼。一个很好的权衡是,例如, 50毫秒。对于你的音频格式,8位,单声道44100Hz,一个好的缓冲区大小是2200字节,差不多50ms。
另请注意,不同的操作系统在Java中具有不同的音频功能。在Windows和Linux上,您可以使用非常小的缓冲区大小,但OS X使用具有明显更大延迟的旧实现。
另外,将数据逐字节写入SourceDataLine是非常低效的(缓冲区大小是在open()
方法中设置的,而不是在write()
中),根据经验,我总是写SourceDataLine的一个完整缓冲区大小。
设置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);
}