arduino-audio-tools
All Classes Namespaces Files Functions Variables Typedefs Enumerations Friends Macros 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";
22 uint16_t udp_port = 6980;
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 //reply for discovery packet
41 uint32_t device_flags = 0x00000001; // default: receiver only
42 uint32_t bitfeature = 0x00000001; // default: audio only
43 uint32_t device_color = 0x00FF00; // green default
44 //const char* stream_name_reply = "VBAN SPOT PING";
45 const char* device_name = nullptr; // nullptr means use MAC by default
46 const char* manufacturer_name = "ESP32 AudioTools";
47 const char* application_name = "VBAN Streamer";
48 const char* host_name = nullptr; // will fallback to WiFi.getHostname()
49 const char* user_name = "User";
50 const char* user_comment = "ESP32 VBAN Audio Device";
51};
52
63class VBANStream : public AudioStream {
64 public:
65 VBANConfig defaultConfig(RxTxMode mode = TX_MODE) {
66 VBANConfig def;
67 def.mode = mode;
68 return def;
69 }
70
71 void setOutput(Print &out){
72 p_out = &out;
73 }
74
75 void setAudioInfo(AudioInfo info) override {
76 cfg.copyFrom(info);
78 auto thc = throttle.defaultConfig();
79 thc.copyFrom(info);
80 thc.correction_us = cfg.throttle_correction_us;
81 throttle.begin(thc);
82 if (cfg.mode == TX_MODE) {
83 configure_tx();
84 }
85 }
86
87 bool begin(VBANConfig cfg) {
88 this->cfg = cfg;
89 setAudioInfo(cfg);
90 return begin();
91 }
92
93 bool begin() {
94 if (cfg.mode == TX_MODE) {
95 if (cfg.bits_per_sample != 16) {
96 LOGE("Only 16 bits supported")
97 return false;
98 }
99 tx_buffer.resize(VBAN_PACKET_NUM_SAMPLES);
100 return begin_tx();
101 } else {
102#ifdef ESP32
103 rx_buffer.resize(DEFAULT_BUFFER_SIZE * cfg.rx_buffer_count);
104 rx_buffer.setReadMaxWait(10);
105#else
106 rx_buffer.resize(DEFAULT_BUFFER_SIZE, cfg.rx_buffer_count);
107#endif
108 return begin_rx();
109 }
110 }
111
112 size_t write(const uint8_t* data, size_t len) override {
113 if (!udp_connected) return 0;
114
115 int16_t* adc_data = (int16_t*)data;
116 size_t samples = len / (cfg.bits_per_sample/8);
117
118 // limit output speed
119 if (cfg.throttle_active) {
120 throttle.delayFrames(samples / cfg.channels);
121 }
122
123 for (int j = 0; j < samples; j++) {
124 tx_buffer.write(adc_data[j]);
125 if (tx_buffer.availableForWrite() == 0) {
126 memcpy(vban.data_frame, tx_buffer.data(), vban.packet_data_bytes);
127 *vban.packet_counter = packet_counter; // increment packet counter
128 // Send packet
129 if (cfg.target_ip == broadcast_address) {
130 udp.broadcastTo((uint8_t*)&vban.packet, vban.packet_total_bytes,
131 cfg.udp_port);
132 } else {
133 udp.writeTo((uint8_t*)&vban.packet, vban.packet_total_bytes,
134 cfg.target_ip, cfg.udp_port);
135 }
136 // defile delay start time
137 packet_counter++;
138 tx_buffer.reset();
139 }
140 }
141 return len;
142 }
143
144 int availableForWrite() { return cfg.max_write_size; }
145
146 size_t readBytes(uint8_t* data, size_t len) override {
147 TRACED();
148 size_t samples = len / (cfg.bits_per_sample/8);
149 if (cfg.throttle_active) {
150 throttle.delayFrames(samples / cfg.channels);
151 }
152 return rx_buffer.readArray(data, len);
153 }
154
155 int available() { return available_active ? rx_buffer.available() : 0; }
156
157 protected:
158 const IPAddress broadcast_address{0, 0, 0, 0};
159 AsyncUDP udp;
160 VBan vban;
161 VBANConfig cfg;
162 SingleBuffer<int16_t> tx_buffer{0};
163 #ifdef ESP32
164 BufferRTOS<uint8_t> rx_buffer{ 0};
165 #else
166 NBuffer<uint8_t> rx_buffer{DEFAULT_BUFFER_SIZE, 0};
167 #endif
168 bool udp_connected = false;
169 uint32_t packet_counter = 0;
170 Throttle throttle;
171 size_t bytes_received = 0;
172 bool available_active = false;
173 Print *p_out = nullptr;
174
175 bool begin_tx() {
176 if (!configure_tx()) {
177 return false;
178 }
179 start_wifi();
180 if (WiFi.status() != WL_CONNECTED) {
181 LOGE("Wifi not connected");
182 return false;
183 }
184 WiFi.setSleep(false);
185 IPAddress myIP = WiFi.localIP();
186 udp_connected = udp.connect(myIP, cfg.udp_port);
187 return udp_connected;
188 }
189
190 bool begin_rx() {
191 start_wifi();
192 if (WiFi.status() != WL_CONNECTED) {
193 LOGE("Wifi not connected");
194 return false;
195 }
196 WiFi.setSleep(false);
197 bytes_received = 0;
198 this->available_active = false;
199 // connect to target
200 if (!udp.listen(cfg.udp_port)) {
201 LOGE("Could not connect to '%s:%d' target", toString(cfg.target_ip),
202 cfg.udp_port);
203 }
204 // handle data
205 udp.onPacket([this](AsyncUDPPacket packet) { receive_udp(packet); });
206
207 return true;
208 }
209
210 bool configure_tx() {
211 int rate = vban_sample_rate();
212 if (rate < 0) {
213 LOGE("Invalid sample rate: %d", cfg.sample_rate);
214 return false;
215 }
216 configure_vban((VBanSampleRates)rate);
217 return true;
218 }
219
220 void start_wifi() {
221 if (cfg.ssid == nullptr) return;
222 if (cfg.password == nullptr) return;
223 LOGI("ssid %s", cfg.ssid);
224 // Setup Wifi:
225 WiFi.begin(cfg.ssid, cfg.password); // Connect to your WiFi router
226 while (WiFi.status() != WL_CONNECTED) { // Wait for connection
227 delay(500);
228 Serial.print(".");
229 }
230 Serial.println();
231
232 LOGI("Wifi connected to IP (%d.%d.%d.%d)", WiFi.localIP()[0],
233 WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
234 }
235
236 void configure_vban(VBanSampleRates rate) {
237 // Set vban packet header, counter, and data frame pointers to respective
238 // parts of packet:
239 vban.hdr = (VBanHeader*)&vban.packet[0];
240 vban.packet_counter = (uint32_t*)&vban.packet[VBAN_PACKET_HEADER_BYTES];
241 vban.data_frame =
242 (uint8_t*)&vban
243 .packet[VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES];
244
245 // Setup the packet header:
246 strncpy(vban.hdr->preamble, "VBAN", 4);
247 vban.hdr->sample_rate =
248 static_cast<int>(VBAN_PROTOCOL_AUDIO) |
249 rate; // 11025 Hz, which matches default sample rate for soundmodem
250 vban.hdr->num_samples =
251 (VBAN_PACKET_NUM_SAMPLES / cfg.channels) - 1; // 255 = 256 samples
252 vban.hdr->num_channels = cfg.channels - 1; // 0 = 1 channel
253 vban.hdr->sample_format =
254 static_cast<int>(VBAN_BITFMT_16_INT) | VBAN_CODEC_PCM; // int16 PCM
255 strncpy(vban.hdr->stream_name, cfg.stream_name,
256 min((int)strlen(cfg.stream_name), VBAN_STREAM_NAME_SIZE));
257
258 vban.packet_data_bytes =
259 (vban.hdr->num_samples + 1) * (vban.hdr->num_channels + 1) *
260 ((vban.hdr->sample_format & VBAN_BIT_RESOLUTION_MASK) + 1);
261 vban.packet_total_bytes = vban.packet_data_bytes +
262 VBAN_PACKET_HEADER_BYTES +
263 VBAN_PACKET_COUNTER_BYTES;
264 }
265
266 int vban_sample_rate() {
267 int result = -1;
268 switch (cfg.sample_rate) {
269 case 6000:
270 result = SAMPLE_RATE_6000_HZ;
271 break;
272 case 12000:
273 result = SAMPLE_RATE_12000_HZ;
274 break;
275 case 24000:
276 result = SAMPLE_RATE_24000_HZ;
277 break;
278 case 48000:
279 result = SAMPLE_RATE_48000_HZ;
280 break;
281 case 96000:
282 result = SAMPLE_RATE_96000_HZ;
283 break;
284 case 192000:
285 result = SAMPLE_RATE_192000_HZ;
286 break;
287 case 384000:
288 result = SAMPLE_RATE_384000_HZ;
289 break;
290 case 8000:
291 result = SAMPLE_RATE_8000_HZ;
292 break;
293 case 16000:
294 result = SAMPLE_RATE_16000_HZ;
295 break;
296 case 32000:
297 result = SAMPLE_RATE_32000_HZ;
298 break;
299 case 64000:
300 result = SAMPLE_RATE_64000_HZ;
301 break;
302 case 128000:
303 result = SAMPLE_RATE_128000_HZ;
304 break;
305 case 256000:
306 result = SAMPLE_RATE_256000_HZ;
307 break;
308 case 512000:
309 result = SAMPLE_RATE_512000_HZ;
310 break;
311 case 11025:
312 result = SAMPLE_RATE_11025_HZ;
313 break;
314 case 22050:
315 result = SAMPLE_RATE_22050_HZ;
316 break;
317 case 44100:
318 result = SAMPLE_RATE_44100_HZ;
319 break;
320 case 88200:
321 result = SAMPLE_RATE_88200_HZ;
322 break;
323 case 176400:
324 result = SAMPLE_RATE_176400_HZ;
325 break;
326 case 352800:
327 result = SAMPLE_RATE_352800_HZ;
328 break;
329 case 705600:
330 result = SAMPLE_RATE_705600_HZ;
331 break;
332 }
333 return result;
334 }
335
336 const char* toString(IPAddress adr) {
337 static char str[11] = {0};
338 snprintf(str, 11, "%d.%d.%d.%d", adr[0], adr[1], adr[2], adr[3]);
339 return str;
340 }
341
357 void receive_udp(AsyncUDPPacket& packet) {
358 uint16_t vban_rx_data_bytes, vban_rx_sample_count;
359 int16_t* vban_rx_data;
360 uint32_t* vban_rx_pkt_nbr;
361 uint16_t outBuf[VBAN_PACKET_MAX_SAMPLES + 1];
362 size_t bytesOut;
363
364 int len = packet.length();
365 if (len > 0) {
366 LOGD("receive_udp %d", len);
367 uint8_t* udpIncomingPacket = packet.data();
368
369 // receive incoming UDP packet
370 // Check if packet length meets VBAN specification:
371 if (len < VBAN_PACKET_HEADER_BYTES) {
372 LOGE("Too short to be VBAN (%u bytes)", len);
373 return;
374 }
375
376 // Check if preamble matches VBAN format:
377 if (strncmp("VBAN", (const char*)udpIncomingPacket, 4) != 0) {
378 LOGE("Unrecognized preamble %.4s", udpIncomingPacket);
379 return;
380 }
381
382 uint8_t protocol = udpIncomingPacket[4] & VBAN_PROTOCOL_MASK;
383
384 if (protocol == VBAN_PROTOCOL_SERVICE) {
385 // Allow up to ~1024 bytes for service packets like Ping0
386 if (len > 1024) {
387 LOGE("Service packet length invalid: %u bytes", len);
388 return;
389 }
390 } else {
391 // Audio, serial, etc
392 if (len <= (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES) || len > VBAN_PACKET_MAX_LEN_BYTES) {
393 LOGE("Audio/other packet length invalid: %u bytes", len);
394 rx_buffer.reset();
395 return;
396 }
397 }
398
399 //LOGI("VBAN format byte: 0x%02X", udpIncomingPacket[7]);
400 //LOGD("VBAN protocol mask applied: 0x%02X", udpIncomingPacket[7] & VBAN_PROTOCOL_MASK);
401 //Serial.printf("Header[7] = 0x%02X\n", udpIncomingPacket[7]);
402
403
404 //-------------------------------------------------------------------------
405 //SUPPORT PING REQUEST
406 if ( protocol == VBAN_PROTOCOL_SERVICE ) {
407
408 uint8_t service_type = udpIncomingPacket[5];
409 uint8_t service_fnct = udpIncomingPacket[6];
410
411 if (service_type == VBAN_SERVICE_IDENTIFICATION) {
412 bool isReply = (service_fnct & VBAN_SERVICE_FNCT_REPLY) != 0;
413 uint8_t function = service_fnct & 0x7F;
414
415 if (!isReply && function == 0) {
416 LOGI("Received VBAN PING0 request");
417 sendVbanPing0Reply(packet);
418 }
419 }
420 return;
421 }
422 //--------------------------------------------------------------------------
423
424 vban_rx_data_bytes =
425 len - (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES);
426 vban_rx_pkt_nbr = (uint32_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES];
427 vban_rx_data = (int16_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES +
428 VBAN_PACKET_COUNTER_BYTES];
429 vban_rx_sample_count = vban_rx_data_bytes / (cfg.bits_per_sample / 8);
430 uint8_t vbanSampleRateIdx = udpIncomingPacket[4] & VBAN_SR_MASK;
431 uint8_t vbchannels = udpIncomingPacket[6] + 1;
432 uint8_t vbframes = udpIncomingPacket[5] + 1;
433 uint8_t vbformat = udpIncomingPacket[7] & VBAN_PROTOCOL_MASK;
434 uint8_t vbformat_bits = udpIncomingPacket[7] & VBAN_BIT_RESOLUTION_MASK;
435 uint32_t vbanSampleRate = VBanSRList[vbanSampleRateIdx];
436
437 //LOGD("sample_count: %d - frames: %d", vban_rx_sample_count, vbframes);
438 //assert (vban_rx_sample_count == vbframes*vbchannels);
439
440 // E.g. do not process any text
441 if (vbformat != cfg.format){
442 LOGE("Format ignored: 0x%x", vbformat);
443 return;
444 }
445
446 // Currently we support only 16 bits.
447 if (vbformat_bits != VBAN_BITFMT_16_INT){
448 LOGE("Format only 16 bits supported");
449 return;
450 }
451
452 // Just to be safe, re-check sample count against max sample count to
453 // avoid overrunning outBuf later
454 if (vban_rx_sample_count > VBAN_PACKET_MAX_SAMPLES) {
455 LOGE("unexpected packet size: %u", vban_rx_sample_count);
456 return;
457 }
458
459 // update sample rate
460 if (cfg.sample_rate != vbanSampleRate || cfg.channels != vbchannels) {
461 // update audio info
462 cfg.sample_rate = vbanSampleRate;
463 cfg.channels = vbchannels;
464 setAudioInfo(cfg);
465 // remove any buffered data
466 rx_buffer.reset();
467 available_active = false;
468 }
469
470 if (p_out!=nullptr){
471 int size_written = p_out->write((uint8_t*)vban_rx_data, vban_rx_data_bytes);
472 if (size_written != vban_rx_data_bytes) {
473 LOGE("buffer overflow %d -> %d", vban_rx_data_bytes, size_written);
474 }
475 return;
476 }
477
478 // write data to buffer
479 int size_written = rx_buffer.writeArray((uint8_t*)vban_rx_data, vban_rx_data_bytes);
480 if (size_written != vban_rx_data_bytes) {
481 LOGE("buffer overflow %d -> %d", vban_rx_data_bytes, size_written);
482 }
483
484 // report available bytes only when buffer is 50% full
485 if (!available_active) {
486 bytes_received += vban_rx_data_bytes;
487 if (bytes_received >= cfg.rx_buffer_count * DEFAULT_BUFFER_SIZE * 0.75){
488 available_active = true;
489 LOGI("Activating vban");
490 }
491 }
492 }
493 }
494//-------------------------------------------------------------------------------------
495 //implement ping reply based on VBAN standard
496 void sendVbanPing0Reply(AsyncUDPPacket& sourcePacket) {
497
498 // Prepare VBAN 28-byte service header
499 uint8_t header[28];
500 memset(header, 0, sizeof(header));
501 memcpy(header, "VBAN", 4);
502 header[4] = VBAN_PROTOCOL_SERVICE;
503 header[5] = VBAN_SERVICE_FNCT_PING0 | VBAN_SERVICE_FNCT_REPLY; // Service function + reply bit
504 header[6] = 0x00; // must be zero
505 // Copy incoming stream name from discovery packet
506 const uint8_t* data = sourcePacket.data();
507 memcpy(&header[8], &data[8], 16);
508 // Copy frame number (little endian)
509
510 uint32_t frameNumber = (uint32_t)((data[24] & 0xFF) | ((data[25] & 0xFF) << 8) | ((data[26] & 0xFF) << 16) | ((data[27] & 0xFF) << 24));
511 memcpy(&header[24], &frameNumber, 4);
512
513 // Construct the PING0 payload using the struct
514 VBAN_PING0 ping0;
515 memset(&ping0, 0, sizeof(ping0));
516
517 // Fill fields with your config data and fixed values
518 ping0.bitType = cfg.device_flags;
519 ping0.bitfeature = cfg.bitfeature;
520 ping0.bitfeatureEx = 0x00000000;
521 ping0.PreferedRate = 44100;
522 ping0.MinRate = 8000;
523 ping0.MaxRate = 96000;
524 ping0.color_rgb = cfg.device_color;
525
526 // Version string, 8 bytes total (zero padded)
527 memcpy(ping0.nVersion, "v1.0", 4);
528
529 // GPS_Position left empty (all zero), so no need to set
530 // USER_Position 8 bytes
531 memcpy(ping0.USER_Position, "USRPOS", 6);
532 // LangCode_ascii 8 bytes ("EN" + padding)
533 memset(ping0.LangCode_ascii, 0, sizeof(ping0.LangCode_ascii));
534 memcpy(ping0.LangCode_ascii, "EN", 2);
535 // reserved_ascii and reservedEx are zeroed by memset
536 // IP as string, max 32 bytes
537
538 char ipStr[16]; // Enough for "255.255.255.255\0"
539 sprintf(ipStr, "%d.%d.%d.%d", WiFi.localIP()[0], WiFi.localIP()[1], WiFi.localIP()[2], WiFi.localIP()[3]);
540 safe_strncpy(ping0.DistantIP_ascii, ipStr, sizeof(ping0.DistantIP_ascii));
541 // Ports (network byte order)
542
543 ping0.DistantPort = cfg.udp_port; //returs port I am listening for VBAN - more useful then UDP ephemeral port
544 ping0.DistantReserved = 0;
545
546 // Device name (64 bytes)
547 if (cfg.device_name && cfg.device_name[0] != '\0') {
548 safe_strncpy(ping0.DeviceName_ascii, cfg.device_name, sizeof(ping0.DeviceName_ascii));
549 } else {
550 uint8_t mac[6];
551 WiFi.macAddress(mac);
552 char macStr[64];
553 snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
554 safe_strncpy(ping0.DeviceName_ascii, macStr, sizeof(ping0.DeviceName_ascii));
555 }
556
557 // Manufacturer name (64 bytes)
558 safe_strncpy(ping0.ManufacturerName_ascii, cfg.manufacturer_name, sizeof(ping0.ManufacturerName_ascii));
559 // Application name (64 bytes)
560 safe_strncpy(ping0.ApplicationName_ascii, cfg.application_name, sizeof(ping0.ApplicationName_ascii));
561 // Host name (64 bytes)
562 const char* hostName = cfg.host_name;
563 if (!hostName || hostName[0] == '\0') {
564 hostName = WiFi.getHostname();
565 if (!hostName) hostName = "ESP32";
566 }
567 safe_strncpy(ping0.HostName_ascii, hostName, sizeof(ping0.HostName_ascii));
568
569 // UserName_utf8
570 safe_strncpy(ping0.UserName_utf8, cfg.user_name, sizeof(ping0.UserName_utf8));
571 //UserComment_utf8
572 safe_strncpy(ping0.UserComment_utf8, cfg.user_comment, sizeof(ping0.UserComment_utf8));
573
574 // Prepare final packet: header + payload
575 uint8_t packet[28 + sizeof(VBAN_PING0)];
576 memcpy(packet, header, 28);
577 memcpy(packet + 28, &ping0, sizeof(VBAN_PING0));
578
579 // Send UDP packet
580 udp.writeTo(packet, sizeof(packet), sourcePacket.remoteIP(), sourcePacket.remotePort());
581}
582
583 // Safely copy a C-string with guaranteed null termination
584 void safe_strncpy(char* dest, const char* src, size_t dest_size) {
585 if (dest_size == 0) return;
586 strncpy(dest, src, dest_size - 1);
587 dest[dest_size - 1] = '\0';
588 }
589 //-----------------------------------------------------------------------------------
590};
591
592} // 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:122
virtual void setAudioInfo(AudioInfo newInfo) override
Defines the input AudioInfo.
Definition BaseStream.h:130
int available() override
provides the number of entries that are available to read
Definition BufferRTOS.h:136
int writeArray(const T data[], int len)
Fills the buffer data.
Definition BufferRTOS.h:94
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:133
int readArray(T data[], int len)
reads multiple values
Definition BufferRTOS.h:76
Definition NoArduino.h:62
bool write(T sample) override
write add an entry to the buffer
Definition Buffers.h:202
int availableForWrite() override
provides the number of entries that are available to write
Definition Buffers.h:234
T * data()
Provides address of actual data.
Definition Buffers.h:271
void reset() override
clears the buffer
Definition Buffers.h:273
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:63
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:357
void setAudioInfo(AudioInfo info) override
Defines the input AudioInfo.
Definition VBANStream.h:75
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 AudioCodecsBase.h:10
Definition vban.h:179
Definition vban.h:51
Definition vban.h:61
Basic Audio information which drives e.g. I2S.
Definition AudioTypes.h:53
void copyFrom(AudioInfo info)
Same as set.
Definition AudioTypes.h:103
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