From 78b21a9ac3f975f35ec7b61108531e1495eb91c0 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sun, 12 Jan 2020 17:34:14 +0100 Subject: Rework instance creation --- backends/sacn.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'backends/sacn.c') diff --git a/backends/sacn.c b/backends/sacn.c index ff2b61e..5096123 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -195,19 +195,14 @@ static int sacn_configure_instance(instance* inst, char* option, char* value){ return 1; } -static instance* sacn_instance(){ - instance* inst = mm_instance(); - if(!inst){ - return NULL; - } - +static int sacn_instance(instance* inst){ inst->impl = calloc(1, sizeof(sacn_instance_data)); if(!inst->impl){ LOG("Failed to allocate memory"); - return NULL; + return 1; } - return inst; + return 0; } static channel* sacn_channel(instance* inst, char* spec, uint8_t flags){ -- cgit v1.2.3 From f12fb1e2f88e29c8060dfe673b65a315a84c1b29 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 6 Mar 2020 00:44:26 +0100 Subject: Implement (optional) rate-limiting for sACN --- backends/sacn.c | 78 ++++++++++++++++++++++++++++++++++++++++++-------------- backends/sacn.h | 17 +++++++++--- backends/sacn.md | 4 +-- 3 files changed, 74 insertions(+), 25 deletions(-) (limited to 'backends/sacn.c') diff --git a/backends/sacn.c b/backends/sacn.c index 5096123..6c08b5a 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -28,12 +28,14 @@ static struct /*_sacn_global_config*/ { size_t fds; sacn_fd* fd; uint64_t last_announce; + uint32_t next_frame; } global_cfg = { .source_name = "MIDIMonster", .cid = {'M', 'I', 'D', 'I', 'M', 'o', 'n', 's', 't', 'e', 'r'}, .fds = 0, .fd = NULL, - .last_announce = 0 + .last_announce = 0, + .next_frame = 0 }; MM_PLUGIN_API int init(){ @@ -46,6 +48,7 @@ MM_PLUGIN_API int init(){ .handle = sacn_set, .process = sacn_handle, .start = sacn_start, + .interval = sacn_interval, .shutdown = sacn_shutdown }; @@ -63,6 +66,13 @@ MM_PLUGIN_API int init(){ return 0; } +static uint32_t sacn_interval(){ + if(global_cfg.next_frame){ + return global_cfg.next_frame; + } + return SACN_KEEPALIVE_INTERVAL; +} + static int sacn_listener(char* host, char* port, uint8_t flags){ int fd = -1, yes = 1; if(global_cfg.fds >= MAX_FDS){ @@ -87,7 +97,6 @@ static int sacn_listener(char* host, char* port, uint8_t flags){ global_cfg.fd[global_cfg.fds].fd = fd; global_cfg.fd[global_cfg.fds].universes = 0; global_cfg.fd[global_cfg.fds].universe = NULL; - global_cfg.fd[global_cfg.fds].last_frame = NULL; if(flags & mcast_loop){ //set IP_MCAST_LOOP to allow local applications to receive output @@ -190,6 +199,10 @@ static int sacn_configure_instance(instance* inst, char* option, char* value){ data->unicast_input = strtoul(value, NULL, 10); return 0; } + else if(!strcmp(option, "realtime")){ + data->realtime = strtoul(value, NULL, 10); + return 0; + } LOGPF("Unknown instance configuration option %s for instance %s", option, inst->name); return 1; @@ -289,10 +302,11 @@ static int sacn_transmit(instance* inst){ LOGPF("Failed to output frame for instance %s: %s", inst->name, strerror(errno)); } - //update last transmit timestamp + //update last transmit timestamp, unmark instance for(u = 0; u < global_cfg.fd[data->fd_index].universes; u++){ - if(global_cfg.fd[data->fd_index].universe[u] == data->uni){ - global_cfg.fd[data->fd_index].last_frame[u] = mm_timestamp(); + if(global_cfg.fd[data->fd_index].universe[u].universe == data->uni){ + global_cfg.fd[data->fd_index].universe[u].last_frame = mm_timestamp(); + global_cfg.fd[data->fd_index].universe[u].mark = 0; } } return 0; @@ -300,6 +314,7 @@ static int sacn_transmit(instance* inst){ static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ size_t u, mark = 0; + uint32_t frame_delta = 0; sacn_instance_data* data = (sacn_instance_data*) inst->impl; if(!num){ @@ -333,6 +348,25 @@ static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ //send packet if required if(mark){ + if(!data->realtime){ + //find output instance data + for(u = 0; u < global_cfg.fd[data->fd_index].universes; u++){ + if(global_cfg.fd[data->fd_index].universe[u].universe == data->uni){ + break; + } + } + + frame_delta = mm_timestamp() - global_cfg.fd[data->fd_index].universe[u].last_frame; + + //check if ratelimiting engaged + if(frame_delta < SACN_FRAME_TIMEOUT){ + global_cfg.fd[data->fd_index].universe[u].mark = 1; + if(!global_cfg.next_frame || global_cfg.next_frame > (SACN_KEEPALIVE_INTERVAL - frame_delta)){ + global_cfg.next_frame = (SACN_KEEPALIVE_INTERVAL - frame_delta); + } + return 0; + } + } sacn_transmit(inst); } @@ -468,6 +502,7 @@ static void sacn_discovery(size_t fd){ static int sacn_handle(size_t num, managed_fd* fds){ size_t u, c; uint64_t timestamp = mm_timestamp(); + uint32_t synthesize_delta = 0; ssize_t bytes_read; char recv_buf[SACN_RECV_BUF]; instance* inst = NULL; @@ -477,7 +512,7 @@ static int sacn_handle(size_t num, managed_fd* fds){ sacn_frame_root* frame = (sacn_frame_root*) recv_buf; sacn_frame_data* data = (sacn_frame_data*) (recv_buf + sizeof(sacn_frame_root)); - if(mm_timestamp() - global_cfg.last_announce > SACN_DISCOVERY_TIMEOUT){ + if(timestamp - global_cfg.last_announce > SACN_DISCOVERY_TIMEOUT){ //send universe discovery pdu for(u = 0; u < global_cfg.fds; u++){ if(global_cfg.fd[u].universes){ @@ -487,17 +522,28 @@ static int sacn_handle(size_t num, managed_fd* fds){ global_cfg.last_announce = timestamp; } - //check for keepalive frames + //check for keepalive frames, synthesize frames if necessary for(u = 0; u < global_cfg.fds; u++){ for(c = 0; c < global_cfg.fd[u].universes; c++){ - if(timestamp - global_cfg.fd[u].last_frame[c] >= SACN_KEEPALIVE_INTERVAL){ + synthesize_delta = timestamp - global_cfg.fd[u].universe[c].last_frame; + + if((global_cfg.fd[u].universe[c].mark + && synthesize_delta >= SACN_FRAME_TIMEOUT + SACN_SYNTHESIZE_MARGIN) + || synthesize_delta >= SACN_KEEPALIVE_INTERVAL){ instance_id.fields.fd_index = u; - instance_id.fields.uni = global_cfg.fd[u].universe[c]; + instance_id.fields.uni = global_cfg.fd[u].universe[c].universe; inst = mm_instance_find(BACKEND_NAME, instance_id.label); if(inst){ sacn_transmit(inst); } } + + //update next frame request + if(global_cfg.fd[u].universe[c].mark + && (!global_cfg.next_frame || global_cfg.next_frame > SACN_FRAME_TIMEOUT + SACN_SYNTHESIZE_MARGIN - synthesize_delta)){ + global_cfg.next_frame = SACN_FRAME_TIMEOUT + SACN_SYNTHESIZE_MARGIN - synthesize_delta; + } + } } @@ -557,7 +603,6 @@ static int sacn_start(size_t n, instance** inst){ if(!global_cfg.fds){ LOG("Failed to start, no descriptors bound"); - free(inst); return 1; } @@ -590,13 +635,15 @@ static int sacn_start(size_t n, instance** inst){ if(data->xmit_prio){ //add to list of advertised universes for this fd - global_cfg.fd[data->fd_index].universe = realloc(global_cfg.fd[data->fd_index].universe, (global_cfg.fd[data->fd_index].universes + 1) * sizeof(uint16_t)); + global_cfg.fd[data->fd_index].universe = realloc(global_cfg.fd[data->fd_index].universe, (global_cfg.fd[data->fd_index].universes + 1) * sizeof(sacn_output_universe)); if(!global_cfg.fd[data->fd_index].universe){ LOG("Failed to allocate memory"); goto bail; } - global_cfg.fd[data->fd_index].universe[global_cfg.fd[data->fd_index].universes] = data->uni; + global_cfg.fd[data->fd_index].universe[global_cfg.fd[data->fd_index].universes].universe = data->uni; + global_cfg.fd[data->fd_index].universe[global_cfg.fd[data->fd_index].universes].last_frame = 0; + global_cfg.fd[data->fd_index].universe[global_cfg.fd[data->fd_index].universes].mark = 0; global_cfg.fd[data->fd_index].universes++; //generate multicast destination address if none set @@ -612,12 +659,6 @@ static int sacn_start(size_t n, instance** inst){ LOGPF("Registering %" PRIsize_t " descriptors to core", global_cfg.fds); for(u = 0; u < global_cfg.fds; u++){ - //allocate memory for storing last frame transmission timestamp - global_cfg.fd[u].last_frame = calloc(global_cfg.fd[u].universes, sizeof(uint64_t)); - if(!global_cfg.fd[u].last_frame){ - LOG("Failed to allocate memory"); - goto bail; - } if(mm_manage_fd(global_cfg.fd[u].fd, BACKEND_NAME, 1, (void*) u)){ goto bail; } @@ -638,7 +679,6 @@ static int sacn_shutdown(size_t n, instance** inst){ for(p = 0; p < global_cfg.fds; p++){ close(global_cfg.fd[p].fd); free(global_cfg.fd[p].universe); - free(global_cfg.fd[p].last_frame); } free(global_cfg.fd); LOG("Backend shut down"); diff --git a/backends/sacn.h b/backends/sacn.h index ac59441..4642e59 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -1,6 +1,7 @@ #include "midimonster.h" MM_PLUGIN_API int init(); +static uint32_t sacn_interval(); static int sacn_configure(char* option, char* value); static int sacn_configure_instance(instance* instance, char* option, char* value); static int sacn_instance(instance* inst); @@ -12,7 +13,11 @@ static int sacn_shutdown(size_t n, instance** inst); #define SACN_PORT "5568" #define SACN_RECV_BUF 8192 -#define SACN_KEEPALIVE_INTERVAL 2000 +//spec 6.6.2.1 +#define SACN_KEEPALIVE_INTERVAL 1000 +//spec 6.6.1 +#define SACN_FRAME_TIMEOUT 15 +#define SACN_SYNTHESIZE_MARGIN 10 #define SACN_DISCOVERY_TIMEOUT 9000 #define SACN_PDU_MAGIC "ASC-E1.17\0\0\0" @@ -35,6 +40,7 @@ typedef struct /*_sacn_universe_model*/ { typedef struct /*_sacn_instance_model*/ { uint16_t uni; + uint8_t realtime; uint8_t xmit_prio; uint8_t cid_filter[16]; uint8_t filter_enabled; @@ -54,11 +60,16 @@ typedef union /*_sacn_instance_id*/ { uint64_t label; } sacn_instance_id; +typedef struct /*_sacn_output_universe*/ { + uint16_t universe; + uint64_t last_frame; + uint8_t mark; +} sacn_output_universe; + typedef struct /*_sacn_socket*/ { int fd; size_t universes; - uint16_t* universe; - uint64_t* last_frame; + sacn_output_universe* universe; } sacn_fd; #pragma pack(push, 1) diff --git a/backends/sacn.md b/backends/sacn.md index f5f1db4..598f430 100644 --- a/backends/sacn.md +++ b/backends/sacn.md @@ -26,6 +26,7 @@ This has the side effect of mirroring the output of instances on those descripto | `destination` | `10.2.2.2` | Universe multicast | Destination address for unicast output. If unset, the multicast destination for the specified universe is used. | | `from` | `0xAA 0xBB` ... | none | 16-byte input source CID filter. Setting this option filters the input stream for this universe. | | `unicast` | `1` | `0` | Prevent this instance from joining its universe multicast group | +| `realtime` | `1` | `0` | Disable the recommended rate-limiting (approx. 44 packets per second) for this instance | Note that instances accepting multicast input also process unicast frames directed at them, while instances in `unicast` mode will not receive multicast frames. @@ -50,9 +51,6 @@ A normal channel that is part of a wide channel can not be mapped individually. The DMX start code of transmitted and received universes is fixed as `0`. -The (upper) limit on packet transmission rate mandated by section 6.6.1 of the sACN specification is disregarded. -The rate of packet transmission is influenced by the rate of incoming mapped events on the instance. - Universe synchronization is currently not supported, though this feature may be implemented in the future. To use multicast input, all networking hardware in the path must support the IGMPv2 protocol. -- cgit v1.2.3 From 3a53a29df4c762a1ea3891be24615f74469b6be2 Mon Sep 17 00:00:00 2001 From: cbdev Date: Fri, 6 Mar 2020 23:47:03 +0100 Subject: Minor fixes --- README.md | 3 ++- backends/sacn.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) (limited to 'backends/sacn.c') diff --git a/README.md b/README.md index 124d6b0..2a71b3a 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,8 @@ dpkg -i .deb ### Building from source To build the MIDIMonster directly from the sources, you'll need some libraries that provide -support for the protocols to translate. +support for the protocols to translate. When building from source, you can also to exclude +backends (for example, if you don't need them or don't want to install their prerequisites). * `libasound2-dev` (for the ALSA MIDI backend) * `libevdev-dev` (for the evdev backend) diff --git a/backends/sacn.c b/backends/sacn.c index 6c08b5a..54cc7b5 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -523,6 +523,7 @@ static int sacn_handle(size_t num, managed_fd* fds){ } //check for keepalive frames, synthesize frames if necessary + global_cfg.next_frame = 0; for(u = 0; u < global_cfg.fds; u++){ for(c = 0; c < global_cfg.fd[u].universes; c++){ synthesize_delta = timestamp - global_cfg.fd[u].universe[c].last_frame; -- cgit v1.2.3 From d72d7cc6366f901eb9503bac613e3bc280ac8fc5 Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 7 Mar 2020 00:01:35 +0100 Subject: Whitespace fixes --- backends/artnet.c | 2 +- backends/openpixelcontrol.c | 1 - backends/sacn.c | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) (limited to 'backends/sacn.c') diff --git a/backends/artnet.c b/backends/artnet.c index f1b8c0e..9fac332 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -356,7 +356,7 @@ static int artnet_handle(size_t num, managed_fd* fds){ for(u = 0; u < artnet_fds; u++){ for(c = 0; c < artnet_fd[u].output_instances; c++){ synthesize_delta = timestamp - artnet_fd[u].output_instance[c].last_frame; - if((artnet_fd[u].output_instance[c].mark + if((artnet_fd[u].output_instance[c].mark && synthesize_delta >= ARTNET_FRAME_TIMEOUT + ARTNET_SYNTHESIZE_MARGIN) //synthesize next frame || synthesize_delta >= ARTNET_KEEPALIVE_INTERVAL){ //keepalive timeout inst = mm_instance_find(BACKEND_NAME, artnet_fd[u].output_instance[c].label); diff --git a/backends/openpixelcontrol.c b/backends/openpixelcontrol.c index ca395d3..d9d7d4b 100644 --- a/backends/openpixelcontrol.c +++ b/backends/openpixelcontrol.c @@ -1,5 +1,4 @@ #define BACKEND_NAME "openpixelcontrol" -#define DEBUG #include diff --git a/backends/sacn.c b/backends/sacn.c index 54cc7b5..79ffb46 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -223,7 +223,7 @@ static channel* sacn_channel(instance* inst, char* spec, uint8_t flags){ char* spec_next = spec; unsigned chan_a = strtoul(spec, &spec_next, 10), chan_b = 0; - + //range check if(!chan_a || chan_a > 512){ LOGPF("Channel out of range on instance %s: %s", inst->name, spec); @@ -540,7 +540,7 @@ static int sacn_handle(size_t num, managed_fd* fds){ } //update next frame request - if(global_cfg.fd[u].universe[c].mark + if(global_cfg.fd[u].universe[c].mark && (!global_cfg.next_frame || global_cfg.next_frame > SACN_FRAME_TIMEOUT + SACN_SYNTHESIZE_MARGIN - synthesize_delta)){ global_cfg.next_frame = SACN_FRAME_TIMEOUT + SACN_SYNTHESIZE_MARGIN - synthesize_delta; } -- cgit v1.2.3 From e48583db0e02336381a1626af814549bf12b1214 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 10 Mar 2020 20:44:38 +0100 Subject: Check ArtNet/sACN channel direction at map time --- backends/artnet.c | 5 +++++ backends/artnet.h | 2 +- backends/sacn.c | 5 +++++ backends/sacn.h | 2 +- 4 files changed, 12 insertions(+), 2 deletions(-) (limited to 'backends/sacn.c') diff --git a/backends/artnet.c b/backends/artnet.c index 9fac332..0a1ed11 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -164,6 +164,11 @@ static channel* artnet_channel(instance* inst, char* spec, uint8_t flags){ } chan_a--; + //check output capabilities + if((flags & mmchannel_output) && !data->dest_len){ + LOGPF("Channel %s.%s mapped for output, but instance is not configured for output (missing destination)", inst->name, spec); + } + //secondary channel setup if(*spec_next == '+'){ chan_b = strtoul(spec_next + 1, NULL, 10); diff --git a/backends/artnet.h b/backends/artnet.h index d83999d..ac69b3d 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -20,7 +20,7 @@ static int artnet_shutdown(size_t n, instance** inst); #define ARTNET_KEEPALIVE_INTERVAL 1000 //limit transmit rate to at most 44 packets per second (1000/44 ~= 22) -#define ARTNET_FRAME_TIMEOUT 15 +#define ARTNET_FRAME_TIMEOUT 20 #define ARTNET_SYNTHESIZE_MARGIN 10 #define MAP_COARSE 0x0200 diff --git a/backends/sacn.c b/backends/sacn.c index 79ffb46..9c7f890 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -231,6 +231,11 @@ static channel* sacn_channel(instance* inst, char* spec, uint8_t flags){ } chan_a--; + //check output capabilities + if((flags & mmchannel_output) && !data->xmit_prio){ + LOGPF("Channel %s.%s mapped for output, but instance is not configured for output (no priority set)", inst->name, spec); + } + //if wide channel, mark fine if(*spec_next == '+'){ chan_b = strtoul(spec_next + 1, NULL, 10); diff --git a/backends/sacn.h b/backends/sacn.h index 4642e59..f2cd49f 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -16,7 +16,7 @@ static int sacn_shutdown(size_t n, instance** inst); //spec 6.6.2.1 #define SACN_KEEPALIVE_INTERVAL 1000 //spec 6.6.1 -#define SACN_FRAME_TIMEOUT 15 +#define SACN_FRAME_TIMEOUT 20 #define SACN_SYNTHESIZE_MARGIN 10 #define SACN_DISCOVERY_TIMEOUT 9000 #define SACN_PDU_MAGIC "ASC-E1.17\0\0\0" -- cgit v1.2.3 From 04494e4543150567a461dd478ce9ccdc067131b2 Mon Sep 17 00:00:00 2001 From: cbdev Date: Tue, 10 Mar 2020 21:57:47 +0100 Subject: Move ArtNet and sACN to local channel stores --- backends/artnet.c | 29 ++++++++++++++++++----------- backends/artnet.h | 1 + backends/sacn.c | 18 +++++++++++++----- backends/sacn.h | 1 + 4 files changed, 33 insertions(+), 16 deletions(-) (limited to 'backends/sacn.c') diff --git a/backends/artnet.c b/backends/artnet.c index 0a1ed11..5d26b31 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -104,12 +104,18 @@ static int artnet_configure(char* option, char* value){ static int artnet_instance(instance* inst){ artnet_instance_data* data = calloc(1, sizeof(artnet_instance_data)); + size_t u; + if(!data){ LOG("Failed to allocate memory"); return 1; } data->net = default_net; + for(u = 0; u < sizeof(data->data.channel) / sizeof(channel); u++){ + data->data.channel[u].ident = u; + data->data.channel[u].instance = inst; + } inst->impl = data; return 0; @@ -197,7 +203,7 @@ static channel* artnet_channel(instance* inst, char* spec, uint8_t flags){ } data->data.map[chan_a] = (*spec_next == '+') ? (MAP_COARSE | chan_b) : (MAP_SINGLE | chan_a); - return mm_channel(inst, chan_a, 1); + return data->data.channel + chan_a; } static int artnet_transmit(instance* inst){ @@ -233,7 +239,7 @@ static int artnet_transmit(instance* inst){ static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v){ uint32_t frame_delta = 0; - size_t u, mark = 0; + size_t u, mark = 0, channel_offset = 0; artnet_instance_data* data = (artnet_instance_data*) inst->impl; if(!data->dest_len){ @@ -242,22 +248,23 @@ static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v) } for(u = 0; u < num; u++){ - if(IS_WIDE(data->data.map[c[u]->ident])){ + channel_offset = c[u]->ident; + if(IS_WIDE(data->data.map[channel_offset])){ uint32_t val = v[u].normalised * ((double) 0xFFFF); //the primary (coarse) channel is the one registered to the core, so we don't have to check for that - if(data->data.out[c[u]->ident] != ((val >> 8) & 0xFF)){ + if(data->data.out[channel_offset] != ((val >> 8) & 0xFF)){ mark = 1; - data->data.out[c[u]->ident] = (val >> 8) & 0xFF; + data->data.out[channel_offset] = (val >> 8) & 0xFF; } - if(data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] != (val & 0xFF)){ + if(data->data.out[MAPPED_CHANNEL(data->data.map[channel_offset])] != (val & 0xFF)){ mark = 1; - data->data.out[MAPPED_CHANNEL(data->data.map[c[u]->ident])] = val & 0xFF; + data->data.out[MAPPED_CHANNEL(data->data.map[channel_offset])] = val & 0xFF; } } - else if(data->data.out[c[u]->ident] != (v[u].normalised * 255.0)){ + else if(data->data.out[channel_offset] != (v[u].normalised * 255.0)){ mark = 1; - data->data.out[c[u]->ident] = v[u].normalised * 255.0; + data->data.out[channel_offset] = v[u].normalised * 255.0; } } @@ -310,10 +317,10 @@ static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ if(data->data.map[p] & MAP_MARK){ data->data.map[p] &= ~MAP_MARK; if(data->data.map[p] & MAP_FINE){ - chan = mm_channel(inst, MAPPED_CHANNEL(data->data.map[p]), 0); + chan = data->data.channel + MAPPED_CHANNEL(data->data.map[p]); } else{ - chan = mm_channel(inst, p, 0); + chan = data->data.channel + p; } if(!chan){ diff --git a/backends/artnet.h b/backends/artnet.h index ac69b3d..a517aa0 100644 --- a/backends/artnet.h +++ b/backends/artnet.h @@ -37,6 +37,7 @@ typedef struct /*_artnet_universe_model*/ { uint8_t in[512]; uint8_t out[512]; uint16_t map[512]; + channel channel[512]; } artnet_universe; typedef struct /*_artnet_instance_model*/ { diff --git a/backends/sacn.c b/backends/sacn.c index 9c7f890..dd05dc7 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -209,12 +209,20 @@ static int sacn_configure_instance(instance* inst, char* option, char* value){ } static int sacn_instance(instance* inst){ - inst->impl = calloc(1, sizeof(sacn_instance_data)); - if(!inst->impl){ + sacn_instance_data* data = calloc(1, sizeof(sacn_instance_data)); + size_t u; + + if(!data){ LOG("Failed to allocate memory"); return 1; } + for(u = 0; u < sizeof(data->data.channel) / sizeof(channel); u++){ + data->data.channel[u].ident = u; + data->data.channel[u].instance = inst; + } + + inst->impl = data; return 0; } @@ -264,7 +272,7 @@ static channel* sacn_channel(instance* inst, char* spec, uint8_t flags){ } data->data.map[chan_a] = (*spec_next == '+') ? (MAP_COARSE | chan_b) : (MAP_SINGLE | chan_a); - return mm_channel(inst, chan_a, 1); + return data->data.channel + chan_a; } static int sacn_transmit(instance* inst){ @@ -424,10 +432,10 @@ static int sacn_process_frame(instance* inst, sacn_frame_root* frame, sacn_frame //unmark and get channel inst_data->data.map[u] &= ~MAP_MARK; if(inst_data->data.map[u] & MAP_FINE){ - chan = mm_channel(inst, MAPPED_CHANNEL(inst_data->data.map[u]), 0); + chan = inst_data->data.channel + MAPPED_CHANNEL(inst_data->data.map[u]); } else{ - chan = mm_channel(inst, u, 0); + chan = inst_data->data.channel + u; } if(!chan){ diff --git a/backends/sacn.h b/backends/sacn.h index f2cd49f..4138f45 100644 --- a/backends/sacn.h +++ b/backends/sacn.h @@ -36,6 +36,7 @@ typedef struct /*_sacn_universe_model*/ { uint8_t in[512]; uint8_t out[512]; uint16_t map[512]; + channel channel[512]; } sacn_universe; typedef struct /*_sacn_instance_model*/ { -- cgit v1.2.3 From 678cc465124ad81dcec47c44cc30827e5246bd3b Mon Sep 17 00:00:00 2001 From: cbdev Date: Wed, 11 Mar 2020 20:50:29 +0100 Subject: Simplify ArtNet & sACN --- backends/artnet.c | 4 +--- backends/artnet.md | 2 ++ backends/sacn.c | 4 +--- 3 files changed, 4 insertions(+), 6 deletions(-) (limited to 'backends/sacn.c') diff --git a/backends/artnet.c b/backends/artnet.c index 5d26b31..7d5d9ee 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -316,12 +316,10 @@ static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ for(p = 0; p <= max_mark; p++){ if(data->data.map[p] & MAP_MARK){ data->data.map[p] &= ~MAP_MARK; + chan = data->data.channel + p; if(data->data.map[p] & MAP_FINE){ chan = data->data.channel + MAPPED_CHANNEL(data->data.map[p]); } - else{ - chan = data->data.channel + p; - } if(!chan){ LOGPF("Active channel %" PRIsize_t " on %s not known to core", p, inst->name); diff --git a/backends/artnet.md b/backends/artnet.md index 7e1ecff..383203d 100644 --- a/backends/artnet.md +++ b/backends/artnet.md @@ -3,6 +3,8 @@ The ArtNet backend provides read-write access to the UDP-based ArtNet protocol for lighting fixture control. +Art-Netâ„¢ Designed by and Copyright Artistic Licence Holdings Ltd. + #### Global configuration | Option | Example value | Default value | Description | diff --git a/backends/sacn.c b/backends/sacn.c index dd05dc7..c9be8ff 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -431,12 +431,10 @@ static int sacn_process_frame(instance* inst, sacn_frame_root* frame, sacn_frame if(inst_data->data.map[u] & MAP_MARK){ //unmark and get channel inst_data->data.map[u] &= ~MAP_MARK; + chan = inst_data->data.channel + u; if(inst_data->data.map[u] & MAP_FINE){ chan = inst_data->data.channel + MAPPED_CHANNEL(inst_data->data.map[u]); } - else{ - chan = inst_data->data.channel + u; - } if(!chan){ LOGPF("Active channel %" PRIsize_t " on %s not known to core", u, inst->name); -- cgit v1.2.3 From 4b120a64f68fd7f36e8080981d68d0830d113205 Mon Sep 17 00:00:00 2001 From: cbdev Date: Thu, 12 Mar 2020 21:51:43 +0100 Subject: Fix rate-limited frame synthesis --- .travis.yml | 2 +- backends/artnet.c | 4 ++-- backends/sacn.c | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) (limited to 'backends/sacn.c') diff --git a/.travis.yml b/.travis.yml index b9c2b9d..d7c25b6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -160,7 +160,7 @@ notifications: - "irc.hackint.org#midimonster" on_success: change # default: always on_failure: always # default: always - nick: MIDIMonster CI + nick: mm_ci use_notice: true deploy: diff --git a/backends/artnet.c b/backends/artnet.c index 7d5d9ee..34fc82d 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -280,8 +280,8 @@ static int artnet_set(instance* inst, size_t num, channel** c, channel_value* v) //check output rate limit, request next frame if(frame_delta < ARTNET_FRAME_TIMEOUT){ artnet_fd[data->fd_index].output_instance[u].mark = 1; - if(!next_frame || next_frame > (ARTNET_KEEPALIVE_INTERVAL - frame_delta)){ - next_frame = (ARTNET_KEEPALIVE_INTERVAL - frame_delta); + if(!next_frame || next_frame > (ARTNET_FRAME_TIMEOUT - frame_delta)){ + next_frame = (ARTNET_FRAME_TIMEOUT - frame_delta); } return 0; } diff --git a/backends/sacn.c b/backends/sacn.c index c9be8ff..495bdf3 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -374,8 +374,8 @@ static int sacn_set(instance* inst, size_t num, channel** c, channel_value* v){ //check if ratelimiting engaged if(frame_delta < SACN_FRAME_TIMEOUT){ global_cfg.fd[data->fd_index].universe[u].mark = 1; - if(!global_cfg.next_frame || global_cfg.next_frame > (SACN_KEEPALIVE_INTERVAL - frame_delta)){ - global_cfg.next_frame = (SACN_KEEPALIVE_INTERVAL - frame_delta); + if(!global_cfg.next_frame || global_cfg.next_frame > (SACN_FRAME_TIMEOUT - frame_delta)){ + global_cfg.next_frame = (SACN_FRAME_TIMEOUT - frame_delta); } return 0; } -- cgit v1.2.3 From 05cdf563fcc4c7835ec422fa5d7ee86b68a9f1df Mon Sep 17 00:00:00 2001 From: cbdev Date: Sat, 14 Mar 2020 21:45:09 +0100 Subject: Restructure backend and instance registry --- TODO | 1 - backend.c | 268 +++++++++++++++++++++++++++++------------------------- backend.h | 4 +- backends/artnet.c | 5 - backends/sacn.c | 5 - config.c | 2 +- 6 files changed, 147 insertions(+), 138 deletions(-) (limited to 'backends/sacn.c') diff --git a/TODO b/TODO index 1ea91f4..900cc1b 100644 --- a/TODO +++ b/TODO @@ -7,4 +7,3 @@ make event collectors threadsafe to stop marshalling data... collect & check backend API version windows strerror move all connection establishment to _start to be able to hot-stop/start all backends -only call _handle and _interval on backends with instances diff --git a/backend.c b/backend.c index ef8f66a..65cbd7d 100644 --- a/backend.c +++ b/backend.c @@ -8,10 +8,15 @@ #include "midimonster.h" #include "backend.h" -static size_t nbackends = 0; -static backend* backends = NULL; -static size_t ninstances = 0; -static instance** instances = NULL; +static struct { + size_t n; + backend* backends; + instance*** instances; +} registry = { + .n = 0 +}; + +//TODO move channel store into registry static size_t nchannels = 0; static channel** channels = NULL; @@ -20,11 +25,11 @@ int backends_handle(size_t nfds, managed_fd* fds){ int rv = 0; managed_fd xchg; - for(u = 0; u < nbackends && !rv; u++){ + for(u = 0; u < registry.n && !rv; u++){ n = 0; for(p = 0; p < nfds; p++){ - if(fds[p].backend == backends + u){ + if(fds[p].backend == registry.backends + u){ xchg = fds[n]; fds[n] = fds[p]; fds[p] = xchg; @@ -32,10 +37,13 @@ int backends_handle(size_t nfds, managed_fd* fds){ } } - DBGPF("Notifying backend %s of %lu waiting FDs\n", backends[u].name, n); - rv |= backends[u].process(n, fds); - if(rv){ - fprintf(stderr, "Backend %s failed to handle input\n", backends[u].name); + //handle if there is data ready or the backend has active instances for polling + if(n || registry.instances[u]){ + DBGPF("Notifying backend %s of %" PRIsize_t " waiting FDs\n", registry.backends[u].name, n); + rv |= registry.backends[u].process(n, fds); + if(rv){ + fprintf(stderr, "Backend %s failed to handle input\n", registry.backends[u].name); + } } } return rv; @@ -45,28 +53,29 @@ int backends_notify(size_t nev, channel** c, channel_value* v){ size_t u, p, n; int rv = 0; channel_value xval; - channel* xchnl; - - //TODO eliminate duplicates - for(u = 0; u < ninstances && !rv; u++){ - n = 0; + channel* xchnl = NULL; - for(p = 0; p < nev; p++){ - if(c[p]->instance == instances[u]){ - xval = v[n]; - xchnl = c[n]; + for(u = 0; u < nev && !rv; u++){ + //sort for this instance + n = u + 1; + for(p = u + 1; p < nev; p++){ + if(c[p]->instance == c[u]->instance){ + xval = v[p]; + xchnl = c[p]; - v[n] = v[p]; - c[n] = c[p]; + v[p] = v[n]; + c[p] = c[n]; - v[p] = xval; - c[p] = xchnl; + v[n] = xval; + c[n] = xchnl; n++; } } - DBGPF("Calling handler for instance %s with %lu events\n", instances[u]->name, n); - rv |= instances[u]->backend->handle(instances[u], n, c, v); + //TODO eliminate duplicates + DBGPF("Calling handler for instance %s with %" PRIsize_t " events\n", c[u]->instance->name, n - u); + rv |= c[u]->instance->backend->handle(c[u]->instance, n - u, c + u, v + u); + u = n; } return 0; @@ -76,17 +85,17 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ size_t u; for(u = 0; u < nchannels; u++){ if(channels[u]->instance == inst && channels[u]->ident == ident){ - DBGPF("Requested channel %lu on instance %s already exists, reusing\n", ident, inst->name); + DBGPF("Requested channel %" PRIu64 " on instance %s already exists, reusing\n", ident, inst->name); return channels[u]; } } if(!create){ - DBGPF("Requested unknown channel %lu on instance %s\n", ident, inst->name); + DBGPF("Requested unknown channel %" PRIu64 " on instance %s\n", ident, inst->name); return NULL; } - DBGPF("Creating previously unknown channel %lu on instance %s\n", ident, inst->name); + DBGPF("Creating previously unknown channel %" PRIu64 " on instance %s\n", ident, inst->name); channel** new_chan = realloc(channels, (nchannels + 1) * sizeof(channel*)); if(!new_chan){ fprintf(stderr, "Failed to allocate memory\n"); @@ -106,34 +115,46 @@ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create){ return channels[nchannels++]; } -instance* mm_instance(){ - instance** new_inst = realloc(instances, (ninstances + 1) * sizeof(instance*)); - if(!new_inst){ - //TODO free - fprintf(stderr, "Failed to allocate memory\n"); - ninstances = 0; - return NULL; - } - instances = new_inst; - instances[ninstances] = calloc(1, sizeof(instance)); - if(!instances[ninstances]){ - fprintf(stderr, "Failed to allocate memory\n"); - return NULL; +instance* mm_instance(backend* b){ + size_t u = 0, n = 0; + + for(u = 0; u < registry.n; u++){ + if(registry.backends + u == b){ + //count existing instances + for(n = 0; registry.instances[u] && registry.instances[u][n]; n++){ + } + + //extend + registry.instances[u] = realloc(registry.instances[u], (n + 2) * sizeof(instance*)); + if(!registry.instances[u]){ + fprintf(stderr, "Failed to allocate memory\n"); + return NULL; + } + //sentinel + registry.instances[u][n + 1] = NULL; + registry.instances[u][n] = calloc(1, sizeof(instance)); + if(!registry.instances[u][n]){ + fprintf(stderr, "Failed to allocate memory\n"); + } + registry.instances[u][n]->backend = b; + return registry.instances[u][n]; + } } - return instances[ninstances++]; + //this should never happen + return NULL; } MM_API instance* mm_instance_find(char* name, uint64_t ident){ - size_t u; - backend* b = backend_match(name); - if(!b){ - return NULL; - } - - for(u = 0; u < ninstances; u++){ - if(instances[u]->backend == b && instances[u]->ident == ident){ - return instances[u]; + size_t b = 0; + instance** iter = NULL; + for(b = 0; b < registry.n; b++){ + if(!strcmp(registry.backends[b].name, name)){ + for(iter = registry.instances[b]; iter && *iter; iter++){ + if((*iter)->ident == ident){ + return *iter; + } + } } } @@ -141,55 +162,40 @@ MM_API instance* mm_instance_find(char* name, uint64_t ident){ } MM_API int mm_backend_instances(char* name, size_t* ninst, instance*** inst){ - backend* b = backend_match(name); - size_t n = 0, u; - //count number of affected instances - for(u = 0; u < ninstances; u++){ - if(instances[u]->backend == b){ - n++; - } + size_t b = 0, i = 0; + if(!ninst || !inst){ + return 1; } - *ninst = n; + for(b = 0; b < registry.n; b++){ + if(!strcmp(registry.backends[b].name, name)){ + //count instances + for(i = 0; registry.instances[b] && registry.instances[b][i]; i++){ + } - if(!n){ - *inst = NULL; - return 0; - } + *ninst = i; + if(!i){ + *inst = NULL; + return 0; + } - *inst = calloc(n, sizeof(instance*)); - if(!*inst){ - fprintf(stderr, "Failed to allocate memory\n"); - return 1; - } + *inst = calloc(i, sizeof(instance*)); + if(!*inst){ + fprintf(stderr, "Failed to allocate memory\n"); + return 1; + } - n = 0; - for(u = 0; u < ninstances; u++){ - if(instances[u]->backend == b){ - (*inst)[n] = instances[u]; - n++; + memcpy(*inst, registry.instances[b], i * sizeof(instance*)); + return 0; } } - return 0; -} - -void instances_free(){ - size_t u; - for(u = 0; u < ninstances; u++){ - free(instances[u]->name); - instances[u]->name = NULL; - instances[u]->backend = NULL; - free(instances[u]); - instances[u] = NULL; - } - free(instances); - ninstances = 0; + return 1; } void channels_free(){ size_t u; for(u = 0; u < nchannels; u++){ - DBGPF("Destroying channel %lu on instance %s\n", channels[u]->ident, channels[u]->instance->name); + DBGPF("Destroying channel %" PRIu64 " on instance %s\n", channels[u]->ident, channels[u]->instance->name); if(channels[u]->impl && channels[u]->instance->backend->channel_free){ channels[u]->instance->backend->channel_free(channels[u]); } @@ -200,11 +206,12 @@ void channels_free(){ nchannels = 0; } + backend* backend_match(char* name){ size_t u; - for(u = 0; u < nbackends; u++){ - if(!strcmp(backends[u].name, name)){ - return backends + u; + for(u = 0; u < registry.n; u++){ + if(!strcmp(registry.backends[u].name, name)){ + return registry.backends + u; } } return NULL; @@ -212,9 +219,12 @@ backend* backend_match(char* name){ instance* instance_match(char* name){ size_t u; - for(u = 0; u < ninstances; u++){ - if(!strcmp(instances[u]->name, name)){ - return instances[u]; + instance** iter = NULL; + for(u = 0; u < registry.n; u++){ + for(iter = registry.instances[u]; iter && *iter; iter++){ + if(!strcmp(name, (*iter)->name)){ + return *iter; + } } } return NULL; @@ -224,16 +234,17 @@ struct timeval backend_timeout(){ size_t u; uint32_t res, secs = 1, msecs = 0; - for(u = 0; u < nbackends; u++){ - if(backends[u].interval){ - res = backends[u].interval(); + for(u = 0; u < registry.n; u++){ + //only call interval if backend has instances + if(registry.instances[u] && registry.backends[u].interval){ + res = registry.backends[u].interval(); if((res / 1000) < secs){ - DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, backends[u].name); + DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, registry.backends[u].name); secs = res / 1000; msecs = res % 1000; } else if(res / 1000 == secs && (res % 1000) < msecs){ - DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, backends[u].name); + DBGPF("Updating interval to %" PRIu32 " msecs by request from %s", res, registry.backends[u].name); msecs = res % 1000; } } @@ -248,14 +259,16 @@ struct timeval backend_timeout(){ MM_API int mm_backend_register(backend b){ if(!backend_match(b.name)){ - backends = realloc(backends, (nbackends + 1) * sizeof(backend)); - if(!backends){ + registry.backends = realloc(registry.backends, (registry.n + 1) * sizeof(backend)); + registry.instances = realloc(registry.instances, (registry.n + 1) * sizeof(instance**)); + if(!registry.backends || !registry.instances){ fprintf(stderr, "Failed to allocate memory\n"); - nbackends = 0; + registry.n = 0; return 1; } - backends[nbackends] = b; - nbackends++; + registry.backends[registry.n] = b; + registry.instances[registry.n] = NULL; + registry.n++; fprintf(stderr, "Registered backend %s\n", b.name); return 0; @@ -265,29 +278,25 @@ MM_API int mm_backend_register(backend b){ int backends_start(){ int rv = 0, current; - size_t n, u, p; instance** inst = NULL; + size_t n, u; - for(u = 0; u < nbackends; u++){ - //only start backends that have instances - for(p = 0; p < ninstances && instances[p]->backend != backends + u; p++){ - } - - //backend has no instances, skip the start call - if(p == ninstances){ + for(u = 0; u < registry.n; u++){ + //skip backends without instances + if(!registry.instances[u]){ continue; } //fetch list of instances - if(mm_backend_instances(backends[u].name, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list for initialization of backend %s\n", backends[u].name); + if(mm_backend_instances(registry.backends[u].name, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list for initialization of backend %s\n", registry.backends[u].name); return 1; } //start the backend - current = backends[u].start(n, inst); + current = registry.backends[u].start(n, inst); if(current){ - fprintf(stderr, "Failed to start backend %s\n", backends[u].name); + fprintf(stderr, "Failed to start backend %s\n", registry.backends[u].name); } //clean up @@ -302,20 +311,33 @@ int backends_stop(){ size_t u, n; instance** inst = NULL; - for(u = 0; u < nbackends; u++){ + //shut down the registry + for(u = 0; u < registry.n; u++){ //fetch list of instances - if(mm_backend_instances(backends[u].name, &n, &inst)){ - fprintf(stderr, "Failed to fetch instance list for shutdown of backend %s\n", backends[u].name); - n = 0; + if(mm_backend_instances(registry.backends[u].name, &n, &inst)){ + fprintf(stderr, "Failed to fetch instance list for shutdown of backend %s\n", registry.backends[u].name); inst = NULL; + n = 0; } - backends[u].shutdown(n, inst); + registry.backends[u].shutdown(n, inst); free(inst); inst = NULL; + + //free instances + for(inst = registry.instances[u]; inst && *inst; inst++){ + free((*inst)->name); + (*inst)->name = NULL; + (*inst)->backend = NULL; + free(*inst); + } + free(registry.instances[u]); + registry.instances[u] = NULL; } - free(backends); - nbackends = 0; + channels_free(); + free(registry.backends); + free(registry.instances); + registry.n = 0; return 0; } diff --git a/backend.h b/backend.h index de9b5dc..6a69508 100644 --- a/backend.h +++ b/backend.h @@ -8,9 +8,7 @@ instance* instance_match(char* name); struct timeval backend_timeout(); int backends_start(); int backends_stop(); -void instances_free(); -void channels_free(); -instance* mm_instance(); +instance* mm_instance(backend* b); /* Backend API */ MM_API channel* mm_channel(instance* inst, uint64_t ident, uint8_t create); diff --git a/backends/artnet.c b/backends/artnet.c index 34fc82d..caab6e0 100644 --- a/backends/artnet.c +++ b/backends/artnet.c @@ -321,11 +321,6 @@ static inline int artnet_process_frame(instance* inst, artnet_pkt* frame){ chan = data->data.channel + MAPPED_CHANNEL(data->data.map[p]); } - if(!chan){ - LOGPF("Active channel %" PRIsize_t " on %s not known to core", p, inst->name); - return 1; - } - if(IS_WIDE(data->data.map[p])){ data->data.map[MAPPED_CHANNEL(data->data.map[p])] &= ~MAP_MARK; wide_val = data->data.in[p] << ((data->data.map[p] & MAP_COARSE) ? 8 : 0); diff --git a/backends/sacn.c b/backends/sacn.c index 495bdf3..bd5c75a 100644 --- a/backends/sacn.c +++ b/backends/sacn.c @@ -436,11 +436,6 @@ static int sacn_process_frame(instance* inst, sacn_frame_root* frame, sacn_frame chan = inst_data->data.channel + MAPPED_CHANNEL(inst_data->data.map[u]); } - if(!chan){ - LOGPF("Active channel %" PRIsize_t " on %s not known to core", u, inst->name); - return 1; - } - //generate value if(IS_WIDE(inst_data->data.map[u])){ inst_data->data.map[MAPPED_CHANNEL(inst_data->data.map[u])] &= ~MAP_MARK; diff --git a/config.c b/config.c index 920e161..aef4f87 100644 --- a/config.c +++ b/config.c @@ -388,7 +388,7 @@ static int config_line(char* line){ return 1; } - current_instance = mm_instance(); + current_instance = mm_instance(current_backend); if(!current_instance){ return 1; } -- cgit v1.2.3