Materiál článku je prevzatý z môjho .
Prenos zvučnih signalov preko RTP toka

V minulosti Zbrali sme shemo daljinskega upravljanja iz generatorja in detektorja tonalnih signalov, ki delujeta znotraj enega programa. V tem članku se bomo naučili uporabljati protokol RTP (RFC 3550 — RTP: Transportni protokol za aplikacije v realnem času) za sprejemanje/pošiljanje zvučnega signala po omrežju Ethernet.
Protokol RTP (Real Time Protocol) v prevodu pomeni protokol v realnem času, uporablja se za prenos zvoka, videa, podatkov, vsega tistega, kar zahteva prenos v realnem času. Kot primer vzemimo zvučni signal. Fleksibilnost protokola je taka, da omogoča prenos zvučnega signala z vnaprej določenim kakovostjo.
Prenos se izvaja s pomočjo UDP paketov, kar pomeni, da je med prenosom povsem sprejemljiva izguba paketov. Vsak paket vključuje poseben RTP naslov in blok podatkov prenesenega signala. V naslovu je naključno izbran identifikator vira signala, informacije o vrsti prenesenega signala, edinstvena zaporedna številka paketa, tako da lahko paketi pri dekodiranju uskladiščenih pravilno, ne glede na to, v katerem vrstnem redu jih je omrežje dostavilo. Naslov lahko vsebuje tudi dodatne informacije, tako imenovano razširitev, ki omogoča prilagoditev naslova za uporabo v določeni aplikaciji.
Podatkovni blok vsebuje uporabno obremenitev paketa. Notranja organizacija vsebine je odvisna od vrste obremenitve, to so lahko meritve monofončnega signala, stereosignal, vrstica video slike itd.
Vrsta obremenitve je označena s sedmimi-bitno številko. Priporočilo RFC3551 (RTP profil za avdio in video konference z minimalno kontrolo) določa več vrst obremenitev v ustrezni tabeli, ki prinaša opis vrst obremenitve in vrednosti kod, s katerimi so označene. Nekatere kode nimajo stroge vezave na katero koli vrsto obremenitve - lahko se uporabljajo za označevanje poljubnih obremenitev.
Veľkosť bloku dát je obmedzená maximálnou veľkosťou paketu, ktorý môže byť prenesený v tejto sieti bez segmentovania (parametr MTU). Vo všeobecnosti to nie je viac ako 1500 bajtov. Takže na zvýšenie množstva prenášaných údajov za sekundu je možné do určitej miery zvyšovať veľkosť paketu a nielen potom bude potrebné zvyšovať frekvenciu odosielania paketov. U mediastreamera je to nastaviteľný parameter. Predvolene je to 50 Hz, teda 50 paketov za sekundu. Sekvenciu prenášaných RTP-paketov budeme nazývať RTP-prúdom.
Na začatie prenosu údajov medzi zdrojom a prijímačom stačí, aby vysielač vedel IP adresu prijímača a číslo portu, ktorý používa na prijímanie. Teda bez akýchkoľvek predbežných procedúr zdroj začína prenášať údaje, zatiaľ čo prijímač je pripravený ich okamžite prijať a spracovať. Podľa štandardu by číslo portu používané na prenos alebo prijímanie RTP-prúdu malo byť párne.
V situáciách, keď nie je možné vopred poznať adresu prijímača, sa používajú servery, na ktorých prijímače zanechávajú svoju adresu, a vysielač si ju môže vyžiadať, odvolávajúc sa na určité unikátne meno prijímača.
V prípadoch, keď nie je známa kvalita komunikačného kanála alebo možnosti prijímača, sa organizuje spätný kanál, prostredníctvom ktorého môže prijímač informovať vysielača o svojich možnostiach, počte paketov, ktoré mu chýbajú a podobne. V tomto kanáli sa používa protokol RTCP. Formát paketov prenášaných v tomto kanáli je definovaný v RFC 3605. Prostredníctvom tohto kanála sa prenáša relatívne málo dát, 200..300 bajtov za sekundu, preto jeho existencia nie je zaťažujúca. Číslo portu, na ktorý sú odosielané RTCP-pakety, by malo byť nepárne a o jednu jednotku vyššie než číslo portu, z ktorého prichádza RTP-prúd. V našom príklade nebudeme tento kanál používať, pretože možnosti prijímača a kanála sú zjavne vyššie než naše, zatiaľ skromné, potreby.
V našom programe bude schéma prenosu údajov, na rozdiel od schémy predchádzajúceho príkladu, rozdelená na dve časti: na odosielaciu a prijímaciu linku. Pre každú časť vytvoríme svoj zdroj hodín, ako je znázornené na úvodnom obrázku.
Jednosmerná komunikácia medzi nimi bude prebiehať prostredníctvom protokolu RTP. V tomto príklade nebudeme potrebovať externú sieť, pretože aj vysielač, aj prijímač budú na jednom počítači — pakety budú prechádzať iba jeho vnútornými vrstvami.
Na vytvorenie RTP toku v mediastreameri sa používajú dva filtre: MS_RTP_SEND a MS_RTP_RECV. Prvý vykonáva prenos, druhý prijíma RTP tok. Aby tieto filtre začali fungovať, je potrebné im poskytnúť ukazovateľ na objekt RTP relácie, ktorý dokáže vykonávať konverziu toku blokov dát na RTP pakety a naopak. Keďže vnútorný formát údajov mediastreamera sa nezlučuje s formátom údajov RTP paketu, pred prenosom údajov do MS_RTP_SEND je potrebné použiť konverzný filter (encoder), ktorý prevádza 16-bitové vzorky zvukového signálu na osembitové vzorky kódované podľa u-zákona (mü-zákona). Na prijímacej strane túto funkciu plní dekodér (decoder).
Nižšie je uvedený text programu, ktorý realizuje schému zobrazenú na obrázku (symbole # pred príkazmi include boli odstránené, nezabudnite ich opäť pridať):
/* Файл 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);
}
}
Kompilujeme a spúšťame. Program bude fungovať ako v predchádzajúcom príklade, ale dáta budú prenášané prostredníctvom RTP toku.
V nasledujúcom článku rozdelíme tento program na dve nezávislé aplikácie — prijímač a vysielač a spustíme ich v rôznych termináloch. Zároveň sa naučíme analyzovať RTP pakety pomocou programu TShark.
Zdroj: habr.com
