Истраживање Медиастреамер2 ВоИП механизма. Део 9

Материјал чланка је преузет са мог зен канал.

Дуплекс интерфон

Истраживање Медиастреамер2 ВоИП механизма. Део 9

У последњих Чланак најављен је дуплекс интерфон и у овом ћемо га направити.

Дијаграм је приказан на насловној слици. Доњи ланац филтера формира пут преноса, који почиње од звучне картице. Обезбеђује узорке сигнала са микрофона. Подразумевано, ово се дешава брзином од 8000 узорака у секунди. Дубина бита података коју користе аудио филтери медијског стримера је 16 бита (ово није важно; ако желите, можете написати филтере који ће радити са већом дубином бита). Подаци су груписани у блокове од 160 узорака. Дакле, сваки блок је величине 320 бајтова. Затим уносимо податке на улаз генератора, који је, када је искључен, "транспарентан" за податке. Додао сам га у случају да се уморите од разговора у микрофон током отклањања грешака - можете користити генератор да „пуцате“ путању тонским сигналом.

Након генератора, сигнал иде до енкодера, који конвертује наше 16-битне узорке према µ-закону (Г.711 стандард) у осмобитне. На излазу енкодера већ имамо блок података упола мањи. Генерално, можемо да преносимо податке без компресије ако не треба да штедимо саобраћај. Али овде је корисно користити енкодер, пошто Виресхарк може да репродукује звук из РТП тока само када је компримован према µ-закону или а-закону.

Након енкодера, лакши блокови података се шаљу у ртпсенд филтер, који ће их ставити у РТП пакет, поставити потребне заставице и дати их медијском стримеру за пренос преко мреже у облику УДП пакета.

Горњи ланац филтера формира пут пријема; РТП пакети које медијски стример прими из мреже улазе у ртпрецв филтер, на чијем се излазу појављују у облику блокова података, од којих сваки одговара једном примљеном пакету. Блок садржи само податке о корисном учитавању; у претходном чланку они су били приказани зеленом бојом на илустрацији.

Затим се блокови шаљу филтеру декодера, који конвертује једнобајтне узорке садржане у њима у линеарне, 16-битне. Које већ могу да обрађују филтери за медијски стример. У нашем случају, једноставно их шаљемо на звучну картицу за репродукцију на звучницима ваших слушалица.

Сада пређимо на имплементацију софтвера. Да бисмо то урадили, комбинућемо датотеке пријемника и предајника које смо претходно раздвојили. Пре овога смо користили фиксна подешавања за портове и адресе, али сада нам је потребно да програм може да користи подешавања која наведемо при покретању. Да бисмо то урадили, додали бисмо функционалност за обраду аргумената командне линије. Након чега ћемо моћи да подесимо ИП адресу и порт интерфона са којим желимо да успоставимо везу.

Прво, додајмо структуру програму која ће чувати своја подешавања:

struct _app_vars
{
  int  local_port;              /* Локальный порт. */
  int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */
  char remote_addr[128];        /* IP-адрес удаленного компьютера. */
  MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
};

typedef struct _app_vars app_vars;

Програм ће декларисати структуру овог типа која се зове варс.
Затим, додајмо функцију за рашчлањивање аргумената командне линије:

/* Функция преобразования аргументов командной строки в
* настройки программы. */
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");
            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]);
        }
    }
}

Као резултат рашчлањивања, аргументи командне линије биће смештени у поља структуре варс. Главна функција апликације ће бити прикупљање путева за пренос и пријем од филтера; након повезивања тикера, контрола ће се пренети на бесконачну петљу која ће, ако је фреквенција генератора подешена на различиту од нуле, поново покренути тест генератор тако да ради без престанка.

Генератору ће бити потребна ова рестартовања због свог дизајна; из неког разлога не може произвести сигнал који траје дуже од 16 секунди. Треба напоменути да је његово трајање одређено 32-битним бројем.

Цео програм ће изгледати овако:

/* Файл mstest8.c Имитатор переговорного устройства. */

#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/msrtp.h>

/* Подключаем файл общих функций. */
#include "mstest_common.c"

/*----------------------------------------------------------*/
struct _app_vars
{
    int  local_port;              /* Локальный порт. */
    int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */
    char remote_addr[128];        /* IP-адрес удаленного компьютера. */
    MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
};

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");
            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]);
        }
    }
}

/*----------------------------------------------------------*/
int main(int argc, char *argv[])
{
    /* Устанавливаем настройки по умолчанию. */
    app_vars vars={5004, 7010, "127.0.0.1", {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");

    /* Создаем фильтр звуковой карты. */
    MSFilter *snd_card_write = ms_snd_card_create_writer(snd_card);

    /* Соединяем фильтры приёмного тракта. */
    ms_filter_link(rtprecv, 0, decoder, 0);
    ms_filter_link(decoder, 0,  snd_card_write, 0);

    /* Создаем источник тактов - тикер. */
    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;
    }

    /* Организуем цикл перезапуска генератора. */
    while(TRUE)
    {
        if(vars.dtmf_cfg.frequencies[0])
        {
            /* Включаем звуковой генератор. */
            ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
                    (void*)&vars.dtmf_cfg);
        }
        /* Укладываем тред в спячку на 20мс, чтобы другие треды
         * приложения получили время на работу. */
        ms_usleep(20000);
    }
}

Хајде да саставимо. Тада се програм може покренути на два рачунара. Или на једном, као што ћу сада да урадим. Покрећемо ТСхарк са следећим аргументима:

$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -x

Ако поље за покретање у конзоли приказује само поруку о почетку снимања, онда је то добар знак - то значи да наш порт највероватније није заузет другим програмима. У другом терминалу покрећемо инстанцу програма која ће симулирати „удаљени“ интерфон навођењем овог броја порта:

$ ./mstest8 --port 9010 --lport 7010

Као што се види из текста програма, подразумевана ИП адреса је 127.0.0.1 (локална петља).

У другом терминалу покрећемо другу инстанцу програма, која симулира локални уређај. Користимо додатни аргумент који омогућава да уграђени тест генератор ради:

$ ./mstest8  --port 7010 --lport 9010 --gen 440

У овом тренутку, пакети који се преносе ка „даљинском“ уређају би требало да почну да трепћу у конзоли са ТСхарком, а из звучника рачунара ће се чути непрекидни тон.

Ако се све догодило како је написано, онда поново покрећемо другу копију програма, али без кључа и аргумента „—ген 440“. Сада ћете играти улогу генератора. Након тога можете правити буку у микрофон; требало би да чујете одговарајући звук у звучнику или слушалицама. Може се чак појавити и акустична самоузбуда; утишајте јачину звучника и ефекат ће нестати.

Ако сте га покренули на два рачунара и нисте се збунили око ИП адреса, онда вас чека исти резултат - двосмерна дигитална квалитетна гласовна комуникација.

У следећем чланку ћемо научити како да напишемо сопствене филтере - додатке, захваљујући овој вештини моћи ћете да користите медиа стреамер не само за аудио и видео, већ иу некој другој специфичној области.

Извор: ввв.хабр.цом

Додај коментар