Eksplorojmë motorin VoIP Mediastreamer2. Pjesa 6

Materiali i artikullit është marrë nga kanali im zen.

Transmetimi i sinjalit audio përmes rrjedhës RTP

Eksplorojmë motorin VoIP Mediastreamer2. Pjesa 6

Në të kaluarën artikullin ne kemi përmbledhur një skemë të kontrollit nga gjeneratori dhe detektori i sinjaleve tonale, që funksionojnë brenda një programi. Në këtë artikull do të mësojmë si të përdorim protokollin RTP (RFC 3550 — RTP: Një Protokoll Transporti për Aplikacione në Koha Reale) për pranimin/transmetimin e sinjalit audio në rrjetin Ethernet.

Protokolli RTP (Protokolli në Kohë Reale) në përkthim do të thotë protokoll që operon në kohë reale, përdoret për transmetimin e zërit, videos, të dhënave, gjithçkaje që kërkon transmetim në kohë reale. Si shembull do të marrim sinjalin e zërit. Fleksibiliteti i protokollit është i tillë që lejon transmetimin e sinjalit audio me një cilësi të caktuar paraprakisht.

Transferi kryhet përmes paketave UDP, çka do të thotë se gjatë transferit pranohet humbja e paketimeve. Çdo paketë përmban një krye të veçantë RTP dhe një bllok të të dhënave të sinjalit që po transferohet. Në krye ndodhet një identifikues i rastësishëm të burimit të sinjalit, informacion mbi llojin e sinjalit që po transferohet, dhe një numër unik rendor të paketës, në mënyrë që paketat, gjatë dekodimit, të mund të renditen në rendin e duhur, pavarësisht nga rendi në të cilin i dorëzoi rrjeta. Krye gjithashtu mund të përmbajë informacion të shtuar, të njohur si zgjatje, e cila përshtat kryet për përdorim në një detyrë specifike aplikative.

Blloku i të dhënave përmban ngarkesën e paketës. Organizimi i brendshëm i përmbajtjes varet nga lloji i ngarkesës; mund të jenë mostrat e sinjalit monofonik, sinjali stereo, një rresht imazhi video etj.

Lloji i ngarkesës shënohet me një numër shtatë-bitësh. Rekomandimi RFC3551 (RTP Profile for Audio and Video Conferences with Minimal Control) vendos disa lloje ngarkesash në tabelën përkatëse, ku jepet përshkrimi i llojeve të ngarkesave dhe vlerat e kodeve me të cilat ato përcaktohen. Disa kode nuk kanë lidhje të fortë me ndonjë lloj ngarkese — ato mund të përdoren për të përfaqësuar ngarkesa të rastit.

Madhësia e bllokut të të dhënave është e kufizuar nga maksimumi i madhësisë së paketës që mund të transmetohet në këtë rrjet pa segmentim (parametri MTU). Në përgjithësi, kjo nuk është më shumë se 1500 byte. Prandaj, për të rritur numrin e të dhënave të transmetuara në sekondë, mund të rritet madhësia e paketës deri në një pikë të caktuar, dhe më pas do të nevojitet rritja e frekuencës së dërgimit të paketave. Në mediastreamer, ky është një parametër i konfigurueshëm. Në mënyrë të parazgjedhur, ai është 50 Hz, domethënë 50 paketa në sekondë. Sekuenca e paketave RTP që transmetohen do të quhet RTP-stream.

Për të filluar transmetimin e të dhënave midis burimit dhe marrësit, mjafton që transmetuesi të dijë adresën IP të marrësit dhe numrin e portit që ai përdor për pranimin. Pra, pa ndonjë procedurë paraprake, burimi fillon të transmetojë të dhëna, ndërsa marrësi, nga ana e tij, është i gatshëm t'i pranojë dhe përpunojë ato menjëherë. Sipas standardit, numri i portit të përdorur për transmetimin ose pranimin e RTP-fluksit duhet të jetë çift.

Në situatat kur nuk dihet paraprakisht adresa e marrësit, përdoren serverët, në të cilët marrëset lënë adresën e tyre, ndërsa transmetuesi mund ta kërkojë atë, duke iu referuar një emri unik të marrësit.

Në rastet kur cilësia e kanalit të komunikimit ose mundësitë e marrësit janë të panjohura, organizohet një kanal kthyes, përmes të cilit marrësi mund ta informojë transmetuesin për mundësitë e tij, numrin e paketave që nuk i ka marrë dhe të tjera. Në këtë kanal përdoret protokolli RTCP. Formati i paketave të dërguara në këtë kanal përcaktohet në RFC 3605. Në këtë kanal dërgohen relativisht pak të dhëna, 200-300 byte në sekondë, prandaj, në gjithë, prania e tij nuk është e rëndë. Numri i portit, në të cilin dërgohen paketat RTCP, duhet të jetë çifti dhe një më i madh se numri i portit nga i cili vjen rrjedha RTP. Në shembullin tonë ne nuk do ta përdorim këtë kanal, pasi mundësitë e marrësit dhe të kanalit padyshim e tejkalojnë ato, përderisa nevojat tona janë ende modeste.

Në programin tonë, skema e transmetimit të të dhënave, ndryshe nga skema e shembullit të kaluar, do të ndahet në dy pjesë: në segmentin e transmetimit dhe segmentin e marrjes. Për secilën pjesë do të bëjmë burimin tonë të taktimeve, siç tregohet në figurën kryesore.

Komunikimi i një drejtimi ndërmjet tyre do të realizohet përmes protokollit RTP. Në këtë shembull, nuk do të na nevojitet një rrjet i jashtëm, pasi si dërguesi ashtu edhe marrësi do të ndodhen në të njëjtin kompjuter — paketat do të qarkullojnë brenda tij.

Për të vendosur një rrjedhë RTP në media stream, përdoren dy filtra: MS_RTP_SEND dhe MS_RTP_RECV. Filtri i parë kryen dërgimin, ndërsa i dyti pranon rrjedhën RTP. Që këta filtra të fillojnë të punojnë, ata duhet të marrin një tregues për objektin e sesionit RTP, i cili mund të kryejë si konvertimin e rrjedhës së blloqeve të të dhënave në një rrjedhë RTP paketash ashtu edhe të kryejë veprimin e kundërt. Duke qenë se formati i brendshëm i të dhënave të media stream nuk përputhet me formatin e të dhënave të paketës RTP, para se të dërgohen të dhënat në MS_RTP_SEND, duhet të përdoret një filtrues konvertues (encoder), i cili e kthen 16-bitëshin e sinjalit audio në tetëbitësh të koduar sipas ligjit u (mu). Në anën e pranimit, funksionin e kundërt e kryen filtri decoder.

Më poshtë është teksti i programit, i cili realizon skemën e paraqitur në figurë (simbolet # para direktivave include janë hequr, mos harroni t'i vendosni):

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

Kompilojmë, e drejtojmë. Programi do të funksionojë si në shembullin e kaluar, por të dhënat do të dërgohen përmes një rrjedhe RTP.

Në artikullin e ardhshëm do ta ndajmë këtë program në dy aplikacione të pavarura — marrës dhe transmetues dhe do t’i drejtojmë ata në terminale të ndryshme. Në të njëjtën kohë, do të mësojmë si të analizojmë paketat RTP duke përdorur programin TShark.

Burimi: habr.com

Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë 🔥 Bli një hosting të besueshëm për faqet me mbrojtje DDoS, VPS VDS serverë | ProHoster