将 SourceDataLine 与 Java 中的显示同步
Synchronizing SourceDataLine with display in Java
我正在 Java 中创建声音可视化工具,但找不到同步播放声音和显示声波的正确方法。
注意:下面的片段只是草图,但我实际上都试过了
我尝试过的方法:
利用 SourceDataLine.write() 阻塞直到数据行缓冲区中有 space 的事实。这导致缓冲区总是几乎满了,并且在刷新整个缓冲区后实际上正在播放每个样本。这意味着半秒的延迟。
try (SourceDataLine sdl = AudioSystem.getSourceDataLine(someFormat)) {
sdl.open(someFormat);
sdl.start();
while(true) {
byte[] samples = getSamplesSomehow();
sdl.write(samples, 0, samples.length); // <-- blocks
displaySamples(samples);
}
}
将要绘制的数据排入队列并在刷新整个缓冲区后将其出列(帧位置增加缓冲区大小或更多)。这只是行不通,我不确定为什么。
Queue<Data> queue = new LinkedList<Data>();
try (SourceDataLine sdl = AudioSystem.getSourceDataLine(someFormat)) {
sdl.open(someFormat);
sdl.start();
while(true) {
int position = sdl.getFramePosition();
byte[] samples = getSamplesSomehow();
Data data = new Data(position, samples); // <-- java bean
// display
if (queue.size > 0 && queue.peek().getPosition() < position - sdl.getBufferSize()) {
byte[] samplesToDisplay = queue.remove().getSamples();
displaySamples(samplesToDisplay);
}
// push new data
queue.add(data);
sdl.write(samples, 0, samples.length);
}
}
等待缓冲区几乎为空。这非常有效,但引入了忙等待,CPU 使用量增加了 5 倍。
try (SourceDataLine sdl = AudioSystem.getSourceDataLine(someFormat)) {
sdl.open(someFormat);
sdl.start();
while(true) {
byte[] samples = getSamplesSomehow();
while(sdl.getBufferSize() - sdl.available() >= data.length); // <-- busy waiting
sdl.write(samples, 0, samples.length); // <-- doesn't get a chance to block
displaySamples(samples);
}
}
解决这个问题的常用方法是什么?
在方法 #1 中,在 SDL 上输出之前将数据发送到可视化工具。
可视化工具和音频播放可能都应该在各自的线程中。因此,对数据切换使用线程安全的松散耦合。也许给您的 Visualizer 一个 ConcurrentLinkedQueue,以 FIFO 方式使用,并让您的声音线程调用它。
半秒延迟表示SDL缓冲区相当大。您可能应该在 SDL 打开方法中指定较小的缓冲区大小。我经常使用 8K 给定或取 2 的因数。在 44100 fps 16 位立体声(每帧四个字节)下,延迟应该只有 0.05 秒左右。如果小缓冲区大小导致丢失,请尝试将其加倍。例如,class AudioCue 使用默认缓冲区大小仅为 1K 进行剪辑等效播放,但混合工具(混合多个剪辑等效)默认设置为更安全的 8K。
根据您获取样本的方式,如果输入的缓冲区大小与您的 SDL 缓冲区大小不同,则可能需要一些 assembly/disassembly。但这在声音线程的 while 循环中应该很容易处理。
我正在 Java 中创建声音可视化工具,但找不到同步播放声音和显示声波的正确方法。
注意:下面的片段只是草图,但我实际上都试过了
我尝试过的方法:
利用 SourceDataLine.write() 阻塞直到数据行缓冲区中有 space 的事实。这导致缓冲区总是几乎满了,并且在刷新整个缓冲区后实际上正在播放每个样本。这意味着半秒的延迟。
try (SourceDataLine sdl = AudioSystem.getSourceDataLine(someFormat)) { sdl.open(someFormat); sdl.start(); while(true) { byte[] samples = getSamplesSomehow(); sdl.write(samples, 0, samples.length); // <-- blocks displaySamples(samples); } }
将要绘制的数据排入队列并在刷新整个缓冲区后将其出列(帧位置增加缓冲区大小或更多)。这只是行不通,我不确定为什么。
Queue<Data> queue = new LinkedList<Data>(); try (SourceDataLine sdl = AudioSystem.getSourceDataLine(someFormat)) { sdl.open(someFormat); sdl.start(); while(true) { int position = sdl.getFramePosition(); byte[] samples = getSamplesSomehow(); Data data = new Data(position, samples); // <-- java bean // display if (queue.size > 0 && queue.peek().getPosition() < position - sdl.getBufferSize()) { byte[] samplesToDisplay = queue.remove().getSamples(); displaySamples(samplesToDisplay); } // push new data queue.add(data); sdl.write(samples, 0, samples.length); } }
等待缓冲区几乎为空。这非常有效,但引入了忙等待,CPU 使用量增加了 5 倍。
try (SourceDataLine sdl = AudioSystem.getSourceDataLine(someFormat)) { sdl.open(someFormat); sdl.start(); while(true) { byte[] samples = getSamplesSomehow(); while(sdl.getBufferSize() - sdl.available() >= data.length); // <-- busy waiting sdl.write(samples, 0, samples.length); // <-- doesn't get a chance to block displaySamples(samples); } }
解决这个问题的常用方法是什么?
在方法 #1 中,在 SDL 上输出之前将数据发送到可视化工具。
可视化工具和音频播放可能都应该在各自的线程中。因此,对数据切换使用线程安全的松散耦合。也许给您的 Visualizer 一个 ConcurrentLinkedQueue,以 FIFO 方式使用,并让您的声音线程调用它。
半秒延迟表示SDL缓冲区相当大。您可能应该在 SDL 打开方法中指定较小的缓冲区大小。我经常使用 8K 给定或取 2 的因数。在 44100 fps 16 位立体声(每帧四个字节)下,延迟应该只有 0.05 秒左右。如果小缓冲区大小导致丢失,请尝试将其加倍。例如,class AudioCue 使用默认缓冲区大小仅为 1K 进行剪辑等效播放,但混合工具(混合多个剪辑等效)默认设置为更安全的 8K。
根据您获取样本的方式,如果输入的缓冲区大小与您的 SDL 缓冲区大小不同,则可能需要一些 assembly/disassembly。但这在声音线程的 while 循环中应该很容易处理。