diff --git a/examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban b/examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban deleted file mode 100644 index 8185af08ff..0000000000 --- a/examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file streams-i2s-vban.ino - * @author Phil Schatzmann - * @brief sends signal from i2s (using an AudioKit) to VBAN Receptor App - */ - -#include "AudioTools.h" -#include "AudioLibs/VBANStream.h" -#include "AudioLibs/AudioKit.h" // comment out when not using AudioKit - -AudioInfo info(44100, 2, 16); -AudioKitStream in; // Audio source e.g. replace with I2SStream -VBANStream out; -StreamCopy copier(out, in, 2048); // copies sound into i2s - -// Arduino Setup -void setup(void) { - // Open Serial - Serial.begin(115200); - while(!Serial); - AudioLogger::instance().begin(Serial, AudioLogger::Info); - - // setup output - auto cfg = out.defaultConfig(TX_MODE); - cfg.copyFrom(info); - cfg.ssid = "ssid"; - cfg.password = "password"; - cfg.stream_name = "Stream1"; - cfg.target_ip = IPAddress{192,168,1,37}; // comment out to broadcast - cfg.throttle_active = false; // generator is much too fast, we need to stall - if (!out.begin(cfg)) stop(); - - // Setup input from mic - // setup input - auto cfg_in = in.defaultConfig(RX_MODE); - cfg_in.sd_active = false; - cfg_in.buffer_size = 256; - cfg_in.buffer_count = 4; - cfg_in.copyFrom(info); - cfg_in.input_device = AUDIO_HAL_ADC_INPUT_LINE2; // microphone - in.begin(cfg_in); -} - -// Arduino loop - copy sound to out -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/communication-ble-client/communication-ble-client.ino new file mode 100644 index 0000000000..639695d2ff --- /dev/null +++ b/examples/sandbox/ble/communication-ble-client/communication-ble-client.ino @@ -0,0 +1,45 @@ +/** + * @file example-serial-receive.ino + * @author Phil Schatzmann + * @brief Receiving audio via BLE and writing to I2S + * @version 0.1 + * @date 2022-03-09 + * + * @copyright Copyright (c) 2022 + */ + + +#include "AudioTools.h" +#include "AudioLibs/AudioKit.h" +#include "AudioCodecs/CodecADPCM.h" // https://github.com/pschatzmann/adpcm +#include "Sandbox/BLE/AudioBLE.h" + +AudioInfo info(8000, 1, 16); +AudioBLEClient ble; +AudioKitStream i2s; +ADPCMDecoder adpcm(AV_CODEC_ID_ADPCM_IMA_WAV); +EncodedAudioStream decoder(&i2s, &adpcm); +StreamCopy copier(decoder, ble); + +void setup() { + Serial.begin(115200); + AudioLogger::instance().begin(Serial, AudioLogger::Info); + + // start I2S + auto config = i2s.defaultConfig(TX_MODE); + config.copyFrom(info); + i2s.begin(config); + + // start decoder + decoder.begin(info); + + // start BLE client - wait at most 10 minutes + ble.begin("ble-send", 60*10); + + Serial.println("started..."); +} + +void loop() { + if (ble) + copier.copy(); +} diff --git a/examples/sandbox/ble/communication-ble-server/communication-ble-server.ino b/examples/sandbox/ble/communication-ble-server/communication-ble-server.ino new file mode 100644 index 0000000000..cc0b3794f9 --- /dev/null +++ b/examples/sandbox/ble/communication-ble-server/communication-ble-server.ino @@ -0,0 +1,36 @@ +/** + * @file communications-ble-send.ino + * @author Phil Schatzmann + * @brief Sending audio via BLE + * @version 0.1 + * @date 2022-03-09 + * + * @copyright Copyright (c) 2022 + */ + +#include "AudioTools.h" +#include "AudioCodecs/CodecADPCM.h" // https://github.com/pschatzmann/adpcm +#include "Sandbox/BLE/AudioBLE.h" + +AudioInfo info(8000, 1, 16); +SineWaveGenerator sineWave( 32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound( sineWave); // Stream generated from sine wave +AudioBLEServer ble; +ADPCMEncoder adpcm(AV_CODEC_ID_ADPCM_IMA_WAV); +EncodedAudioStream encoder(&ble, &adpcm); +StreamCopy copier(encoder, sound); + +void setup() { + Serial.begin(115200); + AudioLogger::instance().begin(Serial, AudioLogger::Info); + + sineWave.begin(info, N_B4); + + encoder.begin(info); + + ble.begin("ble-send"); +} + +void loop() { + if (ble) copier.copy(); +} \ No newline at end of file diff --git a/src/AudioCodecs/AudioEncoded.h b/src/AudioCodecs/AudioEncoded.h index 12df777848..a299f6f4b4 100644 --- a/src/AudioCodecs/AudioEncoded.h +++ b/src/AudioCodecs/AudioEncoded.h @@ -373,6 +373,10 @@ class EncodedAudioOutput : public AudioStream { return 0; } + if(availableForWrite()==0){ + return 0; + } + size_t result = writer_ptr->write(data, len); LOGD("EncodedAudioOutput::write: %d -> %d", (int)len, (int)result); custom_log_level.reset(); diff --git a/src/Experiments/AudioDAC.h b/src/Sandbox/AudioDAC.h similarity index 100% rename from src/Experiments/AudioDAC.h rename to src/Sandbox/AudioDAC.h diff --git a/src/Experiments/AudioUSB.h b/src/Sandbox/AudioUSB.h similarity index 100% rename from src/Experiments/AudioUSB.h rename to src/Sandbox/AudioUSB.h diff --git a/src/Sandbox/BLE/AudioBLE.h b/src/Sandbox/BLE/AudioBLE.h new file mode 100644 index 0000000000..8ca9f3c872 --- /dev/null +++ b/src/Sandbox/BLE/AudioBLE.h @@ -0,0 +1,9 @@ +#pragma once + +#ifdef ESP32 +# include "AudioBLEServer.h" +# include "AudioBLEClient.h" +#else +# error Device not supported +#endif + diff --git a/src/Sandbox/BLE/AudioBLEClient.h b/src/Sandbox/BLE/AudioBLEClient.h new file mode 100644 index 0000000000..25f9097a99 --- /dev/null +++ b/src/Sandbox/BLE/AudioBLEClient.h @@ -0,0 +1,162 @@ +#pragma once + +#include "AudioBLEStream.h" +//#include +#include +#include +#include + +namespace audio_tools { + +class AudioBLEClient; +static AudioBLEClient *selfAudioBLEClient = nullptr; + +/** + * @brief A simple BLE client that implements the serial protocol, so that it + * can be used to send and recevie audio. In BLE terminology this is a Central + * @ingroup communications + * @author Phil Schatzmann + * @copyright GPLv3 + */ + +class AudioBLEClient : public AudioBLEStream, + public BLEAdvertisedDeviceCallbacks { +public: + AudioBLEClient(int mtu = BLE_BUFFER_SIZE) : AudioBLEStream(mtu) { + selfAudioBLEClient = this; + } + + /// starts a BLE client + bool begin(const char *serverName, int seconds) { + TRACEI(); + ble_server_name = serverName; + // Init BLE device + BLEDevice::init("client"); + + // Retrieve a Scanner and set the callback we want to use to be informed + // when we have detected a new device. + BLEScan *pBLEScan = BLEDevice::getScan(); + pBLEScan->setAdvertisedDeviceCallbacks(this); + pBLEScan->setActiveScan(true); + pBLEScan->start(seconds); + return true; + } + + void end() override { + TRACEI(); + flush(); + BLEDevice::deinit(); + } + + size_t readBytes(uint8_t *data, size_t dataSize) override { + TRACED(); + // changed to auto to be version independent (it changed from std::string to String) + auto str = ch02_char->readValue(); + memcpy(data, str.c_str(), str.length()); + return str.length(); + } + + int available() override { return BLE_BUFFER_SIZE; } + + size_t write(const uint8_t *data, size_t dataSize) override { + ch01_char->writeValue((uint8_t *)data, dataSize, false); + return dataSize; + } + + int availableForWrite() override { return BLE_BUFFER_SIZE; } + + virtual bool connected() override { return is_client_connected; } + +protected: + // client + BLEClient *p_client = nullptr; + BLEAdvertising *p_advertising = nullptr; + BLERemoteService *p_remote_service = nullptr; + BLEAddress *p_server_address = nullptr; + BLERemoteCharacteristic *ch01_char = nullptr; + BLERemoteCharacteristic *ch02_char = nullptr; + BLERemoteCharacteristic *info_char = nullptr; + BLEAdvertisedDevice advertised_device; + bool is_client_connected = false; + + void writeAudioInfoCharacteristic(AudioInfo info) override { + TRACEI(); + // send update via BLE + info_char->writeValue((uint8_t *)&info, sizeof(AudioInfo)); + } + + // Scanning Results + void onResult(BLEAdvertisedDevice advertisedDevice) override { + TRACEI(); + // Check if the name of the advertiser matches + if (advertisedDevice.getName() == ble_server_name) { + TRACEI(); + advertised_device = advertisedDevice; + // Scan can be stopped, we found what we are looking for + advertised_device.getScan()->stop(); + // Address of advertiser is the one we need + // p_server_address = new BLEAddress(advertisedDevice.getAddress()); + + LOGI("Device '%s' found: Connecting!", + advertised_device.toString().c_str()); + setupBLEClient(); + } + delay(10); + } + + static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic, + uint8_t *pData, size_t length, bool isNotify) { + TRACEI(); + if (pBLERemoteCharacteristic->getUUID().toString() == + selfAudioBLEClient->BLE_INFO_UUID) { + selfAudioBLEClient->setAudioInfo(pData, length); + } + } + + bool setupBLEClient() { + TRACEI(); + + if (p_client == nullptr) + p_client = BLEDevice::createClient(); + + // Connect to the remove BLE Server. + LOGI("Connecting to %s ...", + advertised_device.getAddress().toString().c_str()); + // p_client->connect(advertised_device.getAddress(),BLE_ADDR_TYPE_RANDOM); + p_client->connect(&advertised_device); + if (!p_client->isConnected()) { + LOGE("connect failed"); + return false; + } + p_client->setMTU(max_transfer_size); + + LOGI("Connected to server: %s", is_client_connected ? "true" : "false"); + + // Obtain a reference to the service we are after in the remote BLE + // server. + if (p_remote_service == nullptr) { + p_remote_service = p_client->getService(BLE_SERIAL_SERVICE_UUID); + if (p_remote_service == nullptr) { + LOGE("Failed to find our service UUID: %s", BLE_SERIAL_SERVICE_UUID); + return (false); + } + } + + if (ch01_char == nullptr) { + ch01_char = p_remote_service->getCharacteristic(BLE_CH1_UUID); + } + + if (ch02_char == nullptr) { + ch02_char= p_remote_service->getCharacteristic(BLE_CH2_UUID); + } + + if (is_audio_info_active && info_char == nullptr) { + info_char = p_remote_service->getCharacteristic(BLE_INFO_UUID); + info_char->registerForNotify(notifyCallback); + } + is_client_connected = true; + return is_client_connected; + } +}; + +} // namespace audio_tools \ No newline at end of file diff --git a/src/Sandbox/BLE/AudioBLEServer.h b/src/Sandbox/BLE/AudioBLEServer.h new file mode 100644 index 0000000000..9572f4b57f --- /dev/null +++ b/src/Sandbox/BLE/AudioBLEServer.h @@ -0,0 +1,250 @@ +#pragma once + +#include "AudioBLEStream.h" +//#include +#include +#include +#include + +namespace audio_tools { +/** + * @brief A simple BLE server that implements the serial protocol, so that it + * can be used to send and recevie audio. In BLE terminologiy this is a Peripheral. + * @ingroup communications + * @author Phil Schatzmann + * @copyright GPLv3 + */ + +class AudioBLEServer : public AudioBLEStream, + public BLECharacteristicCallbacks, + public BLEServerCallbacks { +public: + AudioBLEServer(int mtu = BLE_BUFFER_SIZE) : AudioBLEStream(mtu) {} + + // starts a BLE server with the indicated name + bool begin(const char *name) { + TRACEI(); + ble_server_name = name; + BLEDevice::init(name); + BLEDevice::setMTU(BLE_BUFFER_SIZE); + // Increase connection interval to 30 milliseconds (30 * 1.25 ms) + // BLEDevice::setConnectionParams(30, 30, 0, 0); + + p_server = BLEDevice::createServer(); + p_server->setCallbacks(this); + + setupBLEService(); + + p_advertising = BLEDevice::getAdvertising(); + p_advertising->addServiceUUID(BLE_SERIAL_SERVICE_UUID); + p_advertising->setScanResponse(false); + p_advertising->setMinPreferred(0x00); + // p_advertising->setMinPreferred(0x06); + BLEDevice::startAdvertising(); + return true; + } + + void end() override { + TRACEI(); + flush(); + BLEDevice::deinit(); + } + + size_t readBytes(uint8_t *data, size_t dataSize) override { + TRACED(); + size_t read_size = getReadSize(dataSize); + return receive_buffer.readArray(data, read_size); + } + + int available() override { + if (is_framed) + return receive_sizes.peek(); + return this->receive_buffer.available(); + } + + size_t write(const uint8_t *data, size_t dataSize) override { + LOGD("AudioBLEStream::write: %d", dataSize); + if (!connected()) { + return 0; + } + if (is_framed && availableForWrite() < dataSize) { + return 0; + } + return transmit_buffer.writeArray(data, dataSize); + } + + int availableForWrite() override { + int result = transmit_buffer.availableForWrite(); + // make sure we copy always a consistent amount of data + if (result < DEFAULT_BUFFER_SIZE) result = 0; + return result ; + } + + bool connected() override { return p_server->getConnectedCount() > 0; } + +protected: + // server + BLEServer *p_server = nullptr; + BLEService *p_service = nullptr; + BLEAdvertising *p_advertising = nullptr; + BLECharacteristic *ch01_char; + BLECharacteristic *ch02_char; + BLECharacteristic *info_char; + BLEDescriptor ch01_desc{"2901"}; + BLEDescriptor ch02_desc{"2901"}; + BLEDescriptor info_desc{"2901"}; + RingBuffer receive_buffer{0}; + RingBuffer receive_sizes{0}; + RingBuffer transmit_buffer{0}; + RingBuffer transmit_buffer_sizes{0}; + + virtual void receiveAudio(const uint8_t *data, size_t size) { + setupRXBuffer(); + while (receive_buffer.availableForWrite() < size) { + // wait for ringbuffer to get freed up + delay(10); + } + if (is_framed) + receive_sizes.write(size); + receive_buffer.writeArray(data, size); + } + + void writeAudioInfoCharacteristic(AudioInfo info) { + TRACEI(); + // send update via BLE + Str str = toStr(info); + LOGI("AudioInfo: %s", str.c_str()); + info_char->setValue((uint8_t *)str.c_str(), str.length() + 1); + info_char->notify(); + } + + int getMTU() override { + TRACED(); + if (max_transfer_size == 0) { + int peer_max_transfer_size = + p_server->getPeerMTU(p_server->getConnId()) - 5; + max_transfer_size = std::min(BLE_BUFFER_SIZE, peer_max_transfer_size); + + LOGI("max_transfer_size: %d", max_transfer_size); + } + return max_transfer_size; + } + + void setupBLEService() { + TRACEI(); + // characteristic property is what the other device does. + + if (p_service == nullptr) { + p_service = p_server->createService(BLE_SERIAL_SERVICE_UUID); + + ch01_char = p_service->createCharacteristic( + BLE_CH1_UUID, BLECharacteristic::PROPERTY_READ ); + ch01_desc.setValue("Channel 1"); + ch01_char->addDescriptor(&ch01_desc); + ch01_char->setCallbacks(this); + + ch02_char = p_service->createCharacteristic( + BLE_CH2_UUID, BLECharacteristic::PROPERTY_WRITE); + ch02_desc.setValue("Channel 2"); + ch02_char->addDescriptor(&ch02_desc); + ch02_char->setCallbacks(this); + + // optional setup of audio info notifications + if (is_audio_info_active && info_char == nullptr) { + + info_char = p_service->createCharacteristic( + BLE_INFO_UUID, BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_READ | + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_INDICATE); + info_desc.setValue("Audio Info"); + info_char->addDescriptor(&info_desc); + info_char->setCallbacks(this); + } + + p_service->start(); + } + } + + void onConnect(BLEServer *pServer) override { + TRACEI(); + getMTU(); + writeAudioInfoCharacteristic(info); + } + + void onDisconnect(BLEServer *pServer) override { + TRACEI(); + BLEDevice::startAdvertising(); + } + + /// store the next batch of data + void onWrite(BLECharacteristic *pCharacteristic) override { + TRACED(); + // changed to auto to be version independent (it changed from std::string to String) + auto value = pCharacteristic->getValue(); + if (pCharacteristic->getUUID().toString() == BLE_INFO_UUID) { + setAudioInfo((uint8_t *)&value[0], value.length()); + } else { + receiveAudio((uint8_t *)&value[0], value.length()); + } + } + + /// provide the next batch of audio data + void onRead(BLECharacteristic *pCharacteristic) override { + TRACED(); + // changed to auto to be version independent (it changed from std::string to String) + auto uuid = pCharacteristic->getUUID().toString(); + if (uuid == BLE_CH1_UUID || uuid == BLE_CH2_UUID) { + setupTXBuffer(); + int len = min(getMTU(), (int)transmit_buffer.available()); + if (is_framed) { + len = transmit_buffer_sizes.read(); + } + LOGD("%s: len: %d, buffer: %d", uuid.c_str(), len, + transmit_buffer.size()); + uint8_t tmp[len]; + transmit_buffer.readArray(tmp, len); + pCharacteristic->setValue(tmp, len); + } + } + + void setupTXBuffer() { + if (transmit_buffer.size() == 0) { + transmit_buffer.resize(TX_BUFFER_SIZE); + if (is_framed) { + transmit_buffer_sizes.resize(TX_COUNT); + } + } + } + + void setupRXBuffer() { + if (receive_buffer.size() == 0) { + receive_buffer.resize(RX_BUFFER_SIZE); + if (is_framed) { + receive_sizes.resize(RX_COUNT); + } + } + } + + size_t getReadSize(size_t dataSize) { + size_t read_size = dataSize; + if (is_framed) { + read_size = 0; + if (receive_sizes.available() > 0) { + read_size = receive_sizes.read(); + } + if (dataSize < read_size) { + LOGE("read size too small: %d - it must be >= %d", dataSize, read_size); + return 0; + } + if (receive_buffer.available() < read_size) { + LOGE("missing data in buffer"); + return 0; + } + } + return read_size; + } +}; + + +} // namespace audio_tools \ No newline at end of file diff --git a/src/Sandbox/BLE/AudioBLEStream.h b/src/Sandbox/BLE/AudioBLEStream.h new file mode 100644 index 0000000000..87c534db65 --- /dev/null +++ b/src/Sandbox/BLE/AudioBLEStream.h @@ -0,0 +1,96 @@ +#pragma once + +#include "AudioBasic/Collections/Vector.h" +#include "AudioConfig.h" +#include "AudioTools/Buffers.h" + +// must be greater than MTU, less than ESP_GATT_MAX_ATTR_LEN +#define BLE_BUFFER_SIZE 512 +#define RX_BUFFER_SIZE 4096 +#define RX_COUNT 100 +#define TX_BUFFER_SIZE 4096 +#define TX_COUNT 100 + +namespace audio_tools { + +/** + * @ingroup main + * @brief Transmit and receive data via BLE using a Serial API. + * The following additional experimental features are offered: + * setFramed(true) tries to keep the original write sizes; + * setAudioInfoActive(true) informs about changes in the audio info + */ + +class AudioBLEStream : public AudioStream { +public: + AudioBLEStream(int defaultMTU) { max_transfer_size = defaultMTU; }; + + virtual void end() = 0; + + virtual bool connected() = 0; + + void setAudioInfo(AudioInfo info) { + if (is_audio_info_active && this->info != info) { + TRACED(); + AudioStream::setAudioInfo(info); + writeAudioInfoCharacteristic(info); + } + } + + operator bool() { return connected(); } + + void setServiceUUID(const char *uuid) { BLE_SERIAL_SERVICE_UUID = uuid; } + + void setRxUUID(const char *uuid) { BLE_CH2_UUID = uuid; } + + void setTxUUID(const char *uuid) { BLE_CH1_UUID = uuid; } + + void setAudioInfoUUID(const char *uuid) { BLE_INFO_UUID = uuid; } + + void setAudioInfoActive(bool flag) { is_audio_info_active = flag; } + + void setFramed(bool flag) { is_framed = flag; } + + Str toStr(AudioInfo info) { + sprintf(audio_info_str, "%d:%d:%d", info.sample_rate, info.channels, + info.bits_per_sample); + return Str(audio_info_str); + } + + AudioInfo toInfo(const uint8_t *str) { + AudioInfo result; + sscanf((char*)str,"%d:%d:%d", &result.sample_rate, &result.channels, &result.bits_per_sample); + return result; + } + +protected: + // disable copy constructor + AudioBLEStream(AudioBLEStream const &other) = delete; + // disable assign constructor + void operator=(AudioBLEStream const &other) = delete; + const char *ble_server_name = nullptr; + uint16_t max_transfer_size = 0; + bool is_started = false; + bool is_audio_info_active = false; + bool is_framed = false; + char audio_info_str[40]; + + // Bluetooth LE GATT UUIDs for the Nordic UART profile Change UUID here if + // required + const char *BLE_SERIAL_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e"; + const char *BLE_CH1_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e"; // RX + const char *BLE_CH2_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // TX + const char *BLE_INFO_UUID = "6e400004-b5a3-f393-e0a9-e50e24dcca9e"; + + virtual int getMTU() { return BLE_BUFFER_SIZE; } + + virtual void setAudioInfo(const uint8_t *data, size_t size) { + if (is_audio_info_active) { + AudioInfo ai = toInfo(data); + setAudioInfo(ai); + } + } + virtual void writeAudioInfoCharacteristic(AudioInfo info) = 0; +}; + +} // namespace audio_tools \ No newline at end of file diff --git a/src/Experiments/CodecGGWave.h b/src/Sandbox/CodecGGWave.h similarity index 100% rename from src/Experiments/CodecGGWave.h rename to src/Sandbox/CodecGGWave.h diff --git a/src/Experiments/CodecMTS.h b/src/Sandbox/CodecMTS.h similarity index 100% rename from src/Experiments/CodecMTS.h rename to src/Sandbox/CodecMTS.h diff --git a/src/Experiments/FrequencyDetection.h b/src/Sandbox/FrequencyDetection.h similarity index 100% rename from src/Experiments/FrequencyDetection.h rename to src/Sandbox/FrequencyDetection.h diff --git a/src/Experiments/I2SBitBang.h b/src/Sandbox/I2SBitBang.h similarity index 100% rename from src/Experiments/I2SBitBang.h rename to src/Sandbox/I2SBitBang.h diff --git a/src/Experiments/PDMStream.h b/src/Sandbox/PDMStream.h similarity index 100% rename from src/Experiments/PDMStream.h rename to src/Sandbox/PDMStream.h diff --git a/src/Experiments/README.md b/src/Sandbox/README.md similarity index 100% rename from src/Experiments/README.md rename to src/Sandbox/README.md