This commit is contained in:
2021-10-14 13:47:35 +02:00
commit 6625a8dfaa
4026 changed files with 844291 additions and 0 deletions
@@ -0,0 +1,26 @@
PROJECT(openvibe-plugins-dll-bridge)
SET(PROJECT_VERSION_MAJOR ${OV_GLOBAL_VERSION_MAJOR})
SET(PROJECT_VERSION ${OV_GLOBAL_VERSION_STRING})
FILE(GLOB_RECURSE SRC_FILES src/*.cpp src/*.h src/*.inl)
ADD_LIBRARY(${PROJECT_NAME} SHARED ${SRC_FILES})
SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES
VERSION ${PROJECT_VERSION}
SOVERSION ${PROJECT_VERSION_MAJOR}
FOLDER ${PLUGINS_FOLDER}
COMPILE_FLAGS "-DOVP_Exports -DOVP_Shared")
# ---------------------------------
INCLUDE("FindOpenViBE")
INCLUDE("FindOpenViBECommon")
INCLUDE("FindOpenViBEToolkit")
INCLUDE("FindOpenViBEModuleEBML")
# -----------------------------
# Install files
# -----------------------------
INSTALL(TARGETS ${PROJECT_NAME}
RUNTIME DESTINATION ${DIST_BINDIR}
LIBRARY DESTINATION ${DIST_LIBDIR}
ARCHIVE DESTINATION ${DIST_LIBDIR})
@@ -0,0 +1,27 @@
Example how to use a Fortran component to do signal processing
On Windows,
- Compile the Fortran code with a 32(!) bit fortran compiler.
The Intel Fortran compiler has been observed to work. e.g.
> where ifort
C:\Program Files (x86)\Intel\Composer XE 2013 SP1\bin\ia32\ifort
> ifort /static /dll fortranbox_dll.f90
The resulting .dll file can be specified to the DLL Bridge box.
Remember to set the full path correctly.
Using the /static flag allows you to distribute your built dll
to other computers more easily.
On Linux,
# gfortran -shared fortranbox_dll.f90 -o fortranbox_dll.so
Then specify the .so file to the DLL Bridge box.
@@ -0,0 +1,125 @@
!
! This code illustrates passing a matrix in from OpenViBE, making modifications,
! and sending another matrix back. Care must be taken that the difference in C/Fortran
! row/column major order of the matrix storage is taken into account.
!
! This example adds the channel number to the channel data (so each channel will have noticable and different effect).
! It also adds one channel with a constant pattern, to illustrate how to change the channel count. The
! change of dimensions must be specified in the box_process_header() function.
!
Module box
! Matrix dimensions in and out
INTEGER g_rowsIn,g_colsIn, g_rowsOut,g_colsOut
! Sampling rates in and out
INTEGER g_samplingRateIn, g_samplingRateOut
CONTAINS
! this routine is run once at the beginning of playback. it can be used to precompute/load things needed later.
! set errorCode to 0 on success, a non-zero code otherwise.
subroutine box_init(inputParamsLength, inputParams, errorCode) bind(C)
!DEC$ ATTRIBUTES DLLEXPORT :: box_init
use,intrinsic:: iso_c_binding
implicit none
INTEGER(C_INT32_T), intent(in) :: inputParamsLength;
CHARACTER(KIND=C_CHAR), dimension(inputParamsLength), intent(in) :: inputParams
INTEGER(C_INT32_T), intent(out) :: errorCode;
! in this example, the routine does nothing much
write(*,*) "box_init() in Fortran"
write(*,*) " Received params: ", inputParams
errorCode = 0
end subroutine box_init
! this routine is run when OpenViBE receives a stream header. The purpose of this
! function is to return the sizes of the output matrices, and change in the
! sampling rate if any. If sampling rate is 0, the input of process()
! will be just a matrix without a known sampling frequency.
!
! set errorCode to 0 on success, a non-zero code otherwise.
subroutine box_process_header(rowsIn,colsIn,samplingRateIn,rowsOut,colsOut,samplingRateOut,errorCode) bind(C)
!DEC$ ATTRIBUTES DLLEXPORT :: box_process_header
use iso_c_binding
INTEGER(C_INT32_T), intent(in) :: rowsIn, colsIn, samplingRateIn
INTEGER(C_INT32_T), intent(out) :: rowsOut, colsOut, samplingRateOut
INTEGER(C_INT32_T), intent(out) :: errorCode;
g_rowsIn = rowsIn
g_colsIn = colsIn
g_samplingRateIn = samplingRateIn
! Here rowOut=rowIn+1 since we want to add a channel in this example
g_rowsOut = rowsIn + 1
g_colsOut = colsIn
g_samplingRateOut = samplingRateIn
write(*,*) " Process header: Dims", g_rowsIn, g_colsIn, "->", g_rowsOut, g_colsOut
write(*,*) " Freq", g_SamplingRateIn, "->", g_samplingRateOut
rowsOut = g_rowsOut
colsOut = g_colsOut
samplingRateOut = g_samplingRateOut
errorCode = 0;
end subroutine box_process_header
! this routine is runs once for each matrix chunk of data.
! set errorCode to 0 on success, a non-zero code otherwise.
subroutine box_process(matIn,matOut,errorCode) bind(C)
!DEC$ ATTRIBUTES DLLEXPORT :: box_process
use iso_c_binding
REAL(C_DOUBLE), intent(in) :: matIn(g_rowsIn*g_colsIn)
REAL(C_DOUBLE), intent(out) :: matOut(g_rowsOut*g_colsOut)
INTEGER(C_INT32_T), intent(out) :: errorCode
REAL, DIMENSION(1:g_rowsIn,1:g_colsIn) :: A;
REAL, DIMENSION(1:g_rowsOut,1:g_colsOut) :: B;
! write(*,*) "box_process() in Fortran" , rows, " ", cols
! matrix from C/C++ is in row-major order, reshape
A = reshape(matIn, (/ g_rowsIn, g_colsIn /), ORDER = (/ 2, 1 /) )
! Add +nChannel to each channel
do i=1,g_rowsIn
do j=1,g_colsIn
B(i,j) = A(i,j) + i;
end do
end do
! the new channel just has a simple pattern
do j=1,g_colsOut
B(g_rowsOut,j) = j;
end do
! print the matrix
!WRITE(*,*)
!DO I = LBOUND(B,1), UBOUND(B,1)
! WRITE(*,*) (B(I,J), J = LBOUND(B,2), UBOUND(B,2))
!END DO
matOut = reshape( TRANSPOSE(B), (/ g_rowsOut * g_colsOut /) )
errorCode = 0
end subroutine box_process
! this routine is run once at the end of playback. it can be used to deallocate resources (memory, files, ...).
! set errorCode to 0 on success, a non-zero code otherwise.
subroutine box_uninit(errorCode) bind(C)
!DEC$ ATTRIBUTES DLLEXPORT :: box_uninit
use iso_c_binding
INTEGER(C_INT32_T), intent(out) :: errorCode
! in this example, the routine does nothing much
write(*,*) "box_uninit() in Fortran"
errorCode = 0
end subroutine box_uninit
end module box
@@ -0,0 +1,294 @@
#include "ovpCBoxAlgorithmDLLBridge.h"
#include <iostream>
#if defined(TARGET_OS_Windows)
#include <windows.h>
#elif defined(TARGET_OS_Linux)
#include <dlfcn.h>
#endif
namespace OpenViBE {
namespace Plugins {
namespace DLLBridge {
// #define DLLBRIDGE_DEBUG
#if defined(DLLBRIDGE_DEBUG)
static void dumpMatrix(Kernel::ILogManager& mgr, const CMatrix& mat, const CString& desc)
{
mgr << Kernel::LogLevel_Info << desc << "\n";
for (size_t i = 0; i < mat.getDimensionSize(0); i++)
{
mgr << Kernel::LogLevel_Info << "Row " << i << ": ";
for (size_t j = 0; j < mat.getDimensionSize(1); j++) { mgr << mat.getBuffer()[i * mat.getDimensionSize(1) + j] << " "; }
mgr << "\n";
}
}
#endif
#if defined(TARGET_OS_Windows)
#define OPEN_FUNC(file, library) \
std::string tmp = file.toASCIIString();\
std::replace(tmp.begin(), tmp.end(), '/', '\\');\
file = tmp.c_str();\
library = LoadLibrary(file.toASCIIString());
#define PROC_FUNC GetProcAddress
#define ERR_FUNC GetLastError
#define CLOSE_FUNC FreeLibrary
#else
#define OPEN_FUNC(file, library) library = dlopen(file.toASCIIString(), RTLD_LAZY);
#define PROC_FUNC dlsym
#define ERR_FUNC dlerror
#define CLOSE_FUNC dlclose
#endif
// This function is called once when user presses play in Designer
bool CDLLBridge::initialize()
{
this->getLogManager() << Kernel::LogLevel_Debug << "Initializing\n";
m_encoder = nullptr;
m_decoder = nullptr;
m_library = nullptr;
m_initialize = nullptr;
m_processHeader = nullptr;
m_process = nullptr;
m_uninitialize = nullptr;
m_dllFile = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 0);
m_parameters = FSettingValueAutoCast(*this->getBoxAlgorithmContext(), 1);
if (m_dllFile == CString(""))
{
this->getLogManager() << Kernel::LogLevel_Error << "Need a DLL file\n";
return false;
}
OPEN_FUNC(m_dllFile, m_library)
if (!m_library)
{
this->getLogManager() << Kernel::LogLevel_Error << "Failed to load " << m_dllFile << ", error code " << int64_t(ERR_FUNC()) << "\n";
return false;
}
m_initialize = INITFUNC(PROC_FUNC(m_library, "box_init"));
if (!m_initialize)
{
this->getLogManager() << Kernel::LogLevel_Error << "Unable to find box_init() in the DLL\n";
return false;
}
m_process = PROCESSFUNC(PROC_FUNC(m_library, "box_process"));
if (!m_process)
{
this->getLogManager() << Kernel::LogLevel_Error << "Unable to find box_process() in the DLL\n";
return false;
}
m_processHeader = PROCESSHEADERFUNC(PROC_FUNC(m_library, "box_process_header"));
if (!m_processHeader)
{
this->getLogManager() << Kernel::LogLevel_Error << "Unable to find box_process_header() in the DLL\n";
return false;
}
m_uninitialize = UNINITFUNC(PROC_FUNC(m_library, "box_uninit"));
if (!m_uninitialize)
{
this->getLogManager() << Kernel::LogLevel_Error << "Unable to find box_uninit() in the DLL\n";
return false;
}
const Kernel::IBox& boxContext = this->getStaticBoxContext();
boxContext.getInputType(0, m_inputTypeID);
if (m_inputTypeID == OV_TypeId_StreamedMatrix)
{
Toolkit::TStreamedMatrixDecoder<CDLLBridge>* decoder = new Toolkit::TStreamedMatrixDecoder<CDLLBridge>;
Toolkit::TStreamedMatrixEncoder<CDLLBridge>* encoder = new Toolkit::TStreamedMatrixEncoder<CDLLBridge>;
decoder->initialize(*this, 0);
encoder->initialize(*this, 0);
m_decoder = decoder;
m_encoder = encoder;
}
else if (m_inputTypeID == OV_TypeId_Signal)
{
Toolkit::TSignalDecoder<CDLLBridge>* decoder = new Toolkit::TSignalDecoder<CDLLBridge>;
Toolkit::TSignalEncoder<CDLLBridge>* encoder = new Toolkit::TSignalEncoder<CDLLBridge>;
decoder->initialize(*this, 0);
encoder->initialize(*this, 0);
m_decoder = decoder;
m_encoder = encoder;
}
else
{
this->getLogManager() << Kernel::LogLevel_Error << "Unknown input type " << m_inputTypeID << ". This should never happen.\n";
return false;
}
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_init() : Calling\n";
// Do some initialization in DLL
int length = m_parameters.length();
int code = 0;
m_initialize(&length, m_parameters.toASCIIString(), &code);
if (code)
{
this->getLogManager() << Kernel::LogLevel_Error << "DLL box_init() : Returned error code " << code << "\n";
return false;
}
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_init() : Return ok\n";
return true;
}
// This function is called once when user presses Stop in Designer
bool CDLLBridge::uninitialize()
{
this->getLogManager() << Kernel::LogLevel_Debug << "Uninitializing\n";
if (m_uninitialize)
{
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_uninit() : Calling\n";
// Do some uninitialization in DLL
int code = 0;
m_uninitialize(&code);
if (code)
{
this->getLogManager() << Kernel::LogLevel_Error << "DLL box_uninit() : Returned error code " << code << "\n";
return false;
}
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_uninit() : Return ok\n";
}
if (m_encoder)
{
m_encoder->uninitialize();
delete m_encoder;
m_encoder = nullptr;
}
if (m_decoder)
{
m_decoder->uninitialize();
delete m_decoder;
m_decoder = nullptr;
}
if (m_library)
{
CLOSE_FUNC(m_library);
m_library = nullptr;
}
return true;
}
bool CDLLBridge::processInput(const size_t /*index*/)
{
getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();
return true;
}
// This function is called for every signal chunk (matrix of N samples [channels x samples]).
bool CDLLBridge::process()
{
this->getLogManager() << Kernel::LogLevel_Debug << "Process chunk\n";
Kernel::IBoxIO& boxContext = this->getDynamicBoxContext();
for (uint32_t i = 0; i < boxContext.getInputChunkCount(0); ++i)
{
// m_decoder/m_encoder should point to StreamedMatrix*coder or Signal*Coder by construction,
// the latter appears to be static castable to the former, in practice.
// n.b. dynamic cast does not work here runtime (this does, for the moment). I do not hazard
// an explanation here. Anybody wanting to find out the reasons be prepared to digest the
// codec template inheritance relationships in toolkit for a while.
Toolkit::TStreamedMatrixDecoder<CDLLBridge>* decoder = dynamic_cast<Toolkit::TStreamedMatrixDecoder<CDLLBridge>*>(m_decoder);
Toolkit::TStreamedMatrixEncoder<CDLLBridge>* encoder = dynamic_cast<Toolkit::TStreamedMatrixEncoder<CDLLBridge>*>(m_encoder);
decoder->decode(i);
if (decoder->isHeaderReceived())
{
if (decoder->getOutputMatrix()->getDimensionCount() != 2)
{
this->getLogManager() << Kernel::LogLevel_Error << "Only 2 dimensional matrices supported\n";
return false;
}
int iSampling = 0;
if (m_inputTypeID == OV_TypeId_Signal)
{
Toolkit::TSignalDecoder<CDLLBridge>* signalDecoder = dynamic_cast<Toolkit::TSignalDecoder<CDLLBridge>*>(m_decoder);
iSampling = int(signalDecoder->getOutputSamplingRate());
}
int iNRows = decoder->getOutputMatrix()->getDimensionSize(0);
int iNCols = decoder->getOutputMatrix()->getDimensionSize(1);
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_process_header() : Calling\n";
int code = 0, oNRows = 0, oNCols = 0, oSampling = 0;
m_processHeader(&iNRows, &iNCols, &iSampling, &oNRows, &oNCols, &oSampling, &code);
if (code)
{
this->getLogManager() << Kernel::LogLevel_Error << "DLL box_process_header() : Returned error code " << code << "\n";
return false;
}
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_process_header() : Return ok\n";
this->getLogManager() << Kernel::LogLevel_Trace << "The function declared" << " rowsOut=" << oNRows << " colsOut=" << oNCols << " sRateOut=" << oSampling << "\n";
encoder->getInputMatrix()->resize(oNRows, oNCols);
if (m_inputTypeID == OV_TypeId_Signal)
{
Toolkit::TSignalEncoder<CDLLBridge>* signalEncoder = dynamic_cast<Toolkit::TSignalEncoder<CDLLBridge>*>(m_encoder);
signalEncoder->getInputSamplingRate() = oSampling;
}
encoder->encodeHeader();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (decoder->isBufferReceived())
{
double* input = decoder->getOutputMatrix()->getBuffer();
double* output = encoder->getInputMatrix()->getBuffer();
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_process() : Calling\n";
// Process the sample chunk in DLL
int errorCode = 0;
m_process(input, output, &errorCode);
if (errorCode)
{
this->getLogManager() << Kernel::LogLevel_Error << "DLL box_process() : Returned error code " << errorCode << "\n";
return false;
}
this->getLogManager() << Kernel::LogLevel_Trace << "DLL box_process() : Return ok\n";
encoder->encodeBuffer();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
if (decoder->isEndReceived())
{
encoder->encodeEnd();
boxContext.markOutputAsReadyToSend(0, boxContext.getInputChunkStartTime(0, i), boxContext.getInputChunkEndTime(0, i));
}
}
return true;
}
} // namespace DLLBridge
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,129 @@
#pragma once
#include "../ovp_defines.h"
#include <openvibe/ov_all.h>
#include <toolkit/ovtk_all.h>
#include <string>
#include <queue>
#if defined(TARGET_OS_Windows)
#include <windows.h>
#endif
namespace OpenViBE {
namespace Plugins {
namespace DLLBridge {
class CDLLBridge final : public Toolkit::TBoxAlgorithm<IBoxAlgorithm>
{
public:
void release() override { delete this; }
bool initialize() override;
bool uninitialize() override;
bool processInput(const size_t index) override;
bool process() override;
_IsDerivedFromClass_Final_(Toolkit::TBoxAlgorithm<IBoxAlgorithm>, OVP_ClassId_DLLBridge)
private:
Toolkit::TDecoder<CDLLBridge>* m_decoder = nullptr;
Toolkit::TEncoder<CDLLBridge>* m_encoder = nullptr;
CIdentifier m_inputTypeID = CIdentifier::undefined();
CString m_dllFile;
CString m_parameters;
// These functions are expected from the DLL library
// @note the inputs are non-const on purpose to ensure maximum compatibility with non-C++ dlls
typedef void (* INITFUNC)(int* paramsLength, const char* params, int* errorCode);
typedef void (* UNINITFUNC)(int* errorCode);
typedef void (* PROCESSHEADERFUNC)(int* rowsIn, int* colsIn, int* samplingRateIn, int* rowsOut, int* colsOut, int* samplingRateOut,
int* errorCode);
typedef void (* PROCESSFUNC)(double* matIn, double* matOut, int* errorCode);
INITFUNC m_initialize = nullptr;
UNINITFUNC m_uninitialize = nullptr;
PROCESSHEADERFUNC m_processHeader = nullptr;
PROCESSFUNC m_process = nullptr;
#if defined(TARGET_OS_Windows)
HINSTANCE m_library = nullptr;
#elif defined(TARGET_OS_Linux)
void* m_library = nullptr;
#endif
};
class CDLLBridgeListener final : public Toolkit::TBoxListener<IBoxListener>
{
public:
bool onInputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getInputType(index, typeID);
box.setOutputType(0, typeID);
return true;
}
bool onOutputTypeChanged(Kernel::IBox& box, const size_t index) override
{
CIdentifier typeID = CIdentifier::undefined();
box.getOutputType(index, typeID);
box.setInputType(0, typeID);
return true;
}
_IsDerivedFromClass_Final_(Toolkit::TBoxListener<IBoxListener>, CIdentifier::undefined())
};
/**
* Plugin's description
*/
class CDLLBridgeDesc final : public IBoxAlgorithmDesc
{
public:
CString getName() const override { return CString("DLL Bridge"); }
CString getAuthorName() const override { return CString("Jussi T. Lindgren"); }
CString getAuthorCompanyName() const override { return CString("Inria"); }
CString getShortDescription() const override { return CString("Process a signal or matrix stream with a DLL"); }
CString getDetailedDescription() const override { return CString(""); }
CString getCategory() const override { return CString("Scripting"); }
CString getVersion() const override { return CString("0.2"); }
CString getStockItemName() const override { return CString("gtk-convert"); }
void release() override { }
CIdentifier getCreatedClass() const override { return OVP_ClassId_DLLBridge; }
IPluginObject* create() override { return new CDLLBridge(); }
IBoxListener* createBoxListener() const override { return new CDLLBridgeListener; }
void releaseBoxListener(IBoxListener* listener) const override { delete listener; }
bool getBoxPrototype(Kernel::IBoxProto& prototype) const override
{
prototype.addSetting("DLL file", OV_TypeId_Filename, "");
prototype.addSetting("Parameters", OV_TypeId_String, "");
prototype.addInput("Input",OV_TypeId_Signal);
prototype.addOutput("Output",OV_TypeId_Signal);
prototype.addInputSupport(OV_TypeId_StreamedMatrix);
prototype.addInputSupport(OV_TypeId_Signal);
prototype.addOutputSupport(OV_TypeId_StreamedMatrix);
prototype.addOutputSupport(OV_TypeId_Signal);
prototype.addFlag(Kernel::BoxFlag_CanModifyInput);
prototype.addFlag(Kernel::BoxFlag_CanModifyOutput);
prototype.addFlag(OV_AttributeId_Box_FlagIsUnstable);
return true;
}
_IsDerivedFromClass_Final_(IBoxAlgorithmDesc, OVP_ClassId_DLLBridgeDesc)
};
} // namespace DLLBridge
} // namespace Plugins
} // namespace OpenViBE
@@ -0,0 +1,10 @@
#pragma once
// Boxes
//---------------------------------------------------------------------------------------------------
#define OVP_ClassId_DLLBridge OpenViBE::CIdentifier(0x6F837408, 0x35BA0312)
#define OVP_ClassId_DLLBridgeDesc OpenViBE::CIdentifier(0x2BB7368F, 0x207D6485)
// Global defines
//---------------------------------------------------------------------------------------------------
#define OV_AttributeId_Box_FlagIsUnstable OpenViBE::CIdentifier(0x666FFFFF, 0x666FFFFF)
@@ -0,0 +1,18 @@
#include "ovp_defines.h"
#include "box-algorithms/ovpCBoxAlgorithmDLLBridge.h"
namespace OpenViBE {
namespace Plugins {
namespace DLLBridge {
OVP_Declare_Begin()
context.getTypeManager().registerEnumerationEntry(OV_TypeId_BoxAlgorithmFlag, OV_AttributeId_Box_FlagIsUnstable.toString(),
OV_AttributeId_Box_FlagIsUnstable.id());
OVP_Declare_New(CDLLBridgeDesc);
OVP_Declare_End()
} // namespace DLLBridge
} // namespace Plugins
} // namespace OpenViBE