Esplorare il motore VoIP di Mediastreamer2. Parte 9

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

citofono duplex

Esplorare il motore VoIP di Mediastreamer2. Parte 9

Nel passato Articolo è stato annunciato un citofono duplex, e in questo ce la faremo.

Il diagramma è mostrato nella figura del titolo. La catena inferiore di filtri costituisce il percorso di trasmissione, che inizia dalla scheda audio. Fornisce campioni di segnale dal microfono. Per impostazione predefinita, ciò avviene a una velocità di 8000 campioni al secondo. La profondità di bit dei dati utilizzata dai filtri audio degli streamer multimediali è di 16 bit (questo non è importante; se lo desideri, puoi scrivere filtri che funzioneranno con una profondità di bit maggiore). I dati sono raggruppati in blocchi di 160 campioni. Pertanto, ciascun blocco ha una dimensione di 320 byte. Successivamente, inseriamo i dati nell'ingresso del generatore che, una volta spento, è “trasparente” ai dati. L'ho aggiunto nel caso in cui ti stanchi di parlare al microfono durante il debug: puoi utilizzare il generatore per "sparare" il percorso con un segnale acustico.

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

Dopo il codificatore, i blocchi di dati più leggeri vengono inviati al filtro rtpsend, che li inserirà in un pacchetto RTP, imposterà i flag necessari e li fornirà allo streamer multimediale per la trasmissione sulla rete sotto forma di pacchetto UDP.

La catena superiore di filtri costituisce il percorso di ricezione; i pacchetti RTP ricevuti dal media streamer dalla rete entrano nel filtro rtprecv, all'uscita del quale appaiono sotto forma di blocchi di dati, ciascuno dei quali corrisponde a un pacchetto ricevuto. Il blocco contiene solo i dati del carico utile; nell'articolo precedente erano mostrati in verde nell'illustrazione.

Successivamente, i blocchi vengono inviati al filtro del decodificatore, che converte i campioni a byte singolo contenuti in essi in campioni lineari a 16 bit. Che può già essere elaborato dai filtri dello streamer multimediale. Nel nostro caso li inviamo semplicemente alla scheda audio per la riproduzione sugli altoparlanti delle tue cuffie.

Passiamo ora all'implementazione del software. Per fare ciò, uniremo i file del ricevitore e del trasmettitore che abbiamo separato prima. Prima utilizzavamo impostazioni fisse per porte e indirizzi, ora abbiamo bisogno che il programma possa utilizzare le impostazioni che specifichiamo all'avvio. Per fare ciò, aggiungeremmo funzionalità per l'elaborazione degli argomenti della riga di comando. Dopodiché potremo impostare l'indirizzo IP e la porta dell'interfono con cui vogliamo stabilire una connessione.

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

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

typedef struct _app_vars app_vars;

Il programma dichiarerà una struttura di questo tipo chiamata vars.
Successivamente, 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]);
        }
    }
}

Come risultato dell'analisi, gli argomenti della riga di comando verranno inseriti nei campi della struttura vars. La funzione principale dell'applicazione sarà quella di raccogliere i percorsi di trasmissione e ricezione dai filtri; dopo aver collegato il ticker, il controllo verrà trasferito ad un loop infinito che, se la frequenza del generatore era impostata diversa da zero, riavvierà il generatore di test in modo che funziona senza fermarsi.

Il generatore avrà bisogno di questi riavvii a causa della sua progettazione; per qualche motivo non può produrre un segnale che duri più di 16 secondi. Va notato che la sua durata è specificata da un numero a 32 bit.

L'intero programma sarà simile a questo:

/* Файл 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. Quindi il programma può essere eseguito su due computer. O su uno, come farò ora. Lanciamo 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 nella console viene visualizzato solo un messaggio sull'inizio dell'acquisizione, questo è un buon segno: molto probabilmente la nostra porta non è occupata da altri programmi. In un altro terminale, lanciamo un'istanza del programma che simulerà un citofono “remoto” specificando questo numero di porta:

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

Come si può vedere dal testo del programma, l'indirizzo IP predefinito è 127.0.0.1 (loopback locale).

In un altro terminale lanciamo una seconda istanza del programma, che simula un dispositivo locale. Usiamo un argomento aggiuntivo che consente al generatore di test integrato di funzionare:

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

In questo momento, nella console con TShark, i pacchetti trasmessi al dispositivo “remoto” dovrebbero iniziare a lampeggiare e dall'altoparlante del computer si sentirà un tono continuo.

Se tutto è andato come scritto, riavviamo la seconda copia del programma, ma senza la chiave e l'argomento “—gen 440”. Ora ricoprirai il ruolo di generatore. Successivamente, puoi emettere rumore nel microfono; dovresti sentire il suono corrispondente nell'altoparlante o nelle cuffie. Potrebbe verificarsi anche un'autoeccitazione acustica; abbassare il volume dell'altoparlante e l'effetto scomparirà.

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

Nel prossimo articolo impareremo come scrivere i nostri filtri - plugin, grazie a questa abilità sarai in grado di utilizzare lo streamer multimediale non solo per audio e video, ma anche in qualche altra area specifica.

Fonte: habr.com

Aggiungi un commento