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

发布于 2024-11-28 06:42:01 字数 1757 浏览 2 评论 0原文

我想根据 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 实例中?

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.

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

樱花落人离去 2024-12-05 06:42:01

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

另请注意,不同的操作系统在 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);
}

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.

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.

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.

After setting up the SourceDataLine, use this code:

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文