Найти в Дзене

💬Создаю свой чат на Python. Аудиозвонки и кодек Opus. Часть 2

Всем привет! На связи енотик 🦝 И сегодня в этой короткой статье (с примерами кода) я продолжу историю создания своего 💬 чата на Python. Сегодня я расскажу как у меня получилось записывать звук в Python и сжимать его с помощью библиотеки Opus. Сперва небольшое предисловие 🫠 Я хотел во второй части стати описать весь процесс записи, обработки и отправки аудио. Но в процессе написания статьи понял, что в одну это всё не влезет да и выглядеть будет громоздко 🫣 Так что в этой части мы рассмотрим запись и сжатие записанного звука. В своей предыдущей статье я начал рассказывать о создании своего чата с нуля на Python. В ней я коротко описал возможности чата и чего всё началось. Сегодня я бы хотел подробнее остановиться на аудиозвонках, так как именно их создание отняло у меня львиную долю времени. Любой опытный разработчик, прочитав то, как я реализовал механизм звонков и передачи аудио, закидает меня камнями. Не надо, позялувста, я только учусь 😌 Передо мной стояла задача передать г
Оглавление

Всем привет! На связи енотик 🦝 И сегодня в этой короткой статье (с примерами кода) я продолжу историю создания своего 💬 чата на Python. Сегодня я расскажу как у меня получилось записывать звук в Python и сжимать его с помощью библиотеки Opus.

Сперва небольшое предисловие 🫠 Я хотел во второй части стати описать весь процесс записи, обработки и отправки аудио. Но в процессе написания статьи понял, что в одну это всё не влезет да и выглядеть будет громоздко 🫣 Так что в этой части мы рассмотрим запись и сжатие записанного звука.

🌊вВодная часть

В своей предыдущей статье я начал рассказывать о создании своего чата с нуля на Python. В ней я коротко описал возможности чата и чего всё началось.

Сегодня я бы хотел подробнее остановиться на аудиозвонках, так как именно их создание отняло у меня львиную долю времени.

Любой опытный разработчик, прочитав то, как я реализовал механизм звонков и передачи аудио, закидает меня камнями. Не надо, позялувста, я только учусь 😌

Передо мной стояла задача передать голос человека по интернету с помощью Python, да ещё и в режиме реального времени. Давайте для начала разберемся как вообще в теории работает передача аудио в реальном времени.

Аудиозвонок (или аудиопоток) - это непрерывная передача крошечных отрезков аудио по сети от отправителя к получателю. Крошечные отрезки ещё называют чанками, в контексте аудиозвонка - аудиочанками. Аудиочанк - это крошечный отрезок звука (обычно 20 миллисекунд).

💡 И если один аудиочанк это 20 мс, то 1 секунда аудио это 50 таких чанков.

Ну, становиться немного понятнее. Теперь нужно понять, как записывать эти крошечные отрезки аудио в Python.

🎙Как записать голос в Python?

Здесь нам на помощь приходит библиотека PyAudio, которая превращает ваш микрофон в цифровой конвейер.

Устанавливается библиотека через PIP:

pip install pyaudio

Далее инициализируем объект PyAudio:

import pyaudio
pa = pyaudio.PyAudio()

После нужно инициализировать объект потока записи:

input_stream = pa.open(
format=pyaudio.paInt16,
channels=1,
rate=16000,
input=True,
frames_per_buffer=960,
stream_callback=record_chunk
)

А теперь разберемся что за аргументы передаются в метод инициализации потока записи:

  • format — это формат звука. В примере используется моно аудио 16 бит.
  • channels — количество аудиоканалов. У нас это моно, то есть один канал.
  • rate — частота дискретизации, то есть количество выборок аналогового сигнала в секунду. В нашем случае это 16000 выборок в секунду. Чем выше этот показатель, тем лучше качество звука.
  • input = True — поток настроен на запись звука.
  • frames_per_buffer — размер чанка в байтах.
  • stream_callback — метод, который выполняется после записи каждого чанка.

Как узнать какая будет длинна у записанного чанка? Мы знаем размер чанка и частоту дискретизации. Чтобы узнать длину чанка, нужно разделить частоту дискретизации на размер чанка.

У нас получиться следующая длина:

16000/960=16.6 мс.

Приведу скриншот полного кода записи звука и вывода в консоль длины каждого чанка, дополнительно буду считать общий размер всех записанных чанков (пригодиться в дальнейшем).

-2

Что в этом коде происходит? Я запускаю поток записи звука с микрофона и получаю в методе Callback чанки с микрофона и записываю их в массив record_data. А в бесконечном цикле программы каждую секунду вывожу размер массива record_data в байтах.

