Estudiando el motor VoIP Mediastreamer2. Parte 9

El material del artículo fue tomado de mi canal de Zen.

Dispositivo de comunicación dúplex

Estudiando el motor VoIP Mediastreamer2. Parte 9

En el anterior el artículo Se anunció un dispositivo de comunicación dúplex, y en este lo haremos.

El esquema se muestra en la ilustración principal. La cadena inferior de filtros forma la ruta de transmisión, que comienza con la tarjeta de sonido. Ésta emite muestras de señal desde el micrófono. Por defecto, esto ocurre a un ritmo de 8000 muestras por segundo. La resolución de los datos que utilizan los filtros de medios es de 16 bits (esto no es fundamental, si se desea se pueden crear filtros que funcionen con mayores resoluciones). Los datos se agrupan en bloques de 160 muestras. Así, cada bloque tiene un tamaño de 320 bytes. A continuación, enviamos los datos a la entrada del generador, que en estado apagado es "transparente" para los datos. Lo he añadido por si durante la depuración se cansa de hablar en el micrófono; podrá utilizar el generador para "disparar" la ruta con una señal tonal.

Después del generador, los bloques de datos aligerados llegan al codificador, que convierte nuestras muestras de 16 bits según la ley µ (estándar G.711) a ocho bits. En la salida del codificador ya tenemos un bloque de datos de tamaño dos veces menor. En general, podemos transmitir datos sin compresión si no necesitamos ahorrar tráfico. Pero aquí es útil utilizar el codificador, ya que Wireshark puede reproducir sonido desde el flujo RTP solo cuando está comprimido según la ley µ o la ley a.

Después del codificador, los bloques de datos aligerados llegan al filtro rtpsend, que los colocará en el paquete RTP, establecerá las banderas necesarias y los entregará al mediastreamer para su transmisión en la red en forma de paquete UDP.

La cadena superior de filtros forma la ruta de recepción; los paquetes RTP recibidos por el mediastreamer desde la red llegan al filtro rtprecv, cuya salida aparece ya en forma de bloques de datos, cada uno de los cuales corresponde a un paquete recibido. El bloque contiene solo los datos de carga útil, que en el artículo anterior se mostraron en color verde en la ilustración.

A continuación, los bloques llegan al filtro decodificador, que convierte las muestras de un byte en lineales de 16 bits. Éstas ya se pueden procesar con los filtros del mediastreamer. En nuestro caso, simplemente las enviamos a la tarjeta de sonido para su reproducción en los altavoces de su auricular.

Ahora pasemos a la implementación del software. Para ello, combinaremos los archivos del receptor y el transmisor que hemos separado anteriormente. Antes, utilizamos configuraciones fijas de puertos y direcciones, pero ahora necesitamos que el programa pueda utilizar las configuraciones que especificaremos al iniciarlo. Para esto, añadiremos la funcionalidad de procesamiento de argumentos de línea de comandos. Después de esto, podremos especificar la dirección IP y el puerto del dispositivo de comunicación con el que queremos establecer una conexión.

Primero, agregaremos a la programa una estructura que almacenará su configuración:

struct _app_vars
{
  int  local_port;              /* Puerto local. */
  int  remote_port;             /* Puerto del dispositivo de comunicación en la computadora remota. */
  char remote_addr[128];        /* Dirección IP de la computadora remota. */
  MSDtmfGenCustomTone dtmf_cfg; /* Configuración de la señal de prueba del generador. */
};

typedef struct _app_vars app_vars;

En la programa se declarará una estructura de este tipo con el nombre vars.
Luego, agregaremos una función para analizar los argumentos de línea 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 del análisis, los argumentos de línea de comandos se colocarán en los campos de la estructura vars. La función principal de la aplicación ensamblará los filtros para los tramos de transmisión y recepción, y tras conectar el ticker, el control se transferirá a un ciclo infinito que, si la frecuencia del generador se ha establecido en un valor distinto de cero, reiniciará el generador de prueba para que funcione sin parar.

Estos reinicios serán necesarios para el generador debido a su peculiaridad de construcción; por alguna razón, no puede emitir una señal con una duración superior a 16 segundos. Cabe señalar que la duración se define con un número de 32 bits.

El programa en su totalidad se verá así:

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

Compilamos. Luego, el programa se puede ejecutar en dos computadoras. O en una sola, como haré yo ahora. Ejecutamos TShark con los siguientes argumentos:

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

Si en la consola solo aparece el mensaje que indica el inicio de la captura, eso es una buena señal: significa que nuestro puerto probablemente no está ocupado por otros programas. En otra terminal, ejecutamos una instancia del programa que simulará un dispositivo de comunicación "remoto", especificándole este número de puerto:

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

Como se puede ver en el texto del programa, por defecto se usa la dirección IP 127.0.0.1 (bucle local).

En otra terminal, iniciamos una segunda instancia del programa, que simula un dispositivo local. Utilizamos un argumento adicional que permite el funcionamiento del generador de pruebas integrado:

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

En este momento, en la consola con TShark deberían comenzar a aparecer paquetes enviados hacia el "dispositivo remoto", y de los altavoces de la computadora debería salir un tono continuo.

Si todo ocurrió como se esperaba, reiniciamos la segunda instancia del programa, pero ahora sin la clave y el argumento "—gen 440". Ahora serás tú quien desempeñe el papel del generador. Después de esto, puedes hacer ruido en el micrófono; deberías escuchar el sonido correspondiente en los altavoces o auriculares. Puede que incluso se produzca un acoplamiento acústico, baja el volumen del altavoz y el efecto desaparecerá.

Si has iniciado en dos computadoras y no te has confundido con las direcciones IP, te espera el mismo resultado: comunicación bidireccional de voz de calidad digital.

En el siguiente artículo, aprenderemos a escribir nuestros propios filtros: plugins, gracias a esta habilidad podrás utilizar el transmisor de medios no solo para audio y video, sino también en alguna otra área específica.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster