Mediastreamer2:n VoIP-moottorin tutkiminen. Osa 6

Artikkelin materiaali on otettu minun zen kanava.

Äänisignaalin lähettäminen RTP-virran kautta

Mediastreamer2:n VoIP-moottorin tutkiminen. Osa 6

Viimeisessä статье Olemme koonneet kauko-ohjainpiirin äänigeneraattorista ja äänitunnistimesta, jotka toimivat saman ohjelman sisällä. Tässä artikkelissa opimme käyttämään RTP-protokollaa (RFC 3550 - RTP: Kuljetusprotokolla reaaliaikaisille sovelluksille) äänisignaalin vastaanottamiseen/lähettämiseen Ethernet-verkon kautta.

RTP-protokolla (Reaaliaikainen protokolla) käännetty tarkoittaa reaaliaikaista protokollaa, jolla siirretään ääntä, videota, dataa, kaikkea mikä vaatii lähetyksen reaaliajassa. Otetaan esimerkkinä äänisignaali. Protokollan joustavuus on sellainen, että sen avulla voit lähettää äänisignaalin ennalta määrätyllä laadulla.

Lähetys tapahtuu UDP-pakettien avulla, mikä tarkoittaa, että pakettien häviäminen on melko hyväksyttävää lähetyksen aikana. Jokainen paketti sisältää erityisen RTP-otsikon ja lähetetyn signaalin datalohkon. Otsikko sisältää satunnaisesti valitun signaalilähteen tunnisteen, tiedon lähetettävän signaalin tyypistä ja yksilöllisen paketin järjestysnumeron, jotta paketit voidaan järjestää oikeaan järjestykseen dekoodattaessa riippumatta siitä, missä järjestyksessä ne on toimitettu. verkkoon. Otsikko voi sisältää myös lisätietoa, niin sanotun laajennuksen, jonka avulla otsikko voidaan mukauttaa käytettäväksi tietyssä sovellustehtävässä.

Datalohko sisältää paketin hyötykuorman. Sisällön sisäinen organisaatio riippuu kuormituksen tyypistä, se voi olla näytteitä monosignaalista, stereosignaalista, videokuvalinjasta jne.

Lataustyyppi ilmoitetaan seitsemänbittisellä numerolla. Suositus RFC3551 (RTP-profiili ääni- ja videokonferenssien minimaalisella ohjauksella) määrittää useita kuormatyyppejä; vastaavassa taulukossa on kuvaus kuormatyypeistä ja niiden koodien merkityksestä, joilla ne on merkitty. Joitakin koodeja ei ole tiukasti sidottu mihinkään kuormaan, vaan niitä voidaan käyttää mielivaltaisen kuorman osoittamiseen.

Datalohkon kokoa rajoittaa yläpuolella suurin pakettikoko, joka voidaan lähettää tietyssä verkossa ilman segmentointia (MTU-parametri). Yleensä tämä on enintään 1500 tavua. Siten sekunnissa siirrettävän datan määrän lisäämiseksi voit suurentaa paketin kokoa tiettyyn pisteeseen asti, minkä jälkeen sinun on lisättävä pakettien lähetystiheyttä. Mediastriimauksessa tämä on konfiguroitavissa oleva asetus. Oletuksena se on 50 Hz, ts. 50 pakettia sekunnissa. Kutsumme lähetettyjen RTP-pakettien sarjaa RTP-virraksi.

Tietojen siirtämisen aloittamiseksi lähteen ja vastaanottimen välillä riittää, että lähetin tietää vastaanottimen IP-osoitteen ja portin numeron, jota se käyttää vastaanottamiseen. Nuo. ilman ennakkotoimenpiteitä lähde alkaa lähettää dataa, ja vastaanotin puolestaan ​​on valmis välittömästi vastaanottamaan ja käsittelemään sen. Standardin mukaan RTP-virran lähettämiseen tai vastaanottamiseen käytettävän portin numeron on oltava parillinen.

Tilanteissa, joissa vastaanottajan osoitetta ei ole mahdollista tietää etukäteen, käytetään palvelimia, joissa vastaanottajat jättävät osoitteensa ja lähetin voi pyytää sitä viittaamalla johonkin vastaanottajan yksilölliseen nimeen.

Tapauksissa, joissa viestintäkanavan laatu tai vastaanottimen ominaisuudet eivät ole tiedossa, järjestetään palautekanava, jonka kautta vastaanotin voi ilmoittaa lähettimelle sen ominaisuuksista, ohittamiensa pakettien määrästä jne. Tämä kanava käyttää RTCP-protokollaa. Tällä kanavalla lähetettävien pakettien muoto on määritelty RFC 3605:ssä. Tällä kanavalla siirretään suhteellisen vähän dataa, 200...300 tavua sekunnissa, joten yleisesti sen läsnäolo ei ole rasittavaa. Porttinumeron, johon RTCP-paketit lähetetään, on oltava pariton ja yksi suurempi kuin portin numero, josta RTP-virta tulee. Esimerkissämme emme käytä tätä kanavaa, koska vastaanottimen ja kanavan ominaisuudet selvästi ylittävät toistaiseksi vaatimattomat tarpeemme.

Ohjelmassamme tiedonsiirtopiiri, toisin kuin edellisessä esimerkissä, jaetaan kahteen osaan: lähetyspolkuun ja vastaanottopolkuun. Jokaiselle osalle teemme oman kellolähteen, kuten otsikkokuvassa näkyy.

Yksisuuntainen viestintä niiden välillä tapahtuu RTP-protokollan avulla. Tässä esimerkissä emme tarvitse ulkoista verkkoa, koska sekä lähetin että vastaanotin sijaitsevat samassa tietokoneessa - paketit kulkevat sen sisällä.

RTP-virran muodostamiseksi mediasuoritin käyttää kahta suodatinta: MS_RTP_SEND ja MS_RTP_RECV. Ensimmäinen lähettää toisen ja vastaanottaa RTP-virran. Jotta nämä suodattimet toimisivat, niiden on välitettävä osoitin RTP-istuntoobjektille, joka voi joko muuntaa tietolohkojen virran RTP-pakettien virraksi tai toimia päinvastoin. Koska mediastriimerin sisäinen tietomuoto ei vastaa RTP-paketin tietomuotoa, sinun on käytettävä ennen tietojen siirtämistä MS_RTP_SEND:iin enkooderisuodatinta, joka muuntaa 16-bittiset äänisignaalinäytteet kahdeksanbittisiksi koodatuiksi u-laki (mu-laki). Vastaanottopuolella dekooderin suodatin suorittaa päinvastaisen toiminnon.

Alla on kuvassa näkyvän mallin toteuttavan ohjelman teksti (#-symbolit ennen sisällyttämistä on poistettu, muista sisällyttää ne):

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

Kokoamme ja suoritamme. Ohjelma toimii kuten edellisessä esimerkissä, mutta tiedot välitetään RTP-virran kautta.

Seuraavassa artikkelissa jaamme tämän ohjelman kahteen itsenäiseen sovellukseen - vastaanottimeen ja lähettimeen ja käynnistämme ne eri päätelaitteissa. Samalla opimme analysoimaan RTP-paketteja TShark-ohjelman avulla.

Lähde: will.com

Lisää kommentti