Exploration du moteur VoIP Mediastreamer2. Partie 9

Le matériel de l'article est tiré de mon canal zen.

interphone duplex

Exploration du moteur VoIP Mediastreamer2. Partie 9

À la fin article un interphone duplex a été annoncé, et dans celui-ci nous le réaliserons.

Le diagramme est présenté dans la figure du titre. La chaîne inférieure de filtres constitue le chemin de transmission qui part de la carte son. Il fournit des échantillons de signal du microphone. Par défaut, cela se produit à une fréquence de 8000 16 échantillons par seconde. La profondeur de bits des données utilisée par les filtres audio du streamer multimédia est de 160 bits (ce n'est pas important ; si vous le souhaitez, vous pouvez écrire des filtres qui fonctionneront avec une profondeur de bits plus élevée). Les données sont regroupées en blocs de 320 échantillons. Ainsi, chaque bloc a une taille de XNUMX octets. Ensuite, nous transmettons les données à l'entrée du générateur qui, lorsqu'il est éteint, est « transparent » pour les données. Je l'ai ajouté au cas où vous en auriez assez de parler dans le microphone pendant le débogage - vous pouvez utiliser le générateur pour « filmer » le chemin avec un signal sonore.

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

Après l'encodeur, les blocs de données les plus légers sont envoyés au filtre rtpsend, qui les placera dans un paquet RTP, définira les indicateurs nécessaires et les transmettra au streamer multimédia pour transmission sur le réseau sous la forme d'un paquet UDP.

La chaîne supérieure de filtres forme le chemin de réception ; les paquets RTP reçus par le media streamer depuis le réseau entrent dans le filtre rtprecv, à la sortie duquel ils apparaissent sous forme de blocs de données dont chacun correspond à un paquet reçu. Le bloc contient uniquement des données utiles ; dans l’article précédent, elles étaient affichées en vert dans l’illustration.

Ensuite, les blocs sont envoyés au filtre du décodeur, qui convertit les échantillons à un octet qu'ils contiennent en échantillons linéaires de 16 bits. Ce qui peut déjà être traité par les filtres de streamer multimédia. Dans notre cas, nous les envoyons simplement sur la carte son pour une lecture sur les haut-parleurs de votre casque.

Passons maintenant à la mise en œuvre du logiciel. Pour ce faire, nous combinerons les fichiers récepteur et émetteur que nous avons séparés auparavant. Avant cela, nous utilisions des paramètres fixes pour les ports et les adresses, mais nous avons maintenant besoin que le programme puisse utiliser les paramètres que nous spécifions au démarrage. Pour ce faire, nous ajouterions une fonctionnalité de traitement des arguments de ligne de commande. Après quoi nous pourrons définir l’adresse IP et le port de l’interphone avec lequel nous voulons établir une connexion.

Tout d'abord, ajoutons une structure au programme qui stockera ses paramètres :

struct _app_vars
{
  int  local_port;              /* Локальный порт. */
  int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */
  char remote_addr[128];        /* IP-адрес удаленного компьютера. */
  MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
};

typedef struct _app_vars app_vars;

Le programme déclarera une structure de ce type appelée vars.
Ajoutons ensuite une fonction pour analyser les arguments de 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 sera de collecter les chemins d'émission et de réception des filtres ; après avoir connecté le ticker, le contrôle sera transféré à une boucle infinie qui, si la fréquence du générateur était réglée sur une valeur non nulle, redémarrera le générateur de test afin que ça marche sans s'arrêter.

Le générateur aura besoin de ces redémarrages en raison de sa conception ; pour une raison quelconque, il ne peut pas produire un signal durant plus de 16 secondes. A noter que sa durée est précisée par un nombre de 32 bits.

L'ensemble du programme ressemblera à ceci :

/* Файл 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. Le programme peut ensuite être exécuté sur deux ordinateurs. Ou sur un, comme je vais le faire maintenant. Nous 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 de la console affiche uniquement un message concernant le début de la capture, alors c'est un bon signe - cela signifie que notre port n'est probablement pas occupé par d'autres programmes. Dans un autre terminal, on lance une instance de programme qui va simuler un interphone « à distance » en précisant ce numéro de port :

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

Comme le montre le texte du programme, l'adresse IP par défaut est 127.0.0.1 (bouclage local).

Dans un autre terminal, on lance une deuxième instance du programme, qui simule un appareil local. Nous utilisons un argument supplémentaire qui permet au générateur de tests intégré de fonctionner :

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

À ce moment, les paquets transmis vers le périphérique « distant » devraient commencer à clignoter dans la console avec TShark, et une tonalité continue sera entendue depuis le haut-parleur de l'ordinateur.

Si tout s'est passé comme écrit, alors nous redémarrons la deuxième copie du programme, mais sans la clé et l'argument « —gen 440 ». Vous allez désormais jouer le rôle de générateur. Après cela, vous pouvez faire du bruit dans le microphone ; vous devriez entendre le son correspondant dans le haut-parleur ou les écouteurs. Une auto-excitation acoustique peut même se produire ; baissez le volume du haut-parleur et l’effet disparaîtra.

Si vous l'avez exécuté sur deux ordinateurs et que vous n'avez pas été confus au sujet des adresses IP, le même résultat vous attend : une communication vocale bidirectionnelle de qualité numérique.

Dans le prochain article, nous apprendrons comment écrire nos propres filtres - plugins, grâce à cette compétence, vous pourrez utiliser le streamer multimédia non seulement pour l'audio et la vidéo, mais également dans un autre domaine spécifique.

Source: habr.com

Ajouter un commentaire