From fbd9edf7a8feb08382398a061982afe9b435df2c Mon Sep 17 00:00:00 2001 From: Phil Schatzmann Date: Tue, 10 Oct 2023 23:20:17 +0200 Subject: [PATCH] vban support --- .../streams-audiokit-vban | 47 +++ .../streams-generator-vban.ino | 42 +++ src/AudioLibs/Communication.h | 1 + src/AudioLibs/VBANStream.h | 352 ++++++++++++++++++ src/AudioLibs/vban/vban.h | 205 ++++++++++ src/AudioTools/AudioStreams.h | 2 +- 6 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban create mode 100644 examples/examples-communication/vban/streams-generator-vban/streams-generator-vban.ino create mode 100644 src/AudioLibs/VBANStream.h create mode 100644 src/AudioLibs/vban/vban.h diff --git a/examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban b/examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban new file mode 100644 index 0000000000..8185af08ff --- /dev/null +++ b/examples/examples-communication/vban/streams-audiokit-vban/streams-audiokit-vban @@ -0,0 +1,47 @@ +/** + * @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/examples-communication/vban/streams-generator-vban/streams-generator-vban.ino b/examples/examples-communication/vban/streams-generator-vban/streams-generator-vban.ino new file mode 100644 index 0000000000..e79dd5219d --- /dev/null +++ b/examples/examples-communication/vban/streams-generator-vban/streams-generator-vban.ino @@ -0,0 +1,42 @@ +/** + * @file streams-generator-vban.ino + * @author Phil Schatzmann + * @brief sends sine test signal to VBAN Receptor App + */ + +#include "AudioTools.h" +#include "AudioLibs/VBANStream.h" + +AudioInfo info(44100, 2, 16); +SineWaveGenerator sineWave(32000); // subclass of SoundGenerator with max amplitude of 32000 +GeneratedSoundStream sound(sineWave); // Stream generated from sine wave +VBANStream out; +StreamCopy copier(out, sound, 2048); // 44100 needs 2048 + +// 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}; + cfg.throttle_active = true; + cfg.throttle_correction_ms = -4; // optimize overload and underrun + if (!out.begin(cfg)) stop(); + + // Setup sine wave + sineWave.begin(info, N_B4); + Serial.println("started..."); +} + +// Arduino loop - copy sound to out +void loop() { + copier.copy(); +} \ No newline at end of file diff --git a/src/AudioLibs/Communication.h b/src/AudioLibs/Communication.h index 5a0e507f57..6ac4a273fe 100644 --- a/src/AudioLibs/Communication.h +++ b/src/AudioLibs/Communication.h @@ -780,6 +780,7 @@ class Throttle { int durationMsEff = millis() - start_time; int durationToBe = (samples * 1000) / info.sample_rate; int waitMs = durationToBe - durationMsEff + info.correction_ms; + LOGI("wait: %d", waitMs); if (waitMs > 0) { delay(waitMs); } diff --git a/src/AudioLibs/VBANStream.h b/src/AudioLibs/VBANStream.h new file mode 100644 index 0000000000..ab60a13d63 --- /dev/null +++ b/src/AudioLibs/VBANStream.h @@ -0,0 +1,352 @@ + +#include +#include +#include "AudioLibs/vban/vban.h" +#include "AudioTools/AudioStreams.h" +#include "AudioLibs/Communication.h" // for + +namespace audio_tools { + +class VBANConfig : public AudioInfo { + public: + VBANConfig() { + sample_rate = 11025; + channels = 1; + bits_per_sample = 16; + } + RxTxMode mode; + /// name of the stream + const char* stream_name = "Stream1"; + /// default port is 6980 + uint16_t udp_port = 6980; + /// Use {0,0,0,0}; as broadcast address + IPAddress target_ip{0,0,0,0}; + /// ssid for wifi connection + const char* ssid = nullptr; + /// password for wifi connection + const char* password = nullptr; + int rx_buffer_count = 10; + // set to true if samples are generated faster then sample rate + bool throttle_active = false; + // when negative the number of ms that are subtracted from the calculated wait time to fine tune Overload and Underruns + int throttle_correction_ms = -2; + // defines the max write size + int max_write_size = DEFAULT_BUFFER_SIZE * 2; // just good enough for 44100 stereo +}; + +/** + * @brief VBAN Audio Source and Sink for the ESP32. For further details please + * see https://vb-audio.com/Voicemeeter/vban.htm . + * Inspired by https://github.com/rkinnett/ESP32-VBAN-Audio-Source/tree/master + * and https://github.com/rkinnett/ESP32-VBAN-Network-Audio-Player + * @ingroup communications + * @author Phil Schatzmann + * @copyright GPLv3 +*/ + +class VBANStream : public AudioStream { + public: + VBANConfig defaultConfig(RxTxMode mode=TX_MODE){ + VBANConfig def; + def.mode = mode; + return def; + } + + void setAudioInfo(AudioInfo info) override { + cfg.copyFrom(info); + AudioStream::setAudioInfo(info); + auto thc = throttle.defaultConfig(); + thc.copyFrom(info); + thc.correction_ms = cfg.throttle_correction_ms;; + throttle.begin(thc); + if (cfg.mode==TX_MODE){ + configure_tx(); + } + } + + bool begin(VBANConfig cfg) { + this->cfg = cfg; + setAudioInfo(cfg); + return begin(); + } + + bool begin() { + if (cfg.mode == TX_MODE){ + if (cfg.bits_per_sample!=16){ + LOGE("Only 16 bits supported") + return false; + } + tx_buffer.resize(VBAN_PACKET_NUM_SAMPLES); + return begin_tx(); + } else { + rx_buffer.resize(VBAN_PACKET_MAX_LEN_BYTES, cfg.rx_buffer_count); + return begin_rx(); + } + } + + size_t write(const uint8_t* data, size_t byteCount) override { + if (!udp_connected) return 0; + + int16_t *adc_data = (int16_t*)data;; + size_t samples = byteCount / 2; + + // limit output speed + if (cfg.throttle_active){ + throttle.delaySamples(samples / cfg.channels); + } + + for (int j=0; j tx_buffer{0}; + NBuffer rx_buffer{VBAN_PACKET_MAX_LEN_BYTES, 0}; + bool udp_connected = false; + uint32_t packet_counter = 0; + Throttle throttle; + + + bool begin_tx(){ + if (!configure_tx()){ + return false; + } + start_wifi(); + if (WiFi.status() != WL_CONNECTED){ + LOGE("Wifi not connected"); + return false; + } + WiFi.setSleep(false); + IPAddress myIP = WiFi.localIP(); + udp_connected = udp.connect(myIP, cfg.udp_port); + return udp_connected; + } + + bool begin_rx(){ + start_wifi(); + if (WiFi.status() != WL_CONNECTED){ + LOGE("Wifi not connected"); + return false; + } + WiFi.setSleep(false); + udp.onPacket([this](AsyncUDPPacket packet) { + receive_udp(packet); + }); + + return true; + } + + bool configure_tx(){ + int rate = vban_sample_rate(); + if (rate<0){ + LOGE("Invalid sample rate: %d", cfg.sample_rate); + return false; + } + configure_vban((VBanSampleRates)rate); + return true; + } + + void start_wifi(){ + if(cfg.ssid==nullptr) return; + if(cfg.password==nullptr) return; + LOGI("ssid %s", cfg.ssid); + // Setup Wifi: + WiFi.begin(cfg.ssid, cfg.password); //Connect to your WiFi router + while (WiFi.status() != WL_CONNECTED) { // Wait for connection + delay(500); + Serial.print("."); + } + Serial.println(); + + LOGI("Wifi connected to IP (%d.%d.%d.%d)",WiFi.localIP()[0],WiFi.localIP()[1],WiFi.localIP()[2],WiFi.localIP()[3]); + } + + + void configure_vban(VBanSampleRates rate) { + // Set vban packet header, counter, and data frame pointers to respective parts of packet: + vban.hdr = (VBanHeader*) &vban.packet[0]; + vban.packet_counter = (uint32_t*) &vban.packet[VBAN_PACKET_HEADER_BYTES]; + vban.data_frame = (uint8_t*) &vban.packet[VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES]; + + // Setup the packet header: + strncpy(vban.hdr->preamble, "VBAN", 4); + vban.hdr->sample_rate = VBAN_PROTOCOL_AUDIO | rate; // 11025 Hz, which matches default sample rate for soundmodem + vban.hdr->num_samples = (VBAN_PACKET_NUM_SAMPLES / cfg.channels)-1; // 255 = 256 samples + vban.hdr->num_channels = cfg.channels - 1; // 0 = 1 channel + vban.hdr->sample_format = VBAN_BITFMT_16_INT | VBAN_CODEC_PCM; // int16 PCM + strncpy(vban.hdr->stream_name, cfg.stream_name, min((int)strlen(cfg.stream_name),VBAN_STREAM_NAME_SIZE)); + + vban.packet_data_bytes = (vban.hdr->num_samples+1) * (vban.hdr->num_channels+1) * ((vban.hdr->sample_format & VBAN_BIT_RESOLUTION_MASK)+1); + vban.packet_total_bytes = vban.packet_data_bytes + VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES; + } + + + int vban_sample_rate(){ + int result = -1; + switch(cfg.sample_rate){ + case 6000: + result = SAMPLE_RATE_6000_HZ; + break; + case 12000: + result = SAMPLE_RATE_12000_HZ; + break; + case 24000: + result = SAMPLE_RATE_24000_HZ; + break; + case 48000: + result = SAMPLE_RATE_48000_HZ; + break; + case 96000: + result = SAMPLE_RATE_96000_HZ; + break; + case 192000: + result = SAMPLE_RATE_192000_HZ; + break; + case 384000: + result = SAMPLE_RATE_384000_HZ; + break; + case 8000: + result = SAMPLE_RATE_8000_HZ; + break; + case 16000: + result = SAMPLE_RATE_16000_HZ; + break; + case 32000: + result = SAMPLE_RATE_32000_HZ; + break; + case 64000: + result = SAMPLE_RATE_64000_HZ; + break; + case 128000: + result = SAMPLE_RATE_128000_HZ; + break; + case 256000: + result = SAMPLE_RATE_256000_HZ; + break; + case 512000: + result = SAMPLE_RATE_512000_HZ; + break; + case 11025: + result = SAMPLE_RATE_11025_HZ; + break; + case 22050: + result = SAMPLE_RATE_22050_HZ; + break; + case 44100: + result = SAMPLE_RATE_44100_HZ; + break; + case 88200: + result = SAMPLE_RATE_88200_HZ; + break; + case 176400: + result = SAMPLE_RATE_176400_HZ; + break; + case 352800: + result = SAMPLE_RATE_352800_HZ; + break; + case 705600: + result = SAMPLE_RATE_705600_HZ; + break; + } + return result; + } + + /** + * @brief VBAN adjusts the number of samples per packet according to sample rate. + * Assuming 16-bit PCM mono, sample rates 11025, 22050, 44100, and 88200 yield + * packets containing 64, 128, 256, and 256 samples per packet, respectively. + * The even-thousands sample rates below 48000 yield non-power-of-2 lengths. + * For example, sample rate 24000 yields 139 samples per packet. + * This VBAN->DMA->DAC method seems to require the dma buffer length be set + * equal to the number of samples in each VBAN packet. + * ESP32 I2S/DMA does not seem to handle non-power-of-2 buffer lengths well. + * Sample rate 24000 doesn't work reliably at all. + * Sample rate 32000 is stable but stutters. + * Recommend selecting from sample rates 11025, 22050, 44100, and above + * And set samplesPerPacket to 64 for 11025, 128 for 22050, or 256 for all else. + **/ + + void receive_udp(AsyncUDPPacket &packet){ + uint16_t vban_rx_data_bytes, vban_rx_sample_count; + int16_t* vban_rx_data; + uint32_t* vban_rx_pkt_nbr; + uint16_t outBuf[VBAN_PACKET_MAX_SAMPLES+1]; + size_t bytesOut; + + int len = packet.length(); + if (len>0) { + uint8_t* udpIncomingPacket = packet.data(); + + // receive incoming UDP packet + // Check if packet length meets VBAN specification: + if (len<=(VBAN_PACKET_HEADER_BYTES+VBAN_PACKET_COUNTER_BYTES) || len>VBAN_PACKET_MAX_LEN_BYTES) { + LOGE("Error: packet length %u bytes\n", len); + return; + } + + // Check if preamble matches VBAN format: + if(strncmp("VBAN",(const char*)udpIncomingPacket,4)!=0){ + LOGE("Unrecognized preamble %.4s\n", udpIncomingPacket); + return; + } + + vban_rx_data_bytes = len - (VBAN_PACKET_HEADER_BYTES+VBAN_PACKET_COUNTER_BYTES); + vban_rx_pkt_nbr = (uint32_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES]; + vban_rx_data = (int16_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES+VBAN_PACKET_COUNTER_BYTES]; + vban_rx_sample_count = vban_rx_data_bytes/2; + uint8_t vbanSampleRateIdx = udpIncomingPacket[4] & VBAN_SR_MASK; + uint8_t vbchannels = udpIncomingPacket[6]+1; + uint32_t vbanSampleRate = VBanSRList[vbanSampleRateIdx]; + + // Just to be safe, re-check sample count against max sample count to avoid overrunning outBuf later + if(vban_rx_sample_count > VBAN_PACKET_MAX_SAMPLES){ + LOGE("error: unexpected packet size: %u\n",vban_rx_sample_count); + return; + } + + // update sample rate + if (cfg.sample_rate != vbanSampleRate || cfg.channels != vbchannels){ + cfg.sample_rate = vbanSampleRate; + cfg.channels = vbchannels; + setAudioInfo(cfg); + } + + // write data to buffer + rx_buffer.writeArray((uint8_t*)&vban_rx_data, vban_rx_sample_count*sizeof(uint16_t)); + } + } +}; + +} \ No newline at end of file diff --git a/src/AudioLibs/vban/vban.h b/src/AudioLibs/vban/vban.h new file mode 100644 index 0000000000..59785c2853 --- /dev/null +++ b/src/AudioLibs/vban/vban.h @@ -0,0 +1,205 @@ +/* + * This file is part of vban. + * Copyright (c) 2015 by BenoƮt Quiniou + * + * vban is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * vban is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with vban. If not, see . + */ + + +/////////////////////////////////////////////////////////////////////// +// +// MODIFIED by R. Kinnett, https://github.com/rkinnett, 2020 +// +/////////////////////////////////////////////////////////////////////// + + +#ifndef __VBAN_H__ +#define __VBAN_H__ + +#include + + +#define VBAN_HEADER_SIZE (4 + 1 + 1 + 1 + 1 + 16) +#define VBAN_STREAM_NAME_SIZE 16 +#define VBAN_PROTOCOL_MAX_SIZE 1464 +#define VBAN_DATA_MAX_SIZE (VBAN_PROTOCOL_MAX_SIZE - VBAN_HEADER_SIZE) +#define VBAN_CHANNELS_MAX_NB 256 +#define VBAN_SAMPLES_MAX_NB 256 + + + +#define VBAN_PACKET_NUM_SAMPLES 256 +#define VBAN_PACKET_MAX_SAMPLES 256 +#define VBAN_PACKET_HEADER_BYTES 24 +#define VBAN_PACKET_COUNTER_BYTES 4 +#define VBAN_PACKET_MAX_LEN_BYTES (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES + VBAN_PACKET_MAX_SAMPLES*2) + + +typedef struct +{ + char preamble[4]; /* contains 'V' 'B', 'A', 'N' */ + uint8_t sample_rate; /* SR index (see SRList above) */ + uint8_t num_samples; /* nb sample per frame (1 to 256) */ + uint8_t num_channels; /* nb channel (1 to 256) */ + uint8_t sample_format; /* mask = 0x07 (nb Byte integer from 1 to 4) */ + char stream_name[VBAN_STREAM_NAME_SIZE]; /* stream name */ +} VBanHeader; + + +typedef struct { + VBanHeader* hdr; + uint32_t* packet_counter; + uint8_t* data_frame; + uint8_t packet[VBAN_PROTOCOL_MAX_SIZE]; + uint16_t packet_data_bytes; + uint16_t packet_total_bytes; +} VBan; + + + + +#define VBAN_SR_MASK 0x1F +#define VBAN_SR_MAXNUMBER 21 +static long const VBanSRList[VBAN_SR_MAXNUMBER]= +{ + 6000, 12000, 24000, 48000, 96000, 192000, 384000, + 8000, 16000, 32000, 64000, 128000, 256000, 512000, + 11025, 22050, 44100, 88200, 176400, 352800, 705600 +}; + +enum VBanSampleRates +{ + SAMPLE_RATE_6000_HZ, + SAMPLE_RATE_12000_HZ, + SAMPLE_RATE_24000_HZ, + SAMPLE_RATE_48000_HZ, + SAMPLE_RATE_96000_HZ, + SAMPLE_RATE_192000_HZ, + SAMPLE_RATE_384000_HZ, + SAMPLE_RATE_8000_HZ, + SAMPLE_RATE_16000_HZ, + SAMPLE_RATE_32000_HZ, + SAMPLE_RATE_64000_HZ, + SAMPLE_RATE_128000_HZ, + SAMPLE_RATE_256000_HZ, + SAMPLE_RATE_512000_HZ, + SAMPLE_RATE_11025_HZ, + SAMPLE_RATE_22050_HZ, + SAMPLE_RATE_44100_HZ, + SAMPLE_RATE_88200_HZ, + SAMPLE_RATE_176400_HZ, + SAMPLE_RATE_352800_HZ, + SAMPLE_RATE_705600_HZ +}; + + +#define VBAN_PROTOCOL_MASK 0xE0 +enum VBanProtocol +{ + VBAN_PROTOCOL_AUDIO = 0x00, + VBAN_PROTOCOL_SERIAL = 0x20, + VBAN_PROTOCOL_TXT = 0x40, + VBAN_PROTOCOL_UNDEFINED_1 = 0x80, + VBAN_PROTOCOL_UNDEFINED_2 = 0xA0, + VBAN_PROTOCOL_UNDEFINED_3 = 0xC0, + VBAN_PROTOCOL_UNDEFINED_4 = 0xE0 +}; + +#define VBAN_BIT_RESOLUTION_MASK 0x07 +enum VBanBitResolution +{ + VBAN_BITFMT_8_INT = 0, + VBAN_BITFMT_16_INT, + VBAN_BITFMT_24_INT, + VBAN_BITFMT_32_INT, + VBAN_BITFMT_32_FLOAT, + VBAN_BITFMT_64_FLOAT, + VBAN_BITFMT_12_INT, + VBAN_BITFMT_10_INT, + VBAN_BIT_RESOLUTION_MAX +}; + +static int const VBanBitResolutionSize[VBAN_BIT_RESOLUTION_MAX] = +{ + 1, 2, 3, 4, 4, 8, +}; + +#define VBAN_RESERVED_MASK 0x08 + +#define VBAN_CODEC_MASK 0xF0 +enum VBanCodec +{ + VBAN_CODEC_PCM = 0x00, + VBAN_CODEC_VBCA = 0x10, + VBAN_CODEC_VBCV = 0x20, + VBAN_CODEC_UNDEFINED_3 = 0x30, + VBAN_CODEC_UNDEFINED_4 = 0x40, + VBAN_CODEC_UNDEFINED_5 = 0x50, + VBAN_CODEC_UNDEFINED_6 = 0x60, + VBAN_CODEC_UNDEFINED_7 = 0x70, + VBAN_CODEC_UNDEFINED_8 = 0x80, + VBAN_CODEC_UNDEFINED_9 = 0x90, + VBAN_CODEC_UNDEFINED_10 = 0xA0, + VBAN_CODEC_UNDEFINED_11 = 0xB0, + VBAN_CODEC_UNDEFINED_12 = 0xC0, + VBAN_CODEC_UNDEFINED_13 = 0xD0, + VBAN_CODEC_UNDEFINED_14 = 0xE0, + VBAN_CODEC_USER = 0xF0 +}; + + +/******************************************************** + * TEXT SUB PROTOCOL * + ********************************************************/ + +#define VBAN_BPS_MASK 0xE0 +#define VBAN_BPS_MAXNUMBER 25 +static long const VBanBPSList[VBAN_BPS_MAXNUMBER] = +{ + 0, 110, 150, 300, 600, + 1200, 2400, 4800, 9600, 14400, + 19200, 31250, 38400, 57600, 115200, + 128000, 230400, 250000, 256000, 460800, + 921600,1000000,1500000,2000000, 3000000 +}; + +#define VBAN_DATATYPE_MASK 0x07 +#define VBAN_DATATYPE_MAXNUMBER 1 +enum VBanDataTypeList +{ + VBAN_DATATYPE_8BITS = 0 +}; + +#define VBAN_STREAMTYPE_MASK 0xF0 +enum VBanStreamType +{ + VBAN_TXT_ASCII = 0x00, + VBAN_TXT_UTF8 = 0x10, + VBAN_TXT_WCHAR = 0x20, + VBAN_TXT_UNDEFINED_3 = 0x30, + VBAN_TXT_UNDEFINED_4 = 0x40, + VBAN_TXT_UNDEFINED_5 = 0x50, + VBAN_TXT_UNDEFINED_6 = 0x60, + VBAN_TXT_UNDEFINED_7 = 0x70, + VBAN_TXT_UNDEFINED_8 = 0x80, + VBAN_TXT_UNDEFINED_9 = 0x90, + VBAN_TXT_UNDEFINED_10 = 0xA0, + VBAN_TXT_UNDEFINED_11 = 0xB0, + VBAN_TXT_UNDEFINED_12 = 0xC0, + VBAN_TXT_UNDEFINED_13 = 0xD0, + VBAN_TXT_UNDEFINED_14 = 0xE0, + VBAN_TXT_USER = 0xF0 +}; + +#endif /*__VBAN_H__*/ diff --git a/src/AudioTools/AudioStreams.h b/src/AudioTools/AudioStreams.h index 3db875020a..38d3a6c3db 100644 --- a/src/AudioTools/AudioStreams.h +++ b/src/AudioTools/AudioStreams.h @@ -654,7 +654,7 @@ class GeneratedSoundStream : public AudioStream { } /// This is unbounded so we just return the buffer size - virtual int available() override { return DEFAULT_BUFFER_SIZE; } + virtual int available() override { return DEFAULT_BUFFER_SIZE*2; } /// privide the data as byte stream size_t readBytes(uint8_t *buffer, size_t length) override {