Studujeme VoIP engine Mediastreamer2. Část 9

Materiál článku je převzat z mého zenového kanálu.

Duplexní konferenční zařízení

Studujeme VoIP engine Mediastreamer2. Část 9

V minulosti článku Bylo oznámeno duplexní konferenční zařízení, a v této části ho vyrobíme.

Schéma je znázorněno na úvodním obrázku. Spodní okruh filtrů tvoří vysílací cestu, která začíná zvukovou kartou. Ta vydává vzorky signálu z mikrofonu. Ve výchozím nastavení se to děje tempem 8000 vzorků za sekundu. Bitová hloubka dat, které používají zvukové filtry mediastreamera, je 16 bitů (to není zásadní, pokud chcete, můžete napsat filtry, které budou pracovat s větší bitovou hloubkou). Data jsou seskupena do bloků po 160 vzorcích. Každý blok má tedy velikost 320 bajtů. Následně posíláme data do generátoru, který je v vypnutém stavu pro data "průhledný". Přidal jsem ho pro případ, že byste se při ladění unavili mluvit do mikrofonu — můžete využít generátor k "protržení" traktu tonálním signálem.

Po generátoru signál přichází do kodéru, který převádí naše 16-bitové vzorky podle µ-zákona (standard G.711) na osmibitové. Na výstupu kodéru máme už blok dat o poloviční velikosti. Obecně můžeme přenášet data bez komprese, pokud nám není potřeba šetřit provoz. Ale zde je užitečné využít kodér, protože Wireshark dokáže reprodukovat zvuk z RTP streamu pouze tehdy, když je komprimován podle µ-zákona nebo a-zákona.

Po kodéru, zmenšené bloky dat přicházejí na filtr rtpsend, který je vloží do RTP paketu, nastaví potřebné příznaky a předá je mediastreameru k přenosu po síti ve formě UDP paketu.

Horní okruh filtrů tvoří přijímací cestu, RTP pakety, které mediastreamer přijímá ze sítě, přicházejí do filtru rtprecv, na výstupu kterého se objevují jako bloky dat, z nichž každý odpovídá jednomu přijatému paketu. Blok obsahuje pouze data užitečné zátěže, v předchozím článku na ilustraci byly zobrazeny zelenou barvou.

Následně bloky přicházejí do filtru dekodéru, který převádí jedno- bytelné vzorky na lineární, 16-bitové. Ty už lze zpracovávat filtry mediastreamera. V našem případě je jednoduše posíláme na zvukovou kartu pro přehrání na reproduktorech vaší sluchátkové soupravy.

Nyní přejdeme k programové implementaci. Pro to zkombinujeme soubory přijímače a vysílače, které jsme dříve oddělili. Předtím jsme používali pevně nastavené porty a adresy, ale nyní potřebujeme, aby program mohl používat nastavení, která uvedeme při spuštění. K tomu přidáme funkcionalitu pro zpracování argumentů příkazového řádku. Po tom budeme moci specifikovat IP adresu a port jednacího zařízení, se kterým chceme navázat spojení.

Nejprve přidáme do programu strukturu, která bude uchovávat jeho nastavení:

struct _app_vars
{
  int  local_port;              /* Místní port. */
  int  remote_port;             /* Port jednacího zařízení na vzdáleném počítači. */
  char remote_addr[128];        /* IP adresa vzdáleného počítače. */
  MSDtmfGenCustomTone dtmf_cfg; /* Nastavení testovacího signálu generátoru. */
};

typedef struct _app_vars app_vars;

Ve programu bude deklarována struktura tohoto typu s názvem vars.
Dále přidáme funkci pro analýzu argumentů příkazového řádku:

/* Функция преобразования аргументов командной строки в
* настройки программы. */
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 budou argumenty příkazového řádku uloženy do polí struktury vars. Hlavní funkce aplikace sestaví z filtrů vysílací a přijímací kanály, po připojení tickeru bude ovládání předáno do nekonečné smyčky, která, pokud byla frekvence generátoru nastavena na nenulovou hodnotu, znovu spustí testovací generátor — aby pracoval nepřetržitě.

Tyto restartování budou generátoru potřebná kvůli jeho konstrukčnímu zajištění, z nějakého důvodu nedokáže vydávat signál déle než 16 sekund. Je třeba poznamenat, že délka je určena 32bitovým číslem.

Celý program bude vypadat 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);
    }
}

Kompilujeme. Dále lze program spustit na dvou počítačích. Nebo na jednom, jak to nyní udělám. Spouštíme TShark s následujícími argumenty:

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

Pokud se v poli spuštění na konzoli objeví pouze zpráva o zahájení zachytávání, je to dobré znamení — to znamená, že náš port pravděpodobně není obsazen jinými programy. V jiném terminálu spouštíme instanci programu, která bude simulovat "vzdálené" jednací zařízení a uvedeme mu toto číslo portu:

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

Jak je z textu programu vidět, ve výchozím nastavení se používá IP adresa 127.0.0.1 (místní smyčka).

V dalším terminálu spouštíme druhou instanci programu, která imituje místní zařízení. Používáme další argument, který umožňuje práci vestavěného testovacího generátoru:

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

V tuto chvíli by se v konzoli s TShark měly začít objevovat pakety přenášené směrem k "vzdálenému" zařízení a ze reproduktoru počítače se ozve nepřetržitý tonální signál.

Pokud se vše událo tak, jak bylo psáno, restartujeme druhou instanci programu, ale už bez klíče a argumentu "--gen 440". V tuto chvíli budete hrát roli generátoru vy. Poté můžete povídat do mikrofonu, ve reproduktoru nebo sluchátkách byste měli slyšet odpovídající zvuk. Může dokonce dojít k akustickému samovzbuzení, snižte hlasitost reproduktoru a efekt zmizí.

Pokud jste spouštěli na dvou počítačích a nezapletli se v IP adresách, čeká vás stejný výsledek — obousměrná řečová komunikace digitální kvality.

V dalším článku se naučíme psát své vlastní filtry — pluginy, díky této dovednosti budete moci použít mediastreamer nejen pro zvuk a video, ale i v nějaké jiné specifické oblasti.

Zdroj: habr.com

Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery 🔥 Koupit spolehlivý hosting pro webové stránky s ochranou proti DDoS, VPS VDS servery | ProHoster