Explorando o mecanismo VoIP do Mediastreamer2. Parte 9

O material do artigo foi retirado do meu canal zen.

interfone duplex

Explorando o mecanismo VoIP do Mediastreamer2. Parte 9

No passado статье foi anunciado um interfone duplex, e neste vamos fazer.

O diagrama é mostrado na figura do título. A cadeia inferior de filtros forma o caminho de transmissão, que começa na placa de som. Ele fornece amostras de sinal do microfone. Por padrão, isso ocorre a uma taxa de 8000 amostras por segundo. A profundidade de bits de dados que os filtros de áudio do streamer de mídia usam é de 16 bits (isso não é importante; se desejar, você pode escrever filtros que funcionarão com uma profundidade de bits maior). Os dados são agrupados em blocos de 160 amostras. Assim, cada bloco tem 320 bytes de tamanho. A seguir, alimentamos os dados na entrada do gerador, que, quando desligado, fica “transparente” aos dados. Eu adicionei caso você se canse de falar no microfone durante a depuração - você pode usar o gerador para “disparar” o caminho com um sinal de tom.

Após o gerador, o sinal vai para o codificador, que converte nossas amostras de 16 bits de acordo com a lei µ (padrão G.711) em amostras de oito bits. Na saída do codificador, já temos um bloco de dados com metade do tamanho. Em geral, podemos transmitir dados sem compressão se não precisarmos economizar tráfego. Mas aqui é útil usar um codificador, já que o Wireshark pode reproduzir áudio de um fluxo RTP somente quando ele é compactado de acordo com a lei µ ou lei a.

Após o codificador, os blocos de dados mais leves são enviados para o filtro rtpsend, que os colocará em um pacote RTP, definirá os flags necessários e os entregará ao streamer de mídia para transmissão pela rede na forma de um pacote UDP.

A cadeia superior de filtros forma o caminho de recepção; os pacotes RTP recebidos pelo streamer de mídia da rede entram no filtro rtprecv, na saída do qual aparecem na forma de blocos de dados, cada um correspondendo a um pacote recebido. O bloco contém apenas dados de carga útil; no artigo anterior eles foram mostrados em verde na ilustração.

Em seguida, os blocos são enviados para o filtro decodificador, que converte as amostras de byte único neles contidas em amostras lineares de 16 bits. Que já pode ser processado por filtros de streamer de mídia. No nosso caso, simplesmente os enviamos para a placa de som para reprodução nos alto-falantes do seu fone de ouvido.

Agora vamos passar para a implementação do software. Para isso, combinaremos os arquivos do receptor e do transmissor que separamos anteriormente. Antes disso, usávamos configurações fixas para portas e endereços, mas agora precisamos que o programa seja capaz de usar as configurações que especificamos na inicialização. Para fazer isso, adicionaríamos funcionalidade para processar argumentos de linha de comando. Depois disso poderemos definir o endereço IP e a porta do intercomunicador com o qual queremos estabelecer a ligação.

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

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 irá declarar uma estrutura deste tipo chamada vars.
A seguir, vamos adicionar uma função para analisar argumentos de 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 principal função da aplicação será coletar caminhos de transmissão e recepção de filtros; após conectar o ticker, o controle será transferido para um loop infinito que, se a frequência do gerador for definida como diferente de zero, reiniciará o gerador de teste para que funciona sem parar.

O gerador necessitará destas reinicializações devido ao seu design; por alguma razão, ele não pode produzir um sinal com duração superior a 16 segundos. Deve-se observar que sua duração é especificada por um número de 32 bits.

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

Vamos compilar. Então o programa pode ser executado em dois computadores. Ou em um, como farei agora. Lançamos 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 o campo de inicialização no console exibir apenas uma mensagem sobre o início da captura, isso é um bom sinal - significa que nossa porta provavelmente não está ocupada por outros programas. Em outro terminal, lançamos uma instância de programa que irá simular um intercomunicador “remoto” especificando este número de porta:

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

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

Em outro terminal, lançamos uma segunda instância do programa, que simula um dispositivo local. Usamos um argumento adicional que permite que o gerador de teste integrado funcione:

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

Neste momento, os pacotes transmitidos para o dispositivo “remoto” deverão começar a piscar no console com o TShark, e um tom contínuo será ouvido no alto-falante do computador.

Se tudo aconteceu como está escrito, então reiniciamos a segunda cópia do programa, mas sem a chave e o argumento “—gen 440”. Você agora desempenhará o papel de gerador. Depois disso, você pode fazer barulho no microfone; você deverá ouvir o som correspondente no alto-falante ou nos fones de ouvido. Pode até ocorrer autoexcitação acústica; diminua o volume do alto-falante e o efeito desaparecerá.

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

No próximo artigo aprenderemos como escrever nossos próprios filtros - plugins, graças a essa habilidade você poderá utilizar o streamer de mídia não só para áudio e vídeo, mas também em alguma outra área específica.

Fonte: habr.com

Adicionar um comentário