pschatzmann / arduino-audio-tools

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

Connecting/Disconnecting Bluetooth #235

Closed Denis-2021 closed 2 years ago

Denis-2021 commented 2 years ago

Hello. Is the re a way to control Bluetooth src/AudioLibs/AudioA2DP.h library?

I am trying to combine streams-generator-a2dp.ino with streams-generator-i2s.ino

I have made 2 objects StreamCopy copier1(out, sound); StreamCopy copier2(out2, sound);

I would like to turn Bluetooth ON and than send the data via copier1.copy(); then turn Bluetooth OFF and send data via copier2.copy();

It works, but the only problem is that I can't find the commands to turn Bluetooth ON/OFF or connect to Bluetooth speaker and disconnect from it.

Can you please help me with those commands?

pschatzmann commented 2 years ago

Have a look at the documentation. There are the methods source() and sink() which provide access to the related objects...

Denis-2021 commented 2 years ago

Thank you for your reply.

I am not sure how to use them. Can you give me a little example please?

pschatzmann commented 2 years ago

Here is the link the the A2DP documentation that explains the methods that are available for the source. If you use a A2DPStream as a Source you can e.g. call a2dp.source().disconnect();

However I would suggest that you consider to keep the A2DP source open and just send silence. This way you don't need to bother about starting and stopping A2DP sessions...

ps. if you check the StreamCopy api you will discover that you can dynamically switch the source and destination by calling the begin method: so in your example above one StreamCopy would be good enough.

Denis-2021 commented 2 years ago

I have tried switching the destination by calling the begin method: copier.begin(out, in); and copier.begin(out2, in); It works, my problem is that if I switch them fast it works, but If I switch to I2S for couple of seconds, than when I switch back to Bluetooth something breaks, and it stops working. I think the Bluetooth disconnects if no data received for couple of seconds.

I have no idea how to send silence to keep it "alive". I have tried setting up a second SineWaveGenerator with 0 amplitude, but it makes the sound in the A2DP very distorted.

I have tested the a2dp.source().disconnect(); and it works, my problem is that it reconnects by itself after couple of seconds I guess it is because of cfgA2DP.auto_reconnect = true;, if I set it to false in the Setup, Bluetooth doesn't connect at all and if I try to set cfgA2DP.auto_reconnect = false; in the main, it doesn't seem to change the parameter.

Is there a way to make it connect manually to A2DP? so I could set cfgA2DP.auto_reconnect = false; in Setup ?

pschatzmann commented 2 years ago

Right, it disconnects if you don't provide any data, therefore I suggested to feed it with silence. There are many ways to do this:

To reconnect I think you can use the start method on the BluetoothA2DPSource...

Denis-2021 commented 2 years ago

I am trying to setup SilenceGenerator , but maybe I am doing something wrong. Here is my code:

include "AudioTools.h"

include "AudioLibs/AudioA2DP.h"

typedef int16_t sound_t; // sound will be represented as int16_t (with 2 bytes)//44100 uint16_t sample_rate=44100; uint8_t channels = 1; // The stream will have 2 channels SineWaveGenerator sineWave(32767); // subclass of SoundGenerator with max amplitude of 32000 //32767 GeneratedSoundStream in(sineWave); // Stream generated from sine wave

SilenceGenerator sineWave2; GeneratedSoundStream in2(sineWave2);

A2DPStream out = A2DPStream::instance() ; // A2DP input - A2DPStream is a singleton! I2SStream out2; //analog audio stream out StreamCopy copier(out, in); // copy sineWave to Bluetooth StreamCopy copier2(out2, in2); // copy Silence to I2S auto cfgA2DP = out.defaultConfig(TX_MODE);

setup() { // Setup sine wave 1 auto cfg = in.defaultConfig(); cfg.channels = channels; cfg.sample_rate = sample_rate; in.setNotifyAudioChange(out); in.begin(cfg);

// Setup sine wave 2 auto cfg2 = in2.defaultConfig(); cfg2.channels = channels; cfg2.sample_rate = sample_rate; in2.setNotifyAudioChange(out2); in2.begin(cfg2);

cfgA2DP.name = "RB-T20Pro"; cfgA2DP.auto_reconnect = true; //true out.begin(cfgA2DP);

//I2S out auto config = out2.defaultConfig(TX_MODE);

config.port_no = 0; config.pin_ws = 15; config.pin_bck = 14; config.pin_data = 32; config.is_master = true; config.use_apll = true;

config.sample_rate = sample_rate; config.channels = channels; config.bits_per_sample = (i2s_bits_per_sample_t)16; out2.begin(config); }

loop() { copier.copy(); copier2.copy(); }


The silence part works, but it introduces distortions in the Bluetooth Stream. Maybe it is better to make the silence in other ways?

About Bluetooth connection/disconnection When I call a2dp.source().disconnect(); it disconnects and then reconnects again, but when I call out.begin(TX_MODE,"RB-T20Pro"); the ESP32 crashes, same when I call out.source().start("RB-T20Pro");

pschatzmann commented 2 years ago

I think the distortions come from the fact that the coper2 will block when the buffer is full: you can try to increase the I2S buffer settings. However the cleanest way would be to use a separate task for one of the copy lines.

Denis-2021 commented 2 years ago

I have tried it with 1 copier like that: copier.begin(out2, in); // copy in(SineWave) to out2(AUX) copier.copy(); //feeding the sine wave over AUX copier.begin(out, in2);// copy in2(Silence) to out(Bluetooth) copier.copy(); //feeding the sine wave over AUX

It works, but as soon as I start the silence in the setup with in2.begin(cfg2); the Bluetooth gets distortions. Maybe there is a way to send silence by sending 0 value repeatedly somehow without using the SilenceGenerator ? This problem can be avoided by just disconnecting Bluetooth when I switch the copier to I2S and connecting Bluetooth again when I switch the copier back, unfortunately I still couldn't find a way to disconnect, it just reconnects by itself :(

pschatzmann commented 2 years ago

It automatically reconnects because of cfgA2DP.auto_reconnect = true; //true

Most people use the A2DPSink: it might be that the Source is lacking some functionality to temporarily stop and restart...

By the way what I don't understand: you would use the silence only to keep bluetooth active, so how can you hear distortions on no sound? In the Bluetooth case you will have only 1 copy. In the Aux case you would have 2 copy - one for silence to bluetooth and one for I2S - then I would understand if I2S would have some hickups...

Denis-2021 commented 2 years ago

Thank you very much for your help :) You are right there is no need to feed I2S while sending sound to Bluetooth, and now it works without distortions.

One strange thing that I found is that Bluetooth receives frequency 2X from when the same speaker receives via I2S. I send the same SineWaveGenerator to both of them and only switch copier.begin(out, in); to copier.begin(out2, in); I can just divide the frequency by 2 when I send it to Bluetooth :)

Now I only need to solve the problem with Bluetooth blocking while not connected. I will try to add #include "BluetoothA2DPSource.h" to the same sketch. I can see that the example uses a2dp_source.start("RB-T20Pro");

Do I just replace the A2DPStream out = A2DPStream::instance() ; with BluetoothA2DPSource out; and the copier.copy(); should just work?

pschatzmann commented 2 years ago

From my point of view, the only way to resolve the issue with the blocking API is to directly use the A2DP callback API. This will change your program logic however quite a bit.

In any case it is likely that you will need to use a separate task to manage the startup of the bluetooth if you want to minimize the time of not playing any audio..

Denis-2021 commented 2 years ago

I don't mind starting the Bluetooth and turning it off with a switch. My problem is that I am not able to control it so far. Before I started this project I tested both the streams-generator-a2dp.ino and the bt_music_sender.ino and the last one had some problems like the ESP32 would crash and I couldn't make the soundwave consistent. this it why I switched over to AudioTools library.

I don't want to start everything from scratch.

I just want to find a way to send the sound wave over I2S while the Bluetooth is not connected. a2dp.source().disconnect(); disconnects but the cfgA2DP.auto_reconnect = true/false doesn't make a difference, it connect again any way. I have tried also to start out.begin(cfgA2DP); from the switch function, but then my ESP32 crashes, maybe because it is already running. One interesting thing that I found out is that Bluetooth pairing and A2DP connection is not the same thing. even if I don't run out.begin(cfgA2DP); my speaker says "connected" (it makes voice announcements when it connects/disconnects).

Denis-2021 commented 2 years ago

I think I found the problem After I run out.begin(cfgA2DP); I get:

[ 522][D][BluetoothA2DPSource.cpp:111] BluetoothA2DPSource(): [BT_API] BluetoothA2DPSource, [ 530][I][BluetoothA2DPCommon.h:120] set_volume(): [BT_AV] set_volume: 128 [ 537][D][BluetoothA2DPSource.cpp:154] start(): [BT_API] start, [ 542][D][BluetoothA2DPSource.cpp:172] start_raw(): [BT_API] start_raw, [ 549][D][BluetoothA2DPCommon.cpp:113] get_last_connection(): [BT_AV] get_last_connection [ 558][D][BluetoothA2DPCommon.cpp:137] get_last_connection(): [BT_AV] => 00:00:00:00:00:00 [ 565][D][BluetoothA2DPSource.cpp:235] reset_last_connection(): [BT_API] reset_last_connection, [ 574][D][BluetoothA2DPSource.cpp:237] reset_last_connection(): [BT_API] last connection 00:00:00:00:00:00, [ 1120][D][BluetoothA2DPSource.cpp:250] bt_app_work_dispatch(): [BT_API] bt_app_work_dispatch event 0x0, param len 0 [ 1123][D][BluetoothA2DPSource.cpp:301] bt_app_task_handler(): [BT_API] bt_app_task_handler, sig 0x1, 0x0 [ 1131][D][BluetoothA2DPSource.cpp:527] bt_av_hdl_stack_evt(): [BT_AV] bt_av_hdl_stack_evt evt 0 [ 1140][I][BluetoothA2DPCommon.cpp:241] set_scan_mode_connectable(): [BT_AV] set_scan_mode_connectable true [ 1149][I][BluetoothA2DPSource.cpp:559] bt_av_hdl_stack_evt(): [BT_AV] Starting device discovery... [ 1153][D][BluetoothA2DPSource.cpp:250] bt_app_work_dispatch(): [BT_API] bt_app_work_dispatch event 0x4, param len 16 [ 1169][D][BluetoothA2DPSource.cpp:301] bt_app_task_handler(): [BT_API] bt_app_task_handler, sig 0x1, 0x4 [ 1178][I][BluetoothA2DPSource.cpp:623] bt_app_av_sm_hdlr(): [BT_AV] bt_app_av_sm_hdlr state 1, evt 0x4 [ 1187][D][BluetoothA2DPSource.cpp:591] process_user_state_callbacks(): [BT_AV] process_user_state_callbacks [ 1197][I][BluetoothA2DPSource.cpp:518] bt_app_gap_callback(): [BT_AV] event: 10 [ 1204][I][BluetoothA2DPSource.cpp:518] bt_app_gap_callback(): [BT_AV] event: 10 [ 1213][I][BluetoothA2DPSource.cpp:518] bt_app_gap_callback(): [BT_AV] event: 10 [ 1221][I][BluetoothA2DPSource.cpp:518] bt_app_gap_callback(): [BT_AV] event: 10 [ 1229][I][BluetoothA2DPSource.cpp:469] bt_app_gap_callback(): [BT_AV] Discovery started.

I think this line "set_scan_mode_connectable(): [BT_AV] set_scan_mode_connectable true" makes Bluetooth reconnect in a loop. So far haven't found a way to send set_scan_mode_connectable(false);

pschatzmann commented 2 years ago

I added some proper support for disconnecting and reconnecting again. Here is a test example and a link to the updated documentation