Esploriamo il motore VoIP Mediastreamer2. Parte 9

Il materiale dell'articolo è tratto dal mio canale Zen.

Dispositivo di comunicazione duplex

Esploriamo il motore VoIP Mediastreamer2. Parte 9

Nel passato articolo È stato annunciato un dispositivo di comunicazione duplex, e in questo articolo lo realizzeremo.

Lo schema è mostrato nell'immagine principale. Il circuito inferiore dei filtri forma il percorso di trasmissione, che inizia dalla scheda audio. Essa emette campioni di segnale dal microfono. Per impostazione predefinita, ciò avviene a una velocità di 8000 campioni al secondo. La profondità dei dati utilizzata dai filtri audio del flusso multimediale è di 16 bit (non è fondamentale, se desiderato si possono scrivere filtri che funzionano con una maggiore profondità). I dati sono raggruppati in blocchi di 160 campioni. Pertanto, ogni blocco ha una dimensione di 320 byte. Successivamente, inviamo i dati all'ingresso del generatore, che in stato spento è "trasparente" ai dati. L'ho aggiunto nel caso in cui, durante il debug, vi stanchiate di parlare al microfono: potrete utilizzare il generatore per "colpire" il percorso con un segnale tonale.

Dopo il generatore, il segnale arriva all'encoder, che trasforma i nostri campioni a 16 bit secondo la legge µ (standard G.711) in otto bit. All'uscita dell'encoder abbiamo già un blocco di dati di dimensioni dimezzate. In generale, possiamo trasmettere dati senza compressione, se non è necessario risparmiare traffico. Ma qui è utile utilizzare l'encoder, poiché Wireshark può riprodurre il suono da un flusso RTP solo quando esso è compresso secondo la legge µ o a.

Dopo l'encoder, i blocchi di dati ridotti arrivano al filtro rtpsend, che li inserisce in un pacchetto RTP, imposta i flag necessari e li consegna al media streamer per la trasmissione in rete come pacchetto UDP.

La parte superiore dei filtri forma il percorso di ricezione; i pacchetti RTP ricevuti dal media streamer dalla rete arrivano al filtro rtprecv, la cui uscita consiste già in blocchi di dati, ognuno dei quali corrisponde a un pacchetto ricevuto. Il blocco contiene solo i dati di payload, che nella scorsa articolo erano mostrati in verde nella illustrazione.

Successivamente, i blocchi arrivano al filtro decoder, che trasforma i campioni a un byte contenuti in essi in lineari a 16 bit. Questi possono poi essere elaborati dai filtri del media streamer. Nel nostro caso, li restituiamo semplicemente alla scheda audio per la riproduzione sugli altoparlanti del vostro auricolare.

Ora passiamo all'implementazione software. Per questo unirò i file del ricevitore e del trasmettitore che avevamo separato in precedenza. In precedenza abbiamo utilizzato impostazioni fisse per le porte e gli indirizzi, ma ora abbiamo bisogno che il programma possa utilizzare le impostazioni che specificheremo all'avvio. Per fare ciò, aggiungeremo funzionalità per l'elaborazione degli argomenti della riga di comando. Dopo di che potremo specificare l'indirizzo IP e la porta del dispositivo di comunicazione con cui vogliamo stabilire una connessione.

Per prima cosa, aggiungiamo al programma una struttura che conserverà le sue impostazioni:

struct _app_vars
{
  int  local_port;              /* Porta locale. */
  int  remote_port;             /* Porta del dispositivo di comunicazione sul computer remoto. */
  char remote_addr[128];        /* Indirizzo IP del computer remoto. */
  MSDtmfGenCustomTone dtmf_cfg; /* Impostazioni del segnale di test del generatore. */
};

typedef struct _app_vars app_vars;

Nel programma verrà dichiarata una struttura di questo tipo con il nome vars.
Poi aggiungeremo una funzione per l'analisi degli argomenti della riga di comando:

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

Di conseguenza, gli argomenti della riga di comando verranno inseriti nei campi della struttura vars. La funzione principale dell'applicazione assemblerà i percorsi di trasmissione e ricezione dai filtri, dopo di che, una volta connesso il ticker, il controllo verrà trasferito a un ciclo infinito che, se la frequenza del generatore è stata specificata come non zero, riavvierà il generatore di test — affinché funzioni continuamente.

Questi riavvii saranno necessari al generatore a causa della sua peculiarità costruttiva, per qualche motivo non può emettere un segnale della durata superiore a 16 secondi. Va notato che la durata è specificata da un numero a 32 bit.

Il programma nel suo complesso apparirà così:

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

Compiliamo. Il programma può quindi essere avviato su due computer. Oppure su uno solo, come farò io ora. Avviamo TShark con i seguenti argomenti:

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

Se nel campo di avvio della console appare solo il messaggio di inizio cattura, è un buon segno - significa che la nostra porta probabilmente non è occupata da altri programmi. In un'altra terminale avviamo un'istanza del programma che imiterà il dispositivo di comunicazione "remoto", specificando questo numero di porta:

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

Come si può vedere dal testo del programma, per impostazione predefinita, viene utilizzato l'indirizzo IP 127.0.0.1 (loopback locale).

In un altro terminale avviamo una seconda istanza del programma, che imita un dispositivo locale. Utilizziamo un argomento aggiuntivo che consente il funzionamento del generatore di test integrato:

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

A questo punto, nella console con TShark dovrebbero iniziare ad apparire pacchetti inviati verso il "dispositivo remoto", e dai suoi altoparlanti si sentirà un segnale tonale continuo.

Se tutto è andato secondo i piani, riavviamo la seconda istanza del programma, ma senza opzione e argomento "--gen 440". Adesso tu assumerai il ruolo di generatore. Dopo questo, puoi fare rumore nel microfono; negli altoparlanti o nelle cuffie dovresti sentire il suono corrispondente. Potrebbe anche verificarsi un'autoeccitazione acustica, abbassa il volume dell'altoparlante e l'effetto scomparirà.

Se hai eseguito su due computer e non ti sei confuso con gli indirizzi IP, avrai lo stesso risultato: comunicazione vocale bidirezionale di qualità digitale.

Nel prossimo articolo impareremo a scrivere i nostri filtri - plugin, grazie a questa abilità potrai applicare il media streamer non solo per audio e video, ma anche in qualche altro ambito specifico.

Fonte: habr.com

Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server 🔥 Acquista hosting affidabile per siti web con protezione DDoS, VPS VDS server | ProHoster