# ESP32-A2DP: turning an ESP32 into a Bluetooth audio sink or source

> pschatzmann/ESP32-A2DP wraps Espressif's Bluedroid A2DP stack in an Arduino-style C++ API for building Bluetooth speakers and senders. It is small, well documented through examples, and honest about what it does not do.

**pschatzmann/ESP32-A2DP** — A Simple ESP32 Bluetooth A2DP Library (to implement a Music Receiver or Sender) that supports Arduino, PlatformIO and Espressif IDF

- Repository: https://github.com/pschatzmann/ESP32-A2DP
- Stars: 2,755 · Forks: 400
- Language: C++
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/pschatzmann-esp32-a2dp

## What ESP32-A2DP actually solves

Espressif ships an A2DP sink example in esp-idf, and it works. The author states plainly that the example "did not make me happy", so he rewrote it as an Arduino library. That is the whole premise: the Bluetooth stack underneath is Espressif's, and what this project adds is an API that fits the Arduino sketch model, plus a set of examples that cover the cases people actually hit.

The target user is someone holding an ESP32 dev board and an external DAC or an I2S amplifier, who wants a phone to see the board as a speaker. The library also covers the reverse direction, an A2DP source, which is why the topic list includes both a2dp-sink and a2dp-source. It is not a general Bluetooth audio framework. The README is explicit that A2DP and AVRCP are supported, and that HFP, HSP and standalone AVRCP without A2DP are not. If your product needs a microphone over Bluetooth, this is the wrong starting point.

## The data path from phone to I2S

The mechanism is a callback chain, not a buffer you poll. The ESP32 receives SBC-encoded A2DP frames, the Bluedroid stack decodes them, and the library hands you PCM: 44.1 kHz, two channels, 16-bit samples, as the README describes the callback format. From there you choose an output.

The recommended route is the AudioTools library, because Espressif is retiring the legacy I2S API and the old integration stops working once you move to Arduino v3.0.0 (IDF v5). With AudioTools you declare an I2SStream, pass it into the BluetoothA2DPSink constructor, and call start(). The library writes PCM into that stream. Alternatives exist: you can pass any class that inherits from Arduino Print, use the ESP32 I2SClass directly, or use AnalogAudioStream for the internal DAC on GPIO25 and GPIO26.

If you want the samples yourself, there are two hooks. set_on_data_received fires a notification when a packet arrives. set_stream_reader gives you the raw pointer and length, which the README casts to int16_t with a sample count of length/2. That second call takes an optional flag to disable the I2S output while you consume the stream, which is the clean way to do processing without also driving a DAC. Metadata arrives separately through set_avrc_metadata_callback, and you can narrow what you receive with set_avrc_metadata_attribute_mask, for example requesting only title and playing time.

## Installing ESP32-A2DP and playing the first tone

The library is distributed as an Arduino library and as an IDF component, and it works under PlatformIO. The README points at the Arduino library registry and the repository itself; there is no separate install guide beyond that, so the Arduino Library Manager or a PlatformIO lib_deps entry is the path. The examples directory is where the real instructions live, with around twenty sketches split between receiver and sender cases.

The shortest receiver sketch needs AudioTools and the library. It starts a Bluetooth device named MyMusic and sends decoded audio to the default I2S pins: bck 14, ws 15, data 22. Those pins differ from the legacy API defaults, so do not copy an old tutorial.

```cpp
#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);

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

void loop() {
}
```

If your board routes I2S elsewhere, configure the stream before starting the sink. The keys are pin_bck, pin_ws and pin_data on the I2S config object.

```cpp
#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

I2SStream i2s;
BluetoothA2DPSink a2dp_sink(i2s);

void setup() {
    Serial.begin(115200);
    auto cfg = i2s.defaultConfig();
    cfg.pin_bck = 14;
    cfg.pin_ws = 15;
    cfg.pin_data = 22;
    i2s.begin(cfg);

    a2dp_sink.start("MyMusic");
}
```

After flashing, the board advertises itself under the name you passed to start(). Pair from the phone, play something, and audio should appear on the I2S pins. If nothing comes out, the first thing to check is whether i2s.begin() returned successfully; the Arduino I2S example in the README prints a failure message and halts, which is a reasonable pattern to copy.

## Where the library stops and you have to start

The callback gives you 44.1 kHz stereo PCM. It does not give you a mixer, an equalizer, or a resampler. The examples list includes a resample48000 sketch, which tells you the resampling is something you add, not something the sink does for you. If your DAC or amplifier is fixed at 48 kHz, budget for that work.

