rename to A2DPStream.h

This commit is contained in:
pschatzmann 2024-03-21 08:51:06 +01:00
parent 110ae0ae2e
commit 5977668cc5
20 changed files with 434 additions and 373 deletions

View File

@ -7,7 +7,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" // install https://github.com/pschatzmann/ESP32-A2DP #include "AudioLibs/A2DPStream.h" // install https://github.com/pschatzmann/ESP32-A2DP
#include "AudioLibs/AudioBoardStream.h" // install https://github.com/pschatzmann/arduino-audio-driver #include "AudioLibs/AudioBoardStream.h" // install https://github.com/pschatzmann/arduino-audio-driver

View File

@ -7,7 +7,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" // install https://github.com/pschatzmann/ESP32-A2DP #include "AudioLibs/A2DPStream.h" // install https://github.com/pschatzmann/ESP32-A2DP
#include "AudioLibs/AudioBoardStream.h" // install https://github.com/pschatzmann/arduino-audio-driver #include "AudioLibs/AudioBoardStream.h" // install https://github.com/pschatzmann/arduino-audio-driver

View File

@ -7,7 +7,7 @@
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioBoardStream.h" #include "AudioLibs/AudioBoardStream.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
AudioInfo info(44100, 2, 16); AudioInfo info(44100, 2, 16);
BluetoothA2DPSource a2dp_source; BluetoothA2DPSource a2dp_source;

View File

@ -12,7 +12,7 @@
// install https://github.com/greiman/SdFat.git // install https://github.com/greiman/SdFat.git
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
#include "AudioLibs/AudioBoardStream.h" #include "AudioLibs/AudioBoardStream.h"
#include "AudioLibs/AudioSourceSDFAT.h" #include "AudioLibs/AudioSourceSDFAT.h"
#include "AudioCodecs/CodecMP3Helix.h" #include "AudioCodecs/CodecMP3Helix.h"

View File

@ -9,7 +9,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
#include "AudioLibs/AudioBoardStream.h" #include "AudioLibs/AudioBoardStream.h"

View File

@ -6,7 +6,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
const char* name = "LEXON MINO L"; // Replace with your bluetooth speaker name const char* name = "LEXON MINO L"; // Replace with your bluetooth speaker name
SineWaveGenerator<int16_t> sineWave(15000); // subclass of SoundGenerator, set max amplitude (=volume) SineWaveGenerator<int16_t> sineWave(15000); // subclass of SoundGenerator, set max amplitude (=volume)

View File

@ -8,7 +8,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
AudioInfo info32(44100, 2, 32); AudioInfo info32(44100, 2, 32);
AudioInfo info16(44100, 2, 16); AudioInfo info16(44100, 2, 16);

View File

@ -11,7 +11,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
#include "AudioLibs/AudioSourceSDFAT.h" #include "AudioLibs/AudioSourceSDFAT.h"
#include "AudioCodecs/CodecMP3Helix.h" #include "AudioCodecs/CodecMP3Helix.h"
#include "AudioLibs/AudioBoardStream.h" // for SD Pins #include "AudioLibs/AudioBoardStream.h" // for SD Pins

View File

