arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Modules Pages
VBANStream.h
1
2#include <AsyncUDP.h>
3#include <WiFi.h>
4
5#include "AudioTools/AudioLibs/vban/vban.h"
6#include "AudioTools/CoreAudio/AudioStreams.h"
7#include "AudioTools/Concurrency/RTOS/BufferRTOS.h"
8
9namespace audio_tools {
10
11class VBANConfig : public AudioInfo {
12 public:
13 VBANConfig() {
14 sample_rate = 11025;
15 channels = 1;
16 bits_per_sample = 16;
17 }
18 RxTxMode mode;
20 const char* stream_name = "Stream1";
24 IPAddress target_ip{0, 0, 0, 0};
26 const char* ssid = nullptr;
28 const char* password = nullptr;
29 int rx_buffer_count = 30;
30 // set to true if samples are generated faster then sample rate
31 bool throttle_active = false;
32 // when negative the number of ms that are subtracted from the calculated wait
33 // time to fine tune Overload and Underruns
34 int throttle_correction_us = 0;
35 // defines the max write size
36 int max_write_size =
37 DEFAULT_BUFFER_SIZE * 2; // just good enough for 44100 stereo
38 uint8_t format = 0;
39};
40
51class VBANStream : public AudioStream {
52 public:
53 VBANConfig defaultConfig(RxTxMode mode = TX_MODE) {
55 def.mode = mode;
56 return def;
57 }
58
59 void setOutput(Print &out){
60 p_out = &out;
61 }
62
63 void setAudioInfo(AudioInfo info) override {
64 cfg.copyFrom(info);
66 auto thc = throttle.defaultConfig();
67 thc.copyFrom(info);
68 thc.correction_us = cfg.throttle_correction_us;
69 throttle.begin(thc);
70 if (cfg.mode == TX_MODE) {
71 configure_tx();
72 }
73 }
74
75 bool begin(VBANConfig cfg) {
76 this->cfg = cfg;
77 setAudioInfo(cfg);
78 return begin();
79 }
80
81 bool begin() {
82 if (cfg.mode == TX_MODE) {
83 if (cfg.bits_per_sample != 16) {
84 LOGE("Only 16 bits supported")
85 return false;
86 }
87 tx_buffer.resize(VBAN_PACKET_NUM_SAMPLES);
88 return begin_tx();
89 } else {
90#ifdef ESP32
91 rx_buffer.resize(DEFAULT_BUFFER_SIZE * cfg.rx_buffer_count);
92 rx_buffer.setReadMaxWait(10);
93#else
94 rx_buffer.resize(DEFAULT_BUFFER_SIZE, cfg.rx_buffer_count);
95#endif
96 return begin_rx();
97 }
98 }
99
100 size_t write(const uint8_t* data, size_t len) override {
101 if (!udp_connected) return 0;
102
103 int16_t* adc_data = (int16_t*)data;
104 size_t samples = len / (cfg.bits_per_sample/8);
105
106 // limit output speed
107 if (cfg.throttle_active) {
108 throttle.delayFrames(samples / cfg.channels);
109 }
110
111 for (int j = 0; j < samples; j++) {
112 tx_buffer.write(adc_data[j]);
113 if (tx_buffer.availableForWrite() == 0) {
114 memcpy(vban.data_frame, tx_buffer.data(), vban.packet_data_bytes);
115 *vban.packet_counter = packet_counter; // increment packet counter
116 // Send packet
117 if (cfg.target_ip == broadcast_address) {
118 udp.broadcastTo((uint8_t*)&vban.packet, vban.packet_total_bytes,
119 cfg.udp_port);
120 } else {
121 udp.writeTo((uint8_t*)&vban.packet, vban.packet_total_bytes,
122 cfg.target_ip, cfg.udp_port);
123 }
124 // defile delay start time
125 packet_counter++;
126 tx_buffer.reset();
127 }
128 }
129 return len;
130 }
131
132 int availableForWrite() { return cfg.max_write_size; }
133
134 size_t readBytes(uint8_t* data, size_t len) override {
135 TRACED();
136 size_t samples = len / (cfg.bits_per_sample/8);
137 if (cfg.throttle_active) {
138 throttle.delayFrames(samples / cfg.channels);
139 }
140 return rx_buffer.readArray(data, len);
141 }
142
143 int available() { return available_active ? rx_buffer.available() : 0; }
144
145 protected:
146 const IPAddress broadcast_address{0, 0, 0, 0};
147 AsyncUDP udp;
148 VBan vban;
149 VBANConfig cfg;
150 SingleBuffer<int16_t> tx_buffer{0};
151 #ifdef ESP32
152 BufferRTOS<uint8_t> rx_buffer{ 0};
153 #else
154 NBuffer<uint8_t> rx_buffer{DEFAULT_BUFFER_SIZE, 0};
155 #endif
156 bool udp_connected = false;
157 uint32_t packet_counter = 0;
158 Throttle throttle;
159 size_t bytes_received = 0;
160 bool available_active = false;
161 Print *p_out = nullptr;
162
163 bool begin_tx() {
164 if (!configure_tx()) {
165 return false;
166 }
167 start_wifi();
168 if (WiFi.status() != WL_CONNECTED) {
169 LOGE("Wifi not connected");
170 return false;
171 }
172 WiFi.setSleep(false);
173 IPAddress myIP = WiFi.localIP();
174 udp_connected = udp.connect(myIP, cfg.udp_port);
175 return udp_connected;
176 }
177
178 bool begin_rx() {
179 start_wifi();
180 if (WiFi.status() != WL_CONNECTED) {
181 LOGE("Wifi not connected");
182 return false;
183 }
184 WiFi.setSleep(false);
185 bytes_received = 0;
186 this->available_active = false;
187 // connect to target
188 if (!udp.listen(cfg.udp_port)) {
189 LOGE("Could not connect to '%s:%d' target", toString(cfg.target_ip),
190 cfg.udp_port);
191 }
192 // handle data
193 udp.onPacket([this](AsyncUDPPacket packet) { receive_udp(packet); });
194
195 return true;
196 }
197
198 bool configure_tx() {
199 int rate = vban_sample_rate();
200 if (rate < 0) {
201 LOGE("Invalid sample rate: %d", cfg.sample_rate);
202 return false;
203 }
204 configure_vban((VBanSampleRates)rate);
205 return true;
206 }
207
208 void start_wifi() {
209 if (cfg.ssid == nullptr) return;
210 if (cfg.password == nullptr) return;
211 LOGI("ssid %s", cfg.ssid);
212 // Setup Wifi:
213 WiFi.begin(cfg.ssid, cfg.password); // Connect to your WiFi router
214 while (WiFi.status() != WL_CONNECTED) { // Wait for connection
215 delay(500);
216 Serial.print(".");
217 }
218 Serial.println();
219
220 LOGI("Wifi connected to IP (%d.%d.%d.%d)", WiFi.localIP()[0],
221 WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
222 }
223
224 void configure_vban(VBanSampleRates rate) {
225 // Set vban packet header, counter, and data frame pointers to respective
226 // parts of packet:
227 vban.hdr = (VBanHeader*)&vban.packet[0];
228 vban.packet_counter = (uint32_t*)&vban.packet[VBAN_PACKET_HEADER_BYTES];
229 vban.data_frame =
230 (uint8_t*)&vban
231 .packet[VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES];
232
233 // Setup the packet header:
234 strncpy(vban.hdr->preamble, "VBAN", 4);
235 vban.hdr->sample_rate =
236 static_cast<int>(VBAN_PROTOCOL_AUDIO) |
237 rate; // 11025 Hz, which matches default sample rate for soundmodem
238 vban.hdr->num_samples =
239 (VBAN_PACKET_NUM_SAMPLES / cfg.channels) - 1; // 255 = 256 samples
240 vban.hdr->num_channels = cfg.channels - 1; // 0 = 1 channel
241 vban.hdr->sample_format =
242 static_cast<int>(VBAN_BITFMT_16_INT) | VBAN_CODEC_PCM; // int16 PCM
243 strncpy(vban.hdr->stream_name, cfg.stream_name,
244 min((int)strlen(cfg.stream_name), VBAN_STREAM_NAME_SIZE));
245
246 vban.packet_data_bytes =
247 (vban.hdr->num_samples + 1) * (vban.hdr->num_channels + 1) *
248 ((vban.hdr->sample_format & VBAN_BIT_RESOLUTION_MASK) + 1);
249 vban.packet_total_bytes = vban.packet_data_bytes +
250 VBAN_PACKET_HEADER_BYTES +
251 VBAN_PACKET_COUNTER_BYTES;
252 }
253
254 int vban_sample_rate() {
255 int result = -1;
256 switch (cfg.sample_rate) {
257 case 6000:
258 result = SAMPLE_RATE_6000_HZ;
259 break;
260 case 12000:
261 result = SAMPLE_RATE_12000_HZ;
262 break;
263 case 24000:
264 result = SAMPLE_RATE_24000_HZ;
265 break;
266 case 48000:
267 result = SAMPLE_RATE_48000_HZ;
268 break;
269 case 96000:
270 result = SAMPLE_RATE_96000_HZ;
271 break;
272 case 192000:
273 result = SAMPLE_RATE_192000_HZ;
274 break;
275 case 384000:
276 result = SAMPLE_RATE_384000_HZ;
277 break;
278 case 8000:
279 result = SAMPLE_RATE_8000_HZ;
280 break;
281 case 16000:
282 result = SAMPLE_RATE_16000_HZ;
283 break;
284 case 32000:
285 result = SAMPLE_RATE_32000_HZ;
286 break;
287 case 64000:
288 result = SAMPLE_RATE_64000_HZ;
289 break;
290 case 128000:
291 result = SAMPLE_RATE_128000_HZ;
292 break;
293 case 256000:
294 result = SAMPLE_RATE_256000_HZ;
295 break;
296 case 512000:
297 result = SAMPLE_RATE_512000_HZ;
298 break;
299 case 11025:
300 result = SAMPLE_RATE_11025_HZ;
301 break;
302 case 22050:
303 result = SAMPLE_RATE_22050_HZ;
304 break;
305 case 44100:
306 result = SAMPLE_RATE_44100_HZ;
307 break;
308 case 88200:
309 result = SAMPLE_RATE_88200_HZ;
310 break;
311 case 176400:
312 result = SAMPLE_RATE_176400_HZ;
313 break;
314 case 352800:
315 result = SAMPLE_RATE_352800_HZ;
316 break;
317 case 705600:
318 result = SAMPLE_RATE_705600_HZ;
319 break;
320 }
321 return result;
322 }
323
324 const char* toString(IPAddress adr) {
325 static char str[11] = {0};
326 snprintf(str, 11, "%d.%d.%d.%d", adr[0], adr[1], adr[2], adr[3]);
327 return str;
328 }
329
349 uint16_t outBuf[VBAN_PACKET_MAX_SAMPLES + 1];
350 size_t bytesOut;
351
352 int len = packet.length();
353 if (len > 0) {
354 LOGD("receive_udp %d", len);
355 uint8_t* udpIncomingPacket = packet.data();
356
357 // receive incoming UDP packet
358 // Check if packet length meets VBAN specification:
359 if (len <= (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES) ||
360 len > VBAN_PACKET_MAX_LEN_BYTES) {
361 LOGE("Packet length %u bytes", len);
362 rx_buffer.reset();
363 return;
364 }
365
366 // Check if preamble matches VBAN format:
367 if (strncmp("VBAN", (const char*)udpIncomingPacket, 4) != 0) {
368 LOGE("Unrecognized preamble %.4s", udpIncomingPacket);
369 return;
370 }
371
373 len - (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES);
374 vban_rx_pkt_nbr = (uint32_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES];
375 vban_rx_data = (int16_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES +
376 VBAN_PACKET_COUNTER_BYTES];
377 vban_rx_sample_count = vban_rx_data_bytes / (cfg.bits_per_sample / 8);
378 uint8_t vbanSampleRateIdx = udpIncomingPacket[4] & VBAN_SR_MASK;
381 uint8_t vbformat = udpIncomingPacket[7] & VBAN_PROTOCOL_MASK;;
382 uint8_t vbformat_bits = udpIncomingPacket[7] & VBAN_BIT_RESOLUTION_MASK;;
384
385 //LOGD("sample_count: %d - frames: %d", vban_rx_sample_count, vbframes);
386 //assert (vban_rx_sample_count == vbframes*vbchannels);
387
388 // E.g. do not process any text
389 if (vbformat != cfg.format){
390 LOGE("Format ignored: 0x%x", vbformat);
391 return;
392 }
393
394 // Currently we support only 16 bits.
395 if (vbformat_bits != VBAN_BITFMT_16_INT){
396 LOGE("Format only 16 bits supported");
397 return;
398 }
399
400 // Just to be safe, re-check sample count against max sample count to
401 // avoid overrunning outBuf later
402 if (vban_rx_sample_count > VBAN_PACKET_MAX_SAMPLES) {
403 LOGE("unexpected packet size: %u", vban_rx_sample_count);
404 return;
405 }
406
407 // update sample rate
408 if (cfg.sample_rate != vbanSampleRate || cfg.channels != vbchannels) {
409 // update audio info
410 cfg.sample_rate = vbanSampleRate;
411 cfg.channels = vbchannels;
412 setAudioInfo(cfg);
413 // remove any buffered data
414 rx_buffer.reset();
415 available_active = false;
416 }
417
418 if (p_out!=nullptr){
421 LOGE("buffer overflow %d -> %d", vban_rx_data_bytes, size_written);
422 }
423 return;
424 }
425
426 // write data to buffer
429 LOGE("buffer overflow %d -> %d", vban_rx_data_bytes, size_written);
430 }
431
432 // report available bytes only when buffer is 50% full
433 if (!available_active) {
434 bytes_received += vban_rx_data_bytes;
435 if (bytes_received >= cfg.rx_buffer_count * DEFAULT_BUFFER_SIZE * 0.75){
436 available_active = true;
437 LOGI("Activating vban");
438 }
439 }
440 }
441 }
442};
443
444} // namespace audio_tools
Base class for all Audio Streams. It support the boolean operator to test if the object is ready with...
Definition BaseStream.h:115
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:123
int available() override
provides the number of entries that are available to read
Definition BufferRTOS.h:137
int writeArray(const T data[], int len)
Fills the buffer data.
Definition BufferRTOS.h:95
bool resize(size_t size)
Re-Allocats the memory and the queue.
Definition BufferRTOS.h:50
void reset() override
clears the buffer
Definition BufferRTOS.h:134
int readArray(T data[], int len)
reads multiple values
Definition BufferRTOS.h:77
Definition NoArduino.h:62
bool write(T sample) override
write add an entry to the buffer
Definition Buffers.h:194
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:224
T * data()
Provides address of actual data.
Definition Buffers.h:252
void reset() override
clears the buffer
Definition Buffers.h:254
Definition VBANStream.h:11
const char * ssid
ssid for wifi connection
Definition VBANStream.h:26
uint16_t udp_port
default port is 6980
Definition VBANStream.h:22
IPAddress target_ip
Use {0,0,0,0}; as broadcast address.
Definition VBANStream.h:24
const char * password
password for wifi connection
Definition VBANStream.h:28
const char * stream_name
name of the stream
Definition VBANStream.h:20
VBAN Audio Source and Sink for the ESP32. For further details please see https://vb-audio....
Definition VBANStream.h:51
void receive_udp(AsyncUDPPacket &packet)
VBAN adjusts the number of samples per packet according to sample rate. Assuming 16-bit PCM mono,...
Definition VBANStream.h:345
void setAudioInfo(AudioInfo info) override
Defines the input AudioInfo.
Definition VBANStream.h:63
Vector implementation which provides the most important methods as defined by std::vector....
Definition Vector.h:21
RxTxMode
The Microcontroller is the Audio Source (TX_MODE) or Audio Sink (RX_MODE). RXTX_MODE is Source and Si...
Definition AudioTypes.h:28
Generic Implementation of sound input and output for desktop environments using portaudio.
Definition AudioConfig.h:885
Definition vban.h:50
Definition vban.h:60
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:52
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:107
sample_rate_t sample_rate
Sample Rate: e.g 44100.
Definition AudioTypes.h:55
uint16_t channels
Number of channels: 2=stereo, 1=mono.
Definition AudioTypes.h:57
uint8_t bits_per_sample
Number of bits per sample (int16_t = 16 bits)
Definition AudioTypes.h:59