0
1
mirror of https://github.com/radio95-rnt/fm95.git synced 2026-02-26 19:23:51 +01:00

improve vban a lot

This commit is contained in:
2025-05-20 14:41:32 +02:00
parent aeb183a560
commit 1f1fef70b6
2 changed files with 246 additions and 55 deletions

View File

@@ -25,7 +25,8 @@
"optimization.h": "c", "optimization.h": "c",
"string.h": "c", "string.h": "c",
"getopt.h": "c", "getopt.h": "c",
"audio.h": "c" "audio.h": "c",
"signal.h": "c"
}, },
"C_Cpp.errorSquiggles": "disabled" "C_Cpp.errorSquiggles": "disabled"
} }

View File

@@ -6,6 +6,14 @@
#include <arpa/inet.h> #include <arpa/inet.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <signal.h> #include <signal.h>
#include <stdbool.h>
#include <getopt.h>
#include <stdio.h>
#include <pwd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#define buffer_maxlength 12288 #define buffer_maxlength 12288
#define buffer_tlength_fragsize 12288 #define buffer_tlength_fragsize 12288
@@ -43,25 +51,83 @@ static char VBAN_TextBITList[VBAN_BIT_MAXNUMBER][4] = {
#define VBAN_PROTOCOL_TXT 0x40 #define VBAN_PROTOCOL_TXT 0x40
#define VBAN_PROTOCOL_SERVICE 0x60 #define VBAN_PROTOCOL_SERVICE 0x60
#define VBAN_SERVICE_IDENTIFICATION 0
#define VBAN_SERVICE_CHATUTF8 1
#define VBAN_SERVICE_RTPACKETREGISTER 32
#define VBAN_SERVICE_RTPACKET 33
#define VBANPING_TYPE_RECEPTOR 0x00000001 // Simple receptor
#define VBANPING_TYPE_TRANSMITTER 0x00000002 // Simple Transmitter
#define VBANPING_TYPE_RECEPTORSPOT 0x00000004 // SPOT receptor (able to receive several streams)
#define VBANPING_TYPE_TRANSMITTERSPOT 0x00000008 // SPOT transmitter (able to send several streams)
#define VBANPING_TYPE_VIRTUALDEVICE 0x00000010 // Virtual Device
#define VBANPING_TYPE_VIRTUALMIXER 0x00000020 // Virtual Mixer
#define VBANPING_TYPE_MATRIX 0x00000040 // MATRIX
#define VBANPING_TYPE_DAW 0x00000080 // Workstation
#define VBANPING_TYPE_SERVER 0x01000000 // VBAN SERVER
#define VBANPING_FEATURE_AUDIO 0x00000001
#define VBANPING_FEATURE_AOIP 0x00000002
#define VBANPING_FEATURE_VOIP 0x00000004
#define VBANPING_FEATURE_SERIAL 0x00000100
#define VBANPING_FEATURE_MIDI 0x00000300
#define VBANPING_FEATURE_FRAME 0x00001000
#define VBANPING_FEATURE_TXT 0x00010000
#define BUF_SIZE 2048 #define BUF_SIZE 2048
#define MAX_AUDIO_DATA_SIZE (BUF_SIZE - sizeof(VBANHeader)) #define MAX_AUDIO_DATA_SIZE (BUF_SIZE - sizeof(VBANHeader))
#define MAX_BUFFER_PACKETS 128 #define MAX_BUFFER_PACKETS 128
#define POLL_TIMEOUT_MS 100
#pragma pack(1) #pragma pack(1)
typedef struct { typedef struct {
char vban[4]; char vban[4];
uint8_t sample_rate_idx; uint8_t protocol_sample_rate_idx; // format_SR
uint8_t samples_per_frame; uint8_t samples_per_frame; // format_nbs
uint8_t sample_channels; uint8_t sample_channels; // format_nbc
uint8_t format_type; uint8_t format_type; // format_bit
char streamname[16]; char streamname[16];
uint32_t frame_num; uint32_t frame_num; // nuFrame
} VBANHeader; } VBANHeader;
typedef union { typedef union {
VBANHeader packet_data; VBANHeader packet_data;
char raw_data[sizeof(VBANHeader)]; char raw_data[sizeof(VBANHeader)];
} VBANHeaderUnion; } VBANHeaderUnion;
typedef struct {
uint32_t bitType; // device type
uint32_t bitfeature;
uint32_t bitfeatureEx;
uint32_t PreferredRate;
uint32_t MinRate;
uint32_t MaxRate;
uint32_t colorRGB;
uint8_t nVersion[4];
char GPS_Position[8];
char USER_Position[8];
char LangCode_ascii[8];
char reserved_ascii[8];
char reservedEx[64];
char DistantIP_ascii[32];
uint16_t DistantPort;
uint16_t DistantReserved;
char DeviceName_ascii[64];
char ManufacturerName_ascii[64];
char ApplicationName_ascii[64];
char HostName_ascii[64];
char UserName_utf8[128];
char UserComment_utf8[128];
} VBANPing0Data;
typedef union {
VBANPing0Data data;
char raw_data[sizeof(VBANPing0Data)];
} VBANPing0DataUnion;
#pragma pack() #pragma pack()
typedef struct { typedef struct {
@@ -129,7 +195,6 @@ int add_to_buffer(AudioBuffer* buffer, const char* data, size_t size, const VBAN
return 1; return 1;
} }
volatile uint8_t to_run = 1; volatile uint8_t to_run = 1;
static void stop(int signum) { static void stop(int signum) {
@@ -156,18 +221,72 @@ void reset_audio_buffer(AudioBuffer* buffer) {
buffer->count = 0; buffer->count = 0;
} }
int main(int argc, char *argv[]) { void show_version() {
if (argc < 6) { printf("vban95 (a VBAN AOIP receiver by radio95) version 1.1\n");
fprintf(stderr, "Usage: %s <remote_ip> <port> <streamname> <buffer_size> <pulse_device> <optional: quiet>\n", argv[0]); }
return 1; void show_help(char *name) {
} printf(
"Usage: \t%s\n"
"\t-i,--ip\t\tOverride remote IP address\n"
"\t-p,--port\tOverride listen port\n"
"\t-s,--stream\tOverride stream name\n"
"\t-b,--buffer\tOverride buffer size (1 to %d)\n"
"\t-d,--device\tOverride PulseAudio device\n"
"\t-q,--quiet\tSuppress output messages\n",
name, MAX_BUFFER_PACKETS
);
}
char *remote_ip = argv[1]; int main(int argc, char *argv[]) {
int listen_port = atoi(argv[2]); show_version();
char *stream_name = argv[3];
int buffer_size = atoi(argv[4]); char *remote_ip = "0.0.0.0";
char *pulse_device = argv[5]; int listen_port = 6980;
int quiet = (argc == 7); char *stream_name = "VBAN";
int buffer_size = 1;
char *pulse_device = "";
int quiet = 0;
int opt;
const char *short_opt = "i:p:s:b:d:qh";
const struct option long_opt[] = {
{"ip", required_argument, NULL, 'i'},
{"port", required_argument, NULL, 'p'},
{"stream", required_argument, NULL, 's'},
{"buffer", required_argument, NULL, 'b'},
{"device", required_argument, NULL, 'd'},
{"quiet", no_argument, NULL, 'q'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while ((opt = getopt_long(argc, argv, short_opt, long_opt, NULL)) != -1) {
switch (opt) {
case 'i':
remote_ip = optarg;
break;
case 'p':
listen_port = atoi(optarg);
break;
case 's':
stream_name = optarg;
break;
case 'b':
buffer_size = atoi(optarg);
break;
case 'd':
pulse_device = optarg;
break;
case 'q':
quiet = 1;
break;
case 'h':
show_help(argv[0]);
return 0;
default:
show_help(argv[0]);
return 1;
}
}
if (buffer_size <= 0 || buffer_size > MAX_BUFFER_PACKETS) { if (buffer_size <= 0 || buffer_size > MAX_BUFFER_PACKETS) {
fprintf(stderr, "Buffer size must be between 1 and %d\n", MAX_BUFFER_PACKETS); fprintf(stderr, "Buffer size must be between 1 and %d\n", MAX_BUFFER_PACKETS);
@@ -182,6 +301,17 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl(F_GETFL)");
return -1;
}
if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl(F_SETFL)");
return -1;
}
struct sockaddr_in local_addr; struct sockaddr_in local_addr;
memset(&local_addr, 0, sizeof(local_addr)); memset(&local_addr, 0, sizeof(local_addr));
local_addr.sin_family = AF_INET; local_addr.sin_family = AF_INET;
@@ -217,40 +347,116 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
pa_buffer_attr buffer_attr = {
.maxlength = buffer_maxlength,
.tlength = buffer_tlength_fragsize,
.prebuf = buffer_prebuf
};
signal(SIGINT, stop); signal(SIGINT, stop);
signal(SIGTERM, stop); signal(SIGTERM, stop);
while (to_run) { while (to_run) {
ssize_t recv_len = recvfrom(sockfd, buffer, BUF_SIZE, 0, ssize_t recv_len = recvfrom(sockfd, buffer, BUF_SIZE, 0,
(struct sockaddr *)&sender_addr, &sender_len); (struct sockaddr *)&sender_addr, &sender_len);
if (recv_len < 0) { if (recv_len < 0) {
perror("recvfrom"); if (errno == EAGAIN || errno == EWOULDBLOCK) {
break; // No data available, just continue with the loop
// Add a small sleep to avoid consuming too much CPU
usleep(POLL_TIMEOUT_MS * 1000); // Convert ms to microseconds
continue;
} else {
perror("recvfrom error");
break;
}
} }
if ((size_t)recv_len < sizeof(VBANHeader)) continue;
if (sender_addr.sin_addr.s_addr == remote_addr_bin.s_addr || remote_addr_bin.s_addr == 0) { if (sender_addr.sin_addr.s_addr == remote_addr_bin.s_addr || remote_addr_bin.s_addr == 0) {
VBANHeaderUnion data; VBANHeaderUnion data;
memcpy(&data.raw_data, buffer, sizeof(VBANHeader)); memcpy(&data.raw_data, buffer, sizeof(VBANHeader));
if (memcmp(data.packet_data.vban, "VBAN", 4) != 0) continue; // Not VBAN if (memcmp(data.packet_data.vban, "VBAN", 4) != 0) continue;
if (memcmp(data.packet_data.streamname, stream_name, strlen(stream_name)) != 0) continue; // Not this
if (vban_frame == 0 && data.packet_data.frame_num != 0) { uint8_t protocol = data.packet_data.protocol_sample_rate_idx & 0xe0;
// This means either this is our first packet, sync to the sender then if(protocol != VBAN_PROTOCOL_AUDIO) {
vban_frame = data.packet_data.frame_num; if(protocol == VBAN_PROTOCOL_SERVICE) {
} // Handle Service protocol
uint8_t service_type = data.packet_data.sample_channels;
uint8_t service_function = data.packet_data.samples_per_frame; // 0 if ping, 80 if reply
if(data.packet_data.frame_num != vban_frame) { if(service_type == VBAN_SERVICE_IDENTIFICATION) {
if (data.packet_data.frame_num > vban_frame) { if(service_function == 0) {
if (quiet == 0) printf("Dropped %u packets\n", data.packet_data.frame_num - vban_frame); // Handle ping
} else { VBANPing0DataUnion ping_data;
if (quiet == 0) printf("Packets received out of order (got:%u, expected:%u)\n", data.packet_data.frame_num, vban_frame); memset(&ping_data, 0, sizeof(VBANPing0Data));
ping_data.data.bitType = VBANPING_TYPE_RECEPTOR;
ping_data.data.bitfeature = VBANPING_FEATURE_AUDIO | VBANPING_FEATURE_AOIP;
ping_data.data.nVersion[0] = 1;
ping_data.data.nVersion[1] = 1;
snprintf(ping_data.data.DistantIP_ascii, sizeof(ping_data.data.DistantIP_ascii), "%s", inet_ntoa(sender_addr.sin_addr));
ping_data.data.DistantPort = htons(listen_port);
strncpy(ping_data.data.ApplicationName_ascii, "vban95", sizeof(ping_data.data.ApplicationName_ascii));
uid_t uid = getuid();
struct passwd *pw = getpwuid(uid);
if (pw != NULL) snprintf(ping_data.data.UserName_utf8, sizeof(ping_data.data.UserName_utf8), "%s", pw->pw_name);
gethostname(ping_data.data.HostName_ascii, sizeof(ping_data.data.HostName_ascii));
VBANHeaderUnion reply_header;
memset(&reply_header, 0, sizeof(VBANHeader));
memcpy(reply_header.packet_data.vban, "VBAN", 4);
reply_header.packet_data.protocol_sample_rate_idx = VBAN_PROTOCOL_SERVICE;
reply_header.packet_data.sample_channels = VBAN_SERVICE_IDENTIFICATION;
reply_header.packet_data.samples_per_frame = 0x80; // reply
reply_header.packet_data.frame_num = data.packet_data.frame_num;
char reply_buffer[sizeof(VBANHeader) + sizeof(VBANPing0Data)];
memcpy(reply_buffer, &reply_header.raw_data, sizeof(VBANHeader));
memcpy(reply_buffer + sizeof(VBANHeader), &ping_data.raw_data, sizeof(VBANPing0Data));
ssize_t sent_len = sendto(sockfd, reply_buffer, sizeof(reply_buffer), 0,
(struct sockaddr *)&sender_addr, sender_len);
if (sent_len < 0) {
perror("sendto");
} else {
if (quiet == 0) printf("Sent VBAN ping reply to %s:%d\n", inet_ntoa(sender_addr.sin_addr), ntohs(sender_addr.sin_port));
}
}
}
} }
continue;
}
if (vban_frame == 0) {
// First packet we receive, just accept whatever frame number it has
vban_frame = data.packet_data.frame_num;
} else {
// Normal packet processing
uint32_t expected_frame = vban_frame + 1;
if(data.packet_data.frame_num != expected_frame) {
if (data.packet_data.frame_num > expected_frame) {
if (quiet == 0) printf("Dropped %u packets\n", data.packet_data.frame_num - expected_frame);
} else {
if (quiet == 0) printf("Packets received out of order (got:%u, expected:%u)\n",
data.packet_data.frame_num, expected_frame);
}
}
vban_frame = data.packet_data.frame_num; vban_frame = data.packet_data.frame_num;
} }
if(vban_last_sr != data.packet_data.sample_rate_idx) { if (strncmp(data.packet_data.streamname, stream_name, sizeof(data.packet_data.streamname)) != 0) continue;
vban_last_sr = data.packet_data.sample_rate_idx;
uint8_t actual_sr_idx = data.packet_data.protocol_sample_rate_idx & 0x1f;
if(vban_last_sr != actual_sr_idx) {
vban_last_sr = actual_sr_idx;
if(quiet == 0) printf("New sample rate of %ld\n", VBAN_SRList[vban_last_sr % VBAN_SR_MAXNUMBER]); if(quiet == 0) printf("New sample rate of %ld\n", VBAN_SRList[vban_last_sr % VBAN_SR_MAXNUMBER]);
vban_audio_reset = 1; vban_audio_reset = 1;
reset_audio_buffer(audio_buffer); reset_audio_buffer(audio_buffer);
@@ -270,24 +476,15 @@ int main(int argc, char *argv[]) {
reset_audio_buffer(audio_buffer); reset_audio_buffer(audio_buffer);
} }
// Handle audio reset if needed
if(vban_audio_reset) { if(vban_audio_reset) {
if (vban_last_sr >= VBAN_SR_MAXNUMBER || vban_last_format >= VBAN_BIT_MAXNUMBER) { if (vban_last_sr >= VBAN_SR_MAXNUMBER || vban_last_format >= VBAN_BIT_MAXNUMBER) {
fprintf(stderr, "Unsupported sample rate or format\n"); fprintf(stderr, "Unsupported sample rate or format\n");
continue; continue;
} }
if (output.initialized) { if (output.initialized) free_PulseOutputDevice(&output);
free_PulseOutputDevice(&output);
}
pa_buffer_attr buffer_attr = { int result = init_PulseOutputDevicef( // the f suffix is to specify the format, because without f it defaults to float
.maxlength = buffer_maxlength,
.tlength = buffer_tlength_fragsize,
.prebuf = buffer_prebuf
};
int result = init_PulseOutputDevicef(
&output, &output,
VBAN_SRList[vban_last_sr], VBAN_SRList[vban_last_sr],
vban_last_channels + 1, // Add 1 because VBAN channels are 0-based vban_last_channels + 1, // Add 1 because VBAN channels are 0-based
@@ -298,9 +495,7 @@ int main(int argc, char *argv[]) {
VBAN_BITList[vban_last_format] VBAN_BITList[vban_last_format]
); );
if (result != 0) { if (result != 0) fprintf(stderr, "Failed to initialize PulseAudio output device: %s\n", pa_strerror(result));
fprintf(stderr, "Failed to initialize PulseAudio output device: %s\n", pa_strerror(result));
}
vban_audio_reset = 0; vban_audio_reset = 0;
continue; continue;
@@ -310,19 +505,14 @@ int main(int argc, char *argv[]) {
size_t audio_data_size = recv_len - sizeof(VBANHeader); size_t audio_data_size = recv_len - sizeof(VBANHeader);
if (add_to_buffer(audio_buffer, audio_data, audio_data_size, &data.packet_data) > 0) { if (add_to_buffer(audio_buffer, audio_data, audio_data_size, &data.packet_data) > 0) {
if (audio_buffer->count >= audio_buffer->capacity) { if (audio_buffer->count >= audio_buffer->capacity) process_audio_buffer(audio_buffer, &output);
process_audio_buffer(audio_buffer, &output);
}
} }
vban_frame++;
} }
} }
// Clean up // Clean up
printf("Cleaning up...\n"); printf("Cleaning up...\n");
if (output.initialized) { if (output.initialized) free_PulseOutputDevice(&output);
free_PulseOutputDevice(&output);
}
destroy_audio_buffer(audio_buffer); destroy_audio_buffer(audio_buffer);
close(sockfd); close(sockfd);