A Mediastreamer2 VoIP motort tanulmányozzuk. 6. rész

A cikk anyaga az én Dzen csatornámból származik.

Hangjel atviteli jel RTP folyamán

A Mediastreamer2 VoIP motort tanulmányozzuk. 6. rész

A múltban a Spotify döntéshozatali módszertanáról. Egyre több felhasználó preferálja a zenehallgatást mobilon a streaming szolgáltatásban, de a cégnél túlnyomórészt webprogramozók dolgoznak. A Spotify elemezte ezt a helyzetet, és úgy döntött, hogy több mobilfejlesztőt vesz fel, valamint átnevezi a webes szakembereket új irányba: Összegyűjtöttük a távoli vezérlés sémáját generátor és tonális jel detektor között, melyek egy programon belül működnek. Ebben a cikkben elsajátítjuk az RTP protokoll (RFC 3550 — RTP: Valós idejű alkalmazások szállító protokollja) használatát hangjel átvitelére/átvételére Ethernet-hálózaton.

Az RTP protokoll (Real Time Protocol) fordítva valós idejű protokollt jelent, és a hang, videó, adatok átvitelére használják, mindenre, aminek valós idejű átvitele szükséges. Például vegyük a hangjelet. A protokoll rugalmassága lehetővé teszi a hangjel meghatározott minőséggel való továbbítását.

Az átvitel UDP csomagok segítségével történik, ami azt jelenti, hogy az átvitel során csomagok elvesztése megengedett. Minden csomag egy speciális RTP fejlécet és a továbbított jel adatblokkot tartalmaz. A fejléc tartalmaz egy véletlenszerűen kiválasztott forrásazonosítót, az átvitt jel típusára vonatkozó információt, valamint egy egyedi sorozatszámot, hogy a csomagok dekódoláskor a helyes sorrendbe állíthatók legyenek, függetlenül attól, hogy a hálózat milyen sorrendben szállítja őket. A fejléc további információkat is tartalmazhat, úgynevezett bővítést, amely lehetővé teszi a fejléc testreszabását egy adott alkalmazási feladathoz.

Az adatblokk a csomag hasznos terhét tartalmazza. A tartalom belső szervezése a teher típusától függ, lehet monó jel, sztereó jel, videó kép sor stb.

A teher típusa egy héttagú számjeggyel van megjelölve. Az RFC3551 ajánlás (RTP profil audio és video konferenciákhoz minimális vezérléssel) több teher típust állapít meg, a megfelelő táblázatban megtalálhatók a teher típusának leírása és az azokat jelölő kódok értékei. Néhány kód nem kötődik szorosan egyetlen teher típushoz sem - tetszőleges teher jelölésére használhatók.

A adatblokk méretét a maximális csomagméret korlátozza, amelyet a hálózaton szegmentálás nélkül továbbítani lehet (MTU paraméter). Általában ez nem haladhatja meg az 1500 bájt méretet. Így, hogy növeljük a másodpercenként továbbítható adatok mennyiségét, a csomagméretet egy bizonyos mértékig növelhetjük, majd a csomagok küldési gyakoriságát kell növelni. A médiastrímelőnél ez egy beállítható paraméter. Alapértelmezett értéke 50 Hz, azaz 50 csomag másodpercenként. A továbbított RTP-csomagsorozatot RTP-streamnek nevezzük.

Az adatok forrástól a célállomásig történő továbbításának megkezdéséhez elegendő, ha a küldő tudja a fogadó IP-címét és a port számát, amelyet az az adatok fogadására használ. Vagyis különféle előzetes eljárások nélkül a forrás elkezdi az adatok továbbítását, míg a fogadó azonnal készen áll arra, hogy azokat átvegye és feldolgozza. A szabvány szerint a RTP-stream továbbításához vagy fogadásához használt port számának párosnak kell lennie.

Olyan helyzetekben, amikor nem lehet előre tudni a fogadó címét, szervereket használnak, ahol a fogadók megadják az címüket, és a küldő kérheti azt egy egyedi fogadónév megadásával.

Olyan esetekben, amikor a kommunikációs csatorna minősége vagy a fogadó lehetőségei ismeretlenek, visszacsatoló csatornát szerveznek, amelyen a fogadó tájékoztathatja a küldőt a képességeiről, a hiányzó csomagok számáról stb. Ezen a csatornán RTCP protokollt használnak. Az ezen a csatornán továbbított csomagok formátumát az RFC 3605 határozza meg. Ezen a csatornán viszonylag kevés adat (200-300 bájt másodpercenként) továbbítható, tehát a megléte összességében nem terhes. Az RTCP-csomagok küldésének portszámának páratlan számúnak kell lennie, és eggyel nagyobb, mint az RTP-stream érkező portszáma. Példánkban ezt a csatornát nem használjuk, mivel a fogadó és a csatorna képességei felülmúlják a jelenleg szerény igényeinket.

Programunkban az adatátviteli séma, eltérően az előző példa sémájától, két részre lesz osztva: a küldő és a fogadó ágra. Mindkét részhez saját óraforrást készítünk, ahogy azt a címlapon látható.

A felek közötti egyirányú kommunikáció az RTP-protokoll segítségével valósul meg. Ebben a példában nincs szükség külső hálózatra, mivel a küldő és a vevő egy számítógépen található — a csomagok belsőleg fognak közlekedni.

Az RTP-átvitel létrehozásához a médiastreamerben két szűrőt használnak: MS_RTP_SEND és MS_RTP_RECV. Az első az RTP-stream második vevő átvitelét végzi. Ahhoz, hogy ezek a szűrők működésbe lépjenek, egy mutatót kell átadni az RTP-munkamenet objektumának, amely képes az adatok blokkfolyamát RTP-csomagfolyammá alakítani, és végrehajtani a fordított műveletet is. Mivel a mediastreamer belső adatformátuma nem egyezik meg az RTP-csomag adatformátumával, a MS_RTP_SEND-be történő adatok küldése előtt átalakító szűrőt (encoder) kell használni, amely 16 bites hangmintákat 8 bites, u-törvény szerint (mu-törvény) kódolt adatokra alakít. A vevő oldalon a fordított funkciót egy dekóder szűrő végzi el.

Az alábbiakban bemutatjuk annak a programnak a szövegét, amely a képen látható sémát valósítja meg (az include direktívák előtt lévő # jeleket eltávolítottuk, ne felejtsük el visszatenni őket):

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

Fordítás és futtatás. A program ugyanúgy fog működni, mint az előző példában, de most az adatok RTP-streamen keresztül fognak átmenni.

A következő cikkben kettéosztjuk ezt a programot két független alkalmazásra — a vevőre és a küldőre, és külön terminálokon futtatjuk őket. Ezzel párhuzamosan megtanuljuk az RTP-csomagok elemzését a TShark program segítségével.

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster