arduino-audio-tools
A2DPStream.h
Go to the documentation of this file.
1 
8 #pragma once
9 
10 #include "AudioConfig.h"
11 
12 #include "AudioTools.h"
13 #include "BluetoothA2DPSink.h"
14 #include "BluetoothA2DPSource.h"
15 #include "AudioTools/CoreAudio/AudioStreams.h"
16 #include "AudioTools/Concurrency/BufferRTOS.h"
17 #include "AudioTools/CoreAudio/AudioBasic/StrView.h"
18 
19 
20 namespace audio_tools {
21 
22 class A2DPStream;
23 static A2DPStream *A2DPStream_self=nullptr;
24 // buffer which is used to exchange data
25 static BufferRTOS<uint8_t>a2dp_buffer{0, A2DP_BUFFER_SIZE, portMAX_DELAY, portMAX_DELAY};
26 // flag to indicated that we are ready to process data
27 static bool is_a2dp_active = false;
28 
29 int32_t a2dp_stream_source_sound_data(Frame* data, int32_t len);
30 void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len);
31 
33 enum A2DPStartLogic {StartWhenBufferFull, StartOnConnect};
35 enum A2DPNoData {A2DPSilence, A2DPWhoosh};
36 
42 class A2DPConfig {
43  public:
45  A2DPStartLogic startup_logic = StartWhenBufferFull;
47  A2DPNoData startup_nodata = A2DPSilence;
48  RxTxMode mode = RX_MODE;
50  const char* name = "A2DP";
51  bool auto_reconnect = false;
52  int buffer_size = A2DP_BUFFER_SIZE * A2DP_BUFFER_COUNT;
54  int delay_ms = 1;
56  bool silence_on_nodata = false;
57 };
58 
59 
77 class A2DPStream : public AudioStream, public VolumeSupport {
78 
79  public:
80  A2DPStream() {
81  TRACED();
82  // A2DPStream can only be used once
83  assert(A2DPStream_self==nullptr);
84  A2DPStream_self = this;
85  info.bits_per_sample = 16;
86  info.sample_rate = 44100;
87  info.channels = 2;
88  }
89 
92  TRACED();
93  if (a2dp_source!=nullptr) delete a2dp_source;
94  if (a2dp_sink!=nullptr) delete a2dp_sink;
95  A2DPStream_self = nullptr;
96  }
97 
98  A2DPConfig defaultConfig(RxTxMode mode=RX_MODE){
99  A2DPConfig cfg;
100  cfg.mode = mode;
101  if(mode==TX_MODE){
102  cfg.name="[Unknown]";
103  }
104  return cfg;
105  }
106 
108  BluetoothA2DPSource &source() {
109  if (a2dp_source==nullptr){
110  a2dp = a2dp_source = new BluetoothA2DPSource();
111  }
112  return *a2dp_source;
113  }
114 
116  BluetoothA2DPSink &sink(){
117  if (a2dp_sink==nullptr){
118  a2dp = a2dp_sink = new BluetoothA2DPSink();
119  }
120  return *a2dp_sink;
121  }
122 
124  bool begin(RxTxMode mode, const char* name){
125  A2DPConfig cfg;
126  cfg.mode = mode;
127  cfg.name = name;
128  return begin(cfg);
129  }
130 
132  bool begin(A2DPConfig cfg){
133  this->config = cfg;
134  bool result = false;
135  LOGI("Connecting to %s",cfg.name);
136 
137  if (!a2dp_buffer.resize(cfg.buffer_size)){
138  LOGE("a2dp_buffer resize failed");
139  return false;
140  }
141 
142  // initialize a2dp_silence_timeout
143  if (config.silence_on_nodata){
144  LOGI("Using StartOnConnect")
145  config.startup_logic = StartOnConnect;
146  }
147 
148  switch (cfg.mode){
149  case TX_MODE:
150  LOGI("Starting a2dp_source...");
151  source(); // allocate object
152  a2dp_source->set_auto_reconnect(cfg.auto_reconnect);
153  a2dp_source->set_volume(volume() * A2DP_MAX_VOL);
154  if(StrView(cfg.name).equals("[Unknown]")){
155  //search next available device
156  a2dp_source->set_ssid_callback(detected_device);
157  }
158  a2dp_source->set_on_connection_state_changed(a2dp_state_callback, this);
159  a2dp_source->start_raw((char*)cfg.name, a2dp_stream_source_sound_data);
160  while(!a2dp_source->is_connected()){
161  LOGD("waiting for connection");
162  delay(1000);
163  }
164  LOGI("a2dp_source is connected...");
165  notify_base_Info(44100);
166  //is_a2dp_active = true;
167  result = true;
168  break;
169 
170  case RX_MODE:
171  LOGI("Starting a2dp_sink...");
172  sink(); // allocate object
173  a2dp_sink->set_auto_reconnect(cfg.auto_reconnect);
174  a2dp_sink->set_stream_reader(&a2dp_stream_sink_sound_data, false);
175  a2dp_sink->set_volume(volume() * A2DP_MAX_VOL);
176  a2dp_sink->set_on_connection_state_changed(a2dp_state_callback, this);
177  a2dp_sink->set_sample_rate_callback(sample_rate_callback);
178  a2dp_sink->start((char*)cfg.name);
179  while(!a2dp_sink->is_connected()){
180  LOGD("waiting for connection");
181  delay(1000);
182  }
183  LOGI("a2dp_sink is connected...");
184  is_a2dp_active = true;
185  result = true;
186  break;
187  default:
188  LOGE("Undefined mode: %d", cfg.mode);
189  break;
190  }
191 
192  return result;
193  }
194 
195  void end() override {
196  if (a2dp != nullptr) {
197  a2dp->disconnect();
198  }
199  AudioStream::end();
200  }
201 
203  bool isConnected() {
204  if (a2dp_source==nullptr && a2dp_sink==nullptr) return false;
205  if (a2dp_source!=nullptr) return a2dp_source->is_connected();
206  return a2dp_sink->is_connected();
207  }
208 
210  bool isReady() {
211  return is_a2dp_active;
212  }
213 
215  operator bool() {
216  return isReady();
217  }
218 
220  size_t write(const uint8_t* data, size_t len) override {
221  LOGD("%s: %zu", LOG_METHOD, len);
222  if (config.mode == TX_MODE){
223  // at 80% we activate the processing
224  if(!is_a2dp_active
225  && config.startup_logic == StartWhenBufferFull
226  && a2dp_buffer.available() >= 0.8f * a2dp_buffer.size()){
227  LOGI("set active");
228  is_a2dp_active = true;
229  }
230 
231  // blocking write: if buffer is full we wait
232  size_t free = a2dp_buffer.availableForWrite();
233  while(len > free){
234  LOGD("Waiting for buffer: writing %d > available %d", (int) len, (int) free);
235  delay(5);
236  free = a2dp_buffer.availableForWrite();
237  }
238  }
239 
240  // write to buffer
241  size_t result = a2dp_buffer.writeArray(data, len);
242  LOGD("write %d -> %d", len, result);
243  if (config.mode == TX_MODE){
244  // give the callback a chance to retrieve the data
245  delay(config.delay_ms);
246  }
247  return result;
248  }
249 
251  size_t readBytes(uint8_t *data, size_t len) override {
252  if (!is_a2dp_active){
253  LOGW( "readBytes failed because !is_a2dp_active");
254  return 0;
255  }
256  LOGD("readBytes %d", len);
257  size_t result = a2dp_buffer.readArray(data, len);
258  LOGI("readBytes %d->%d", len,result);
259  return result;
260  }
261 
263  int available() override {
264  // only supported in tx mode
265  if (config.mode!=RX_MODE) return 0;
266  return a2dp_buffer.available();
267  }
268 
270  int availableForWrite() override {
271  // only supported in tx mode
272  if (config.mode!=TX_MODE ) return 0;
273  // return infor from buffer
274  return a2dp_buffer.availableForWrite();
275  }
276 
278  bool setVolume(float volume) override {
280  // 128 is max volume
281  if (a2dp!=nullptr) a2dp->set_volume(volume * A2DP_MAX_VOL);
282  return true;
283  }
284 
287  return a2dp_buffer;
288  }
289 
291  void setSilenceOnNoData(bool silence){
292  config.silence_on_nodata = silence;
293  }
294 
295  protected:
296  A2DPConfig config;
297  BluetoothA2DPSource *a2dp_source = nullptr;
298  BluetoothA2DPSink *a2dp_sink = nullptr;
299  BluetoothA2DPCommon *a2dp=nullptr;
300  const int A2DP_MAX_VOL = 128;
301 
302  // auto-detect device to send audio to (TX-Mode)
303  static bool detected_device(const char* ssid, esp_bd_addr_t address, int rssi){
304  LOGW("found Device: %s rssi: %d", ssid, rssi);
305  //filter out weak signals
306  return (rssi > -75);
307  }
308 
309  static void a2dp_state_callback(esp_a2d_connection_state_t state, void *caller){
310  TRACED();
311  A2DPStream *self = (A2DPStream*)caller;
312  if (state==ESP_A2D_CONNECTION_STATE_CONNECTED && self->config.startup_logic==StartOnConnect){
313  is_a2dp_active = true;
314  }
315  LOGW("==> state: %s", self->a2dp->to_str(state));
316  }
317 
318 
319  // callback used by A2DP to provide the a2dp_source sound data
320  static int32_t a2dp_stream_source_sound_data(uint8_t* data, int32_t len) {
321  int32_t result_len = 0;
322  A2DPConfig config = A2DPStream_self->config;
323 
324  // at first call we start with some empty data
325  if (is_a2dp_active){
326  // the data in the file must be in int16 with 2 channels
327  yield();
328  result_len = a2dp_buffer.readArray((uint8_t*)data, len);
329 
330  // provide silence data
331  if (config.silence_on_nodata && result_len == 0){
332  memset(data,0, len);
333  result_len = len;
334  }
335  } else {
336 
337  // prevent underflow on first call
338  switch (config.startup_nodata) {
339  case A2DPSilence:
340  memset(data, 0, len);
341  break;
342  case A2DPWhoosh:
343  int16_t *data16 = (int16_t*)data;
344  for (int j=0;j<len/4;j+=2){
345  data16[j+1] = data16[j] = (rand() % 50) - 25;
346  }
347  break;
348  }
349  result_len = len;
350 
351  // Priority: 22 on core 0
352  // LOGI("Priority: %d on core %d", uxTaskPriorityGet(NULL), xPortGetCoreID());
353 
354  }
355  LOGD("a2dp_stream_source_sound_data: %d -> %d", len, result_len);
356  return result_len;
357  }
358 
360  static void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len) {
361  if (is_a2dp_active){
362  uint32_t result_len = a2dp_buffer.writeArray(data, len);
363  LOGD("a2dp_stream_sink_sound_data %d -> %d", len, result_len);
364  }
365  }
366 
368  void notify_base_Info(int rate){
369  AudioInfo info;
370  info.channels = 2;
371  info.bits_per_sample = 16;
372  info.sample_rate = rate;
373  notifyAudioChange(info);
374  }
375 
377  static void sample_rate_callback(uint16_t rate) {
378  A2DPStream_self->info.sample_rate = rate;
379  A2DPStream_self->notify_base_Info(rate);
380  }
381 
382 };
383 
384 }
Configuration for A2DPStream.
Definition: A2DPStream.h:42
bool silence_on_nodata
when a2dp source is active but has no data we generate silence data
Definition: A2DPStream.h:56
int delay_ms
Delay in ms which is added to each write.
Definition: A2DPStream.h:54
A2DPNoData startup_nodata
Action when a2dp is not active yet.
Definition: A2DPStream.h:47
const char * name
A2DP name.
Definition: A2DPStream.h:50
A2DPStartLogic startup_logic
Logic when the processing is activated.
Definition: A2DPStream.h:45
Stream support for A2DP using https://github.com/pschatzmann/ESP32-A2DP: begin(TX_MODE) opens a a2dp_...
Definition: A2DPStream.h:77
void notify_base_Info(int rate)
notify subscriber with AudioInfo
Definition: A2DPStream.h:368
BluetoothA2DPSink & sink()
provides access to the BluetoothA2DPSink
Definition: A2DPStream.h:116
BaseBuffer< uint8_t > & buffer()
Provides access to the buffer.
Definition: A2DPStream.h:286
size_t readBytes(uint8_t *data, size_t len) override
Reads the data from the temporary buffer.
Definition: A2DPStream.h:251
static void a2dp_stream_sink_sound_data(const uint8_t *data, uint32_t len)
callback used by A2DP to write the sound data
Definition: A2DPStream.h:360
bool setVolume(float volume) override
Define the volume (values between 0.0 and 1.0)
Definition: A2DPStream.h:278
int available() override
Provides the number of bytes available to read.
Definition: A2DPStream.h:263
static void sample_rate_callback(uint16_t rate)
callback to update audio info with used a2dp sample rate
Definition: A2DPStream.h:377
size_t write(const uint8_t *data, size_t len) override
Writes the data into a temporary send buffer - where it can be picked up by the callback.
Definition: A2DPStream.h:220
bool isConnected()
checks if we are connected
Definition: A2DPStream.h:203
void setSilenceOnNoData(bool silence)
Manage config.silence_on_nodata dynamically.
Definition: A2DPStream.h:291
int availableForWrite() override
Provides the number of bytes available to write.
Definition: A2DPStream.h:270
bool begin(RxTxMode mode, const char *name)
Starts the processing.
Definition: A2DPStream.h:124
~A2DPStream()
Release the allocate a2dp_source or a2dp_sink.
Definition: A2DPStream.h:91
bool isReady()
is ready to process data
Definition: A2DPStream.h:210
BluetoothA2DPSource & source()
provides access to the
Definition: A2DPStream.h:108
bool begin(A2DPConfig cfg)
Starts the processing.
Definition: A2DPStream.h:132
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition: BaseStream.h:109
int available() override
provides the number of entries that are available to read
Definition: BufferRTOS.h:137
int availableForWrite() override
provides the number of entries that are available to write
Definition: BufferRTOS.h:142
int writeArray(const T data[], int len)
Fills the buffer data.
Definition: BufferRTOS.h:95
bool resize(size_t size)
Re-Allocats the memory and the queue.
Definition: BufferRTOS.h:50
int readArray(T data[], int len)
reads multiple values
Definition: BufferRTOS.h:77
A simple wrapper to provide string functions on existing allocated char*. If the underlying char* is ...
Definition: StrView.h:28
virtual bool equals(const char *str)
checks if the string equals indicated parameter string
Definition: StrView.h:165
Supports the setting and getting of the volume.
Definition: AudioTypes.h:207
virtual float volume()
provides the actual volume in the range of 0.0f to 1.0f
Definition: AudioTypes.h:210
virtual bool setVolume(float volume)
define the actual volume in the range of 0.0f to 1.0f
Definition: AudioTypes.h:212
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition: AudioTypes.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition: AudioConfig.h:823
A2DPNoData
A2DP Action when there is no data.
Definition: A2DPStream.h:35
A2DPStartLogic
A2DP Startup Logic.
Definition: A2DPStream.h:33
Basic Audio information which drives e.g. I2S.
Definition: AudioTypes.h:52
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition: AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition: AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition: AudioTypes.h:59