Mediastreamer2 VoIP-mootori uurimine. 6. osa

Artikli materjal on võetud minu zen kanal.

Helisignaali edastamine RTP-voo kaudu

Mediastreamer2 VoIP-mootori uurimine. 6. osa

Viimases siit Oleme kokku pannud kaugjuhtimisahela toonigeneraatorist ja toonidetektorist, mis töötavad sama programmi raames. Selles artiklis õpime kasutama RTP-protokolli (RFC 3550 - RTP: reaalajas rakenduste transpordiprotokoll) helisignaali vastuvõtmiseks/edastamiseks Etherneti võrgu kaudu.

RTP protokoll (Reaalajas Protokoll) tõlgitud tähendab reaalajas protokolli, seda kasutatakse heli, video, andmete, kõige reaalajas edastamist vajava edastamiseks. Võtame näiteks helisignaali. Protokolli paindlikkus on selline, et see võimaldab teil edastada etteantud kvaliteediga helisignaali.

Edastamine toimub UDP-pakettide abil, mis tähendab, et pakettide kadu on edastamise ajal üsna vastuvõetav. Iga pakett sisaldab spetsiaalset RTP päist ja edastatava signaali andmeplokki. Päis sisaldab juhuslikult valitud signaaliallika identifikaatorit, teavet edastatava signaali tüübi kohta ja unikaalset paketi järjekorranumbrit, et pakette saaks dekodeerimisel paigutada õigesse järjekorda, olenemata sellest, millises järjekorras need edastati. võrku. Päis võib sisaldada ka lisateavet, nn laiendit, mis võimaldab päist kohandada konkreetse rakenduse ülesande jaoks kasutamiseks.

Andmeplokk sisaldab paketi kasulikku koormust. Sisu sisemine korraldus sõltub koormuse tüübist, see võib olla monosignaali, stereosignaali, videopildirea vms näidised.

Koormustüüp on tähistatud seitsmebitise numbriga. Soovitus RFC3551 (RTP-profiil minimaalse juhtimisega heli- ja videokonverentside jaoks) kehtestab mitu veoseliiki, vastavas tabelis on toodud veoseliikide kirjeldus ja nende tähistamise koodide tähendus. Mõned koodid ei ole rangelt seotud ühegi laadimiskoormusega; neid saab kasutada suvalise koormuse tähistamiseks.

Andmeploki suurus on ülalpool piiratud maksimaalse paketi suurusega, mida saab antud võrgus edastada ilma segmenteerimiseta (MTU parameeter). Üldiselt ei ületa see 1500 baiti. Seega saate sekundis edastatava andmemahu suurendamiseks suurendada paketi suurust kuni teatud punktini ja seejärel peate suurendama pakettide saatmise sagedust. Meediumistriimi puhul on see konfigureeritav säte. Vaikimisi on see 50 Hz, st. 50 paketti sekundis. Edastatud RTP-pakettide jada nimetame RTP-vooks.

Andmete edastamise alustamiseks allika ja vastuvõtja vahel piisab, kui saatja teab vastuvõtja IP-aadressi ja pordi numbrit, mida ta vastuvõtmiseks kasutab. Need. ilma eelnevate protseduurideta hakkab allikas andmeid edastama ning vastuvõtja on omakorda valmis neid kohe vastu võtma ja töötlema. Standardi kohaselt peab RTP-voo edastamiseks või vastuvõtmiseks kasutatav pordinumber olema paaris.

Olukordades, kus vastuvõtja aadressi ei ole võimalik ette teada, kasutatakse servereid, kus vastuvõtjad jätavad oma aadressi ning saatja saab seda küsida, viidates mõnele unikaalsele vastuvõtja nimele.

Juhtudel, kui sidekanali kvaliteet või vastuvõtja võimalused pole teada, korraldatakse tagasisidekanal, mille kaudu saab vastuvõtja teavitada saatjat oma võimalustest, vastamata jäänud pakettide arvust jne. See kanal kasutab RTCP-protokolli. Sellel kanalil edastatavate pakettide vorming on määratletud RFC 3605-s. Selle kanali kaudu edastatakse suhteliselt vähe andmeid, 200...300 baiti sekundis, seega üldiselt ei ole selle olemasolu koormav. Pordinumber, kuhu RTCP-paketid saadetakse, peab olema paaritu ja ühe võrra suurem kui pordi number, kust RTP-voog tuleb. Meie näites me seda kanalit ei kasuta, kuna vastuvõtja ja kanali võimalused ilmselgelt ületavad meie seni tagasihoidlikke vajadusi.

Meie programmis jagatakse andmeedastusahel erinevalt eelmisest näitest kaheks osaks: saatetee ja vastuvõtutee. Iga osa jaoks teeme oma kellaallika, nagu on näidatud tiitlipildil.

Ühesuunaline side nende vahel toimub RTP-protokolli abil. Selles näites ei vaja me välist võrku, kuna nii saatja kui ka vastuvõtja asuvad samas arvutis - paketid liiguvad selle sees.

