Mediastreamer2 VoIP-mootori uurimine. 9. osa

Artikli materjal on võetud minu zen kanal.

Dupleks sisetelefon

Mediastreamer2 VoIP-mootori uurimine. 9. osa

Viimases siit teatati dupleks-intercomist ja sellega saame hakkama.

Diagramm on näidatud pealkirja joonisel. Alumine filtrite ahel moodustab edastustee, mis algab helikaardilt. See pakub mikrofoni signaalinäidiseid. Vaikimisi toimub see kiirusega 8000 proovi sekundis. Andmebittide sügavus, mida meediastriimi helifiltrid kasutavad, on 16 bitti (see pole oluline; soovi korral saate kirjutada filtreid, mis töötavad suurema bitisügavusega). Andmed on rühmitatud 160 proovist koosnevateks plokkideks. Seega on iga ploki suurus 320 baiti. Järgmisena sisestame andmed generaatori sisendisse, mis väljalülitatuna on andmetele “läbipaistev”. Lisasin selle juhuks, kui te tüdinete silumise ajal mikrofoni rääkimisest - saate generaatori abil helisignaaliga rada "tulistada".

Peale generaatorit läheb signaal kodeerijasse, mis teisendab meie 16-bitised näidised vastavalt µ-seadusele (G.711 standard) kaheksabitisteks. Kodeerija väljundis on meil juba poole väiksem andmeplokk. Üldiselt saame andmeid edastada ilma tihendamiseta, kui meil pole vaja liiklust säästa. Kuid siin on kasulik kasutada kodeerijat, kuna Wireshark suudab RTP-voost heli reprodutseerida ainult siis, kui see on tihendatud vastavalt µ-seadusele või a-seadusele.

Pärast kodeerijat saadetakse kergemad andmeplokid rtpsend-filtrisse, mis paneb need RTP-paketti, seab vajalikud lipud ja annab need UDP-paketi kujul üle võrgu edastamiseks meediumistriimile.

Vastuvõtutee moodustab filtrite ülemine ahel; võrgust meediastriimija poolt vastu võetud RTP-paketid sisenevad rtprecv-filtrisse, mille väljundis ilmuvad need andmeplokkidena, millest igaüks vastab ühele vastuvõetud paketile. Plokk sisaldab ainult kasuliku koormuse andmeid, eelmises artiklis näidati neid illustratsioonil roheliselt.

Järgmisena saadetakse plokid dekoodri filtrisse, mis muundab neis sisalduvad ühebaidised näidised lineaarseteks, 16-bitisteks. Mida saab meediumivoofiltritega juba töödelda. Meie puhul saadame need lihtsalt peakomplekti kõlarites taasesitamiseks helikaardile.

Liigume nüüd edasi tarkvara juurutamise juurde. Selleks ühendame varem eraldatud vastuvõtja ja saatja failid. Enne seda kasutasime portide ja aadresside jaoks fikseeritud sätteid, kuid nüüd on meil vaja, et programm saaks kasutada käivitamisel määratud sätteid. Selleks lisame funktsiooni käsurea argumentide töötlemiseks. Pärast seda saame määrata selle sisetelefoni IP-aadressi ja pordi, millega soovime ühenduse luua.

Esmalt lisame programmile struktuuri, mis salvestab selle sätted:

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

typedef struct _app_vars app_vars;

Programm deklareerib seda tüüpi struktuuri nimega vars.
Järgmisena lisame käsurea argumentide sõelumiseks funktsiooni:

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

Parsimise tulemusena paigutatakse käsurea argumendid vars-struktuuri väljadele. Rakenduse põhiülesanne on koguda filtritelt saate- ja vastuvõtuteed, pärast tickeri ühendamist kantakse juhtimine üle lõpmatusse ahelasse, mis kui generaatori sagedus on seatud nullist erinevale, taaskäivitab testgeneraatori nii, et see töötab peatumata.

Generaator vajab neid taaskäivitusi oma konstruktsiooni tõttu, miskipärast ei saa see anda üle 16 sekundi kestvat signaali. Tuleb märkida, et selle kestus on määratud 32-bitise numbriga.

Kogu programm näeb välja selline:

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

Koostame. Seejärel saab programmi käivitada kahes arvutis. Või ühel, nagu ma nüüd teen. Käivitame TSharki järgmiste argumentidega:

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

Kui konsooli käivitusväljal kuvatakse teade ainult hõivamise alguse kohta, on see hea märk – see tähendab, et meie porti ei ole suure tõenäosusega hõivatud teiste programmidega. Teises terminalis käivitame programmieksemplari, mis simuleerib "kaugtelefoni", määrates selle pordi numbri:

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

Nagu programmi tekstist näha, on vaikimisi IP-aadress 127.0.0.1 (local loopback).

Teises terminalis käivitame programmi teise eksemplari, mis simuleerib kohalikku seadet. Kasutame täiendavat argumenti, mis võimaldab sisseehitatud testigeneraatoril töötada:

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

Sel hetkel peaksid TSharkiga konsoolis hakkama vilkuma "kaugseadme" suunas edastatavad paketid ja arvuti kõlarist kostab pidev heli.

Kui kõik juhtus nii, nagu kirjutatud, siis taaskäivitame programmi teise eksemplari, kuid ilma võtme ja argumendita “—gen 440”. Nüüd hakkate mängima generaatori rolli. Pärast seda saate mikrofoni müra teha, kõlarist või kõrvaklappidest peaksite kuulma vastavat heli. Võib isegi tekkida akustiline eneseergastus; keerake kõlari helitugevus madalamaks ja efekt kaob.

Kui kasutasite seda kahes arvutis ja ei sattunud IP-aadresside pärast segadusse, siis ootab teid sama tulemus – kahesuunaline digitaalse kvaliteediga kõneside.

Järgmises artiklis õpime, kuidas kirjutada oma filtreid - pluginaid, tänu sellele oskusele saate kasutada meediumivooge mitte ainult heli ja video jaoks, vaid ka mõnes muus konkreetses valdkonnas.

Allikas: www.habr.com

Lisa kommentaar