In my Arduino ESP32-A2DP library I am providing some very simple examples that show how to transform the ESP32 into a A2DP source and transmit sound to a Bluetooth Sink (e.g. some Bluetooth Speakers).

I had quite a few questions on how to do this with files, microphones and I2S as input. So I started a small “glue” project which provides some additional audio tools. I plan to provide plenty of additional examples and some more advances scenarios.

In my last blog, I showed how to read audio data from I2S using a digital microphone. In this blog I provide the RAW audio data directly from a file on a SD card! In some future posts, I will demonstrate how to process different file formats.

Streaming from Files on a SD card

The Sketch

Here is the Arduino Sketch that you can use with an I2S audio source:

#include "BluetoothA2DPSource.h"
#include <SPI.h>
#include <SD.h>

BluetoothA2DPSource a2dp_source;
File sound_file;
const char* file_name = "/audio.raw";
const int sd_ss_pin = 5;
const int frame_size_bytes = sizeof(int16_t) * 2;

// callback used by A2DP to provide the sound data
int32_t get_sound_data(Channels* data, int32_t len) {
  // the data in the file must be in int16 with 2 channels 
  size_t result_len_bytes = sound_file.read((uint8_t*)data, len * frame_size_bytes );
  // result is in number of frames
  int32_t result_len = result_len_bytes / frame_size_bytes;
  ESP_LOGD("get_sound_data", "%d -> %d",len );
  return result_len;
}

// Arduino Setup
void setup(void) {
  Serial.begin(115200);

  // Setup SD and open file
  SD.begin(sd_ss_pin);
  sound_file = SD.open(file_name, FILE_READ);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.start("MyMusic", get_sound_data);  
}

// Arduino loop - repeated processing 
void loop() {
}

The get_sound_data() callback is quite straight forward: We are directly reading the raw audio file from the SD card. The audio file must be available using 16 bit integers with 2 channels. In the callback we just need to translate between number of frames and bytes!

Audacity might help you to create the right file format: export with the file name audio.raw as RAW signed 16 bit PCM and copy it to the SD card. In my example I was using the file audio.raw.

The Device

sd

The SD module is connected with the help of the SPI bus

Pins

SD ESP32
VCC 5V
GND GND
CS CS GP5
SCK SCK GP18
MOSI MOSI GP23
MISO MISO GP19

Source Code

Both the project and the example can be found on Github.


28 Comments

nilesh · 6. October 2024 at 18:23

WiFi and ESP Now coexist limit 250 instead 1024. But not seen MP3DecoderHelix decoder; implementation in given examples . Would you add please

    pschatzmann · 6. October 2024 at 18:39

    You can find several A2DP examples in the AudioTools project that use a mp3 decoder:
    – basic-player-a2dp
    – player-sdfat-a2dp
    If you read the Wiki about Codecs there is now the functionality of ‘Decoding on the Input Side’. So you can just try to read from the EncodedAudioStream to get the decoded information in the callback. I never tried this myself however in related with A2DP…

    Nilesh · 8. October 2024 at 11:08

    I mean to say ESP Now streaming mp3 or url from internet radio to Other ESP I2S Speaker , but I am not able to decode it correctly passing out as stream to other side. Thanks certainly I am checking Wiki about Codecs .

Jonas · 9. July 2024 at 9:40

Hey,
Thank you so much for the fantastic explanation of using A2DP. You wrote that Bluetooth and WiFi can not be used simultaneousley. So I bought an extern HC-05-Bluetooth-Modul. It`s possible to use that extern Bluetooth-modul instead of the on-Board-Bluetooth from ESP32? Which settings do I have to change to make it work?

Thanks in advance!

    pschatzmann · 9. July 2024 at 9:57

    No, the HC-05 only supports a Serial communication protocol and not Audio!

    You could replace my library with a A2DP Module or you could try to connect a separate WiFi module, but I am not sure if the speed is sufficient for audio.

      Jonas · 9. July 2024 at 11:08

      Thanks for the quick reply and the suggestion. I will try another solution, cause I need the whole WiFi-speed and performance.

Litchi · 27. May 2024 at 20:54

Hi There! I love your project. i have 1 problem tho. is there any way how i can use the dfplayer mini for this? thanks in regard.

Geoff · 18. May 2023 at 15:46

Not sure if I’ve missed something, but how do I pair my bluetooth speaker with the esp32?

    pschatzmann · 18. May 2023 at 15:50

    This happens automatically when you call the a2dp_source.start() method: That’s the reason why you need to specify the bluetooth name!

Sebastian · 26. April 2023 at 13:47

Hey is there any way to change the pins ?. I use a TTGO T ESP32 where all the SPI pins are used for the screen?

    pschatzmann · 26. April 2023 at 14:16

    Sure, just read the Readme of the project!

Bill · 22. April 2023 at 9:34

Hello again. I am using your code with success. It is for my bedroom clock which does many things. My problem is that I am using 2 ESP32 Vroom modules. ESP01 to access the Wifi to get some sensor values, etc. ESP02 to read my SDcard music, sending it to a BT speaker. I cannot do both because the antenna cannot share BT & WiFi at the same time. I tried looking at ESP32 coexistence but I have trouble understanding what I need to do. Do you have any suggestions? The link https://drive.google.com/drive/folders/13K31VNKsQOInrO46VaHUR54ss-hIHPTB?usp=sharing shows my 2 ESP32 codes.

Thanks in advance!

Sebastian · 14. April 2023 at 4:05

Hey there.

I was doing a project with the BluetoothA2DPSource library, but I need to play an audio after playing the first one and using the a2dp_source.start function again gives an error. So I looked up the arduino audio tools class and I saw in the examples that there is an example called “A Simple SdFat Audio Player with Output to Bluetooth Speaker” that might be useful for my project, the thing is that it seems that the example is not available anymore, can you help me with that?

    pschatzmann · 14. April 2023 at 7:06

    The url has been updated

Sebastian · 8. March 2023 at 23:43

Hey there.

I´m having some issues trying to connect a ESP32 board with some earbuds, have you try doing that?

Bill · 16. December 2022 at 4:09

hello:
Cannot compile. I have the following message can you help? Thanks:

BluetoothSD:13:24: error: ‘Channels’ was not declared in this scope
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~~~~~
D:\temp\BluetoothSD\BluetoothSD.ino:13:24: note: suggested alternative: ‘ChannelInfo’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~~~~~
ChannelInfo
BluetoothSD:13:34: error: ‘data’ was not declared in this scope
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~
D:\temp\BluetoothSD\BluetoothSD.ino:13:34: note: suggested alternative: ‘atan’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~
atan
BluetoothSD:13:48: error: expected primary-expression before ‘len’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~
BluetoothSD:13:51: error: expression list treated as compound expression in initializer [-fpermissive]
int32_t get_sound_data(Channels* data, int32_t len) {
^
BluetoothSD:13:24: error: redefinition of ‘int32_t get_sound_data’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~~~~~
D:\temp\BluetoothSD\BluetoothSD.ino:13:9: note: ‘int32_t get_sound_data’ previously defined here
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~~~~~~~~~~~
BluetoothSD:13:24: error: ‘Channels’ was not declared in this scope
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~~~~~
D:\temp\BluetoothSD\BluetoothSD.ino:13:24: note: suggested alternative: ‘ChannelInfo’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~~~~~
ChannelInfo
BluetoothSD:13:34: error: ‘data’ was not declared in this scope
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~
D:\temp\BluetoothSD\BluetoothSD.ino:13:34: note: suggested alternative: ‘atan’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~~
atan
BluetoothSD:13:48: error: expected primary-expression before ‘len’
int32_t get_sound_data(Channels* data, int32_t len) {
^~~
D:\temp\BluetoothSD\BluetoothSD.ino: In function ‘void setup()’:
BluetoothSD:32:32: error: invalid conversion from ‘int32_t’ {aka ‘int’} to ‘music_data_channels_cb_t’ {aka ‘int ()(Frame, int)’} [-fpermissive]
a2dp_source.start(“MyMusic”, get_sound_data);
^~~~~~~~~~~~~~
In file included from D:\temp\BluetoothSD\BluetoothSD.ino:2:
C:\Users\chris\Documents\Arduino\libraries\libraries\ESP32-A2DP-main\src/BluetoothA2DPSource.h:95:67: note: initializing argument 2 of ‘virtual void BluetoothA2DPSource::start(const char*, music_data_channels_cb_t)’
virtual void start(const char* name, music_data_channels_cb_t callback = NULL);
^
Plusieurs bibliothèque trouvées pour “SD.h”
Utilisé : C:\Users\chris\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.5\libraries\SD
Non utilisé : C:\Program Files (x86)\Arduino\libraries\SD
exit status 1
‘Channels’ was not declared in this scope

    pschatzmann · 16. December 2022 at 4:17

    I don’t understand why you don’t just use the example that is provided in Arduino or the indicated in the link to the updated code on Github!
    As stated in my last answer the Blog represents only what was valid at the time of posting!

Anonymous · 15. December 2022 at 8:17

Cannot compile using ESP32-A2DP V1.7.1

Error msg: ‘Channels’ was not declared in this scope

Do you have a suggestion?

Thanks
Bill
billglass38@gmail.com

    pschatzmann · 15. December 2022 at 9:40

    Yes, this is a pretty old blog which documents how things were at that point of time!
    Just use the updated examples that you can find in Github or in the Arduino examples…

      Bill · 16. December 2022 at 5:17

      I don’t use Github but I seem to have your latest ESP32-A2DP library. In my Arduino examples I don’t have streaming from SD card. Only from PROGMEM (piano) or sine generator. They both work fine.

        pschatzmann · 16. December 2022 at 8:58

        Just click on the link in the blog under the Github title and you will see the updated example.
        It’s as simple a that…
        ps. I committed some correction to the A2DP Library that will accept even this obsolete syntax…

          Bill · 16. December 2022 at 12:22

          Thank you very much! I will try it.

          Bill Glass · 29. December 2022 at 10:38

          yes it works!! thank you

Anonymous · 26. November 2021 at 16:41

Hi there, how do you play another file after playing the first? Running a2dp_source.start again gives an error

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *