Как потоковое воспроизведение звука в Java без задержки, используя SourceDataLine - PullRequest
7 голосов
/ 08 августа 2011

Я хочу генерировать звуки на основе действий пользователя в 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?

1 Ответ

16 голосов
/ 09 августа 2011

Исправление - указать размер буфера в методе open(AudioFormat,int).Задержка 10 мс-100 мс будет приемлемой для звука в реальном времени.Очень низкие задержки, например, не будут работать на всех компьютерных системах, и 100 мс или более, вероятно, будут раздражать ваших пользователей.Хороший компромисс, например, 50 мс.Для вашего аудиоформата, 8-битного, моно при 44100 Гц, хороший размер буфера составляет 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);
}
...