Skip to content

Commit 8faffc9

Browse files
authored
added support for VBAN ping packet (#2098)
1 parent aca823e commit 8faffc9

File tree

2 files changed

+197
-9
lines changed

2 files changed

+197
-9
lines changed

src/AudioTools/AudioLibs/VBANStream.h

Lines changed: 154 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,18 @@ class VBANConfig : public AudioInfo {
3636
int max_write_size =
3737
DEFAULT_BUFFER_SIZE * 2; // just good enough for 44100 stereo
3838
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";
3951
};
4052

4153
/**
@@ -356,10 +368,8 @@ class VBANStream : public AudioStream {
356368

357369
// receive incoming UDP packet
358370
// 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);
363373
return;
364374
}
365375

@@ -369,6 +379,48 @@ class VBANStream : public AudioStream {
369379
return;
370380
}
371381

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+
372424
vban_rx_data_bytes =
373425
len - (VBAN_PACKET_HEADER_BYTES + VBAN_PACKET_COUNTER_BYTES);
374426
vban_rx_pkt_nbr = (uint32_t*)&udpIncomingPacket[VBAN_PACKET_HEADER_BYTES];
@@ -378,10 +430,10 @@ class VBANStream : public AudioStream {
378430
uint8_t vbanSampleRateIdx = udpIncomingPacket[4] & VBAN_SR_MASK;
379431
uint8_t vbchannels = udpIncomingPacket[6] + 1;
380432
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;
383435
uint32_t vbanSampleRate = VBanSRList[vbanSampleRateIdx];
384-
436+
385437
//LOGD("sample_count: %d - frames: %d", vban_rx_sample_count, vbframes);
386438
//assert (vban_rx_sample_count == vbframes*vbchannels);
387439

@@ -439,6 +491,101 @@ class VBANStream : public AudioStream {
439491
}
440492
}
441493
}
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+
//-----------------------------------------------------------------------------------
442589
};
443590

444591
} // namespace audio_tools

src/AudioTools/AudioLibs/vban/vban.h

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
// MODIFIED by R. Kinnett, https://github.com/rkinnett, 2020
2323
//
2424
///////////////////////////////////////////////////////////////////////
25-
25+
#include <stdint.h>
26+
#include <string.h>
2627

2728
#ifndef __VBAN_H__
2829
#define __VBAN_H__
@@ -104,7 +105,7 @@ enum VBanSampleRates
104105
};
105106

106107

107-
#define VBAN_PROTOCOL_MASK 0xE0
108+
#define VBAN_PROTOCOL_MASK 0xE0
108109
enum VBanProtocol
109110
{
110111
VBAN_PROTOCOL_AUDIO = 0x00,
@@ -159,6 +160,46 @@ enum VBanCodec
159160
};
160161

161162

163+
/********************************************************
164+
* SERVICE SUB PROTOCOL *
165+
********************************************************/
166+
// VBAN SERVICE PROTOCOL definitions
167+
#define VBAN_PROTOCOL_SERVICE 0x60
168+
169+
// Service Types (format_nbc)
170+
#define VBAN_SERVICE_IDENTIFICATION 0x00
171+
#define VBAN_SERVICE_CHATUTF8 0x01
172+
#define VBAN_SERVICE_RTPACKETREGISTER 0x20
173+
#define VBAN_SERVICE_RTPACKET 0x21
174+
175+
// Service Functions (format_nbs)
176+
#define VBAN_SERVICE_FNCT_PING0 0x00
177+
#define VBAN_SERVICE_FNCT_REPLY 0x80
178+
179+
struct VBAN_PING0 {
180+
uint32_t bitType;
181+
uint32_t bitfeature;
182+
uint32_t bitfeatureEx;
183+
uint32_t PreferedRate;
184+
uint32_t MinRate;
185+
uint32_t MaxRate;
186+
uint32_t color_rgb;
187+
uint8_t nVersion[4];
188+
char GPS_Position[8]; // Keep empty (all zero)
189+
char USER_Position[8];
190+
char LangCode_ascii[8];
191+
char reserved_ascii[8];
192+
char reservedEx[64];
193+
char DistantIP_ascii[32];
194+
uint16_t DistantPort;
195+
uint16_t DistantReserved;
196+
char DeviceName_ascii[64];
197+
char ManufacturerName_ascii[64];
198+
char ApplicationName_ascii[64];
199+
char HostName_ascii[64];
200+
char UserName_utf8[128];
201+
char UserComment_utf8[128];
202+
} __attribute__((packed));
162203
/********************************************************
163204
* TEXT SUB PROTOCOL *
164205
********************************************************/

0 commit comments

Comments
 (0)