Explorando el motor VoIP de Mediastreamer2. Parte 9

El material del artículo está tomado de mi canal zen.

intercomunicador dúplex

Explorando el motor VoIP de Mediastreamer2. Parte 9

En el pasado статье Se anunció un intercomunicador dúplex, y en este lo haremos.

El diagrama se muestra en la figura del título. La cadena inferior de filtros forma la ruta de transmisión, que comienza en la tarjeta de sonido. Proporciona muestras de señal del micrófono. De forma predeterminada, esto ocurre a una velocidad de 8000 muestras por segundo. La profundidad de bits de datos que utilizan los filtros de audio del transmisor multimedia es de 16 bits (esto no es importante; si lo desea, puede escribir filtros que funcionen con una profundidad de bits mayor). Los datos se agrupan en bloques de 160 muestras. Por tanto, cada bloque tiene un tamaño de 320 bytes. A continuación, alimentamos los datos a la entrada del generador, que, cuando se apaga, es "transparente" a los datos. Lo agregué en caso de que te canses de hablar por el micrófono durante la depuración; puedes usar el generador para "disparar" la ruta con una señal de tono.

Después del generador, la señal pasa al codificador, que convierte nuestras muestras de 16 bits según la ley µ (estándar G.711) en muestras de ocho bits. A la salida del codificador ya tenemos un bloque de datos de la mitad de tamaño. En general, podemos transmitir datos sin comprimir si no necesitamos ahorrar tráfico. Pero aquí es útil utilizar un codificador, ya que Wireshark puede reproducir audio de un flujo RTP solo cuando está comprimido según la ley µ o la ley a.

Después del codificador, los bloques de datos más ligeros se envían al filtro rtpsend, que los colocará en un paquete RTP, establecerá las banderas necesarias y se los entregará al transmisor de medios para su transmisión a través de la red en forma de paquete UDP.

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

A continuación, los bloques se envían al filtro decodificador, que convierte las muestras de un solo byte que contienen en muestras lineales de 16 bits. Que ya pueden ser procesados ​​por filtros de transmisión de medios. En nuestro caso, simplemente los enviamos a la tarjeta de sonido para su reproducción en los altavoces de nuestros auriculares.

Ahora pasemos a la implementación del software. Para ello combinaremos los archivos de receptor y transmisor que separamos antes. Antes de esto, usábamos configuraciones fijas para puertos y direcciones, pero ahora necesitamos que el programa pueda usar las configuraciones que especificamos al inicio. Para hacer esto, agregaríamos funcionalidad para procesar argumentos de línea de comando. Tras lo cual ya podremos configurar la dirección IP y el puerto del intercomunicador con el que queremos establecer conexión.

Primero, agreguemos una estructura al programa que almacenará su 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;

El programa declarará una estructura de este tipo llamada vars.
A continuación, agreguemos una función para analizar los argumentos de la línea de 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]);
        }
    }
}

Como resultado del análisis, los argumentos de la línea de comando se colocarán en los campos de la estructura vars. La función principal de la aplicación será recopilar rutas de transmisión y recepción de los filtros; después de conectar el ticker, el control se transferirá a un bucle infinito que, si la frecuencia del generador se estableció en un valor distinto de cero, reiniciará el generador de prueba para que Funciona sin parar.

El generador necesitará estos reinicios debido a su diseño; por alguna razón no puede producir una señal que dure más de 16 segundos. Cabe señalar que su duración está especificada por un número de 32 bits.

Todo el programa 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);
    }
}

Compilemos. Entonces el programa se puede ejecutar en dos computadoras. O en uno, como haré ahora. Lanzamos 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 el campo de inicio en la consola muestra solo un mensaje sobre el inicio de la captura, entonces esto es una buena señal: significa que nuestro puerto probablemente no esté ocupado por otros programas. En otra terminal lanzamos una instancia del programa que simulará un intercomunicador “remoto” especificando este número de puerto:

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

Como puede verse en el texto del programa, la dirección IP predeterminada es 127.0.0.1 (bucle invertido local).

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

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

En este momento, los paquetes transmitidos hacia el dispositivo “remoto” deberían comenzar a parpadear en la consola con TShark y se escuchará un tono continuo desde el altavoz de la computadora.

Si todo sucedió como está escrito, reiniciamos la segunda copia del programa, pero sin la clave y el argumento “—gen 440”. Ahora desempeñarás el papel de generador. Después de esto, puedes hacer ruido en el micrófono; deberías escuchar el sonido correspondiente en el altavoz o en los auriculares. Incluso puede producirse una autoexcitación acústica; baje el volumen del altavoz y el efecto desaparecerá.

Si lo ejecutó en dos computadoras y no se confundió con las direcciones IP, le espera el mismo resultado: comunicación de voz bidireccional con calidad digital.

En el próximo artículo aprenderemos cómo escribir nuestros propios filtros - complementos, 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

Añadir un comentario