Table of Contents

Adding new module

If you don't want to read all the text, just skip to the final code

Tutorial

This section will guide you through the (very simple) process of adding a new module to libyuri. We will add a module called pass that will simply pass frames from it's output to the output without any processing.

The tutorial assumes compiling the module in the source tree. All paths specified bellow are relative to the root of the sources (usually directory called yuri-light). Compiling outside of the tree is possible, but not covered here.

Prepare the build environment

  • Make sure you have the sources and can compile them successfully.
  • Create a directory call pass in src/modules
  • Add file called CMakeLists.txt to the newly created directory. Add definition of the new module there. It should look like:
CMakeLists.txt
# Set name of the module
SET (MODULE pass)
 
# Set all source files module uses
SET (SRC 
	Pass.cpp
	Pass.h)
 
add_library(${MODULE} MODULE ${SRC})
target_link_libraries(${MODULE} ${LIBNAME})
 
YURI_INSTALL_MODULE(${MODULE})
  • And finally tell the build system about this new module. To do so, simply add this line to the end of file src/modules/CMakeLists.txt:
add_subdirectory(pass)

Creating the module

Now we are ready to write the module itself. All we do here will take place in directory src/modules/pass. First create an empty file Pass.h. With the usual include guards, it should look like this:

#ifndef PASS_H_
#define PASS_H_
 
#endif /* PASS_H_ */

Now include the required headers and create empty class Pass in a namespace yuri::pass. The class should publicly inherit from yuri::core::BasicIOThread. Let's make the destructor public and virtual so anyone can destroy instances of the class, but make the constructor private, so the instances can be created only by generator we add later. Our header file should look like this now:

#ifndef PASS_H_
#define PASS_H_
 
#include "yuri/core/BasicIOThread.h"
 
namespace yuri {
namespace pass {
 
class Pass: public core::BasicIOThread
{
private:
	Pass();
public:
	virtual ~Pass();
};
 
} /* namespace pass */
} /* namespace yuri */
 
#endif /* PASS_H_ */

Now change the signature of the constuctor to common signature for modules in libyuri:

    Pass(log::Log &log_, core::pwThreadBase parent, core::Parameters &parameters);

Parameter log_ is the logging class for our module, parent is parent object (used during shutdown to correctly destroy everything) and parameters are the parameters user specified for this instance.

Lastly we add the common interface for the modules: the generator and configure method. Adding generator is simple: Just add this line to public section of the class declaration:

    IO_THREAD_GENERATOR_DECLARATION

The generator is later used to create instances of the class from a factory, for most class the generated generator is just fine. And last mandatory part is to add static method configure. This method should return list of all supported configuration options. It's signature is:

    static core::pParameters configure();

Our header file now looks like this:

#ifndef PASS_H_
#define PASS_H_
 
#include "yuri/core/BasicIOThread.h"
 
namespace yuri {
namespace pass {
 
class Pass: public core::BasicIOThread
{
private:
	Pass(log::Log &log_, core::pwThreadBase parent, core::Parameters &parameters);
public:
	virtual ~Pass();
	IO_THREAD_GENERATOR_DECLARATION
	static core::pParameters configure();
};
 
} /* namespace pass */
} /* namespace yuri */
 
#endif /* PASS_H_ */

Let's now move to the file Pass.cpp.

We need to include Pass.h and yuri/core/Module.h. The later is just proxy header including some parts of libyuri usually needed from a module. The we add code to register the class to the factory:

    REGISTER("pass",Pass)

and define the generator:

    IO_THREAD_GENERATOR(Pass)

This was simple. Now to the configure method. It should return an instance of core::pParameters. The usual way to do it is to get an instance from parent (BasicIOThread) and just add own description and parameters. We're not adding any parameters yet, so the method is simple:

core::pParameters Pass::configure()
{
    core::pParameters p = core::BasicIOThread::configure();
    p->set_description("Passthrough module.");
    p->set_max_pipes(1,1);
    return p;
}

Lastly constructor and destructor. The destructor can be empty. Constructor should simply pass the log_ and parent parameters to BasicIOThread constructor. Third/fourth parameter to BasicIOThread's constructor is number of input/output pipes this object should have. Last parameter is the name of the class for creating the prefix for logging class. In the cody of the constructor, we now only add a macro to initialize the class:

IO_THREAD_INIT("Passthrough")

So the code looks like this:

#include "Pass.h"
#include "yuri/core/Module.h"
 
namespace yuri {
namespace pass {
 
REGISTER("pass",Pass)
IO_THREAD_GENERATOR(Pass)
 
core::pParameters Pass::configure()
{
	core::pParameters p = core::BasicIOThread::configure();
	p->set_description("Passthrough module.");
	p->set_max_pipes(1,1);
	return p;
}
 
 
Pass::Pass(log::Log &log_, core::pwThreadBase parent, core::Parameters &parameters):
core::BasicIOThread(log_,parent,1,1,std::string("pass"))
{
	IO_THREAD_INIT("Passthrough")
}
 
Pass::~Pass()
{
}
 
 
} /* namespace pass */
} /* namespace yuri */

