OpenViBE Documentation
Implementing a Box
This documentation page is deprecated since OpenViBE 0.12.0 (oct 2011), and won't be maintained. The new page can be found here .
  • NB: Document based on OpenViBE 0.8.0 (30-sep-2010).

This section of the tutorial presents a detailed implementation of the OpenViBE box that uses the signal processing algorithm. It is made of two distinct parts : the Box algorithm definition (c++ header file) and the Box algorithm implementation (c++ cpp file).

Box algorithm definition

Here is the file containing the box algorithm definition, we will detail each line of the file later on.

#ifndef __OpenViBEPlugins_BoxAlgorithm_SignalProcessingBoxAlgorithm_H__
#define __OpenViBEPlugins_BoxAlgorithm_SignalProcessingBoxAlgorithm_H__

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

#define OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithm OpenViBE::CIdentifier(0x330E3A87, 0x31565BA6)
#define OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithmDesc OpenViBE::CIdentifier(0x376A4712, 0x1AA65567)

namespace OpenViBEPlugins
{
        namespace Samples
        {
                class CBoxAlgorithmSignalProcessingBoxAlgorithm : 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_SignalProcessingBoxAlgorithm);

                protected:

                        OpenViBE::boolean m_bActive;

                        OpenViBE::Kernel::IAlgorithmProxy* m_pSignalDecoder;
                        OpenViBE::Kernel::TParameterHandler < const OpenViBE::IMemoryBuffer* > ip_pMemoryBufferToDecode;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::uint64 > op_ui64SamplingRate;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pDecodedMatrix;

                        OpenViBE::Kernel::IAlgorithmProxy* m_pSignalProcessingAlgorithm;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pSignalProcessingAlgorithmMatrix;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pSignalProcessingAlgorithmMatrix;

                        OpenViBE::Kernel::IAlgorithmProxy* m_pSignalEncoder;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::uint64 > ip_ui64SamplingRate;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pMatrixToEncode;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMemoryBuffer* > op_pEncodedMemoryBuffer;
                };

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

                        virtual void release(void) { }

                        virtual OpenViBE::CString getName(void) const                { return OpenViBE::CString("Signal processing box algorithm"); }
                        virtual OpenViBE::CString getAuthorName(void) const          { return OpenViBE::CString("Yann Renard"); }
                        virtual OpenViBE::CString getAuthorCompanyName(void) const   { return OpenViBE::CString("INRIA"); }
                        virtual OpenViBE::CString getShortDescription(void) const    { return OpenViBE::CString("This is a sample signal processing box algorithm that uses the sample signal processing algorithm in order to demonstrate how to build a signal processing box algorithm"); }
                        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_BoxAlgorithm_SignalProcessingBoxAlgorithm; }
                        virtual OpenViBE::Plugins::IPluginObject* create(void)       { return new OpenViBEPlugins::Samples::CBoxAlgorithmSignalProcessingBoxAlgorithm; }

                        virtual OpenViBE::boolean getBoxPrototype(
                                OpenViBE::Kernel::IBoxProto& rBoxAlgorithmPrototype) const
                        {
                                rBoxAlgorithmPrototype.addInput  ("Input signal",  OV_TypeId_Signal);
                                rBoxAlgorithmPrototype.addOutput ("Output signal", OV_TypeId_Signal);
                                rBoxAlgorithmPrototype.addSetting("Active", OV_TypeId_Boolean, "true");
                                rBoxAlgorithmPrototype.addFlag   (OpenViBE::Kernel::BoxFlag_IsUnstable);
                                return true;
                        }

                        _IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithmDesc);
                };
        };
};

#endif // __OpenViBEPlugins_BoxAlgorithm_SignalProcessingBoxAlgorithm_H__

First of all, we will include every identifier / define needed for this plugin to work. The OpenViBE toolkit will help in the implementation so we include it also.

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

The next part defines all the unique CIdentifier objects that identify the box and its descriptor. These random identifiers can be generated randomly using the dedicated tool id-generator, provided by the OpenViBE software platform. Please be sure to use unique identifiers.

#define OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithm OpenViBE::CIdentifier(0x330E3A87, 0x31565BA6)
#define OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithmDesc OpenViBE::CIdentifier(0x376A4712, 0x1AA65567)

In order to avoid name collisions, it is desirable to define all classes within namespaces. All standard OpenViBE plugins are defined in sub namespaces of OpenViBEPlugins.

namespace OpenViBEPlugins
{
        namespace Samples
        {

We are now working within the OpenViBEPlugins::Samples namespace. We have to define two classes : the box descriptor and the box algorithm itself. The descriptor is retrieved by the kernel at startup to get general information about the box algorithm. It is this descriptor which allows to create actual instances of this box algorithm.

The interface common to all box algorithms is IBoxAlgorithm. However, box algorithm implementation is made easier thanks to the OpenViBEToolkit::TBoxAlgorithm template, which implements operations common to all box algorithms such as direct access to managers. We therfore inherit from this template, using the IBoxAlgorithm interface as template argument.

                class CBoxAlgorithmSignalProcessingBoxAlgorithm : virtual public OpenViBEToolkit::TBoxAlgorithm < OpenViBE::Plugins::IBoxAlgorithm >
                {

We start by declaring the release method which deletes the box algorithm.

                public:

                        virtual void release(void) { delete this; }

The core of a box algorithm is split into three methods : initialize, uninitialize and process. Additionally, a box algorithm can use several notification callbacks for input reception, clock ticks, message reception...

Its life cycle looks like the following :

  • one initialize call,
  • several notification/process calls
  • one uninitialize call.

Each of these methods returns a boolean reflecting whether it operated successfully. If any of these methods returns false, the kernel will stop using the box-algorithm.

                        virtual OpenViBE::boolean initialize(void);
                        virtual OpenViBE::boolean uninitialize(void);
                        virtual OpenViBE::boolean processInput(OpenViBE::uint32 ui32InputIndex);
                        virtual OpenViBE::boolean process(void);

As with any other OpenViBE class, the box algorithm should be given a class identifier. This is easily done using the _IsDerivedFromClass_Final_ macro.

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

The m_bActive member will be used to activate/deactivate the box from the Designer.

                protected:

                        OpenViBE::boolean m_bActive;

The box implementation heavily relies on algorithms to do its job. This signal processing box algorithm will use three algorithms :

  • the first is responsible for decoding the input stream,
  • the second does the actual signal processing, and
  • the last one encodes the output stream.

Each of these algorithms has input and/or output parameters. In order to facilite access to these parameters, our box algorithm embeds them in parameter handlers.

First, we define the signal decoder and its associated parameter handlers. The signal decoder takes a memory buffer to decode and produces a signal matrix and an unsigned integer holding the sampling rate.

Second, we define the signal processing algorithm and its associated parameter handlers. This is the algorithm that was designed earlier in this tutorial. It takes a signal matrix as input and produces a new signal matrix as output.

                        OpenViBE::Kernel::IAlgorithmProxy* m_pSignalProcessingAlgorithm;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > ip_pSignalProcessingAlgorithmMatrix;
                        OpenViBE::Kernel::TParameterHandler < OpenViBE::IMatrix* > op_pSignalProcessingAlgorithmMatrix;

Lastly, we define the signal encoder and its associated parameter handlers. The signal encoder takes a signal matrix and an unsigned integer for the sampling rate as inputs and produces a memory buffer as output.

We're done with the declaration of the box algorithm, and move on to the descriptor. It provides information about the box algorithm, including its name, author, version and so on. The 'category' it should appear get listed under in the Designer may be specified here as well.

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

                        virtual void release(void) { }

                        virtual OpenViBE::CString getName(void) const                { return OpenViBE::CString("Signal processing box algorithm"); }
                        virtual OpenViBE::CString getAuthorName(void) const          { return OpenViBE::CString("Yann Renard"); }
                        virtual OpenViBE::CString getAuthorCompanyName(void) const   { return OpenViBE::CString("INRIA"); }
                        virtual OpenViBE::CString getShortDescription(void) const    { return OpenViBE::CString("This is a sample signal processing box algorithm that uses the sample signal processing algorithm in order to demonstrate how to build a signal processing box algorithm"); }
                        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"); }

The descriptor also allows to create actual instances of the box algorithm. To that end, it tells the kernel what kind of box it can create and then the create method performs the instanciation.

                        virtual OpenViBE::CIdentifier getCreatedClass(void) const    { return OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithm; }
                        virtual OpenViBE::Plugins::IPluginObject* create(void)       { return new OpenViBEPlugins::Samples::CBoxAlgorithmSignalProcessingBoxAlgorithm; }

The getBoxPrototype method retrieves the prototype of the box, i.e. a description of its inputs and outputs as well as its settings and flags.

  • Inputs and outputs are streams and will have to be decoded / encoded by specific algorithms.
  • Settings offer a way to preconfigure a box before using it.
  • Flags are used to remind the user about some unusual properties of a box, such as its being under development ('unstable' flag) or deprecated.

                        virtual OpenViBE::boolean getBoxPrototype(
                                OpenViBE::Kernel::IBoxProto& rBoxAlgorithmPrototype) const
                        {

Our signal processing box will have one input and one output, both of 'signal' type.

                                rBoxAlgorithmPrototype.addInput  ("Input signal",  OV_TypeId_Signal);
                                rBoxAlgorithmPrototype.addOutput ("Output signal", OV_TypeId_Signal);

Here a single boolean setting is declared and meant to hold the initial activation state.

                                rBoxAlgorithmPrototype.addSetting("Active", OV_TypeId_Boolean, "true");

Since this box is only meant to be used as a testbed, it is safer to flag it as unstable so as not to be mistaken with more robust boxes!

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

Finally, the descriptor returns true, notifying the kernel the box prototype was successfully retrieved.

                                return true;
                        }

Of course, the descriptor also has to be given an identifier using the _IsDerivedFromClass_Final_ macro.

                        _IsDerivedFromClass_Final_(OpenViBE::Plugins::IBoxAlgorithmDesc, OVP_ClassId_BoxAlgorithm_SignalProcessingBoxAlgorithmDesc);
                };
        };
};

Box algorithm implementation

Here is the file containing the box algorithm implementation, we will detail each line of the file later on.

#include "ovpCBoxAlgorithmSignalProcessingBoxAlgorithm.h"

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

