Skip to content

Design Overview

pschatzmann edited this page Oct 3, 2026 · 24 revisions

This page is for you if you want to understand how the library works, or if you want to adapt or extend it.

Design Principles

  • 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.

Supported Frameworks

Because the core only depends on ESP-IDF, the library can be used with:

This restriction however limits the provided examples: most of them use the AudioTools library for the audio output.

Classes

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

Callbacks and Tasks

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.

Audio Data Flow

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.

Configuration

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).

Contributing

If you extend the functionality, please let me know and share your work with the community by submitting a pull request.

Clone this wiki locally