Istražujemo VoIP motor Mediastreamer2. Dio 9

Materijal članka preuzet je s mog zen kanala.

Duplex uređaj za razgovor

Istražujemo VoIP motor Mediastreamer2. Dio 9

U prošloj članku bilo je najavljeno duplex uređaj za razgovor, a u ovoj ćemo ga izraditi.

Shema je prikazana na naslovnoj slici. Donji sklop filtara čini prenosni put, koji počinje s audio karticom. Ona pruža uzorke signala s mikrofona. Po defaultu, to se događa brzinom od 8000 uzoraka u sekundi. Dubina podataka koju koriste audio filtri mediastreamera je 16 bita (to nije ključno, po želji se mogu napisati filtri koji će raditi s većom dubinom). Podaci su grupirani u blokove od 160 uzoraka. Tako svaki blok ima veličinu od 320 bajtova. Zatim, podatke unosimo u generator koji je u isključenom stanju "proziran" za podatke. Dodao sam ga za slučaj kada vam se pri debagiranju dosadi razgovarati u mikrofon — moći ćete koristiti generator da "prostrijelte" put tonalskim signalom.

Nakon generatora, olakšani blokovi podataka idu na enkoder, koji pretvara naših 16-bitnih uzoraka prema µ-zakonu (standarde G.711) u osmobitne. Na izlazu enkodera već imamo blok podataka dvostruko manje veličine. Općenito, možemo prenositi podatke bez kompresije, ako ne trebamo štedjeti promet. Međutim, ovdje je korisno koristiti enkoder, jer Wireshark može reproducirati zvuk iz RTP toka samo kada je komprimiran prema µ-zakonu ili a-zakonu.

Nakon enkodera, olakšani blokovi podataka dolaze na filter rtpsend, koji ih stavlja u RTP paket, postavlja potrebne zastavice i šalje ih mediastreameru za prijenos putem mreže u obliku UDP paketa.

Gornji sklop filtara čini prijemni put, RTP paketi koje mediastreamer primi iz mreže dolaze u filter rtprecv, na izlazu kojeg se pojavljuju već u obliku blokova podataka, od kojih svaki odgovara jednom prihvaćenom paketu. Blok sadrži samo podatke korisnog opterećenja, a u prethodnom članku na ilustraciji su prikazani zelenom bojom.

Nakon toga, blokovi idu na dekodera filtar, koji transformira one byte-ova podatke u linearne, 16-bitne. Koji se već mogu obraditi filtarima za medijski streaming. U našem slučaju, jednostavno ih predajemo zvučnoj kartici za reprodukciju na zvučnicima vaših slušalica.

Sada prelazimo na programske implementacije. Za to ćemo spojiti datoteke prijemnika i odašiljača, koje smo ranije razdvojili. Do sada smo koristili fiksne postavke portova i adresa, ali sada trebamo da program koristi postavke koje ćemo navesti pri pokretanju. Za to bismo dodali funkcionalnost obrade argumenata komandne linije. Nakon toga, moći ćemo postaviti IP adresu i port razgovornog uređaja s kojim želimo uspostaviti vezu.

Prvo ćemo dodati strukturu u program koja će pohranjivati njene postavke:

struct _app_vars
{
  int  local_port;              /* Lokalni port. */
  int  remote_port;             /* Port razgovornog uređaja na udaljenom računalu. */
  char remote_addr[128];        /* IP adresa udaljenog računala. */
  MSDtmfGenCustomTone dtmf_cfg; /* Postavke testnog signala generatora. */
};

typedef struct _app_vars app_vars;

U programu će biti deklarirana struktura ovog tipa pod imenom vars.
Zatim dodajemo funkciju za analizu 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 analize, argumenti komandne linije bit će smješteni u polja strukture vars. Glavna funkcija aplikacije će sastaviti prijemne i odašiljačke puteve od filtara, a nakon povezivanja ticker-a, kontrola će biti predana beskonačnoj petlji koja, ako je frekvencija generatora postavljena na nenultu, će ponovo pokrenuti testni generator — kako bi radio neprekidno.

Ova ponovna pokretanja će biti potrebna generatoru zbog njegove posebnosti u konstrukciji, jer iz nekog razloga ne može emitirati signal trajanja duljih od 16 sekundi. Pri tome je potrebno napomenuti da se trajanje postavlja 32-bitnim brojem.

Program u cijelosti ć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);
    }
}

Kompajliramo. Nakon toga, program se može pokrenuti na dva računala. Ili na jednom, kao što ću sada učiniti. Pokrećemo TShark s sljedećim argumentima:

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

Ako se u konzoli pojavi samo poruka o početku hvatanja, to je dobar znak - to znači da naš port vjerojatno nije zauzet drugim programima. U drugom terminalu pokrećemo primjer programa koji će imitirati "udaljeni" uređaj za razgovor, navodeći mu ovaj broj porta:

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

Kao što se može vidjeti iz programa, prema zadanim postavkama koristi se IP adresa 127.0.0.1 (lokalna petlja).

U još jednom terminalu pokrećemo drugi primjer programa koji imitira lokalni uređaj. Koristimo dodatni argument koji omogućuje rad ugrađenog testnog generatora:

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

U ovom trenutku, u konzoli s TSharkom trebali bi početi bljeskati paketi koji se prenose prema "udaljenom" uređaju, a iz zvučnika računala će se čuti neprekidni ton.

Ako se sve dogodilo kako je zapisano, ponovno pokrećemo drugi primjer programa, ali već bez ključa i argumenta "--gen 440". Ulogu generatora sada ćete preuzeti vi. Nakon toga možete malo napraviti buku u mikrofonu, u zvučnicima ili slušalicama trebali biste čuti odgovarajući zvuk. Moguće je čak i pojavljivanje akustičkog samoodržavanja, smanjite jačinu zvučnika i efekt će nestati.

Ako ste pokretali na dva računala i niste se zbunili s IP adresama, očekuje vas isti rezultat – dvosmjerna komunikacija glasa digitalne kvalitete.

U sljedećem članku naučit ćemo pisati vlastite filtre – plugine, zahvaljujući ovoj vještini moći ćete primijeniti medijski streamer ne samo za zvuk i video, nego i u nekoj drugoj specifičnoj oblasti.

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster