Mediastreamer2 VoIP エンジンを探索します。 パート9

記事の素材は私のものから引用しました 禅チャンネル.

二重インターホン

Mediastreamer2 VoIP エンジンを探索します。 パート9

過去に статье 二重インターホンが発表されましたが、今回はそれを実現します。

その図を表題図に示します。 下部のフィルターチェーンは、サウンドカードから始まる伝送パスを形成します。 マイクからの信号サンプルを提供します。 デフォルトでは、これは 8000 秒あたり 16 サンプルの速度で発生します。 メディア ストリーマーのオーディオ フィルターが使用するデータ ビット深度は 160 ビットです (これは重要ではありません。必要に応じて、より高いビット深度で機能するフィルターを作成できます)。 データは 320 サンプルのブロックにグループ化されます。 したがって、各ブロックのサイズは XNUMX バイトです。 次に、ジェネレーターの入力にデータを供給します。ジェネレーターは、オフにするとデータに対して「透過的」になります。 デバッグ中にマイクに向かって話すのに飽きた場合に備えて、ジェネレーターを使用してトーン信号でパスを「撮影」することができるように追加しました。

信号はジェネレーターの後、エンコーダーに送られ、μ-law (G.16 標準) に従って 711 ビットのサンプルが XNUMX ビットのサンプルに変換されます。 エンコーダーの出力には、半分のサイズのデータ​​ ブロックがすでにあります。 一般に、トラフィックを節約する必要がない場合は、データを圧縮せずに送信できます。 ただし、ここではエンコーダを使用すると便利です。Wireshark は、µ-law または a-law に従って圧縮されている場合にのみ RTP ストリームからオーディオを再生できるからです。

エンコーダーの後、データの軽いブロックは rtpsend フィルターに送信され、rtpsend フィルターはそれらを RTP パケットに入れ、必要なフラグを設定して、UDP パケットの形式でネットワーク経由で送信するためにメディア ストリーマーに渡します。

フィルターの上部チェーンは受信パスを形成します。ネットワークからメディア ストリーマーによって受信された RTP パケットは rtprecv フィルターに入り、その出力でデータ ブロックの形式で表示され、それぞれが XNUMX つの受信パケットに対応します。 ブロックにはペイロード データのみが含まれています。前の記事では、図の緑色で示されていました。

次に、ブロックはデコーダ フィルタに送信され、ブロックに含まれるシングルバイト サンプルが線形の 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);
    }
}

コンパイルしましょう。 その後、プログラムを XNUMX 台のコンピュータで実行できます。 あるいは、これから行うように、XNUMX つで。 次の引数を使用して 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 (ローカル ループバック) です。

別の端末で、ローカル デバイスをシミュレートするプログラムの XNUMX 番目のインスタンスを起動します。 組み込みのテスト ジェネレーターが動作できるようにする追加の引数を使用します。

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

この時点で、「リモート」デバイスに向けて送信されたパケットが TShark のコンソール内で点滅し始め、コンピューターのスピーカーから連続音が聞こえます。

すべてが記述どおりに起こった場合は、プログラムの 440 番目のコピーを再起動しますが、キーと引数「-gen XNUMX」は使用しません。 ここであなたはジェネレーターの役割を果たします。 この後、マイクにノイズを入力すると、対応する音がスピーカーまたはヘッドフォンから聞こえるはずです。 音響の自己励起が発生する場合もありますが、スピーカーの音量を下げると効果が消えます。

XNUMX 台のコンピュータで実行し、IP アドレスについて混乱しなければ、同じ結果、つまり双方向のデジタル品質の音声通信が待っています。

次の記事では、独自のフィルター (プラグイン) を作成する方法を学びます。このスキルのおかげで、オーディオやビデオだけでなく、他の特定の領域でもメディア ストリーマーを使用できるようになります。

出所: habr.com

コメントを追加します