init
This commit is contained in:
+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