„Mediastreamer2 VoIP“ variklio tyrinėjimas. 9 dalis

Straipsnio medžiaga paimta iš mano zen kanalas.

Dvipusis domofonas

„Mediastreamer2 VoIP“ variklio tyrinėjimas. 9 dalis

Paskutiniame straipsnis buvo paskelbtas dvipusis domofonas, o šiuo mes jį padarysime.

Diagrama parodyta pavadinimo paveikslėlyje. Apatinė filtrų grandinė sudaro perdavimo kelią, kuris prasideda nuo garso plokštės. Jis pateikia signalo pavyzdžius iš mikrofono. Pagal numatytuosius nustatymus tai vyksta 8000 mėginių per sekundę greičiu. Duomenų bitų gylis, kurį naudoja medijos srauto garso filtrai, yra 16 bitų (tai nėra svarbu; jei norite, galite rašyti filtrus, kurie veiks su didesniu bitų gyliu). Duomenys sugrupuoti į 160 pavyzdžių blokus. Taigi kiekvienas blokas yra 320 baitų dydžio. Toliau duomenis pateikiame į generatoriaus įvestį, kuri išjungus yra „skaidri“ duomenims. Pridėjau jį tam atvejui, jei derinimo metu pavargtumėte kalbėti į mikrofoną - galite naudoti generatorių, kad „nušautumėte“ kelią toniniu signalu.

Po generatoriaus signalas patenka į kodavimo įrenginį, kuris pagal µ dėsnį (G.16 standartas) paverčia mūsų 711 bitų mėginius į aštuonių bitų. Kodavimo įrenginio išvestyje jau turime perpus mažesnį duomenų bloką. Apskritai galime perduoti duomenis be suspaudimo, jei mums nereikia taupyti srauto. Tačiau čia naudinga naudoti kodavimo įrenginį, nes „Wireshark“ gali atkurti garsą iš RTP srauto tik tada, kai jis yra suspaustas pagal µ-dėsnį arba a-dėsnį.

Po kodavimo lengvesni duomenų blokai siunčiami į rtpsend filtrą, kuris juos įdės į RTP paketą, nustatys reikiamas vėliavėles ir atiduos medijos transliuotojui perduoti tinklu UDP paketo pavidalu.

Viršutinė filtrų grandinė sudaro priėmimo kelią; RTP paketai, kuriuos medijos srautas gauna iš tinklo, patenka į rtprecv filtrą, kurio išvestyje jie pasirodo duomenų blokų pavidalu, kurių kiekvienas atitinka vieną gautą paketą. Bloke yra tik naudingos apkrovos duomenys; ankstesniame straipsnyje jie iliustracijoje buvo parodyti žalia spalva.

Toliau blokai siunčiami į dekoderio filtrą, kuris juose esančius vieno baito pavyzdžius paverčia tiesiniais, 16 bitų. Kurią jau gali apdoroti medijos srauto filtrai. Mūsų atveju mes tiesiog siunčiame juos į garso plokštę, kad būtų galima atkurti jūsų ausinių garsiakalbiuose.

Dabar pereikime prie programinės įrangos diegimo. Norėdami tai padaryti, sujungsime imtuvo ir siųstuvo failus, kuriuos anksčiau atskirėme. Prieš tai naudojome fiksuotus prievadų ir adresų nustatymus, bet dabar mums reikia, kad programa galėtų naudoti nustatymus, kuriuos nurodome paleidžiant. Norėdami tai padaryti, pridėtume komandinės eilutės argumentų apdorojimo funkciją. Po to galėsime nustatyti domofono, su kuriuo norime užmegzti ryšį, IP adresą ir prievadą.

Pirma, pridėkime prie programos struktūrą, kurioje bus saugomi jos nustatymai:

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

typedef struct _app_vars app_vars;

Programa paskelbs tokio tipo struktūrą, vadinamą vars.
Tada pridėkime funkciją, skirtą komandinės eilutės argumentams analizuoti:

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

Dėl analizės komandinės eilutės argumentai bus patalpinti į vars struktūros laukus. Pagrindinė programos funkcija bus surinkti siuntimo ir priėmimo kelius iš filtrų; prijungus stulpelį, valdymas bus perkeltas į begalinę kilpą, kuri, jei generatoriaus dažnis buvo nustatytas į nulį, iš naujo paleis bandomąjį generatorių, kad veikia be perstojo.

Generatorių reikės paleisti iš naujo dėl jo konstrukcijos; dėl tam tikrų priežasčių jis negali sukurti signalo, trunkančio ilgiau nei 16 sekundžių. Reikėtų pažymėti, kad jo trukmė nurodoma 32 bitų skaičiumi.

Visa programa atrodys taip:

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

Sukompiliuokime. Tada programa gali būti paleista dviejuose kompiuteriuose. Arba ant vieno, kaip dabar darysiu. Mes paleidžiame TShark su šiais argumentais:

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

Jei paleidimo lauke konsolėje rodomas tik pranešimas apie fiksavimo pradžią, tai geras ženklas – tai reiškia, kad mūsų prievadas greičiausiai nėra užimtas kitų programų. Kitame terminale paleidžiame programos egzempliorių, kuris imituos „nuotolinį“ domofoną, nurodydamas šį prievado numerį:

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

Kaip matyti iš programos teksto, numatytasis IP adresas yra 127.0.0.1 (local loopback).

Kitame terminale paleidžiame antrąjį programos egzempliorių, kuris imituoja vietinį įrenginį. Naudojame papildomą argumentą, leidžiantį veikti integruotam bandymų generatoriui:

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

Šiuo metu paketai, perduodami į „nuotolinį“ įrenginį, turėtų pradėti mirksėti konsolėje su TShark, o iš kompiuterio garsiakalbio bus girdimas nuolatinis tonas.

Jei viskas atsitiko taip, kaip parašyta, iš naujo paleidžiame antrąją programos kopiją, bet be rakto ir argumento „—gen 440“. Dabar atliksite generatoriaus vaidmenį. Po to galite kelti triukšmą į mikrofoną; turėtumėte išgirsti atitinkamą garsą garsiakalbyje arba ausinėse. Gali atsirasti net akustinis savaiminis sužadinimas; sumažinkite garsiakalbio garsumą ir efektas išnyks.

Jei paleidote jį dviejuose kompiuteriuose ir nesusipainiojote dėl IP adresų, jūsų laukia tas pats rezultatas - dvipusis skaitmeninės kokybės balso ryšys.

Kitame straipsnyje išmoksime rašyti savo filtrus – įskiepius, šio įgūdžio dėka galėsite naudoti medijos transliuotoją ne tik garsui ir vaizdo įrašams, bet ir kitoje konkrečioje srityje.

Šaltinis: www.habr.com

Добавить комментарий