De Mediastreamer2 VoIP-engine verkennen. Deel 9

Het materiaal van het artikel is afkomstig uit mijn zen-kanaal.

Duplex-intercom

De Mediastreamer2 VoIP-engine verkennen. Deel 9

In het verleden статье er werd een duplex-intercom aangekondigd, en deze gaan we maken.

Het diagram wordt weergegeven in de titelafbeelding. De onderste filterketen vormt het transmissiepad, dat begint bij de geluidskaart. Het biedt signaalmonsters van de microfoon. Standaard gebeurt dit met een snelheid van 8000 samples per seconde. De databitdiepte die de audiofilters van mediastreamers gebruiken is 16 bits (dit is niet belangrijk; als u dat wenst, kunt u filters schrijven die met een hogere bitdiepte werken). De gegevens zijn gegroepeerd in blokken van 160 monsters. Elk blok is dus 320 bytes groot. Vervolgens voeren we de data naar de ingang van de generator, die, wanneer uitgeschakeld, “transparant” is voor de data. Ik heb het toegevoegd voor het geval je het beu wordt om tijdens het debuggen in de microfoon te praten - je kunt de generator gebruiken om het pad te "schieten" met een toonsignaal.

Na de generator gaat het signaal naar de encoder, die onze 16-bits samples volgens de µ-wet (G.711-standaard) omzet in XNUMX-bits samples. Aan de uitgang van de encoder hebben we al een datablok dat half zo groot is. Over het algemeen kunnen we gegevens zonder compressie verzenden als we geen verkeer hoeven te besparen. Maar hier is het handig om een ​​encoder te gebruiken, aangezien Wireshark alleen audio van een RTP-stream kan reproduceren als deze is gecomprimeerd volgens de µ-wet of a-wet.

Na de encoder worden de lichtere datablokken naar het rtpsend-filter gestuurd, dat ze in een RTP-pakket plaatst, de nodige vlaggen instelt en ze aan de mediastreamer geeft voor verzending over het netwerk in de vorm van een UDP-pakket.

De bovenste keten van filters vormt het ontvangstpad; RTP-pakketten die door de mediastreamer van het netwerk worden ontvangen, komen in het rtprecv-filter terecht, aan de uitgang waarvan ze verschijnen in de vorm van datablokken, die elk overeenkomen met één ontvangen pakket. Het blok bevat alleen payload-gegevens; in het vorige artikel waren ze in de afbeelding groen weergegeven.

Vervolgens worden de blokken naar het decoderfilter gestuurd, dat de daarin aanwezige single-byte samples omzet in lineaire 16-bits samples. Die kunnen al worden verwerkt door mediastreamerfilters. In ons geval sturen we ze gewoon naar de geluidskaart om af te spelen op de speakers van je headset.

Laten we nu verder gaan met de software-implementatie. Om dit te doen, zullen we de ontvanger- en zenderbestanden combineren die we eerder hebben gescheiden. Voorheen gebruikten we vaste instellingen voor poorten en adressen, maar nu hebben we het programma nodig om de instellingen te kunnen gebruiken die we opgeven bij het opstarten. Om dit te doen, zouden we functionaliteit toevoegen voor het verwerken van opdrachtregelargumenten. Daarna kunnen we het IP-adres en de poort instellen van de intercom waarmee we verbinding willen maken.

Laten we eerst een structuur aan het programma toevoegen waarin de instellingen worden opgeslagen:

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

typedef struct _app_vars app_vars;

Het programma zal een structuur van dit type declareren, genaamd vars.
Laten we vervolgens een functie toevoegen om opdrachtregelargumenten te ontleden:

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

Als gevolg van het parseren worden de opdrachtregelargumenten in de velden van de vars-structuur geplaatst. De belangrijkste functie van de applicatie is het verzamelen van zend- en ontvangstpaden van filters; na het aansluiten van de ticker wordt de besturing overgedragen naar een oneindige lus die, als de generatorfrequentie op niet nul is ingesteld, de testgenerator opnieuw zal opstarten, zodat het werkt zonder te stoppen.

De generator heeft deze herstarts nodig vanwege zijn ontwerp; om de een of andere reden kan hij geen signaal produceren dat langer dan 16 seconden duurt. Opgemerkt moet worden dat de duur ervan wordt gespecificeerd door een 32-bits getal.

Het gehele programma zal er als volgt uitzien:

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

Laten we compileren. Vervolgens kan het programma op twee computers worden uitgevoerd. Of op één, zoals ik nu zal doen. We starten TShark met de volgende argumenten:

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

Als het startveld in de console alleen een bericht weergeeft over het begin van de opname, dan is dit een goed teken: het betekent dat onze poort hoogstwaarschijnlijk niet bezet is door andere programma's. In een andere terminal lanceren we een programma-instantie die een “externe” intercom zal simuleren door dit poortnummer op te geven:

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

Zoals uit de programmatekst blijkt, is het standaard IP-adres 127.0.0.1 (lokale loopback).

In een andere terminal starten we een tweede exemplaar van het programma, dat een lokaal apparaat simuleert. We gebruiken een extra argument waarmee de ingebouwde testgenerator kan werken:

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

Op dit moment zouden pakketten die naar het "externe" apparaat worden verzonden, in de console met TShark moeten beginnen te knipperen, en er zal een ononderbroken toon uit de computerluidspreker klinken.

Als alles is gebeurd zoals geschreven, starten we het tweede exemplaar van het programma opnieuw op, maar zonder de sleutel en het argument “—gen 440”. Je speelt nu de rol van generator. Hierna kunt u geluid in de microfoon maken; u zou het overeenkomstige geluid in de luidspreker of hoofdtelefoon moeten horen. Er kan zelfs akoestische zelfexcitatie optreden; zet het luidsprekervolume lager en het effect zal verdwijnen.

Als u het op twee computers hebt uitgevoerd en niet in de war bent geraakt over de IP-adressen, wacht u hetzelfde resultaat: tweerichtingsspraakcommunicatie van digitale kwaliteit.

In het volgende artikel zullen we leren hoe we onze eigen filters - plug-ins kunnen schrijven. Dankzij deze vaardigheid kun je de mediastreamer niet alleen voor audio en video gebruiken, maar ook op een ander specifiek gebied.

Bron: www.habr.com

Voeg een reactie