Я реализую приложение, которое записывает и анализирует звук в режиме реального времени (или, по крайней мере, как можно ближе к реальному времени), используя обновление 201 версии 8 JDK. Выполняя тест, имитирующий типичные случаи использования приложения, я заметил что после нескольких часов непрерывной записи звука была введена внезапная задержка где-то от одной до двух секунд. До этого момента заметной задержки не было. Эта задержка начала происходить только после этой критической точки записи в течение нескольких часов.
Что я пробовал до сих пор
Чтобы проверить, не ошибся ли мой код для синхронизации записи аудиосэмплов, я закомментировал все, что связано с синхронизацией. По сути, это оставило меня с этим циклом обновления, который извлекает аудиосэмплы, как только они будут готовы (Примечание: код 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)
}
Однако даже без тайминга с моей стороны проблема не решилась. Поэтому я немного поэкспериментировал и позволил приложению работать с разными аудиоформатами, что привело к очень запутанным результатам (я собираюсь использовать 16-битный стереофонический аудиоформат со знаком PCM с прямым порядком байтов и частотой дискретизации 44 100,0 Гц). по умолчанию, если не указано иное):
- Критическое количество времени, которое должно пройти, прежде чем появится задержка, по-видимому, различается в зависимости от используемой машины. На моем настольном ПК с Windows 10 это где-то между 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. Поэтому я думаю, что это может быть ошибкой в специфических для платформы частях API javax.sound.sampled.
Хотя я думаю, что мог бы найти способ измениться, когда эта проблема начнет происходить, я не удовлетворен результатом. Я мог периодически закрывать и снова открывать линию, чтобы проблема вообще не начинала появляться. Однако это приведет к произвольному небольшому количеству времени, когда я не смогу захватить аудиосэмплы. Кроме того, в Javadoc говорится, что некоторые строки вообще нельзя открыть после закрытия. Поэтому в моем случае это не лучшее решение.
В идеале, всей этой проблемы вообще не должно быть. Есть ли что-то, что я полностью упускаю, или я испытываю ограничения того, что возможно с API javax.sound.sampled? Как вообще избавиться от этой проблемы?
Редактировать: по предложению Xtreme Biker и gidds я создал небольшой пример приложения. Вы можете найти его в этом репозитории Github.