Esplorante la Mediastreamer2 VoIP-motoron. Parto 9

La materialo de la artikolo estas prenita el mia zen kanalo.

Dupleksa interfono

Esplorante la Mediastreamer2 VoIP-motoron. Parto 9

En la lasta artikolo oni anoncis dupleksa interfono, kaj en ĉi tiu ni faros ĝin.

La diagramo estas montrita en la titolfiguro. La pli malalta ĉeno de filtriloj formas la dissendvojon, kiu komenciĝas de la sonkarto. Ĝi provizas signalajn specimenojn de la mikrofono. Defaŭlte, tio okazas kun rapideco de 8000 specimenoj je sekundo. La datumbitprofundo kiun uzas amaskomunikilaj streamer sonfiltriloj estas 16 bitoj (ĉi tio ne gravas; se vi volas, vi povas skribi filtrilojn kiuj funkcios kun pli alta bitprofundo). La datumoj estas grupigitaj en blokojn de 160 specimenoj. Tiel, ĉiu bloko estas 320 bajtoj en grandeco. Poste, ni nutras la datumojn al la enigo de la generatoro, kiu, kiam malŝaltita, estas "travidebla" al la datumoj. Mi aldonis ĝin se vi laciĝos paroli en la mikrofonon dum senararigado - vi povas uzi la generatoron por "pafi" la vojon per tonsignalo.

Post la generatoro, la signalo iras al la kodilo, kiu konvertas niajn 16-bitajn specimenojn laŭ la µ-leĝo (G.711 normo) en ok-bitajn. Ĉe la eligo de la kodilo, ni jam havas datumblokon duono de la grandeco. Ĝenerale, ni povas transdoni datumojn sen kunpremo se ni ne bezonas ŝpari trafikon. Sed ĉi tie estas utile uzi kodilon, ĉar Wireshark povas reprodukti aŭdion de RTP-fluo nur kiam ĝi estas kunpremita laŭ la µ-leĝo aŭ a-leĝo.

Post la kodilo, la pli malpezaj blokoj de datumoj estas senditaj al la rtpsend-filtrilo, kiu metos ilin en RTP-pakon, starigos la necesajn flagojn kaj donos ilin al la amaskomunikilaro por transdono tra la reto en la formo de UDP-pako.

La supra ĉeno de filtriloj formas la ricevan vojon; RTP-pakoj ricevitaj de la amaskomunikilaro de la reto eniras la rtprecv-filtrilon, ĉe kies eligo ili aperas en la formo de datumblokoj, ĉiu el kiuj egalrilatas al unu ricevita pakaĵeto. La bloko enhavas nur utilajn datumojn; en la antaŭa artikolo ili estis montritaj verde en la ilustraĵo.

Poste, la blokoj estas senditaj al la malĉifrilo, kiu konvertas la unubajtajn specimenojn enhavitajn en ili en liniajn, 16-bitajn. Kiu jam povas esti procesita per amaskomunikiloj streamer filtriloj. En nia kazo, ni simple sendas ilin al la sonkarto por reproduktado sur la laŭtparoliloj de via aŭdilo.

Nun ni transiru al programaro efektivigo. Por fari tion, ni kombinos la ricevilo- kaj dissendilo dosierojn, kiujn ni antaŭe apartigis. Antaŭ tio, ni uzis fiksajn agordojn por havenoj kaj adresoj, sed nun ni bezonas la programon por povi uzi la agordojn, kiujn ni specifas ĉe ekfunkciigo. Por fari tion, ni aldonus funkciojn por prilaborado de komandliniaj argumentoj. Post tio ni povos agordi la IP-adreson kaj havenon de la interfono kun kiu ni volas establi konekton.

Unue, ni aldonu strukturon al la programo, kiu stokos ĝiajn agordojn:

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

typedef struct _app_vars app_vars;

La programo deklaros strukturon de ĉi tiu tipo nomita vars.
Poste, ni aldonu funkcion por analizi komandliniajn argumentojn:

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

Kiel rezulto de analizado, la komandliniaj argumentoj estos metitaj en la kampojn de la vars-strukturo. La ĉefa funkcio de la aplikaĵo estos kolekti elsendajn kaj ricevajn vojojn de filtriloj; post konekto de la teletajpilo, kontrolo estos transdonita al senfina buklo kiu, se la generatora frekvenco estis agordita al ne-nula, rekomencos la testan generatoron tiel ke ĝi funkcias sen halto.

La generatoro bezonos ĉi tiujn rekomencojn pro sia dezajno; ial ĝi ne povas produkti signalon daŭrantan pli ol 16 sekundojn. Oni devas rimarki, ke ĝia daŭro estas specifita per 32-bita nombro.

La tuta programo aspektos jene:

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

Ni kompilu. Tiam la programo povas ruliĝi sur du komputiloj. Aŭ sur unu, kiel mi faros nun. Ni lanĉas TShark kun la sekvaj argumentoj:

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

Se la lanĉa kampo en la konzolo nur montras mesaĝon pri la komenco de kapto, tiam ĉi tio estas bona signo - tio signifas, ke nia haveno plej verŝajne ne estas okupata de aliaj programoj. En alia terminalo, ni lanĉas program-instancon, kiu simulos "foran" interfonon specifante ĉi tiun havenon:

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

Kiel videblas el la programo teksto, la defaŭlta IP-adreso estas 127.0.0.1 (loka loopback).

En alia terminalo, ni lanĉas duan okazon de la programo, kiu simulas lokan aparaton. Ni uzas kroman argumenton, kiu ebligas al la enkonstruita testgeneratoro funkcii:

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

En ĉi tiu momento, pakaĵoj elsenditaj al la "fora" aparato devus komenci ekbrili en la konzolo kun TShark, kaj kontinua tono aŭdiĝos de la komputila laŭtparolilo.

Se ĉio okazis kiel skribite, tiam ni rekomencas la duan kopion de la programo, sed sen la ŝlosilo kaj argumento "—gen 440". Vi nun ludos la rolon de generatoro. Post tio, vi povas brui en la mikrofonon; vi devus aŭdi la respondan sonon en la laŭtparolilo aŭ aŭdiloj. Akustika mem-ekscito eĉ povas okazi; malaltigu la laŭtparolilan volumon kaj la efiko malaperos.

Se vi kuris ĝin sur du komputiloj kaj ne konfuziĝis pri la IP-adresoj, tiam la sama rezulto atendas vin - dudirekta cifereca kvalita voĉa komunikado.

En la sekva artikolo ni lernos kiel skribi niajn proprajn filtrilojn - kromaĵojn, danke al ĉi tiu lerteco vi povos uzi la amaskomunikilaron ne nur por audio kaj video, sed ankaŭ en iu alia specifa areo.

fonto: www.habr.com

Aldoni komenton