Estudando o motor VoIP Mediastreamer2. Parte 9

O conteúdo deste artigo foi retirado do meu canal Zen.

Dispositivo de comunicação duplex

Estudando o motor VoIP Mediastreamer2. Parte 9

Na parte anterior artigo Foi anunciado um dispositivo de comunicação duplex, e nesta iremos implementá-lo.

O esquema é mostrado na ilustração principal. A cadeia inferior de filtros forma o sistema de transmissão, que começa com a placa de som. Ela fornece amostras de sinal do microfone. Por padrão, isso ocorre a uma taxa de 8000 amostras por segundo. A profundidade de bits utilizada pelos filtros de som do mediastreamer é de 16 bits (isso não é fundamental, se desejado, pode-se escrever filtros que funcionem com maior profundidade). Os dados são agrupados em blocos de 160 amostras. Assim, cada bloco tem um tamanho de 320 bytes. Em seguida, fornecemos os dados na entrada do gerador, que em seu estado desligado é "transparente" para os dados. Eu o adicionei para o caso de, durante a depuração, você ficar cansado de falar no microfone — poderá usar o gerador para "disparar" o sistema com um sinal tonal.

Após o gerador, os sinais são enviados para o codificador, que converte nossas amostras de 16 bits de acordo com a lei µ (padrão G.711) em oito bits. Na saída do codificador, já temos um bloco de dados de tamanho reduzido pela metade. Em geral, podemos transmitir dados sem compressão se não precisarmos economizar largura de banda. Mas aqui é útil usar o codificador, pois o Wireshark pode reproduzir som do fluxo RTP somente quando estiver comprimido pela lei µ ou pela lei a.

Depois do codificador, os blocos de dados aliviados são enviados para o filtro rtpsend, que os coloca em um pacote RTP, define as bandeiras necessárias e os entrega ao mediastreamer para transmissão pela rede na forma de um pacote UDP.

A cadeia superior de filtros forma o sistema de recepção, os pacotes RTP recebidos pelo mediastreamer da rede são enviados para o filtro rtprecv, na saída do qual eles já aparecem na forma de blocos de dados, cada um correspondente a um pacote recebido. O bloco contém apenas os dados da carga útil, que na ilustração do artigo anterior foram mostrados em verde.

Em seguida, os blocos são enviados ao filtro decodificador, que converte as amostras de um byte contidas neles em amostras lineares de 16 bits. Estas já podem ser processadas pelos filtros do mediastreamer. Em nosso caso, simplesmente as enviamos para a placa de som, para reprodução nos alto-falantes do seu fone de ouvido.

Agora, vamos para a implementação do software. Para isso, vamos unir os arquivos do receptor e do transmissor que dividimos anteriormente. Antes, utilizamos configurações fixas de portas e endereços, mas agora precisamos que o programa possa usar as configurações que especificarmos durante a execução. Para isso, adicionaremos a funcionalidade de processamento de argumentos da linha de comando. Assim, poderemos definir o endereço IP e a porta do dispositivo de comunicação com o qual desejamos estabelecer a conexão.

Primeiro, vamos adicionar à programa uma estrutura que armazenará suas configurações:

struct _app_vars
{
  int  local_port;              /* Porta local. */
  int  remote_port;             /* Porta do dispositivo de comunicação no computador remoto. */
  char remote_addr[128];        /* Endereço IP do computador remoto. */
  MSDtmfGenCustomTone dtmf_cfg; /* Configurações do sinal de teste do gerador. */
};

typedef struct _app_vars app_vars;

Na programa, será declarada uma estrutura desse tipo com o nome vars.
Em seguida, adicionaremos a função para analisar os argumentos da linha 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 da análise, os argumentos da linha de comando serão colocados nos campos da estrutura vars. A função principal do aplicativo montará os caminhos de transmissão e recepção a partir dos filtros; após a conexão do ticker, o controle será transferido para um loop infinito que, se a frequência do gerador for definida como não zero, reiniciará o gerador de teste — para que ele funcione continuamente.

Esses reinícios serão necessários para o gerador devido à sua construção, pois aparentemente ele não consegue emitir um sinal de duração superior a 16 segundos. Ao mesmo tempo, deve-se notar que a duração é definida por um número de 32 bits.

O programa como um todo ficará assim:

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

Compilando. Em seguida, o programa pode ser executado em dois computadores. Ou em um, como eu farei agora. Iniciaremos o TShark com os seguintes argumentos:

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

Se a linha de comando no console mostrar apenas uma mensagem de início da captura, isso é um bom sinal - significa que nossa porta provavelmente não está ocupada por outros programas. Em outro terminal, executamos a instância do programa que irá simular o "dispositivo de comunicação" remoto, especificando esse número de porta:

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

Como pode ser visto no texto do programa, por padrão é utilizado o endereço IP 127.0.0.1 (loopback local).

Em outro terminal, iniciamos uma segunda instância do programa que simula um dispositivo local. Usamos um argumento adicional que permite a operação do gerador de teste embutido:

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

Neste momento, no console com TShark, os pacotes enviados para o "dispositivo remoto" devem começar a aparecer e um sinal sonoro contínuo deverá ser ouvido nos alto-falantes do computador.

Se tudo ocorreu como descrito, reiniciamos a segunda instância do programa, mas agora sem a chave e o argumento "--gen 440". Agora, você desempenhará o papel de gerador. Depois disso, você pode fazer barulho no microfone; nos alto-falantes ou fones de ouvido, você deverá ouvir o som correspondente. É possível que ocorra até uma auto-excitação acústica, diminua o volume dos alto-falantes e o efeito desaparecerá.

Se você executou em dois computadores e não se confundiu com os endereços IP, você terá o mesmo resultado - comunicação de voz bidirecional de qualidade digital.

No próximo artigo, aprenderemos a escrever nossos próprios filtros - plugins, com essa habilidade você poderá aplicar o media streamer não apenas para áudio e vídeo, mas também em alguma outra área específica.

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster