Εξερευνώντας τη μηχανή VoIP Mediastreamer2. Μέρος 9

Το υλικό του άρθρου είναι παρμένο από το δικό μου κανάλι ζεν.

Ενδοεπικοινωνία διπλής όψης

Εξερευνώντας τη μηχανή VoIP Mediastreamer2. Μέρος 9

Στο παρελθόν άρθρο ανακοινώθηκε μια αμφίδρομη ενδοεπικοινωνία, και σε αυτήν θα την κάνουμε.

Το διάγραμμα φαίνεται στο σχήμα του τίτλου. Η κάτω αλυσίδα των φίλτρων σχηματίζει τη διαδρομή μετάδοσης, η οποία ξεκινά από την κάρτα ήχου. Παρέχει δείγματα σήματος από το μικρόφωνο. Από προεπιλογή, αυτό συμβαίνει με ρυθμό 8000 δειγμάτων ανά δευτερόλεπτο. Το βάθος bit δεδομένων που χρησιμοποιούν τα φίλτρα ήχου της ροής πολυμέσων είναι 16 bit (αυτό δεν είναι σημαντικό. Εάν θέλετε, μπορείτε να γράψετε φίλτρα που θα λειτουργούν με μεγαλύτερο βάθος bit). Τα δεδομένα ομαδοποιούνται σε μπλοκ των 160 δειγμάτων. Έτσι, κάθε μπλοκ έχει μέγεθος 320 byte. Στη συνέχεια, τροφοδοτούμε τα δεδομένα στην είσοδο της γεννήτριας, η οποία, όταν είναι απενεργοποιημένη, είναι «διαφανής» στα δεδομένα. Το πρόσθεσα σε περίπτωση που κουραστείτε να μιλάτε στο μικρόφωνο κατά τη διάρκεια του εντοπισμού σφαλμάτων - μπορείτε να χρησιμοποιήσετε τη γεννήτρια για να "πυροβολήσετε" τη διαδρομή με ένα ηχητικό σήμα.

Μετά τη γεννήτρια, το σήμα πηγαίνει στον κωδικοποιητή, ο οποίος μετατρέπει τα δείγματά μας 16 bit σύμφωνα με τον μ-νόμο (πρότυπο G.711) σε οκτώ bit. Στην έξοδο του κωδικοποιητή, έχουμε ήδη ένα μπλοκ δεδομένων στο μισό μέγεθος. Γενικά, μπορούμε να μεταδώσουμε δεδομένα χωρίς συμπίεση εάν δεν χρειάζεται να εξοικονομήσουμε κίνηση. Αλλά εδώ είναι χρήσιμο να χρησιμοποιήσετε έναν κωδικοποιητή, καθώς το Wireshark μπορεί να αναπαράγει ήχο από μια ροή RTP μόνο όταν συμπιέζεται σύμφωνα με τον μ-νόμο ή τον νόμο.

Μετά τον κωδικοποιητή, τα ελαφρύτερα μπλοκ δεδομένων αποστέλλονται στο φίλτρο rtpsend, το οποίο θα τα τοποθετήσει σε ένα πακέτο RTP, θα ορίσει τις απαραίτητες σημαίες και θα τα δώσει στη ροή πολυμέσων για μετάδοση μέσω του δικτύου με τη μορφή ενός πακέτου UDP.

Η ανώτερη αλυσίδα των φίλτρων σχηματίζει τη διαδρομή λήψης· τα πακέτα RTP που λαμβάνονται από τη ροή πολυμέσων από το δίκτυο εισέρχονται στο φίλτρο rtprecv, στην έξοδο του οποίου εμφανίζονται με τη μορφή μπλοκ δεδομένων, καθένα από τα οποία αντιστοιχεί σε ένα ληφθέν πακέτο. Το μπλοκ περιέχει μόνο δεδομένα ωφέλιμου φορτίου· στο προηγούμενο άρθρο εμφανίζονταν με πράσινο χρώμα στην εικόνα.

Στη συνέχεια, τα μπλοκ αποστέλλονται στο φίλτρο αποκωδικοποιητή, το οποίο μετατρέπει τα δείγματα ενός byte που περιέχονται σε αυτά σε γραμμικά, 16-bit. Το οποίο μπορεί ήδη να υποβληθεί σε επεξεργασία από φίλτρα ροής πολυμέσων. Στην περίπτωσή μας, απλώς τα στέλνουμε στην κάρτα ήχου για αναπαραγωγή στα ηχεία του ακουστικού σας.

Τώρα ας προχωρήσουμε στην εφαρμογή λογισμικού. Για να γίνει αυτό, θα συνδυάσουμε τα αρχεία δέκτη και πομπού που χωρίσαμε πριν. Πριν από αυτό, χρησιμοποιούσαμε σταθερές ρυθμίσεις για θύρες και διευθύνσεις, αλλά τώρα χρειαζόμαστε το πρόγραμμα για να μπορεί να χρησιμοποιήσει τις ρυθμίσεις που καθορίζουμε κατά την εκκίνηση. Για να γίνει αυτό, θα προσθέσουμε λειτουργικότητα για την επεξεργασία ορισμάτων γραμμής εντολών. Μετά από αυτό θα μπορούμε να ορίσουμε τη διεύθυνση IP και τη θύρα της ενδοεπικοινωνίας με την οποία θέλουμε να δημιουργήσουμε σύνδεση.

