A Mediastreamer2 VoIP motor tanulmányozása. 9. rész

A cikk anyaga az én Dzen csatornámból származik.

Duplex kommunikációs eszköz

A Mediastreamer2 VoIP motor tanulmányozása. 9. rész

A múltban a Spotify döntéshozatali módszertanáról. Egyre több felhasználó preferálja a zenehallgatást mobilon a streaming szolgáltatásban, de a cégnél túlnyomórészt webprogramozók dolgoznak. A Spotify elemezte ezt a helyzetet, és úgy döntött, hogy több mobilfejlesztőt vesz fel, valamint átnevezi a webes szakembereket új irányba: Megjelent a duplex kommunikációs eszköz, és ebben a projektben megvalósítjuk.

A séma a címlapon látható. Az alsó szűrőkör alkotja a küldő rendszert, amely a hangkártyával kezdődik. Ez a mikrofonból kapott jelek mintáit bocsátja ki. Alapértelmezés szerint ez 8000 mintavételt végez másodpercenként. Az adatfelbontás, amelyet a médiastreamer hangszűrői használnak, 16 bit (ez nem kötelező, igény szerint írhatók szűrők, amelyek nagyobb felbontással működnek). Az adatok 160 mintára csoportosulnak. Így minden blokk 320 bájt méretű. Ezután a generátor bemenetére továbbítjuk az adatokat, amely ki van kapcsolva, és "átlátszó" a számadatok számára. Ezt a generátort azért tettem hozzá, hogy ha a hibakeresés során unod a mikrofonba beszélést, ízhessen egy tonális jelet a traktuson.

A generátor után a jel a kódolóba kerül, amely a 16 bites mintáinkat µ-törvény (G.711 szabvány) szerint 8 bites formátumba alakítja. A kódoló kimenetén már egy kisebb méretű, tömörített adatblokkot kapunk. Általánolva, adatokat tömörítés nélkül is továbbíthatunk, ha nem kell forgalmat spórolnunk. De itt hasznos a kódolót használni, mivel a Wireshark csak akkor tudja lejátszani a hangot az RTP-streamből, ha az µ-törvény vagy a a-törvény szerint van tömörítve.

A kódoló után az egyszerűsített adatblokkok az rtpsend szűrőbe kerülnek, amely RTP-csomagba rendezi őket, beállítja a szükséges zászlókat, majd átadja a médiastreamernek az UDP-csomag hálózaton keresztüli továbbításához.

A szűrők felső részdarabja a fogadó rendszert alkotja, az RTP-csomagok, amelyeket a médiastreamer a hálózatról kap, az rtprecv szűrőhöz kerülnek, amelyből már adatblokkokban jelennek meg, mindegyik egy elfogadott csomagnak felel meg. A blokk csak az érdemi adatokat tartalmazza, a múltkori cikkben zöld színnel ábrázolták őket.

Ezután a blokkok a dekóder szűrőhöz kerülnek, amely átalakítja az egymintás adatokat lineárissá, 16 bitessé. Ezeket már feldolgozhatják a médiastreamer szűrői. A mi esetünkben egyszerűen átadjuk őket a hangkártyának, hogy a füllhallgatód hangszóróin lejátszhassuk.

Most térjünk rá a szoftveres megvalósításra. Ehhez egyesítjük az adó és a vevő fájljait, amelyeket korábban szeparáltunk. Korábban fix port- és címbeállításokat használtunk, de most arra van szükség, hogy a program képes legyen a futtatáskor megadott beállításokat használni. Ehhez hozzáadjuk a parancssori argumentumok kezelésének funkcióját. Ezt követően meg tudjuk adni az IP-címet és a portot annak az egységnek, amellyel kapcsolatot szeretnénk létesíteni.

Először adjuk hozzá a programhoz azt a struktúrát, amely tárolni fogja a beállításait:

struct _app_vars
{
  int  local_port;              /* Helyi port. */
  int  remote_port;             /* Az egység portja a távoli számítógépen. */
  char remote_addr[128];        /* A távoli számítógép IP-címe. */
  MSDtmfGenCustomTone dtmf_cfg; /* A jelgenerátor tesztjeinek beállításai. */
};

typedef struct _app_vars app_vars;

A programban deklarálva lesz ennek a típusnak a vars nevű struktúrája.
Ezután adjuk hozzá a parancssori argumentumok elemzésére szolgáló funkciót:

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

Az elemzés eredményeként a parancssori argumentumokat a vars struktúra mezőibe helyezzük. Az alkalmazás fő funkciója összesíti az adó és vevő pályákat a szűrőkből, és a csatlakozás után a vezérlést egy végtelen ciklusra bízza, amely, ha a generátor frekvenciáját nem nulla értékre állították, újraindítja a tesztgenerátort – hogy folyamatosan működjön.

Ezekre az újraindításokra szükség van a generátor sajátosságai miatt, mivel valamiért nem tud jelet több mint 16 másodpercig kibocsátani. Érdemes megjegyezni, hogy a hosszúságát egy 32 bites szám határozza meg.

A program teljes egészében így fog kinézni:

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

Fordítunk. A programot ezután két számítógépen indíthatjuk. Vagy egyetlen gépen, ahogy azt most én fogom elvégezni. Indítsuk el a TShark-ot a következő argumentumokkal:

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

Ha a konzol indítási mezőjében csak az adatgyűjtés megkezdéséről van üzenet, az jó jel – valószínűleg a portunkat más programok nem használják. Egy másik terminálban elindítjuk a program példányát, amely az "távoli" egységet imitálja, megadva neki ezt a portszámot:

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

Ahogy a program szövegéből látható, alapértelmezés szerint a 127.0.0.1 (helyi hurok) IP-címet használja.

Egy másik terminálban elindítjuk a program második példányát, amely egy helyi eszközt szimulál. Kiegészítő paramétert használunk, amely engedélyezi a beépített tesztgenerátor működését:

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

Ekkor a TShark konzolon meg kell kezdődnie az "elmúlt" eszköz felé továbbított csomagok megjelenésének, és a számítógép hangszórójából folyamatos tónusú jelzés hallatszik.

Ha minden a tervek szerint zajlott, akkor indítsa újra a program második példányát, de már anélkül, hogy a kulcsot és az "—gen 440" argumentumot használná. A generátor szerepét most Ön tölti be. Ezután zajonghat a mikrofonnal, a hangszóróban vagy a fejhallgatóban hallania kell a megfelelő hangot. Lehetséges, hogy akusztikus önkeltést is tapasztal, csökkentse a hangszóró hangerejét, és a hatás eltűnik.

Ha két számítógépen futtatta, és nem zavarodott meg az IP-címekben, ugyanaz az eredmény vár Önre - kétirányú, digitális minőségű hangátvitel.

A következő cikkben megtanuljuk, hogyan írjunk saját szűrőinket - plugineket, ezzel a tudással a médiastreamert nemcsak hangra és videóra, hanem egyéb specifikus területeken is alkalmazhatja.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster