diff --git a/examples/sandbox/ble/client-as-source/communication-ble-client-send/communication-ble-client-send.ino b/examples/sandbox/ble/client-as-source/communication-ble-client-send/communication-ble-client-send.ino new file mode 100644 index 0000000000..be766c7062 --- /dev/null +++ b/examples/sandbox/ble/client-as-source/communication-ble-client-send/communication-ble-client-send.ino @@ -0,0 +1,39 @@ +/** + * @file communications-ble-client-send.ino + * @author Phil Schatzmann + * @brief Sending audio via BLE: the client acts as audio source + * @version 0.1 + * @date 2022-11-04 + * + * @copyright Copyright (c) 2022 + */ + + +#include "AudioTools.h" +#include "AudioCodecs/CodecADPCM.h" // https://github.com/pschatzmann/adpcm +#include "Sandbox/BLE/AudioBLE.h" + +AudioInfo info(16000, 1, 16); +SineWaveGenerator sineWave( 32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound( sineWave); // Stream generated from sine wave +Throttle throttle(sound); +AudioBLEClient ble; +ADPCMEncoder adpcm(AV_CODEC_ID_ADPCM_IMA_WAV); +EncodedAudioStream encoder(&ble, &adpcm); +StreamCopy copier(encoder, throttle); + +void setup() { + Serial.begin(115200); + AudioLogger::instance().begin(Serial, AudioLogger::Info); + + sineWave.begin(info, N_B4); + throttle.begin(info); + encoder.begin(info); + ble.begin("ble-send", 60*10); + + Serial.println("started..."); +} + +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/examples/sandbox/ble/client-as-source/communication-ble-server-receive/communication-ble-server-receive.ino b/examples/sandbox/ble/client-as-source/communication-ble-server-receive/communication-ble-server-receive.ino new file mode 100644 index 0000000000..63ab3185d5 --- /dev/null +++ b/examples/sandbox/ble/client-as-source/communication-ble-server-receive/communication-ble-server-receive.ino @@ -0,0 +1,38 @@ +/** + * @file communications-ble-server-receive.ino + * @author Phil Schatzmann + * @brief Receiving audio via BLE: the server acts as audio sink + * @version 0.1 + * @date 2022-11-04 + * + * @copyright Copyright (c) 2022 + */ + +#include "AudioTools.h" +#include "AudioCodecs/CodecADPCM.h" // https://github.com/pschatzmann/adpcm +#include "Sandbox/BLE/AudioBLE.h" +//#include "AudioLibs/AudioKit.h" + +AudioInfo info(16000, 1, 16); +ADPCMDecoder adpcm(AV_CODEC_ID_ADPCM_IMA_WAV); +I2SStream i2s; // or AudioKitStream ... +EncodedAudioStream decoder(&i2s, &adpcm); +AudioBLEServer ble; +StreamCopy copier(decoder, ble); + +void setup() { + Serial.begin(115200); + AudioLogger::instance().begin(Serial, AudioLogger::Warning); + + decoder.begin(info); + + auto cfg = i2s.defaultConfig(); + cfg.copyFrom(info); + i2s.begin(cfg); + + ble.begin("ble-receive"); +} + +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/examples/sandbox/ble/communication-ble-client/communication-ble-client.ino b/examples/sandbox/ble/server-as-source/communication-ble-client-receive/communication-ble-client-receive.ino similarity index 81% rename from examples/sandbox/ble/communication-ble-client/communication-ble-client.ino rename to examples/sandbox/ble/server-as-source/communication-ble-client-receive/communication-ble-client-receive.ino index 3f2269e5d8..f4ef9cff92 100644 --- a/examples/sandbox/ble/communication-ble-client/communication-ble-client.ino +++ b/examples/sandbox/ble/server-as-source/communication-ble-client-receive/communication-ble-client-receive.ino @@ -1,11 +1,11 @@ /** - * @file example-serial-receive.ino + * @file communications-ble-client-receive.ino * @author Phil Schatzmann - * @brief Receiving audio via BLE and writing to I2S + * @brief Receiving audio via BLE: The client acts as audio sink and is writing to I2S * @version 0.1 - * @date 2022-03-09 + * @date 2022-11-04 * - * @copyright Copyright (c) 2022 + * @copyright Copyright (c) 2023 */ diff --git a/examples/sandbox/ble/communication-ble-server/communication-ble-server.ino b/examples/sandbox/ble/server-as-source/communication-ble-server-send/communication-ble-server-send.ino similarity index 83% rename from examples/sandbox/ble/communication-ble-server/communication-ble-server.ino rename to examples/sandbox/ble/server-as-source/communication-ble-server-send/communication-ble-server-send.ino index 1f3a1545dd..233b917cac 100644 --- a/examples/sandbox/ble/communication-ble-server/communication-ble-server.ino +++ b/examples/sandbox/ble/server-as-source/communication-ble-server-send/communication-ble-server-send.ino @@ -1,11 +1,11 @@ /** - * @file communications-ble-send.ino + * @file communications-ble-server-send.ino * @author Phil Schatzmann - * @brief Sending audio via BLE + * @brief Sending audio via BLE: The server acts as audio source * @version 0.1 - * @date 2022-03-09 + * @date 2022-11-04 * - * @copyright Copyright (c) 2022 + * @copyright Copyright (c) 2023 */ #include "AudioTools.h" diff --git a/examples/tests/performance/throttle/throttle.ino b/examples/tests/performance/throttle/throttle.ino new file mode 100644 index 0000000000..0eb64d5f55 --- /dev/null +++ b/examples/tests/performance/throttle/throttle.ino @@ -0,0 +1,20 @@ +#include "AudioTools.h" + +AudioInfo info(44100, 2, 16); +SilenceGenerator silence; +GeneratedSoundStream sound(silence); +Throttle throttle(sound); +MeasuringStream out(200, &Serial); +StreamCopy copier(out, throttle); + +void setup() { + Serial.begin(115200); + AudioLogger::instance().begin(Serial, AudioLogger::Warning); + + out.begin(info); + sound.begin(info); + throttle.begin(info); + +} + +void loop() { copier.copy(); } \ No newline at end of file diff --git a/src/AudioCodecs/AudioEncoded.h b/src/AudioCodecs/AudioEncoded.h index 5fd15f96fd..9f76dbe9a9 100644 --- a/src/AudioCodecs/AudioEncoded.h +++ b/src/AudioCodecs/AudioEncoded.h @@ -374,7 +374,7 @@ class EncodedAudioOutput : public AudioStream { return 0; } - if(availableForWrite()==0){ + if(check_available_for_write && availableForWrite()==0){ return 0; } @@ -400,6 +400,10 @@ class EncodedAudioOutput : public AudioStream { void setLogLevel(AudioLogger::LogLevel level){ custom_log_level.set(level); } + /// Is Available for Write check activated ? + bool isCheckAvailableForWrite() { + return check_available_for_write; + } protected: //AudioInfo info; @@ -407,7 +411,8 @@ class EncodedAudioOutput : public AudioStream { AudioEncoder *encoder_ptr = CodecNOP::instance(); // decoder AudioWriter *writer_ptr = nullptr; Print *ptr_out = nullptr; - bool active; + bool active = false; + bool check_available_for_write = false; CustomLogLevel custom_log_level; }; diff --git a/src/AudioTools/AudioOutput.h b/src/AudioTools/AudioOutput.h index 3e03927413..288d9dc7ed 100644 --- a/src/AudioTools/AudioOutput.h +++ b/src/AudioTools/AudioOutput.h @@ -731,92 +731,4 @@ class ChannelSplitOutput : public AudioOutput { Vector out_chanels; }; - -/** - * @brief Configure Throttle setting - * @author Phil Schatzmann - * @copyright GPLv3 - */ -struct ThrottleConfig : public AudioInfo { - ThrottleConfig() { - sample_rate = 44100; - bits_per_sample = 16; - channels = 2; - } - int correction_us = 0; -}; - - -/** - * @brief Throttle the sending of the audio data to limit it to the indicated - * sample rate. - * @author Phil Schatzmann - * @copyright GPLv3 - */ -class Throttle : public AudioOutput { - public: - Throttle() = default; - Throttle(Print &out) { p_out = &out; } - - ThrottleConfig defaultConfig() { - ThrottleConfig c; - return c; - } - - bool begin(ThrottleConfig info) { - AudioOutput::begin(info); - this->info = info; - return begin(); - } - - bool begin(){ - info.copyFrom(cfg); - bytesPerSample = info.bits_per_sample / 8 * info.channels; - startDelay(); - return true; - } - - // (re)starts the timing - void startDelay() { - start_time = micros(); - sum_samples = 0; - } - - int availableForWrite() { - if (p_out){ - return p_out->availableForWrite(); - } - return DEFAULT_BUFFER_SIZE; - } - - size_t write(const uint8_t* data, size_t len){ - size_t result = p_out->write(data, len); - delayBytes(len); - return result; - } - - // delay - void delayBytes(size_t bytes) { delaySamples(bytes / bytesPerSample); } - - // delay - void delaySamples(size_t samples) { - sum_samples += samples; - int64_t durationUsEff = micros() - start_time; - int64_t durationUsToBe = (sum_samples * 1000000) / info.sample_rate; - int64_t waitUs = durationUsToBe - durationUsEff + info.correction_us; - LOGI("wait: %d", (int)waitUs); - if (waitUs > 0) { - delayMicroseconds(waitUs); - } - } - - protected: - uint32_t start_time = 0; - uint32_t sum_samples = 0; - ThrottleConfig info; - int bytesPerSample = 0; - Print *p_out = nullptr; -}; - - } // namespace audio_tools \ No newline at end of file diff --git a/src/AudioTools/AudioPlayer.h b/src/AudioTools/AudioPlayer.h index 45439aa090..f82adb837c 100644 --- a/src/AudioTools/AudioPlayer.h +++ b/src/AudioTools/AudioPlayer.h @@ -169,9 +169,7 @@ namespace audio_tools { autonext = p_source->isAutoNext(); // initial audio info for fade from output when not defined yet - if (fade.audioInfo().channels==0){ - setupFade(); - } + setupFade(); // start dependent objects p_out_decoding->begin(); diff --git a/src/AudioTools/AudioStreams.h b/src/AudioTools/AudioStreams.h index 9f37650e61..a143399c4b 100644 --- a/src/AudioTools/AudioStreams.h +++ b/src/AudioTools/AudioStreams.h @@ -1062,8 +1062,18 @@ class MeasuringStream : public AudioStream { return start_time; } - void setBytesPerSample(int size){ - sample_div = size; + void setAudioInfo(AudioInfo info){ + AudioStream::info = info; + setFrameSize(info.bits_per_sample / 8 *info.channels); + } + + bool begin(AudioInfo info){ + setAudioInfo(info); + return true; + } + + void setFrameSize(int size){ + frame_size = size; } protected: @@ -1074,7 +1084,7 @@ class MeasuringStream : public AudioStream { uint32_t start_time; int total_bytes = 0; int bytes_per_second = 0; - int sample_div = 0; + int frame_size = 0; NullStream null; Print *p_logout=nullptr; @@ -1098,10 +1108,10 @@ class MeasuringStream : public AudioStream { void printResult() { char msg[70]; - if (sample_div==0){ + if (frame_size==0){ sprintf(msg, "==> Bytes per second: %d", bytes_per_second); } else { - sprintf(msg, "==> Samples per second: %d", bytes_per_second/sample_div); + sprintf(msg, "==> Samples per second: %d", bytes_per_second/frame_size); } if (p_logout!=nullptr){ p_logout->println(msg); @@ -1270,6 +1280,130 @@ class ProgressStream : public AudioStream { }; +/** + * @brief Configure Throttle setting + * @author Phil Schatzmann + * @copyright GPLv3 + */ +struct ThrottleConfig : public AudioInfo { + ThrottleConfig() { + sample_rate = 44100; + bits_per_sample = 16; + channels = 2; + } + int correction_us = 0; +}; + +/** + * @brief Throttle the sending or receiving of the audio data to limit it to the indicated + * sample rate. + * @author Phil Schatzmann + * @copyright GPLv3 + */ +class Throttle : public AudioStream { + public: + Throttle() = default; + Throttle(Print &out) { p_out = &out; } + Throttle(Stream &out) { p_out = &out; p_in = &out; } + + ThrottleConfig defaultConfig() { + ThrottleConfig c; + return c; + } + + bool begin(ThrottleConfig cfg) { + LOGI("begin sample_rate: %d, channels: %d, bits: %d", info.sample_rate, info.channels, info.bits_per_sample); + this->info = cfg; + this->cfg = cfg; + return begin(); + } + + bool begin(AudioInfo info) { + LOGI("begin sample_rate: %d, channels: %d, bits: %d", info.sample_rate, info.channels, info.bits_per_sample); + this->info = info; + this->cfg.copyFrom(info); + return begin(); + } + + bool begin(){ + frame_size = cfg.bits_per_sample / 8 * cfg.channels; + startDelay(); + return true; + } + + // (re)starts the timing + void startDelay() { + start_time = micros(); + sum_frames = 0; + } + + int availableForWrite() { + if (p_out){ + return p_out->availableForWrite(); + } + return DEFAULT_BUFFER_SIZE; + } + + size_t write(const uint8_t* data, size_t len){ + size_t result = p_out->write(data, len); + delayBytes(len); + return result; + } + + int available() { + if (p_in==nullptr) return 0; + return p_in->available(); + } + + size_t readBytes(uint8_t* data, size_t len){ + if (p_in==nullptr) { + delayBytes(len); + return 0; + } + size_t result = p_in->readBytes(data, len); + delayBytes(len); + return result; + } + + // delay + void delayBytes(size_t bytes) { delayFrames(bytes / frame_size); } + + // delay + void delayFrames(size_t frames) { + sum_frames += frames; + uint64_t durationUsEff = micros() - start_time; + uint64_t durationUsToBe = getDelayUs(sum_frames); + int64_t waitUs = durationUsToBe - durationUsEff + cfg.correction_us; + LOGI("wait us: %ld", waitUs); + if (waitUs > 0) { + int64_t waitMs = waitUs / 1000; + if (waitMs > 0) delay(waitMs); + delayMicroseconds(waitUs - (waitMs * 1000)); + } else { + LOGD("negative delay!") + } + } + + inline int64_t getDelayUs(uint64_t frames){ + return (frames * 1000000) / cfg.sample_rate; + } + + inline int64_t getDelayMs(uint64_t frames){ + return getDelayUs(frames) / 1000; + } + + inline int64_t getDelaySec(uint64_t frames){ + return getDelayUs(frames) / 1000000l; + } + + protected: + uint32_t start_time = 0; + uint32_t sum_frames = 0; + ThrottleConfig cfg; + int frame_size = 0; + Print *p_out = nullptr; + Stream *p_in = nullptr; +}; /** diff --git a/src/AudioTools/Fade.h b/src/AudioTools/Fade.h index 8512e6e7da..62e18d89a3 100644 --- a/src/AudioTools/Fade.h +++ b/src/AudioTools/Fade.h @@ -253,6 +253,7 @@ class FadeStream : public AudioStream { /// same as setStream void setOutput(Stream &io) { p_io = &io; + p_out = &io; } /// same as setOutput @@ -277,6 +278,7 @@ class FadeStream : public AudioStream { int available() override { return p_io == nullptr ? 0 : p_io->available(); } size_t write(const uint8_t *buffer, size_t size) override { + if (p_out==nullptr) return 0; if (!active) { LOGE("%s", error_msg); return 0; diff --git a/src/Sandbox/BLE/AudioBLEClient.h b/src/Sandbox/BLE/AudioBLEClient.h index 009971c7d3..89224e56bf 100644 --- a/src/Sandbox/BLE/AudioBLEClient.h +++ b/src/Sandbox/BLE/AudioBLEClient.h @@ -70,17 +70,31 @@ class AudioBLEClient : public AudioBLEStream, size_t write(const uint8_t *data, size_t dataSize) override { TRACED(); setupBLEClient(); - int result = 0; if (!is_client_connected || !is_client_set_up) return 0; - if (ch02_char->canWrite()) { - ch02_char->writeValue((uint8_t *)data, dataSize, false); - result = dataSize; + if (!ch02_char->canWrite()){ + return 0; + } + + if (is_framed){ + writeChannel2Characteristic(data, dataSize); + delay(1); + } else { + // send only data with max mtu + for (int j=0; j write_buffer{0}; + int write_throttle = 0; + bool write_confirmation_flag = false; volatile bool is_client_connected = false; bool is_client_set_up = false; @@ -125,6 +148,13 @@ class AudioBLEClient : public AudioBLEStream, info_char->writeValue((uint8_t *)&info, sizeof(AudioInfo)); } + void writeChannel2Characteristic(const uint8_t*data, size_t len){ + if (ch02_char->canWrite()) { + ch02_char->writeValue((uint8_t *)data, len, write_confirmation_flag); + delay(write_throttle); + } + } + bool readAudioInfoCharacteristic(){ if (!info_char->canRead()) return false; @@ -166,6 +196,11 @@ class AudioBLEClient : public AudioBLEStream, TRACEI(); + // setup buffer + if (write_buffer.size()==0){ + write_buffer.resize(getMTU() - BLE_MTU_OVERHEAD); + } + if (p_client == nullptr) p_client = BLEDevice::createClient();