This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,213 @@
#include <openvibe/ov_all.h>
#include <fs/Files.h>
#include "BoxAdapter.h"
#include "CodecFactory.h"
namespace OpenViBE {
namespace Tracker {
bool BoxAdapter::initialize()
{
m_boxAlgorithm = m_kernelCtx.getPluginManager().createBoxAlgorithm(m_algorithmID, nullptr);
if (!m_boxAlgorithm)
{
log() << Kernel::LogLevel_Error << "Error: failed to create algorithm for id " << m_algorithmID.str() << "\n";
return false;
}
m_boxAlgorithmCtx.getTrackerBoxIO()->initialize(m_boxAlgorithmCtx.getStaticBoxContext());
return true;
}
bool BoxAdapter::uninitialize()
{
if (m_boxAlgorithm)
{
m_boxAlgorithm->uninitialize(m_boxAlgorithmCtx);
m_kernelCtx.getPluginManager().releasePluginObject(m_boxAlgorithm);
m_boxAlgorithm = nullptr;
}
return true;
}
BoxAdapterBundle::~BoxAdapterBundle()
{
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
}
bool BoxAdapterBundle::initialize()
{
if (!BoxAdapter::initialize()) { return false; }
Kernel::IBox* staticBoxContext = const_cast<Kernel::IBox*>(m_boxAlgorithmCtx.getStaticBoxContext());
if (m_src)
{
for (size_t i = 0; i < m_src->getNumStreams(); ++i)
{
// @fixme test boxes input support here
std::stringstream ss;
ss << "Stream" << i; // not really used since there's no GUI for the box
staticBoxContext->addInput(ss.str().c_str(), m_src->getStream(i)->getTypeIdentifier());
m_encoders.push_back(CodecFactory::getEncoder(m_kernelCtx, *m_src->getStream(i)));
if (m_dst)
{
m_dst->createStream(i, m_src->getStream(i)->getTypeIdentifier());
m_decoders.push_back(CodecFactory::getDecoder(m_kernelCtx, *m_dst->getStream(i)));
staticBoxContext->addOutput(ss.str().c_str(), m_dst->getStream(i)->getTypeIdentifier());
}
}
}
m_boxAlgorithm->initialize(m_boxAlgorithmCtx);
return true;
}
bool BoxAdapterBundle::spool(const bool /* verbose */)
{
Kernel::IBoxIO* boxCtx = const_cast<Kernel::IBoxIO*>(m_boxAlgorithmCtx.getDynamicBoxContext());
TrackerBoxIO* ioCtx = static_cast<TrackerBoxIO*>(boxCtx);
if (!m_src)
{
log() << Kernel::LogLevel_Error << "Error: box wrapper doesn't yet support processing without source\n";
return false;
}
m_src->rewind();
const uint32_t nOutput = m_boxAlgorithmCtx.getStaticBoxContext()->getOutputCount();
while (true)
{
size_t index;
StreamPtr stream = m_src->getNextStream(index);
if (!stream) { break; }
EncodedChunk chk;
EChunkType outputType;
m_encoders[index]->encode(chk, outputType);
ioCtx->addInputChunk(index, chk);
m_boxAlgorithm->process(m_boxAlgorithmCtx);
ioCtx->clearInputChunks();
if (m_dst)
{
for (size_t j = 0; j < nOutput; ++j)
{
if (ioCtx->isReadyToSend(j))
{
ioCtx->getOutputChunk(j, chk);
m_decoders[j]->decode(chk);
ioCtx->deprecateOutput(j);
}
}
}
stream->step();
}
m_src->rewind();
return true;
}
bool BoxAdapterStream::initialize()
{
if (!BoxAdapter::initialize()) { return false; }
Kernel::IBox* boxCtx = const_cast<Kernel::IBox*>(m_boxAlgorithmCtx.getStaticBoxContext());
CIdentifier typeID;
boxCtx->getInputType(0, typeID);
if (typeID != m_src->getTypeIdentifier())
{
log() << Kernel::LogLevel_Error << "Error: Box alg first input stream is wrong type\n";
return false;
}
boxCtx->getOutputType(0, typeID);
if (typeID != m_dst->getTypeIdentifier())
{
log() << Kernel::LogLevel_Error << "Error: Box alg first output stream is wrong type\n";
return false;
}
m_boxAlgorithm->initialize(m_boxAlgorithmCtx);
return true;
}
bool BoxAdapterStream::spool(const bool verbose)
{
Kernel::IBoxIO* boxCtx = const_cast<Kernel::IBoxIO*>(m_boxAlgorithmCtx.getDynamicBoxContext());
TrackerBoxIO* ioCtx = static_cast<TrackerBoxIO*>(boxCtx);
if (!m_src)
{
log() << Kernel::LogLevel_Error << "Error: box wrapper doesn't yet support processing without source\n";
return false;
}
m_src->reset();
EncoderBase* encoder = CodecFactory::getEncoder(m_kernelCtx, *m_src);
DecoderBase* decoder = CodecFactory::getDecoder(m_kernelCtx, *m_dst);
if (verbose) { log() << Kernel::LogLevel_Info; }
bool finished = false;
uint64_t cnt = 0;
while (!finished)
{
if (m_src)
{
EncodedChunk chk;
EChunkType outputType;
if (encoder->encode(chk, outputType))
{
// @fixme here we assume the box takes data in at the first slot
ioCtx->clearInputChunks();
ioCtx->addInputChunk(0, chk);
m_src->step();
}
else { finished = true; }
}
m_boxAlgorithm->process(m_boxAlgorithmCtx);
if (m_dst)
{
if (ioCtx->isReadyToSend(0))
{
EncodedChunk chk;
ioCtx->getOutputChunk(0, chk);
decoder->decode(chk);
ioCtx->deprecateOutput(0);
}
}
if (verbose && cnt++ % 100 == 0) { log() << "."; }
}
if (verbose) { log() << "\n"; }
m_src->reset();
ioCtx->clearInputChunks();
delete encoder;
delete decoder;
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,97 @@
#include "BoxPlugins.h"
#include "ovkCBoxProto.h"
namespace OpenViBE {
namespace Tracker {
#define OVP_ClassId_BoxAlgorithm_TemporalFilter CIdentifier(0xB4F9D042, 0x9D79F2E5)
#define OVP_ClassId_BoxAlgorithm_TemporalFilterDesc CIdentifier(0x7BF6BA62, 0xAF829A37)
#define OVP_ClassId_BoxAlgorithm_SignalResampling CIdentifier(0x0E923A5E, 0xDA474058)
#define OVP_ClassId_BoxAlgorithm_SignalResamplingDesc CIdentifier(0xA675A433, 0xC6690920)
#define OVP_ClassId_BoxAlgorithm_SimpleDSP CIdentifier(0x00E26FA1, 0x1DBAB1B2)
#define OVP_ClassId_BoxAlgorithm_SimpleDSPDesc CIdentifier(0x00C44BFE, 0x76C9269E)
#define OVP_ClassId_BoxAlgorithm_Crop CIdentifier(0x7F1A3002, 0x358117BA)
#define OVP_ClassId_BoxAlgorithm_CropDesc CIdentifier(0x64D619D7, 0x26CC42C9)
#define OVP_ClassId_BoxAlgorithm_SpatialFilter CIdentifier(0xDD332C6C, 0x195B4FD4)
#define OVP_ClassId_BoxAlgorithm_SpatialFilterDesc CIdentifier(0x72A01C92, 0xF8C1FA24)
#define OVP_ClassId_BoxAlgorithm_TimeBasedEpoching CIdentifier(0x00777FA0, 0x5DC3F560)
#define OVP_ClassId_BoxAlgorithm_TimeBasedEpochingDesc CIdentifier(0x00ABDABE, 0x41381683)
#define OVP_ClassId_BoxAlgorithm_StimulationFilter CIdentifier(0x02F96101, 0x5E647CB8)
#define OVP_ClassId_BoxAlgorithm_StimulationFilterDesc CIdentifier(0x4D2A23FC, 0x28191E18)
#define OVP_ClassId_FastICA CIdentifier(0x00649B6E, 0x6C88CD17)
#define OVP_ClassId_FastICADesc CIdentifier(0x00E9436C, 0x41C904CA)
#define OVP_ClassId_BoxAlgorithm_CommonAverageReference CIdentifier(0x009C0CE3, 0x6BDF71C3)
#define OVP_ClassId_BoxAlgorithm_CommonAverageReferenceDesc CIdentifier(0x0033EAF8, 0x09C65E4E)
#define OVP_ClassId_BoxAlgorithm_ChannelSelector CIdentifier(0x361722E8, 0x311574E8)
#define OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc CIdentifier(0x67633C1C, 0x0D610CD8)
#define OVP_ClassId_BoxAlgorithm_ChannelRename CIdentifier(0x1FE50479, 0x39040F40)
#define OVP_ClassId_BoxAlgorithm_ChannelRenameDesc CIdentifier(0x20EA1F00, 0x7AED5645)
#define OVP_ClassId_BoxAlgorithm_ReferenceChannel CIdentifier(0x444721AD, 0x78342CF5)
#define OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc CIdentifier(0x42856103, 0x45B125AD)
#define OVP_ClassId_BoxAlgorithm_FrequencyBandSelector CIdentifier(0x140C19C6, 0x4E6E187B)
#define OVP_ClassId_BoxAlgorithm_FrequencyBandSelectorDesc CIdentifier(0x13462C56, 0x794E3C07)
#include <algorithm>
BoxPlugins::BoxPlugins(const Kernel::IKernelContext& ctx) : Contexted(ctx)
{
// Register some boxes that can be used as filters
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_TemporalFilter, OVP_ClassId_BoxAlgorithm_TemporalFilterDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_SimpleDSP, OVP_ClassId_BoxAlgorithm_SimpleDSPDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_TimeBasedEpoching, OVP_ClassId_BoxAlgorithm_TimeBasedEpochingDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_Crop, OVP_ClassId_BoxAlgorithm_CropDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_SpatialFilter, OVP_ClassId_BoxAlgorithm_SpatialFilterDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_SignalResampling, OVP_ClassId_BoxAlgorithm_SignalResamplingDesc);
create(OV_TypeId_Signal, OVP_ClassId_FastICA, OVP_ClassId_FastICADesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_CommonAverageReference, OVP_ClassId_BoxAlgorithm_CommonAverageReferenceDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_ChannelSelector, OVP_ClassId_BoxAlgorithm_ChannelSelectorDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_ChannelRename, OVP_ClassId_BoxAlgorithm_ChannelRenameDesc);
create(OV_TypeId_Signal, OVP_ClassId_BoxAlgorithm_ReferenceChannel, OVP_ClassId_BoxAlgorithm_ReferenceChannelDesc);
create(OV_TypeId_Spectrum, OVP_ClassId_BoxAlgorithm_FrequencyBandSelector, OVP_ClassId_BoxAlgorithm_FrequencyBandSelectorDesc);
create(OV_TypeId_Stimulations, OVP_ClassId_BoxAlgorithm_StimulationFilter, OVP_ClassId_BoxAlgorithm_StimulationFilterDesc);
std::sort(m_boxPlugins.begin(), m_boxPlugins.end(), [](BoxAdapterStream* a, BoxAdapterStream* b)
{
return (a->getBox().getName()) < (b->getBox().getName());
});
}
bool BoxPlugins::create(const CIdentifier& streamType, const CIdentifier& alg, const CIdentifier& desc)
{
BoxAdapterStream* ptr = new BoxAdapterStream(m_kernelCtx, alg);
// Initialize the context by polling the descriptor
const Plugins::IPluginObjectDesc* pod = getKernelContext().getPluginManager().getPluginObjectDesc(desc);
if (!pod)
{
log() << Kernel::LogLevel_Error << "Unable to load box algorithm " << alg.str() << "\n";
return false;
}
const Plugins::IBoxAlgorithmDesc* pBoxAlgorithmDescriptor = dynamic_cast<const Plugins::IBoxAlgorithmDesc*>(pod);
Kernel::CBoxProto boxProto(m_kernelCtx, ptr->getBox());
pBoxAlgorithmDescriptor->getBoxPrototype(boxProto);
// We need to force this so we don't launch the plugin on streams of other types, and not all boxes declare
// their capabilities. Here we assume that if the box is created for this stream type, it can support it.
boxProto.addInputSupport(streamType);
CIdentifier typeID;
ptr->getBox().getInputType(0, typeID);
if (typeID != streamType) { ptr->getBox().setInputType(0, streamType); }
ptr->getBox().getOutputType(0, typeID);
if (typeID != streamType) { ptr->getBox().setOutputType(0, streamType); }
const CString boxName = m_kernelCtx.getTypeManager().getTypeName(streamType) + CString(" : ") + pBoxAlgorithmDescriptor->getName();
ptr->getBox().setName(boxName);
m_boxPlugins.push_back(ptr);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,454 @@
//
// OpenViBE Tracker
//
// @todo catch way more errors, currently most return values are ignored by callers
// @todo Change error handling to use the macros introduced in certivibe
// @todo clean the code to be more conforming to coding rules
// @todo Implement vertical slider / scale
// @todo Fix time units
// @todo Add track renderer only when the track appears
//
// @todo add dataset manager (workspace == datasets)
// @todo enable a view where only 'current track' is shown and the rest are minimized (unloaded?)
// @todo add option to collapse tracks to take less visual space
// @todo add record button
// @todo implement undo
// @todo fix issues with empty tracks/streams and if the processing fails (e.g. incompatible processor)
// @todo fix creation of noncontinuous streams in the catenate mode e.g. by padding all source streams to equal length?
// @todo write some tutorial processors, e.g. erp analysis, file export ... ?
// @todo allow running several scenarios sequentially
// @todo selection could be saved in the .ovw file simply as track/stream tokens (small perf hit)?
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "CTracker.h"
#include "StreamBundleImportExport.h"
#include "Workspace.h"
namespace OpenViBE {
namespace Tracker {
CTracker::CTracker(const Kernel::IKernelContext& ctx)
: Contexted(ctx), m_workspace(ctx), m_boxPlugins(ctx), m_trackerPlugins(ctx)
{
// Default config load deferred until initialize() which can be called after booting the GUI
// --> so we can get the possible log messages to the GUI
}
CTracker::~CTracker()
{
const CString configFile = m_kernelCtx.getConfigurationManager().expand("${Path_UserData}/openvibe-tracker.conf");
saveConfig(configFile);
m_executor.uninitialize();
}
bool CTracker::initialize()
{
const CString configFile = m_kernelCtx.getConfigurationManager().expand("${Path_UserData}/openvibe-tracker.conf");
// n.b. Initing the executor is in loadConfig so its not done twice
m_workspace.setParallelExecutor(&m_executor);
return loadConfig(configFile);
}
bool CTracker::play(const bool playFast)
{
switch (m_state)
{
case EStates::Stopped:
if (!m_workspace.play(playFast))
{
log() << Kernel::LogLevel_Error << "Error: play failed\n";
return false;
}
m_state = EStates::Playing;
break;
case EStates::Playing:
m_state = EStates::Paused;
break;
case EStates::Paused:
m_state = EStates::Playing;
break;
default:
break;
}
return true;
}
bool CTracker::stop()
{
// Request workspace to stop, do not change state until it has stopped
return m_workspace.stop();
}
CTracker::EStates CTracker::step()
{
if (m_state == EStates::Playing) { if (!m_workspace.step()) { m_state = EStates::Stopping; } }
else if (m_state == EStates::Stopping) { if (m_executor.isIdle()) { m_state = EStates::Stopped; } }
else { System::Time::sleep(1); }
return m_state;
}
struct Workpackage
{
StreamBundle* source = nullptr;
std::mutex oMutex;
size_t trackIndex;
std::deque<size_t> streamsToProcess;
};
bool CTracker::applyBoxPlugin(const size_t index)
{
auto& plugins = getBoxPlugins().getBoxPlugins();
if (index >= plugins.size())
{
log() << Kernel::LogLevel_Error << "Plugin index exceeds plugin array size\n";
return false;
}
if (getWorkspace().getMemorySaveMode() && getWorkspace().getWorkingPath().length() == 0)
{
log() << Kernel::LogLevel_Error << "Memory save mode requires a workspace path\n";
return false;
}
const CTime startTime = CTime(System::Time::zgetTime());
BoxAdapterStream* box = plugins[index];
const CString name = box->getBox().getName();
log() << Kernel::LogLevel_Info << "Applying method " << name << " (" << (m_workspace.getInplaceMode() ? "Inplace" : "Normal") << " mode)\n";
for (size_t trackIndex = 0; trackIndex < getWorkspace().getNumTracks(); ++trackIndex)
{
Workpackage* wptr = new Workpackage;
Workspace& ws = getWorkspace();
StreamBundle* sourceTrack = ws.getTrack(trackIndex);
// Count
const size_t numStreams = ws.getTrack(trackIndex)->getNumStreams();
for (size_t j = 0; j < numStreams; ++j)
{
if (sourceTrack->getStream(j)->getSelected() && box->getBox().hasInputSupport(sourceTrack->getStream(j)->getTypeIdentifier()))
{
wptr->streamsToProcess.push_back(j);
}
}
const size_t nToProcess = wptr->streamsToProcess.size();
if (nToProcess == 0) { continue; }
wptr->trackIndex = trackIndex;
wptr->source = (m_workspace.getMemorySaveMode() ? nullptr : sourceTrack);
for (size_t j = 0; j < nToProcess; ++j)
{
const bool inplaceMode = m_workspace.getInplaceMode();
const bool multithread = (m_executor.getNumThreads() > 1);
const bool memorySave = m_workspace.getMemorySaveMode();
const Kernel::IKernelContext& ctx = getKernelContext();
auto job = [&ws, wptr, &ctx, box, multithread, inplaceMode, memorySave](uint32_t /*threadNumber*/)
{
size_t sourceStreamIndex;
{
std::unique_lock<std::mutex>(wptr->oMutex);
if (memorySave && !wptr->source)
{
wptr->source = readStreamBundleFromFile(ctx,
ws.getTrack(wptr->trackIndex)->getSource().c_str(), false);
}
sourceStreamIndex = wptr->streamsToProcess.front();
wptr->streamsToProcess.pop_front();
}
StreamBundle* sourceTrack = wptr->source;
const StreamPtr sourceStream = sourceTrack->getStream(sourceStreamIndex);
// process
if (!multithread)
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Processing track " << (wptr->trackIndex + 1) << " stream " << (sourceStreamIndex + 1) << "\n";
}
std::unique_lock<std::mutex>(wptr->oMutex);
const size_t targetStreamIndex = sourceTrack->getNumStreams();
sourceTrack->createStream(targetStreamIndex, sourceStream->getTypeIdentifier());
StreamPtr targetStream = sourceTrack->getStream(targetStreamIndex);
// We need a copy of the box since otherwise different threads init the same box differently
BoxAdapterStream* boxCopy = new BoxAdapterStream(ctx, box->getAlgorithmId());
boxCopy->getBox().initializeFromExistingBox(box->getBox());
boxCopy->setSource(sourceStream);
boxCopy->setTarget(targetStream);
if (!boxCopy->initialize())
{
std::unique_lock<std::mutex>(wptr->oMutex);
sourceTrack->deleteStream(targetStreamIndex);
delete boxCopy;
return;
}
boxCopy->spool(!multithread);
boxCopy->uninitialize();
delete boxCopy;
if (inplaceMode)
{
std::unique_lock<std::mutex>(wptr->oMutex);
sourceTrack->swapStreams(sourceStreamIndex, targetStreamIndex);
sourceTrack->deleteStream(targetStreamIndex);
}
else { targetStream->setSelected(false); }
// Spool resources to disk and free memory
{
std::unique_lock<std::mutex>(wptr->oMutex);
if (memorySave)
{
// Free resources if this thread is the last
if (wptr->streamsToProcess.empty())
{
if (wptr->source->getDirtyBit())
{
// @fixme not a good solution with the filenaming; rethink the whole thing
std::stringstream ss;
ss << ws.getWorkingPath() << "/workspace-track-" << (wptr->trackIndex + 1) << ".ov";
// In this mode, sourceTrack is not from the track array. We spool it to disk, then
// set the filename of the track in the array, and reload it back.
saveStreamBundleToFile(ctx, wptr->source, ss.str().c_str());
ws.getTrack(wptr->trackIndex)->setSource(ss.str());
ws.reloadTrack(wptr->trackIndex);
}
delete wptr->source;
}
}
if (wptr->streamsToProcess.empty()) { delete wptr; }
}
};
m_executor.pushJob(job);
if (!multithread) { m_executor.waitForAll(); }
}
}
m_executor.waitForAll();
// pBoxAlgorithmDescriptor->release();
const CTime elapsed = CTime(System::Time::zgetTime()) - startTime;
log() << Kernel::LogLevel_Info << "Applying plugin took " << elapsed.toSeconds() << " sec.\n";
return true;
}
bool CTracker::applyTrackerPlugin(const size_t index)
{
auto& plugins = getTrackerPlugins().getTrackerPlugins();
if (index >= plugins.size())
{
log() << Kernel::LogLevel_Error << "Plugin index exceeds plugin array size\n";
return false;
}
if (getWorkspace().getMemorySaveMode() && getWorkspace().getWorkingPath().length() == 0)
{
log() << Kernel::LogLevel_Error << "Memory save mode requires a workspace path\n";
return false;
}
ITrackerPlugin* plugin = plugins[index];
const CString name(plugin->getName().c_str());
log() << Kernel::LogLevel_Info << "Applying method " << name << " (" << (m_workspace.getInplaceMode() ? "Inplace" : "Normal") << " mode)\n";
bool retVal = true;
if (plugin->hasCapability(ITrackerPlugin::ECapabilities::Workspace))
{
retVal = plugin->process(getWorkspace(), m_executor);
if (!retVal) { log() << Kernel::LogLevel_Error << "Error processing workspace with the plugin\n"; }
}
else if (plugin->hasCapability(ITrackerPlugin::ECapabilities::Tracks))
{
// @note since the different jobs handle different tracks, we don't do locking here.
for (size_t trackIndex = 0; trackIndex < getWorkspace().getNumTracks(); ++trackIndex)
{
ITrackerPlugin* pluginCopy = getTrackerPlugins().getPluginCopy(index);
Workspace& ws = getWorkspace();
const Kernel::IKernelContext& ctx = getKernelContext();
auto job = [pluginCopy,&ws,&ctx,trackIndex](uint32_t /*threadNumber*/)
{
StreamBundle* sourceTrack;
if (ws.getMemorySaveMode())
{
ctx.getLogManager() << Kernel::LogLevel_Info << "Loading " << ws.getTrack(trackIndex)->getSource().c_str()
<< " from file\n";
sourceTrack = readStreamBundleFromFile(ctx,
ws.getTrack(trackIndex)->getSource().c_str(), false);
}
else { sourceTrack = ws.getTrack(trackIndex); }
if (!pluginCopy->process(*sourceTrack))
{
// log() << Kernel::LogLevel_Error << "Error processing track << " << (i + 1) << " with the plugin\n";
// retVal = false;
}
delete pluginCopy;
if (ws.getMemorySaveMode())
{
if (sourceTrack->getDirtyBit())
{
// @fixme not a good solution with the filenaming; rethink the whole thing
std::stringstream ss;
ss << ws.getWorkingPath() << "/workspace-track-" << (trackIndex + 1) << ".ov";
// In this mode, sourceTrack is not from the track array. We spool it to disk, then
// set the filename of the track in the array, and reload it back.
saveStreamBundleToFile(ctx, sourceTrack, ss.str().c_str());
ws.getTrack(trackIndex)->setSource(ss.str());
ws.reloadTrack(trackIndex);
}
delete sourceTrack;
}
};
m_executor.pushJob(job);
// @todo copy sourcetrack, process the copy, if the processing is
// successful then replace the original (or not) depending on inplacemode setting
if (m_executor.getNumThreads() == 1) { m_executor.waitForAll(); }
}
// @todo in principle there's no need to freeze the GUI meanwhile, but allowing it to run in the bg would require locking
m_executor.waitForAll();
}
else { log() << Kernel::LogLevel_Error << "Plugin does not have any known capabilities and cannot be run.\n"; }
// pBoxAlgorithmDescriptor->release();
return retVal;
}
bool CTracker::setNumThreads(uint32_t numThreads)
{
if (numThreads != m_executor.getNumThreads())
{
if (numThreads < 1)
{
log() << Kernel::LogLevel_Warning << "Minimum number of threads is 1, setting that.\n";
numThreads = 1;
}
else if (numThreads > 1)
{
log() << Kernel::LogLevel_Info << "Using " << numThreads <<
" threads. Concurrency control has not been carefully tested. If you notice issues, switch to 1 thread.\n";
}
m_executor.uninitialize();
return m_executor.initialize(numThreads);
}
return true;
}
bool CTracker::loadConfig(const CString& filename)
{
if (!m_kernelCtx.getConfigurationManager().addConfigurationFromFile(filename))
{
m_executor.initialize(1);
return false;
}
const uint32_t numThreads = uint32_t(m_kernelCtx.getConfigurationManager().expandAsUInteger("${Tracker_NumThreads}", 1));
m_executor.initialize(numThreads);
// m_Executor.launchTest();
const CString workspaceFile = m_kernelCtx.getConfigurationManager().expand("${Tracker_Last_Workspace}");
//if (m_kernelCtx.getConfigurationManager().lookUpConfigurationTokenIdentifier("Tracker_Last_Workspace"))
// != CIdentifier::undefined())
//{
// m_Workspace.load(m_kernelCtx.getConfigurationManager().expand("${Tracker_Last_Workspace}");
// Tracker_Last_Workspace
// )
if (workspaceFile.length() != 0) { return m_workspace.load(workspaceFile.toASCIIString()); }
return false;
}
// @note : does not save the workspace itself
bool CTracker::saveConfig(const CString& filename) const
{
FILE* file = fopen(filename, "wt");
if (file)
{
fprintf(file, "# Configuration file for OpenViBE Tracker, autosaved on Tracker exit\n");
fprintf(file, "#\n");
fprintf(file, "\n");
fprintf(file, "# Last settings used in the Tracker\n");
fprintf(file, "Tracker_Last_Workspace = %s\n", m_workspace.getFilename().toASCIIString());
fprintf(file, "Tracker_NumThreads = %d\n", m_executor.getNumThreads());
fclose(file);
}
else { return false; }
return true;
}
#if 0
void testCode()
{
Workspace wp(*kernelWrapper.m_kernelCtx);
// TestClass tmp(*kernelWrapper.m_kernelCtx);
/*
const CString eegFile = Directories::getDataDir() + CString("/scenarios/signals/bci-motor-imagery.ov");
// const CString eegFile = CString("E:/jl/noise-test.ov");
const CString scenarioFile = Directories::getDataDir() + CString("/applications/tracker/tracker-debug-display.xml");
if(!wp.setTrack(eegFile.toASCIIString())) { return 2; }
if(!wp.setprocessor(scenarioFile.toASCIIString())) { return 3; }
// Push some chunks to selection
Selection& selection = wp.m_track.m_Selection;
selection.addRange(Range(3,5));
selection.addRange(Range(9,11));
if(!wp.play()) { return 4; }
*/
}
#endif
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,55 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeChannelLocalization.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationDecoder<BoxAlgorithmProxy>>::getHeaderImpl(
TypeChannelLocalization::Header& h)
{
h.m_Dynamic = m_impl.getOutputDynamic();
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationDecoder<BoxAlgorithmProxy>>::getBufferImpl(
TypeChannelLocalization::Buffer& b)
{
CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(
const TypeChannelLocalization::Header& hdr)
{
m_impl.getInputDynamic() = hdr.m_Dynamic;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeChannelLocalization, Toolkit::TChannelLocalisationEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(
const TypeChannelLocalization::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,51 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeChannelUnits.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeChannelUnits::Header& h)
{
h.m_Dynamic = m_impl.getOutputDynamic();
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeChannelUnits::Buffer& b)
{
CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeChannelUnits::Header& hdr)
{
m_impl.getInputDynamic() = hdr.m_Dynamic;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeChannelUnits, Toolkit::TChannelUnitsEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeChannelUnits::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,68 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeExperimentInfo.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoDecoder<BoxAlgorithmProxy>>::getHeaderImpl(
TypeExperimentInfo::Header& h)
{
h.m_ExperimentID = m_impl.getOutputExperimentID();
h.m_ExperimentDate = (*m_impl.getOutputExperimentDate()).toASCIIString();
h.m_SubjectID = m_impl.getOutputSubjectID();
h.m_SubjectName = (*m_impl.getOutputSubjectName()).toASCIIString();
h.m_SubjectAge = m_impl.getOutputSubjectAge();
h.m_SubjectGender = m_impl.getOutputSubjectGender();
h.m_LaboratoryID = m_impl.getOutputLaboratoryID();
h.m_LaboratoryName = (*m_impl.getOutputLaboratoryName()).toASCIIString();
h.m_TechnicianID = m_impl.getOutputTechnicianID();
h.m_TechnicianName = (*m_impl.getOutputTechnicianName()).toASCIIString();
return true;
}
template <>
bool DecoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeExperimentInfo::Buffer& /*b*/)
{
// Should be no buffer in the experiment stream
return true;
}
template <>
bool EncoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(
const TypeExperimentInfo::Header& hdr)
{
// @fixme the new() calls may imply memory leaks, a bit odd the codec takes pointers
m_impl.getInputExperimentID() = hdr.m_ExperimentID;
m_impl.getInputExperimentDate() = new CString(hdr.m_ExperimentDate.c_str());
m_impl.getInputSubjectID() = hdr.m_SubjectID;
m_impl.getInputSubjectName() = new CString(hdr.m_SubjectName.c_str());
m_impl.getInputSubjectAge() = hdr.m_SubjectAge;
m_impl.getInputSubjectGender() = hdr.m_SubjectGender;
m_impl.getInputLaboratoryID() = hdr.m_LaboratoryID;
m_impl.getInputLaboratoryName() = new CString(hdr.m_LaboratoryName.c_str());
m_impl.getInputTechnicianID() = hdr.m_TechnicianID;
m_impl.getInputTechnicianName() = new CString(hdr.m_TechnicianName.c_str());
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeExperimentInfo, Toolkit::TExperimentInfoEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(
const TypeExperimentInfo::Buffer& /*buf*/)
{
// Should be no buffer in the experiment stream
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,52 @@
// @todo this is identical to CodecFeatureMatrix. Refactor?
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeFeatureVector.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeFeatureVector::Header& h)
{
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeFeatureVector::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(
const TypeFeatureVector::Header& hdr)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeFeatureVector, Toolkit::TFeatureVectorEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(
const TypeFeatureVector::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,48 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeMatrix.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeMatrix::Header& h)
{
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeMatrix::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeMatrix::Header& hdr)
{
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeMatrix, Toolkit::TStreamedMatrixEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeMatrix::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,51 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeSignal.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeSignal, Toolkit::TSignalDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeSignal::Header& h)
{
h.m_Sampling = m_impl.getOutputSamplingRate();
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
return true;
}
template <>
bool DecoderAdapter<TypeSignal, Toolkit::TSignalDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeSignal::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeSignal, Toolkit::TSignalEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeSignal::Header& hdr)
{
m_impl.getInputSamplingRate() = hdr.m_Sampling;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeSignal, Toolkit::TSignalEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeSignal::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,58 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeSpectrum.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeSpectrum, Toolkit::TSpectrumDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeSpectrum::Header& h)
{
CMatrix* decoded = m_impl.getOutputMatrix();
h.m_Header.copy(*decoded);
h.m_Sampling = m_impl.getOutputSamplingRate();
CMatrix* abscissas = m_impl.getOutputFrequencyAbscissa();
h.m_Abscissas.copy(*abscissas);
return true;
}
template <>
bool DecoderAdapter<TypeSpectrum, Toolkit::TSpectrumDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeSpectrum::Buffer& b)
{
const CMatrix* decoded = m_impl.getOutputMatrix();
b.m_buffer.copy(*decoded);
return true;
}
template <>
bool EncoderAdapter<TypeSpectrum, Toolkit::TSpectrumEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeSpectrum::Header& hdr)
{
m_impl.getInputSamplingRate() = hdr.m_Sampling;
CMatrix* header = m_impl.getInputMatrix();
header->copy(hdr.m_Header);
CMatrix* abscissas = m_impl.getInputFrequencyAbscissa();
abscissas->copy(hdr.m_Abscissas);
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeSpectrum, Toolkit::TSpectrumEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeSpectrum::Buffer& buf)
{
CMatrix* buffer = m_impl.getInputMatrix();
buffer->copy(buf.m_buffer);
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,50 @@
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include "TypeStimulation.h"
#include "Decoder.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
template <>
bool DecoderAdapter<TypeStimulation, Toolkit::TStimulationDecoder<BoxAlgorithmProxy>>::getHeaderImpl(TypeStimulation::Header& /*target*/) { return true; }
template <>
bool DecoderAdapter<TypeStimulation, Toolkit::TStimulationDecoder<BoxAlgorithmProxy>>::getBufferImpl(TypeStimulation::Buffer& b)
{
IStimulationSet* decoded = m_impl.getOutputStimulationSet();
b.m_buffer.clear();
for (size_t i = 0; i < decoded->getStimulationCount(); ++i)
{
b.m_buffer.appendStimulation(decoded->getStimulationIdentifier(i), decoded->getStimulationDate(i), decoded->getStimulationDuration(i));
}
return true;
}
template <>
bool EncoderAdapter<TypeStimulation, Toolkit::TStimulationEncoder<BoxAlgorithmProxy>>::encodeHeaderImpl(const TypeStimulation::Header& /*hdr*/)
{
return m_impl.encodeHeader();
}
template <>
bool EncoderAdapter<TypeStimulation, Toolkit::TStimulationEncoder<BoxAlgorithmProxy>>::encodeBufferImpl(const TypeStimulation::Buffer& buf)
{
IStimulationSet* inputSet = m_impl.getInputStimulationSet();
inputSet->clear();
for (size_t i = 0; i < buf.m_buffer.getStimulationCount(); ++i)
{
inputSet->appendStimulation(buf.m_buffer.getStimulationIdentifier(i), buf.m_buffer.getStimulationDate(i) + m_offset.time(),
buf.m_buffer.getStimulationDuration(i));
}
return m_impl.encodeBuffer();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,184 @@
//
// @note some ov files which have lots of stimulation chunks take long time to import
// when launching tracker from visual studio. this is probably due to memory allocation, similar to slow simple dsp grammar parsing.
//
#include "Demuxer.h"
#include <iostream>
#include <thread>
#include <deque>
#include <vector>
#include "../../../../plugins/processing/file-io/src/ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include "Stream.h"
#include "CodecFactory.h"
namespace OpenViBE {
namespace Tracker {
bool Demuxer::initialize()
{
// log() << Kernel::LogLevel_Info << "Source: Initializing with " << signalFile << "\n";
m_chunksSent = 0;
m_pending = false;
m_target.setSource(m_origin.getSource());
return true;
}
bool Demuxer::uninitialize() const
{
log() << Kernel::LogLevel_Info << "Source: Uninitializing\n";
m_origin.uninitialize();
return true;
}
// bool Demuxer::pullChunk(MemoryBufferWithType& output)
bool Demuxer::step()
{
// log() << Kernel::LogLevel_Info << "Source: Trying to pull a chunk\n";
while (!m_origin.isEOF())
{
while (!m_origin.isEOF() && m_reader.getCurrentNodeID() == EBML::CIdentifier())
{
std::vector<uint8_t> bytes;
m_origin.read(bytes, 1);
//OV_ERROR_UNLESS_KRF(s == 1 || justStarted, "Unexpected EOF in " << m_filename, Kernel::ErrorType::BadParsing);
if (!bytes.empty()) { m_reader.processData(&bytes[0], bytes.size()); }
}
if (!m_origin.isEOF() && m_reader.getCurrentNodeSize() != 0)
{
std::vector<uint8_t> bytes;
m_origin.read(bytes, size_t(m_reader.getCurrentNodeSize()));
//OV_ERROR_UNLESS_KRF(s == m_swap.getSize(), "Unexpected EOF in " << m_filename, Kernel::ErrorType::BadParsing);
m_pendingChunk.m_Buffer.resize(0);
m_pendingChunk.m_StartTime = CTime::max();
m_pendingChunk.m_EndTime = CTime::max();
m_pendingChunk.m_StreamIndex = std::numeric_limits<uint32_t>::max();
m_reader.processData(&bytes[0], bytes.size());
}
if (m_pending)
{
// We have dada
// log() << Kernel::LogLevel_Info << "Source: Found a chunk, queueing\n";
const size_t streamIndex = m_pendingChunk.m_StreamIndex;
m_pending = false;
const StreamPtr stream = m_target.getStream(streamIndex);
if (!stream)
{
log() << Kernel::LogLevel_Error << "Error: Trying to decode stream without creating it first (buffer before header in EBML?)\n";
return false;
}
m_decoders[streamIndex]->decode(m_pendingChunk);
return true;
}
}
if (m_origin.isEOF()) { log() << Kernel::LogLevel_Trace << "Source file EOF reached\n"; }
else { log() << Kernel::LogLevel_Warning << "Issue with source file\n"; }
return false;
}
bool Demuxer::isMasterChild(const EBML::CIdentifier& identifier)
{
if (identifier == EBML_Identifier_Header) { return true; }
if (identifier == OVP_NodeId_OpenViBEStream_Header) { return true; }
if (identifier == OVP_NodeId_OpenViBEStream_Header_Compression) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Header_StreamType) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer) { return true; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_StreamIndex) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_StartTime) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_EndTime) { return false; }
if (identifier == OVP_NodeId_OpenViBEStream_Buffer_Content) { return false; }
return false;
}
void Demuxer::openChild(const EBML::CIdentifier& identifier)
{
m_nodes.push(identifier);
EBML::CIdentifier& top = m_nodes.top();
if (top == EBML_Identifier_Header) { m_hasEBMLHeader = true; }
if (top == OVP_NodeId_OpenViBEStream_Header)
{
if (!m_hasEBMLHeader)
{
//this->getLogManager() << Kernel::LogLevel_Info << "The file " << m_filename << " uses an outdated (but still compatible) version of the .ov file format\n";
}
}
if (top == OVP_NodeId_OpenViBEStream_Header)
{
m_streamIdxToOutputIdxs.clear();
m_streamIdxToTypeIDs.clear();
}
}
void Demuxer::processChildData(const void* buffer, const size_t size)
{
EBML::CIdentifier& top = m_nodes.top();
// Uncomment this when ebml version will be used
//if(top == EBML_Identifier_EBMLVersion) { const uint64_t versionNumber=(uint64_t)m_readerHelper.getUInt(buffer, size); }
if (top == OVP_NodeId_OpenViBEStream_Header_Compression)
{
//if (m_readerHelper.getUInt(buffer, size) != 0) { OV_WARNING_K("Impossible to use compression as it is not yet implemented"); }
}
else if (top == OVP_NodeId_OpenViBEStream_Header_StreamType)
{
const uint64_t typeID = m_readerHelper.getUInt(buffer, size);
const size_t index = m_target.getNumStreams();
m_target.createStream(index, typeID);
DecoderBase* decoder = CodecFactory::getDecoder(m_kernelCtx, *m_target.getStream(index));
m_decoders.push_back(decoder);
}
else if (top == OVP_NodeId_OpenViBEStream_Buffer_StreamIndex)
{
// @note if trying to do -1 to map to [0,...] convention, something breaks
m_pendingChunk.m_StreamIndex = size_t(m_readerHelper.getUInt(buffer, size));
//log() << Kernel::LogLevel_Info << "Run into index " << m_pendingChunk.streamIndex << "\n";
}
else if (top == OVP_NodeId_OpenViBEStream_Buffer_StartTime) { m_pendingChunk.m_StartTime = m_readerHelper.getUInt(buffer, size); }
else if (top == OVP_NodeId_OpenViBEStream_Buffer_EndTime) { m_pendingChunk.m_EndTime = m_readerHelper.getUInt(buffer, size); }
else if (top == OVP_NodeId_OpenViBEStream_Buffer_Content)
{
m_pendingChunk.m_Buffer.resize(size_t(size));
memcpy(&m_pendingChunk.m_Buffer[0], reinterpret_cast<const uint8_t*>(buffer), size_t(size));
}
}
void Demuxer::closeChild()
{
EBML::CIdentifier& top = m_nodes.top();
if (top == OVP_NodeId_OpenViBEStream_Header) { } // Assign file streams to outputs here
if (top == OVP_NodeId_OpenViBEStream_Buffer)
{
m_pending = ((m_pendingChunk.m_StreamIndex != std::numeric_limits<size_t>::max()) &&
(m_pendingChunk.m_StartTime != CTime::max()) && (m_pendingChunk.m_EndTime != CTime::max()));
}
m_nodes.pop();
}
} // namespace Tracker
} // namespace OpenViBE
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,186 @@
#include <iostream>
#include <system/ovCTime.h>
#include "ParallelExecutor.h"
#include <array>
namespace OpenViBE {
namespace Tracker {
bool ParallelExecutor::initialize(const uint32_t nThreads)
{
m_quit = false;
m_nJobsRunning = 0;
m_jobList.clear();
// ExecutorView passes the working threads a few function handles to the executor that holds the state
ExecutorView ctx(*this);
for (uint32_t i = 0; i < nThreads; ++i)
{
m_workerThreads.push_back(new CWorkerThread());
m_threads.push_back(new std::thread(std::bind(&CWorkerThread::startWorkerThread, m_workerThreads[i], ctx, i)));
}
return true;
}
bool ParallelExecutor::uninitialize()
{
// Tell the threads waiting in the cond its time to quit, don't care of pending jobs
{ // scope for lock
std::unique_lock<std::mutex> lock(m_jobMutex);
m_jobList.clear();
m_quit = true;
}
m_haveWork.notify_all();
for (uint32_t i = 0; i < m_threads.size(); ++i)
{
m_threads[i]->join();
delete m_threads[i];
delete m_workerThreads[i];
}
m_threads.clear();
m_workerThreads.clear();
return true;
}
bool ParallelExecutor::pushJob(const jobCall& someJob)
{
// @fixme to add better concurrency, push a list instead; lock();add list;unlock();notify_all();
{ // lock scope
std::lock_guard<std::mutex> lock(m_jobMutex);
m_jobList.push_back(someJob);
}
m_haveWork.notify_one();
return true;
}
bool ParallelExecutor::pushJobList(const std::deque<jobCall>& vJobList)
{
{ // lock scope
std::lock_guard<std::mutex> lock(m_jobMutex);
if (!m_jobList.empty())
{
std::cout << "Error, trying to push list with old jobs pending\n";
return false;
}
m_jobList = vJobList;
}
m_haveWork.notify_all();
return true;
}
bool ParallelExecutor::waitForAll()
{
std::unique_lock<std::mutex> lock(m_jobMutex);
while (!m_jobList.empty()) { m_jobDone.wait(lock); }
return true;
}
bool ParallelExecutor::getJob(jobCall& job)
{
// Wait until we get a job or are told to quit
std::unique_lock<std::mutex> lock(m_jobMutex);
m_haveWork.wait(lock, [this]() { return (this->m_quit || !this->m_jobList.empty()); });
if (m_quit) { return false; }
// Ok, we have a job
job = m_jobList.front();
m_jobList.pop_front();
m_nJobsRunning++;
return true;
}
bool ParallelExecutor::declareDone()
{
std::unique_lock<std::mutex> lock(m_jobMutex);
m_nJobsRunning--;
m_jobDone.notify_one();
return true;
}
bool ParallelExecutor::clearPendingJobs()
{
std::unique_lock<std::mutex> lock(m_jobMutex);
m_jobList.clear();
return true;
}
size_t ParallelExecutor::getJobCount() const
{
std::unique_lock<std::mutex> lock(m_jobMutex);
return m_jobList.size();
}
bool ParallelExecutor::isIdle() const
{
std::unique_lock<std::mutex> lock(m_jobMutex);
return (m_jobList.empty() && m_nJobsRunning == 0);
}
//___________________________________________________________________//
// //
void testFunction(void* data)
{
for (uint32_t i = 0; i < 10; ++i)
{
std::cout << "Fun: " << *static_cast<uint32_t*>(data) << "\n";
System::Time::sleep(*static_cast<uint32_t*>(data));
}
// return true;
}
bool ParallelExecutor::launchTest()
{
std::array<int, 6> stuff = { 500, 666, 50, 1000, 300, 100 };
std::cout << "Push test\n";
this->pushJob(std::bind(testFunction, &stuff[0]));
this->waitForAll();
this->pushJob(std::bind(testFunction, &stuff[1]));
this->pushJob(std::bind(testFunction, &stuff[2]));
this->pushJob(std::bind(testFunction, &stuff[3]));
this->pushJob(std::bind(testFunction, &stuff[4]));
this->pushJob(std::bind(testFunction, &stuff[2]));
this->waitForAll();
this->waitForAll();
std::cout << "Pushlist test\n";
std::deque<jobCall> jobList;
jobList.push_back(std::bind(testFunction, &stuff[0]));
jobList.push_back(std::bind(testFunction, &stuff[1]));
this->pushJobList(jobList);
this->waitForAll();
std::cout << "Done\n";
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,512 @@
#include "ProcExternalProcessing.h"
#include <iostream>
#include <thread>
#include <deque>
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include <algorithm> // std::max
#include "CodecFactory.h"
// thread::
// wait for connection;
// while(connected ||!quit)
// {
// waitForChunk();
// pushChunk();
// }
// if(quit) {
// pushEndStim(); // XML have player controller that will quit
// }
// exit;
// #include "StreamChunk.h"
// #include "StreamSignalChunk.h"
// #include "StreamStimulationChunk.h"
#include "Stream.h"
#include "TypeSignal.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
void playerLaunch(const char* xmlFile, const char* args, bool playFast, bool noGUI, uint32_t identifier); // In processor.cpp
ProcExternalProcessing::ProcExternalProcessing(const Kernel::IKernelContext& ctx, const ProcExternalProcessing& other) : Processor(ctx)
{
uint32_t sendPort, recvPort;
other.getProcessorPorts(sendPort, recvPort);
setProcessorPorts(sendPort, recvPort);
bool noGui, doSend, doReceive;
other.getProcessorFlags(noGui, doSend, doReceive);
setProcessorFlags(noGui, doSend, doReceive);
setArguments(other.getArguments());
initialize(other.getFilename());
}
bool ProcExternalProcessing::initialize(const std::string& xmlFile)
{
m_pushStartTime = BufferedClient::CLIENT_NOT_STARTED;
m_pullStartTime = BufferedClient::CLIENT_NOT_STARTED;
if (xmlFile.length() == 0)
{
// log() << Kernel::LogLevel_Trace << "No processor configured\n";
m_xmlFilename.clear();
return true;
}
// log() << Kernel::LogLevel_Trace << "Processor: Initializing with " << xmlFile << "\n";
m_xmlFilename = xmlFile;
return true;
}
bool ProcExternalProcessing::uninitialize()
{
// log() << Kernel::LogLevel_Trace << "Processor: Uninitializing\n";
stop();
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
return true;
}
CTime ProcExternalProcessing::getCurrentTime() const
{
const CTime startTime = (m_doSend ? m_pushStartTime : m_pullStartTime);
if (startTime == BufferedClient::CLIENT_NOT_STARTED) { return CTime::min(); }
const CTime refTime = CTime(m_pushClient ? m_pushClient->getTime() : m_pullClient->getTime());
const CTime currentTime = refTime - startTime;
// log() << Kernel::LogLevel_Info << "Its " << CTime(currentTime).toSeconds() << "\n";
return currentTime;
}
// If source track changes, we need a new codec since they are bound to the streams
// @todo make streamless?
bool ProcExternalProcessing::setNewSource(StreamBundle* source, const bool sendHeader, const bool sendEnd)
{
m_sendHeader = sendHeader;
m_sendEnd = sendEnd;
m_src = source;
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
return true;
}
bool ProcExternalProcessing::setNewTarget(StreamBundle* target)
{
m_dst = target;
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
return true;
}
// Sends all chunks up to the current time point t
bool ProcExternalProcessing::push()
{
if (m_xmlFilename.length() == 0) { return false; }
if (!m_doSend)
{
// This processor is configured to receive, so we do nothing on send
return true;
}
if (!m_pushClient || m_pushClient->hasQuit())
{
this->stop();
return false;
}
if (!m_src) { return false; }
if (m_pushStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
m_pushStartTime = m_pushClient->getStartTime();
if (m_pushStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
// Not yet synced
return true;
}
}
bool foundSomething = false;
m_sentSomething = false;
uint64_t chunksSent = 0;
while (true)
{
size_t streamIndex;
StreamPtr stream = m_src->getNextStream(streamIndex);
if (!stream) { break; }
if (m_encoders.size() <= streamIndex)
{
m_encoders.resize(streamIndex + 1, nullptr);
m_encoders[streamIndex] = CodecFactory::getEncoder(m_kernelCtx, *stream);
}
CTime chunkStartTime = CTime::min(), chunkEndTime = CTime::min();
if (!stream->peek(chunkStartTime, chunkEndTime))
{
// if peek fails maybe the stream has ended?
break;
}
// There is something to send, now or later
foundSomething = true;
const CTime elapsedTime = getCurrentTime();
// Send if its time
if (elapsedTime >= chunkStartTime + m_previousEnd)
{
EncodedChunk chunk;
EChunkType outputType;
m_encoders[streamIndex]->setEncodeOffset(m_previousEnd);
if (m_encoders[streamIndex]->encode(chunk, outputType))
{
const bool dontPush = (!m_sendHeader && outputType == EChunkType::Header) || (!m_sendEnd && outputType == EChunkType::End);
if (!dontPush)
{
chunk.m_StreamIndex = streamIndex;
m_pushClient->pushBuffer(chunk);
m_pushLastTime = chunk.m_EndTime;
/*
log() << Kernel::LogLevel_Info << "Enc str " << streamIndex << " at " << CTime(elapsedTime).toSeconds()
<< " chk [" << CTime(chunk.m_startTime).toSeconds()
<< "," << CTime(chunk.m_endTime).toSeconds()
<< "]\n";
*/
m_sentSomething = true;
chunksSent++;
}
stream->step();
}
else
{
// log() << Kernel::LogLevel_Error << "Error: Failed to encode chunk\n";
return false;
}
}
else
{
// We are early
std::this_thread::sleep_for(std::chrono::milliseconds(1));
break;
}
// } while (sentSomething || m_PlayFast); // we loop until we have sent everything up to this moment.
}
if (m_sentSomething)
{
//log() << Kernel::LogLevel_Info << "flush\n";
m_pushClient->requestFlush();
}
// @fixme note that the while loop above may take long to return the control flow for some
// bad streams
if (!foundSomething)
{
// Maybe all streams ended?
// log() << Kernel::LogLevel_Info << "Nothing to send - all streams ended?\n";
// Update time offset for 'continuous sending' mode
// n.b. we can always do this, since for noncontinuous mode, new play() will be called, resetting these
if (m_src->isFinished() && !m_requestNewOffset)
{
const CTime maxEndTime = m_src->getMaxDuration();
m_previousEnd += maxEndTime; // Incremental for more than 2 tracks
m_requestNewOffset = true;
// log() << Kernel::LogLevel_Info << "New offset at " << CTime(m_PreviousEnd).toSeconds() << "\n";
}
// We do this to keep the External Processing box running on the designer side
m_pushClient->requestFlush();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
return false;
}
if (!m_playFast)
{
// std::this_thread::sleep_for(std::chrono::milliseconds(1));
// std::this_thread::yield();
}
return true;
}
// Sends all chunks up to the current time point t
bool ProcExternalProcessing::pop()
{
if (m_xmlFilename.length() == 0) { return false; }
if (!m_doReceive)
{
// This processor is configured to send, so we do nothing on receive
return true;
}
if (!m_pullClient || !m_dst) { return false; }
if (m_pullClient->hasQuit())
{
this->stop();
return false;
}
if (m_pullStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
m_pullStartTime = m_pullClient->getStartTime();
if (m_pullStartTime == BufferedClient::CLIENT_NOT_STARTED)
{
// Not yet synced
return true;
}
}
EncodedChunk chunk;
bool gotSomething = false;
while (m_pullClient->pullBuffer(chunk))
{
// log() << Kernel::LogLevel_Info << "Got chunk " << chunk.m_startTime << "," << chunk.m_endTime << "\n";
StreamPtr targetStream = m_dst->getStream(chunk.m_StreamIndex);
if (!targetStream)
{
if (!m_dst->createStream(chunk.m_StreamIndex, chunk.m_StreamType)) { continue; }
targetStream = m_dst->getStream(chunk.m_StreamIndex);
if (m_decoders.size() <= chunk.m_StreamIndex) { m_decoders.resize(chunk.m_StreamIndex + 1, nullptr); }
m_decoders[chunk.m_StreamIndex] = CodecFactory::getDecoder(m_kernelCtx, *targetStream);
}
// @note Decoder doesn't need to add offsets as these times are coming from the
// external processor, so they are already offset by the past length.
if (!chunk.m_Buffer.empty())
{
m_decoders[chunk.m_StreamIndex]->decode(chunk);
/*
log() << Kernel::LogLevel_Info << "Dec str " << chunk.streamIndex << " chk [" << CTime(chunk.m_startTime).toSeconds()
<< "," << CTime(chunk.m_endTime).toSeconds() << "]\n";
*/
}
m_pullLastTime = chunk.m_EndTime;
gotSomething = true;
}
if (!gotSomething) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); }
if (m_pullClient->hasQuit()) { return false; }
return true;
}
bool ProcExternalProcessing::play(const bool playFast, const std::function<bool(CTime)>& quitCB, const std::function<bool()>& nextTrackFun)
{
m_pushLastTime = CTime::min();
m_pullLastTime = CTime::min();
m_isRunning = false;
if (m_xmlFilename.length() == 0)
{
log() << Kernel::LogLevel_Error << "Error: No processor initialized\n";
return false;
}
m_chunksSent = 0;
// m_PreviousChunkEnd = 0;
m_playFast = playFast; // @todo To work neatly it'd be better to be able to pass in the chunk times to the designer side
m_previousEnd = 0;
m_requestNewOffset = false;
for (auto decoder : m_decoders) { delete decoder; }
m_decoders.clear();
for (auto encoder : m_encoders) { delete encoder; }
m_encoders.clear();
// log() << Kernel::LogLevel_Info << "Reset offset to " << CTime(m_PreviousEnd).toSeconds() << "\n";
const CString expandedName = m_kernelCtx.getConfigurationManager().expand(m_xmlFilename.c_str());
std::stringstream ss;
ss << "--define Tracker_Port_Send " << m_sendPort << " ";
ss << "--define Tracker_Port_Receive " << m_recvPort << " ";
const std::string allArgs = m_arguments + " " + ss.str();
m_playerThread = new std::thread(std::bind(&playerLaunch, expandedName, allArgs.c_str(), m_playFast, m_noGUI, m_sendPort));
// @fixme this ad-hoc sleep is pretty terrible, but it seems that if player launch is slow enough and
// we launch the client threads immediately below, in Windows it results in deadlocks and errors, suggesting that
// the concurrency control logic is not really correct in the communication with the external processing box.
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
if (m_doSend)
{
m_pushClient = new PushClient(m_sendPort);
m_pushClientThread = new std::thread(&PushClient::start, m_pushClient);
}
if (m_doReceive)
{
m_pullClient = new PullClient(m_recvPort);
m_pullClientThread = new std::thread(&PullClient::start, m_pullClient);
}
// log() << Kernel::LogLevel_Debug << "External processing thread(s) launched, waiting sync\n";
for (size_t retries = 0; retries < 20; ++retries)
{
if (m_doSend) { m_pushStartTime = m_pushClient->getStartTime(); }
if (m_doReceive) { m_pullStartTime = m_pullClient->getStartTime(); }
if (isSynced())
{
// log() << Kernel::LogLevel_Debug << "Clients synced after " << retries << " retries\n";
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
if (!isSynced())
{
// log() << Kernel::LogLevel_Error << "Error syncing client(s) after 10 secs of retries\n";
if (m_pushClient) { m_pushClient->requestQuit(); }
if (m_pullClient) { m_pullClient->requestQuit(); }
return false;
}
m_isRunning = true;
while (m_isRunning)
{
m_isRunning &= push();
m_isRunning &= pop();
if (!m_isRunning && nextTrackFun) { m_isRunning = nextTrackFun(); }
// @fixme the quit callback might be a bit expensive, shouldn't hammer it
if (quitCB && quitCB((m_doSend ? m_pushLastTime : m_pullLastTime))) { m_isRunning = false; }
}
stop();
return true;
}
bool ProcExternalProcessing::stop()
{
m_isRunning = false;
if (m_pushClient)
{
// log() << Kernel::LogLevel_Info << "Stopping external processing push client\n";
m_pushClient->requestQuit();
if (m_pushClientThread) { m_pushClientThread->join(); }
delete m_pushClientThread;
m_pushClientThread = nullptr;
delete m_pushClient;
m_pushClient = nullptr;
}
if (m_pullClient)
{
// log() << Kernel::LogLevel_Info << "Stopping external processing pull client\n";
m_pullClient->requestQuit();
if (m_pullClientThread) { m_pullClientThread->join(); }
delete m_pullClientThread;
m_pullClientThread = nullptr;
delete m_pullClient;
m_pullClient = nullptr;
}
// tear down the player object
if (m_playerThread)
{
// log() << Kernel::LogLevel_Trace << "Joining player thread\n";
m_playerThread->join();
delete m_playerThread;
m_playerThread = nullptr;
}
m_pushStartTime = BufferedClient::CLIENT_NOT_STARTED;
m_pullStartTime = BufferedClient::CLIENT_NOT_STARTED;
return true;
}
bool ProcExternalProcessing::save()
{
auto& mgr = m_kernelCtx.getConfigurationManager();
std::stringstream sPort;
sPort << m_sendPort;
// std::stringstream rPort; rPort << m_RecvPort;
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor", m_xmlFilename.c_str());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_FirstPort", sPort.str().c_str());
// mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_RecvPort", rPort.str().c_str());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_NoGUI", (m_noGUI ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_DoSend", (m_doSend ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_DoReceive", (m_doReceive ? "true" : "false"));
return true;
}
bool ProcExternalProcessing::load()
{
auto& mgr = m_kernelCtx.getConfigurationManager();
if (mgr.lookUpConfigurationTokenIdentifier("Tracker_Workspace_Processor") != CIdentifier::undefined())
{
initialize(mgr.lookUpConfigurationTokenValue("Tracker_Workspace_Processor").toASCIIString());
}
if (mgr.lookUpConfigurationTokenIdentifier("Tracker_Workspace_Processor_FirstPort") != CIdentifier::undefined())
{
const uint32_t port = uint32_t(mgr.expandAsUInteger("${Tracker_Workspace_Processor_FirstPort}"));
setProcessorPorts(port, port + 1);
}
m_noGUI = mgr.expandAsBoolean("${Tracker_Workspace_Processor_NoGUI}", m_noGUI);
m_doSend = mgr.expandAsBoolean("${Tracker_Workspace_Processor_DoSend}", m_doSend);
m_doReceive = mgr.expandAsBoolean("${Tracker_Workspace_Processor_DoReceive}", m_doReceive);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,262 @@
#include <iostream>
#include <thread>
#include <sstream>
#include <system/ovCTime.h>
#include "ProcExternalProcessingHelper.h"
namespace OpenViBE {
namespace Tracker {
const CTime BufferedClient::CLIENT_NOT_STARTED = CTime::max();
void BufferedClient::requestQuit()
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_pleaseQuit = true;
m_bufferCondition.notify_one();
}
bool BufferedClient::hasQuit()
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
return m_hasQuit;
}
void BufferedClient::start()
{
if (!connectClient())
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_hasQuit = true;
return;
}
while (true)
{
// Push or pull, uses lock internally
if (!step()) { break; }
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
// Exit the loop if we're told to quit or if we've lost the connection
if (m_pleaseQuit || !isConnected() || isEndReceived()) { break; }
}
}
// Shut the connection
this->close();
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_hasQuit = true;
}
// The thread will exit here and can be joined
}
bool BufferedClient::connectClient()
{
static int connectId = 0;
std::stringstream cId;
cId << std::string("tracker");
cId << connectId++;
const std::string connectionID = cId.str();
this->setConnectionID(connectionID);
int errorCount = 0;
while (!this->connect("127.0.0.1", m_port))
{
const ELibraryError error = this->getLastError();
if (error == Socket_FailedToConnect)
{
std::this_thread::sleep_for(std::chrono::milliseconds(500));
if (errorCount++ > 20)
{
std::cout << "Designer not responding on port " << m_port << " retried 5 secs" << std::endl;
return false;
}
}
else
{
std::cout << "Error " << error << std::endl;
return false;
}
//if (s_DidRequestForcedQuit) { exit(EXIT_SUCCESS); }
}
// Announce to server that the box has finished initializing and wait for acknowledgement
errorCount = 0;
while (!this->waitForSyncMessage())
{
if (errorCount++ > 10)
{
std::cout << "Server not syncing in port " << m_port << std::endl;
this->close();
return false;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
this->pushLog(Communication::ELogLevel::LogLevel_Info, "Received Ping");
this->pushSync();
this->pushLog(Communication::ELogLevel::LogLevel_Info, "Sent Pong");
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_startTime = this->getTime();
}
return true;
}
bool PushClient::pushBuffer(const EncodedChunk& encodedChunk)
{
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
if (!m_pleaseQuit)
{
EncodedChunk* buffer = new EncodedChunk(encodedChunk);
m_buffer.push_back(buffer);
}
}
// No big harm notifying in any case, though if in 'quit' state, the quit request has already notified
m_bufferCondition.notify_one();
return true;
}
bool PushClient::step()
{
std::unique_lock<std::mutex> oLock(m_threadMutex, std::defer_lock);
oLock.lock();
// Normal condition for the wait to exit is the flush request or having more data
m_bufferCondition.wait(oLock, [this]() { return (m_pleaseFlush || !m_buffer.empty() || m_pleaseQuit || !isConnected() || isEndReceived()); });
while (!m_buffer.empty())
{
EncodedChunk* chunk = m_buffer[0];
m_buffer.pop_front();
if (!this->pushEBML(chunk->m_StreamIndex, chunk->m_StartTime.time(), chunk->m_EndTime.time(),
std::make_shared<const std::vector<uint8_t>>(chunk->m_Buffer)))
{
std::cerr << "Failed to push EBML.\n";
std::cerr << "Error " << this->getLastError() << "\n";
oLock.unlock();
return false;
}
delete chunk;
}
if (m_pleaseFlush)
{
this->pushSync();
m_pleaseFlush = false;
// We don't use condition variable here as users of the client should never
// be able to interrupt this wait unless error state is reached
while (!m_pleaseQuit && isConnected() && !isEndReceived() && !this->waitForSyncMessage())
{
oLock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
oLock.lock();
}
}
oLock.unlock();
return true;
}
void PushClient::requestFlush()
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_pleaseFlush = true;
m_bufferCondition.notify_one();
}
bool PullClient::pullBuffer(EncodedChunk& chunk)
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
if (m_buffer.empty()) { return false; }
EncodedChunk* ptr = m_buffer[0];
m_buffer.pop_front();
chunk = *ptr;
delete ptr;
return true;
}
bool PullClient::popMessagesToBuffer()
{
uint64_t packetId, startTime, endTime;
size_t streamIndex;
std::shared_ptr<const std::vector<uint8_t>> ebml;
bool gotSomething = false;
while (this->popEBML(packetId, streamIndex, startTime, endTime, ebml))
{
// @todo optimize by passing in the buffer to popEBML already
EncodedChunk* chunk = new EncodedChunk();
chunk->m_StartTime = startTime;
chunk->m_EndTime = endTime;
chunk->m_StreamIndex = streamIndex;
// @fixme inefficient to query for each chunk since its stream specific
uint64_t id;
std::string streamName;
this->getInput(chunk->m_StreamIndex, id, chunk->m_StreamType, streamName);
chunk->m_Buffer.resize(ebml->size());
for (size_t i = 0; i < ebml->size(); ++i) { chunk->m_Buffer[i] = (*ebml)[i]; }
{
std::lock_guard<std::mutex> oLock(m_threadMutex);
m_buffer.push_back(chunk);
}
gotSomething = true;
//std::cout << "Got pkg " << packetId << " idx " << streamIndex << " siz " << chunk.bufferData.size() << "\n";
}
return gotSomething;
}
// pull
bool PullClient::step()
{
// Pull items until we get the sync message (no more to send)
while (!this->waitForSyncMessage() && this->isConnected() && !this->isEndReceived())
{
popMessagesToBuffer();
std::this_thread::sleep_for(std::chrono::milliseconds(1));
// std::this_thread::yield();
}
// Got sync, pull whatever remaining buffered on the sender
popMessagesToBuffer();
if (!this->isConnected() || this->isEndReceived()) { return false; }
// Notify we've processed everything
this->pushSync();
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,118 @@
#include "Processor.h"
#include <iostream>
#include <fstream>
#include <chrono>
#include <ctime>
#include <fs/Files.h>
#include <system/ovCMath.h>
#include <thread>
namespace OpenViBE {
namespace Tracker {
void playerLaunch(const char* xmlFile, const char* args, bool playFast, bool noGUI, uint32_t identifier)
{
std::string designer = std::string(Directories::getBinDir().toASCIIString()) + "/openvibe-designer.exe";
std::string argsAll = std::string(" --no-session-management ") + (args ? args : "") + (noGUI ? "--no-gui " : "") + (playFast ? "--play-fast " : " --play ")
+ " " + "\"" + xmlFile + "\"";
if (!FS::Files::directoryExists(Directories::getUserDataDir().toASCIIString())) { FS::Files::createPath(Directories::getUserDataDir().toASCIIString()); }
std::stringstream ss;
ss << std::string(Directories::getLogDir().toASCIIString()) << "/tracker-processor-dump-" << identifier << ".txt";
std::string outputDump = ss.str();
std::time_t now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::ofstream outStream(outputDump.c_str(), std::ios::app);
outStream << std::endl << "Date of launch: " << std::ctime(&now) << std::endl;
outStream << "Trying to launch: " << designer << std::endl;
outStream << "Args: " << argsAll << std::endl;
outStream << "Logging to: " << outputDump << std::endl;
outStream << std::endl;
outStream.close();
#if TARGET_OS_Windows
STARTUPINFO si;
PROCESS_INFORMATION pi;
// set the size of the structures
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = nullptr;
sa.bInheritHandle = TRUE;
HANDLE h = CreateFile(outputDump.c_str(), FILE_APPEND_DATA, FILE_SHARE_WRITE | FILE_SHARE_READ, &sa, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
si.dwFlags |= STARTF_USESTDHANDLES;
si.hStdInput = nullptr;
si.hStdError = h;
si.hStdOutput = h;
argsAll = std::string("\"") + designer + std::string("\"") + std::string(" ") + argsAll;
LPSTR argsLp = const_cast<char*>(argsAll.c_str());
BOOL retVal = CreateProcess(designer.c_str(), // the path
argsLp, // Command line
nullptr, // Process handle not inheritable
nullptr, // Thread handle not inheritable
TRUE, // Set handle inheritance to FALSE
0, // No creation flags
nullptr, // Use parent's environment block
nullptr, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
);
// Close process and thread handles.
if (!retVal) { std::cout << "err: " << GetLastError() << "\n"; }
else { WaitForSingleObject(pi.hProcess, INFINITE); }
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(h);
#else
auto cmd = designer + argsAll + " >" + outputDump;
// std::cout << "Call is: " << cmd << "\n";
if (system(cmd.c_str()) != 0)
{
std::cout << "Launch of [" << cmd << "] failed\n";
}
#endif
}
bool Processor::configure(const char* filename)
{
const std::string usedFilename = (filename ? filename : m_xmlFilename.c_str());
if (usedFilename.length() == 0)
{
log() << Kernel::LogLevel_Error << "Error: Please set processor filename first\n";
return false;
}
const CString expandedName = m_kernelCtx.getConfigurationManager().expand(usedFilename.c_str());
const std::string designer = std::string(Directories::getBinDir().toASCIIString()) + "/openvibe-designer --no-session-management --open ";
const std::string outputDump = std::string(Directories::getDistRootDir().toASCIIString()) + "/tracker-processor-configure-dump.txt";
const std::string cmd = designer + expandedName.toASCIIString() + " >" + outputDump;
if (system(cmd.c_str()) != 0)
{
log() << Kernel::LogLevel_Error << "Launch of [" << cmd << "] failed\n";
return false;
}
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,135 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include "Selection.h"
#include <sstream>
#include <iomanip>
namespace OpenViBE {
namespace Tracker {
bool Selection::reset(const bool state) const
{
for (auto& t : m_tracks) { for (auto& s : t->getAllStreams()) { s->setSelected(state); } }
return true;
}
bool Selection::isEmpty() const
{
for (const auto& t : m_tracks) { if (t->getNumStreams() > 0) { return false; } }
return true;
}
bool Selection::isSomethingSelected() const
{
for (const auto& t : m_tracks) { for (const auto& str : t->getAllStreams()) { if (str->getSelected()) { return true; } } }
return false;
}
bool Selection::isTrackSelected(const size_t track) const
{
if (track >= m_tracks.size()) { return false; }
for (const auto& s : m_tracks[track]->getAllStreams()) { if (s->getSelected()) { return true; } }
return false;
}
bool Selection::isSelectionConsistent() const
{
if (isEmpty() || !isSomethingSelected()) { return true; }
std::vector<CIdentifier> selectedTypesPrevious;
// Check that selection for each track is identical to selection of previous track
// (empty selections per track are ignored)
for (auto& t : m_tracks)
{
std::vector<CIdentifier> selectedTypes;
for (const auto& s : t->getAllStreams()) { if (s->getSelected()) { selectedTypes.push_back(s->getTypeIdentifier()); } }
if (!selectedTypesPrevious.empty() && !selectedTypes.empty() && selectedTypesPrevious != selectedTypes) { return false; }
if (!selectedTypes.empty()) { selectedTypesPrevious = selectedTypes; }
}
return true;
}
size_t Selection::countSelectedTracks() const
{
size_t selectedTracks = 0;
for (const auto& t : m_tracks)
{
for (const auto& s : t->getAllStreams())
{
if (s->getSelected())
{
selectedTracks++;
break;
}
}
}
return selectedTracks;
}
size_t Selection::countSelectedStreams(const size_t trackIndex) const
{
if (trackIndex >= m_tracks.size()) { return 0; }
size_t numSelected = 0;
for (const auto& str : m_tracks[trackIndex]->getAllStreams()) { if (str && str->getSelected()) { numSelected++; } }
return numSelected;
}
bool Selection::save(const char* prefix) const
{
auto& mgr = this->getKernelContext().getConfigurationManager();
// @note with really huge datasets the .ovw file handling can get slow. should be profiled.
for (size_t t = 0; t < m_tracks.size(); ++t)
{
std::stringstream token;
token << prefix << "Track_" << std::setw(3) << std::setfill('0') << (t + 1) << "_Selected";
std::stringstream value;
for (size_t s = 0; s < m_tracks[t]->getNumStreams(); ++s) { if (m_tracks[t]->getStream(s)->getSelected()) { value << (s + 1) << " "; } }
mgr.addOrReplaceConfigurationToken(token.str().c_str(), value.str().c_str());
}
return true;
}
// Relies on the current track/stream set being compatible with the tokens. no error checking
bool Selection::load(const char* prefix) const
{
auto& mgr = this->getKernelContext().getConfigurationManager();
for (size_t t = 0; t < m_tracks.size(); ++t)
{
std::stringstream token;
token << prefix << "Track_" << std::setw(3) << std::setfill('0') << (t + 1) << "_Selected";
std::stringstream value(mgr.lookUpConfigurationTokenValue(token.str().c_str()).toASCIIString());
// Mark everything as unselected by default
for (size_t s = 0; s < m_tracks[t]->getNumStreams(); ++s) { m_tracks[t]->getStream(s)->setSelected(false); }
// Load sparse selection from file
std::string selected;
while (std::getline(value, selected, ' '))
{
const uint32_t selectedIdx = atoi(selected.c_str()) - 1;
if (selectedIdx < m_tracks[t]->getNumStreams()) { m_tracks[t]->getStream(selectedIdx)->setSelected(true); }
}
}
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,70 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include "Stream.h"
#include "StimulationStreamFilter.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
// @note At some point I thought to make a derived class of stimulation stream that simply wouldn't pass through
// the filtered stimulations on calling the getChunk(), but this resulted in some issues. Since modifying
// the original stream would not have been appropriate, I'd have had to choose one of the following,
// 1) make getChunk() in general copy all data instead of returning pointers 2) drop const qualifier
// from the getChunk() to keep track of allocated memory internally in the derived class or 3) make getChunk()
// return a smart pointer. I wasn't very happy about these options, so instead we just make a modded copy of the stream.
std::shared_ptr<StreamBase> filterStimulationStreamEndPoints(const std::shared_ptr<const StreamBase>& src, const Kernel::IKernelContext& ctx)
{
const std::vector<uint64_t> stims = { OVTK_StimulationId_ExperimentStop, OVTK_StimulationId_EndOfFile, OVTK_GDF_End_Of_Session };
auto result = filterStimulationStream(src, ctx, stims);
return result;
}
std::shared_ptr<StreamBase> filterStimulationStream(const std::shared_ptr<const StreamBase>& src, const Kernel::IKernelContext& ctx,
const std::vector<uint64_t>& stimsToFilter)
{
if (src->getTypeIdentifier() != OV_TypeId_Stimulations) { return nullptr; }
const auto typedSrc = std::static_pointer_cast<const Stream<TypeStimulation>>(src);
auto target = std::make_shared<Stream<TypeStimulation>>(ctx);
target->clear();
target->getHeader().m_StartTime = typedSrc->getHeader().m_StartTime;
target->getHeader().m_EndTime = typedSrc->getHeader().m_EndTime;
for (size_t chk = 0; chk < typedSrc->getChunkCount(); ++chk)
{
const auto chunk = typedSrc->getChunk(chk);
auto newChunk = new TypeStimulation::Buffer;
for (size_t i = 0; i < chunk->m_buffer.getStimulationCount(); ++i)
{
const uint64_t id = chunk->m_buffer.getStimulationIdentifier(i);
if (std::none_of(stimsToFilter.begin(), stimsToFilter.end(), [id](const uint64_t val) { return val == id; }))
{
const uint64_t timestamp = chunk->m_buffer.getStimulationDate(i);
const uint64_t duration = chunk->m_buffer.getStimulationDuration(i);
newChunk->m_buffer.appendStimulation(id, timestamp, duration);
}
}
newChunk->m_StartTime = chunk->m_StartTime;
newChunk->m_EndTime = chunk->m_EndTime;
target->push(newChunk);
}
target->getEnd().m_StartTime = typedSrc->getEnd().m_StartTime;
target->getEnd().m_EndTime = typedSrc->getEnd().m_EndTime;
return target;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,197 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <iostream>
#include <algorithm>
#include "StreamBundle.h"
#include "Stream.h"
#include "StreamFactory.h"
namespace OpenViBE {
namespace Tracker {
bool StreamBundle::deepCopy(const StreamBundle& other)
{
bool retVal = true;
// Clear streams
initialize();
// Copy each stream
for (size_t i = 0; i < other.getNumStreams(); ++i)
{
retVal &= createStream(i, other.getStream(i)->getTypeIdentifier());
retVal &= getStream(i)->copy(*other.getStream(i));
}
m_source = other.getSource();
setDirtyBit(other.getDirtyBit());
return retVal;
}
bool StreamBundle::copyFrom(StreamBundle& other)
{
initialize();
for (const auto& str : other.getAllStreams()) { if (str->getSelected()) { this->setStream(m_streams.size(), str); } }
setSource(other.getSource());
setDirtyBit(other.getDirtyBit());
// n.b. this will affect the streams we copied
rewind();
return true;
}
bool StreamBundle::initialize()
{
//reset first
uninitialize();
m_dirty = true;
// log() << Kernel::LogLevel_Debug << "Streams initialized ok\n";
return true;
}
bool StreamBundle::uninitialize()
{
// Since m_Streams are shared pointers, no need to delete them
m_streams.clear();
return true;
}
bool StreamBundle::rewind()
{
bool returnValue = true;
std::for_each(m_streams.begin(), m_streams.end(), [&returnValue](const StreamPtr& entry) { if (entry) { returnValue &= entry->reset(); } });
return returnValue;
}
bool StreamBundle::createStream(const size_t index, const CIdentifier& typeID)
{
if (index >= m_streams.size()) { m_streams.resize(index + 1, nullptr); }
if (m_streams[index] == nullptr)
{
const StreamPtr stream = StreamFactory::getStream(m_kernelCtx, typeID);
if (!stream) { return false; }
m_streams[index] = stream;
setDirtyBit(true);
return true;
}
log() << Kernel::LogLevel_Error << "Error: Slot " << index << " is already used\n";
return false;
}
bool StreamBundle::deleteStream(const size_t index)
{
if (index > m_streams.size())
{
log() << Kernel::LogLevel_Error << "Error: Stream index exceeds array size\n";
return false;
}
// m_Streams is shared ptrs, no need to delete
m_streams.erase(m_streams.begin() + index);
setDirtyBit(true);
return true;
}
bool StreamBundle::getNextStreamIndex(size_t& index) const
{
if (m_streams.empty()) { return false; }
// Find the stream with the earliest chunk, return the stream
CTime earliestTime = CTime::max();
bool foundSomething = false;
for (size_t i = 0; i < m_streams.size(); ++i)
{
const StreamPtr ptr = m_streams[i];
CTime startTime = CTime::min(), endTime = CTime::min();
if (ptr && ptr->peek(startTime, endTime) && startTime < earliestTime)
{
earliestTime = startTime;
index = int(i);
foundSomething = true;
}
}
if (!foundSomething)
{
//log() << Kernel::LogLevel_Info << "All streams exhausted\n";
return false;
}
return true;
}
StreamPtr StreamBundle::getNextStream(size_t& index)
{
index = -1;
if (getNextStreamIndex(index)) { return m_streams[index]; }
return nullptr;
}
CTime StreamBundle::getMaxDuration() const
{
CTime maxDuration = CTime::min();
for (size_t i = 0; i < m_streams.size(); ++i)
{
if (m_streams[i])
{
CTime streamDuration = m_streams[i]->getDuration();
maxDuration = std::max<CTime>(maxDuration, streamDuration);
}
}
return maxDuration;
}
bool StreamBundle::setStream(const size_t index, const std::shared_ptr<StreamBase>& ptr)
{
if (index >= m_streams.size()) { m_streams.resize(index + 1, nullptr); }
m_streams[index] = ptr;
setDirtyBit(true);
return true;
}
bool StreamBundle::swapStreams(const size_t idx1, const size_t idx2)
{
if (idx1 >= m_streams.size() || idx2 >= m_streams.size()) { return false; }
const auto it1 = m_streams.begin() + idx1;
const auto it2 = m_streams.begin() + idx2;
std::iter_swap(it1, it2);
setDirtyBit(true);
return true;
}
bool StreamBundle::moveStream(const size_t srcIdx, const size_t dstIdx)
{
if (srcIdx >= getNumStreams() || dstIdx >= getNumStreams()) { return false; }
if (srcIdx == dstIdx) { return true; }
const auto oldPtr = m_streams[srcIdx];
m_streams.erase(m_streams.begin() + srcIdx);
m_streams.insert(m_streams.begin() + dstIdx, oldPtr);
setDirtyBit(true);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,106 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// n.b. having this as a separate file so EBML/Demuxer dependencies do not get pulled into StreamBundle
#include <iostream>
#include <algorithm>
#include "StreamBundleImportExport.h"
#include "EBMLSourceFile.h"
#include "Demuxer.h"
#include "BoxAdapterGenericStreamWriter.h"
namespace OpenViBE {
namespace Tracker {
StreamBundle* readStreamBundleFromFile(const Kernel::IKernelContext& ctx, const char* filename, const bool memorySaveMode)
{
StreamBundle* newTrack = new StreamBundle(ctx);
EBMLSourceFile origin(ctx);
if (!origin.initialize(filename))
{
ctx.getLogManager() << Kernel::LogLevel_Error << "Unable to read from file " << filename << "\n";
delete newTrack;
return nullptr;
}
// Construct a track from the origin stream
Demuxer demux(ctx, origin, *newTrack);
if (memorySaveMode)
{
// Try to get the stream types but don't pull the buffers
bool go = true;
while (go)
{
if (!demux.step())
{
ctx.getLogManager() << Kernel::LogLevel_Trace << "Demuxer EOF\n";
go = false;
}
// n.b. here we use a heuristic that if at least a single chunk has been loaded,
// then all the headers should have been demuxed before that. There is no guarantee
// that this would always be the case, but it is very likely if the stream originates from
// an .ov file created by OpenViBE
for (size_t j = 0; j < newTrack->getNumStreams(); ++j)
{
if (newTrack->getStream(j)->getDuration().time() > 0)
{
go = false;
break;
}
}
}
origin.uninitialize();
// Release memory
for (size_t i = 0; i < newTrack->getNumStreams(); ++i) { newTrack->getStream(i)->clear(); }
}
else
{
// Just read the whole file to memory
while (true)
{
if (!demux.step())
{
ctx.getLogManager() << Kernel::LogLevel_Trace << "Demuxer EOF\n";
break;
}
}
}
origin.uninitialize();
newTrack->setDirtyBit(false);
return newTrack;
}
bool saveStreamBundleToFile(const Kernel::IKernelContext& ctx, StreamBundle* track, const char* filename)
{
BoxAdapterGenericStreamWriter writer(ctx, *track, filename);
bool retval = true;
retval &= writer.initialize();
retval &= writer.spool(false);
retval &= writer.uninitialize();
if (retval)
{
track->setSource(filename);
retval &= true;
track->setDirtyBit(false);
}
return retval;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,322 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @todo add horizontal scaling support
// @todo add event handlers
// @todo add ruler, stimulations, channel names, a million of other things
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include <mensia/advanced-visualization.hpp>
#include <m_GtkGL.hpp>
#include "StreamRendererBase.h"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererBase::initialize()
{
GtkBuilder* builder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/advanced-visualization.ui";
GError* errorCode = nullptr;
if (!gtk_builder_add_from_file(builder, filename, &errorCode))
{
log() << Kernel::LogLevel_Error << "Problem loading [" << filename << "] : "
<< (errorCode ? errorCode->code : 0) << " " << (errorCode ? errorCode->message : "") << "\n";
g_object_unref(builder);
return false;
}
GtkWidget* window = GTK_WIDGET(gtk_builder_get_object(builder, "window"));
m_main = GTK_WIDGET(gtk_builder_get_object(builder, "table"));
// ::GtkWidget* toolbar = GTK_WIDGET(gtk_builder_get_object(pBuilder, "toolbar-window"));
gtk_widget_ref(m_main);
gtk_container_remove(GTK_CONTAINER(window), m_main);
// We keep one ref, the caller can unref after having assigned the widget
m_viewport = GTK_WIDGET(gtk_builder_get_object(builder, "viewport"));
m_top = GTK_WIDGET(gtk_builder_get_object(builder, "label_top")); // caption
m_left = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea_left"));
m_right = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea_right"));
m_bottom = GTK_WIDGET(gtk_builder_get_object(builder, "drawingarea_bottom"));
m_cornerLeft = GTK_WIDGET(gtk_builder_get_object(builder, "label_corner_left"));
m_cornerRight = GTK_WIDGET(gtk_builder_get_object(builder, "label_corner_right"));
// @note this or something similar is needed or otherwise the widgets will all be crammed to the
// same fixed size aperture with no scrolling
// @todo give users some scaling options
// @fixme for some reason this causes a mess on the 'message' bar below in the UI, as if it didn't redraw properly
gtk_widget_set_size_request(m_main, 640, 200);
m_color.r = 1;
m_color.g = 1;
m_color.b = 1;
g_object_unref(builder);
builder = nullptr;
return true;
}
bool StreamRendererBase::uninitialize()
{
for (size_t i = 0; i < m_renderers.size(); ++i) { AdvancedVisualization::IRenderer::release(m_renderers[i]); }
m_renderers.clear();
if (m_rendererCtx)
{
delete m_rendererCtx;
m_rendererCtx = nullptr;
}
if (m_subRendererCtx)
{
delete m_subRendererCtx;
m_subRendererCtx = nullptr;
}
if (m_ruler)
{
delete m_ruler;
m_ruler = nullptr;
}
return true;
}
bool StreamRendererBase::setTitle(const char* title)
{
if (title) { gtk_label_set_text(GTK_LABEL(m_top), title); }
else { gtk_label_set_text(GTK_LABEL(m_top), ""); }
return true;
}
bool StreamRendererBase::setRulerVisibility(const bool isVisible)
{
m_isScaleVisible = isVisible;
return updateRulerVisibility();
}
bool StreamRendererBase::updateRulerVisibility()
{
/*
if ((gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pScaleVisible)) ? true : false) != m_isScaleVisible)
{
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pScaleVisible), m_isScaleVisible);
}
*/
void (*action)(GtkWidget*) = m_isScaleVisible ? gtk_widget_show : gtk_widget_hide;
(*action)(this->m_top);
(*action)(this->m_left);
(*action)(this->m_right);
(*action)(this->m_bottom);
(*action)(this->m_cornerLeft);
(*action)(this->m_cornerRight);
return true;
}
bool StreamRendererBase::realize()
{
gtk_widget_realize(m_top);
gtk_widget_realize(m_left);
gtk_widget_realize(m_right);
gtk_widget_realize(m_bottom);
gtk_widget_realize(m_viewport);
gtk_widget_realize(m_main);
return true;
}
bool StreamRendererBase::redraw(const bool bImmediate /* = false */)
{
m_gtkGLWidget.redraw(bImmediate);
m_gtkGLWidget.redrawLeft(bImmediate);
m_gtkGLWidget.redrawRight(bImmediate);
m_gtkGLWidget.redrawBottom(bImmediate);
return true;
}
bool StreamRendererBase::reshape(const uint32_t width, const uint32_t height)
{
m_width = uint32_t(width);
m_height = uint32_t(height);
m_rendererCtx->setAspect(width * 1.0F / height);
return true;
}
bool StreamRendererBase::draw()
{
StreamRendererBase::preDraw();
glPushAttrib(GL_ALL_ATTRIB_BITS);
//::glClearColor(1.0,1.0,1.0,1.0);
glColor4f(m_color.r, m_color.g, m_color.b, m_rendererCtx->getTranslucency());
if (m_rotate)
{
glScalef(1, -1, 1);
glRotatef(-90, 0, 0, 1);
}
m_renderers[0]->render(*m_rendererCtx);
glPopAttrib();
StreamRendererBase::postDraw();
return true;
}
void StreamRendererBase::drawLeft() { if (m_ruler) { m_ruler->doRenderLeft(m_left); } }
void StreamRendererBase::drawRight() { if (m_ruler) { m_ruler->doRenderRight(m_right); } }
void StreamRendererBase::drawBottom() { if (m_ruler) { m_ruler->doRenderBottom(m_bottom); } }
bool StreamRendererBase::preDraw()
{
this->updateRulerVisibility();
// auto m_sColorGradient=CString("0:0,0,0; 100:100,100,100");
const char* gradient =
"0:100, 100, 100; 12:50, 100, 100; 25:0, 50, 100; 38:0, 0, 50; 50:0, 0, 0; 62:50, 0, 0; 75:100, 50, 0; 88:100, 100, 50; 100:100, 100, 100";
if (!m_textureID) { m_textureID = m_gtkGLWidget.createTexture(gradient); }
glBindTexture(GL_TEXTURE_1D, m_textureID);
m_rendererCtx->setAspect(m_viewport->allocation.width * 1.0F / m_viewport->allocation.height);
return true;
}
bool StreamRendererBase::postDraw()
{
glPushAttrib(GL_ALL_ATTRIB_BITS);
if (m_ruler) { m_ruler->doRender(); }
glPopAttrib();
return true;
}
bool StreamRendererBase::mouseButton(int /*x*/, int /*y*/, const int button, const int status)
{
m_buttons[button] = status;
/*
if (button == 1 && status == 1)
{
m_isScaleVisible = !m_isScaleVisible;
m_pRendererContext->setScaleVisibility(m_isScaleVisible);
}
*/
return true;
}
bool StreamRendererBase::mouseMotion(const int x, const int y)
{
if (!m_mouseInitialized)
{
m_mouseX = x;
m_mouseY = y;
m_mouseInitialized = true;
}
if (m_buttons[3])
{
const float value = powf(0.99F, float(y - m_mouseY));
// std::cout << "scale " << value << "\n";
m_rendererCtx->scaleBy(value);
redraw();
}
if (m_buttons[2])
{
const float value = powf(0.99F, float(y - m_mouseY));
// std::cout << "zoom " << value << "\n";
m_rendererCtx->zoomBy(value);
}
if (m_buttons[1])
{
// std::cout << "Rotate\n";
m_rendererCtx->rotateByY(float(x - m_mouseX) * 0.1F);
m_rendererCtx->rotateByX(float(y - m_mouseY) * 0.1F);
}
m_mouseX = x;
m_mouseY = y;
return true;
}
bool StreamRendererBase::finalize()
{
for (size_t i = 0; i < m_renderers.size(); ++i)
{
m_renderers[i]->rebuild(*m_rendererCtx);
m_renderers[i]->refresh(*m_rendererCtx);
}
redraw(true);
return true;
}
bool StreamRendererBase::restoreSettings(const std::string& prefix)
{
if (!m_rendererCtx) { return false; }
// Lets see if we have a scale token
const std::string token = std::string("${") + prefix + "_Scale}";
const float newScale = float(m_kernelCtx.getConfigurationManager().expandAsFloat(token.c_str(), m_rendererCtx->getScale()));
m_rendererCtx->setScale(newScale);
return true;
}
bool StreamRendererBase::storeSettings(const std::string& prefix)
{
if (!m_rendererCtx) { return false; }
const std::string token = prefix + "_Scale";
std::stringstream value;
value << m_rendererCtx->getScale();
m_kernelCtx.getConfigurationManager().addOrReplaceConfigurationToken(token.c_str(), value.str().c_str());
return true;
}
CString StreamRendererBase::renderAsText(const size_t indent) const
{
return (std::string(indent, ' ') + "Detail printing unimplemented for stream type or placeholder renderer in use\n").c_str();
}
void add_column(GtkTreeView* treeView, const char* name, const uint32_t id, const uint32_t minWidth)
{
GtkTreeViewColumn* column = gtk_tree_view_column_new();
GtkCellRenderer* cell = gtk_cell_renderer_text_new();
gtk_tree_view_column_set_title(column, name);
gtk_tree_view_column_pack_start(column, cell, TRUE);
gtk_tree_view_column_set_attributes(column, cell, "text", id, nullptr);
gtk_tree_view_column_set_sort_column_id(column, id);
gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_set_expand(column, TRUE);
gtk_tree_view_column_set_resizable(column, TRUE);
gtk_tree_view_column_set_min_width(column, minWidth);
gtk_tree_view_append_column(treeView, column);
gtk_tree_view_column_set_sort_indicator(column, TRUE);
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,47 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "StreamRendererChannelLocalization.h"
namespace OpenViBE {
namespace Tracker {
CString StreamRendererChannelLocalization::renderAsText(const size_t indent) const
{
const TypeChannelLocalization::Header& hdr = m_Stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Dynamic: " << (hdr.m_Dynamic ? "True" : "False") << std::endl;
for (size_t i = 0; i < m_Stream->getChunkCount(); ++i)
{
const TypeChannelLocalization::Buffer* buf = m_Stream->getChunk(i);
ss << std::string(indent, ' ') << "Configuration at time " << buf->m_StartTime.toSeconds() << "s:" << std::endl;
const double* ptr = buf->m_buffer.getBuffer();
for (uint32_t chn = 0; chn < buf->m_buffer.getDimensionSize(0); ++chn)
{
ss << std::string(indent, ' ') << " Channel " << chn << " (" << hdr.m_Header.getDimensionLabel(0, chn) << ") " << "x=" << ptr[chn * 3 + 0] << " "
<< "y=" << ptr[chn * 3 + 1] << " " << "z=" << ptr[chn * 3 + 2] << std::endl;
}
}
return ss.str().c_str();
}
bool StreamRendererChannelLocalization::showChunkList() { return StreamRendererLabel::showChunkList("Channel localization stream details"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,49 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <system/ovCTime.h>
#include "StreamRendererChannelUnits.h"
#include <iostream>
namespace OpenViBE {
namespace Tracker {
CString StreamRendererChannelUnits::renderAsText(const size_t indent) const
{
const TypeChannelUnits::Header& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Dynamic: "
<< (hdr.m_Dynamic ? "True" : "False") << std::endl;
for (size_t i = 0; i < m_stream->getChunkCount(); ++i)
{
const TypeChannelUnits::Buffer* buf = m_stream->getChunk(i);
ss << std::string(indent, ' ') << "Configuration at time " << buf->m_StartTime.toSeconds() << "s:" << std::endl;
const double* ptr = buf->m_buffer.getBuffer();
for (uint32_t chn = 0; chn < buf->m_buffer.getDimensionSize(0); ++chn)
{
const CString unit = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_MeasurementUnit, uint64_t(ptr[chn * 2 + 0]));
const CString factor = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Factor, uint64_t(ptr[chn * 2 + 1]));
ss << std::string(indent, ' ') << " Channel " << chn << " (" << hdr.m_Header.getDimensionLabel(0, chn) << ") " << "Unit: " << unit << ", Factor: "
<< factor << std::endl;
}
}
return ss.str().c_str();
}
bool StreamRendererChannelUnits::showChunkList() { return StreamRendererLabel::showChunkList("Channel units stream details"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,45 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "StreamRendererExperimentInfo.h"
namespace OpenViBE {
namespace Tracker {
CString StreamRendererExperimentInfo::renderAsText(const size_t indent) const
{
const TypeExperimentInfo::Header& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Experiment id: " << hdr.m_ExperimentID << std::endl;
ss << std::string(indent, ' ') << "Experiment date: " << hdr.m_ExperimentDate << std::endl;
ss << std::string(indent, ' ') << "Subject id: " << hdr.m_SubjectID << std::endl;
ss << std::string(indent, ' ') << "Subject name: " << hdr.m_SubjectName << std::endl;
ss << std::string(indent, ' ') << "Subject age: " << hdr.m_SubjectAge << std::endl;
ss << std::string(indent, ' ') << "Subject gender: " << hdr.m_SubjectGender << std::endl;
ss << std::string(indent, ' ') << "Laboratory id: " << hdr.m_LaboratoryID << std::endl;
ss << std::string(indent, ' ') << "Laboratory name: " << hdr.m_LaboratoryName << std::endl;
ss << std::string(indent, ' ') << "Technician id: " << hdr.m_TechnicianID << std::endl;
ss << std::string(indent, ' ') << "Technician name: " << hdr.m_TechnicianName << std::endl;
// ss << string(indent, ' ') << "Channels: " << m_Header.m_header.getDimensionSize(0) << std::endl;
// ss << string(indent, ' ') << "Samples per chunk: " << m_Header.m_header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererExperimentInfo::showChunkList() { return StreamRendererLabel::showChunkList("Experiment information stream details"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,82 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <system/ovCTime.h>
#include "StreamRendererLabel.h"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererLabel::initialize()
{
m_main = gtk_frame_new("");
m_label = gtk_label_new("");
gtk_label_set_justify(GTK_LABEL(m_label), GTK_JUSTIFY_LEFT);
gtk_container_add(GTK_CONTAINER(m_main), m_label);
gtk_widget_ref(m_main);
// @fixme hardcoded numbers likely not such a great idea as different font sizes etc
gtk_widget_set_size_request(m_main, 640, 40);
gtk_widget_show(m_main);
gtk_widget_show(m_label);
return true;
}
bool StreamRendererLabel::setTitle(const char* title)
{
gtk_label_set(GTK_LABEL(m_label), title);
return true;
}
// Instead of showing chunk list, this prints the stream structure information
// in a window. How it works is that a derived class can implement a text render
// routine which this function then calls. This way each derived class
// doesn't need to copy-paste the identical window code.
bool StreamRendererLabel::showChunkList(const char* title)
{
// @fixme Not very pretty but better than nothing; here we have the benefit
// that since we make a new builder here, reusing the workspace information
// window does not mess up the real one
GtkBuilder* builder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/tracker.ui";
if (!gtk_builder_add_from_file(builder, filename, nullptr))
{
std::cout << "Problem loading [" << filename << "]\n";
return false;
}
GtkWidget* window = GTK_WIDGET(gtk_builder_get_object(builder, "tracker-workspace_information"));
GtkTextBuffer* buffer = GTK_TEXT_BUFFER(gtk_builder_get_object(builder, "tracker-textbuffer-workspace_information"));
gtk_window_set_title(GTK_WINDOW(window), title);
// Hide instead of destroy on closing the window
g_signal_connect(window, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
const CString details = renderAsText(0);
gtk_text_buffer_set_text(buffer, details.toASCIIString(), -1);
gtk_widget_show_all(window);
gtk_window_present(GTK_WINDOW(window));
g_object_unref(builder);
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,235 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @todo add horizontal scaling support
// @todo add event handlers
// @todo add ruler, stimulations, channel names, a million of other things
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <m_GtkGL.hpp>
#include <system/ovCTime.h>
#include "StreamRendererMatrix.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
#include "ruler/mCRulerRightFrequency.hpp"
#include "ruler/mCRulerRightTexture.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererMatrix::initialize()
{
const TypeMatrix::Buffer* firstChunk = m_stream->getChunk(0);
if (firstChunk)
{
m_chunkDuration = firstChunk->m_EndTime - firstChunk->m_StartTime;
if (firstChunk->m_buffer.getDimensionCount() == 1)
{
// Degenerate case like a feature vector
m_nRows = firstChunk->m_buffer.getDimensionSize(0);
m_nCols = 0;
}
else if (firstChunk->m_buffer.getDimensionCount() == 2)
{
m_nRows = firstChunk->m_buffer.getDimensionSize(0);
m_nCols = firstChunk->m_buffer.getDimensionSize(1);
}
else if (firstChunk->m_buffer.getDimensionCount() > 2)
{
std::cout << "Warning: The matrix renderer does not work correctly if dims>2 (for tensors)\n";
m_nRows = firstChunk->m_buffer.getDimensionSize(0);
m_nCols = firstChunk->m_buffer.getDimensionSize(1);
}
else { log() << Kernel::LogLevel_Error << "Error: Dimension count " << firstChunk->m_buffer.getDimensionCount() << " not supported\n"; }
}
else
{
std::cout << "Stream is empty\n";
return false;
}
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setTimeLocked(true);
m_rendererCtx->setPositiveOnly(false);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Matrix);
m_rendererCtx->setSampleDuration(m_chunkDuration.time());
m_rendererCtx->setParentRendererContext(&AdvancedVisualization::getContext());
m_rendererCtx->setAxisDisplay(true);
auto& hdr = m_stream->getHeader();
for (uint32_t j = 0; j < m_nRows; ++j) { m_rendererCtx->addChannel(std::string(hdr.m_Header.getDimensionLabel(0, j))); }
m_swap.resize(m_nRows);
m_renderers.push_back(AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Bitmap, false));
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::CRulerProgressV, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::IRuler>,
AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerRightTexture>>>;
// m_pRuler = new TRulerPair < TRulerConditionalPair < CRulerBottomTime, CRulerBottomCount, CRulerConditionIsTimeLocked >, TRulerPair < TRulerAutoType < IRuler, IRuler, CRulerRightFrequency >, TRulerPair < CRulerLeftChannelNames, CRulerProgressV > > >;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(m_renderers[0]);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
return true;
}
bool StreamRendererMatrix::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
const uint32_t numBuffers = ((m_endTime - m_startTime).ceil().time() / m_chunkDuration.time());
m_rendererCtx->setElementCount(numBuffers);
m_renderers[0]->clear(0);
m_renderers[0]->setSampleCount(numBuffers); // $$$
m_renderers[0]->setChannelCount(m_nRows);
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
return true;
}
bool StreamRendererMatrix::push(const TypeMatrix::Buffer& chunk, bool /*zeroInput*/)
{
#if 0
static uint32_t pushed = 0;
std::cout << "Push spec chk " << pushed << " " << chunk.m_buffer.getDimensionSize(0)
<< " " << chunk.m_buffer.getDimensionSize(1) << " "
<< CTime(chunk.m_startTime).toSeconds() << ","
<< CTime(chunk.m_endTime).toSeconds()
<< "\n";
std::cout << pushed << " first bytes "
<< chunk.m_buffer.getBuffer()[0]
<< chunk.m_buffer.getBuffer()[1]
<< chunk.m_buffer.getBuffer()[2]
<< chunk.m_buffer.getBuffer()[3]
<< "\n";
pushed++;
#endif
m_rendererCtx->setSpectrumFrequencyRange(uint32_t((uint64_t(m_nRows) << 32) / m_chunkDuration.time()));
// Handle the degenerate case NumCols=0
const size_t actualCols = std::max<size_t>(m_nCols, 1);
// Feed renderer with actual samples
for (uint32_t j = 0; j < actualCols; ++j)
{
for (uint32_t k = 0; k < m_nRows; ++k) { m_swap[k] = float(chunk.m_buffer.getBuffer()[k * actualCols + j]); }
m_renderers[0]->feed(&m_swap[0]);
}
return true;
}
bool StreamRendererMatrix::draw()
{
StreamRendererMatrix::preDraw();
glPushAttrib(GL_ALL_ATTRIB_BITS);
glColor4f(m_color.r, m_color.g, m_color.b, m_rendererCtx->getTranslucency());
m_renderers[0]->render(*m_rendererCtx);
glPopAttrib();
StreamRendererMatrix::postDraw();
return true;
}
bool StreamRendererMatrix::preDraw()
{
this->updateRulerVisibility();
// auto m_sColorGradient=CString("0:0,0,0; 100:100,100,100");
const std::string gradient = "0:0, 0, 50; 25:0, 100, 100; 50:0, 50, 0; 75:100, 100, 0; 100:75, 0, 0";
// auto m_sColorGradient = CString("0:100, 100, 100; 12:50, 100, 100; 25:0, 50, 100; 38:0, 0, 50; 50:0, 0, 0; 62:50, 0, 0; 75:100, 50, 0; 88:100, 100, 50; 100:100, 100, 100");
if (!m_textureID) { m_textureID = m_gtkGLWidget.createTexture(gradient); }
glBindTexture(GL_TEXTURE_1D, m_textureID);
m_rendererCtx->setAspect(m_viewport->allocation.width * 1.0F / m_viewport->allocation.height);
return true;
}
bool StreamRendererMatrix::finalize()
{
m_renderers[0]->rebuild(*m_rendererCtx);
m_renderers[0]->refresh(*m_rendererCtx);
redraw(true);
return true;
}
CString StreamRendererMatrix::renderAsText(const size_t indent) const
{
std::stringstream ss;
ss << std::string(indent, ' ') << "Rows: " << m_nRows << std::endl;
ss << std::string(indent, ' ') << "Cols: " << m_nCols << std::endl;
// ss << std::string(indent, ' ') << "Channels: " << m_Header.m_header.getDimensionSize(0) << std::endl;
// ss << std::string(indent, ' ') << "Samples per chunk: " << m_Header.m_header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererMatrix::mouseButton(const int x, const int y, const int button, const int status)
{
//if (button == 3 && status == 1) { showChunkList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
bool StreamRendererMatrix::showChunkList() { return showMatrixList<TypeMatrix>(m_stream, &m_streamListWindow, "List of chunks for Matrix stream"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,153 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <mTGtkGLWidget.hpp>
#include <m_GtkGL.hpp>
#include "StreamRendererSignal.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererSignal::initialize()
{
// TRendererStimulation < false, CRendererLine >:new CRendererLine
AdvancedVisualization::IRenderer* pRenderer = AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Line, true);
if (pRenderer == nullptr) { return false; }
const TypeSignal::Buffer* firstChunk = m_stream->getChunk(0);
if (!firstChunk) { return false; }
m_nChannel = firstChunk->m_buffer.getDimensionSize(0);
m_samplesPerChunk = firstChunk->m_buffer.getDimensionSize(1);
m_chunkDuration = firstChunk->m_EndTime - firstChunk->m_StartTime;
// Creates renderer context
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setScale(1.0);
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Signal);
const CTime sampleDuration = CTime(m_chunkDuration.time() / m_samplesPerChunk);
m_rendererCtx->setSampleDuration(sampleDuration.time());
const TypeSignal::Header& hdr = m_stream->getHeader();
for (uint32_t i = 0; i < m_nChannel; ++i)
{
const char* label = hdr.m_Header.getDimensionLabel(0, i);
m_rendererCtx->addChannel(std::string(label));
}
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::CRulerProgressV, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::IRuler>,
AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerRightScale>>>;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(pRenderer);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
m_renderers.push_back(pRenderer);
return true;
}
bool StreamRendererSignal::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
// std::cout << "Overridden signal renderer reset\n";
m_renderers[0]->clear(0);
// std::cout << "Start time is " << CTime(m_StartTime).toSeconds()
// << " end is " << CTime(m_EndTime).toSeconds() << "\n";
const uint64_t chunkCount = (m_endTime - m_startTime).ceil().time() / m_chunkDuration.time();
const uint32_t numSamples = uint32_t(m_samplesPerChunk * chunkCount);
m_renderers[0]->setSampleCount(numSamples);
m_renderers[0]->setChannelCount(m_nChannel);
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
// m_renderers[0]->setHistoryDrawIndex(samplesBeforeStart);
m_renderers[0]->rebuild(*m_rendererCtx);
return true;
}
bool StreamRendererSignal::push(const TypeSignal::Buffer& chunk, const bool zeroInput /* = false */)
{
std::vector<float> tmp;
if (!zeroInput)
{
tmp.resize(chunk.m_buffer.getBufferElementCount());
for (size_t i = 0; i < chunk.m_buffer.getBufferElementCount(); ++i) { tmp[i] = float(chunk.m_buffer.getBuffer()[i]); }
}
else { tmp.resize(chunk.m_buffer.getBufferElementCount(), 0); }
m_renderers[0]->feed(&tmp[0], chunk.m_buffer.getDimensionSize(1));
return true;
}
CString StreamRendererSignal::renderAsText(const size_t indent) const
{
auto& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Sampling rate: " << hdr.m_Sampling << "hz" << std::endl;
ss << std::string(indent, ' ') << "Channels: " << hdr.m_Header.getDimensionSize(0) << std::endl;
ss << std::string(indent, ' ') << "Samples per chunk: " << hdr.m_Header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererSignal::mouseButton(const int x, const int y, const int button, const int status)
{
//if (button == 3 && status == 1) { showChunkList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
bool StreamRendererSignal::showChunkList() { return showMatrixList<TypeSignal>(m_stream, &m_streamListWindow, "List of chunks for Signal stream"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,270 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @todo add horizontal scaling support
// @todo add event handlers
// @todo add ruler, stimulations, channel names, a million of other things
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <m_GtkGL.hpp>
#include <system/ovCTime.h>
#include "StreamRendererSpectrum.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
#include "ruler/mCRulerRightFrequency.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererSpectrum::initialize()
{
const TypeSpectrum::Buffer* firstChunk = m_stream->getChunk(0);
if (!firstChunk) { return false; }
m_nChannel = firstChunk->m_buffer.getDimensionSize(0);
m_spectrumElements = firstChunk->m_buffer.getDimensionSize(1);
m_chunkDuration = firstChunk->m_EndTime - firstChunk->m_StartTime;
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setTimeLocked(true);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Spectrum);
m_rendererCtx->setSampleDuration(m_chunkDuration.time());
const TypeSpectrum::Header& hdr = m_stream->getHeader();
for (uint32_t j = 0; j < m_nChannel; ++j)
{
const char* name = hdr.m_Header.getDimensionLabel(0, j);
m_rendererCtx->addChannel(std::string(name));
}
m_subRendererCtx = new AdvancedVisualization::CRendererContext();
m_subRendererCtx->clear();
m_subRendererCtx->setParentRendererContext(m_rendererCtx);
m_subRendererCtx->setTimeLocked(true);
m_subRendererCtx->setStackCount(m_nChannel);
m_subRendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Spectrum);
m_subRendererCtx->setSampleDuration(m_chunkDuration.time());
for (uint32_t j = 0; j < m_spectrumElements; ++j)
{
std::stringstream ss;
ss << j;
m_subRendererCtx->addChannel(ss.str());
}
m_swaps.resize(m_spectrumElements);
for (size_t i = 0; i < m_renderers.size(); ++i) { AdvancedVisualization::IRenderer::release(m_renderers[i]); }
m_renderers.clear();
m_renderers.resize(m_nChannel);
for (uint32_t j = 0; j < m_nChannel; ++j)
{
m_renderers[j] = AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Bitmap, false);
m_renderers[j]->setChannelCount(m_spectrumElements);
}
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::IRuler,
AdvancedVisualization::CRulerRightFrequency>, AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerProgressV>>>;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(m_renderers[0]);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
m_rotate = true;
return true;
}
bool StreamRendererSpectrum::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
// Each spectrum buffer has one spectrum per channel, so numBuffers is just:
// ( @todo: is this really needed in addition to setSampleCount? )
const size_t numBuffers = (m_endTime - m_startTime).ceil().time() / m_chunkDuration.time();
m_rendererCtx->setElementCount(numBuffers);
for (size_t j = 0; j < m_nChannel; ++j)
{
m_renderers[j]->clear(0);
m_renderers[j]->setSampleCount(numBuffers); // $$$
}
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
return true;
}
// Spectrum chunks are organized as [channel x freqs], so to push
// with freqs on the y axis for each subrenderer, we transpose
bool StreamRendererSpectrum::push(const TypeSpectrum::Buffer& chunk, const bool zeroInput /* = false */)
{
#if 0
std::cout << "Push spec chk " << m_Pushed << " " << chunk.m_buffer.getDimensionSize(0) << " " << chunk.m_buffer.getDimensionSize(1) << " "
<< CTime(chunk.m_startTime).toSeconds() << "," << CTime(chunk.m_endTime).toSeconds() << "\n";
std::cout << m_Pushed << " first bytes " << chunk.m_buffer.getBuffer()[0] << chunk.m_buffer.getBuffer()[1]
<< chunk.m_buffer.getBuffer()[2] << chunk.m_buffer.getBuffer()[3] << "\n";
m_Pushed++;
#endif
const size_t numFreq = chunk.m_buffer.getDimensionSize(1);
m_rendererCtx->setSpectrumFrequencyRange(uint32_t((uint64_t(numFreq) << 32) / m_chunkDuration.time()));
for (uint32_t j = 0; j < m_renderers.size(); ++j)
{
if (!zeroInput)
{
// Feed renderer with actual samples
for (uint32_t k = 0; k < numFreq; ++k) { m_swaps[numFreq - k - 1] = float(chunk.m_buffer.getBuffer()[j * numFreq + k]); }
}
else { std::fill(m_swaps.begin(), m_swaps.end(), 0.0F); }
m_renderers[j]->feed(&m_swaps[0]);
}
return true;
}
bool StreamRendererSpectrum::draw()
{
StreamRendererSpectrum::preDraw();
if (m_rendererCtx->getSelectedCount() != 0)
{
glPushMatrix();
glScalef(1, 1.0F / m_rendererCtx->getSelectedCount(), 1);
for (size_t i = 0; i < m_rendererCtx->getSelectedCount(); ++i)
{
glPushAttrib(GL_ALL_ATTRIB_BITS);
glPushMatrix();
glColor4f(m_color.r, m_color.g, m_color.b, m_rendererCtx->getTranslucency());
glTranslatef(0, float(m_rendererCtx->getSelectedCount() - i) - 1.0F, 0);
if (!m_rotate)
{
glScalef(1, -1, 1);
glRotatef(-90, 0, 0, 1);
}
m_subRendererCtx->setAspect(m_rendererCtx->getAspect());
m_subRendererCtx->setStackCount(m_rendererCtx->getSelectedCount());
m_subRendererCtx->setStackIndex(i);
m_renderers[m_rendererCtx->getSelected(i)]->render(*m_subRendererCtx);
/*
if (0) //if (bDrawBorders)
{
glDisable(GL_TEXTURE_1D);
glDisable(GL_BLEND);
glColor3f(0, 0, 0);
glBegin(GL_LINE_LOOP);
glVertex2f(0, 0);
glVertex2f(1, 0);
glVertex2f(1, 1);
glVertex2f(0, 1);
glEnd();
}
*/
glPopMatrix();
glPopAttrib();
}
glPopMatrix();
}
StreamRendererSpectrum::postDraw();
return true;
}
bool StreamRendererSpectrum::preDraw()
{
this->updateRulerVisibility();
// auto m_sColorGradient=CString("0:0,0,0; 100:100,100,100");
const std::string gradient =
"0:100, 100, 100; 12:50, 100, 100; 25:0, 50, 100; 38:0, 0, 50; 50:0, 0, 0; 62:50, 0, 0; 75:100, 50, 0; 88:100, 100, 50; 100:100, 100, 100";
if (!m_textureID) { m_textureID = m_gtkGLWidget.createTexture(gradient); }
glBindTexture(GL_TEXTURE_1D, m_textureID);
m_rendererCtx->setAspect(m_viewport->allocation.width * 1.0F / m_viewport->allocation.height);
return true;
}
bool StreamRendererSpectrum::finalize()
{
for (size_t i = 0; i < m_renderers.size(); ++i)
{
m_renderers[i]->rebuild(*m_subRendererCtx);
m_renderers[i]->refresh(*m_subRendererCtx);
}
redraw(true);
return true;
}
CString StreamRendererSpectrum::renderAsText(const size_t indent) const
{
auto& hdr = m_stream->getHeader();
std::stringstream ss;
ss << std::string(indent, ' ') << "Sampling rate: " << hdr.m_Sampling << "hz" << std::endl;
ss << std::string(indent, ' ') << "Channels: " << hdr.m_Header.getDimensionSize(0) << std::endl;
ss << std::string(indent, ' ') << "Abscissas per spectrum: " << hdr.m_Abscissas.getBufferElementCount() << std::endl;
// ss << std::string(indent, ' ') << "Channels: " << m_Header.m_header.getDimensionSize(0) << std::endl;
// ss << std::string(indent, ' ') << "Samples per chunk: " << m_Header.m_header.getDimensionSize(1) << std::endl;
return ss.str().c_str();
}
bool StreamRendererSpectrum::mouseButton(const int x, const int y, const int button, const int status)
{
//if (button == 3 && status == 1) { showChunkList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
bool StreamRendererSpectrum::showChunkList() { return showMatrixList<TypeSpectrum>(m_stream, &m_streamListWindow, "List of chunks for Spectrum stream"); }
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,207 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
// @note This renderer is intended to be used when there is only
// a stimulation track. More often, the Tracker users might want to
// see the stimulations overlayed on the signal track.
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include <mensia/advanced-visualization.hpp>
#include <mTGtkGLWidget.hpp>
#include <m_GtkGL.hpp>
#include "StreamRendererStimulation.h"
#include "TypeStimulation.h"
#include "ruler/mTRulerAutoType.hpp"
#include "ruler/mTRulerPair.hpp"
#include "ruler/mTRulerConditionalPair.hpp"
#include "ruler/mCRulerConditionIsTimeLocked.hpp"
#include "ruler/mCRulerProgressV.hpp"
#include "ruler/mCRulerBottomCount.hpp"
#include "ruler/mCRulerBottomTime.hpp"
#include "ruler/mCRulerLeftChannelNames.hpp"
#include "ruler/mCRulerRightCount.hpp"
#include "ruler/mCRulerRightScale.hpp"
#include "ruler/mCRulerRightLabels.hpp"
namespace OpenViBE {
namespace Tracker {
bool StreamRendererStimulation::initialize()
{
m_nChannel = 1;
m_sampling = 512;
m_samplesPerChunk = 32;
m_chunkDuration = CTime(m_sampling, m_samplesPerChunk);
// TRendererStimulation < false, CRendererLine >:new CRendererLine
AdvancedVisualization::IRenderer* renderer = AdvancedVisualization::IRenderer::create(AdvancedVisualization::ERendererType::Line, true);
if (renderer == nullptr) { return false; }
// Creates renderer context
m_rendererCtx = new AdvancedVisualization::CRendererContext();
m_rendererCtx->clear();
m_rendererCtx->setTimeScale(1);
m_rendererCtx->setCheckBoardVisibility(true);
m_rendererCtx->setScaleVisibility(m_isScaleVisible);
m_rendererCtx->setDataType(AdvancedVisualization::CRendererContext::EDataType::Signal);
m_rendererCtx->addChannel("Stims");
m_rendererCtx->setSampleDuration(CTime(m_sampling, 1).time());
m_ruler = new AdvancedVisualization::TRulerPair<AdvancedVisualization::CRulerProgressV, AdvancedVisualization::TRulerPair<
AdvancedVisualization::TRulerAutoType<
AdvancedVisualization::IRuler, AdvancedVisualization::TRulerConditionalPair<
AdvancedVisualization::CRulerBottomTime, AdvancedVisualization::CRulerBottomCount,
AdvancedVisualization::CRulerConditionIsTimeLocked>, AdvancedVisualization::IRuler>,
AdvancedVisualization::TRulerPair<
AdvancedVisualization::CRulerLeftChannelNames, AdvancedVisualization::CRulerRightScale>>>;
m_ruler->setRendererContext(m_rendererCtx);
m_ruler->setRenderer(renderer);
if (!StreamRendererBase::initialize()) { return false; }
m_gtkGLWidget.initialize(*this, m_viewport, m_left, m_right, m_bottom);
m_gtkGLWidget.setPointSmoothingActive(false);
m_renderers.push_back(renderer);
gtk_widget_set_size_request(m_main, 640, 100);
return true;
}
bool StreamRendererStimulation::reset(const CTime startTime, const CTime endTime)
{
m_startTime = startTime;
m_endTime = endTime;
// std::cout << "Overridden stimulation renderer reset\n";
m_renderers[0]->clear(0);
const uint64_t chunkCount = (m_endTime - m_startTime).time() / m_chunkDuration.time();
const uint32_t numSamples = uint32_t(m_samplesPerChunk * chunkCount);
m_renderers[0]->setChannelCount(m_nChannel);
m_renderers[0]->setSampleCount(numSamples);
// @FIXME The offset is needed to have correct numbers on the ruler; remove ifdef once the feature is in
#ifdef RENDERER_SUPPORTS_OFFSET
m_renderers[0]->setTimeOffset(m_startTime.time());
#endif
// Stick in empty chunks to get a background
std::vector<float> empty;
empty.resize(m_samplesPerChunk * 1, 0); // 1 channel
for (uint64_t i = 0; i < chunkCount; ++i) { m_renderers[0]->feed(&empty[0], m_samplesPerChunk); }
m_renderers[0]->rebuild(*m_rendererCtx);
return true;
}
bool StreamRendererStimulation::push(const TypeStimulation::Buffer& chunk, bool /*zeroInput*/)
{
for (size_t i = 0; i < chunk.m_buffer.getStimulationCount(); ++i)
{
m_renderers[0]->feed(chunk.m_buffer.getStimulationDate(i) - m_startTime.time(), chunk.m_buffer.getStimulationIdentifier(i));
}
return true;
}
CString StreamRendererStimulation::renderAsText(const size_t /*indent*/) const
{
// No specific details for stimulation streams
return "";
}
bool StreamRendererStimulation::showChunkList()
{
if (m_stimulationListWindow)
{
gtk_window_present(GTK_WINDOW(m_stimulationListWindow));
return true;
}
GtkBuilder* pBuilder = gtk_builder_new();
const CString filename = Directories::getDataDir() + "/applications/tracker/tracker.ui";
if (!gtk_builder_add_from_file(pBuilder, filename, nullptr))
{
std::cout << "Problem loading [" << filename << "]\n";
return false;
}
m_stimulationListWindow = GTK_WIDGET(gtk_builder_get_object(pBuilder, "tracker-stimulation_list"));
GtkTreeView* channelTreeView = GTK_TREE_VIEW(gtk_builder_get_object(pBuilder, "tracker-stimulation_list-treeview"));
// GtkListStore* channelListStore = GTK_LIST_STORE(gtk_builder_get_object(pBuilder, "liststore_select"));
GtkTreeStore* channelListStore = gtk_tree_store_new(7, G_TYPE_UINT, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_DOUBLE, G_TYPE_UINT64, G_TYPE_STRING,
G_TYPE_DOUBLE);
gtk_window_set_title(GTK_WINDOW(m_stimulationListWindow), "List of Stimulations in the stream");
add_column(channelTreeView, "Chunk#", 0, 20);
add_column(channelTreeView, "ChunkStart (s)", 1, 10);
add_column(channelTreeView, "ChunkEnd (s)", 2, 10);
add_column(channelTreeView, "Stim Time (s)", 3, 20);
add_column(channelTreeView, "Stim Id", 4, 5);
add_column(channelTreeView, "Stim Name", 5, 40);
add_column(channelTreeView, "Stim Duration", 6, 10);
gtk_tree_view_set_model(channelTreeView, GTK_TREE_MODEL(channelListStore));
GtkTreeIter it;
gtk_tree_store_clear(channelListStore);
// ::gtk_tree_view_set_model(m_pChannelTreeView, nullptr);
for (size_t i = 0; i < m_stream->getChunkCount(); ++i)
{
const TypeStimulation::Buffer* ptr = m_stream->getChunk(i);
if (!ptr) { break; }
for (uint32_t s = 0; s < ptr->m_buffer.getStimulationCount(); ++s)
{
const CString stimName = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(
OV_TypeId_Stimulation, ptr->m_buffer.getStimulationIdentifier(s));
gtk_tree_store_append(channelListStore, &it, nullptr);
gtk_tree_store_set(channelListStore, &it,
0, i,
1, ptr->m_StartTime.toSeconds(),
2, ptr->m_EndTime.toSeconds(),
3, CTime(ptr->m_buffer.getStimulationDate(s)).toSeconds(),
4, ptr->m_buffer.getStimulationIdentifier(s),
5, stimName.toASCIIString(),
6, CTime(ptr->m_buffer.getStimulationDuration(s)).toSeconds(), -1);
}
}
// GList* cols = gtk_tree_view_get_columns(m_pChannelTreeView);
// Hide instead of destroy on closing the window
g_signal_connect(m_stimulationListWindow, "delete_event", G_CALLBACK(gtk_widget_hide_on_delete), nullptr);
gtk_widget_show_all(GTK_WIDGET(m_stimulationListWindow));
g_object_unref(pBuilder);
return true;
}
bool StreamRendererStimulation::mouseButton(const int x, const int y, const int button, const int status)
{
// if (button == 3 && status == 1) { showStimulationList(); }
return StreamRendererBase::mouseButton(x, y, button, status);
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,87 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include "Testclass.h"
// #include "Source.h"
#include "Stream.h"
#include "Encoder.h"
namespace OpenViBE {
namespace Tracker {
TestClass::TestClass(Kernel::IKernelContext& ctx) : m_Ctx(ctx)
{
std::cout << "Testing\n";
// Encoder2<TypeMatrix::Header, TypeMatrix::Buffer, TypeMatrix::End> testCoder(ctx);
// Encoder<TypeMatrix> testCoder(ctx);
#if 0
StreamSignal testStream(ctx);
StreamMatrix testStream2(ctx);
std::vector<Stream*> list;
list.push_back(&testStream);
list.push_back(&testStream2);
Stream* tmp = list[0];
std::cout << "First is " << ctx.getTypeManager().getTypeName(tmp->getTypeIdentifier()) << "\n";
#endif
//const CString eegFile = Directories::getDataDir() + CString("/scenarios/signals/bci-motor-imagery.ov");
//Source src;
//src.initialize(eegFile.toASCIIString());
#if 0
// Test code illustrating how to alter stimulation stream
for (auto it = m_Streams.begin();it != m_Streams.end(); ++it)
{
if(it->second->getTypeIdentifier() == OV_TypeId_Stimulations)
{
TypeError::Buffer *ptr = nullptr;
it->second->peek(CTime(5.0).time(), &ptr);
TypeStimulation::Buffer *ptr2 = reinterpret_cast<TypeStimulation::Buffer*>(ptr);
// std::cout << "cnt: " << ptr2->m_buffer.getStimulationCount() << "\n";
// Request early stop
ptr2->m_buffer.clear();
ptr2->m_buffer.appendStimulation(OVTK_StimulationId_ExperimentStop, CTime(5.0).time(),0);
}
}
#endif
#if 0
StreamHeaderSignal signalHeader;
signalHeader.m_samplingRate = 512;
Stream<StreamHeaderSignal, StreamDataSignal> testStream(m_ctx);
testStream.initialize(signalHeader);
StreamDataSignal* data = new(StreamDataSignal);
data->data.setDimensionSize(10,2);
testStream.push(data);
// Encode a stream
Encoder<StreamHeaderSignal, StreamDataSignal> encoder(ctx);
std::vector<CMemoryBuffer> encoded;
encoded.push_back( encoder.encodeHeader(testStream.getHeader()) );
for (size_t i=0;i<testStream.m_chunks.size(); ++i) { encoded.push_back( encoder.encodeBuffer(*testStream.m_chunks[i]) ); }
Stream* ptr = testStream;
#endif
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,53 @@
#include "TrackerPluginChannelCheck.h"
#include "Stream.h"
#include "TypeSignal.h"
namespace OpenViBE {
namespace Tracker {
bool TrackerPluginChannelCheck::process(Workspace& wp, ParallelExecutor& /* exec */)
{
log() << Kernel::LogLevel_Info << "TrackerPluginChannelCheck: Testing if all selected signal streams have the same number of channels ...\n";
uint32_t nChannels = 0;
size_t nTested = 0;
for (size_t t = 0; t < wp.getNumTracks(); ++t)
{
const auto& track = wp.getTrack(t);
for (size_t s = 0; s < track->getNumStreams(); ++s)
{
const auto stream = track->getStream(s);
// This plugin only handles signal streams
if (stream->getSelected() && stream->getTypeIdentifier() == OV_TypeId_Signal)
{
nTested++;
auto typedStream = std::static_pointer_cast<Stream<TypeSignal>>(stream);
const auto& hdr = typedStream->getHeader();
const uint32_t thisChannels = hdr.m_Header.getDimensionSize(0);
if (nTested == 1) { nChannels = thisChannels; }
else if (nChannels != thisChannels)
{
log() << Kernel::LogLevel_Error << "Stream " << (s + 1) << " of Track " << t + 1 << " has different number of signal channels ("
<< thisChannels << ") than previous signal streams (" << nChannels << ")\n";
return true;
}
}
}
// This plugin does not modify the track. If it did, we would need to call the following:
// track.setDirtyBit(true);
}
log() << Kernel::LogLevel_Info << "All signal streams have the same amount of channels (" << nTested << " tested)\n";
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,67 @@
#include <memory> // shared_ptr
#include "TrackerPluginCountStimulations.h"
#include "Stream.h"
#include "TypeStimulation.h"
namespace OpenViBE {
namespace Tracker {
bool TrackerPluginCountStimulations::process(StreamBundle& track)
{
log() << Kernel::LogLevel_Info << "TrackerPluginCountStimulation: Counting stimulations in the selected streams\n";
bool processedSomething = false;
for (size_t s = 0; s < track.getNumStreams(); ++s)
{
const auto stream = track.getStream(s);
// This plugin only handles stimulation streams
if (stream->getSelected() && stream->getTypeIdentifier() == OV_TypeId_Stimulations)
{
processedSomething = true;
std::map<uint64_t, uint64_t> histogram;
double numStims = 0;
const auto typedStream = std::static_pointer_cast<const Stream<TypeStimulation>>(stream);
// Count occurrences
for (size_t i = 0; i < typedStream->getChunkCount(); ++i)
{
const auto chunk = typedStream->getChunk(i);
for (size_t stimIdx = 0; stimIdx < chunk->m_buffer.getStimulationCount(); ++stimIdx)
{
auto id = chunk->m_buffer.getStimulationIdentifier(stimIdx);
auto it = histogram.find(id);
if (it != histogram.end()) { it->second++; }
else { histogram[id] = 1; }
numStims++;
}
}
// Print
log() << Kernel::LogLevel_Info << "Stimulation counts for stream " << (s + 1) << " ...\n";
for (auto& it : histogram)
{
const CString name = m_kernelCtx.getTypeManager().getEnumerationEntryNameFromValue(OV_TypeId_Stimulation, it.first);
log() << Kernel::LogLevel_Info << (name.length() > 0 ? name : "Unregistered") << " (" << it.first << ") " << " : " << it.second
<< " (" << (it.second / numStims) * 100 << "%)" << "\n";
}
}
}
// This plugin does not modify the track. If it did, we would need to call the following:
// track.setDirtyBit(true);
if (!processedSomething) { log() << Kernel::LogLevel_Info << "No selected streams compatible with stimulation counting plugin\n"; }
return true;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,59 @@
#include <algorithm> // std::sort
#include "TrackerPlugins.h"
#include "TrackerPluginCountStimulations.h"
#include "TrackerPluginChannelCheck.h"
namespace OpenViBE {
namespace Tracker {
TrackerPlugins::TrackerPlugins(const Kernel::IKernelContext& ctx) : Contexted(ctx)
{
// Declare all plugins here
m_pluginCreateCalls.push_back([&ctx]() { return new TrackerPluginCountStimulations(ctx); });
m_pluginCreateCalls.push_back([&ctx]() { return new TrackerPluginChannelCheck(ctx); });
// Create example instances of the plugins so the GUI can display a list
for (auto& fun : m_pluginCreateCalls)
{
auto ptr = fun();
m_trackerPlugins.push_back(ptr);
}
if (m_trackerPlugins.empty()) { return; }
// get both arrays sorted. No code beauty contest winners here...
std::vector<size_t> indexes;
for (size_t i = 0; i < m_trackerPlugins.size(); ++i) { indexes.push_back(i); }
auto& pluginRef = m_trackerPlugins;
auto& callRef = m_pluginCreateCalls;
std::sort(indexes.begin(), indexes.end(),
[&pluginRef](const size_t a, const size_t b) { return (pluginRef[a]->getName()) < (pluginRef[b]->getName()); });
std::vector<ITrackerPlugin*> sortedPlugins;
std::transform(indexes.begin(), indexes.end(), std::back_inserter(sortedPlugins), [pluginRef](const size_t i) { return pluginRef[i]; });
m_trackerPlugins = sortedPlugins;
std::vector<std::function<ITrackerPlugin*()>> sortedCalls;
std::transform(indexes.begin(), indexes.end(), std::back_inserter(sortedCalls), [callRef](const size_t i) { return callRef[i]; });
m_pluginCreateCalls = sortedCalls;
}
TrackerPlugins::~TrackerPlugins()
{
for (auto f : m_trackerPlugins) { delete f; }
m_trackerPlugins.clear();
m_pluginCreateCalls.clear();
}
ITrackerPlugin* TrackerPlugins::getPluginCopy(const size_t index) const
{
if (index >= m_pluginCreateCalls.size()) { return nullptr; }
const auto fun = m_pluginCreateCalls[index];
return fun();
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,889 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <iostream>
#include <ctime> // std::ctime
#include <algorithm> // std::replace, std::max
#include <iomanip> //std::setw, setfill
#include <fs/Files.h>
#include "Workspace.h"
#include "StreamBundleImportExport.h"
#include "StimulationStreamFilter.h"
namespace OpenViBE {
namespace Tracker {
Workspace::~Workspace()
{
for (size_t i = 0; i < m_tracks.size(); ++i)
{
if (m_tracks[i])
{
m_tracks[i]->uninitialize();
delete m_tracks[i];
}
}
m_tracks.clear();
}
bool Workspace::setUniqueWorkingPath()
{
const CString workspacePrefix = m_kernelCtx.getConfigurationManager().expand("${Path_UserData}/tracker-workspace-");
const auto currentTime = std::chrono::system_clock::now();
std::time_t t = std::chrono::system_clock::to_time_t(currentTime);
std::string stringTime = std::ctime(&t);
std::replace(stringTime.begin(), stringTime.end(), ' ', '-');
std::replace(stringTime.begin(), stringTime.end(), ':', '-');
std::replace(stringTime.begin(), stringTime.end(), '/', '-');
std::replace(stringTime.begin(), stringTime.end(), '\\', '-');
std::replace(stringTime.begin(), stringTime.end(), '\n', '-');
// This is not a super safe way to create a directory name but it is unlikely the user
// would run many trackers at the same time
const std::string workspacePath = std::string(workspacePrefix.toASCIIString()) + stringTime;
uint32_t counter = 0;
while (FS::Files::directoryExists((workspacePath + std::to_string(counter)).c_str())) { counter++; }
setWorkingPath(CString((workspacePath + std::to_string(counter)).c_str()));
return true;
}
bool Workspace::step()
{
if (m_executor->isIdle())
{
log() << Kernel::LogLevel_Info << "All jobs finished\n";
stop();
return false;
}
System::Time::sleep(1);
return true;
}
bool Workspace::spoolRecordingToDisk(const size_t trackIndex)
{
if (trackIndex >= m_tracks.size())
{
log() << Kernel::LogLevel_Error << "Index " << trackIndex << " is outside array.\n";
return false;
}
// Spool the new track to disk
std::stringstream filename;
filename << m_workspacePath << "/workspace"
<< "-track" << std::setw(3) << std::setfill('0') << trackIndex + 1
<< "-rev" << std::setw(3) << std::setfill('0') << m_revision << ".ov";
saveStreamBundleToFile(m_kernelCtx, m_tracks[trackIndex], filename.str().c_str());
m_tracks[trackIndex]->setSource(filename.str());
if (m_memorySaveMode)
{
// Load back in memory save mode
delete m_tracks[trackIndex];
m_tracks[trackIndex] = readStreamBundleFromFile(m_kernelCtx, filename.str().c_str(), true);
}
return true;
}
std::string Workspace::getProcessorArguments(const size_t index)
{
std::string trackSource("None");
if (index >= 0 && m_tracks[index]->getSource().length() > 0) { trackSource = m_tracks[index]->getSource(); }
// Append generic configuration tokens. n.b. make sure no define argument is an empty string.
std::stringstream ss;
ss << " ";
ss << "--define Tracker_Workspace_File " << "\"" << (m_workspaceFile.length() > 0 ? m_workspaceFile : "None") << "\" ";
ss << "--define Tracker_Workspace_Path " << "\"" << (m_workspacePath.length() > 0 ? m_workspacePath : "None") << "\" ";
ss << "--define Tracker_CurrentTrack_Number " << index + 1 << " ";
ss << "--define Tracker_CurrentTrack_Source " << "\"" + trackSource << "\" ";
ss << "--define Tracker_CatenatePlayback " << (m_catenateMode ? "True" : "False") << " ";
// Include those potentially set by the user in the GUI
std::string args = std::string(m_processorArguments.toASCIIString()) + ss.str();
return args;
}
bool Workspace::getNextTrack(size_t& nextTrack) const
{
nextTrack = nextTrack + 1;
while (nextTrack < getNumTracks() && !m_selection.isTrackSelected(nextTrack)) { nextTrack++; }
if (nextTrack >= getNumTracks())
{
nextTrack = size_t(-1);
return false;
}
return true;
}
bool Workspace::assemblePlaylist()
{
m_playlist.clear();
m_playlistDuration = 0;
size_t nextTrack = size_t(-1);
while (getNextTrack(nextTrack))
{
StreamBundle* source = m_tracks[nextTrack];
m_playlistDuration += source->getMaxDuration();
SourceTimePair tmp(source, CTime::min());
m_playlist.push_back(tmp);
}
return true;
}
// Construct list of streamsubsets to process
// make processing each streamsubset a job
// connect input and output to the job
// monitor until jobs are complete
//
bool Workspace::play(const bool playFast)
{
m_playFast = playFast;
m_pleaseQuit = false;
m_tracksDone = 0;
m_playlistDuration = 0;
if (!m_executor)
{
log() << Kernel::LogLevel_Error << "Need a parallel executor set\n";
return false;
}
if (!m_selection.isSelectionConsistent())
{
log() << Kernel::LogLevel_Error <<
"For processing, the selected streams for each track must have equal types, in equal amounts, and in the same stream type order.\n";
return false;
}
if (!m_processor.canPush() && !m_processor.canPull())
{
this->getLogManager() << Kernel::LogLevel_Error << "Please configure the processor to send, receive, or both.\n";
return false;
}
if (!m_processor.canPush()) { return playReceiveOnly(); }
// Generate streamsubsets to process
assemblePlaylist();
if (m_catenateMode) { return playCatenate(); }
return playNormal();
}
bool Workspace::playCatenate()
{
uint32_t portToUse, dummy;
m_processor.getProcessorPorts(portToUse, dummy);
const std::string filename = m_processorFilename;
bool playFast = m_playFast;
auto& refProc = m_processor;
std::string args = getProcessorArguments(0);
auto job = [playFast/*, portToUse*/, args, filename, &refProc, this](uint32_t /*threadNumber*/)
{
uint32_t playlistIndex = 0;
StreamBundle* target = new StreamBundle(this->getKernelContext());
ProcExternalProcessing proc(getKernelContext(), refProc);
proc.setArguments(args.c_str());
proc.setNewTarget(target);
StreamBundle* loaded = nullptr;
StreamBundle* subset = nullptr;
auto quitCallback = [this,playlistIndex](const CTime spent)
{
{
std::unique_lock<std::mutex> (m_Mutex);
this->m_playlist[playlistIndex].second = spent;
}
return isQuitRequested();
};
auto nextTrackFun = [this,&playlistIndex,&proc,&subset, &loaded]()
{
delete subset;
if (playlistIndex >= m_playlist.size()) { return false; }
this->log() << Kernel::LogLevel_Info << "Switching to process track " << playlistIndex + 1 << " out of " << m_playlist.size() << "\n";
subset = new StreamBundle(this->m_kernelCtx);
const auto& ptr = m_playlist[playlistIndex];
StreamBundle* source = ptr.first;
if (this->m_memorySaveMode)
{
this->log() << Kernel::LogLevel_Info << "Memory save mode: Loading " << source->getSource().c_str() << " from disk\n";
loaded = readStreamBundleFromFile(this->m_kernelCtx, source->getSource().c_str(), false);
// copy selection from the one in memory
for (size_t i = 0; i < source->getNumStreams(); ++i) { loaded->getStream(i)->setSelected(source->getStream(i)->getSelected()); }
subset->copyFrom(*loaded);
delete loaded;
}
else
{
// Select subset of streams to play
subset->copyFrom(*source);
}
const bool isFirst = (playlistIndex == 0);
const bool isLast = (playlistIndex >= m_playlist.size() - 1);
proc.setNewSource(subset, isFirst, isLast);
{
std::unique_lock<std::mutex>(this->getMutex());
this->m_tracksDone++;
}
playlistIndex++;
return true;
};
proc.play(playFast, quitCallback, nextTrackFun);
this->log() << Kernel::LogLevel_Info << "Track complete.\n";
if (target->getNumStreams() > 0)
{
// Single threaded here, so can get m_Tracks.size() outside mutex
const size_t newIndex = m_tracks.size();
this->setTrack(newIndex, target);
// Spool result to disk, note that it might be incomplete as the user requested stop
if (m_memorySaveMode)
{
log() << Kernel::LogLevel_Info << "Writing new track to disk.\n";
spoolRecordingToDisk(newIndex);
}
}
else { delete target; }
};
m_executor->pushJob(job);
return true;
}
bool Workspace::playNormal()
{
uint32_t firstPort, dummy;
m_processor.getProcessorPorts(firstPort, dummy);
const size_t lastFreeIndex = m_tracks.size();
bool playFast = m_playFast;
const size_t totalTracks = m_playlist.size();
for (size_t playlistIndex = 0; playlistIndex < totalTracks; ++playlistIndex)
{
auto& ptr = m_playlist[playlistIndex];
std::string args = getProcessorArguments(playlistIndex);
const std::string filename = m_processorFilename;
auto& refProc = m_processor;
auto job = [&ptr,&refProc,args, playFast,filename, lastFreeIndex, playlistIndex, firstPort, totalTracks, this](const uint32_t threadNumber)
{
StreamBundle* original = ptr.first;
StreamBundle* target = new StreamBundle(this->m_kernelCtx);
StreamBundle* subset = new StreamBundle(this->m_kernelCtx);
this->log() << Kernel::LogLevel_Trace << "Switching to process track " << playlistIndex + 1 << " out of " << totalTracks << "\n";
if (this->m_memorySaveMode)
{
this->log() << Kernel::LogLevel_Info << "Memory save mode: Loading " << original->getSource().c_str() << " from disk\n";
StreamBundle* loaded = readStreamBundleFromFile(this->m_kernelCtx, original->getSource().c_str(), false);
// copy selection from the one in memory
for (size_t i = 0; i < original->getNumStreams(); ++i) { loaded->getStream(i)->setSelected(original->getStream(i)->getSelected()); }
subset->copyFrom(*loaded);
delete loaded;
}
else
{
// Select subset of streams to play
subset->copyFrom(*original);
}
auto quitCallback = [this,playlistIndex](const CTime spent)
{
{
std::unique_lock<std::mutex> (m_Mutex);
this->m_playlist[playlistIndex].second = spent;
}
this->m_playlist[playlistIndex].second = spent;
return isQuitRequested();
};
// Process
ProcExternalProcessing proc(this->getKernelContext(), refProc);
proc.setNewSource(subset, true, true);
proc.setNewTarget(target);
proc.setArguments(args.c_str());
proc.setProcessorPorts(firstPort + 2 * threadNumber, firstPort + 2 * threadNumber + 1);
proc.play(playFast, quitCallback);
this->log() << Kernel::LogLevel_Trace << "Track " << playlistIndex + 1 << " complete.\n";
{
std::unique_lock<std::mutex>(this->getMutex());
this->m_tracksDone++;
}
// Clean-up
delete subset;
if (target->getNumStreams() > 0)
{
// if not inplacemode, attempt to insert in the same order as the sources were in the list
const size_t newIndex = (this->getInplaceMode() ? playlistIndex : lastFreeIndex + playlistIndex);
this->setTrack(newIndex, target);
// Spool result to disk, note that it might be incomplete as the user requested stop
if (m_memorySaveMode)
{
log() << Kernel::LogLevel_Info << "Writing new track to disk.\n";
spoolRecordingToDisk(newIndex);
}
}
else
{
// this->log() << Kernel::LogLevel_Warnin
delete target;
}
};
m_executor->pushJob(job);
}
return true;
}
bool Workspace::playReceiveOnly()
{
const size_t lastFreeIndex = m_tracks.size();
bool playFast = m_playFast;
std::string args = getProcessorArguments(-1);
const std::string filename = m_processorFilename;
auto& refProc = m_processor;
m_playlist.clear();
m_playlist.push_back(SourceTimePair(nullptr, CTime()));
auto job = [&refProc,args, playFast,filename, lastFreeIndex, this](uint32_t /*threadNumber*/)
{
StreamBundle* target = new StreamBundle(this->m_kernelCtx);
this->log() << Kernel::LogLevel_Info << "Recording a track (noSend configured)\n";
auto quitCallback = [this](const CTime spent)
{
// single thread, no cc
this->m_playlistDuration = spent;
this->m_playlist[0].second = spent;
return isQuitRequested();
};
// Process
ProcExternalProcessing proc(this->getKernelContext(), refProc);
proc.setNewSource(nullptr, true, true);
proc.setNewTarget(target);
proc.setArguments(args.c_str());
proc.play(playFast, quitCallback);
this->log() << Kernel::LogLevel_Info << "Recording complete.\n";
if (target->getNumStreams() > 0)
{
// if not inplacemode, attempt to insert in the same order as the sources were in the list
const size_t newIndex = lastFreeIndex;
this->setTrack(newIndex, target);
// Spool result to disk, note that it might be incomplete as the user requested stop
if (m_memorySaveMode)
{
log() << Kernel::LogLevel_Info << "Writing new track to disk.\n";
spoolRecordingToDisk(newIndex);
}
}
else { delete target; }
};
m_executor->pushJob(job);
return true;
}
bool Workspace::stop(const bool stopProcessor)
{
if (stopProcessor)
{
std::unique_lock<std::mutex> m_Mutex;
m_executor->clearPendingJobs();
m_pleaseQuit = true;
}
return true;
}
bool Workspace::clearTracks()
{
for (size_t i = 0; i < m_tracks.size(); ++i) { delete m_tracks[i]; }
m_tracks.clear();
return true;
}
bool Workspace::removeTrack(const size_t idx)
{
if (idx >= m_tracks.size()) { return false; }
delete m_tracks[idx];
m_tracks.erase(m_tracks.begin() + idx);
return true;
}
bool Workspace::removeStream(const size_t track, const size_t stream)
{
if (track >= getNumTracks() || stream >= getTrack(track)->getNumStreams()) { return false; }
if (getTrack(track)->deleteStream(stream)) { return true; }
return false;
}
bool Workspace::addTrack(const char* filename)
{
if (!filename || !filename[0]) { return false; }
StreamBundle* newTrack = readStreamBundleFromFile(m_kernelCtx, filename, m_memorySaveMode);
if (!newTrack) { return false; }
log() << Kernel::LogLevel_Debug << "The loaded track has " << newTrack->getNumStreams() << " streams\n";
for (size_t i = 0; i < newTrack->getNumStreams(); ++i)
{
if (newTrack->getStream(i))
{
log() << Kernel::LogLevel_Debug << " Stream " << i << " has type "
<< newTrack->getStream(i)->getTypeIdentifier().str() << " == "
<< m_kernelCtx.getTypeManager().getTypeName(newTrack->getStream(i)->getTypeIdentifier())
<< "\n";
}
else
{
// @fixme this has the issue that even though the stream may have a definition, its lost currently if there's not even a header chunk in the stream
log() << Kernel::LogLevel_Info << " Stream " << i << " has a type the Tracker couldn't decode (or the stream was empty)\n";
}
}
m_tracks.push_back(newTrack);
for (auto& str : newTrack->getAllStreams()) { str->setSelected(true); }
return true;
}
bool Workspace::moveStream(const size_t sourceTrack, const size_t sourceStream, const size_t targetTrack, const size_t targetStream)
{
if (sourceTrack >= getNumTracks() || targetTrack >= getNumTracks()) { return false; }
if (sourceTrack == targetTrack && sourceStream == targetStream) { return true; }
if (sourceStream >= m_tracks[sourceTrack]->getNumStreams() ||
targetStream >= m_tracks[targetTrack]->getNumStreams()) { return false; }
if (sourceTrack != targetTrack)
{
const auto oldPtr = m_tracks[sourceTrack]->getStream(sourceStream);
// Move pointer, do not free memory
auto& allStreams = m_tracks[sourceTrack]->getAllStreams();
allStreams.erase(allStreams.begin() + sourceStream);
m_tracks[sourceTrack]->setDirtyBit(true);
m_tracks[targetTrack]->setStream(m_tracks[targetTrack]->getNumStreams(), oldPtr); // append to the end
return m_tracks[targetTrack]->moveStream(m_tracks[targetTrack]->getNumStreams() - 1, targetStream);
}
return m_tracks[sourceTrack]->moveStream(sourceStream, targetStream);
}
bool Workspace::moveTrack(const size_t sourceIdx, const size_t targetIdx)
{
if (sourceIdx >= getNumTracks() || targetIdx >= getNumTracks()) { return false; }
if (sourceIdx == targetIdx) { return true; }
const auto oldPtr = m_tracks[sourceIdx];
m_tracks.erase(m_tracks.begin() + sourceIdx);
m_tracks.insert(m_tracks.begin() + targetIdx, oldPtr);
return true;
}
bool Workspace::reloadTrack(const size_t index)
{
if (index >= m_tracks.size()) { return false; }
StreamBundle* newTrack = readStreamBundleFromFile(m_kernelCtx, m_tracks[index]->getSource().c_str(), m_memorySaveMode);
delete m_tracks[index];
m_tracks[index] = newTrack;
return true;
}
CTime Workspace::getMaxDuration() const
{
CTime maxDuration = CTime::min();
for (size_t i = 0; i < m_tracks.size(); ++i) { if (m_tracks[i]) { maxDuration = std::max<CTime>(maxDuration, m_tracks[i]->getMaxDuration()); } }
return maxDuration;
}
bool Workspace::setProcessor(const char* scenarioXml)
{
m_kernelCtx.getConfigurationManager().addOrReplaceConfigurationToken("Tracker_Workspace_Processor", scenarioXml);
m_processorFilename = scenarioXml;
return m_processor.initialize(scenarioXml);
}
bool Workspace::setProcessorFlags(const bool noGUI, const bool doSend, const bool doReceive) { return m_processor.setProcessorFlags(noGUI, doSend, doReceive); }
bool Workspace::getProcessorFlags(bool& noGUI, bool& doSend, bool& doReceive) const { return m_processor.getProcessorFlags(noGUI, doSend, doReceive); }
bool Workspace::setProcessorPorts(const uint32_t sendPort, const uint32_t recvPort) { return m_processor.setProcessorPorts(sendPort, recvPort); }
bool Workspace::saveAll()
{
if (m_workspacePath.length() == 0)
{
log() << Kernel::LogLevel_Error << "Error: Workspace path not set...\n";
return false;
}
if (!FS::Files::directoryExists(m_workspacePath.toASCIIString()))
{
if (!FS::Files::createPath(m_workspacePath.toASCIIString()))
{
log() << Kernel::LogLevel_Error << "Error: Unable to create directory " << m_workspacePath << "\n";
return false;
}
}
bool retVal = true;
if (getNumTracks() > 0) { log() << Kernel::LogLevel_Info << "Saving modified tracks to " << m_workspacePath << " ...\n"; }
uint32_t tracksSaved = 0;
for (size_t i = 0; i < getNumTracks(); ++i)
{
if (!getTrack(i)->getDirtyBit())
{
log() << Kernel::LogLevel_Trace << "Skipping track " << i + 1 << " / " << getNumTracks() << ", no modifications ...\n";
continue;
}
log() << Kernel::LogLevel_Trace << "Saving track " << i + 1 << " / " << getNumTracks() << " ...\n";
std::stringstream filename;
filename << m_workspacePath << "/workspace"
<< "-track" << std::setw(3) << std::setfill('0') << i + 1
<< "-rev" << std::setw(3) << std::setfill('0') << m_revision << ".ov";
// If we're in the memory save mode, we need to copy contents of the track to the new location
StreamBundle* track = (m_memorySaveMode ? readStreamBundleFromFile(m_kernelCtx, getTrack(i)->getSource().c_str(), false) : getTrack(i));
retVal &= saveStreamBundleToFile(m_kernelCtx, track, filename.str().c_str());
tracksSaved++;
if (m_memorySaveMode)
{
getTrack(i)->setSource(track->getSource());
getTrack(i)->setDirtyBit(false);
delete track;
}
}
log() << Kernel::LogLevel_Info << "Done, " << tracksSaved << " had changed and needed to be saved.\n";
return retVal;
}
bool Workspace::setMemorySaveMode(const bool active)
{
if (active != m_memorySaveMode)
{
m_memorySaveMode = active;
if (active == false)
{
// Going from memory save mode to the normal mode
log() << Kernel::LogLevel_Info << "Switching to full mode. Loading tracks from disk.\n";
}
else
{
// Going from normal mode to memory save mode
// We need to spool all the modifications to the disk so the other mode can get them
log() << Kernel::LogLevel_Info << "Switching to memory save mode. Saving all modified tracks to disk.\n";
saveAll();
}
// Read tracks back in the new mode
for (size_t i = 0; i < m_tracks.size(); ++i)
{
const std::string filename = m_tracks[i]->getSource();
delete m_tracks[i];
m_tracks[i] = readStreamBundleFromFile(m_kernelCtx, filename.c_str(), m_memorySaveMode);
}
}
return true;
}
bool Workspace::clear()
{
clearTracks();
setFilename("");
setUniqueWorkingPath();
return true;
}
std::vector<std::pair<std::string, std::string>> Workspace::getConfigurationTokens() const
{
std::vector<std::pair<std::string, std::string>> tokens; // For sorting
CIdentifier iter = CIdentifier::undefined();
while ((iter = m_kernelCtx.getConfigurationManager().getNextConfigurationTokenIdentifier(iter)) != CIdentifier::undefined())
{
std::string prefix("Tracker_Workspace_");
std::string token(m_kernelCtx.getConfigurationManager().getConfigurationTokenName(iter).toASCIIString());
// See if token has the prefix?
auto res = std::mismatch(prefix.begin(), prefix.end(), token.begin());
if (res.first == prefix.end())
{
std::string value(m_kernelCtx.getConfigurationManager().getConfigurationTokenValue(iter));
tokens.push_back(std::pair<std::string, std::string>(token, value));
// ::fprintf(file, "%s = %s\n", token.c_str(), value.toASCIIString());
}
}
std::sort(tokens.begin(), tokens.end());
return tokens;
}
// @fixme for multiple workspaces this solution needs to be reworked
bool Workspace::save(const CString& filename)
{
if (!m_tracks.empty() && !saveAll()) { return false; }
// Save selection to configuration manager
m_selection.save("Tracker_Workspace_");
// Save processor configuration to manager
m_processor.save();
// Set workspaces own configuration tokens to manager
// @todo Why not already do this in the setters?
std::stringstream trackCount;
trackCount << m_tracks.size();
std::stringstream revision;
revision << m_revision;
auto& mgr = m_kernelCtx.getConfigurationManager();
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Path", m_workspacePath.toASCIIString());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_CatenatePlayback", (m_catenateMode ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_MemorySaveMode", (m_memorySaveMode ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_InplaceMode", (m_inplaceMode ? "true" : "false"));
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Track_Count", trackCount.str().c_str());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Processor_Arguments", m_processorArguments.toASCIIString());
mgr.addOrReplaceConfigurationToken("Tracker_Workspace_Revision", revision.str().c_str());
FILE* file = fopen(filename.toASCIIString(), "wt");
if (file)
{
fprintf(file, "# Workspace configuration file generated by OpenViBE Tracker\n");
fprintf(file, "#\n");
fprintf(file, "\n");
for (size_t i = 0; i < m_tracks.size(); ++i)
{
std::stringstream token;
token << "Tracker_Workspace_Track_" << std::setw(3) << std::setfill('0') << (i + 1);
mgr.addOrReplaceConfigurationToken(token.str().c_str(), m_tracks[i]->getSource().c_str());
}
// Spool all generic Tracker tokens from the manager to the file
auto tokens = getConfigurationTokens();
for (auto& token : tokens) { fprintf(file, "%s = %s\n", token.first.c_str(), token.second.c_str()); }
fprintf(file, "\n");
fclose(file);
}
else
{
log() << Kernel::LogLevel_Error << "Error: Couldn't open " << filename.toASCIIString() << " for writing\n";
return false;
}
m_workspaceFile = filename;
// Save workspace notes
if (m_notes.getText().length() > 0 && m_workspacePath.length() > 0)
{
CString notesFile = m_workspacePath + CString("/workspace-notes.txt");
m_notes.save(notesFile);
}
return true;
}
bool Workspace::load(const CString& filename)
{
// @todo might wipe only specific tokens in the future, esp. if multiple workspaces become supported
wipeConfigurationTokens("Tracker_Workspace_");
auto& mgr = m_kernelCtx.getConfigurationManager();
if (!mgr.addConfigurationFromFile(filename)) { return false; }
clearTracks();
m_workspaceFile = filename;
const CString savedWorkspacePath = mgr.expand("${Tracker_Workspace_Path}");
if (savedWorkspacePath.length() > 0) { m_workspacePath = savedWorkspacePath; }
setCatenateMode(mgr.expandAsBoolean("${Tracker_Workspace_CatenatePlayback}", m_catenateMode));
// Here we don't use the setter as we're loading from scratch
m_memorySaveMode = mgr.expandAsBoolean("${Tracker_Workspace_MemorySaveMode}", m_memorySaveMode);
m_inplaceMode = mgr.expandAsBoolean("${Tracker_Workspace_InplaceMode}", m_inplaceMode);
m_revision = mgr.expandAsUInteger("${Tracker_Workspace_Revision}", m_revision);
// m_NumRevisions = mgr.expandAsUInteger("${Tracker_Workspace_NumRevisions}", m_NumRevisions);
m_processorArguments = mgr.expand("${Tracker_Workspace_Processor_Arguments}");
const uint32_t trackCount = uint32_t(mgr.expandAsUInteger("${Tracker_Workspace_Track_Count}", 0));
for (uint32_t i = 0; i < trackCount; ++i)
{
std::stringstream token;
token << "Tracker_Workspace_Track_" << std::setw(3) << std::setfill('0') << (i + 1);
if (mgr.lookUpConfigurationTokenIdentifier(token.str().c_str()) != CIdentifier::undefined())
{
CString tokenValue = mgr.lookUpConfigurationTokenValue(token.str().c_str());
CString trackFile = mgr.expand(tokenValue);
log() << Kernel::LogLevel_Debug << "Loading track " << i + 1 << " : " << trackFile << "\n";
addTrack(trackFile.toASCIIString());
}
}
log() << Kernel::LogLevel_Info << "Loaded " << m_tracks.size() << " tracks of the workspace\n";
// Load processor config
m_processor.load();
// Load workspace notes
const CString notesFile = m_workspacePath + CString("/workspace-notes.txt");
m_notes.load(notesFile);
// Load selection
m_selection.load("Tracker_Workspace_");
return true;
}
bool Workspace::incrementRevisionAndSave(const CString& /*filename*/)
{
if (m_workspacePath.length() == 0)
{
log() << Kernel::LogLevel_Error << "Please set workspace path before saving revision\n";
return false;
}
for (auto& ptr : m_tracks) { ptr->setDirtyBit(true); }
m_revision++;
// m_NumRevisions++;
log() << Kernel::LogLevel_Info << "Revision updated to " << m_revision << "\n";
const bool retVal = save(m_workspaceFile);
std::stringstream ss;
ss << m_workspacePath + "/revision-" << std::setw(3) << std::setfill('0') << m_revision << "-backup.ovw";
const std::string fn = ss.str();
FS::Files::remove(fn.c_str());
if (FS::Files::copyFile(m_workspaceFile.toASCIIString(), fn.c_str())) { log() << Kernel::LogLevel_Info << "Revision backup saved to " << ss.str() << "\n"; }
else { log() << Kernel::LogLevel_Info << "Error saving backup to " << ss.str() << "\n"; }
return retVal;
}
bool Workspace::wipeConfigurationTokens(const std::string& prefix) const
{
CIdentifier it = CIdentifier::undefined(), prev = CIdentifier::undefined();
while ((it = m_kernelCtx.getConfigurationManager().getNextConfigurationTokenIdentifier(it)) != CIdentifier::undefined())
{
std::string token(m_kernelCtx.getConfigurationManager().getConfigurationTokenName(it).toASCIIString());
// See if token has the prefix?
auto res = std::mismatch(prefix.begin(), prefix.end(), token.begin());
if (res.first == prefix.end())
{
m_kernelCtx.getConfigurationManager().releaseConfigurationToken(it);
it = prev;
}
else { prev = it; }
}
return true;
}
CTime Workspace::getProcessedTime() const
{
std::unique_lock<std::mutex> m_Mutex;
CTime processed = CTime::min();
for (auto& ptr : m_playlist) { processed += ptr.second; }
return processed;
}
} // namespace Tracker
} // namespace OpenViBE
@@ -0,0 +1,278 @@
//
// OpenViBE Tracker
//
// @author J.T. Lindgren
//
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <iostream>
#include "CTracker.h"
#include "GUI.h"
#include "Testclass.h"
#if defined TARGET_OS_Windows
#include "Windows.h"
#include "shellapi.h"
#endif
typedef enum
{
CommandLineFlag_None = 0x00000000, // 0
CommandLineFlag_Define = 0x00000040, // 64
CommandLineFlag_Config = 0x00000080, // 128
} ECommandLineFlag;
typedef struct SConfiguration
{
SConfiguration() { }
OpenViBE::Designer::ECommandLineFlag getFlags() const { return OpenViBE::Designer::ECommandLineFlag(define | config); }
std::vector<std::pair<ECommandLineFlag, std::string>> flags;
ECommandLineFlag define = CommandLineFlag_None;
ECommandLineFlag config = CommandLineFlag_None;
bool help = false;
// to resolve warning: padding struct '_SConfiguration' with 4 bytes to align 'm_oTokenMap
int structPadding = 0;
std::map<std::string, std::string> tokens;
} configuration_t;
bool parse_arguments(int argc, char** argv, configuration_t& rConfiguration)
{
configuration_t config;
std::vector<std::string> argValue;
#if defined TARGET_OS_Windows
int nArg;
LPWSTR* argListUtf16 = CommandLineToArgvW(GetCommandLineW(), &nArg);
for (int i = 1; i < nArg; ++i)
{
GError* error = nullptr;
glong itemsRead, itemsWritten;
char* argUtf8 = g_utf16_to_utf8(reinterpret_cast<gunichar2*>(argListUtf16[i]), size_t(wcslen(argListUtf16[i])), &itemsRead, &itemsWritten, &error);
argValue.push_back(argUtf8);
if (error)
{
g_error_free(error);
return false;
}
}
#else
argValue = std::vector<std::string>(argv + 1, argv + argc);
#endif
argValue.push_back("");
for (auto it = argValue.cbegin(); it != argValue.cend(); ++it)
{
if (*it == "") { }
else if (*it == "-h" || *it == "--help")
{
config.help = true;
rConfiguration = config;
return false;
}
else if (*it == "-c" || *it == "--config")
{
if (*++it == "")
{
std::cout << "Error: Switch --config needs an argument\n";
return false;
}
config.flags.push_back(std::make_pair(CommandLineFlag_Config, *it));
}
else if (*it == "-d" || *it == "--define")
{
if (*++it == "")
{
std::cout << "Error: Need two arguments after -d / --define.\n";
return false;
}
// Were not using = as a separator for token/value, as on Windows its a problem passing = to the cmd interpreter
// which is used to launch the actual designer exe.
const std::string& token = *it;
if (*++it == "")
{
std::cout << "Error: Need two arguments after -d / --define.\n";
return false;
}
const std::string& value = *it; // iterator will increment later
config.tokens[token] = value;
}
else if (*it == "--g-fatal-warnings")
{
// Do nothing here but accept this gtk flag
}
else if (*it == "-v" || *it == "--version")
{
#if defined(TARGET_OS_Windows)
const std::string platform("Windows");
#elif defined(TARGET_OS_Linux)
const std::string platform("Linux");
#else
const std::string platform("Other");
#endif
#if defined(TARGET_ARCHITECTURE_x64)
const std::string arch("64bit");
#else
const std::string arch("32bit");
#endif
#if defined(TARGET_BUILDTYPE_Debug)
const std::string buildType("Debug");
#elif defined(TARGET_BUILDTYPE_Release)
const std::string buildType("Release");
#else
const std::string buildType("Unknown");
#endif
std::cout << OV_PROJECT_NAME << " Tracker - Version " << OV_VERSION_MAJOR << "." << OV_VERSION_MINOR << "." << OV_VERSION_PATCH
<< " (" << platform << " " << arch << " " << buildType << " build)" << std::endl;
exit(0);
}
else { return false; }
}
rConfiguration = config;
return true;
}
void user_info(char** argv, OpenViBE::Kernel::ILogManager* logMgr)
{
const std::vector<std::string> messages =
{
"Syntax : " + std::string(argv[0]) + " [ switches ]\n", "Possible switches :\n",
" --help : displays this help message and exits\n",
" --config filename : path to config file\n",
" --define token value : specify configuration token with a given value\n",
" --version : shows version information and exits"
};
if (logMgr) { for (const auto& m : messages) { (*logMgr) << OpenViBE::Kernel::LogLevel_Info << m; } }
else { for (const auto& m : messages) { std::cout << m; } }
}
class KernelWrapper
{
public:
KernelWrapper() : m_KernelCtx(nullptr), m_KernelDesc(nullptr) { }
~KernelWrapper()
{
if (m_KernelCtx)
{
std::cout << "Unloading kernel" << std::endl;
OpenViBE::Toolkit::uninitialize(*m_KernelCtx);
m_KernelDesc->releaseKernel(m_KernelCtx);
m_KernelCtx = nullptr;
}
std::cout << "Unloading loader" << std::endl;
m_KernelLoader.uninitialize();
m_KernelLoader.unload();
}
bool initialize(const configuration_t& config)
{
std::cout << "[ INF ] Created kernel loader, trying to load kernel module" << std::endl;
#if defined TARGET_OS_Windows
const OpenViBE::CString kernelFile = OpenViBE::Directories::getLibDir() + "/openvibe-kernel.dll";
#else
const OpenViBE::CString kernelFile = OpenViBE::Directories::getLibDir() + "/libopenvibe-kernel.so";
#endif
OpenViBE::CString error;
if (!m_KernelLoader.load(kernelFile, &error))
{
std::cout << "[ FAILED ] Error loading kernel from [" << kernelFile << "]: " << error << "\n";
return false;
}
std::cout << "[ INF ] Kernel module loaded, trying to get kernel descriptor" << std::endl;
m_KernelLoader.initialize();
m_KernelLoader.getKernelDesc(m_KernelDesc);
if (!m_KernelDesc)
{
std::cout << "[ FAILED ] No kernel descriptor" << std::endl;
return false;
}
std::cout << "[ INF ] Got kernel descriptor, trying to create kernel" << std::endl;
const OpenViBE::CString configFile = OpenViBE::CString(OpenViBE::Directories::getDataDir() + "/kernel/openvibe.conf");
m_KernelCtx = m_KernelDesc->createKernel("tracker", configFile);
if (!m_KernelCtx)
{
std::cout << "[ FAILED ] No kernel created by kernel descriptor" << std::endl;
return false;
}
m_KernelCtx->initialize();
// Gets the 'stimulation id,name' mappings loaded to TypeManager etc
OpenViBE::Toolkit::initialize(*m_KernelCtx);
OpenViBE::Kernel::IConfigurationManager& configManager = m_KernelCtx->getConfigurationManager();
// configManager.addConfigurationFromFile(configManager.expand("${Path_Data}/applications/acquisition-server/acquisition-server-defaults.conf"));
// User configuration mods
configManager.addConfigurationFromFile(configManager.expand("${Path_UserData}/openvibe-tracker.conf"));
// File pointed to by --config flag overrides earlier
for (const auto& it : config.flags)
{
if (it.first == CommandLineFlag_Config) { configManager.addConfigurationFromFile(configManager.expand(it.second.c_str())); }
}
// Explicit --define tokens override all earlier
for (const auto& it : config.tokens) { configManager.addOrReplaceConfigurationToken(it.first.c_str(), it.second.c_str()); }
// Load all the plugins. Note that most are not needed by tracker, but will avoid some confusion
// when somebody adds a plugin
m_KernelCtx->getPluginManager().addPluginsFromFiles(configManager.expand("${Kernel_Plugins}"));
return true;
}
OpenViBE::Kernel::IKernelContext* m_KernelCtx;
OpenViBE::Kernel::IKernelDesc* m_KernelDesc;
OpenViBE::CKernelLoader m_KernelLoader;
};
int main(const int argc, char* argv[])
{
configuration_t config;
const bool argParseResult = parse_arguments(argc, argv, config);
if (!argParseResult)
{
if (config.help)
{
user_info(argv, nullptr);
return 0;
}
}
KernelWrapper kernelWrapper;
if (!kernelWrapper.initialize(config)) { return 1; }
OpenViBE::Tracker::CTracker app(*kernelWrapper.m_KernelCtx);
OpenViBE::Tracker::GUI gui(argc, argv, app);
// We initialize the app after launching the GUI so we get the log into the GUI as early as possible
app.initialize();
const bool retVal = gui.run();
return (retVal == true ? 0 : 1);
}