Mediastreamer2 VoIP programmas izpēte. 9. daļa

Raksta materiāls ņemts no mana zen kanāls.

Dupleksais domofons

Mediastreamer2 VoIP programmas izpēte. 9. daļa

Pagātnē raksts tika paziņots par duplekso domofonu, un šajā mēs to izdarīsim.

Diagramma ir parādīta virsraksta attēlā. Apakšējā filtru ķēde veido pārraides ceļu, kas sākas no skaņas kartes. Tas nodrošina signālu paraugus no mikrofona. Pēc noklusējuma tas notiek ar ātrumu 8000 paraugu sekundē. Datu bitu dziļums, ko izmanto multivides straumētāja audio filtri, ir 16 biti (tas nav svarīgi; ja vēlaties, varat rakstīt filtrus, kas darbosies ar lielāku bitu dziļumu). Dati ir sagrupēti blokos pa 160 paraugiem. Tādējādi katra bloka izmērs ir 320 baiti. Tālāk mēs ievadām datus ģeneratora ievadei, kas, izslēdzot, ir “caurspīdīga” datiem. Es to pievienoju gadījumam, ja atkļūdošanas laikā apnīk runāt mikrofonā - varat izmantot ģeneratoru, lai “izšautu” ceļu ar toņa signālu.

Pēc ģeneratora signāls nonāk kodētājā, kas pārvērš mūsu 16 bitu paraugus atbilstoši µ-likumam (G.711 standarts) astoņu bitu paraugiem. Kodētāja izejā mums jau ir uz pusi mazāks datu bloks. Kopumā mēs varam pārsūtīt datus bez saspiešanas, ja mums nav nepieciešams ietaupīt trafiku. Bet šeit ir lietderīgi izmantot kodētāju, jo Wireshark var reproducēt audio no RTP straumes tikai tad, ja tas ir saspiests saskaņā ar µ-likumu vai a-likumu.

Pēc kodētāja vieglākie datu bloki tiek nosūtīti uz rtpsend filtru, kas tos ievietos RTP paketē, iestatīs nepieciešamos karogus un nodos multivides straumētājam pārraidei pa tīklu UDP paketes veidā.

Filtru augšējā ķēde veido uztveršanas ceļu, multivides straumētāja no tīkla saņemtās RTP paketes nonāk rtprecv filtrā, kura izejā tās parādās datu bloku veidā, no kuriem katrs atbilst vienai saņemtajai paketei. Blokā ir tikai lietderīgās slodzes dati; iepriekšējā rakstā tie ilustrācijā tika parādīti zaļā krāsā.

Tālāk bloki tiek nosūtīti uz dekodera filtru, kas tajos esošos viena baita paraugus pārvērš lineāros, 16 bitu paraugos. Ko jau var apstrādāt ar multivides straumētāju filtriem. Mūsu gadījumā mēs tos vienkārši nosūtām uz skaņas karti atskaņošanai austiņu skaļruņos.

Tagad pāriesim pie programmatūras ieviešanas. Lai to izdarītu, mēs apvienosim iepriekš atdalītos uztvērēja un raidītāja failus. Pirms tam mēs izmantojām fiksētus portu un adrešu iestatījumus, bet tagad mums ir nepieciešams, lai programma varētu izmantot iestatījumus, ko norādījām startēšanas laikā. Lai to izdarītu, mēs pievienosim funkcionalitāti komandrindas argumentu apstrādei. Pēc tam mēs varēsim iestatīt domofona IP adresi un portu, ar kuru mēs vēlamies izveidot savienojumu.

Vispirms pievienosim programmai struktūru, kurā tiks saglabāti tās iestatījumi:

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

typedef struct _app_vars app_vars;

Programma deklarēs šāda veida struktūru, ko sauc par vars.
Pēc tam pievienosim funkciju komandrindas argumentu parsēšanai:

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

Parsēšanas rezultātā komandrindas argumenti tiks ievietoti vars struktūras laukos. Lietojumprogrammas galvenā funkcija būs no filtriem savākt raidīšanas un saņemšanas ceļus; pēc svārsta pievienošanas vadība tiks nodota bezgalīgai cilpai, kas, ja ģeneratora frekvence ir iestatīta uz nulli, restartēs testa ģeneratoru tā, lai tas darbojas bez apstāšanās.

Ģeneratoram būs nepieciešamas šīs restartēšanas tā konstrukcijas dēļ, nez kāpēc tas nevar radīt signālu, kas ilgst vairāk par 16 sekundēm. Jāņem vērā, ka tā ilgumu nosaka 32 bitu skaitlis.

Visa programma izskatīsies šādi:

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

Sastādīsim. Pēc tam programmu var palaist divos datoros. Vai uz vienu, kā es tagad darīšu. Mēs palaižam TShark ar šādiem argumentiem:

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

Ja konsoles palaišanas laukā tiek parādīts tikai ziņojums par uztveršanas sākumu, tad tā ir laba zīme - tas nozīmē, ka mūsu ports, visticamāk, nav aizņemts ar citām programmām. Citā terminālī mēs palaižam programmas gadījumu, kas simulēs “attālo” domofonu, norādot šo porta numuru:

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

Kā redzams no programmas teksta, noklusējuma IP adrese ir 127.0.0.1 (local loopback).

Citā terminālī mēs palaižam otro programmas gadījumu, kas simulē vietējo ierīci. Mēs izmantojam papildu argumentu, kas ļauj iebūvētajam testa ģeneratoram darboties:

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

Šajā brīdī konsolē ar TShark jāsāk mirgot paketēm, kas tiek pārsūtītas uz “attālo” ierīci, un no datora skaļruņa tiks dzirdams nepārtraukts signāls.

Ja viss notika kā rakstīts, tad restartējam otro programmas eksemplāru, bet bez atslēgas un argumenta “—gen 440”. Tagad jūs spēlēsit ģeneratora lomu. Pēc tam jūs varat radīt troksni mikrofonā; jums vajadzētu dzirdēt attiecīgo skaņu skaļrunī vai austiņās. Var rasties pat akustiska pašizdegšanās; samaziniet skaļruņa skaļumu, un efekts pazudīs.

Ja palaidāt to divos datoros un nesapratāt par IP adresēm, tad jūs gaida tas pats rezultāts - divvirzienu digitālās kvalitātes balss sakari.

Nākamajā rakstā mēs iemācīsimies rakstīt savus filtrus - spraudņus, pateicoties šai prasmei, jūs varēsiet izmantot multivides straumētāju ne tikai audio un video, bet arī kādā citā specifiskā jomā.

Avots: www.habr.com

Pievieno komentāru