@ -1,15 +1,20 @@
/** /**
* We use the default Arduino SPI API to send/write the data as SPI Master. * We use the default Arduino SPI API to send/write the data as SPI Master.
* * For a sample rate of 44100 with 2 channels and 16 bit data you need to be
* able to transmit faster then 44100 * 2 channels * 2 bytes = 176400 bytes per
* second. Using a SPI communication this gives 176400 * 8 =
* 1'411'200 bps!
*
* Untested DRAFT implementation! * Untested DRAFT implementation!
*/ */
#include <SPI.h> #include <SPI.h>
#include "AudioTools.h" #include "AudioTools.h"
const size_t BUFFER_SIZE = 1024; const size_t BUFFER_SIZE = 1024;
const int SPI_CLOCK = 2000000; // 2 MHz const int SPI_CLOCK = 2000000; // 2 MHz
AudioInfo info(44100, 2, 16); // AudioInfo info(44100, 2, 16); //
Vector<uint8_t> buffer(BUFFER_SIZE); Vector<uint8_t> buffer(BUFFER_SIZE);
SineWaveGenerator<int16_t> sineWave(32000); SineWaveGenerator<int16_t> sineWave(32000);
GeneratedSoundStream<int16_t> sound(sineWave); GeneratedSoundStream<int16_t> sound(sineWave);
@ -17,11 +22,13 @@ GeneratedSoundStream<int16_t> sound(sineWave);
void setup() { void setup() {
SPI.begin(); SPI.begin();
sineWave.begin(info, N_B4); sineWave.begin(info, N_B4);
SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0));
} }
void loop() { void loop() {
// get data
size_t result = sound.readBytes(&buffer[0], buffer.size()); size_t result = sound.readBytes(&buffer[0], buffer.size());
SPI.beginTransaction(SPISettings(SPI_CLOCK, MSBFIRST, SPI_MODE0)); // transmit data
SPI.transfer(&buffer[0], result); SPI.transfer(&buffer[0], result);
SPI.endTransaction(); //SPI.endTransaction();
} }

View File

@ -0,0 +1,50 @@
/**
* Demo how to use the Mozzi API to provide a stream of int16_t data.
* Inspired by https://sensorium.github.io/Mozzi/examples/#01.Basics
*/
#include "AudioTools.h"
#include "AudioLibs/A2DPStream.h"
#include "AudioLibs/MozziStream.h"
#include <Oscil.h> // oscillator template
#include <tables/sin2048_int8.h> // sine table for oscillator
const int sample_rate = 44100;
AudioInfo info(sample_rate, 2, 16); // bluetooth requires 44100, stereo, 16 bits
A2DPStream out;
MozziStream mozzi; // audio source
StreamCopy copier(out, mozzi); // copy source to sink
// use: Oscil <table_size, update_rate> oscilName (wavetable), look in .h file
// of table #included above
Oscil<SIN2048_NUM_CELLS, sample_rate> aSin(SIN2048_DATA);
// control variable, use the smallest data size you can for anything used in
// audio
byte gain = 255;
void setup() {
Serial.begin(115200);
AudioLogger::instance().begin(Serial, AudioLogger::Info);
// setup mozzi
auto cfg = mozzi.defaultConfig();
cfg.control_rate = CONTROL_RATE;
cfg.copyFrom(info);
mozzi.begin(cfg);
// setup output
out.begin(TX_MODE, "Mozzi");
// setup mozzi sine
aSin.setFreq(3320); // set the frequency
}
void loop() { copier.copy(); }
void updateControl() {
// as byte, this will automatically roll around to 255 when it passes 0
gain = gain - 3;
}
int updateAudio() {
return (aSin.next() * gain) >>
8; // shift back to STANDARD audio range, like /256 but faster
}

View File

@ -1,5 +1,5 @@
/** /**
* Demo how to use the Mozzi API to provide a stream on int16_t data. * Demo how to use the Mozzi API to provide a stream of int16_t data.
* Inspired by https://sensorium.github.io/Mozzi/examples/#01.Basics * Inspired by https://sensorium.github.io/Mozzi/examples/#01.Basics
*/ */
#include "AudioTools.h" #include "AudioTools.h"

View File

@ -10,7 +10,7 @@
#define HELIX_LOGGING_ACTIVE false #define HELIX_LOGGING_ACTIVE false
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
#include "AudioLibs/AudioSourceSDFAT.h" #include "AudioLibs/AudioSourceSDFAT.h"
#include "AudioCodecs/CodecMP3Helix.h" #include "AudioCodecs/CodecMP3Helix.h"

View File

@ -9,7 +9,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
A2DPStream in; A2DPStream in;

View File

