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.


20 Comments

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 *