Étudions le moteur VoIP Mediastreamer2. Partie 9

Le contenu de cet article est tiré de ma chaîne Zen.

Appareil de communication duplex

Étudions le moteur VoIP Mediastreamer2. Partie 9

Dans le passé, article Un appareil de communication duplex a été annoncé, et dans cet article, nous allons le réaliser.

Le schéma est présenté sur l'illustration principale. La chaîne de filtres inférieure constitue le chemin de transmission, qui commence par la carte son. Elle émet des échantillons de signal à partir du microphone. Par défaut, cela se fait à un rythme de 8000 échantillons par seconde. La profondeur des données utilisées par les filtres audio du médiastreamer est de 16 bits (ce n'est pas essentiel, si vous le souhaitez, il est possible d'écrire des filtres qui fonctionneront avec une plus grande profondeur). Les données sont regroupées en blocs de 160 échantillons. Ainsi, chaque bloc a une taille de 320 octets. Ensuite, nous envoyons les données à l'entrée du générateur, qui est "transparent" pour les données lorsqu'il est désactivé. Je l'ai ajouté au cas où vous vous fatigueriez de parler dans le microphone lors du débogage - vous pouvez utiliser le générateur pour "tirer" le chemin avec un signal tonale.

Après le générateur, les blocs de données allégés passent par l'encodeur, qui convertit nos échantillons de 16 bits selon la loi µ (norme G.711) en échantillons de huit bits. À la sortie de l'encodeur, nous avons déjà un bloc de données de taille deux fois plus petite. En général, nous pouvons transmettre des données sans compression si nous n'avons pas besoin d'économiser de la bande passante. Mais ici, il est utile d'utiliser l'encodeur, car Wireshark peut reproduire le son du flux RTP uniquement lorsqu'il est compressé selon la loi µ ou a.

Après l'encodeur, les blocs de données allégés arrivent au filtre rtpsend, qui les placera dans un paquet RTP, établira les drapeaux nécessaires et les transmettra au médiastreamer pour les envoyer sur le réseau sous forme de paquet UDP.

La chaîne de filtres supérieure forme le chemin de réception, les paquets RTP reçus par le médiastreamer depuis le réseau passent par le filtre rtprecv, dont la sortie contient des blocs de données, chacun correspondant à un paquet reçu. Chaque bloc ne contient que les données utiles, qui étaient représentées en vert sur l'illustration de l'article précédent.

Ensuite, les blocs passent par le filtre décodeur, qui convertit les échantillons unidimensionnels qu'ils contiennent en linéaires, en 16 bits. Ceux-ci peuvent être traités par les filtres du médiastreamer. Dans notre cas, nous les envoyons simplement à la carte son pour une reproduction sur les haut-parleurs de votre casque.

Passons maintenant à la mise en œuvre logicielle. Pour cela, nous allons combiner les fichiers du récepteur et du transmetteur que nous avons séparés précédemment. Auparavant, nous avions utilisé des paramètres de ports et d'adresses fixes, mais maintenant nous devons permettre au programme d'utiliser les paramètres que nous spécifierons lors du lancement. Pour cela, nous ajouterons des fonctionnalités pour traiter les arguments de la ligne de commande. Après cela, nous pourrons spécifier l'adresse IP et le port de l'appareil de communication avec lequel nous souhaitons établir une connexion.

Commençons par ajouter à notre programme une structure qui stockera ses paramètres :

struct _app_vars
{
  int  local_port;              
  int  remote_port;             
  char remote_addr[128];        
  MSDtmfGenCustomTone dtmf_cfg; 
};

typedef struct _app_vars app_vars;

Une structure de ce type sera déclarée dans le programme sous le nom de vars.
Ajoutons ensuite la fonction d'analyse des arguments de la ligne de commande :

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

À la suite de l'analyse, les arguments de la ligne de commande seront placés dans les champs de la structure vars. La fonction principale de l'application assemblera les filtres pour les voies de transmission et de réception ; après la connexion, le contrôle sera transféré dans une boucle infinie qui, si la fréquence du générateur a été définie à un nombre non nul, redémarrera le générateur de test afin qu'il fonctionne en continu.

Ces redémarrages seront nécessaires au générateur en raison de sa conception particulière ; pour une raison quelconque, il ne peut pas émettre un signal d'une durée supérieure à 16 secondes. Il convient également de noter que la durée est spécifiée par un nombre 32 bits.

Le programme dans son intégralité se présentera ainsi :

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

Compilons maintenant. Ensuite, le programme peut être lancé sur deux ordinateurs. Ou sur un seul, ce que je ferai maintenant. Lançons TShark avec les arguments suivants :

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

Si le champ de lancement dans la console affiche uniquement un message de début de capture, c'est un bon signe - cela signifie que notre port n'est probablement pas utilisé par d'autres programmes. Dans un autre terminal, lançons une instance du programme qui simule l'appareil de communication "à distance" en lui indiquant ce numéro de port :

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

Comme on peut le voir dans le texte du programme, l'adresse IP 127.0.0.1 (boucle locale) est utilisée par défaut.

Dans un autre terminal, nous lançons une deuxième instance du programme, qui simule un appareil local. Utilisons un argument supplémentaire qui permet le fonctionnement du générateur de test intégré :

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

À ce moment-là, dans la console avec TShark, des paquets devraient commencer à défiler en direction de l'"appareil distant", et un signal tonal continu devrait retentir depuis les haut-parleurs de l'ordinateur.

Si tout s'est déroulé comme prévu, redémarrez la deuxième instance du programme, mais sans la clé et l'argument "—gen 440". Vous jouerez maintenant le rôle de générateur. Après cela, vous pourrez faire du bruit dans le microphone, et vous devriez entendre le son correspondant dans les haut-parleurs ou les écouteurs. Il pourrait même y avoir un auto-oscillation acoustique, réduisez le volume des haut-parleurs et cet effet disparaîtra.

Si vous avez lancé le programme sur deux ordinateurs et que vous ne vous êtes pas trompé dans les adresses IP, vous obtiendrez le même résultat : une communication vocale bidirectionnelle de qualité numérique.

Dans le prochain article, nous apprendrons à écrire nos propres filtres — des plugins, grâce à cette compétence, vous pourrez utiliser le médiastreamer non seulement pour le son et la vidéo, mais aussi dans un autre domaine spécifique.

Source : habr.com

Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS 🔥 Acheter un hébergement fiable pour les sites avec protection DDoS, serveurs VPS VDS | ProHoster