ការរុករកម៉ាស៊ីន Mediastreamer2 VoIP ។ ផ្នែកទី 10

សម្ភារៈនៃអត្ថបទគឺយកពីខ្ញុំ ប៉ុស្តិ៍ zen.

ក្នុង​អតីតកាល អត្ថបទ យើងបានបង្កើត duplex intercom ដែលផ្លាស់ប្តូរសញ្ញាសំឡេងតាមរយៈវគ្គ RTP ពីរ។ នៅក្នុងអត្ថបទនេះ យើងនឹងរៀនពីរបៀបសរសេរតម្រង និងបន្ថែមតម្រង DIY ទៅ intercom DIY ។

យើងកំពុងបង្កើតកម្មវិធីជំនួយ

ការរុករកម៉ាស៊ីន Mediastreamer2 VoIP ។ ផ្នែកទី 10

កម្មវិធីជំនួយនៅក្នុងកម្មវិធីផ្សាយមេឌៀ ដូចនៅក្នុងកម្មវិធីជាច្រើនផ្សេងទៀត ត្រូវបានប្រើដើម្បីពង្រីកមុខងារដោយមិនចាំបាច់ចងក្រងកម្មវិធីផ្សាយមេឌៀឡើងវិញ។

ដើម្បីប្រើកម្មវិធីជំនួយក្នុងកម្មវិធីរបស់អ្នក អ្នកត្រូវប្រើ រួមបញ្ចូលទាំង ត្រូវតែរួមបញ្ចូលឯកសារបឋមកថាកម្មវិធីជំនួយ។ នៅក្នុងតួនៃកម្មវិធីដោយប្រើមុខងារ y ms_filter_register() ចុះឈ្មោះតម្រងថ្មី។ ជាធម្មតា កម្មវិធីរបស់អ្នក និងប្រភពកម្មវិធីជំនួយត្រូវតែត្រូវបានចងក្រង និងបញ្ចូលទៅក្នុងកម្មវិធីតែមួយ។

ឥឡូវនេះ ចូរយើងងាកទៅសរសេរកម្មវិធីជំនួយ។ តម្រង និងកម្មវិធីជំនួយប្រព័ន្ធផ្សព្វផ្សាយទាំងអស់ធ្វើតាម Canon ទូទៅនៅក្នុងការសរសេររបស់ពួកគេ ដែលធ្វើឱ្យវាកាន់តែងាយស្រួលយល់អំពីរចនាសម្ព័ន្ធនៃតម្រងបន្ទាប់ដែលអ្នកចង់សិក្សា។ ដូច្នេះ បន្ថែមទៀត ដើម្បីកុំឱ្យគុណធាតុ ខ្ញុំនឹងហៅកម្មវិធីជំនួយតម្រង។

ចូរនិយាយថាយើងចង់បង្កើតតម្រងថ្មីមួយដែលមានឈ្មោះថា NASH_FILTR ។ វានឹងធ្វើរឿងសាមញ្ញមួយ - ទទួលប្លុកពីការបញ្ចូលតែមួយរបស់វា ហើយបញ្ជូនវាទៅលទ្ធផលទាំងប្រាំរបស់វា។ វាក៏នឹងបង្កើតព្រឹត្តិការណ៍មួយផងដែរ ប្រសិនបើប្លុកច្រើនជាងប្រាំដែលមានកម្រិតសញ្ញាទាបជាងកម្រិតដែលបានផ្តល់ឱ្យឆ្លងកាត់វា ហើយប្រសិនបើច្រើនជាងប្រាំប្លុកដែលមានកម្រិតសញ្ញាលើសពីកម្រិតកំណត់ឆ្លងកាត់វា វាក៏នឹងបង្កើតព្រឹត្តិការណ៍ផងដែរ។

កម្រិតនឹងត្រូវបានកំណត់ដោយប្រើវិធីសាស្ត្រតម្រង។ វិធីសាស្រ្តទីពីរ និងទីបីនឹងអនុញ្ញាត/ហាមឃាត់ការឆ្លងកាត់ប្លុកទៅកាន់ច្រកចេញ។

តោះ​ចាប់ផ្តើម។ នៅពេលសរសេរតម្រង អ្នកត្រូវចាប់ផ្តើមជាមួយឯកសារបឋមកថា។ នៅក្នុងជួរទីមួយវាគួរតែរួមបញ្ចូលឯកសារ msfilter.hដោយប្រើម៉ាក្រូ MS_FILTER_METHOD ប្រកាសវិធីសាស្រ្តនៃតម្រងថ្មី (ប្រសិនបើមាន) ប្រកាសព្រឹត្តិការណ៍ដែលបង្កើតដោយតម្រង (ប្រសិនបើមាន) និងប្រកាសរចនាសម្ព័ន្ធដែលបាននាំចេញនៃប្រភេទ MSFilterDesc ជាមួយនឹងការពិពណ៌នាអំពីប៉ារ៉ាម៉ែត្រតម្រង៖

/* Файл 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 */

ឥឡូវអ្នកអាចបន្តទៅឯកសារប្រភព។ កូដប្រភពសម្រាប់តម្រងដែលមានមតិយោបល់ត្រូវបានបង្ហាញខាងក្រោម។ វិធីសាស្ត្រតម្រង និងមុខងារតម្រងដែលត្រូវការត្រូវបានកំណត់នៅទីនេះ។ បន្ទាប់មកសេចក្តីយោងទៅវិធីសាស្រ្ត និងមុខងារត្រូវបានដាក់ក្នុងលំដាប់ជាក់លាក់មួយនៅក្នុងរចនាសម្ព័ន្ធដែលបាននាំចេញ our_filter_desc. ដែល​ត្រូវ​បាន​ប្រើ​ដោយ​អ្នក​ផ្សាយ​មេឌៀ​ដើម្បី "ផ្សាំ" តម្រង​ប្រភេទ​នេះ​ទៅក្នុង​លំហូរ​ការងារ​ដំណើរការ​ទិន្នន័យ។

/* Файл 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)

ឥឡូវនេះដោយគ្មានការពន្យាពេល ចូរយើងប្រើតម្រងរបស់យើងនៅក្នុង intercom ដែលយើងបានបង្កើតមុន។ រូបភាពចំណងជើងបង្ហាញពីដ្យាក្រាមនៃ intercom ដែលបានកែប្រែ។
យើងចង់ពណ៌នាតម្រងធ្វើដោយដៃរបស់យើងតាមរបៀបភ្លឺពិសេស។ ដូច្នេះអ្នកនឹងឃើញតម្រងរបស់យើងភ្លាមៗនៅក្នុងដ្យាក្រាម។

ឧបករណ៍ថតចម្លងត្រូវបានបន្ថែមទៅសៀគ្វីដែលសរសេរសញ្ញាបញ្ចូលទៅឯកសារ wav ។ ដូចដែលបានគ្រោងទុក តម្រងរបស់យើងនឹងអនុញ្ញាតឱ្យអ្នកជៀសវាងការសរសេរផ្អាកក្នុងការនិយាយទៅក្នុងឯកសារ។ ដូច្នេះកាត់បន្ថយទំហំរបស់វា។
នៅដើមអត្ថបទ យើងបានពណ៌នាអំពីក្បួនដោះស្រាយរបស់តម្រង។ កម្មវិធីចម្បងគ្រប់គ្រងព្រឹត្តិការណ៍ដែលវាបង្កើត។ ប្រសិនបើព្រឹត្តិការណ៍មានទង់ "0" នោះកម្មវិធីម៉ាស៊ីននឹងផ្អាកការថត។ ដរាបណាព្រឹត្តិការណ៍ដែលមានទង់ “1” មកដល់ ការថតនឹងបន្ត។

អាគុយម៉ង់​បន្ទាត់​ពាក្យ​បញ្ជា​ពីរ​ទៀត​ត្រូវ​បាន​បន្ថែម​ទៅ​ចំណុច​មុន​ៗ៖ --ngដែលកំណត់កម្រិតកម្រិតតម្រង និង --recដែលចាប់ផ្តើមសរសេរទៅឯកសារដែលហៅថា 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);
}

ដោយសារតែយើងបន្ថែមឯកសារ និងប្រើប្រាស់បណ្ណាល័យ គណិតវិទ្យាបន្ទាត់ពាក្យបញ្ជាសម្រាប់ការចងក្រងកាន់តែស្មុគស្មាញ ហើយមើលទៅដូចនេះ៖

$ gcc mstest9.c nash_filter.c -o mstest9   `pkg-config mediastreamer   --libs --cflags`  -lm

បន្ទាប់ពីបង្កើតកម្មវិធី សូមដំណើរការវានៅលើកុំព្យូទ័រដំបូងដែលមានអាគុយម៉ង់ដូចខាងក្រោម៖

$ ./mstest9  --lport 7010  --port 8010 --addr <тут адрес второго компьютера> --rec

នៅលើកុំព្យូទ័រទីពីរ យើងបើកដំណើរការជាមួយការកំណត់ដូចខាងក្រោម៖

$ ./mstest9  --lport 8010  --port 7010 --addr <тут адрес первого компьютера>

បន្ទាប់ពីនេះកុំព្យូទ័រទីមួយនឹងចាប់ផ្តើមកត់ត្រាអ្វីគ្រប់យ៉ាងដែលអ្នកនិយាយទៅក្នុងមីក្រូហ្វូនទីពីរ។ ក្នុងករណីនេះពាក្យ "កំពុងថត…"។ ដរាបណាអ្នកស្ងាត់ ការថតនឹងត្រូវបានផ្អាកជាមួយនឹងសារដែលបង្ហាញ"ផ្អាក ...msgstr "អ្នក​ប្រហែល​ជា​ត្រូវ​ធ្វើ​ការ​ពិសោធន៍​ជាមួយ​កម្រិត​កម្រិត​ចាប់ផ្ដើម។

នៅក្នុងអត្ថបទនេះ យើងបានរៀនពីរបៀបសរសេរតម្រង។ ដូចដែលអ្នកប្រហែលជាបានកត់សម្គាល់ មុខងារ nash_filter_process() ដំណើរការឧបាយកលជាមួយប្លុកទិន្នន័យ។ ដោយសារឧទាហរណ៍នេះមានលក្ខណៈអប់រំ សមត្ថភាពអប្បបរមារបស់ឧបករណ៍ផ្សាយព័ត៌មានសម្រាប់រៀបចំប្លុកទិន្នន័យត្រូវបានប្រើប្រាស់។

បន្ទាប់ អត្ថបទ យើង​នឹង​ពិនិត្យ​មើល​មុខងារ​តម្រង់​ជួរ​សារ និង​មុខងារ​គ្រប់គ្រង​សារ។ វានឹងជួយនៅពេលអនាគតដើម្បីអភិវឌ្ឍតម្រងជាមួយនឹងដំណើរការព័ត៌មានស្មុគស្មាញបន្ថែមទៀត។

ប្រភព: www.habr.com

ទិញការបង្ហោះដែលអាចទុកចិត្តបានសម្រាប់គេហទំព័រដែលមានការការពារ DDoS, ម៉ាស៊ីនមេ VPS VDS 🔥 ទិញសេវាបង្ហោះគេហទំព័រដែលអាចទុកចិត្តបានជាមួយនឹងការការពារ DDoS និងម៉ាស៊ីនមេ VPS VDS | ProHoster