Istraživanje Mediastreamer2 VoIP motora. Dio 6

Materijal članka je preuzet sa mog zen kanal.

Prijenos audio signala putem RTP toka

Istraživanje Mediastreamer2 VoIP motora. Dio 6

U poslednjem članak Sastavili smo daljinski upravljač od generatora tona i detektor tona koji rade u okviru istog programa. U ovom članku ćemo naučiti kako koristiti RTP protokol (RFC 3550 - RTP: Transportni protokol za aplikacije u realnom vremenu) za prijem/prenos audio signala preko Ethernet mreže.

RTP protokol (Protokol u realnom vremenu) translated znači protokol u realnom vremenu, koristi se za prijenos audio, video, podataka, svega što je potrebno za prijenos u realnom vremenu. Uzmimo audio signal kao primjer. Fleksibilnost protokola je takva da vam omogućava prijenos audio signala unaprijed određenog kvaliteta.

Prijenos se vrši korištenjem UDP paketa, što znači da je gubitak paketa sasvim prihvatljiv tokom prijenosa. Svaki paket sadrži posebno RTP zaglavlje i blok podataka o prenošenom signalu. Zaglavlje sadrži nasumično odabran identifikator izvora signala, informacije o vrsti signala koji se prenosi i jedinstveni broj sekvence paketa kako bi paketi mogli biti raspoređeni u ispravnom redoslijedu prilikom dekodiranja, bez obzira na redoslijed kojim su isporučeni od strane mreže. Zaglavlje također može sadržavati dodatne informacije, takozvanu ekstenziju, koja omogućava da se zaglavlje prilagodi za korištenje u određenom zadatku aplikacije.

Blok podataka sadrži korisni teret paketa. Unutrašnja organizacija sadržaja zavisi od vrste opterećenja, to mogu biti uzorci mono signala, stereo signala, linije video slike itd.

Vrsta opterećenja je označena sedmobitnim brojem. Preporuka RFC3551 (RTP profil za audio i video konferencije sa minimalnom kontrolom) utvrđuje nekoliko vrsta opterećenja, a odgovarajuća tabela daje opis tipova opterećenja i značenje šifri kojima su označene. Neki kodovi nisu striktno vezani za bilo koju vrstu opterećenja; mogu se koristiti za označavanje proizvoljnog opterećenja.

Veličina bloka podataka je gore ograničena maksimalnom veličinom paketa koji se može prenijeti na datu mrežu bez segmentacije (MTU parametar). Općenito, ovo nije više od 1500 bajtova. Dakle, da biste povećali količinu podataka koji se prenose u sekundi, možete povećati veličinu paketa do određene tačke, a zatim ćete morati povećati učestalost slanja paketa. U media streameru, ovo je postavka koja se može konfigurirati. Podrazumevano je 50 Hz, tj. 50 paketa u sekundi. Redoslijed prenesenih RTP paketa ćemo nazvati RTP streamom.

Za početak prijenosa podataka između izvora i prijemnika, dovoljno je da predajnik zna IP adresu prijemnika i broj porta koji koristi za prijem. One. bez ikakvih preliminarnih procedura, izvor počinje da prenosi podatke, a prijemnik je zauzvrat spreman da ih odmah primi i obradi. Prema standardu, broj porta koji se koristi za prijenos ili prijem RTP toka mora biti paran.

U situacijama kada je nemoguće unaprijed znati adresu prijemnika, koriste se serveri gdje prijemnici ostavljaju svoju adresu, a predajnik to može zatražiti pozivajući se na neko jedinstveno ime prijemnika.

U slučajevima kada je kvalitet komunikacijskog kanala ili mogućnosti prijemnika nepoznat, organizira se povratni kanal preko kojeg prijemnik može obavijestiti predajnik o svojim mogućnostima, broju paketa koje je propustio itd. Ovaj kanal koristi RTCP protokol. Format paketa koji se prenose na ovom kanalu definisan je u RFC 3605. Preko ovog kanala se prenosi relativno malo podataka, 200..300 bajtova u sekundi, tako da generalno njegovo prisustvo nije opterećujuće. Broj porta na koji se šalju RTCP paketi mora biti neparan i za jedan veći od broja porta sa kojeg dolazi RTP tok. U našem primjeru nećemo koristiti ovaj kanal, jer mogućnosti prijemnika i kanala očito premašuju naše, do sada skromne, potrebe.

U našem programu, kolo za prijenos podataka, za razliku od prethodnog primjera, bit će podijeljeno na dva dijela: put za prijenos i put za prijem. Za svaki dio ćemo napraviti vlastiti izvor sata, kao što je prikazano na naslovnoj slici.

Jednosmjerna komunikacija između njih će se odvijati korištenjem RTP protokola. U ovom primjeru nije nam potrebna eksterna mreža, jer će se i predajnik i prijemnik nalaziti na istom računaru - paketi će putovati unutar njega.

Za uspostavljanje RTP toka, medijski streamer koristi dva filtera: MS_RTP_SEND i MS_RTP_RECV. Prvi prenosi drugi i prima RTP tok. Da bi ovi filteri radili, potrebno je da proslijede pokazivač na objekt RTP sesije, koji može ili pretvoriti tok blokova podataka u tok RTP paketa ili učiniti suprotno. Budući da se interni format podataka medijskog streamera ne poklapa s formatom podataka RTP paketa, prije prijenosa podataka na MS_RTP_SEND, trebate koristiti filter za kodiranje koji pretvara 16-bitne uzorke audio signala u osmobitne kodirane prema u-zakon (mu-zakon). Na prijemnoj strani, filter dekodera obavlja suprotnu funkciju.

Ispod je tekst programa koji implementira shemu prikazanu na slici (simboli # prije indirekta uključivanja su uklonjeni, ne zaboravite ih uključiti):

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

Sastavljamo i pokrećemo. Program će raditi kao u prethodnom primjeru, ali će se podaci prenositi putem RTP toka.

U sljedećem članku ćemo ovaj program podijeliti na dvije nezavisne aplikacije - prijemnik i predajnik i pokrenuti ih na različitim terminalima. Istovremeno ćemo naučiti kako analizirati RTP pakete pomoću programa TShark.

izvor: www.habr.com

Dodajte komentar