RTP-voo loomiseks kasutab meediumistriimija kahte filtrit: MS_RTP_SEND ja MS_RTP_RECV. Esimene edastab teise ja võtab vastu RTP-voo. Nende filtrite töötamiseks peavad nad suunama kursori RTP-seansiobjektile, mis võib andmeplokkide voo teisendada RTP-pakettide vooks või toimida vastupidiselt. Kuna meediumistriimi sisemine andmevorming ei ühti RTP-paketi andmevorminguga, peate enne andmete edastamist MS_RTP_SEND-i kasutama kodeerija filtrit, mis teisendab 16-bitise helisignaali näidised kaheksabitiseks kodeeringuks vastavalt u-seadus (mu-seadus). Vastuvõtupoolel täidab dekoodri filter vastupidist funktsiooni.

Allpool on joonisel näidatud skeemi rakendava programmi tekst (sümbolid # enne kaasamiskäske on eemaldatud, ärge unustage neid lisada):

/* Файл mstest6.c Имитатор пульта управления и приемника. */
#include <mediastreamer2/msfilter.h>
#include <mediastreamer2/msticker.h>
#include <mediastreamer2/dtmfgen.h>
#include <mediastreamer2/mssndcard.h>
#include <mediastreamer2/msvolume.h>
#include <mediastreamer2/mstonedetector.h>
#include <mediastreamer2/msrtp.h>
#include <ortp/rtpsession.h>
#include <ortp/payloadtype.h>
/* Подключаем заголовочный файл с функциями управления событиями
* медиастримера.*/
include <mediastreamer2/mseventqueue.h>
#define PCMU 0
/* Функция обратного вызова, она будет вызвана фильтром, как только он
обнаружит совпадение характеристик входного сигнала с заданными. */
static void tone_detected_cb(void *data, MSFilter *f, unsigned int event_id,
MSToneDetectorEvent *ev)
{
printf("Принята команда: %sn", ev->tone_name);
}
/*----------------------------------------------------------------------------*/
/* Функция регистрации типов полезных нагрузок. */
void register_payloads(void)
{
/*Регистрируем типы нагрузок в таблице профилей. Позднее, по индексу
взятому из заголовка RTP-пакета из этой таблицы будут извлекаться
параметры нагрузки, необходимые для декодирования данных пакета. */
rtp_profile_set_payload (&av_profile, PCMU, &payload_type_pcm8000);
}
/*----------------------------------------------------------------------------*/
/* Эта функция создана из функции create_duplex_rtpsession() в audiostream.c
медиастримера2. */
static RtpSession *
create_rtpsession (int loc_rtp_port, int loc_rtcp_port,
bool_t ipv6, RtpSessionMode mode)
{
RtpSession *rtpr;
rtpr = rtp_session_new ((int) mode);
rtp_session_set_scheduling_mode (rtpr, 0);
rtp_session_set_blocking_mode (rtpr, 0);
rtp_session_enable_adaptive_jitter_compensation (rtpr, TRUE);
rtp_session_set_symmetric_rtp (rtpr, TRUE);
rtp_session_set_local_addr (rtpr, ipv6 ? "::" : "0.0.0.0", loc_rtp_port,
loc_rtcp_port);
rtp_session_signal_connect (rtpr, "timestamp_jump",
(RtpCallback) rtp_session_resync, 0);
rtp_session_signal_connect (rtpr, "ssrc_changed",
(RtpCallback) rtp_session_resync, 0);
rtp_session_set_ssrc_changed_threshold (rtpr, 0);
rtp_session_set_send_payload_type(rtpr, PCMU);
/* По умолчанию выключаем RTCP-сессию, так как наш пульт не будет использовать её. */
rtp_session_enable_rtcp (rtpr, FALSE);
return rtpr;
}
/*----------------------------------------------------------------------------*/
int main()
{
ms_init();
/* Создаем экземпляры фильтров. */
MSFilter *voidsource = ms_filter_new(MS_VOID_SOURCE_ID);
MSFilter *dtmfgen = ms_filter_new(MS_DTMF_GEN_ID);
MSFilter *volume = ms_filter_new(MS_VOLUME_ID);
MSSndCard *card_playback =
ms_snd_card_manager_get_default_card(ms_snd_card_manager_get());
MSFilter *snd_card_write = ms_snd_card_create_writer(card_playback);
MSFilter *detector = ms_filter_new(MS_TONE_DETECTOR_ID);
/* Очищаем массив находящийся внутри детектора тонов, он описывает
* особые приметы разыскиваемых сигналов.*/
ms_filter_call_method(detector, MS_TONE_DETECTOR_CLEAR_SCANS, 0);
/* Подключаем к фильтру функцию обратного вызова. */
ms_filter_set_notify_callback(detector,
(MSFilterNotifyFunc)tone_detected_cb, NULL);
/* Создаем массив, каждый элемент которого описывает характеристику
* одного из тонов, который требуется обнаруживать: Текстовое имя
* данного элемента, частота в герцах, длительность в миллисекундах,
* минимальный уровень относительно 0,775В. */
MSToneDetectorDef scan[6]=
{
{"V+",440, 100, 0.1}, /* Команда "Увеличить громкость". */
{"V-",540, 100, 0.1}, /* Команда "Уменьшить громкость". */
{"C+",640, 100, 0.1}, /* Команда "Увеличить номер канала". */
{"C-",740, 100, 0.1}, /* Команда "Уменьшить номер канала". */
{"ON",840, 100, 0.1}, /* Команда "Включить телевизор". */
{"OFF", 940, 100, 0.1}/* Команда "Выключить телевизор". */
};
/* Передаем "приметы" сигналов детектор тонов. */
int i;
for (i = 0; i < 6; i++)
{
ms_filter_call_method(detector, MS_TONE_DETECTOR_ADD_SCAN,
&scan[i]);
}
/* Создаем фильтры кодера и декодера */
MSFilter *encoder = ms_filter_create_encoder("PCMU");
MSFilter *decoder=ms_filter_create_decoder("PCMU");
/* Регистрируем типы нагрузки. */
register_payloads();
/* Создаем RTP-сессию передатчика. */
RtpSession *tx_rtp_session = create_rtpsession (8010, 8011, FALSE, RTP_SESSION_SENDONLY);
rtp_session_set_remote_addr_and_port(tx_rtp_session,"127.0.0.1", 7010, 7011);
rtp_session_set_send_payload_type(tx_rtp_session, PCMU);
MSFilter *rtpsend = ms_filter_new(MS_RTP_SEND_ID);
ms_filter_call_method(rtpsend, MS_RTP_SEND_SET_SESSION, tx_rtp_session);
/* Создаем RTP-сессию приемника. */
MSFilter *rtprecv = ms_filter_new(MS_RTP_RECV_ID);
RtpSession *rx_rtp_session = create_rtpsession (7010, 7011, FALSE, RTP_SESSION_RECVONLY);
ms_filter_call_method(rtprecv, MS_RTP_RECV_SET_SESSION, rx_rtp_session);
/* Создаем источники тактов - тикеры. */
MSTicker *ticker_tx = ms_ticker_new();
MSTicker *ticker_rx = ms_ticker_new();
/* Соединяем фильтры передатчика. */
ms_filter_link(voidsource, 0, dtmfgen, 0);
ms_filter_link(dtmfgen, 0, volume, 0);
ms_filter_link(volume, 0, encoder, 0);
ms_filter_link(encoder, 0, rtpsend, 0);
/* Соединяем фильтры приёмника. */
ms_filter_link(rtprecv, 0, decoder, 0);
ms_filter_link(decoder, 0, detector, 0);
ms_filter_link(detector, 0, snd_card_write, 0);
/* Подключаем источник тактов. */
ms_ticker_attach(ticker_tx, voidsource);
ms_ticker_attach(ticker_rx, rtprecv);
/* Настраиваем структуру, управляющую выходным сигналом генератора. */
MSDtmfGenCustomTone dtmf_cfg;
dtmf_cfg.tone_name[0] = 0;
dtmf_cfg.duration = 1000;
dtmf_cfg.frequencies[0] = 440;
/* Будем генерировать один тон, частоту второго тона установим в 0. */
dtmf_cfg.frequencies[1] = 0;
dtmf_cfg.amplitude = 1.0;
dtmf_cfg.interval = 0.;
dtmf_cfg.repeat_count = 0.;
/* Организуем цикл сканирования нажатых клавиш. Ввод нуля завершает
* цикл и работу программы. */
char key='9';
printf("Нажмите клавишу команды, затем ввод.n"
"Для завершения программы введите 0.n");
while(key != '0')
{
key = getchar();
if ((key >= 49) && (key <= 54))
{
printf("Отправлена команда: %cn", key);
/* Устанавливаем частоту генератора в соответствии с
* кодом нажатой клавиши. */
dtmf_cfg.frequencies[0] = 440 + 100*(key-49);
/* Включаем звуковой генератор c обновленной частотой. */
ms_filter_call_method(dtmfgen, MS_DTMF_GEN_PLAY_CUSTOM,
(void*)&dtmf_cfg);
}
/* Укладываем тред в спячку на 20мс, чтобы другие треды
* приложения получили время на работу. */
ms_usleep(20000);
}
}

Koostame ja käivitame. Programm töötab nagu eelmises näites, kuid andmed edastatakse RTP voo kaudu.

Järgmises artiklis jagame selle programmi kaheks sõltumatuks rakenduseks - vastuvõtjaks ja saatjaks ning käivitame need erinevates terminalides. Samal ajal õpime analüüsima RTP pakette programmi TShark abil.

Allikas: www.habr.com

Lisa kommentaar