Repository navigation
Design Overview
This page is for you if you want to understand how the library works, or if you want to adapt or extend it.
- The core is only dependent on the ESP-IDF framework (which is also provided by the Arduino ESP32 core). There are no other mandatory dependencies, and this includes the Arduino API! The AudioTools library is optional: it is recommended for the audio output and it is needed for the custom codecs.
- All state is managed with class variables, and there should not be any need for global variables.
- The method names use the snake_case convention.
- All methods are virtual: if you have project specific requirements that can't be resolved with callbacks, you can create your own subclass and override the method that you want to change or extend. There is usually no need to change the library code.
Because the core only depends on ESP-IDF, the library can be used with:
- Arduino
- PlatformIO
- Espressif IDF
This restriction however limits the provided examples: most of them use the AudioTools library for the audio output.
| Class | Purpose |
|---|---|
| BluetoothA2DPCommon | Common base class: Bluetooth initialization, connection handling, auto reconnect, volume and the application task |
| BluetoothA2DPSink | Receives audio (e.g. from a phone) and writes the PCM data to an output; AVRC metadata and commands |
| BluetoothA2DPSinkQueued | Sink which decouples the output with a ringbuffer and a separate output task |
| BluetoothA2DPSource | Sends audio to a speaker or headphones; device discovery and selection |
| BluetoothA2DPOutput | Output abstraction of the sink, with implementations for AudioTools, Arduino Print and the legacy I2S API |
| A2DPVolumeControl | Volume curves, which can be replaced with your own implementation |
| Codecs | Optional decoders (sink) and encoders (source) for AAC or your own SBC implementation |
The ESP-IDF Bluetooth stack reports its events with C callbacks. These are forwarded to the C++ objects with small ccall_... functions that use a static pointer to the active object. Therefore you can have only one sink and one source object at a time.
The callbacks are called in the task of the Bluetooth stack, which must not be blocked. Most events are therefore copied into a queue and processed in a separate application task (BtAppT): this is where the state machines, the user callbacks and the AVRC handling are executed. You can adjust this task with set_event_queue_size(), set_event_stack_size() and set_task_priority().
Depending on the configuration, the following additional tasks are used:
- BluetoothA2DPSinkQueued: an output task (BtI2STask) that writes the audio from the ringbuffer to the output.
- Sink with custom decoders: a decode task that decodes the received encoded audio.
- Source with custom encoders: an encode task that reads the PCM data from your data callback, encodes it and sends it in real time.
A2DP Sink
- By default, ESP-IDF decodes the SBC data and provides the PCM data in a callback. The sink applies the volume, calls your stream reader callbacks and writes the data to the output.
- With custom decoders, ESP-IDF provides the encoded data. The decode task decodes it and passes the PCM data on through the same path, so the output, the volume and the callbacks work as before.
The output is done with the help of the output classes. Initially this project contained its own I2S logic: this has been deprecated in favor of the AudioTools (see Audio Output). Alternatively you can use any Arduino Print, or the data callback for a version independent, portable way to access the audio.
A2DP Source
- By default, ESP-IDF requests the PCM data with a callback and encodes it with its internal SBC encoder. The source gets the data from your data callback or Stream and applies the volume.
- With custom encoders, the encode task requests the PCM data from the same data callbacks, encodes it and sends it to ESP-IDF.
The compile time settings are defined in config.h. They all have a default and can be overridden with a compiler define:
| Define | Description |
|---|---|
| A2DP_I2S_AUDIOTOOLS | Output with the AudioTools: active when the AudioTools library is available |
| A2DP_LEGACY_I2S_SUPPORT | Legacy I2S API: only active for ESP-IDF < 5.0 |
| A2DP_SPP_SUPPORT | Serial Port Profile support |
| A2DP_MANAGED_DECODER_SUPPORTED | Custom decoders: ESP-IDF >= 5.5 with the AudioTools |
| A2DP_MANAGED_ENCODER_SUPPORTED | Custom encoders: ESP-IDF >= 5.5 with the AudioTools |
config.h also contains the settings of the decode and encode tasks (e.g. stack size and priority).
If you extend the functionality, please let me know and share your work with the community by submitting a pull request.
Getting Started
A2DP Sink (Receiver)
- Overview & Examples
- Audio Output & DACs
- Sample Rate
- Volume Control
- AVRC: Controlling your Phone
- Metadata & Notifications
- Performance Optimizations
A2DP Source (Sender)
Common
Background
Help