Почему иногда я могу объединять аудиоданные, используя NodeJS буферы, а иногда нет? - PullRequest
1 голос
/ 19 марта 2020

В рамках проекта, над которым я работаю, необходимо объединить несколько фрагментов аудиоданных в один большой аудиофайл. Аудиофайлы создаются из четырех источников, а отдельные файлы хранятся в хранилище Google Cloud. Каждый файл представляет собой mp3-файл, и легко проверить, что каждый отдельный файл генерируется правильно (по отдельности я могу их воспроизвести, отредактировать их в своем любимом программном обеспечении и т. Д. c.).

Для объединения вместе со звуковыми файлами сервер nodejs загружает файлы из хранилища Google Cloud в виде буфера массива с помощью запроса ax ios POST. Оттуда он помещает каждый буфер массива в буфер узла, используя Buffer.from(), так что теперь у нас есть массив объектов Buffer. Затем он использует Buffer.concat() для объединения объектов Buffer в один большой буфер, который мы затем конвертируем в данные Base64 и отправляем на клиентский сервер.

Это круто, но проблема возникает при объединении аудио, сгенерированного из разных источники. 4 источника, которые я упомянул выше, это программные платформы Text to Speech, такие как Google Cloud Voice и Amazon Polly. В частности, у нас есть файлы из Google Cloud Voice, Amazon Polly, IBM Watson и Microsoft Azure Text to Speech. По сути только пять текстовых речевых решений. Опять же, все отдельные файлы работают, но при объединении их вместе с помощью этого метода есть некоторые интересные эффекты.

Когда звуковые файлы объединяются, по-видимому, в зависимости от того, с какой платформы они исходят, звуковые данные либо будут, либо будут не будет включен в окончательный звуковой файл. Ниже приведена таблица «совместимости», основанная на моем тестировании:

|------------|--------|--------|-----------|-----|
| Platform / | Google | Amazon | Microsoft | IBM |
|------------|--------|--------|-----------|-----|
| Google     | Yes    | No     | No        | No  |
|------------|--------|--------|-----------|-----|
| Amazon     |        | No     | No        | Yes |
|------------|--------|--------|-----------|-----|
| Microsoft  |        |        | Yes       | No  |
|------------|--------|--------|-----------|-----|
| IBM        |        |        |           | Yes |
|------------|--------|--------|-----------|-----|

Эффект выглядит следующим образом: Когда я воспроизводю большой выходной файл, он всегда начинает воспроизведение первого включенного звукового файла. Оттуда, если следующий звуковой файл совместим, он слышен, в противном случае он полностью пропускается (нет пустого звука или чего-либо еще). Если он был пропущен, «длина» этого файла (например, аудиофайл длиной 10 с) включается в конец сгенерированного выходного звукового файла. Однако в тот момент, когда мой аудиоплеер достигает точки, где воспроизводилось последнее «совместимое» аудио, он сразу же переходит к концу.

В качестве сценария:

Input:
sound1.mp3 (3s) -> Google
sound2.mp3 (5s) -> Amazon
sound3.mp3 (7s)-> Google
sound4.mp3 (11s) -> IBM

Output:
output.mp3 (26s) -> first 10s is sound1 and sound3, last 16s is skipped.

В этом случае выходной звуковой файл будет длиться 26 секунд. В течение первых 10 секунд вы услышите, как sound1.mp3 и sound3.mp3 воспроизводятся вплотную. Затем в 10 секунд (по крайней мере, воспроизведение этого mp3-файла в firefox) проигрыватель сразу переходит к концу в 26 секунд.

Мой вопрос: у кого-нибудь есть идеи, почему иногда я могу объединить аудиоданные таким образом , а другие времена я не могу? И как получаются эти «отсутствующие» данные, включенные в конец выходного файла? Разве объединение двоичных данных не должно работать во всех случаях, если оно работает в некоторых случаях, так как все файлы имеют кодировку mp3? Если я ошибаюсь, пожалуйста, дайте мне знать, что я могу сделать, чтобы успешно объединить любые mp3-файлы :) Я могу предоставить свой nodeJS внутренний код, но используемые процессы и методы описаны выше.

Спасибо за чтение?

Ответы [ 2 ]

0 голосов
/ 23 марта 2020

@ Ответом Брэда было решение! Первое решение, которое он предложил, сработало. Потребовалось некоторое время, чтобы заставить FFMpeg работать правильно, но в конце концов использование библиотеки fluent-ffmpeg сработало.

Каждый файл в моем случае хранился в облачном хранилище Google, а не на жестком диске сервера. Это создало некоторые проблемы для FFmpeg, так как он требует, чтобы пути к файлам имели несколько файлов, или входной поток (но поддерживается только один, так как имеется только один STDIN).

Одним из решений является временное размещение файлов на жестком диске, но это не сработает для нашего варианта использования, поскольку у нас может быть много использования этой функции, и жесткий диск добавляет задержку.

Итак, вместо этого мы сделали так, как предлагалось, и загрузили каждый файл в ffmpeg, чтобы преобразовать его в стандартизированный формат. Это было немного сложно, но, в конце концов, запросил каждый файл как поток, используя этот поток в качестве входных данных для ffmpeg, а затем с помощью метода pipe() fluent-ffmpeg (который возвращает поток) в качестве выходного сработал.

Затем мы связали прослушиватель событий с событием «data» для этого канала и поместили данные в массив (bufs.push(data)), а в потоке «end» мы объединили этот массив, используя Buffer.concat(bufs), с последующим обещанием решить. Затем, после того как все запросы-обещания были решены, мы могли быть уверены, что ffmpeg обработал каждый файл, а затем эти буферы были объединены в требуемые группы, как и раньше, с использованием Buffer.concat(), преобразованы в данные base64 и отправлены клиенту.