The protocol boundary is the harder limit. No HFP and no HSP means no two-way voice: you cannot build a Bluetooth headset with a microphone using this library alone. Standalone AVRCP without A2DP is also out, so a pure remote-control device is not a supported shape. And the I2S situation is a moving target. The README warns that the legacy I2S integration is unavailable from Arduino v3.0.0 onward and that the legacy syntax only keeps working if you do not upgrade. That is a real upgrade cliff for existing sketches, and it is the reason the documentation and examples were rewritten around AudioTools.

There is also a version trap in the Arduino I2S example itself: the README notes the snippet shown is for ESP32 core >= 3.0.0. Pin constants and mode flags differ across core versions, so a sketch copied from a blog post written against an older core may not compile.

## ESP32-A2DP against the Espressif IDF example

The obvious alternative is the A2DP sink example in esp-idf, which the README links to directly. Both sit on the same Bluedroid stack, so the radio behaviour and codec support are not where they differ. The difference is the programming model and the maintenance surface.

The IDF example is a standalone application: you work in the IDF build system, you own the callback wiring, and you extend it yourself. ESP32-A2DP is a library you include, with the wiring already done and a constructor that takes your output stream. For an Arduino user the second is less work; for a team already building an IDF product, the example may be easier to fold into an existing CMake tree, and pulling a third-party library into a firmware image is a decision that needs its own justification.

The other practical difference is the example catalogue. The repository ships separate sketches for reconnect, disconnect-reconnect, auto shutdown, RSSI, queued playback, 32-bit output, BLE coexistence, metadata, playing status callbacks, and a sender with autoreconnect and AVRCP. You can read those instead of deriving the same behaviour from the IDF API. That catalogue is the strongest argument for the library over the raw example, more than the wrapper itself.

## Maintenance, licensing and what an upgrade costs

The repository is not archived, and the last push was on 2026-09-22, days before this writing. Releases are frequent: v1.8.9 in February 2026, v1.8.10 in April 2026, v1.8.11 in May 2026. The v1.8.9 release notes tie that version to Arduino ESP32 Core 3.3.7 support, which is the pattern to expect: the library tracks Arduino core releases, and each core bump can move the I2S API under it.

That is the upgrade cost in one sentence. You are not only upgrading this library, you are upgrading the core, and the core is what retires APIs. Pinning your core version and your library version together is the only way to keep a working sketch stable, and moving forward means re-testing the I2S configuration path.

The licence is Apache-2.0, which is permissive and includes an explicit patent grant. It does not impose copyleft on your sketch. It does require that you keep the licence and notices with any redistribution, and the usual disclaimer of warranty applies. This is a description of the terms, not legal advice; if you are shipping a product, have your own counsel read the file.

Documentation lives in the README, a docs directory, a Doxyfile and a project wiki referenced from the README. There is no homepage field, so the repository and the wiki are the canonical sources.

## Conclusion

Adopt it if you are building an ESP32 Bluetooth speaker, headphone adapter or A2DP sender and you are already inside the Arduino, PlatformIO or Espressif IDF toolchain. Do not adopt it if you need HFP or HSP headset profiles, or if you expect the library to handle resampling and audio processing on its own. Before wiring anything, confirm the I2S pin defaults in your installed version (bck 14, ws 15, data 22 with AudioTools) and check whether you are on Arduino core 3.x, because the legacy I2S API is not available there.

## FAQ

### Can I use an ESP32 as a Bluetooth audio receiver with ESP32-A2DP?

Yes. The A2DP sink mode is the library's main use case, and the README describes building your own Bluetooth speaker with it. The decoded output is a PCM stream at 44.1 kHz, two channels, 16-bit, which you route to I2S or the internal DAC.

### What does A2DP stand for in ESP32-A2DP?

A2DP is the Bluetooth profile for audio streaming, and the README notes that it only provides audio streaming. The library also supports AVRCP alongside A2DP, but not HFP, HSP or standalone AVRCP without A2DP.

### Is A2DP good quality on the ESP32?

The README does not make a quality claim. It states that the stream is decoded from SBC format and delivered as 44.1 kHz, two-channel, 16-bit PCM, so the ceiling is set by the SBC codec and by the DAC you connect.

### What is A2DP in Bluetooth?

It is the Bluetooth profile the library is built around, and the README describes it as providing audio streaming only. The library adds AVRCP support alongside it for metadata and playback control.

## Sources

- [Issues](https://github.com/pschatzmann/ESP32-A2DP/issues)
- [License: Apache-2.0](https://github.com/pschatzmann/ESP32-A2DP/blob/main/LICENSE)
- [pschatzmann/ESP32-A2DP on GitHub](https://github.com/pschatzmann/ESP32-A2DP)
- [README](https://github.com/pschatzmann/ESP32-A2DP/blob/main/README.md)
- [Releases](https://github.com/pschatzmann/ESP32-A2DP/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/pschatzmann-esp32-a2dp
