Prozkoumání enginu Mediastreamer2 VoIP. Část 6

Materiál článku je převzat z mého zenový kanál.

Přenos audio signálu přes RTP stream

Prozkoumání enginu Mediastreamer2 VoIP. Část 6

V minulosti článek Sestavili jsme obvod dálkového ovládání z tónového generátoru a tónového detektoru, které pracují v rámci stejného programu. V tomto článku se naučíme používat protokol RTP (RFC 3550 - RTP: Transport Protocol for Real-Time Applications) pro příjem/přenos audio signálu přes síť Ethernet.

protokol RTP (Protokol v reálném čase) přeložený znamená protokol v reálném čase, používá se k přenosu zvuku, videa, dat, všeho, co vyžaduje přenos v reálném čase. Vezměme si jako příklad zvukový signál. Flexibilita protokolu je taková, že umožňuje přenášet zvukový signál s předem stanovenou kvalitou.

Přenos se provádí pomocí paketů UDP, což znamená, že ztráta paketů je během přenosu celkem přijatelná. Každý paket obsahuje speciální hlavičku RTP a datový blok přenášeného signálu. Záhlaví obsahuje náhodně vybraný identifikátor zdroje signálu, informaci o typu přenášeného signálu a jedinečné pořadové číslo paketu, takže pakety mohou být při dekódování uspořádány ve správném pořadí, bez ohledu na pořadí, ve kterém byly doručeny. síť. Hlavička může obsahovat i další informace, tzv. rozšíření, které umožňuje upravit hlavičku pro použití v konkrétní aplikační úloze.

Datový blok obsahuje užitečné zatížení paketu. Vnitřní organizace obsahu závisí na typu zátěže, může se jednat o vzorky mono signálu, stereo signálu, obrazového řádku videa atd.

Typ zatížení je označen sedmibitovým číslem. Doporučení RFC3551 (Profil RTP pro audio a video konference s minimální kontrolou) stanoví několik typů zatížení, v odpovídající tabulce je uveden popis druhů zatížení a význam kódů, kterými jsou označeny. Některé kódy nejsou striktně vázány na žádný typ zatížení, lze je použít k označení libovolného zatížení.

Velikost datového bloku je výše omezena maximální velikostí paketů, které lze v dané síti přenášet bez segmentace (parametr MTU). Obecně to není více než 1500 bajtů. Chcete-li tedy zvýšit množství přenášených dat za sekundu, můžete zvětšit velikost paketu až do určitého bodu a poté budete muset zvýšit frekvenci odesílání paketů. V streameru médií se jedná o konfigurovatelné nastavení. Standardně je to 50 Hz, tzn. 50 paketů za sekundu. Sekvenci přenášených RTP paketů budeme nazývat RTP stream.

Pro zahájení přenosu dat mezi zdrojem a přijímačem stačí, aby vysílač znal IP adresu přijímače a číslo portu, který používá pro příjem. Tito. bez jakýchkoliv předběžných procedur zdroj začne vysílat data a přijímač je zase připraven je okamžitě přijmout a zpracovat. Podle standardu musí být číslo portu používané pro vysílání nebo příjem RTP streamu sudé.

V situacích, kdy není možné předem znát adresu přijímače, se používají servery, kde přijímače nechají svou adresu a vysílač si ji může vyžádat odkazem na nějaké jedinečné jméno přijímače.

V případech, kdy není známa kvalita komunikačního kanálu nebo schopnosti přijímače, je organizován zpětnovazební kanál, jehož prostřednictvím může přijímač informovat vysílač o svých schopnostech, počtu zmeškaných paketů atd. Tento kanál používá protokol RTCP. Formát paketů přenášených tímto kanálem je definován v RFC 3605. Tímto kanálem je přenášeno relativně málo dat, 200..300 bajtů za sekundu, takže obecně jeho přítomnost není zatěžující. Číslo portu, na který se odesílají pakety RTCP, musí být liché a o jedničku větší než číslo portu, ze kterého přichází stream RTP. V našem příkladu tento kanál nepoužijeme, protože možnosti přijímače a kanálu zjevně přesahují naše, zatím skromné, potřeby.

V našem programu bude obvod přenosu dat, na rozdíl od předchozího příkladu, rozdělen na dvě části: vysílací cestu a přijímací cestu. Pro každý díl si vyrobíme vlastní zdroj hodin, jak je znázorněno na titulním obrázku.

Jednosměrná komunikace mezi nimi bude probíhat pomocí protokolu RTP. V tomto příkladu nepotřebujeme externí síť, protože vysílač i přijímač budou umístěny na stejném počítači – pakety budou putovat uvnitř něj.

K vytvoření streamu RTP používá streamer médií dva filtry: MS_RTP_SEND a MS_RTP_RECV. První vysílá druhý a přijímá RTP stream. Aby tyto filtry fungovaly, potřebují předat ukazatel na objekt relace RTP, který může buď převést proud datových bloků na proud paketů RTP, nebo udělat opak. Protože interní datový formát media streameru neodpovídá datovému formátu RTP paketu, musíte před přenosem dat do MS_RTP_SEND použít kodérový filtr, který převede 16bitové vzorky audio signálu na osmibitové kódované podle u-law (mu-law). Na přijímací straně plní filtr dekodéru opačnou funkci.

Níže je text programu, který implementuje schéma znázorněné na obrázku (symboly # před příkazy include byly odstraněny, nezapomeňte je zahrnout):

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

Zkompilujeme a spustíme. Program bude fungovat jako v předchozím příkladu, ale data budou přenášena prostřednictvím RTP streamu.

V příštím článku rozdělíme tento program na dvě nezávislé aplikace - přijímač a vysílač a spustíme je v různých terminálech. Zároveň se naučíme analyzovat RTP pakety pomocí programu TShark.

Zdroj: www.habr.com

Přidat komentář