using namespace OpenViBEPlugins;
using namespace OpenViBEPlugins::Samples;

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::initialize(void)
{
        IBox& l_rStaticBoxContext=this->getStaticBoxContext();

        CString l_sActive;
        l_rStaticBoxContext.getSettingValue(0, l_sActive);
        m_bActive=this->getConfigurationManager().expandAsBoolean(l_sActive);

        m_pSignalDecoder=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalStreamDecoder));
        m_pSignalDecoder->initialize();
        ip_pMemoryBufferToDecode.initialize(m_pSignalDecoder->getInputParameter(OVP_GD_Algorithm_SignalStreamDecoder_InputParameterId_MemoryBufferToDecode));
        op_ui64SamplingRate.initialize(m_pSignalDecoder->getOutputParameter(OVP_GD_Algorithm_SignalStreamDecoder_OutputParameterId_SamplingRate));
        op_pDecodedMatrix.initialize(m_pSignalDecoder->getOutputParameter(OVP_GD_Algorithm_SignalStreamDecoder_OutputParameterId_Matrix));

        m_pSignalProcessingAlgorithm=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_SignalProcessingAlgorithm));
        m_pSignalProcessingAlgorithm->initialize();
        ip_pSignalProcessingAlgorithmMatrix.initialize(m_pSignalProcessingAlgorithm->getInputParameter(OVP_Algorithm_SignalProcessingAlgorithm_InputParameterId_Matrix));
        op_pSignalProcessingAlgorithmMatrix.initialize(m_pSignalProcessingAlgorithm->getOutputParameter(OVP_Algorithm_SignalProcessingAlgorithm_OutputParameterId_Matrix));

        m_pSignalEncoder=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalStreamEncoder));
        m_pSignalEncoder->initialize();
        ip_ui64SamplingRate.initialize(m_pSignalEncoder->getInputParameter(OVP_GD_Algorithm_SignalStreamEncoder_InputParameterId_SamplingRate));
        ip_pMatrixToEncode.initialize(m_pSignalEncoder->getInputParameter(OVP_GD_Algorithm_SignalStreamEncoder_InputParameterId_Matrix));
        op_pEncodedMemoryBuffer.initialize(m_pSignalEncoder->getOutputParameter(OVP_GD_Algorithm_SignalStreamEncoder_OutputParameterId_EncodedMemoryBuffer));

        ip_ui64SamplingRate.setReferenceTarget(op_ui64SamplingRate);
        ip_pSignalProcessingAlgorithmMatrix.setReferenceTarget(op_pDecodedMatrix);
        ip_pMatrixToEncode.setReferenceTarget(op_pSignalProcessingAlgorithmMatrix);

        return true;
}

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::uninitialize(void)
{
        ip_pMatrixToEncode.uninitialize();
        ip_ui64SamplingRate.uninitialize();
        m_pSignalEncoder->uninitialize();
        this->getAlgorithmManager().releaseAlgorithm(*m_pSignalEncoder);

        op_pSignalProcessingAlgorithmMatrix.uninitialize();
        ip_pSignalProcessingAlgorithmMatrix.uninitialize();
        m_pSignalProcessingAlgorithm->uninitialize();
        this->getAlgorithmManager().releaseAlgorithm(*m_pSignalProcessingAlgorithm);

        op_pDecodedMatrix.uninitialize();
        op_ui64SamplingRate.uninitialize();
        m_pSignalDecoder->uninitialize();
        this->getAlgorithmManager().releaseAlgorithm(*m_pSignalDecoder);

        return true;
}

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

        return true;
}

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::process(void)
{
        IBoxIO& l_rDynamicBoxContext=this->getDynamicBoxContext();

        for(uint32 i=0; i<l_rDynamicBoxContext.getInputChunkCount(0); i++)
        {
                ip_pMemoryBufferToDecode=l_rDynamicBoxContext.getInputChunk(0, i);
                op_pEncodedMemoryBuffer=l_rDynamicBoxContext.getOutputChunk(0);

                m_pSignalDecoder->process();

                if(m_pSignalDecoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalStreamDecoder_OutputTriggerId_ReceivedHeader))
                {
                        m_pSignalProcessingAlgorithm->process(OVP_Algorithm_SignalProcessingAlgorithm_InputTriggerId_Initialize);
                        m_pSignalEncoder->process(OVP_GD_Algorithm_SignalStreamEncoder_InputTriggerId_EncodeHeader);

                        l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
                }
                if(m_pSignalDecoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalStreamDecoder_OutputTriggerId_ReceivedBuffer))
                {
                        m_pSignalProcessingAlgorithm->process(OVP_Algorithm_SignalProcessingAlgorithm_InputTriggerId_Process);

                        if(m_pSignalProcessingAlgorithm->isOutputTriggerActive(OVP_Algorithm_SignalProcessingAlgorithm_OutputTriggerId_ProcessDone))
                        {
                                m_pSignalEncoder->process(OVP_GD_Algorithm_SignalStreamEncoder_InputTriggerId_EncodeBuffer);

                                l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
                        }
                }
                if(m_pSignalDecoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalStreamDecoder_OutputTriggerId_ReceivedEnd))
                {
                        m_pSignalEncoder->process(OVP_GD_Algorithm_SignalStreamEncoder_InputTriggerId_EncodeEnd);

                        l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
                }

                l_rDynamicBoxContext.markInputAsDeprecated(0, i);
        }

        return true;
}

First off, for easier development, it is convenient to declare using some common OpenViBE namespaces so that they don't have to be explicitly typed every time. Again, such statements should never appear in in header files, but are acceptable in implementation files given that OpenViBE does not have conflicting classes among its namespaces.

#include "ovpCBoxAlgorithmSignalProcessingBoxAlgorithm.h"

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

The same can be done for the OpenViBEPlugins project namespaces.

using namespace OpenViBEPlugins;
using namespace OpenViBEPlugins::Samples;

Now we have two simple functions to implement, namely initialize and uninitialize. In our case, they consist in retrieving setting values, creating or releasing the necessary algorithms and connecting or disconnecting our parameter handlers to the actual parameters. These handlers will be used later to ease parameter value manipulation.

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::initialize(void)
{

The static box context allows to programmatically retrieve information about the box prototype, including the number of inputs / outputs / settings and their type.

        IBox& l_rStaticBoxContext=this->getStaticBoxContext();

Here we will use this static box context to retrieve the value of the "Active" setting. Settings are accessed with their 0-based index thanks to the getSettingValue function. The returned string may be parsed by the configuration manager. For example, in this case, we know the setting string is a boolean. So the expandAsBoolean function will return the boolean value corresponding to the setting string.

        CString l_sActive;
        l_rStaticBoxContext.getSettingValue(0, l_sActive);
        m_bActive=this->getConfigurationManager().expandAsBoolean(l_sActive);

Now we use the algorithm manager to create the three algorithms used by this box, then initialize these algorithms, and finally connect them together.

The stream decoder and encoder algorithms are common algorithms owned by another project in the platform. Their identifiers may be found in the ov_global_defines.h file which can be generated thanks to the plugin inspector tool.

Let's start with the signal decoder. It is created using the algorithm manager. Next, we initialize it.

Then we connect the parameter handlers of the box to their corresponding parameters. For example, ip_pMemoryBufferToDecode is the input memory buffer the decoder will work on. This parameter was declared in the decoder algorithm descriptor as being of type OV_TypeId_MemoryBuffer and with identifier OVP_GD_Algorithm_SignalStreamDecoder_InputParameterId_MemoryBufferToDecode. The parameter can be retrieved with the getInputParameter function and given to the handler. Past this point, ip_pMemoryBufferToDecode can be used as an IMemoryBuffer* pointer using the -> operator.

The same is done to initialize the sampling rate output parameter of the box with the output parameter of the decoder. And the decoded matrix parameter handler is connected to the matrix output parameter of the decoder.

        m_pSignalDecoder=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalStreamDecoder));
        m_pSignalDecoder->initialize();
        ip_pMemoryBufferToDecode.initialize(m_pSignalDecoder->getInputParameter(OVP_GD_Algorithm_SignalStreamDecoder_InputParameterId_MemoryBufferToDecode));
        op_ui64SamplingRate.initialize(m_pSignalDecoder->getOutputParameter(OVP_GD_Algorithm_SignalStreamDecoder_OutputParameterId_SamplingRate));
        op_pDecodedMatrix.initialize(m_pSignalDecoder->getOutputParameter(OVP_GD_Algorithm_SignalStreamDecoder_OutputParameterId_Matrix));

Now we create and initialize the signal processing algorithm. This algorithm is defined in this project, so we can directly use the class identifier from the ovp_defines.h file. Then we connect our parameter handlers to their corresponding parameters.

        m_pSignalProcessingAlgorithm=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_ClassId_Algorithm_SignalProcessingAlgorithm));
        m_pSignalProcessingAlgorithm->initialize();
        ip_pSignalProcessingAlgorithmMatrix.initialize(m_pSignalProcessingAlgorithm->getInputParameter(OVP_Algorithm_SignalProcessingAlgorithm_InputParameterId_Matrix));
        op_pSignalProcessingAlgorithmMatrix.initialize(m_pSignalProcessingAlgorithm->getOutputParameter(OVP_Algorithm_SignalProcessingAlgorithm_OutputParameterId_Matrix));

the last algorithm to create and initialize is the signal encoder. As for the decoder, we have to use the class identifier and the parameter identifiers from the ovp_global_defines.h file.

        m_pSignalEncoder=&this->getAlgorithmManager().getAlgorithm(this->getAlgorithmManager().createAlgorithm(OVP_GD_ClassId_Algorithm_SignalStreamEncoder));
        m_pSignalEncoder->initialize();
        ip_ui64SamplingRate.initialize(m_pSignalEncoder->getInputParameter(OVP_GD_Algorithm_SignalStreamEncoder_InputParameterId_SamplingRate));
        ip_pMatrixToEncode.initialize(m_pSignalEncoder->getInputParameter(OVP_GD_Algorithm_SignalStreamEncoder_InputParameterId_Matrix));
        op_pEncodedMemoryBuffer.initialize(m_pSignalEncoder->getOutputParameter(OVP_GD_Algorithm_SignalStreamEncoder_OutputParameterId_EncodedMemoryBuffer));

Now that algorithms and parameter handlers are initialized, we must forward decoded data to the signal processing algorithm, and then forward processed data to the encoding algorithm.

To facilite this, it is convenient to connect parameters together. This minimizes the efforts needed to pass data from one algorithm to the next.

For example, the signal processing algorithm does not change the sampling rate value. This means this parameter can be connected from the decoder straight to the encoder.

However, this is not the true of the decoded matrix, which must go through the signal processing algorithm first. This is where the setReferenceTarget method comes in handy. This method allows to link one parameter to another.

Past the following line, any modification on the value of the sampling rate parameter of the decoder will immediately affect the sampling rate parameter of the encoder.

        ip_ui64SamplingRate.setReferenceTarget(op_ui64SamplingRate);

The following code block also makes use of setReferenceTarget to forward the signal matrix from one algorithm to another, and then to a third one.

The first statement links the decoder output matrix to the signal processing algorithm input matrix. The second statement links the processed matrix to the encoder input matrix. All parameters used here will actually share the same object value.

        ip_pSignalProcessingAlgorithmMatrix.setReferenceTarget(op_pDecodedMatrix);
        ip_pMatrixToEncode.setReferenceTarget(op_pSignalProcessingAlgorithmMatrix);

Again, returning true tells the kernel the box was correctly initialized. Returning false would have the kernel assume this box failed to initialize and it would stop calling it in the future.

        return true;
}

Uninitialization notifies the box algorithm that it won't be used anymore. Thus every initialized member should be freed and the whole environment should be left as it was before initialize was called.

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::uninitialize(void)
{

To release an algorithm, three steps have to be followed :

  • disconnect parameter handlers from their embedded parameter by calling uninitialize.
  • call uninitialize on the algorithm itself
  • have the algorithm manager release the algorithm

In the following code block, ip_pMatrixToEncode is disconnected from its parameter thanks to the uninitialize method. Past this point, using operator -> will throw an exception and cause a crash. The same is done on each of the parameter handlers of the signal encoder. Then the signal encoder itself can be uninitialized and a request can be sent to the algorithm manager to release this algorithm that won't be used anymore.

        ip_pMatrixToEncode.uninitialize();
        ip_ui64SamplingRate.uninitialize();
        m_pSignalEncoder->uninitialize();
        this->getAlgorithmManager().releaseAlgorithm(*m_pSignalEncoder);

The signal processing algorithm is released next.

        op_pSignalProcessingAlgorithmMatrix.uninitialize();
        ip_pSignalProcessingAlgorithmMatrix.uninitialize();
        m_pSignalProcessingAlgorithm->uninitialize();
        this->getAlgorithmManager().releaseAlgorithm(*m_pSignalProcessingAlgorithm);

And finally, the signal decoder is released.

        op_pDecodedMatrix.uninitialize();
        op_ui64SamplingRate.uninitialize();
        m_pSignalDecoder->uninitialize();
        this->getAlgorithmManager().releaseAlgorithm(*m_pSignalDecoder);

Again, this function should return true to notify the kernel everything went on fine.

        return true;
}

All that is left to code now concerns event notification and data processing.

Let's start with notifications. A box can ask to be notified of different types of events, such as message arrival or clock ticks. This particular box only cares about input data arrival, an event upon which the box will trigger a data processing procedure. This is why a single event handler (processInput) is implemented here.

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::processInput(uint32 ui32InputIndex)
{

Entering this method means there is pending input data. In this simple example, the box is ready to process such data as soon as it arrives. The following statement marks the box as candidate for such processing, which will lead to its process method to be called. on the input.

        this->getBoxAlgorithmContext()->markAlgorithmAsReadyToProcess();

        return true;
}

On to the heart of the box implementation : the processing part. This is where input data chunks are retrieved in order to be decoded, processed and encoded before being forwarded to the next box as output chunks.

boolean CBoxAlgorithmSignalProcessingBoxAlgorithm::process(void)
{

The dynamic box context contains box communication information. Part of such information is made up of pending input chunks. Keeping a reference on this context allows to access directly input and output chunks.

        IBoxIO& l_rDynamicBoxContext=this->getDynamicBoxContext();

Notifying the kernel that a box algorithm is ready to process input data does not necessarily trigger an immediate process call. Therefore, multiple input chunks may be pending when process gets called eventually. This is why one should always iterate on input chunks to be sure none is left unprocessed.

Since our box has only one input, a single loop iterates over chunks of this input (index 0).

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

Each chunk retrieved from this input is going to be decoded, processed and encoded again.

Here, parameter handlers initialized earlier are told where to fetch input data and where to store output data. The getInputChunk method retreives input chunks from a given input (first argument) at a given index (second argument). Similarly, getOutputChunk retrieves output chunks from a given output at a given index.

                ip_pMemoryBufferToDecode=l_rDynamicBoxContext.getInputChunk(0, i);
                op_pEncodedMemoryBuffer=l_rDynamicBoxContext.getOutputChunk(0);

At this point, the box is ready to start processing the chunk. First, we have the decoder decode it.

                m_pSignalDecoder->process();

The decoder has several output triggers telling us what was just decoded. There are three chunk categories :

  • headers (received once per input)
  • buffers (received an undeterminate number of times, depending on how much data there is to process)
  • end nodes (received once per input).

Depending on what was decoded, different actions will be undertaken.

                if(m_pSignalDecoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalStreamDecoder_OutputTriggerId_ReceivedHeader))
                {

In case a header was received, we simply request the initialization of the signal processing algorithm by activating its initialize trigger. When its process method is called next, this trigger activation status evaluates to true, causing the algorithm to initialize its output matrix.

                        m_pSignalProcessingAlgorithm->process(OVP_Algorithm_SignalProcessingAlgorithm_InputTriggerId_Initialize);

At this point, the header part of the output stream can be encoded.

                        m_pSignalEncoder->process(OVP_GD_Algorithm_SignalStreamEncoder_InputTriggerId_EncodeHeader);

Lastly, the output chunk can be marked as ready to be sent. This will let the kernel send it to the boxes that are connected to this output. One thing that we did not notice is that each stream buffer corresponds to a given time period. This time period is retrieved thanks to the getInputChunkStartTime and getInputChunkEndTime functions of the dynamic context. Similarly, when marking an output chunk as ready to send, the box must specify the time period that this chunk corresponds to.

                        l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
                }

In case a buffer is received, we can request the signal processing algorithm to process it. This is where the first sample of each channel is reset to 0, while any other sample is left untouched.

                if(m_pSignalDecoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalStreamDecoder_OutputTriggerId_ReceivedBuffer))
                {
                        m_pSignalProcessingAlgorithm->process(OVP_Algorithm_SignalProcessingAlgorithm_InputTriggerId_Process);

Depending on the outcome of the actual processing, we may be ready to request a buffer encoding. The processing status is checked using the "process done" output trigger : when set to true, a buffer was successfully processed.

                        if(m_pSignalProcessingAlgorithm->isOutputTriggerActive(OVP_Algorithm_SignalProcessingAlgorithm_OutputTriggerId_ProcessDone))
                        {

It is now time to encode the processed matrix.

                                m_pSignalEncoder->process(OVP_GD_Algorithm_SignalStreamEncoder_InputTriggerId_EncodeBuffer);

As was done for the header chunk, this new output chunk is flagged as ready to send with the corresponding time period.

                                l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
                        }
                }

Lastly, in case the decoder decoded an end node, we do not have to process anything. This just means that the input stream is closed and that no more buffers will be received from it. A new header would have to be sent first for new buffers to be received again.

Here, we just have to notify the following boxes that an end node was decoded. This is done by encoding and sending such a node.

                if(m_pSignalDecoder->isOutputTriggerActive(OVP_GD_Algorithm_SignalStreamDecoder_OutputTriggerId_ReceivedEnd))
                {
                        m_pSignalEncoder->process(OVP_GD_Algorithm_SignalStreamEncoder_InputTriggerId_EncodeEnd);

As for header and buffer chunks, we mark this new output chunk as ready to send with the corresponding time period.

                        l_rDynamicBoxContext.markOutputAsReadyToSend(0, l_rDynamicBoxContext.getInputChunkStartTime(0, i), l_rDynamicBoxContext.getInputChunkEndTime(0, i));
                }

Finally, since the input chunk was processed, we can notify the kernel that it can now be released. The chunk object won't be effectively released until the process function returns. This call can rather be seen as 'flagging' the chunk for deletion.

                l_rDynamicBoxContext.markInputAsDeprecated(0, i);
        }

        return true;
}

Conclusion

Now that both plugins are created, we have to register them to the kernel at plugin loading time. For this reason, in ovp_main.cpp we use the OVP_Declare_New macro as follows :

 OVP_Declare_New(OpenViBEPlugins::Samples::CAlgorithmSignalProcessingAlgorithmDesc);
 OVP_Declare_New(OpenViBEPlugins::Samples::CBoxAlgorithmSignalProcessingBoxAlgorithmDesc);