Explorarea motorului VoIP Mediastreamer2. Partea 6

Materialul articolului este preluat de pe mine canal zen.

Transmiterea unui semnal audio prin flux RTP

Explorarea motorului VoIP Mediastreamer2. Partea 6

În trecut articol Am asamblat un circuit de control de la distanță de la un generator de ton și un detector de ton care funcționează în cadrul aceluiași program. Î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 unui semnal audio printr-o rețea Ethernet.

protocol RTP (Protocol în timp real) tradus înseamnă protocol în timp real, este folosit pentru a transmite audio, video, date, tot ceea ce necesită transmisie în timp real. Să luăm ca exemplu un semnal audio. Flexibilitatea protocolului este de așa natură încât vă permite să transmiteți un semnal audio cu o calitate predeterminată.

Transmisia se realizează folosind pachete UDP, ceea ce înseamnă că pierderea pachetelor este destul de acceptabilă în timpul transmisiei. Fiecare pachet conține un antet RTP special și un bloc de date al semnalului transmis. Antetul conține un identificator de sursă de semnal selectat aleatoriu, informații despre tipul de semnal care este transmis și un număr unic de secvență de pachet, astfel încât pachetele să poată fi aranjate în ordinea corectă la decodificare, indiferent de ordinea în care au fost livrate de către reţea. Antetul poate conține și informații suplimentare, așa-numita extensie, care permite adaptarea antetului pentru utilizare într-o anumită sarcină a aplicației.

Blocul de date conține sarcina utilă a pachetului. Organizarea internă a conținutului depinde de tipul de încărcare, pot fi mostre ale unui semnal mono, un semnal stereo, o linie de imagine video etc.

Tipul de încărcare este indicat 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 sarcină; tabelul corespunzător oferă o descriere a tipurilor de sarcină și semnificația codurilor prin care acestea sunt desemnate. Unele coduri nu sunt strict legate de niciun tip de sarcină; ele pot fi folosite pentru a desemna o încărcare arbitrară.

Dimensiunea unui bloc de date este limitată mai sus de dimensiunea maximă a pachetului care poate fi transmis pe o anumită rețea fără segmentare (parametru MTU). În general, aceasta nu este mai mare de 1500 de octeți. Astfel, pentru a crește cantitatea de date transmise pe secundă, puteți crește dimensiunea pachetului până la un anumit punct, iar apoi va trebui să creșteți frecvența de trimitere a pachetelor. Într-un streamer media, aceasta este o setare configurabilă. În mod implicit, este de 50 Hz, adică 50 de pachete pe secundă. Vom numi secvența de pachete RTP transmise un flux RTP.

Pentru a începe transmiterea datelor între sursă și receptor, este suficient ca emițătorul să cunoască adresa IP a receptorului și numărul portului pe care îl folosește pentru recepție. Acestea. fără proceduri preliminare, sursa începe să transmită date, iar receptorul, la rândul său, este gata să le primească și să le proceseze imediat. Conform standardului, numărul portului folosit pentru a transmite sau a primi un flux RTP trebuie să fie par.

În situațiile în care este imposibil să cunoașteți în prealabil adresa receptorului, se folosesc servere unde receptorii își lasă adresa, iar emițătorul o poate solicita făcând referire la un nume unic al receptorului.

În cazurile în care calitatea canalului de comunicație sau capacitățile receptorului sunt necunoscute, se organizează un canal de feedback prin care receptorul poate informa emițătorul despre capacitățile acestuia, numărul de pachete pe care le-a ratat etc. Acest canal folosește protocolul RTCP. Formatul pachetelor transmise pe acest canal este definit în RFC 3605. Pe acest canal se transmit relativ puține date, 200..300 de octeți pe secundă, deci, în general, prezența acestuia nu este împovărătoare. Numărul portului către care sunt trimise pachetele RTCP trebuie să fie impar și cu unul mai mare decât numărul portului din care provine fluxul RTP. În exemplul nostru, nu vom folosi acest canal, deoarece capacitățile receptorului și ale canalului depășesc în mod evident nevoile noastre, până acum modeste.

În programul nostru, circuitul de transmisie a datelor, spre deosebire de exemplul anterior, va fi împărțit în două părți: o cale de transmisie și o cale de recepție. Pentru fiecare parte vom crea propria noastră sursă de ceas, așa cum se arată în imaginea din titlu.

Comunicarea unidirecțională între ei se va realiza folosind protocolul RTP. În acest exemplu, nu avem nevoie de o rețea externă, deoarece atât transmițătorul, cât și receptorul vor fi amplasate pe același computer - pachetele vor călători în interiorul acestuia.

Pentru a stabili un flux RTP, streamerul media folosește două filtre: MS_RTP_SEND și MS_RTP_RECV. Primul îl transmite pe al doilea și primește fluxul RTP. Pentru ca aceste filtre să funcționeze, trebuie să treacă un pointer către un obiect de sesiune RTP, care poate fie să convertească un flux de blocuri de date într-un flux de pachete RTP, fie să facă opusul. Deoarece formatul de date intern al streamer-ului media nu se potrivește cu formatul de date al pachetului RTP, înainte de a transfera datele către MS_RTP_SEND, trebuie să utilizați un filtru codificator care convertește mostre de semnal audio pe 16 biți în coduri de opt biți conform u-law (mu-law). Pe partea de recepție, filtrul decodor îndeplinește funcția opusă.

Mai jos este textul programului care implementează schema prezentată în figură (simbolurile # înainte de directivele include au fost eliminate, nu uitați să le includeț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 și rulăm. Programul va funcționa ca în exemplul anterior, dar datele vor fi transmise printr-un flux RTP.

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

Sursa: www.habr.com

Adauga un comentariu