mirror of
https://github.com/pschatzmann/arduino-audio-tools.git
synced 2024-09-21 10:27:27 +00:00
rename to A2DPStream.h
This commit is contained in:
parent
110ae0ae2e
commit
5977668cc5
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "AudioTools.h"
|
#include "AudioTools.h"
|
||||||
#include "AudioLibs/AudioA2DP.h"
|
#include "AudioLibs/A2DPStream.h"
|
||||||
|
|
||||||
|
|
||||||
A2DPStream in;
|
A2DPStream in;
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
354
src/AudioLibs/A2DPStream.h
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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){
|
||||||
|
Loading…
Reference in New Issue
Block a user