Poznawanie silnika Mediastreamer2 VoIP. Część 9

Materiał artykułu pochodzi z mojego kanał zen.

domofon dupleksowy

Poznawanie silnika Mediastreamer2 VoIP. Część 9

W przeszłości Artykuł zapowiedziano domofon duplex i w tym to zrobimy.

Schemat pokazano na rysunku tytułowym. Dolny łańcuch filtrów tworzy ścieżkę transmisji, która zaczyna się od karty dźwiękowej. Dostarcza próbki sygnału z mikrofonu. Domyślnie dzieje się to z szybkością 8000 próbek na sekundę. Głębia bitowa danych używana przez filtry audio do strumieniowego przesyłania multimediów wynosi 16 bitów (nie jest to ważne; jeśli chcesz, możesz napisać filtry, które będą działać z większą głębią bitową). Dane są pogrupowane w bloki po 160 próbek. Zatem każdy blok ma rozmiar 320 bajtów. Następnie wprowadzamy dane na wejście generatora, który po wyłączeniu jest „przejrzysty” dla danych. Dodałem go na wypadek, gdybyś znudził Ci się mówienie do mikrofonu podczas debugowania - możesz użyć generatora, aby „wystrzelić” ścieżkę sygnałem tonowym.

Za generatorem sygnał trafia do kodera, który zamienia nasze 16-bitowe próbki zgodnie z prawem µ (standard G.711) na ośmiobitowe. Na wyjściu kodera mamy już blok danych o połowę mniejszy. Ogólnie rzecz biorąc, możemy przesyłać dane bez kompresji, jeśli nie musimy oszczędzać ruchu. Ale tutaj przydatne jest użycie kodera, ponieważ Wireshark może odtwarzać dźwięk ze strumienia RTP tylko wtedy, gdy jest on skompresowany zgodnie z prawem µ lub a.

Za koderem lżejsze bloki danych przesyłane są do filtra rtpsend, który umieszcza je w pakiecie RTP, ustawia niezbędne flagi i przekazuje do streamera multimediów w celu transmisji przez sieć w postaci pakietu UDP.

Górny łańcuch filtrów tworzy ścieżkę odbiorczą; pakiety RTP odebrane przez streamer multimediów z sieci trafiają do filtra rtprecv, na wyjściu którego pojawiają się w postaci bloków danych, z których każdy odpowiada jednemu odebranemu pakietowi. Blok zawiera wyłącznie dane dotyczące ładunku, w poprzednim artykule zostały one pokazane na ilustracji kolorem zielonym.

Następnie bloki przesyłane są do filtra dekodera, który przetwarza zawarte w nich próbki jednobajtowe na liniowe, 16-bitowe. Które mogą być już przetwarzane przez filtry strumieniowe multimediów. W naszym przypadku po prostu wysyłamy je na kartę dźwiękową w celu odtwarzania na głośnikach zestawu słuchawkowego.

Przejdźmy teraz do wdrożenia oprogramowania. W tym celu połączymy pliki odbiornika i nadajnika, które wcześniej rozdzieliliśmy. Wcześniej używaliśmy stałych ustawień portów i adresów, ale teraz potrzebujemy, aby program mógł korzystać z ustawień, które określamy przy uruchomieniu. Aby to zrobić, dodalibyśmy funkcję przetwarzania argumentów wiersza poleceń. Po czym będziemy mogli ustawić adres IP i port domofonu, z którym chcemy nawiązać połączenie.

Na początek dodajmy do programu strukturę, która będzie przechowywać jego ustawienia:

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

typedef struct _app_vars app_vars;

Program zadeklaruje strukturę tego typu zwaną vars.
Następnie dodajmy funkcję analizującą argumenty wiersza poleceń:

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

W wyniku parsowania argumenty wiersza poleceń zostaną umieszczone w polach struktury vars. Główną funkcją aplikacji będzie zbieranie z filtrów torów nadawczych i odbiorczych, po podłączeniu tickera sterowanie zostanie przeniesione do pętli nieskończonej, która w przypadku ustawienia częstotliwości generatora na wartość niezerową zrestartuje generator testowy tak, aby działa bez przerwy.

Generator będzie potrzebował tych ponownych uruchomień ze względu na swoją konstrukcję; z jakiegoś powodu nie może wytworzyć sygnału trwającego dłużej niż 16 sekund. Należy zaznaczyć, że jego czas trwania jest określony liczbą 32-bitową.

Cały program będzie wyglądał następująco:

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

Skompilujmy. Wówczas program można uruchomić na dwóch komputerach. Albo na jednym, tak jak to zrobię teraz. Uruchamiamy TShark z następującymi argumentami:

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

Jeśli w polu uruchamiania w konsoli wyświetla się jedynie komunikat o rozpoczęciu przechwytywania, to jest to dobry znak - oznacza to, że najprawdopodobniej nasz port nie jest zajęty przez inne programy. W innym terminalu uruchamiamy instancję programu, która będzie symulować „zdalny” domofon podając ten numer portu:

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

Jak widać z tekstu programu, domyślny adres IP to 127.0.0.1 (lokalna pętla zwrotna).

W innym terminalu uruchamiamy drugą instancję programu, która symuluje urządzenie lokalne. Używamy dodatkowego argumentu, który pozwala na działanie wbudowanego generatora testów:

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

W tym momencie pakiety przesyłane w stronę „zdalnego” urządzenia powinny zacząć migać w konsoli z TShark, a z głośnika komputera będzie słyszalny ciągły dźwięk.

Jeśli wszystko stało się tak, jak napisano, to restartujemy drugą kopię programu, ale bez klucza i argumentu „—gen 440”. Wcielisz się teraz w rolę generatora. Następnie możesz hałasować do mikrofonu, powinieneś usłyszeć odpowiedni dźwięk w głośniku lub słuchawkach. Może nawet wystąpić samowzbudzenie akustyczne; zmniejsz głośność głośnika, a efekt zniknie.

Jeśli uruchomiłeś go na dwóch komputerach i nie pomyliłeś się z adresami IP, czeka Cię ten sam wynik - dwukierunkowa komunikacja głosowa o cyfrowej jakości.

W następnym artykule dowiemy się jak napisać własne filtry - wtyczki, dzięki tej umiejętności będziesz mógł wykorzystać streamer multimediów nie tylko do audio i wideo, ale także w jakimś innym konkretnym obszarze.

Źródło: www.habr.com

Dodaj komentarz