Αρχικά, ας προσθέσουμε μια δομή στο πρόγραμμα που θα αποθηκεύει τις ρυθμίσεις του:

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

typedef struct _app_vars app_vars;

Το πρόγραμμα θα δηλώσει μια δομή αυτού του τύπου που ονομάζεται vars.
Στη συνέχεια, ας προσθέσουμε μια συνάρτηση για την ανάλυση ορισμάτων γραμμής εντολών:

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

Ως αποτέλεσμα της ανάλυσης, τα ορίσματα της γραμμής εντολών θα τοποθετηθούν στα πεδία της δομής vars. Η κύρια λειτουργία της εφαρμογής θα είναι να συλλέγει διαδρομές μετάδοσης και λήψης από φίλτρα· μετά τη σύνδεση του ticker, ο έλεγχος θα μεταφερθεί σε έναν άπειρο βρόχο ο οποίος, εάν η συχνότητα της γεννήτριας ρυθμιστεί σε μη μηδενική, θα επανεκκινήσει τη δοκιμαστική γεννήτρια έτσι ώστε λειτουργεί χωρίς διακοπή.

Η γεννήτρια θα χρειαστεί αυτές τις επανεκκινήσεις λόγω του σχεδιασμού της· για κάποιο λόγο δεν μπορεί να παράγει σήμα που διαρκεί περισσότερο από 16 δευτερόλεπτα. Θα πρέπει να σημειωθεί ότι η διάρκειά του καθορίζεται από έναν αριθμό 32 bit.

Όλο το πρόγραμμα θα μοιάζει με αυτό:

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

Ας μεταγλωττίσουμε. Στη συνέχεια, το πρόγραμμα μπορεί να εκτελεστεί σε δύο υπολογιστές. Ή σε ένα, όπως θα κάνω τώρα. Ξεκινάμε το TShark με τα ακόλουθα ορίσματα:

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

Εάν το πεδίο εκκίνησης στην κονσόλα εμφανίζει μόνο ένα μήνυμα σχετικά με την έναρξη της λήψης, τότε αυτό είναι καλό σημάδι - σημαίνει ότι η θύρα μας πιθανότατα δεν είναι κατειλημμένη από άλλα προγράμματα. Σε ένα άλλο τερματικό, ξεκινάμε μια παρουσία προγράμματος που θα προσομοιώνει μια "απομακρυσμένη" ενδοεπικοινωνία καθορίζοντας αυτόν τον αριθμό θύρας:

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

Όπως φαίνεται από το κείμενο του προγράμματος, η προεπιλεγμένη διεύθυνση IP είναι 127.0.0.1 (τοπικός βρόχος).

Σε ένα άλλο τερματικό, ξεκινάμε μια δεύτερη παρουσία του προγράμματος, η οποία προσομοιώνει μια τοπική συσκευή. Χρησιμοποιούμε ένα πρόσθετο όρισμα που επιτρέπει στην ενσωματωμένη δοκιμαστική γεννήτρια να λειτουργεί:

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

Αυτή τη στιγμή, τα πακέτα που μεταδίδονται προς την "απομακρυσμένη" συσκευή θα πρέπει να αρχίσουν να αναβοσβήνουν στην κονσόλα με το TShark και ένας συνεχής τόνος θα ακουστεί από το ηχείο του υπολογιστή.

Εάν όλα έγιναν όπως γράφτηκαν, τότε κάνουμε επανεκκίνηση του δεύτερου αντιγράφου του προγράμματος, αλλά χωρίς το κλειδί και το όρισμα «—gen 440». Τώρα θα παίξετε το ρόλο του γεννήτριας. Μετά από αυτό, μπορείτε να κάνετε θόρυβο στο μικρόφωνο· θα πρέπει να ακούσετε τον αντίστοιχο ήχο στο ηχείο ή στα ακουστικά. Μπορεί ακόμη και να προκληθεί ακουστική αυτοδιέγερση· χαμηλώστε την ένταση του ηχείου και το εφέ θα εξαφανιστεί.

Εάν το εκτελέσατε σε δύο υπολογιστές και δεν μπερδευτήκατε σχετικά με τις διευθύνσεις IP, τότε σας περιμένει το ίδιο αποτέλεσμα - αμφίδρομη φωνητική επικοινωνία ψηφιακής ποιότητας.

Στο επόμενο άρθρο θα μάθουμε πώς να γράφουμε τα δικά μας φίλτρα - πρόσθετα, χάρη σε αυτή τη δεξιότητα θα μπορείτε να χρησιμοποιείτε το media streamer όχι μόνο για ήχο και βίντεο, αλλά και σε κάποια άλλη συγκεκριμένη περιοχή.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο