Materiál článku je převzat z mého .
Přenos zvukového signálu přes RTP tok

V minulosti Sestavili jsme schéma dálkového ovládání z generátoru a detektoru tónových signálů, které pracují v rámci jednoho programu. V tomto článku se naučíme používat protokol RTP (RFC 3550 — RTP: Transportní protokol pro aplikace v reálném čase) pro příjem/přenos zvukového signálu po Ethernetové síti.
Protokol RTP (Real Time Protocol) v překladu znamená protokol reálného času, používá se pro přenos zvuku, videa, dat, všeho, co vyžaduje přenos v reálném čase. Jako příklad vezmeme 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í UDP paketů, což znamená, že během přenosu je zcela přípustná ztráta paketů. Do každého paketu je vložen speciální RTP header a blok dat přenášeného signálu. V headeru se nachází náhodně vybraný identifikátor zdroje signálu, informace o typu přenášeného signálu, unikátní pořadové číslo paketu, aby pakety mohly být při dekódování uspořádány ve správném pořadí, bez ohledu na to, v jakém pořadí je doručila síť. Header může také obsahovat dodatečné informace, takzvané rozšíření, které umožňují přizpůsobit header pro použití v konkrétním aplikačním úkolu.
Blok dat obsahuje payload paketu. Vnitřní organizace obsahu závisí na typu payloadu, mohou to být vzorky monofonního signálu, stereofonní signál, řádek video obrazu atd.
Typ payloadu je označen sedmibitovým číslem. Doporučení RFC3551 (RTP Profile for Audio and Video Conferences with Minimal Control) stanoví několik typů payloadů, v odpovídající tabulce jsou uvedena popisy typů payloadů a hodnoty kódů, kterými jsou označovány. Některé kódy nemají pevnou vazbu na žádný typ payloadu - mohou být používány k označení libovolného payloadu.
Velikost bloku dat je omezena maximální velikostí paketu, který může být v této síti odeslán bez fragmentace (parametr MTU). Obecně to není více než 1500 bajtů. Abychom tak zvýšili množství přenášených dat za sekundu, můžeme až do určitého okamžiku zvyšovat velikost paketu, a poté bude nutné zvyšovat frekvenci odesílání paketů. U mediastreameru je to nastavitelné parametr. Ve výchozím nastavení je to 50 Hz, tj. 50 paketů za sekundu. Sekvenci přenášených RTP paketů budeme nazývat RTP tokem.
K zahájení přenosu dat mezi zdrojem a příjemcem stačí, aby vysílač znal IP adresu příjemce a číslo portu, který používá pro příjem. Tj. bez jakýchkoliv předchozích procedur začíná zdroj data okamžitě přenášet, a příjemce je připraven je okamžitě přijmout a zpracovat. Podle standardu by číslo portu používané pro přenos nebo příjem RTP toku mělo být sudé.
V situacích, kdy nelze předem znát adresu příjemce, se používají servery, na kterých příjemci zanechávají svou adresu, a vysílač si ji může vyžádat s odkazem na určité jedinečné jméno příjemce.
Pokud není známa kvalita spojového kanálu nebo možnosti příjemce, zřizuje se kanál zpětné vazby, který umožňuje příjemci informovat vysílače o svých možnostech, počtu paketů, které jim chybí, atd. V tomto kanále se používá protokol RTCP. Formát paketů přenášených v tomto kanále je definován v RFC 3605. Přenáší se zde relativně málo dat 200..300 bajtů za sekundu, takže jeho existence není zatěžující. Číslo portu, na který se odesílají RTCP pakety, musí být liché a o jednu jednotku vyšší než číslo portu, ze kterého přichází RTP tok. V našem příkladu nebudeme tento kanál používat, protože možnosti příjemce a kanálu mají zjevně vyšší požadavky, než jsou naše, zatím skromné, potřeby.
V naší aplikaci bude schéma přenosu dat, na rozdíl od schématu předchozího příkladu, rozděleno na dvě části: na vysílací a přijímací trakt. Pro každou část vytvoříme svůj časový zdroj, jak je znázorněno na úvodním obrázku.
Jednosměrná komunikace mezi nimi bude probíhat pomocí protokolu RTP. V tomto příkladu nebudeme potřebovat externí síť, protože vysílač i přijímač budou umístěny na jednom počítači — pakety budou cestovat uvnitř něj.
Pro nastavení RTP toku v mediastreameru se používají dva filtry: MS_RTP_SEND a MS_RTP_RECV. První provádí vysílání, druhý příjem RTP toku. Aby tyto filtry začaly fungovat, musí dostat ukazatel na objekt RTP session, který může provádět jak převod toku bloků dat na RTP pakety, tak vykonávat opačnou akci. Protože interní formát dat mediastreameru se neshoduje s formátem dat RTP paketu, je nutné použít konvertor (encoder) filtr před odesláním dat do MS_RTP_SEND, který převede 16-bitové vzorky zvukového signálu na osmi-bitové, kódované podle u-zákona (mü-zákona). Na přijímací straně tuto zpětnou funkci vykonává filtr decoder.
Níže je uvedený text programu, který implementuje schéma znázorněné na obrázku (symboly # před direktivami include byly odstraněny, nezapomeňte je přidat):
/* Файл 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, spouštíme. Program bude fungovat stejně jako v předchozím příkladu, ale data budou přenášena skrze RTP tok.
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. Současně se naučíme analyzovat RTP pakety pomocí programu TShark.
Zdroj: habr.com
