Vi studerar VoIP-motorn Mediastreamer2. Del 6

Artikelmaterialet är hämtat från min Zen-kanal.

Överföring av ljudsignal via RTP-flöde

Vi studerar VoIP-motorn Mediastreamer2. Del 6

Tidigare artikeln Vi har sammanställt ett schema för fjärrstyrning från en generator och en detektor av tonala signaler, som fungerar inom ett och samma program. I den här artikeln lär vi oss att använda protokollet RTP (RFC 3550 — RTP: Ett transportprotokoll för realtidsapplikationer) för mottagning/överföring av ljudsignal över Ethernet-nätverk.

RTP-protokollet (Real Time Protocol) används för överföring av ljud, video och data som kräver realtidsöverföring. Ett exempel är ljudsignalen. Protokollets flexibilitet gör att det kan överföra ljudsignal med förutbestämd kvalitet.

Överföringen sker med hjälp av UDP-paket, vilket innebär att paketförluster är acceptabla. Varje paket innehåller en speciell RTP-header och en datablok för den överförda signalen. I headern finns ett slumpmässigt valt identifieringsnummer för signalens källa, information om typen av överförd signal och ett unikt sekvensnummer för paketet, så att paketen kan ordnas korrekt vid avkodning, oavsett i vilken ordning nätverket levererade dem. Headern kan även innehålla ytterligare information, så kallad ett tillägg, som möjliggör anpassning av headern för specifika tillämpningar.

Datablocket innehåller paketets nyttolast. Den inre organisationen av innehållet beror på typen av nyttolast, vilket kan vara mätvärden för monoljud, stereoljud, en videobildrad och så vidare.

Typen av nyttolast anges med ett sju-bitarsnummer. Rekommendationen RFC3551 (RTP Profile for Audio and Video Conferences with Minimal Control) fastställer flera typer av nyttolaster, där en motsvarande tabell beskriver typer av nyttolaster och värdena för de koder som används för att identifiera dem. Vissa koder är inte hårt kopplade till någon typ av nyttolast – de kan användas för att beteckna vilketsomhelst slags nyttolast.

Storlängden för data block begränsas av det maximala paketstorleken som kan överföras i detta nät utan segmentering (MTU-parameter). Detta är normalt högst 1500 byte. För att öka antalet överförda data per sekund kan paketstorleken ökas till en viss punkt, efter det måste sändningsfrekvensen för paketen ökas. För en mediastreamer är detta en konfigurerbar parameter. Som standard är den 50 Hz, dvs. 50 paket per sekund. Följden av överförda RTP-paket kallas RTP-ström.

För att börja överföra data mellan källan och mottagaren räcker det att sändaren känner till mottagarens IP-adress och portnumret som denne använder för att ta emot. Det vill säga utan några förberedande procedurer börjar källan överföra data, och mottagaren är i sin tur redo att omedelbart ta emot och bearbeta dem. Enligt standarden ska portnumret som används för överföring eller mottagning av RTP-ström vara jämnt.

I situationer där mottagaradressen inte kan förutses används servrar där mottagarna lämnar sin adress, och sändaren kan begära den genom att referera till ett unikt namn för mottagaren.

I fall där kvaliteten på kommunikationskanalen eller mottagarens kapacitet är okänd organiseras en återkopplingskanal, där mottagaren kan informera sändaren om sina möjligheter, antalet paket som saknas osv. I en sådan kanal används RTCP-protokollet. Formatet för paketen som överförs i denna kanal definieras i RFC 3605. Genom denna kanal överförs relativt lite data, 200–300 byte per sekund, så dess närvaro är generellt sett inte betungande. Portnumret som RTCP-paketen skickas till ska vara udda och en enhet större än portnumret från vilket RTP-strömmen kommer. I vårt exempel kommer vi inte att använda denna kanal, eftersom mottagarens och kanalens kapacitet med god marginal överstiger våra än så länge blygsamma behov.

I vårt program kommer dataöverföringsschemat, till skillnad från schemat i det föregående exemplet, att delas in i två delar: sändnings- och mottagningskanaler. För varje del kommer vi att skapa vår egen klocka, som visas i den första bilden.

Enkelriktad kommunikation mellan dem kommer att ske via RTP-protokollet. I detta exempel behövs inget externt nätverk, eftersom både sändaren och mottagaren kommer att vara på samma dator — paketen kommer att gå internt.

För att etablera RTP-strömmen i medieströmmen används två filter: MS_RTP_SEND och MS_RTP_RECV. Det första utför sändning, det andra tar emot RTP-strömmen. För att dessa filter ska börja fungera behöver de en pekare till RTP-sessionens objekt, som kan utföra både konverteringen av datablokkar till RTP-paketström och den omvända processen. Eftersom det interna dataformatet för medieströmmen inte matchar RTP-paketformatet, måste ett konverteringsfilter (encoder) användas innan data skickas till MS_RTP_SEND, vilket omvandlar 16-bitars ljudprover till 8-bitars prov, kodade enligt u-law (mu-law). På mottagarsidan utför filtern decoder den omvända funktionen.

Nedanstående text är ett program som implementerar schemat som visas i bilden (symbolerna # före include-direktiven har tagits bort, glöm inte att sätta dit 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 kompilera och kör programmet. Programmet kommer att fungera som i föregående exempel, men data kommer nu att överföras genom RTP-strömmen.

I nästa artikel kommer vi att dela upp detta program i två oberoende program — mottagare och sändare och köra dem i olika terminaler. Samtidigt kommer vi att lära oss att analysera RTP-paket med programmet TShark.

Källa: habr.com

Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar 🔥 Köp pålitlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster