pschatzmann / arduino-audio-tools

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

"basic-generator-a2dp" example not working #1692

Closed y-nk closed 1 week ago

y-nk commented 1 week ago

Problem Description

As stated in the wiki, I've started my exploration by using examples. I've changed the name of the device, also changed log level to ::Info as recommended. Device got automatically connected at boot, which I found was a good thing, but then no sound came out.

I've added a simple Serial.println() in the get_sound_data function and i can't ~see any lines pulled~ hear any sound from speaker, so i guess the ~stream~ example is broken?

Device Description

I'm using ESP Wroom, esp32 core 3.0.4 (Wiki mentions 2.0.17 being latest, is v3 supported?). Board is unknown chinese but I found some ref online mentioning "hw-394" and that the profile to use should be ESP32-WROOM-DA board.

Sketch

/**
 * @file basic-generator-a2dp.ino
 * @author Phil Schatzmann
 * @brief We send a test sine signal to a bluetooth speaker
 * @copyright GPLv3
*/

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

const char* name = "Bose Color II SoundLink";                        // Replace with your bluetooth speaker name  
SineWaveGenerator<int16_t> sineWave(32000);               // subclass of SoundGenerator, set max amplitude (=volume)
GeneratedSoundStream<int16_t> in_stream(sineWave);        // Stream generated from sine wave
BluetoothA2DPSource a2dp_source;                          // A2DP Sender

// callback used by A2DP to provide the sound data - usually len is 128 * 2 channel int16 frames
int32_t get_sound_data(uint8_t * data, int32_t len) {
  Serial.println("get_sound_data");
  return in_stream.readBytes((uint8_t*)data, len);
}

// Arduino Setup
void setup(void) {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);

  // start input 
  auto cfg = in_stream.defaultConfig();
  cfg.bits_per_sample = 16;
  cfg.channels = 2;
  cfg.sample_rate = 44100;
  in_stream.begin(cfg);
  sineWave.begin(cfg, N_B4);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.set_auto_reconnect(true);
  a2dp_source.start_raw(name, get_sound_data);  
}

// Arduino loop - repeated processing 
void loop() {
  delay(1000);
}

Other Steps to Reproduce

Build and run

What is your development environment

Arduino IDE (2.3.2)

I have checked existing issues, discussions and online documentation

pschatzmann commented 1 week ago

I don't see any reason why v3.0.4 should not work.

The A2DP library uses the Arduino ESP32 logger, so if you want to see any warnings or errors you need to activate the logger in the Arduino Tools menu

y-nk commented 1 week ago

@pschatzmann

EDIT2: I see the logs, but there's just no logging appended after some point:

17:03:04.680 -> rst:0x1 (POWERON_RESET),boot:0x12 (SPI_FAST_FLASH_BOOT)
17:03:04.680 -> configsip: 0, SPIWP:0xee
17:03:04.680 -> clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
17:03:04.680 -> mode:DIO, clock div:1
17:03:04.680 -> load:0x3fff0030,len:4832
17:03:04.680 -> load:0x40078000,len:16460
17:03:04.722 -> load:0x40080400,len:4
17:03:04.722 -> load:0x40080404,len:3504
17:03:04.722 -> entry 0x400805cc
17:03:05.339 -> [I] SoundGenerator.h : 156 - SineWaveGenerator::begin(channels=2, sample_rate=44100)
17:03:05.339 -> [I] SoundGenerator.h : 149 - bool audio_tools::SineWaveGenerator<T>::begin() [with T = short int]
17:03:05.339 -> [I] AudioTypes.h : 128 - SoundGenerator: sample_rate: 44100 / channels: 2 / bits_per_sample: 16
17:03:05.371 -> [I] Buffers.h : 372 - resize: 4
17:03:05.371 -> [I] SoundGenerator.h : 164 - SineWaveGenerator::begin(channels=2, sample_rate=44100, frequency=493.88)
17:03:05.371 -> [I] SoundGenerator.h : 149 - bool audio_tools::SineWaveGenerator<T>::begin() [with T = short int]
17:03:05.371 -> [I] AudioTypes.h : 128 - SoundGenerator: sample_rate: 44100 / channels: 2 / bits_per_sample: 16
17:03:05.404 -> [I] SoundGenerator.h : 192 - setFrequency: 493.88
17:03:05.404 -> [I] SoundGenerator.h : 193 - active: true
17:03:05.404 -> starting A2DP...

That is a typical log chunk i see after booting the ESP32. I still wonder what could be a reason why there's no audible output then πŸ€”?

EDIT: As suggested in the wiki I've tried to add a visualiser (i guess over Serial plotter) in my sketch, but nothing is showing on the plotter (so far). serial is also totally silent after "starting A2DP..." print.

/**
 * @file basic-generator-a2dp.ino
 * @author Phil Schatzmann
 * @brief We send a test sine signal to a bluetooth speaker
 * @copyright GPLv3
*/

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

const char* name = "Bose Color II SoundLink";                        // Replace with your bluetooth speaker name  
+AudioInfo info(44100, 2, 16);
SineWaveGenerator<int16_t> sineWave(32000);               // subclass of SoundGenerator, set max amplitude (=volume)
GeneratedSoundStream<int16_t> in_stream(sineWave);        // Stream generated from sine wave
BluetoothA2DPSource a2dp_source;                          // A2DP Sender
+CsvOutput<int32_t> csvStream(Serial);
+StreamCopy copier(csvStream, in_stream); // copy i2sStream to csvStream

// callback used by A2DP to provide the sound data - usually len is 128 * 2 channel int16 frames
int32_t get_sound_data(uint8_t * data, int32_t len) {
-  return in_stream.readBytes((uint8_t*)data, len);
+  int32_t result = in_stream.readBytes(data, len);
+  LOGI("get_sound_data %d->%d",len, result);
+  return result;
}

// Arduino Setup
void setup(void) {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);

  // start input 
  auto cfg = in_stream.defaultConfig();
  cfg.bits_per_sample = 16;
  cfg.channels = 2;
  cfg.sample_rate = 44100;
  in_stream.begin(cfg);
  sineWave.begin(cfg, N_B4);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.set_auto_reconnect(true);
  a2dp_source.start_raw(name, get_sound_data);  
+  csvStream.begin(info);
}

// Arduino loop - repeated processing 
void loop() {
  delay(1000);
}

With this i'll see the LOGI call properly flooding my logger with [I] basic-generator-a2dp.ino : 23 - get_sound_data 512->512 lines but still no lines shown on the plotter or heard on the speaker.

y-nk commented 1 week ago
image

I have found that maybe the issue is that the data is not playable. I'm now looking at an example to build an mp3 encoded sine wave using your mp3 encoder https://github.com/pschatzmann/arduino-liblame.

y-nk commented 1 week ago

It took me a couple of hours, but i got a compiling sketch (posting this in case somebody finds it useful).

#include <AudioTools.h>
#include <AudioLibs/A2DPStream.h>
#include <AudioCodecs/CodecMP3LAME.h>

const char* name = "Bose Color II SoundLink"; 

SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> in_stream(sineWave);

BufferedStream out_stream(1024);

AudioInfo info(44100, 2, 16);
MP3EncoderLAME mp3;
EncodedAudioOutput enc_stream(&out_stream, &mp3);
Throttle throttle(enc_stream);

static int frame_size = 498;
StreamCopy copier(throttle, in_stream, frame_size); 

BluetoothA2DPSource a2dp_source;

// callback used by A2DP to provide the sound data - usually len is 128 * 2 channel int16 frames
int32_t get_sound_data(uint8_t * data, int32_t len) {
  int32_t result = out_stream.readBytes((uint8_t*)data, len);
  // LOGI("get_sound_data %d->%d",len, result);
  return result;
}

// Arduino Setup
void setup(void) {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);

  throttle.begin(info);
  in_stream.begin(info);
  sineWave.begin(info, N_B4);
  enc_stream.begin(info);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.set_auto_reconnect(true);
  a2dp_source.start_raw(name, get_sound_data);  
}

// Arduino loop - repeated processing 
void loop() {
  copier.copy();
}

The flow i aimed to design was:

Sine β†’ SineStream β†’ Copier β†’ Throttle β†’ EncodedAudioOutput β†’ BufferedStream β†’ BluetoothA2DPSource

The problem i was not expecting was my board doesn't have SPIRAM which is required by the LAME encoder. This was easily spotted in the logs, hopefully.

[Error] lame.c : 2792 - calloc(1,85840) -> 0x0 
available MALLOC_CAP_8BIT: 110580 / MALLOC_CAP_32BIT: 110580  / MALLOC_CAP_SPIRAM: 0

I will switch to AAC/SBC and see if sound shows up, just to confirm that it was a codec issue.

y-nk commented 1 week ago

Weirdly, a drop-in replacement with the SBC does not produce sound either:

#include <AudioTools.h>
#include <AudioLibs/A2DPStream.h>
#include <AudioCodecs/CodecSBC.h>

const char* name = "Bose Color II SoundLink"; 

SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> in_stream(sineWave);

BufferedStream out_stream(1024);

AudioInfo info(44100, 2, 16);
SBCEncoder encoder;
EncodedAudioOutput enc_stream(&out_stream, &encoder);
Throttle throttle(enc_stream);

static int frame_size = 498;
StreamCopy copier(throttle, in_stream, frame_size); 

BluetoothA2DPSource a2dp_source;

int32_t get_sound_data(uint8_t * data, int32_t len) {
  int32_t result = out_stream.readBytes((uint8_t*)data, len);
  // LOGI("get_sound_data %d->%d",len, result);
  return result;
}

// Arduino Setup
void setup(void) {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);

  Serial.println("Building pipeline...");
  throttle.begin(info);
  in_stream.begin(info);
  sineWave.begin(info, N_B4);
  enc_stream.begin(info);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.set_auto_reconnect(true);
  a2dp_source.start_raw(name, get_sound_data);  
}

// Arduino loop - repeated processing 
void loop() {
  copier.copy();
}

we can see the copier gets some data in/out: [I] StreamCopy.h : 158 - StreamCopy::copy 498 -> 496 -> 496 bytes - in 1 hops but nothing further. either it's throttled to infinity, or the encoder does something else.

It's quite hard to debug what's happening in a pipeline, so far there's no way for me to know/discover what's happening. I'll keep hacking around until i get results.

y-nk commented 1 week ago

Dropping the throttler does not help further:

#include <AudioTools.h>
#include <AudioLibs/A2DPStream.h>
#include <AudioCodecs/CodecSBC.h>

const char* name = "Bose Color II SoundLink"; 

SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> in_stream(sineWave);

BufferedStream out_stream(1024);

AudioInfo info(44100, 2, 16);
SBCEncoder encoder;
EncodedAudioOutput enc_stream(&out_stream, &encoder);

static int frame_size = 498;
StreamCopy copier(enc_stream, in_stream, frame_size); 

BluetoothA2DPSource a2dp_source;

int32_t get_sound_data(uint8_t * data, int32_t len) {
  int32_t result = out_stream.readBytes((uint8_t*)data, len);
  // LOGI("get_sound_data %d->%d",len, result);
  return result;
}

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

  Serial.println("Building pipeline...");
  in_stream.begin(info);
  sineWave.begin(info, N_B4);
  enc_stream.begin(info);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.set_auto_reconnect(true);
  a2dp_source.start_raw(name, get_sound_data);  
}

// Arduino loop - repeated processing 
void loop() {
  copier.copy();
}
y-nk commented 1 week ago

I've gone to an easier script. Due to memory limitation i cannot upload the full StarWars30.h content, so i had to split it in 2 (i literally split the array in 2, final size is 660640).

Now doing:

#include "StarWars30.h"
#include <AudioTools.h>
#include <AudioLibs/A2DPStream.h>

const char* name = "Bose Color II SoundLink"; 

MemoryStream music(StarWars30_raw, StarWars30_raw_len);
BluetoothA2DPSource a2dp_source;

int32_t get_sound_data(uint8_t * data, int32_t len) {
  int32_t result = music.readBytes((uint8_t*)data, len);
  LOGI("get_sound_data %d->%d",len, result);
  return result;
}

// Arduino Setup
void setup(void) {
  Serial.begin(115200);
  AudioLogger::instance().begin(Serial, AudioLogger::Info);

  // start the bluetooth
  Serial.println("starting A2DP...");
  a2dp_source.set_auto_reconnect(true);
  a2dp_source.start_raw(name, get_sound_data);  
  music.begin();
}

void loop() {}

Logs show there's something wrong (i guess) since the result is always 0: [I] test_audio_tool.ino : 19 - get_sound_data 352->0

josef2600 commented 1 week ago

if i may,few questions.

  1. what is your output device? how do you get the sound out? are you using internal DAC or PWM or I2S? since i couldn't fined it.
  2. have you config the output correctly? because each device has its own settings. specially if you want to use I2S for output audio.
  3. and why would you change codecs for your start? that is a bad rabbit hole! you should never start with them. easiest setup is to do it by default with internal DAC. it has 2 for stereo.
y-nk commented 1 week ago

@josef2600

  1. My output device is a bluetooth speaker (the "Bose Color II SoundLink") which is connected by using BluetoothA2DPSource. There's no need for DAC/PWM/I2S, the BluetoothA2DPSource's target is the sink. (you can check the examples/communication/ad2p/basic_generator...)

  2. Settings are provided by BluetoothA2DPSource, you only need to pass the device name.

  3. I would expect the SineGenerator to create PCM values but as stated here the output device seems not to support PCM (only AAC, MP3 and SBC)

y-nk commented 1 week ago

@pschatzmann may i ask for some pointers at a solution? is the hardware an issue (board, speaker) or rather the software? and more importantly, how would i debug that? I had never succeeded so far at having a sine showing in the plotter.

pschatzmann commented 1 week ago

The Wiki contains a chapter on how to find this out: https://github.com/pschatzmann/arduino-audio-tools/wiki/It's-not-working

The ESP32 A2DP API only supports PCM data as input which will be converted to SBC internally. (As you can find in the Readme of the A2DP library).

Finally you never seemed to analyse the A2DP logs....

y-nk commented 1 week ago

@pschatzmann thank you and sorry for mentioning you. I'll follow your pointers and will update... until it works πŸ˜…

EDIT: if a2dp only supports PCM as input, then does that mean we can't send mp3s (stored on an sdcard)? or with additional decoder stream in between?

EDIT2: i'd wish to convert this into a discussion but i dont think i'm allowed to.