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 use an analog microphone. The quality of the sound however is much better if you use a digital microphone that provides the data via I2S.

Digital I2S Microphones

The Sketch

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

#include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h"

using namespace audio_tools;  

/**
 * @brief We use a ADS1015 I2S microphone as input and send the data to A2DP
 * Unfortunatly the data type from the microphone (int32_t)  does not match with the required data type by A2DP (int16_t),
 * so we need to convert.
 */ 

BluetoothA2DPSource a2dp_source;
I2S<int32_t> i2s;
ChannelConverter<int32_t> converter(&convertFrom32To16);
ConverterFillLeftAndRight<int32_t> bothChannels;
const size_t max_buffer_len = 1024;
int32_t buffer[max_buffer_len][2];

// callback used by A2DP to provide the sound data
int32_t get_sound_data(Channels* data, int32_t len) {
   size_t req_len = min(max_buffer_len,(size_t) len);

   // the microphone provides data in int32_t -> we read it into the buffer of int32_t data
   size_t result_len = i2s.read(buffer, req_len);

   // we have data only in 1 channel but we want to fill both
   bothChannels.convert(buffer, result_len);

   // convert buffer to int16 for A2DP
   converter.convert(buffer, data, result_len);
   return result_len;
}

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

  // start i2s input with default configuration
  Serial.println("starting I2S...");
  i2s.begin(i2s.defaultConfig(RX_MODE));

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

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

We implement a A2DP source: We stream the sound input which we read in from the I2S interface to a A2DP sink. We can use any device which provides the sound data via I2S. In order to test the functionality we use the INMP441 microphone.

We use the I2S class which just wraps the native ESP32 I2S calls. The INMP441 provides the data for one channel only. The ConverterFillLeftAndRight class makes sure that we have sound on both channels and finally A2DP requires int16_t data – so we need to convert it using the ChannelConverter class;

The Device

INMP441

The INMP441 is a high-performance, low power, digital-output, omnidirectional MEMS microphone with a bottom port. The complete INMP441 solution consists of a MEMS sensor, signal conditioning, an analog-to-digital converter, anti-aliasing filters, power management, and an industry-standard 24-bit I²S interface. The I²S interface allows the INMP441 to connect directly to digital processors, such as DSPs and microcontrollers, without the need for an audio codec in the system.

Pins

INMP441 ESP32
VDD 3.3
GND GND
SD IN (GPIO32)
L/R GND
WS WS (GPIO15)
SCK BCK (GPIO14)

SCK: Serial data clock for I²S interface
WS: Select serial data words for the I²S interface
L/R: Left / right channel selection
When set to low, the microphone emits signals on the left channel of the I²S frame.
When the high level is set, the microphone will send signals on the right channel.
ExSD: Serial data output of the I²S interface
VCC: input power 1.8V to 3.3V
GND: Power ground

Source Code

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

Final Comments: Please note that the project is under active development and that things might change. If you want to try it out please use the version from the example directory!


27 Comments

Mark R McCornack · 18. October 2023 at 21:44

Hello Phil. Amazing site! I have what I think is a fairly simple implementation, but I am missing some basic pieces. Perhaps you can lead me in the right direction.
I have an INMP441 mic and my ESP board is a LILYGO T-display S3. Running fresh Arduino IDE and fresh audio-tools from git. Win11. What I want to do is take 2048,16-bit mono samples at 44.1Khz and put it into an array for processing, display some info on the TFT display, then grab another 2K data sample. Rinse and repeat.
First question: I did not see any sample source code that defines or allows the re-definition of the GPIO lines used from the ESP to the i2s device. Are there specific hard coded lines, or can I define these pins, and if so, how?
Second question: I’m not familiar with working with DMA, so I don’t know what will be involved there, but ultimately, what I want is the data in an array of 16 bit words. Any general advice or examples you can point me to?
Third: Defining DMA max buffer length and max buffer size are beyond my scope. All I want to do is periodically make a call to a routine that sucks off 2048 words (16-bit) from the i2s microphone into an array that I can then process.
Any guidance would be most appreciated, and I’ll keep looking through your impressive site!
Regards, Mark

    pschatzmann · 18. October 2023 at 21:53

    The AudioTools project comes with a lot of documentation and everything is already answered there (see the Wiki). It has evolved quite a bit, so what you see in this blog is not up to date any more.
    Nothing is hardcoded, but comes with some reasonable default values that you can accept or change.

    The project also contains the relevant dokumentation for the INMP441: you need to be careful with using 16 bits!

      Mark R McCornack · 19. October 2023 at 9:35

      Thanks! I was not aware of the wiki. I drilled down to the header file with the GPIO pin assignments (in AudioConfig.h) and got your stream_i2s_serial_16bit.ino code up and running on my ESP32 board. So, the basic API and hardware seems to be working and I will keep digging through the documentation to get the rest going. Many thanks!

Tej · 3. July 2023 at 8:04

Hi, I am trying to run i2s to a2dp stream example on lyrat board. I have changed the i2s pins according to lyrat. I am using Aux In port on lyrat to give Audio to board. but I am not getting. anything on sink side. I know, I am missing something. can you please help me to do this on lyrat.

Ibraheem · 5. January 2023 at 21:30

I would like to implement a2dp to i2s while having Bluetooth in the source mode. Is there any way I can do that?

    pschatzmann · 9. January 2023 at 21:18

    I am afraid that I don’t understand: Bluetooth to I2S is implemented by the A2DP Sink!
    If you want to use a A2DP Source you would read the audio data e.g. from I2S!

Anonymous · 27. July 2022 at 15:33

i ran into an error: BluetoothA2DP.h: No such file or directory
can someone please help?

    pschatzmann · 27. July 2022 at 17:38

    Please use the updated source coded as indicated in the last section!

Valter · 31. March 2022 at 21:01

Hello, I have available a ESP32 CAM could you please indicate me what is the INMP441 connection with it and how to set the GPIO I have to used ? Thanks

Loïc · 28. November 2021 at 13:38

You right! I delete manually the library in arduino. I download the ZIP of the Arduino audio tools on github and install on Arduino.

Now I could compilate !

Just I need to change I2S pins according to my existing hardware.

I add this :

i2s_pin_config_t my_pin_config = {
.bck_io_num = 26,
.ws_io_num = 25,
.data_out_num = 22,
.data_in_num = 17
};

I don’t know how modify this line to take effet :

i2sStream.begin(i2sStream.defaultConfig(RX_MODE));

I try replace .defaultConfig by my_pin_config but that not work.

Best regards

Loïc · 24. November 2021 at 17:58

The code don’t work.

Could you update it please ?

Thanks by advance

    pschatzmann · 24. November 2021 at 18:08

    Please use the examples in the example directory of the project. I will keep those up to date.
    I recommend the player and stream examples over the obsolete basic API

      Loïc · 28. November 2021 at 12:01

      I don’t find any working code for I2S to A2DP SOURCE in the project directory.

      When compiling the code bellow, the compiler report this error :

      ‘Channels’ was not declared in this scope

      Please, could you send me a working arduino code for I2S to A2DP SOURCE ?

      Best regards

          Loïc · 28. November 2021 at 12:40

          I get theses errors during compilation :

          In file included from G:\Mon Drive\Arduino\libraries\ESP32-A2DP-main\src/BluetoothA2DPCommon.h:41:0,
          from G:\Mon Drive\Arduino\libraries\ESP32-A2DP-main\src/BluetoothA2DPSink.h:17,
          from G:\Mon Drive\Arduino\libraries\arduino-audio-tools-main\src/AudioLibs/AudioA2DP.h:14,
          from G:\Mon Drive\Arduino\libraries\arduino-audio-tools-main\src/AudioTools.h:33,
          from G:\Mon Drive\Arduino\sketch_nov28d\sketch_nov28d.ino:13:
          G:\Mon Drive\Arduino\libraries\arduino-audio-tools-main\src/AudioExperiments/AudioDAC.h: In member function ‘virtual size_t audio_tools::OversamplingDAC::write(const uint8_t*, size_t)’:
          G:\Mon Drive\Arduino\libraries\ESP32-A2DP-main\src/SoundData.h:21:18: error: expected unqualified-id before ‘(‘ token
          #define min(a,b) (((a)<(b))?(a):(b))
          ^
          G:\Mon Drive\Arduino\libraries\arduino-audio-tools-main\src/AudioExperiments/AudioDAC.h:163:31: note: in expansion of macro 'min'
          int frames = std::min(size/(bytes_per_sample*info.channels), (size_t) availableFramesToWrite());
          ^
          G:\Mon Drive\Arduino\libraries\ESP32-A2DP-main\src/SoundData.h:21:18: error: expected unqualified-id before '(' token
          #define min(a,b) (((a)<(b))?(a):(b))
          ^
          G:\Mon Drive\Arduino\libraries\arduino-audio-tools-main\src/AudioExperiments/AudioDAC.h:166:31: note: in expansion of macro 'min'
          frames = std::min(size/(bytes_per_sample*info.channels), (size_t) availableFramesToWrite());

          pschatzmann · 28. November 2021 at 12:47

          Please make sure that you use the latest version of all libraries.
          If the problem persists you should file an issue on github!

          pschatzmann · 28. November 2021 at 13:57

          Please open an issue on github.
          This is not the right place for this

Andreas · 28. June 2021 at 12:40

This isn’t up to date anymore.
The “BluetoothA2DPSource.h” include is missing and some functions have different names.

    pschatzmann · 28. June 2021 at 14:24

    Thanks for letting me know. I have updated the blog to be in line with the latest changes.
    Please note however that I recommend to use the Stream API which is described in the Readme of the project. You would just copy the data from a I2SStream to a A2DPStream which is much less code!

      Andreas · 3. July 2021 at 17:08

      My pleasure, without your work I still would be hearing nothing.
      Regarding your recommendation, I know that it is in the sandbox, but I can’t quite see the cause of the problems.
      I made small changes, again the “AudioA2DP.h” was missing as well as some minor thing like type mismatches. I used some of your examples to compare but is still doesn’t work.
      I put in the code, since it is note so big.

      #include “Arduino.h”
      #include “AudioTools.h”
      #include “AudioA2DP.h”

      using namespace audio_tools;

      I2S i2s;
      I2SStream i2sStream; // Access I2S as stream
      A2DPStream a2dpStream = A2DPStream::instance(); // access A2DP as stream
      ChannelConverter converter(&convertFrom32To16);
      ConverterFillLeftAndRight bothChannels;
      StreamCopy copier(i2sStream, a2dpStream, 1024); // copy a2dpStream to i2sStream

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

      // start i2s input with default configuration
      Serial.println(“starting I2S…”);
      i2s.begin(i2s.defaultConfig(RX_MODE));

      // start the bluetooth
      Serial.println(“starting A2DP…”);
      a2dpStream.begin(TX_MODE, “MyMusic”);
      }

      // Arduino loop – copy data
      void loop()
      {
      if (a2dpStream)
      copier.copy();
      }

      Of course this is not a good place to discuss but I think the fastest

        pschatzmann · 4. July 2021 at 14:35

        Not sure what the issue exaclty is
        – I suggest to double check the sampling rates: Input and output sampling rates need to match. Bluetooth is also a little bit picky and if the sample rate is too small the data gets ignored.
        – Maybe you can split at the sketch and try to output to Serial1 as text to double check if you receive anything which makes sense

Mike · 5. May 2021 at 8:48

Hey Phil, this is excellent. I will test this asap and come back to you! Thanks a lot for your great work.

    pschatzmann · 5. May 2021 at 9:19

    I propose that you wait a couple of days. I am currently working to make things even simpler…

Leave a Reply

Avatar placeholder

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