java 如何使用SourceDataLine在java中无延迟地流式传输声音

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/6986572/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 18:10:13  来源:igfitidea点击:

how to stream sound in java without delay using SourceDataLine

javaaudiodelay

提问by andi

I want to generate sounds based on user's action in Java. Even if I set the buffer size in SourceDataLine to the smallest possible value (1 frame) I still have delay of about 1 second.

我想根据用户在 Java 中的操作生成声音。即使我将 SourceDataLine 中的缓冲区大小设置为最小可能值(1 帧),我仍然有大约 1 秒的延迟。

Because a code snippet is worth a thousand words (or was it a picture?), here is the code:

因为一个代码片段值一千字(或者是一张图片?),这里是代码:

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++;
        }
    }
}

Try moving the slider while listening to the sound. Is it possible, or do I have to create in-memory buffers and wrap them in Clip instances?

尝试在聆听声音的同时移动滑块。是否有可能,或者我是否必须创建内存缓冲区并将它们包装在 Clip 实例中?

回答by Florian

The fix is to specify the buffer size in the open(AudioFormat,int)method. A delay of 10ms-100ms will be acceptable for realtime audio. Very low latencies like will not work on all computer systems, and 100ms or more will probably be annoying for your users. A good tradeoff is, e.g. 50ms. For your audio format, 8-bit, mono at 44100Hz, a good buffer size is 2200 bytes, which is almost 50ms.

解决方法是在open(AudioFormat,int)方法中指定缓冲区大小。对于实时音频,10ms-100ms 的延迟是可以接受的。非常低的延迟,例如不适用于所有计算机系统,并且 100 毫秒或更长的时间可能会让您的用户感到烦恼。一个好的折衷是,例如 50ms。对于您的音频格式,8 位,44100Hz 的单声道,合适的缓冲区大小是 2200 字节,几乎是 50 毫秒。

Also note that different OS's have different audio capabilities in Java. On Windows and Linux you can work with quite small buffer sizes, but OS X uses an old implementation with significantly larger delay.

另请注意,不同的操作系统在 Java 中具有不同的音频功能。在 Windows 和 Linux 上,您可以使用非常小的缓冲区大小,但 OS X 使用具有明显更大延迟的旧实现。

Also, writing data byte by byte to the SourceDataLine is very inefficient (the buffer size is set in the open()method, not in write()), as a rule of thumb I'd always write one full buffer size to the SourceDataLine.

此外,将数据逐字节写入 SourceDataLine 的效率非常低(缓冲区大小在open()方法中设置,而不是在 中write()),根据经验,我总是将一个完整的缓冲区大小写入 SourceDataLine。

After setting up the SourceDataLine, use this code:

设置 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);
}