Tutorial 2: Implement algorithm and use it in boxes

  • NB: Document updated for OpenViBE 1.0.0 (05.Jun.2015).

Introduction

This tutorial will cover the implementation of Algorithms, the generic processing objects that can be embedded in boxes.

The objectives are:

  • Allowing more than one boxes to use the same processing based on a generic component
  • Simplify the code of boxes by dividing the complexity between algorithms and box

We will implement a matrix processing algorithm, that computes the maximum vector of a 2-dimension matrix.The i-th element of the vector will be the maximum of all the i-th elements of the matrix lines.

Then this simple algorithm will be embedded in a data-driven box that want to do this computation on a signal input to output the maximum channel.

Box and Algorithm will be placed in the Signal Processing project, under contrib/plugins/processing/signal-processing/src.

Design

As usual, it’s better to think about the design we want to achieve before going into the code directly. We will need an algorithm with:

  • 1 input : the matrix with 2 dimensions (we will need to check that)
  • 1 output : the matrix with only one vector
  • 2 input triggers : “Initialize” and “Process”
  • 1 output trigger : “Process Done”

The Box will be very simple as box implementation has been covered in previous tutorial.

  • 1 input : signal
  • 1 output : signal
  • No setting

The Matrix Maximum Algorithm

The algorithm is made of 2 files, the header and the implementation.

The skeleton-generator is not currently capable of creating algorithm skeletons.

As usual, you will need to register the algorithm in the ovp_main.cpp file to be able to use it elsewhere.

Define the CIdentifiers

The Algorithm and its inputs, outputs and triggers are all identified by a unique CIdentifier. The developer tool id-generator is capable of generating random CIdentifier for you to copy-paste in your code.

To use an algorithm, the box just needs to know its identifiers. That’s all ! You will do everything using the AlgorithmProxy or ParameterHandler generic classes, and you don’t need the algorithm specification.

We will need 7 identifiers for:

  • Algorithm
  • Algorithm Descriptor
  • Matrix Input
  • Matrix Output
  • “Initialize” input trigger
  • “Process” input trigger
  • “Process Done” output trigger

If we want to be able to use the algorithm in any other openvibe boxes (in any plugin subproject) easily, we can declare all the identifiers in the global define file of the project ovp_defines.h.

Doing so can simplify the implementation of boxes relying on algorithms, as they just need to include the ovp_defines.h file in order to be able to use any algorithm whose identifiers are declared in it.

Of course, declaring the CIdentifiers directly in the Algorithm header can also be done. But every box using your algorithm will have to include the algorithm header. Your choice…

Here is the CIdentifier used by our algorithm; we defined them in ovp_defines.h:

...

#define OVP_ClassId_Algorithm_MatrixMaximum                     OpenViBE::CIdentifier(0x3B7723EC, 0x16C30A39)
#define OVP_ClassId_Algorithm_MatrixMaximumDesc                 OpenViBE::CIdentifier(0x11BE2168, 0x5B444BBB)
#define OVP_Algorithm_MatrixMaximum_InputParameterId_Matrix     OpenViBE::CIdentifier(0x56254223, 0x42180588)
#define OVP_Algorithm_MatrixMaximum_OutputParameterId_Matrix    OpenViBE::CIdentifier(0x025A4450, 0x6DFD17DB)
#define OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize   OpenViBE::CIdentifier(0x41803B07, 0x667A69BC)
#define OVP_Algorithm_MatrixMaximum_InputTriggerId_Process      OpenViBE::CIdentifier(0x641A59C0, 0x12FB7F74)
#define OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone OpenViBE::CIdentifier(0x37802521, 0x785D51FD)

...

Header

 

 File: ovpCAlgorithmMatrixMaximum.h(click here to show content)
 File: ovpCAlgorithmMatrixMaximum.h(click here to hide content)

#ifndef __OpenViBEPlugins_Algorithm_MatrixMaximum_H__
#define __OpenViBEPlugins_Algorithm_MatrixMaximum_H__

#include "ovp_defines.h"
//#include <openvibe/ov_all.h>
#include <openvibe-toolkit/ovtk_all.h>

namespace OpenViBEPlugins
{
        namespace SignalProcessing
        {
                class CAlgorithmMatrixMaximum : public OpenViBEToolkit::TAlgorithm < OpenViBE::Plugins::IAlgorithm >
                {
                public:

                        virtual void release(void) { delete this; }

                        virtual OpenViBE::boolean initialize(void);
                        virtual OpenViBE::boolean uninitialize(void);
                        virtual OpenViBE::boolean process(void);

                        _IsDerivedFromClass_Final_(OpenViBEToolkit::TAlgorithm < OpenViBE::Plugins::IAlgorithm >, OVP_ClassId_Algorithm_MatrixMaximum);

                protected:

                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pMatrix; // input matrix
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pMatrix; // output matrix
                };

                class CAlgorithmMatrixMaximumDesc : public OpenViBE::Plugins::IAlgorithmDesc
                {
                public:

                        virtual void release(void) { }

                        virtual OpenViBE::CString getName(void) const                { return OpenViBE::CString("Signal maximum algorithm"); }
                        virtual OpenViBE::CString getAuthorName(void) const          { return OpenViBE::CString("Laurent Bonnet"); }
                        virtual OpenViBE::CString getAuthorCompanyName(void) const   { return OpenViBE::CString("INRIA"); }
                        virtual OpenViBE::CString getShortDescription(void) const    { return OpenViBE::CString("Computes the maximum vector of a 2-dimension matrix"); }
                        virtual OpenViBE::CString getDetailedDescription(void) const { return OpenViBE::CString(""); }
                        virtual OpenViBE::CString getCategory(void) const            { return OpenViBE::CString("Samples"); }
                        virtual OpenViBE::CString getVersion(void) const             { return OpenViBE::CString("1.0"); }
                        virtual OpenViBE::CString getStockItemName(void) const       { return OpenViBE::CString("gtk-execute"); }

                        virtual OpenViBE::CIdentifier getCreatedClass(void) const    { return OVP_ClassId_Algorithm_MatrixMaximum; }
                        virtual OpenViBE::Plugins::IPluginObject* create(void)       { return new OpenViBEPlugins::SignalProcessing::CAlgorithmMatrixMaximum; }

                        virtual OpenViBE::boolean getAlgorithmPrototype(
                                OpenViBE::Kernel::IAlgorithmProto& rAlgorithmPrototype) const
                        {
                                rAlgorithmPrototype.addInputParameter (OVP_Algorithm_MatrixMaximum_InputParameterId_Matrix,     "Matrix", OpenViBE::Kernel::ParameterType_Matrix);
                                rAlgorithmPrototype.addOutputParameter(OVP_Algorithm_MatrixMaximum_OutputParameterId_Matrix,    "Matrix", OpenViBE::Kernel::ParameterType_Matrix);

                                rAlgorithmPrototype.addInputTrigger   (OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize,   "Initialize");
                                rAlgorithmPrototype.addInputTrigger   (OVP_Algorithm_MatrixMaximum_InputTriggerId_Process,      "Process");
                                rAlgorithmPrototype.addOutputTrigger  (OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone, "Process done");

                                return true;
                        }

                        _IsDerivedFromClass_Final_(OpenViBE::Plugins::IAlgorithmDesc, OVP_ClassId_Algorithm_MatrixMaximumDesc);
                };
        };
};

#endif // __OpenViBEPlugins_Algorithm_MatrixMaximum_H__

We firstly include everything we need :

  • ovp_defines.h: it’s where our identifiers are defined
  • ovtk_all.h: to be able to use algorithm proxy and other utility classes

The header declares 2 classes:

  • CAlgorithmMatrixMaximum: the algorithm, derived from the toolkit template OpenViBEToolkit::TAlgorithm<OpenViBE::Plugins::IAlgorithm>
  • CAlgorithmMatrixMaximumDesc: the algorithm descriptor used by the kernel to the algorithm structure

