Esploriamo il motore VoIP Mediastreamer2. Parte 9

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

Apparecchio per conferenze duplex

Esploriamo il motore VoIP Mediastreamer2. Parte 9

Nell'articolo precedente abbiamo chiarito il corretto completamento dei programmi che utilizzano il mediastreamer. È stato annunciato un apparecchio per conferenze duplex, e in questo articolo lo realizzeremo.

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

Dopo il generatore, i blocchi di dati alleggeriti passano al codificatore, che converte i nostri campioni a 16 bit secondo la legge µ (standard G.711) in otto bit. All'uscita del codificatore abbiamo già un blocco di dati di dimensioni dimezzate. In generale, possiamo trasmettere i dati senza compressione se non abbiamo bisogno di risparmiare traffico. Tuttavia, qui è utile utilizzare il codificatore, poiché Wireshark può riprodurre il suono dal flusso RTP solo se è compresso secondo la legge µ o a.

Dopo il codificatore, i blocchi di dati alleggeriti passano al filtro rtpsend, che li inserirà in un pacchetto RTP, imposterà i flag necessari e li passerà al mediastreamer per la trasmissione sulla rete sotto forma di pacchetto UDP.

Il circuito superiore dei filtri forma il percorso di ricezione; i pacchetti RTP, ricevuti dal mediastreamer dalla rete, passano al filtro rtprecv, all'uscita del quale appaiono già come blocchi di dati, ognuno dei quali corrisponde a un pacchetto ricevuto. Il blocco contiene solo i dati del payload, che nell'articolo precedente erano mostrati in verde nell'illustrazione.

Successivamente, i blocchi passano al filtro decodificatore, che converte i campioni a un byte in lineari, a 16 bit. Questi possono già essere elaborati dai filtri del mediastreamer. Nel nostro caso, semplicemente li inviamo alla scheda audio, per la riproduzione sugli altoparlanti delle vostre cuffie.

Ora passiamo all'implementazione del programma. Per questo uniremo i file del ricevitore e del trasmettitore che abbiamo separato in precedenza. In precedenza abbiamo utilizzato impostazioni fisse per porte e indirizzi, ma ora dobbiamo fare in modo che il programma possa utilizzare le impostazioni che specificeremo all'avvio. A tal fine aggiungeremo la funzionalità per elaborare gli 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.

Prima aggiungiamo al programma una struttura che conterrà le sue impostazioni:

struct _app_vars
{
  int  local_port;              
  int  remote_port;             
  char remote_addr[128];        
  MSDtmfGenCustomTone dtmf_cfg; 
};

typedef struct _app_vars app_vars;

Nel programma sarà dichiarata una struttura di questo tipo con il nome vars.
Poi aggiungiamo una funzione per analizzare gli 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]);
        }
    }
}

A seguito dell'analisi, gli argomenti della riga di comando saranno inseriti nei campi della struttura vars. La funzione principale dell'applicazione assemblerà i filtri per i percorsi di trasmissione e ricezione; dopo la connessione del ticker, il controllo sarà trasferito a un ciclo infinito che, se la frequenza del generatore è stata impostata su valore diverso da zero, riavvierà il generatore di test affinché funzioni senza interruzioni.

Questi riavvii saranno necessari per il generatore a causa della sua peculiarità costruttiva; per qualche motivo non riesce a emettere un segnale della durata superiore a 16 secondi. È importante notare che la durata è definita da un numero a 32 bit.

Il programma nel suo insieme 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. Successivamente, il programma può essere eseguito su due computer. Oppure su uno solo, come farò io adesso. 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 compare solo un messaggio che indica l'inizio della 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 simulerà un dispositivo di comunicazione "remoto" specificando questo numero di porta:

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

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

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

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

In questo momento, nella console con TShark dovrebbero iniziare a comparire pacchetti inviati verso il "dispositivo remoto", e dal diffusore del computer si sentirà un segnale tonale continua.

Se tutto è andato come previsto, riavviamo il secondo istanza del programma, ma già senza la chiave e l'argomento "—gen 440". Il ruolo del generatore ora sarà svolto da te. Dopo di ciò, puoi fare rumore nel microfono, nel diffusore o nelle cuffie dovresti sentire il suono corrispondente. È possibile che si verifichi un'autoeccitazione acustica, abbassa il volume del diffusore e l'effetto svanirà.

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

Nell'articolo seguente apprenderemo come scrivere i nostri filtri – plugin, grazie a questa abilità potrai utilizzare il mediastreamer non solo per audio e video, ma anche in qualche altra area specifica.

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