Explorant el motor de VoIP Mediastreamer2. Part 9

El material de l'article està extret del meu canal zen.

Intèrfon dúplex

Explorant el motor de VoIP Mediastreamer2. Part 9

En l'últim article es va anunciar un intèrfon dúplex, i en aquest el farem.

El diagrama es mostra a la figura del títol. La cadena inferior de filtres forma el camí de transmissió, que comença des de la targeta de so. Proporciona mostres de senyal del micròfon. Per defecte, això es produeix a una velocitat de 8000 mostres per segon. La profunditat de bits de dades que utilitzen els filtres d'àudio de transmissió multimèdia és de 16 bits (això no és important; si ho voleu, podeu escriure filtres que funcionin amb una profunditat de bits més alta). Les dades s'agrupen en blocs de 160 mostres. Així, cada bloc té una mida de 320 bytes. A continuació, alimentem les dades a l'entrada del generador, que, quan s'apaga, és "transparent" a les dades. L'he afegit en cas que us canseu de parlar al micròfon durant la depuració; podeu utilitzar el generador per "disparar" el camí amb un senyal de to.

Després del generador, el senyal va al codificador, que converteix les nostres mostres de 16 bits segons la llei µ (estàndard G.711) en de vuit bits. A la sortida del codificador, ja tenim un bloc de dades de la meitat de la mida. En general, podem transmetre dades sense compressió si no necessitem estalviar trànsit. Però aquí és útil utilitzar un codificador, ja que Wireshark només pot reproduir àudio d'un flux RTP quan es comprimeix segons la llei µ o la llei a.

Després del codificador, els blocs de dades més lleugers s'envien al filtre rtpsend, que els posarà en un paquet RTP, establirà els senyaladors necessaris i els donarà al transmissor de mitjans per a la seva transmissió a la xarxa en forma de paquet UDP.

La cadena superior de filtres forma el camí de recepció; Els paquets RTP rebuts pel transmissor de mitjans de la xarxa entren al filtre rtprecv, a la sortida del qual apareixen en forma de blocs de dades, cadascun dels quals correspon a un paquet rebut. El bloc només conté dades de càrrega útil; a l'article anterior es mostraven en verd a la il·lustració.

A continuació, els blocs s'envien al filtre descodificador, que converteix les mostres d'un sol byte que hi conté en lineals de 16 bits. Que ja es pot processar mitjançant filtres de transmissió multimèdia. En el nostre cas, simplement els enviem a la targeta de so per reproduir-los als altaveus dels vostres auriculars.

Ara passem a la implementació del programari. Per fer-ho, combinarem els fitxers receptor i transmissor que hem separat abans. Abans, havíem utilitzat configuracions fixes per a ports i adreces, però ara necessitem el programa per poder utilitzar la configuració que especifiquem a l'inici. Per fer-ho, afegiríem una funcionalitat per processar arguments de línia d'ordres. Després d'això podrem establir l'adreça IP i el port de l'intercomunicador amb el qual volem establir una connexió.

Primer, afegim una estructura al programa que emmagatzemarà la seva configuració:

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 d'aquest tipus anomenada vars.
A continuació, afegim una funció per analitzar els arguments de la línia d'ordres:

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

Com a resultat de l'anàlisi, els arguments de la línia d'ordres es col·locaran als camps de l'estructura vars. La funció principal de l'aplicació serà recollir els camins de transmissió i recepció dels filtres; després de connectar el ticker, el control es transferirà a un bucle infinit que, si la freqüència del generador estava configurada en diferent de zero, reiniciarà el generador de prova de manera que funciona sense parar.

El generador necessitarà aquests reinicis pel seu disseny; per alguna raó no pot produir un senyal que duri més de 16 segons. Cal tenir en compte que la seva durada està especificada per un número de 32 bits.

Tot el programa tindrà aquest aspecte:

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

Compilem. Aleshores, el programa es pot executar en dos ordinadors. O en un, com faré ara. Llencem TShark amb els arguments següents:

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

Si el camp d'inici de la consola només mostra un missatge sobre l'inici de la captura, això és un bon senyal: és probable que el nostre port no estigui ocupat per altres programes. En un altre terminal, iniciem una instància de programa que simularà un intercomunicador "remot" especificant aquest número de port:

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

Com es pot veure al text del programa, l'adreça IP predeterminada és 127.0.0.1 (loopback local).

En un altre terminal, iniciem una segona instància del programa, que simula un dispositiu local. Utilitzem un argument addicional que permet que el generador de proves integrat funcioni:

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

En aquest moment, els paquets transmesos cap al dispositiu "remot" haurien de començar a parpellejar a la consola amb TShark i s'escoltarà un to continu des de l'altaveu de l'ordinador.

Si tot va passar tal com està escrit, reiniciem la segona còpia del programa, però sense la clau i l'argument "—gen 440". Ara jugaràs el paper de generador. Després d'això, podeu fer soroll al micròfon; haureu d'escoltar el so corresponent a l'altaveu o als auriculars. Fins i tot es pot produir una autoexcitació acústica; baixeu el volum de l'altaveu i l'efecte desapareixerà.

Si l'heu executat en dos ordinadors i no us heu confós amb les adreces IP, us espera el mateix resultat: comunicació de veu de qualitat digital bidireccional.

En el següent article aprendrem a escriure els nostres propis filtres: complements, gràcies a aquesta habilitat podreu utilitzar el reproductor multimèdia no només per a àudio i vídeo, sinó també en alguna altra àrea específica.

Font: www.habr.com

Afegeix comentari