Java 笔记本电脑退出休眠后 MIDI 音频延迟
Java MIDI audio is delayed after laptop comes out of hibernation
我正在开发 music programming language,并使用 JVM(通过 Clojure)播放用这种语言编写的乐谱。到目前为止,我们只是使用 javax.sound.midi MidiSynthesizer 来播放乐谱。
因为 Clojure 的启动时间很慢,我们希望能够从命令行播放乐谱并立即听到,我们选择将乐谱解释器构建为后台服务器进程,并与它使用用 Java.
编写的更轻量级的命令行客户端
所有这一切在大多数情况下都运行良好,但是,有一个奇怪的问题,我们看到如果您启动服务器,然后关闭笔记本电脑*并让它休眠,然后再次打开它,然后让服务器播放乐谱,音频不会立即出现,而是会延迟几秒钟。 运行 具有调试日志记录的服务器,我实际上可以看到 MIDI 音符 on/off 事件立即发生(并且计时正确),但音频延迟。
*这可能是也可能不是特定于平台的。我在我的 2014 Macbook Pro 运行 OS X 10.9.5 Mavericks 上看到了这个问题。
为了帮助缩小范围,我将这个简单的示例(使用 Java,而不是 Clojure)放在一起来演示问题:
https://github.com/daveyarwood/java-midi-delayed-audio-example
我已经为此绞尽脑汁了一段时间。为什么音频延迟,我们能做些什么?
这看起来像是 Sun 的 Synthesizer 实现中的错误。
我没有对此进行深入调查,但我发现问题显然出在包装 AudioInputStream
的抖动校正器中。抖动校正器线程依赖于 System.nanoTime()
。但是,当计算机从待机或休眠模式唤醒时,nanoTime
可能会跳转。
解决方法是禁用抖动校正器。您可以通过以下方式打开合成器:
synth = MidiSystem.getSynthesizer();
if (synth instanceof com.sun.media.sound.SoftSynthesizer) {
Map<String, Object> params = Collections.singletonMap("jitter correction", false);
((com.sun.media.sound.SoftSynthesizer) synth).open(null, params);
} else {
synth.open();
}
除了@apangin的解决方案,我还找到了另外两个解决方法:
每次播放前,关闭和re-open相同的合成器实例。
每次播放使用新的合成器实例。
这些都不是理想的,因为打开合成器实例需要几秒钟的时间(即使它是以前打开的现有实例),但这些解决方法对于某些用例可能就足够了。
我正在开发 music programming language,并使用 JVM(通过 Clojure)播放用这种语言编写的乐谱。到目前为止,我们只是使用 javax.sound.midi MidiSynthesizer 来播放乐谱。
因为 Clojure 的启动时间很慢,我们希望能够从命令行播放乐谱并立即听到,我们选择将乐谱解释器构建为后台服务器进程,并与它使用用 Java.
编写的更轻量级的命令行客户端所有这一切在大多数情况下都运行良好,但是,有一个奇怪的问题,我们看到如果您启动服务器,然后关闭笔记本电脑*并让它休眠,然后再次打开它,然后让服务器播放乐谱,音频不会立即出现,而是会延迟几秒钟。 运行 具有调试日志记录的服务器,我实际上可以看到 MIDI 音符 on/off 事件立即发生(并且计时正确),但音频延迟。
*这可能是也可能不是特定于平台的。我在我的 2014 Macbook Pro 运行 OS X 10.9.5 Mavericks 上看到了这个问题。
为了帮助缩小范围,我将这个简单的示例(使用 Java,而不是 Clojure)放在一起来演示问题:
https://github.com/daveyarwood/java-midi-delayed-audio-example
我已经为此绞尽脑汁了一段时间。为什么音频延迟,我们能做些什么?
这看起来像是 Sun 的 Synthesizer 实现中的错误。
我没有对此进行深入调查,但我发现问题显然出在包装 AudioInputStream
的抖动校正器中。抖动校正器线程依赖于 System.nanoTime()
。但是,当计算机从待机或休眠模式唤醒时,nanoTime
可能会跳转。
解决方法是禁用抖动校正器。您可以通过以下方式打开合成器:
synth = MidiSystem.getSynthesizer();
if (synth instanceof com.sun.media.sound.SoftSynthesizer) {
Map<String, Object> params = Collections.singletonMap("jitter correction", false);
((com.sun.media.sound.SoftSynthesizer) synth).open(null, params);
} else {
synth.open();
}
除了@apangin的解决方案,我还找到了另外两个解决方法:
每次播放前,关闭和re-open相同的合成器实例。
每次播放使用新的合成器实例。
这些都不是理想的,因为打开合成器实例需要几秒钟的时间(即使它是以前打开的现有实例),但这些解决方法对于某些用例可能就足够了。