Að kanna Mediastreamer2 VoIP vélina. 9. hluti

Efni greinarinnar er tekið úr mínum zen rás.

Tvíhliða kallkerfi

Að kanna Mediastreamer2 VoIP vélina. 9. hluti

Í fortíðinni grein tilkynnt var um tvíhliða kallkerfi og í þessum munum við gera það.

Skýringarmyndin er sýnd á titilmyndinni. Neðri keðjan af síum myndar flutningsleiðina sem byrjar frá hljóðkortinu. Það veitir merkissýni úr hljóðnemanum. Sjálfgefið er að þetta gerist á hraðanum 8000 sýni á sekúndu. Gagnabitadýptin sem hljóðsíur fjölmiðlastrauma nota er 16 bitar (þetta er ekki mikilvægt; ef þú vilt geturðu skrifað síur sem virka með meiri bitadýpt). Gögnin eru flokkuð í blokkir með 160 sýnum. Þannig er hver blokk 320 bæti að stærð. Næst færum við gögnin í inntak rafallsins, sem, þegar slökkt er á því, er „gagnsætt“ fyrir gögnin. Ég bætti því við ef þú verður þreyttur á að tala í hljóðnemann meðan á kembiforritinu stendur - þú getur notað rafallinn til að „skjóta“ leiðina með tónmerki.

Eftir rafallinn fer merkið til umritarans, sem breytir 16 bita sýnum okkar samkvæmt µ-lögmálinu (G.711 staðlinum) í átta bita. Við úttak kóðarans höfum við nú þegar gagnablokk sem er helmingi stærri. Almennt getum við sent gögn án þjöppunar ef við þurfum ekki að spara umferð. En hér er gagnlegt að nota kóðara, þar sem Wireshark getur endurskapað hljóð frá RTP straumi aðeins þegar það er þjappað í samræmi við µ-lögmálið eða a-lögmálið.

Eftir kóðann eru léttari gagnablokkirnar sendar í rtpsend síuna, sem mun setja þær í RTP pakka, setja nauðsynlega fána og gefa þeim til fjölmiðlastraumarans til sendingar yfir netið í formi UDP pakka.

Efri keðjan af síum myndar móttökuleiðina; RTP pakkar sem miðlunarstraumarinn tekur á móti frá netinu fara inn í rtprecv síuna, við úttakið sem þeir birtast í formi gagnablokka, sem hver um sig samsvarar einum mótteknum pakka. Kubburinn inniheldur aðeins gagnaflutningsgögn; í fyrri greininni voru þau sýnd með grænu á myndinni.

Næst eru kubbarnir sendar í afkóðarasíuna, sem breytir einsbæta sýnunum sem eru í þeim í línuleg, 16 bita. Sem er nú þegar hægt að vinna með fjölmiðlastraumsíum. Í okkar tilfelli sendum við þau einfaldlega á hljóðkortið til að spila á hátölurum heyrnartólsins.

Nú skulum við halda áfram að innleiðingu hugbúnaðar. Til að gera þetta munum við sameina móttakara- og sendiskrárnar sem við aðskildum áður. Fyrir þetta notuðum við fastar stillingar fyrir port og vistföng, en núna þurfum við forritið til að geta notað þær stillingar sem við tilgreinum við ræsingu. Til að gera þetta myndum við bæta við virkni til að vinna úr skipanalínurökum. Eftir það munum við geta stillt IP tölu og tengi kallkerfisins sem við viljum koma á tengingu við.

Í fyrsta lagi skulum við bæta uppbyggingu við forritið sem mun geyma stillingar þess:

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

typedef struct _app_vars app_vars;

Forritið mun lýsa yfir uppbyggingu af þessari gerð sem kallast vars.
Næst skulum við bæta við falli til að flokka skipanalínurök:

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

Sem afleiðing af þáttun verða skipanalínuröksemdirnar settar á reiti vars uppbyggingarinnar. Meginhlutverk forritsins verður að safna sendingar- og móttökuleiðum úr síum; eftir að merkið hefur verið tengt verður stjórnin færð yfir í óendanlega lykkju sem, ef rafalltíðnin var stillt á ekki núll, mun endurræsa prófunarrafalinn þannig að það virkar án þess að stoppa.

Rafallinn mun þurfa þessar endurræsingar vegna hönnunar hans; af einhverjum ástæðum getur hann ekki framkallað merki sem varir lengur en 16 sekúndur. Það skal tekið fram að lengd þess er tilgreind með 32 bita tölu.

Allt forritið mun líta svona út:

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

Við skulum setja saman. Þá er hægt að keyra forritið á tveimur tölvum. Eða á einum, eins og ég mun gera núna. Við ræsum TShark með eftirfarandi rökum:

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

Ef sjósetningarreiturinn í stjórnborðinu sýnir aðeins skilaboð um upphaf töku, þá er þetta gott merki - það þýðir að höfnin okkar er líklega ekki upptekin af öðrum forritum. Í annarri flugstöð ræsum við forritstilvik sem líkir eftir „fjarlægri“ kallkerfi með því að tilgreina þetta gáttarnúmer:

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

Eins og sést á forritstextanum er sjálfgefið IP-tala 127.0.0.1 (local loopback).

Í annarri flugstöð ræsum við annað tilvik af forritinu, sem líkir eftir staðbundnu tæki. Við notum viðbótarrök sem gerir innbyggða prófunarrafallnum kleift að virka:

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

Á þessari stundu ættu pakkar sem sendir eru í átt að „fjarlæga“ tækinu að byrja að blikka í stjórnborðinu með TShark og samfelldur tónn mun heyrast frá tölvuhátalaranum.

Ef allt gerðist eins og skrifað er, endurræsum við annað eintakið af forritinu, en án lykilsins og röksemdarinnar „—gen 440“. Þú munt nú gegna hlutverki rafalls. Eftir þetta geturðu gert hávaða í hljóðnemanum; þú ættir að heyra samsvarandi hljóð í hátalara eða heyrnartólum. Hljóðræn sjálfsörvun getur jafnvel átt sér stað; lækkaðu hljóðstyrk hátalarans og áhrifin hverfa.

Ef þú keyrðir það á tveimur tölvum og ruglast ekki á IP tölunum, þá bíður þín sama niðurstaða - tvíhliða stafræn gæði raddsamskipta.

Í næstu grein munum við læra hvernig á að skrifa okkar eigin síur - viðbætur, þökk sé þessari kunnáttu muntu geta notað fjölmiðlastrauminn ekki aðeins fyrir hljóð og mynd, heldur einnig á einhverju öðru sérstöku svæði.

Heimild: www.habr.com

Bæta við athugasemd