Today I got very upset by a discussion where a user replaced the original question with a misleading conclusion that mixing an A2DP sink with a sine signal was not possible! Unfortunately he also ignored some helpful comments by some other users. So in order to avoid any confusion I had to delete the whole discussion!

Mixing the output from an A2DP sink with a sine signal can be done not only very easily but also very efficiently with the help of the AudioTools framework.

The project contains extensive documentation and examples that show

  • how to process A2DP
  • how to do mixing
  • how to generate and output a sine signal

So, in the end it is just up to the programmer to combine the different concepts (or examples) into one final solution.

Mixing audio means that you need to provide the input from multiple audio sources to be mixed to one audio result. As the documentation states you can mix on the input side or on the output side. Mixing on the input side is usually much more efficient, but there is a simple trick to reduce the memory usage of the output mixer: just split up the output into smaller chunks.

Here is a first working version:

#include "AudioTools.h"
#include "BluetoothA2DPSink.h"

AudioInfo info(44100, 2, 16);
BluetoothA2DPSink a2dp_sink;
I2SStream i2s;
SineWaveGenerator<int16_t> sineWave(10000);  // max amplitude of 10000
GeneratedSoundStream<int16_t> sound(sineWave); 
OutputMixer<int16_t> mixer(i2s, 2);  
const int buffer_size = 5000; 
uint8_t sound_buffer[buffer_size];

// Write data to mixer
void read_data_stream(const uint8_t *data, uint32_t length) {
  // write a2dp
  mixer.write(data, length);

  // write sine tone with identical length
  sound.readBytes(sound_buffer, length);
  mixer.write(sound_buffer, length);
}

void setup() {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Warning);

  // setup Output mixer with min necessary memory
  mixer.begin(buffer_size);

  // Register data callback
  a2dp_sink.set_stream_reader(read_data_stream, false);

  // Start Bluetooth Audio Receiver
  a2dp_sink.set_auto_reconnect(false);
  a2dp_sink.start("a2dp-i2s");

  // Update sample rate
  info.sample_rate = a2dp_sink.sample_rate();

  // start sine wave
  sineWave.begin(info, N_B4);

  // setup output
  auto cfg = i2s.defaultConfig();
  cfg.copyFrom(info);
  // cfg.pin_data = 23;
  cfg.buffer_count = 8;
  cfg.buffer_size = 256;
  i2s.begin(cfg);
}

void loop() { delay(100); } 

As you can see, there are no surprises here: We just write the A2DP signal and the sine signal (with the same length) to the output mixer.

This is just using quite a lot of RAM: the mixer allocates 2 * 5000 bytes. As stated above we can easily optimize this by reducing the buffer size. Here is a version that just uses 2 * 80 = 160 bytes:

const int buffer_size = 80;  // split up the output into small slices
uint8_t sound_buffer[buffer_size];

// Write data to mixer
void read_data_stream(const uint8_t *data, uint32_t length) {
  // To keep the mixing buffer small we split up the output into small slices
  int count = length / buffer_size + 1;
  for (int j = 0; j < count; j++) {
    const uint8_t *start = data + (j * buffer_size);
    const uint8_t *end = min(data + length, start + buffer_size);
    int len = end - start;
    if (len > 0) {
      // write a2dp slice
      mixer.write(start, len);

      // write sine tone with identical length
      sound.readBytes(sound_buffer, len);
      mixer.write(sound_buffer, len);
    }
  }
}

So, the only complexity comes from the memory optimization which is splitting the output into smaller slices!

In order simplify the processing and minimize the risk of adding any errors I have added the new Slice class:

const int buffer_size = 80;  // split up the output into small slices
uint8_t sound_buffer[buffer_size];

// Write data to mixer
void read_data_stream(const uint8_t *data, uint32_t length) {
  // To keep the mixing buffer small we split up the output into small slices
  Slice<uint8_t> slice(data, length);
  for (int j = 0; j < slice.slices(buffer_size); j++) {
      // write j'th a2dp slice
      Slice<uint8_t> out = slice.slice(buffer_size, j);
      mixer.write(out.data(), out.size());

      // write sine tone with identical length
      sound.readBytes(sound_buffer, out.size());
      mixer.write(sound_buffer, out.size());
    }
  }
}

Of cause you can use this approach to mix the input not only from a sine signal but from any other audio source


0 Comments

Leave a Reply

Avatar placeholder

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