Mediastreamer2 VoIP motorunu keşfetme. 9. Bölüm

Makalenin materyali benim zenci kanalı.

dubleks interkom

Mediastreamer2 VoIP motorunu keşfetme. 9. Bölüm

Geçmişte Makale çift ​​yönlü interkom duyuruldu ve bunda bunu başaracağız.

Diyagram başlıktaki şekilde gösterilmektedir. Alt filtre zinciri, ses kartından başlayan iletim yolunu oluşturur. Mikrofondan sinyal örnekleri sağlar. Varsayılan olarak bu, saniyede 8000 örnek hızında gerçekleşir. Media Streamer ses filtrelerinin kullandığı veri bit derinliği 16 bittir (bu önemli değil; dilerseniz daha yüksek bit derinliğinde çalışacak filtreler yazabilirsiniz). Veriler 160 örnekten oluşan bloklar halinde gruplandırılmıştır. Böylece her blok 320 byte boyutunda olur. Daha sonra verileri, kapatıldığında veriler için "şeffaf" olan jeneratörün girişine besliyoruz. Hata ayıklama sırasında mikrofona konuşmaktan yorulmanız durumunda bunu ekledim - yolu bir ton sinyaliyle "çekmek" için jeneratörü kullanabilirsiniz.

Jeneratörden sonra sinyal, µ-yasasına (G.16 standardı) göre 711 bitlik örneklerimizi sekiz bitlik örneklere dönüştüren kodlayıcıya gider. Kodlayıcının çıkışında zaten yarı boyutunda bir veri bloğumuz var. Genel olarak trafikten tasarruf etmemiz gerekmiyorsa verileri sıkıştırmadan aktarabiliriz. Ancak burada bir kodlayıcı kullanmak faydalıdır, çünkü Wireshark bir RTP akışından sesi yalnızca µ-yasası veya a-yasasına göre sıkıştırıldığında yeniden üretebilir.

Kodlayıcıdan sonra, daha hafif veri blokları, onları bir RTP paketine koyacak, gerekli bayrakları ayarlayacak ve bunları bir UDP paketi biçiminde ağ üzerinden iletilmek üzere medya aktarıcısına verecek olan rtpsend filtresine gönderilir.

Filtrelerin üst zinciri alma yolunu oluşturur; medya aktarıcı tarafından ağdan alınan RTP paketleri, çıktısında her biri alınan bir pakete karşılık gelen veri blokları şeklinde göründükleri rtprecv filtresine girer. Blok yalnızca yük verilerini içerir; önceki makalede bunlar çizimde yeşil renkle gösterilmiştir.

Daha sonra bloklar, içerdikleri tek baytlık örnekleri doğrusal, 16 bitlik örneklere dönüştüren kod çözücü filtresine gönderilir. Bu zaten medya aktarıcı filtreleri tarafından işlenebilmektedir. Bizim durumumuzda, bunları kulaklığınızın hoparlörlerinde oynatılmak üzere ses kartına göndermeniz yeterlidir.

Şimdi yazılım uygulamasına geçelim. Bunun için daha önce ayırdığımız alıcı ve verici dosyalarını birleştireceğiz. Bundan önce port ve adresler için sabit ayarlar kullanıyorduk ancak artık programın açılışta belirlediğimiz ayarları kullanabilmesi gerekiyor. Bunu yapmak için komut satırı argümanlarını işlemeye yönelik işlevsellik ekleyeceğiz. Bundan sonra bağlantı kurmak istediğimiz interkomun IP adresini ve portunu ayarlayabileceğiz.

Öncelikle programa ayarlarını saklayacak bir yapı ekleyelim:

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

typedef struct _app_vars app_vars;

Program, vars adı verilen bu türden bir yapıyı bildirecektir.
Sonra, komut satırı bağımsız değişkenlerini ayrıştırmak için bir işlev ekleyelim:

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

Ayrıştırma sonucunda komut satırı argümanları vars yapısının alanlarına yerleştirilecektir. Uygulamanın ana işlevi, filtrelerden gönderme ve alma yollarını toplamak olacaktır; şerit şeridi bağlandıktan sonra kontrol, jeneratör frekansı sıfırdan farklı bir değere ayarlandığında test jeneratörünü yeniden başlatacak şekilde sonsuz bir döngüye aktarılacaktır. durmadan çalışır.

Jeneratör tasarımı gereği bu yeniden başlatmalara ihtiyaç duyacaktır; bazı nedenlerden dolayı 16 saniyeden uzun süren bir sinyal üretememektedir. Süresinin 32 bitlik bir sayı ile belirtildiğine dikkat edilmelidir.

Programın tamamı şöyle görünecek:

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

Hadi derleyelim. Daha sonra program iki bilgisayarda çalıştırılabilir. Veya şimdi yapacağım gibi. TShark'ı aşağıdaki argümanlarla başlatıyoruz:

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

Konsoldaki başlatma alanında yalnızca yakalamanın başlamasıyla ilgili bir mesaj görüntüleniyorsa, bu iyi bir işarettir - bu, bağlantı noktamızın büyük olasılıkla başka programlar tarafından işgal edilmediği anlamına gelir. Başka bir terminalde, bu bağlantı noktası numarasını belirterek "uzaktan" interkomu simüle edecek bir program örneğini başlatıyoruz:

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

Program metninden de anlaşılacağı üzere varsayılan IP adresi 127.0.0.1'dir (yerel geridöngü).

Başka bir terminalde, programın yerel bir cihazı simüle eden ikinci bir örneğini başlatıyoruz. Yerleşik test oluşturucunun çalışmasına izin veren ek bir argüman kullanıyoruz:

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

Bu anda “uzak” cihaza iletilen paketler konsolda TShark ile yanıp sönmeye başlamalı ve bilgisayar hoparlöründen sürekli bir ses duyulmalıdır.

Her şey yazıldığı gibi gerçekleşirse, programın ikinci kopyasını yeniden başlatırız, ancak "—gen 440" anahtarı ve argümanı olmadan. Artık jeneratör rolünü oynayacaksınız. Bundan sonra mikrofona ses verebilirsiniz; karşılık gelen sesi hoparlörde veya kulaklıkta duymalısınız. Akustik kendi kendine uyarılma bile meydana gelebilir; hoparlörün sesini kıstığınızda efekt kaybolacaktır.

İki bilgisayarda çalıştırdıysanız ve IP adresleri konusunda kafanız karışmadıysa, aynı sonuç sizi bekliyor: iki yönlü dijital kalitede sesli iletişim.

Bir sonraki makalede kendi filtrelerimizi - eklentilerimizi nasıl yazacağımızı öğreneceğiz, bu beceri sayesinde medya aktarıcıyı yalnızca ses ve video için değil, aynı zamanda başka belirli alanlarda da kullanabileceksiniz.

Kaynak: habr.com

Yorum ekle