探索 Mediastreamer2 VoIP 引擎。 第 9 部分

文章素材取自我 禪頻道.

雙工對講

探索 Mediastreamer2 VoIP 引擎。 第 9 部分

在過去 文章 一款雙工對講機已經發布,我們將在這款中實現它。

該圖如標題圖所示。 較低的濾波器鏈形成從聲音卡開始的傳輸路徑。 它提供來自麥克風的信號樣本。 預設情況下,這種情況發生的速率為每秒 8000 個樣本。 媒體串流音訊過濾器使用的資料位元深度是 16 位元(這並不重要;如果您願意,您可以編寫適用於更高位元深度的過濾器)。 數據被分成 160 個樣本的區塊。 因此,每個區塊的大小為 320 位元組。 接下來,我們將資料提供給生成器的輸入,生成器關閉時對資料是「透明」的。 我添加了它,以防您在調試過程中厭倦了對著麥克風說話 - 您可以使用發生器用音調信號“射擊”路徑。

在發生器之後,訊號進入編碼器,編碼器根據 µ 律(G.16 標準)將 711 位元樣本轉換為 XNUMX 位元樣本。 在編碼器的輸出處,我們已經有了一半大小的資料塊。 一般來說,如果不需要節省流量,我們可以不壓縮地傳輸資料。 但在這裡使用編碼器很有用,因為 Wireshark 只能在根據 µ 律或 a 律壓縮時從 RTP 流中再現音訊。

經過編碼器之後,較輕的資料塊被傳送到rtpsend 過濾器,該過濾器會將它們放入RTP 資料包中,設定必要的標誌並將它們提供給媒體流處理器,以便以UDP 資料包的形式透過網路傳輸。

上層過濾器鏈形成接收路徑;媒體串流從網路接收的 RTP 封包進入 rtprecv 過濾器,在其輸出處以資料區塊的形式出現,每個資料區塊對應於一個接收到的資料包。 該區塊僅包含有效負載資料;在上一篇文章中,它們在插圖中顯示為綠色。

接下來,這些區塊被送到解碼器過濾器,解碼器過濾器將其中包含的單字節樣本轉換為線性 16 位元樣本。 它已經可以由媒體串流過濾器處理。 在我們的例子中,我們只需將它們發送到聲卡,以便在耳機的揚聲器上播放。

現在讓我們繼續進行軟體實作。 為此,我們將合併先前分離的接收器和發送器檔案。 在此之前,我們使用連接埠和位址的固定設置,但現在我們需要程式能夠使用我們在啟動時指定的設定。 為此,我們將新增處理命令列參數的功能。 之後,我們將能夠設定要與之建立連線的對講機的 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 結構的欄位中。 該應用程式的主要功能是收集來自濾波器的發射和接收路徑;連接自動收錄器後,控制將轉移到無限循環,如果發電機頻率設定為非零,則將重新啟動測試發電機,以便它不停地工作。

由於其設計,發生器需要重新啟動;由於某種原因,它無法產生持續超過 16 秒的訊號。 需要注意的是,它的持續時間是由一個32位數字指定的。

整個程序將如下所示:

/* Файл 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 位址感到困惑,那麼相同的結果就等著您 - 雙向數位品質語音通訊。

在下一篇文章中,我們將學習如何編寫自己的過濾器 - 插件,由於這項技能,您將不僅能夠將媒體串流媒體用於音頻和視頻,而且還可以在其他一些特定領域使用。

來源: www.habr.com

添加評論