استكشاف محرك Mediastreamer2 VoIP. الجزء 9

مادة المقال مأخوذة من بلدي قناة زين.

اتصال داخلي مزدوج

استكشاف محرك Mediastreamer2 VoIP. الجزء 9

في الماضي مقالة تم الإعلان عن نظام اتصال داخلي مزدوج، وسنقوم بذلك في هذا.

يظهر الرسم التخطيطي في شكل العنوان. تشكل السلسلة السفلية للمرشحات مسار النقل الذي يبدأ من بطاقة الصوت. ويوفر عينات إشارة من الميكروفون. افتراضيًا، يحدث هذا بمعدل 8000 عينة في الثانية. عمق بت البيانات الذي تستخدمه مرشحات الصوت الخاصة بدفق الوسائط هو 16 بت (وهذا ليس مهمًا؛ إذا كنت ترغب في ذلك، يمكنك كتابة المرشحات التي ستعمل بعمق بت أعلى). يتم تجميع البيانات في كتل من 160 عينة. وبالتالي، يبلغ حجم كل كتلة 320 بايت. بعد ذلك، نقوم بتغذية البيانات إلى مدخلات المولد، والذي، عند إيقاف تشغيله، يكون "شفافًا" للبيانات. لقد أضفته في حالة سئمك من التحدث إلى الميكروفون أثناء تصحيح الأخطاء - يمكنك استخدام المولد "لتصوير" المسار بإشارة نغمة.

بعد المولد، تذهب الإشارة إلى جهاز التشفير، الذي يحول عيناتنا ذات 16 بت وفقًا لقانون μ (معيار G.711) إلى عينات ذات ثمانية بتات. عند إخراج المشفر، لدينا بالفعل كتلة بيانات بنصف الحجم. بشكل عام، يمكننا نقل البيانات دون ضغط إذا لم نكن بحاجة إلى حفظ حركة المرور. ولكن من المفيد هنا استخدام برنامج تشفير، نظرًا لأن Wireshark يمكنه إعادة إنتاج الصوت من دفق RTP فقط عندما يتم ضغطه وفقًا لقانون μ أو قانون A.

بعد التشفير، يتم إرسال كتل البيانات الأخف إلى مرشح 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

إضافة تعليق