Изучаваме 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