Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 9

Das Material des Artikels stammt von mir Zen-Kanal.

Duplex-Gegensprechanlage

Entdecken Sie die VoIP-Engine von Mediastreamer2. Teil 9

In der Vergangenheit Artikel eine Duplex-Gegensprechanlage wurde angekündigt, und in dieser werden wir es schaffen.

Das Diagramm ist in der Titelabbildung dargestellt. Die untere Filterkette bildet den Übertragungsweg, der von der Soundkarte ausgeht. Es liefert Signalproben vom Mikrofon. Standardmäßig erfolgt dies mit einer Rate von 8000 Samples pro Sekunde. Die von Media-Streamer-Audiofiltern verwendete Datenbittiefe beträgt 16 Bit (dies ist nicht wichtig; wenn Sie möchten, können Sie Filter schreiben, die mit einer höheren Bittiefe arbeiten). Die Daten werden in Blöcke von 160 Proben gruppiert. Somit ist jeder Block 320 Byte groß. Als nächstes speisen wir die Daten in den Eingang des Generators ein, der im ausgeschalteten Zustand für die Daten „transparent“ ist. Ich habe es hinzugefügt, falls Sie es satt haben, während des Debuggens in das Mikrofon zu sprechen – Sie können den Generator verwenden, um den Pfad mit einem Tonsignal zu „schießen“.

Nach dem Generator gelangt das Signal zum Encoder, der unsere 16-Bit-Samples gemäß dem µ-Gesetz (G.711-Standard) in XNUMX-Bit-Samples umwandelt. Am Ausgang des Encoders haben wir bereits einen halb so großen Datenblock. Im Allgemeinen können wir Daten ohne Komprimierung übertragen, wenn wir keinen Datenverkehr einsparen müssen. Hier ist es jedoch sinnvoll, einen Encoder zu verwenden, da Wireshark Audio aus einem RTP-Stream nur dann wiedergeben kann, wenn dieser nach dem µ-Law oder A-Law komprimiert ist.

Nach dem Encoder werden die leichteren Datenblöcke an den rtpsend-Filter gesendet, der sie in ein RTP-Paket packt, die erforderlichen Flags setzt und sie an den Medienstreamer zur Übertragung über das Netzwerk in Form eines UDP-Pakets weitergibt.

Die obere Filterkette bildet den Empfangspfad; RTP-Pakete, die der Medienstreamer aus dem Netzwerk empfängt, gelangen in den Rtprecv-Filter, an dessen Ausgang sie in Form von Datenblöcken erscheinen, die jeweils einem empfangenen Paket entsprechen. Der Block enthält ausschließlich Nutzdaten, im vorherigen Artikel waren diese in der Abbildung grün dargestellt.

Anschließend werden die Blöcke an den Decoderfilter gesendet, der die darin enthaltenen Einzelbyte-Abtastwerte in lineare 16-Bit-Abtastwerte umwandelt. Was bereits von Media-Streamer-Filtern verarbeitet werden kann. In unserem Fall senden wir sie einfach an die Soundkarte zur Wiedergabe über die Lautsprecher Ihres Headsets.

Kommen wir nun zur Softwareimplementierung. Dazu kombinieren wir die zuvor getrennten Empfänger- und Senderdateien. Früher haben wir feste Einstellungen für Ports und Adressen verwendet, jetzt muss das Programm die Einstellungen verwenden können, die wir beim Start festlegen. Dazu würden wir Funktionen zur Verarbeitung von Befehlszeilenargumenten hinzufügen. Anschließend können wir die IP-Adresse und den Port der Gegensprechanlage einstellen, mit der wir eine Verbindung herstellen möchten.

Fügen wir dem Programm zunächst eine Struktur hinzu, die seine Einstellungen speichert:

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

typedef struct _app_vars app_vars;

Das Programm deklariert eine Struktur dieses Typs namens vars.
Als Nächstes fügen wir eine Funktion zum Parsen von Befehlszeilenargumenten hinzu:

/* Функция преобразования аргументов командной строки в
* настройки программы. */
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 Ergebnis der Analyse werden die Befehlszeilenargumente in den Feldern der vars-Struktur platziert. Die Hauptfunktion der Anwendung besteht darin, Sende- und Empfangspfade von Filtern zu sammeln. Nach dem Anschließen des Tickers wird die Steuerung an eine Endlosschleife übergeben, die, wenn die Generatorfrequenz auf einen Wert ungleich Null eingestellt wurde, den Testgenerator neu startet es funktioniert ohne Unterbrechung.

Der Generator benötigt aufgrund seiner Konstruktion diese Neustarts; aus irgendeinem Grund kann er kein Signal erzeugen, das länger als 16 Sekunden dauert. Es ist zu beachten, dass seine Dauer durch eine 32-Bit-Zahl angegeben wird.

Das gesamte Programm sieht folgendermaßen aus:

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

Lassen Sie uns kompilieren. Dann kann das Programm auf zwei Computern ausgeführt werden. Oder auf einem, wie ich es jetzt tun werde. Wir starten TShark mit den folgenden Argumenten:

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

Wenn im Startfeld der Konsole nur eine Meldung über den Beginn der Aufnahme angezeigt wird, ist dies ein gutes Zeichen – es bedeutet, dass unser Port höchstwahrscheinlich nicht von anderen Programmen belegt ist. In einem anderen Terminal starten wir eine Programminstanz, die durch Angabe dieser Portnummer eine „entfernte“ Gegensprechanlage simuliert:

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

Wie aus dem Programmtext hervorgeht, lautet die Standard-IP-Adresse 127.0.0.1 (lokaler Loopback).

In einem anderen Terminal starten wir eine zweite Instanz des Programms, die ein lokales Gerät simuliert. Wir verwenden ein zusätzliches Argument, das die Funktion des integrierten Testgenerators ermöglicht:

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

In diesem Moment sollten die an das „entfernte“ Gerät übertragenen Pakete in der Konsole mit TShark zu blinken beginnen und aus dem Computerlautsprecher ist ein Dauerton zu hören.

Wenn alles wie beschrieben abgelaufen ist, starten wir die zweite Kopie des Programms neu, jedoch ohne den Schlüssel und das Argument „—gen 440“. Sie übernehmen nun die Rolle des Generators. Anschließend können Sie Geräusche in das Mikrofon einspeisen; Sie sollten den entsprechenden Ton im Lautsprecher oder Kopfhörer hören. Es kann sogar zu akustischer Selbsterregung kommen; reduzieren Sie die Lautsprecherlautstärke und der Effekt verschwindet.

Wenn Sie es auf zwei Computern ausgeführt haben und nicht über die IP-Adressen verwirrt waren, erwartet Sie das gleiche Ergebnis – bidirektionale Sprachkommunikation in digitaler Qualität.

Im nächsten Artikel erfahren Sie, wie Sie unsere eigenen Filter-Plugins schreiben. Dank dieser Fähigkeit können Sie den Media Streamer nicht nur für Audio und Video, sondern auch in einem anderen spezifischen Bereich verwenden.

Source: habr.com

Kommentar hinzufügen