===== Adding new module ===== If you don't want to read all the text, just skip to [[#final_code|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: # 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 **public**ly 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 ¶meters); 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 ¶meters); 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 ¶meters): 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: bool Pass::step() { if (!in[0]) return true; core::pBasicFrame frame = in[0]->pop_frame(); if (!frame) return true; push_raw_frame(0,frame); return true; } 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 ¶m); 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: bool Pass::set_param(const core::Parameter ¶m) { if (param.name == "discard") { discard = param.get(); } else return core::BasicIOThread::set_param(param); return true; } 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: #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 ¶meters); virtual bool step(); virtual bool set_param(const core::Parameter ¶m); bool discard; public: virtual ~Pass(); IO_THREAD_GENERATOR_DECLARATION static core::pParameters configure(); }; } /* namespace pass */ } /* namespace yuri */ #endif /* PASS_H_ */ and it's implementation: #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 ¶meters): 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 ¶m) { if (param.name == "discard") { discard = param.get(); } 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 [[:yuri:design:module_api|Core API]] on a separate page. == Old== Take also a look at [[:yuri:module:dummy|Detailed description of dummy module]] and [[:yuri:module:tutorial|Walk through writing imagemagick_source module]]