如何使用 SourceDataLine 在 java 中无延迟地传输声音
我想根据 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
修复方法是在
open(AudioFormat,int)
方法中指定缓冲区大小。对于实时音频来说,10ms-100ms 的延迟是可以接受的。非常低的延迟并不适用于所有计算机系统,100 毫秒或更长可能会让您的用户感到烦恼。一个好的权衡是,例如 50ms。对于您的音频格式(8 位、44100Hz 的单声道),合适的缓冲区大小是 2200 字节,几乎是 50 毫秒。另请注意,不同的操作系统在 Java 中具有不同的音频功能。在 Windows 和 Linux 上,您可以使用相当小的缓冲区大小,但 OS X 使用旧的实现,延迟明显更大。
此外,按规则将数据逐字节写入 SourceDataLine 效率非常低(缓冲区大小在
open()
方法中设置,而不是在write()
中设置)根据经验,我总是将一个完整的缓冲区大小写入 SourceDataLine。设置 SourceDataLine 后,使用以下代码:
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 inwrite()
), as a rule of thumb I'd always write one full buffer size to the SourceDataLine.After setting up the SourceDataLine, use this code: