Внезапная задержка при записи звука в течение длительного периода времени внутри JVM

Я реализую приложение, которое записывает и анализирует звук в режиме реального времени (или, по крайней мере, как можно ближе к реальному времени), используя обновление 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 Гц). по умолчанию, если не указано иное):

  1. Критическое количество времени, которое должно пройти, прежде чем появится задержка, по-видимому, различается в зависимости от используемой машины. На моем настольном ПК с Windows 10 это где-то между 6,5 и 7 часами. Однако на моем ноутбуке (также с Windows 10) это где-то между 4 и 5 часами для того же аудиоформата.
  2. Количество используемых аудиоканалов, по-видимому, имеет значение. Если я изменю количество каналов со стерео на моно, время до того, как задержка начнет появляться, удвоится и составит где-то между 13 и 13,5 часами на моем рабочем столе.
  3. Уменьшение размера выборки с 16 бит до 8 бит также приводит к удвоению времени до начала появления задержки. Где-то между 13 и 13,5 часами на моем рабочем столе.
  4. Изменение порядка байтов с прямого на прямой не имеет никакого эффекта.
  5. Переключение со стереомикса на физический микрофон также не дает никакого эффекта.
  6. Я попытался открыть строку, используя разные размеры буфера (1024, 2048 и 3072 выборочных кадра), а также размер буфера по умолчанию. Это тоже ничего не изменило.
  7. Сброс TargetDataLine после начала задержки приводит к тому, что все байты равны нулю в течение примерно одной-двух секунд. После этого я снова получаю ненулевые значения. Задержка, однако, все еще есть. Если я очищаю строку до критической точки, я не получаю эти нулевые байты.
  8. Остановка и перезапуск TargetDataLine после появления задержки также ничего не меняет.
  9. Однако закрытие и повторное открытие TargetDataLine избавляет от задержки, пока она не появится снова через несколько часов.
  10. Автоматическая очистка внутреннего буфера TargetDataLines каждые десять минут не помогает решить проблему. Таким образом, переполнение буфера во внутреннем буфере, похоже, не является причиной.
  11. Использование параллельного сборщика мусора во избежание зависаний приложений также не помогает.
  12. Используемая частота дискретизации кажется важной. Если я удвою частоту дискретизации до 88200 Гц, задержка начнет происходить где-то между 3 и 3,5 часами работы.
  13. Если я позволю ему работать под Linux, используя мой аудиоформат «по умолчанию», он все равно будет работать нормально после примерно 9 часов работы.

Выводы, которые я сделал:

Эти результаты позволили мне сделать вывод, что время, в течение которого я могу записывать звук до того, как эта проблема начнет происходить, зависит от машины, на которой запущено приложение, и зависит от скорости передачи данных (т. е. размера кадра и частоты дискретизации) аудиоформат. Это кажется верным (хотя я не могу полностью подтвердить это на данный момент), потому что, если я объединим изменения, сделанные в 2 и 3, я предполагаю, что могу записать аудиосэмплы в четыре раза быстрее, чем долго (что будет где-то между 26 и 27 часами), как при использовании моего аудиоформата «по умолчанию», прежде чем начнет появляться задержка. Поскольку я еще не нашел времени, чтобы приложение работало так долго, я могу только сказать, что оно работало нормально около 15 часов, прежде чем мне пришлось его остановить из-за нехватки времени на моей стороне. Так что эту гипотезу еще предстоит подтвердить или опровергнуть.

Судя по пункту 13, проблема возникает только при использовании Windows. Поэтому я думаю, что это может быть ошибкой в ​​специфических для платформы частях API javax.sound.sampled.

Хотя я думаю, что мог бы найти способ измениться, когда эта проблема начнет происходить, я не удовлетворен результатом. Я мог периодически закрывать и снова открывать линию, чтобы проблема вообще не начинала появляться. Однако это приведет к произвольному небольшому количеству времени, когда я не смогу захватить аудиосэмплы. Кроме того, в Javadoc говорится, что некоторые строки вообще нельзя открыть после закрытия. Поэтому в моем случае это не лучшее решение.

В идеале, всей этой проблемы вообще не должно быть. Есть ли что-то, что я полностью упускаю, или я испытываю ограничения того, что возможно с API javax.sound.sampled? Как вообще избавиться от этой проблемы?

Редактировать: по предложению Xtreme Biker и gidds я создал небольшой пример приложения. Вы можете найти его в этом репозитории Github.


person Fabian B.    schedule 02.04.2019    source источник
comment
Комментарии не для расширенного обсуждения; этот разговор был перенесено в чат.   -  person Bhargav Rao    schedule 13.04.2019


Ответы (1)


У меня (довольно) большой опыт работы с аудиоинтерфейсом Java. Вот несколько моментов, которые могут помочь вам найти правильное решение:

  1. Дело не в версии JVM - аудиосистема java почти не обновлялась с Java 1.3 или 1.5.
  2. Аудиосистема Java представляет собой оболочку бедняка для любого API аудиоинтерфейса, который может предложить операционная система. В Linux это библиотека Pulseaudio, для Windows это API прямого показа аудио (если я не ошибаюсь насчет последнего).
  3. Опять же, API аудиосистемы является своего рода устаревшим API — некоторые функции не работают или не реализованы, другие ведут себя просто странно, поскольку зависят от устаревшего дизайна (при необходимости я могу привести примеры).
  4. Это не вопрос сборки мусора. Если ваше определение «задержки» — это то, что я понимаю (аудиоданные задерживаются на 1-2 секунды, то есть вы начинаете слышать материал на 1-2 секунды позже), ну, сборщик мусора не может заставить пустые данные волшебным образом захватываться целевой строкой данных, а затем добавлять данные, как обычно, со смещением байта на 2 секунды.
  5. Скорее всего, здесь происходит либо аппаратное обеспечение, либо драйвер, который в какой-то момент предоставляет вам искаженные данные за 2 секунды, а затем передает остальные данные в обычном режиме, что приводит к «задержке», которую вы испытываете.
  6. Тот факт, что он отлично работает в Linux, означает, что это не аппаратная проблема, а проблема, связанная с драйвером.
  7. Чтобы подтвердить это подозрение, вы можете попробовать записать звук через FFmpeg в течение той же продолжительности и посмотреть, воспроизводится ли проблема.
  8. Если вы используете специализированное оборудование для захвата звука, лучше обратитесь к производителю оборудования и узнайте у него о проблеме, с которой вы столкнулись в Windows.
  9. В любом случае, при написании приложения для захвата звука с нуля я настоятельно рекомендую держаться подальше от аудиосистемы Java, если это возможно. Это хорошо для POC, но это неподдерживаемый устаревший API. JNA всегда является жизнеспособным вариантом (я использовал его в Linux с ALSA/Pulse-audio для управления атрибутами аудиооборудования, которые аудиосистема Java не могла изменить), поэтому вы можете найти примеры захвата звука на C++ для Windows и перевести их на Ява. Это даст вам точный контроль над устройствами аудиозахвата, намного больше, чем то, что JVM предоставляет OOTB. Если вы хотите взглянуть на живой/дышащий пример использования JNA, ознакомьтесь с моим кодировщиком JNA AAC. проект.
  10. Опять же, если вы используете специальное оборудование для захвата, есть большая вероятность, что производитель уже предоставляет свой собственный низкоуровневый C API для взаимодействия с оборудованием, и вам также следует подумать о том, чтобы взглянуть на него.
  11. Если это не так, возможно, вам и вашей компании/клиенту следует рассмотреть возможность использования специализированного оборудования для захвата (не обязательно такого дорогого).
person Sheinbergon    schedule 14.04.2019
comment
Аудиосистема работает нормально, и для вас, чтобы деградировать, я не знаю, откуда вы пришли! - person gpasch; 15.04.2019
comment
@gpasch Ну, ему не хватает регулировки громкости (и других функций управления микшером, которые, по крайней мере, задокументированы), демультиплексирования каналов и обнаружения устройств на лету (аудиоинтерфейс не всегда может присутствовать во время загрузки класса JVM ). Я просто не думаю, что разработчику разумно использовать JNA и глубоко копаться в заголовочных файлах ALSA, чтобы сделать что-то вроде усиления на первой паре каналов (аудиосистема не могла этого добиться, даже при следовании документации). Я никоим образом не хотел игнорировать все усилия, вложенные в этот API разработчиками Sun. - person Sheinbergon; 15.04.2019