Commit b3b0a9c8 authored by Jussi Lindgren's avatar Jussi Lindgren

Plugins: LSL Export plugins will now set timestamps relative to lsl::local_clock().

- Previous behavior can be enabled by setting OpenViBE configuration
token "LSL_UseAbsoluteTimeStamps = True". However, you shouldn't do
that unless you know what you are doing.
- Gipsa LSL box has not been changed. It lets LSL do the stamping.
parent 03c00528
......@@ -512,7 +512,15 @@ boolean CDriverLabStreamingLayer::loop(void)
// float64 l_f64StimTime = captureTime + l_f64Correction - l_f64FirstCaptureTime;
// For openvibe, we need to set the stimulus time relative to the start of the chunk
const float64 l_f64StimTime = captureTime - l_f64BlockStartTime;
// n.b. stimulations in the past are not supported and will be stamped with the start of the chunk time
static bool warningPrinted = false;
if(captureTime < l_f64BlockStartTime && !warningPrinted)
{
m_rDriverContext.getLogManager() << LogLevel_Warning << "Received marker from LSL dated before the start of the current chunk. Stamp will be adjusted to the chunk start time. Will not warn again.";
warningPrinted = true;
}
const float64 l_f64StimTime = (captureTime > l_f64BlockStartTime ? captureTime - l_f64BlockStartTime : 0);
// m_rDriverContext.getLogManager() << LogLevel_Info << "Got a marker " << l_i32Marker << " at " << captureTime << " -> "
// << l_f64StimTime << "s."
......
......@@ -14,6 +14,7 @@ SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEModuleSystem")
INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindThirdPartyBoost")
INCLUDE("FindThirdPartyBoost_System")
......
......@@ -11,6 +11,7 @@
#include <iostream>
#include <string>
#include <system/ovCTime.h>
#include <openvibe/ovITimeArithmetics.h>
using namespace OpenViBE;
......@@ -20,6 +21,29 @@ using namespace OpenViBE::Plugins;
using namespace OpenViBEPlugins;
using namespace OpenViBEPlugins::NetworkIO;
// Inputs: a timestamp (in ov's 32:32 bit fixed point time) comparable to zgetTime() output
// Output: a timestamp comparable to LSL's local_clock() output
// Note: Duplicate code in ovasCPluginLSLOutput.cpp
double getLSLRelativeTime(uint64_t timeStamp)
{
const uint64_t ovNow = System::Time::zgetTime();
const double lslNow = lsl::local_clock();
double diffToCurrent;
if(timeStamp > ovNow)
{
diffToCurrent = ITimeArithmetics::timeToSeconds(timeStamp - ovNow);
}
else
{
diffToCurrent = -ITimeArithmetics::timeToSeconds(ovNow - timeStamp);
}
const double lslRelativeTime = lslNow + diffToCurrent;
return lslRelativeTime;
}
boolean CBoxAlgorithmLSLExport::initialize(void)
{
m_pSignalOutlet = nullptr;
......@@ -41,6 +65,8 @@ boolean CBoxAlgorithmLSLExport::initialize(void)
m_sMarkerStreamID = CIdentifier::random().toString();
}
m_bUseAbsoluteTimeStamps = this->getConfigurationManager().expandAsBoolean("${LSL_UseAbsoluteTimeStamps}", m_bUseAbsoluteTimeStamps);
this->getLogManager() << LogLevel_Trace << "Will create streams [" << m_sSignalStreamName << ", id " << m_sSignalStreamID << "] and ["
<< m_sMarkerStreamName << ", id " << m_sMarkerStreamID << "]\n";
......@@ -91,6 +117,14 @@ boolean CBoxAlgorithmLSLExport::process(void)
// Process signals
for(uint32 j=0; j<l_rDynamicBoxContext.getInputChunkCount(0); j++)
{
if(m_ui64StartTime==0)
{
// This should be the ov time when Acquisition Client tagged the first chunk with [0,t], t = bufferSize/samplingRate.
// As the true time is not known here, we have to be content with a slightly delayed estimate (delay is the duration
// it took to get from the stamping of the first chunk to this point in the code)
m_ui64StartTime = System::Time::zgetTime();
}
m_oSignalDecoder.decode(j);
if(m_oSignalDecoder.isHeaderReceived() && !m_pSignalOutlet)
{
......@@ -146,23 +180,56 @@ boolean CBoxAlgorithmLSLExport::process(void)
const uint32 l_ui32SamplesPerBlock = l_pMatrix->getDimensionSize(1);
const float64* l_pInputBuffer = l_pMatrix->getBuffer();
const uint64 l_ui64StartTime = l_rDynamicBoxContext.getInputChunkStartTime(0, j);
const uint64 l_ui64EndTime = l_rDynamicBoxContext.getInputChunkEndTime(0, j);
const uint64 l_ui64ChunkStart = l_rDynamicBoxContext.getInputChunkStartTime(0, j);
const uint64 l_ui64ChunkEnd = l_rDynamicBoxContext.getInputChunkEndTime(0, j);
// note: the step computed below should be exactly the same as could be obtained from the sampling rate
const float64 l_f64Start = ITimeArithmetics::timeToSeconds(l_ui64StartTime);
const float64 l_f64Step = ITimeArithmetics::timeToSeconds(l_ui64EndTime-l_ui64StartTime)/static_cast<float64>(l_ui32SamplesPerBlock);
// n.b. this would work more 'lsl-like' if the timestamps were the real acquisition timestamps
if(m_bUseAbsoluteTimeStamps)
{
const float64 l_f64SampleStep = ITimeArithmetics::timeToSeconds(l_ui64ChunkEnd-l_ui64ChunkStart)/static_cast<float64>(l_ui32SamplesPerBlock);
const float64 l_f64ChunkStart = ITimeArithmetics::timeToSeconds(l_ui64ChunkStart);
for(uint32_t s=0;s<l_ui32SamplesPerBlock;s++)
{
for(uint32 c=0;c<l_ui32ChannelCount;c++) {
m_pSingleSampleBuffer[c] = static_cast<float32>(l_pInputBuffer[c*l_ui32SamplesPerBlock+s]);
}
m_pSignalOutlet->push_sample(m_pSingleSampleBuffer, l_f64ChunkStart + s*l_f64SampleStep);
}
}
else
{
const uint64_t l_ui64SampleStep = (l_ui64ChunkEnd-l_ui64ChunkStart)/static_cast<uint64>(l_ui32SamplesPerBlock);
for(uint32_t s=0;s<l_ui32SamplesPerBlock;s++)
{
for(uint32 c=0;c<l_ui32ChannelCount;c++) {
m_pSingleSampleBuffer[c] = static_cast<float32>(l_pInputBuffer[c*l_ui32SamplesPerBlock+s]);
}
// startTime+start+i*step computes the time of the sample relative to current time [ zgetTime() ]
const double lslTime = getLSLRelativeTime(m_ui64StartTime + l_ui64ChunkStart + s*l_ui64SampleStep);
m_pSignalOutlet->push_sample(m_pSingleSampleBuffer, lslTime);
}
}
// note: the step computed below should be exactly the same as could be obtained from the sampling rate
const float64 l_f64ChunkStart = ITimeArithmetics::timeToSeconds(l_ui64ChunkStart);
const float64 l_f64SampleStep = ITimeArithmetics::timeToSeconds(l_ui64ChunkEnd-l_ui64ChunkStart)/static_cast<float64>(l_ui32SamplesPerBlock);
for(uint32 s=0;s<l_ui32SamplesPerBlock;s++)
{
for(uint32 c=0;c<l_ui32ChannelCount;c++) {
m_pSingleSampleBuffer[c] = static_cast<float32>(l_pInputBuffer[c*l_ui32SamplesPerBlock+s]);
}
m_pSignalOutlet->push_sample(m_pSingleSampleBuffer, l_f64Start + s*l_f64Step);
m_pSignalOutlet->push_sample(m_pSingleSampleBuffer, l_f64ChunkStart + s*l_f64SampleStep);
}
// m_rKernelContext.getLogManager() << LogLevel_Info << "Pushed first signal at " << l_f64Start << "\n";
// m_rKernelContext.getLogManager() << LogLevel_Info << "Step is " << l_f64Step << "\n";
// m_rKernelContext.getLogManager() << LogLevel_Info << "Pushed first signal at " << l_f64ChunkStart << "\n";
// m_rKernelContext.getLogManager() << LogLevel_Info << "Step is " << l_f64SampleStep << "\n";
}
}
......@@ -180,6 +247,12 @@ boolean CBoxAlgorithmLSLExport::process(void)
// Note that stimuli with identifiers not fitting to int32 will be mangled by a static cast.
for(uint32 j=0; j<l_rDynamicBoxContext.getInputChunkCount(1); j++)
{
if(m_ui64StartTime==0)
{
// We may receive only stims, so this init is needed here for that case (see also previous explanation)
m_ui64StartTime = System::Time::zgetTime();
}
m_oStimulationDecoder.decode(j);
if(m_oStimulationDecoder.isHeaderReceived() && !m_pStimulusOutlet)
{
......@@ -211,10 +284,12 @@ boolean CBoxAlgorithmLSLExport::process(void)
for(uint32 s=0;s<l_pStimulationSet->getStimulationCount();s++)
{
const int32 l_i32StimulationCode = static_cast<int32>(l_pStimulationSet->getStimulationIdentifier(s));
const float64 l_f64StimulationDate = ITimeArithmetics::timeToSeconds(l_pStimulationSet->getStimulationDate(s));
const double l_dStimulationDate = m_bUseAbsoluteTimeStamps ?
static_cast<double>(ITimeArithmetics::timeToSeconds(l_pStimulationSet->getStimulationDate(s))) :
getLSLRelativeTime(m_ui64StartTime + l_pStimulationSet->getStimulationDate(s));
// m_rKernelContext.getLogManager() << LogLevel_Info << "Push stim " << l_i32StimulationCode << " at " << l_f64StimulationDate << "s\n";
m_pStimulusOutlet->push_sample(&l_i32StimulationCode,l_f64StimulationDate);
m_pStimulusOutlet->push_sample(&l_i32StimulationCode,l_dStimulationDate);
}
}
}
......
......@@ -70,6 +70,9 @@ namespace OpenViBEPlugins
OpenViBE::CString m_sSignalStreamID;
OpenViBE::CString m_sMarkerStreamName;
OpenViBE::CString m_sMarkerStreamID;
bool m_bUseAbsoluteTimeStamps = false;
uint64_t m_ui64StartTime = 0;
};
class CBoxAlgorithmLSLExportListener : public OpenViBEToolkit::TBoxListener < OpenViBE::Plugins::IBoxListener >
......
......@@ -15,6 +15,7 @@
#include <stdio.h> // sprintf on Linux
#include <openvibe/ovITimeArithmetics.h>
#include <system/ovCTime.h>
#include "../ovasCSettingsHelper.h"
#include "../ovasCSettingsHelperOperators.h"
......@@ -27,6 +28,29 @@ using namespace OpenViBEAcquisitionServer;
using namespace OpenViBEAcquisitionServerPlugins;
using namespace std;
// Inputs: a timestamp (in ov's 32:32 bit fixed point time) comparable to zgetTime() output
// Output: a timestamp comparable to LSL's local_clock() output
// Note: Duplicate code in ovpCBoxAlgorithmLSLExport.cpp
double getLSLRelativeTime(uint64_t timeStamp)
{
const uint64_t ovNow = System::Time::zgetTime();
const double lslNow = lsl::local_clock();
double diffToCurrent;
if(timeStamp > ovNow)
{
diffToCurrent = ITimeArithmetics::timeToSeconds(timeStamp - ovNow);
}
else
{
diffToCurrent = -ITimeArithmetics::timeToSeconds(ovNow - timeStamp);
}
const double lslRelativeTime = lslNow + diffToCurrent;
return lslRelativeTime;
}
CPluginLSLOutput::CPluginLSLOutput(const IKernelContext& rKernelContext) :
IAcquisitionServerPlugin(rKernelContext, CString("AcquisitionServer_Plugin_LabStreamingLayerOutput")),
m_bIsLSLOutputEnabled(false),
......@@ -73,6 +97,9 @@ CPluginLSLOutput::~CPluginLSLOutput()
bool CPluginLSLOutput::startHook(const std::vector<OpenViBE::CString>& vSelectedChannelNames, uint32_t ui32SamplingFrequency, uint32_t ui32ChannelCount, uint32_t ui32SampleCountPerSentBlock)
{
m_bUseAbsoluteTimeStamps = m_rKernelContext.getConfigurationManager().expandAsBoolean("${LSL_UseAbsoluteTimeStamps}", m_bUseAbsoluteTimeStamps);
m_ui64StartTime = System::Time::zgetTime();
m_ui32SampleCountPerSentBlock = ui32SampleCountPerSentBlock;
......@@ -123,16 +150,32 @@ void CPluginLSLOutput::loopHook(std::deque < std::vector < OpenViBE::float32 > >
if(m_oSignalOutlet->have_consumers())
{
// note: the step computed below should be exactly the same as could be obtained from the sampling rate
const float64 l_f64Start = ITimeArithmetics::timeToSeconds(start);
const float64 l_f64Step = ITimeArithmetics::timeToSeconds(end-start)/static_cast<float64>(m_ui32SampleCountPerSentBlock);
// n.b. this would work more 'lsl-like' if the timestamps were the real acquisition timestamps
if(m_bUseAbsoluteTimeStamps)
{
const float64 l_f64SampleStep = ITimeArithmetics::timeToSeconds(end-start)/static_cast<float64>(m_ui32SampleCountPerSentBlock);
const float64 l_f64ChunkStart = ITimeArithmetics::timeToSeconds(start);
for(uint32_t i=0;i<m_ui32SampleCountPerSentBlock;i++)
for(uint32_t i=0;i<m_ui32SampleCountPerSentBlock;i++)
{
m_oSignalOutlet->push_sample(vPendingBuffer[i], l_f64ChunkStart + i*l_f64SampleStep);
}
}
else
{
m_oSignalOutlet->push_sample(vPendingBuffer[i], l_f64Start + i*l_f64Step);
const uint64_t l_ui64SampleStep = (end-start)/static_cast<uint64>(m_ui32SampleCountPerSentBlock);
for(uint32_t i=0;i<m_ui32SampleCountPerSentBlock;i++)
{
// startTime+start+i*step computes the time of the sample relative to current time [ zgetTime() ]
const double lslTime = getLSLRelativeTime(m_ui64StartTime + start + i*l_ui64SampleStep);
m_oSignalOutlet->push_sample(vPendingBuffer[i], lslTime);
}
}
// m_rKernelContext.getLogManager() << LogLevel_Info << "Pushed first signal at " << l_f64Start << "\n";
// m_rKernelContext.getLogManager() << LogLevel_Info << "Step is " << l_f64Step << "\n";
// m_rKernelContext.getLogManager() << LogLevel_Info << "Pushed first signal at " << l_f64ChunkStart << "\n";
// m_rKernelContext.getLogManager() << LogLevel_Info << "Step is " << l_f64SampleStep << "\n";
}
// Output stimuli
......@@ -144,10 +187,18 @@ void CPluginLSLOutput::loopHook(std::deque < std::vector < OpenViBE::float32 > >
stimulationSet.getStimulationDate(i) < end)
{
const int32_t l_i32StimulationCode = static_cast<int32_t>(stimulationSet.getStimulationIdentifier(i));
const float64 l_f64StimulationDate = ITimeArithmetics::timeToSeconds(stimulationSet.getStimulationDate(i));
// m_rKernelContext.getLogManager() << LogLevel_Info << "Push stim " << l_i32StimulationCode << " at " << l_f64StimulationDate << "s\n";
m_oStimulusOutlet->push_sample(&l_i32StimulationCode,l_f64StimulationDate);
if(m_bUseAbsoluteTimeStamps)
{
const float64 l_f64StimulationDate = ITimeArithmetics::timeToSeconds(stimulationSet.getStimulationDate(i));
m_oStimulusOutlet->push_sample(&l_i32StimulationCode,l_f64StimulationDate);
}
else
{
const double lslTime = getLSLRelativeTime(m_ui64StartTime + stimulationSet.getStimulationDate(i));
m_oStimulusOutlet->push_sample(&l_i32StimulationCode, lslTime);
}
}
}
}
......
......@@ -40,12 +40,15 @@ namespace OpenViBEAcquisitionServer
OpenViBE::CString m_sMarkerStreamName;
OpenViBE::CString m_sMarkerStreamID;
private:
lsl::stream_outlet* m_oSignalOutlet;
lsl::stream_outlet* m_oStimulusOutlet;
uint32_t m_ui32SampleCountPerSentBlock;
uint64_t m_ui64StartTime;
bool m_bUseAbsoluteTimeStamps = false;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment