如何使用SourceDataLine在java中毫不延迟地传输声音

如何使用SourceDataLine在java中毫不延迟地传输声音,java,audio,delay,Java,Audio,Delay,我想在Java中根据用户的动作生成声音。即使我将SourceDataLine中的缓冲区大小设置为可能的最小值(1帧),我仍然有大约1秒的延迟 因为一个代码片段价值千言万语(或者它是一幅图片?),下面是代码: import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.So

我想在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++;
        }
    }
}

尝试在收听声音时移动滑块。是否可能,或者我必须创建内存缓冲区并将其包装到剪辑实例中?

修复方法是在
打开(AudioFormat,int)
方法中指定缓冲区大小。实时音频可接受10毫秒至100毫秒的延迟。非常低的延迟(如延迟)不会在所有计算机系统上工作,100毫秒或更高的延迟可能会让您的用户感到恼火。一个好的折衷方案是,例如50ms。对于44100Hz的8位单声道音频格式,一个好的缓冲区大小是2200字节,几乎是50ms

还请注意,不同的操作系统在Java中具有不同的音频功能。在Windows和Linux上,您可以使用非常小的缓冲区大小,但OSX使用的是一种延迟大得多的旧实现

此外,将数据逐字节写入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);
}
final int bufferSize=2200;//以字节为单位
soundLine.open(音频格式、缓冲区大小);
soundLine.start();
字节计数器=0;
最终字节[]缓冲区=新字节[bufferSize];
字节符号=1;
while(frame.isVisible()){
int threshold=audioFormat.getFrameRate()/sliderValue;
for(int i=0;i阈值){
符号=(字节)-符号;
计数器=0;
}
缓冲区[i]=(字节)(符号*30);
计数器++;
}
//下一个调用将被阻塞,直到整个缓冲区被释放
//发送到源数据线
soundLine.write(缓冲区,0,缓冲区大小);
}

谢谢。我被new DataLine.Info(SourceDataLine.class,audioFormat,1)中的bufferSize参数弄瞎了。当然我不会用这么小的缓冲区。这只是为了说明我的问题。@Florian谢谢你举这个例子。如果
intn=soundLine.write(buffer,0,bufferSize),这意味着什么
n在2次首次写入后返回0值?@user390525,根据SourceDataLine.write()的规范,它只能在出现错误(参数格式错误)或SourceDataLine停止、刷新或关闭时返回小于指定缓冲区大小的值。如果您100%确定这些条件都不适用,那么该SourceDataLine的Java实现中可能存在错误。