Explorăm motorul VoIP Mediastreamer2. Partea 6

Materialul articolului provine de pe canalul meu zen.

Transmisia semnalului audio prin flux RTP

Explorăm motorul VoIP Mediastreamer2. Partea 6

În trecut, pe care l-ați citit Am adunat un schemă de control de la distanță din generator și detector de semnale tonale, care funcționează în cadrul aceleași aplicații. În acest articol, vom învăța cum să folosim protocolul RTP (RFC 3550 — RTP: Un protocol de transport pentru aplicații în timp real) pentru primirea/ transmiterea semnalului audio prin rețeaua Ethernet.

Protocolul RTP (Protocolul de Timp Real) se traduce prin protocoale de timp real, fiind utilizat pentru transmiterea sunetului, video-ului, datelor, tot ceea ce necesită o transmisie în timp real. Ca exemplu, să luăm semnalul audio. Flexibilitatea protocolului permite transmiterea semnalului audio cu o calitate presetată.

Transmiterea se realizează prin pachete UDP, ceea ce înseamnă că, în timpul transmiterii, pierderea pachetelor este acceptabilă. Fiecare pachet conține un antet special RTP și un bloc de date al semnalului transmis. În antet se află un identificator aleatoriu al sursei semnalului, informații despre tipul de semnal transmis, un număr secvențial unic al pachetului, astfel încât pachetele să poată fi rearanjate corect la decodare, indiferent de ordinea în care au fost livrate de rețea. Antetul poate conține, de asemenea, informații suplimentare, numite extinderi, ce permit adaptarea antetului la o aplicație specifică.

Blocul de date conține sarcina utilă a pachetului. Organizarea internă a conținutului depinde de tipul sarcinii, care poate fi datele unui semnal monofonic, semnal stereo, un flux video etc.

Tipul sarcinii este denumit printr-un număr de șapte biți. Recomandarea RFC3551 (Profil RTP pentru conferințe audio și video cu control minim) stabilește mai multe tipuri de sarcini, în tabelul corespunzător sunt prezentate descrierile tipurilor de sarcini și valorile codurilor prin care sunt denumite. Unele coduri nu au legături stricte cu un anumit tip de sarcină - ele pot fi utilizate pentru denumirea unei sarcini arbitrare.

Dimensiunea blocului de date este limitată superior de dimensiunea maximă a pachetului care poate fi transmis în această rețea fără fragmentare (parametrul MTU). În general, aceasta nu depășește 1500 de octeți. Astfel, pentru a crește cantitatea de date transmise pe secundă, este posibil să se mărească dimensiunea pachetului până la un anumit moment, după care va fi necesară creșterea frecvenței trimiterii pachetelor. În cazul unui media streamer, acesta este un parametru configurabil. Implicit, acesta este setat la 50 Hz, adică 50 de pachete pe secundă. Secvența pachetelor RTP transmise va fi numită flux RTP.

Pentru a începe transmiterea de date între sursă și receptor, este suficient ca emitatorul să cunoască adresa IP a receptorului și numărul portului pe care acesta îl folosește pentru a primi datele. Așadar, fără nicio procedură prealabilă, sursa începe să transmită date, iar receptorul este pregătit să le primească și să le proceseze imediat. Conform standardului, numărul portului utilizat pentru transmiterea sau primirea fluxului RTP trebuie să fie par.

În situațiile în care nu se poate cunoaște dinainte adresa receptorului, se folosesc servere pe care receptorii își lasă adresa, iar emitatorul poate să o solicite, referindu-se la un nume unic al receptorului.

În cazurile în care calitatea canalului de comunicație sau capacitățile receptorului nu sunt cunoscute, se organizează un canal de feedback prin care receptorul poate informa emitatorul despre capacitățile sale, numărul de pachete pe care nu le-a primit etc. În acest canal se utilizează protocolul RTCP. Formatul pachetelor transmise în acest canal este definit în RFC 3605. Prin acest canal se transmit relativ puține date, 200..300 de octeți pe secundă, așa că, în general, prezența acestuia nu este o povară. Numărul portului la care sunt trimise pachetele RTCP trebuie să fie impar și cu o unitate mai mare decât numărul portului de la care vine fluxul RTP. În exemplul nostru, nu vom utiliza acest canal, deoarece capacitățile receptorului și ale canalului depășesc în mod evident nevoile noastre, care sunt încă modeste.

În programul nostru, schema de transmitere a datelor, spre deosebire de schema precedentului exemplu, va fi împărțită în două părți: în circuitul de transmitere și circuitul de recepție. Pentru fiecare parte vom crea propria sursă de ceas, așa cum este ilustrat în imaginea de titlu.

Comunicarea unidirecțională între ele se va realiza prin intermediul protocolului RTP. În acest exemplu, nu vom avea nevoie de o rețea externă, deoarece atât emițătorul, cât și receptorul vor fi situate pe același computer — pachetele vor circula între ele în interior.

Pentru a stabili fluxul RTP în media streamer, se utilizează două filtre: MS_RTP_SEND și MS_RTP_RECV. Primul efectuează transmiterea, iar al doilea primește fluxul RTP. Pentru ca aceste filtre să înceapă să funcționeze, trebuie să le transmitem un pointer către obiectul sesiunii RTP, care poate efectua atât conversia fluxului de blocuri de date în flux de pachete RTP, cât și acțiunea inversă. Deoarece formatul intern al datelor din media streamer nu corespunde formatului datelor din pachetul RTP, înainte de a transmite datele către MS_RTP_SEND, trebuie să folosim un filtru de conversie (encoder) care convertește eșantioanele sonore de 16 biți în cele de 8 biți, codificate conform legii u (legea mu). Pe partea de recepție, funcția inversă este realizată de filtru decoder.

Mai jos este prezentat textul programului care implementează schema arătată în figură (simbolurile # de înaintea directivelor include au fost eliminate, nu uitați să le adăugați):

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

Compilăm, lansăm. Programul va funcționa la fel ca în exemplul anterior, dar datele vor fi transmise prin intermediul fluxului RTP.

În articolul următor, vom împărți acest program în două aplicații independente — receptor și emițător și le vom lansa în terminale diferite. În paralel, vom învăța să analizăm pachetele RTP folosind programul TShark.

Sursa: habr.com

Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS 🔥 Cumpără un hosting fiabil pentru site-uri cu protecție DDoS, servere VPS VDS | ProHoster