Как превратить микроконтроллер за 300 рублей в Bluetooth-колонку или беспроводной плеер

15 авг 2026
2,709
393
52
1 месяц

Если вы хоть раз открывали официальные примеры от Espressif для работы со звуком по Bluetooth, то наверняка помните это чувство легкого ужаса. Десятки строчек низкоуровневой магии, куча коллбэков Bluedroid, настройка очередей FreeRTOS и постоянное ощущение, что где-то сейчас уплывет указатель.

Швейцарский разработчик Фил Шацманн (Phil Schatzmann) столкнулся ровно с этим же раздражением и написал библиотеку ESP32-A2DP. Она прячет весь сложный boilerplate под капот и дает лаконичный C++ интерфейс для стриминга аудио.

esp32

Что под капотом и как это работает

Библиотека закрывает две главные задачи:

  • A2DP Sink (приемник): ESP32 притворяется беспроводной колонкой или наушниками, ловит поток со смартфона и отдает его на внешний ЦАП, внутренний DAC платы или в сырой буфер памяти.
  • A2DP Source (передатчик): микроконтроллер сам транслирует звук (сгенерированный алгоритмически, прочитанный с SD-карты или микрофона) на внешнюю Bluetooth-акустику.

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

Реклама

Что касается вывода звука, библиотека работает по шине I2S. В ESP-IDF v5 и Arduino ESP32 v3 API для I2S заметно переписали. Чтобы не ломать совместимость при обновлениях фреймворка, автор советует связывать библиотеку со своим же проектом arduino-audio-tools, хотя можно обойтись стандартным классом I2SClass или вовсе забрать сырые PCM-семплы.

Как собрать беспроводной приемник за пять строк

Вот минимальный пример для запуска приемника звука:

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);

void setup() {
    Serial.begin(115200);
    a2dp_sink.start("MyMusic");
}

void loop() {
}

После прошивки плата начинает определяться в списке Bluetooth-устройств как «MyMusic». По умолчанию аудиопоток (16 бит, 44.1 кГц стерео) пойдет на I2S пины GPIO14 (BCK), GPIO15 (WS) и GPIO22 (Data Out). Если ваша разводка отличается, пины задаются в две строчки через конфиг i2s.defaultConfig().

Если внешней микросхемы ЦАП (вроде популярного PCM5102A или MAX98357A) под рукой нет, можно задействовать встроенный 8-битный DAC микроконтроллера на GPIO25 и GPIO26:

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

AnalogAudioStream out;
BluetoothA2DPSink a2dp_sink(out);

void setup() {
    Serial.begin(115200);
    a2dp_sink.start("MyMusic");
}

void loop() {}

Звук через встроенный ЦАП будет шумноват для аудиофилов, но для голосовых оповещений или радио на кухне этого вполне хватает.

Чтение метаданных и управление треками

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

void avrc_metadata_callback(uint8_t data1, const uint8_t *data2) {
    Serial.printf("Метаданные: id 0x%x, текст: %s\n", data1, data2);
}

void setup() {
    Serial.begin(115200);
    a2dp_sink.set_avrc_metadata_callback(avrc_metadata_callback);
    a2dp_sink.start("ESP32_Receiver");
}

void loop() {}

Чтобы управлять плеером на смартфоне с физических кнопок, предусмотрены методы play(), pause(), next(), previous(), fast_forward() и rewind().

Режим передатчика: когда звук генерирует ESP32

Если вам нужно отправить звук на Bluetooth-наушники или саундбар, переключаемся на BluetoothA2DPSource. Поток скармливается библиотеке через коллбэк:

#include "BluetoothA2DPSource.h"

BluetoothA2DPSource a2dp_source;

int32_t get_sound_data(uint8_t *data, int32_t byteCount) {
    // Здесь заполняем буфер PCM данными
    return byteCount;
}

void setup() {
    a2dp_source.set_data_callback(get_sound_data);
    a2dp_source.start("Target_Speaker");  
}

void loop() {}

Кстати, в метод start() можно передать список названий устройств (через std::vector<const char*>). Плата автоматически подключится к первому найденному из списка.

Доступ к сырому аудиопотоку и кодеки

Если I2S вам не нужен, а звук требуется обработать алгоритмически прямо на микроконтроллере (например, нарисовать цветомузыку через БПФ или записать фрагмент в память), можно перехватить PCM-пакеты через set_stream_reader():

void read_data_stream(const uint8_t *data, uint32_t length) {
    int16_t *samples = (int16_t*) data;
    uint32_t sample_count = length / 2;
    // Обработка семплов
}

void setup() {
    a2dp_sink.set_stream_reader(read_data_stream, false); // false отключает вывод на I2S
    a2dp_sink.start("DSP_Node");
}

Базово кодек связи — стандартный SBC. В свежих версиях автор добавил экспериментальную возможность подключать внешние декодеры через add_decoder(), включая AAC (правда, для полноценной работы на стороне IDF потребуется ESP-IDF версии 6.1 или выше).

Ограничения, о которых нужно знать

  • Профили HFP и HSP (громкая связь, звонки с микрофоном) не поддерживаются. Проект строго про передачу медиапотока.
  • Библиотека требовательна к ресурсам Bluetooth-стека. Совмещать тяжелый Wi-Fi обмен и стабильный A2DP стриминг на базовом ESP32 бывает непросто, возможны заикания звука.
  • Документация в репозитории подробная, но автор строго относится к оформлению баг-репортов: перед открытием issue в GitHub обязательно проверьте версии библиотек и ядра ESP32.

Кому пригодится

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

Библиотека совместима с Arduino IDE, PlatformIO и чистым Espressif IDF в качестве компонента. Для старта достаточно платы ESP32 WROOM и платы ЦАП на базе PCM5102A за пару сотен рублей.

🍪 Мы используем файлы cookie и сервис аналитики Яндекс.Метрика, чтобы сайт работал лучше. Продолжая пользоваться devtrends.ru, вы соглашаетесь с обработкой данных согласно Политике конфиденциальности.