کاوش در موتور VoIP Mediastreamer2. قسمت 9

مطالب مقاله از من گرفته شده است کانال ذن.

اینترکام دوبلکس

کاوش در موتور VoIP Mediastreamer2. قسمت 9

در آخر مقاله اینترکام دوبلکس اعلام شد و در این یکی آن را خواهیم ساخت.

نمودار در شکل عنوان نشان داده شده است. زنجیره پایینی فیلترها مسیر انتقال را تشکیل می دهد که از کارت صدا شروع می شود. نمونه های سیگنال را از میکروفون ارائه می دهد. به طور پیش فرض، این با نرخ 8000 نمونه در ثانیه رخ می دهد. عمق بیت داده ای که فیلترهای صوتی پخش کننده رسانه استفاده می کنند 16 بیت است (این مهم نیست؛ در صورت تمایل می توانید فیلترهایی بنویسید که با عمق بیت بالاتر کار کنند). داده ها در بلوک های 160 نمونه گروه بندی می شوند. بنابراین، هر بلوک 320 بایت اندازه دارد. در مرحله بعد، داده ها را به ورودی ژنراتور تغذیه می کنیم، که وقتی خاموش شود، برای داده ها "شفاف" می شود. در صورتی که در حین اشکال زدایی از صحبت کردن با میکروفون خسته شدید، آن را اضافه کردم - می توانید از ژنراتور برای "تصویربرداری" مسیر با سیگنال آهنگ استفاده کنید.

پس از ژنراتور، سیگنال به رمزگذار می رود، که نمونه های 16 بیتی ما را مطابق با قانون μ (استاندارد G.711) به نمونه های هشت بیتی تبدیل می کند. در خروجی رمزگذار، ما یک بلوک داده به اندازه نصف آن داریم. به طور کلی، اگر نیازی به صرفه جویی در ترافیک نداشته باشیم، می توانیم داده ها را بدون فشرده سازی انتقال دهیم. اما در اینجا استفاده از رمزگذار مفید است، زیرا Wireshark می تواند صدا را از یک جریان RTP تنها زمانی که بر اساس قانون μ یا a-قانون فشرده شده است، بازتولید کند.

پس از رمزگذار، بلوک‌های سبک‌تر داده به فیلتر rtpsend ارسال می‌شوند که آنها را در یک بسته RTP قرار می‌دهد، پرچم‌های لازم را تنظیم می‌کند و برای انتقال از طریق شبکه در قالب یک بسته UDP در اختیار پخش‌کننده رسانه قرار می‌دهد.

زنجیره بالایی فیلترها مسیر دریافت را تشکیل می دهد؛ بسته های RTP دریافت شده توسط پخش کننده رسانه از شبکه وارد فیلتر rtprecv می شوند که در خروجی آن به شکل بلوک های داده ظاهر می شوند که هر کدام مربوط به یک بسته دریافتی است. بلوک فقط حاوی داده های بار است؛ در مقاله قبلی آنها در تصویر به رنگ سبز نشان داده شده بودند.

سپس، بلوک‌ها به فیلتر رمزگشا ارسال می‌شوند که نمونه‌های تک بایتی موجود در آن‌ها را به نمونه‌های خطی ۱۶ بیتی تبدیل می‌کند. که قبلاً می تواند توسط فیلترهای پخش کننده رسانه پردازش شود. در مورد ما، ما به سادگی آنها را برای پخش در بلندگوهای هدست شما به کارت صدا می فرستیم.

حال به سراغ پیاده سازی نرم افزار می رویم. برای این کار فایل های گیرنده و فرستنده را که قبلا از هم جدا کرده بودیم با هم ترکیب می کنیم. قبل از این، از تنظیمات ثابت برای پورت ها و آدرس ها استفاده می کردیم، اما اکنون نیاز داریم که برنامه بتواند از تنظیماتی که در هنگام راه اندازی مشخص می کنیم استفاده کند. برای انجام این کار، ما قابلیتی برای پردازش آرگومان های خط فرمان اضافه می کنیم. پس از آن می‌توانیم آدرس 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

اضافه کردن نظر