What’s Mediastreamer2. Lua‑machine Embedding
Igor Plastov
(plastov.igor@yandex.ru)
Abstract
A Software framework Mediastreamer2 is a powerful and lightweight streaming engine for voice/video telephony applications. It is responsible for receiving and sending all multimedia streams in Linphone (soft‑phone application). It has a lot of filters providing digital processing of audio and video signals. Nowadays it has become an engine for purposes pretty far from telephony. A new area of the application imposes additional requirements on a capabilities of the engine, namely it does not have a filter for a flexible logical control of an application’s behavior. The article suggests solution based on a Lua language machine.
1 Introduction
“From the box”, the Mediastreamer2 has filters whose behavior, is hard-coded by the developers of the engine. There are demand of a filter with programmable behavior. It could be used for a custom signal processing and a logical control purposes. This article describes approach of those filter based on the Lua‑machine. Script loaded into the filter “on the fly” allows to change the filter’s algorithm without recompiling or stopping the executable code of application.
An example of the signal processing by Lua‑filter will be shown further.
The program code of this article can be downloaded from GitHub at the link:
https://github.com/chetverovod/Mediastreamer2_LuaFilter
2 Workflow
The Lua‑filter will be arranged as follows. In the filter, when it is initialized, an instance of the Lua‑machine will be created. The Figure 1 shows a data movement inside the suggested filter.
The input data block will be passed into the Lua‑machine through it’s stack using two global variables:
- lf_data a long string, containing the bytes of the data block;
- lf_data_len an integer, the length of the lf_data string in bytes.
After these both variables are pushed onto the stack, an execution is transferred to the Lua‑machine. It starts the lf_data string processing in accordance with loaded script. The resulting block of data will be placed to another pair of the global variables:
- lf_data_out a long string, containing the bytes of the output data block;
- lf_data_out_len an integer, the length of the lf_data_out string in bytes.
The filter’s C-code then pops the data from the stack and forms an output data block that is exposed to the filter’s output. In fact, a text string of the appropriate size is used to transfer the data to the Lua‑machine and back. More sophisticated communication options can be implemented using Lua User‑Defined Types [2], their implementation is beyond the scope of this article.
Since it is known that the block of the Mediastreamer2’s data consists of 16‑bit signed samples, then in the string lf_data, each sample will be encoded by two bytes in two’s complement code.
2.1 Organization Of The Script
Initially, the filter does not contain the Lua‑script, it must be loaded there. In this case, it is advisable to split the script into two parts. The first part does the initialization. This part of the script has executed only once. The second part is intended for multiple, cyclic execution, for each tick of a ticker:
- script preamble has executed once when the first tick arrives from the ticker;
- script body will be launched for execution on each tick of the ticker (10 ms).
For the preamble and the body loading into the filter, 2 methods are provided:
- LUA_FILTER_SET_PREAMBLE;
- LUA_FILTER_RUN.
2.1.1 Script Preamble
It loads the libraries, the declaration of functions (used by the script body) and does initial initialization of the variables. Because the preamble starts on the first tick of the ticker, the code should be loaded before the ticker is fired. To do this, the LUA_FILTER_SET_PREAMBLE method must be called. It requires arguments:
- pointer to the filter structure;
- code of the preamble.
The preamble can be written/rewritten to the filter repeatedly and at any time. Each rewriting of the preamble is followed by its one‑time execution on the next cycle of the ticker.
2.1.2 Script body
Unlike the preamble, the body is executing on every tick of the ticker (every 10ms by default). This part of the code can be written/rewritten to the filter at any time. To do this, the LUA_FILTER_RUN method is defined. The arguments of method:
- pointer to the filter structure;
- text of the body.
2.1.3 Stopping the script
At any time, the script can be stopped (paused) using the LUA_FILTER_STOP method. After calling this method, the incoming data blocks are transferred immediately to the filter output, bypassing script processing. Another words, if the script is stopped filter becomes transparent for the data.
2.1.4 Starting the script
The processing can be resumed by new call of the LUA_FILTER_RUN method. As a second argument can be used a null pointer or a pointer to the new body text.
2.1.5 Additional functions
The body of script needs couple of Lua‑functions for data access. First one for a data from the string extraction and second to put back the result of work into the string:
- get_sample() returns one 16‑bit sample of the signal from the string;
- append_sample() appends single 16‑bit sample to the end of string.
Their text is given in the Listing 1.
Listing 1: Data access functions
-- funcs.lua
-----------------------------------------------------
-- The function extracts one 16-bit sample from the string.
function get_sample(s, sample_index)
local byte_index = 2*sample_index - 1
local L = string.byte(s, byte_index)
local H = string.byte(s, byte_index + 1)
local v = 256 * H + L
if (H >= 128) then
v = - ((~(v - 1)) & 65535)
end
return v
end
-----------------------------------------------------
-- The function adds one 16-bit sample to the string.
function append_sample(s, sample_value)
local v = math.floor(sample_value + 0.5)
if v < 0 then
v = - v
v = ((~v) + 1) & 65535
end
local H = v // 256
local L = v - H * 256
return s .. string.char(L, H)
end
The function get_sample() computes from value sample_index position of 2 bytes. Further, it assembles a 16-bit number from these 2 bytes. Since the sample is a signed number, these bytes store the number in 2’s complement format. Consequently, the function converts the number to its normal form. First, it checks the state of the 15th bit (high bit) of the number. If it is equal to 0, then the number is positive and no additional transformations are needed. If the bit is 1, then the number is negative. In this case the function subtracts 1 from the number and does an inversion of each bit and multiplies it by -1.
The function append_sample() is used to assemble the string of output data. It converts sample_value to 2’s complement format. After that, it splits value to pair of bytes and appends them to the first argument (string s).
The functions are placed to a file “funcs.lua” in a directory with the filter executable code.
2.1.6 Simple example
of script
The Listing 2 depicts an example of simple script preamble.
Listing 2: Script’s preamble
-- preambula2.lua
-- This script is executed in a Lua filter as a preamble.
-- Include file with functions.
require "funcs"
preambula_status = 0
body_status = 0 -- This variable will be incremented in the script's body.
local greetings = 'Hello world from preambula!\n'
print(greetings)
return preambula_status
The preamble loads the file “funcs.lua” and initiates variables.
The script body is shown in the Listing 3:
Listing 3: Script’s body
-- body2.lua
-- This is the script executed in the Lua filter as the body of the script.
-- Connect the file with data access functions.
require "funcs"
-- We shift the result of work into output variables.
lf_data_out =""
if lf_data_len == nil then
print("Bad lf_data_len.\n")
end
for i = 1, lf_data_len/2 do
s = get_sample(lf_data, i)
lf_data_out = append_sample(lf_data_out, s)
end
lf_data_out_len = string.len(lf_data_out)
return body_status
Nothing special is done in the body of the script, just the samples from the input string are transferring one by one to the output string. Obviously, between the get_sample() and append_sample() function calls, any transformation of samples can be inserted. Another option is also possible, when the filter does not process samples, but can control other filters in accordance with the input data.
2.2 Filter Code
2.2.1 The Header Code
The Lua‑filter’s header file “lua_filter.h” is shown in the Listing 4.
Listing 4: Lua-filter header file
#ifndef lua_filter_h
#define lua_filter_h
/* Include a header file listing the filters of the media streamer. */
#include <mediastreamer2/msfilter.h>
/* Connect the Lua interpreter. */
#include <lua5.3/lua.h>
#include <lua5.3/lauxlib.h>
#include <lua5.3/lualib.h>
/*
Set the numeric ID of the new filter type. This number should not
match none of the other types. In the media streamer in the file
allfilters.h there is a corresponding enum MSFilterId. Unfortunately,
it is not clear how to determine the maximum occupied value other than
to look into this file. But we will take a larger id for our filter
value: 4001. We will assume that developers adding new filters do not
will get to that number soon.
*/
#define LUA_FILTER_ID 4001
/*
The name of the global variable into which the filter function places
the input block data.
*/
#define LF_DATA_CONST "lf_data"
/*
The name of the global variable into which the filter function places
the size
of the output block of data.
*/
#define LF_DATA_LEN_CONST "lf_data_len"
/*
The name of the global variable into which the filter function places
the output block of data.
*/
#define LF_DATA_OUT_CONST "lf_data_out"
/*
The name of the global variable into which the filter function places
the size of the output block data.
*/
#define LF_DATA_OUT_LEN_CONST "lf_data_out_len"
/* This flag indicates that the filter's input queue is empty. */
#define LF_INPUT_EMPTY_CONST "input_empty"
/* Filter's constants. */
#define LF_DATA LF_DATA_CONST
#define LF_DATA_LEN LF_DATA_LEN_CONST
#define LF_DATA_OUT LF_DATA_OUT_CONST
#define LF_DATA_OUT_LEN LF_DATA_OUT_LEN_CONST
#define LF_INPUT_EMPTY LF_INPUT_EMPTY_CONST
/*
We define the methods of our filter. The second parameter of
the macro should ordinal number of the method, a number from 0.
The third parameter is the type of the argument method, the
pointer to which will be passed to the method when called.
*/
#define LUA_FILTER_RUN MS_FILTER_METHOD(LUA_FILTER_ID,0,char)
#define LUA_FILTER_STOP MS_FILTER_METHOD(LUA_FILTER_ID,1,int)
#define LUA_FILTER_SET_PREAMBLE MS_FILTER_METHOD(LUA_FILTER_ID,2,char)
/*
Define an exported variable that will be
store characteristics for a given filter type.
*/
extern MSFilterDesc lua_filter_desc;
#endif /* lua_filter_h */
The macros and names of the global variables are declared here. Also, three filter’s methods mentioned above are declared:
- LUA_FILTER_RUN;
- LUA_FILTER_STOP;
- LUA_FILTER_SET_PREAMBLE.
2.2.2 The Source Code
The Listing 5 shows only an important part of the source code — a control_process() method (please, see Appendix A for a full source of the code). This method runs the Lua‑machine on each tick of the ticker.
Listing 5: Method control_process()
static void
control_process(MSFilter *f)
{
ControlData *d = (ControlData *)f->data;
mblk_t *im;
mblk_t *out_im = NULL;
int err = 0;
int i;
if ((!d->stopped) && (!d->preabmle_was_run))
{
run_preambula(f);
}
while ((im = ms_queue_get(f->inputs[0])) != NULL)
{
unsigned int disabled_out = 0;
if ((!d->stopped) && (d->script_code) && (d->preabmle_was_run))
{
bool_t input_empty = ms_queue_empty(f->inputs[0]);
lua_pushinteger(d->L, (lua_Integer)input_empty);
lua_setglobal(d->L, LF_INPUT_EMPTY);
/* Put the data block from the filter input onto the stack of the
Lua-machine. */
size_t sz = 2 * (size_t)msgdsize(im); /* Block size in bytes. */
lua_pushinteger(d->L, (lua_Integer)sz);
lua_setglobal(d->L, LF_DATA_LEN);
lua_pushlstring(d->L, (const char *)im->b_rptr, sz);
lua_setglobal(d->L, LF_DATA);
/* Remove from the stack everything that may have remained there. */
int values_on_stack;
values_on_stack = lua_gettop(d->L);
lua_pop(d->L, values_on_stack);
/* Execute the body of the script. */
err = luaL_dostring(d->L, d->script_code);
/* We process the result of the execution. */
if (!err)
{
int script_body_status = lua_tointeger(d->L,
lua_gettop(d->L));
if (script_body_status < 0)
{
printf("\nFilter <%s> bad script_body_status: %i.\n",
f->desc->name,
script_body_status);
}
/* We extract the size of the output data block, it may
have changed. */
lua_getglobal(d->L, LF_DATA_OUT_LEN);
size_t real_size = 0;
char type_on_top = lua_type(d->L, lua_gettop(d->L));
if (type_on_top == LUA_TNUMBER)
{
real_size =
(size_t)lua_tointeger(d->L,
lua_gettop(d->L));
}
lua_pop(d->L, 1);
/* Retrieve a long string with transformed input block data
data. Let's push it further. */
lua_getglobal(d->L, LF_DATA_OUT);
size_t str_len = 0;
if (lua_type(d->L, lua_gettop(d->L)) == LUA_TSTRING)
{
const char *msg_body = lua_tolstring(d->L, -1,
&str_len);
if (msg_body && str_len)
{
size_t msg_len = real_size / 2;
out_im = allocb((int)msg_len, 0);
memcpy(out_im->b_wptr, msg_body, msg_len);
out_im->b_wptr = out_im->b_wptr + msg_len;
}
}
lua_pop(d->L, 1);
/* Subtract and discard everything that is possibly left
on the stack. */
values_on_stack = lua_gettop(d->L);
lua_pop(d->L, values_on_stack);
}
else
{
printf("\nFilter <%s> Lua error.\n", f->desc->name);
const char *answer = lua_tostring(d->L, lua_gettop(d->L));
if (answer)
{
printf("Lua error description:<%s>.\n", answer);
}
}
}
mblk_t *p = im;
if (out_im)
p = out_im;
for (i = 0; i < f->desc->noutputs; i++)
{
if ((!disabled_out) && (f->outputs[i] != NULL))
if (p)
ms_queue_put(f->outputs[i], dupmsg(p));
}
freemsg(out_im);
freemsg(im);
}
}
The method reads the data blocks from an input queue, puts it to the global values (lf_sata aka LF_DATA, lf_data_len aka LF_DATA_LEN) of script and triggers Lua‑machine (d->L). When Lua‑machine has finished, function puts the output data block to the output queue.
2.3 The Testbench
The scheme shown in a Figure 2 was used as a testbench for the filter’s testing.
The testbench has 2 sources of a sound signal:
- linear input of a sound card (sound_card_read)
- DTMF signal generator (dtmf_generator)
Particular source can be selected by a command line option. By default the linear input is used. An option “gen” activates the dtmf_generator. The samples from the signal source are propagating to the lua_filter. It makes the signal transformation in accordance with the loaded script. Then the samples go to the splitter (Tee). It forms on the outputs two copies of the incoming data stream. One of these streams feeds the recorder and the second goes to the sound card for playback (sound_card_write). The recorder, saves incoming data to a disk in raw‑format (wav‑file without header). This way we can listen and get the result of the Lua‑filter.
The algorithm of the testbench application is follow. After the application start, the filters (Figure 2) will be created and connected to each other. Before the ticker activation, the filters initialization procedure will begin. In fact, it consists in calling of init() method of each filter. The Lua‑filter in addition to usual for init() actions executes the Lua‑preamble code. A command line options “scp” and “scb” should be used for pointing preamble and body file names. The full list of testbench keys is given in the Listing 6.
Listing 6: Test application command line switches
--help List of options.
--version Version of application.
--scp Full name of preambula of Lua-script file.
--scb Full name of body of Lua-script file.
--gen Set generator's frequency, Hz.
--rec Make recording to a file 'record.wav'.
The ticker is activating at the last moment. The scheme starts data processing.
The test application source code is given in the Appendix B. File README.md contains instructions for testbench compilation.
Example of command line for application start:
$ ./lua_filter_demo --scb ../scripts/body2.lua --scp ../scripts/preambula2.lua --gen 600
After starting, a 600 Hz tone will be heard in the headphones for 10 seconds. This means that the signal has passed through the Lua‑filter.
3 Lua-filter
Data Processing
In this section will be described more sophisticated example of the filter’s script. The new script, starting from the 5000th sample of signal (i.e. 5/8 of a second), multiplies the input signal by a low frequency sinusoidal signal (i.e. modulates in amplitude) for 2 seconds. After that time signal becomes nonmodulated again.
Transformation could by described by a formula:
The code of preamble is shown in the Lsting 7:
Listing 7: Preamble of modulating script
-- preambula3.lua
-- This script is executed in a Lua-filter as a preamble.
-- Include file with functions.
require "funcs"
preambula_status = 0
body_status = 0 -- This variable will be incremented in the script's body.
local greetings = 'Hello world from preambula!\n'
print(greetings)
-- Variables for calculations.
samples_count = 0
sampling_rate = 8000
low_frequency = 2 -- Modulating frequency.
phase_step = 2 * math.pi / sampling_rate * low_frequency
return preambula_status
The modulation will be implemented in the body, see the Listing 8:
Listing 8: Body of modulating script
-- body3.lua
-- This is the script executed in the Lua filter as the body of the script.
-- The script modulates the input signal.
lf_data_out =""
if lf_data_len == nil then
print("Bad lf_data_len.\n")
end
for i = 1, lf_data_len/2 do
s = get_sample(lf_data, i)
if (samples_count > 5000) and (samples_count < 21000) then
output_s = s * math.sin( phase_step * samples_count )
else
output_s = s
end
samples_count = samples_count + 1
lf_data_out = append_sample(lf_data_out, output_s)
end
lf_data_out_len = string.len(lf_data_out)
return body_status
Run the testbench with a new script:
$ ./lua_filter_demo --scb ../scripts/body3.lua --scp ../scripts/preambula3.lua --gen 1200 --rec
after 5 seconds, to stop the program, press the “Enter” key. Resulting file record.raw could play like this:
$ aplay -t raw --format s16_be --channels 1 ./record.raw
Let’s convert the output file to wav‑format:
$ sox -t raw -r 8000 -b 16 -c 1 -L -e signed-integer ./record.raw ./recording.wav
To draw the signal in the Gnuplot, we need to convert it to a file with two columns of data. The tool sox will do this:
$ sox ./recording.wav -r 8000 recording.dat && grep -v «^;» recording.dat > clean_recording.dat
Next operation is passing the resulting recording.wav file to the Gnuplot utility:
$ gnuplot -e "set terminal png; set output 'recording.png'; plot 'clean_recording.dat' using 1:2 with lines"
The Figure 3 depicts the result of Lua‑filter work.
The figure, shows an envelope of the sinusoidal signal after it has passed through the Lua‑filter. For about 5/8 of a second, the signal amplitude remained unchanged, then the script branch with the modulation algorithm was put into operation, and for a second there was a modulated signal at the output. After that, the script turned off the modulation and the signal became transmitted to the output without modulation.
4 Conclusions And Future Work
The filter developed is publicly available on https://github.com/chetverovod/Mediastreamer2_LuaFilter. It can be used in any design based on the Mediastreamer2.
All the filter’s methods are implemented in minimal approach and can be improved in the future.
References
[1] Igor Plastov “What’s Mediastreamer2”
[2] Lua:User‑Defined Types
https://www.lua.org/pil/28.html)||https://www.lua.org/pil/28 .html
Appendix A
Listing 9: Lua‑filter source code
#include "lua_filter.h"
/*--------------------------------------------------*/
/* New print function from Lua script. */
static int
l_my_print (lua_State* L)
{
int nargs = lua_gettop (L);
for (int i = 1; i <= nargs; i++)
{
if (lua_isstring (L, i))
{
/* Pop the next arg using lua_tostring(L, i) and do your print */
const char *str = lua_tostring (L, lua_gettop (L));
printf ("%s", str);
}
else
{
/* Do something with non-strings if you like */
}
}
return 0;
}
/*--------------------------------------------------*/
/* Override the print function from the Lua script. */
static const struct luaL_Reg printlib[] = {
{"print", l_my_print},
{NULL, NULL} /* end of array */
};
//*--------------------------------------------------*/
int
luaopen_luamylib (lua_State* L)
{
lua_getglobal (L, "_G");
luaL_setfuncs (L, printlib, 0); // for Lua versions 5.2 or greater
lua_pop (L, 1);
return 0;
}
/*--------------------------------------------------*/
typedef struct _ControlData
{
lua_State* L;
char *script_preamble; // Script that is executed once by executing
// preliminary actions.
char *script_code; // Script that runs cyclically
// each tick.
bool_t stopped; // Flag that the Lua-machine is stopped.
bool_t preabmle_was_run; // Flag that the preamble has already been executed.
char padding[6];
} ControlData;
/*--------------------------------------------------*/
void
set_queues (MSFilter * f)
{
MS_UNUSED(f);
}
/*--------------------------------------------------*/
/* Script preamble execution function. */
static void
run_preambula(MSFilter *f)
{
ControlData *d = (ControlData *)f->data;
if (d->script_preamble)
{
int err = 0;
// Execute the preamble script.
err = luaL_dostring(d->L, d->script_preamble);
if (err)
{
printf("filter <%s> Lua script preamble error.\n",
f->desc->name);
const char *answer = lua_tostring(d->L, lua_gettop(d->L));
if (answer)
{
printf("Error description:<%s>\n", answer);
}
}
else
{
d->preabmle_was_run = TRUE;
}
}
else
{
/* If the preamble is not defined, then just set the flag that it
is done. */
d->preabmle_was_run = TRUE;
}
}
/*--------------------------------------------------*/
static void
control_init(MSFilter *f)
{
ControlData *cd = ms_new0(ControlData, 1);
f->data = cd;
cd->L = luaL_newstate(); // Create an instance of the Lua virtual machine.
luaL_openlibs(cd->L); // Load standard libraries.
luaopen_luamylib(cd->L);
}
/*--------------------------------------------------*/
static void
control_uninit(MSFilter *f)
{
ControlData *cd = (ControlData *)f->data;
lua_close(cd->L); // Stop the Lua machine.
ms_free(cd->script_code);
ms_free(cd->script_preamble);
ms_free(cd);
}
/*--------------------------------------------------*/
static void
control_process(MSFilter *f)
{
ControlData *d = (ControlData *)f->data;
mblk_t *im;
mblk_t *out_im = NULL;
int err = 0;
int i;
if ((!d->stopped) && (!d->preabmle_was_run))
{
run_preambula(f);
}
while ((im = ms_queue_get(f->inputs[0])) != NULL)
{
unsigned int disabled_out = 0;
if ((!d->stopped) && (d->script_code) && (d->preabmle_was_run))
{
bool_t input_empty = ms_queue_empty(f->inputs[0]);
lua_pushinteger(d->L, (lua_Integer)input_empty);
lua_setglobal(d->L, LF_INPUT_EMPTY);
/* Put the data block from the filter input onto the stack of the
Lua-machine. */
size_t sz = 2 * (size_t)msgdsize(im); /* Block size in bytes. */
lua_pushinteger(d->L, (lua_Integer)sz);
lua_setglobal(d->L, LF_DATA_LEN);
lua_pushlstring(d->L, (const char *)im->b_rptr, sz);
lua_setglobal(d->L, LF_DATA);
/* Remove from the stack everything that may have remained there. */
int values_on_stack;
values_on_stack = lua_gettop(d->L);
lua_pop(d->L, values_on_stack);
/* Execute the body of the script. */
err = luaL_dostring(d->L, d->script_code);
/* We process the result of the execution. */
if (!err)
{
int script_body_status = lua_tointeger(d->L,
lua_gettop(d->L));
if (script_body_status < 0)
{
printf("\nFilter <%s> bad script_body_status: %i.\n",
f->desc->name,
script_body_status);
}
/* We extract the size of the output data block, it may have
changed. */
lua_getglobal(d->L, LF_DATA_OUT_LEN);
size_t real_size = 0;
char type_on_top = lua_type(d->L, lua_gettop(d->L));
if (type_on_top == LUA_TNUMBER)
{
real_size =
(size_t)lua_tointeger(d->L,
lua_gettop(d->L));
}
lua_pop(d->L, 1);
/* Retrieve a long string with transformed input block data
data. Let's push it further. */
lua_getglobal(d->L, LF_DATA_OUT);
size_t str_len = 0;
if (lua_type(d->L, lua_gettop(d->L)) == LUA_TSTRING)
{
const char *msg_body = lua_tolstring(d->L, -1,
&str_len);
if (msg_body && str_len)
{
size_t msg_len = real_size / 2;
out_im = allocb((int)msg_len, 0);
memcpy(out_im->b_wptr, msg_body, msg_len);
out_im->b_wptr = out_im->b_wptr + msg_len;
}
}
lua_pop(d->L, 1);
/* Subtract and discard everything that is possibly left
on the stack. */
values_on_stack = lua_gettop(d->L);
lua_pop(d->L, values_on_stack);
}
else
{
printf("\nFilter <%s> Lua error.\n", f->desc->name);
const char *answer = lua_tostring(d->L, lua_gettop(d->L));
if (answer)
{
printf("Lua error description:<%s>.\n", answer);
}
}
}
mblk_t *p = im;
if (out_im)
p = out_im;
for (i = 0; i < f->desc->noutputs; i++)
{
if ((!disabled_out) && (f->outputs[i] != NULL))
if (p)
ms_queue_put(f->outputs[i], dupmsg(p));
}
freemsg(out_im);
freemsg(im);
}
}
/*--------------------------------------------------*/
/* The function to pause the script. */
static int
control_stop(MSFilter *f, void *arg)
{
ControlData *d = (ControlData *)f->data;
d->stopped = TRUE;
if (arg)
{
if (d->script_code)
ms_free(d->script_code);
d->script_code = ms_strdup(*(char **)arg);
ms_free(*(char **)arg);
}
return 0;
}
/*--------------------------------------------------*/
/* The function starts the body of the script to work. If the argument
contains text script, the old script is discarded and the new one is used. */
static int
control_run(MSFilter *f, void *arg)
{
ControlData *d = (ControlData *)f->data;
d->stopped = FALSE;
if (arg)
{
if (d->script_code)
ms_free(d->script_code);
d->script_code = ms_strdup(*(char **)arg);
ms_free(*(char **)arg);
}
return 0;
}
/*--------------------------------------------------*/
/* Function to set script preamble. The old preamble is replaced by the new
one. */
static int
control_set_preamble(MSFilter *f, void *arg)
{
ControlData *d = (ControlData *)f->data;
d->preabmle_was_run = FALSE;
if (arg)
{
if (d->script_preamble)
ms_free(d->script_preamble);
d->script_preamble = ms_strdup(*(char **)arg);
ms_free(*(char **)arg);
}
return 0;
}
/*--------------------------------------------------*/
/* Set up the callback function table. */
static MSFilterMethod control_methods[] = {
{LUA_FILTER_STOP, control_stop},
{LUA_FILTER_RUN, control_run},
{LUA_FILTER_SET_PREAMBLE, control_set_preamble},
{0, NULL}
};
#ifdef _MSC_VER
MSFilterDesc lua_filter_desc = {
LUA_FILTER_ID,
"lua_filter",
N_("A filter that runs a Lua script."),
MS_FILTER_OTHER,
NULL,
1,
1,
control_init,
NULL,
control_process,
NULL,
control_uninit,
control_methods
};
#else
MSFilterDesc lua_filter_desc = {
.id = (MSFilterId) LUA_FILTER_ID,
.name = "LUA_FILTER",
.text = ("A filter that runs a Lua-script."),
.category = MS_FILTER_OTHER,
.ninputs = 1,
.noutputs = 1,
.init = control_init,
.process = control_process,
.uninit = control_uninit,
.methods = control_methods
};
#endif
MS_FILTER_DESC_EXPORT (lua_filter_desc)
Appendix B
Listing 10: Testing application source code
/* lua_filter_demo.c A program-demonstrator of a filter with a built-in
Lua-machine. */
#include <sys/stat.h>
/* Include header files for Mediastreamer2 filters. */
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/msfilerec.h>
#include <mediastreamer2/mscommon.h>
#include <mediastreamer2/msfactory.h>
#include <mediastreamer2/msticker.h>
/* Index of the sound card that we will use by default. */
#define DEF_CARD 0
/* Connect our filter. */
#include "lua_filter.h"
/*--------------------------------------------------*/
/* Application state variables. */
struct _app_vars
{
MSDtmfGenCustomTone dtmf_cfg; /* Generator test signal settings. */
MSFilter* recorder; /* Pointer to the filter-recorder. */
bool_t file_is_open; /* Flag that the file is open for writing. */
bool_t en_gen; /* Turn on the sound generator. */
bool_t en_rec; /* Enable writing to file. */
MSFactory *mf; /* Media streamer filter factory. */
MSSndCardManager *scm; /* Sound card manager. */
char cards_count; /* The number of available sound cards. */
const char **cards; /* List of available sound cards. */
char* script_preambula_name; /* Script preamble file. */
char* script_body_name; /* The file of the main part of the script. */
};
typedef struct _app_vars app_vars;
/*-----------------------------------------------------*/
/* Function to convert command line arguments to
program settings. */
void scan_args(int argc, char *argv[], app_vars *v)
{
int i;
for (i=0; i<argc; i++)
{
if (!strcmp(argv[i], "--help"))
{
char *p=argv[0]; p=p + 2;
printf(" %s, developed by Igor Plastov\nigor.plastov@yandex.ru\n\n", p);
printf("--help List of options.\n");
printf("--version Version of application.\n");
printf("--scp Full name of containing preambula of Lua-script file.\n");
printf("--scb Full name of containing body of Lua-script file.\n");
printf("--gen Set generator's frequency, Hz.\n");
printf("--rec Make recording to file 'record.wav'.\n");
exit(0);
}
if (!strcmp(argv[i], "--version"))
{
printf("0.1\n");
exit(0);
}
if (!strcmp(argv[i], "--scp"))
{
v->script_preambula_name = ms_strdup(argv[i+1]);
printf("Lua-script ptrambula file name: %s\n",
v->script_preambula_name);
}
if (!strcmp(argv[i], "--scb"))
{
v->script_body_name = ms_strdup(argv[i+1]);
printf("Lua-script body file name: %s\n", v->script_body_name);
}
if (!strcmp(argv[i], "--gen"))
{
v -> en_gen = TRUE;
int freq = atoi(argv[i+1]);
v -> dtmf_cfg.frequencies[0] = freq;
printf("Generator's frequency: %i Hz\n",
v -> dtmf_cfg.frequencies[0]);
}
if (!strcmp(argv[i], "--rec"))
{
v -> en_rec = TRUE;
printf("enable recording: %i\n", v -> en_rec);
}
}
}
*-----------------------------------------------------*/
/* The function returns the script text read from the file. */
char* get_file(char *filename) {
struct stat file_status;
if (stat(filename, &file_status) < 0) {
return NULL;
}
char* res = NULL;
const size_t file_size = file_status.st_size;
char* buf = ms_malloc0(file_size + 1);
FILE* f = fopen( filename, "r");
size_t read_res = fread(buf, 1, file_size, f);
fclose(f);
if ((read_res > 0) && (read_res <= file_size))
{
printf("\nScript's code <%s>:\n{\n%s\n}\nwill be loaded to lua-filter.\n\n",
filename, buf);
/* The memory allocated for buf will be freed by the
filter method itself. */
res = buf;
}
return res;
}
/*-----------------------------------------------------*/
/* Function for loading the main part (body) of the script. */
static void load_script_body(app_vars *v, MSFilter* filter)
{
if (!v->script_body_name) return;
char *buf = get_file(v->script_body_name);
if (buf)
{
ms_filter_call_method(filter, LUA_FILTER_RUN, &buf);
}
else
{
printf("Script's body <%s> was not loaded.\n",
v->script_body_name);
}
}
/*-----------------------------------------------------*/
/* Script preamble loading function. */
static void load_script_preambula(app_vars *v, MSFilter* filter)
{
if (!v->script_preambula_name) return;
char *buf = get_file(v->script_preambula_name);
if (buf)
{
ms_filter_call_method(filter, LUA_FILTER_SET_PREAMBLE, &buf);
}
else
{
printf("Script's preambula <%s> was not loaded.\n",
v -> script_preambula_name);
}
}
/*-----------------------------------------------------*/
/* The function creates a table of available sound cards. */
static void build_sound_cards_table(app_vars *v)
{
size_t ndev;
int i;
const bctbx_list_t *elem =
ms_snd_card_manager_get_list(
ms_factory_get_snd_card_manager(v -> mf));
ndev = bctbx_list_size(elem);
v -> cards = ms_malloc((ndev + 1)*sizeof(const char *));
for ( i = 0; elem != NULL; elem = elem->next, i++)
{
v -> cards[i] = ms_snd_card_get_string_id(
(MSSndCard *)elem -> data);
}
v -> cards[ndev] = NULL;
v -> cards_count = ndev;
}
/*-----------------------------------------------------*/
int main(int argc, char *argv[])
{
/* Set the default settings. */
app_vars vars={0};
vars.mf = ms_factory_new();
ms_factory_init_voip(vars.mf);
vars.scm = ms_factory_get_snd_card_manager(vars.mf);
build_sound_cards_table(&vars);
/* Set the program settings in accordance to command line arguments. */
scan_args(argc, argv, &vars);
/* Create instances of the transmit path filters. */
printf("Will be used soundcard:\n<\n%s\n>\n", vars.cards[DEF_CARD]);
MSSndCard *snd_card =
ms_snd_card_manager_get_card(vars.scm, vars.cards[DEF_CARD]);
MSFilter *snd_card_read = ms_snd_card_create_reader(snd_card);
MSFilter *snd_card_write = ms_snd_card_create_writer(snd_card);
MSFilter *voidsource = ms_factory_create_filter(vars.mf, MS_VOID_SOURCE_ID);
MSFilter *dtmfgen = ms_factory_create_filter(vars.mf, MS_DTMF_GEN_ID);
/* Register our Lua filter. */
ms_factory_register_filter(vars.mf, &lua_filter_desc);
MSFilter *lua_filter = ms_factory_create_filter(vars.mf, LUA_FILTER_ID);
/* Create the recorder filter. */
MSFilter *recorder = ms_factory_create_filter(vars.mf, MS_FILE_REC_ID);
vars.recorder = recorder;
/* Create a splitting filter. */
MSFilter *tee = ms_factory_create_filter(vars.mf, MS_TEE_ID);
/* Create a clock source - a ticker. */
MSTicker *ticker = ms_ticker_new();
/* We connect filters according to the scheme. */
if (vars.en_gen)
{
ms_filter_link(voidsource, 0, dtmfgen, 0);
ms_filter_link(dtmfgen, 0, lua_filter, 0);
ms_filter_link(lua_filter, 0, tee, 0);
ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
(void*)&vars.dtmf_cfg);
}
else
{
ms_filter_link(snd_card_read, 0,lua_filter, 0);
ms_filter_link(lua_filter, 0, tee, 0);
}
ms_filter_link(tee, 0, recorder, 0);
ms_filter_link(tee, 1, snd_card_write, 0);
/* Set the Lua filter preamble. */
load_script_preambula(&vars, lua_filter);
/* Connect the clock source. */
if (vars.en_gen)
{
ms_ticker_attach(ticker, voidsource);
}
else
{
ms_ticker_attach(ticker, snd_card_read);
}
load_script_body(&vars, lua_filter);
if ( vars.en_rec ) ms_filter_call_method(recorder, MS_FILE_REC_START, 0);
/* If the generator frequency setting is non-zero, then run
generator. */
if (vars.en_gen)
{
/* We set up a structure that controls the output signal
of the generator. */
strncpy(vars.dtmf_cfg.tone_name, "sound",
sizeof(vars.dtmf_cfg.tone_name));
vars.dtmf_cfg.duration = 10000;
vars.dtmf_cfg.amplitude = 1.0;
vars.dtmf_cfg.frequencies[1]=0;
vars.dtmf_cfg.amplitude = 1.0;
vars.dtmf_cfg.interval = 0.;
vars.dtmf_cfg.repeat_count = 0.;
/* We start the generator. */
ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
(void *)&vars.dtmf_cfg);
printf("Sound from generator lasts %i ms.\n", vars.dtmf_cfg.duration);
}
printf("Press ENTER to exit.\n ");
char c=getchar();
while(c != '\n')
{
ms_usleep(500000);
c=getchar();
}
if (vars.en_rec )
{
/* Finish writing to the file. */
ms_filter_call_method(recorder, MS_FILE_REC_CLOSE, 0);
printf("File recording was finished.\n");
}
}