pschatzmann / arduino-audio-tools

Arduino Audio Tools (a powerful Audio library not only for Arduino)
GNU General Public License v3.0
1.47k stars 227 forks source link

A2DP to PWM help needed #6

Closed hchahine closed 3 years ago

hchahine commented 3 years ago

Hi,

I've got a ESP32 project where I read audio from a source (right now it's a simple WAV file located in flash) and then send it to a PWM ouput via the ESP32's ledc module.

So its 8bit unisigned WAV data TO 150kHz PWM who's duty cycle is updated at the sample rate of the audio file (40kHz).

This is to play clear audio on a tesla coil. I got it working pretty well right now via my own simple code. I now want to upgrade this so I can stream audio from bluetooth as the audio source via A2DP using your libraries.

I believe the audio-tools stream functions should do the job but I can't quite figure out how to connect the A2DP input stream to my own output PWM module. Or better yet, do it all from your libraries.

I'm aware there's a AudioPWM example but I can't get this work for ESP32 because I believe it's only support the Rpi Pico at the moment. The other output streams are only things like I2S and CSV/Serial.

How to do I get access to the A2DP data to then send to the PWM module OR better yet how do I simply stream the data as it comes in using your libraries? Something akin to a simple memcopy - even via a callback would do.

Any help would be appreciated.

pschatzmann commented 3 years ago

I suggest that you try AnalogAudioStream - this is using the built in DAC of of the ESP32.

hchahine commented 3 years ago

Thanks but I'm not sure the DAC will help me. Perhaps I haven't explained what I need properly. I just need the raw incoming digital audio data. My output has to be PWM as that's what the rest of my tesla coil circuit works with - An analog output won't work.

And I plan on extending the resolution of the output beyond 8 bit - possibly up to 10bit.

I just need access to the raw audio data coming from the A2DP source as digital data which I can then manipulate. E.g. Scale to my required resolution and convert to PWM for outputting to a GPIO.

pschatzmann commented 3 years ago

Have a look at the Accessing the Sink Data Stream with Callbacks in the Readme of the A2DP project. set_stream_reader is giving you access to the data...

There is no need to use the arduino-audio-tools project then. PS. I have planned to provide PWM output for other platforms but this might take quite some time to complete...

hchahine commented 3 years ago

OK thanks, I'll check that out again. I thought about being able to use it initially but wasn't sure what format that data comes in as I'm not too familiar with A2DP and it's data output - e.g. not sure if data is encoded or something or whether it's as simple as extracting raw data from the packet. I'll dig into it and hopefully see if I can extract what I need from within the callback.

Great set of tools here though, just discovered your libraries and I'm already thinking of a bunch of other project to do with them. Appreciate the work.

pschatzmann commented 3 years ago

Its just simple PCM data in signed 16 bits with 2 channels.

hchahine commented 3 years ago

Finally got it to work and functions reasonably well. Not super elegant or efficient but good enough to get the job done. Open to suggestions for improvement and optimization - particular ideas to avoid buffering and synchronization of timing with sample rate.

Entire working code below:

     /*  ESP32 Tesla Speaker
     *  -------------------
     *  Allows connection from a BT source and then sends PCM data to
     *  tesla coil driver for audio modulation
     *  
     *  Jun 2021
     *  H.Chahine
     */
    #include "BluetoothA2DPSink.h"

    BluetoothA2DPSink a2dp_sink;

    // Buffer to buffer incoming A2DP audio data
    unsigned char cirBbuf[40000];
    unsigned int head = 0;
    unsigned int tail = 0;

    // PWM pin on GPIO5
    #define pwmPin  5

    // Set PWM properties
    /*  
     *  RES | BITS | MAX_FREQ (kHz)
     *  ----|------|-------
     *  8   | 256  |  312.5
     *  9   | 512  |  156.25
     *  10  | 1024 |  78.125
     *  11  | 2048 |  39.0625
     */
    const int pwmChannel = 0;
    const int freq = 150000;
    const int resolution = 9;

    hw_timer_t * timer = NULL;
    portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

    // Timer Interrupt
    void IRAM_ATTR onTimer() {
      portENTER_CRITICAL_ISR(&timerMux);

      // Don't overrun buffer if depleted
      if(head != tail)
      {
        // Convert bytes to int16_t
        int16_t dat = ((int16_t)cirBbuf[head+1] << 8) | (int16_t)cirBbuf[head];

        // Convert from signed to unsigned and then rescale to defined resolution
        dat += 0x8000;
        dat >>= 16 - resolution;
        dat &= 0x01ff; 

        head += 2;
        if(head >= sizeof(cirBbuf))
          head = 0;
        ledcWrite(pwmChannel, (int)dat);
      }

      portEXIT_CRITICAL_ISR(&timerMux);
    }

    void setup() {
      // Set up PWM
      ledcSetup(pwmChannel, freq, resolution);
      ledcAttachPin(pwmPin, pwmChannel);

      // Attach timer int at sample rate
      timer = timerBegin(0, 1, true); // Timer at full 40Mhz, no prescaling
      timerAttachInterrupt(timer, &onTimer, true);
      timerAlarmWrite(timer, 907, true); // Timer fires at ~44100Hz [40Mhz / 907]
      timerAlarmEnable(timer);

      // Create A2DP link
      a2dp_sink.set_stream_reader(read_data_stream, false);
      a2dp_sink.start("Tesla_Speaker");  
    }

    // Gets A2DP data as it comes in
    void read_data_stream(const uint8_t *data, uint32_t length)
    {
      //Store only left channel data to buffer
      for(unsigned int x = 0; x < length; x += 4)
      {
        cirBbuf[tail++] = data[x];
        cirBbuf[tail++] = data[x+1];
        if(tail >= sizeof(cirBbuf))
          tail = 0; 
      }
    }

    void loop() {
    }
pschatzmann commented 3 years ago

Thanks for sharing. This will definitely help me to come up with an ESP32 AudioPWM class for arduino-audio-tools

pschatzmann commented 3 years ago

I just wanted to let you know that this library is supporting PWM now as well