The article material is taken from my .
Previously We created a duplex intercom device that exchanges audio signals through a duplex RTP session. In this article, we will learn how to write filters and add a DIY filter to the DIY intercom device.
Developing a Plugin

Plugins in the media streamer, as in many other programs, are used to extend functionality without the need to recompile the media streamer itself.
To use a plugin in your program, you need to include include the plugin's header file. In the body of the program, you will use the function ms_filter_register() to register the new filter. Naturally, your application and the source code of the plugin must be compiled and built into a single application.
Now let’s turn to writing the plugin. All filters of the media streamer and plugins follow a common canon in writing, which greatly simplifies understanding the structure of the filter you want to study. Therefore, to avoid proliferating entities, plugins will be referred to as filters.
Let’s assume we want to develop a new filter named NAS_H_FILTER (NASH_FILTR). It will perform a simple task — receiving blocks from its single input and passing them to its five outputs. Additionally, it will generate an event if more than five blocks with a signal level below a specified threshold pass through it, and if more than five blocks with a level above the threshold pass through it, it will also generate an event.
The threshold will be set using the filter method. The second and third methods will allow/forbid the passage of blocks to the outputs.
Let’s get started. Writing a filter should begin with the header file. It should include the file msfilter.h, declare the methods of the new filter (if any) using the MS_FILTER_METHOD macro, declare the events generated by the filter (if any), and declare an exported structure type MSFilterDesc with a description of the filter parameters:
/* Файл nash_filter.h, описывает фильтр-разветвитель и нойзгейт. */
#ifndef myfilter_h
#define myfilter_h
/* Подключаем заголовочный файл с перечислением фильтров медиастримера. */
#include <mediastreamer2/msticker.h>
/*
Задаем числовой идентификатор нового типа фильтра. Это число не должно
совпадать ни с одним из других типов. В медиастримере в файле allfilters.h
есть соответствующее перечисление enum MSFilterId. К сожалению, непонятно
как определить максимальное занятое значение, кроме как заглянуть в этот
файл. Но мы возьмем в качестве id для нашего фильтра заведомо большее
значение: 4000. Будем полагать, что разработчики добавляя новые фильтры, не
скоро доберутся до этого номера.
*/
#define NASH_FILTER_ID 4000
/*
Определяем методы нашего фильтра. Вторым параметром макроса должен
порядковый номер метода, число от 0. Третий параметр это тип аргумента
метода, указатель на который будет передаваться методу при вызове. У методов
аргументов может и не быть, как показано ниже.
*/
#define NASH_FILTER_SET_TRESHOLD MS_FILTER_METHOD(NASH_FILTER_ID , 0, float)
#define NASH_FILTER_TUNE_OFF MS_FILTER_METHOD_NO_ARG(NASH_FILTER_ID ,1)
#define NASH_FILTER_TUNE_ON MS_FILTER_METHOD_NO_ARG(NASH_FILTER_ID ,2)
/* Теперь определяем структуру, которая будет передаваться вместе с событием. */
struct _NASHFilterEvent
{
/* Это поле, которое будет выполнять роль флага,
0 - появились нули, 1 - появился сигнал.*/
char state;
/* Время, когда произошло событие. */
uint64_t time;
};
typedef struct _NASHFilterEvent NASHFilterEvent;
/* Определяем событие для нашего фильтра. */
#define NASH_FILTER_EVENT MS_FILTER_EVENT(MS_RTP_RECV_ID, 0, NASHFilterEvent)
/* Определяем экспортируемую переменную, которая будет
хранить характеристики для данного типа фильтров. */
extern MSFilterDesc nash_filter_desc;
#endif /* myfilter_h */Now we can move on to the source file. The source code of the filter with comments is shown below. Here the filter methods and mandatory filter functions are defined. Then the references to the methods and functions are placed in a specific order in the exported structure nash_filter_descWhich is used by the media streamer to "embed" filters of this type into the data processing workflow.
/* Файл nash_filter.с, описывает фильтр-разветвитель и нойзгейт. */
#include "nash_filter.h"
#include <math.h>
#define NASH_FILTER_NOUTPUTS 5
/* Определяем структуру, которая хранит внутреннее состояние фильтра. */
typedef struct _nash_filterData
{
bool_t disable_out; /* Разрешение передачи блоков на выход. */
int last_state; /* Текущее состояние переключателя. */
char zero_count; /* Счетчик нулевых блоков. */
char lag; /* Количество блоков для принятия решения нойзгейтом. */
char n_count; /* Счетчик НЕнулевых блоков. */
float skz_level; /* Среднеквадратическое значение сигнала внутри
блока, при котором фильтр будет пропускать сигнал. Одновременно это порог
срабатывания, по которому будет формироваться событие. */
} nash_filterData;
/*----------------------------------------------------------*/
/* Обязательная функция инициализации. */
static void nash_filter_init(MSFilter *f)
{
nash_filterData *d=ms_new0(nash_filterData, 1);
d->lag=5;
f->data=d;
}
/*----------------------------------------------------------*/
/* Обязательная функция финализации работы фильтра,
освобождается память. */
static void nash_filter_uninit(MSFilter *f)
{
ms_free(f->data);
}
/*----------------------------------------------------------*/
/* Определяем образцовый массив с нулями, заведомо
большего размера чем блок. */
char zero_array[1024]={0};
/* Определяем событие фильтра. */
NASHFilterEvent event;
/*----------------------------------------------------------*/
/* Функция отправки события. */
static void send_event(MSFilter *f, int state)
{
nash_filterData *d =( nash_filterData* ) f->data;
d->last_state = state;
/* Устанавливаем время возникновения события,
от момента первого тика. Время в миллисекундах. */
event.time=f -> ticker -> time;
event.state=state;
ms_filter_notify(f, NASH_FILTER_EVENT, &event);
}
/*----------------------------------------------------------*/
/* Функция вычисляет среднеквадратическое (эффективное) значение сигнала внутри
блока. */
static float calc_skz(nash_filterData *d, int16_t *signal, int numsamples)
{
int i;
float acc = 0;
for (i=0; i<numsamples; i++)
{
int s=signal[i];
acc = acc + s * s;
}
float skz = (float)sqrt(acc / numsamples);
return skz;
}
/*----------------------------------------------------------*/
/* Обязательная функция основного цикла фильтра,
вызывается с каждым тиком. */
static void nash_filter_process(MSFilter *f)
{
nash_filterData *d=(nash_filterData*)f->data;
/* Указатель на входное сообщение содержащее блок данных. */
mblk_t *im;
int i;
int state;
/* Вычитываем сообщения из входной очереди
до полного её опустошения. */
while((im=ms_queue_get(f->inputs[0]))!=NULL)
{
/* Если выходы запрещены, то просто удаляем входное сообщение. */
if ( d -> disable_out)
{
freemsg(im);
continue;
}
/* Измеряем уровень сигнала и принимаем решение об отправке сигнала. */
float skz = calc_skz(d, (int16_t*)im->b_rptr, msgdsize(im));
state = (skz > d->skz_level) ? 1 : 0;
if (state)
{
d->n_count++;
d->zero_count = 0;
}
else
{
d->n_count = 0;
d->zero_count++;
}
if (((d->zero_count > d->lag) || (d->n_count > d->lag))
&& (d->last_state != state)) send_event(f, state);
/* Приступаем к копированию входного сообщения и раскладке по выходам. Но
* только по тем, к которым подключена нагрузка. Оригинальное сообщение
* уйдет на выход с индексом 0, а его копии попадут на остальные
* выходы. */
int output_count = 0;
mblk_t *outm; /* Указатель на сообщение с выходным блоком данных. */
for(i=0; i < f->desc->noutputs; i++)
{
if (f->outputs[i]!=NULL)
{
if (output_count == 0)
{
outm = im;
}
else
{
/* Создаем легкую копию сообщения. */
outm = dupmsg(im);
}
/* Помещаем копию или оригинал входного сообщения на очередной
* выход фильтра. */
ms_queue_put(f->outputs[i], outm);
output_count++;
}
}
}
}
/*----------------------------------------------------------*/
/* Функция-обработчик вызова метода NASH_FILTER_SET_LAG. */
static int nash_filter_set_treshold(MSFilter *f, void *arg)
{
nash_filterData *d=(nash_filterData*)f->data;
d->skz_level=*(float*)arg;
return 0;
}
/*----------------------------------------------------------*/
/* Функция-обработчик вызова метода NASH_FILTER_TUNE_OFF. */
static int nash_filter_tune_off(MSFilter *f, void *arg)
{
nash_filterData *d=(nash_filterData*)f->data;
d->disable_out=TRUE;
return 0;
}
/*----------------------------------------------------------*/
/* Функция-обработчик вызова метода NASH_FILTER_TUNE_ON. */
static int nash_filter_tune_on(MSFilter *f, void *arg)
{
nash_filterData *d=(nash_filterData*)f->data;
d->disable_out=FALSE;
return 0;
}
/*----------------------------------------------------------*/
/* Заполняем таблицу методов фильтра, сколько методов
мы определили в заголовочном файле столько ненулевых
строк. */
static MSFilterMethod nash_filter_methods[]={
{ NASH_FILTER_SET_TRESHOLD, nash_filter_set_treshold },
{ NASH_FILTER_TUNE_OFF, nash_filter_tune_off },
{ NASH_FILTER_TUNE_ON, nash_filter_tune_on },
{ 0 , NULL } /* Маркер конца таблицы. */
};
/*----------------------------------------------------------*/
/* Описание фильтра для медиастримера. */
MSFilterDesc nash_filter_desc=
{
NASH_FILTER_ID,
"NASH_FILTER",
"A filter with noise gate that reads from input and copy to it's five outputs.",
MS_FILTER_OTHER,
NULL,
1,
NASH_FILTER_NOUTPUTS,
nash_filter_init,
NULL,
nash_filter_process,
NULL,
nash_filter_uninit,
nash_filter_methods
};
MS_FILTER_DESC_EXPORT(nash_filter_desc)
Now, without further ado, let’s apply our filter to the previously created intercom device. The main image shows the diagram of the modified intercom device.
We wanted to depict our handmade filter in a particularly bright way. Therefore, you will immediately find our filter in the diagram.
The diagram added a recording filter that writes the input signal to a wav file. The idea is that our filter will not write pauses in speech to the file, thus reducing its size.
At the beginning of the article, we described the filter's action algorithm. In the main application, the processing of events generated by it is performed. If the event contains a flag "0", the main application pauses the recording. As soon as an event with the flag "1" comes, the recording resumes.
Two more command-line arguments have been added to the previous ones: --ng, which sets the filter's trigger threshold level and --rec, which starts recording to a file named record.wav.
/* Файл mstest9.c Имитатор переговорного устройства c регистратором и
* нойзгейтом. */
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/msrtp.h>
#include <mediastreamer2/msfilerec.h>
/* Подключаем наш фильтр. */
#include "nash_filter.h"
/* Подключаем файл общих функций. */
#include "mstest_common.c"
/*----------------------------------------------------------*/
struct _app_vars
{
int local_port; /* Локальный порт. */
int remote_port; /* Порт переговорного устройства на удаленном компьютере. */
char remote_addr[128]; /* IP-адрес удаленного компьютера. */
MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
MSFilter* recorder; /* Указатель на фильтр регистратор. */
bool_t file_is_open; /* Флаг того, что файл для записи открыт. */
/* Порог, при котором прекращается запись принимаемого сигнала в файл. */
float treshold;
bool_t en_rec; /*Включить запись в файл.*/
};
typedef struct _app_vars app_vars;
/*----------------------------------------------------------*/
/* Создаем дуплексную RTP-сессию. */
RtpSession* create_duplex_rtp_session(app_vars v)
{
RtpSession *session = create_rtpsession (v.local_port, v.local_port + 1,
FALSE, RTP_SESSION_SENDRECV);
rtp_session_set_remote_addr_and_port(session, v.remote_addr, v.remote_port,
v.remote_port + 1);
rtp_session_set_send_payload_type(session, PCMU);
return session;
}
/*----------------------------------------------------------*/
/* Функция преобразования аргументов командной строки в
* настройки программы. */
void scan_args(int argc, char *argv[], app_vars *v)
{
char i;
for (i=0; i<argc; i++)
{
if (!strcmp(argv[i], "--help"))
{
char *p=argv[0]; p=p + 2;
printf(" %s walkie talkienn", p);
printf("--help List of options.n");
printf("--version Version of application.n");
printf("--addr Remote abonent IP address string.n");
printf("--port Remote abonent port number.n");
printf("--lport Local port number.n");
printf("--gen Generator frequency.n");
printf("--ng Noise gate treshold level from 0. to 1.0n");
printf("--rec record to file 'record.wav'.n");
exit(0);
}
if (!strcmp(argv[i], "--version"))
{
printf("0.1n");
exit(0);
}
if (!strcmp(argv[i], "--addr"))
{
strncpy(v->remote_addr, argv[i+1], 16);
v->remote_addr[16]=0;
printf("remote addr: %sn", v->remote_addr);
}
if (!strcmp(argv[i], "--port"))
{
v->remote_port=atoi(argv[i+1]);
printf("remote port: %in", v->remote_port);
}
if (!strcmp(argv[i], "--lport"))
{
v->local_port=atoi(argv[i+1]);
printf("local port : %in", v->local_port);
}
if (!strcmp(argv[i], "--gen"))
{
v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);
printf("gen freq : %in", v -> dtmf_cfg.frequencies[0]);
}
if (!strcmp(argv[i], "--ng"))
{
v -> dtmf_cfg.frequencies[0] = atoi(argv[i+1]);
printf("noise gate treshold: %fn", v -> treshold);
}
if (!strcmp(argv[i], "--rec"))
{
v -> en_rec = TRUE;
printf("enable recording: %in", v -> en_rec);
}
}
}
/*----------------------------------------------------------*/
/* Функция обратного вызова, она будет вызвана фильтром, как только он
* заметит, что наступила тишина или наоборот тишина сменилась звуками. */
static void change_detected_cb(void *data, MSFilter *f, unsigned int event_id,
NASHFilterEvent *ev)
{
app_vars *vars = (app_vars*) data;
/* Если запись не была разрешена, то выходим. */
if (! vars -> en_rec) return;
if (ev -> state)
{
/* Возобновляем запись. */
if(!vars->file_is_open)
{
ms_filter_call_method(vars->recorder, MS_FILE_REC_OPEN, "record.wav");
vars->file_is_open = 1;
}
ms_filter_call_method(vars->recorder, MS_FILE_REC_START, 0);
printf("Recording...n");
}
else
{
/* Приостанавливаем запись. */
ms_filter_call_method(vars->recorder, MS_FILE_REC_STOP, 0);
printf("Pause...n");
}
}
/*----------------------------------------------------------*/
int main(int argc, char *argv[])
{
/* Устанавливаем настройки по умолчанию. */
app_vars vars={5004, 7010, "127.0.0.1", {0}, 0, 0, 0.01, 0};
/* Устанавливаем настройки настройки программы в
* соответствии с аргументами командной строки. */
scan_args(argc, argv, &vars);
ms_init();
/* Создаем экземпляры фильтров передающего тракта. */
MSSndCard *snd_card =
ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
MSFilter *snd_card_read = ms_snd_card_create_reader(snd_card);
MSFilter *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);
MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);
/* Создаем фильтр кодера. */
MSFilter *encoder = ms_filter_create_encoder("PCMU");
/* Регистрируем типы нагрузки. */
register_payloads();
/* Создаем дуплексную RTP-сессию. */
RtpSession* rtp_session = create_duplex_rtp_session(vars);
ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, rtp_session);
/* Соединяем фильтры передатчика. */
ms_filter_link(snd_card_read, 0, dtmfgen, 0);
ms_filter_link(dtmfgen, 0, encoder, 0);
ms_filter_link(encoder, 0, rtpsend, 0);
/* Создаем фильтры приемного тракта. */
MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);
ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rtp_session);
/* Создаем фильтр декодера. */
MSFilter *decoder=ms_filter_create_decoder("PCMU");
//MS_FILE_REC_ID
/* Регистрируем наш фильтр. */
ms_filter_register(&nash_filter_desc);
MSFilter *nash = ms_filter_new(NASH_FILTER_ID);
/* Создаем фильтр звуковой карты. */
MSFilter *snd_card_write = ms_snd_card_create_writer(snd_card);
/* Создаем фильтр регистратора. */
MSFilter *recorder=ms_filter_new(MS_FILE_REC_ID);
vars.recorder = recorder;
/* Соединяем фильтры приёмного тракта. */
ms_filter_link(rtprecv, 0, decoder, 0);
ms_filter_link(decoder, 0, nash, 0);
ms_filter_link(nash, 0, snd_card_write, 0);
ms_filter_link(nash, 1, recorder, 0);
/* Подключаем к фильтру функцию обратного вызова, и передаем ей в
* качестве пользовательских данных указатель на структуру с настройками
* программы, в которой среди прочих есть указать на фильтр
* регистратора. */
ms_filter_set_notify_callback(nash,
(MSFilterNotifyFunc)change_detected_cb, &vars);
ms_filter_call_method(nash,NASH_FILTER_SET_TRESHOLD, &vars.treshold);
/* Создаем источник тактов - тикер. */
MSTicker *ticker = ms_ticker_new();
/* Подключаем источник тактов. */
ms_ticker_attach(ticker, snd_card_read);
ms_ticker_attach(ticker, rtprecv);
/* Если настройка частоты генератора отлична от нуля, то запускаем генератор. */
if (vars.dtmf_cfg.frequencies[0])
{
/* Настраиваем структуру, управляющую выходным сигналом генератора. */
vars.dtmf_cfg.duration = 10000;
vars.dtmf_cfg.amplitude = 1.0;
}
/* Организуем цикл перезапуска генератора. */
printf("Press ENTER to exit.n ");
char c=getchar();
while(c != 'n')
{
if(vars.dtmf_cfg.frequencies[0])
{
/* Включаем звуковой генератор. */
ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
(void*)&vars.dtmf_cfg);
}
char c=getchar();
printf("--n");
}
if (vars.en_rec ) ms_filter_call_method(recorder, MS_FILE_REC_CLOSE, 0);
}
Due to the addition of files and the use of the library math, the command line for compilation has become more complex and looks like this:
$ gcc mstest9.c nash_filter.c -o mstest9 `pkg-config mediastreamer --libs --cflags` -lmAfter building the application, we run it on the first computer with the following arguments:
$ ./mstest9 --lport 7010 --port 8010 --addr --recOn the second computer, we run it with the following settings:
$ ./mstest9 --lport 8010 --port 7010 --addr After this, the first computer will start recording everything you say into the microphone of the second one. Meanwhile, the console will display the word "Recording…". As soon as you stop speaking, the recording will be paused with the message "Pause…". You may need to experiment with the threshold level.
In this article, we learned how to write filters. As you may have noticed, the function nash_filter_process() manipulates data blocks. Since this is a learning example, it utilized a minimal set of media streamer capabilities for data block manipulation.
In the next We will look at the organization of message queues and message management functions, which will further help develop filters with more complex information processing.
Source: habr.com
