init
This commit is contained in:
+213
@@ -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
|
||||
+97
@@ -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
|
||||
Executable
+55
@@ -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
|
||||
Executable
+51
@@ -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
|
||||
+68
@@ -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
|
||||
Executable
+52
@@ -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
|
||||
+48
@@ -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
|
||||
+51
@@ -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
|
||||
+58
@@ -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
|
||||
Executable
+50
@@ -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
|
||||
+184
@@ -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
Executable
+186
@@ -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
|
||||
Executable
+512
@@ -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
|
||||
+262
@@ -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
|
||||
+118
@@ -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
|
||||
+135
@@ -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
|
||||
Executable
+70
@@ -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
|
||||
+197
@@ -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
|
||||
Executable
+106
@@ -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
|
||||
Executable
+322
@@ -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
|
||||
+47
@@ -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
|
||||
Masterarbeit/openvibe/extras-master/applications/platform/tracker/src/StreamRendererChannelUnits.cpp
Executable
+49
@@ -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
|
||||
+45
@@ -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
|
||||
Executable
+82
@@ -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
|
||||
Executable
+235
@@ -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
|
||||
Executable
+153
@@ -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
|
||||
Executable
+270
@@ -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
|
||||
Executable
+207
@@ -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
|
||||
+87
@@ -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
|
||||
Executable
+53
@@ -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
|
||||
+67
@@ -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
|
||||
+59
@@ -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
|
||||
+889
@@ -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
|
||||
+278
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user