Inachunguza injini ya Mediastreamer2 VoIP. Sehemu ya 9

Nyenzo za kifungu zimechukuliwa kutoka kwangu chaneli ya zen.

Duplex intercom

Inachunguza injini ya Mediastreamer2 VoIP. Sehemu ya 9

Zamani Ibara ya intercom ya duplex ilitangazwa, na katika hii tutaifanya.

Mchoro unaonyeshwa kwenye takwimu ya kichwa. Mlolongo wa chini wa filters huunda njia ya maambukizi, ambayo huanza kutoka kwa kadi ya sauti. Inatoa sampuli za ishara kutoka kwa kipaza sauti. Kwa chaguo-msingi, hii hutokea kwa kiwango cha sampuli 8000 kwa pili. Kina kidogo cha data ambacho vichujio vya sauti vya kipeperushi cha media hutumia ni biti 16 (hii sio muhimu; ukitaka, unaweza kuandika vichujio ambavyo vitafanya kazi na kina kidogo zaidi). Data imejumuishwa katika vikundi vya sampuli 160. Kwa hivyo, kila block ina ukubwa wa ka 320. Ifuatayo, tunalisha data kwa pembejeo ya jenereta, ambayo, wakati imezimwa, ni "uwazi" kwa data. Niliiongeza ikiwa utachoka kuzungumza kwenye kipaza sauti wakati wa kurekebisha - unaweza kutumia jenereta "kupiga" njia na ishara ya sauti.

Baada ya jenereta, mawimbi huenda kwa kisimbaji, ambacho hubadilisha sampuli zetu za biti-16 kulingana na µ-law (kiwango cha G.711) kuwa biti nane. Katika pato la programu ya kusimba, tayari tunayo kizuizi cha data cha nusu ya ukubwa. Kwa ujumla, tunaweza kusambaza data bila mbano ikiwa hatuhitaji kuokoa trafiki. Lakini hapa ni muhimu kutumia kisimbaji, kwani Wireshark inaweza kutoa sauti kutoka kwa mkondo wa RTP tu inapobanwa kulingana na µ-sheria au sheria.

Baada ya encoder, vitalu vyepesi vya data vinatumwa kwenye chujio cha rtpsend, ambacho kitaziweka kwenye pakiti ya RTP, kuweka bendera zinazohitajika na kuwapa vyombo vya habari vya kusambaza habari kwa maambukizi kwenye mtandao kwa namna ya pakiti ya UDP.

Mlolongo wa juu wa vichungi huunda njia ya kupokea; pakiti za RTP zilizopokelewa na kipeperushi cha media kutoka kwa mtandao huingia kwenye kichungi cha rtprecv, kwa matokeo ambayo huonekana kwa njia ya vizuizi vya data, ambayo kila moja inalingana na pakiti moja iliyopokelewa. Kizuizi kina data ya upakiaji pekee; katika nakala iliyotangulia zilionyeshwa kwa kijani kwenye kielelezo.

Ifuatayo, vizuizi vinatumwa kwa kichungi cha decoder, ambacho hubadilisha sampuli za baiti moja zilizomo ndani yao kuwa laini, 16-bit. Ambayo inaweza tayari kuchakatwa na vichujio vya vipeperushi vya media. Kwa upande wetu, tunawatuma kwa kadi ya sauti ili kucheza tena kwenye spika za vifaa vyako vya sauti.

Sasa hebu tuendelee kwenye utekelezaji wa programu. Ili kufanya hivyo, tutachanganya faili za mpokeaji na za kupitisha ambazo tulitenganisha hapo awali. Kabla ya hili, tulitumia mipangilio iliyowekwa kwa bandari na anwani, lakini sasa tunahitaji programu ili kutumia mipangilio ambayo tunabainisha wakati wa kuanza. Ili kufanya hivyo, tungeongeza utendaji wa usindikaji wa hoja za mstari wa amri. Baada ya hapo tutaweza kuweka anwani ya IP na bandari ya intercom ambayo tunataka kuanzisha uhusiano.

Kwanza, hebu tuongeze muundo kwenye programu ambayo itahifadhi mipangilio yake:

struct _app_vars
{
  int  local_port;              /* Локальный порт. */
  int  remote_port;             /* Порт переговорного устройства на удаленном компьютере. */
  char remote_addr[128];        /* IP-адрес удаленного компьютера. */
  MSDtmfGenCustomTone dtmf_cfg; /* Настройки тестового сигнала генератора. */
};

typedef struct _app_vars app_vars;

Mpango huo utatangaza muundo wa aina hii inayoitwa vars.
Ifuatayo, wacha tuongeze kitendakazi ili kuchanganua hoja za mstari wa amri:

/* Функция преобразования аргументов командной строки в
* настройки программы. */
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]);
        }
    }
}

Kama matokeo ya kuchanganua, hoja za mstari wa amri zitawekwa kwenye uwanja wa muundo wa vars. Kazi kuu ya programu itakuwa kukusanya njia za kupitisha na kupokea kutoka kwa vichungi; baada ya kuunganisha ticker, udhibiti utahamishiwa kwenye kitanzi kisicho na kipimo ambacho, ikiwa frequency ya jenereta imewekwa kuwa isiyo ya sifuri, itaanzisha tena jenereta ya majaribio ili inafanya kazi bila kuacha.

Jenereta itahitaji kuwasha tena kwa sababu ya muundo wake; kwa sababu fulani haiwezi kutoa ishara inayodumu zaidi ya sekunde 16. Ikumbukwe kwamba muda wake unatajwa na nambari ya 32-bit.

Mpango mzima utaonekana kama hii:

/* Файл 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);
    }
}

Hebu tukusanye. Kisha programu inaweza kuendeshwa kwenye kompyuta mbili. Au kwa moja, kama nitafanya sasa. Tunazindua TShark kwa hoja zifuatazo:

$ sudo tshark -i lo -f "udp dst port 7010" -P -V -O RTP -o rtp.heuristic_rtp:TRUE -x

Ikiwa uwanja wa uzinduzi kwenye koni unaonyesha tu ujumbe kuhusu kuanza kwa kukamata, basi hii ni ishara nzuri - inamaanisha kuwa bandari yetu ina uwezekano mkubwa wa kutochukuliwa na programu zingine. Katika terminal nyingine, tunazindua mfano wa programu ambao utaiga intercom ya "mbali" kwa kubainisha nambari hii ya bandari:

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

Kama inavyoonekana kutoka kwa maandishi ya programu, anwani ya IP chaguo-msingi ni 127.0.0.1 (kitanzi cha ndani).

Katika terminal nyingine, tunazindua mfano wa pili wa programu, ambayo inaiga kifaa cha ndani. Tunatumia hoja ya ziada inayoruhusu jenereta ya majaribio iliyojengewa ndani kufanya kazi:

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

Kwa wakati huu, pakiti zinazopitishwa kuelekea kifaa cha "mbali" zinapaswa kuanza kuwaka kwenye koni na TShark, na sauti inayoendelea itasikika kutoka kwa spika ya kompyuta.

Ikiwa kila kitu kilifanyika kama ilivyoandikwa, basi tunaanzisha upya nakala ya pili ya programu, lakini bila ufunguo na hoja "-gen 440". Sasa utacheza jukumu la jenereta. Baada ya hayo, unaweza kufanya kelele kwenye kipaza sauti; unapaswa kusikia sauti inayolingana kwenye spika au vichwa vya sauti. Msisimko wa akustisk unaweza hata kutokea; punguza sauti ya spika na athari itatoweka.

Ikiwa uliiendesha kwenye kompyuta mbili na haukuchanganyikiwa kuhusu anwani za IP, basi matokeo sawa yanakungoja - mawasiliano ya sauti ya ubora wa dijiti ya njia mbili.

Katika makala inayofuata tutajifunza jinsi ya kuandika filters zetu wenyewe - programu-jalizi, shukrani kwa ustadi huu utaweza kutumia mkondo wa media sio tu kwa sauti na video, lakini pia katika eneo lingine maalum.

Chanzo: mapenzi.com

Kuongeza maoni