Istraživanje Mediastreamer2 VoIP mehanizma. dio 6

Materijal članka preuzet je iz mog zen kanal.

Prijenos audio signala putem RTP streama

Istraživanje Mediastreamer2 VoIP mehanizma. dio 6

U posljednjih članak Sastavili smo krug daljinskog upravljanja od generatora tona i detektora tona koji rade unutar istog programa. U ovom članku naučit ćemo kako koristiti RTP protokol (RFC 3550 - RTP: Prijenosni protokol za aplikacije u stvarnom vremenu) za primanje/prijenos audio signala preko Ethernet mreže.

RTP protokol (Protokol u stvarnom vremenu) u prijevodu znači protokol u stvarnom vremenu, koristi se za prijenos zvuka, videa, podataka, svega što zahtijeva prijenos u stvarnom vremenu. Uzmimo audio signal kao primjer. Fleksibilnost protokola je takva da vam omogućuje prijenos audio signala unaprijed određene kvalitete.

Prijenos se odvija pomoću UDP paketa, što znači da je gubitak paketa sasvim prihvatljiv tijekom prijenosa. Svaki paket sadrži posebno RTP zaglavlje i podatkovni blok odaslanog signala. Zaglavlje sadrži nasumično odabrani identifikator izvora signala, informacije o vrsti signala koji se prenosi i jedinstveni redni broj paketa tako da se paketi mogu poredati ispravnim redoslijedom prilikom dekodiranja, bez obzira na redoslijed kojim ih je isporučio mreža. Zaglavlje također može sadržavati dodatne informacije, tzv. proširenje, koje omogućuje prilagodbu zaglavlja za korištenje u određenom aplikacijskom zadatku.

Podatkovni blok sadrži sadržaj paketa. Unutarnja organizacija sadržaja ovisi o vrsti opterećenja, to mogu biti uzorci mono signala, stereo signala, linija video slike itd.

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

Veličina podatkovnog bloka je gore ograničena maksimalnom veličinom paketa koji se može prenijeti na određenoj mreži bez segmentacije (MTU parametar). Općenito, to nije više od 1500 bajtova. Dakle, kako biste povećali količinu podataka prenesenih u sekundi, možete povećati veličinu paketa do određene točke, a zatim ćete morati povećati učestalost slanja paketa. U medijskom streameru ovo je postavka koja se može konfigurirati. Standardno je 50 Hz, tj. 50 paketa u sekundi. Slijed odaslanih RTP paketa nazvat ćemo RTP tok.

Za početak prijenosa podataka između izvora i prijamnika dovoljno je da odašiljač zna IP adresu prijamnika i broj porta koji koristi za prijem. Oni. bez ikakvih prethodnih postupaka izvor počinje slati podatke, a primatelj je zauzvrat spreman odmah ih primiti i obraditi. Prema standardu, broj priključka koji se koristi za prijenos ili primanje RTP streama mora biti paran.

U situacijama kada je nemoguće unaprijed znati adresu primatelja, koriste se serveri na kojima primatelji ostavljaju svoju adresu, a odašiljač je može zatražiti pozivajući se na neko jedinstveno ime primatelja.

U slučajevima kada je kvaliteta komunikacijskog kanala ili mogućnosti prijamnika nepoznata, organizira se povratni kanal preko kojeg prijamnik može obavijestiti odašiljač o svojim mogućnostima, broju paketa koje je propustio i sl. Ovaj kanal koristi RTCP protokol. Format paketa koji se prenose ovim kanalom definiran je u RFC 3605. Preko ovog kanala prenosi se relativno malo podataka, 200..300 bajtova u sekundi, tako da općenito njegova prisutnost nije opterećujuća. Broj porta na koji se šalju RTCP paketi mora biti neparan i za jedan veći od broja porta s kojeg dolazi RTP tok. U našem primjeru nećemo koristiti ovaj kanal, budući da mogućnosti prijemnika i kanala očito nadilaze naše, za sada skromne potrebe.

U našem programu, krug prijenosa podataka, za razliku od prethodnog primjera, bit će podijeljen na dva dijela: put prijenosa i put primanja. Za svaki dio napravit ćemo vlastiti izvor sata, kao što je prikazano na naslovnoj slici.

Jednosmjerna komunikacija između njih odvijat će se korištenjem RTP protokola. U ovom primjeru ne trebamo vanjsku mrežu, jer će se i odašiljač i prijamnik nalaziti na istom računalu - paketi će putovati unutar njega.

Za uspostavljanje RTP streama, medijski streamer koristi dva filtra: MS_RTP_SEND i MS_RTP_RECV. Prvi odašilje drugi i prima RTP stream. Kako bi ti filtri radili, trebaju proslijediti pokazivač na objekt RTP sesije, koji može pretvoriti tok blokova podataka u tok RTP paketa ili učiniti suprotno. Budući da interni format podataka medijskog streamera ne odgovara formatu podataka RTP paketa, prije prijenosa podataka na MS_RTP_SEND, trebate upotrijebiti filtar kodera koji pretvara 16-bitne uzorke audio signala u osam-bitne kodirane prema u-zakon (mu-zakon). Na prijemnoj strani filtar dekodera obavlja suprotnu funkciju.

Ispod je tekst programa koji implementira shemu prikazanu na slici (uklonjeni su simboli # ispred direktiva uključivanja, 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 streama.

U sljedećem članku podijelit ćemo ovaj program u dvije neovisne aplikacije - prijemnik i odašiljač i pokrenuti ih na različitim terminalima. Ujedno ćemo naučiti analizirati RTP pakete pomoću programa TShark.

Izvor: www.habr.com

Dodajte komentar