Истражување на VoIP моторот Mediastreamer2. Дел 9

Материјалот на статијата е преземен од моето зен канал.

Дуплекс домофон

Истражување на VoIP моторот Mediastreamer2. Дел 9

Во последниот Член најавен е дуплекс домофон, а во овој ќе го направиме.

Дијаграмот е прикажан на насловната слика. Долниот синџир на филтри ја формира патеката за пренос, која започнува од звучната картичка. Обезбедува примероци на сигнал од микрофонот. Стандардно, ова се случува со брзина од 8000 примероци во секунда. Длабочината на битови на податоци што ја користат аудио филтрите на медиумскиот преносник е 16 бита (ова не е важно; ако сакате, можете да напишете филтри што ќе работат со поголема длабочина на битови). Податоците се групирани во блокови од 160 примероци. Така, секој блок е со големина од 320 бајти. Следно, ги внесуваме податоците на влезот на генераторот, кој, кога е исклучен, е „транспарентен“ за податоците. Го додадов во случај да ви здосади да зборувате во микрофонот за време на дебагирање - можете да го користите генераторот за да ја „пукате“ патеката со тонски сигнал.

По генераторот, сигналот оди до енкодерот, кој ги конвертира нашите 16-битни примероци според μ-законот (стандард G.711) во осум-битни. На излезот од енкодерот веќе имаме податочен блок со половина големина. Во принцип, можеме да пренесуваме податоци без компресија ако не треба да заштедиме сообраќај. Но, овде е корисно да се користи енкодер, бидејќи Wireshark може да репродуцира аудио од RTP-стрим само кога е компресиран според µ-законот или a-законот.

По енкодерот, полесните блокови на податоци се испраќаат до филтерот rtpsend, кој ќе ги стави во RTP пакет, ќе ги постави потребните знаменца и ќе ги даде на медиумскиот стример за пренос преку мрежата во форма на UDP пакет.

Горниот синџир на филтри ја формира патеката за примање; RTP пакетите што ги добива медиумскиот стример од мрежата влегуваат во филтерот rtprecv, на чиј излез се појавуваат во форма на податочни блокови, од кои секоја одговара на еден примен пакет. Блокот содржи само податоци за носивост; во претходната статија тие беа прикажани со зелено на илустрацијата.

Следно, блоковите се испраќаат до филтерот за декодер, кој ги претвора примероците од еден бајт содржани во нив во линеарни, 16-битни. Кои веќе може да се обработат со филтри за медиумски стример. Во нашиот случај, ние едноставно ги испраќаме на звучната картичка за репродукција на звучниците на вашите слушалки.

Сега да преминеме на имплементација на софтвер. За да го направите ова, ќе ги комбинираме датотеките на приемникот и предавателот што ги разделивме претходно. Пред ова, користевме фиксни поставки за пристаништа и адреси, но сега ни треба програмата да може да ги користи поставките што ги наведовме при стартување. За да го направите ова, ќе додадеме функционалност за обработка на аргументите на командната линија. По што ќе можеме да ја поставиме IP адресата и портата на домофонот со кој сакаме да воспоставиме врска.

Прво, ајде да додадеме структура на програмата што ќе ги зачува нејзините поставки:

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

typedef struct _app_vars app_vars;

Програмата ќе декларира структура од овој тип наречена vars.
Следно, ајде да додадеме функција за анализирање на аргументите на командната линија:

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

Како резултат на парсирањето, аргументите на командната линија ќе бидат поставени во полињата на структурата vars. Главната функција на апликацијата ќе биде собирање патеки за пренос и примање од филтрите; по поврзувањето на тикерот, контролата ќе се префрли на бесконечна јамка која, ако фреквенцијата на генераторот е поставена на не-нула, ќе го рестартира тест генераторот така што работи без запирање.

На генераторот ќе му требаат овие рестартирање поради неговиот дизајн; поради некоја причина не може да произведе сигнал кој трае повеќе од 16 секунди. Треба да се напомене дека неговото времетраење е одредено со 32-битен број.

Целата програма ќе изгледа вака:

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

Ајде да составиме. Тогаш програмата може да се изврши на два компјутери. Или на еден, како што ќе направам сега. Го лансираме TShark со следниве аргументи:

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

Ако полето за стартување во конзолата прикажува само порака за почетокот на снимањето, тогаш ова е добар знак - тоа значи дека нашата порта најверојатно не е окупирана од други програми. Во друг терминал, стартуваме програмска инстанца која ќе симулира „далечински“ домофон со наведување на овој број на порта:

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

Како што може да се види од текстот на програмата, стандардната IP адреса е 127.0.0.1 (локален повратен циклус).

Во друг терминал, стартуваме втор пример од програмата, која симулира локален уред. Ние користиме дополнителен аргумент кој му овозможува на вградениот тест генератор да работи:

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

Во овој момент, пакетите што се пренесуваат кон „далечинскиот“ уред треба да почнат да трепкаат во конзолата со TShark и ќе се слушне континуиран тон од звучникот на компјутерот.

Ако сè се случи како што е напишано, тогаш ја рестартираме втората копија од програмата, но без клучот и аргументот „-gen 440“. Сега ќе ја играте улогата на генератор. После тоа, можете да правите бучава во микрофонот; треба да го слушнете соодветниот звук во звучникот или слушалките. Може да се појави дури и акустична самовозбуда; намалете ја јачината на звукот на звучникот и ефектот ќе исчезне.

Ако сте го користеле на два компјутера и не сте се збуниле за IP-адресите, тогаш ве чека истиот резултат - двонасочна говорна комуникација со дигитален квалитет.

Во следната статија ќе научиме како да пишуваме сопствени филтри - приклучоци, благодарение на оваа вештина ќе можете да го користите медиумскиот стример не само за аудио и видео, туку и во некоја друга специфична област.

Извор: www.habr.com

Додадете коментар