@ -9,7 +9,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
const char* name = "LEXON MINO L"; // Replace with your device name const char* name = "LEXON MINO L"; // Replace with your device name
AudioInfo info(44100, 2, 16); AudioInfo info(44100, 2, 16);

View File

@ -8,7 +8,7 @@
*/ */
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
I2SStream i2sStream; // Access I2S as stream I2SStream i2sStream; // Access I2S as stream
A2DPStream a2dpStream; // access A2DP as stream A2DPStream a2dpStream; // access A2DP as stream

View File

@ -8,7 +8,7 @@
#include "AudioTools.h" #include "AudioTools.h"
#include "AudioCodecs/CodecMP3Helix.h" #include "AudioCodecs/CodecMP3Helix.h"
#include "AudioLibs/AudioA2DP.h" #include "AudioLibs/A2DPStream.h"
#include "SimpleTTS.h" #include "SimpleTTS.h"
const char* name = "LEXON MINO L"; // Replace with your device name const char* name = "LEXON MINO L"; // Replace with your device name

354
src/AudioLibs/A2DPStream.h Normal file
View File

@ -0,0 +1,354 @@
/**
* @file A2DPStream.h
* @author Phil Schatzmann
* @brief A2DP Support via Arduino Streams
* @copyright GPLv3
*
*/
#pragma once
#include "AudioConfig.h"
#include "AudioTools.h"
#include "BluetoothA2DPSink.h"
#include "BluetoothA2DPSource.h"
#include "AudioTools/AudioStreams.h"
#include "Concurrency/BufferRTOS.h"
namespace audio_tools {
class A2DPStream;
static A2DPStream *A2DPStream_self=nullptr;
// buffer which is used to exchange data
static BufferRTOS<uint8_t>a2dp_buffer{A2DP_BUFFER_SIZE * A2DP_BUFFER_COUNT, A2DP_BUFFER_SIZE, portMAX_DELAY, portMAX_DELAY};
// flag to indicated that we are ready to process data
static bool is_a2dp_active = false;
int32_t a2dp_stream_source_sound_data(Frame* data, int32_t len);
void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len);
enum A2DPStartLogic {StartWhenBufferFull, StartOnConnect};
enum A2DPNoData {A2DPSilence, A2DPWhoosh};
/**
* @brief Configuration for A2DPStream
* @author Phil Schatzmann
* @copyright GPLv3
*/
class A2DPConfig {
public:
A2DPStartLogic startLogic = StartWhenBufferFull;
A2DPNoData noData = A2DPSilence;
RxTxMode mode = RX_MODE;
const char* name = "A2DP";
bool auto_reconnect = false;
int bufferSize = A2DP_BUFFER_SIZE * A2DP_BUFFER_COUNT;
int delay_ms = 1;
/// when a2dp source has no data we generate silence data
bool silence_on_nodata = false;
};
/**
* @brief Stream support for A2DP: begin(TX_MODE) uses a2dp_source - begin(RX_MODE) a a2dp_sink
* The data is in int16_t with 2 channels at 44100 hertz.
* We support only one instance of the class!
* Please note that this is a conveniance class that supports the stream api,
* however this is rather inefficient, beause quite a bit buffer needs to be allocated.
* It is recommended to use the API with the callbacks. Examples can be found in the examples-basic-api
* directory.
*
* Requires: https://github.com/pschatzmann/ESP32-A2DP
*
* @ingroup io
* @ingroup communications
* @author Phil Schatzmann
* @copyright GPLv3
*/
class A2DPStream : public AudioStream, public VolumeSupport {
public:
A2DPStream() {
TRACED();
// A2DPStream can only be used once
assert(A2DPStream_self==nullptr);
A2DPStream_self = this;
info.bits_per_sample = 16;
info.sample_rate = 44100;
info.channels = 2;
}
/// Release the allocate a2dp_source or a2dp_sink
~A2DPStream(){
TRACED();
if (a2dp_source!=nullptr) delete a2dp_source;
if (a2dp_sink!=nullptr) delete a2dp_sink;
A2DPStream_self = nullptr;
}
A2DPConfig defaultConfig(RxTxMode mode=RX_MODE){
A2DPConfig cfg;
cfg.mode = mode;
if(mode==TX_MODE){
cfg.name="[Unknown]";
}
return cfg;
}
/// provides access to the
BluetoothA2DPSource &source() {
if (a2dp_source==nullptr){
a2dp = a2dp_source = new BluetoothA2DPSource();
}
return *a2dp_source;
}
/// provides access to the BluetoothA2DPSink
BluetoothA2DPSink &sink(){
if (a2dp_sink==nullptr){
a2dp = a2dp_sink = new BluetoothA2DPSink();
}
return *a2dp_sink;
}
/// Starts the processing
bool begin(RxTxMode mode, const char* name){
A2DPConfig cfg;
cfg.mode = mode;
cfg.name = name;
return begin(cfg);
}
/// Starts the processing
bool begin(A2DPConfig cfg){
this->config = cfg;
bool result = false;
LOGI("Connecting to %s",cfg.name);
a2dp_buffer.resize(cfg.bufferSize);
// initialize a2dp_silence_timeout
if (config.silence_on_nodata){
LOGI("Using StartOnConnect")
config.startLogic = StartOnConnect;
}
switch (cfg.mode){
case TX_MODE:
LOGI("Starting a2dp_source...");
source(); // allocate object
a2dp_source->set_auto_reconnect(cfg.auto_reconnect);
a2dp_source->set_volume(volume() * A2DP_MAX_VOL);
if(Str(cfg.name).equals("[Unknown]")){
//search next available device
a2dp_source->set_ssid_callback(detected_device);
}
a2dp_source->set_on_connection_state_changed(a2dp_state_callback, this);
a2dp_source->start_raw((char*)cfg.name, a2dp_stream_source_sound_data);
while(!a2dp_source->is_connected()){
LOGD("waiting for connection");
delay(1000);
}
LOGI("a2dp_source is connected...");
notify_base_Info(44100);
//is_a2dp_active = true;
result = true;
break;
case RX_MODE:
LOGI("Starting a2dp_sink...");
sink(); // allocate object
a2dp_sink->set_auto_reconnect(cfg.auto_reconnect);
a2dp_sink->set_stream_reader(&a2dp_stream_sink_sound_data, false);
a2dp_sink->set_volume(volume() * A2DP_MAX_VOL);
a2dp_sink->set_on_connection_state_changed(a2dp_state_callback, this);
a2dp_sink->set_sample_rate_callback(sample_rate_callback);
a2dp_sink->start((char*)cfg.name);
while(!a2dp_sink->is_connected()){
LOGD("waiting for connection");
delay(1000);
}
LOGI("a2dp_sink is connected...");
is_a2dp_active = true;
result = true;
break;
default:
LOGE("Undefined mode: %d", cfg.mode);
break;
}
return result;
}
void end() override {
if (a2dp != nullptr) {
a2dp->disconnect();
}
AudioStream::end();
}
/// checks if we are connected
bool isConnected() {
if (a2dp_source==nullptr && a2dp_sink==nullptr) return false;
if (a2dp_source!=nullptr) return a2dp_source->is_connected();
return a2dp_sink->is_connected();
}
/// is ready to process data
bool isReady() {
return is_a2dp_active;
}
/// convert to bool
operator bool() {
return isReady();
}
/// Writes the data into a temporary send buffer - where it can be picked up by the callback
size_t write(const uint8_t* data, size_t len) override {
LOGD("%s: %zu", LOG_METHOD, len);
if (config.mode==TX_MODE){
// if buffer is full we wait
while(len > a2dp_buffer.availableForWrite()){
LOGD("Waiting for buffer to be available");
delay(5);
if (config.startLogic==StartWhenBufferFull){
is_a2dp_active = true;
}
}
}
// write to buffer
size_t result = a2dp_buffer.writeArray(data, len);
LOGD("write %d -> %d", len, result);
if (config.mode==TX_MODE){
// give the callback a chance to retrieve the data
delay(config.delay_ms);
}
return result;
}
/// Reads the data from the temporary buffer
size_t readBytes(uint8_t *data, size_t len) override {
if (!is_a2dp_active){
LOGW( "readBytes failed because !is_a2dp_active");
return 0;
}
LOGD("readBytes %d", len);
size_t result = a2dp_buffer.readArray(data, len);
LOGI("readBytes %d->%d", len,result);
return result;
}
int available() override {
// only supported in tx mode
if (config.mode!=RX_MODE) return 0;
return a2dp_buffer.available();
}
int availableForWrite() override {
// only supported in tx mode
if (config.mode!=TX_MODE ) return 0;
// return infor from buffer
return a2dp_buffer.availableForWrite();
}
// Define the volme (values between 0.0 and 1.0)
bool setVolume(float volume) override {
VolumeSupport::setVolume(volume);
// 128 is max volume
if (a2dp!=nullptr) a2dp->set_volume(volume * A2DP_MAX_VOL);
return true;
}
protected:
A2DPConfig config;
BluetoothA2DPSource *a2dp_source = nullptr;
BluetoothA2DPSink *a2dp_sink = nullptr;
BluetoothA2DPCommon *a2dp=nullptr;
const int A2DP_MAX_VOL = 128;
// auto-detect device to send audio to (TX-Mode)
static bool detected_device(const char* ssid, esp_bd_addr_t address, int rssi){
LOGW("found Device: %s rssi: %d", ssid, rssi);
//filter out weak signals
return (rssi > -75);
}
static void a2dp_state_callback(esp_a2d_connection_state_t state, void *caller){
TRACED();
A2DPStream *self = (A2DPStream*)caller;
if (state==ESP_A2D_CONNECTION_STATE_CONNECTED && self->config.startLogic==StartOnConnect){
is_a2dp_active = true;
}
LOGW("==> state: %s", self->a2dp->to_str(state));
}
// callback used by A2DP to provide the a2dp_source sound data
static int32_t a2dp_stream_source_sound_data(uint8_t* data, int32_t len) {
int32_t result_len = 0;
A2DPConfig config = A2DPStream_self->config;
// at first call we start with some empty data
if (is_a2dp_active){
// the data in the file must be in int16 with 2 channels
yield();
result_len = a2dp_buffer.readArray((uint8_t*)data, len);
// provide silence data
if (config.silence_on_nodata && result_len == 0){
memset(data,0, len);
result_len = len;
}
} else {
// prevent underflow on first call
switch (config.noData) {
case A2DPSilence:
memset(data, 0, len);
break;
case A2DPWhoosh:
int16_t *data16 = (int16_t*)data;
for (int j=0;j<len/4;j+=2){
data16[j+1] = data16[j] = (rand() % 50) - 25;
}
break;
}
result_len = len;
// Priority: 22 on core 0
// LOGI("Priority: %d on core %d", uxTaskPriorityGet(NULL), xPortGetCoreID());
}
LOGD("a2dp_stream_source_sound_data: %d -> %d", len, result_len);
return result_len;
}
/// callback used by A2DP to write the sound data
static void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len) {
if (is_a2dp_active){
uint32_t result_len = a2dp_buffer.writeArray(data, len);
LOGD("a2dp_stream_sink_sound_data %d -> %d", len, result_len);
}
}
/// notify subscriber with AudioInfo
void notify_base_Info(int rate){
AudioInfo info;
info.channels = 2;
info.bits_per_sample = 16;
info.sample_rate = rate;
notifyAudioChange(info);
}
/// callback to update audio info with used a2dp sample rate
static void sample_rate_callback(uint16_t rate) {
A2DPStream_self->info.sample_rate = rate;
A2DPStream_self->notify_base_Info(rate);
}
};
}

