Explorarea motorului VoIP Mediastreamer2. Partea 9

Materialul articolului este preluat de pe mine canal zen.

Interfon duplex

Explorarea motorului VoIP Mediastreamer2. Partea 9

În trecut articol s-a anuntat un interfon duplex, iar in acesta il vom realiza.

Diagrama este prezentată în figura din titlu. Lanțul inferior de filtre formează calea de transmisie, care începe de la placa de sunet. Oferă mostre de semnal de la microfon. În mod implicit, acest lucru are loc la o rată de 8000 de mostre pe secundă. Adâncimea de biți a datelor pe care o folosesc filtrele audio media streamer este de 16 biți (acest lucru nu este important; dacă doriți, puteți scrie filtre care vor funcționa cu o adâncime de biți mai mare). Datele sunt grupate în blocuri de 160 de mostre. Astfel, fiecare bloc are o dimensiune de 320 de octeți. Apoi, transmitem datele la intrarea generatorului, care, atunci când este oprit, este „transparent” pentru date. L-am adăugat în cazul în care vă săturați să vorbiți la microfon în timpul depanării - puteți folosi generatorul pentru a „trage” calea cu un semnal de ton.

După generator, semnalul ajunge la encoder, care convertește mostrele noastre de 16 biți conform legii µ (standard G.711) în cele de opt biți. La ieșirea codificatorului, avem deja un bloc de date de jumătate din dimensiune. În general, putem transmite date fără compresie dacă nu avem nevoie să economisim trafic. Dar aici este util să folosiți un encoder, deoarece Wireshark poate reproduce audio dintr-un flux RTP numai atunci când este comprimat conform legii µ sau legii a.

După codificator, blocurile mai ușoare de date sunt trimise la filtrul rtpsend, care le va pune într-un pachet RTP, va seta steagurile necesare și le va oferi media streamer-ului pentru transmisie prin rețea sub forma unui pachet UDP.

Lanțul superior de filtre formează calea de primire; pachetele RTP primite de streamerul media din rețea intră în filtrul rtprecv, la ieșirea căruia apar sub formă de blocuri de date, fiecare dintre ele corespunzând unui pachet primit. Blocul conține doar date de sarcină utilă; în articolul anterior, acestea au fost afișate cu verde în ilustrație.

În continuare, blocurile sunt trimise la filtrul decodor, care convertește mostrele de un singur octet conținute în ele în cele liniare, pe 16 biți. Care poate fi deja procesat de filtrele media streamer. În cazul nostru, pur și simplu le trimitem pe placa de sunet pentru redare pe difuzoarele căștilor.

Acum să trecem la implementarea software-ului. Pentru a face acest lucru, vom combina fișierele receptor și emițător pe care le-am separat anterior. Înainte de aceasta, folosim setări fixe pentru porturi și adrese, dar acum avem nevoie de program pentru a putea folosi setările pe care le specificăm la pornire. Pentru a face acest lucru, am adăuga funcționalitate pentru procesarea argumentelor liniei de comandă. După care vom putea seta adresa IP și portul interfonului cu care dorim să stabilim o conexiune.

Mai întâi, să adăugăm o structură programului care va stoca setările acestuia:

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

typedef struct _app_vars app_vars;

Programul va declara o structură de acest tip numită vars.
Apoi, să adăugăm o funcție pentru a analiza argumentele liniei de comandă:

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

Ca rezultat al analizei, argumentele liniei de comandă vor fi plasate în câmpurile structurii vars. Funcția principală a aplicației va fi colectarea căilor de transmisie și recepție de la filtre; după conectarea tickerului, controlul va fi transferat la o buclă infinită care, dacă frecvența generatorului a fost setată la non-zero, va reporni generatorul de test, astfel încât functioneaza fara oprire.

Generatorul va avea nevoie de aceste reporniri datorită designului său; din anumite motive, nu poate produce un semnal care durează mai mult de 16 secunde. Trebuie remarcat faptul că durata sa este specificată de un număr de 32 de biți.

Întregul program va arăta astfel:

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

Să compilam. Apoi programul poate fi rulat pe două computere. Sau pe una, așa cum voi face acum. Lansăm TShark cu următoarele argumente:

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

Dacă câmpul de lansare din consolă afișează doar un mesaj despre începutul capturii, atunci acesta este un semn bun - înseamnă că cel mai probabil portul nostru nu este ocupat de alte programe. Într-un alt terminal, lansăm o instanță de program care va simula un interfon „la distanță”, specificând acest număr de port:

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

După cum se poate vedea din textul programului, adresa IP implicită este 127.0.0.1 (loopback local).

Într-un alt terminal, lansăm oa doua instanță a programului, care simulează un dispozitiv local. Folosim un argument suplimentar care permite generatorului de teste încorporat să funcționeze:

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

În acest moment, pachetele transmise către dispozitivul „la distanță” ar trebui să înceapă să clipească în consola cu TShark și se va auzi un ton continuu din difuzorul computerului.

Dacă totul s-a întâmplat așa cum este scris, atunci repornim a doua copie a programului, dar fără cheia și argumentul „—gen 440”. Acum vei juca rolul de generator. După aceasta, puteți face zgomot în microfon; ar trebui să auziți sunetul corespunzător în difuzor sau în căști. Poate apărea chiar și autoexcitarea acustică; reduceți volumul difuzorului și efectul va dispărea.

Dacă l-ați rulat pe două computere și nu v-ați confundat cu adresele IP, atunci vă așteaptă același rezultat - comunicare vocală bidirecțională de calitate digitală.

În următorul articol vom învăța cum să scriem propriile filtre - pluginuri, datorită acestei abilități, veți putea folosi media streamer-ul nu numai pentru audio și video, ci și într-o altă zonă specifică.

Sursa: www.habr.com

Adauga un comentariu