Materijal u članku preuzet je sa mog .
Prenos zvočnega signala prek RTP toka

V prejšnjem zbrali smo shemo daljinskega upravljanja iz generatorja in detektorja tonskih signalov, ki delujeta znotraj enega samega programa. V tem članku se bomo naučili uporabljati protokol RTP (RFC 3550 — RTP: Transportni protokol za aplikacije v realnem času) za sprejem/prenos zvočnega signala po Ethernet omrežju.
Protokol RTP (Real Time Protocol) v prevodu pomeni protokol realnega časa, uporablja se za prenos zvoka, videa, podatkov, vsega, kar zahteva prenos v realnem času. Kot primer vzemimo zvočni signal. Fleksibilnost protokola je taka, da omogoča prenos zvočnega signala z vnaprej določenimi kakovostmi.
Prenos poteka s pomočjo UDP paketov, kar pomeni, da je med prenosom povsem sprejemljivo izgubljati pakete. Vsak paket vsebuje poseben RTP glavi in blok podatkov prenesenega signala. V glavi je naključno izbran identifikator vira signala, informacije o vrsti prenesenega signala, edinstvena zaporedna številka paketa, da se lahko paketi ob dekodiranju postavijo v pravilen vrstni red, ne glede na to, v katerem vrstnem redu jih je omrežje dostavilo. Glava lahko prav tako vsebuje dodatne informacije, tako imenovano razširitev, ki omogoča prilagoditev glave za uporabo v določenem aplikacijskem nalogu.
Blok podatkov vsebuje uporabno obremenitev paketa. Notranja organizacija vsebine je odvisna od vrste obremenitve, to so lahko vzorci monofoničnega signala, stereo signal, vrstica videa itd.
Vrsta obremenitve je označena s sedembitno številko. Priporočilo RFC3551 (RTP Profile for Audio and Video Conferences with Minimal Control) določa več vrst obremenitev, v ustrezni tabeli so predstavljeni opisi vrst obremenitev in vrednosti kod, s katerimi so označene. Nekatere kode nimajo stroge pripetosti na kakršno koli vrsto obremenitve - lahko se uporabljajo za označevanje poljubne obremenitve.
Velikost bloka podatkov je omejena z največjo velikostjo paketa, ki ga je mogoče prenesti v tej mreži brez segmentacije (parameter MTU). Na splošno to ni več kot 1500 bajtov. Tako lahko povečamo količino prenesenih podatkov na sekundo do določene točke s povečanjem velikosti paketa, nato pa bo treba povečati frekvenco pošiljanja paketov. Pri medijskem predvajalniku je to nastavljiv parameter. Privzeto je 50 Hz, tj. 50 paketov na sekundo. Zaporedje prenesenih RTP-paketov bomo imenovali RTP-potok.
Za začetek prenosa podatkov med virom in prejemnikom je dovolj, da oddajnik ve IP-naslov prejemnika in številko vratarja, ki jo ta uporablja za sprejem. To pomeni, da brez kakršnih koli predhodnih postopkov vir začne prenos podatkov, prejemnik pa je hkrati pripravljen, da jih takoj sprejme in obdeluje. Po standardu mora biti številka vratarja, ki se uporablja za prenos ali sprejem RTP-potoka, sodo število.
V situacijah, kjer ni mogoče vnaprej poznati naslova prejemnika, se uporabljajo strežniki, kjer prejemniki pustijo svoj naslov, oddajnik pa ga lahko zahteva, sklicujoč se na neko edinstveno ime prejemnika.
V primerih, ko kakovost kanalov povezave ali zmogljivosti prejemnika niso znane, se organizira povratni kanal, po katerem prejemnik lahko obvešča oddajnika o svojih zmožnostih, številu paketov, ki jih je pogrešal itd. V tem kanalu se uporablja protokol RTCP. Oblika paketov, ki se prenašajo v tem kanalu, je določena v RFC 3605. Po tem kanalu se prenaša razmeroma malo podatkov, 200..300 bajtov na sekundo, zato njegovo obstoj ni obremenjujoč. Številka vratarja, na katero se pošiljajo RTCP-paketi, mora biti neparna in večja za ena od številke vratarja, s katerega prihaja RTP-potok. V našem primeru ne bomo uporabljali tega kanala, saj zmogljivosti prejemnika in kanala očitno presegajo naše skromne potrebe.
V našem programu bo shema prenosa podatkov, za razliko od sheme prejšnjega primera, razdeljena na dva dela: prenosni trakt in sprejemni trakt. Za vsak del bomo naredili svoj vir taktov, kot je prikazano na naslovni sliki.
Enosmerna komunikacija med njima bo potekala prek protokola RTP. V tem primeru zunanje omrežje ne bo potrebno, saj bosta tako oddajnik kot sprejemnik na istem računalniku — paketi bodo prehajali znotraj njega.
Za vzpostavitev RTP toka v medijskem strežniku se uporabljata dva filtra: MS_RTP_SEND in MS_RTP_RECV. Prvi prenaša, drugi pa sprejema RTP tok. Da bi ti filtri začeli delovati, morajo prejeti kazalec na objekt RTP seje, ki lahko izvaja tako pretvorbo toka podatkovnih blokov v RTP pakete kot izvajanje nasprotne funkcije. Ker notranji format podatkov medijskega strežnika ni enak formatu podatkov RTP paketa, je pred prenosom podatkov v MS_RTP_SEND potrebno uporabiti konverter (encoder), ki pretvori 16-bitne vzorce zvoka v osem bitne, kodirane po zakonih u (mi). Na sprejemni strani to funkcijo opravi dekoder (decoder).
Spodaj je besedilo programa, ki izvaja shemo prikazano na sliki (simboli # pred direktivami include so odstranjeni, ne pozabite jih dodati):
/* Файл 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);
}
}
Kompiliramo, zaženemo. Program bo deloval kot v prejšnjem primeru, vendar bodo podatki prenašani prek RTP toka.
V naslednjem članku bomo ta program razdelili na dva neodvisna aplikacije — sprejemnik in oddajnik in ju zagnali v različnih terminalih. Hkrati se bomo naučili analizirati RTP pakete s programom TShark.
Izvor: habr.com
