Artiklens materiale er hentet fra min
I fortiden
Vi er ved at udvikle et plugin
Plugins i mediestreameren, som i mange andre programmer, bruges til at udvide funktionaliteten uden at skulle genkompilere selve mediestreameren.
For at bruge pluginnet i dit program, bruger du omfatter skal inkludere plugin-header-filen. I programmets krop, ved hjælp af funktionen y ms_filter_register() registrere et nyt filter. Naturligvis skal dit program og plugin-kilden kompileres og samles i én applikation.
Lad os nu gå til at skrive et plugin. Alle mediestreamerfiltre og plugins følger en fælles kanon i deres skrivning, hvilket gør det meget nemmere at forstå strukturen af det næste filter, som du vil studere. Derfor vil jeg yderligere, for ikke at multiplicere entiteter, kalde plugins-filtre.
Lad os sige, at vi vil udvikle et nyt filter kaldet NASH_FILTR. Det vil gøre en simpel ting - modtage blokke fra dens enkelte input og sende den til dens fem udgange. Den vil også generere en hændelse, hvis mere end fem blokke med et signalniveau under en given tærskel passerer gennem den, og hvis mere end fem blokke med et signalniveau over tærsklen passerer gennem den, vil den også generere en hændelse.
Tærsklen indstilles ved hjælp af filtermetoden. Den anden og tredje metode vil tillade/forbyde passage af blokke til udgangene.
Lad os komme igang. Når du skriver et filter, skal du starte med en header-fil. I de første linjer skal den inkludere filen msfilter.h, ved hjælp af MS_FILTER_METHOD makroen, erklærer metoderne for det nye filter (hvis nogen), erklærer hændelser genereret af filteret (hvis nogen) og erklærer den eksporterede struktur af typen MSFilterDesc med en beskrivelse af filterparametrene:
/* Файл 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 */
Nu kan du gå videre til kildefilen. Kildekoden til filteret med kommentarer er vist nedenfor. Filtermetoder og nødvendige filterfunktioner er defineret her. Derefter placeres referencer til metoder og funktioner i en bestemt rækkefølge i den eksporterede struktur vores_filter_desc. Som bruges af mediestreameren til at "implantere" filtre af denne type i databehandlingens arbejdsgang.
/* Файл 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)
Lad os nu uden forsinkelse bruge vores filter i det samtaleanlæg, vi lavede tidligere. Titelbilledet viser et diagram over et modificeret samtaleanlæg.
Vi ønskede at skildre vores håndlavede filter på en særlig lys måde. Derfor finder du straks vores filter i diagrammet.
En filter-recorder er blevet tilføjet til kredsløbet, som skriver inputsignalet til en wav-fil. Som planlagt giver vores filter dig mulighed for at undgå at skrive pauser i tale ind i filen. Dermed reduceres dens størrelse.
I begyndelsen af artiklen beskrev vi filterets algoritme. Hovedapplikationen håndterer de hændelser, den genererer. Hvis hændelsen indeholder "0"-flaget, sætter værtsapplikationen optagelsen på pause. Så snart en begivenhed med flag "1" ankommer, genoptages optagelsen.
Yderligere to kommandolinjeargumenter er blevet tilføjet til de foregående: --ng
, som indstiller filtertærskelniveauet og --rec
som begynder at skrive til en fil kaldet 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);
}
På grund af det faktum, at vi tilføjede filer og brugte biblioteket matematik, er kommandolinjen til kompilering blevet mere kompliceret og ser sådan ud:
$ gcc mstest9.c nash_filter.c -o mstest9 `pkg-config mediastreamer --libs --cflags` -lm
Når du har bygget programmet, skal du køre det på den første computer med følgende argumenter:
$ ./mstest9 --lport 7010 --port 8010 --addr <тут адрес второго компьютера> --rec
På den anden computer starter vi med følgende indstillinger:
$ ./mstest9 --lport 8010 --port 7010 --addr <тут адрес первого компьютера>
Herefter begynder den første computer at optage alt, hvad du siger i den andens mikrofon. I dette tilfælde er ordet "Indspilning…". Så snart du bliver stille, vil optagelsen blive sat på pause med en meddelelse vist"Pause..."Du skal muligvis eksperimentere med tærskelniveauet.
I denne artikel lærte vi, hvordan man skriver filtre. Som du måske har bemærket, udfører funktionen nash_filter_process() manipulationer med datablokke. Da eksemplet er uddannelsesmæssigt, blev mediestreamerens minimumskapacitet til at manipulere datablokke brugt.
Næste
Kilde: www.habr.com