Udforskning af Mediastreamer2 VoIP-motoren. Del 9

Artiklens materiale er hentet fra min zen kanal.

Duplex samtaleanlæg

Udforskning af Mediastreamer2 VoIP-motoren. Del 9

I fortiden artiklen et duplex samtaleanlæg blev annonceret, og i denne vil vi lave det.

Diagrammet er vist i titelfiguren. Den nederste kæde af filtre danner transmissionsvejen, som starter fra lydkortet. Det giver signaleksempler fra mikrofonen. Som standard sker dette med en hastighed på 8000 prøver pr. sekund. Databitdybden, som mediastreamer-lydfiltre bruger, er 16 bit (dette er ikke vigtigt; hvis du ønsker det, kan du skrive filtre, der fungerer med en højere bitdybde). Dataene er grupperet i blokke med 160 prøver. Hver blok er således 320 bytes stor. Dernæst fører vi dataene til generatorens input, som, når den er slukket, er "gennemsigtig" for dataene. Jeg tilføjede det, hvis du bliver træt af at tale i mikrofonen under fejlfinding - du kan bruge generatoren til at "skyde" stien med et tonesignal.

Efter generatoren går signalet til encoderen, som konverterer vores 16-bit samples i henhold til µ-loven (G.711 standard) til otte-bit ener. Ved udgangen af ​​koderen har vi allerede en datablok, der er halvt så stor. Generelt kan vi overføre data uden komprimering, hvis vi ikke skal spare trafik. Men her er det nyttigt at bruge en encoder, da Wireshark kun kan gengive lyd fra en RTP-stream, når den er komprimeret i henhold til µ-loven eller a-loven.

Efter indkoderen sendes de lettere datablokke til rtpsend-filteret, som vil lægge dem i en RTP-pakke, indstille de nødvendige flag og give dem til mediestreameren til transmission over netværket i form af en UDP-pakke.

Den øverste kæde af filtre danner modtagestien; RTP-pakker modtaget af mediestreameren fra netværket går ind i rtprecv-filteret, ved hvis output de vises i form af datablokke, som hver svarer til én modtaget pakke. Blokken indeholder kun nyttelastdata; i den forrige artikel blev de vist med grønt i illustrationen.

Derefter sendes blokkene til dekoderfilteret, som konverterer single-byte samples indeholdt i dem til lineære 16-bit. Som allerede kan behandles af mediestreamerfiltre. I vores tilfælde sender vi dem blot til lydkortet til afspilning på dit headsets højttalere.

Lad os nu gå videre til softwareimplementering. For at gøre dette vil vi kombinere modtager- og senderfilerne, som vi adskilte før. Før dette brugte vi faste indstillinger for porte og adresser, men nu har vi brug for programmet til at kunne bruge de indstillinger, som vi angiver ved opstart. For at gøre dette vil vi tilføje funktionalitet til behandling af kommandolinjeargumenter. Hvorefter vi vil være i stand til at indstille IP-adressen og porten på samtaleanlægget, som vi ønsker at etablere forbindelse med.

Lad os først tilføje en struktur til programmet, der gemmer dets indstillinger:

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

typedef struct _app_vars app_vars;

Programmet vil erklære en struktur af denne type kaldet vars.
Lad os derefter tilføje en funktion til at analysere kommandolinjeargumenter:

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

Som et resultat af parsing vil kommandolinjeargumenterne blive placeret i felterne i vars-strukturen. Applikationens hovedfunktion vil være at opsamle sende- og modtagestier fra filtre; efter tilslutning af tickeren vil kontrollen blive overført til en uendelig sløjfe, som, hvis generatorfrekvensen blev sat til ikke-nul, vil genstarte testgeneratoren, således at det virker uden at stoppe.

Generatoren har brug for disse genstarter på grund af dens design; af en eller anden grund kan den ikke producere et signal, der varer mere end 16 sekunder. Det skal bemærkes, at dens varighed er angivet med et 32-bit tal.

Hele programmet vil se sådan ud:

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

Lad os kompilere. Så kan programmet køres på to computere. Eller på en, som jeg vil gøre nu. Vi starter TShark med følgende argumenter:

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

Hvis startfeltet i konsollen kun viser en besked om starten af ​​optagelsen, så er dette et godt tegn - det betyder, at vores port højst sandsynligt ikke er optaget af andre programmer. I en anden terminal lancerer vi en programinstans, der simulerer et "fjern" intercom ved at angive dette portnummer:

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

Som det kan ses af programteksten, er standard IP-adressen 127.0.0.1 (local loopback).

I en anden terminal starter vi en anden instans af programmet, som simulerer en lokal enhed. Vi bruger et ekstra argument, der tillader den indbyggede testgenerator at fungere:

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

I dette øjeblik skulle pakker, der sendes til den "fjerne" enhed, begynde at blinke i konsollen med TShark, og en kontinuerlig tone vil blive hørt fra computerens højttaler.

Hvis alt skete som skrevet, genstarter vi den anden kopi af programmet, men uden nøglen og argumentet "—gen 440". Du vil nu spille rollen som generator. Herefter kan du lave støj i mikrofonen; du skal høre den tilsvarende lyd i højttaleren eller hovedtelefonerne. Der kan endda forekomme akustisk selvexcitering; skru ned for højttalerlydstyrken, og effekten forsvinder.

Hvis du kørte det på to computere og ikke blev forvirret over IP-adresserne, så venter det samme resultat på dig - tovejs digital stemmekommunikation.

I den næste artikel vil vi lære at skrive vores egne filtre - plugins, takket være denne færdighed vil du være i stand til at bruge mediestreameren ikke kun til lyd og video, men også i et andet specifikt område.

Kilde: www.habr.com

Tilføj en kommentar