Ряд способов для передачи голоса на расстояние с помощью ESP32/Arduino
Источник картинки: silenthollywood.com
Использование микроконтроллеров ESP32/Arduino позволяет создавать достаточно любопытные проекты, среди которых особняком стоят проекты для передачи голоса. Например, создать собственную рацию, которая может быть весьма тонко настроена на программном уровне. В этой статье мы обзорно рассмотрим несколько известных способов для реализации подобной задумки.
В одном из проектов был продемонстрирован достаточно любопытный подход: возможность выбора одного из двух способов передачи данных:
Использование широковещательной рассылки UDP
Широковещательная рассылка UDP — очень простой механизм. Вы отправляете UDP-пакет на специальный IP-адрес, и ваш маршрутизатор передаёт этот пакет всем другим устройствам в вашей сети.
Мы можем безопасно отправлять до 1436 байт в пакете UDP, поэтому, если мы семплируем на частоте 16 кГц и используем 8-битные сэмплы, это около 90 мс аудиоданных. Таким образом, нам нужно отправить около 11 пакетов в секунду. Это вполне соответствует возможностям ESP32.
Большим преимуществом использования широковещательного UDP является то, что нам не нужно знать о наших одноранговых узлах, мы можем просто передать сообщение, и любой, кто его слушает, получит его.
Однако у UDP есть и некоторые недостатки:
- Доставка UDP-пакетов — это только максимальные усилия — нет никакой гарантии, что кто-то получит отправленный вами пакет.
- Также нет гарантии порядка пакетов — кто-то может получить отправленные вами пакеты в совершенно случайном порядке.
Другим большим преимуществом широковещательной передачи UDP является то, что вы можете получать пакеты на свой настольный компьютер или телефон, поэтому было бы довольно легко создавать дополнительных клиентов, не основанных на ESP32.
Использование ESP-NOW
ESP-NOW — это протокол, разработанный Esppresif, который позволяет нескольким устройствам ESP взаимодействовать друг с другом без необходимости использования Wi-Fi.
Это даёт нам большое преимущество перед опцией UDP, заключающееся в том, что нам не нужна сеть Wi-Fi, чтобы наша рация работала.
Недостатком ESP-NOW является гораздо меньший размер пакета — 250 байт. Это означает, что нам нужно отправлять пакеты 64 раза в секунду.
У нас также есть все те же недостатки, что и у UDP — доставка пакетов осуществляется с максимальной эффективностью и нет гарантий, в каком порядке будут приходить пакеты.
Выбор одного из двух режимов осуществляется с помощью комментирования / раскомментирования строки 45 в файле Config:
Единственным минусом использования решения на базе радиомодуля ESP32 является некоторая ограниченность радиуса действия устройства, так как wi-fi модуль ESP32 не предназначен изначально до передачи на большие расстояния. Да, я понимаю, вы можете сказать, что с использованием отражателя или тарелки — расстояние можно существенно увеличить: мне известны подобные эксперименты, где радиус действия увеличили вплоть до 10 км. Однако подобное решение всё-таки существенно выбивается за рамки и требует достаточно точной настройки друг на друга как приёмника, так и передатчика.
Альтернативным способом передачи голосового потока является использование известных дальнобойных плат радиосвязи NRF24L01+PA+LNA, которые позволяют (при соответствующем падении скорости передачи с ростом дальности) увеличить радиус, вплоть до более чем 1 километра.

Для этого нам надо будет использовать библиотеку RF24Audio — это поистине библиотека «для ленивых людей», так как сам скетч полноценной рации не содержит даже и 10 строк:
Чтобы работать с ним, нам необходимо:
- микрофон подключить к пину А0;
- кнопку переключателя, которая переключает режимы, для осуществления передачи следует соединить с пином А1;
- кнопка прибавления громкости должна быть подключена к пину A2;
- убавление громкости к пину А3;
- кстати сказать, библиотека предоставляет достаточно интересную функцию, которая позволяет включать удалённо передающее устройство! То есть не совсем включать, а переводить его в режим «прослушки, что вокруг происходит» ;). Для этого следует использовать кнопку, присоединённую к пину A4.
Следует отметить, что если кто-то соберётся экспериментировать с этим передатчиком, то стоит понимать, радиомодуль nrf является весьма чувствительным к качеству питания (напряжение и сила тока), поэтому питать его от стандартного пина Arduino совершенно недостаточно.
В идеале следует использовать специальные модули для питания:

Если же это недоступно, то тебе следует использовать любой другой модуль, например, регулятор/стабилизатор напряжения (так называемый DC-DC модуль). Например, MT3608:

Аппаратная часть проекта на основе этой идеи хорошо разобрана здесь, если кому интересно.
Сейчас я специально не останавливаюсь подробно на аппаратной части подобных затей, так как это выйдет далеко за пределы статьи, если описывать всё подробно. Поэтому я привёл выше только ссылки, которые помогут вам разобраться, если эта тема вас заинтересует.
Говоря о способах передачи голоса на расстояние, я совершенно не могу пройти мимо одного из проектов, поскольку он «реализует мои потаённые желания» и одну нереализованную задумку из далёкой молодости — передача голоса с помощью лазерного луча.
В своё время мне очень хотелось создать нечто подобное, так как в те времена, когда динозавры были зеленее, а сотовой связи не было, мы с одним товарищем жили в прямой видимости друг от друга, на расстоянии порядка 100 метров, и подобный проект был бы как нельзя кстати. Единственная проблема, что в те времена не существовало ещё Arduino 🙂
Приведённую ниже затею можно взять в качестве основы, так как основная задумка, положенная в основу проекта, заключается в использовании ШИМ-модуляции на 11 пине, для воспроизведения аудио (ссылка на скетч):
То есть, другими словами, он берёт заранее заготовленный массив данных (подключаемый как библиотека sounddata.h) и воспроизводит его. Выглядит конечно «зело эффектно»:
Чтобы использовать его как рацию, в качестве источника данных следует использовать микрофон, что потребует соответствующей доработки скетча. Так что заинтересовавшемуся читателю будет над чем поработать 😉
Хотя, честно говоря, требуется для этого не так уж много – обеспечить модуляцию лазерного луча. Другими словами, мерцать его в такт голосу. Например, захватывать сигнал с аналогового пина ардуино с подключенным к нему микрофоном и выводить на другом аналоговом пине, к которому подключен лазер (это на стороне передатчика). На стороне приёмника – обратное преобразование: снимать сигнал с аналогового пина и выводить на другом аналоговом пине, к которому подключено устройство воспроизведения – колонка, динамик и т. д. Понятно, что качество звучания будет оставлять желать лучшего – но для коммуникации сгодится 😉
Нечто примерно подобное было продемонстрировано вот в этой самоделке, только без участия ардуино (просто как принцип действия, который можно взять за основу):
Рассмотренные способы не охватывают все вероятные аппаратно-программные комбинации для передачи голоса, однако их вполне можно использовать в своих проектах, а вариант передачи с помощью радиомодуля nrf — вообще с минимальным вмешательством в код.
В заключение, если говорить об интересных дальнобойных способах передачи информации (хотя это уже и не относится напрямую к передаче голоса) — следует упомянуть ещё один, который вряд ли подойдёт для передачи голоса ввиду его низкой скорости, однако вполне может быть использован для передачи текстовых сообщений — LoRa.
LoRa (Long Range) — запатентованная проприетарная технология модуляции маломощной глобальной сети. Среди её особенностей в рамках нашей задачи можно выделить:
- Дальность передачи существенно выше, чем у других способов (10-15 км. Есть даже прецеденты передачи на 200! км).
- Высокая проникающая способность в городских условиях.
- Низкая пропускная способность (сотни бит/с – десятки кбит/сек). Да, «фоточки» уже не покачаешь. Если только текстовые сообщения.
Если экспериментировать с LoRa, то имеет смысл это делать с модулями TTGO LoRa32 SX1276 OLED, которые построены на основе ESP32 и имеют кроме модуля LoRa в своём составе — ещё и OLED дисплей, что может быть достаточно полезно в целом. Например, вот тут есть подробная инструкция по соединению двух таких модулей и исходный код для этого.
Таким образом, можно сказать, что есть множество способов передачи голосовой информации на расстояние и выбор конкретного из них базируется только на конкретной поставленной задаче и аппаратных возможностях того железа, которое будет обеспечивать выбранный тип решения.
Как передать звук через esp32 bluetooth

Sending Sound from an ESP32 to a Bluetooth Sink (e.g. Bluetooth Speaker)
A couple of months ago, I described how you can use my Arduino A2DP library to receive music with an ESP32 via Bluetooth e.g. from a mobile phone.
I have extended the library and implemented the BluetoothA2DPSource class that you can use to to the opposite and send sound data from an ESP32 to a Bluetooth Receiver:
That’s pretty easy to use: In the start you just indicate the Bluetooth Device name that you want to connect to and indicate a call back function. So the only challenge is to provide an implementation for the get_sound_data callback method. The supported audio codec in ESP32 A2DP is SBC: SBC audio stream is encoded from PCM data normally formatted as 44.1kHz sampling rate with two-channel 16-bit sample data.
Here is an simple implementation that just generates some sound with the help of the sin() function:
The new version 1.1 of the library is available new on Github.
see also:
17 Comments
Paul · 7. November 2021 at 1:02
What you have done is great. Many thanks and Kudos!! Question: Is it possible for the ESP32 to connect to a two-way bluetooth device (e.g. headset microphone), by name, and then be both a sink and a source at the same time?
My interest is in operating a two-way radio with the ESP32 and instead of hard wiring a microphone and speaker/earphone I would like to be able to do both audio exchanges using an off the shelf BT headset. And if it’s important… the radio is “half-duplex” so the streams of audio in the two directions wouldn’t have to be simultaneous so long as the change in direction was without significant delay.
As you likely know the limitations of the ESP32 BT interface well, I thought that you may be able to answer this with some certainty.
Thanks again.
Holger · 4. November 2021 at 23:57
Hi, is it possible to send audio to ANY audio device without specifying its name?
ESP32 Audio Project — Part II: Bluetooth Receiver Add-on
In Part II of this project, the M5StickC (ESP32 Pico) gets a bluetooth receiver mode to play A2DP audio over the external I²S DAC.
Things used in this project
Hardware components
Software apps and online services
Story
In this part of the ESP32 audio project, the internet radio built in Part I gets an extension: bluetooth audio streaming. The hardware from the first part is used, i.e. an M5StickC plus with an I²S DAC. The software includes an additional library, the ESP32-A2DP library which allows to turn the ESP32 into a bluetooth audio (A2DP) sink.
The integration of both audio libraries, ESP32-audioI2S and ESP32-A2DP, in a single application turned out to be not that easy. Hence, this is the main focus of the project description.
Key Features (Part II)
- Bluteooth audio streaming e.g. from Android or iOS devices
- Artist and song title shown on display
- Switching between bluetooth receiver mode and internet radio mode on button press
- Audio playback via I²S and an external digital-to-analog converter (DAC) board
I used Visual Studio Code with the PlatformIO IDE for this project. In the platformio.ini file of the project, library dependencies to the M5StickCPlus , the ESP32-audioI2S , and the ESP32-A2DP libraries are included.
With the monitor_filters option, file logging and exception stack trace decoding are enabled.
The usage of the board_build.partitions option is quite important for this project. It is explained in one of the subsequent sections.
The commented source code is available in the GitHub repository of this project in the a2dp branch. Please note that a WifiCredentials.cpp file needs to be created in the src folder (see first part of this project).
Program Size and the Partition Table
Since the application uses both, Bluetooth and WiFi (and the lwIP TCP/IP stack on top), the program size gets quite large. Thus, the first obstacle in this project was an error message after linking stating that the program size became greater than the allowed maximum of 1, 310, 720 bytes.
Now, it comes into play that the M5StickC plus is based on the ESP32-PICO-D4 module which has 4 MB (4*2^20 bytes) of SPI flash memory. How does it come then that the allowed program size is considerably smaller, i.e. 1, 310, 720 bytes?
The answer lies in the partition table which divides the flash memory into sections (partitions) for different types data and one or several applications. The default partition table layout is defined in the defaul.csv file of Arduino-ESP32 package. Therein, two application partitions, app0 and app1 , are defined, each of which has a size of 0x140000 bytes (decimal: 1, 310, 720).
To overcome the error stated above, a different partitioning of the flash memory can be applied such as the huge_app.csv which defines an app partition of 0x300000 bytes size (decimal: 3, 145, 728). This can be accomplished in PlatformIO using the board_build.partitions option in the platformio.ini file. With that option set, the build ouput changes to:
More Trouble with Memory Size
My first (and probably naive) idea was to run both libraries, ESP32-audioI2S and ESP32-A2DP, along with each other. Yet, this resulted in a non-functioning application that was producing varying exception traces most of the time such as this one:
Although I am not completely sure about the definite cause for the exceptions, I think that the most probable reason is insufficient RAM size.
After trying a few trial and error, I found a satisfying solution by deciding at startup of the application which library is initialized and started. The choice of the application mode is stored in non-volatile memory using the EEPROM library:
The mode is changed on pressing Button B of the M5StickC. The new mode is stored in non-volatile memory followed by a reboot of the device:
The Audio class is instantiated dynamically and only if needed:
If the BluetoothA2DPSink is instantiated dynamically, it causes exception traces when being used later on. However, the instance itself consumes only a few hundred bytes. The allocation of resources such as the bluetooth stack does not happen until calling the start method. Therefore, I instantiate it statically and call start only when the application boots in «A2DP» mode.
Displaying Artist and Song Title
The following callback routine is used for processing song meta data provided by the bluetooth source. When the routine receives the title or the artist attribute, it updates the string that is shown on the TFT display:
Outlook on Part III
In Part III of this project, we will send the artist and title of the current radio song to an IFTTT webhook in order to store it in a list of favourite songs.
Sending Sound from an ESP32 to a Bluetooth Sink (e.g. Bluetooth Speaker)
A couple of months ago, I described how you can use my Arduino A2DP library to receive music with an ESP32 via Bluetooth e.g. from a mobile phone.
I have extended the library and implemented the BluetoothA2DPSource class that you can use to to the opposite and send sound data from an ESP32 to a Bluetooth Receiver:
That’s pretty easy to use: In the start you just indicate the Bluetooth Device name that you want to connect to and indicate a call back function. So the only challenge is to provide an implementation for the get_sound_data callback method. The supported audio codec in ESP32 A2DP is SBC: SBC audio stream is encoded from PCM data normally formatted as 44.1kHz sampling rate with two-channel 16-bit sample data.
Here is an simple implementation that just generates some sound with the help of the sin() function:
The new version 1.1 of the library is available new on Github.
see also:
26 Comments
Andrew Banchieri · 6. February 2023 at 23:08
Thank you for your efforts. I have a Bluetooth speaker system that uses a pairing code. I’d like to use the ESP32 to send an audio signal to this speaker system. Is there a method to have the ESP32 send a pairing code?
pschatzmann · 7. February 2023 at 8:31
Did you have a look at the class documentation ? There you should find the following entry:
/// Defines the pin code. If nothing is defined we use “1234”
virtual void set_pin_code(const char* pin_code, esp_bt_pin_type_t pin_type=ESP_BT_PIN_TYPE_VARIABLE);
Michael · 9. October 2022 at 9:41
Hi, is it possible to connect a esp32 to an audio source and stream this audio over ble to a hearing aid? Like the asha gatt protocol that android use.
Cheers
pschatzmann · 9. October 2022 at 9:49
No, the ESP32 still does not provide any audio functionality with BLE…
Joel Leon · 6. October 2022 at 23:06
All your work is quite exceptional and greatly appreciated. Thank you.
My project uses an Esp32 to create a varying tone in response to a sensor. To date I play the tone on a hard wired speaker. I would like to send the tone to a Smartphone over BLE. Can you point me in the right direction to continue my project.
Thanks,
Joel
pschatzmann · 7. October 2022 at 18:42
Nop, the ESP32 currently does not provide any audio functionality with BLE…
saadistan · 23. July 2022 at 10:46
can you stream audio from PDM MIC to a mobile device and simultaneously use Bluetooth serial communication as well for two-way serial data transfer?
pschatzmann · 25. July 2022 at 21:02
No you can have only one Bluetooth connection at any time!
Please note that when you use Bluetooth you also can’t use WIFI
Paul · 20. July 2022 at 21:00
Thanks again for all your hard working creating this A2DP Source library. Much appreciated! Regarding the function write_data(), how do we assess the state of the queue before calling write_data()? I’ve looked at the library’s source code, but am not able to figure this out (yet). Thanks!
Paul · 7. November 2021 at 1:02
What you have done is great. Many thanks and Kudos!! Question: Is it possible for the ESP32 to connect to a two-way bluetooth device (e.g. headset microphone), by name, and then be both a sink and a source at the same time?
My interest is in operating a two-way radio with the ESP32 and instead of hard wiring a microphone and speaker/earphone I would like to be able to do both audio exchanges using an off the shelf BT headset. And if it’s important… the radio is “half-duplex” so the streams of audio in the two directions wouldn’t have to be simultaneous so long as the change in direction was without significant delay.
As you likely know the limitations of the ESP32 BT interface well, I thought that you may be able to answer this with some certainty.
Thanks again.
Holger · 4. November 2021 at 23:57
Hi, is it possible to send audio to ANY audio device without specifying its name?