Mediastreamer2:n VoIP-moottorin tutkiminen. Osa 9

Artikkelin materiaali on otettu minun zen kanava.

Kaksipuolinen sisäpuhelin

Mediastreamer2:n VoIP-moottorin tutkiminen. Osa 9

Viimeisessä статье kaksipuolinen sisäpuhelin ilmoitettiin, ja tässä onnistumme.

Kaavio näkyy otsikon kuvassa. Alempi suodatinketju muodostaa siirtotien, joka alkaa äänikortista. Se tarjoaa signaalinäytteitä mikrofonista. Oletuksena tämä tapahtuu nopeudella 8000 näytettä sekunnissa. Media streamer -äänisuodattimien käyttämä datan bittisyvyys on 16 bittiä (tämä ei ole tärkeää; jos haluat, voit kirjoittaa suodattimia, jotka toimivat suuremmalla bittisyvyydellä). Tiedot on ryhmitelty 160 näytteen lohkoihin. Siten jokainen lohko on kooltaan 320 tavua. Seuraavaksi syötämme tiedot generaattorin tuloon, joka sammutettuna on tiedoille "läpinäkyvä". Lisäsin sen siltä varalta, että kyllästyt puhumaan mikrofoniin virheenkorjauksen aikana - voit käyttää generaattoria polun "ampumiseen" äänisignaalilla.

Generaattorin jälkeen signaali menee kooderiin, joka muuntaa 16-bittiset näytteemme µ-lain (G.711-standardi) mukaisesti kahdeksanbittisiksi. Enkooderin lähdössä meillä on jo puolet pienempi tietolohko. Yleensä voimme lähettää tietoja ilman pakkausta, jos meidän ei tarvitse säästää liikennettä. Mutta tässä on hyödyllistä käyttää kooderia, koska Wireshark voi toistaa ääntä RTP-virrasta vain, kun se on pakattu µ-lain tai a-lain mukaan.

Enkooderin jälkeen kevyemmät datalohkot lähetetään rtpsend-suodattimelle, joka laittaa ne RTP-pakettiin, asettaa tarvittavat liput ja antaa ne mediasuorittimelle lähetettäväksi verkon yli UDP-paketin muodossa.

Suodattimien ylempi ketju muodostaa vastaanottopolun, mediastreamerin verkosta vastaanottamat RTP-paketit menevät rtprecv-suodattimeen, jonka lähdössä ne näkyvät tietolohkoina, joista jokainen vastaa yhtä vastaanotettua pakettia. Lohko sisältää vain hyötykuormatiedot, edellisessä artikkelissa ne on esitetty kuvassa vihreällä.

Seuraavaksi lohkot lähetetään dekooderin suodattimelle, joka muuntaa niiden sisältämät yksitavuiset näytteet lineaarisiksi, 16-bittisiksi. Joka voidaan jo käsitellä median striimaussuodattimilla. Meidän tapauksessamme lähetämme ne vain äänikortille toistettavaksi kuulokkeiden kaiuttimilla.

Siirrytään nyt ohjelmistojen toteuttamiseen. Tätä varten yhdistämme aiemmin erottamamme vastaanottimen ja lähettimen tiedostot. Ennen tätä käytimme kiinteitä asetuksia porteille ja osoitteille, mutta nyt tarvitsemme ohjelman voidakseen käyttää käynnistyksen yhteydessä määrittämiämme asetuksia. Tätä varten lisäämme toiminnot komentoriviargumenttien käsittelyyn. Tämän jälkeen voimme asettaa IP-osoitteen ja portin sisäpuhelimelle, johon haluamme muodostaa yhteyden.

Lisätään ensin ohjelmaan rakenne, joka tallentaa sen asetukset:

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

typedef struct _app_vars app_vars;

Ohjelma ilmoittaa tämän tyyppisen rakenteen nimeltä vars.
Lisätään seuraavaksi funktio komentoriviargumenttien jäsentämiseksi:

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

Jäsentämisen seurauksena komentorivin argumentit sijoitetaan vars-rakenteen kenttiin. Sovelluksen päätehtävänä on kerätä lähetys- ja vastaanottopolut suodattimista; tickerin kytkemisen jälkeen ohjaus siirtyy äärettömään silmukkaan, joka käynnistää testigeneraattorin uudelleen, jos generaattorin taajuus on asetettu nollasta poikkeavaksi. se toimii pysähtymättä.

Generaattori tarvitsee nämä uudelleenkäynnistykset suunnittelunsa vuoksi, joten se ei jostain syystä pysty tuottamaan yli 16 sekuntia kestävää signaalia. On huomattava, että sen kesto määritellään 32-bittisellä numerolla.

Koko ohjelma näyttää tältä:

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

Kootaan. Sitten ohjelmaa voidaan ajaa kahdella tietokoneella. Tai yhdellä, kuten nyt teen. Käynnistämme TSharkin seuraavilla argumenteilla:

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

Jos konsolin käynnistyskentässä näkyy vain viesti kaappauksen alkamisesta, tämä on hyvä merkki - se tarkoittaa, että porttimme ei todennäköisesti ole muiden ohjelmien käytössä. Toisessa päätteessä käynnistämme ohjelmaesiintymän, joka simuloi "etäpuhelinta" määrittämällä tämän portin numeron:

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

Kuten ohjelman tekstistä näkyy, oletus-IP-osoite on 127.0.0.1 (paikallinen silmukka).

Toisessa päätteessä käynnistämme ohjelman toisen esiintymän, joka simuloi paikallista laitetta. Käytämme lisäargumenttia, joka sallii sisäänrakennetun testigeneraattorin toiminnan:

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

Tällä hetkellä "etä"-laitteeseen lähetettyjen pakettien pitäisi alkaa vilkkua konsolissa TSharkilla ja tietokoneen kaiuttimesta kuuluu jatkuva ääni.

Jos kaikki tapahtui kuten kirjoitettu, käynnistämme ohjelman toisen kopion uudelleen, mutta ilman avainta ja argumenttia "—gen 440". Sinä näytät nyt generaattorin roolia. Tämän jälkeen voit tehdä kohinaa mikrofoniin, sinun pitäisi kuulla vastaava ääni kaiuttimesta tai kuulokkeista. Akustista itseherätystä voi jopa esiintyä; vähennä kaiuttimen äänenvoimakkuutta ja efekti häviää.

Jos suoritit sen kahdella tietokoneella etkä hämmentynyt IP-osoitteista, sama tulos odottaa sinua - kaksisuuntainen digitaalinen äänenlaatu.

Seuraavassa artikkelissa opimme kirjoittamaan omia suodattimiamme - lisäosia, tämän taidon ansiosta pystyt käyttämään mediasuojia paitsi äänen ja videon, myös jollain muulla tietyllä alueella.

Lähde: will.com

Lisää kommentti