You can compile it and you can already see a file yuri2_module_pass.so in bin/modules directory. But it's not doing anything now….

Adding some functionality

For simple modules generating output based on input frames, the library provides simplified interface using method step with signature

virtual bool step();

It is called whenever new frame (in any connected input pipe) arrives and should return true most of the time. Returning false signalizes that some serious error occurred and the modules wants to be shut down. Note that the method may be called spuriously even when there's no new frame, so you should always check it.

Simple implementation for our class can look like this:

 1: bool Pass::step()
 2: {
 3:     if (!in[0]) return true;
 4:     core::pBasicFrame frame = in[0]->pop_frame();
 5:     if (!frame) return true;
 6:
 7:     push_raw_frame(0,frame);
 8:     return true;
 9: }

On line 3 we check if there's and input pipe connected to first input. If there's none, we bail out. Line 4 fetches a new frame from the input pipe 0 (first input). If there's no frame returned (the pipe was empty) we return as well. When we are sure we have a frame, we simply pass it to output pipe without any changes on line 7. Method push_raw_frame outputs the frame without any changes to a pipe specified by first parameter. And lastly, we return true on line 8.

Adding parameters

Now suppose we want to have a parameter for the class specifying whether we should pass the frames through or simply throw them away. for this purpose we add a bool variable discard to the class. In order to be able to set it's value, we also add a method set_param, with this signature:

virtual bool set_param(const core::Parameter &param);

To actully implement this we need to: (1) add this parameter to the configure method:

(*p)["discard"]["Should all frames be discarded?"]=false;

The first string specified the name of parameter, the second it's description. We also set the default value to false. (2) initialize variable discard in constructor. (3) Implement set_param method:

1: bool Pass::set_param(const core::Parameter &param)
2: {
3:     if (param.name == "discard") {
4:         discard = param.get<bool>();
5:     } else return core::BasicIOThread::set_param(param);
6:     return true;
7: }

What it does is simple: If the parameter is the one we wanted, let's get it's value as a bool (and return true as a result of the method). If it's some different parameter, then we pass it to parent's set_param method. (4) Lastly, we change line 7 in step to:

if(!discard) push_raw_frame(0,frame);

Here you may ask “hey, aren't we supposed to delete the frame? Won't it leak the memory?”. Well, no, it's OK like this. core::pBasicFrame is a shared pointer, so the memory it's pointing to will get deleted automagically when the last instance of the shared pointer is deleted. And it happens exactly at the end of our step function, because variable frame goes out of scope.

That's all.

Final code

Now our final header looks like this:

Pass.h
#ifndef PASS_H_
#define PASS_H_
 
#include "yuri/core/BasicIOThread.h"
 
namespace yuri {
namespace pass {
 
class Pass: public core::BasicIOThread
{
private:
	Pass(log::Log &log_, core::pwThreadBase parent, core::Parameters &parameters);
	virtual bool step();
	virtual bool set_param(const core::Parameter &param);
	bool discard;
public:
	virtual ~Pass();
	IO_THREAD_GENERATOR_DECLARATION
	static core::pParameters configure();
};
 
} /* namespace pass */
} /* namespace yuri */
 
#endif /* PASS_H_ */

and it's implementation:

Pass.cpp
#include "Pass.h"
#include "yuri/core/Module.h"
 
namespace yuri {
namespace pass {
 
REGISTER("pass",Pass)
IO_THREAD_GENERATOR(Pass)
 
core::pParameters Pass::configure()
{
	core::pParameters p = core::BasicIOThread::configure();
	p->set_description("Passthrough module.");
	(*p)["discard"]["Should all frames be discarded?"]=false;
	p->set_max_pipes(1,1);
	return p;
}
 
 
Pass::Pass(log::Log &log_, core::pwThreadBase parent, core::Parameters &parameters):
core::BasicIOThread(log_,parent,1,1,std::string("pass")),discard(false)
{
	IO_THREAD_INIT("Passthrough")
}
 
Pass::~Pass()
{
}
 
bool Pass::step()
{
	if (!in[0]) return true;
	core::pBasicFrame frame = in[0]->pop_frame();
	if (!frame) return true;
 
	if(!discard) push_raw_frame(0,frame);
	return true;
}
bool Pass::set_param(const core::Parameter &param)
{
	if (param.name == "discard") {
		discard = param.get<bool>();
	} else return core::BasicIOThread::set_param(param);
	return true;
}
 
} /* namespace pass */
} /* namespace yuri */

If you compile it and run bin/yuri2 -l, you should see new module pass in there, with some parameters inherited from BasicIOThread and with parameter discard.

Core API

To continu with the module, go to the description of Core API on a separate page.

Old
 
yuri/modules/add.txt · Last modified: 2013/02/23 11:33 by neneko
 
Except where otherwise noted, content on this wiki is licensed under the following license: GNU Free Documentation License 1.3
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki