Skúmanie enginu Mediastreamer2 VoIP. Časť 6

Materiál článku je prevzatý z môjho zenový kanál.

Prenos audio signálu cez RTP stream

Skúmanie enginu Mediastreamer2 VoIP. Časť 6

V poslednom článok Zostavili sme obvod diaľkového ovládania z tónového generátora a tónového detektora, ktoré pracujú v rámci toho istého programu. V tomto článku sa naučíme používať protokol RTP (RFC 3550 - RTP: Transport Protocol for Real-Time Applications) na príjem/prenos zvukového signálu cez sieť Ethernet.

RTP protokol (Protokol v reálnom čase) preložený znamená protokol v reálnom čase, používa sa na prenos zvuku, videa, dát, všetkého, čo vyžaduje prenos v reálnom čase. Vezmime si ako príklad zvukový signál. Flexibilita protokolu je taká, že umožňuje prenášať zvukový signál s vopred stanovenou kvalitou.

Prenos sa uskutočňuje pomocou paketov UDP, čo znamená, že strata paketov je počas prenosu celkom prijateľná. Každý paket obsahuje špeciálnu RTP hlavičku a dátový blok prenášaného signálu. Hlavička obsahuje náhodne vybraný identifikátor zdroja signálu, informácie o type prenášaného signálu a jedinečné poradové číslo paketu, aby bolo možné pakety pri dekódovaní usporiadať do správneho poradia bez ohľadu na poradie, v ktorom boli doručené. siete. Hlavička môže obsahovať aj ďalšie informácie, takzvané rozšírenie, ktoré umožňuje prispôsobiť hlavičku na použitie v konkrétnej úlohe aplikácie.

Dátový blok obsahuje užitočné zaťaženie paketu. Vnútorná organizácia obsahu závisí od typu záťaže, môže ísť o vzorky mono signálu, stereo signálu, obrazového riadku videa atď.

Typ zaťaženia je označený sedembitovým číslom. Odporúčanie RFC3551 (RTP profil pre audio a video konferencie s minimálnou kontrolou) stanovuje niekoľko typov zaťaženia, príslušná tabuľka poskytuje popis typov zaťaženia a význam kódov, ktorými sú označené. Niektoré kódy nie sú striktne viazané na žiadny typ zaťaženia, možno ich použiť na označenie ľubovoľného zaťaženia.

Veľkosť bloku dát je vyššie obmedzená maximálnou veľkosťou paketu, ktorý je možné prenášať v danej sieti bez segmentácie (parameter MTU). Vo všeobecnosti to nie je viac ako 1500 bajtov. Preto, aby ste zvýšili množstvo prenášaných dát za sekundu, môžete zväčšiť veľkosť paketu až do určitého bodu a potom budete musieť zvýšiť frekvenciu odosielania paketov. V streameri médií ide o konfigurovateľné nastavenie. Štandardne je to 50 Hz, t.j. 50 paketov za sekundu. Postupnosť prenášaných RTP paketov budeme nazývať RTP stream.

Na spustenie prenosu dát medzi zdrojom a prijímačom stačí, aby vysielač poznal IP adresu prijímača a číslo portu, ktorý používa na príjem. Tie. bez akýchkoľvek predbežných procedúr zdroj začne dáta vysielať a prijímač je zase pripravený ich okamžite prijať a spracovať. Podľa štandardu musí byť číslo portu používané na prenos alebo príjem RTP streamu párne.

V situáciách, keď nie je možné vopred poznať adresu prijímača, sa používajú servery, kde prijímače nechávajú svoju adresu a vysielač si ju môže vyžiadať odkazom na nejaký jedinečný názov prijímača.

V prípadoch, keď nie je známa kvalita komunikačného kanála alebo schopnosti prijímača, je organizovaný spätnoväzbový kanál, prostredníctvom ktorého môže prijímač informovať vysielač o svojich schopnostiach, počte zmeškaných paketov atď. Tento kanál používa protokol RTCP. Formát paketov prenášaných týmto kanálom je definovaný v RFC 3605. Týmto kanálom sa prenáša relatívne málo dát, 200..300 bajtov za sekundu, takže vo všeobecnosti jeho prítomnosť nie je zaťažujúca. Číslo portu, na ktorý sa odosielajú pakety RTCP, musí byť nepárne a o jedno väčšie ako číslo portu, z ktorého prichádza tok RTP. V našom príklade tento kanál nepoužijeme, pretože možnosti prijímača a kanála zjavne presahujú naše, zatiaľ skromné ​​potreby.

V našom programe bude obvod prenosu dát, na rozdiel od predchádzajúceho príkladu, rozdelený na dve časti: vysielaciu cestu a prijímaciu cestu. Pre každú časť si vyrobíme vlastný zdroj hodín, ako je znázornené na titulnom obrázku.

Jednosmerná komunikácia medzi nimi bude prebiehať pomocou protokolu RTP. V tomto príklade nepotrebujeme externú sieť, keďže vysielač aj prijímač budú umiestnené na tom istom počítači – pakety budú putovať v ňom.

Na vytvorenie toku RTP používa streamer médií dva filtre: MS_RTP_SEND a MS_RTP_RECV. Prvý vysiela druhý a prijíma RTP stream. Aby tieto filtre fungovali, musia odovzdať ukazovateľ na objekt relácie RTP, ktorý môže buď konvertovať prúd dátových blokov na prúd paketov RTP, alebo urobiť opak. Keďže interný dátový formát mediálneho streamera sa nezhoduje s dátovým formátom paketu RTP, pred prenosom dát do MS_RTP_SEND musíte použiť kódovací filter, ktorý konvertuje 16-bitové vzorky audio signálu na osembitové kódované podľa u-law (mu-law). Na prijímacej strane plní filter dekodéra opačnú funkciu.

Nižšie je uvedený text programu, ktorý implementuje schému znázornenú na obrázku (symboly # pred príkazmi na zahrnutie boli odstránené, nezabudnite ich zahrnúť):

/* Файл 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 spustíme. Program bude fungovať ako v predchádzajúcom príklade, ale dáta sa budú prenášať cez RTP stream.

V ďalšom č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: hab.com

Pridať komentár