Raziskovanje mehanizma VoIP Mediastreamer2. 9. del

Gradivo članka je vzeto iz mojega zen kanal.

Duplex domofon

Raziskovanje mehanizma VoIP Mediastreamer2. 9. del

V preteklosti članek napovedan je bil duplex domofon in v tem ga bomo naredili.

Diagram je prikazan na naslovni sliki. Spodnja veriga filtrov tvori prenosno pot, ki se začne od zvočne kartice. Zagotavlja vzorce signala iz mikrofona. Privzeto se to zgodi s hitrostjo 8000 vzorcev na sekundo. Podatkovna bitna globina, ki jo uporabljajo zvočni filtri media streamerja, je 16 bitov (to ni pomembno; če želite, lahko napišete filtre, ki bodo delovali z višjo bitno globino). Podatki so združeni v bloke po 160 vzorcev. Tako je vsak blok velik 320 bajtov. Nato podamo podatke na vhod generatorja, ki je, ko je izklopljen, "transparenten" za podatke. Dodal sem ga, če se med odpravljanjem napak naveličate govoriti v mikrofon - z generatorjem lahko "ustrelite" pot s tonskim signalom.

Po generatorju gre signal v kodirnik, ki naše 16-bitne vzorce po µ-zakonu (standard G.711) pretvori v osembitne. Na izhodu kodirnika že imamo za polovico manjši podatkovni blok. Na splošno lahko prenašamo podatke brez stiskanja, če nam ni treba varčevati s prometom. Toda tukaj je koristno uporabiti kodirnik, saj Wireshark lahko reproducira zvok iz toka RTP le, če je stisnjen v skladu z µ-zakonom ali a-zakonom.

Po kodirniku se lažji bloki podatkov pošljejo filtru rtpsend, ki jih postavi v paket RTP, nastavi potrebne zastavice in jih posreduje medijskemu pretakalniku za prenos po omrežju v obliki paketa UDP.

Zgornja veriga filtrov tvori sprejemno pot; RTP paketi, ki jih medijski streamer prejme iz omrežja, vstopijo v filter rtprecv, na izhodu katerega se pojavijo v obliki podatkovnih blokov, od katerih vsak ustreza enemu prejetemu paketu. Blok vsebuje samo podatke o tovoru, v prejšnjem članku so bili na sliki prikazani zeleno.

Nato se bloki pošljejo filtru dekoderja, ki pretvori enobajtne vzorce v njih v linearne, 16-bitne. Kar je že mogoče obdelati s filtri za pretakanje medijev. V našem primeru jih preprosto pošljemo na zvočno kartico za predvajanje na zvočnikih vaših slušalk.

Zdaj pa preidimo na implementacijo programske opreme. Da bi to naredili, bomo združili datoteke sprejemnika in oddajnika, ki smo jih prej ločili. Pred tem smo uporabljali fiksne nastavitve za vrata in naslove, zdaj pa potrebujemo, da program lahko uporablja nastavitve, ki jih določimo ob zagonu. Da bi to naredili, bi dodali funkcionalnost za obdelavo argumentov ukazne vrstice. Po tem bomo lahko nastavili IP naslov in vrata domofona s katerim želimo vzpostaviti povezavo.

Najprej dodajmo programu strukturo, ki bo shranila njegove nastavitve:

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

typedef struct _app_vars app_vars;

Program bo deklariral strukturo te vrste, imenovano vars.
Nato dodajmo funkcijo za razčlenjevanje argumentov ukazne vrstice:

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

Kot rezultat razčlenjevanja bodo argumenti ukazne vrstice postavljeni v polja strukture vars. Glavna funkcija aplikacije bo zbiranje oddajnih in sprejemnih poti iz filtrov; po priklopu tickerja bo krmiljenje preneseno na neskončno zanko, ki bo, če je bila frekvenca generatorja nastavljena na nič, ponovno zagnala testni generator, tako da deluje brez ustavljanja.

Generator bo potreboval te ponovne zagone zaradi svoje zasnove; iz nekega razloga ne more proizvesti signala, ki traja več kot 16 sekund. Upoštevati je treba, da je njegovo trajanje določeno z 32-bitnim številom.

Celoten program bo izgledal takole:

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

Sestavimo. Nato lahko program izvajamo na dveh računalnikih. Ali pa na enem, kot bom naredil zdaj. TShark zaženemo z naslednjimi argumenti:

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

Če se v zagonskem polju v konzoli prikaže samo sporočilo o začetku zajemanja, je to dober znak - pomeni, da naša vrata najverjetneje niso zasedena z drugimi programi. V drugem terminalu zaženemo primerek programa, ki bo simuliral "oddaljeni" domofon z navedbo te številke vrat:

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

Kot je razvidno iz besedila programa, je privzeti naslov IP 127.0.0.1 (lokalna povratna zanka).

V drugem terminalu zaženemo drugo instanco programa, ki simulira lokalno napravo. Uporabimo dodaten argument, ki omogoča delovanje vgrajenega testnega generatorja:

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

V tem trenutku bi morali paketi, poslani proti "oddaljeni" napravi, začeti utripati v konzoli s TSharkom, iz zvočnika računalnika pa se bo slišal neprekinjen ton.

Če se je vse zgodilo tako, kot je napisano, potem ponovno zaženemo drugo kopijo programa, vendar brez ključa in argumenta “—gen 440”. Zdaj boste igrali vlogo generatorja. Po tem lahko povzročite hrup v mikrofon; ustrezen zvok bi morali slišati v zvočniku ali slušalkah. Lahko pride celo do akustičnega samovzbujanja; zmanjšajte glasnost zvočnika in učinek bo izginil.

Če ste ga zagnali na dveh računalnikih in se niste zmedli glede naslovov IP, potem vas čaka enak rezultat - dvosmerna glasovna komunikacija digitalne kakovosti.

V naslednjem članku se bomo naučili, kako napisati lastne filtre - vtičnike, zahvaljujoč tej spretnosti boste lahko uporabljali media streamer ne samo za zvok in video, ampak tudi na nekaterih drugih specifičnih področjih.

Vir: www.habr.com

Dodaj komentar