Импульсно-кодовая модуляция (ИКМ) - это метод, используемый для цифрового представления дискретизированных аналоговых сигналов, и это стандартный способ хранения аудиосэмплов в цифровом виде [1]. Сэмплы PCM - это просто целые числа или числа с плавающей запятой, которые представляют «дискретизированные» значения акустической волны в определенный момент времени.

Получить образцы звука PCM с микрофона в браузере непросто, поэтому я решил написать это руководство для этого процесса. В этом руководстве будет рассмотрен код JavaScript, необходимый для получения моноканальных 16-битных подписанных аудиосэмплов PCM с микрофона в браузере. Приведенный ниже код был протестирован на 64-разрядной версии Chrome V73.0.3683.103 на Ubuntu 16.04, но он должен работать и для современных версий других браузеров, если они поддерживают API веб-аудио, который мы будем использовать в этом руководстве.

Это руководство должно быть как можно более простым, поэтому, чтобы продолжить, вам просто нужно скопировать и вставить код в терминал браузера. Будут отмечены любые вызовы асинхронных функций, и вам придется подождать секунду или две, прежде чем копировать следующий фрагмент кода. Конечно, чтобы программно ждать результатов функций или ждать определенный промежуток времени, пока вы говорите в микрофон и получаете образцы PCM, а затем выходите, вы можете использовать обещания JavaScript, функции обратного вызова или setTimeout функция.

Давайте начнем:

(1) Откройте Chrome или любой другой браузер на своем компьютере и нажмите Ctrl + Shift + I (в Linux или Windows) или Option + Cmd + J в MacOS, чтобы открыть консоль браузера.

(2) Скопируйте и вставьте следующий фрагмент кода в консоль браузера:

Давайте переварим это. Поскольку метод getUserMedia существует под разными именами в разных браузерах и / или версиях браузеров, первая строка просто гарантирует, что мы получим этот метод и назначим его navigator.getUserMedia независимо от того, того, что он называется в API текущего браузера. Если вы не знакомы с тем, как "||" Оператор or используется в JavaScript (и Python) с нестрого логическими значениями, см. этот ресурс для объяснения.

Затем мы инициализируем неопределенную переменную с именем audioStream, а следующее условие if просто устанавливает переменную audioStream на фактический аудиопоток, который мы можем получить из navigator.getUserMedia . Обратите внимание, как мы устанавливаем {audio: true}, потому что мы запрашиваем только звук. Если бы мы запрашивали видео, мы бы передали {video: true}. Если вы не получаете оповещения в браузере, мы можем продолжить. Обратите внимание, что вам нужно подождать секунду или две, пока этот фрагмент кода завершится, поскольку navigator.getUserMedia является асинхронным вызовом функции.

(3) Скопируйте и вставьте следующий фрагмент кода в консоль браузера:

В приведенном выше фрагменте кода много чего происходит, поэтому я остановлюсь только на тех частях, которые вам, скорее всего, понадобятся. Первый - это переменная sampleRate, которая пригодится, если вам нужно уменьшить или повысить дискретизацию сэмплов PCM, которые вы получаете с микрофона в браузере. Обратите внимание, что на дату публикации этой статьи эта частота дискретизации, которую вы получаете, является фиксированной, и нет возможности изменить ее в браузере. Обычно это 44 100 или 48 000 в зависимости от вашего компьютера, браузера и / или версии браузера.

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

Третье примечательное обстоятельство: мы создаем рекордер, который будет обрабатывать начало и конец записи. Обратите внимание, что мы передаем ему bufferSize, а также количество входных и выходных каналов, которые мы записываем. В нашем случае, поскольку мы хотим записать сэмплы одноканального ИКМ, мы просто передаем 1 для обоих.

(4) Скопируйте и вставьте следующий фрагмент кода в консоль браузера:

Теперь приступаем к записи! Мы инициализируем пустой массив с именем leftChannel, который будет содержать массивы 32-битных выборок PCM с плавающей запятой того размера, который мы выбрали ранее, то есть 2048. Обратите внимание, что позже мы конвертируем все это в 16-битные подписанные образцы int PCM.

Затем мы устанавливаем функцию обратного вызова всякий раз, когда аудиобуфер заполнен. Внутри него мы выполняем getChannelData во входном буфере и передаем идентификатор канала, из которого получаем данные. Первый канал имеет идентификатор 0, второй канал имеет идентификатор 1 и так далее. Важно отметить, что мы должны клонировать образцы из буфера, потому что значения буфера будут перезаписаны при следующем обратном вызове. Другими словами, то, что мы получаем от каждой функции обратного вызова, - это просто указатель на буфер, но этот буфер повторно используется и перезаписывается на протяжении всего сеанса записи микрофона.

Наконец, последняя строка запускает запись. Говорите в микрофон компьютера сколько угодно долго, а когда закончите, выполните следующую строку:

(5) На этом этапе массив leftChannel содержит 32-битные суббуферы PCM с плавающей запятой, каждый размером 2048, который мы выбрали ранее. Теперь мы хотим объединить все эти буферы вместе в один массив, который будет содержать 32-битные образцы PCM с плавающей запятой, поэтому мы выполняем следующее:

В конце этого кода мы получаем 32-битные образцы PCM с плавающей запятой в массиве с именем PCM32fSamples.

(6) Наконец, мы хотим преобразовать этот одноканальный массив 32-битных выборок PCM с плавающей запятой в 16-битные целые числа со знаком:

Вот и все! Мы просто умножаем каждую 32-битную выборку с плавающей запятой на 32767, и мы берем ее на себя, чтобы преобразовать ее в целое число. Затем мы убеждаемся, что образец находится в диапазоне от -32768 до 32767, поскольку это минимум и максимум 16-битного целого числа со знаком, соответственно. Наконец, мы добавляем образец в массив.

Массив PCM16iSamples теперь содержит одноканальные 16-битные целочисленные образцы PCM со знаком, с которыми мы можем делать все, что захотим.

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

Источники:

[1] https://en.wikipedia.org/wiki/Pulse-code_modulation

[2] https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API

[3] http://typedarray.org/from-microphone-to-wav-with-getusermedia-and-web-audio/

[4] https://stackoverflow.com/questions/59252870/obading-microphone-pcm-data-from-getchanneldata-method-using-webaudio-api-does