Это прекрасно работает, и теперь, похоже, он способен обрабатывать каждую комбинацию файлов / источников, которые я могу добавить в него!

В заключение:

Ответ на этот вопрос состоял в том, что данные mp3 должны быть закодированы по-разному (разные каналы, частоты дискретизации и т. Д. c.), А загрузка их через ffmpeg и вывод их «унифицированным» способом сделала данные mp3 совместимыми. ,

Решением было обработать каждый файл в ffmpeg отдельно, перенаправить вывод ffmpeg в буфер, а затем объединить буферы.

Спасибо @Brad за ваши предложения и подробный ответ!

0 голосов
/ 19 марта 2020

Потенциальные источники проблем

Частота дискретизации

44,1 кГц часто используется для musi c, так как это то, что используется в аудио CD. 48 кГц обычно используется для видео, как и на DVD. Обе эти частоты дискретизации намного выше, чем требуется для речи, поэтому вполне вероятно, что ваши различные провайдеры преобразования текста в речь выводят что-то другое. 22,05 кГц (половина от 44,1 кГц) распространены, и 11,025 кГц тоже там.

Хотя каждый кадр определяет свою собственную частоту выборки, что позволяет генерировать поток с различными частотами выборки, я никогда видел попытку декодера переключать частоты дискретизации в среднем потоке. Я подозреваю, что декодер пропускает эти кадры или, возможно, даже пропускает произвольный блок, пока не получит согласованные данные снова.

Используйте что-то вроде FFmpeg (или FFprobe), чтобы выяснить, что Частоты дискретизации ваших файлов:

ffmpeg -i sound2.mp3

Вы получите такой вывод:

Duration: 00:13:50.22, start: 0.011995, bitrate: 192 kb/s
  Stream #0:0: Audio: mp3, 44100 Hz, stereo, fltp, 192 kb/s

В этом примере частота дискретизации составляет 44,1 кГц.

Количество каналов

Я ожидаю, что ваши голосовые MP3 будут в моно, но это не помешало бы проверить, чтобы быть уверенным. Как и выше, проверьте вывод FFmpeg. В моем примере выше написано stereo.

Как и в случае с частотой дискретизации, технически каждый кадр может указывать свой собственный счетчик каналов, но я не знаю ни одного проигрывателя, который бы выполнял переключение счетчика каналов в среднем потоке. , Поэтому, если вы объединяете, вам нужно убедиться, что все количества каналов одинаковы.

Теги ID3

Обычно существует метаданных ID3 в начало (ID3v2) и / или конец (ID3v1) файла. Меньше ожидается, что эти данные будут в середине потока. Вы должны убедиться, что все эти метаданные удалены перед объединением.

MP3 Bit Reservoir

Кадры MP3 не обязательно стоят отдельно. Если у вас есть постоянный битрейт, кодировщик может по-прежнему использовать меньше данных для кодирования одного кадра и больше данных для кодирования другого. Когда это происходит, некоторые кадры содержат данные для других кадров. Таким образом, кадры, которые могут извлечь выгоду из дополнительной полосы пропускания, могут получить ее, в то же время приспосабливая весь поток в пределах постоянной скорости передачи данных. Это «резервуар битов».

Если вы обрезаете поток и объединяете его в другом потоке, вы можете разделить кадр и зависимые от него кадры. Это обычно вызывает сбой в работе аудио, но может также привести к пропуску декодера вперед. Некоторые плохо ведущие себя декодеры просто перестанут играть. В вашем примере вы ничего не режете, так что это, вероятно, не является источником вашей проблемы ... но я упоминаю об этом здесь, потому что это определенно относится к тому, как вы работаете с этими потоками.

См. Также: http://wiki.hydrogenaud.io/index.php?title=Bit_reservoir

Решения

Выберите «нормальный» формат, выполните повторную выборку и перекодируйте несоответствующие файлы

Если большинство ваших источников имеют один и тот же формат и только один или два ожидающих обработки, вы можете преобразовать несоответствующий файл. Оттуда извлеките теги ID3 из всего и объедините их.

Чтобы выполнить преобразование, я бы рекомендовал перенести его в FFmpeg как дочерний процесс .

child_process.spawn('ffmpeg' [
  // Input
  '-i', inputFile, // Use '-' to write to STDIN instead

  // Set sample rate
  '-ar', '44100',

  // Set audio channel count
  '-ac', '1',

  // Audio bitrate... try to match others, but not as critical
  '-b:a', '64k',

  // Ensure we output an MP3
  '-f', 'mp3',

  // Output
  outputFile // As with input, use '-' to write to STDOUT
]);

Лучшее решение: пусть FFmpeg (или аналогичный) сделает всю работу за вас

Самое простое и надежное решение всего этого - позволить FFmpeg создать для вас совершенно новый поток. Это приведет к тому, что ваши аудиофайлы будут декодированы в PCM, и будет создан новый поток. Вы можете добавить параметры для повторной выборки этих входов и, при необходимости, изменить количество каналов. Затем выведите один поток. Используйте concat filter .

Таким образом, вы можете принимать аудиофайлы любого типа, вам не нужно писать код, чтобы взламывать эти потоки вместе, и после настройки вы выиграли ' об этом не стоит беспокоиться.

Единственный недостаток в том, что для этого потребуется перекодировка всего, что означает потерю качества другого поколения. В любом случае это потребуется для любых несоответствующих файлов, и это всего лишь речь, поэтому я бы не стал об этом думать.

...