Проучване на 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 (local loopback).

В друг терминал стартираме второ копие на програмата, което симулира локално устройство. Използваме допълнителен аргумент, който позволява на вградения тестов генератор да работи:

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

В този момент пакетите, предадени към „отдалеченото“ устройство, трябва да започнат да мигат в конзолата с TShark и от високоговорителя на компютъра ще се чуе непрекъснат тон.

Ако всичко се е случило както е написано, рестартираме второто копие на програмата, но без ключ и аргумент „—gen 440“. Сега ще играете ролята на генератор. След това можете да издавате шум в микрофона; трябва да чуете съответния звук в високоговорителя или слушалките. Може дори да възникне акустично самовъзбуждане; намалете силата на звука на високоговорителя и ефектът ще изчезне.

Ако сте го пуснали на два компютъра и не сте се объркали с IP адресите, тогава ви очаква същият резултат - двупосочна гласова комуникация с цифрово качество.

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

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

Добавяне на нов коментар