Like in any other algorithm, 3 functions must be implemented:

  • boolean initialize(void): to initialize the input and output parameters
  • boolean uninitialize(void): to cleanup the i/o parameters
  • boolean process(void): where the processing is actually performed and output triggers produced, according to input triggers

We need 2 parameters:

  • OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pMatrix : the NxM input matrix
  • OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pMatrix : the output matrix with the maximum vector

The Algorithm Descriptor is relatively close to a Box Descriptor. You need to provide some generic information (author, category, description, etc.) along with the algorithm prototype in getAlgorithmPrototype.

Note that when adding new input and output to your algorithm, you must provide the corresponding unique identifier along with name and type:

rAlgorithmPrototype.addInputParameter (OVP_Algorithm_MatrixMaximum_InputParameterId_Matrix,     "Matrix",
                                       OpenViBE::Kernel::ParameterType_Matrix);
rAlgorithmPrototype.addOutputParameter(OVP_Algorithm_MatrixMaximum_OutputParameterId_Matrix,    "Matrix",
                                       OpenViBE::Kernel::ParameterType_Matrix);

rAlgorithmPrototype.addInputTrigger   (OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize,   "Initialize");
rAlgorithmPrototype.addInputTrigger   (OVP_Algorithm_MatrixMaximum_InputTriggerId_Process,      "Process");
rAlgorithmPrototype.addOutputTrigger  (OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone, "Process done");

Implementation

 

 File: ovpCAlgorithmMatrixMaximum.cpp(click here to show content)
 File: ovpCAlgorithmMatrixMaximum.cpp(click here to hide content)

#include "ovpCAlgorithmMatrixMaximum.h"

using namespace OpenViBE;
using namespace OpenViBE::Kernel;
using namespace OpenViBE::Plugins;

using namespace OpenViBEPlugins;
using namespace OpenViBEPlugins::SignalProcessing;

boolean CAlgorithmMatrixMaximum::initialize(void)
{
    ip_pMatrix.initialize(this->getInputParameter(OVP_Algorithm_MatrixMaximum_InputParameterId_Matrix));
    op_pMatrix.initialize(this->getOutputParameter(OVP_Algorithm_MatrixMaximum_OutputParameterId_Matrix));
    return true;
}

boolean CAlgorithmMatrixMaximum::uninitialize(void)
{
    op_pMatrix.uninitialize();
    ip_pMatrix.uninitialize();
    return true;
}

boolean CAlgorithmMatrixMaximum::process(void)
{

    if(this->isInputTriggerActive(OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize))
    {
        if( ip_pMatrix->getDimensionCount() != 2)
        {
            this->getLogManager() << LogLevel_Error << "The input matrix must have 2 dimensions";
            return false;
        }
        op_pMatrix->setDimensionCount(2);
        op_pMatrix->setDimensionSize(0,1); // only one - maximum - vector
        op_pMatrix->setDimensionSize(1,ip_pMatrix->getDimensionSize(1)); // same number of elements in the vector
    }

    if(this->isInputTriggerActive(OVP_Algorithm_MatrixMaximum_InputTriggerId_Process))
    {
        // we iterate over the columns (second dimension)
        for(uint32 i=0; i<ip_pMatrix->getDimensionSize(1); i++)
        {
            float64 l_f64Maximum = ip_pMatrix->getBuffer()[i];
            // and try to find the maximum among the values on column i
            for(uint32 j=1; j<ip_pMatrix->getDimensionSize(0); j++)
            {
                if(l_f64Maximum < ip_pMatrix->getBuffer()[i+j*ip_pMatrix->getDimensionSize(1)])
                {
                    l_f64Maximum = ip_pMatrix->getBuffer()[i+j*ip_pMatrix->getDimensionSize(1)];
                }
            }
            op_pMatrix->getBuffer()[i]= l_f64Maximum;
        }
        this->activateOutputTrigger(OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone, true);
    }

    return true;
}

Initialize

We simply initialize the matrix parameters to handle the input and output of the algorithm, using the corresponding identifiers:

ip_pMatrix.initialize(this->getInputParameter(OVP_Algorithm_MatrixMaximum_InputParameterId_Matrix));
op_pMatrix.initialize(this->getOutputParameter(OVP_Algorithm_MatrixMaximum_OutputParameterId_Matrix));

Uninitialize

Uninitializing the parameters is done easily using:

op_pMatrix.uninitialize();
ip_pMatrix.uninitialize();

Process

The process function is basically a switch between different behaviors, according to the input triggers.The triggers can be checked using the isInputTriggerActive(trigger_id) function.

In the case of input trigger “Initialize”, the goal is to verify that the input matrices will have 2 dimensions (NxM) and to prepare the output matrix structure (2 dimensions, 1xM):

    if(this->isInputTriggerActive(OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize))
    {
        if( ip_pMatrix->getDimensionCount() != 2)
        {
            // we return false so the box which called process knows something went wrong
            this->getLogManager() << LogLevel_Error << "The input matrix must have 2 dimensions\n";
            return false;
        }
        op_pMatrix->setDimensionCount(2);
        op_pMatrix->setDimensionSize(0,1); // only one - maximum - vector
        op_pMatrix->setDimensionSize(1,ip_pMatrix->getDimensionSize(1)); // same number of elements in the vector
    }

If a “Process” has been requested, we can compute the maximum vector over the incoming matrix. When the process is finally done, we can activate the corresponding output trigger:

    if(this->isInputTriggerActive(OVP_Algorithm_MatrixMaximum_InputTriggerId_Process))
    {
        // we iterate over the columns (second dimension)
        for(uint32 i=0; i<ip_pMatrix->getDimensionSize(1); i++)
        {
            float64 l_f64Maximum = ip_pMatrix->getBuffer()[i];
            // and try to find the maximum among the values on column i
            for(uint32 j=1; j<ip_pMatrix->getDimensionSize(0); j++)
            {
                if(l_f64Maximum < ip_pMatrix->getBuffer()[i+j*ip_pMatrix->getDimensionSize(1)])
                {
                    l_f64Maximum = ip_pMatrix->getBuffer()[i+j*ip_pMatrix->getDimensionSize(1)];
                }
            }
            op_pMatrix->getBuffer()[i]= l_f64Maximum;
        }
        this->activateOutputTrigger(OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone, true);
    }

The Signal Maximum Box

As we already covered in a previous tutorial the implementation of a box, we will get straight to the interesting point.

Header

 

 File: ovpCBoxAlgorithmSignalMaximum.h(click here to show content)
 File: ovpCBoxAlgorithmSignalMaximum.h(click here to hide content)

#ifndef __OpenViBEPlugins_BoxAlgorithm_SignalMaximum_H__
#define __OpenViBEPlugins_BoxAlgorithm_SignalMaximum_H__

#include "ovp_defines.h"

#include <ov_all.h>
#include <toolkit/ovtk_all.h>

#define OVP_ClassId_BoxAlgorithm_SignalMaximum OpenViBE::CIdentifier(0x6A12346A, 0x6758940)
#define OVP_ClassId_BoxAlgorithm_SignalMaximumDesc OpenViBE::CIdentifier(0x6AAEAB12, 0x67124560)

namespace OpenViBEPlugins
{
    namespace SignalProcessing
    {
        class CBoxAlgorithmSignalMaximum : virtual public OpenViBEToolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >
        {
        public:
            virtual void release(void) { delete this; }

            virtual OpenViBE::boolean initialize(void);
            virtual OpenViBE::boolean uninitialize(void);

            virtual OpenViBE::boolean processInput(OpenViBE::uint32 ui32InputIndex);

            virtual OpenViBE::boolean process(void);

            _IsDerivedFromClass_Final_(OpenViBEToolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >, OVP_ClassId_BoxAlgorithm_SignalMaximum);

        protected:
            OpenViBEToolkit::TSignalDecoder < CBoxAlgorithmSignalMaximum > m_oSignalDecoder;

            OpenViBE::Kernel::IAlgorithmProxy* m_pMatrixMaximumAlgorithm;
            OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pMatrixMaximumAlgorithm_Matrix;
            OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pMatrixMaximumAlgorithm_Matrix;

            OpenViBEToolkit::TSignalEncoder < CBoxAlgorithmSignalMaximum > m_oSignalEncoder;
        };

        class CBoxAlgorithmSignalMaximumDesc : virtual public OpenViBE::Plugins::IBoxAlgorithmDesc
        {
        public:

            virtual void release(void) { }

            virtual OpenViBE::CString getName(void) const                { return OpenViBE::CString("Signal Maximum"); }
            virtual OpenViBE::CString getAuthorName(void) const          { return OpenViBE::CString("Laurent Bonnet"); }
            virtual OpenViBE::CString getAuthorCompanyName(void) const   { return OpenViBE::CString("INRIA"); }
            virtual OpenViBE::CString getShortDescription(void) const    { return OpenViBE::CString("Max channel !"); }
            virtual OpenViBE::CString getDetailedDescription(void) const { return OpenViBE::CString("Output a one channel signal corresponding to the maximum of each input channel for each sample index."); }
            virtual OpenViBE::CString getCategory(void) const            { return OpenViBE::CString("Signal processing"); }
            virtual OpenViBE::CString getVersion(void) const             { return OpenViBE::CString("1.0"); }
            virtual OpenViBE::CString getStockItemName(void) const       { return OpenViBE::CString("gtk-add"); }

            virtual OpenViBE::CIdentifier getCreatedClass(void) const    { return OVP_ClassId_BoxAlgorithm_SignalMaximum; }
            virtual OpenViBE::Plugins::IPluginObject* create(void)       { return new OpenViBEPlugins::SignalProcessing::CBoxAlgorithmSignalMaximum; }

            virtual OpenViBE::boolean getBoxPrototype(
                OpenViBE::Kernel::IBoxProto& rBoxAlgorithmPrototype) const
            {
                rBoxAlgorithmPrototype.addInput("Signal",OV_TypeId_Signal);

                rBoxAlgorithmPrototype.addOutput("Processed Signal",OV_TypeId_Signal);

                rBoxAlgorithmPrototype.addFlag(OpenViBE::Kernel::BoxFlag_IsUnstable);

                return true;
            }
            _IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalMaximumDesc);
        };
    };
};

#endif // __OpenViBEPlugins_BoxAlgorithm_SignalMaximum_H__

The interesting point here is the cohabitation of the algorithms provided by the codec toolkit and our new signal processing algorithm:

OpenViBEToolkit::TSignalDecoder < CBoxAlgorithmSignalMaximum > m_oSignalDecoder;

OpenViBE::Kernel::IAlgorithmProxy* m_pMatrixMaximumAlgorithm;
OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pMatrixMaximumAlgorithm_Matrix;
OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pMatrixMaximumAlgorithm_Matrix;

OpenViBEToolkit::TSignalEncoder < CBoxAlgorithmSignalMaximum > m_oSignalEncoder;

Without using the codec toolkit, this is what your encoders and decoders would also look like : an algorithm proxy, and the input and outputs parameter handlers.

Implementation

 

 File: ovpCBoxAlgorithmSignalMaximum.cpp(click here to show content)
 File: ovpCBoxAlgorithmSignalMaximum.cpp(click here to hide content)

#include "ovpCBoxAlgorithmSignalMaximum.h"

using namespace OpenViBE;
using namespace OpenViBE::Kernel;
using namespace OpenViBE::Plugins;

using namespace OpenViBEPlugins;
using namespace OpenViBEPlugins::SignalProcessing;

boolean CBoxAlgorithmSignalMaximum::initialize(void)
{
    m_oSignalDecoder.initialize(*this);

    m_pMatrixMaximumAlgorithm=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_MatrixMaximum));
    m_pMatrixMaximumAlgorithm->initialize();
    ip_pMatrixMaximumAlgorithm_Matrix.initialize(m_pMatrixMaximumAlgorithm->getInputParameter(OVP_Algorithm_MatrixMaximum_InputParameterId_Matrix));
    op_pMatrixMaximumAlgorithm_Matrix.initialize(m_pMatrixMaximumAlgorithm->getOutputParameter(OVP_Algorithm_MatrixMaximum_OutputParameterId_Matrix));

    // Connect the encoder to the output 0
    m_oSignalEncoder.initialize(*this, 0);

    // we connect the algorithms
    // the Signal Maximum algorithm will take the matrix coming from the signal decoder:
    ip_pMatrixMaximumAlgorithm_Matrix.setReferenceTarget(m_oSignalDecoder.getOutputMatrix());
    // The Signal Encoder will take the sampling rate from the Signal Decoder:
    m_oSignalEncoder.getInputSamplingRate().setReferenceTarget(m_oSignalDecoder.getOutputSamplingRate());
    // And the matrix from the Signal Maximum algorithm:
    m_oSignalEncoder.getInputMatrix().setReferenceTarget(op_pMatrixMaximumAlgorithm_Matrix);

    return true;
}
/*******************************************************************************/

boolean CBoxAlgorithmSignalMaximum::uninitialize(void)
{
    m_oSignalDecoder.uninitialize();

    op_pMatrixMaximumAlgorithm_Matrix.uninitialize();
    ip_pMatrixMaximumAlgorithm_Matrix.uninitialize();
    m_pMatrixMaximumAlgorithm->uninitialize();
    this->getAlgorithmManager().releaseAlgorithm(*m_pMatrixMaximumAlgorithm);

    m_oSignalEncoder.uninitialize();

    return true;
}
/*******************************************************************************/

boolean CBoxAlgorithmSignalMaximum::processInput(uint32 ui32InputIndex)
{
    getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();

    return true;
}
/*******************************************************************************/

boolean CBoxAlgorithmSignalMaximum::process(void)
{

    IBoxIO& l_rDynamicBoxContext=this->getDynamicBoxContext();

    //we decode the input signal chunks
    for(uint32 i=0; i<l_rDynamicBoxContext.getInputChunkCount(0); i++)
    {
        m_oSignalDecoder.decode(i);
        if(m_oSignalDecoder.isHeaderReceived())
        {
            if(!m_pMatrixMaximumAlgorithm->process(OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize)) return false;

            m_oSignalEncoder.encodeHeader();
            l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
        }
        if(m_oSignalDecoder.isBufferReceived())
        {
            // we process the signal matrice with our algorithm
            m_pMatrixMaximumAlgorithm->process(OVP_Algorithm_MatrixMaximum_InputTriggerId_Process);

            // If the process is done successfully, we can encode the buffer
            if(m_pMatrixMaximumAlgorithm->isOutputTriggerActive(OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone))
            {
                m_oSignalEncoder.encodeBuffer();
                l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
            }
        }
        if(m_oSignalDecoder.isEndReceived())
        {
            m_oSignalEncoder.encodeEnd();
            l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
        }
    }

    return true;
}

Initialize

We need to initiliaze the codecs as usual, and the algorithm proxy and parameter handlers of the signal processing algorithm.

The interesting point here is the connection between algorithm through setReferenceTarget calls:

// we connect the algorithms
// the Signal Maximum algorithm will take the matrix coming from the signal decoder:
ip_pMatrixMaximumAlgorithm_Matrix.setReferenceTarget(m_oSignalDecoder.getOutputMatrix());
// The Signal Encoder will take the sampling rate from the Signal Decoder:
m_oSignalEncoder.getInputSamplingRate().setReferenceTarget(m_oSignalDecoder.getOutputSamplingRate());
// And the matrix from the Signal Maximum algorithm:
m_oSignalEncoder.getInputMatrix().setReferenceTarget(op_pMatrixMaximumAlgorithm_Matrix);

