Prozkoumání enginu Mediastreamer2 VoIP. Část 9

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

duplexní interkom

Prozkoumání enginu Mediastreamer2 VoIP. Část 9

V minulosti článek byl oznámen duplexní interkom a v tomto to zvládneme.

Schéma je znázorněno na obrázku v názvu. Spodní řetězec filtrů tvoří přenosovou cestu, která začíná od zvukové karty. Poskytuje vzorky signálu z mikrofonu. Ve výchozím nastavení k tomu dochází rychlostí 8000 vzorků za sekundu. Bitová hloubka dat, kterou zvukové filtry streameru médií používají, je 16 bitů (to není důležité, pokud chcete, můžete napsat filtry, které budou pracovat s vyšší bitovou hloubkou). Data jsou seskupena do bloků po 160 vzorcích. Každý blok má tedy velikost 320 bajtů. Dále přivádíme data na vstup generátoru, který je po vypnutí vůči datům „transparentní“. Přidal jsem to pro případ, že by vás omrzelo mluvit do mikrofonu při ladění – pomocí generátoru můžete cestu „střílet“ tónovým signálem.

Za generátorem jde signál do kodéru, který převádí naše 16bitové vzorky podle µ-law (standard G.711) na osmibitové. Na výstupu kodéru již máme datový blok poloviční velikosti. Obecně můžeme data přenášet bez komprese, pokud nepotřebujeme šetřit provoz. Zde je však užitečné použít kodér, protože Wireshark může reprodukovat zvuk z RTP streamu pouze tehdy, když je komprimován podle µ-law nebo a-law.

Po kodéru jsou lehčí bloky dat odeslány filtru rtpsend, který je vloží do RTP paketu, nastaví potřebné příznaky a předá je media streameru k přenosu po síti ve formě UDP paketu.

Horní řetězec filtrů tvoří přijímací cestu, RTP pakety přijaté media streamerem ze sítě vstupují do rtprecv filtru, na jehož výstupu se objevují ve formě datových bloků, z nichž každý odpovídá jednomu přijatému paketu. Blok obsahuje pouze údaje o užitečné zátěži, v předchozím článku byly na obrázku zobrazeny zeleně.

Dále jsou bloky odeslány do filtru dekodéru, který převádí jednobajtové vzorky v nich obsažené na lineární, 16bitové. Což už umí zpracovat media streamer filtry. V našem případě je jednoduše pošleme na zvukovou kartu pro přehrávání na reproduktorech vašeho headsetu.

Nyní přejdeme k implementaci softwaru. K tomu zkombinujeme soubory přijímače a vysílače, které jsme předtím oddělili. Předtím jsme používali pevná nastavení pro porty a adresy, ale nyní potřebujeme, aby program mohl používat nastavení, která zadáme při spuštění. K tomu bychom přidali funkcionalitu pro zpracování argumentů příkazového řádku. Poté budeme moci nastavit IP adresu a port interkomu, se kterým chceme navázat spojení.

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

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 strukturu tohoto typu nazvanou 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 umístěny do polí struktury vars. Hlavní funkcí aplikace bude sbírat vysílací a přijímací cesty z filtrů, po připojení tickeru se řízení přenese do nekonečné smyčky, která v případě nastavení frekvence generátoru na nenulovou restartuje testovací generátor tak, aby funguje bez zastavení.

Generátor bude tyto restarty potřebovat kvůli své konstrukci, z nějakého důvodu nemůže vytvořit signál trvající déle než 16 sekund. Je třeba poznamenat, že jeho trvání je určeno 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);
    }
}

Pojďme sestavit. Poté lze program spustit na dvou počítačích. Nebo na jednom, jak to teď 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 spouštěcí pole v konzole zobrazuje pouze zprávu o zahájení snímání, je to dobré znamení - znamená to, že náš port s největší pravděpodobností není obsazen jinými programy. V jiném terminálu spustíme instanci programu, která bude simulovat „vzdálený“ interkom zadáním tohoto čísla portu:

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

Jak je patrné z textu programu, výchozí IP adresa je 127.0.0.1 (local loopback).

V jiném terminálu spustíme druhou instanci programu, která simuluje lokální zařízení. Používáme další argument, který umožňuje, aby vestavěný testovací generátor fungoval:

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

V tuto chvíli by pakety přenášené směrem k „vzdálenému“ zařízení měly začít blikat v konzole s TShark a z reproduktoru počítače bude slyšet nepřetržitý tón.

Pokud se vše stalo tak, jak bylo napsáno, restartujeme druhou kopii programu, ale bez klíče a argumentu „—gen 440“. Nyní budete hrát roli generátoru. Poté můžete do mikrofonu vydávat hluk, v reproduktoru nebo sluchátkách byste měli slyšet odpovídající zvuk. Může dokonce dojít k akustickému samobuzení, snižte hlasitost reproduktoru a efekt zmizí.

Pokud jste jej spustili na dvou počítačích a nepletli se do IP adres, čeká vás stejný výsledek – obousměrná hlasová komunikace v digitální kvalitě.

V příštím článku se naučíme psát vlastní filtry – pluginy, díky této dovednosti budete moci media streamer používat nejen pro audio a video, ale i v nějaké další specifické oblasti.

Zdroj: www.habr.com

Přidat komentář