View File

@ -1,354 +1,3 @@
/** // legacy include
* @file AudioA2DP.h
* @author Phil Schatzmann
* @brief A2DP Support via Arduino Streams
* @copyright GPLv3
*
*/
#pragma once #pragma once
#include "A2DPStream.h"
#include "AudioConfig.h"
#include "AudioTools.h"
#include "BluetoothA2DPSink.h"
#include "BluetoothA2DPSource.h"
#include "AudioTools/AudioStreams.h"
#include "Concurrency/BufferRTOS.h"
namespace audio_tools {
class A2DPStream;
static A2DPStream *A2DPStream_self=nullptr;
// buffer which is used to exchange data
static BufferRTOS<uint8_t>a2dp_buffer{A2DP_BUFFER_SIZE * A2DP_BUFFER_COUNT, A2DP_BUFFER_SIZE, portMAX_DELAY, portMAX_DELAY};
// flag to indicated that we are ready to process data
static bool is_a2dp_active = false;
int32_t a2dp_stream_source_sound_data(Frame* data, int32_t len);
void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len);
enum A2DPStartLogic {StartWhenBufferFull, StartOnConnect};
enum A2DPNoData {A2DPSilence, A2DPWhoosh};
/**
* @brief Configuration for A2DPStream
* @author Phil Schatzmann
* @copyright GPLv3
*/
class A2DPConfig {
public:
A2DPStartLogic startLogic = StartWhenBufferFull;
A2DPNoData noData = A2DPSilence;
RxTxMode mode = RX_MODE;
const char* name = "A2DP";
bool auto_reconnect = false;
int bufferSize = A2DP_BUFFER_SIZE * A2DP_BUFFER_COUNT;
int delay_ms = 1;
/// when a2dp source has no data we generate silence data
bool silence_on_nodata = false;
};
/**
* @brief Stream support for A2DP: begin(TX_MODE) uses a2dp_source - begin(RX_MODE) a a2dp_sink
* The data is in int16_t with 2 channels at 44100 hertz.
* We support only one instance of the class!
* Please note that this is a conveniance class that supports the stream api,
* however this is rather inefficient, beause quite a bit buffer needs to be allocated.
* It is recommended to use the API with the callbacks. Examples can be found in the examples-basic-api
* directory.
*
* Requires: https://github.com/pschatzmann/ESP32-A2DP
*
* @ingroup io
* @ingroup communications
* @author Phil Schatzmann
* @copyright GPLv3
*/
class A2DPStream : public AudioStream, public VolumeSupport {
public:
A2DPStream() {
TRACED();
// A2DPStream can only be used once
assert(A2DPStream_self==nullptr);
A2DPStream_self = this;
info.bits_per_sample = 16;
info.sample_rate = 44100;
info.channels = 2;
}
/// Release the allocate a2dp_source or a2dp_sink
~A2DPStream(){
TRACED();
if (a2dp_source!=nullptr) delete a2dp_source;
if (a2dp_sink!=nullptr) delete a2dp_sink;
A2DPStream_self = nullptr;
}
A2DPConfig defaultConfig(RxTxMode mode=RX_MODE){
A2DPConfig cfg;
cfg.mode = mode;
if(mode==TX_MODE){
cfg.name="[Unknown]";
}
return cfg;
}
/// provides access to the
BluetoothA2DPSource &source() {
if (a2dp_source==nullptr){
a2dp = a2dp_source = new BluetoothA2DPSource();
}
return *a2dp_source;
}
/// provides access to the BluetoothA2DPSink
BluetoothA2DPSink &sink(){
if (a2dp_sink==nullptr){
a2dp = a2dp_sink = new BluetoothA2DPSink();
}
return *a2dp_sink;
}
/// Starts the processing
bool begin(RxTxMode mode, const char* name){
A2DPConfig cfg;
cfg.mode = mode;
cfg.name = name;
return begin(cfg);
}
/// Starts the processing
bool begin(A2DPConfig cfg){
this->config = cfg;
bool result = false;
LOGI("Connecting to %s",cfg.name);
a2dp_buffer.resize(cfg.bufferSize);
// initialize a2dp_silence_timeout
if (config.silence_on_nodata){
LOGI("Using StartOnConnect")
config.startLogic = StartOnConnect;
}
switch (cfg.mode){
case TX_MODE:
LOGI("Starting a2dp_source...");
source(); // allocate object
a2dp_source->set_auto_reconnect(cfg.auto_reconnect);
a2dp_source->set_volume(volume() * A2DP_MAX_VOL);
if(Str(cfg.name).equals("[Unknown]")){
//search next available device
a2dp_source->set_ssid_callback(detected_device);
}
a2dp_source->set_on_connection_state_changed(a2dp_state_callback, this);
a2dp_source->start_raw((char*)cfg.name, a2dp_stream_source_sound_data);
while(!a2dp_source->is_connected()){
LOGD("waiting for connection");
delay(1000);
}
LOGI("a2dp_source is connected...");
notify_base_Info(44100);
//is_a2dp_active = true;
result = true;
break;
case RX_MODE:
LOGI("Starting a2dp_sink...");
sink(); // allocate object
a2dp_sink->set_auto_reconnect(cfg.auto_reconnect);
a2dp_sink->set_stream_reader(&a2dp_stream_sink_sound_data, false);
a2dp_sink->set_volume(volume() * A2DP_MAX_VOL);
a2dp_sink->set_on_connection_state_changed(a2dp_state_callback, this);
a2dp_sink->set_sample_rate_callback(sample_rate_callback);
a2dp_sink->start((char*)cfg.name);
while(!a2dp_sink->is_connected()){
LOGD("waiting for connection");
delay(1000);
}
LOGI("a2dp_sink is connected...");
is_a2dp_active = true;
result = true;
break;
default:
LOGE("Undefined mode: %d", cfg.mode);
break;
}
return result;
}
void end() override {
if (a2dp != nullptr) {
a2dp->disconnect();
}
AudioStream::end();
}
/// checks if we are connected
bool isConnected() {
if (a2dp_source==nullptr && a2dp_sink==nullptr) return false;
if (a2dp_source!=nullptr) return a2dp_source->is_connected();
return a2dp_sink->is_connected();
}
/// is ready to process data
bool isReady() {
return is_a2dp_active;
}
/// convert to bool
operator bool() {
return isReady();
}
/// Writes the data into a temporary send buffer - where it can be picked up by the callback
size_t write(const uint8_t* data, size_t len) override {
LOGD("%s: %zu", LOG_METHOD, len);
if (config.mode==TX_MODE){
// if buffer is full we wait
while(len > a2dp_buffer.availableForWrite()){
LOGD("Waiting for buffer to be available");
delay(5);
if (config.startLogic==StartWhenBufferFull){
is_a2dp_active = true;
}
}
}
// write to buffer
size_t result = a2dp_buffer.writeArray(data, len);
LOGD("write %d -> %d", len, result);
if (config.mode==TX_MODE){
// give the callback a chance to retrieve the data
delay(config.delay_ms);
}
return result;
}
/// Reads the data from the temporary buffer
size_t readBytes(uint8_t *data, size_t len) override {
if (!is_a2dp_active){
LOGW( "readBytes failed because !is_a2dp_active");
return 0;
}
LOGD("readBytes %d", len);
size_t result = a2dp_buffer.readArray(data, len);
LOGI("readBytes %d->%d", len,result);
return result;
}
int available() override {
// only supported in tx mode
if (config.mode!=RX_MODE) return 0;
return a2dp_buffer.available();
}
int availableForWrite() override {
// only supported in tx mode
if (config.mode!=TX_MODE ) return 0;
// return infor from buffer
return a2dp_buffer.availableForWrite();
}
// Define the volme (values between 0.0 and 1.0)
bool setVolume(float volume) override {
VolumeSupport::setVolume(volume);
// 128 is max volume
if (a2dp!=nullptr) a2dp->set_volume(volume * A2DP_MAX_VOL);
return true;
}
protected:
A2DPConfig config;
BluetoothA2DPSource *a2dp_source = nullptr;
BluetoothA2DPSink *a2dp_sink = nullptr;
BluetoothA2DPCommon *a2dp=nullptr;
const int A2DP_MAX_VOL = 128;
// auto-detect device to send audio to (TX-Mode)
static bool detected_device(const char* ssid, esp_bd_addr_t address, int rssi){
LOGW("found Device: %s rssi: %d", ssid, rssi);
//filter out weak signals
return (rssi > -75);
}
static void a2dp_state_callback(esp_a2d_connection_state_t state, void *caller){
TRACED();
A2DPStream *self = (A2DPStream*)caller;
if (state==ESP_A2D_CONNECTION_STATE_CONNECTED && self->config.startLogic==StartOnConnect){
is_a2dp_active = true;
}
LOGW("==> state: %s", self->a2dp->to_str(state));
}
// callback used by A2DP to provide the a2dp_source sound data
static int32_t a2dp_stream_source_sound_data(uint8_t* data, int32_t len) {
int32_t result_len = 0;
A2DPConfig config = A2DPStream_self->config;
// at first call we start with some empty data
if (is_a2dp_active){
// the data in the file must be in int16 with 2 channels
yield();
result_len = a2dp_buffer.readArray((uint8_t*)data, len);
// provide silence data
if (config.silence_on_nodata && result_len == 0){
memset(data,0, len);
result_len = len;
}
} else {
// prevent underflow on first call
switch (config.noData) {
case A2DPSilence:
memset(data, 0, len);
break;
case A2DPWhoosh:
int16_t *data16 = (int16_t*)data;
for (int j=0;j<len/4;j+=2){
data16[j+1] = data16[j] = (rand() % 50) - 25;
}
break;
}
result_len = len;
// Priority: 22 on core 0
// LOGI("Priority: %d on core %d", uxTaskPriorityGet(NULL), xPortGetCoreID());
}
LOGD("a2dp_stream_source_sound_data: %d -> %d", len, result_len);
return result_len;
}
/// callback used by A2DP to write the sound data
static void a2dp_stream_sink_sound_data(const uint8_t* data, uint32_t len) {
if (is_a2dp_active){
uint32_t result_len = a2dp_buffer.writeArray(data, len);
LOGD("a2dp_stream_sink_sound_data %d -> %d", len, result_len);
}
}
/// notify subscriber with AudioInfo
void notify_base_Info(int rate){
AudioInfo info;
info.channels = 2;
info.bits_per_sample = 16;
info.sample_rate = rate;
notifyAudioChange(info);
}
/// callback to update audio info with used a2dp sample rate
static void sample_rate_callback(uint16_t rate) {
A2DPStream_self->info.sample_rate = rate;
A2DPStream_self->notify_base_Info(rate);
}
};
}

View File

@ -65,7 +65,7 @@ class MozziStream : public AudioStream, public VolumeSupport {
void end() { active = false; } void end() { active = false; }
/// Defines the multiplication factor to scale the Mozzi value range to int16_t /// Defines the multiplication factor to scale the Mozzi value range to int16_t
void setVolume(int16_t vol) { bool setVolume(int16_t vol) {
cfg.output_volume = vol; cfg.output_volume = vol;
return VolumeSupport::setVolume(vol); return VolumeSupport::setVolume(vol);
} }

View File

@ -109,10 +109,11 @@ public:
} }
/// Sets both input and output volume value (from 0 to 1.0) /// Sets both input and output volume value (from 0 to 1.0)
void setVolume(float vol){ bool setVolume(float vol){
// make sure that value is between 0 and 1 // make sure that value is between 0 and 1
setVolumeIn(vol); setVolumeIn(vol);
setVolumeOut(vol); setVolumeOut(vol);
return true;
} }
void setVolumeIn(float vol){ void setVolumeIn(float vol){