As you can see codec toolkit and old-school algorithm are totally compatible, as they manipulate the same generic objects : algorithm proxy and parameter handlers.

After this moment the algorithms are linked together, thus when decoding a new chunk the outputed sampling frequency will be directly set as encoder input, and the matrix given to the Signal Maximum algorithm.

Uninitialize

We free the parameter handlers and algorithms as usual.

m_oSignalDecoder.uninitialize(); 

op_pMatrixMaximumAlgorithm_Matrix.uninitialize();
ip_pMatrixMaximumAlgorithm_Matrix.uninitialize();
m_pMatrixMaximumAlgorithm->uninitialize();
this->getAlgorithmManager().releaseAlgorithm(*m_pMatrixMaximumAlgorithm);

m_oSignalEncoder.uninitialize();

Process

We iterate over all signal input chunks, and decode it:

    for(uint32 i=0; i<l_rDynamicBoxContext.getInputChunkCount(0); i++)
    {
        m_oSignalDecoder.decode(i);

As usual 3 cases are possible : Header, Buffer or End of stream. In case of header, we need to trigger the initialization of the algorithm. This process will initialize the input matrix of the signal encoder, as we set its reference target to the algorithm output matrix. If the algorithm process returned in an erroneous state, we must deactivate the box by returning false in the box process.

        if(m_oSignalDecoder.isHeaderReceived())
        {
            ip_pMatrixMaximumAlgorithm_Matrix->setDimensionCount(3);
            if(!m_pMatrixMaximumAlgorithm->process(OVP_Algorithm_MatrixMaximum_InputTriggerId_Initialize))
            {
                return false;
            }
            m_oSignalEncoder.encodeHeader();
            l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i),
                                                            l_rDynamicBoxContext.getInputChunkEndTime(0, i));
        }

If a buffer is received, we simply trigger the “Process” of the algorithm, and check if the output trigger “Process Done” is raised:

        if(m_oSignalDecoder.isBufferReceived())
        {
            // we process the signal matrice with our algorithm
            m_pMatrixMaximumAlgorithm->process(OVP_Algorithm_MatrixMaximum_InputTriggerId_Process);

            // If the process is done successfully, we can encode the buffer
            if(m_pMatrixMaximumAlgorithm->isOutputTriggerActive(OVP_Algorithm_MatrixMaximum_OutputTriggerId_ProcessDone))
            {
                m_oSignalEncoder.encodeBuffer();
                l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i),
                                                                l_rDynamicBoxContext.getInputChunkEndTime(0, i));
            }
        }

Finally if an end is received we forward that information to the output:

        if(m_oSignalDecoder.isEndReceived())
        {
            m_oSignalEncoder.encodeEnd();
            l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i),
                                                            l_rDynamicBoxContext.getInputChunkEndTime(0, i));
        }
    }

Test the Box

We test this box with the following scenario:

Testing the Signal Maximum Box

We produce a 4-channel signal with the Sinus Oscillator, a 1-channel linear signal with the Time Signal. The Signal Merger box will merge the two inputs to produce a 5-channel signal (last channel is the time signal). To do this job properly, the 2 sampling boxes must produce a signal with the same sampling frequency and number of samples per buffer.

The Signal Maximum is computed and displayed as shown below. Once the time signal is above the maximum amplitude of the generated sinusoids, its samples are naturally chosen as the maximum.

Running the scenario

Conclusion

This advanced tutorial covered the implementation of algorithms and how to use them in boxes.

The algorithm mechanism contributes to the high modularity of OpenViBE. It’s also a powerful tool to lower the complexity of a box. For example, the Classifier Trainer box is able to use different classification methods (e.g. SVM, LDA). These methods are implemented in dedicated algorithms, and the trainer box manipulates these algorithms, without knowing what’s inside them. Thus the box is relatively simple. And it’s quiet easy to add more classification algorithms…

If any questions remain, please contact us on the regular channels to get help from experienced programmers!

This entry was posted in Box plugins and tagged , , . Bookmark the permalink.