A Mediastreamer2 VoIP motor felfedezése. 6. rész

A cikk anyaga az én zen csatorna.

Audiojel továbbítása RTP adatfolyamon keresztül

A Mediastreamer2 VoIP motor felfedezése. 6. rész

Az utolsóban cikk Összeállítottunk egy távirányító áramkört egy hanggenerátorból és egy hangérzékelőből, amelyek ugyanazon a programon belül működnek. Ebben a cikkben megtudjuk, hogyan kell használni az RTP protokollt (RFC 3550 - RTP: A szállítási protokoll valós idejű alkalmazásokhoz) audiojel vételéhez/továbbításához Ethernet hálózaton keresztül.

RTP protokoll (Valós idejű protokoll) lefordítva valós idejű protokollt jelent, hang, kép, adat, minden valós idejű átvitelre szolgál. Vegyünk példának egy hangjelet. A protokoll rugalmassága olyan, hogy lehetővé teszi egy előre meghatározott minőségű audiojel továbbítását.

Az átvitel UDP-csomagokkal történik, ami azt jelenti, hogy az átvitel során a csomagvesztés meglehetősen elfogadható. Minden csomag tartalmaz egy speciális RTP fejlécet és egy adatblokkot a továbbított jelről. A fejléc tartalmaz egy véletlenszerűen kiválasztott jelforrás azonosítót, a továbbított jel típusára vonatkozó információkat, valamint egy egyedi csomagsorszámot, hogy a csomagokat a dekódolás során a megfelelő sorrendbe lehessen rendezni, függetlenül attól, hogy milyen sorrendben kézbesítették őket. hálózat. A fejléc további információkat is tartalmazhat, az úgynevezett kiterjesztést, amely lehetővé teszi a fejléc adaptálását egy adott alkalmazási feladathoz.

Az adatblokk tartalmazza a csomag hasznos terhét. A tartalom belső felépítése a terhelés típusától függ, lehet minták monó jelből, sztereó jelből, videó képsorból stb.

A betöltés típusát hétbites szám jelzi. RFC3551 ajánlás (RTP-profil audio- és videokonferenciákhoz minimális vezérléssel) többféle rakományfajtát határoz meg, a megfelelő táblázat a rakománytípusok leírását és a jelölésükre szolgáló kódok jelentését tartalmazza. Egyes kódok nincsenek szigorúan semmilyen típusú terheléshez kötve, ezek tetszőleges terhelés kijelölésére használhatók.

Egy adatblokk méretét fentebb korlátozza az adott hálózaton szegmentálás nélkül továbbítható maximális csomagméret (MTU paraméter). Általában ez nem több 1500 bájtnál. Így a másodpercenként továbbított adatmennyiség növelése érdekében egy bizonyos pontig növelheti a csomag méretét, majd növelni kell a csomagok küldésének gyakoriságát. Média streamerben ez egy konfigurálható beállítás. Alapértelmezésben 50 Hz, azaz. 50 csomag másodpercenként. A továbbított RTP-csomagok sorozatát RTP-folyamnak nevezzük.

A forrás és a vevő közötti adatátvitel megkezdéséhez elegendő, ha az adó ismeri a vevő IP-címét és a vételhez használt port számát. Azok. minden előzetes eljárás nélkül a forrás megkezdi az adatok továbbítását, a vevő pedig készen áll azok azonnali fogadására és feldolgozására. A szabvány szerint az RTP folyam továbbítására vagy fogadására használt portszámnak párosnak kell lennie.

Azokban a helyzetekben, amikor a vevő címét nem lehet előre tudni, olyan szervereket használnak, ahol a vevők elhagyják a címüket, és az adó a vevő egyedi nevére hivatkozva kérheti azt.

Azokban az esetekben, amikor a kommunikációs csatorna minősége vagy a vevő képességei nem ismertek, visszacsatoló csatornát szerveznek, amelyen keresztül a vevő tájékoztathatja az adót a képességeiről, a kihagyott csomagok számáról stb. Ez a csatorna az RTCP protokollt használja. 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 adatot továbbítanak, másodpercenként 200...300 bájt, így általánosságban a jelenléte nem megterhelő. Annak a portszámnak, amelyre az RTCP-csomagokat küldik, páratlannak és eggyel nagyobbnak kell lennie, mint az a portszám, amelyről az RTP-folyam érkezik. Példánkban ezt a csatornát nem fogjuk használni, mivel a vevő és a csatorna képességei nyilvánvalóan meghaladják az eddigi szerény igényeinket.

Programunkban az adatátviteli áramkör az előző példától eltérően két részre lesz osztva: egy adóútra és egy vételi útra. Minden részhez saját óraforrást készítünk, a címképen látható módon.

A köztük lévő egyirányú kommunikáció az RTP protokoll használatával történik. Ebben a példában nincs szükségünk külső hálózatra, mivel az adó és a vevő is ugyanazon a számítógépen lesz – a csomagok azon belül fognak közlekedni.

Az RTP adatfolyam létrehozásához a média streamer két szűrőt használ: MS_RTP_SEND és MS_RTP_RECV. Az első továbbítja a másodikat, és fogadja az RTP adatfolyamot. Ahhoz, hogy ezek a szűrők működjenek, mutatót kell átadniuk egy RTP-munkamenet-objektumnak, amely vagy átalakíthatja az adatblokkok folyamát RTP-csomagok folyamává, vagy az ellenkezőjét. Mivel a média streamer belső adatformátuma nem egyezik az RTP-csomag adatformátumával, az adatok MS_RTP_SEND-re történő átvitele előtt kódolószűrőt kell használnia, amely a 16 bites hangjelmintákat nyolc bites kódolásúvá alakítja a u-törvény (mu-törvény). A fogadó oldalon a dekóderszűrő az ellenkező funkciót látja el.

Az alábbiakban az ábrán látható sémát megvalósító program szövege látható (az include direktívák előtti # szimbólumok eltávolításra kerültek, ne felejtse el beilleszteni ő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);
}
}

Összeállítjuk és futtatjuk. A program úgy fog működni, mint az előző példában, de az adatok továbbítása RTP adatfolyamon keresztül történik.

A következő cikkben ezt a programot két független alkalmazásra osztjuk fel - egy vevőre és egy adóra, és indítjuk el őket különböző terminálokon. Ugyanakkor megtanuljuk az RTP-csomagok elemzését a TShark programmal.

Forrás: will.com

Hozzászólás