Udforskning af Mediastreamer2 VoIP-motoren. Del 6

Artiklens materiale er hentet fra min zen kanal.

Sender et lydsignal via RTP-stream

Udforskning af Mediastreamer2 VoIP-motoren. Del 6

I fortiden artiklen Vi har samlet et fjernbetjeningskredsløb fra en tonegenerator og en tonedetektor, der fungerer inden for det samme program. I denne artikel lærer vi, hvordan du bruger RTP-protokollen (RFC 3550 - RTP: En transportprotokol til realtidsapplikationer) til modtagelse/transmission af et lydsignal over et Ethernet-netværk.

RTP protokol (Realtidsprotokol) oversat betyder realtidsprotokol, den bruges til at transmittere lyd, video, data, alt hvad der kræver transmission i realtid. Lad os tage et lydsignal som et eksempel. Protokollens fleksibilitet er sådan, at den giver dig mulighed for at transmittere et lydsignal med en forudbestemt kvalitet.

Transmissionen udføres ved hjælp af UDP-pakker, hvilket betyder, at pakketab er ganske acceptabelt under transmission. Hver pakke indeholder en speciel RTP-header og en datablok af det transmitterede signal. Headeren indeholder en tilfældigt valgt signalkildeidentifikator, information om typen af ​​signal, der transmitteres, og et unikt pakkesekvensnummer, så pakkerne kan arrangeres i den rigtige rækkefølge ved afkodning, uanset i hvilken rækkefølge de blev leveret af netværk. Headeren kan også indeholde yderligere information, den såkaldte udvidelse, som gør, at headeren kan tilpasses til brug i en specifik applikationsopgave.

Datablokken indeholder pakkens nyttelast. Den interne organisering af indholdet afhænger af typen af ​​belastning, det kan være prøver af et monosignal, et stereosignal, en videobilledlinje osv.

Belastningstypen er angivet med et syv-bit tal. Anbefaling RFC3551 (RTP-profil til lyd- og videokonferencer med minimal kontrol) etablerer flere typer belastning; den tilsvarende tabel giver en beskrivelse af belastningstyperne og betydningen af ​​de koder, som de er betegnet med. Nogle koder er ikke strengt bundet til nogen form for belastning; de kan bruges til at udpege en vilkårlig belastning.

Størrelsen af ​​en datablok er ovenfor begrænset af den maksimale pakkestørrelse, der kan transmitteres på et givet netværk uden segmentering (MTU-parameter). Generelt er dette ikke mere end 1500 bytes. For at øge mængden af ​​transmitterede data pr. sekund kan du således øge pakkestørrelsen op til et vist punkt, og så bliver du nødt til at øge frekvensen af ​​afsendelse af pakker. I en mediestreamer er dette en konfigurerbar indstilling. Som standard er det 50 Hz, dvs. 50 pakker i sekundet. Vi vil kalde sekvensen af ​​transmitterede RTP-pakker for en RTP-strøm.

For at begynde at sende data mellem kilden og modtageren er det nok, at senderen kender IP-adressen på modtageren og portnummeret, som den bruger til at modtage. De der. uden nogen foreløbige procedurer begynder kilden at transmittere data, og modtageren er til gengæld klar til straks at modtage og behandle dem. Ifølge standarden skal portnummeret, der bruges til at sende eller modtage en RTP-stream, være lige.

I situationer, hvor det er umuligt at kende modtagerens adresse på forhånd, bruges servere, hvor modtagere forlader deres adresse, og senderen kan anmode om det ved at henvise til et unikt navn på modtageren.

I tilfælde, hvor kvaliteten af ​​kommunikationskanalen eller modtagerens muligheder er ukendte, organiseres en feedbackkanal, hvorigennem modtageren kan informere senderen om dens muligheder, antallet af pakker, den gik glip af osv. Denne kanal bruger RTCP-protokollen. Formatet af pakker transmitteret i denne kanal er defineret i RFC 3605. Relativt lidt data transmitteres over denne kanal, 200..300 bytes pr. sekund, så generelt er dets tilstedeværelse ikke byrdefuldt. Portnummeret, som RTCP-pakker sendes til, skal være ulige og én større end det portnummer, som RTP-strømmen kommer fra. I vores eksempel vil vi ikke bruge denne kanal, da modtagerens og kanalens muligheder åbenbart overstiger vores indtil videre beskedne behov.

I vores program vil datatransmissionskredsløbet, i modsætning til det foregående eksempel, være opdelt i to dele: en sendesti og en modtagesti. Til hver del vil vi lave vores egen urkilde, som vist på titelbilledet.

Envejskommunikation mellem dem vil blive udført ved hjælp af RTP-protokollen. I dette eksempel har vi ikke brug for et eksternt netværk, da både senderen og modtageren vil være placeret på den samme computer - pakkerne vil rejse inde i den.

For at etablere en RTP-stream bruger mediestreameren to filtre: MS_RTP_SEND og MS_RTP_RECV. Den første sender den anden og modtager RTP-strømmen. For at disse filtre kan fungere, skal de sende en pointer til et RTP-sessionsobjekt, som enten kan konvertere en strøm af datablokke til en strøm af RTP-pakker eller gøre det modsatte. Da det interne dataformat for mediestreameren ikke matcher dataformatet for RTP-pakken, skal du, før du overfører dataene til MS_RTP_SEND, bruge et encoder-filter, der konverterer 16-bit lydsignaleksempler til otte-bit kodet i henhold til u-lov (mu-lov). På den modtagende side udfører dekoderfilteret den modsatte funktion.

Nedenfor er teksten til programmet, der implementerer skemaet vist i figuren (# symbolerne før inkluderingsdirektiverne er blevet fjernet, glem ikke at inkludere dem):

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

Vi kompilerer og kører. Programmet vil fungere som i det foregående eksempel, men dataene vil blive transmitteret via en RTP-stream.

I den næste artikel vil vi opdele dette program i to uafhængige applikationer - en modtager og en sender og starte dem i forskellige terminaler. Samtidig vil vi lære at analysere RTP-pakker ved hjælp af TShark-programmet.

Kilde: www.habr.com

Tilføj en kommentar