在JVM内长时间录制音频时突然延迟
我正在实现一个应用程序,该应用程序使用JDK版本8 Update 201实时(或至少尽可能接近实时)记录和分析音频。在执行模拟应用程序典型用例的测试时,我注意到在连续录制音频几个小时后,引入了一到两秒之间的突然延迟。在此之前,没有明显的延迟。直到这个记录的关键点之后几个小时,这种延迟才开始发生。
到目前为止,我尝试过什么
为了检查我的音频样本录制时序代码是否错误,我注释掉了与计时有关的所有内容。这给我留下了这个更新循环,它一旦音频样本准备就绪就会立即获取它们(注意:Kotlin代码):
while (!isInterrupted) {
val audioData = read(sampleSize, false)
listener.audioFrameCaptured(audioData)
}
这是我的阅读方法:
fun read(samples: Int, buffered: Boolean = true): AudioData {
//Allocate a byte array in which the read audio samples will be stored.
val bytesToRead = samples * format.frameSize
val data = ByteArray(bytesToRead)
//Calculate the maximum amount of bytes to read during each iteration.
val bufferSize = (line.bufferSize / BUFFER_SIZE_DIVIDEND / format.frameSize).roundToInt() * format.frameSize
val maxBytesPerCycle = if (buffered) bufferSize else bytesToRead
//Read the audio data in one or multiple iterations.
var bytesRead = 0
while (bytesRead < bytesToRead) {
bytesRead += (line as TargetDataLine).read(data, bytesRead, min(maxBytesPerCycle, bytesToRead - bytesRead))
}
return AudioData(data, format)
}
但是,即使我这边没有任何时机,问题也没有得到解决。因此,我继续进行了一些实验,并让应用程序使用不同的音频格式运行,这导致非常混乱的结果(除非另有说明,否则我将使用PCM签名的16位立体声音频格式,字节序很小,采样率为44100.0 Hz):
- 在出现延迟之前必须经过的关键时间量似乎因所使用的计算机而异。在我的Windows 10台式PC上,它介于6.5到7个小时之间。但是,在我的笔记本电脑(也使用Windows 10)上,对于相同的音频格式,它需要4到5个小时。
- 使用的音频通道数量似乎有影响。如果我将声道数量从立体声更改为单声道,则延迟开始出现之前的时间会加倍,在桌面上达到13到13.5小时之间的时间。
- 将样本数量从 16 位减少到 8 位也会导致延迟开始出现之前的时间增加一倍。在我的桌面上大约13到13.5个小时。
- 将字节顺序从小端序更改为大端序没有任何效果。
- 从立体声混音切换到物理麦克风也没有效果。
- 我尝试使用不同的缓冲区大小(1024,2048和3072样本帧)及其默认缓冲区大小打开行。这也没有改变任何东西。
- 在开始发生延迟后刷新 TargetDataLine 会导致所有字节在大约一到两秒钟内为零。在此之后,我再次获得非零值。然而,拖延仍然存在。如果我在临界点之前刷新行,我就不会得到那些零字节。
- 在出现延迟后停止并重新启动 TargetDataLine 也不会更改任何内容。
- 但是,关闭并重新打开TargetDataLine确实可以消除延迟,直到几个小时后它再次出现。
- 每十分钟自动刷新一次 TargetDataLines 内部缓冲区无助于解决此问题。因此,内部缓冲区中的缓冲区溢出似乎不是原因。
- 使用并行垃圾回收器来避免应用程序冻结也无济于事。
- 使用的采样率似乎很重要。如果我将采样率加倍至 88200 赫兹,则延迟开始发生在 3 到 3.5 小时的运行时间之间。
- 如果我让它使用我的“默认”音频格式在Linux下运行,它在大约9个小时的运行时后仍然运行良好。
我得出的结论是:
这些结果让我得出结论,在此问题开始发生之前,我可以录制音频的时间取决于运行应用程序的机器,并取决于音频格式的字节速率(即帧大小和采样率)。这似乎是正确的(尽管我现在还不能完全确认这一点),因为如果我结合在2和3中所做的更改,我会假设我可以录制音频样本的时间是延迟开始出现之前使用我的“默认”音频格式的四倍(这将在26到27小时之间)。由于我还没有找到时间让应用程序运行这么长时间,我只能说它确实运行了大约15个小时,然后由于时间限制而不得不停止它。因此,这一假设仍有待证实或否认。
根据项目符号第13点的结果,似乎整个问题仅在使用Windows时才出现。因此,我认为这可能是javax.sound.sampled API的平台特定部分中的一个错误。
即使我认为当这个问题开始发生时,我可能已经找到了一种改变的方法,但我对结果并不满意。我可以定期关闭并重新打开线路,以避免问题开始出现。但是,这样做会导致一些任意的少量时间,我无法捕获音频样本。此外,Javadoc指出,某些行在关闭后根本无法重新打开。因此,在我的情况下,这不是一个好的解决方案。
理想情况下,这整个问题根本不应该发生。我是否完全缺少某些东西,或者我是否遇到了javax.sound.sampled API可能的限制?我怎样才能摆脱这个问题?
编辑:根据Xtreme Biker和gidds的建议,我创建了一个小型示例应用程序。你可以在这个Github存储库中找到它。