Внезапная задержка во время записи звука в течение длительного времени внутри JVM - PullRequest
16 голосов
/ 02 апреля 2019

Я реализую приложение, которое записывает и анализирует аудио в режиме реального времени (или, по крайней мере, настолько близко к реальному времени, насколько это возможно), используя обновление 201 версии JDK. Выполняя тест, имитирующий типичные сценарии использования приложенияЯ заметил, что после нескольких часов непрерывной записи звука была введена внезапная задержка где-то между одной и двумя секундами.До этого момента не было заметной задержки.Только после этой критической точки записи в течение нескольких часов эта задержка начала возникать.

Что я пробовал до сих пор

Чтобы проверить, есть ли мой код для синхронизации записи аудиоОбразцы не так, я прокомментировал все, что касается времени.Это оставило меня по существу с этим циклом обновления, который выбирает аудиосэмплы, как только они будут готовы (Примечание: код Котлина):

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 со слабым порядком байтов и частотой дискретизации 44100,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. Поэтому я думаю , что это может быть ошибкой в ​​специфичных для платформы частях javax.sound.sampled API.

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

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

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

1 Ответ

6 голосов
/ 15 апреля 2019

У меня (довольно) огромный опыт взаимодействия с аудио 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 ++ для окон и перевести их наДжава.Это даст вам точный контроль над устройствами захвата звука, намного больше, чем то, что JVM предоставляет OOTB.Если вы хотите взглянуть на живой и дышащий пример использования JNA, посмотрите мой проект JNA AAC .
  10. Опять же, если вы используете специальную систему захвата, есть хороший шанс, чтопроизводитель уже предоставляет свой собственный низкоуровневый API-интерфейс C для взаимодействия с оборудованием, и вам следует рассмотреть его также.
  11. Если это не так, возможно, вам и вашей компании / клиенту следует рассмотреть возможность использованияспециализированное оборудование для захвата (не должно быть так дорого).
...