@@ -36,6 +36,18 @@ class VBANConfig : public AudioInfo {
36
36
int max_write_size =
37
37
DEFAULT_BUFFER_SIZE * 2 ; // just good enough for 44100 stereo
38
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" ;
39
51
};
40
52
41
53
/* *
@@ -356,10 +368,8 @@ class VBANStream : public AudioStream {
356
368
357
369
// receive incoming UDP packet
358
370
// 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 ();
371
+ if (len < VBAN_PACKET_HEADER_BYTES) {
372
+ LOGE (" Too short to be VBAN (%u bytes)" , len);
363
373
return ;
364
374
}
365
375
@@ -369,6 +379,48 @@ class VBANStream : public AudioStream {
369
379
return ;
370
380
}
371
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
+
372
424
vban_rx_data_bytes =
373
425
len - (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES);
374
426
vban_rx_pkt_nbr = (uint32_t *)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES];
@@ -378,10 +430,10 @@ class VBANStream : public AudioStream {
378
430
uint8_t vbanSampleRateIdx = udpIncomingPacket[4 ] & VBAN_SR_MASK;
379
431
uint8_t vbchannels = udpIncomingPacket[6 ] + 1 ;
380
432
uint8_t vbframes = udpIncomingPacket[5 ] + 1 ;
381
- uint8_t vbformat = udpIncomingPacket[7 ] & VBAN_PROTOCOL_MASK;;
382
- uint8_t vbformat_bits = udpIncomingPacket[7 ] & VBAN_BIT_RESOLUTION_MASK;;
433
+ uint8_t vbformat = udpIncomingPacket[7 ] & VBAN_PROTOCOL_MASK;
434
+ uint8_t vbformat_bits = udpIncomingPacket[7 ] & VBAN_BIT_RESOLUTION_MASK;
383
435
uint32_t vbanSampleRate = VBanSRList[vbanSampleRateIdx];
384
-
436
+
385
437
// LOGD("sample_count: %d - frames: %d", vban_rx_sample_count, vbframes);
386
438
// assert (vban_rx_sample_count == vbframes*vbchannels);
387
439
@@ -439,6 +491,101 @@ class VBANStream : public AudioStream {
439
491
}
440
492
}
441
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
+ ping0.DistantPort = htons (sourcePacket.remotePort ());
543
+ ping0.DistantReserved = 0 ;
544
+
545
+ // Device name (64 bytes)
546
+ if (cfg.device_name && cfg.device_name [0 ] != ' \0 ' ) {
547
+ safe_strncpy (ping0.DeviceName_ascii , cfg.device_name , sizeof (ping0.DeviceName_ascii ));
548
+ } else {
549
+ uint8_t mac[6 ];
550
+ WiFi.macAddress (mac);
551
+ char macStr[64 ];
552
+ snprintf (macStr, sizeof (macStr), " %02X:%02X:%02X:%02X:%02X:%02X" , mac[0 ], mac[1 ], mac[2 ], mac[3 ], mac[4 ], mac[5 ]);
553
+ safe_strncpy (ping0.DeviceName_ascii , macStr, sizeof (ping0.DeviceName_ascii ));
554
+ }
555
+
556
+ // Manufacturer name (64 bytes)
557
+ safe_strncpy (ping0.ManufacturerName_ascii , cfg.manufacturer_name , sizeof (ping0.ManufacturerName_ascii ));
558
+ // Application name (64 bytes)
559
+ safe_strncpy (ping0.ApplicationName_ascii , cfg.application_name , sizeof (ping0.ApplicationName_ascii ));
560
+ // Host name (64 bytes)
561
+ const char * hostName = cfg.host_name ;
562
+ if (!hostName || hostName[0 ] == ' \0 ' ) {
563
+ hostName = WiFi.getHostname ();
564
+ if (!hostName) hostName = " ESP32" ;
565
+ }
566
+ safe_strncpy (ping0.HostName_ascii , hostName, sizeof (ping0.HostName_ascii ));
567
+
568
+ // UserName_utf8
569
+ safe_strncpy (ping0.UserName_utf8 , cfg.user_name , sizeof (ping0.UserName_utf8 ));
570
+ // UserComment_utf8
571
+ safe_strncpy (ping0.UserComment_utf8 , cfg.user_comment , sizeof (ping0.UserComment_utf8 ));
572
+
573
+ // Prepare final packet: header + payload
574
+ uint8_t packet[28 + sizeof (VBAN_PING0)];
575
+ memcpy (packet, header, 28 );
576
+ memcpy (packet + 28 , &ping0, sizeof (VBAN_PING0));
577
+
578
+ // Send UDP packet
579
+ udp.writeTo (packet, sizeof (packet), sourcePacket.remoteIP (), sourcePacket.remotePort ());
580
+ }
581
+
582
+ // Safely copy a C-string with guaranteed null termination
583
+ void safe_strncpy (char * dest, const char * src, size_t dest_size) {
584
+ if (dest_size == 0 ) return ;
585
+ strncpy (dest, src, dest_size - 1 );
586
+ dest[dest_size - 1 ] = ' \0 ' ;
587
+ }
588
+ // -----------------------------------------------------------------------------------
442
589
};
443
590
444
591
} // namespace audio_tools
0 commit comments