Istraživanje Mediastreamer2 VoIP motora. Dio 9

Materijal članka je preuzet sa mog zen kanal.

Duplex interfon

Istraživanje Mediastreamer2 VoIP motora. Dio 9

U poslednjem članak najavljen je duplex interfon, a u ovom ćemo ga napraviti.

Dijagram je prikazan na naslovnoj slici. Donji lanac filtera formira put prijenosa, koji počinje od zvučne kartice. Pruža uzorke signala iz mikrofona. Podrazumevano, ovo se dešava brzinom od 8000 uzoraka u sekundi. Dubina bitova podataka koju koriste audio filteri medijskog strimera je 16 bita (ovo nije važno; ako želite, možete napisati filtere koji će raditi s većom dubinom bita). Podaci su grupirani u blokove od 160 uzoraka. Dakle, svaki blok je veličine 320 bajtova. Zatim unosimo podatke na ulaz generatora, koji je, kada je isključen, "transparentan" za podatke. Dodao sam ga u slučaju da se umorite od razgovora u mikrofon tokom otklanjanja grešaka - možete koristiti generator da "pucate" putanju tonskim signalom.

Nakon generatora, signal ide u koder, koji konvertuje naše 16-bitne uzorke prema µ-zakonu (G.711 standard) u osmobitne. Na izlazu enkodera već imamo blok podataka upola manji. Općenito, možemo prenositi podatke bez kompresije ako ne trebamo uštedjeti promet. Ali ovdje je korisno koristiti koder, jer Wireshark može reproducirati zvuk iz RTP toka samo kada je komprimiran prema µ-zakonu ili a-zakonu.

Nakon enkodera, lakši blokovi podataka se šalju u rtpsend filter, koji će ih staviti u RTP paket, postaviti potrebne zastavice i dati ih medijskom streameru za prijenos preko mreže u obliku UDP paketa.

Gornji lanac filtera formira put prijema; RTP paketi koje medijski streamer primi iz mreže ulaze u rtprecv filter, na čijem se izlazu pojavljuju u obliku blokova podataka, od kojih svaki odgovara jednom primljenom paketu. Blok sadrži samo podatke o korisnom učitavanju; u prethodnom članku oni su prikazani zelenom bojom na ilustraciji.

Zatim se blokovi šalju filteru dekodera, koji konvertuje jednobajtne uzorke sadržane u njima u linearne, 16-bitne. Koje već mogu obraditi filteri medijskih strimera. U našem slučaju, jednostavno ih šaljemo na zvučnu karticu za reprodukciju na zvučnicima vaših slušalica.

Sada pređimo na implementaciju softvera. Da bismo to učinili, kombinirat ćemo datoteke prijemnika i odašiljača koje smo prije razdvojili. Prije toga smo koristili fiksne postavke za portove i adrese, ali sada nam je potrebno da program može koristiti postavke koje navedemo pri pokretanju. Da bismo to učinili, dodali bismo funkcionalnost za obradu argumenata komandne linije. Nakon čega ćemo moći da podesimo IP adresu i port interfona sa kojim želimo da uspostavimo vezu.

Prvo, dodajmo strukturu programu koja će pohraniti svoje postavke:

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

typedef struct _app_vars app_vars;

Program će deklarisati strukturu ovog tipa koja se zove vars.
Zatim, dodajmo funkciju za raščlanjivanje argumenata komandne linije:

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

Kao rezultat raščlanjivanja, argumenti komandne linije bit će smješteni u polja strukture vars. Glavna funkcija aplikacije će biti prikupljanje odašiljačkih i prijemnih puteva od filtera; nakon povezivanja tikera, kontrola će se prenijeti na beskonačnu petlju koja će, ako je frekvencija generatora postavljena na različitu od nule, ponovo pokrenuti test generator tako da radi bez prestanka.

Generatoru će biti potrebna ova ponovno pokretanje zbog svog dizajna; iz nekog razloga ne može proizvesti signal koji traje duže od 16 sekundi. Treba napomenuti da je njegovo trajanje određeno 32-bitnim brojem.

Cijeli program će izgledati ovako:

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

Hajde da kompajliramo. Tada se program može pokrenuti na dva računara. Ili na jednom, kao što ću sada učiniti. Pokrećemo TShark sa sljedećim argumentima:

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

Ako polje za pokretanje u konzoli prikazuje samo poruku o početku snimanja, onda je to dobar znak - to znači da naš port najvjerovatnije nije zauzet drugim programima. U drugom terminalu pokrećemo instancu programa koja će simulirati "udaljeni" interfon navođenjem ovog broja porta:

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

Kao što se vidi iz teksta programa, podrazumevana IP adresa je 127.0.0.1 (lokalna petlja).

U drugom terminalu pokrećemo drugu instancu programa, koja simulira lokalni uređaj. Koristimo dodatni argument koji omogućava da ugrađeni test generator radi:

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

U ovom trenutku, paketi koji se prenose prema „daljinskom“ uređaju bi trebali početi da trepću u konzoli sa TSharkom, a iz zvučnika računara će se čuti neprekidni ton.

Ako se sve dogodilo kako je napisano, onda ponovo pokrećemo drugu kopiju programa, ali bez ključa i argumenta “—gen 440”. Sada ćete igrati ulogu generatora. Nakon toga možete praviti buku u mikrofon; trebali biste čuti odgovarajući zvuk u zvučniku ili slušalicama. Može se čak pojaviti i akustična samopobuda; utišajte glasnoću zvučnika i efekat će nestati.

Ako ste ga pokrenuli na dva računara i niste se zbunili oko IP adresa, onda vas čeka isti rezultat - dvosmjerna digitalna kvalitetna glasovna komunikacija.

U sljedećem članku ćemo naučiti kako pisati vlastite filtere - dodatke, zahvaljujući ovoj vještini moći ćete koristiti media streamer ne samo za audio i video, već i u nekoj drugoj specifičnoj oblasti.

izvor: www.habr.com

Dodajte komentar