Изучаваме VoIP енджин Mediastreamer2. Част 9

Материалът на статията е взет от моя дзен-канал.

Дуплексно устройство за преговори

Изучаваме VoIP енджин Mediastreamer2. Част 9

В предходната статията Беше анонсирано дуплексно устройство за преговори, а в тази статия ще го направим.

Схемата е показана на заглавната рисунка. Долната верига на филтрите образува предавателен тракт, който започва от звуковата карта. Тя подава отсчетите на сигнала от микрофона. По подразбиране това става с темп от 8000 отсчета в секунда. Разрядността на данните, използвани от звуковите филтри на медиастримера, е 16 бита (не е принципиално, при желание могат да се напишат филтри, които работят с по-висока разрядност). Данните са групирани в блокове по 160 отсчета. Така всеки блок има размер 320 байта. След това подаваме данните на входа на генератора, който в изключен режим е "прозрачен" за данните. Добавих го за случай, ако ви омръзне да говорите в микрофона по време на отстраняване на грешки — ще можете да използвате генератора, за да "изстреляте" тракта с тонален сигнал.

След генератора сигналът постъпва на кодера, който преобразува нашите 16-битови отсчети по µ-закона (стандарт G.711) в осембитови. На изхода на кодера вече имаме блок данни с два пъти по-малък размер. В общия случай, можем да предаваме данни без компресия, ако не е необходимо да пестим трафик. Но тук е полезно да се използва кодер, тъй като Wireshark може да възпроизвежда звук от RTP-потока само когато е компресиран по µ-закона или а-закона.

След кодера, облекчените блокове данни постъпват на филтъра 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 адресите, ви очаква същият резултат — двустранна речева връзка с цифрово качество.

В следващата статия ще научим как да пишем свои собствени филтри — плъгини, благодарение на които ще можете да приложите медиастриймера не само за звук и видео, но и в друга специфична област.

Източник: habr.com

Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри 🔥 Купете надежден хостинг за сайтове с защита от DDoS, VPS VDS сървъри | ProHoster