Utforsker Mediastreamer2 VoIP-motoren. Del 9

Stoffet til artikkelen er hentet fra min zen-kanal.

Duplex intercom

Utforsker Mediastreamer2 VoIP-motoren. Del 9

I den siste artikkel en dupleks intercom ble annonsert, og i denne vil vi gjøre det.

Diagrammet er vist i tittelfiguren. Den nedre kjeden av filtre danner overføringsveien, som starter fra lydkortet. Den gir signalprøver fra mikrofonen. Som standard skjer dette med en hastighet på 8000 prøver per sekund. Databitdybden som mediastreamer-lydfiltre bruker er 16 biter (dette er ikke viktig; hvis du ønsker det, kan du skrive filtre som fungerer med en høyere bitdybde). Dataene er gruppert i blokker med 160 prøver. Dermed er hver blokk 320 byte stor. Deretter mater vi dataene til inngangen til generatoren, som, når den er slått av, er "gjennomsiktig" for dataene. Jeg la det til i tilfelle du blir lei av å snakke i mikrofonen under feilsøking - du kan bruke generatoren til å "skyte" banen med et tonesignal.

Etter generatoren går signalet til koderen, som konverterer våre 16-bits sampler i henhold til µ-loven (G.711-standarden) til åtte-biters. Ved utgangen av koderen har vi allerede en datablokk halvparten av størrelsen. Generelt kan vi overføre data uten komprimering hvis vi ikke trenger å spare trafikk. Men her er det nyttig å bruke en koder, siden Wireshark kan reprodusere lyd fra en RTP-strøm bare når den er komprimert i henhold til µ-loven eller a-loven.

Etter koderen sendes de lettere blokkene med data til rtpsend-filteret, som vil sette dem i en RTP-pakke, sette de nødvendige flaggene og gi dem til mediastreameren for overføring over nettverket i form av en UDP-pakke.

Den øvre kjeden av filtre danner mottaksbanen; RTP-pakker mottatt av mediestreameren fra nettverket går inn i rtprecv-filteret, ved utgangen som de vises i form av datablokker, som hver tilsvarer en mottatt pakke. Blokken inneholder kun nyttelastdata; i forrige artikkel ble de vist i grønt i illustrasjonen.

Deretter sendes blokkene til dekoderfilteret, som konverterer enkeltbyte-samplene i dem til lineære 16-biters. Som allerede kan behandles av mediastreamer-filtre. I vårt tilfelle sender vi dem ganske enkelt til lydkortet for avspilling på høyttalerne til headsettet ditt.

La oss nå gå videre til programvareimplementering. For å gjøre dette vil vi kombinere mottaker- og senderfilene som vi skilte fra før. Før dette brukte vi faste innstillinger for porter og adresser, men nå trenger vi programmet for å kunne bruke innstillingene som vi angir ved oppstart. For å gjøre dette, vil vi legge til funksjonalitet for behandling av kommandolinjeargumenter. Deretter vil vi kunne angi IP-adressen og porten til intercomet som vi ønsker å opprette en forbindelse med.

Først, la oss legge til en struktur til programmet som vil lagre innstillingene:

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 av denne typen kalt vars.
La oss deretter legge til en funksjon for å 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 av parsing vil kommandolinjeargumentene bli plassert i feltene til vars-strukturen. Hovedfunksjonen til applikasjonen vil være å samle sende- og mottaksveier fra filtre; etter tilkobling av tickeren vil kontrollen overføres til en uendelig sløyfe som, hvis generatorfrekvensen ble satt til ikke-null, vil starte testgeneratoren på nytt slik at det fungerer uten å stoppe.

Generatoren vil trenge disse omstarter på grunn av sin design; av en eller annen grunn kan den ikke produsere et signal som varer mer enn 16 sekunder. Det skal bemerkes at varigheten er spesifisert med et 32-bits tall.

Hele programmet vil se slik ut:

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

La oss kompilere. Da kan programmet kjøres på to datamaskiner. Eller på en, som jeg skal gjøre nå. Vi lanserer 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 bare viser en melding om starten av fangst, så er dette et godt tegn - det betyr at porten vår mest sannsynlig ikke er okkupert av andre programmer. I en annen terminal lanserer vi en programforekomst som vil simulere en "ekstern" intercom ved å spesifisere dette portnummeret:

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

Som det fremgår av programteksten, er standard IP-adresse 127.0.0.1 (local loopback).

I en annen terminal lanserer vi en andre forekomst av programmet, som simulerer en lokal enhet. Vi bruker et ekstra argument som lar den innebygde testgeneratoren fungere:

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

I dette øyeblikket skal pakker som sendes til den "eksterne" enheten begynne å blinke i konsollen med TShark, og en kontinuerlig tone vil høres fra datamaskinens høyttaler.

Hvis alt skjedde som skrevet, starter vi den andre kopien av programmet på nytt, men uten nøkkelen og argumentet "—gen 440". Du vil nå spille rollen som generator. Etter dette kan du lage støy i mikrofonen, du bør høre den tilsvarende lyden i høyttaleren eller hodetelefonene. Akustisk selveksitasjon kan til og med forekomme; skru ned høyttalervolumet og effekten vil forsvinne.

Hvis du kjørte den på to datamaskiner og ikke ble forvirret om IP-adressene, venter det samme resultatet på deg - toveis digital stemmekommunikasjon.

I den neste artikkelen vil vi lære hvordan du skriver våre egne filtre - plugins, takket være denne ferdigheten vil du kunne bruke mediastreameren ikke bare for lyd og video, men også i et annet spesifikt område.

Kilde: www.habr.com

Legg til en kommentar