Skip to content

Communication A2DP

pschatzmann edited this page Oct 22, 2026 · 1 revision

A2DP is the Bluetooth profile for high quality audio: it lets your microcontroller play music from a mobile phone (A2DP sink - it acts as a Bluetooth speaker), or send audio to Bluetooth speakers and headphones (A2DP source). It is based on my ESP32-A2DP library - install it. See Communication for the general considerations.

Hardware

A2DP needs Bluetooth Classic, which only the original ESP32 provides: the ESP32-S2, -S3, -C3, -C6, -H2 and -P4 support Bluetooth LE only (or no Bluetooth at all) and can't do A2DP.

Two ways to use it

  • The callbacks of ESP32-A2DP (recommended): the A2DP library calls your function with the received data, or asks your function for the data to send. This is the most efficient way - all examples starting with basic- use it.
  • A2DPStream (AudioTools/Communication/A2DPStream.h): wraps ESP32-A2DP as an AudioStream, so you can use it like any other source or sink with StreamCopy. This is convenient, but less efficient, because it needs a big buffer between the callbacks and the stream. Only one instance is supported.

The audio format of A2DP is fixed: 16 bit stereo at 44100 Hz (a sink may receive 48000 Hz, depending on the phone).

Receiving: a Bluetooth speaker

From basic-a2dp-i2s.ino: the received data is written to I2S in the callback:

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"  // https://github.com/pschatzmann/ESP32-A2DP

BluetoothA2DPSink a2dp_sink;
I2SStream i2s;

void read_data_stream(const uint8_t *data, uint32_t length) {
  i2s.write(data, length);
}

void setup() {
  a2dp_sink.set_stream_reader(read_data_stream, false);
  a2dp_sink.start("a2dp-i2s");  // the name that the phone shows

  auto cfg = i2s.defaultConfig();
  cfg.pin_data = 23;
  cfg.sample_rate = a2dp_sink.sample_rate();
  cfg.channels = 2;
  cfg.bits_per_sample = 16;
  i2s.begin(cfg);
}

void loop() { delay(100); }

ESP32-A2DP itself also provides an I2SStream based sink (BluetoothA2DPSink a2dp_sink(i2s)) - see its documentation.

Sending: to a Bluetooth speaker

From streams-generator-a2dp.ino, using A2DPStream as output:

#include "AudioTools.h"
#include "AudioTools/Communication/A2DPStream.h"

AudioInfo info(44100, 2, 16);
SineGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> in(sineWave);
A2DPStream out;
StreamCopy copier(out, in);

void setup(void) {
  sineWave.setFrequency(N_B4);
  auto cfg = in.defaultConfig();
  cfg.copyFrom(info);
  in.addNotifyAudioChange(out);
  in.begin(cfg);

  auto cfgA2DP = out.defaultConfig(TX_MODE);
  cfgA2DP.name = "LEXON MINO L";  // the name of your speaker
  out.begin(cfgA2DP);             // waits until the speaker is connected
  out.setVolume(0.3);
}

void loop() { copier.copy(); }

The A2DP source pulls the data at its own rate, so a fast source (generator, file) is automatically throttled.

A2DPConfig

Field Description
mode TX_MODE (source: sends to a speaker) or RX_MODE (sink: receives from a phone)
name in TX_MODE the name of the speaker to connect to, in RX_MODE the name shown to the phone
auto_reconnect reconnect automatically if the connection is lost
wait_for_connection begin() waits until a device is connected (default true)
buffer_size buffer between the A2DP callbacks and the stream
startup_logic, startup_nodata, silence_on_nodata what to do before the buffer is filled / when there is no data
tx_write_timeout_ms write timeout in TX_MODE (-1: blocking)

Examples

The a2dp examples show the receiving side with I2S, AudioKit, an equalizer, FFT and LED displays, a mixer and SPDIF, and the sending side from an ADC, a microphone, a synthesizer, MP3 files and the SD card.

Things to consider

  • Memory: Bluetooth Classic needs a lot of RAM - WiFi and A2DP at the same time are possible, but tight.
  • Sample rate: in RX_MODE check a2dp_sink.sample_rate() (or use the AudioInfo notifications of A2DPStream), since some phones send 48000 Hz.
  • See the ESP32-A2DP Wiki for the details of the A2DP library.

Clone this wiki locally