Skúmanie enginu Mediastreamer2 VoIP. Časť 9

Materiál článku je prevzatý z môjho zenový kanál.

Duplexný interkom

Skúmanie enginu Mediastreamer2 VoIP. Časť 9

V poslednom článok bol ohlásený duplexný interkom a v tomto to zvládneme.

Schéma je znázornená na obrázku v názve. Spodný reťazec filtrov tvorí prenosovú cestu, ktorá začína od zvukovej karty. Poskytuje vzorky signálu z mikrofónu. Štandardne sa to deje rýchlosťou 8000 16 vzoriek za sekundu. Bitová hĺbka údajov, ktorú používajú zvukové filtre streamovania médií, je 160 bitov (toto nie je dôležité, ak chcete, môžete napísať filtre, ktoré budú pracovať s vyššou bitovou hĺbkou). Údaje sú zoskupené do blokov po 320 vzorkách. Každý blok má teda veľkosť XNUMX bajtov. Ďalej privádzame údaje na vstup generátora, ktorý, keď je vypnutý, je pre údaje „transparentný“. Pridal som to pre prípad, že by vás omrzelo hovoriť do mikrofónu počas ladenia - pomocou generátora môžete „vystreliť“ cestu tónovým signálom.

Za generátorom ide signál do kodéra, ktorý konvertuje naše 16-bitové vzorky podľa µ-law (štandard G.711) na osembitové. Na výstupe kodéra už máme dátový blok polovičnej veľkosti. Vo všeobecnosti môžeme dáta prenášať bez kompresie, ak nepotrebujeme šetriť prevádzku. Tu je však užitočné použiť kodér, pretože Wireshark dokáže reprodukovať zvuk z RTP streamu iba vtedy, keď je komprimovaný podľa µ-law alebo a-law.

Po kódovači sú ľahšie bloky dát odoslané filtru rtpsend, ktorý ich vloží do paketu RTP, nastaví potrebné príznaky a odovzdá ich streameru médií na prenos po sieti vo forme paketu UDP.

Horný reťazec filtrov tvorí prijímaciu cestu, RTP pakety prijaté media streamerom zo siete vstupujú do rtprecv filtra, na výstupe ktorého sa objavujú vo forme dátových blokov, z ktorých každý zodpovedá jednému prijatému paketu. Blok obsahuje iba údaje o užitočnom zaťažení, v predchádzajúcom článku boli na obrázku znázornené zelenou farbou.

Ďalej sú bloky odoslané do filtra dekodéra, ktorý konvertuje jednobajtové vzorky v nich obsiahnuté na lineárne, 16-bitové. Ktoré už dokážu spracovať filtre media streamerov. V našom prípade ich jednoducho odošleme na zvukovú kartu na prehrávanie na reproduktoroch vášho headsetu.

Teraz prejdime k implementácii softvéru. Aby sme to dosiahli, skombinujeme súbory prijímača a vysielača, ktoré sme predtým oddelili. Predtým sme používali pevné nastavenia pre porty a adresy, ale teraz potrebujeme, aby program mohol používať nastavenia, ktoré zadáme pri spustení. Aby sme to dosiahli, pridali by sme funkčnosť na spracovanie argumentov príkazového riadku. Potom budeme môcť nastaviť IP adresu a port interkomu, s ktorým chceme nadviazať spojenie.

Najprv do programu pridáme štruktúru, ktorá bude uchovávať jeho nastavenia:

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

typedef struct _app_vars app_vars;

Program deklaruje štruktúru tohto typu s názvom vars.
Ďalej pridajme funkciu na analýzu argumentov príkazového riadka:

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

V dôsledku analýzy budú argumenty príkazového riadka umiestnené do polí štruktúry vars. Hlavnou funkciou aplikácie bude zber vysielacích a prijímacích ciest z filtrov, po pripojení tickeru sa riadenie prenesie do nekonečnej slučky, ktorá v prípade, že je frekvencia generátora nastavená na nenulovú, reštartuje testovací generátor tak, aby funguje bez zastavenia.

Generátor bude potrebovať tieto reštarty kvôli svojej konštrukcii, z nejakého dôvodu nemôže produkovať signál trvajúci viac ako 16 sekúnd. Treba poznamenať, že jeho trvanie je určené 32-bitovým číslom.

Celý program bude vyzerať takto:

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

Poďme kompilovať. Potom je možné program spustiť na dvoch počítačoch. Alebo na jednom, ako to urobím teraz. Spúšťame TShark s nasledujúcimi argumentmi:

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

Ak sa v spúšťacom poli v konzole zobrazuje iba správa o začatí zachytávania, je to dobré znamenie - znamená to, že náš port s najväčšou pravdepodobnosťou nie je obsadený inými programami. V inom termináli spustíme inštanciu programu, ktorá bude simulovať „vzdialený“ interkom zadaním tohto čísla portu:

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

Ako je zrejmé z textu programu, predvolená IP adresa je 127.0.0.1 (lokálna slučka).

V inom termináli spustíme druhú inštanciu programu, ktorá simuluje lokálne zariadenie. Používame ďalší argument, ktorý umožňuje fungovanie vstavaného generátora testov:

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

V tomto okamihu by pakety prenášané smerom k „vzdialenému“ zariadeniu mali začať blikať v konzole s TShark a z reproduktora počítača bude počuť nepretržitý tón.

Ak sa všetko stalo tak, ako je napísané, reštartujeme druhú kópiu programu, ale bez kľúča a argumentu „—gen 440“. Teraz budete hrať úlohu generátora. Potom môžete do mikrofónu vydávať hluk, v reproduktore alebo slúchadlách by ste mali počuť zodpovedajúci zvuk. Môže dokonca dôjsť k akustickému samobudeniu, znížte hlasitosť reproduktora a efekt zmizne.

Ak ste ho spustili na dvoch počítačoch a nezamotali ste sa IP adresami, čaká vás rovnaký výsledok – obojsmerná hlasová komunikácia v digitálnej kvalite.

V ďalšom článku sa naučíme písať vlastné filtre – pluginy, vďaka tejto zručnosti budete môcť mediálny streamer využívať nielen pre audio a video, ale aj v nejakej inej špecifickej oblasti.

Zdroj: hab.com

Pridať komentár