Explorando o motor de VoIP Mediastreamer2. Parte 9

O material do artigo está tirado do meu canle zen.

Intercomunicador dúplex

Explorando o motor de VoIP Mediastreamer2. Parte 9

No último Artigo anunciouse un intercomunicador dúplex, e neste farémolo.

O diagrama móstrase na figura do título. A cadea inferior de filtros forma o camiño de transmisión, que parte da tarxeta de son. Ofrece mostras de sinal do micrófono. Por defecto, isto ocorre a unha velocidade de 8000 mostras por segundo. A profundidade de bits de datos que usan os filtros de audio de transmisión multimedia é de 16 bits (isto non é importante; se o desexa, pode escribir filtros que funcionen cunha profundidade de bits maior). Os datos agrúpanse en bloques de 160 mostras. Así, cada bloque ten un tamaño de 320 bytes. A continuación, alimentamos os datos á entrada do xerador, que, cando está apagado, é "transparente" aos datos. Engadino no caso de que te cansas de falar polo micrófono durante a depuración: podes usar o xerador para "disparar" o camiño cun sinal de ton.

Despois do xerador, o sinal vai ao codificador, que converte as nosas mostras de 16 bits segundo a lei µ (estándar G.711) en oito bits. Na saída do codificador, xa temos un bloque de datos a metade do tamaño. En xeral, podemos transmitir datos sen compresión se non necesitamos aforrar tráfico. Pero aquí é útil usar un codificador, xa que Wireshark pode reproducir audio dun fluxo RTP só cando se comprime segundo a lei µ ou a lei.

Despois do codificador, os bloques de datos máis lixeiros envíanse ao filtro rtpsend, que os colocará nun paquete RTP, establecerá as bandeiras necesarias e entregaraos ao transmisor multimedia para a súa transmisión pola rede en forma de paquete UDP.

A cadea superior de filtros forma o camiño de recepción; os paquetes RTP recibidos polo transmisor multimedia desde a rede entran no filtro rtprecv, na saída do cal aparecen en forma de bloques de datos, cada un dos cales corresponde a un paquete recibido. O bloque só contén datos de carga útil; no artigo anterior mostráronse en verde na ilustración.

A continuación, os bloques son enviados ao filtro decodificador, que converte as mostras dun só byte contidas neles en lineais de 16 bits. Que xa se pode procesar mediante filtros de transmisión multimedia. No noso caso, simplemente enviámolos á tarxeta de son para reproducilos nos altofalantes dos teus auriculares.

Agora imos pasar á implementación do software. Para iso, combinaremos os ficheiros do receptor e do transmisor que separamos antes. Antes usabamos configuracións fixas para portos e enderezos, pero agora necesitamos o programa para poder utilizar a configuración que especificamos no inicio. Para iso, engadiriamos unha funcionalidade para procesar argumentos da liña de comandos. Despois do cal poderemos establecer o enderezo IP e o porto do intercomunicador co que queremos establecer unha conexión.

Primeiro, imos engadir unha estrutura ao programa que almacenará a súa configuración:

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

typedef struct _app_vars app_vars;

O programa declarará unha estrutura deste tipo chamada vars.
A continuación, engademos unha función para analizar os argumentos da liña de comandos:

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

Como resultado da análise, os argumentos da liña de comandos colocaranse nos campos da estrutura vars. A función principal da aplicación será recoller os camiños de transmisión e recepción dos filtros; despois de conectar o ticker, o control transferirase a un bucle infinito que, se a frecuencia do xerador se estableceu en diferente de cero, reiniciará o xerador de proba para que funciona sen parar.

O xerador necesitará estes reinicios debido ao seu deseño; por algún motivo non pode producir un sinal que dure máis de 16 segundos. Nótese que a súa duración está especificada por un número de 32 bits.

Todo o programa terá o seguinte aspecto:

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

Compilemos. A continuación, o programa pódese executar en dous ordenadores. Ou nun, como farei agora. Lanzamos TShark cos seguintes argumentos:

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

Se o campo de lanzamento da consola só amosa unha mensaxe sobre o inicio da captura, isto é un bo sinal: é probable que o noso porto non estea ocupado por outros programas. Noutro terminal, lanzamos unha instancia de programa que simulará un intercomunicador "remoto" especificando este número de porto:

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

Como se pode ver no texto do programa, o enderezo IP predeterminado é 127.0.0.1 (loopback local).

Noutro terminal, lanzamos unha segunda instancia do programa, que simula un dispositivo local. Usamos un argumento adicional que permite que o xerador de probas integrado funcione:

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

Neste momento, os paquetes transmitidos cara ao dispositivo "remoto" deberían comezar a parpadear na consola con TShark e escoitarase un ton continuo dende o altofalante do ordenador.

Se todo aconteceu como está escrito, entón reiniciamos a segunda copia do programa, pero sen a clave e o argumento "—gen 440". Agora xogarás o papel de xerador. Despois diso, podes facer ruído no micrófono; deberías escoitar o son correspondente no altofalante ou nos auriculares. Incluso pode producirse unha autoexcitación acústica; baixa o volume do altofalante e o efecto desaparecerá.

Se o executou en dous ordenadores e non se confundiu sobre os enderezos IP, o mesmo resultado agarda por vostede: comunicación de voz de calidade dixital bidireccional.

No seguinte artigo aprenderemos a escribir os nosos propios filtros: complementos, grazas a esta habilidade poderás usar o streamer multimedia non só para audio e vídeo, senón tamén nalgunha outra área específica.

Fonte: www.habr.com

Compre hospedaxe fiable para sitios con protección DDoS, servidores VPS VDS 🔥 Compra aloxamento web fiable con protección DDoS, servidores VPS VDS | ProHoster