สำรวจเครื่องมือ Mediastreamer2 VoIP ส่วนที่ 9

เนื้อหาของบทความนำมาจากของฉัน ช่องเซน.

อินเตอร์คอมดูเพล็กซ์

สำรวจเครื่องมือ Mediastreamer2 VoIP ส่วนที่ 9

ในที่สุด статье มีการประกาศอินเตอร์คอมดูเพล็กซ์และในอันนี้เราจะสร้างมันขึ้นมา

แผนภาพแสดงอยู่ในรูปชื่อเรื่อง สายโซ่ตัวกรองด้านล่างจะสร้างเส้นทางการส่งสัญญาณซึ่งเริ่มต้นจากการ์ดเสียง โดยจะให้ตัวอย่างสัญญาณจากไมโครโฟน โดยค่าเริ่มต้น เหตุการณ์นี้เกิดขึ้นที่อัตรา 8000 ตัวอย่างต่อวินาที ความลึกบิตข้อมูลที่ตัวกรองเสียงของสตรีมสื่อใช้คือ 16 บิต (ซึ่งไม่สำคัญ คุณสามารถเขียนตัวกรองที่จะทำงานกับความลึกของบิตที่สูงกว่าได้หากต้องการ) ข้อมูลถูกจัดกลุ่มเป็นบล็อกจำนวน 160 ตัวอย่าง ดังนั้น แต่ละบล็อกจะมีขนาด 320 ไบต์ ต่อไป เราจะป้อนข้อมูลไปยังอินพุตของเครื่องกำเนิดไฟฟ้า ซึ่งเมื่อปิดเครื่อง ข้อมูลจะ "โปร่งใส" ฉันเพิ่มไว้ในกรณีที่คุณเบื่อที่จะพูดใส่ไมโครโฟนระหว่างการดีบัก - คุณสามารถใช้เครื่องกำเนิดเพื่อ "ยิง" เส้นทางด้วยสัญญาณเสียง

หลังจากเครื่องกำเนิดไฟฟ้า สัญญาณจะถูกส่งไปยังตัวเข้ารหัส ซึ่งจะแปลงตัวอย่าง 16 บิตของเราตามกฎ µ- (มาตรฐาน G.711) ให้เป็นตัวอย่าง XNUMX บิต ที่เอาต์พุตของตัวเข้ารหัส เรามีบล็อกข้อมูลที่มีขนาดเพียงครึ่งหนึ่งอยู่แล้ว โดยทั่วไป เราสามารถส่งข้อมูลโดยไม่มีการบีบอัดได้ หากเราไม่ต้องการบันทึกการรับส่งข้อมูล แต่ที่นี่การใช้ตัวเข้ารหัสจะมีประโยชน์ เนื่องจาก Wireshark สามารถสร้างเสียงจากสตรีม RTP ได้ก็ต่อเมื่อมีการบีบอัดตามกฎ µ หรือ a-law เท่านั้น

หลังจากตัวเข้ารหัส บล็อกข้อมูลที่เบากว่าจะถูกส่งไปยังตัวกรอง 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 (local loopback)

ในเทอร์มินัลอื่น เราจะเปิดตัวอินสแตนซ์ที่สองของโปรแกรม ซึ่งจำลองอุปกรณ์ในเครื่อง เราใช้อาร์กิวเมนต์เพิ่มเติมที่อนุญาตให้ตัวสร้างการทดสอบในตัวทำงานได้:

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

ในขณะนี้ แพ็กเก็ตที่ส่งไปยังอุปกรณ์ "ระยะไกล" ควรเริ่มกะพริบในคอนโซลด้วย TShark และจะได้ยินเสียงต่อเนื่องจากลำโพงคอมพิวเตอร์

หากทุกอย่างเกิดขึ้นตามที่เขียนไว้ เราจะรีสตาร์ทสำเนาที่สองของโปรแกรม แต่ไม่มีคีย์และอาร์กิวเมนต์ "—gen 440" ตอนนี้คุณจะเล่นบทบาทของเครื่องกำเนิดไฟฟ้า หลังจากนี้ คุณสามารถส่งเสียงรบกวนเข้าไมโครโฟนได้ คุณควรได้ยินเสียงที่เกี่ยวข้องในลำโพงหรือหูฟัง การกระตุ้นตัวเองด้วยเสียงอาจเกิดขึ้นได้ ให้ลดระดับเสียงของลำโพงลงและเอฟเฟกต์จะหายไป

หากคุณใช้งานบนคอมพิวเตอร์สองเครื่องและไม่สับสนเกี่ยวกับที่อยู่ IP ผลลัพธ์เดียวกันก็รอคุณอยู่ - การสื่อสารด้วยเสียงคุณภาพดิจิทัลแบบสองทาง

ในบทความถัดไป เราจะได้เรียนรู้วิธีเขียนตัวกรองของเราเอง - ปลั๊กอิน ด้วยทักษะนี้ คุณจะสามารถใช้มีเดียสตรีมเมอร์ได้ไม่เฉพาะกับเสียงและวิดีโอเท่านั้น แต่ยังรวมถึงพื้นที่เฉพาะอื่น ๆ ด้วย

ที่มา: will.com

เพิ่มความคิดเห็น