Istražujemo VoIP engine Mediastreamer2. Dio 9

Materijal u članku preuzet je sa mog zen kanala.

Duplexno komunikacijsko uređaj

Istražujemo VoIP engine Mediastreamer2. Dio 9

V prejšnjem članku Duplexno komunikacijsko uređaj je najavljeno, a u ovom ćemo ga napraviti.

Šema je prikazana na naslovnoj ilustraciji. Donji lanac filtera formira prenosni trakt, koji počinje od zvučne kartice. Ona izbacuje uzorke signala sa mikrofona. Po defaultu se to dešava u tempu od 8000 uzoraka u sekundi. Bittna razlučivost koju koriste zvučni filteri medijskog strimer-a je 16 bita (nije ključno, ako želite, možete napisati filtere koji će raditi sa većom razlučivosti). Podaci su grupisani u blokove od 160 uzoraka. Tako svaki blok ima veličinu od 320 bajta. Zatim, podatke šaljemo na ulaz generatora, koji u isključenom stanju "prolazi" podatke. Dodao sam ga za slučaj kada vam tokom debagovanja dosadi da govorite u mikrofon — moći ćete iskoristiti generator da "pucate" kroz trakt tonalnim signalom.

Nakon generatora, signal odlazi do enkodera, koji pretvara naših 16-bitnih uzoraka prema µ-zakonu (standard G.711) u osmobitne. Na izlazu enkodera već imamo blok podataka polovine veličine. U opštem slučaju, možemo prenositi podatke bez kompresije, ako ne trebamo štedeti saobraćaj. No ovde je korisno iskoristiti enkoder, jer Wireshark može reprodukovati zvuk iz RTP toka samo kada je kompresovan prema µ-zakonu ili a-zakonu.

Nakon enkodera, olakšani blokovi podataka idu do filtera rtpsend, koji će ih staviti u RTP paket, postaviti potrebne zastavice i proslediti ih medijskom strimeru za prenos putem mreže u formi UDP paketa.

Gornji lanac filtera formira prijemni trakt, RTP paketi, koje medijski strimer prima iz mreže, idu na filter rtprecv, na izlazu kojeg se pojavljuju već u formi blokova podataka, od kojih svaki odgovara jednom prihvaćenom paketu. Blok sadrži samo podatke korisnog opterećenja, u prošlom članku su na ilustraciji prikazani zelenom bojom.

Zatim blokovi idu do dekoder filtera, koji pretvara sadržane u njima jednonitne uzorke u linearne, 16-bitne. Koji se već mogu obraditi kroz filtere medijskog strimer-a. U našem slučaju, jednostavno ih šaljemo na zvučnu karticu, za reprodukciju na zvučnicima vaše slušalice.

Sedaj preidimo na programsko izvedbo. Za to bomo združili datoteke sprejemnika in oddajnika, ki smo jih prej ločili. Prej smo uporabljali fiksne nastavitve vrat in naslovov, vendar zdaj moramo omogočiti, da program uporabi nastavitve, ki jih bomo navedli ob zagonu. Za to bomo dodali funkcionalnost obdelave argumentov ukazne vrstice. Po tem bomo lahko določili IP-naslov in vrata komunikacijskega naprave, s katero želimo vzpostaviti povezavo.

Najprej bomo dodali v program strukturo, ki bo shranjevala njene nastavitve:

struct _app_vars
{
  int  local_port;              /* Lokalna vrata. */
  int  remote_port;             /* Vrata komunikacijske naprave na oddaljenem računalniku. */
  char remote_addr[128];        /* IP-naslov oddaljenega računalnika. */
  MSDtmfGenCustomTone dtmf_cfg; /* Nastavitve testnega signala generatorja. */
};

typedef struct _app_vars app_vars;

V programu bo razglašena struktura tega tipa z imenom vars.
Nato dodamo funkcijo za razčlenitev 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členitve bodo argumenti ukazne vrstice vstavljeni v polja strukture vars. Glavna funkcija aplikacije bo sestavila prenosni in sprejemni sistem iz filtrov, po povezavi bo upravljanje predano neskončnemu ciklu, ki, če je bila frekvenca generatorja nastavljena na nič, ponovno zažene testni generator — da deluje neprekinjeno.

Ti ponovni zagonji bodo potrebni generatorju zaradi njegove posebnosti izgradnje, nekako ne more oddajati signala daljšega od 16 sekund. Pri tem je treba opaziti, da je trajanje določeno s 32-bitnim številom.

Program bo v celoti 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);
    }
}

Kompiliramo. Nato lahko program zaženete na dveh računalnikih. Ali na enem, kot bom storil zdaj. Zaženite TShark s naslednjimi argumenti:

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

Če se v konzoli po zagonu prikaže samo sporočilo o začetku zajema, je to dober znak - to pomeni, da naš port verjetno ni zaseden z drugimi programi. V drugem terminalu zaženite primer programa, ki bo posnemal "oddaljeno" komunikacijsko napravo, ter navedite to številko vrat:

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

Kot je razvidno iz besedila programa, se privzeto uporablja IP-naslov 127.0.0.1 (lokalna zanka).

В другом терминале запускаем второй экземпляр программы, который эмулирует локальное устройство. Используем дополнительный аргумент, позволяющий запуск встроенного тестового генератора:

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

В этот момент в консоли TShark должны начать появляться пакеты, передаваемые на 'удаленное' устройство, а из динамиков компьютера будет доноситься непрерывный тон.

Если все прошло правильно, перезапускаем второй экземпляр программы, уже без ключа и аргумента '--gen 440'. Теперь генератором будете выступать вы. После этого можно пошуметь в микрофон, в динамиках или наушниках вы должны будете услышать соответствующий звук. Возможно возникновение акустического самовозбуждения, убавьте громкость динамиков, и эффект исчезнет.

Если вы запускали на двух компьютерах и не запутались в IP-адресах, то вас ждет тот же результат — двусторонняя речевая связь цифрового качества.

В следующей статье мы научимся писать свои собственные фильтры — плагины, благодаря этому навыку вы сможете использовать медиастример не только для аудио и видео, но и в каких-то других специфических областях.

Izvor: habr.com

Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki 🔥 Kupite zanesljivo gostovanje za spletne strani z zaščito pred DDoS, VPS VDS strežniki | ProHoster