init
This commit is contained in:
@@ -0,0 +1,61 @@
|
||||
PROJECT(openvibe-plugins-network-io)
|
||||
|
||||
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
|
||||
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
|
||||
|
||||
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl)
|
||||
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
|
||||
TARGET_LINK_LIBRARIES(${PROJECT_NAME}
|
||||
openvibe-module-labstreamlayer)
|
||||
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
|
||||
VERSION ${PROJECT_VERSION}
|
||||
SOVERSION ${PROJECT_VERSION_MAJOR}
|
||||
FOLDER ${PLUGINS_FOLDER}
|
||||
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
|
||||
|
||||
# ---------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------
|
||||
IF(WIN32)
|
||||
ADD_DEFINITIONS(-D_WIN32_WINNT=0x0501) # for boost::asio
|
||||
ENDIF(WIN32)
|
||||
|
||||
|
||||
INCLUDE("FindOpenViBE")
|
||||
INCLUDE("FindOpenViBECommon")
|
||||
INCLUDE("FindOpenViBEToolkit")
|
||||
INCLUDE("FindOpenViBEModuleSystem")
|
||||
INCLUDE("FindThirdPartyBoost")
|
||||
INCLUDE("FindThirdPartyBoost_System")
|
||||
INCLUDE("FindThirdPartyLSL")
|
||||
|
||||
#so that boost won't need to link against DateTime when using the interprocess communication library
|
||||
#shared memory writer box
|
||||
ADD_DEFINITIONS(-DBOOST_DATE_TIME_NO_LIB)
|
||||
|
||||
# ---------------------------------
|
||||
# Finds standard library winmm
|
||||
# Adds library to target
|
||||
# Adds include path
|
||||
# ---------------------------------
|
||||
IF(WIN32)
|
||||
FIND_LIBRARY(LIB_STANDARD_MODULE_WINMM winmm)
|
||||
IF(LIB_STANDARD_MODULE_WINMM)
|
||||
MESSAGE(STATUS " Found winmm...")
|
||||
TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${LIB_STANDARD_MODULE_WINMM})
|
||||
ELSE(LIB_STANDARD_MODULE_WINMM)
|
||||
MESSAGE(STATUS " FAILED to find winmm...")
|
||||
ENDIF(LIB_STANDARD_MODULE_WINMM)
|
||||
ENDIF(WIN32)
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Install files
|
||||
# -----------------------------
|
||||
INSTALL(TARGETS ${PROJECT_NAME}
|
||||
RUNTIME DESTINATION ${DIST_BINDIR}
|
||||
LIBRARY DESTINATION ${DIST_LIBDIR}
|
||||
ARCHIVE DESTINATION ${DIST_LIBDIR})
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_LSLExport LSL Export
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Description|
|
||||
This box forwards the signals and stimulations it gets to two corresponding LabStreamingLayer (LSL) streams.
|
||||
|
||||
The user can configure the names of the LSL streams. Each stream will get a randomized source ID (in 64bit hex).
|
||||
|
||||
The stimulations and signals output by the box are timestamped with LSL clock by default. If you would like them to be
|
||||
timestamped with local OpenViBE time, you can add the following line to the configuration file "openvibe.conf":
|
||||
|
||||
\b LSL_UseOVTimestamps \b = \b True
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Inputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Inputs|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Input1|
|
||||
The signal input stream.
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Input1|
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Input1|
|
||||
The stimulation input stream.
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Input1|
|
||||
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Settings|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Setting1|
|
||||
Name of the signal stream. Default is 'openvibeSignal'.
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Setting1|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Setting2|
|
||||
Name of the stimulation stream. Default is 'openvibeMarkers'.
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Setting2|
|
||||
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Examples|
|
||||
Easiest way to test the box is to use the Acquisition Server (AS) LabStreamingLayer (LSL) driver to read from the box. First, send signal to the box. Then connect to it using AS. Start AS playback. In another Designer tab, use the Network Acquisition box to read the data back from the Acquisition Server.
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_LSLExport_Miscellaneous|
|
||||
The box creates each LSL stream at the beginning of the playback, after it has received its corresponding OpenViBE stream header information. Hence, if either signal or stimulus socket
|
||||
of the box is not connected or does not receive a header, the box will not create the corresponding LSL stream.
|
||||
|
||||
Stream formats: The signal stream is continuous float. The marker stream is noncontinuous int, and each 64bit OpenViBE stimulation is truncated to an int marker.
|
||||
|
||||
This box should be compatible with the conventions used by OpenViBE Acquisition Server LabStreamingLayer (LSL) Driver, as well as its' LSL Output plugin.
|
||||
* |OVP_DocEnd_BoxAlgorithm_LSLExport_Miscellaneous|
|
||||
*/
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_SharedMemoryWriter Shared Memory Writer
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Description|
|
||||
The box creates (via boost interprocess library) a shared memory and within it uniquely named variables for each input.
|
||||
When receiving chunks on its inputs, the box will write them to shared memory so that they can be read by another process. The box can write stimuli and streamed matrices, which are transformed in a format that can be written into shared memory. Based on the input types, a metainfo variable will be created in shared memory that will specify which variables have which type in shared memory. This way the client knows what it will be reading.
|
||||
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Inputs|
|
||||
The supported input stream types are StreamedMatrix and Stimulations. The stream type of the input can be changed by the user and additional inputs can be added.
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Inputs|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Input1|
|
||||
Default streamed matrix input.
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Input1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Settings|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Setting1|
|
||||
Name of the shared memory to be created. Default to 'SharedMemory_P300Stimulator'
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Setting1|
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Examples|
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_SharedMemoryWriter_Miscellaneous|
|
||||
You can use the test_sharedmemory application to test the shared memory.
|
||||
* |OVP_DocEnd_BoxAlgorithm_SharedMemoryWriter_Miscellaneous|
|
||||
*/
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* \page BoxAlgorithm_TCPWriter TCP Writer
|
||||
__________________________________________________________________
|
||||
|
||||
Detailed description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Description|
|
||||
This box works as a TCP server that writes its input stream out to a TCP socket with minimal header information and encoding.
|
||||
|
||||
The main motivation for this box is to allow external applications to receive information in a simple manner from OpenViBE without
|
||||
requiring unusual dependencies such as VRPN.
|
||||
|
||||
Output:
|
||||
|
||||
1) If the outputs of the box are raw numeric values, the box first sends every connecting client eight variables of uint32_t: format version number (in network byte order), endianness of the stream (in network byte order, 0==unknown, 1==little, 2==big, 3==pdp), sampling frequency of the signal, the number of channels, the number of samples per chunk and three variables of padding, 8*4=32 bytes in total. The last 6 variables are in the byte order of the stream. Note that only those variables will be non-zero that are meaningful for the input in question.
|
||||
|
||||
Header layout as a table,
|
||||
|
||||
\verbatim
|
||||
| Name | Type | Bytes from start |
|
||||
| ------------------- | ------------- | ---------------- |
|
||||
| Format version | uint32_t | 0 |
|
||||
| Endianness | uint32_t | 4 |
|
||||
| Sampling frequency | uint32_t | 8 |
|
||||
| Number of channels | uint32_t | 12 |
|
||||
| Samples per chunk | uint32_t | 16 |
|
||||
| Reserved0 | uint32_t | 20 |
|
||||
| Reserved1 | uint32_t | 24 |
|
||||
| Reserved2 | uint32_t | 28 |
|
||||
\endverbatim
|
||||
|
||||
1b) If the output is chosen as hex string or descriptive string (these are valid for Stimulation input only), no header is sent.
|
||||
|
||||
2) After the possible global header, the data itself is sent. The data is a stream of double chunks for Signal and StreamedMatrix.
|
||||
Each chunk is a matrix [nChannels x nSamples], sent in row-major order, i.e. all samples for one channel are sent in a sequence (a row),
|
||||
then all samples of the next channel (next row), and so on. This is the same order that OpenViBE uses internally for signal chunks.
|
||||
|
||||
Signal/matrix data layout as a table (k = nSamples, n = nChannels),
|
||||
|
||||
\verbatim
|
||||
| Name | Type | Bytes from start |
|
||||
| -------------------- | ------------- | ------------------ |
|
||||
| Channel 1, sample 1 | double | 32 + (k*0+0)*8 |
|
||||
| Channel 1, sample 2 | double | 32 + (k*0+1)*8 |
|
||||
| ... | ... | ... |
|
||||
| Channel 1, sample k | double | 32 + (k*0+(k-1))*8 |
|
||||
| Channel 2, sample 1 | double | 32 + (k*1+0)*8 |
|
||||
| Channel 2, sample 2 | double | 32 + (k*1+1)*8 |
|
||||
| ... | ... | ... |
|
||||
| Channel 1, sample k+1 | double | 32 + (k*n+0)*8 |
|
||||
| ... | ... | .... |
|
||||
\endverbatim
|
||||
|
||||
For Stimulations, the data is a sequence of uint64_t if the user chooses raw output, or char strings otherwise.
|
||||
|
||||
Multiple clients can connect to the socket of the box. The box keeps sending data to
|
||||
each client until either the scenario is stopped or that client disconnects. When
|
||||
kernel calls box::process() at time t, all clients connected before or at t,
|
||||
get forwarded the chunks that are pending in box::process() at time t. Note that
|
||||
the information how long time has elapsed between the acquisition or scenario startup
|
||||
and the client connection is not currently relayed by TCP Writer.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Description|
|
||||
__________________________________________________________________
|
||||
|
||||
Inputs description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Inputs|
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Inputs|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Input1|
|
||||
The supported input stream types are StreamedMatrix, Signal and Stimulations. The stream type of the input can be changed by the user.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Input1|
|
||||
__________________________________________________________________
|
||||
|
||||
Settings description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Settings|
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Settings|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Setting1|
|
||||
Port denotes the TCP port that will accept the client connections. Default is 5678.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Setting1|
|
||||
*
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Setting2|
|
||||
If the input is Stimulations, this setting can change the format the stimulations are sent in to the TCP socket. The choices are raw uint64_t, hex string, or a descriptive string. For other inputs, this setting is ignored.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Setting2|
|
||||
*
|
||||
__________________________________________________________________
|
||||
|
||||
Examples description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Examples|
|
||||
There is an example application 'openvibe-examples-openvibe-to-tcpip' that can be used to receive data from this box.
|
||||
The application is intended to be used together with the 'tcp-writer.xml' box tutorial.
|
||||
|
||||
Another way to test the box is to connect to the socket of the box with a 'telnet' application and redirect the telnet output to a file.
|
||||
|
||||
It is also possible to use Acquisition Server's Generic Raw Telnet Reader to read data from the box. In that case, set Start Skip to 32 and header and footer skips to 0, and data format to double. Remember to also set the number of channels and the sampling frequency correctly. This information is not read from the stream by the Generic Raw Telnet Reader.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Examples|
|
||||
__________________________________________________________________
|
||||
|
||||
Miscellaneous description
|
||||
__________________________________________________________________
|
||||
|
||||
* |OVP_DocBegin_BoxAlgorithm_TCPWriter_Miscellaneous|
|
||||
The box performs no little-endian/big-endian conversions. The data is sent raw in the native format of the host system. The host endianness is encoded into the sent header.
|
||||
|
||||
The box sends signal data as chunks with the same chunk size as the stream has.
|
||||
|
||||
The box supports only 2 dimensional matrices. To send 1 dimensional matrix, you can try to upgrade it to 2 dimensions with Matrix Transpose box. The box cannot be used to send more than 2 dimensions presently.
|
||||
|
||||
The box writes to all the sockets synchronously in the process() call and drops no data. If the connection is too slow to accommodate the data flow, the box will lag.
|
||||
|
||||
Detected transmission errors will cause a disconnection of the client.
|
||||
|
||||
Streamed Matrix can be recognized from the TCPWriter header by the sampling rate 0. If the stream is a signal,
|
||||
the sampling rate is a positive number. Raw stimulation streams have channel and sample counts per buffer 0 as well.
|
||||
|
||||
Known issues: Note that it can be difficult to time-synchronize signals and stimulations exactly on the client side when the client receives data from
|
||||
two TCP Writer boxes. Maintaining such synchronization was not a design goal of this box. If you need synchronized streams, it is advised to build
|
||||
the connection with LabStreamingLayer: Use LSL Export box and include LSL support in the client.
|
||||
* |OVP_DocEnd_BoxAlgorithm_TCPWriter_Miscellaneous|
|
||||
*/
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
#ifdef TARGET_HAS_ThirdPartyLSL
|
||||
|
||||
/*
|
||||
* Notes: This code should be kept compatible with changes to LSL Input Driver and Output Plugin in OpenViBE Acquisition Server.
|
||||
*
|
||||
*/
|
||||
#include "ovpCBoxAlgorithmLSLExport.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
#include <system/ovCTime.h>
|
||||
#include <labstreamlayer/Utils.hpp>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
|
||||
bool CBoxAlgorithmLSLExport::initialize()
|
||||
{
|
||||
m_signalOutlet = nullptr;
|
||||
m_stimulusOutlet = nullptr;
|
||||
|
||||
m_signalDecoder.initialize(*this, 0);
|
||||
m_stimDecoder.initialize(*this, 1);
|
||||
|
||||
m_signalName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
m_markerName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
|
||||
// These are supposed to be unique, so we don't have them in the box config
|
||||
m_signalID = CIdentifier::random().toString();
|
||||
m_markerID = CIdentifier::random().toString();
|
||||
|
||||
while (m_markerID == m_signalID) { m_markerID = CIdentifier::random().toString(); } // very unlikely
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Will create streams [" << m_signalName << ", id " << m_signalID
|
||||
<< "] and [" << m_markerName << ", id " << m_markerID << "]\n";
|
||||
|
||||
m_useOVTimestamps = this->getConfigurationManager().expandAsBoolean("${LSL_UseOVTimestamps}", m_useOVTimestamps);
|
||||
return true;
|
||||
}
|
||||
/*******************************************************************************/
|
||||
|
||||
bool CBoxAlgorithmLSLExport::uninitialize()
|
||||
{
|
||||
m_signalDecoder.uninitialize();
|
||||
m_stimDecoder.uninitialize();
|
||||
|
||||
if (m_signalOutlet)
|
||||
{
|
||||
delete m_signalOutlet;
|
||||
m_signalOutlet = nullptr;
|
||||
}
|
||||
if (m_stimulusOutlet)
|
||||
{
|
||||
delete m_stimulusOutlet;
|
||||
m_stimulusOutlet = nullptr;
|
||||
}
|
||||
|
||||
m_buffer.clear();
|
||||
|
||||
return true;
|
||||
}
|
||||
/*******************************************************************************/
|
||||
|
||||
bool CBoxAlgorithmLSLExport::processInput(const size_t /*index*/)
|
||||
{
|
||||
// ready to process !
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
/*******************************************************************************/
|
||||
|
||||
bool CBoxAlgorithmLSLExport::process()
|
||||
{
|
||||
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
|
||||
// Process signals
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
|
||||
{
|
||||
if (m_startTime == CTime(0))
|
||||
{
|
||||
// This should be the ov time when acquisition client tagged the first chunk with [0, t], t=bufferSize/samplingRate.
|
||||
// As the true run time is not know here, we have to do with a slightly delayed estimate. (The delay is the duration
|
||||
// it took from stamping the first chunk to this point in the code)
|
||||
m_startTime = System::Time::zgetTime();
|
||||
}
|
||||
|
||||
m_signalDecoder.decode(i);
|
||||
if (m_signalDecoder.isHeaderReceived() && !m_signalOutlet)
|
||||
{
|
||||
const size_t nChannel = m_signalDecoder.getOutputMatrix()->getDimensionSize(0);
|
||||
const size_t samplesPerBlock = m_signalDecoder.getOutputMatrix()->getDimensionSize(1);
|
||||
const size_t frequency = m_signalDecoder.getOutputSamplingRate();
|
||||
|
||||
m_buffer.resize(nChannel);
|
||||
|
||||
// Open a signal stream
|
||||
lsl::stream_info signalInfo(m_signalName.toASCIIString(), "signal", int(nChannel), int(frequency), lsl::cf_float32, m_signalID.toASCIIString());
|
||||
|
||||
lsl::xml_element channels = signalInfo.desc().append_child("channels");
|
||||
//m_signalDecoder.getOutputMatrix()->getDimensionLabel(0, 1);
|
||||
|
||||
for (size_t c = 0; c < nChannel; ++c)
|
||||
{
|
||||
const char* name = m_signalDecoder.getOutputMatrix()->getDimensionLabel(0, c);
|
||||
channels.append_child("channel").append_child_value("label", name).append_child_value("unit", "unknown").append_child_value("type", "signal");
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
lsl::xml_element debug = signalInfo.desc().child("channels");
|
||||
if(debug.child("channel").child_value("label")) { std::cout << "channel label " << debug.child("channel").child_value("label") << "\n"; }
|
||||
#endif
|
||||
|
||||
// make a new outlet
|
||||
try { m_signalOutlet = new lsl::stream_outlet(signalInfo, samplesPerBlock); }
|
||||
catch (...)
|
||||
{
|
||||
this->getLogManager() << "Unable to create signal outlet\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_signalDecoder.isBufferReceived() && m_signalOutlet)
|
||||
{
|
||||
if (m_signalOutlet->have_consumers())
|
||||
{
|
||||
const CMatrix* matrix = m_signalDecoder.getOutputMatrix();
|
||||
const size_t nChannel = matrix->getDimensionSize(0);
|
||||
const size_t samplesPerBlock = matrix->getDimensionSize(1);
|
||||
const double* iBuffer = matrix->getBuffer();
|
||||
|
||||
const CTime chunkStartTime(boxContext.getInputChunkStartTime(0, i));
|
||||
const CTime chunkEndTime(boxContext.getInputChunkEndTime(0, i));
|
||||
|
||||
if (samplesPerBlock == 0)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Unable to process signal with 0 samples per buffer \n";
|
||||
return false;
|
||||
}
|
||||
// note: the step computed below should be exactly the same as could be obtained from the sampling rate
|
||||
|
||||
// n.b. this would work more "lsl-like" if the timestamps were the real acquisition timestamps
|
||||
if (m_useOVTimestamps)
|
||||
{
|
||||
const double sampleStepInSec =
|
||||
(chunkEndTime - chunkStartTime).toSeconds() / static_cast<double>(samplesPerBlock);
|
||||
const double chunkStartInSec = chunkStartTime.toSeconds();
|
||||
|
||||
for (size_t sample = 0; sample < samplesPerBlock; ++sample)
|
||||
{
|
||||
for (size_t channel = 0; channel < nChannel; ++channel)
|
||||
{
|
||||
m_buffer[channel] = static_cast<float>(iBuffer[channel * samplesPerBlock + sample]);
|
||||
}
|
||||
m_signalOutlet->push_sample(m_buffer, chunkStartInSec + sampleStepInSec * static_cast<double>(sample));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint64_t sampleStep = (chunkEndTime - chunkStartTime).time() / uint64_t(samplesPerBlock);
|
||||
for (size_t sample = 0; sample < samplesPerBlock; ++sample)
|
||||
{
|
||||
for (size_t channel = 0; channel < nChannel; ++channel)
|
||||
{
|
||||
m_buffer[channel] = static_cast<float>(iBuffer[channel * samplesPerBlock + sample]);
|
||||
}
|
||||
const double lslTime = OpenViBE::LabStreamLayer::getLSLRelativeTime(m_startTime + chunkStartTime + CTime(sample * sampleStep));
|
||||
m_signalOutlet->push_sample(m_buffer, lslTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_signalDecoder.isEndReceived())
|
||||
{
|
||||
if (m_signalOutlet)
|
||||
{
|
||||
delete m_signalOutlet;
|
||||
m_signalOutlet = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process stimuli -> LSL markers.
|
||||
// Note that stimuli with identifiers not fitting to int will be mangled by a static cast.
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(1); ++i)
|
||||
{
|
||||
if (m_startTime == CTime(0))
|
||||
{
|
||||
// Initialisation done here too as the box may only receive stimulations
|
||||
m_startTime = System::Time::zgetTime();
|
||||
}
|
||||
|
||||
m_stimDecoder.decode(i);
|
||||
if (m_stimDecoder.isHeaderReceived() && !m_stimulusOutlet)
|
||||
{
|
||||
// Open a stimulus stream
|
||||
lsl::stream_info info(m_markerName.toASCIIString(), "Markers", 1, lsl::IRREGULAR_RATE, lsl::cf_int32, m_markerID.toASCIIString());
|
||||
|
||||
info.desc().append_child("channels").append_child("channel").append_child_value("label", "Stimulations").append_child_value("type", "marker");
|
||||
|
||||
try { m_stimulusOutlet = new lsl::stream_outlet(info); }
|
||||
catch (...)
|
||||
{
|
||||
this->getLogManager() << "Unable to create marker outlet\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (m_stimDecoder.isBufferReceived() && m_stimulusOutlet)
|
||||
{
|
||||
// Output stimuli
|
||||
if (m_stimulusOutlet->have_consumers())
|
||||
{
|
||||
const IStimulationSet* stimSet = m_stimDecoder.getOutputStimulationSet();
|
||||
|
||||
for (size_t s = 0; s < stimSet->getStimulationCount(); ++s)
|
||||
{
|
||||
const int code = int(stimSet->getStimulationIdentifier(s));
|
||||
const double date = m_useOVTimestamps ? CTime(stimSet->getStimulationDate(s)).toSeconds()
|
||||
: OpenViBE::LabStreamLayer::getLSLRelativeTime(m_startTime + CTime(stimSet->getStimulationDate(s)));
|
||||
|
||||
m_stimulusOutlet->push_sample(&code, date);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_stimDecoder.isEndReceived())
|
||||
{
|
||||
if (m_stimulusOutlet)
|
||||
{
|
||||
delete m_stimulusOutlet;
|
||||
m_stimulusOutlet = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_HAS_ThirdPartyLSL
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <lsl_cpp.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
/**
|
||||
* \class CBoxAlgorithmLSLExport
|
||||
* \author Jussi T. Lindgren (Inria)
|
||||
* \date Fri Jan 30 09:55:22 2015
|
||||
* \brief The class CBoxAlgorithmLSLExport describes the box LSL Export.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmLSLExport final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool processInput(const size_t index) override;
|
||||
|
||||
bool process() override;
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_LSLExport)
|
||||
|
||||
protected:
|
||||
|
||||
// Decoders
|
||||
Toolkit::TStimulationDecoder<CBoxAlgorithmLSLExport> m_stimDecoder;
|
||||
Toolkit::TSignalDecoder<CBoxAlgorithmLSLExport> m_signalDecoder;
|
||||
|
||||
lsl::stream_outlet* m_signalOutlet = nullptr;
|
||||
lsl::stream_outlet* m_stimulusOutlet = nullptr;
|
||||
|
||||
std::vector<float> m_buffer;
|
||||
|
||||
CString m_signalName;
|
||||
CString m_signalID;
|
||||
CString m_markerName;
|
||||
CString m_markerID;
|
||||
|
||||
bool m_useOVTimestamps = false;
|
||||
CTime m_startTime = CTime(0);
|
||||
};
|
||||
|
||||
class CBoxAlgorithmLSLExportListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
};
|
||||
|
||||
/**
|
||||
* \class CBoxAlgorithmLSLExportDesc
|
||||
* \author Jussi T. Lindgren (Inria)
|
||||
* \date Fri Jan 30 09:55:22 2015
|
||||
* \brief Descriptor of the box LSL Export.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmLSLExportDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { }
|
||||
|
||||
CString getName() const override { return CString("LSL Export"); }
|
||||
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("Send input stream out via LabStreamingLayer (LSL)"); }
|
||||
CString getDetailedDescription() const override { return CString("\n"); }
|
||||
CString getCategory() const override { return CString("Acquisition and network IO"); }
|
||||
CString getVersion() const override { return CString("0.1"); }
|
||||
CString getStockItemName() const override { return CString("gtk-connect"); }
|
||||
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_LSLExport; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmLSLExport; }
|
||||
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmLSLExportListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Input signal", OV_TypeId_Signal);
|
||||
prototype.addInput("Input stimulations", OV_TypeId_Stimulations);
|
||||
|
||||
prototype.addSetting("Signal stream", OV_TypeId_String, "openvibeSignal");
|
||||
prototype.addSetting("Marker stream", OV_TypeId_String, "openvibeMarkers");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_LSLExportDesc)
|
||||
};
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_Boost
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
#include "ovpCBoxAlgorithmSharedMemoryWriter.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <boost/interprocess/sync/scoped_lock.hpp>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileReadingAndWriting {
|
||||
|
||||
//struct timeval currentLTime;
|
||||
|
||||
#define time2ms(x, y) ((x) * 1000 + (y) / 1000.0) + 0.5
|
||||
|
||||
bool CBoxAlgorithmSharedMemoryWriter::initialize()
|
||||
{
|
||||
//remove and create shared memory
|
||||
m_sharedMemoryName = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0); // can be local variable
|
||||
boost::interprocess::shared_memory_object::remove(m_sharedMemoryName.toASCIIString());
|
||||
m_sharedMemoryArray = boost::interprocess::managed_shared_memory(boost::interprocess::create_only, m_sharedMemoryName.toASCIIString(), 655360);
|
||||
|
||||
//remove and create mutex
|
||||
m_mutexName = m_sharedMemoryName + CString("_Mutex");
|
||||
boost::interprocess::named_mutex::remove(m_mutexName.toASCIIString());
|
||||
m_mutex = new boost::interprocess::named_mutex(boost::interprocess::open_or_create, m_mutexName.toASCIIString());
|
||||
|
||||
//create shared vector for meta info (type and name)
|
||||
const ShmemAllocatorMetaInfo metaInfo(m_sharedMemoryArray.get_segment_manager());
|
||||
MyVectorMetaInfo* metaInfoVector = m_sharedMemoryArray.construct<MyVectorMetaInfo>("MetaInfo")(std::less<ShmString>(), metaInfo);
|
||||
const StringAllocator instString(m_sharedMemoryArray.get_segment_manager());
|
||||
|
||||
//fill meta info vector and create shared vector variable for the appropriate types
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
CIdentifier typeID;
|
||||
std::ostringstream convert; // stream used for the conversion
|
||||
convert << i;
|
||||
|
||||
boxContext.getInputType(i, typeID);
|
||||
if (typeID == OVTK_TypeId_StreamedMatrix)
|
||||
{
|
||||
m_decoders.push_back(new Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSharedMemoryWriter>());
|
||||
ShmString name("Matrix", instString);
|
||||
name += ShmString(convert.str().c_str(), instString);
|
||||
metaInfoVector->insert(std::make_pair(name, typeID));
|
||||
|
||||
const ShmemAllocatorMatrix inst(m_sharedMemoryArray.get_segment_manager());
|
||||
m_streamedMatrices.push_back(m_sharedMemoryArray.construct<MyVectorStreamedMatrix>(name.c_str())(inst));
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Constructed variable in shared memory of type matrix with name " << name.c_str() << "\n";
|
||||
}
|
||||
else if (typeID == OVTK_TypeId_Stimulations)
|
||||
{
|
||||
m_decoders.push_back(new Toolkit::TStimulationDecoder<CBoxAlgorithmSharedMemoryWriter>());
|
||||
ShmString name("Stimuli", instString);
|
||||
name += ShmString(convert.str().c_str(), instString);
|
||||
metaInfoVector->insert(std::make_pair(name, typeID));
|
||||
|
||||
const ShmemAllocatorStimulation inst(m_sharedMemoryArray.get_segment_manager());
|
||||
m_stimSets.push_back(m_sharedMemoryArray.construct<MyVectorStimulation>(name.c_str())(inst));
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Constructed variable in shared memory of type stimulation with name " << name.c_str() << "\n";
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Warning << "Input type " << typeID << " is not supported\n"; }
|
||||
m_decoders.back()->initialize(*this, i);
|
||||
}
|
||||
|
||||
//m_inputCounter = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmSharedMemoryWriter::uninitialize()
|
||||
{
|
||||
m_sharedMemoryArray.destroy<MyVectorMetaInfo>("MetaInfo");
|
||||
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
for (int i = int(boxContext.getInputCount() - 1); i >= 0; i--)
|
||||
{
|
||||
CIdentifier typeID;
|
||||
boxContext.getInputType(i, typeID);
|
||||
const std::string idxStr = std::to_string(i);
|
||||
|
||||
if (typeID == OVTK_TypeId_StreamedMatrix)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Uninitialize shared memory variable associated with input " << i << "\n";
|
||||
for (size_t j = 0; j < m_streamedMatrices.back()->size(); ++j)
|
||||
{
|
||||
m_sharedMemoryArray.deallocate(m_streamedMatrices.back()->at(j)->data.get());
|
||||
m_sharedMemoryArray.deallocate(m_streamedMatrices.back()->at(j).get());
|
||||
}
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Deallocated shared memory for variable with name Matrix " << idxStr << "\n";
|
||||
m_streamedMatrices.back()->clear();
|
||||
m_sharedMemoryArray.destroy<MyVectorStreamedMatrix>(("Matrix " + idxStr).c_str());
|
||||
|
||||
//TODO: pop_back()?
|
||||
}
|
||||
else if (typeID == OVTK_TypeId_Stimulations)
|
||||
{
|
||||
m_stimSets.back()->clear();
|
||||
m_sharedMemoryArray.destroy<MyVectorStimulation>(("Stimuli" + idxStr).c_str());
|
||||
}
|
||||
}
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Destroyed all shared variables associated with input" << "\n";
|
||||
boost::interprocess::shared_memory_object::remove(m_sharedMemoryName.toASCIIString());
|
||||
boost::interprocess::named_mutex::remove(m_mutexName.toASCIIString());
|
||||
delete m_mutex;
|
||||
for (size_t i = 0; i < boxContext.getInputCount(); ++i)
|
||||
{
|
||||
m_decoders[i]->uninitialize();
|
||||
delete m_decoders[i];
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmSharedMemoryWriter::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmSharedMemoryWriter::process()
|
||||
{
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
const Kernel::IBox& staticBoxContext = this->getStaticBoxContext();
|
||||
|
||||
size_t iStimulusCounter = 0;
|
||||
size_t iMatrixCounter = 0;
|
||||
|
||||
for (size_t i = 0; i < staticBoxContext.getInputCount(); ++i)
|
||||
{
|
||||
CIdentifier typeID;
|
||||
staticBoxContext.getInputType(i, typeID);
|
||||
if (typeID == OVTK_TypeId_Stimulations)
|
||||
{
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
|
||||
{
|
||||
//m_oAlgo0_StimulationDecoder.decode(j,i, false);
|
||||
m_decoders[i]->decode(j, false);
|
||||
//CStimulationSet stimSet;
|
||||
IStimulationSet* stimSet = dynamic_cast<Toolkit::TStimulationDecoder<CBoxAlgorithmSharedMemoryWriter>*>(m_decoders[i])->
|
||||
getOutputStimulationSet();
|
||||
//Toolkit::StimulationSet::copy(l_oStimSet, *m_oAlgo0_StimulationDecoder.getOutputStimulationSet());
|
||||
if (m_decoders[i]->isHeaderReceived()) { boxContext.markInputAsDeprecated(i, j); }
|
||||
if (m_decoders[i]->isBufferReceived())
|
||||
{
|
||||
if (stimSet->getStimulationCount() > 0)
|
||||
{
|
||||
boost::interprocess::scoped_lock<boost::interprocess::named_mutex> lock(*m_mutex, boost::interprocess::try_to_lock);
|
||||
if (lock)
|
||||
{
|
||||
for (size_t k = 0; k < stimSet->getStimulationCount(); ++k)
|
||||
{
|
||||
// @fixme m_stimSets defined as 32bit will only work correctly with stimuli that fit in 32bits (OV stimulations can be 64bit)
|
||||
m_stimSets[iStimulusCounter]->push_back(size_t(stimSet->getStimulationIdentifier(k)));
|
||||
this->getLogManager() << Kernel::LogLevel_Info << "Added stimulus with id " << m_stimSets[iStimulusCounter]->back() <<
|
||||
" to shared memory variable\n";
|
||||
}
|
||||
boxContext.markInputAsDeprecated(i, j);
|
||||
}
|
||||
else { this->getLogManager() << Kernel::LogLevel_Warning << "Shared memory writer could not lock mutex\n"; }
|
||||
}
|
||||
else { boxContext.markInputAsDeprecated(i, j); }
|
||||
}
|
||||
if (m_decoders[i]->isEndReceived()) { boxContext.markInputAsDeprecated(i, j); }
|
||||
}
|
||||
iStimulusCounter++;
|
||||
}
|
||||
else if (typeID == OVTK_TypeId_StreamedMatrix)
|
||||
{
|
||||
for (size_t j = 0; j < boxContext.getInputChunkCount(i); ++j)
|
||||
{
|
||||
m_decoders[i]->decode(j, false);
|
||||
CMatrix* matrix = dynamic_cast<Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmSharedMemoryWriter>*>(m_decoders[i])->getOutputMatrix();
|
||||
if (m_decoders[i]->isHeaderReceived()) { boxContext.markInputAsDeprecated(i, j); }
|
||||
if (m_decoders[i]->isBufferReceived())
|
||||
{
|
||||
boost::interprocess::scoped_lock<boost::interprocess::named_mutex> lock(*m_mutex, boost::interprocess::try_to_lock);
|
||||
if (lock)
|
||||
{
|
||||
boost::interprocess::offset_ptr<SMatrix> shmMatrix = static_cast<SMatrix*>(m_sharedMemoryArray.allocate(sizeof(SMatrix)));
|
||||
|
||||
//if we receive a vector (second dimension to 0) we force to one otherwise no memory will be allocated
|
||||
const size_t row = (matrix->getDimensionSize(1) == 0) ? 1 : matrix->getDimensionSize(1);
|
||||
|
||||
shmMatrix->rowDim = row;
|
||||
shmMatrix->colDim = matrix->getDimensionSize(0);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "dimensions " << matrix->getDimensionCount() << "row " << matrix->getDimensionSize(1)
|
||||
<< " by " << " columns " << matrix->getDimensionSize(0) << "\n";
|
||||
|
||||
shmMatrix->data = static_cast<double*>(m_sharedMemoryArray.allocate(matrix->getDimensionSize(0) * row * sizeof(double)));
|
||||
for (size_t k = 0; k < matrix->getBufferElementCount(); ++k) { *(shmMatrix->data + k) = *(matrix->getBuffer() + k); }
|
||||
|
||||
m_streamedMatrices[iMatrixCounter]->push_back(shmMatrix);
|
||||
|
||||
boxContext.markInputAsDeprecated(i, j);
|
||||
}
|
||||
}
|
||||
if (m_decoders[i]->isEndReceived()) { boxContext.markInputAsDeprecated(i, j); }
|
||||
}
|
||||
iMatrixCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace FileReadingAndWriting
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
#pragma once
|
||||
|
||||
//You may have to change this path to match your folder organisation
|
||||
#include "../ovp_defines.h"
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
//#include <pair>
|
||||
#include <vector>
|
||||
#include <boost/interprocess/managed_shared_memory.hpp>
|
||||
#include <boost/interprocess/containers/vector.hpp>
|
||||
#include <boost/interprocess/containers/map.hpp>
|
||||
#include <boost/interprocess/allocators/allocator.hpp>
|
||||
#include <boost/interprocess/sync/named_mutex.hpp>
|
||||
#include <boost/interprocess/containers/string.hpp>
|
||||
|
||||
// The unique identifiers for the box and its descriptor.
|
||||
// Identifier are randomly chosen by the skeleton-generator.
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace FileReadingAndWriting {
|
||||
/**
|
||||
* \class CBoxAlgorithmSharedMemoryWriter
|
||||
* \author Dieter Devlaminck (INRIA)
|
||||
* \date Thu Jan 17 13:34:58 2013
|
||||
* \brief The class CBoxAlgorithmSharedMemoryWriter describes the box SharedMemoryWriter.
|
||||
*
|
||||
*/
|
||||
|
||||
struct SMatrix
|
||||
{
|
||||
size_t rowDim;
|
||||
size_t colDim;
|
||||
boost::interprocess::offset_ptr<double> data;
|
||||
};
|
||||
|
||||
typedef boost::interprocess::allocator<char, boost::interprocess::managed_shared_memory::segment_manager> CharAllocator;
|
||||
typedef boost::interprocess::basic_string<char, std::char_traits<char>, CharAllocator> ShmString;
|
||||
typedef boost::interprocess::allocator<ShmString, boost::interprocess::managed_shared_memory::segment_manager> StringAllocator;
|
||||
//typedef vector<ShmString, StringAllocator> MyShmStringVector;
|
||||
typedef boost::interprocess::allocator<std::pair<const ShmString, CIdentifier>, boost::interprocess::managed_shared_memory::segment_manager>
|
||||
ShmemAllocatorMetaInfo;
|
||||
typedef boost::interprocess::map<ShmString, CIdentifier, std::less<ShmString>, ShmemAllocatorMetaInfo> MyVectorMetaInfo;
|
||||
|
||||
typedef boost::interprocess::allocator<size_t, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocatorStimulation;
|
||||
typedef boost::interprocess::allocator<boost::interprocess::offset_ptr<SMatrix>, boost::interprocess::managed_shared_memory::segment_manager>
|
||||
ShmemAllocatorMatrix;
|
||||
typedef boost::interprocess::vector<size_t, ShmemAllocatorStimulation> MyVectorStimulation;
|
||||
typedef boost::interprocess::vector<boost::interprocess::offset_ptr<SMatrix>, ShmemAllocatorMatrix> MyVectorStreamedMatrix;
|
||||
|
||||
class CBoxAlgorithmSharedMemoryWriter final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { delete this; }
|
||||
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
|
||||
bool processInput(const size_t index) override;
|
||||
|
||||
bool process() override;
|
||||
|
||||
// As we do with any class in openvibe, we use the macro below
|
||||
// to associate this box to an unique identifier.
|
||||
// The inheritance information is also made available,
|
||||
// as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_SharedMemoryWriter)
|
||||
|
||||
protected:
|
||||
std::vector<Toolkit::TDecoder<CBoxAlgorithmSharedMemoryWriter>*> m_decoders;
|
||||
CString m_sharedMemoryName;
|
||||
boost::interprocess::managed_shared_memory m_sharedMemoryArray;
|
||||
|
||||
private:
|
||||
CIdentifier m_typeID = CIdentifier::undefined();
|
||||
CString m_mutexName;
|
||||
boost::interprocess::named_mutex* m_mutex = nullptr;
|
||||
std::vector<MyVectorStimulation*> m_stimSets;
|
||||
std::vector<MyVectorStreamedMatrix*> m_streamedMatrices;
|
||||
};
|
||||
|
||||
|
||||
// The box listener can be used to call specific callbacks whenever the box structure changes : input added, name changed, etc.
|
||||
// Please uncomment below the callbacks you want to use.
|
||||
class CBoxAlgorithmSharedMemoryWriterListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
|
||||
bool onInputAdded(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
|
||||
bool onInputRemoved(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
|
||||
bool onInputTypeChanged(Kernel::IBox& /*box*/, const size_t /*index*/) override { return true; }
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* \class CBoxAlgorithmSharedMemoryWriterDesc
|
||||
* \author Dieter Devlaminck (INRIA)
|
||||
* \date Thu Jan 17 13:34:58 2013
|
||||
* \brief Descriptor of the box SharedMemoryWriter.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmSharedMemoryWriterDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { }
|
||||
|
||||
CString getName() const override { return CString("SharedMemoryWriter"); }
|
||||
CString getAuthorName() const override { return CString("Dieter Devlaminck"); }
|
||||
CString getAuthorCompanyName() const override { return CString("INRIA"); }
|
||||
CString getShortDescription() const override { return CString("Stream input to shared memory"); }
|
||||
|
||||
CString getDetailedDescription() const override
|
||||
{
|
||||
return CString(
|
||||
"The box writes input to shared memory so that it can be read by another process. Stimuli and streamed matrices are supported, and transformed into a format that can be written into shared memory. Based on the input types, a metainfo variable will be created in shared memory that will specify which variables have which type. This way the client can know what it will be reading.");
|
||||
}
|
||||
|
||||
CString getCategory() const override { return CString("File reading and writing"); }
|
||||
CString getVersion() const override { return CString("1.0"); }
|
||||
CString getStockItemName() const override { return CString(""); }
|
||||
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_SharedMemoryWriter; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmSharedMemoryWriter; }
|
||||
|
||||
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmSharedMemoryWriterListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("prediction1",OV_TypeId_StreamedMatrix);
|
||||
//prototype.addInput("prediction2",OV_TypeId_Stimulations);
|
||||
|
||||
|
||||
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
|
||||
prototype.addFlag(Kernel::BoxFlag_CanAddInput);
|
||||
//prototype.addFlag(Kernel::BoxFlag_CanRemoveInput);
|
||||
|
||||
prototype.addSetting("SharedMemoryName",OV_TypeId_String, "SharedMemory_P300Stimulator");
|
||||
|
||||
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
|
||||
|
||||
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
|
||||
prototype.addInputSupport(OV_TypeId_Stimulations);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SharedMemoryWriterDesc)
|
||||
};
|
||||
} // namespace FileReadingAndWriting
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
#ifdef TARGET_HAS_Boost
|
||||
|
||||
#include "ovpCBoxAlgorithmTCPWriter.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <iostream>
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/enable_shared_from_this.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/detail/endian.hpp>
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
|
||||
using boost::asio::ip::tcp;
|
||||
|
||||
void CBoxAlgorithmTCPWriter::startAccept()
|
||||
{
|
||||
tcp::socket* socket = new tcp::socket(m_acceptor->get_io_service());
|
||||
|
||||
// Since startAccept will only be called inside ioService.poll(), there is no need to access control m_sockets
|
||||
m_sockets.push_back(socket);
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "We are now using " << m_sockets.size() << " socket(s)\n";
|
||||
|
||||
m_acceptor->async_accept(*socket, boost::bind(&CBoxAlgorithmTCPWriter::handleAccept, this, boost::asio::placeholders::error, socket));
|
||||
}
|
||||
|
||||
void CBoxAlgorithmTCPWriter::handleAccept(const boost::system::error_code& ec, tcp::socket* pSocket)
|
||||
{
|
||||
if (!m_acceptor->is_open())
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "handleAccept() was called with acceptor already closed\n";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ec)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Handling a new incoming connection\n";
|
||||
|
||||
// Send the known configuration to the client
|
||||
if (m_activeDecoder != &m_stimulationDecoder || m_outputStyle == TCPWRITER_RAW)
|
||||
{
|
||||
try
|
||||
{
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_rawVersion), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_endianness), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_frequency), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_nChannels), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_nSamplesPerChunk), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_reserved0), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_reserved1), sizeof(uint32_t)));
|
||||
write(*pSocket, boost::asio::buffer(static_cast<void*>(&m_reserved2), sizeof(uint32_t)));
|
||||
}
|
||||
catch (boost::system::system_error& error)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Issue '" << error.code().message() << "' with writing header to client\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// @fixme should the socket be closed in this case?
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Issue '" << ec.message() << "' with accepting a connection.\n";
|
||||
}
|
||||
// Already schedule the accepting of the next connection
|
||||
startAccept();
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmTCPWriter::initialize()
|
||||
{
|
||||
const Kernel::IBox& boxContext = this->getStaticBoxContext();
|
||||
|
||||
boxContext.getInputType(0, m_inputType);
|
||||
if (m_inputType == OV_TypeId_StreamedMatrix) { m_activeDecoder = &m_matrixDecoder; }
|
||||
else if (m_inputType == OV_TypeId_Signal) { m_activeDecoder = &m_signalDecoder; }
|
||||
else { m_activeDecoder = &m_stimulationDecoder; }
|
||||
m_activeDecoder->initialize(*this, 0);
|
||||
|
||||
const uint64_t port = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
|
||||
m_outputStyle = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
|
||||
|
||||
m_rawVersion = htonl(1); // TCP Writer output format version
|
||||
#if defined(BOOST_LITTLE_ENDIAN)
|
||||
m_endianness = htonl(1);
|
||||
#elif defined(BOOST_BIG_ENDIAN)
|
||||
m_endianness = htonl(2);
|
||||
#elif defined(BOOST_PDP_ENDIAN)
|
||||
m_endianness = htonl(3);
|
||||
#else
|
||||
m_endianness = htonl(0);
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Platform endianness was not recognized\n";
|
||||
#endif
|
||||
|
||||
m_frequency = 0;
|
||||
m_nChannels = 0;
|
||||
m_nSamplesPerChunk = 0;
|
||||
m_reserved0 = 0;
|
||||
m_reserved1 = 0;
|
||||
m_reserved2 = 0;
|
||||
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Setting up an acceptor at port " << port << "\n";
|
||||
|
||||
try
|
||||
{
|
||||
#ifdef TARGET_OS_Windows
|
||||
// On Windows, unless we deny reuse_addr, it seems several different servers can bind to socket. This is not what we want.
|
||||
m_acceptor = new tcp::acceptor(m_ioService, tcp::endpoint(tcp::v4(), uint32_t(port)), false);
|
||||
#else
|
||||
// On Linux, unless we allow reuse_addr, disconnection may set the socket to TIME_WAIT state and prevent opening it again until that state expires
|
||||
m_acceptor = new tcp::acceptor(m_ioService, tcp::endpoint(tcp::v4(), uint32_t(port)), true);
|
||||
#endif
|
||||
}
|
||||
catch (boost::system::system_error& error)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Got error '" << error.code().message() << "' allocating acceptor to port " << port << "\n";
|
||||
m_activeDecoder->uninitialize();
|
||||
m_activeDecoder = nullptr;
|
||||
m_acceptor = nullptr; // if new throws, deleting the returned m_acceptor causes problems on Linux. So we NULL it.
|
||||
return false;
|
||||
}
|
||||
|
||||
const boost::asio::socket_base::linger option(true, 0);
|
||||
m_acceptor->set_option(option);
|
||||
startAccept();
|
||||
m_ioService.poll();
|
||||
return true;
|
||||
}
|
||||
/*******************************************************************************/
|
||||
|
||||
bool CBoxAlgorithmTCPWriter::uninitialize()
|
||||
{
|
||||
if (m_activeDecoder)
|
||||
{
|
||||
m_activeDecoder->uninitialize();
|
||||
m_activeDecoder = nullptr;
|
||||
}
|
||||
|
||||
for (tcp::socket* sock : m_sockets)
|
||||
{
|
||||
if (sock->is_open())
|
||||
{
|
||||
try
|
||||
{
|
||||
sock->shutdown(boost::asio::socket_base::shutdown_both);
|
||||
sock->close();
|
||||
}
|
||||
catch (boost::system::system_error& error)
|
||||
{
|
||||
// Just report...
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Error in uninitialize() socket shutdown/close: '" << error.code().message() << "'\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
m_ioService.poll();
|
||||
m_ioService.stop();
|
||||
|
||||
for (tcp::socket* sock : m_sockets) { delete sock; }
|
||||
m_sockets.clear();
|
||||
|
||||
delete m_acceptor;
|
||||
m_acceptor = nullptr;
|
||||
|
||||
return true;
|
||||
}
|
||||
/*******************************************************************************/
|
||||
|
||||
bool CBoxAlgorithmTCPWriter::processInput(const size_t /*index*/)
|
||||
{
|
||||
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
|
||||
return true;
|
||||
}
|
||||
/*******************************************************************************/
|
||||
|
||||
bool CBoxAlgorithmTCPWriter::sendToClients(const void* buffer, const size_t size)
|
||||
{
|
||||
if (size == 0 || buffer == nullptr)
|
||||
{
|
||||
// Nothing to send, shouldn't happen
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Asked to send an empty buffer to clients (shouldn't happen)\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = m_sockets.begin();
|
||||
while (it != m_sockets.end())
|
||||
{
|
||||
tcp::socket* tmpSock = (*it);
|
||||
bool hadError = false;
|
||||
if (tmpSock->is_open())
|
||||
{
|
||||
try { write(*tmpSock, boost::asio::buffer(buffer, size)); }
|
||||
catch (boost::system::system_error& error)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Got error '" << error.code().message() << "' while trying to write to socket\n";
|
||||
hadError = true;
|
||||
}
|
||||
}
|
||||
if (hadError)
|
||||
{
|
||||
// Close the socket
|
||||
this->getLogManager() << Kernel::LogLevel_Debug << "Closing the socket\n";
|
||||
try
|
||||
{
|
||||
tmpSock->shutdown(boost::asio::socket_base::shutdown_both);
|
||||
tmpSock->close();
|
||||
}
|
||||
catch (boost::system::system_error& error)
|
||||
{
|
||||
// Just report...
|
||||
this->getLogManager() << Kernel::LogLevel_Warning << "Error while socket shutdown/close: '" << error.code().message() << "'\n";
|
||||
}
|
||||
m_ioService.poll();
|
||||
delete tmpSock;
|
||||
it = m_sockets.erase(it);
|
||||
}
|
||||
else { ++it; }
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CBoxAlgorithmTCPWriter::process()
|
||||
{
|
||||
// the dynamic box context describes the current state of the box inputs and outputs (i.e. the chunks)
|
||||
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
|
||||
|
||||
// Process the asio loop once (e.g. see if there's new connections)
|
||||
m_ioService.poll();
|
||||
|
||||
for (size_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
|
||||
{
|
||||
m_activeDecoder->decode(i);
|
||||
if (m_activeDecoder->isHeaderReceived())
|
||||
{
|
||||
// Matrix part
|
||||
if (m_activeDecoder == &m_matrixDecoder || m_activeDecoder == &m_signalDecoder)
|
||||
{
|
||||
// Casting to base class, ok
|
||||
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTCPWriter>* decoder = (Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTCPWriter>*)(m_activeDecoder);
|
||||
|
||||
const size_t nDim = decoder->getOutputMatrix()->getDimensionCount();
|
||||
switch (nDim)
|
||||
{
|
||||
case 0:
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Nothing to send, zero size matrix stream received\n";
|
||||
return false;
|
||||
case 1:
|
||||
// Ok, this is a vector, openvibe style. Interpret it as 1 channel row vector.
|
||||
m_nChannels = 1;
|
||||
m_nSamplesPerChunk = decoder->getOutputMatrix()->getDimensionSize(0);
|
||||
break;
|
||||
case 2:
|
||||
m_nChannels = decoder->getOutputMatrix()->getDimensionSize(0);
|
||||
m_nSamplesPerChunk = decoder->getOutputMatrix()->getDimensionSize(1);
|
||||
break;
|
||||
default:
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Only 1 and 2 dimensional matrices are supported\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Conformance checking for all matrix based streams
|
||||
if (m_nChannels == 0 || m_nSamplesPerChunk == 0)
|
||||
{
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "For matrix-like inputs, both input dimensions must be larger than 0\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Signal specific part
|
||||
if (m_activeDecoder == &m_signalDecoder) { m_frequency = size_t(m_signalDecoder.getOutputSamplingRate()); }
|
||||
|
||||
//if (m_activeDecoder == &m_stimDecoder) { } // Stimulus, do nothing
|
||||
}
|
||||
if (m_activeDecoder->isBufferReceived())
|
||||
{
|
||||
if (m_activeDecoder == &m_matrixDecoder)
|
||||
{
|
||||
const CMatrix* matrix = m_matrixDecoder.getOutputMatrix();
|
||||
|
||||
sendToClients((void*)matrix->getBuffer(), matrix->getBufferElementCount() * sizeof(double));
|
||||
}
|
||||
else if (m_activeDecoder == &m_signalDecoder)
|
||||
{
|
||||
const CMatrix* matrix = m_signalDecoder.getOutputMatrix();
|
||||
|
||||
sendToClients((void*)matrix->getBuffer(), matrix->getBufferElementCount() * sizeof(double));
|
||||
}
|
||||
else // stimulus
|
||||
{
|
||||
const IStimulationSet* stimSet = m_stimulationDecoder.getOutputStimulationSet();
|
||||
for (size_t j = 0; j < stimSet->getStimulationCount(); ++j)
|
||||
{
|
||||
const uint64_t id = stimSet->getStimulationIdentifier(j);
|
||||
// uint64_t date = stimSet->getStimulationDate(j);
|
||||
this->getLogManager() << Kernel::LogLevel_Trace << "Sending out " << id << "\n";
|
||||
|
||||
switch (m_outputStyle)
|
||||
{
|
||||
case TCPWRITER_RAW:
|
||||
sendToClients((void*)&id, sizeof(id));
|
||||
break;
|
||||
case TCPWRITER_HEX:
|
||||
{
|
||||
CString tmp = CIdentifier(id).toString() + CString("\r\n");
|
||||
const char* ptr = tmp.toASCIIString();
|
||||
sendToClients((void*)ptr, strlen(ptr));
|
||||
}
|
||||
break;
|
||||
case TCPWRITER_STRING:
|
||||
{
|
||||
CString tmp = this->getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, id);
|
||||
if (tmp == CString("")) { tmp = CString("Unregistered_stimulus ") + CIdentifier(id).toString(); }
|
||||
tmp = tmp + CString("\r\n");
|
||||
|
||||
const char* ptr = tmp.toASCIIString();
|
||||
sendToClients((void*)ptr, strlen(ptr));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this->getLogManager() << Kernel::LogLevel_Error << "Unknown stimulus output style\n";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (m_activeDecoder->isEndReceived()) { }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif // TARGET_HAS_Boost
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef TARGET_HAS_Boost
|
||||
|
||||
#include "../ovp_defines.h"
|
||||
|
||||
#include <openvibe/ov_all.h>
|
||||
#include <toolkit/ovtk_all.h>
|
||||
|
||||
#include <ctime>
|
||||
#include <boost/asio.hpp>
|
||||
|
||||
enum { TCPWRITER_RAW, TCPWRITER_HEX, TCPWRITER_STRING }; // stimulation output types
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
namespace NetworkIO {
|
||||
/**
|
||||
* \class CBoxAlgorithmTCPWriter
|
||||
* \author Jussi T. Lindgren (Inria)
|
||||
* \date Wed Sep 11 12:55:22 2013
|
||||
* \brief The class CBoxAlgorithmTCPWriter describes the box TCP Writer.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmTCPWriter final : virtual public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
|
||||
{
|
||||
public:
|
||||
void release() override { delete this; }
|
||||
|
||||
bool initialize() override;
|
||||
bool uninitialize() override;
|
||||
|
||||
//Here is the different process callbacks possible
|
||||
// - On clock ticks :
|
||||
//virtual bool processClock(Kernel::CMessageClock& msg);
|
||||
// - On new input received (the most common behaviour for signal processing) :
|
||||
bool processInput(const size_t index) override;
|
||||
|
||||
// If you want to use processClock, you must provide the clock frequency.
|
||||
//virtual uint64_t getClockFrequency();
|
||||
|
||||
bool process() override;
|
||||
|
||||
// As we do with any class in openvibe, we use the macro below
|
||||
// to associate this box to an unique identifier.
|
||||
// The inheritance information is also made available,
|
||||
// as we provide the superclass Toolkit::TBoxAlgorithm < IBoxAlgorithm >
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_BoxAlgorithm_TCPWriter)
|
||||
|
||||
protected:
|
||||
|
||||
bool sendToClients(const void* buffer, size_t size);
|
||||
|
||||
// Stream decoder
|
||||
Toolkit::TStimulationDecoder<CBoxAlgorithmTCPWriter> m_stimulationDecoder;
|
||||
Toolkit::TStreamedMatrixDecoder<CBoxAlgorithmTCPWriter> m_matrixDecoder;
|
||||
Toolkit::TSignalDecoder<CBoxAlgorithmTCPWriter> m_signalDecoder;
|
||||
Toolkit::TDecoder<CBoxAlgorithmTCPWriter>* m_activeDecoder = nullptr;
|
||||
|
||||
boost::asio::io_service m_ioService;
|
||||
boost::asio::ip::tcp::acceptor* m_acceptor = nullptr;
|
||||
std::vector<boost::asio::ip::tcp::socket*> m_sockets;
|
||||
|
||||
uint64_t m_outputStyle = 0;
|
||||
|
||||
CIdentifier m_inputType = CIdentifier::undefined();
|
||||
|
||||
// Data written as global output header, 8*4 = 32 bytes. Padding allows dumb readers to step with double (==8 bytes).
|
||||
size_t m_rawVersion = 0; // in network byte order, version of the raw stream
|
||||
size_t m_endianness = 0; // in network byte order, 0==unknown, 1==little, 2==big, 3==pdp
|
||||
size_t m_frequency = 0; // this and the rest are in host byte order
|
||||
size_t m_nChannels = 0;
|
||||
size_t m_nSamplesPerChunk = 0;
|
||||
size_t m_reserved0 = 0;
|
||||
size_t m_reserved1 = 0;
|
||||
size_t m_reserved2 = 0;
|
||||
|
||||
void startAccept();
|
||||
void handleAccept(const boost::system::error_code& ec, boost::asio::ip::tcp::socket* pSocket);
|
||||
};
|
||||
|
||||
class CBoxAlgorithmTCPWriterListener final : public Toolkit::TBoxListener<IBoxListener>
|
||||
{
|
||||
public:
|
||||
CBoxAlgorithmTCPWriterListener(): m_lastType(CIdentifier::undefined()) { }
|
||||
|
||||
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
|
||||
{
|
||||
CIdentifier newType = CIdentifier::undefined();
|
||||
box.getInputType(index, newType);
|
||||
// Set the right enumeration according to the type if we actualy change it
|
||||
// TODO find a way to init m_lastType with the right value
|
||||
if (m_lastType != newType)
|
||||
{
|
||||
if (newType != OV_TypeId_Stimulations) { box.setSettingType(1, OVP_TypeID_TCPWriter_RawOutputStyle); }
|
||||
else { box.setSettingType(1, OVP_TypeID_TCPWriter_OutputStyle); }
|
||||
box.setSettingValue(1, "Raw");
|
||||
m_lastType = newType;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
CIdentifier m_lastType = CIdentifier::undefined();
|
||||
|
||||
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
|
||||
};
|
||||
|
||||
/**
|
||||
* \class CBoxAlgorithmTCPWriterDesc
|
||||
* \author Jussi T. Lindgren (Inria)
|
||||
* \date Wed Sep 11 12:55:22 2013
|
||||
* \brief Descriptor of the box TCP Writer.
|
||||
*
|
||||
*/
|
||||
class CBoxAlgorithmTCPWriterDesc final : virtual public IBoxAlgorithmDesc
|
||||
{
|
||||
public:
|
||||
|
||||
void release() override { }
|
||||
|
||||
CString getName() const override { return CString("TCP Writer"); }
|
||||
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
|
||||
CString getAuthorCompanyName() const override { return CString("Inria"); }
|
||||
CString getShortDescription() const override { return CString("Send input stream out via a TCP socket"); }
|
||||
CString getDetailedDescription() const override { return CString("\n"); }
|
||||
CString getCategory() const override { return CString("Acquisition and network IO"); }
|
||||
CString getVersion() const override { return CString("0.2"); }
|
||||
CString getStockItemName() const override { return CString("gtk-connect"); }
|
||||
|
||||
CIdentifier getCreatedClass() const override { return OVP_ClassId_BoxAlgorithm_TCPWriter; }
|
||||
IPluginObject* create() override { return new CBoxAlgorithmTCPWriter; }
|
||||
|
||||
IBoxListener* createBoxListener() const override { return new CBoxAlgorithmTCPWriterListener; }
|
||||
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
|
||||
|
||||
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
|
||||
{
|
||||
prototype.addInput("Input 1",OV_TypeId_StreamedMatrix);
|
||||
|
||||
prototype.addSetting("Port",OV_TypeId_Integer, "5678");
|
||||
prototype.addSetting("Output format", OVP_TypeID_TCPWriter_RawOutputStyle, "Raw");
|
||||
|
||||
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
|
||||
|
||||
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
|
||||
prototype.addInputSupport(OV_TypeId_Signal);
|
||||
prototype.addInputSupport(OV_TypeId_Stimulations);
|
||||
|
||||
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_TCPWriterDesc)
|
||||
};
|
||||
} // namespace NetworkIO
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
|
||||
|
||||
#endif // TARGET_HAS_Boost
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
// Boxes
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#define OVP_ClassId_BoxAlgorithm_SharedMemoryWriter OpenViBE::CIdentifier(0xACC272DD, 0xC1BDC1B1)
|
||||
#define OVP_ClassId_BoxAlgorithm_SharedMemoryWriterDesc OpenViBE::CIdentifier(0xACC727DD, 0xCACDC1B1)
|
||||
#define OVP_ClassId_BoxAlgorithm_LSLExport OpenViBE::CIdentifier(0x6F3467FF, 0x52794DA6)
|
||||
#define OVP_ClassId_BoxAlgorithm_LSLExportDesc OpenViBE::CIdentifier(0x40C03C3F, 0x034A19C2)
|
||||
#define OVP_ClassId_BoxAlgorithm_TCPWriter OpenViBE::CIdentifier(0x02F24947, 0x17FA0477)
|
||||
#define OVP_ClassId_BoxAlgorithm_TCPWriterDesc OpenViBE::CIdentifier(0x3C32489D, 0x46F565D3)
|
||||
#define OVP_TypeID_TCPWriter_OutputStyle OpenViBE::CIdentifier(0x6D7E53DD, 0x6A0A4753)
|
||||
#define OVP_TypeID_TCPWriter_RawOutputStyle OpenViBE::CIdentifier(0x77D3E238, 0xB954EC48)
|
||||
|
||||
|
||||
// Global defines
|
||||
//---------------------------------------------------------------------------------------------------
|
||||
#ifdef TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
#include "ovp_global_defines.h"
|
||||
#endif // TARGET_HAS_ThirdPartyOpenViBEPluginsGlobalDefines
|
||||
|
||||
|
||||
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
#include "ovp_defines.h"
|
||||
|
||||
#include "box-algorithms/ovpCBoxAlgorithmLSLExport.h"
|
||||
#include "box-algorithms/ovpCBoxAlgorithmTCPWriter.h"
|
||||
#include "box-algorithms/ovpCBoxAlgorithmSharedMemoryWriter.h"
|
||||
|
||||
namespace OpenViBE {
|
||||
namespace Plugins {
|
||||
|
||||
OVP_Declare_Begin()
|
||||
#ifdef TARGET_HAS_ThirdPartyLSL
|
||||
OVP_Declare_New(NetworkIO::CBoxAlgorithmLSLExportDesc);
|
||||
#endif
|
||||
|
||||
#ifdef TARGET_HAS_Boost
|
||||
OVP_Declare_New(FileReadingAndWriting::CBoxAlgorithmSharedMemoryWriterDesc);
|
||||
OVP_Declare_New(NetworkIO::CBoxAlgorithmTCPWriterDesc);
|
||||
|
||||
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
|
||||
OV_AttributeId_Box_FlagIsUnstable.id());
|
||||
|
||||
context.getTypeManager().registerEnumerationType(OVP_TypeID_TCPWriter_OutputStyle, "Stimulus output");
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_OutputStyle, "Raw", TCPWRITER_RAW);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_OutputStyle, "Hex", TCPWRITER_HEX);
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_OutputStyle, "String", TCPWRITER_STRING);
|
||||
|
||||
context.getTypeManager().registerEnumerationType(OVP_TypeID_TCPWriter_RawOutputStyle, "Raw output");
|
||||
context.getTypeManager().registerEnumerationEntry(OVP_TypeID_TCPWriter_RawOutputStyle, "Raw", TCPWRITER_RAW);
|
||||
#endif
|
||||
|
||||
OVP_Declare_End()
|
||||
|
||||
} // namespace Plugins
|
||||
} // namespace OpenViBE
|
||||
Reference in New Issue
Block a user