Вот что получается при работе программы

-3

Получается, что за 12 секунд записи у нас вышло 382 тысячи байт или 382 Кб. Довольно внушительный размер для передачи по сети. Если передавать пакеты с таким размером по сети, то возможны не только задержки, но и артефакты в самом аудио. Да и к тому же нагрузка на сеть будет сильная. Поэтому давайте эти чанки сжимать, благо для этого уже созданы различные кодеки. Например мы рассмотрим использование кодека Opus.

🔈Что такое Opus и как он сжимает аудио?

Opus (ранее известный как Harmony) — это аудиокодек, предназначенный для сжатия звука с потерями. Он идеально подходит для использования в приложениях, работающих в режиме реального времени в интернете, к примеру в приложениях для аудиосвязи.

Opus (кодек) — Википедия

Этот кодек использует большинство популярных мессенджеров в режиме звонков, например Telegram. И для создания аудиозвонков в своем чате я так же решил использовать Opus. Данный кодек умеет неплохо адаптироваться под условия сети: если соединение хорошее, он передает звук в высоком качестве, а если сеть перегружена — снижает качество, чтобы избежать задержек и прерываний.

Как же нам использовать возможности кодека Opus в нашем приложении на Python. Для этого нам понадобиться библиотека-мост между Opus и Python opuslib-next. Устанавливается эта библиотека так же через PIP:

pip install opuslib-next

Далее добавим в программу кодировщик из библиотеки Opus

encoder = opuslib_next.Encoder(
fs=16000, # Частота записи (16 кГц достаточно для речи)
channels=1, # Один канал (моно)
application='voip' # Режим для голосовой связи
)

Некоторые параметры нам уже знакомы:

  • sample_rate - частота дискретизации (она должна быть такой же как и у записываемого звука, у нас это 16000)
  • channels - количество каналов записи (также должно соответствовать записываемому звуку, у нас моно, поэтому 1)
  • application - это параметр отвечающий за режим кодировки звука (в нашем случае voip отлично подходит, так как будет обеспечивать максимальное сжатие для передачи голоса в реальном времени)

Не зря я написал выше, что библиотека opuslib-next является мостом между Python и Opus. Для полноценной работы с кодеком Opus нам ещё потребуется сама библиотека opus.dll. Библиотека собирается из исходников, но можно попытаться найти её в сети в уже собранном виде. Я как человек любящий, в некотором роде, сложности, собрал её из исходников.

GitHub - xiph/opus: Modern audio compression for the internet.

Если будет интересно, да и у меня будет время, опишу процесс сборки Opus для каждой ОС. Так как при разработке своего чата для Windows, Linux и macOS я собирал эту библиотеку на каждой ОС отдельно.

Дорабатываем код с учетом того, что файл библиотеки лежит рядом с исполняемым файлом. Добавляем Encoder от Opus и проверяем результат. Ниже приведен доработанный код.

-4

Перед тем как положить записанные чанки в record_data мы их сжимаем через Opus. Метод encoder.encode() принимает два агрумента: данные, которые нужно сжать и размер этих данных (т. е. размер записанного чанка). И результаты работы весьма впечатляют.

-5

За те же 12 секунд работы вышло 19,7 Кб, что примерно в 20 раз меньше!

Это лишь крошечная часть всего алгоритма аудиозвонков. Понадобиться несколько статей, чтобы описать его полностью. Сейчас мы реализовали запись и сжатие аудиочанков. Далее нужно упаковать их в пакет, указав получателя, и отправить на сервер-ретранслятор (почему ретранслятор - расскажу в будущих статьях). Также нужно описать алгоритм приема пакетов с аудиочанками, алгоритм декомпрессии и наконец воспроизведение.

Помимо этого, необходимо уделить внимание:

  • ✅ усилению громкости (уже реализовано),
  • ✅ шумоподавлению (также реализовано),
  • ⏳ динамичной буферизации (в процессе разработки),
  • 🛑 алгоритму компенсации эха (так называемый AEC фильтр, создать который у меня так и не получилось до сих пор).
Как на момент написания статьи выглядит интерфейс моего чата
Как на момент написания статьи выглядит интерфейс моего чата

А на сегодня всё! В следующих статьях я продолжу описывать процесс работы аудиозвонков 🔊 В дальнейших публикациях, если интересно, опишу процесс обмена картинками (недавно была добавлена такая возможность).

Спасибо за прочтение! 🦝 Всем удачи! Надеюсь было интересно

-7