This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,702 @@
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 2.12 -->
<!-- interface-naming-policy toplevel-contextual -->
<object class="GtkAdjustment" id="adjustment1">
<property name="value">1025</property>
<property name="upper">49151</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment2">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment3">
<property name="upper">100</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkAdjustment" id="adjustment4">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">1024</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
<object class="GtkListStore" id="model1">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">unspecified</col>
</row>
<row>
<col id="0" translatable="yes">female</col>
</row>
<row>
<col id="0" translatable="yes">male</col>
</row>
<row>
<col id="0" translatable="yes">unknown</col>
</row>
</data>
</object>
<object class="GtkListStore" id="model2">
<columns>
<!-- column-name gchararray -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">128</col>
</row>
<row>
<col id="0" translatable="yes">256</col>
</row>
<row>
<col id="0" translatable="yes">512</col>
</row>
<row>
<col id="0" translatable="yes">1024</col>
</row>
<row>
<col id="0" translatable="yes">2048</col>
</row>
<row>
<col id="0" translatable="yes">4096</col>
</row>
</data>
</object>
<object class="GtkDialog" id="openvibe-acquisition-server-settings">
<property name="border_width">5</property>
<property name="title" translatable="yes">Device configuration</property>
<property name="window_position">center</property>
<property name="type_hint">dialog</property>
<property name="gravity">center</property>
<property name="has_separator">False</property>
<child internal-child="vbox">
<object class="GtkVBox" id="dialog-vbox">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="spacing">8</property>
<child>
<object class="GtkLabel" id="label_title">
<property name="visible">True</property>
<property name="label" translatable="yes">Fieldtrip</property>
<property name="justify">center</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox">
<property name="visible">True</property>
<property name="border_width">8</property>
<property name="spacing">8</property>
<child>
<object class="GtkHBox" id="hbox">
<property name="visible">True</property>
<property name="spacing">8</property>
<child>
<object class="GtkTable" id="table2">
<property name="visible">True</property>
<property name="n_rows">7</property>
<property name="n_columns">2</property>
<property name="homogeneous">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_host_port">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment1</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="entry_host_name">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="max_length">15</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_port">
<property name="visible">True</property>
<property name="label" translatable="yes">Buffer port number</property>
</object>
<packing>
<property name="top_attach">4</property>
<property name="bottom_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_host_name">
<property name="visible">True</property>
<property name="label" translatable="yes">Buffer host name</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_identifier">
<property name="visible">True</property>
<property name="label" translatable="yes">Identifier :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
</child>
<child>
<object class="GtkLabel" id="label_age">
<property name="visible">True</property>
<property name="label" translatable="yes">Age :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_gender">
<property name="visible">True</property>
<property name="label" translatable="yes">Gender :</property>
<property name="justify">right</property>
<property name="single_line_mode">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_identifier">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment2</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_age">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment3</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_gender">
<property name="visible">True</property>
<property name="model">model1</property>
<child>
<object class="GtkCellRendererText" id="renderer1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_minSamples">
<property name="visible">True</property>
<property name="label" translatable="yes">Min nb of samples in buffer</property>
</object>
<packing>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_minSamples">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x25CF;</property>
<property name="adjustment">adjustment5</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">5</property>
<property name="bottom_attach">6</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_SRCorrection">
<property name="label" translatable="yes">Correct manually drift due to non-integer sampling rate</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
</object>
<packing>
<property name="right_attach">2</property>
<property name="top_attach">6</property>
<property name="bottom_attach">7</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkHSeparator" id="hseparator1">
<property name="visible">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="position">4</property>
</packing>
</child>
<child internal-child="action_area">
<object class="GtkHButtonBox" id="dialog-action_area">
<property name="visible">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="button_apply">
<property name="label">gtk-apply</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_cancel">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="pack_type">end</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-10">button_apply</action-widget>
<action-widget response="-6">button_cancel</action-widget>
</action-widgets>
</object>
<object class="GtkWindow" id="dummy-widgets">
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">10</property>
<property name="n_columns">10</property>
<child>
<object class="GtkSpinButton" id="spinbutton_number_of_channels">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="adjustment">adjustment4</property>
<property name="snap_to_ticks">True</property>
<property name="numeric">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_sampling_frequency">
<property name="visible">True</property>
<property name="model">model2</property>
<child>
<object class="GtkCellRendererText" id="renderer2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_change_channel_names">
<property name="label" translatable="yes">Change channel names</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="receives_default">False</property>
</object>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
</child>
</object>
<object class="GtkAdjustment" id="adjustment5">
<property name="value">1</property>
<property name="lower">1</property>
<property name="upper">10000</property>
<property name="step_increment">1</property>
<property name="page_increment">10</property>
</object>
</interface>
@@ -0,0 +1,32 @@
#pragma once
#include "windows.h"
#include "ov_defines.h"
#include "ovCString.h"
//___________________________________________________________________//
// //
// Get Cpu Time //
//___________________________________________________________________//
// //
namespace OpenViBE
{
OV_API bool GetEnvVar( CString& sVar, CString& sValue );
OV_API bool PutEnvVar( CString& sVar, CString& sValue );
OV_API inline double GetCPUTimeInMilliseconds()
{
static bool bGotCPUFreq = false;
static double timerFrequency = 1;
if ( !bGotCPUFreq )
{
unsigned __int64 cpufreq = 1;
QueryPerformanceFrequency((LARGE_INTEGER*)&cpufreq);
timerFrequency = (1000.0/cpufreq);
}
unsigned __int64 curTime = 0;
QueryPerformanceCounter((LARGE_INTEGER *)&curTime);
return timerFrequency * curTime;
};
}
@@ -0,0 +1,128 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
/* prevent double include */
#ifndef BUFFER_H
#define BUFFER_H
#include <cstdlib>
#include <string.h>
#include <stdio.h>
#include "platform_includes.h"
#include "message.h"
#ifndef POLLRDNORM
#define POLLRDNORM POLLIN
#endif
#ifndef POLLRDBAND
#define POLLRDBAND POLLPRI
#endif
#ifndef POLLWRNORM
#define POLLWRNORM POLLOUT
#endif
#ifndef POLLWRBAND
#define POLLWRBAND POLLOUT
#endif
#define BACKLOG 16
#define DEFAULT_HOSTNAME "localhost"
#define DEFAULT_PORT 1972
#define SO_RCVBUF_SIZE 16384
#define SO_SNDBUF_SIZE 16384
/* this is because the function has been renamed, but is perhaps already in use in other software */
#define open_remotehost open_connection
/* FIXME these should be variable */
#define MAXNUMBYTE (512*1024*1024)
#define MAXNUMSAMPLE 600000
#define MAXNUMEVENT 100
#define WRAP(x,y) ((x) - (int(float(x)/(y)))*(y))
#define FREE(x) {if (x) {free(x); x= nullptr;}}
#ifdef __cplusplus
extern "C" {
#endif
/* declaration of "public" buffer API functions */
/* SK: where are these, and what are they for ? */
int read_header( const char *hostname, int port, void **ppw);
int read_data( const char *hostname, int port, int *pnw, void **ppw);
int read_event( const char *hostname, int port, int *pnw, void **ppw);
int write_header(const char *hostname, int port, void **ppw);
int write_data( const char *hostname, int port, int *pnw, void **ppw);
int write_event( const char *hostname, int port, int *pnw, void **ppw);
int flush_header(const char *hostname, int port);
int flush_data( const char *hostname, int port);
int flush_event( const char *hostname, int port);
void cleanup_buffer();
/* definition of the functions that implement the network transparent server */
void *tcpserver(void *);
void *tcpsocket(void *);
/* definition of test functions that emulate an acquisition system */
void *sinewave_thread(void *);
void *event_thread(void *);
/* definition of the functions used in thread cancelation, see cleanup.c */
void cleanup_message(void **arg);
void cleanup_header(void **arg);
void cleanup_data(void **arg);
void cleanup_event(void **arg);
void cleanup_buf(void **arg);
void cleanup_socket(int *);
/* definition of helper functions for debugging and printing the content of various structures */
void print_request(messagedef_t *);
void print_response(messagedef_t *);
void print_headerdef(headerdef_t *);
void print_datadef(datadef_t *);
void print_eventdef(eventdef_t *);
void print_datasel(datasel_t *);
void print_eventsel(eventsel_t *);
void print_buf(void *, int);
/* definition of even more helper functions, see util.c */
int open_connection(const char*, int);
int open_unix_connection(const char *name);
int close_connection(int);
unsigned int append(void **, unsigned int, void *, unsigned int);
unsigned int bufread(int, void *, unsigned int);
unsigned int bufwrite(int, const void *, unsigned int);
int clientrequest(int, const message_t *, message_t**);
int dmarequest(const message_t *, message_t**);
int tcprequest(int, const message_t *, message_t**);
unsigned int wordsize_from_type(UINT32_T data_type);
void check_datatypes();
int check_event_array(unsigned int size, const void *buf);
const ft_chunk_t *find_chunk(const void *buf, unsigned int offset0, unsigned int size, UINT32_T chunk_type);
void ft_swap16(unsigned int numel, void *data);
void ft_swap32(unsigned int numel, void *data);
void ft_swap64(unsigned int numel, void *data);
int ft_swap_buf_to_native(UINT16_T command, UINT32_T bufsize, void *buf);
int ft_convert_chunks_from_native(UINT32_T size, UINT32_T nchans, void *buf);
int ft_swap_from_native(UINT16_T orgCommand, message_t *msg);
typedef struct {
char name[256];
int port;
} host_t;
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,75 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdlib.h>
#include <stdio.h>
#include "buffer.h"
/* this is used for debugging */
int verbose = 0;
void cleanup_socket(int *arg) {
if (verbose>0) fprintf(stderr, "cleanup_socket: s = %d\n", *arg);
if ((*arg)>0) {
close_connection(*arg);
}
*arg = 0;
return;
}
void cleanup_message(void **arg) {
message_t *message = (message_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_message()\n");
if (message) {
FREE(message->def);
FREE(message->buf);
FREE(message);
}
return;
}
void cleanup_header(void **arg) {
header_t *header = (header_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_header()\n");
if (header) {
FREE(header->def);
FREE(header->buf);
FREE(header);
}
return;
}
void cleanup_data(void **arg) {
data_t *data = (data_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_data()\n");
if (data) {
FREE(data->def);
FREE(data->buf);
FREE(data);
}
return;
}
void cleanup_event(void **arg) {
event_t *event = (event_t *)*arg;
if (verbose>0) fprintf(stderr, "cleanup_event()\n");
if (event) {
FREE(event->def);
FREE(event->buf);
FREE(event);
}
return;
}
void cleanup_buf(void **arg) {
if (verbose>0) fprintf(stderr, "cleanup_buf()\n");
if (*arg) {
FREE(*arg);
}
return;
}
@@ -0,0 +1,44 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
/*******************************************************************************
* this function is called by the client
* it takes care that the request is processed by the buffer
*******************************************************************************/
int clientrequest(int server, const message_t *request, message_t **response_ptr) {
int verbose = 0;
if (verbose>0) fprintf(stderr, "clientrequest: server = %d\n", server);
if (verbose>0) print_request(request->def);
if (server<0) {
fprintf(stderr, "clientrequest: invalid value for server (%d)\n", server);
return -1;
}
else if (server==0) {
/* use direct memory acces to the buffer */
if (dmarequest(request, response_ptr)!=0)
return -2;
}
else if (server>0) {
/* use TCP connection to the buffer */
if (tcprequest(server, request, response_ptr)!=0)
return -3;
}
if (verbose>0) print_response((*response_ptr)->def);
/* everything went fine */
return 0;
}
@@ -0,0 +1,19 @@
/* prevent double include */
#pragma once
#if defined (__BORLANDC__)
#define COMPILER_BORLAND
#elif defined (_MSC_VER)
#define COMPILER_MSVC
#elif defined (__CYGWIN32__)
#define COMPILER_CYGWIN
#elif defined (__MINGW32__)
#define COMPILER_MINGW
#elif defined (__LCC__)
#define COMPILER_LCC
#endif
@@ -0,0 +1,735 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "buffer.h"
#include <pthread.h>
#ifdef TARGET_OS_Linux
#include <sys/time.h>
#endif
/* FIXME should these be static? */
static header_t *header = NULL;
static data_t *data = NULL;
static event_t *event = NULL;
static unsigned int current_max_num_sample = 0;
static int thissample = 0; /* points at the buffer */
static int thisevent = 0; /* points at the buffer */
/* Note that there have been problems with the order of the mutexes (e.g.
* http://bugzilla.fcdonders.nl/show_bug.cgi?id=933).
* I have attempted to make the order of locking consistent, but can't give
* guarantees. A more long term solution could be:
* - find the dependencies between modifications of volatile data (e.g. events
* depend on header),
* - keep locks as shortly as possible (get info, release again).
*
* This could results in a global lock (robust, probably not optimal in terms
* of speed), or a series of locks sandwiching modification code in dedicated
* functions.
*
* -- Boris
*/
pthread_mutex_t mutexheader = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexdata = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutexevent = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t getData_cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t getData_mutex = PTHREAD_MUTEX_INITIALIZER;
#define DIE_BAD_MALLOC(ptr) if ((ptr) == nullptr) { fprintf(stderr,"Out of memory with unchecked malloc in line %d",__LINE__); exit(1); }
/*****************************************************************************/
void free_header() {
int verbose = 0;
if (verbose>0) fprintf(stderr, "free_header: freeing header buffer\n");
if (header) {
FREE(header->def);
FREE(header->buf);
FREE(header);
}
}
void free_data() {
int verbose = 0;
if (verbose>0) fprintf(stderr, "free_data: freeing data buffer\n");
if (data) {
FREE(data->def);
FREE(data->buf);
FREE(data);
}
thissample = 0;
if (header) header->def->nsamples = 0;
}
void free_event() {
int verbose = 0;
int i;
if (verbose>0) fprintf(stderr, "free_event: freeing event buffer\n");
if (event) {
for (i=0; i<MAXNUMEVENT; ++i) {
FREE(event[i].def);
FREE(event[i].buf);
}
FREE(event);
}
thisevent = 0;
if (header) header->def->nevents = 0;
}
/*****************************************************************************/
void init_data() {
int verbose = 0;
if (verbose>0) fprintf(stderr, "init_data: creating data buffer\n");
if (header) {
unsigned int wordsize = wordsize_from_type(header->def->data_type);
if (wordsize==0) {
fprintf(stderr, "init_data: unsupported data type (%u)\n", header->def->data_type);
return;
}
/* heuristic of choosing size of buffer:
set current_max_num_sample to MAXNUMSAMPLE if nchans <= 256
otherwise, allocate about MAXNUMBYTE and calculate current_max_num_sample from nchans + wordsize
*/
if (header->def->nchans <= 256) {
current_max_num_sample = MAXNUMSAMPLE;
} else {
current_max_num_sample = MAXNUMBYTE / (wordsize * header->def->nchans);
}
data = (data_t*)malloc(sizeof(data_t));
DIE_BAD_MALLOC(data);
data->def = (datadef_t*)malloc(sizeof(datadef_t));
DIE_BAD_MALLOC(data->def);
data->def->nchans = header->def->nchans;
data->def->nsamples = current_max_num_sample;
data->def->data_type = header->def->data_type;
data->buf = malloc(header->def->nchans*current_max_num_sample*wordsize);
DIE_BAD_MALLOC(data->buf);
}
}
void init_event() {
int verbose = 0;
int i;
if (verbose>0) fprintf(stderr, "init_event: creating event buffer\n");
if (header) {
event = (event_t*)malloc(MAXNUMEVENT*sizeof(event_t));
DIE_BAD_MALLOC(event);
for (i=0; i<MAXNUMEVENT; ++i) {
event[i].def = NULL;
event[i].buf = NULL;
}
}
}
/*****************************************************************************
* this function handles the direct memory access to the buffer
* and copies objects to and from memory
*****************************************************************************/
int dmarequest(const message_t *request, message_t **response_ptr) {
unsigned int offset;
/*
int blockrequest = 0;
*/
int verbose = 0;
/* these are used for blocking the read requests */
struct timeval tp;
struct timespec ts;
/* use a local variable for datasel (in GET_DAT) */
datasel_t datasel;
/* these are for typecasting */
headerdef_t *headerdef;
datadef_t *datadef;
eventdef_t *eventdef;
eventsel_t *eventsel;
/* this will hold the response */
message_t *response;
response = (message_t*)malloc(sizeof(message_t));
/* check for "out of memory" problems */
if (response == nullptr) {
*response_ptr = NULL;
return -1;
}
response->def = (messagedef_t*)malloc(sizeof(messagedef_t));
/* check for "out of memory" problems */
if (response->def == nullptr) {
*response_ptr = NULL;
free(response);
return -1;
}
response->buf = NULL;
/* the response should be passed to the calling function, where it should be freed */
*response_ptr = response;
if (verbose>1) print_request(request->def);
switch (request->def->command) {
case PUT_HDR:
if (verbose>1) fprintf(stderr, "dmarequest: PUT_HDR\n");
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
pthread_mutex_lock(&mutexevent);
headerdef = (headerdef_t*)request->buf;
if (verbose>1) print_headerdef(headerdef);
/* delete the old header, data and events */
free_header();
free_data();
free_event();
/* store the header and re-initialize */
header = (header_t*)malloc(sizeof(header_t));
DIE_BAD_MALLOC(header);
header->def = (headerdef_t*)malloc(sizeof(headerdef_t));
DIE_BAD_MALLOC(header->def);
header->buf = malloc(headerdef->bufsize);
DIE_BAD_MALLOC(header->buf);
memcpy(header->def, request->buf, sizeof(headerdef_t));
memcpy(header->buf, (char*)request->buf+sizeof(headerdef_t), headerdef->bufsize);
header->def->nsamples = 0;
header->def->nevents = 0;
init_data();
init_event();
response->def->version = VERSION;
response->def->bufsize = 0;
/* check whether memory could indeed be allocated */
if (data!= NULL && data->buf != nullptr && data->def != nullptr) {
response->def->command = PUT_OK;
} else {
/* let's at least tell the client that something's wrong */
response->def->command = PUT_ERR;
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case PUT_DAT:
if (verbose>1) fprintf(stderr, "dmarequest: PUT_DAT\n");
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
datadef = (datadef_t*)request->buf;
if (verbose>1) print_datadef(datadef);
if (verbose>2) print_buf(request->buf, request->def->bufsize);
response->def->version = VERSION;
response->def->bufsize = 0;
if (request->def->bufsize < sizeof(datadef_t))
response->def->command = PUT_ERR;
else if (header == nullptr || data == nullptr)
response->def->command = PUT_ERR;
else if (header->def->nchans != datadef->nchans)
response->def->command = PUT_ERR;
else if (header->def->data_type != datadef->data_type)
response->def->command = PUT_ERR;
else if (datadef->nsamples > current_max_num_sample)
response->def->command = PUT_ERR;
else {
unsigned int i;
unsigned int wordsize = wordsize_from_type(header->def->data_type);
unsigned int datasize = wordsize * datadef->nsamples * datadef->nchans;
response->def->command = PUT_OK;
if (wordsize == 0) {
fprintf(stderr, "dmarequest: unsupported data type (%d)\n", datadef->data_type);
response->def->command = PUT_ERR;
} else if (datasize > datadef->bufsize || (datadef->bufsize + sizeof(datadef_t)) > request->def->bufsize) {
fprintf(stderr, "dmarequest: invalid size definitions in PUT_DAT request\n");
response->def->command = PUT_ERR;
} else {
/* number of bytes per sample (all channels) is given by wordsize x number of channels */
unsigned int chansize = wordsize * data->def->nchans;
/* request_data points to actual data samples within the request, use char* for convenience */
const char *request_data = (const char *) request->buf + sizeof(datadef_t);
char *buffer_data = (char *)data->buf;
for (i=0; i<datadef->nsamples; ++i) {
memcpy(buffer_data+(thissample*chansize), request_data+(i*chansize), chansize);
header->def->nsamples++;
thissample++;
thissample = WRAP(thissample, current_max_num_sample);
}
/* Signal possibly waiting threads that we have received data */
pthread_cond_broadcast(&getData_cond);
}
}
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case PUT_EVT:
if (verbose>1) fprintf(stderr, "dmarequest: PUT_EVT\n");
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexevent);
/* Give an error message if there is no header, or if the given event array is defined badly */
if (header == nullptr || event == nullptr || check_event_array(request->def->bufsize, request->buf) < 0) {
response->def->version = VERSION;
response->def->command = PUT_ERR;
response->def->bufsize = 0;
}
else { /* go over all events and store them one by one */
response->def->version = VERSION;
response->def->command = PUT_OK;
response->def->bufsize = 0;
offset = 0; /* this represents the offset of the event in the buffer */
while (offset<request->def->bufsize) {
FREE(event[thisevent].def);
FREE(event[thisevent].buf);
eventdef = (eventdef_t*)((char*)request->buf+offset);
if (verbose>1) print_eventdef(eventdef);
event[thisevent].def = (eventdef_t*)malloc(sizeof(eventdef_t));
DIE_BAD_MALLOC(event[thisevent].def);
memcpy(event[thisevent].def, (char*)request->buf+offset, sizeof(eventdef_t));
/* automatically convert event->def->sample to current sample number
(thus this event "belongs" to the first sample of the next block from PUT_DAT)
*/
if (event[thisevent].def->sample == EVENT_AUTO_SAMPLE) {
event[thisevent].def->sample = header->def->nsamples;
}
offset += sizeof(eventdef_t);
event[thisevent].buf = malloc(eventdef->bufsize);
DIE_BAD_MALLOC(event[thisevent].buf);
memcpy(event[thisevent].buf, (char*)request->buf+offset, eventdef->bufsize);
offset += eventdef->bufsize;
if (verbose>1) print_eventdef(event[thisevent].def);
thisevent++;
thisevent = WRAP(thisevent, MAXNUMEVENT);
header->def->nevents++;
}
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexheader);
break;
case GET_HDR:
if (verbose>1) fprintf(stderr, "dmarequest: GET_HDR\n");
if (header == nullptr) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
break;
}
pthread_mutex_lock(&mutexheader);
response->def->version = VERSION;
response->def->command = GET_OK;
response->def->bufsize = 0;
response->def->bufsize = append(&response->buf, response->def->bufsize, header->def, sizeof(headerdef_t));
response->def->bufsize = append(&response->buf, response->def->bufsize, header->buf, header->def->bufsize);
pthread_mutex_unlock(&mutexheader);
break;
case GET_DAT:
if (verbose>1) fprintf(stderr, "dmarequest: GET_DAT\n");
if (header == nullptr || data == nullptr) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
break;
}
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
if (request->def->bufsize) {
/* the selection has been specified */
memcpy(&datasel, request->buf, sizeof(datasel_t));
/* If endsample is -1 read the buffer to the end */
if(datasel.endsample == UINT32_T(-1))
{
datasel.endsample = header->def->nsamples - 1;
}
}
else {
/* determine a valid selection */
if (header->def->nsamples>current_max_num_sample) {
/* the ringbuffer is completely full */
datasel.begsample = header->def->nsamples - current_max_num_sample;
datasel.endsample = header->def->nsamples - 1;
}
else {
/* the ringbuffer is not yet completely full */
datasel.begsample = 0;
datasel.endsample = header->def->nsamples - 1;
}
}
/*
// if the read should block...
if(blockrequest == 1)
{
// check whether data is available
while((datasel.begsample >= (datasel.endsample+1)) || (datasel.endsample > header->def->nsamples - 1))
{
// if not unlock all mutexes
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
// wait for the condition to be signaled
pthread_mutex_lock(&getData_mutex);
gettimeofday(&tp, nullptr);
ts.tv_sec = tp.tv_sec;
ts.tv_nsec = tp.tv_usec * 1000;
ts.tv_sec += 1;
pthread_cond_timedwait(&getData_cond, &getData_mutex, &ts);
pthread_mutex_unlock(&getData_mutex);
// Lock the mutexes again
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
if(datasel.begsample == (datasel.endsample+1))
datasel.endsample = header->def->nsamples - 1;
}
}
*/
if (verbose>1) print_headerdef(header->def);
if (verbose>1) print_datasel(&datasel);
if (datasel.begsample < 0 || datasel.endsample < 0) {
fprintf(stderr, "dmarequest: err1\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if (datasel.begsample >= header->def->nsamples || datasel.endsample >= header->def->nsamples) {
fprintf(stderr, "dmarequest: err2\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if ((header->def->nsamples - datasel.begsample) > current_max_num_sample) {
fprintf(stderr, "dmarequest: err3\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else {
unsigned int wordsize = wordsize_from_type(data->def->data_type);
if (wordsize==0) {
fprintf(stderr, "dmarequest: unsupported data type (%d)\n", data->def->data_type);
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
} else {
unsigned int n;
response->def->version = VERSION;
response->def->command = GET_OK;
response->def->bufsize = 0;
/* determine the number of samples to return */
n = datasel.endsample - datasel.begsample + 1;
response->buf = malloc(sizeof(datadef_t) + n*data->def->nchans*wordsize);
if (response->buf == nullptr) {
/* not enough space for copying data into response */
fprintf(stderr, "dmarequest: out of memory\n");
response->def->command = GET_ERR;
}
else {
/* number of bytes per sample (all channels) */
unsigned int chansize = data->def->nchans * wordsize;
/* convenience pointer to start of actual data in response */
char *resp_data = ((char *) response->buf) + sizeof(datadef_t);
/* this is the location of begsample within the ringbuffer */
unsigned int start_index = WRAP(datasel.begsample, current_max_num_sample);
/* have datadef point into the freshly allocated response buffer and directly
fill in the information */
datadef = (datadef_t *) response->buf;
datadef->nchans = data->def->nchans;
datadef->data_type = data->def->data_type;
datadef->nsamples = n;
datadef->bufsize = n*chansize;
response->def->bufsize = sizeof(datadef_t) + datadef->bufsize;
if (start_index + n <= current_max_num_sample) {
/* we can copy everything in one go */
memcpy(resp_data, (char*)(data->buf) + start_index*chansize, n*chansize);
} else {
/* need to wrap around at current_max_num_sample */
unsigned int na = current_max_num_sample - start_index;
unsigned int nb = n - na;
memcpy(resp_data, (char*)(data->buf) + start_index*chansize, na*chansize);
memcpy(resp_data + na*chansize, (char*)(data->buf), nb*chansize);
/* std::cout << "Wrapped around!\n"; */
}
}
}
}
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case GET_EVT:
if (verbose>1) fprintf(stderr, "dmarequest: GET_EVT\n");
if (header == nullptr || event == nullptr || header->def->nevents==0) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
break;
}
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexevent);
eventsel = (eventsel_t*)malloc(sizeof(eventsel_t));
DIE_BAD_MALLOC(eventsel);
/* determine the selection */
if (request->def->bufsize) {
/* the selection has been specified */
memcpy(eventsel, request->buf, sizeof(eventsel_t));
}
else {
/* determine a valid selection */
if (header->def->nevents>MAXNUMEVENT) {
/* the ringbuffer is completely full */
eventsel->begevent = header->def->nevents - MAXNUMEVENT;
eventsel->endevent = header->def->nevents - 1;
}
else {
/* the ringbuffer is not yet completely full */
eventsel->begevent = 0;
eventsel->endevent = header->def->nevents - 1;
}
}
if (verbose>1) print_headerdef(header->def);
if (verbose>1) print_eventsel(eventsel);
if (eventsel == nullptr) {
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if (eventsel->begevent < 0 || eventsel->endevent < 0) {
fprintf(stderr, "dmarequest: err1\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if (eventsel->begevent >= header->def->nevents || eventsel->endevent >= header->def->nevents) {
fprintf(stderr, "dmarequest: err2\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else if ((header->def->nevents-eventsel->begevent) > MAXNUMEVENT) {
fprintf(stderr, "dmarequest: err3\n");
response->def->version = VERSION;
response->def->command = GET_ERR;
response->def->bufsize = 0;
}
else {
unsigned int j,n;
response->def->version = VERSION;
response->def->command = GET_OK;
response->def->bufsize = 0;
/* determine the number of events to return */
n = eventsel->endevent - eventsel->begevent + 1;
for (j=0; j<n; ++j) {
if (verbose>1) print_eventdef(event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].def);
response->def->bufsize = append(&response->buf, response->def->bufsize, event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].def, sizeof(eventdef_t));
response->def->bufsize = append(&response->buf, response->def->bufsize, event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].buf, event[WRAP(eventsel->begevent+j, MAXNUMEVENT)].def->bufsize);
}
}
FREE(eventsel);
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexheader);
break;
case FLUSH_HDR:
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
pthread_mutex_lock(&mutexevent);
if (header) {
free_header();
free_data();
free_event();
response->def->version = VERSION;
response->def->command = FLUSH_OK;
response->def->bufsize = 0;
}
else {
response->def->version = VERSION;
response->def->command = FLUSH_ERR;
response->def->bufsize = 0;
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case FLUSH_DAT:
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexdata);
if (header && data) {
header->def->nsamples = thissample = 0;
response->def->version = VERSION;
response->def->command = FLUSH_OK;
response->def->bufsize = 0;
}
else {
response->def->version = VERSION;
response->def->command = FLUSH_ERR;
response->def->bufsize = 0;
}
pthread_mutex_unlock(&mutexdata);
pthread_mutex_unlock(&mutexheader);
break;
case FLUSH_EVT:
pthread_mutex_lock(&mutexheader);
pthread_mutex_lock(&mutexevent);
if (header && event) {
unsigned int i;
header->def->nevents = thisevent = 0;
for (i=0; i<MAXNUMEVENT; ++i) {
FREE(event[i].def);
FREE(event[i].buf);
}
response->def->version = VERSION;
response->def->command = FLUSH_OK;
response->def->bufsize = 0;
}
else {
response->def->version = VERSION;
response->def->command = FLUSH_ERR;
response->def->bufsize = 0;
}
pthread_mutex_unlock(&mutexevent);
pthread_mutex_unlock(&mutexheader);
break;
case WAIT_DAT:
/* SK: This request means that the client wants to wait until
MORE than waitdef_t.threshold.nsamples samples OR
MORE THAN waitdef_t.threshold.nevents events
are in the buffer, BUT
only for the time given in waitdef_t.milliseconds.
The response is just the number of samples and events
in the buffer as described by samples_events_t.
*/
response->def->version = VERSION;
if (header == nullptr || request->def->bufsize!=sizeof(waitdef_t)) {
response->def->command = WAIT_ERR;
response->def->bufsize = 0;
} else {
int waiterr;
waitdef_t *wd = (waitdef_t *) request->buf;
samples_events_t *nret = (samples_events_t*)malloc(sizeof(samples_events_t));
UINT32_T nsmp, nevt;
if (nret == nullptr) {
/* highly unlikely, but we cannot allocate a sample_event_t - return an error */
response->def->command = WAIT_ERR;
response->def->bufsize = 0;
break;
}
/* Let response->buf point to the new sample_event_t structure */
response->def->command = WAIT_OK;
response->def->bufsize = sizeof(samples_events_t);
response->buf = nret;
/* get current number of samples */
pthread_mutex_lock(&mutexheader);
nsmp = header->def->nsamples;
nevt = header->def->nevents;
pthread_mutex_unlock(&mutexheader);
if (wd->milliseconds == 0 || nsmp > wd->threshold.nsamples || nevt > wd->threshold.nevents) {
/* the client doesn't want to wait, or
we're already above the threshold:
return immediately */
nret->nsamples = nsmp;
nret->nevents = nevt;
break;
}
gettimeofday(&tp, nullptr);
ts.tv_sec = tp.tv_sec + (wd->milliseconds/1000);
ts.tv_nsec = 1000 * (tp.tv_usec + (wd->milliseconds % 1000)*1000);
while (ts.tv_nsec >= 1000000000) {
ts.tv_sec++;
ts.tv_nsec-=1000000000;
}
/* FIXME: The getData condition variable is only triggered by incoming data, not events */
do {
pthread_mutex_lock(&getData_mutex);
waiterr = pthread_cond_timedwait(&getData_cond, &getData_mutex, &ts);
pthread_mutex_unlock(&getData_mutex);
/* get current number of samples */
pthread_mutex_lock(&mutexheader);
nsmp = header->def->nsamples;
nevt = header->def->nevents;
pthread_mutex_unlock(&mutexheader);
} while (nsmp <= wd->threshold.nsamples && nevt <= wd->threshold.nevents && waiterr==0);
nret->nsamples = nsmp;
nret->nevents = nevt;
}
break;
default:
fprintf(stderr, "dmarequest: unknown command\n");
}
if (verbose>0) fprintf(stderr, "dmarequest: thissample = %u, thisevent = %u\n", thissample, thisevent);
/* everything went fine */
return 0;
}
@@ -0,0 +1,240 @@
/*
* Copyright (C) 2010, Stefan Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <buffer.h>
/* TODO: see if these can be optimized using compiler intrinsics etc. */
void ft_swap16(unsigned int numel, void *data) {
unsigned int n;
char *d = (char *) data;
for (n=0;n<numel;n++) {
char t = d[0];
d[0] = d[1];
d[1] = t;
d+=2;
}
}
void ft_swap32(unsigned int numel, void *data) {
unsigned int n;
char *d = (char *) data;
for (n=0;n<numel;n++) {
char t0 = d[0];
char t1 = d[1];
d[0] = d[3];
d[1] = d[2];
d[2] = t1;
d[3] = t0;
d+=4;
}
}
void ft_swap64(unsigned int numel, void *data) {
unsigned int n;
char *d = (char *) data;
for (n=0;n<numel;n++) {
char t0 = d[0];
char t1 = d[1];
char t2 = d[2];
char t3 = d[3];
d[0] = d[7];
d[1] = d[6];
d[2] = d[5];
d[3] = d[4];
d[4] = t3;
d[5] = t2;
d[6] = t1;
d[7] = t0;
d+=8;
}
}
void ft_swap_data(UINT32_T numel, UINT32_T datatype, void *data) {
switch(datatype) {
case DATATYPE_CHAR:
case DATATYPE_UINT8:
case DATATYPE_INT8:
return;
case DATATYPE_UINT16:
case DATATYPE_INT16:
ft_swap16(numel, data);
return;
case DATATYPE_UINT32:
case DATATYPE_INT32:
case DATATYPE_FLOAT32:
ft_swap32(numel, data);
return;
case DATATYPE_UINT64:
case DATATYPE_INT64:
case DATATYPE_FLOAT64:
ft_swap64(numel, data);
return;
}
}
int ft_swap_chunks_to_native(UINT32_T size, UINT32_T nchans, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(ft_chunkdef_t) <= size) {
ft_chunk_t *chunk = (ft_chunk_t *) ((char *) buf + offset);
ft_swap32(2, &(chunk->def));
offset += sizeof(ft_chunkdef_t) + chunk->def.size;
/* chunk definition fault (=too big) ? */
if (offset > size) return -1;
switch(chunk->def.type) {
case FT_CHUNK_RESOLUTIONS:
if (chunk->def.size >= nchans*sizeof(FLOAT64_T)) {
ft_swap64(nchans, chunk->data);
}
break;
/* Add other cases here as needed */
}
offset += sizeof(ft_chunkdef_t) + chunk->def.size;
}
return 0;
}
/* returns 0 on success, -1 on error */
int ft_swap_events_to_native(UINT32_T size, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(eventdef_t) <= size) {
unsigned int wst, wsv;
eventdef_t *edef = (eventdef_t *) ((char *) buf + offset);
ft_swap32(8, edef); /* all fields are 32-bit */
/* Increase offset to beginning of next event */
offset += sizeof(eventdef_t) + edef->bufsize;
if (offset > size) return -1; /* this event is too big for "buf" */
wst = wordsize_from_type(edef->type_type);
wsv = wordsize_from_type(edef->value_type);
/* check if type and value fit into this event's local buffer */
if (wst*edef->type_numel + wsv*edef->value_numel > edef->bufsize) return -1;
ft_swap_data(edef->type_numel, edef->type_type, (char *) buf + offset);
ft_swap_data(edef->value_numel, edef->value_type, (char *) buf + offset + wst*edef->type_numel);
}
return 0;
}
/* returns 0 on success, -1 on error */
int ft_swap_buf_to_native(UINT16_T command, UINT32_T bufsize, void *buf) {
datadef_t *ddef;
switch(command) {
case GET_HDR:
/* This should not have a buf attached */
return 0;
case GET_DAT:
/* buf contains a datsel_t = 2x UINT32_T */
if (bufsize == 8) ft_swap32(2, buf);
return 0;
case GET_EVT:
/* buf contains a datsel_t = 2x UINT32_T */
if (bufsize == 8) ft_swap32(2, buf);
return 0;
case WAIT_DAT:
/* buf contains a waitdef_t = 3x UINT32_T */
ft_swap32(3, buf);
return 0;
case PUT_DAT:
/* buf contains a datadef_t and after that the data */
ddef = (datadef_t *) buf;
ft_swap32(4, ddef); /* this is for datadef_t */
ft_swap_data(ddef->nchans*ddef->nsamples, ddef->data_type, ddef + 1); /* ddef+1 points to first data byte */
return 0;
case PUT_HDR:
/* buf contains a headerdef_t and optionally chunks */
ft_swap32(6, buf); /* all fields are 32-bit values */
return ft_swap_chunks_to_native(bufsize - sizeof(headerdef_t), ((headerdef_t *) buf)->nchans, (char *) buf + sizeof(headerdef_t));
case PUT_EVT:
/* buf contains multiple eventdef_t and buf's */
return ft_swap_events_to_native(bufsize, buf);
}
return -1;
}
int ft_swap_chunks_from_native(UINT32_T size, UINT32_T nchans, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(ft_chunkdef_t) <= size) {
ft_chunk_t *chunk = (ft_chunk_t *) ((char *) buf + offset);
offset += sizeof(ft_chunkdef_t) + chunk->def.size;
/* chunk definition fault (=too big) ? */
if (offset > size) return -1;
switch(chunk->def.type) {
case FT_CHUNK_RESOLUTIONS:
if (chunk->def.size >= nchans*sizeof(FLOAT64_T)) {
ft_swap64(nchans, chunk->data);
}
break;
/* Add other cases here as needed */
}
ft_swap32(2, &(chunk->def));
}
return 0;
}
int ft_swap_events_from_native(UINT32_T size, void *buf) {
UINT32_T offset = 0;
while (offset + sizeof(eventdef_t) <= size) {
unsigned int wst;
eventdef_t *edef = (eventdef_t *) ((char *) buf + offset);
offset += sizeof(eventdef_t) + edef->bufsize;
wst = wordsize_from_type(edef->type_type);
ft_swap_data(edef->type_numel, edef->type_type, (char *) buf + offset);
ft_swap_data(edef->value_numel, edef->value_type, (char *) buf + offset + wst*edef->type_numel);
ft_swap32(8, edef); /* all fields are 32-bit */
}
return 0;
}
int ft_swap_from_native(UINT16_T orgCommand, message_t *msg) {
datadef_t *ddef;
UINT32_T nchans;
UINT32_T bufsize = msg->def->bufsize;
ft_swap16(1, &msg->def->version);
ft_swap16(1, &msg->def->command);
ft_swap32(1, &msg->def->bufsize);
if (bufsize == 0) return 0;
switch(orgCommand) {
case GET_HDR:
nchans = ((headerdef_t *) msg->buf)->nchans;
ft_swap32(6, msg->buf); /* all fields are 32-bit values */
return ft_swap_chunks_from_native(bufsize - sizeof(headerdef_t), nchans, (char *) msg->buf + sizeof(headerdef_t));
case GET_DAT:
ddef = (datadef_t *) msg->buf;
ft_swap_data(ddef->nchans*ddef->nsamples, ddef->data_type, ddef + 1); /* ddef+1 points to first data byte */
ft_swap32(5, ddef); /* all fields are 32-bit */
return 0;
case GET_EVT:
return ft_swap_events_from_native(bufsize, msg->buf);
case WAIT_DAT:
ft_swap32(2, msg->buf); /* nsamples + nevents = 32bit */
return 0;
}
return -1;
}
@@ -0,0 +1,22 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include "buffer.h"
#include <pthread.h>
pthread_mutex_t mutexstatus = PTHREAD_MUTEX_INITIALIZER;
int tcpserverStatus = 0;
pthread_mutex_t mutexthreadcount = PTHREAD_MUTEX_INITIALIZER;
int threadcount = 0;
pthread_mutex_t mutexsocketcount = PTHREAD_MUTEX_INITIALIZER;
int socketcount = 0;
pthread_mutex_t mutexappendcount = PTHREAD_MUTEX_INITIALIZER;
int appendcount = 0;
@@ -0,0 +1,21 @@
#ifndef EXTERN_H
#define EXTERN_H
#include "buffer.h"
extern pthread_mutex_t mutexstatus;
extern int tcpserverStatus;
extern pthread_mutex_t mutexthreadcount;
extern int threadcount;
extern pthread_mutex_t mutexsocketcount;
extern int socketcount;
extern pthread_mutex_t mutexthreadcount;
extern int threadcount;
extern pthread_mutex_t mutexappendcount;
extern int appendcount;
#endif
@@ -0,0 +1,266 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
/* prevent double include */
#ifndef MESSAGE_H
#define MESSAGE_H
#include "platform_includes.h"
#ifdef __cplusplus
extern "C" {
#endif
/* FIXME these are obvious at the moment, but should be formally defined */
typedef char CHAR_T;
typedef float FLOAT32_T;
typedef double FLOAT64_T;
/* the following types should be according to "ISO C99: 7.18 Integer types" (see /usr/include/stdint.h on OSX and Linux) */
/* FIXME different endianness between client/server is not supported at the moment */
#ifndef INT8_T
typedef int8_t INT8_T;
#endif
#ifndef INT16_T
typedef int16_t INT16_T;
#endif
#ifndef INT32_T
typedef int INT32_T;
#endif
#ifndef INT64_T
typedef int64_t INT64_T;
#endif
#ifndef UINT8_T
typedef uint8_t UINT8_T;
#endif
#ifndef UINT16_T
typedef uint16_t UINT16_T;
#endif
#ifndef UINT32_T
typedef uint32_t UINT32_T;
#endif
#ifndef UINT64_T
typedef uint64_t UINT64_T;
#endif
/* these can be used for indexing the buffer pointer as array */
#define WORDSIZE_CHAR sizeof(CHAR_T )
#define WORDSIZE_UINT8 sizeof(UINT8_T )
#define WORDSIZE_UINT16 sizeof(UINT16_T )
#define WORDSIZE_UINT32 sizeof(UINT32_T )
#define WORDSIZE_UINT64 sizeof(UINT64_T )
#define WORDSIZE_INT8 sizeof(INT8_T )
#define WORDSIZE_INT16 sizeof(INT16_T )
#define WORDSIZE_INT32 sizeof(INT32_T )
#define WORDSIZE_INT64 sizeof(INT64_T )
#define WORDSIZE_FLOAT32 sizeof(FLOAT32_T)
#define WORDSIZE_FLOAT64 sizeof(FLOAT64_T)
/* define the version of the message packet */
#define VERSION (UINT16_T)0x0001
/* the same version number in the "other" endianness */
#define VERSION_OE (UINT16_T) (((VERSION & 0x00FF) << 8) | ((VERSION & 0xFF00) >> 8))
/* these define the commands that can be used, which are split over the two available bytes */
#define PUT_HDR (UINT16_T)0x0101
#define PUT_DAT (UINT16_T)0x0102
#define PUT_EVT (UINT16_T)0x0103
#define PUT_OK (UINT16_T)0x0104
#define PUT_ERR (UINT16_T)0x0105
#define GET_HDR (UINT16_T)0x0201
#define GET_DAT (UINT16_T)0x0202
#define GET_EVT (UINT16_T)0x0203
#define GET_OK (UINT16_T)0x0204
#define GET_ERR (UINT16_T)0x0205
#define FLUSH_HDR (UINT16_T)0x0301
#define FLUSH_DAT (UINT16_T)0x0302
#define FLUSH_EVT (UINT16_T)0x0303
#define FLUSH_OK (UINT16_T)0x0304
#define FLUSH_ERR (UINT16_T)0x0305
#define WAIT_DAT (UINT16_T)0x0402
#define WAIT_OK (UINT16_T)0x0404
#define WAIT_ERR (UINT16_T)0x0405
/* these are used in the data_t and event_t structure */
#define DATATYPE_CHAR (UINT32_T)0
#define DATATYPE_UINT8 (UINT32_T)1
#define DATATYPE_UINT16 (UINT32_T)2
#define DATATYPE_UINT32 (UINT32_T)3
#define DATATYPE_UINT64 (UINT32_T)4
#define DATATYPE_INT8 (UINT32_T)5
#define DATATYPE_INT16 (UINT32_T)6
#define DATATYPE_INT32 (UINT32_T)7
#define DATATYPE_INT64 (UINT32_T)8
#define DATATYPE_FLOAT32 (UINT32_T)9
#define DATATYPE_FLOAT64 (UINT32_T)10
/* this should never be used to put data into the buffer,
but is handy for handling conversions of other data types
*/
#define DATATYPE_UNKNOWN (UINT32_T)0xFFFFFFFF
/* these are used in the specification of the event selection criteria */
#define EVENTSEL_TYPE 1
#define EVENTSEL_VALUE 2
#define EVENTSEL_SAMPLE 3 /* for an exact match */
#define EVENTSEL_MINSAMPLE 4
#define EVENTSEL_MAXSAMPLE 5
/* if event->def->sample == EVENT_AUTO_SAMPLE, automatically insert
current sample index instead
*/
#define EVENT_AUTO_SAMPLE -1
/** The following enumeration is for specifying types of chunks that may be present
in the "buf" part of the Fieldtrip header.
*/
enum {
/** FT_CHUNK_UNSPECIFIED refers to a binary blob of known length, but unknown contents.
Clients encountering this can try to use auto-detection, or just ignore this chunk.
Unknown chunk types should be treated in the same manner. */
FT_CHUNK_UNSPECIFIED = 0,
/** FT_CHUNK_CHANNEL_NAMES contains the channel names in ASCII format. Each channel is
represented as a 0-terminated string (includes the case of just a 0 for an empty string).
Example: chunk_data = "Left\0Right\0" for stereo sound signals. */
FT_CHUNK_CHANNEL_NAMES = 1,
/** FT_CHUNK_CHANNEL_FLAGS contains a 0-terminated string describing the type of flags,
and after that N (=#channels) bytes describing each channel. This is useful for
specifying that a channel can have a discrete number of different types, e.g.
chunk_data = "meg_ad_eog\0\1\1\1\1\3\3\2\2" should be used for a system with 8 channels,
the first four of which are for MEG, then 2 channels EOG, then 2 channels A/D. */
FT_CHUNK_CHANNEL_FLAGS = 2,
/** FT_CHUNK_RESOLUTIONS contains N double precision values mapping from A/D values to physical
quantities such as micro-Volts in EEG. */
FT_CHUNK_RESOLUTIONS = 3,
/** FT_CHUNK_ASCII_KEYVAL contains an arbitrary number of key/value pairs, each of
which is given as a 0-terminated string. An empty key (=double 0) indicates the
end of the list. Example: "amplifier_gain\0high\0noise_reduction\0active\0\0". */
FT_CHUNK_ASCII_KEYVAL = 4,
/** FT_CHUNK_NIFTI1 contains a NIFTI-1 header (348 bytes long) */
FT_CHUNK_NIFTI1 = 5,
/** FT_CHUNK_SIEMENS_AP contains Siemens Protocol data in ASCII format (string) */
FT_CHUNK_SIEMENS_AP = 6,
/** FT_CHUNK_CTF_RES4 contains a .res4 file as written by the CTF MEG acquisition software (binary) */
FT_CHUNK_CTF_RES4 = 7
};
#pragma pack(push,1)
/* a packet that is sent over the network (or to disk) should contain the following */
typedef struct {
UINT16_T version; /* see VERSION */
UINT16_T command; /* see PUT_xxx, GET_xxx and FLUSH_xxx */
UINT32_T bufsize; /* size of the buffer in bytes */
} messagedef_t;
/* the header definition is fixed, except for the channel labels */
typedef struct {
UINT32_T nchans;
UINT32_T nsamples;
UINT32_T nevents;
FLOAT32_T fsample;
UINT32_T data_type;
UINT32_T bufsize; /* size of the buffer in bytes */
} headerdef_t;
/* the data definition is fixed */
typedef struct {
UINT32_T nchans;
UINT32_T nsamples;
UINT32_T data_type;
UINT32_T bufsize; /* size of the buffer in bytes */
} datadef_t;
/* the event definition is fixed */
typedef struct {
UINT32_T type_type; /* usual would be DATATYPE_CHAR */
UINT32_T type_numel; /* length of the type string */
UINT32_T value_type;
UINT32_T value_numel;
INT32_T sample;
INT32_T offset;
INT32_T duration;
UINT32_T bufsize; /* size of the buffer in bytes */
} eventdef_t;
typedef struct {
messagedef_t *def;
void *buf;
} message_t;
typedef struct {
headerdef_t *def;
void *buf; /* FIXME this should contain the channel names */
} header_t;
typedef struct {
datadef_t *def;
void *buf;
} data_t;
typedef struct {
eventdef_t *def;
void *buf;
} event_t;
typedef struct {
UINT32_T begsample; /* indexing starts with 0, should be >=0 */
UINT32_T endsample; /* indexing starts with 0, should be <header.nsamples */
} datasel_t;
typedef struct {
UINT32_T begevent;
UINT32_T endevent;
} eventsel_t;
typedef struct {
UINT32_T nsamples;
UINT32_T nevents;
} samples_events_t;
typedef struct {
samples_events_t threshold;
UINT32_T milliseconds;
} waitdef_t;
typedef struct {
UINT32_T type; /* One of FT_CHUNK_** (see above) */
UINT32_T size; /* Size of chunk.data, total size is given by adding sizeof(ft_chunkdef_t)=8 */
} ft_chunkdef_t;
typedef struct {
ft_chunkdef_t def; /* See above. Note that this is not a pointer! */
char data[1]; /* Data contained in this chunk */
} ft_chunk_t;
#pragma pack(pop)
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,75 @@
/* prevent double include */
#pragma once
#if defined(linux) || defined(__linux) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__)
/* linux, also other platforms (Hurd etc) that use GLIBC */
#define PLATFORM_LINUX
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
/* BSD */
#define PLATFORM_BSD
#elif defined(sun) || defined(__sun)
/* Solaris */
#define PLATFORM_SUN
#elif defined(__sgi)
/* SGI Irix */
#define PLATFORM_SGI
#elif defined(__hpux)
/* hp unix */
#define PLATFORM_HP
#elif defined(__CYGWIN__)
/* cygwin is not win32 */
#define PLATFORM_CYGWIN
#elif defined(_WIN64) || defined(__WIN64__) || defined(WIN64)
/* win64 */
#define PLATFORM_WIN64
#define PLATFORM_WINDOWS
#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
/* win32 */
#define PLATFORM_WIN32
#define PLATFORM_WINDOWS
#elif defined(__BEOS__)
/* BeOS */
#define PLATFORM_BEOS
#elif defined (__APPLE__) && defined (__MACH__)
/* MacOSX */
#define PLATFORM_OSX
#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)
/* MacOS classic */
#define PLATFORM_MAC
#elif defined(__IBMCPP__) || defined(_AIX)
/* IBM */
#define PLATFORM_AIX
#elif defined(__amigaos__)
/* AmigaOS */
#define PLATFORM_AMIGA
#elif defined(__QNXNTO__)
/* QNX */
#define PLATFORM_QNX
#elif defined(__VXWORKS__)
/* vxWorks */
#define PLATFORM_VXWORKS
#elif defined(unix) || defined(__unix) || defined(_XOPEN_SOURCE) || defined(_POSIX_SOURCE)
/* generic unix platform */
#define PLATFORM_UNIX
#else
/* the platform cannot be determined at compile time */
#error "Unknown platform - please report the platform details to http://fieldtrip.fcdonders.nl"
#endif
@@ -0,0 +1,149 @@
#include "platform.h"
#include "compiler.h"
#if defined (COMPILER_BORLAND)
#endif
/* these platforms will always use a similar gcc compiler */
#if defined (PLATFORM_LINUX) || defined (PLATFORM_OSX)
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <strings.h>
#include <stdint.h>
#define closesocket(s) (close(s))
#elif defined (PLATFORM_WIN64)
#if defined (COMPILER_MSVC)
#include <winsock2.h> /* for timeval */
#include "win32/gettimeofday.h"
#include "win32/stdint.h"
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_MINGW)
#include <winsock2.h>
#include <sys/time.h>
#include <stdint.h>
#define bzero(b,len) memset(b,0,len)
#define usleep(x) (Sleep((x)/1000))
#ifndef strcasecmp
#define strcasecmp(a,b) (strcmpi(a,b))
#endif
#else
#error "Unsupported compiler"
#endif
#elif defined (PLATFORM_WIN32)
/* there are various compiler options for windows */
#if defined (COMPILER_BORLAND)
#include <windows.h>
#include "win32/gettimeofday.h"
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
/* without the following, compilation with the Borland command line tools fails -- SK */
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#elif defined (COMPILER_MSVC)
#include <winsock2.h> /* for timeval */
#if (_MSC_VER >= 1600 )
#include <stdint.h>
#else
typedef __int8 int8_t;
typedef __int16 int16_t;
typedef __int32 int;
typedef __int64 int64_t;
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
typedef unsigned __int32 uint32_t;
typedef unsigned __int64 uint64_t;
#endif
#include "win32/gettimeofday.h"
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_MINGW)
#include <winsock2.h>
#include <sys/time.h>
#include <stdint.h>
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_CYGWIN)
#include <winsock2.h>
#define strcasecmp(a,b) (strcmpi(a,b))
#elif defined (COMPILER_LCC)
#include <winsock2.h>
#include <windows.h>
#include "win32/gettimeofday.h"
#define strcasecmp(a,b) (strcmpi(a,b))
#define bzero(b,len) (memset((b), '\0', (len)), (void) 0)
#define usleep(x) (Sleep((x)/1000))
#ifndef UINT8_T
#define UINT8_T unsigned char
#endif
#ifndef INT8_T
#define INT8_T char
#endif
#ifndef UINT16_T
#define UINT16_T unsigned short
#endif
#ifndef INT16_T
#define INT16_T short
#endif
#ifndef UINT32_T
#define UINT32_T unsigned int
#endif
#ifndef INT32_T
#define INT32_T int
#endif
#ifndef UINT64_T
#define UINT64_T unsigned long long
#endif
#ifndef INT64_T
#define INT64_T long long
#endif
/* #define PTW32_STATIC_LIB
#define __cdecl
#define PTW_CDECL
*/
#endif /* compiler */
#endif /* platform */
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
void print_request(messagedef_t *request) {
fprintf(stderr, "request.version = 0x%04x\n", request->version);
fprintf(stderr, "request.command = 0x%04x\n", request->command);
fprintf(stderr, "request.bufsize = %u\n", request->bufsize);
}
void print_response(messagedef_t *response) {
fprintf(stderr, "response.version = 0x%04x\n", response->version);
fprintf(stderr, "response.command = 0x%04x\n", response->command);
fprintf(stderr, "response.bufsize = %u\n", response->bufsize);
}
void print_headerdef(headerdef_t *headerdef) {
if (headerdef == nullptr)
fprintf(stderr, "headerdef == nullptr\n");
else {
fprintf(stderr, "headerdef.nchans = %u\n", headerdef->nchans);
fprintf(stderr, "headerdef.nsamples = %u\n", headerdef->nsamples);
fprintf(stderr, "headerdef.nevents = %u\n", headerdef->nevents);
fprintf(stderr, "headerdef.fsample = %f\n", headerdef->fsample);
fprintf(stderr, "headerdef.data_type = %u\n", headerdef->data_type);
fprintf(stderr, "headerdef.bufsize = %u\n", headerdef->bufsize);
}
}
void print_datadef(datadef_t *datadef) {
if (datadef == nullptr)
fprintf(stderr, "datadef == nullptr\n");
else {
fprintf(stderr, "datadef.nchans = %u\n", datadef->nchans);
fprintf(stderr, "datadef.nsamples = %u\n", datadef->nsamples);
fprintf(stderr, "datadef.data_type = %u\n", datadef->data_type);
fprintf(stderr, "datadef.bufsize = %u\n", datadef->bufsize);
}
}
void print_eventdef(eventdef_t *eventdef) {
if (eventdef == nullptr)
fprintf(stderr, "eventdef == nullptr\n");
else {
fprintf(stderr, "eventdef.type_type = %u\n", eventdef->type_type );
fprintf(stderr, "eventdef.type_numel = %u\n", eventdef->type_numel );
fprintf(stderr, "eventdef.value_type = %u\n", eventdef->value_type );
fprintf(stderr, "eventdef.value_numel = %u\n", eventdef->value_numel );
fprintf(stderr, "eventdef.sample = %d\n", eventdef->sample );
fprintf(stderr, "eventdef.offset = %d\n", eventdef->offset );
fprintf(stderr, "eventdef.duration = %d\n", eventdef->duration );
fprintf(stderr, "eventdef.bufsize = %u\n", eventdef->bufsize );
}
}
void print_datasel(datasel_t *datasel) {
fprintf(stderr, "datasel.begsample = %u\n", datasel->begsample);
fprintf(stderr, "datasel.endsample = %u\n", datasel->endsample);
}
void print_eventsel(eventsel_t *eventsel) {
fprintf(stderr, "eventsel.begevent = %u\n", eventsel->begevent);
fprintf(stderr, "eventsel.endevent = %u\n", eventsel->endevent);
}
void print_buf(void *buf, int bufsize) {
int i;
fprintf(stderr, "buf =");
if (buf == nullptr)
fprintf(stderr, " NULL");
else
for (i=0; i<bufsize; ++i)
fprintf(stderr, " %02x", ((unsigned char*)buf)[i]);
fprintf(stderr, "\n");
}
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2010 S. Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*
*/
#ifndef __rdadefs_h
#define __rdadefs_h
#include "message.h" /* for the integer datytypes */
#ifdef __cplusplus
extern "C" {
#endif
/** Message types as sent to RDA clients */
#define RDA_START_MSG 1
#define RDA_INT_MSG 2
#define RDA_STOP_MSG 3
#define RDA_FLOAT_MSG 4
#pragma pack(push,1)
/** Structure of the first 24 bytes of all RDA messages */
typedef struct {
UINT8_T guid[16];
UINT32_T nSize; /* Size of the message block in bytes including this header */
UINT32_T nType; /* 1:start 2:int16_t 3:stop 4:float */
} rda_msg_hdr_t;
/** Describes the structure of RDA data messages (fixed part only) */
typedef struct {
rda_msg_hdr_t hdr;
UINT32_T nBlock; /* Block number, i.e. acquired blocks since acquisition started. */
UINT32_T nPoints; /* Number of data points (samples) in this block */
UINT32_T nMarkers; /* Number of markers in this block */
/* after this, you get the data, and then an array of markers */
} rda_msg_data_t;
/** Describes the structure of an RDA start message (fixed part only) */
typedef struct {
rda_msg_hdr_t hdr;
UINT32_T nChannels; /* Number of channels */
double dSamplingInterval; /* Sampling interval in microseconds */
/* after this, you have double dResolutions[] and
channels names come after this as 0-terminated strings */
} rda_msg_start_t;
/* TODO: we never send a STOP packet, because it's hard to tell when the buffer will
stop receiving data. We should maybe try to detect if a new header is put into the
buffer, and send a STOP and a START in that case.
*/
/** Describes the structure of an RDA marker (fixed part only) */
typedef struct {
UINT32_T nSize; /* Size of this marker */
UINT32_T nPosition; /* Relative position in the data block */
UINT32_T nPoints; /* Number of points of this marker */
INT32_T nChannel; /* Associated channel number (-1 = all channels) */
/* char sTypeDesc[1]; Type description in ASCII delimited by '\0', variable length actually */
} rda_marker_t;
#pragma pack(pop)
#ifdef __cplusplus
}
#endif
#endif /* __rdadefs_h */
@@ -0,0 +1,119 @@
/*
* Copyright (C) 2010 S. Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*
*/
#ifndef __rdaserver_h
#define __rdaserver_h
#include <pthread.h>
#include "buffer.h"
#include "rdadefs.h"
#ifdef __cplusplus
extern "C" {
#endif
/* On Windows, sockets are not described by a plain int, but by the type SOCKET, which has
the size of a pointer. On WIN64, sizeof(SOCKET) != sizeof(int), although some people still
argue that it is safe to cast between those. To be sure, the RDA server implementation
always uses SOCKET as the base type, and defines this as an 'int' on POSIX systems.
*/
#ifndef WIN32
typedef int SOCKET;
#define INVALID_SOCKET -1
#endif
/** Error values as returned by rda_start_server */
#define FT_NO_ERROR 0
#define FT_ERR_OUT_OF_MEM 1
#define FT_ERR_SOCKET 2
#define FT_ERR_THREADING 3
/** 'select' cannot handle more than 64 elements on Windows, but this
should really be enough for all practical purposes. Depending on
the sampling rate and number of channels, you would probably hit
other performance boundaries first. Since the server socket itself
also needs listening to (taking 1 away from the available 64),
NEVER set the following number to more than 63!!!
*/
#define RDA_MAX_NUM_CLIENTS 32
/** Number of blocks any client can lag behind before being disconnected */
#define RDA_MAX_LAG 5
/** RDA server control structure for starting, inspecting, and stopping a server */
typedef struct {
pthread_t thread; ///< Thread handle
pthread_mutex_t mutex; ///< Mutex for protecting num_clients (actually not really necessary)
SOCKET server_socket; ///< The server socket that clients connect to
int ft_buffer; ///< Connection to FieldTrip buffer (socket or 0 for dmarequests)
volatile int num_clients; ///< Current number of clients
volatile int should_exit; ///< Flag to notify the server thread that it should stop
volatile int is_running; ///< Flag that indicates whether the thread is still running
int blocksize; ///< Block size for streaming out samples, 0 => adapt to incoming data
int use16bit; ///< Flag that indicates whether 16 bit data should be streamed
int verbosity; ///< Option that determines how much status information is printed during operation
} rda_server_ctrl_t;
/** Internally used data structure to keep a linked list of
data packets that need to be sent out */
typedef struct rda_buffer_item {
void *data; ///< Points to complete RDA packet
size_t size; ///< Size of the packet (=allocated memory block)
int blockNumber; ///< Number of this data block (or -1 for start packet)
unsigned int refCount; ///< Reference count (multiple clients get the same data)
struct rda_buffer_item *next; ///< Next item in list or NULL
} rda_buffer_item_t;
/** Internally used data structure to describe a client and its pending jobs */
typedef struct {
SOCKET sock; ///< Client socket
rda_buffer_item_t *item; ///< Points to the current/next packet to be written
size_t written; ///< Number of bytes that have been written (from item->data)
} rda_client_job_t;
/** Helper function for converting any FieldTrip data type to single precision floats
@param N number of values to convert
@param dest destination, must point to an array of at least N floats
@param data_type data type as described by FieldTrip DATATYPE_** constants
@param src source buffer
*/
void rda_aux_convert_to_float(UINT32_T N, void *dest, UINT32_T data_type, const void *src);
/** Starts an RDA server with a given FieldTrip connection (usually 0 for DMA), serving
either single precision or 16 bit integer data.
@param ft_buffer FieldTrip connection (0 for DMA, or socket for TCP connection)
@param use16bit pass 0 to serve single precision data (with conversion as necessary)
pass non-zero to serve 16 bit integers (only works if the FieldTrip buffer
also contains 16 bit data)
@param port Port number to bind to, or 0 for default port (51244 for 16 bit, 51344 for single precision)
@param blocksize Block size for streaming out samples (0=send out variable blocks depending on incoming data)
@param errval Optional pointer to an integer error value. Will contain either
FT_NO_ERROR, FT_ERR_SOCKET, FT_OUT_OF_MEM or FT_THREADING on exit.
@return Pointer to RDA server control structure, or NULL if an error occurred
*/
rda_server_ctrl_t *rda_start_server(int ft_buffer, int use16bit, int port, int blocksize, int *errval);
/** Stops an RDA server by closing the associated connections, stopping the background thread, and
deallocating its memory (including the control structure pointed to by the argument)
@param SC Must point to an RDA server control structure as created by rda_start_server
@return -1 if SC is NULL
0 on success
*/
int rda_stop_server(rda_server_ctrl_t *SC);
/** Simple helper function for retrieving the current number of clients of an RDA server
@param SC Must point to an RDA server control structure as created by rda_start_server
@return The number of clients
*/
int rda_get_num_clients(rda_server_ctrl_t *SC);
#ifdef __cplusplus
}
#endif
#endif /* __rdaserver_h */
@@ -0,0 +1,474 @@
/*
* Copyright (C) 2010, Stefan Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <socketserver.h>
/************************************************************************
* This function deals with the incoming client requests in a loop until
* the user requests to stop the server, or until the remote side closes
* the connection. The implementation follows the idea of a state machine
* with the four different states:
* state = 0 means we are waiting for a request to come in, or we are
* in the process of reading the first 8 bytes (the "def" part)
* state = 1 means we are in the process of reading the remainder of
* the request (the "buf" part")
* state = 2 means we are in the process of writing the response (def)
* state = 3 means ... writing the 2nd. part of the response ("buf")
*
* On top of those 4 states, we maintain two variables "bytesDone" and
* "bytesTotal" that determine how many bytes we've read/written within
* the current state, and how many bytes we need to process in total,
* and a variable "curPtr" which points to the memory region we currently
* need to read into, or write from. Whether any action is actually taken
* inside the while loop also depends on the state of the socket which
* we find out using "select" (and then set "canRead" + "canWrite" flags).
*
* Depending on the nature of the request, we might skip states 1 and 3.
* This is the case if there is no "buf" attached to the message, or if
* an outgoing message can be merged in to a single packet.
*
* The actual processing of the message happens before moving to state 2
* and consists of
* 1) possibly swapping the message to native endianness
* 2) calling dmarequest or the user-supplied callback function
* 3) possibly swapping back to remote endianness
************************************************************************/
void *_buffer_socket_func(void *arg) {
SOCKET sock;
ft_buffer_server_t *SC;
int mergePackets;
messagedef_t reqdef;
message_t request;
message_t *response = nullptr;
int state=0; /* 0 = reading def, 1=reading buf, 2=writing def, 3=writing buf */
int bytesDone, bytesTotal;
char mergeBuffer[MERGE_THRESHOLD];
char *curPtr; /* points at buffer that needs to be filled or written out */
int swap = 0;
int canRead, canWrite;
UINT16_T reqCommand;
UINT32_T respBufSize;
fd_set readSet, writeSet;
if (arg == nullptr) return NULL;
/* copy over necessary variables and free the given structure */
SC = ((ft_buffer_socket_t *) arg)->server;
sock = ((ft_buffer_socket_t *) arg)->clientSocket;
mergePackets = ((ft_buffer_socket_t *) arg)->mergePackets;
free(arg);
if (SC->verbosity > 0) {
printf("Started new client thread with packet merging = %i\n", mergePackets);
}
pthread_mutex_lock(&SC->lock);
SC->numClients++;
pthread_mutex_unlock(&SC->lock);
request.def = &reqdef;
request.buf = NULL;
bytesDone = 0;
bytesTotal = sizeof(messagedef_t);
curPtr = (char *) request.def;
while (SC->keepRunning) {
int sel, res, n;
struct timeval tv = {0, 10000}; /* 10ms */
FD_ZERO(&readSet);
FD_ZERO(&writeSet);
if (state < 2) {
FD_SET(sock, &readSet);
} else {
FD_SET(sock, &writeSet);
}
sel = select((int) sock+1, &readSet, &writeSet, nullptr, &tv);
if (sel == 0) continue;
if (sel < 0) {
fprintf(stderr, "Error in 'select' operation - closing client connection.\n");
break;
}
canRead = FD_ISSET(sock, &readSet);
canWrite = FD_ISSET(sock, &writeSet);
if (canRead) {
n = recv(sock, curPtr + bytesDone, bytesTotal - bytesDone, 0);
if (n<=0) {
/* socket was closed */
if (SC->verbosity>0) {
std::cout << "Remote side closed client connection\n";
}
break;
}
bytesDone+=n;
if (bytesDone<bytesTotal) continue;
if (state == 0) {
/* we've read the request.def completely */
if (reqdef.version==VERSION_OE) {
swap = 1;
ft_swap16(2, &reqdef.version); /* version + command */
ft_swap32(1, &reqdef.bufsize);
reqCommand = reqdef.command;
}
if (reqdef.version!=VERSION) {
fprintf(stderr,"Incorrect version requested - closing socket.\n");
break;
}
if (reqdef.bufsize > 0) {
request.buf = malloc(reqdef.bufsize);
if (request.buf == nullptr) {
fprintf(stderr, "Out of memory\n");
break;
}
curPtr = request.buf;
bytesDone = 0;
bytesTotal = reqdef.bufsize;
state = 1;
continue;
}
} else {
/* Reaching this point means that the state=1, and that we've
read request.buf completely, so swap the endianness if
necessary, and then move on to handling the request.
*/
if (swap) ft_swap_buf_to_native(reqCommand, reqdef.bufsize, request.buf);
}
/* Request has been read completely, now deal with it */
if (SC->callback != nullptr) {
/* User supplied a callback function in ft_start_buffer_server */
res = SC->callback(&request, &response, SC->user_data);
if (res != 0 || response == nullptr || response->def == nullptr) {
fprintf(stderr, "buffer_socket_func: an unexpected error occurred in user-defined request handler\n");
break;
}
} else {
/* No callback, use normal dmarequest */
res = dmarequest(&request, &response);
if (res != 0 || response == nullptr || response->def == nullptr) {
fprintf(stderr, "buffer_socket_func: an unexpected error occurred in dmarequest\n");
break;
}
}
/* Ok, the request has been handled, results are in response.
We can free the memory pointed to by request.buf ...
*/
if (request.buf != nullptr) {
free(request.buf);
request.buf = NULL;
}
/* ... swap the response to the remote endianness, if necessary ... */
respBufSize = response->def->bufsize;
if (swap) ft_swap_from_native(reqCommand, response);
/* ... and then start writing back the response. To reduce latency,
we try to merge response->def and response->buf if they are small,
so we can send it in one go over TCP. To fit the merged packet into
our state machine logic, we apply a trick and jump to state=3 directly,
where "curPtr" points to the merged packet.
Otherwise, we move to state=2, transmit response->def, move to state=3,
and there transmit response->buf.
*/
if (mergePackets && respBufSize > 0 && respBufSize + sizeof(messagedef_t) <= MERGE_THRESHOLD) {
memcpy(mergeBuffer, response->def, sizeof(messagedef_t));
memcpy(mergeBuffer + sizeof(messagedef_t), response->buf, respBufSize);
curPtr = mergeBuffer;
bytesDone = 0;
bytesTotal = respBufSize + sizeof(messagedef_t);
state = 3;
} else {
curPtr = (char *) response->def;
bytesDone = 0;
bytesTotal = sizeof(messagedef_t);
state = 2;
}
canWrite = 1;
}
if (state >= 2 && canWrite) {
n = send(sock, curPtr + bytesDone, bytesTotal - bytesDone, 0);
if (n<=0) {
/* socket was closed */
fprintf(stderr, "Cannot write to socket -- closing client connection.\n");
break;
}
bytesDone+=n;
if (bytesDone < bytesTotal) continue;
if (state==2 && respBufSize > 0) {
curPtr = (char *) response->buf;
bytesDone = 0;
bytesTotal = respBufSize;
state = 3;
continue;
}
/* Reaching this point means we are done with writing out the response,
so we will now free the allocated memory, and reset to state=0.
*/
if (response->buf) free(response->buf);
free(response->def);
free(response);
response = NULL;
state = 0;
curPtr = (char *) request.def;
bytesDone = 0;
bytesTotal = sizeof(messagedef_t);
}
}
pthread_mutex_lock(&SC->lock);
SC->numClients--;
pthread_mutex_unlock(&SC->lock);
closesocket(sock);
if (request.buf != nullptr) free(request.buf);
if (response != nullptr) {
if (response->buf != nullptr) free(response->buf);
if (response->def != nullptr) free(response->def);
free(response);
}
return NULL;
}
/***********************************************************************
* this thread listens to incoming TCP/UNIX domain socket connections
* if a connection is made by a client, it starts _buffer_socket_func
***********************************************************************/
void *_buffer_server_func(void *arg) {
ft_buffer_server_t *SC = (ft_buffer_server_t *) arg;
int n;
if (SC == nullptr) {
fprintf(stderr, "FieldTrip buffer server thread started with invalid argument\n");
return NULL;
}
while (SC->keepRunning) {
SOCKET c;
fd_set readSet;
int sel, rc, merge;
pthread_t tid;
ft_buffer_socket_t *CC;
struct timeval tv = {0,10000}; /* 10 ms for select timeout */
FD_ZERO(&readSet);
FD_SET(SC->serverSocket, &readSet);
sel = select((int) SC->serverSocket + 1, &readSet, nullptr, nullptr, &tv);
if (sel == 0) continue;
/* The following code portion looks weird because of the preprocessor
defines splitting an if/else clause, but it's just what we want:
On Windows, there is no if/else clause, and the second (TCP) bit
is always called. On POSIX systems, we branch depending on whether
the server runs a local domain socket or TCP socket.
*/
#ifndef WIN32
if (SC->isUnixDomain) {
struct sockaddr_un sa;
socklen_t size_sa = sizeof(sa);
c = accept(SC->serverSocket, (struct sockaddr *)&sa, &size_sa);
if (c == INVALID_SOCKET) {
perror("buffer_server, accept");
continue;
}
/* never merge packets for (local) UNIX sockets */
merge = 0;
} else
#endif
{
struct sockaddr_in sa;
socklen_t size_sa = sizeof(sa);
c = accept(SC->serverSocket, (struct sockaddr *)&sa, &size_sa);
if (c == INVALID_SOCKET) {
perror("buffer_server, accept");
continue;
}
/* enable packet merging only if it's not localhost */
merge = (sa.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) ? 0 : 1;
}
CC = (ft_buffer_socket_t *) malloc(sizeof(ft_buffer_socket_t));
if (CC == nullptr) {
fprintf(stderr, "Out of memory\n");
closesocket(c);
continue;
}
CC->server = SC;
CC->clientSocket = c;
CC->mergePackets = merge;
rc = pthread_create(&tid, nullptr, _buffer_socket_func, CC);
if (rc) {
fprintf(stderr, "tcpserver: return code from pthread_create() is %d\n", rc);
closesocket(c);
free(CC);
}
}
while ((n=SC->numClients)>0) {
printf("Waiting for %i remaining client threads to stop...\n", n);
usleep(10000);
}
pthread_mutex_lock(&SC->lock);
SC->numClients--;
pthread_mutex_unlock(&SC->lock);
return NULL;
}
ft_buffer_server_t *ft_start_buffer_server(int port, const char *name, ft_request_callback_t callback, void *user_data) {
ft_buffer_server_t *SC;
int optval;
SOCKET s = INVALID_SOCKET;
SC = (ft_buffer_server_t *) malloc(sizeof(ft_buffer_server_t));
if (SC == nullptr) return NULL;
SC->callback = callback;
SC->user_data = user_data;
#ifdef WIN32
{
/* We only need to do this once ... and actually have a corresponding WSACleanup call somewhere */
static WSADATA wsa = {0,0};
if (wsa.wVersion == 0) {
if(WSAStartup(MAKEWORD(1, 1), &wsa)) {
fprintf(stderr, "ft_start_buffer_server: cannot start WIN32 sockets.\n");
goto cleanup;
}
}
}
#endif
/* setup socket */
if (port == 0) {
#ifdef WIN32
fprintf(stderr, "ft_start_buffer_server: invalid port number given.\n");
goto cleanup;
#else
struct sockaddr_un sa;
/* UNIX domain socket */
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
perror("ft_start_buffer_server, socket");
goto cleanup;
}
sa.sun_family = AF_UNIX;
strncpy(sa.sun_path, name, sizeof(sa.sun_path));
if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
perror("ft_start_buffer_server, bind");
goto cleanup;
}
SC->isUnixDomain = 0;
#endif
} else {
/* TCP socket */
struct sockaddr_in sa;
s = socket(PF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET) {
perror("ft_start_buffer_server, socket");
goto cleanup;
}
/* prevend "bind: address already in use" */
optval = 1;
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char*)&optval, sizeof(optval)) < 0) {
perror("ft_start_buffer_server, setsockopt");
goto cleanup;
}
bzero(&sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(s, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
perror("ft_start_buffer_server, bind");
goto cleanup;
}
SC->isUnixDomain = 0;
}
/* place the socket in non-blocking mode, required to do thread cancelation */
#ifdef WIN32
{
unsigned long enable = 0;
ioctlsocket(s, FIONBIO, &enable);
}
#else
optval = fcntl(s, F_GETFL, nullptr);
optval = optval | O_NONBLOCK;
if (fcntl(s, F_SETFL, optval)<0) {
perror("ft_start_buffer_server, fcntl");
goto cleanup;
}
#endif
if (listen(s, BACKLOG)<0) {
perror("ft_start_buffer_server, listen");
goto cleanup;
}
/* set some control variables */
SC->numClients = 0;
SC->keepRunning = 1;
SC->serverSocket = s;
SC->verbosity = 10; /* TODO: specify proper values */
/* create the mutex */
if (pthread_mutex_init(&SC->lock, nullptr) != 0) {
fprintf(stderr,"start_tcp_server: mutex could not be initialised\n");
goto cleanup;
}
/* create thread with default attributes */
if (pthread_create(&SC->threadID, nullptr, _buffer_server_func, SC) == 0) {
/* everything went fine - thread should be running now */
return SC;
}
fprintf(stderr,"start_tcp_server: could not spawn thread\n");
pthread_mutex_destroy(&SC->lock);
cleanup:
if (SC != nullptr) free(SC);
if (s != INVALID_SOCKET) {
#ifdef WIN32
shutdown(s, SD_BOTH);
#else
shutdown(s, SHUT_RDWR);
#endif
closesocket(s);
}
return NULL;
}
void ft_stop_buffer_server(ft_buffer_server_t *S) {
if (S == nullptr) return;
S->keepRunning = 0;
pthread_join(S->threadID, nullptr);
pthread_detach(S->threadID);
free(S);
}
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2010, Stefan Klanke
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*/
#ifndef __socketserver_h
#define __socketserver_h
#include "buffer.h"
#include <pthread.h>
#define MERGE_THRESHOLD 4096 /* TODO: optimize this value? Maybe look at MTU size */
#ifdef __cplusplus
extern "C" {
#endif
#ifndef WIN32
#define INVALID_SOCKET -1
typedef int SOCKET;
#include <sys/un.h>
#else
typedef int socklen_t;
#endif
typedef int (*ft_request_callback_t)(const message_t *request, message_t **response, void *user_data);
/** The following structure is used for managing a server. The structure is
allocated and filled in ft_start_buffer_server and then passed on to the
actual server thread, as well as to all threads handling the client
connections. Thread functions are written such that they monitor the
"keepRunning" member of this structure, and once this is set to 0, the
threads stop and exit.
*/
typedef struct {
SOCKET serverSocket; ///< Socket the server listens on (TCP or UNIX domain)
int keepRunning; ///< Flag=1: thread functions keep looping, 0: threads exit
int numClients; ///< Current number of clients connected to the server
int verbosity; ///< Determines how much information is being printed during operation
int isUnixDomain; ///< 1: UNIX domain socket, 0: TCP socket
pthread_t threadID; ///< POSIX thread identifier of the server thread, client threads are detached immediately
pthread_mutex_t lock; ///< Mutex to protect the "numClients" member, commonly used by all threads
ft_request_callback_t callback; ///< Callback function to be called *instead* of dmarequest
void *user_data; ///< Pointer to user-defined data structure, passed on to callback
} ft_buffer_server_t;
/** Small helper structure that is passed to client threads. Get's allocated
using malloc() in the server thread, and disposed using free() in the client
thread.
*/
typedef struct {
ft_buffer_server_t *server; ///< Pointer to the common control structure
SOCKET clientSocket; ///< The newly created socket (from "accept")
int mergePackets; ///< 1: merge packets if total size below threshold, 0: never merge (=>local host)
} ft_buffer_socket_t;
/** Creates a server socket, binds it to the specified UNIX domain name or port,
starts listening on this socket, and spawns a background thread to serve
requests on this socket.
TCP sockets are created for positive port numbers (name is ignored),
UNIX domain sockets are created for port=0 (name needs to be a UNIX pathname).
If callback != nullptr, that function is called like
callback(request, &response, user_data)
for every request coming in over the socket, *instead* of the normal dmarequest.
Usually the user will call the latter function internally. Make sure your
callback is re-entrant!!!
Returns allocated control structure, or NULL in case of errors.
*/
ft_buffer_server_t *ft_start_buffer_server(int port, const char *name, ft_request_callback_t callback, void *user_data);
/** Stops background thread(s), closes the sockets, and disposes the control structure S.
S cannot be used anymore after this call.
*/
void ft_stop_buffer_server(ft_buffer_server_t *S);
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,104 @@
/*
* Copyright (C) 2008, Robert Oostenveld
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include "buffer.h"
#define MERGE_THRESHOLD 4096 /* TODO: optimize this value? Maybe look at MTU size */
/*******************************************************************************
* communicate with the buffer through TCP
*******************************************************************************/
int tcprequest(int server, const message_t *request, message_t **response_ptr) {
unsigned int n, total;
/* this will hold the response */
message_t *response;
response = (message_t*)malloc(sizeof(message_t));
response->def = (messagedef_t*)malloc(sizeof(messagedef_t));
response->buf = NULL;
/* the response should be passed to the calling function, where it should be freed */
*response_ptr = response;
total = sizeof(messagedef_t) + request->def->bufsize;
/* Check whether request->def and request->buf are already contiguous in memory,
or whether request->buf is empty. If that's the case, we can write the request in one go.
*/
if (request->def->bufsize == 0 || (request->def+1) == (messagedef_t *) request->buf) {
if ((n = bufwrite(server, request->def, total)) != total) {
fprintf(stderr, "write size = %d, should be %d\n", n, total);
goto cleanup;
}
}
/* Now check whether the total size is below the merge threshold, in which case
we'll copy it to contiguous memory and again send it in one go
*/
else if (total <= MERGE_THRESHOLD) {
char merged[MERGE_THRESHOLD];
memcpy(merged, request->def, sizeof(messagedef_t));
memcpy(merged + sizeof(messagedef_t), request->buf, request->def->bufsize);
if ((n = bufwrite(server, merged, total)) != total) {
fprintf(stderr, "write size = %d, should be %d\n", n, total);
goto cleanup;
}
}
/* Otherwise, send "def" and "buf" in separate pieces. This might introduce latencies
if the other end runs Windows :-(
*/
else {
/* send the request to the server, first the message definition */
if ((n = bufwrite(server, request->def, sizeof(messagedef_t)))!=sizeof(messagedef_t)) {
fprintf(stderr, "write size = %d, should be %lu\n", n, (long unsigned int)sizeof(messagedef_t));
goto cleanup;
}
/* send the request to the server, then the message payload */
if ((n = bufwrite(server, request->buf, request->def->bufsize))!=request->def->bufsize) {
fprintf(stderr, "write size = %d, should be %d\n", n, request->def->bufsize);
goto cleanup;
}
}
/* read the response from the server, first the message definition */
if ((n = bufread(server, response->def, sizeof(messagedef_t))) != sizeof(messagedef_t)) {
fprintf(stderr, "packet size = %d, should be %lu\n", n, (long unsigned int)sizeof(messagedef_t));
goto cleanup;
}
if (response->def->version!=VERSION) {
fprintf(stderr, "incorrect version\n");
goto cleanup;
}
/* read the response from the server, then the message payload */
if (response->def->bufsize>0) {
response->buf = malloc(response->def->bufsize);
if ((n = bufread(server, response->buf, response->def->bufsize)) != response->def->bufsize) {
fprintf(stderr, "read size = %d, should be %d\n", n, response->def->bufsize);
goto cleanup;
}
}
/* everything went fine, return with the response */
/* print_response(response->def); */
return 0;
cleanup:
/* there was a problem, clear the response and return */
FREE(response->def);
FREE(response->buf);
FREE(response);
*response_ptr = NULL; /* SK: this was missing a "*", effectively never really returning 0 */
return -1;
}
@@ -0,0 +1,4 @@
/*
* This will contain the implementation of the TiA server on top of the FieldTrip buffer, similar to the RDA server.
*
*/
@@ -0,0 +1,4 @@
/*
* This will contain the implementation of the TiA server on top of the FieldTrip buffer, similar to the RDA server.
*
*/
@@ -0,0 +1,482 @@
/*
* Copyright (C) 2008, Robert Oostenveld & Christian Hesse
* F.C. Donders Centre for Cognitive Neuroimaging, Radboud University Nijmegen,
* Kapittelweg 29, 6525 EN Nijmegen, The Netherlands
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* for strerror */
#include "buffer.h"
#include <pthread.h>
#include "extern.h"
unsigned int bufread(int s, void* buf, unsigned int numel)
{
unsigned int numcall = 0, numread = 0, verbose = 0;
int numthis = 0;
while (numread < numel)
{
numthis = recv(s, (char*)buf + numread, numel - numread, 0);
if (numthis < 0)
{
perror("bufread");
break;
}
else if (numthis == 0) { break; }
if (verbose > 0) { fprintf(stderr, "bufread: read %d bytes\n", numthis); }
numread += numthis;
numcall++;
#ifndef PLATFORM_WIN32 /* SK: I think this shouldn't be necessary on any platform: the sockets are blocking */
if (numread < numel) { usleep(1000); }
#endif
}
if (verbose > 1) { fprintf(stderr, "bufread: reading the complete buffer required %d calls\n", numcall); }
return numread;
}
unsigned int bufwrite(int s, const void* buf, unsigned int numel)
{
int numthis = 0;
unsigned int numcall = 0, numwrite = 0, verbose = 0;
while (numwrite < numel)
{
numthis = send(s, (char*)buf + numwrite, numel - numwrite, 0);
if (numthis < 0)
{
perror("bufwrite");
break;
}
else if (numthis == 0) { break; }
if (verbose) fprintf(stderr, "bufwrite: wrote %d bytes\n", numthis);
numwrite += numthis;
numcall++;
#ifndef PLATFORM_WIN32 /* SK: I think this shouldn't be necessary on any platform: the sockets are blocking */
if (numwrite < numel) { usleep(1000); }
#endif
}
if (verbose > 1) { fprintf(stderr, "bufwrite: writing the complete buffer required %d calls\n", numcall); }
return numwrite;
}
unsigned int append(void** buf1, unsigned int bufsize1, void* buf2, unsigned int bufsize2)
{
int verbose = 0;
if (verbose > 1)
{
pthread_mutex_lock(&mutexappendcount);
appendcount++;
fprintf(stderr, "append: appendcount = %d\n", appendcount);
pthread_mutex_unlock(&mutexappendcount);
}
if (((*buf1) != nullptr) && (bufsize1 == 0))
{
perror("append err1");
return 0; /* was -1, but this is never checked anyway */
}
else if (((*buf1) == nullptr) && (bufsize1 != 0))
{
perror("append err2");
return 0; /* was -1, but this is never checked anyway */
}
if ((*buf1) == nullptr)
{
if (verbose > 0) { fprintf(stderr, "append: allocating %d bytes\n", bufsize2); }
(*buf1) = malloc(bufsize2);
}
else if ((*buf1) != nullptr)
{
if (verbose > 0) { fprintf(stderr, "append: reallocating from %d to %d bytes\n", bufsize1, bufsize1 + bufsize2); }
(*buf1) = realloc((*buf1), bufsize1 + bufsize2);
}
memcpy((char*)(*buf1) + bufsize1, buf2, bufsize2);
return (bufsize1 + bufsize2);
}
int close_connection(int s)
{
int status = 0, verbose = 0;
if (verbose > 0) { fprintf(stderr, "close_connection: socket = %d\n", s); }
if (s > 0) status = closesocket(s); /* it is a TCP connection */
if (status != 0) { perror("close_connection"); }
return status;
}
int open_connection(const char* hostname, int port)
{
int verbose = 0;
int s, retry;
struct sockaddr_in sa;
struct hostent* host;
#ifdef WIN32
static WSADATA wsa = { 0, 0 }; /* check version fields to only initialise once */
#endif
if (port == 0)
{
if (verbose > 0) { fprintf(stderr, "open_connection: using direct memory copy\n"); }
return 0;
}
else { if (verbose > 0) { fprintf(stderr, "open_connection: server = %s, port = %d\n", hostname, port); } }
#ifdef WIN32
if (wsa.wVersion == 0)
{
/* We only need to do this once ... and actually have a corresponding WSACleanup call somewhere */
if (WSAStartup(MAKEWORD(1, 1), &wsa))
{
fprintf(stderr, "open_connection: cannot start sockets\n");
/* FIXME should this exception be handled more explicitely? */
}
}
#endif
if ((host = gethostbyname(hostname)) == nullptr)
{
fprintf(stderr, "open_connection: nslookup1 failed on '%s'\n", hostname);
return -1;
}
if (host->h_length == 0)
{
fprintf(stderr, "open_connection: nslookup2 failed on '%s'\n", hostname);
return -1;
}
bzero(&sa, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
memcpy(&(sa.sin_addr.s_addr), host->h_addr_list[0], sizeof(sa.sin_addr.s_addr));
if ((s = socket(PF_INET, SOCK_STREAM, 0)) < 0)
{
if (verbose > 0) { fprintf(stderr, "open_connection: socket = %d\n", s); }
perror("open_connection");
return -1;
}
retry = 10;
while (retry > 0)
{
if (connect(s, (struct sockaddr*)&sa, sizeof sa) < 0)
{
/* wait 5 miliseconds and try again */
usleep(5000);
retry--;
}
else
{
/* this signals that the connection has been made */
retry = -1;
}
}
if (retry == 0)
{
/* close the socket */
closesocket(s);
/* it failed on mutliple attempts, give up */
return -2;
}
/*
while (connect(s, (struct sockaddr *)&sa, sizeof sa) < 0) {
perror("open_connection connect");
usleep(1000000);
}
*/
if (verbose > 0) { fprintf(stderr, "open_connection: connected to %s:%d on socket %d\n", hostname, port, s); }
#ifdef DISABLE_NAGLE
{
int optval = 1;
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));
}
#endif
return s;
}
void check_datatypes()
{
/* check datatypes */
if (WORDSIZE_CHAR != 1)
{
fprintf(stderr, "invalid size of CHAR (%d)\n", (int)WORDSIZE_CHAR);
exit(-1);
}
if (WORDSIZE_UINT8 != 1)
{
fprintf(stderr, "invalid size of UINT8 (%d)\n", (int)WORDSIZE_UINT8);
exit(-1);
}
if (WORDSIZE_UINT16 != 2)
{
fprintf(stderr, "invalid size of UINT16 (%d)\n", (int)WORDSIZE_UINT16);
exit(-1);
}
if (WORDSIZE_UINT32 != 4)
{
fprintf(stderr, "invalid size of UINT32 (%d)\n", (int)WORDSIZE_UINT32);
exit(-1);
}
if (WORDSIZE_UINT64 != 8)
{
fprintf(stderr, "invalid size of UINT64 (%d)\n", (int)WORDSIZE_UINT64);
exit(-1);
}
if (WORDSIZE_INT8 != 1)
{
fprintf(stderr, "invalid size of INT8 (%d)\n", (int)WORDSIZE_INT8);
exit(-1);
}
if (WORDSIZE_INT16 != 2)
{
fprintf(stderr, "invalid size of INT16 (%d)\n", (int)WORDSIZE_INT16);
exit(-1);
}
if (WORDSIZE_INT32 != 4)
{
fprintf(stderr, "invalid size of INT32 (%d)\n", (int)WORDSIZE_INT32);
exit(-1);
}
if (WORDSIZE_INT64 != 8)
{
fprintf(stderr, "invalid size of INT64 (%d)\n", (int)WORDSIZE_INT64);
exit(-1);
}
if (WORDSIZE_FLOAT32 != 4)
{
fprintf(stderr, "invalid size of FLOAT32 (%d)\n", (int)WORDSIZE_FLOAT32);
exit(-1);
}
if (WORDSIZE_FLOAT64 != 8)
{
fprintf(stderr, "invalid size of FLOAT64 (%d)\n", (int)WORDSIZE_FLOAT64);
exit(-1);
}
if (sizeof(messagedef_t) != 8)
{
fprintf(stderr, "invalid size of messagedef_t\n");
exit(-1);
}
if (sizeof(headerdef_t) != 24)
{
fprintf(stderr, "invalid size of headerdef_t \n");
exit(-1);
}
if (sizeof(datadef_t) != 16)
{
fprintf(stderr, "invalid size of datadef_t \n");
exit(-1);
}
if (sizeof(eventdef_t) != 32)
{
fprintf(stderr, "invalid size of eventdef_t \n");
exit(-1);
}
if (sizeof(datasel_t) != 8)
{
fprintf(stderr, "invalid size of datasel_t \n");
exit(-1);
}
if (sizeof(eventsel_t) != 8)
{
fprintf(stderr, "invalid size of eventsel_t \n");
exit(-1);
}
}
unsigned int wordsize_from_type(UINT32_T data_type)
{
switch (data_type)
{
case DATATYPE_CHAR: return WORDSIZE_CHAR;
case DATATYPE_UINT8:
case DATATYPE_INT8: return WORDSIZE_INT8;
case DATATYPE_UINT16:
case DATATYPE_INT16: return WORDSIZE_INT16;
case DATATYPE_UINT32:
case DATATYPE_INT32: return WORDSIZE_INT32;
case DATATYPE_UINT64:
case DATATYPE_INT64: return WORDSIZE_INT64;
case DATATYPE_FLOAT32: return WORDSIZE_FLOAT32;
case DATATYPE_FLOAT64: return WORDSIZE_FLOAT64;
}
return 0;
}
const ft_chunk_t* find_chunk(const void* buf, unsigned int offset0, unsigned int size, UINT32_T chunk_type)
{
unsigned int bufpos = offset0;
while (bufpos + sizeof(ft_chunkdef_t) <= size)
{
const ft_chunk_t* chunk = (ft_chunk_t*)((char*)buf + bufpos);
if (chunk->def.type == chunk_type) { return chunk; }
bufpos += sizeof(ft_chunkdef_t) + chunk->def.size;
}
return nullptr;
}
/** Iterate through an array of events and check whether all of them are properly defined,
that is, whether the "type" and "value" fields are of valid type and size, and whether the
"bufsize" fields are correct (that is, fully contained in the passed buffer, and big enough
to hold "type" and "value".
Returns the number of events on success (might also be 0), or
a negative number that indicates in which event definition an error happend,
for example, a return value of -2 means that the first event was ok, but the second event
definition was invalid. This function returns at the first error.
*/
int check_event_array(unsigned int size, const void* buf)
{
unsigned int offset = 0;
int numEvents = 0;
while (offset + sizeof(eventdef_t) <= size)
{
const eventdef_t* E;
unsigned int wsType, wsValue;
/* Set our event pointer to the current location within the array */
E = (const eventdef_t*)((char*)buf + offset);
/* Increase the offset by the size of this event, and check whether it's fully
contained within the given array.
*/
offset += sizeof(eventdef_t) + E->bufsize;
if (offset > size) { goto error; }
/* Check whether "type" and "value" are of known type */
wsType = wordsize_from_type(E->type_type);
if (wsType == 0) { goto error; }
wsValue = wordsize_from_type(E->value_type);
if (wsValue == 0) { goto error; }
/* Check whether "type" and "value" are contained in this event's "buf" */
if (wsType * E->type_numel + wsValue * E->value_numel > E->bufsize) { goto error; }
/* all checks passed, continue at next offset */
++numEvents;
}
return numEvents;
error:
return -(1 + numEvents);
}
#ifdef WIN32
int open_unix_connection(const char* name) { return -1; }
#else
int open_unix_connection(const char* name)
{
int verbose = 0;
int s, retry;
struct sockaddr_un sa;
bzero(&sa, sizeof(sa));
sa.sun_family = AF_UNIX;
strncpy(sa.sun_path, name, sizeof(sa.sun_path));
s = socket(AF_UNIX, SOCK_STREAM, 0);
if (s < 0) {
perror("open_unix_connection, socket");
return -1;
}
retry = 10;
while (retry > 0) {
if (connect(s, (struct sockaddr*) & sa, sizeof(sa)) < 0) {
/* wait 5 miliseconds and try again */
perror("open_connection");
usleep(5000);
retry--;
}
else {
/* this signals that the connection has been made */
retry = -1;
}
}
if (retry == 0) {
/* it failed on mutliple attempts, give up */
return -2;
}
if (verbose > 0)
fprintf(stderr, "open_unix_connection: connected to %s on socket %d\n", name, s);
return s;
}
#endif
#ifdef WIN32
#ifndef COMPILER_MINGW
/*
* timeval.h 1.0 01/12/19
*
* Defines gettimeofday, timeval, etc. for Win32
*
* By Wu Yongwei
*
*/
//#define EPOCHFILETIME (116444736000000000i64)
#define EPOCHFILETIME ((INT64_T) 116444736000000000LL)
#ifdef COMPILER_LCC
VOID STDCALL GetSystemTimeAsFileTime(LPFILETIME);
#endif
int gettimeofday(struct timeval* tv, struct timezone* tz)
{
FILETIME ft;
LARGE_INTEGER li;
INT64_T t;
static int tzflag;
if (tv)
{
GetSystemTimeAsFileTime(&ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
t = li.QuadPart; /* In 100-nanosecond intervals */
t -= EPOCHFILETIME; /* Offset to the Epoch time */
t /= 10; /* In microseconds */
tv->tv_sec = (long)(t / 1000000);
tv->tv_usec = (long)(t % 1000000);
}
#ifndef COMPILER_LCC
/* LCC that comes with Matlab has problems with _timezone and _daylight,
and we don't need it anyway */
if (tz)
{
if (!tzflag)
{
_tzset();
tzflag++;
}
tz->tz_minuteswest = _timezone / 60;
tz->tz_dsttime = _daylight;
}
#endif
return 0;
}
#endif
#endif
@@ -0,0 +1,80 @@
/* SK: This has now been moved into util.c for simplification of compile scripts and Makefiles */
/*
* timeval.h 1.0 01/12/19
*
* Defines gettimeofday, timeval, etc. for Win32
*
* By Wu Yongwei
*
*/
#ifndef _TIMEVAL_H
#define _TIMEVAL_H
#if TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#elif HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#if ! HAVE_GETTIMEOFDAY || MINGW
#if __WIN32__
#include <windows.h>
#else
#include <errno.h>
#endif
#ifndef __GNUC__
#define EPOCHFILETIME (116444736000000000i64)
#else
#define EPOCHFILETIME (116444736000000000LL)
#endif
#include "gettimeofday.h"
/*!
\brief A Windows gettimeofday implementation.
*/
int gettimeofday(struct timeval *tv, struct timezone *tz)
{
#if __WIN32__
FILETIME ft;
LARGE_INTEGER li;
__int64 t;
static int tzflag;
if (tv) {
GetSystemTimeAsFileTime(&ft);
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
t = li.QuadPart; /* In 100-nanosecond intervals */
t -= EPOCHFILETIME; /* Offset to the Epoch time */
t /= 10; /* In microseconds */
tv->tv_sec = (long)(t / 1000000);
tv->tv_usec = (long)(t % 1000000);
}
if (tz) {
if (!tzflag) {
_tzset();
tzflag++;
}
tz->tz_minuteswest = _timezone / 60;
tz->tz_dsttime = _daylight;
}
return 0;
#else
errno = ENOSYS;
return -1;
#endif
}
#endif
#endif
@@ -0,0 +1,12 @@
#ifndef __gettimeofday_h
#define __gettimeofday_h
#include <time.h>
struct timezone {
int tz_minuteswest; /* minutes W of Greenwich */
int tz_dsttime; /* type of dst correction */
};
int gettimeofday(struct timeval *tv, struct timezone *tz);
#endif
@@ -0,0 +1,61 @@
#include <winsock2.h>
#include "poll.h"
int poll (struct pollfd *p, int num, int timeout)
{
struct timeval tv;
fd_set read, write, except;
int i, n, ret;
char buf[1024];
FD_ZERO (&read);
FD_ZERO (&write);
FD_ZERO (&except);
n = -1;
for (i = 0; i < num; ++i)
{
if (p[i].fd < 0)
continue;
if (p[i].events & POLLIN)
FD_SET (p[i].fd, &read);
if (p[i].events & POLLOUT)
FD_SET (p[i].fd, &write);
if (p[i].events & POLLERR)
FD_SET (p[i].fd, &except);
if (p[i].fd > n)
n = p[i].fd;
}
if (n == -1)
return (0);
if (timeout < 0)
ret = select (n+1, &read, &write, &except, nullptr);
else
{
tv.tv_sec = timeout / 1000;
tv.tv_usec = 1000 * (timeout % 1000);
ret = select (n+1, &read, &write, &except, &tv);
}
for (i = 0; ret >= 0 && i < num; ++i)
{
p[i].revents = 0;
if (FD_ISSET (p[i].fd, &read))
{
int j = recv(p[i].fd, buf, 1024, MSG_PEEK);
if(j>0)
p[i].revents |= POLLIN;
else
p[i].revents |= POLLHUP;
}
if (FD_ISSET (p[i].fd, &write))
p[i].revents |= POLLOUT;
if (FD_ISSET (p[i].fd, &except))
p[i].revents |= POLLERR;
}
return (ret);
}
@@ -0,0 +1,50 @@
#if defined(TARGET_HAS_PThread)
#include "ovasCConfigurationFieldtrip.h"
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <fstream>
#include <list>
namespace OpenViBE {
namespace AcquisitionServer {
bool CConfigurationFieldtrip::preConfigure()
{
const bool res = CConfigurationBuilder::preConfigure();
m_pHostName = GTK_WIDGET(gtk_builder_get_object(m_builder, "entry_host_name"));
m_pHostPort = GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_host_port"));
m_pMinSamples = GTK_WIDGET(gtk_builder_get_object(m_builder, "spinbutton_minSamples"));
m_pSRCorrection = GTK_WIDGET(gtk_builder_get_object(m_builder, "checkbutton_SRCorrection"));
gtk_spin_button_set_range(GTK_SPIN_BUTTON(m_pMinSamples), 1.0, 10000.0);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_pMinSamples), m_minSamples);
gtk_spin_button_set_value(GTK_SPIN_BUTTON(m_pHostPort), m_hostPort);
gtk_entry_set_text(GTK_ENTRY(m_pHostName), m_hostName.toASCIIString());
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pSRCorrection), m_srCorrection);
return res;
}
bool CConfigurationFieldtrip::postConfigure()
{
if (m_applyConfig)
{
gtk_spin_button_update(GTK_SPIN_BUTTON(m_pMinSamples));
gtk_spin_button_update(GTK_SPIN_BUTTON(m_pHostPort));
m_minSamples = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_pMinSamples));
m_hostPort = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(m_pHostPort));
m_hostName = gtk_entry_get_text(GTK_ENTRY(m_pHostName));
m_srCorrection = (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pSRCorrection)) > 0);
}
return CConfigurationBuilder::postConfigure();
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif //#if defined(TARGET_HAS_PThread)
@@ -0,0 +1,59 @@
#pragma once
#if defined(TARGET_HAS_PThread)
#include "../ovasCConfigurationBuilder.h"
namespace OpenViBE
{
namespace AcquisitionServer
{
/**
* \class CConfigurationFieldtrip
* \author Amelie Serpollet (CEA/LETI/CLINATEC)
* \date Mon May 23 09:48:21 2011
* \brief The CDriverFieldtrip allows the acquisition server to acquire data from a Fieldtrip buffer.
*
*/
class CConfigurationFieldtrip final : public CConfigurationBuilder
{
public:
CConfigurationFieldtrip(const char* gtkBuilderFilename) : CConfigurationBuilder(gtkBuilderFilename) { }
~CConfigurationFieldtrip() override { }
void setHostName(const CString& hostName) { m_hostName = hostName; }
void setHostPort(const uint32_t hostPort) { m_hostPort = hostPort; }
void setMinSamples(const uint32_t minSamples) { m_minSamples = minSamples; }
void setSRCorrection(const bool bSRCorrection) { m_srCorrection = bSRCorrection; }
CString getHostName() const { return m_hostName; }
uint32_t getHostPort() const { return m_hostPort; }
uint32_t getMinSamples() const { return m_minSamples; }
bool getSRCorrection() const { return m_srCorrection; }
protected:
bool preConfigure() override;
bool postConfigure() override;
private:
CConfigurationFieldtrip();
protected:
GtkWidget* m_pHostName = nullptr;
GtkWidget* m_pHostPort = nullptr;
GtkWidget* m_pMinSamples = nullptr;
GtkWidget* m_pSRCorrection = nullptr;
CString m_hostName = "localhost";
uint32_t m_hostPort = 4000;
uint32_t m_minSamples = 1;
bool m_srCorrection = true;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // #if defined(TARGET_HAS_PThread)
@@ -0,0 +1,533 @@
/* This driver uses the FieldTrip buffer open source library.
* See http://www.ru.nl/fcdonders/fieldtrip for details.
*/
#if defined(TARGET_HAS_PThread)
#include "ovasCDriverFieldtrip.h"
#include "ovasCConfigurationFieldtrip.h"
#include <toolkit/ovtk_all.h>
#include <pthread.h>
#include "fieldtrip/buffer.h"
#include "fieldtrip/extern.h"
#include "fieldtrip/extern.c"
#include "fieldtrip/util.c"
#include "fieldtrip/printstruct.c"
#include "fieldtrip/tcprequest.c"
#include "fieldtrip/dmarequest.c"
#include "fieldtrip/clientrequest.c"
#include <system/ovCTime.h>
//#include "GetCpuTime.h"
namespace OpenViBE {
namespace AcquisitionServer {
CDriverFieldtrip::CDriverFieldtrip(IDriverContext& ctx)
: IDriver(ctx), m_settings("AcquisitionServer_Driver_FieldTrip", m_driverCtx.getConfigurationManager())
{
m_header.setSamplingFrequency(0);
m_header.setChannelCount(0);
m_waitDataRequest = new message_t();
m_waitDataRequest->def = new messagedef_t();
m_waitDataRequest->buf = nullptr;
m_getDataRequest = new message_t();
m_getDataRequest->def = new messagedef_t();
m_getDataRequest->buf = nullptr;
m_settings.add("Header", &m_header);
m_settings.add("MinSamples", &m_minSamples);
m_settings.add("PortNumber", &m_portNumber);
m_settings.add("HostName", &m_hostName);
m_settings.add("CorrectNonIntegerSR", &m_correctNonIntegerSR);
m_settings.load();
}
CDriverFieldtrip::~CDriverFieldtrip()
{
if (m_waitDataRequest)
{
//m_waitDataRequest->buf deleted with m_waitDataRequest->def
if (m_waitDataRequest->def) { delete m_waitDataRequest->def; }
delete m_waitDataRequest;
}
if (m_getDataRequest)
{
//m_getDataRequest->buf deleted with m_getDataRequest->def
if (m_getDataRequest->def) { delete m_getDataRequest->def; }
delete m_getDataRequest;
}
}
const char* CDriverFieldtrip::getName() { return "Fieldtrip Driver"; }
//___________________________________________________________________//
// //
bool CDriverFieldtrip::initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback)
{
if (m_driverCtx.isConnected()) { return false; }
// ...
// initialize hardware and get available header information
// from it :
// connect to buffer
if (m_connectionID != -1)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Already connected to Fieldtrip buffer " << m_hostName << ":" << m_portNumber << "\n";
return false;
}
m_connectionID = open_connection(m_hostName.toASCIIString(), int(m_portNumber));
if (m_connectionID < 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to connect to Fieldtrip buffer :\n" << m_hostName << ":" << m_portNumber << "\n";
m_connectionID = -1;
return false;
}
else { m_driverCtx.getLogManager() << Kernel::LogLevel_Info << "Connection to Fieldtrip buffer succeeded !\n"; }
// request header
if (!requestHeader())
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Request header failed, disconnecting.\n";
if (close_connection(m_connectionID) != 0)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to disconnect correctly from Fieldtrip buffer\n";
}
m_connectionID = -1;
return false;
}
if (!m_header.isChannelCountSet() || !m_header.isSamplingFrequencySet()) { return false; }
// Builds up a buffer to store acquired samples. This buffer
// will be sent to the acquisition server later...
m_sample = new float[m_header.getChannelCount() * nSamplePerSentBlock];
if (!m_sample)
{
delete [] m_sample;
m_sample = nullptr;
return false;
}
// Saves parameters
m_callback = &callback;
m_nSamplePerSentBlock = nSamplePerSentBlock;
if (m_minSamples < 1) { m_minSamples = 1; }
if (m_minSamples > m_nSamplePerSentBlock) { m_minSamples = m_nSamplePerSentBlock; }
return true;
}
bool CDriverFieldtrip::start()
{
if (!m_driverCtx.isConnected() || m_driverCtx.isStarted()) { return false; }
// ...
// request hardware to start
// sending data
// ...
m_firstGetDataRequest = true;
m_waitingTimeMs = (m_header.getSamplingFrequency() > 1000 ? 1 : (1000 / m_header.getSamplingFrequency())
); //time for 1 sample if >= 1ms //(1000*m_nSamplePerSentBlock)
m_nTotalSample = 0;
m_diffPerSample = (m_realSampling - m_header.getSamplingFrequency()) / m_realSampling;
if (m_diffPerSample <= 0.0) { m_diffPerSample = 0.0; }
m_driftSinceLastCorrection = 0.0;
return true;
}
bool CDriverFieldtrip::loop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return true; }
CStimulationSet stimSet;
stimSet.setStimulationCount(0);
// ...
// receive samples from hardware
// put them the correct way in the sample array
// whether the buffer is full, send it to the acquisition server
//...
const int count = requestChunk(stimSet);
if (count < 0) { return false; }
if (count == 0) { return true; }
m_callback->setSamples(m_sample, count);
m_callback->setStimulationSet(stimSet);
m_driverCtx.correctDriftSampleCount(m_driverCtx.getSuggestedDriftCorrectionSampleCount());
return true;
}
bool CDriverFieldtrip::stop()
{
if (!m_driverCtx.isConnected()) { return false; }
if (!m_driverCtx.isStarted()) { return false; }
return true;
}
bool CDriverFieldtrip::uninitialize()
{
if (!m_driverCtx.isConnected()) { return false; }
if (m_driverCtx.isStarted()) { return false; }
if (close_connection(m_connectionID) != 0) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << "Failed to disconnect correctly from Fieldtrip buffer\n"; }
m_connectionID = -1;
delete [] m_sample;
m_sample = nullptr;
m_callback = nullptr;
return true;
}
//___________________________________________________________________//
// //
bool CDriverFieldtrip::isConfigurable()
{
return true; // change to false if your device is not configurable
}
bool CDriverFieldtrip::configure()
{
CConfigurationFieldtrip config(Directories::getDataDir() + "/applications/acquisition-server/interface-Fieldtrip.ui");
config.setMinSamples(m_minSamples);
config.setHostPort(m_portNumber);
config.setHostName(m_hostName);
config.setSRCorrection(m_correctNonIntegerSR);
if (config.configure(m_header))
{
m_minSamples = config.getMinSamples();
m_portNumber = config.getHostPort();
m_hostName = config.getHostName();
m_correctNonIntegerSR = config.getSRCorrection();
m_settings.save();
return true;
}
return false;
}
//___________________________________________________________________//
// //
bool CDriverFieldtrip::requestHeader()
{
m_waitDataRequest->def->command = GET_HDR;
m_waitDataRequest->def->version = VERSION;
m_waitDataRequest->def->bufsize = 0;
m_waitDataRequest->buf = nullptr;
message_t* response = nullptr;
const int res = clientrequest(m_connectionID, m_waitDataRequest, &response);
if (res != 0)
{
FreeResponse(response, "Error while asking for header. Buffer aborted ?");
return false;
}
else if (response == nullptr || response->def == nullptr)
{
FreeResponse(response, "Error while asking for header");
return false;
}
else if (response->def->command != GET_OK || response->def->bufsize == 0)
{
FreeResponse(response, "No header in the buffer");
return false;
}
else
{
const unsigned int size = response->def->bufsize;
headerdef_t* headerDef = (headerdef_t*)response->buf;
if (size < sizeof(headerdef_t))
{
FreeResponse(response, "Header received has wrong format");
return false;
}
m_header.setSamplingFrequency(uint32_t(headerDef->fsample));
m_realSampling = headerDef->fsample;
m_header.setChannelCount(headerDef->nchans);
m_dataType = headerDef->data_type;
if (m_dataType != DATATYPE_FLOAT32 && m_dataType != DATATYPE_FLOAT64)
{
FreeResponse(response, "Data type is not supported");
return false;
}
if (size == sizeof(headerdef_t)) //no chunk attached to the header
{
for (uint32_t i = 0; i < headerDef->nchans; i++) { m_header.setChannelName(i, ("Channel " + std::to_string(i)).c_str()); }
}
else //chunk(s) attached to the header, maybe channel names
{
int bytesInHeaderBuffer = headerDef->bufsize;
void* chunk = (headerdef_t*)headerDef + 1;
bool foundChannelNames = false;
while (bytesInHeaderBuffer > 0)
{
if (((ft_chunk_t*)chunk)->def.type == FT_CHUNK_CHANNEL_NAMES)
{
foundChannelNames = true;
char* chunkdata = ((ft_chunk_t*)chunk)->data;
for (uint32_t i = 0; i < headerDef->nchans; i++)
{
std::string name = chunkdata;
m_header.setChannelName(i, name.c_str());
chunkdata = (char*)chunkdata + name.size() + 1;
}
}
bytesInHeaderBuffer -= ((ft_chunk_t*)chunk)->def.size + sizeof(ft_chunkdef_t);
if (bytesInHeaderBuffer > 0) { chunk = (char*)chunk + ((ft_chunk_t*)chunk)->def.size + sizeof(ft_chunkdef_t); }
}
if (!foundChannelNames)
{
for (uint32_t i = 0; i < headerDef->nchans; i++) { m_header.setChannelName(i, ("Channel " + std::to_string(i)).c_str()); }
}
}
} /* end valid header */
FreeResponse(response, nullptr);
return true;
}
int CDriverFieldtrip::requestChunk(CStimulationSet& oStimulationSet)
{
//There are two basic opertations:
// - configure m_getDataRequest and m_waitDataRequest
// - use m_getDataRequest to call fieldtrip clientrequest() that gets the data from fieldtrip
// - transpose the data to fill m_sample in the correct way expected by OpenVube
//The rest are data validity checks and correction for non-integer sampling frequency
//configure "wait data" request
m_waitDataRequest->def->command = WAIT_DAT;
m_waitDataRequest->def->version = VERSION;
if (m_waitDataRequest->buf == nullptr)
{
m_waitDataRequest->def->bufsize = 0;
waitdef_t* waitDef = new waitdef_t();
unsigned int requestSize = 0;
requestSize = append((void**)&m_waitDataRequest->def, sizeof(messagedef_t), waitDef, sizeof(waitdef_t));
m_waitDataRequest->def->bufsize = requestSize - sizeof(messagedef_t);
m_waitDataRequest->buf = (messagedef_t*)m_waitDataRequest->def + 1;
}
waitdef_t* waitDef = (waitdef_t*)m_waitDataRequest->buf;
waitDef->threshold.nevents = 0xFFFFFFFF;
waitDef->threshold.nsamples = m_nSamplePerSentBlock;
waitDef->milliseconds = m_waitingTimeMs;
message_t* response = nullptr;
int res = clientrequest(m_connectionID, m_waitDataRequest, &response);
uint32_t nDataReceived = 0;
uint32_t nDataToSend = 0;
if (res)
{
FreeResponse(response, "Error while asking for data. Buffer aborted ?");
return -1;
}
else if (response == nullptr || response->def == nullptr)
{
FreeResponse(response, "Error while asking for data");
return -1;
}
else if (response->def->command != WAIT_OK || response->def->bufsize != 8 || response->buf == nullptr)
{
FreeResponse(response, "No header in buffer anymore");
return -1;
}
else
{
// new header received ? stop acquisition
if (((samples_events_t*)response->buf)->nsamples < m_nTotalSample)
{
FreeResponse(response, "End of data");
return -1;
}
// no new data
if (((samples_events_t*)response->buf)->nsamples <= m_nTotalSample + m_minSamples)
{
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "No new data\n";
FreeResponse(response, nullptr);
return 0;
}
// get data
uint32_t lastSample = ((samples_events_t*)response->buf)->nsamples;
if (lastSample > m_nTotalSample + m_nSamplePerSentBlock)
{
if (m_firstGetDataRequest) { m_nTotalSample = lastSample - m_nSamplePerSentBlock; }
else { lastSample = m_nTotalSample + m_nSamplePerSentBlock; }
}
if (m_firstGetDataRequest) { m_firstGetDataRequest = false; }
FreeResponse(response, nullptr); //prevents memory leak
// "get data" request
m_getDataRequest->def->command = GET_DAT;
m_getDataRequest->def->version = VERSION;
if (m_getDataRequest->buf == nullptr)
{
m_getDataRequest->def->bufsize = 0;
datasel_t* dataSel = new datasel_t();
unsigned int requestSize = 0;
requestSize = append((void**)&m_getDataRequest->def, sizeof(messagedef_t), dataSel, sizeof(datasel_t));
m_getDataRequest->def->bufsize = requestSize - sizeof(messagedef_t);
m_getDataRequest->buf = (messagedef_t*)m_getDataRequest->def + 1;
}
datasel_t* dataSel = (datasel_t*)m_getDataRequest->buf;
dataSel->begsample = m_nTotalSample;
dataSel->endsample = lastSample - 1;
//actual data acquisition
res = clientrequest(m_connectionID, m_getDataRequest, &response);
if (res || !response || !response->def || response->def->version != VERSION)
{
FreeResponse(response, "Error while asking for data");
return -1;
}
else if (response->def->command != GET_OK || response->def->bufsize == 0 || response->buf == nullptr)
{
FreeResponse(response, "Data are not available anymore");
return -1;
}
else // data received
{
datadef_t* datadef = (datadef_t*)response->buf;
void* databuf = (datadef_t*)response->buf + 1;
if (datadef->bufsize / (wordsize_from_type(datadef->data_type) * datadef->nchans) != lastSample - m_nTotalSample)
{
FreeResponse(response, "Data received from buffer are invalid");
return -1;
}
else // data correct
{
nDataReceived = lastSample - m_nTotalSample;
// Delete some samples if necessary.
// Sampling rate is converted into integer in openvibe,
// so we can have up to 1 sample too many per second.
nDataToSend = nDataReceived;
if (m_correctNonIntegerSR)
{
m_driftSinceLastCorrection += (m_diffPerSample * nDataReceived);
if (m_driftSinceLastCorrection >= 1.0)
{
// delete samples
const uint32_t diffSamples = uint32_t(m_driftSinceLastCorrection);
nDataToSend -= diffSamples;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "Correction for non-integer sampling rate : " << diffSamples << " samples deleted\n";
m_driftSinceLastCorrection -= double(diffSamples);
//oStimulationSet.appendStimulation(OVTK_GDF_Missing, CTime(m_header.getSamplingFrequency(), nDataToSend).time(), CTime(m_header.getSamplingFrequency(), diffSamples)).time();
}
}
// set data in m_sample
double* buffer64;
float* buffer32;
switch (m_dataType)
{
case DATATYPE_FLOAT64:
buffer64 = (double*)databuf;
for (size_t j = 0; j < m_header.getChannelCount(); j++)
{
for (uint32_t i = 0; i < nDataToSend; i++)
{
const double value = buffer64[i * m_header.getChannelCount() + j];
/*if ( _isnan(value) || !_finite(value) || value==DBL_MAX )
{
m_sample[j*nDataToSend + i] = FLT_MAX;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "NaN or infinite sample received.\n";
}
else
{
//data from IHM implant are in volts, must be in uvolts in openvibe
m_sample[j*nDataToSend + i] = (float) 1000000.0f*value;
}*/
m_sample[j * nDataToSend + i] = float(value);
}
}
break;
case DATATYPE_FLOAT32:
buffer32 = (float*)databuf;
for (size_t j = 0; j < m_header.getChannelCount(); j++)
{
for (uint32_t i = 0; i < nDataToSend; i++)
{
const float value = buffer32[i * m_header.getChannelCount() + j];
/*if ( _isnan(value) || !_finite(value) || value==FLT_MAX )
{
m_sample[j*nDataToSend + i] = FLT_MAX;
m_driverCtx.getLogManager() << Kernel::LogLevel_Trace << "NaN or infinite sample received.\n";
}
else
{
//data from IHM implant are in volts, must be in uvolts in openvibe
m_sample[j*nDataToSend + i] = 1000000.0f*value;
}*/
m_sample[j * nDataToSend + i] = value;
}
}
break;
default:
FreeResponse(response, "DEV ERROR : data type not suppported");
return -1;
}//end switch
}//end data correct
}//end data received
FreeResponse(response, nullptr);//we copied the data from response, so now we need to release this memory to avoid memory leak
m_nTotalSample = lastSample;
}
return nDataToSend; // no error
}
void CDriverFieldtrip::FreeResponse(message_t* response, const char* message)
{
if (message != nullptr) { m_driverCtx.getLogManager() << Kernel::LogLevel_Error << message << "\n"; }
if (response->buf) { free(response->buf); }
if (response->def) { free(response->def); }
free(response);
response = nullptr;
}
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif
@@ -0,0 +1,93 @@
/* This driver uses the FieldTrip buffer open source library.
* See http://www.ru.nl/fcdonders/fieldtrip for details.
*/
#pragma once
#if defined(TARGET_HAS_PThread)
#include "ovasIDriver.h"
#include "../ovasCHeader.h"
#include <openvibe/ov_all.h>
#include "fieldtrip/message.h"
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
// for GET_CPU_TIME
#include <iostream>
#include <fstream>
namespace OpenViBE
{
namespace AcquisitionServer
{
/**
* \class CDriverFieldtrip
* \author Amelie Serpollet (CEA/LETI/CLINATEC)
* \date Mon May 23 09:48:21 2011
* \brief The CDriverFieldtrip allows the acquisition server to acquire data from a Fieldtrip buffer.
*
* TODO: details
*
*/
class CDriverFieldtrip final : public IDriver
{
public:
CDriverFieldtrip(IDriverContext& ctx);
~CDriverFieldtrip() override;
const char* getName() override;
bool initialize(const uint32_t nSamplePerSentBlock, IDriverCallback& callback) override;
bool uninitialize() override;
bool start() override;
bool stop() override;
bool loop() override;
bool isConfigurable() override;
bool configure() override;
const IHeader* getHeader() override { return &m_header; }
void FreeResponse(message_t* response, const char* message);
protected:
bool requestHeader();
int requestChunk(CStimulationSet& oStimulationSet);
IDriverCallback* m_callback = nullptr;
CHeader m_header;
SettingsHelper m_settings;
uint32_t m_nSamplePerSentBlock = 0;
float* m_sample = nullptr;
uint32_t m_dataType = DATATYPE_UNKNOWN;
// Connection to Fieldtrip buffer
CString m_hostName = "localhost";
uint32_t m_portNumber = 1979;
int m_connectionID = -1;
uint32_t m_minSamples = 1;
// Avoid frequent memory allocation
message_t* m_waitDataRequest = nullptr;
message_t* m_getDataRequest = nullptr;
uint32_t m_nTotalSample = 0;
uint32_t m_waitingTimeMs = 0;
bool m_firstGetDataRequest = false;
bool m_correctNonIntegerSR = true; // ???
double m_realSampling = 0;
double m_diffPerSample = 0; // ???
double m_driftSinceLastCorrection = 0;
// count time lost for "get cpu time" :
//double m_mesureLostTime = 0;
//uint32_t m_mesureNumber = 0;
};
} // namespace AcquisitionServer
} // namespace OpenViBE
#endif // #if defined(TARGET_HAS_PThread)